Merge pull request #740 from atom/webview

Implement <webview> tag
This commit is contained in:
Cheng Zhao 2014-10-26 19:43:52 +08:00
commit 0839915538
56 changed files with 2303 additions and 602 deletions

View file

@ -29,6 +29,7 @@
'atom/browser/api/lib/tray.coffee',
'atom/browser/api/lib/web-contents.coffee',
'atom/browser/lib/chrome-extension.coffee',
'atom/browser/lib/guest-view-manager.coffee',
'atom/browser/lib/init.coffee',
'atom/browser/lib/objects-registry.coffee',
'atom/browser/lib/rpc-server.coffee',
@ -41,12 +42,14 @@
'atom/common/lib/init.coffee',
'atom/common/lib/asar.coffee',
'atom/renderer/lib/chrome-api.coffee',
'atom/renderer/lib/guest-view-internal.coffee',
'atom/renderer/lib/init.coffee',
'atom/renderer/lib/inspector.coffee',
'atom/renderer/lib/override.coffee',
'atom/renderer/lib/web-view.coffee',
'atom/renderer/api/lib/ipc.coffee',
'atom/renderer/api/lib/remote.coffee',
'atom/renderer/api/lib/web-view.coffee',
'atom/renderer/api/lib/web-frame.coffee',
],
'lib_sources': [
'atom/app/atom_content_client.cc',
@ -100,8 +103,6 @@
'atom/browser/atom_browser_main_parts_mac.mm',
'atom/browser/atom_javascript_dialog_manager.cc',
'atom/browser/atom_javascript_dialog_manager.h',
'atom/browser/atom_resource_dispatcher_host_delegate.cc',
'atom/browser/atom_resource_dispatcher_host_delegate.h',
'atom/browser/atom_speech_recognition_manager_delegate.cc',
'atom/browser/atom_speech_recognition_manager_delegate.h',
'atom/browser/browser.cc',
@ -178,6 +179,10 @@
'atom/browser/ui/win/notify_icon.h',
'atom/browser/ui/x/x_window_utils.cc',
'atom/browser/ui/x/x_window_utils.h',
'atom/browser/web_view/web_view_manager.cc',
'atom/browser/web_view/web_view_manager.h',
'atom/browser/web_view/web_view_renderer_state.cc',
'atom/browser/web_view/web_view_renderer_state.h',
'atom/browser/window_list.cc',
'atom/browser/window_list.h',
'atom/browser/window_list_observer.h',
@ -222,6 +227,7 @@
'atom/common/native_mate_converters/accelerator_converter.cc',
'atom/common/native_mate_converters/accelerator_converter.h',
'atom/common/native_mate_converters/file_path_converter.h',
'atom/common/native_mate_converters/gfx_converter.h',
'atom/common/native_mate_converters/gurl_converter.h',
'atom/common/native_mate_converters/image_converter.cc',
'atom/common/native_mate_converters/image_converter.h',
@ -248,8 +254,8 @@
'atom/renderer/api/atom_api_renderer_ipc.cc',
'atom/renderer/api/atom_renderer_bindings.cc',
'atom/renderer/api/atom_renderer_bindings.h',
'atom/renderer/api/atom_api_web_view.cc',
'atom/renderer/api/atom_api_web_view.h',
'atom/renderer/api/atom_api_web_frame.cc',
'atom/renderer/api/atom_api_web_frame.h',
'atom/renderer/atom_render_view_observer.cc',
'atom/renderer/atom_render_view_observer.h',
'atom/renderer/atom_renderer_client.cc',

View file

@ -4,7 +4,9 @@
#include "atom/browser/api/atom_api_web_contents.h"
#include "atom/browser/atom_browser_context.h"
#include "atom/common/api/api_messages.h"
#include "atom/common/native_mate_converters/gfx_converter.h"
#include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/native_mate_converters/value_converter.h"
@ -12,16 +14,118 @@
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/resource_request_details.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "atom/common/node_includes.h"
namespace atom {
namespace api {
namespace {
v8::Persistent<v8::ObjectTemplate> template_;
} // namespace
WebContents::WebContents(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
web_contents_(web_contents) {
guest_instance_id_(-1),
guest_opaque_(true),
auto_size_enabled_(false) {
}
WebContents::WebContents(const mate::Dictionary& options)
: guest_instance_id_(-1),
guest_opaque_(true),
auto_size_enabled_(false) {
options.Get("guestInstanceId", &guest_instance_id_);
auto browser_context = AtomBrowserContext::Get();
content::SiteInstance* site_instance = content::SiteInstance::CreateForURL(
browser_context, GURL("chrome-guest://fake-host"));
content::WebContents::CreateParams params(browser_context, site_instance);
bool is_guest;
if (options.Get("isGuest", &is_guest) && is_guest)
params.guest_delegate = this;
storage_.reset(content::WebContents::Create(params));
storage_->SetDelegate(this);
Observe(storage_.get());
}
WebContents::~WebContents() {
Destroy();
}
bool WebContents::AddMessageToConsole(content::WebContents* source,
int32 level,
const base::string16& message,
int32 line_no,
const base::string16& source_id) {
base::ListValue args;
args.AppendInteger(level);
args.AppendString(message);
args.AppendInteger(line_no);
args.AppendString(source_id);
Emit("console-message", args);
return true;
}
bool WebContents::ShouldCreateWebContents(
content::WebContents* web_contents,
int route_id,
WindowContainerType window_container_type,
const base::string16& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) {
base::ListValue args;
args.AppendString(target_url.spec());
args.AppendString(partition_id);
Emit("new-window", args);
return false;
}
void WebContents::CloseContents(content::WebContents* source) {
Emit("close");
}
content::WebContents* WebContents::OpenURLFromTab(
content::WebContents* source,
const content::OpenURLParams& params) {
if (params.disposition != CURRENT_TAB)
return NULL;
content::NavigationController::LoadURLParams load_url_params(params.url);
load_url_params.referrer = params.referrer;
load_url_params.transition_type = params.transition;
load_url_params.extra_headers = params.extra_headers;
load_url_params.should_replace_current_entry =
params.should_replace_current_entry;
load_url_params.is_renderer_initiated = params.is_renderer_initiated;
load_url_params.transferred_global_request_id =
params.transferred_global_request_id;
web_contents()->GetController().LoadURLWithParams(load_url_params);
return web_contents();
}
void WebContents::HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) {
if (!attached())
return;
// Send the unhandled keyboard events back to the embedder to reprocess them.
embedder_web_contents_->GetDelegate()->HandleKeyboardEvent(
web_contents(), event);
}
void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) {
@ -47,6 +151,16 @@ void WebContents::DidFinishLoad(content::RenderFrameHost* render_frame_host,
Emit("did-finish-load");
}
void WebContents::DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) {
base::ListValue args;
args.AppendInteger(error_code);
args.AppendString(error_description);
Emit("did-fail-load", args);
}
void WebContents::DidStartLoading(content::RenderViewHost* render_view_host) {
Emit("did-start-loading");
}
@ -55,6 +169,17 @@ void WebContents::DidStopLoading(content::RenderViewHost* render_view_host) {
Emit("did-stop-loading");
}
void WebContents::DidGetRedirectForResourceRequest(
content::RenderViewHost* render_view_host,
const content::ResourceRedirectDetails& details) {
base::ListValue args;
args.AppendString(details.url.spec());
args.AppendString(details.new_url.spec());
args.AppendBoolean(
details.resource_type == content::RESOURCE_TYPE_MAIN_FRAME);
Emit("did-get-redirect-request", args);
}
bool WebContents::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(WebContents, message)
@ -67,12 +192,88 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) {
return handled;
}
void WebContents::RenderViewReady() {
if (!is_guest())
return;
// We don't want to accidentally set the opacity of an interstitial page.
// WebContents::GetRenderWidgetHostView will return the RWHV of an
// interstitial page if one is showing at this time. We only want opacity
// to apply to web pages.
web_contents()->GetRenderViewHost()->GetView()->
SetBackgroundOpaque(guest_opaque_);
content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
if (auto_size_enabled_) {
rvh->EnableAutoResize(min_auto_size_, max_auto_size_);
} else {
rvh->DisableAutoResize(element_size_);
}
}
void WebContents::WebContentsDestroyed() {
// The RenderViewDeleted was not called when the WebContents is destroyed.
RenderViewDeleted(web_contents_->GetRenderViewHost());
RenderViewDeleted(web_contents()->GetRenderViewHost());
Emit("destroyed");
}
void WebContents::WillAttach(content::WebContents* embedder_web_contents,
const base::DictionaryValue& extra_params) {
embedder_web_contents_ = embedder_web_contents;
extra_params_.reset(extra_params.DeepCopy());
}
content::WebContents* WebContents::CreateNewGuestWindow(
const content::WebContents::CreateParams& create_params) {
NOTREACHED() << "Should not create new window from guest";
return nullptr;
}
void WebContents::DidAttach() {
base::ListValue args;
args.Append(extra_params_.release());
Emit("did-attach", args);
}
int WebContents::GetGuestInstanceID() const {
return guest_instance_id_;
}
void WebContents::ElementSizeChanged(const gfx::Size& old_size,
const gfx::Size& new_size) {
element_size_ = new_size;
}
void WebContents::GuestSizeChanged(const gfx::Size& old_size,
const gfx::Size& new_size) {
if (!auto_size_enabled_)
return;
guest_size_ = new_size;
GuestSizeChangedDueToAutoSize(old_size, new_size);
}
void WebContents::RequestPointerLockPermission(
bool user_gesture,
bool last_unlocked_by_target,
const base::Callback<void(bool enabled)>& callback) {
callback.Run(true);
}
void WebContents::RegisterDestructionCallback(
const DestructionCallback& callback) {
destruction_callback_ = callback;
}
void WebContents::Destroy() {
if (storage_) {
if (!destruction_callback_.is_null())
destruction_callback_.Run();
Observe(nullptr);
storage_.reset();
}
}
bool WebContents::IsAlive() const {
return web_contents() != NULL;
}
@ -155,6 +356,14 @@ bool WebContents::IsCrashed() const {
return web_contents()->IsCrashed();
}
void WebContents::SetUserAgent(const std::string& user_agent) {
web_contents()->SetUserAgentOverride(user_agent);
}
void WebContents::InsertCSS(const std::string& css) {
web_contents()->InsertCSS(css);
}
void WebContents::ExecuteJavaScript(const base::string16& code) {
web_contents()->GetMainFrame()->ExecuteJavaScript(code);
}
@ -164,36 +373,84 @@ bool WebContents::SendIPCMessage(const base::string16& channel,
return Send(new AtomViewMsg_Message(routing_id(), channel, args));
}
void WebContents::SetAutoSize(bool enabled,
const gfx::Size& min_size,
const gfx::Size& max_size) {
min_auto_size_ = min_size;
min_auto_size_.SetToMin(max_size);
max_auto_size_ = max_size;
max_auto_size_.SetToMax(min_size);
enabled &= !min_auto_size_.IsEmpty() && !max_auto_size_.IsEmpty();
if (!enabled && !auto_size_enabled_)
return;
auto_size_enabled_ = enabled;
if (!attached())
return;
content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
if (auto_size_enabled_) {
rvh->EnableAutoResize(min_auto_size_, max_auto_size_);
} else {
rvh->DisableAutoResize(element_size_);
guest_size_ = element_size_;
GuestSizeChangedDueToAutoSize(guest_size_, element_size_);
}
}
void WebContents::SetAllowTransparency(bool allow) {
if (guest_opaque_ != allow)
return;
guest_opaque_ = !allow;
if (!web_contents()->GetRenderViewHost()->GetView())
return;
web_contents()->GetRenderViewHost()->GetView()->SetBackgroundOpaque(!allow);
}
mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return mate::ObjectTemplateBuilder(isolate)
.SetMethod("isAlive", &WebContents::IsAlive)
.SetMethod("loadUrl", &WebContents::LoadURL)
.SetMethod("getUrl", &WebContents::GetURL)
.SetMethod("getTitle", &WebContents::GetTitle)
.SetMethod("isLoading", &WebContents::IsLoading)
.SetMethod("isWaitingForResponse", &WebContents::IsWaitingForResponse)
.SetMethod("stop", &WebContents::Stop)
.SetMethod("reload", &WebContents::Reload)
.SetMethod("reloadIgnoringCache", &WebContents::ReloadIgnoringCache)
.SetMethod("canGoBack", &WebContents::CanGoBack)
.SetMethod("canGoForward", &WebContents::CanGoForward)
.SetMethod("canGoToOffset", &WebContents::CanGoToOffset)
.SetMethod("goBack", &WebContents::GoBack)
.SetMethod("goForward", &WebContents::GoForward)
.SetMethod("goToIndex", &WebContents::GoToIndex)
.SetMethod("goToOffset", &WebContents::GoToOffset)
.SetMethod("getRoutingId", &WebContents::GetRoutingID)
.SetMethod("getProcessId", &WebContents::GetProcessID)
.SetMethod("isCrashed", &WebContents::IsCrashed)
.SetMethod("_executeJavaScript", &WebContents::ExecuteJavaScript)
.SetMethod("_send", &WebContents::SendIPCMessage);
if (template_.IsEmpty())
template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate)
.SetMethod("destroy", &WebContents::Destroy)
.SetMethod("isAlive", &WebContents::IsAlive)
.SetMethod("loadUrl", &WebContents::LoadURL)
.SetMethod("getUrl", &WebContents::GetURL)
.SetMethod("getTitle", &WebContents::GetTitle)
.SetMethod("isLoading", &WebContents::IsLoading)
.SetMethod("isWaitingForResponse", &WebContents::IsWaitingForResponse)
.SetMethod("stop", &WebContents::Stop)
.SetMethod("reload", &WebContents::Reload)
.SetMethod("reloadIgnoringCache", &WebContents::ReloadIgnoringCache)
.SetMethod("canGoBack", &WebContents::CanGoBack)
.SetMethod("canGoForward", &WebContents::CanGoForward)
.SetMethod("canGoToOffset", &WebContents::CanGoToOffset)
.SetMethod("goBack", &WebContents::GoBack)
.SetMethod("goForward", &WebContents::GoForward)
.SetMethod("goToIndex", &WebContents::GoToIndex)
.SetMethod("goToOffset", &WebContents::GoToOffset)
.SetMethod("getRoutingId", &WebContents::GetRoutingID)
.SetMethod("getProcessId", &WebContents::GetProcessID)
.SetMethod("isCrashed", &WebContents::IsCrashed)
.SetMethod("setUserAgent", &WebContents::SetUserAgent)
.SetMethod("insertCSS", &WebContents::InsertCSS)
.SetMethod("_executeJavaScript", &WebContents::ExecuteJavaScript)
.SetMethod("_send", &WebContents::SendIPCMessage)
.SetMethod("setAutoSize", &WebContents::SetAutoSize)
.SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency)
.Build());
return mate::ObjectTemplateBuilder(
isolate, v8::Local<v8::ObjectTemplate>::New(isolate, template_));
}
void WebContents::OnRendererMessage(const base::string16& channel,
const base::ListValue& args) {
// webContents.emit(channel, new Event(), args...);
Emit(base::UTF16ToUTF8(channel), args, web_contents(), NULL);
Emit(base::UTF16ToUTF8(channel), args);
}
void WebContents::OnRendererMessageSync(const base::string16& channel,
@ -203,12 +460,42 @@ void WebContents::OnRendererMessageSync(const base::string16& channel,
Emit(base::UTF16ToUTF8(channel), args, web_contents(), message);
}
void WebContents::GuestSizeChangedDueToAutoSize(const gfx::Size& old_size,
const gfx::Size& new_size) {
base::ListValue args;
args.AppendInteger(old_size.width());
args.AppendInteger(old_size.height());
args.AppendInteger(new_size.width());
args.AppendInteger(new_size.height());
Emit("size-changed", args);
}
// static
mate::Handle<WebContents> WebContents::Create(
mate::Handle<WebContents> WebContents::CreateFrom(
v8::Isolate* isolate, content::WebContents* web_contents) {
return mate::CreateHandle(isolate, new WebContents(web_contents));
}
// static
mate::Handle<WebContents> WebContents::Create(
v8::Isolate* isolate, const mate::Dictionary& options) {
return mate::CreateHandle(isolate, new WebContents(options));
}
} // namespace api
} // namespace atom
namespace {
void Initialize(v8::Handle<v8::Object> exports, v8::Handle<v8::Value> unused,
v8::Handle<v8::Context> context, void* priv) {
v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports);
dict.SetMethod("create", &atom::api::WebContents::Create);
}
} // namespace
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_web_contents, Initialize)

