diff --git a/atom.gyp b/atom.gyp index 6fccc3e14ef3..d191b004983c 100644 --- a/atom.gyp +++ b/atom.gyp @@ -7,6 +7,7 @@ ], 'coffee_sources': [ 'browser/api/lib/atom.coffee', + 'browser/api/lib/window.coffee', 'browser/atom/atom.coffee', ], 'lib_sources': [ @@ -38,6 +39,8 @@ 'common/node_bindings_mac.mm', 'common/options_switches.cc', 'common/options_switches.h', + 'common/v8_value_converter_impl.cc', + 'common/v8_value_converter_impl.h', 'renderer/atom_render_view_observer.cc', 'renderer/atom_render_view_observer.h', 'renderer/atom_renderer_client.cc', diff --git a/browser/api/atom_api_window.cc b/browser/api/atom_api_window.cc index 3a4cff2fd273..0edb9f35bd4b 100644 --- a/browser/api/atom_api_window.cc +++ b/browser/api/atom_api_window.cc @@ -7,7 +7,8 @@ #include "base/values.h" #include "browser/atom_browser_context.h" #include "browser/native_window.h" -#include "content/public/renderer/v8_value_converter.h" +#include "common/v8_value_converter_impl.h" +#include "content/public/browser/web_contents.h" using content::V8ValueConverter; @@ -18,6 +19,12 @@ namespace api { Window::Window(v8::Handle wrapper, base::DictionaryValue* options) : EventEmitter(wrapper), window_(NativeWindow::Create(AtomBrowserContext::Get(), options)) { + window_->InitFromOptions(options); + window_->GetWebContents()->GetController().LoadURL( + GURL("https://github.com"), + content::Referrer(), + content::PAGE_TRANSITION_AUTO_TOPLEVEL, + std::string()); } Window::~Window() { @@ -30,7 +37,7 @@ v8::Handle Window::New(const v8::Arguments &args) { if (!args[0]->IsObject()) return node::ThrowTypeError("Bad argument"); - scoped_ptr converter(V8ValueConverter::create()); + scoped_ptr converter(new V8ValueConverterImpl()); scoped_ptr options( converter->FromV8Value(args[0], v8::Context::GetCurrent())); diff --git a/browser/api/atom_bindings.cc b/browser/api/atom_bindings.cc index 35e5a944df82..94c08dbf4709 100644 --- a/browser/api/atom_bindings.cc +++ b/browser/api/atom_bindings.cc @@ -4,6 +4,7 @@ #include "browser/api/atom_bindings.h" +#include "base/logging.h" #include "vendor/node/src/node.h" #include "vendor/node/src/node_internals.h" @@ -31,6 +32,21 @@ void AtomBindings::BindTo(v8::Handle process) { node::SetMethod(process, "atom_binding", Binding); } +void AtomBindings::AfterLoad() { + v8::HandleScope scope; + v8::Context::Scope context_scope(node::g_context); + + v8::Handle global = node::g_context->Global(); + v8::Handle atom = + global->Get(v8::String::New("__atom"))->ToObject(); + DCHECK(!atom.IsEmpty()); + + browser_main_parts_ = v8::Persistent::New( + node_isolate, + atom->Get(v8::String::New("browserMainParts"))->ToObject()); + DCHECK(!browser_main_parts_.IsEmpty()); +} + // static v8::Handle AtomBindings::Binding(const v8::Arguments& args) { v8::HandleScope scope; diff --git a/browser/api/atom_bindings.h b/browser/api/atom_bindings.h index fdc6216a6af7..351c212627fc 100644 --- a/browser/api/atom_bindings.h +++ b/browser/api/atom_bindings.h @@ -17,13 +17,23 @@ class AtomBindings { // Add process.atom_binding function, which behaves like process.binding but // load native code from atom-shell instead. - void BindTo(v8::Handle process); + virtual void BindTo(v8::Handle process); + + // Called when the node.js main script has been loaded. + virtual void AfterLoad(); + + // The require('atom').browserMainParts object. + v8::Handle browser_main_parts() { + return browser_main_parts_; + } private: static v8::Handle Binding(const v8::Arguments& args); static v8::Persistent binding_cache_; + v8::Persistent browser_main_parts_; + DISALLOW_COPY_AND_ASSIGN(AtomBindings); }; diff --git a/browser/api/lib/window.coffee b/browser/api/lib/window.coffee new file mode 100644 index 000000000000..fb2e820b4218 --- /dev/null +++ b/browser/api/lib/window.coffee @@ -0,0 +1,3 @@ +Window = process.atom_binding('window').Window + +module.exports = Window diff --git a/browser/atom_browser_main_parts.cc b/browser/atom_browser_main_parts.cc index 658152ff9286..7bc8e6cc59bc 100644 --- a/browser/atom_browser_main_parts.cc +++ b/browser/atom_browser_main_parts.cc @@ -4,14 +4,11 @@ #include "browser/atom_browser_main_parts.h" -#include "base/values.h" #include "browser/api/atom_bindings.h" #include "browser/atom_browser_context.h" #include "browser/native_window.h" -#include "brightray/browser/browser_context.h" -#include "brightray/browser/inspectable_web_contents.h" -#include "brightray/browser/inspectable_web_contents_view.h" #include "common/node_bindings.h" +#include "vendor/node/src/node.h" #include "vendor/node/src/node_internals.h" namespace atom { @@ -36,6 +33,8 @@ void AtomBrowserMainParts::PostEarlyInitialization() { atom_bindings_->BindTo(node::process); node_bindings_->Load(); + + atom_bindings_->AfterLoad(); } void AtomBrowserMainParts::PreMainMessageLoopStart() { @@ -47,22 +46,17 @@ void AtomBrowserMainParts::PreMainMessageLoopStart() { void AtomBrowserMainParts::PreMainMessageLoopRun() { brightray::BrowserMainParts::PreMainMessageLoopRun(); + { + v8::HandleScope scope; + v8::Context::Scope context_scope(node::g_context); + + v8::Handle args; + node::MakeCallback(atom_bindings_->browser_main_parts(), + "preMainMessageLoopRun", + 0, &args); + } + node_bindings_->RunMessageLoop(); - - scoped_ptr options(new base::DictionaryValue); - options->SetInteger("width", 800); - options->SetInteger("height", 600); - options->SetString("title", "Atom"); - - // FIXME: Leak object here. - NativeWindow* window = NativeWindow::Create(browser_context(), options.get()); - window->InitFromOptions(options.get()); - - window->GetWebContents()->GetController().LoadURL( - GURL("http://localhost"), - content::Referrer(), - content::PAGE_TRANSITION_AUTO_TOPLEVEL, - std::string()); } } // namespace atom diff --git a/browser/atom_browser_main_parts.h b/browser/atom_browser_main_parts.h index ace9103f6003..732152628885 100644 --- a/browser/atom_browser_main_parts.h +++ b/browser/atom_browser_main_parts.h @@ -6,7 +6,6 @@ #define ATOM_BROWSER_ATOM_BROWSER_MAIN_PARTS_ #include "brightray/browser/browser_main_parts.h" -#include "common/node_bindings.h" namespace atom { diff --git a/browser/default_app/main.js b/browser/default_app/main.js index 77c0bc27e797..35e9881ac2a3 100644 --- a/browser/default_app/main.js +++ b/browser/default_app/main.js @@ -1,5 +1,8 @@ var atom = require('atom'); +var Window = require('window'); + +var mainWindow = null; atom.browserMainParts.preMainMessageLoopRun = function() { - console.log('Create new window'); + mainWindow = new Window({ width: 800, height: 600 }); } diff --git a/common/v8_value_converter_impl.cc b/common/v8_value_converter_impl.cc new file mode 100644 index 000000000000..e76ddf94cec4 --- /dev/null +++ b/common/v8_value_converter_impl.cc @@ -0,0 +1,333 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/v8_value_converter_impl.h" + +#include + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "v8/include/v8.h" + +namespace atom { + +V8ValueConverterImpl::V8ValueConverterImpl() + : date_allowed_(false), + reg_exp_allowed_(false), + function_allowed_(false), + strip_null_from_objects_(false), + avoid_identity_hash_for_testing_(false) {} + +void V8ValueConverterImpl::SetDateAllowed(bool val) { + date_allowed_ = val; +} + +void V8ValueConverterImpl::SetRegExpAllowed(bool val) { + reg_exp_allowed_ = val; +} + +void V8ValueConverterImpl::SetFunctionAllowed(bool val) { + function_allowed_ = val; +} + +void V8ValueConverterImpl::SetStripNullFromObjects(bool val) { + strip_null_from_objects_ = val; +} + +v8::Handle V8ValueConverterImpl::ToV8Value( + const base::Value* value, v8::Handle context) const { + v8::Context::Scope context_scope(context); + v8::HandleScope handle_scope; + return handle_scope.Close(ToV8ValueImpl(value)); +} + +Value* V8ValueConverterImpl::FromV8Value( + v8::Handle val, + v8::Handle context) const { + v8::Context::Scope context_scope(context); + v8::HandleScope handle_scope; + HashToHandleMap unique_map; + return FromV8ValueImpl(val, &unique_map); +} + +v8::Handle V8ValueConverterImpl::ToV8ValueImpl( + const base::Value* value) const { + CHECK(value); + switch (value->GetType()) { + case base::Value::TYPE_NULL: + return v8::Null(); + + case base::Value::TYPE_BOOLEAN: { + bool val = false; + CHECK(value->GetAsBoolean(&val)); + return v8::Boolean::New(val); + } + + case base::Value::TYPE_INTEGER: { + int val = 0; + CHECK(value->GetAsInteger(&val)); + return v8::Integer::New(val); + } + + case base::Value::TYPE_DOUBLE: { + double val = 0.0; + CHECK(value->GetAsDouble(&val)); + return v8::Number::New(val); + } + + case base::Value::TYPE_STRING: { + std::string val; + CHECK(value->GetAsString(&val)); + return v8::String::New(val.c_str(), val.length()); + } + + case base::Value::TYPE_LIST: + return ToV8Array(static_cast(value)); + + case base::Value::TYPE_DICTIONARY: + return ToV8Object(static_cast(value)); + + default: + LOG(ERROR) << "Unexpected value type: " << value->GetType(); + return v8::Null(); + } +} + +v8::Handle V8ValueConverterImpl::ToV8Array( + const base::ListValue* val) const { + v8::Handle result(v8::Array::New(val->GetSize())); + + for (size_t i = 0; i < val->GetSize(); ++i) { + const base::Value* child = NULL; + CHECK(val->Get(i, &child)); + + v8::Handle child_v8 = ToV8ValueImpl(child); + CHECK(!child_v8.IsEmpty()); + + v8::TryCatch try_catch; + result->Set(static_cast(i), child_v8); + if (try_catch.HasCaught()) + LOG(ERROR) << "Setter for index " << i << " threw an exception."; + } + + return result; +} + +v8::Handle V8ValueConverterImpl::ToV8Object( + const base::DictionaryValue* val) const { + v8::Handle result(v8::Object::New()); + + for (base::DictionaryValue::Iterator iter(*val); + !iter.IsAtEnd(); iter.Advance()) { + const std::string& key = iter.key(); + v8::Handle child_v8 = ToV8ValueImpl(&iter.value()); + CHECK(!child_v8.IsEmpty()); + + v8::TryCatch try_catch; + result->Set(v8::String::New(key.c_str(), key.length()), child_v8); + if (try_catch.HasCaught()) { + LOG(ERROR) << "Setter for property " << key.c_str() << " threw an " + << "exception."; + } + } + + return result; +} + +Value* V8ValueConverterImpl::FromV8ValueImpl(v8::Handle val, + HashToHandleMap* unique_map) const { + CHECK(!val.IsEmpty()); + + if (val->IsNull()) + return base::Value::CreateNullValue(); + + if (val->IsBoolean()) + return new base::FundamentalValue(val->ToBoolean()->Value()); + + if (val->IsInt32()) + return new base::FundamentalValue(val->ToInt32()->Value()); + + if (val->IsNumber()) + return new base::FundamentalValue(val->ToNumber()->Value()); + + if (val->IsString()) { + v8::String::Utf8Value utf8(val->ToString()); + return new base::StringValue(std::string(*utf8, utf8.length())); + } + + if (val->IsUndefined()) + // JSON.stringify ignores undefined. + return NULL; + + if (val->IsDate()) { + if (!date_allowed_) + // JSON.stringify would convert this to a string, but an object is more + // consistent within this class. + return FromV8Object(val->ToObject(), unique_map); + v8::Date* date = v8::Date::Cast(*val); + return new base::FundamentalValue(date->NumberValue() / 1000.0); + } + + if (val->IsRegExp()) { + if (!reg_exp_allowed_) + // JSON.stringify converts to an object. + return FromV8Object(val->ToObject(), unique_map); + return new base::StringValue(*v8::String::Utf8Value(val->ToString())); + } + + // v8::Value doesn't have a ToArray() method for some reason. + if (val->IsArray()) + return FromV8Array(val.As(), unique_map); + + if (val->IsFunction()) { + if (!function_allowed_) + // JSON.stringify refuses to convert function(){}. + return NULL; + return FromV8Object(val->ToObject(), unique_map); + } + + if (val->IsObject()) { + return FromV8Object(val->ToObject(), unique_map); + } + + LOG(ERROR) << "Unexpected v8 value type encountered."; + return NULL; +} + +Value* V8ValueConverterImpl::FromV8Array(v8::Handle val, + HashToHandleMap* unique_map) const { + if (!UpdateAndCheckUniqueness(unique_map, val)) + return base::Value::CreateNullValue(); + + scoped_ptr scope; + // If val was created in a different context than our current one, change to + // that context, but change back after val is converted. + if (!val->CreationContext().IsEmpty() && + val->CreationContext() != v8::Context::GetCurrent()) + scope.reset(new v8::Context::Scope(val->CreationContext())); + + base::ListValue* result = new base::ListValue(); + + // Only fields with integer keys are carried over to the ListValue. + for (uint32 i = 0; i < val->Length(); ++i) { + v8::TryCatch try_catch; + v8::Handle child_v8 = val->Get(i); + if (try_catch.HasCaught()) { + LOG(ERROR) << "Getter for index " << i << " threw an exception."; + child_v8 = v8::Null(); + } + + if (!val->HasRealIndexedProperty(i)) + continue; + + base::Value* child = FromV8ValueImpl(child_v8, unique_map); + if (child) + result->Append(child); + else + // JSON.stringify puts null in places where values don't serialize, for + // example undefined and functions. Emulate that behavior. + result->Append(base::Value::CreateNullValue()); + } + return result; +} + +Value* V8ValueConverterImpl::FromV8Object( + v8::Handle val, + HashToHandleMap* unique_map) const { + if (!UpdateAndCheckUniqueness(unique_map, val)) + return base::Value::CreateNullValue(); + scoped_ptr scope; + // If val was created in a different context than our current one, change to + // that context, but change back after val is converted. + if (!val->CreationContext().IsEmpty() && + val->CreationContext() != v8::Context::GetCurrent()) + scope.reset(new v8::Context::Scope(val->CreationContext())); + + scoped_ptr result(new base::DictionaryValue()); + v8::Handle property_names(val->GetOwnPropertyNames()); + + for (uint32 i = 0; i < property_names->Length(); ++i) { + v8::Handle key(property_names->Get(i)); + + // Extend this test to cover more types as necessary and if sensible. + if (!key->IsString() && + !key->IsNumber()) { + NOTREACHED() << "Key \"" << *v8::String::AsciiValue(key) << "\" " + "is neither a string nor a number"; + continue; + } + + // Skip all callbacks: crbug.com/139933 + if (val->HasRealNamedCallbackProperty(key->ToString())) + continue; + + v8::String::Utf8Value name_utf8(key->ToString()); + + v8::TryCatch try_catch; + v8::Handle child_v8 = val->Get(key); + + if (try_catch.HasCaught()) { + LOG(ERROR) << "Getter for property " << *name_utf8 + << " threw an exception."; + child_v8 = v8::Null(); + } + + scoped_ptr child(FromV8ValueImpl(child_v8, unique_map)); + if (!child.get()) + // JSON.stringify skips properties whose values don't serialize, for + // example undefined and functions. Emulate that behavior. + continue; + + // Strip null if asked (and since undefined is turned into null, undefined + // too). The use case for supporting this is JSON-schema support, + // specifically for extensions, where "optional" JSON properties may be + // represented as null, yet due to buggy legacy code elsewhere isn't + // treated as such (potentially causing crashes). For example, the + // "tabs.create" function takes an object as its first argument with an + // optional "windowId" property. + // + // Given just + // + // tabs.create({}) + // + // this will work as expected on code that only checks for the existence of + // a "windowId" property (such as that legacy code). However given + // + // tabs.create({windowId: null}) + // + // there *is* a "windowId" property, but since it should be an int, code + // on the browser which doesn't additionally check for null will fail. + // We can avoid all bugs related to this by stripping null. + if (strip_null_from_objects_ && child->IsType(Value::TYPE_NULL)) + continue; + + result->SetWithoutPathExpansion(std::string(*name_utf8, name_utf8.length()), + child.release()); + } + + return result.release(); +} + +bool V8ValueConverterImpl::UpdateAndCheckUniqueness( + HashToHandleMap* map, + v8::Handle handle) const { + typedef HashToHandleMap::const_iterator Iterator; + + int hash = avoid_identity_hash_for_testing_ ? 0 : handle->GetIdentityHash(); + // We only compare using == with handles to objects with the same identity + // hash. Different hash obviously means different objects, but two objects in + // a couple of thousands could have the same identity hash. + std::pair range = map->equal_range(hash); + for (Iterator it = range.first; it != range.second; ++it) { + // Operator == for handles actually compares the underlying objects. + if (it->second == handle) + return false; + } + + map->insert(std::pair >(hash, handle)); + return true; +} + +} // namespace atom diff --git a/common/v8_value_converter_impl.h b/common/v8_value_converter_impl.h new file mode 100644 index 000000000000..47e485d098ea --- /dev/null +++ b/common/v8_value_converter_impl.h @@ -0,0 +1,86 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_V8_VALUE_CONVERTER_IMPL_H_ +#define ATOM_COMMON_V8_VALUE_CONVERTER_IMPL_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/renderer/v8_value_converter.h" + +namespace base { +class BinaryValue; +class DictionaryValue; +class ListValue; +class Value; +} + +namespace atom { + +// The content::V8ValueConverter depends on WebKit, we get rid of the WebKit +// dependency by removing support of binary data. +class V8ValueConverterImpl : public content::V8ValueConverter { + public: + V8ValueConverterImpl(); + + // V8ValueConverter implementation. + virtual void SetDateAllowed(bool val) OVERRIDE; + virtual void SetRegExpAllowed(bool val) OVERRIDE; + virtual void SetFunctionAllowed(bool val) OVERRIDE; + virtual void SetStripNullFromObjects(bool val) OVERRIDE; + virtual v8::Handle ToV8Value( + const base::Value* value, + v8::Handle context) const OVERRIDE; + virtual base::Value* FromV8Value( + v8::Handle value, + v8::Handle context) const OVERRIDE; + + private: + friend class ScopedAvoidIdentityHashForTesting; + typedef std::multimap > HashToHandleMap; + + v8::Handle ToV8ValueImpl(const base::Value* value) const; + v8::Handle ToV8Array(const base::ListValue* list) const; + v8::Handle ToV8Object( + const base::DictionaryValue* dictionary) const; + + base::Value* FromV8ValueImpl(v8::Handle value, + HashToHandleMap* unique_map) const; + base::Value* FromV8Array(v8::Handle array, + HashToHandleMap* unique_map) const; + + base::Value* FromV8Object(v8::Handle object, + HashToHandleMap* unique_map) const; + + // If |handle| is not in |map|, then add it to |map| and return true. + // Otherwise do nothing and return false. Here "A is unique" means that no + // other handle B in the map points to the same object as A. Note that A can + // be unique even if there already is another handle with the same identity + // hash (key) in the map, because two objects can have the same hash. + bool UpdateAndCheckUniqueness(HashToHandleMap* map, + v8::Handle handle) const; + + // If true, we will convert Date JavaScript objects to doubles. + bool date_allowed_; + + // If true, we will convert RegExp JavaScript objects to string. + bool reg_exp_allowed_; + + // If true, we will convert Function JavaScript objects to dictionaries. + bool function_allowed_; + + // If true, undefined and null values are ignored when converting v8 objects + // into Values. + bool strip_null_from_objects_; + + bool avoid_identity_hash_for_testing_; + + DISALLOW_COPY_AND_ASSIGN(V8ValueConverterImpl); +}; + +} // namespace atom + +#endif // ATOM_COMMON_V8_VALUE_CONVERTER_IMPL_H_ diff --git a/vendor/node b/vendor/node index ff00d60bcf92..44cac2fd2486 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit ff00d60bcf92fdb229cf0326e31d1aff82ed357e +Subproject commit 44cac2fd2486151f13b7ce3f033158154eedbfbd