View file

@ -5,20 +5,36 @@
#ifndef ATOM_BROWSER_API_ATOM_API_WEB_CONTENTS_H_
#define ATOM_BROWSER_API_ATOM_API_WEB_CONTENTS_H_
#include <string>
#include "atom/browser/api/event_emitter.h"
#include "content/public/browser/browser_plugin_guest_delegate.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "native_mate/handle.h"
namespace mate {
class Dictionary;
}
namespace atom {
namespace api {
class WebContents : public mate::EventEmitter,
public content::BrowserPluginGuestDelegate,
public content::WebContentsDelegate,
public content::WebContentsObserver {
public:
static mate::Handle<WebContents> Create(v8::Isolate* isolate,
content::WebContents* web_contents);
// Create from an existing WebContents.
static mate::Handle<WebContents> CreateFrom(
v8::Isolate* isolate, content::WebContents* web_contents);
// Create a new WebContents.
static mate::Handle<WebContents> Create(
v8::Isolate* isolate, const mate::Dictionary& options);
void Destroy();
bool IsAlive() const;
void LoadURL(const GURL& url);
GURL GetURL() const;
@ -38,28 +54,98 @@ class WebContents : public mate::EventEmitter,
int GetRoutingID() const;
int GetProcessID() const;
bool IsCrashed() const;
void SetUserAgent(const std::string& user_agent);
void InsertCSS(const std::string& css);
void ExecuteJavaScript(const base::string16& code);
bool SendIPCMessage(const base::string16& channel,
const base::ListValue& args);
// Toggles autosize mode for corresponding <webview>.
void SetAutoSize(bool enabled,
const gfx::Size& min_size,
const gfx::Size& max_size);
// Sets the transparency of the guest.
void SetAllowTransparency(bool allow);
// Returns whether this is a guest view.
bool is_guest() const { return guest_instance_id_ != -1; }
// Returns whether this guest has an associated embedder.
bool attached() const { return !!embedder_web_contents_; }
content::WebContents* web_contents() const {
return content::WebContentsObserver::web_contents();
}
protected:
explicit WebContents(content::WebContents* web_contents);
explicit WebContents(const mate::Dictionary& options);
~WebContents();
// mate::Wrappable implementations:
// mate::Wrappable:
virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) OVERRIDE;
v8::Isolate* isolate) override;
// content::WebContentsObserver implementations:
virtual void RenderViewDeleted(content::RenderViewHost*) OVERRIDE;
virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
// content::WebContentsDelegate:
bool AddMessageToConsole(content::WebContents* source,
int32 level,
const base::string16& message,
int32 line_no,
const base::string16& source_id) override;
bool ShouldCreateWebContents(
content::WebContents* web_contents,
int route_id,
WindowContainerType window_container_type,
const base::string16& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) override;
void CloseContents(content::WebContents* source) override;
content::WebContents* OpenURLFromTab(
content::WebContents* source,
const content::OpenURLParams& params) override;
void HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override;
// content::WebContentsObserver:
virtual void RenderViewDeleted(content::RenderViewHost*) override;
virtual void RenderProcessGone(base::TerminationStatus status) override;
virtual void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) OVERRIDE;
const GURL& validated_url) override;
virtual void DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) override;
virtual void DidStartLoading(
content::RenderViewHost* render_view_host) OVERRIDE;
content::RenderViewHost* render_view_host) override;
virtual void DidStopLoading(
content::RenderViewHost* render_view_host) OVERRIDE;
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
virtual void WebContentsDestroyed() OVERRIDE;
content::RenderViewHost* render_view_host) override;
virtual void DidGetRedirectForResourceRequest(
content::RenderViewHost* render_view_host,
const content::ResourceRedirectDetails& details) override;
virtual bool OnMessageReceived(const IPC::Message& message) override;
virtual void RenderViewReady() override;
virtual void WebContentsDestroyed() override;
// content::BrowserPluginGuestDelegate:
virtual void WillAttach(content::WebContents* embedder_web_contents,
const base::DictionaryValue& extra_params) override;
virtual content::WebContents* CreateNewGuestWindow(
const content::WebContents::CreateParams& create_params) override;
virtual void DidAttach() override;
virtual int GetGuestInstanceID() const override;
virtual void ElementSizeChanged(const gfx::Size& old_size,
const gfx::Size& new_size) override;
virtual void GuestSizeChanged(const gfx::Size& old_size,
const gfx::Size& new_size) override;
virtual void RequestPointerLockPermission(
bool user_gesture,
bool last_unlocked_by_target,
const base::Callback<void(bool enabled)>& callback) override;
virtual void RegisterDestructionCallback(
const DestructionCallback& callback) override;
private:
// Called when received a message from renderer.
@ -71,7 +157,44 @@ class WebContents : public mate::EventEmitter,
const base::ListValue& args,
IPC::Message* message);
content::WebContents* web_contents_; // Weak.
void GuestSizeChangedDueToAutoSize(const gfx::Size& old_size,
const gfx::Size& new_size);
// Unique ID for a guest WebContents.
int guest_instance_id_;
DestructionCallback destruction_callback_;
// Stores whether the contents of the guest can be transparent.
bool guest_opaque_;
// The extra parameters associated with this guest view passed
// in from JavaScript. This will typically be the view instance ID,
// the API to use, and view-specific parameters. These parameters
// are passed along to new guests that are created from this guest.
scoped_ptr<base::DictionaryValue> extra_params_;
// Stores the WebContents that managed by this class.
scoped_ptr<content::WebContents> storage_;
// The WebContents that attaches this guest view.
content::WebContents* embedder_web_contents_;
// The size of the container element.
gfx::Size element_size_;
// The size of the guest content. Note: In autosize mode, the container
// element may not match the size of the guest.
gfx::Size guest_size_;
// Indicates whether autosize mode is enabled or not.
bool auto_size_enabled_;
// The maximum size constraints of the container element in autosize mode.
gfx::Size max_auto_size_;
// The minimum size constraints of the container element in autosize mode.
gfx::Size min_auto_size_;
DISALLOW_COPY_AND_ASSIGN(WebContents);
};

View file

@ -6,13 +6,11 @@
#include "atom/browser/api/atom_api_web_contents.h"
#include "atom/browser/native_window.h"
#include "atom/common/native_mate_converters/gfx_converter.h"
#include "content/public/browser/render_process_host.h"
#include "native_mate/callback.h"
#include "native_mate/constructor.h"
#include "native_mate/dictionary.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
#include "atom/common/node_includes.h"
@ -40,23 +38,6 @@ struct Converter<PrintSettings> {
}
};
template<>
struct Converter<gfx::Rect> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
gfx::Rect* out) {
if (!val->IsObject())
return false;
mate::Dictionary dict(isolate, val->ToObject());
int x, y, width, height;
if (!dict.Get("x", &x) || !dict.Get("y", &y) ||
!dict.Get("width", &width) || !dict.Get("height", &height))
return false;
*out = gfx::Rect(x, y, width, height);
return true;
}
};
} // namespace mate
namespace atom {
@ -375,12 +356,12 @@ void Window::SetProgressBar(double progress) {
}
mate::Handle<WebContents> Window::GetWebContents(v8::Isolate* isolate) const {
return WebContents::Create(isolate, window_->GetWebContents());
return WebContents::CreateFrom(isolate, window_->GetWebContents());
}
mate::Handle<WebContents> Window::GetDevToolsWebContents(
v8::Isolate* isolate) const {
return WebContents::Create(isolate, window_->GetDevToolsWebContents());
return WebContents::CreateFrom(isolate, window_->GetDevToolsWebContents());
}
// static

View file

@ -4,8 +4,6 @@
#include "atom/browser/api/event_emitter.h"
#include <vector>
#include "atom/browser/api/event.h"
#include "atom/common/native_mate_converters/v8_value_converter.h"
#include "base/memory/scoped_ptr.h"
@ -64,6 +62,23 @@ bool EventEmitter::Emit(const base::StringPiece& name,
v8::Handle<v8::Context> context = isolate->GetCurrentContext();
scoped_ptr<atom::V8ValueConverter> converter(new atom::V8ValueConverter);
// v8_args = [args...];
Arguments v8_args;
v8_args.reserve(args.GetSize());
for (size_t i = 0; i < args.GetSize(); i++) {
const base::Value* value(NULL);
if (args.Get(i, &value))
v8_args.push_back(converter->ToV8Value(value, context));
}
return Emit(isolate, name, v8_args, sender, message);
}
bool EventEmitter::Emit(v8::Isolate* isolate,
const base::StringPiece& name,
Arguments args,
content::WebContents* sender,
IPC::Message* message) {
v8::Handle<v8::Object> event;
bool use_native_event = sender && message;
@ -75,20 +90,13 @@ bool EventEmitter::Emit(const base::StringPiece& name,
event = CreateEventObject(isolate);
}
// v8_args = [name, event, args...];
std::vector<v8::Handle<v8::Value>> v8_args;
v8_args.reserve(args.GetSize() + 2);
v8_args.push_back(mate::StringToV8(isolate, name));
v8_args.push_back(event);
for (size_t i = 0; i < args.GetSize(); i++) {
const base::Value* value(NULL);
if (args.Get(i, &value))
v8_args.push_back(converter->ToV8Value(value, context));
}
// args = [name, event, args...];
args.insert(args.begin(), event);
args.insert(args.begin(), mate::StringToV8(isolate, name));
// this.emit.apply(this, v8_args);
node::MakeCallback(isolate, GetWrapper(isolate), "emit", v8_args.size(),
&v8_args[0]);
// this.emit.apply(this, args);
node::MakeCallback(isolate, GetWrapper(isolate), "emit", args.size(),
&args[0]);
if (use_native_event) {
Handle<Event> native_event;

View file

@ -5,6 +5,8 @@
#ifndef ATOM_BROWSER_API_EVENT_EMITTER_H_
#define ATOM_BROWSER_API_EVENT_EMITTER_H_
#include <vector>
#include "native_mate/wrappable.h"
namespace base {
@ -23,6 +25,9 @@ namespace mate {
// Provide helperers to emit event in JavaScript.
class EventEmitter : public Wrappable {
public:
typedef std::vector<v8::Handle<v8::Value>> Arguments;
protected:
EventEmitter();
@ -36,6 +41,13 @@ class EventEmitter : public Wrappable {
bool Emit(const base::StringPiece& name, const base::ListValue& args,
content::WebContents* sender, IPC::Message* message);
// Lower level implementations.
bool Emit(v8::Isolate* isolate,
const base::StringPiece& name,
Arguments args,
content::WebContents* sender = nullptr,
IPC::Message* message = nullptr);
private:
DISALLOW_COPY_AND_ASSIGN(EventEmitter);
};

View file

@ -1,4 +1,5 @@
EventEmitter = require('events').EventEmitter
binding = process.atomBinding 'web_contents'
ipc = require 'ipc'
module.exports.wrap = (webContents) ->
@ -31,12 +32,15 @@ module.exports.wrap = (webContents) ->
process.emit 'ATOM_BROWSER_RELEASE_RENDER_VIEW', "#{processId}-#{routingId}"
# Dispatch IPC messages to the ipc module.
webContents.on 'ipc-message', (event, channel, args...) =>
webContents.on 'ipc-message', (event, channel, args...) ->
Object.defineProperty event, 'sender', value: webContents
ipc.emit channel, event, args...
webContents.on 'ipc-message-sync', (event, channel, args...) =>
webContents.on 'ipc-message-sync', (event, channel, args...) ->
Object.defineProperty event, 'returnValue', set: (value) -> event.sendReply JSON.stringify(value)
Object.defineProperty event, 'sender', value: webContents
ipc.emit channel, event, args...
webContents
module.exports.create = (options={}) ->
@wrap binding.create(options)

View file

@ -7,15 +7,17 @@
#include "atom/browser/atom_access_token_store.h"
#include "atom/browser/atom_browser_context.h"
#include "atom/browser/atom_browser_main_parts.h"
#include "atom/browser/atom_resource_dispatcher_host_delegate.h"
#include "atom/browser/atom_speech_recognition_manager_delegate.h"
#include "atom/browser/native_window.h"
#include "atom/browser/web_view/web_view_renderer_state.h"
#include "atom/browser/window_list.h"
#include "atom/common/options_switches.h"
#include "base/command_line.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/printing/printing_message_filter.h"
#include "chrome/browser/speech/tts_message_filter.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/web_preferences.h"
@ -58,12 +60,6 @@ void AtomBrowserClient::RenderProcessWillLaunch(
host->AddFilter(new TtsMessageFilter(id, host->GetBrowserContext()));
}
void AtomBrowserClient::ResourceDispatcherHostCreated() {
resource_dispatcher_delegate_.reset(new AtomResourceDispatcherHostDelegate);
content::ResourceDispatcherHost::Get()->SetDelegate(
resource_dispatcher_delegate_.get());
}
content::SpeechRecognitionManagerDelegate*
AtomBrowserClient::GetSpeechRecognitionManagerDelegate() {
return new AtomSpeechRecognitionManagerDelegate;
@ -80,7 +76,7 @@ void AtomBrowserClient::OverrideWebkitPrefs(
prefs->javascript_enabled = true;
prefs->web_security_enabled = true;
prefs->javascript_can_open_windows_automatically = true;
prefs->plugins_enabled = false;
prefs->plugins_enabled = true;
prefs->dom_paste_enabled = true;
prefs->java_enabled = false;
prefs->allow_scripts_to_close_windows = true;
@ -146,8 +142,20 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
window = *iter;
}
if (window != NULL)
if (window != NULL) {
window->AppendExtraCommandLineSwitches(command_line, child_process_id);
} else {
// Append commnad line arguments for guest web view.
WebViewRendererState::WebViewInfo info;
if (WebViewRendererState::GetInstance()->GetInfo(child_process_id, &info)) {
command_line->AppendSwitchASCII(
switches::kGuestInstanceID,
base::IntToString(info.guest_instance_id));
command_line->AppendSwitchASCII(
switches::kNodeIntegration,
info.node_integration ? "true" : "false");
}
}
dying_render_process_ = NULL;
}

View file

@ -11,8 +11,6 @@
namespace atom {
class AtomResourceDispatcherHostDelegate;
class AtomBrowserClient : public brightray::BrowserClient {
public:
AtomBrowserClient();
@ -22,7 +20,6 @@ class AtomBrowserClient : public brightray::BrowserClient {
// content::ContentBrowserClient:
virtual void RenderProcessWillLaunch(
content::RenderProcessHost* host) OVERRIDE;
virtual void ResourceDispatcherHostCreated() OVERRIDE;
virtual content::SpeechRecognitionManagerDelegate*
GetSpeechRecognitionManagerDelegate() override;
virtual content::AccessTokenStore* CreateAccessTokenStore() OVERRIDE;
@ -41,8 +38,6 @@ class AtomBrowserClient : public brightray::BrowserClient {
virtual brightray::BrowserMainParts* OverrideCreateBrowserMainParts(
const content::MainFunctionParams&) OVERRIDE;
scoped_ptr<AtomResourceDispatcherHostDelegate> resource_dispatcher_delegate_;
// The render process which would be swapped out soon.
content::RenderProcessHost* dying_render_process_;

View file

@ -7,6 +7,7 @@
#include "atom/browser/atom_browser_main_parts.h"
#include "atom/browser/net/atom_url_request_job_factory.h"
#include "atom/browser/net/asar/asar_protocol_handler.h"
#include "atom/browser/web_view/web_view_manager.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/worker_pool.h"
#include "chrome/browser/browser_process.h"
@ -68,6 +69,12 @@ net::URLRequestJobFactory* AtomBrowserContext::CreateURLRequestJobFactory(
return top_job_factory.release();
}
content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() {
if (!guest_manager_)
guest_manager_.reset(new WebViewManager(this));
return guest_manager_.get();
}
// static
AtomBrowserContext* AtomBrowserContext::Get() {
return static_cast<AtomBrowserContext*>(

View file

@ -12,6 +12,7 @@ class BrowserProcess;
namespace atom {
class AtomURLRequestJobFactory;
class WebViewManager;
class AtomBrowserContext : public brightray::BrowserContext {
public:
@ -21,17 +22,20 @@ class AtomBrowserContext : public brightray::BrowserContext {
// Returns the browser context singleton.
static AtomBrowserContext* Get();
AtomURLRequestJobFactory* job_factory() const { return job_factory_; }
protected:
// brightray::URLRequestContextGetter::Delegate:
virtual net::URLRequestJobFactory* CreateURLRequestJobFactory(
content::ProtocolHandlerMap* handlers,
content::URLRequestInterceptorScopedVector* interceptors) OVERRIDE;
content::URLRequestInterceptorScopedVector* interceptors) override;
// content::BrowserContext:
virtual content::BrowserPluginGuestManager* GetGuestManager() override;
AtomURLRequestJobFactory* job_factory() const { return job_factory_; }
private:
// A fake BrowserProcess object that used to feed the source code from chrome.
scoped_ptr<BrowserProcess> fake_browser_process_;
scoped_ptr<WebViewManager> guest_manager_;
AtomURLRequestJobFactory* job_factory_; // Weak reference.

View file

@ -1,50 +0,0 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/atom_resource_dispatcher_host_delegate.h"
#include <string>
#include "base/logging.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/resource_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
namespace atom {
namespace {
const char* kDisableXFrameOptions = "disable-x-frame-options";
} // namespace
AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() {
}
AtomResourceDispatcherHostDelegate::~AtomResourceDispatcherHostDelegate() {
}
void AtomResourceDispatcherHostDelegate::OnResponseStarted(
net::URLRequest* request,
content::ResourceContext* resource_context,
content::ResourceResponse* response,
IPC::Sender* sender) {
// Check if frame's name contains "disable-x-frame-options"
int p, f;
if (!content::ResourceRequestInfo::GetRenderFrameForRequest(request, &p, &f))
return;
content::RenderFrameHost* frame = content::RenderFrameHost::FromID(p, f);
if (!frame)
return;
if (frame->GetFrameName().find(kDisableXFrameOptions) == std::string::npos)
return;
// Remove the "X-Frame-Options" from response headers.
net::HttpResponseHeaders* response_headers = request->response_headers();
if (response_headers && response_headers->HasHeader("x-frame-options"))
response_headers->RemoveHeader("x-frame-options");
}
} // namespace atom

View file

@ -1,31 +0,0 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_
#define ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_
#include "base/compiler_specific.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
namespace atom {
class AtomResourceDispatcherHostDelegate
: public content::ResourceDispatcherHostDelegate {
public:
AtomResourceDispatcherHostDelegate();
virtual ~AtomResourceDispatcherHostDelegate();
// content::ResourceDispatcherHostDelegate:
virtual void OnResponseStarted(net::URLRequest* request,
content::ResourceContext* resource_context,
content::ResourceResponse* response,
IPC::Sender* sender) OVERRIDE;
private:
DISALLOW_COPY_AND_ASSIGN(AtomResourceDispatcherHostDelegate);
};
} // namespace atom
#endif // ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_

View file

@ -0,0 +1,89 @@
ipc = require 'ipc'
webContents = require 'web-contents'
webViewManager = null # Doesn't exist in early initialization.
supportedWebViewEvents = [
'did-finish-load'
'did-fail-load'
'did-frame-finish-load'
'did-start-loading'
'did-stop-loading'
'did-get-redirect-request'
'console-message'
'new-window'
'close'
'crashed'
'destroyed'
]
nextInstanceId = 0
guestInstances = {}
# Generate guestInstanceId.
getNextInstanceId = (webContents) ->
++nextInstanceId
# Create a new guest instance.
createGuest = (embedder, params) ->
webViewManager ?= process.atomBinding 'web_view_manager'
id = getNextInstanceId embedder
guest = webContents.create
isGuest: true
guestInstanceId: id
storagePartitionId: params.storagePartitionId
guestInstances[id] = {guest, embedder}
webViewManager.addGuest id, embedder, guest, params.nodeIntegration
# Destroy guest when the embedder is gone.
embedder.once 'render-view-deleted', ->
destroyGuest id if guestInstances[id]?
# Init guest web view after attached.
guest.once 'did-attach', (event, params) ->
@viewInstanceId = params.instanceId
min = width: params.minwidth, height: params.minheight
max = width: params.maxwidth, height: params.maxheight
@setAutoSize params.autosize, min, max
if params.src
@loadUrl params.src
if params.allowtransparency?
@setAllowTransparency params.allowtransparency
# Dispatch events to embedder.
for event in supportedWebViewEvents
do (event) ->
guest.on event, (_, args...) ->
embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{guest.viewInstanceId}", event, args...
# Autosize.
guest.on 'size-changed', (_, args...) ->
embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED", args...
id
# Destroy an existing guest instance.
destroyGuest = (id) ->
webViewManager.removeGuest id
guestInstances[id].guest.destroy()
delete guestInstances[id]
ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', (event, type, params, requestId) ->
event.sender.send "ATOM_SHELL_RESPONSE_#{requestId}", createGuest(event.sender, params)
ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', (event, id) ->
destroyGuest id
ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_AUTO_SIZE', (event, id, params) ->
guestInstances[id]?.guest.setAutoSize params.enableAutoSize, params.min, params.max
ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', (event, id, allowtransparency) ->
guestInstances[id]?.guest.setAllowTransparency allowtransparency
# Returns WebContents from its guest id.
exports.getGuest = (id) ->
guestInstances[id]?.guest
# Returns the embedder of the guest.
exports.getEmbedder = (id) ->
guestInstances[id]?.embedder

View file

@ -23,9 +23,8 @@ process.argv.splice startMark, endMark - startMark + 1
globalPaths = module.globalPaths
globalPaths.push path.join process.resourcesPath, 'atom', 'browser', 'api', 'lib'
# Do loading in next tick since we still need some initialize work before
# native bindings can work.
setImmediate ->
# Following operations need extra bindings by AtomBindings.
process.once 'BIND_DONE', ->
# Import common settings.
require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init.js')
@ -61,6 +60,9 @@ setImmediate ->
# Load the RPC server.
require './rpc-server.js'
# Load the guest view manager.
require './guest-view-manager.js'
# Now we try to load app's package.json.
packageJson = null

View file

@ -94,11 +94,15 @@ ipc.on 'ATOM_BROWSER_GLOBAL', (event, name) ->
catch e
event.returnValue = errorToMeta e
ipc.on 'ATOM_BROWSER_CURRENT_WINDOW', (event) ->
ipc.on 'ATOM_BROWSER_CURRENT_WINDOW', (event, guestInstanceId) ->
try
BrowserWindow = require 'browser-window'
window = BrowserWindow.fromWebContents event.sender
window = BrowserWindow.fromDevToolsWebContents event.sender unless window?
if guestInstanceId?
guestViewManager = require './guest-view-manager'
window = BrowserWindow.fromWebContents guestViewManager.getEmbedder(guestInstanceId)
else
window = BrowserWindow.fromWebContents event.sender
window = BrowserWindow.fromDevToolsWebContents event.sender unless window?
event.returnValue = valueToMeta event.sender, window
catch e
event.returnValue = errorToMeta e
@ -157,3 +161,10 @@ ipc.on 'ATOM_BROWSER_MEMBER_GET', (event, id, name) ->
ipc.on 'ATOM_BROWSER_DEREFERENCE', (event, storeId) ->
objectsRegistry.remove event.sender.getId(), storeId
ipc.on 'ATOM_BROWSER_GUEST_WEB_CONTENTS', (event, guestInstanceId) ->
try
guestViewManager = require './guest-view-manager'
event.returnValue = valueToMeta event.sender, guestViewManager.getGuest(guestInstanceId)
catch e
event.returnValue = errorToMeta e

View file

@ -84,7 +84,7 @@ NativeWindow::NativeWindow(content::WebContents* web_contents,
has_frame_(true),
enable_larger_than_screen_(false),
is_closed_(false),
node_integration_("except-iframe"),
node_integration_(true),
has_dialog_attached_(false),
zoom_factor_(1.0),
weak_factory_(this),
@ -94,12 +94,16 @@ NativeWindow::NativeWindow(content::WebContents* web_contents,
options.Get(switches::kFrame, &has_frame_);
options.Get(switches::kEnableLargerThanScreen, &enable_larger_than_screen_);
options.Get(switches::kNodeIntegration, &node_integration_);
// Read icon before window is created.
options.Get(switches::kIcon, &icon_);
// Read iframe security before any navigation.
options.Get(switches::kNodeIntegration, &node_integration_);
// Be compatible with old API of "node-integration" option.
std::string old_string_token;
if (options.Get(switches::kNodeIntegration, &old_string_token) &&
old_string_token != "disable")
node_integration_ = true;
// Read the web preferences.
options.Get(switches::kWebPreferences, &web_preferences_);
@ -341,7 +345,7 @@ void NativeWindow::AppendExtraCommandLineSwitches(
base::CommandLine* command_line, int child_process_id) {
// Append --node-integration to renderer process.
command_line->AppendSwitchASCII(switches::kNodeIntegration,
node_integration_);
node_integration_ ? "true" : "false");
// Append --zoom-factor.
if (zoom_factor_ != 1.0)
@ -358,6 +362,10 @@ void NativeWindow::AppendExtraCommandLineSwitches(
command_line->AppendSwitch(::switches::kDisableDirectWrite);
#endif
// Check if plugins are enabled.
if (web_preferences_.Get("plugins", &b) && b)
command_line->AppendSwitch(switches::kEnablePlugins);
// This set of options are not availabe in WebPreferences, so we have to pass
// them via command line and enable them in renderer procss.
for (size_t i = 0; i < arraysize(kWebRuntimeFeatures); ++i) {
@ -388,8 +396,6 @@ void NativeWindow::OverrideWebkitPrefs(const GURL& url,
prefs->experimental_webgl_enabled = b;
if (web_preferences_.Get("webaudio", &b))
prefs->webaudio_enabled = b;
if (web_preferences_.Get("plugins", &b))
prefs->plugins_enabled = b;
if (web_preferences_.Get("extra-plugin-dirs", &list))
for (size_t i = 0; i < list.size(); ++i)
content::PluginService::GetInstance()->AddExtraPluginDir(list[i]);

View file

@ -288,8 +288,8 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate,
// The windows has been closed.
bool is_closed_;
// The security token of iframe.
std::string node_integration_;
// Whether node integration is enabled.
bool node_integration_;
// There is a dialog that has been attached to window.
bool has_dialog_attached_;

View file

@ -0,0 +1,112 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/web_view/web_view_manager.h"
#include "atom/browser/api/atom_api_web_contents.h"
#include "atom/browser/atom_browser_context.h"
#include "atom/browser/web_view/web_view_renderer_state.h"
#include "base/bind.h"
#include "base/stl_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "atom/common/node_includes.h"
namespace mate {
template<>
struct Converter<content::WebContents*> {
static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
content::WebContents** out) {
atom::api::WebContents* contents;
if (!Converter<atom::api::WebContents*>::FromV8(isolate, val, &contents))
return false;
*out = contents->web_contents();
return true;
}
};
} // namespace mate
namespace atom {
WebViewManager::WebViewManager(content::BrowserContext* context) {
}
WebViewManager::~WebViewManager() {
}
void WebViewManager::AddGuest(int guest_instance_id,
content::WebContents* embedder,
content::WebContents* web_contents,
bool node_integration) {
web_contents_map_[guest_instance_id] = { web_contents, embedder };
WebViewRendererState::WebViewInfo web_view_info = {
guest_instance_id, node_integration
};
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&WebViewRendererState::AddGuest,
base::Unretained(WebViewRendererState::GetInstance()),
web_contents->GetRenderProcessHost()->GetID(),
web_view_info));
}
void WebViewManager::RemoveGuest(int guest_instance_id) {
auto web_contents = web_contents_map_[guest_instance_id].web_contents;
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(
&WebViewRendererState::RemoveGuest,
base::Unretained(WebViewRendererState::GetInstance()),
web_contents->GetRenderProcessHost()->GetID()));
web_contents_map_.erase(guest_instance_id);
}
void WebViewManager::MaybeGetGuestByInstanceIDOrKill(
int guest_instance_id,
int embedder_render_process_id,
const GuestByInstanceIDCallback& callback) {
if (ContainsKey(web_contents_map_, guest_instance_id))
callback.Run(web_contents_map_[guest_instance_id].web_contents);
else
callback.Run(nullptr);
}
bool WebViewManager::ForEachGuest(content::WebContents* embedder_web_contents,
const GuestCallback& callback) {
for (auto& item : web_contents_map_)
if (item.second.embedder == embedder_web_contents &&
callback.Run(item.second.web_contents))
return true;
return false;
}
} // namespace atom
namespace {
void Initialize(v8::Handle<v8::Object> exports, v8::Handle<v8::Value> unused,
v8::Handle<v8::Context> context, void* priv) {
using atom::WebViewManager;
auto manager = static_cast<WebViewManager*>(
atom::AtomBrowserContext::Get()->GetGuestManager());
mate::Dictionary dict(context->GetIsolate(), exports);
dict.SetMethod("addGuest",
base::Bind(&WebViewManager::AddGuest,
base::Unretained(manager)));
dict.SetMethod("removeGuest",
base::Bind(&WebViewManager::RemoveGuest,
base::Unretained(manager)));
}
} // namespace
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_web_view_manager, Initialize)

View file

@ -0,0 +1,50 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_WEB_VIEW_WEB_VIEW_MANAGER_H_
#define ATOM_BROWSER_WEB_VIEW_WEB_VIEW_MANAGER_H_
#include <map>
#include "content/public/browser/browser_plugin_guest_manager.h"
namespace content {
class BrowserContext;
}
namespace atom {
class WebViewManager : public content::BrowserPluginGuestManager {
public:
explicit WebViewManager(content::BrowserContext* context);
virtual ~WebViewManager();
void AddGuest(int guest_instance_id,
content::WebContents* embedder,
content::WebContents* web_contents,
bool node_integration);
void RemoveGuest(int guest_instance_id);
protected:
// content::BrowserPluginGuestManager:
virtual void MaybeGetGuestByInstanceIDOrKill(
int guest_instance_id,
int embedder_render_process_id,
const GuestByInstanceIDCallback& callback) override;
virtual bool ForEachGuest(content::WebContents* embedder_web_contents,
const GuestCallback& callback) override;
private:
struct WebContentsWithEmbedder {
content::WebContents* web_contents; // Weak ref.
content::WebContents* embedder;
};
std::map<int, WebContentsWithEmbedder> web_contents_map_;
DISALLOW_COPY_AND_ASSIGN(WebViewManager);
};
} // namespace atom
#endif // ATOM_BROWSER_WEB_VIEW_WEB_VIEW_MANAGER_H_

View file

@ -0,0 +1,46 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/web_view/web_view_renderer_state.h"
#include "content/public/browser/browser_thread.h"
namespace atom {
// static
WebViewRendererState* WebViewRendererState::GetInstance() {
return Singleton<WebViewRendererState>::get();
}
WebViewRendererState::WebViewRendererState() {
}
WebViewRendererState::~WebViewRendererState() {
}
bool WebViewRendererState::IsGuest(int render_process_id) {
return webview_info_map_.find(render_process_id) !=
webview_info_map_.end();
}
void WebViewRendererState::AddGuest(int guest_process_id,
const WebViewInfo& webview_info) {
webview_info_map_[guest_process_id] = webview_info;
}
void WebViewRendererState::RemoveGuest(int guest_process_id) {
webview_info_map_.erase(guest_process_id);
}
bool WebViewRendererState::GetInfo(int guest_process_id,
WebViewInfo* webview_info) {
WebViewInfoMap::iterator iter = webview_info_map_.find(guest_process_id);
if (iter != webview_info_map_.end()) {
*webview_info = iter->second;
return true;
}
return false;
}
} // namespace atom

View file

@ -0,0 +1,56 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_WEB_VIEW_WEB_VIEW_RENDERER_STATE_H_
#define ATOM_BROWSER_WEB_VIEW_WEB_VIEW_RENDERER_STATE_H_
#include <map>
#include <string>
#include <utility>
#include "base/memory/singleton.h"
namespace atom {
class WebViewManager;
// This class keeps track of <webview> renderer state for use on the IO thread.
// All methods should be called on the IO thread.
class WebViewRendererState {
public:
struct WebViewInfo {
int guest_instance_id;
bool node_integration;
};
static WebViewRendererState* GetInstance();
// Looks up the information for the embedder <webview> for a given render
// view, if one exists. Called on the IO thread.
bool GetInfo(int guest_process_id, WebViewInfo* webview_info);
// Returns true if the given renderer is used by webviews.
bool IsGuest(int render_process_id);
private:
friend class WebViewManager;
friend struct DefaultSingletonTraits<WebViewRendererState>;
typedef std::map<int, WebViewInfo> WebViewInfoMap;
WebViewRendererState();
~WebViewRendererState();
// Adds or removes a <webview> guest render process from the set.
void AddGuest(int render_process_id, const WebViewInfo& webview_info);
void RemoveGuest(int render_process_id);
WebViewInfoMap webview_info_map_;
DISALLOW_COPY_AND_ASSIGN(WebViewRendererState);
};
} // namespace atom
#endif // ATOM_BROWSER_WEB_VIEW_WEB_VIEW_RENDERER_STATE_H_

View file

@ -2,61 +2,9 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "native_mate/dictionary.h"
#include "ui/gfx/screen.h"
#include "atom/common/native_mate_converters/gfx_converter.h"
#include "atom/common/node_includes.h"
namespace mate {
template<>
struct Converter<gfx::Point> {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Point& val) {
return mate::ObjectTemplateBuilder(isolate).SetValue("x", val.x())
.SetValue("y", val.y())
.Build()->NewInstance();
}
};
template<>
struct Converter<gfx::Size> {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Size& val) {
return mate::ObjectTemplateBuilder(isolate).SetValue("width", val.width())
.SetValue("height", val.height())
.Build()->NewInstance();
}
};
template<>
struct Converter<gfx::Rect> {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Rect& val) {
return mate::ObjectTemplateBuilder(isolate).SetValue("x", val.x())
.SetValue("y", val.y())
.SetValue("width", val.width())
.SetValue("height", val.height())
.Build()->NewInstance();
}
};
template<>
struct Converter<gfx::Display> {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Display& display) {
return mate::ObjectTemplateBuilder(isolate)
.SetValue("bounds", display.bounds())
.SetValue("workArea", display.work_area())
.SetValue("size", display.size())
.SetValue("workAreaSize", display.work_area_size())
.SetValue("scaleFactor", display.device_scale_factor())
.Build()->NewInstance();
}
};
} // namespace mate
namespace {
void Initialize(v8::Handle<v8::Object> exports, v8::Handle<v8::Value> unused,

View file

@ -85,6 +85,9 @@ void AtomBindings::BindTo(v8::Isolate* isolate,
versions->Set(mate::StringToV8(isolate, "chrome"),
mate::StringToV8(isolate, CHROME_VERSION_STRING));
}
v8::Handle<v8::Value> event = mate::StringToV8(isolate, "BIND_DONE");
node::MakeCallback(isolate, process, "emit", 1, &event);
}
void AtomBindings::ActivateUVLoop(v8::Isolate* isolate) {

View file

@ -0,0 +1,101 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_GFX_CONVERTER_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_GFX_CONVERTER_H_
#include "native_mate/dictionary.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/screen.h"
#include "ui/gfx/size.h"
namespace mate {
template<>
struct Converter<gfx::Point> {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Point& val) {
mate::Dictionary dict(isolate, v8::Object::New(isolate));
dict.Set("x", val.x());
dict.Set("y", val.y());
return dict.GetHandle();
}
static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
gfx::Point* out) {
mate::Dictionary dict;
if (!ConvertFromV8(isolate, val, &dict))
return false;
int x, y;
if (!dict.Get("x", &x) || !dict.Get("y", &y))
return false;
*out = gfx::Point(x, y);
return true;
}
};
template<>
struct Converter<gfx::Size> {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Size& val) {
mate::Dictionary dict(isolate, v8::Object::New(isolate));
dict.Set("width", val.width());
dict.Set("height", val.height());
return dict.GetHandle();
}
static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
gfx::Size* out) {
mate::Dictionary dict;
if (!ConvertFromV8(isolate, val, &dict))
return false;
int width, height;
if (!dict.Get("width", &width) || !dict.Get("height", &height))
return false;
*out = gfx::Size(width, height);
return true;
}
};
template<>
struct Converter<gfx::Rect> {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Rect& val) {
mate::Dictionary dict(isolate, v8::Object::New(isolate));
dict.Set("x", val.x());
dict.Set("y", val.y());
dict.Set("width", val.width());
dict.Set("height", val.height());
return dict.GetHandle();
}
static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
gfx::Rect* out) {
mate::Dictionary dict;
if (!ConvertFromV8(isolate, val, &dict))
return false;
int x, y, width, height;
if (!dict.Get("x", &x) || !dict.Get("y", &y) ||
!dict.Get("width", &width) || !dict.Get("height", &height))
return false;
*out = gfx::Rect(x, y, width, height);
return true;
}
};
template<>
struct Converter<gfx::Display> {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Display& display) {
mate::Dictionary dict(isolate, v8::Object::New(isolate));
dict.Set("bounds", display.bounds());
dict.Set("workArea", display.work_area());
dict.Set("size", display.size());
dict.Set("workAreaSize", display.work_area_size());
dict.Set("scaleFactor", display.device_scale_factor());
return dict.GetHandle();
}
};
} // namespace mate
#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_GFX_CONVERTER_H_

View file

@ -68,6 +68,7 @@ REFERENCE_MODULE(atom_browser_power_monitor);
REFERENCE_MODULE(atom_browser_protocol);
REFERENCE_MODULE(atom_browser_global_shortcut);
REFERENCE_MODULE(atom_browser_tray);
REFERENCE_MODULE(atom_browser_web_contents);
REFERENCE_MODULE(atom_browser_window);
REFERENCE_MODULE(atom_common_asar);
REFERENCE_MODULE(atom_common_clipboard);
@ -77,7 +78,7 @@ REFERENCE_MODULE(atom_common_screen);
REFERENCE_MODULE(atom_common_shell);
REFERENCE_MODULE(atom_common_v8_util);
REFERENCE_MODULE(atom_renderer_ipc);
REFERENCE_MODULE(atom_renderer_web_view);
REFERENCE_MODULE(atom_renderer_web_frame);
#undef REFERENCE_MODULE
namespace atom {

View file

@ -60,6 +60,12 @@ const char kDarkTheme[] = "dark-theme";
// Enable DirectWrite on Windows.
const char kDirectWrite[] = "direct-write";
// Enable plugins.
const char kEnablePlugins[] = "enable-plugins";
// Instancd ID of guest WebContents.
const char kGuestInstanceID[] = "guest-instance-id";
// Web runtime features.
const char kExperimentalFeatures[] = "experimental-features";
const char kExperimentalCanvasFeatures[] = "experimental-canvas-features";

View file

@ -36,6 +36,8 @@ extern const char kAutoHideMenuBar[];
extern const char kEnableLargerThanScreen[];
extern const char kDarkTheme[];
extern const char kDirectWrite[];
extern const char kEnablePlugins[];
extern const char kGuestInstanceID[];
extern const char kExperimentalFeatures[];
extern const char kExperimentalCanvasFeatures[];

View file

@ -0,0 +1,86 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/renderer/api/atom_api_web_frame.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "atom/common/node_includes.h"
namespace atom {
namespace api {
WebFrame::WebFrame()
: web_frame_(blink::WebLocalFrame::frameForCurrentContext()) {
}
WebFrame::~WebFrame() {
}
void WebFrame::SetName(const std::string& name) {
web_frame_->setName(blink::WebString::fromUTF8(name));
}
double WebFrame::SetZoomLevel(double level) {
return web_frame_->view()->setZoomLevel(level);
}
double WebFrame::GetZoomLevel() const {
return web_frame_->view()->zoomLevel();
}
double WebFrame::SetZoomFactor(double factor) {
return blink::WebView::zoomLevelToZoomFactor(SetZoomLevel(
blink::WebView::zoomFactorToZoomLevel(factor)));
}
double WebFrame::GetZoomFactor() const {
return blink::WebView::zoomLevelToZoomFactor(GetZoomLevel());
}
v8::Handle<v8::Value> WebFrame::RegisterEmbedderCustomElement(
const base::string16& name, v8::Handle<v8::Object> options) {
blink::WebExceptionCode c = 0;
return web_frame_->document().registerEmbedderCustomElement(name, options, c);
}
mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return mate::ObjectTemplateBuilder(isolate)
.SetMethod("setName", &WebFrame::SetName)
.SetMethod("setZoomLevel", &WebFrame::SetZoomLevel)
.SetMethod("getZoomLevel", &WebFrame::GetZoomLevel)
.SetMethod("setZoomFactor", &WebFrame::SetZoomFactor)
.SetMethod("getZoomFactor", &WebFrame::GetZoomFactor)
.SetMethod("registerEmbedderCustomElement",
&WebFrame::RegisterEmbedderCustomElement);
}
// static
mate::Handle<WebFrame> WebFrame::Create(v8::Isolate* isolate) {
return CreateHandle(isolate, new WebFrame);
}
} // namespace api
} // namespace atom
namespace {
void Initialize(v8::Handle<v8::Object> exports, v8::Handle<v8::Value> unused,
v8::Handle<v8::Context> context, void* priv) {
v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports);
dict.Set("webFrame", atom::api::WebFrame::Create(isolate));
}
} // namespace
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_renderer_web_frame, Initialize)

View file

@ -2,44 +2,51 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_RENDERER_API_ATOM_API_WEB_VIEW_H_
#define ATOM_RENDERER_API_ATOM_API_WEB_VIEW_H_
#ifndef ATOM_RENDERER_API_ATOM_API_WEB_FRAME_H_
#define ATOM_RENDERER_API_ATOM_API_WEB_FRAME_H_
#include <string>
#include "native_mate/handle.h"
#include "native_mate/wrappable.h"
namespace blink {
class WebView;
class WebLocalFrame;
}
namespace atom {
namespace api {
class WebView : public mate::Wrappable {
class WebFrame : public mate::Wrappable {
public:
static mate::Handle<WebView> Create(v8::Isolate* isolate);
static mate::Handle<WebFrame> Create(v8::Isolate* isolate);
private:
WebView();
virtual ~WebView();
WebFrame();
virtual ~WebFrame();
void SetName(const std::string& name);
double SetZoomLevel(double level);
double GetZoomLevel() const;
double SetZoomFactor(double factor);
double GetZoomFactor() const;
v8::Handle<v8::Value> RegisterEmbedderCustomElement(
const base::string16& name, v8::Handle<v8::Object> options);
// mate::Wrappable:
virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate);
blink::WebView* web_view_;
blink::WebLocalFrame* web_frame_;
DISALLOW_COPY_AND_ASSIGN(WebView);
DISALLOW_COPY_AND_ASSIGN(WebFrame);
};
} // namespace api
} // namespace atom
#endif // ATOM_RENDERER_API_ATOM_API_WEB_VIEW_H_
#endif // ATOM_RENDERER_API_ATOM_API_WEB_FRAME_H_

View file

@ -1,81 +0,0 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/renderer/api/atom_api_web_view.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "atom/common/node_includes.h"
namespace atom {
namespace api {
namespace {
blink::WebView* GetCurrentWebView() {
blink::WebLocalFrame* frame = blink::WebLocalFrame::frameForCurrentContext();
if (!frame)
return NULL;
return frame->view();
}
} // namespace
WebView::WebView() : web_view_(GetCurrentWebView()) {
}
WebView::~WebView() {
}
double WebView::SetZoomLevel(double level) {
return web_view_->setZoomLevel(level);
}
double WebView::GetZoomLevel() const {
return web_view_->zoomLevel();
}
double WebView::SetZoomFactor(double factor) {
return blink::WebView::zoomLevelToZoomFactor(SetZoomLevel(
blink::WebView::zoomFactorToZoomLevel(factor)));
}
double WebView::GetZoomFactor() const {
return blink::WebView::zoomLevelToZoomFactor(GetZoomLevel());
}
mate::ObjectTemplateBuilder WebView::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return mate::ObjectTemplateBuilder(isolate)
.SetMethod("setZoomLevel", &WebView::SetZoomLevel)
.SetMethod("getZoomLevel", &WebView::GetZoomLevel)
.SetMethod("setZoomFactor", &WebView::SetZoomFactor)
.SetMethod("getZoomFactor", &WebView::GetZoomFactor);
}
// static
mate::Handle<WebView> WebView::Create(v8::Isolate* isolate) {
return CreateHandle(isolate, new WebView);
}
} // namespace api
} // namespace atom
namespace {
void Initialize(v8::Handle<v8::Object> exports, v8::Handle<v8::Value> unused,
v8::Handle<v8::Context> context, void* priv) {
v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports);
dict.Set("webView", atom::api::WebView::Create(isolate));
}
} // namespace
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_renderer_web_view, Initialize)

View file

@ -1,4 +1,5 @@
EventEmitter = require('events').EventEmitter
process = global.process
ipc = process.atomBinding('ipc')
class Ipc extends EventEmitter

View file

@ -1,6 +1,7 @@
process = global.process
ipc = require 'ipc'
CallbacksRegistry = require 'callbacks-registry'
v8Util = process.atomBinding 'v8_util'
CallbacksRegistry = require 'callbacks-registry'
callbacksRegistry = new CallbacksRegistry
@ -110,7 +111,7 @@ exports.require = (module) ->
windowCache = null
exports.getCurrentWindow = ->
return windowCache if windowCache?
meta = ipc.sendChannelSync 'ATOM_BROWSER_CURRENT_WINDOW'
meta = ipc.sendChannelSync 'ATOM_BROWSER_CURRENT_WINDOW', process.guestInstanceId
windowCache = metaToValue meta
# Get a global object in browser.
@ -129,3 +130,8 @@ exports.createFunctionWithReturnValue = (returnValue) ->
func = -> returnValue
v8Util.setHiddenValue func, 'returnValue', true
func
# Get the guest WebContents from guestInstanceId.
exports.getGuestWebContents = (guestInstanceId) ->
meta = ipc.sendChannelSync 'ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId
metaToValue meta

View file

@ -0,0 +1 @@
module.exports = process.atomBinding('web_frame').webFrame

View file

@ -1 +0,0 @@
module.exports = process.atomBinding('web_view').webView

View file

@ -76,13 +76,6 @@ bool AtomRenderViewObserver::OnMessageReceived(const IPC::Message& message) {
void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel,
const base::ListValue& args) {
if (!render_view()->GetWebView())
return;
blink::WebFrame* frame = render_view()->GetWebView()->mainFrame();
if (!renderer_client_->IsNodeBindingEnabled(frame))
return;
renderer_client_->atom_bindings()->OnBrowserMessage(
render_view(), channel, args);
}

View file

@ -13,13 +13,15 @@
#include "atom/renderer/atom_render_view_observer.h"
#include "chrome/renderer/printing/print_web_view_helper.h"
#include "chrome/renderer/tts_dispatcher.h"
#include "content/public/common/content_constants.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "content/public/renderer/render_thread.h"
#include "base/command_line.h"
#include "native_mate/converter.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebCustomElement.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebPluginParams.h"
#include "third_party/WebKit/public/web/WebKit.h"
#include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
@ -29,13 +31,6 @@ namespace atom {
namespace {
// Security tokens.
const char* kSecurityAll = "all";
const char* kSecurityExceptIframe = "except-iframe";
const char* kSecurityManualEnableIframe = "manual-enable-iframe";
const char* kSecurityDisable = "disable";
const char* kSecurityEnableNodeIntegration = "enable-node-integration";
bool IsSwitchEnabled(base::CommandLine* command_line,
const char* switch_string,
bool* enabled) {
@ -73,24 +68,9 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
} // namespace
AtomRendererClient::AtomRendererClient()
: node_integration_(EXCEPT_IFRAME),
: node_bindings_(NodeBindings::Create(false)),
atom_bindings_(new AtomRendererBindings),
main_frame_(NULL) {
// Translate the token.
std::string token = CommandLine::ForCurrentProcess()->
GetSwitchValueASCII(switches::kNodeIntegration);
if (token == kSecurityExceptIframe)
node_integration_ = EXCEPT_IFRAME;
else if (token == kSecurityManualEnableIframe)
node_integration_ = MANUAL_ENABLE_IFRAME;
else if (token == kSecurityDisable)
node_integration_ = DISABLE;
else if (token == kSecurityAll)
node_integration_ = ALL;
if (IsNodeBindingEnabled()) {
node_bindings_.reset(NodeBindings::Create(false));
atom_bindings_.reset(new AtomRendererBindings);
}
}
AtomRendererClient::~AtomRendererClient() {
@ -99,8 +79,8 @@ AtomRendererClient::~AtomRendererClient() {
void AtomRendererClient::WebKitInitialized() {
EnableWebRuntimeFeatures();
if (!IsNodeBindingEnabled())
return;
blink::WebCustomElement::addEmbedderCustomElementName("webview");
blink::WebCustomElement::addEmbedderCustomElementName("browserplugin");
node_bindings_->Initialize();
node_bindings_->PrepareMessageLoop();
@ -134,6 +114,20 @@ blink::WebSpeechSynthesizer* AtomRendererClient::OverrideSpeechSynthesizer(
return new TtsDispatcher(client);
}
bool AtomRendererClient::OverrideCreatePlugin(
content::RenderFrame* render_frame,
blink::WebLocalFrame* frame,
const blink::WebPluginParams& params,
blink::WebPlugin** plugin) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (params.mimeType.utf8() == content::kBrowserPluginMimeType ||
command_line->HasSwitch(switches::kEnablePlugins))
return false;
*plugin = nullptr;
return true;
}
void AtomRendererClient::DidCreateScriptContext(blink::WebFrame* frame,
v8::Handle<v8::Context> context,
int extension_group,
@ -142,9 +136,6 @@ void AtomRendererClient::DidCreateScriptContext(blink::WebFrame* frame,
if (main_frame_ == NULL)
main_frame_ = frame;
if (!IsNodeBindingEnabled(frame))
return;
v8::Context::Scope scope(context);
// Check the existance of process object to prevent duplicate initialization.
@ -173,9 +164,6 @@ void AtomRendererClient::WillReleaseScriptContext(
blink::WebFrame* frame,
v8::Handle<v8::Context> context,
int world_id) {
if (!IsNodeBindingEnabled(frame))
return;
node::Environment* env = node::Environment::GetCurrent(context);
if (env == NULL) {
LOG(ERROR) << "Encounter a non-node context when releasing script context";
@ -210,6 +198,10 @@ bool AtomRendererClient::ShouldFork(blink::WebFrame* frame,
bool is_initial_navigation,
bool is_server_redirect,
bool* send_referrer) {
// Never fork renderer process for guests.
if (frame->uniqueName().utf8() == "ATOM_SHELL_GUEST_WEB_VIEW")
return false;
// Handle all the navigations and reloads in browser.
// FIXME We only support GET here because http method will be ignored when
// the OpenURLFromTab is triggered, which means form posting would not work,
@ -217,27 +209,6 @@ bool AtomRendererClient::ShouldFork(blink::WebFrame* frame,
return http_method == "GET";
}
bool AtomRendererClient::IsNodeBindingEnabled(blink::WebFrame* frame) {
if (node_integration_ == DISABLE)
return false;
// Node integration is enabled in main frame unless explictly disabled.
else if (frame == main_frame_)
return true;
// Enable node integration in chrome extensions.
else if (frame != NULL &&
GURL(frame->document().url()).SchemeIs("chrome-extension"))
return true;
else if (node_integration_ == MANUAL_ENABLE_IFRAME &&
frame != NULL &&
frame->uniqueName().utf8().find(kSecurityEnableNodeIntegration)
== std::string::npos)
return false;
else if (node_integration_ == EXCEPT_IFRAME && frame != NULL)
return false;
else
return true;
}
void AtomRendererClient::EnableWebRuntimeFeatures() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
bool b;

View file

@ -26,8 +26,6 @@ class AtomRendererClient : public content::ContentRendererClient,
AtomRendererClient();
virtual ~AtomRendererClient();
bool IsNodeBindingEnabled(blink::WebFrame* frame = NULL);
// Forwarded by RenderFrameObserver.
void WillReleaseScriptContext(blink::WebFrame* frame,
v8::Handle<v8::Context> context,
@ -47,21 +45,25 @@ class AtomRendererClient : public content::ContentRendererClient,
virtual void WebKitInitialized() OVERRIDE;
// content::ContentRendererClient:
virtual void RenderThreadStarted() OVERRIDE;
virtual void RenderFrameCreated(content::RenderFrame* render_frame) OVERRIDE;
virtual void RenderViewCreated(content::RenderView*) OVERRIDE;
virtual blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer(
blink::WebSpeechSynthesizerClient* client);
virtual void DidCreateScriptContext(blink::WebFrame* frame,
v8::Handle<v8::Context> context,
int extension_group,
int world_id) OVERRIDE;
virtual bool ShouldFork(blink::WebFrame* frame,
const GURL& url,
const std::string& http_method,
bool is_initial_navigation,
bool is_server_redirect,
bool* send_referrer) OVERRIDE;
void RenderThreadStarted() override;
void RenderFrameCreated(content::RenderFrame* render_frame) override;
void RenderViewCreated(content::RenderView*) override;
blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer(
blink::WebSpeechSynthesizerClient* client) override;
bool OverrideCreatePlugin(content::RenderFrame* render_frame,
blink::WebLocalFrame* frame,
const blink::WebPluginParams& params,
blink::WebPlugin** plugin) override;
void DidCreateScriptContext(blink::WebFrame* frame,
v8::Handle<v8::Context> context,
int extension_group,
int world_id) override;
bool ShouldFork(blink::WebFrame* frame,
const GURL& url,
const std::string& http_method,
bool is_initial_navigation,
bool is_server_redirect,
bool* send_referrer) override;
void EnableWebRuntimeFeatures();
@ -70,9 +72,6 @@ class AtomRendererClient : public content::ContentRendererClient,
scoped_ptr<NodeBindings> node_bindings_;
scoped_ptr<AtomRendererBindings> atom_bindings_;
// The level of node integration we should support.
NodeIntegration node_integration_;
// The main frame.
blink::WebFrame* main_frame_;

View file

@ -0,0 +1,48 @@
ipc = require 'ipc'
requestId = 0
WEB_VIEW_EVENTS =
'did-finish-load': []
'did-fail-load': ['errorCode', 'errorDescription']
'did-frame-finish-load': ['isMainFrame']
'did-start-loading': []
'did-stop-loading': []
'did-get-redirect-request': ['oldUrl', 'newUrl', 'isMainFrame']
'console-message': ['level', 'message', 'line', 'sourceId']
'new-window': ['url', 'partitionId']
'close': []
'crashed': []
'destroyed': []
dispatchEvent = (webView, event, args...) ->
throw new Error("Unkown event #{event}") unless WEB_VIEW_EVENTS[event]?
domEvent = new Event(event)
for f, i in WEB_VIEW_EVENTS[event]
domEvent[f] = args[i]
webView.dispatchEvent domEvent
module.exports =
registerEvents: (webView, viewInstanceId) ->
ipc.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}", (event, args...) ->
dispatchEvent webView, event, args...
ipc.on 'ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED', (args...) ->
domEvent = new Event('size-changed')
for f, i in ['oldWidth', 'oldHeight', 'newWidth', 'newHeight']
domEvent[f] = args[i]
webView.onSizeChanged domEvent
createGuest: (type, params, callback) ->
requestId++
ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', type, params, requestId
ipc.on "ATOM_SHELL_RESPONSE_#{requestId}", callback
destroyGuest: (guestInstanceId) ->
ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId
setAutoSize: (guestInstanceId, params) ->
ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_AUTO_SIZE', guestInstanceId, params
setAllowTransparency: (guestInstanceId, allowtransparency) ->
ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', guestInstanceId, allowtransparency

View file

@ -20,32 +20,16 @@ globalPaths.push path.join(process.resourcesPath, 'app')
# Import common settings.
require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init.js')
# Expose global variables.
global.require = require
global.module = module
# Emit the 'exit' event when page is unloading.
window.addEventListener 'unload', ->
process.emit 'exit'
# Set the __filename to the path of html file if it's file: or asar: protocol.
if window.location.protocol in ['file:', 'asar:']
pathname =
if process.platform is 'win32' and window.location.pathname[0] is '/'
window.location.pathname.substr 1
else
window.location.pathname
global.__filename = path.normalize decodeURIComponent(pathname)
global.__dirname = path.dirname global.__filename
# Set module's filename so relative require can work as expected.
module.filename = global.__filename
# Also search for module under the html file.
module.paths = module.paths.concat Module._nodeModulePaths(global.__dirname)
else
global.__filename = __filename
global.__dirname = __dirname
# Process command line arguments.
nodeIntegration = 'false'
for arg in process.argv
if arg.indexOf('--guest-instance-id=') == 0
# This is a guest web view.
process.guestInstanceId = parseInt arg.substr(arg.indexOf('=') + 1)
# Set the frame name to make AtomRendererClient recognize this guest.
require('web-frame').setName 'ATOM_SHELL_GUEST_WEB_VIEW'
else if arg.indexOf('--node-integration=') == 0
nodeIntegration = arg.substr arg.indexOf('=') + 1
if location.protocol is 'chrome-devtools:'
# Override some inspector APIs.
@ -56,3 +40,46 @@ else if location.protocol is 'chrome-extension:'
else
# Override default web functions.
require path.join(__dirname, 'override')
# Load webview tag implementation.
require path.join(__dirname, 'web-view') unless process.guestInstanceId?
if nodeIntegration in ['true', 'all', 'except-iframe', 'manual-enable-iframe']
# Export node bindings to global.
global.require = require
global.module = module
# Set the __filename to the path of html file if it's file: or asar: protocol.
if window.location.protocol in ['file:', 'asar:']
pathname =
if process.platform is 'win32' and window.location.pathname[0] is '/'
window.location.pathname.substr 1
else
window.location.pathname
global.__filename = path.normalize decodeURIComponent(pathname)
global.__dirname = path.dirname global.__filename
# Set module's filename so relative require can work as expected.
module.filename = global.__filename
# Also search for module under the html file.
module.paths = module.paths.concat Module._nodeModulePaths(global.__dirname)
else
global.__filename = __filename
global.__dirname = __dirname
# Redirect window.onerror to uncaughtException.
window.onerror = (error) ->
if global.process.listeners('uncaughtException').length > 0
global.process.emit 'uncaughtException', error
true
else
false
# Emit the 'exit' event when page is unloading.
window.addEventListener 'unload', ->
process.emit 'exit'
else
# There still some native initialization codes needs "process", delete the
# global reference after they are done.
process.once 'BIND_DONE', ->
delete global.process

View file

@ -1,50 +1,43 @@
# Redirect window.onerror to uncaughtException.
window.onerror = (error) ->
if global.process.listeners('uncaughtException').length > 0
global.process.emit 'uncaughtException', error
true
else
false
process = global.process
remote = require 'remote'
# Override default window.close, see:
# https://github.com/atom/atom-shell/issues/70
window.close = ->
require('remote').getCurrentWindow().close()
unless process.guestInstanceId?
# Override default window.close, see:
window.close = ->
remote.getCurrentWindow().close()
# Override default window.open.
window.open = (url, name, features) ->
options = {}
for feature in features.split ','
[name, value] = feature.split '='
options[name] =
if value is 'yes'
true
else if value is 'no'
false
else
value
# Override default window.open.
window.open = (url, name, features) ->
options = {}
for feature in features.split ','
[name, value] = feature.split '='
options[name] =
if value is 'yes'
true
else if value is 'no'
false
else
value
options.x ?= options.left
options.y ?= options.top
options.title ?= name
options.width ?= 800
options.height ?= 600
options.x ?= options.left
options.y ?= options.top
options.title ?= name
options.width ?= 800
options.height ?= 600
BrowserWindow = require('remote').require 'browser-window'
browser = new BrowserWindow options
browser.loadUrl url
browser
BrowserWindow = require('remote').require 'browser-window'
browser = new BrowserWindow options
browser.loadUrl url
browser
# Use the dialog API to implement alert().
window.alert = (message, title='') ->
remote = require 'remote'
dialog = remote.require 'dialog'
buttons = ['OK']
dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons}
# And the confirm().
window.confirm = (message, title='') ->
remote = require 'remote'
dialog = remote.require 'dialog'
buttons = ['OK', 'Cancel']
not dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons}

View file

@ -0,0 +1,547 @@
v8Util = process.atomBinding 'v8_util'
guestViewInternal = require './guest-view-internal'
webFrame = require 'web-frame'
remote = require 'remote'
# ID generator.
nextId = 0
getNextId = -> ++nextId
# FIXME
# Discarded after Chrome 39
PLUGIN_METHOD_ATTACH = '-internal-attach'
# Attributes.
WEB_VIEW_ATTRIBUTE_ALLOWTRANSPARENCY = 'allowtransparency'
WEB_VIEW_ATTRIBUTE_AUTOSIZE = 'autosize'
WEB_VIEW_ATTRIBUTE_MAXHEIGHT = 'maxheight'
WEB_VIEW_ATTRIBUTE_MAXWIDTH = 'maxwidth'
WEB_VIEW_ATTRIBUTE_MINHEIGHT = 'minheight'
WEB_VIEW_ATTRIBUTE_MINWIDTH = 'minwidth'
WEB_VIEW_ATTRIBUTE_PARTITION = 'partition'
WEB_VIEW_ATTRIBUTE_NODEINTEGRATION = 'nodeintegration'
AUTO_SIZE_ATTRIBUTES = [
WEB_VIEW_ATTRIBUTE_AUTOSIZE,
WEB_VIEW_ATTRIBUTE_MAXHEIGHT,
WEB_VIEW_ATTRIBUTE_MAXWIDTH,
WEB_VIEW_ATTRIBUTE_MINHEIGHT,
WEB_VIEW_ATTRIBUTE_MINWIDTH,
]
# Error messages.
ERROR_MSG_ALREADY_NAVIGATED =
'The object has already navigated, so its partition cannot be changed.'
ERROR_MSG_CANNOT_INJECT_SCRIPT = '<webview>: ' +
'Script cannot be injected into content until the page has loaded.'
ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE = '<webview>: ' +
'contentWindow is not available at this time. It will become available ' +
'when the page has finished loading.'
ERROR_MSG_INVALID_PARTITION_ATTRIBUTE = 'Invalid partition attribute.'
# Represents the state of the storage partition.
class Partition
constructor: ->
@validPartitionId = true
@persistStorage = false
@storagePartitionId = ''
toAttribute: ->
return '' unless @validPartitionId
(if @persistStorage then 'persist:' else '') + @storagePartitionId
fromAttribute: (value, hasNavigated) ->
result = {}
if hasNavigated
result.error = ERROR_MSG_ALREADY_NAVIGATED
return result
value ?= ''
LEN = 'persist:'.length
if value.substr(0, LEN) == 'persist:'
value = value.substr LEN
unless value
@validPartitionId = false
result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE
return result
@persistStorage = true
else
@persistStorage = false
@storagePartitionId = value
result
# Represents the internal state of the WebView node.
class WebView
constructor: (@webviewNode) ->
v8Util.setHiddenValue @webviewNode, 'internal', this
@attached = false
@pendingGuestCreation = false
@elementAttached = false
@beforeFirstNavigation = true
@contentWindow = null
@validPartitionId = true
# Used to save some state upon deferred attachment.
# If <object> bindings is not available, we defer attachment.
# This state contains whether or not the attachment request was for
# newwindow.
@deferredAttachState = null
# on* Event handlers.
@on = {}
@browserPluginNode = @createBrowserPluginNode()
shadowRoot = @webviewNode.createShadowRoot()
@partition = new Partition()
@setupWebViewSrcAttributeMutationObserver()
@setupFocusPropagation()
@setupWebviewNodeProperties()
@viewInstanceId = getNextId()
guestViewInternal.registerEvents this, @viewInstanceId
shadowRoot.appendChild @browserPluginNode
createBrowserPluginNode: ->
# We create BrowserPlugin as a custom element in order to observe changes
# to attributes synchronously.
browserPluginNode = new WebView.BrowserPlugin()
v8Util.setHiddenValue browserPluginNode, 'internal', this
browserPluginNode
getGuestInstanceId: ->
@guestInstanceId
# Resets some state upon reattaching <webview> element to the DOM.
reset: ->
# If guestInstanceId is defined then the <webview> has navigated and has
# already picked up a partition ID. Thus, we need to reset the initialization
# state. However, it may be the case that beforeFirstNavigation is false BUT
# guestInstanceId has yet to be initialized. This means that we have not
# heard back from createGuest yet. We will not reset the flag in this case so
# that we don't end up allocating a second guest.
if @guestInstanceId
guestViewInternal.destroyGuest @guestInstanceId
@guestInstanceId = undefined
@beforeFirstNavigation = true
@validPartitionId = true
@partition.validPartitionId = true
@contentWindow = null
@internalInstanceId = 0
# Sets the <webview>.request property.
setRequestPropertyOnWebViewNode: (request) ->
Object.defineProperty @webviewNode, 'request', value: request, enumerable: true
setupFocusPropagation: ->
unless @webviewNode.hasAttribute 'tabIndex'
# <webview> needs a tabIndex in order to be focusable.
# TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
# to allow <webview> to be focusable.
# See http://crbug.com/231664.
@webviewNode.setAttribute 'tabIndex', -1
@webviewNode.addEventListener 'focus', (e) =>
# Focus the BrowserPlugin when the <webview> takes focus.
@browserPluginNode.focus()
@webviewNode.addEventListener 'blur', (e) =>
# Blur the BrowserPlugin when the <webview> loses focus.
@browserPluginNode.blur()
# Validation helper function for executeScript() and insertCSS().
validateExecuteCodeCall: ->
throw new Error(ERROR_MSG_CANNOT_INJECT_SCRIPT) unless @guestInstanceId
setupAutoSizeProperties: ->
for attributeName in AUTO_SIZE_ATTRIBUTES
this[attributeName] = @webviewNode.getAttribute attributeName
Object.defineProperty @webviewNode, attributeName,
get: => this[attributeName]
set: (value) => @webviewNode.setAttribute attributeName, value
enumerable: true
setupWebviewNodeProperties: ->
@setupAutoSizeProperties()
Object.defineProperty @webviewNode, WEB_VIEW_ATTRIBUTE_ALLOWTRANSPARENCY,
get: => @allowtransparency
set: (value) =>
@webviewNode.setAttribute WEB_VIEW_ATTRIBUTE_ALLOWTRANSPARENCY, value
enumerable: true
# We cannot use {writable: true} property descriptor because we want a
# dynamic getter value.
Object.defineProperty @webviewNode, 'contentWindow',
get: =>
return @contentWindow if @contentWindow?
window.console.error ERROR_MSG_CONTENTWINDOW_NOT_AVAILABLE
# No setter.
enumerable: true
Object.defineProperty @webviewNode, 'partition',
get: => @partition.toAttribute()
set: (value) =>
result = @partition.fromAttribute value, @hasNavigated()
throw result.error if result.error?
@webviewNode.setAttribute 'partition', value
enumerable: true
@src = @webviewNode.getAttribute 'src'
Object.defineProperty @webviewNode, 'src',
get: => @src
set: (value) => @webviewNode.setAttribute 'src', value
# No setter.
enumerable: true
# The purpose of this mutation observer is to catch assignment to the src
# attribute without any changes to its value. This is useful in the case
# where the webview guest has crashed and navigating to the same address
# spawns off a new process.
setupWebViewSrcAttributeMutationObserver: ->
@srcAndPartitionObserver = new MutationObserver (mutations) =>
for mutation in mutations
oldValue = mutation.oldValue
newValue = @webviewNode.getAttribute mutation.attributeName
return if oldValue isnt newValue
@handleWebviewAttributeMutation mutation.attributeName, oldValue, newValue
params =
attributes: true,
attributeOldValue: true,
attributeFilter: ['src', 'partition']
@srcAndPartitionObserver.observe @webviewNode, params
# This observer monitors mutations to attributes of the <webview> and
# updates the BrowserPlugin properties accordingly. In turn, updating
# a BrowserPlugin property will update the corresponding BrowserPlugin
# attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
# details.
handleWebviewAttributeMutation: (name, oldValue, newValue) ->
if name in AUTO_SIZE_ATTRIBUTES
this[name] = newValue
return unless @guestInstanceId
# Convert autosize attribute to boolean.
autosize = @webviewNode.hasAttribute WEB_VIEW_ATTRIBUTE_AUTOSIZE
guestViewInternal.setAutoSize @guestInstanceId,
enableAutoSize: autosize,
min:
width: parseInt @minwidth || 0
height: parseInt @minheight || 0
max:
width: parseInt @maxwidth || 0
height: parseInt @maxheight || 0
else if name is WEB_VIEW_ATTRIBUTE_ALLOWTRANSPARENCY
# We treat null attribute (attribute removed) and the empty string as
# one case.
oldValue ?= ''
newValue ?= ''
return if oldValue is newValue
@allowtransparency = newValue != ''
return unless @guestInstanceId
guestViewInternal.setAllowTransparency @guestInstanceId, @allowtransparency
else if name is 'src'
# We treat null attribute (attribute removed) and the empty string as
# one case.
oldValue ?= ''
newValue ?= ''
# Once we have navigated, we don't allow clearing the src attribute.
# Once <webview> enters a navigated state, it cannot be return back to a
# placeholder state.
if newValue == '' and oldValue != ''
# src attribute changes normally initiate a navigation. We suppress
# the next src attribute handler call to avoid reloading the page
# on every guest-initiated navigation.
@ignoreNextSrcAttributeChange = true
@webviewNode.setAttribute 'src', oldValue
@src = newValue
if @ignoreNextSrcAttributeChange
# Don't allow the src mutation observer to see this change.
@srcAndPartitionObserver.takeRecords()
@ignoreNextSrcAttributeChange = false
return
result = {}
@parseSrcAttribute result
throw result.error if result.error?
else if name is 'partition'
# Note that throwing error here won't synchronously propagate.
@partition.fromAttribute newValue, @hasNavigated()
handleBrowserPluginAttributeMutation: (name, oldValue, newValue) ->
# FIXME
# internalbindings => internalInstanceid after Chrome 39
if name is 'internalbindings' and !oldValue and !!newValue
@browserPluginNode.removeAttribute 'internalbindings'
# FIXME
# @internalInstanceId = parseInt newValue
if !!@guestInstanceId and @guestInstanceId != 0
isNewWindow = if @deferredAttachState then @deferredAttachState.isNewWindow else false
params = @buildAttachParams isNewWindow
# FIXME
# guestViewInternalNatives.AttachGuest
# @internalInstanceId,
# @guestInstanceId,
# params,
# (w) => @contentWindow = w
@browserPluginNode[PLUGIN_METHOD_ATTACH] @guestInstanceId, params
onSizeChanged: (webViewEvent) ->
newWidth = webViewEvent.newWidth
newHeight = webViewEvent.newHeight
node = @webviewNode
width = node.offsetWidth
height = node.offsetHeight
# Check the current bounds to make sure we do not resize <webview>
# outside of current constraints.
if node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXWIDTH) and
node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]
maxWidth = node[WEB_VIEW_ATTRIBUTE_MAXWIDTH]
else
maxWidth = width
if node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINWIDTH) and
node[WEB_VIEW_ATTRIBUTE_MINWIDTH]
minWidth = node[WEB_VIEW_ATTRIBUTE_MINWIDTH]
else
minWidth = width
minWidth = maxWidth if minWidth > maxWidth
if node.hasAttribute(WEB_VIEW_ATTRIBUTE_MAXHEIGHT) and
node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT]
maxHeight = node[WEB_VIEW_ATTRIBUTE_MAXHEIGHT]
else
maxHeight = height
if node.hasAttribute(WEB_VIEW_ATTRIBUTE_MINHEIGHT) and
node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]
minHeight = node[WEB_VIEW_ATTRIBUTE_MINHEIGHT]
else
minHeight = height
minHeight = maxHeight if minHeight > maxHeight
if not @webviewNode.hasAttribute WEB_VIEW_ATTRIBUTE_AUTOSIZE or
(newWidth >= minWidth and
newWidth <= maxWidth and
newHeight >= minHeight and
newHeight <= maxHeight)
node.style.width = newWidth + 'px'
node.style.height = newHeight + 'px'
# Only fire the DOM event if the size of the <webview> has actually
# changed.
@dispatchEvent webViewEvent
# Returns if <object> is in the render tree.
isPluginInRenderTree: ->
# FIXME
# !!@internalInstanceId && @internalInstanceId != 0
'function' == typeof this.browserPluginNode[PLUGIN_METHOD_ATTACH]
hasNavigated: ->
not @beforeFirstNavigation
parseSrcAttribute: (result) ->
unless @partition.validPartitionId
result.error = ERROR_MSG_INVALID_PARTITION_ATTRIBUTE
return
@src = @webviewNode.getAttribute 'src'
return unless @src
unless @guestInstanceId?
if @beforeFirstNavigation
@beforeFirstNavigation = false
@createGuest()
return
# Navigate to |this.src|.
remote.getGuestWebContents(@guestInstanceId).loadUrl @src
parseAttributes: ->
return unless @elementAttached
hasNavigated = @hasNavigated()
attributeValue = @webviewNode.getAttribute 'partition'
result = @partition.fromAttribute attributeValue, hasNavigated
@parseSrcAttribute result
createGuest: ->
return if @pendingGuestCreation
storagePartitionId =
@webviewNode.getAttribute(WEB_VIEW_ATTRIBUTE_PARTITION) or
@webviewNode[WEB_VIEW_ATTRIBUTE_PARTITION]
params =
storagePartitionId: storagePartitionId
nodeIntegration: @webviewNode.hasAttribute WEB_VIEW_ATTRIBUTE_NODEINTEGRATION
guestViewInternal.createGuest 'webview', params, (guestInstanceId) =>
@pendingGuestCreation = false
unless @elementAttached
guestViewInternal.destroyGuest guestInstanceId
return
@attachWindow guestInstanceId, false
@pendingGuestCreation = true
dispatchEvent: (webViewEvent) ->
@webviewNode.dispatchEvent webViewEvent
# Adds an 'on<event>' property on the webview, which can be used to set/unset
# an event handler.
setupEventProperty: (eventName) ->
propertyName = 'on' + eventName.toLowerCase()
Object.defineProperty @webviewNode, propertyName,
get: => @on[propertyName]
set: (value) =>
if @on[propertyName]
@webviewNode.removeEventListener eventName, @on[propertyName]
@on[propertyName] = value
if value
@webviewNode.addEventListener eventName, value
enumerable: true
# Updates state upon loadcommit.
onLoadCommit: (@baseUrlForDataUrl, @currentEntryIndex, @entryCount, @processId, url, isTopLevel) ->
oldValue = @webviewNode.getAttribute 'src'
newValue = url
if isTopLevel and (oldValue != newValue)
# Touching the src attribute triggers a navigation. To avoid
# triggering a page reload on every guest-initiated navigation,
# we use the flag ignoreNextSrcAttributeChange here.
this.ignoreNextSrcAttributeChange = true
this.webviewNode.setAttribute 'src', newValue
onAttach: (storagePartitionId) ->
@webviewNode.setAttribute 'partition', storagePartitionId
@partition.fromAttribute storagePartitionId, this.hasNavigated()
buildAttachParams: (isNewWindow) ->
allowtransparency: @allowtransparency || false
autosize: @webviewNode.hasAttribute WEB_VIEW_ATTRIBUTE_AUTOSIZE
instanceId: @viewInstanceId
maxheight: parseInt @maxheight || 0
maxwidth: parseInt @maxwidth || 0
minheight: parseInt @minheight || 0
minwidth: parseInt @minwidth || 0
# We don't need to navigate new window from here.
src: if isNewWindow then undefined else @src
# If we have a partition from the opener, that will also be already
# set via this.onAttach().
storagePartitionId: @partition.toAttribute()
userAgentOverride: @userAgentOverride
attachWindow: (guestInstanceId, isNewWindow) ->
@guestInstanceId = guestInstanceId
params = @buildAttachParams isNewWindow
unless @isPluginInRenderTree()
@deferredAttachState = isNewWindow: isNewWindow
return true
@deferredAttachState = null
# FIXME
# guestViewInternalNatives.AttachGuest @internalInstanceId, @guestInstanceId, params, (w) => @contentWindow = w
@browserPluginNode[PLUGIN_METHOD_ATTACH] @guestInstanceId, params
# Registers browser plugin <object> custom element.
registerBrowserPluginElement = ->
proto = Object.create HTMLObjectElement.prototype
proto.createdCallback = ->
@setAttribute 'type', 'application/browser-plugin'
@setAttribute 'id', 'browser-plugin-' + getNextId()
# The <object> node fills in the <webview> container.
@style.width = '100%'
@style.height = '100%'
proto.attributeChangedCallback = (name, oldValue, newValue) ->
internal = v8Util.getHiddenValue this, 'internal'
return unless internal
internal.handleBrowserPluginAttributeMutation name, oldValue, newValue
proto.attachedCallback = ->
# Load the plugin immediately.
unused = this.nonExistentAttribute
WebView.BrowserPlugin = webFrame.registerEmbedderCustomElement 'browserplugin',
extends: 'object', prototype: proto
delete proto.createdCallback
delete proto.attachedCallback
delete proto.detachedCallback
delete proto.attributeChangedCallback
# Registers <webview> custom element.
registerWebViewElement = ->
proto = Object.create HTMLObjectElement.prototype
proto.createdCallback = ->
new WebView(this)
proto.attributeChangedCallback = (name, oldValue, newValue) ->
internal = v8Util.getHiddenValue this, 'internal'
return unless internal
internal.handleWebviewAttributeMutation name, oldValue, newValue
proto.detachedCallback = ->
internal = v8Util.getHiddenValue this, 'internal'
return unless internal
internal.elementAttached = false
internal.reset()
proto.attachedCallback = ->
internal = v8Util.getHiddenValue this, 'internal'
return unless internal
unless internal.elementAttached
internal.elementAttached = true
internal.parseAttributes()
# Public-facing API methods.
methods = [
"getUrl"
"getTitle"
"isLoading"
"isWaitingForResponse"
"stop"
"reload"
"reloadIngoringCache"
"canGoBack"
"canGoForward"
"canGoToOffset"
"goBack"
"goForward"
"goToIndex"
"goToOffset"
"isCrashed"
"setUserAgent"
"executeJavaScript"
"insertCSS"
"send"
]
# Forward proto.foo* method calls to WebView.foo*.
createHandler = (m) ->
(args...) ->
internal = v8Util.getHiddenValue this, 'internal'
remote.getGuestWebContents(internal.guestInstanceId)[m] args...
proto[m] = createHandler m for m in methods
window.WebView = webFrame.registerEmbedderCustomElement 'webview',
prototype: proto
# Delete the callbacks so developers cannot call them and produce unexpected
# behavior.
delete proto.createdCallback
delete proto.attachedCallback
delete proto.detachedCallback
delete proto.attributeChangedCallback
useCapture = true
listener = (event) ->
return if document.readyState == 'loading'
registerBrowserPluginElement()
registerWebViewElement()
window.removeEventListener event.type, listener, useCapture
window.addEventListener 'readystatechange', listener, true

View file

@ -14,6 +14,11 @@
* [Process object](api/process.md)
* [Supported Chrome command line switches](api/chrome-command-line-switches.md)
Custom DOM elements:
* [`File` object](app/file-object.md)
* [`<webview>` tag](app/web-view-tag.md)
Modules for browser side:
* [app](api/app.md)
@ -33,7 +38,7 @@ Modules for web page:
* [ipc (renderer)](api/ipc-renderer.md)
* [remote](api/remote.md)
* [web-view](api/web-view.md)
* [web-frame](api/web-frame.md)
Modules for both sides:
@ -47,8 +52,6 @@ Modules for both sides:
* [Coding style](development/coding-style.md)
* [Source code directory structure](development/source-code-directory-structure.md)
* [Technical differences to node-webkit](development/atom-shell-vs-node-webkit.md)
* [How node.js is integrated into atom-shell](https://speakerdeck.com/zcbenz/practice-on-embedding-node-dot-js-into-atom-editor) (slides) ([中文版](http://2014.jsconf.cn/slides/Practice%20on%20embedding%20Node.js%20into%20Atom%20Editor.pdf
))
* [Build instructions (Mac)](development/build-instructions-mac.md)
* [Build instructions (Windows)](development/build-instructions-windows.md)
* [Build instructions (Linux)](development/build-instructions-linux.md)

View file

@ -18,9 +18,6 @@ win.show();
You can also create a window without chrome by using
[Frameless Window](frameless-window.md) API.
Security strategy of web pages showed by `BrowserWindow` is a bit different from
normal browsers, see [Web Security](web-security.md) for more.
## Class: BrowserWindow
`BrowserWindow` is an
@ -54,9 +51,8 @@ normal browsers, see [Web Security](web-security.md) for more.
* `show` Boolean - Whether window should be shown when created
* `frame` Boolean - Specify `false` to create a
[Frameless Window](frameless-window.md)
* `node-integration` String - Default value is `except-iframe`, can also be
`all`, `manual-enable-iframe` or `disable`, see
[Web Security](web-security.md) for more informations.
* `node-integration` Boolean - Whether node integration is enabled, default
is `true`
* `accept-first-mouse` Boolean - Whether the web view accepts a single
mouse-down event that simultaneously activates the window
* `auto-hide-menu-bar` Boolean - Auto hide the menu bar unless the `Alt`
@ -512,14 +508,19 @@ A `WebContents` is responsible for rendering and controlling a web page.
`WebContents` is an
[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter).
### Event: 'crashed'
Emitted when the renderer process is crashed.
### Event: 'did-finish-load'
Emitted when the navigation is done, i.e. the spinner of the tab will stop
spinning, and the onload event was dispatched.
spinning, and the `onload` event was dispatched.
### Event: 'did-fail-load'
* `event` Event
* `errorCode` Integer
* `errorDescription` String
This event is like `did-finish-load`, but emitted when the load failed or was
cancelled, e.g. `window.stop()` is invoked.
### Event: 'did-frame-finish-load'
@ -530,8 +531,29 @@ Emitted when a frame has done navigation.
### Event: 'did-start-loading'
Corresponds to the points in time when the spinner of the tab starts spinning.
### Event: 'did-stop-loading'
Corresponds to the points in time when the spinner of the tab stops spinning.
### Event: 'did-get-redirect-request'
* `event` Event
* `oldUrl` String
* `newUrl` String
* `isMainFrame` Boolean
Emitted when a redirect was received while requesting a resource.
### Event: 'crashed'
Emitted when the renderer process is crashed.
### Event: 'destroyed'
Emitted when the WebContents is destroyed.
### WebContents.loadUrl(url)
* `url` URL
@ -602,10 +624,22 @@ Navigates to the specified absolute index.
Navigates to the specified offset from the "current entry".
### WebContents.IsCrashed()
### WebContents.isCrashed()
Whether the renderer process has crashed.
### WebContents.setUserAgent(userAgent)
* `userAgent` String
Overrides the user agent for this page.
### WebContents.insertCSS(css)
* `css` String
Injects CSS into this page.
### WebContents.executeJavaScript(code)
* `code` String

30
docs/api/file-object.md Normal file
View file

@ -0,0 +1,30 @@
# `File` object
The DOM's File interface provides abstraction around native files, in order to
let users work on native files directly with HTML5 file API, atom-shell has
added a `path` attribute to `File` interface which exposes the file's real path
on filesystem.
Example on getting real path of a dragged file:
```html
<div id="holder">
Drag your file here
</div>
<script>
var holder = document.getElementById('holder');
holder.ondragover = function () {
return false;
};
holder.ondragleave = holder.ondragend = function () {
return false;
};
holder.ondrop = function (e) {
e.preventDefault();
var file = e.dataTransfer.files[0];
console.log('File you dragged here is', file.path);
return false;
};
</script>
```

View file

@ -1,26 +1,26 @@
# web-view
# web-frame
The `web-view` module can custom the rendering of current web page.
The `web-frame` module can custom the rendering of current web page.
An example of zooming current page to 200%.
```javascript
var webView = require('web-view');
webView.setZoomFactor(2);
var webFrame = require('web-frame');
webFrame.setZoomFactor(2);
```
## webView.setZoomFactor(factor)
## webFrame.setZoomFactor(factor)
* `factor` Number - Zoom factor
Changes the zoom factor to the specified factor, zoom factor is
zoom percent / 100, so 300% = 3.0.
## webView.getZoomFactor()
## webFrame.getZoomFactor()
Returns the current zoom factor.
## webView.setZoomLevel(level)
## webFrame.setZoomLevel(level)
* `level` Number - Zoom level
@ -28,6 +28,6 @@ Changes the zoom level to the specified level, 0 is "original size", and each
increment above or below represents zooming 20% larger or smaller to default
limits of 300% and 50% of original size, respectively.
## webView.getZoomLevel()
## webFrame.getZoomLevel()
Returns the current zoom level.

View file

@ -1,62 +0,0 @@
# Web Security
Because atom-shell has added node integration to normal web pages, there are
some security adjustments that made atom-shell both more safe and more
convenient.
## Overriding `X-Frame-Options` header
May websites (including Google and Youtube) use the
[X-Frame-Options][x-frame-options] header to disable access to their websites
in `iframe`s. In atom-shell you can add a `disable-x-frame-options` string in
the `iframe`'s name to disable this:
```html
<!-- Refused to display -->
<iframe name="google" src="https://google.com"></iframe>
<!-- Loads as expected -->
<iframe name="google-disable-x-frame-options" src="https://google.com"></iframe>
```
## Frames are sandboxed by default
In normal browsers, `iframe`s are not sandboxed by default, which means a remote
page in `iframe` can easily access its parent's JavaScript context.
In atom-shell because the parent frame may have the power to access native
resources, this could cause security problems. In order to fix it, `iframe`s
in atom-shell are sandboxed with all permissions except the `allow-same-origin`
by default.
If you want to enable things like `parent.window.process.exit()` in `iframe`s,
you need to explicitly add `allow-same-origin` to the `sandbox` attribute, or
just set `sandbox` to `none`:
```html
<iframe sandbox="none" src="https://github.com"></iframe>
```
## Node integration in frames
The `node-integration` option of [BrowserWindow](browser-window.md) controls
whether node integration is enabled in web page and its `iframe`s.
By default the `node-integration` option is `except-iframe`, which means node
integration is disabled in all `iframe`s. You can also set it to `all`, with
which node integration is available to the main page and all its `iframe`s, or
`manual-enable-iframe`, which is like `except-iframe`, but enables `iframe`s
whose name contains string `enable-node-integration`. And setting to `disable`
would disable the node integration in both the main page and its `iframe`s.
An example of enable node integration in `iframe` with `node-integration` set to
`manual-enable-iframe`:
```html
<!-- iframe with node integration enabled -->
<iframe name="gh-enable-node-integration" src="https://github.com"></iframe>
<!-- iframe with node integration disabled -->
<iframe src="http://jandan.net"></iframe>
```
[x-frame-options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options

271
docs/api/web-view-tag.md Normal file
View file

@ -0,0 +1,271 @@
# `<webview>` tag
Use the `webview` tag to embed 'guest' content (such as web pages) in your
atom-shell app. The guest content is contained within the `webview` container;
an embedder page within your app controls how the guest content is laid out and
rendered.
Different from the `iframe`, the `webview` runs in a separate process than your
app; it doesn't have the same permissions as your web page and all interactions
between your app and embedded content will be asynchronous. This keeps your app
safe from the embedded content.
## Example
To embed a web page in your app, add the `webview` tag to your app's embedder
page (this is the app page that will display the guest content). In its simplest
form, the `webview` tag includes the `src` of the web page and css styles that
control the appearance of the `webview` container:
```html
<webview id="foo" src="https://www.github.com/" style="width:640px; height:480px"></webview>
```
If you want to control the guest content in any way, you can write JavaScript
that listens for `webview` events and responds to those events using the
`webview` methods. Here's sample code with two event listeners: one that listens
for the web page to start loading, the other for the web page to stop loading,
and displays a "loading..." message during the load time:
```html
<script>
onload = function() {
var webview = document.getElementById("foo");
var indicator = document.querySelector(".indicator");
var loadstart = function() {
indicator.innerText = "loading...";
}
var loadstop = function() {
indicator.innerText = "";
}
webview.addEventListener("did-start-loading", loadstart);
webview.addEventListener("id-stop-loading", loadstop);
}
</script>
```
## Tag attributes
### src
```html
<webview src="http://www.google.com/"></webview>
```
Returns the visible URL. Writing to this attribute initiates top-level
navigation.
Assigning `src` its own value will reload the current page.
The `src` attribute can also accept data URLs, such as
`data:text/plain,Hello, world!`.
### autosize
```html
<webview src="http://www.google.com/" autosize="on" minwidth="576" minheight="432"></webview>
```
If "on", the `webview` will container will automatically resize within the
bounds specified by the attributes `minwidth`, `minheight`, `maxwidth`, and
`maxheight`. These contraints do not impact the `webview` UNLESS `autosize` is
enabled. When `autosize` is enabled, the `webview` container size cannot be less
than the minimum values or greater than the maximum.
### nodeintegration
```html
<webview src="http://www.google.com/" nodeintegration></webview>
```
If "on", the guest page in `webview` will have node integration and can use node
APIs like `require` and `process` to access low level system resources.
## Methods
### `<webview>`.getUrl()
Returns URL of guest page.
### `<webview>`.getTitle()
Returns the title of guest page.
### `<webview>`.isLoading()
Returns whether guest page is still loading resources.
### `<webview>`.isWaitingForResponse()
Returns whether guest page is waiting for a first-response for the main resource
of the page.
### `<webview>`.stop()
Stops any pending navigation.
### `<webview>`.reload()
Reloads guest page.
### `<webview>`.reloadIgnoringCache()
Reloads guest page and ignores cache.
### `<webview>`.canGoBack()
Returns whether guest page can go back.
### `<webview>`.canGoForward()
Returns whether guest page can go forward.
### `<webview>`.canGoToOffset(offset)
* `offset` Integer
Returns whether guest page can go to `offset`.
### `<webview>`.goBack()
Makes guest page go back.
### `<webview>`.goForward()
Makes guest page go forward.
### `<webview>`.goToIndex(index)
* `index` Integer
Navigates to the specified absolute index.
### `<webview>`.goToOffset(offset)
* `offset` Integer
Navigates to the specified offset from the "current entry".
### `<webview>`.isCrashed()
Whether the renderer process has crashed.
### `<webview>`.setUserAgent(userAgent)
* `userAgent` String
Overrides the user agent for guest page.
### `<webview>`.insertCSS(css)
* `css` String
Injects CSS into guest page.
### `<webview>`.executeJavaScript(code)
* `code` String
Evaluate `code` in guest page.
### `<webview>`.send(channel[, args...])
* `channel` String
Send `args..` to guest page via `channel` in asynchronous message, the guest
page can handle it by listening to the `channel` event of `ipc` module.
See [WebContents.send](browser-window.md#webcontentssendchannel-args) for
examples.
## DOM events
### did-finish-load
Fired when the navigation is done, i.e. the spinner of the tab will stop
spinning, and the `onload` event was dispatched.
### did-fail-load
* `errorCode` Integer
* `errorDescription` String
This event is like `did-finish-load`, but fired when the load failed or was
cancelled, e.g. `window.stop()` is invoked.
### did-frame-finish-load
* `isMainFrame` Boolean
Fired when a frame has done navigation.
### did-start-loading
Corresponds to the points in time when the spinner of the tab starts spinning.
### did-stop-loading
Corresponds to the points in time when the spinner of the tab stops spinning.
### did-get-redirect-request
* `oldUrl` String
* `newUrl` String
* `isMainFrame` Boolean
Fired when a redirect was received while requesting a resource.
### console-message
* `level` Integer
* `message` String
* `line` Integer
* `sourceId` String
Fired when the guest window logs a console message.
The following example code forwards all log messages to the embedder's console
without regard for log level or other properties.
```javascript
webview.addEventListener('console-message', function(e) {
console.log('Guest page logged a message: ', e.message);
});
```
### new-window
* `url` String
* `partitionId` String
Fired when the guest page attempts to open a new browser window.
The following example code opens the new url in system's default browser.
```javascript
webview.addEventListener('new-window', function(e) {
require('shell').openExternal(e.url);
});
```
### close
Fired when the guest window attempts to close itself.
The following example code navigates the `webview` to `about:blank` when the
guest attempts to close itself.
```javascript
webview.addEventListener('close', function() {
webview.src = 'about:blank';
});
```
### crashed
Fired when the renderer process is crashed.
### destroyed
Fired when the WebContents is destroyed.

View file

@ -4,7 +4,7 @@ import platform
import sys
BASE_URL = 'https://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent'
LIBCHROMIUMCONTENT_COMMIT = '56984fa0e4c3c745652510f342c0fb2724d846c2'
LIBCHROMIUMCONTENT_COMMIT = '2dfdf169b582e3f051e1fec3dd7df2bc179e1aa6'
ARCH = {
'cygwin': '32bit',

View file

@ -387,7 +387,6 @@ describe 'asar package', ->
w = new BrowserWindow(show: false, width: 400, height: 400)
p = path.resolve fixtures, 'asar', 'web.asar', 'index.html'
u = url.format protocol: 'asar', slashed: false, pathname: p
console.log u
w.loadUrl u
ipc.on 'dirname', (event, dirname) ->
assert.equal dirname, path.dirname(p)

View file

@ -37,34 +37,6 @@ describe 'chromium feature', ->
assert.equal b.constructor.name, 'BrowserWindow'
b.destroy()
describe 'iframe', ->
page = path.join fixtures, 'pages', 'change-parent.html'
beforeEach ->
global.changedByIframe = false
it 'can not modify parent by default', (done) ->
iframe = $('<iframe>')
iframe.hide()
iframe.attr 'src', "file://#{page}"
iframe.appendTo 'body'
isChanged = ->
iframe.remove()
assert.equal global.changedByIframe, false
done()
setTimeout isChanged, 30
it 'can modify parent when sanbox is set to none', (done) ->
iframe = $('<iframe sandbox="none">')
iframe.hide()
iframe.attr 'src', "file://#{page}"
iframe.appendTo 'body'
isChanged = ->
iframe.remove()
assert.equal global.changedByIframe, true
done()
setTimeout isChanged, 30
describe 'creating a Uint8Array under browser side', ->
it 'does not crash', ->
RUint8Array = require('remote').getGlobal 'Uint8Array'

View file

@ -1,7 +1,7 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
window.parent.changedByIframe = true;
console.log('a');
</script>
</body>
</html>

8
spec/fixtures/pages/b.html vendored Normal file
View file

@ -0,0 +1,8 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
console.log('b');
</script>
</body>
</html>

7
spec/fixtures/pages/c.html vendored Normal file
View file

@ -0,0 +1,7 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
console.log([typeof require, typeof module, typeof process].join(' '));
</script>
</body>
</html>

7
spec/fixtures/pages/d.html vendored Normal file
View file

@ -0,0 +1,7 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
console.log([typeof require, typeof module, typeof process].join(' '));
</script>
</body>
</html>

48
spec/webview-spec.coffee Normal file
View file

@ -0,0 +1,48 @@
assert = require 'assert'
path = require 'path'
describe '<webview> tag', ->
fixtures = path.join __dirname, 'fixtures'
webview = null
beforeEach ->
webview = new WebView
afterEach ->
document.body.removeChild webview
describe 'src attribute', ->
it 'specifies the page to load', (done) ->
webview.addEventListener 'console-message', (e) ->
assert.equal e.message, 'a'
done()
webview.src = "file://#{fixtures}/pages/a.html"
document.body.appendChild webview
it 'navigates to new page when changed', (done) ->
listener = (e) ->
webview.src = "file://#{fixtures}/pages/b.html"
webview.addEventListener 'console-message', (e) ->
assert.equal e.message, 'b'
done()
webview.removeEventListener 'did-finish-load', listener
webview.addEventListener 'did-finish-load', listener
webview.src = "file://#{fixtures}/pages/a.html"
document.body.appendChild webview
describe 'nodeintegration attribute', ->
it 'inserts no node symbols when not set', (done) ->
webview.addEventListener 'console-message', (e) ->
assert.equal e.message, 'undefined undefined undefined'
done()
webview.src = "file://#{fixtures}/pages/c.html"
document.body.appendChild webview
it 'inserts node symbols when set', (done) ->
webview.addEventListener 'console-message', (e) ->
assert.equal e.message, 'function object object'
done()
webview.setAttribute 'nodeintegration', 'on'
webview.src = "file://#{fixtures}/pages/d.html"
document.body.appendChild webview