diff --git a/atom.gyp b/atom.gyp index eb4302d74c27..0960572dac2c 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.36.2', + 'version%': '0.36.4', }, 'includes': [ 'filenames.gypi', @@ -28,7 +28,7 @@ 'target_name': '<(project_name)', 'type': 'executable', 'dependencies': [ - 'compile_coffee', + 'js2asar', '<(project_name)_lib', ], 'sources': [ @@ -221,7 +221,7 @@ 'target_name': '<(project_name)_lib', 'type': 'static_library', 'dependencies': [ - 'atom_coffee2c', + 'atom_js2c', 'vendor/brightray/brightray.gyp:brightray', 'vendor/node/node.gyp:node', ], @@ -351,11 +351,11 @@ ], }, # target <(product_name)_lib { - 'target_name': 'compile_coffee', + 'target_name': 'js2asar', 'type': 'none', 'actions': [ { - 'action_name': 'compile_coffee', + 'action_name': 'js2asar', 'variables': { 'conditions': [ ['OS=="mac"', { @@ -366,41 +366,41 @@ ], }, 'inputs': [ - '<@(coffee_sources)', + '<@(js_sources)', ], 'outputs': [ '<(resources_path)/atom.asar', ], 'action': [ 'python', - 'tools/coffee2asar.py', + 'tools/js2asar.py', '<@(_outputs)', '<@(_inputs)', ], } ], - }, # target compile_coffee + }, # target js2asar { - 'target_name': 'atom_coffee2c', + 'target_name': 'atom_js2c', 'type': 'none', 'actions': [ { - 'action_name': 'atom_coffee2c', + 'action_name': 'atom_js2c', 'inputs': [ - '<@(coffee2c_sources)', + '<@(js2c_sources)', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/atom_natives.h', ], 'action': [ 'python', - 'tools/coffee2c.py', + 'tools/js2c.py', '<@(_outputs)', '<@(_inputs)', ], } ], - }, # target atom_coffee2c + }, # target atom_js2c ], 'conditions': [ ['OS=="mac"', { diff --git a/atom/app/uv_task_runner.cc b/atom/app/uv_task_runner.cc index f49ba259bac8..097cc3bcda18 100644 --- a/atom/app/uv_task_runner.cc +++ b/atom/app/uv_task_runner.cc @@ -37,7 +37,7 @@ bool UvTaskRunner::PostNonNestableDelayedTask( const tracked_objects::Location& from_here, const base::Closure& task, base::TimeDelta delay) { - return PostDelayedTask(from_here, task, delay);; + return PostDelayedTask(from_here, task, delay); } // static diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 40ee4d0d9b51..0a544c56468c 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -41,6 +41,7 @@ namespace { void ShowMessageBox(int type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, @@ -54,12 +55,13 @@ void ShowMessageBox(int type, if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, cancel_id, - options, title, message, detail, icon, callback); + atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, + default_id, cancel_id, options, title, + message, detail, icon, callback); } else { int chosen = atom::ShowMessageBox(window, (atom::MessageBoxType)type, - buttons, cancel_id, options, title, - message, detail, icon); + buttons, default_id, cancel_id, + options, title, message, detail, icon); args->Return(chosen); } } diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 8b73d61622ec..1cc76ebfa23d 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -22,6 +22,7 @@ #include "atom/common/node_includes.h" #include "base/files/file_path.h" #include "base/prefs/pref_service.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/thread_task_runner_handle.h" #include "brightray/browser/net/devtools_network_conditions.h" @@ -114,14 +115,25 @@ struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, net::ProxyConfig* out) { - std::string proxy; - if (!ConvertFromV8(isolate, val, &proxy)) + std::string proxy_rules; + GURL pac_url; + mate::Dictionary options; + // Fallback to previous API when passed String. + // https://git.io/vuhjj + if (ConvertFromV8(isolate, val, &proxy_rules)) { + pac_url = GURL(proxy_rules); // Assume it is PAC script if it is URL. + } else if (ConvertFromV8(isolate, val, &options)) { + options.Get("pacScript", &pac_url); + options.Get("proxyRules", &proxy_rules); + } else { return false; - auto pac_url = GURL(proxy); - if (pac_url.is_valid()) { + } + + // pacScript takes precedence over proxyRules. + if (!pac_url.is_empty() && pac_url.is_valid()) { out->set_pac_url(pac_url); } else { - out->proxy_rules().ParseFromString(proxy); + out->proxy_rules().ParseFromString(proxy_rules); } return true; } @@ -193,7 +205,7 @@ class ResolveProxyHelper { }; // Runs the callback in UI thread. -template +template void RunCallbackInUI(const base::Callback& callback, T... result) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(callback, result...)); @@ -201,19 +213,35 @@ void RunCallbackInUI(const base::Callback& callback, T... result) { // Callback of HttpCache::GetBackend. void OnGetBackend(disk_cache::Backend** backend_ptr, + Session::CacheAction action, const net::CompletionCallback& callback, int result) { if (result != net::OK) { RunCallbackInUI(callback, result); } else if (backend_ptr && *backend_ptr) { - (*backend_ptr)->DoomAllEntries(base::Bind(&RunCallbackInUI, callback)); + if (action == Session::CacheAction::CLEAR) { + (*backend_ptr)->DoomAllEntries(base::Bind(&RunCallbackInUI, + callback)); + } else if (action == Session::CacheAction::STATS) { + base::StringPairs stats; + (*backend_ptr)->GetStats(&stats); + for (size_t i = 0; i < stats.size(); ++i) { + if (stats[i].first == "Current size") { + int current_size; + base::StringToInt(stats[i].second, ¤t_size); + RunCallbackInUI(callback, current_size); + break; + } + } + } } else { RunCallbackInUI(callback, net::ERR_FAILED); } } -void ClearHttpCacheInIO( +void DoCacheActionInIO( const scoped_refptr& context_getter, + Session::CacheAction action, const net::CompletionCallback& callback) { auto request_context = context_getter->GetURLRequestContext(); auto http_cache = request_context->http_transaction_factory()->GetCache(); @@ -224,7 +252,7 @@ void ClearHttpCacheInIO( using BackendPtr = disk_cache::Backend*; BackendPtr* backend_ptr = new BackendPtr(nullptr); net::CompletionCallback on_get_backend = - base::Bind(&OnGetBackend, base::Owned(backend_ptr), callback); + base::Bind(&OnGetBackend, base::Owned(backend_ptr), action, callback); int rv = http_cache->GetBackend(backend_ptr, on_get_backend); if (rv != net::ERR_IO_PENDING) on_get_backend.Run(net::OK); @@ -276,10 +304,12 @@ void Session::ResolveProxy(const GURL& url, ResolveProxyCallback callback) { new ResolveProxyHelper(browser_context(), url, callback); } -void Session::ClearCache(const net::CompletionCallback& callback) { +template +void Session::DoCacheAction(const net::CompletionCallback& callback) { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&ClearHttpCacheInIO, + base::Bind(&DoCacheActionInIO, make_scoped_refptr(browser_context_->GetRequestContext()), + action, callback)); } @@ -301,6 +331,12 @@ void Session::ClearStorageData(mate::Arguments* args) { base::Time(), base::Time::Max(), callback); } +void Session::FlushStorageData() { + auto storage_partition = + content::BrowserContext::GetStoragePartition(browser_context(), nullptr); + storage_partition->Flush(); +} + void Session::SetProxy(const net::ProxyConfig& config, const base::Closure& callback) { auto getter = browser_context_->GetRequestContext(); @@ -403,8 +439,10 @@ void Session::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype) .MakeDestroyable() .SetMethod("resolveProxy", &Session::ResolveProxy) - .SetMethod("clearCache", &Session::ClearCache) + .SetMethod("getCacheSize", &Session::DoCacheAction) + .SetMethod("clearCache", &Session::DoCacheAction) .SetMethod("clearStorageData", &Session::ClearStorageData) + .SetMethod("flushStorageData", &Session::FlushStorageData) .SetMethod("setProxy", &Session::SetProxy) .SetMethod("setDownloadPath", &Session::SetDownloadPath) .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 0034b1480632..37a5a45a6c95 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -38,6 +38,11 @@ class Session: public mate::TrackableObject, public: using ResolveProxyCallback = base::Callback; + enum class CacheAction { + CLEAR, + STATS, + }; + // Gets or creates Session from the |browser_context|. static mate::Handle CreateFrom( v8::Isolate* isolate, AtomBrowserContext* browser_context); @@ -62,8 +67,10 @@ class Session: public mate::TrackableObject, private: void ResolveProxy(const GURL& url, ResolveProxyCallback callback); - void ClearCache(const net::CompletionCallback& callback); + template + void DoCacheAction(const net::CompletionCallback& callback); void ClearStorageData(mate::Arguments* args); + void FlushStorageData(); void SetProxy(const net::ProxyConfig& config, const base::Closure& callback); void SetDownloadPath(const base::FilePath& path); void EnableNetworkEmulation(const mate::Dictionary& options); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 3f39519a3f7b..0173abf4eef0 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -33,7 +33,6 @@ #include "chrome/browser/printing/print_view_manager_basic.h" #include "chrome/browser/printing/print_preview_message_handler.h" #include "content/common/view_messages.h" -#include "content/public/browser/browser_plugin_guest_manager.h" #include "content/public/browser/favicon_status.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_details.h" @@ -75,15 +74,6 @@ void SetUserAgentInIO(scoped_refptr getter, user_agent)); } -bool NotifyZoomLevelChanged( - double level, content::WebContents* guest_web_contents) { - guest_web_contents->SendToAllFrames( - new AtomViewMsg_SetZoomLevel(MSG_ROUTING_NONE, level)); - - // Return false to iterate over all guests. - return false; -} - } // namespace namespace mate { @@ -290,14 +280,17 @@ WebContents::WebContents(v8::Isolate* isolate, } WebContents::~WebContents() { - if (type_ == WEB_VIEW && managed_web_contents()) { - // When force destroying the "destroyed" event is not emitted. + // The destroy() is called. + if (managed_web_contents()) { + // For webview we need to tell content module to do some cleanup work before + // destroying it. + if (type_ == WEB_VIEW) + guest_delegate_->Destroy(); + + // The WebContentsDestroyed will not be called automatically because we + // unsubscribe from webContents before destroying it. So we have to manually + // call it here to make sure "destroyed" event is emitted. WebContentsDestroyed(); - - guest_delegate_->Destroy(); - - Observe(nullptr); - DestroyWebContents(); } } @@ -625,18 +618,43 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(AtomViewHostMsg_Message, OnRendererMessage) IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_Message_Sync, OnRendererMessageSync) - IPC_MESSAGE_HANDLER(AtomViewHostMsg_ZoomLevelChanged, OnZoomLevelChanged) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } +// There are three ways of destroying a webContents: +// 1. call webContents.destory(); +// 2. garbage collection; +// 3. user closes the window of webContents; +// For webview only #1 will happen, for BrowserWindow both #1 and #3 may +// happen. The #2 should never happen for webContents, because webview is +// managed by GuestViewManager, and BrowserWindow's webContents is managed +// by api::Window. +// For #1, the destructor will do the cleanup work and we only need to make +// sure "destroyed" event is emitted. For #3, the content::WebContents will +// be destroyed on close, and WebContentsDestroyed would be called for it, so +// we need to make sure the api::WebContents is also deleted. void WebContents::WebContentsDestroyed() { // The RenderViewDeleted was not called when the WebContents is destroyed. RenderViewDeleted(web_contents()->GetRenderViewHost()); - Emit("destroyed"); + + // This event is only for internal use, which is emitted when WebContents is + // being destroyed. + Emit("will-destroy"); + + // Cleanup relationships with other parts. RemoveFromWeakMap(); + + // We can not call Destroy here because we need to call Emit first, but we + // also do not want any method to be used, so just mark as destroyed here. + MarkDestroyed(); + + Emit("destroyed"); + + // Destroy the native class in next tick. + base::MessageLoop::current()->PostTask(FROM_HERE, GetDestroyClosure()); } void WebContents::NavigationEntryCommitted( @@ -748,11 +766,6 @@ bool WebContents::SavePage(const base::FilePath& full_file_path, return handler->Handle(full_file_path, save_type); } -void WebContents::ExecuteJavaScript(const base::string16& code, - bool has_user_gesture) { - Send(new AtomViewMsg_ExecuteJavaScript(routing_id(), code, has_user_gesture)); -} - void WebContents::OpenDevTools(mate::Arguments* args) { if (type_ == REMOTE) return; @@ -992,7 +1005,7 @@ void WebContents::SendInputEvent(v8::Isolate* isolate, return; } } else if (blink::WebInputEvent::isKeyboardEventType(type)) { - content::NativeWebKeyboardEvent keyboard_event;; + content::NativeWebKeyboardEvent keyboard_event; if (mate::ConvertFromV8(isolate, input_event, &keyboard_event)) { host->ForwardKeyboardEvent(keyboard_event); return; @@ -1085,7 +1098,6 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("getUserAgent", &WebContents::GetUserAgent) .SetMethod("insertCSS", &WebContents::InsertCSS) .SetMethod("savePage", &WebContents::SavePage) - .SetMethod("_executeJavaScript", &WebContents::ExecuteJavaScript) .SetMethod("openDevTools", &WebContents::OpenDevTools) .SetMethod("closeDevTools", &WebContents::CloseDevTools) .SetMethod("isDevToolsOpened", &WebContents::IsDevToolsOpened) @@ -1152,15 +1164,6 @@ void WebContents::OnRendererMessageSync(const base::string16& channel, EmitWithSender(base::UTF16ToUTF8(channel), web_contents(), message, args); } -void WebContents::OnZoomLevelChanged(double level) { - auto manager = web_contents()->GetBrowserContext()->GetGuestManager(); - if (!manager) - return; - manager->ForEachGuest(web_contents(), - base::Bind(&NotifyZoomLevelChanged, - level)); -} - // static mate::Handle WebContents::CreateFrom( v8::Isolate* isolate, content::WebContents* web_contents) { diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index bd7149e38a93..bcef57b9a4aa 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -74,8 +74,6 @@ class WebContents : public mate::TrackableObject, bool SavePage(const base::FilePath& full_file_path, const content::SavePageType& save_type, const SavePageHandler::SavePageCallback& callback); - void ExecuteJavaScript(const base::string16& code, - bool has_user_gesture); void OpenDevTools(mate::Arguments* args); void CloseDevTools(); bool IsDevToolsOpened(); @@ -265,10 +263,6 @@ class WebContents : public mate::TrackableObject, const base::ListValue& args, IPC::Message* message); - // Called when guests need to be notified of - // embedders' zoom level change. - void OnZoomLevelChanged(double level); - v8::Global session_; v8::Global devtools_web_contents_; diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 222f2cae03f1..f32b6d19ccaf 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -107,7 +107,6 @@ void TranslateOldOptions(v8::Isolate* isolate, v8::Local options) { } } -#if defined(OS_WIN) // Converts binary data to Buffer. v8::Local ToBuffer(v8::Isolate* isolate, void* val, int size) { auto buffer = node::Buffer::New(isolate, static_cast(val), size); @@ -116,7 +115,6 @@ v8::Local ToBuffer(v8::Isolate* isolate, void* val, int size) { else return buffer.ToLocalChecked(); } -#endif } // namespace @@ -594,6 +592,12 @@ void Window::SetAspectRatio(double aspect_ratio, mate::Arguments* args) { window_->SetAspectRatio(aspect_ratio, extra_size); } +v8::Local Window::GetNativeWindowHandle() { + gfx::AcceleratedWidget handle = window_->GetAcceleratedWidget(); + return ToBuffer( + isolate(), static_cast(&handle), sizeof(gfx::AcceleratedWidget)); +} + void Window::SetVisibleOnAllWorkspaces(bool visible) { return window_->SetVisibleOnAllWorkspaces(visible); } @@ -634,6 +638,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setFullScreen", &Window::SetFullScreen) .SetMethod("isFullScreen", &Window::IsFullscreen) .SetMethod("setAspectRatio", &Window::SetAspectRatio) + .SetMethod("getNativeWindowHandle", &Window::GetNativeWindowHandle) .SetMethod("getBounds", &Window::GetBounds) .SetMethod("setBounds", &Window::SetBounds) .SetMethod("getSize", &Window::GetSize) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 9297b2fe7562..fe9a7e828d39 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -137,6 +137,7 @@ class Window : public mate::TrackableObject, void SetMenuBarVisibility(bool visible); bool IsMenuBarVisible(); void SetAspectRatio(double aspect_ratio, mate::Arguments* args); + v8::Local GetNativeWindowHandle(); #if defined(OS_WIN) typedef base::Callback, diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee deleted file mode 100644 index d0ec41c4d23c..000000000000 --- a/atom/browser/api/lib/app.coffee +++ /dev/null @@ -1,72 +0,0 @@ -{deprecate, session, Menu} = require 'electron' -{EventEmitter} = require 'events' - -bindings = process.atomBinding 'app' -downloadItemBindings = process.atomBinding 'download_item' - -app = bindings.app -app.__proto__ = EventEmitter.prototype - -app.setApplicationMenu = (menu) -> - Menu.setApplicationMenu menu - -app.getApplicationMenu = -> - Menu.getApplicationMenu() - -app.commandLine = - appendSwitch: bindings.appendSwitch, - appendArgument: bindings.appendArgument - -if process.platform is 'darwin' - app.dock = - bounce: (type='informational') -> bindings.dockBounce type - cancelBounce: bindings.dockCancelBounce - setBadge: bindings.dockSetBadgeText - getBadge: bindings.dockGetBadgeText - hide: bindings.dockHide - show: bindings.dockShow - setMenu: bindings.dockSetMenu - -appPath = null -app.setAppPath = (path) -> - appPath = path - -app.getAppPath = -> - appPath - -# Routes the events to webContents. -for name in ['login', 'certificate-error', 'select-client-certificate'] - do (name) -> - app.on name, (event, webContents, args...) -> - webContents.emit name, event, args... - -# Deprecated. -app.getHomeDir = deprecate 'app.getHomeDir', 'app.getPath', -> - @getPath 'home' -app.getDataPath = deprecate 'app.getDataPath', 'app.getPath', -> - @getPath 'userData' -app.setDataPath = deprecate 'app.setDataPath', 'app.setPath', (path) -> - @setPath 'userData', path -app.resolveProxy = deprecate 'app.resolveProxy', 'session.defaultSession.resolveProxy', (url, callback) -> - session.defaultSession.resolveProxy url, callback -deprecate.rename app, 'terminate', 'quit' -deprecate.event app, 'finish-launching', 'ready', -> - setImmediate => # give default app a chance to setup default menu. - @emit 'finish-launching' -deprecate.event app, 'activate-with-no-open-windows', 'activate', (event, hasVisibleWindows) -> - @emit 'activate-with-no-open-windows', event if not hasVisibleWindows -deprecate.event app, 'select-certificate', 'select-client-certificate' - -# Wrappers for native classes. -wrapDownloadItem = (downloadItem) -> - # downloadItem is an EventEmitter. - downloadItem.__proto__ = EventEmitter.prototype - # Deprecated. - deprecate.property downloadItem, 'url', 'getURL' - deprecate.property downloadItem, 'filename', 'getFilename' - deprecate.property downloadItem, 'mimeType', 'getMimeType' - deprecate.rename downloadItem, 'getUrl', 'getURL' -downloadItemBindings._setWrapDownloadItem wrapDownloadItem - -# Only one App object pemitted. -module.exports = app diff --git a/atom/browser/api/lib/app.js b/atom/browser/api/lib/app.js new file mode 100644 index 000000000000..c4cdb5124651 --- /dev/null +++ b/atom/browser/api/lib/app.js @@ -0,0 +1,123 @@ +const deprecate = require('electron').deprecate; +const session = require('electron').session; +const Menu = require('electron').Menu; +const EventEmitter = require('events').EventEmitter; + +const bindings = process.atomBinding('app'); +const downloadItemBindings = process.atomBinding('download_item'); +const app = bindings.app; + +var slice = [].slice; + +app.__proto__ = EventEmitter.prototype; + +app.setApplicationMenu = function(menu) { + return Menu.setApplicationMenu(menu); +}; + +app.getApplicationMenu = function() { + return Menu.getApplicationMenu(); +}; + +app.commandLine = { + appendSwitch: bindings.appendSwitch, + appendArgument: bindings.appendArgument +}; + +if (process.platform === 'darwin') { + app.dock = { + bounce: function(type) { + if (type == null) { + type = 'informational'; + } + return bindings.dockBounce(type); + }, + cancelBounce: bindings.dockCancelBounce, + setBadge: bindings.dockSetBadgeText, + getBadge: bindings.dockGetBadgeText, + hide: bindings.dockHide, + show: bindings.dockShow, + setMenu: bindings.dockSetMenu + }; +} + +var appPath = null; + +app.setAppPath = function(path) { + return appPath = path; +}; + +app.getAppPath = function() { + return appPath; +}; + +// Routes the events to webContents. +var ref1 = ['login', 'certificate-error', 'select-client-certificate']; +var fn = function(name) { + return app.on(name, function() { + var args, event, webContents; + event = arguments[0], webContents = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + return webContents.emit.apply(webContents, [name, event].concat(slice.call(args))); + }); +}; +var i, len, name; +for (i = 0, len = ref1.length; i < len; i++) { + name = ref1[i]; + fn(ref1[i]); +} + +// Deprecated. + +app.getHomeDir = deprecate('app.getHomeDir', 'app.getPath', function() { + return this.getPath('home'); +}); + +app.getDataPath = deprecate('app.getDataPath', 'app.getPath', function() { + return this.getPath('userData'); +}); + +app.setDataPath = deprecate('app.setDataPath', 'app.setPath', function(path) { + return this.setPath('userData', path); +}); + +app.resolveProxy = deprecate('app.resolveProxy', 'session.defaultSession.resolveProxy', function(url, callback) { + return session.defaultSession.resolveProxy(url, callback); +}); + +deprecate.rename(app, 'terminate', 'quit'); + +deprecate.event(app, 'finish-launching', 'ready', function() { + + // give default app a chance to setup default menu. + return setImmediate((function(_this) { + return function() { + return _this.emit('finish-launching'); + }; + })(this)); +}); + +deprecate.event(app, 'activate-with-no-open-windows', 'activate', function(event, hasVisibleWindows) { + if (!hasVisibleWindows) { + return this.emit('activate-with-no-open-windows', event); + } +}); + +deprecate.event(app, 'select-certificate', 'select-client-certificate'); + +// Wrappers for native classes. +var wrapDownloadItem = function(downloadItem) { + + // downloadItem is an EventEmitter. + downloadItem.__proto__ = EventEmitter.prototype; + + // Deprecated. + deprecate.property(downloadItem, 'url', 'getURL'); + deprecate.property(downloadItem, 'filename', 'getFilename'); + deprecate.property(downloadItem, 'mimeType', 'getMimeType'); + return deprecate.rename(downloadItem, 'getUrl', 'getURL'); +}; + +downloadItemBindings._setWrapDownloadItem(wrapDownloadItem); + +// Only one App object pemitted. +module.exports = app; diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee deleted file mode 100644 index 28df59fbc3db..000000000000 --- a/atom/browser/api/lib/auto-updater.coffee +++ /dev/null @@ -1,12 +0,0 @@ -{deprecate} = require 'electron' - -autoUpdater = - if process.platform is 'win32' - require './auto-updater/auto-updater-win' - else - require './auto-updater/auto-updater-native' - -# Deprecated. -deprecate.rename autoUpdater, 'setFeedUrl', 'setFeedURL' - -module.exports = autoUpdater diff --git a/atom/browser/api/lib/auto-updater.js b/atom/browser/api/lib/auto-updater.js new file mode 100644 index 000000000000..9cc1fada0675 --- /dev/null +++ b/atom/browser/api/lib/auto-updater.js @@ -0,0 +1,7 @@ +const deprecate = require('electron').deprecate; +const autoUpdater = process.platform === 'win32' ? require('./auto-updater/auto-updater-win') : require('./auto-updater/auto-updater-native'); + +// Deprecated. +deprecate.rename(autoUpdater, 'setFeedUrl', 'setFeedURL'); + +module.exports = autoUpdater; diff --git a/atom/browser/api/lib/auto-updater/auto-updater-native.coffee b/atom/browser/api/lib/auto-updater/auto-updater-native.coffee deleted file mode 100644 index 187be64f5ade..000000000000 --- a/atom/browser/api/lib/auto-updater/auto-updater-native.coffee +++ /dev/null @@ -1,6 +0,0 @@ -{EventEmitter} = require 'events' -{autoUpdater} = process.atomBinding 'auto_updater' - -autoUpdater.__proto__ = EventEmitter.prototype - -module.exports = autoUpdater diff --git a/atom/browser/api/lib/auto-updater/auto-updater-native.js b/atom/browser/api/lib/auto-updater/auto-updater-native.js new file mode 100644 index 000000000000..20c69cdb5a2b --- /dev/null +++ b/atom/browser/api/lib/auto-updater/auto-updater-native.js @@ -0,0 +1,6 @@ +const EventEmitter = require('events').EventEmitter; +const autoUpdater = process.atomBinding('auto_updater').autoUpdater; + +autoUpdater.__proto__ = EventEmitter.prototype; + +module.exports = autoUpdater; diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee deleted file mode 100644 index e7cb194ffcf4..000000000000 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee +++ /dev/null @@ -1,42 +0,0 @@ -{app} = require 'electron' -{EventEmitter} = require 'events' -url = require 'url' - -squirrelUpdate = require './squirrel-update-win' - -class AutoUpdater extends EventEmitter - quitAndInstall: -> - squirrelUpdate.processStart() - app.quit() - - setFeedURL: (updateURL) -> - @updateURL = updateURL - - checkForUpdates: -> - return @emitError 'Update URL is not set' unless @updateURL - return @emitError 'Can not find Squirrel' unless squirrelUpdate.supported() - - @emit 'checking-for-update' - - squirrelUpdate.download @updateURL, (error, update) => - return @emitError error if error? - return @emit 'update-not-available' unless update? - - @emit 'update-available' - - squirrelUpdate.update @updateURL, (error) => - return @emitError error if error? - - {releaseNotes, version} = update - # Following information is not available on Windows, so fake them. - date = new Date - url = @updateURL - - @emit 'update-downloaded', {}, releaseNotes, version, date, url, => @quitAndInstall() - - # Private: Emit both error object and message, this is to keep compatibility - # with Old APIs. - emitError: (message) -> - @emit 'error', new Error(message), message - -module.exports = new AutoUpdater diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.js b/atom/browser/api/lib/auto-updater/auto-updater-win.js new file mode 100644 index 000000000000..bbe02555a0b8 --- /dev/null +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.js @@ -0,0 +1,71 @@ +const app = require('electron').app; +const EventEmitter = require('events').EventEmitter; +const url = require('url'); +const squirrelUpdate = require('./squirrel-update-win'); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, +hasProp = {}.hasOwnProperty; + +var AutoUpdater = (function(superClass) { + extend(AutoUpdater, superClass); + + function AutoUpdater() { + return AutoUpdater.__super__.constructor.apply(this, arguments); + } + + AutoUpdater.prototype.quitAndInstall = function() { + squirrelUpdate.processStart(); + return app.quit(); + }; + + AutoUpdater.prototype.setFeedURL = function(updateURL) { + return this.updateURL = updateURL; + }; + + AutoUpdater.prototype.checkForUpdates = function() { + if (!this.updateURL) { + return this.emitError('Update URL is not set'); + } + if (!squirrelUpdate.supported()) { + return this.emitError('Can not find Squirrel'); + } + this.emit('checking-for-update'); + return squirrelUpdate.download(this.updateURL, (function(_this) { + return function(error, update) { + if (error != null) { + return _this.emitError(error); + } + if (update == null) { + return _this.emit('update-not-available'); + } + _this.emit('update-available'); + return squirrelUpdate.update(_this.updateURL, function(error) { + var date, releaseNotes, version; + if (error != null) { + return _this.emitError(error); + } + releaseNotes = update.releaseNotes, version = update.version; + + // Following information is not available on Windows, so fake them. + date = new Date; + url = _this.updateURL; + return _this.emit('update-downloaded', {}, releaseNotes, version, date, url, function() { + return _this.quitAndInstall(); + }); + }); + }; + })(this)); + }; + + + // Private: Emit both error object and message, this is to keep compatibility + // with Old APIs. + AutoUpdater.prototype.emitError = function(message) { + return this.emit('error', new Error(message), message); + }; + + return AutoUpdater; + +})(EventEmitter); + +module.exports = new AutoUpdater; diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee deleted file mode 100644 index ee914c4fa827..000000000000 --- a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee +++ /dev/null @@ -1,67 +0,0 @@ -fs = require 'fs' -path = require 'path' -{spawn} = require 'child_process' - -appFolder = path.dirname process.execPath # i.e. my-app/app-0.1.13/ -updateExe = path.resolve appFolder, '..', 'Update.exe' # i.e. my-app/Update.exe -exeName = path.basename process.execPath - -# Spawn a command and invoke the callback when it completes with an error -# and the output from standard out. -spawnUpdate = (args, detached, callback) -> - try - spawnedProcess = spawn updateExe, args, {detached} - catch error - # Shouldn't happen, but still guard it. - process.nextTick -> callback error - return - - stdout = '' - stderr = '' - spawnedProcess.stdout.on 'data', (data) -> stdout += data - spawnedProcess.stderr.on 'data', (data) -> stderr += data - - errorEmitted = false - spawnedProcess.on 'error', (error) -> - errorEmitted = true - callback error - spawnedProcess.on 'exit', (code, signal) -> - # We may have already emitted an error. - return if errorEmitted - - # Process terminated with error. - if code isnt 0 - return callback "Command failed: #{signal ? code}\n#{stderr}" - - # Success. - callback null, stdout - -# Start an instance of the installed app. -exports.processStart = (callback) -> - spawnUpdate ['--processStart', exeName], true, -> - -# Download the releases specified by the URL and write new results to stdout. -exports.download = (updateURL, callback) -> - spawnUpdate ['--download', updateURL], false, (error, stdout) -> - return callback(error) if error? - - try - # Last line of output is the JSON details about the releases - json = stdout.trim().split('\n').pop() - update = JSON.parse(json)?.releasesToApply?.pop?() - catch - return callback "Invalid result:\n#{stdout}" - - callback null, update - -# Update the application to the latest remote version specified by URL. -exports.update = (updateURL, callback) -> - spawnUpdate ['--update', updateURL], false, callback - -# Is the Update.exe installed with the current application? -exports.supported = -> - try - fs.accessSync updateExe, fs.R_OK - return true - catch - return false diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.js b/atom/browser/api/lib/auto-updater/squirrel-update-win.js new file mode 100644 index 000000000000..8fe8a3786db9 --- /dev/null +++ b/atom/browser/api/lib/auto-updater/squirrel-update-win.js @@ -0,0 +1,100 @@ +const fs = require('fs'); +const path = require('path'); +const spawn = require('child_process').spawn; + +// i.e. my-app/app-0.1.13/ +const appFolder = path.dirname(process.execPath); + +// i.e. my-app/Update.exe +const updateExe = path.resolve(appFolder, '..', 'Update.exe'); + +const exeName = path.basename(process.execPath); + +// Spawn a command and invoke the callback when it completes with an error +// and the output from standard out. +var spawnUpdate = function(args, detached, callback) { + var error, error1, errorEmitted, spawnedProcess, stderr, stdout; + try { + spawnedProcess = spawn(updateExe, args, { + detached: detached + }); + } catch (error1) { + error = error1; + + // Shouldn't happen, but still guard it. + process.nextTick(function() { + return callback(error); + }); + return; + } + stdout = ''; + stderr = ''; + spawnedProcess.stdout.on('data', function(data) { + return stdout += data; + }); + spawnedProcess.stderr.on('data', function(data) { + return stderr += data; + }); + errorEmitted = false; + spawnedProcess.on('error', function(error) { + errorEmitted = true; + return callback(error); + }); + return spawnedProcess.on('exit', function(code, signal) { + + // We may have already emitted an error. + if (errorEmitted) { + return; + } + + // Process terminated with error. + if (code !== 0) { + return callback("Command failed: " + (signal != null ? signal : code) + "\n" + stderr); + } + + // Success. + return callback(null, stdout); + }); +}; + +// Start an instance of the installed app. +exports.processStart = function(callback) { + return spawnUpdate(['--processStart', exeName], true, function() {}); +}; + +// Download the releases specified by the URL and write new results to stdout. +exports.download = function(updateURL, callback) { + return spawnUpdate(['--download', updateURL], false, function(error, stdout) { + var error1, json, ref, ref1, update; + if (error != null) { + return callback(error); + } + try { + + // Last line of output is the JSON details about the releases + json = stdout.trim().split('\n').pop(); + update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === "function" ? ref1.pop() : void 0 : void 0 : void 0; + } catch (error1) { + return callback("Invalid result:\n" + stdout); + } + return callback(null, update); + }); +}; + + +// Update the application to the latest remote version specified by URL. +exports.update = function(updateURL, callback) { + return spawnUpdate(['--update', updateURL], false, callback); +}; + + +// Is the Update.exe installed with the current application? +exports.supported = function() { + var error1; + try { + fs.accessSync(updateExe, fs.R_OK); + return true; + } catch (error1) { + return false; + } +}; diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee deleted file mode 100644 index 92a230ba45af..000000000000 --- a/atom/browser/api/lib/browser-window.coffee +++ /dev/null @@ -1,113 +0,0 @@ -{ipcMain, deprecate} = require 'electron' -{EventEmitter} = require 'events' - -{BrowserWindow} = process.atomBinding 'window' -BrowserWindow::__proto__ = EventEmitter.prototype - -BrowserWindow::_init = -> - {app} = require 'electron' # avoid recursive require. - - # Simulate the application menu on platforms other than OS X. - if process.platform isnt 'darwin' - menu = app.getApplicationMenu() - @setMenu menu if menu? - - # Make new windows requested by links behave like "window.open" - @webContents.on '-new-window', (event, url, frameName) -> - options = show: true, width: 800, height: 600 - ipcMain.emit 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, options - - # window.resizeTo(...) - # window.moveTo(...) - @webContents.on 'move', (event, size) => - @setBounds size - - # Hide the auto-hide menu when webContents is focused. - @webContents.on 'activate', => - if process.platform isnt 'darwin' and @isMenuBarAutoHide() and @isMenuBarVisible() - @setMenuBarVisibility false - - # Forward the crashed event. - @webContents.on 'crashed', => - @emit 'crashed' - - # Change window title to page title. - @webContents.on 'page-title-updated', (event, title, explicitSet) => - @emit 'page-title-updated', event, title - @setTitle title unless event.defaultPrevented - - # Sometimes the webContents doesn't get focus when window is shown, so we have - # to force focusing on webContents in this case. The safest way is to focus it - # when we first start to load URL, if we do it earlier it won't have effect, - # if we do it later we might move focus in the page. - # Though this hack is only needed on OS X when the app is launched from - # Finder, we still do it on all platforms in case of other bugs we don't know. - @webContents.once 'load-url', -> - @focus() - - # Redirect focus/blur event to app instance too. - @on 'blur', (event) => - app.emit 'browser-window-blur', event, this - @on 'focus', (event) => - app.emit 'browser-window-focus', event, this - - # Notify the creation of the window. - app.emit 'browser-window-created', {}, this - - # Be compatible with old APIs. - @webContents.on 'devtools-focused', => @emit 'devtools-focused' - @webContents.on 'devtools-opened', => @emit 'devtools-opened' - @webContents.on 'devtools-closed', => @emit 'devtools-closed' - Object.defineProperty this, 'devToolsWebContents', - enumerable: true, - configurable: false, - get: -> @webContents.devToolsWebContents - -BrowserWindow.getFocusedWindow = -> - windows = BrowserWindow.getAllWindows() - return window for window in windows when window.isFocused() - -BrowserWindow.fromWebContents = (webContents) -> - windows = BrowserWindow.getAllWindows() - return window for window in windows when window.webContents?.equal webContents - -BrowserWindow.fromDevToolsWebContents = (webContents) -> - windows = BrowserWindow.getAllWindows() - return window for window in windows when window.devToolsWebContents?.equal webContents - -# Helpers. -BrowserWindow::loadURL = -> @webContents.loadURL.apply @webContents, arguments -BrowserWindow::getURL = -> @webContents.getURL() -BrowserWindow::reload = -> @webContents.reload.apply @webContents, arguments -BrowserWindow::send = -> @webContents.send.apply @webContents, arguments -BrowserWindow::openDevTools = -> @webContents.openDevTools.apply @webContents, arguments -BrowserWindow::closeDevTools = -> @webContents.closeDevTools() -BrowserWindow::isDevToolsOpened = -> @webContents.isDevToolsOpened() -BrowserWindow::isDevToolsFocused = -> @webContents.isDevToolsFocused() -BrowserWindow::toggleDevTools = -> @webContents.toggleDevTools() -BrowserWindow::inspectElement = -> @webContents.inspectElement.apply @webContents, arguments -BrowserWindow::inspectServiceWorker = -> @webContents.inspectServiceWorker() - -# Deprecated. -deprecate.member BrowserWindow, 'undo', 'webContents' -deprecate.member BrowserWindow, 'redo', 'webContents' -deprecate.member BrowserWindow, 'cut', 'webContents' -deprecate.member BrowserWindow, 'copy', 'webContents' -deprecate.member BrowserWindow, 'paste', 'webContents' -deprecate.member BrowserWindow, 'selectAll', 'webContents' -deprecate.member BrowserWindow, 'reloadIgnoringCache', 'webContents' -deprecate.member BrowserWindow, 'isLoading', 'webContents' -deprecate.member BrowserWindow, 'isWaitingForResponse', 'webContents' -deprecate.member BrowserWindow, 'stop', 'webContents' -deprecate.member BrowserWindow, 'isCrashed', 'webContents' -deprecate.member BrowserWindow, 'print', 'webContents' -deprecate.member BrowserWindow, 'printToPDF', 'webContents' -deprecate.rename BrowserWindow, 'restart', 'reload' -deprecate.rename BrowserWindow, 'loadUrl', 'loadURL' -deprecate.rename BrowserWindow, 'getUrl', 'getURL' -BrowserWindow::executeJavaScriptInDevTools = deprecate 'executeJavaScriptInDevTools', 'devToolsWebContents.executeJavaScript', (code) -> - @devToolsWebContents?.executeJavaScript code -BrowserWindow::getPageTitle = deprecate 'getPageTitle', 'webContents.getTitle', -> - @webContents?.getTitle() - -module.exports = BrowserWindow diff --git a/atom/browser/api/lib/browser-window.js b/atom/browser/api/lib/browser-window.js new file mode 100644 index 000000000000..1268b0f80127 --- /dev/null +++ b/atom/browser/api/lib/browser-window.js @@ -0,0 +1,241 @@ +const ipcMain = require('electron').ipcMain; +const deprecate = require('electron').deprecate; +const EventEmitter = require('events').EventEmitter; +const BrowserWindow = process.atomBinding('window').BrowserWindow; + +BrowserWindow.prototype.__proto__ = EventEmitter.prototype; + +BrowserWindow.prototype._init = function() { + + // avoid recursive require. + var app, menu; + app = require('electron').app; + + // Simulate the application menu on platforms other than OS X. + if (process.platform !== 'darwin') { + menu = app.getApplicationMenu(); + if (menu != null) { + this.setMenu(menu); + } + } + + // Make new windows requested by links behave like "window.open" + this.webContents.on('-new-window', function(event, url, frameName) { + var options; + options = { + show: true, + width: 800, + height: 600 + }; + return ipcMain.emit('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, options); + }); + + // window.resizeTo(...) + // window.moveTo(...) + this.webContents.on('move', (function(_this) { + return function(event, size) { + return _this.setBounds(size); + }; + })(this)); + + // Hide the auto-hide menu when webContents is focused. + this.webContents.on('activate', (function(_this) { + return function() { + if (process.platform !== 'darwin' && _this.isMenuBarAutoHide() && _this.isMenuBarVisible()) { + return _this.setMenuBarVisibility(false); + } + }; + })(this)); + + // Forward the crashed event. + this.webContents.on('crashed', (function(_this) { + return function() { + return _this.emit('crashed'); + }; + })(this)); + + // Change window title to page title. + this.webContents.on('page-title-updated', (function(_this) { + return function(event, title, explicitSet) { + _this.emit('page-title-updated', event, title); + if (!event.defaultPrevented) { + return _this.setTitle(title); + } + }; + })(this)); + + // Sometimes the webContents doesn't get focus when window is shown, so we have + // to force focusing on webContents in this case. The safest way is to focus it + // when we first start to load URL, if we do it earlier it won't have effect, + // if we do it later we might move focus in the page. + // Though this hack is only needed on OS X when the app is launched from + // Finder, we still do it on all platforms in case of other bugs we don't know. + this.webContents.once('load-url', function() { + return this.focus(); + }); + + // Redirect focus/blur event to app instance too. + this.on('blur', (function(_this) { + return function(event) { + return app.emit('browser-window-blur', event, _this); + }; + })(this)); + this.on('focus', (function(_this) { + return function(event) { + return app.emit('browser-window-focus', event, _this); + }; + })(this)); + + // Notify the creation of the window. + app.emit('browser-window-created', {}, this); + + // Be compatible with old APIs. + this.webContents.on('devtools-focused', (function(_this) { + return function() { + return _this.emit('devtools-focused'); + }; + })(this)); + this.webContents.on('devtools-opened', (function(_this) { + return function() { + return _this.emit('devtools-opened'); + }; + })(this)); + this.webContents.on('devtools-closed', (function(_this) { + return function() { + return _this.emit('devtools-closed'); + }; + })(this)); + return Object.defineProperty(this, 'devToolsWebContents', { + enumerable: true, + configurable: false, + get: function() { + return this.webContents.devToolsWebContents; + } + }); +}; + +BrowserWindow.getFocusedWindow = function() { + var i, len, window, windows; + windows = BrowserWindow.getAllWindows(); + for (i = 0, len = windows.length; i < len; i++) { + window = windows[i]; + if (window.isFocused()) { + return window; + } + } + return null; +}; + +BrowserWindow.fromWebContents = function(webContents) { + var i, len, ref1, window, windows; + windows = BrowserWindow.getAllWindows(); + for (i = 0, len = windows.length; i < len; i++) { + window = windows[i]; + if ((ref1 = window.webContents) != null ? ref1.equal(webContents) : void 0) { + return window; + } + } +}; + +BrowserWindow.fromDevToolsWebContents = function(webContents) { + var i, len, ref1, window, windows; + windows = BrowserWindow.getAllWindows(); + for (i = 0, len = windows.length; i < len; i++) { + window = windows[i]; + if ((ref1 = window.devToolsWebContents) != null ? ref1.equal(webContents) : void 0) { + return window; + } + } +}; + +// Helpers. + +BrowserWindow.prototype.loadURL = function() { + return this.webContents.loadURL.apply(this.webContents, arguments); +}; + +BrowserWindow.prototype.getURL = function() { + return this.webContents.getURL(); +}; + +BrowserWindow.prototype.reload = function() { + return this.webContents.reload.apply(this.webContents, arguments); +}; + +BrowserWindow.prototype.send = function() { + return this.webContents.send.apply(this.webContents, arguments); +}; + +BrowserWindow.prototype.openDevTools = function() { + return this.webContents.openDevTools.apply(this.webContents, arguments); +}; + +BrowserWindow.prototype.closeDevTools = function() { + return this.webContents.closeDevTools(); +}; + +BrowserWindow.prototype.isDevToolsOpened = function() { + return this.webContents.isDevToolsOpened(); +}; + +BrowserWindow.prototype.isDevToolsFocused = function() { + return this.webContents.isDevToolsFocused(); +}; + +BrowserWindow.prototype.toggleDevTools = function() { + return this.webContents.toggleDevTools(); +}; + +BrowserWindow.prototype.inspectElement = function() { + return this.webContents.inspectElement.apply(this.webContents, arguments); +}; + +BrowserWindow.prototype.inspectServiceWorker = function() { + return this.webContents.inspectServiceWorker(); +}; + +// Deprecated. + +deprecate.member(BrowserWindow, 'undo', 'webContents'); + +deprecate.member(BrowserWindow, 'redo', 'webContents'); + +deprecate.member(BrowserWindow, 'cut', 'webContents'); + +deprecate.member(BrowserWindow, 'copy', 'webContents'); + +deprecate.member(BrowserWindow, 'paste', 'webContents'); + +deprecate.member(BrowserWindow, 'selectAll', 'webContents'); + +deprecate.member(BrowserWindow, 'reloadIgnoringCache', 'webContents'); + +deprecate.member(BrowserWindow, 'isLoading', 'webContents'); + +deprecate.member(BrowserWindow, 'isWaitingForResponse', 'webContents'); + +deprecate.member(BrowserWindow, 'stop', 'webContents'); + +deprecate.member(BrowserWindow, 'isCrashed', 'webContents'); + +deprecate.member(BrowserWindow, 'print', 'webContents'); + +deprecate.member(BrowserWindow, 'printToPDF', 'webContents'); + +deprecate.rename(BrowserWindow, 'restart', 'reload'); + +deprecate.rename(BrowserWindow, 'loadUrl', 'loadURL'); + +deprecate.rename(BrowserWindow, 'getUrl', 'getURL'); + +BrowserWindow.prototype.executeJavaScriptInDevTools = deprecate('executeJavaScriptInDevTools', 'devToolsWebContents.executeJavaScript', function(code) { + var ref1; + return (ref1 = this.devToolsWebContents) != null ? ref1.executeJavaScript(code) : void 0; +}); + +BrowserWindow.prototype.getPageTitle = deprecate('getPageTitle', 'webContents.getTitle', function() { + var ref1; + return (ref1 = this.webContents) != null ? ref1.getTitle() : void 0; +}); + +module.exports = BrowserWindow; diff --git a/atom/browser/api/lib/content-tracing.coffee b/atom/browser/api/lib/content-tracing.coffee deleted file mode 100644 index 08cd36e4aa59..000000000000 --- a/atom/browser/api/lib/content-tracing.coffee +++ /dev/null @@ -1 +0,0 @@ -module.exports = process.atomBinding 'content_tracing' diff --git a/atom/browser/api/lib/content-tracing.js b/atom/browser/api/lib/content-tracing.js new file mode 100644 index 000000000000..b00c5666df59 --- /dev/null +++ b/atom/browser/api/lib/content-tracing.js @@ -0,0 +1 @@ +module.exports = process.atomBinding('content_tracing'); diff --git a/atom/browser/api/lib/dialog.coffee b/atom/browser/api/lib/dialog.coffee deleted file mode 100644 index f10ce58c17f5..000000000000 --- a/atom/browser/api/lib/dialog.coffee +++ /dev/null @@ -1,125 +0,0 @@ -{app, BrowserWindow} = require 'electron' - -binding = process.atomBinding 'dialog' -v8Util = process.atomBinding 'v8_util' - -fileDialogProperties = - openFile: 1 << 0 - openDirectory: 1 << 1 - multiSelections: 1 << 2 - createDirectory: 1 << 3 - -messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] - -messageBoxOptions = - noLink: 1 << 0 - -parseArgs = (window, options, callback) -> - unless window is null or window?.constructor is BrowserWindow - # Shift. - callback = options - options = window - window = null - if not callback? and typeof options is 'function' - # Shift. - callback = options - options = null - [window, options, callback] - -checkAppInitialized = -> - throw new Error('dialog module can only be used after app is ready') unless app.isReady() - -module.exports = - showOpenDialog: (args...) -> - checkAppInitialized() - [window, options, callback] = parseArgs args... - - options ?= title: 'Open', properties: ['openFile'] - options.properties ?= ['openFile'] - throw new TypeError('Properties need to be array') unless Array.isArray options.properties - - properties = 0 - for prop, value of fileDialogProperties - properties |= value if prop in options.properties - - options.title ?= '' - options.defaultPath ?= '' - options.filters ?= [] - - wrappedCallback = - if typeof callback is 'function' - (success, result) -> callback(if success then result) - else - null - - binding.showOpenDialog String(options.title), - String(options.defaultPath), - options.filters - properties, - window, - wrappedCallback - - showSaveDialog: (args...) -> - checkAppInitialized() - [window, options, callback] = parseArgs args... - - options ?= title: 'Save' - options.title ?= '' - options.defaultPath ?= '' - options.filters ?= [] - - wrappedCallback = - if typeof callback is 'function' - (success, result) -> callback(if success then result) - else - null - - binding.showSaveDialog String(options.title), - String(options.defaultPath), - options.filters - window, - wrappedCallback - - showMessageBox: (args...) -> - checkAppInitialized() - [window, options, callback] = parseArgs args... - - options ?= type: 'none' - options.type ?= 'none' - messageBoxType = messageBoxTypes.indexOf options.type - throw new TypeError('Invalid message box type') unless messageBoxType > -1 - - throw new TypeError('Buttons need to be array') unless Array.isArray options.buttons - - options.title ?= '' - options.message ?= '' - options.detail ?= '' - options.icon ?= null - - # Choose a default button to get selected when dialog is cancelled. - unless options.cancelId? - options.cancelId = 0 - for text, i in options.buttons - if text.toLowerCase() in ['cancel', 'no'] - options.cancelId = i - break - - flags = if options.noLink then messageBoxOptions.noLink else 0 - - binding.showMessageBox messageBoxType, - options.buttons, - options.cancelId, - flags, - options.title, - options.message, - options.detail, - options.icon, - window, - callback - - showErrorBox: (args...) -> - binding.showErrorBox args... - -# Mark standard asynchronous functions. -for api in ['showMessageBox', 'showOpenDialog', 'showSaveDialog'] - v8Util.setHiddenValue module.exports[api], 'asynchronous', true diff --git a/atom/browser/api/lib/dialog.js b/atom/browser/api/lib/dialog.js new file mode 100644 index 000000000000..b77773be2096 --- /dev/null +++ b/atom/browser/api/lib/dialog.js @@ -0,0 +1,170 @@ +const app = require('electron').app; +const BrowserWindow = require('electron').BrowserWindow; +const binding = process.atomBinding('dialog'); +const v8Util = process.atomBinding('v8_util'); + +var slice = [].slice; +var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +var fileDialogProperties = { + openFile: 1 << 0, + openDirectory: 1 << 1, + multiSelections: 1 << 2, + createDirectory: 1 << 3 +}; + +var messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']; + +var messageBoxOptions = { + noLink: 1 << 0 +}; + +var parseArgs = function(window, options, callback) { + if (!(window === null || (window != null ? window.constructor : void 0) === BrowserWindow)) { + // Shift. + callback = options; + options = window; + window = null; + } + if ((callback == null) && typeof options === 'function') { + // Shift. + callback = options; + options = null; + } + return [window, options, callback]; +}; + +var checkAppInitialized = function() { + if (!app.isReady()) { + throw new Error('dialog module can only be used after app is ready'); + } +}; + +module.exports = { + showOpenDialog: function() { + var args, callback, options, prop, properties, ref1, value, window, wrappedCallback; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + checkAppInitialized(); + ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; + if (options == null) { + options = { + title: 'Open', + properties: ['openFile'] + }; + } + if (options.properties == null) { + options.properties = ['openFile']; + } + if (!Array.isArray(options.properties)) { + throw new TypeError('Properties need to be array'); + } + properties = 0; + for (prop in fileDialogProperties) { + value = fileDialogProperties[prop]; + if (indexOf.call(options.properties, prop) >= 0) { + properties |= value; + } + } + if (options.title == null) { + options.title = ''; + } + if (options.defaultPath == null) { + options.defaultPath = ''; + } + if (options.filters == null) { + options.filters = []; + } + wrappedCallback = typeof callback === 'function' ? function(success, result) { + return callback(success ? result : void 0); + } : null; + return binding.showOpenDialog(String(options.title), String(options.defaultPath), options.filters, properties, window, wrappedCallback); + }, + showSaveDialog: function() { + var args, callback, options, ref1, window, wrappedCallback; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + checkAppInitialized(); + ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; + if (options == null) { + options = { + title: 'Save' + }; + } + if (options.title == null) { + options.title = ''; + } + if (options.defaultPath == null) { + options.defaultPath = ''; + } + if (options.filters == null) { + options.filters = []; + } + wrappedCallback = typeof callback === 'function' ? function(success, result) { + return callback(success ? result : void 0); + } : null; + return binding.showSaveDialog(String(options.title), String(options.defaultPath), options.filters, window, wrappedCallback); + }, + showMessageBox: function() { + var args, callback, flags, i, j, len, messageBoxType, options, ref1, ref2, ref3, text, window; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + checkAppInitialized(); + ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; + if (options == null) { + options = { + type: 'none' + }; + } + if (options.type == null) { + options.type = 'none'; + } + messageBoxType = messageBoxTypes.indexOf(options.type); + if (!(messageBoxType > -1)) { + throw new TypeError('Invalid message box type'); + } + if (!Array.isArray(options.buttons)) { + throw new TypeError('Buttons need to be array'); + } + if (options.title == null) { + options.title = ''; + } + if (options.message == null) { + options.message = ''; + } + if (options.detail == null) { + options.detail = ''; + } + if (options.icon == null) { + options.icon = null; + } + if (options.defaultId == null) { + options.defaultId = -1; + } + + // Choose a default button to get selected when dialog is cancelled. + if (options.cancelId == null) { + options.cancelId = 0; + ref2 = options.buttons; + for (i = j = 0, len = ref2.length; j < len; i = ++j) { + text = ref2[i]; + if ((ref3 = text.toLowerCase()) === 'cancel' || ref3 === 'no') { + options.cancelId = i; + break; + } + } + } + flags = options.noLink ? messageBoxOptions.noLink : 0; + return binding.showMessageBox(messageBoxType, options.buttons, options.defaultId, options.cancelId, flags, options.title, options.message, options.detail, options.icon, window, callback); + }, + showErrorBox: function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return binding.showErrorBox.apply(binding, args); + } +}; + +// Mark standard asynchronous functions. +var ref1 = ['showMessageBox', 'showOpenDialog', 'showSaveDialog']; +var j, len, api; +for (j = 0, len = ref1.length; j < len; j++) { + api = ref1[j]; + v8Util.setHiddenValue(module.exports[api], 'asynchronous', true); +} diff --git a/atom/browser/api/lib/exports/electron.coffee b/atom/browser/api/lib/exports/electron.coffee deleted file mode 100644 index 9c61a5507061..000000000000 --- a/atom/browser/api/lib/exports/electron.coffee +++ /dev/null @@ -1,57 +0,0 @@ -common = require '../../../../common/api/lib/exports/electron' - -# Import common modules. -common.defineProperties exports - -Object.defineProperties exports, - # Browser side modules, please sort with alphabet order. - app: - enumerable: true - get: -> require '../app' - autoUpdater: - enumerable: true - get: -> require '../auto-updater' - BrowserWindow: - enumerable: true - get: -> require '../browser-window' - contentTracing: - enumerable: true - get: -> require '../content-tracing' - dialog: - enumerable: true - get: -> require '../dialog' - ipcMain: - enumerable: true - get: -> require '../ipc-main' - globalShortcut: - enumerable: true - get: -> require '../global-shortcut' - Menu: - enumerable: true - get: -> require '../menu' - MenuItem: - enumerable: true - get: -> require '../menu-item' - powerMonitor: - enumerable: true - get: -> require '../power-monitor' - powerSaveBlocker: - enumerable: true - get: -> require '../power-save-blocker' - protocol: - enumerable: true - get: -> require '../protocol' - screen: - enumerable: true - get: -> require '../screen' - session: - enumerable: true - get: -> require '../session' - Tray: - enumerable: true - get: -> require '../tray' - # The internal modules, invisible unless you know their names. - NavigationController: - get: -> require '../navigation-controller' - webContents: - get: -> require '../web-contents' diff --git a/atom/browser/api/lib/exports/electron.js b/atom/browser/api/lib/exports/electron.js new file mode 100644 index 000000000000..7f97fcdbc4cf --- /dev/null +++ b/atom/browser/api/lib/exports/electron.js @@ -0,0 +1,112 @@ +const common = require('../../../../common/api/lib/exports/electron'); + + +// Import common modules. +common.defineProperties(exports); + +Object.defineProperties(exports, { + + // Browser side modules, please sort with alphabet order. + app: { + enumerable: true, + get: function() { + return require('../app'); + } + }, + autoUpdater: { + enumerable: true, + get: function() { + return require('../auto-updater'); + } + }, + BrowserWindow: { + enumerable: true, + get: function() { + return require('../browser-window'); + } + }, + contentTracing: { + enumerable: true, + get: function() { + return require('../content-tracing'); + } + }, + dialog: { + enumerable: true, + get: function() { + return require('../dialog'); + } + }, + ipcMain: { + enumerable: true, + get: function() { + return require('../ipc-main'); + } + }, + globalShortcut: { + enumerable: true, + get: function() { + return require('../global-shortcut'); + } + }, + Menu: { + enumerable: true, + get: function() { + return require('../menu'); + } + }, + MenuItem: { + enumerable: true, + get: function() { + return require('../menu-item'); + } + }, + powerMonitor: { + enumerable: true, + get: function() { + return require('../power-monitor'); + } + }, + powerSaveBlocker: { + enumerable: true, + get: function() { + return require('../power-save-blocker'); + } + }, + protocol: { + enumerable: true, + get: function() { + return require('../protocol'); + } + }, + screen: { + enumerable: true, + get: function() { + return require('../screen'); + } + }, + session: { + enumerable: true, + get: function() { + return require('../session'); + } + }, + Tray: { + enumerable: true, + get: function() { + return require('../tray'); + } + }, + + // The internal modules, invisible unless you know their names. + NavigationController: { + get: function() { + return require('../navigation-controller'); + } + }, + webContents: { + get: function() { + return require('../web-contents'); + } + } +}); diff --git a/atom/browser/api/lib/global-shortcut.coffee b/atom/browser/api/lib/global-shortcut.coffee deleted file mode 100644 index 56c3e128767e..000000000000 --- a/atom/browser/api/lib/global-shortcut.coffee +++ /dev/null @@ -1,3 +0,0 @@ -{globalShortcut} = process.atomBinding 'global_shortcut' - -module.exports = globalShortcut diff --git a/atom/browser/api/lib/global-shortcut.js b/atom/browser/api/lib/global-shortcut.js new file mode 100644 index 000000000000..daca6c232796 --- /dev/null +++ b/atom/browser/api/lib/global-shortcut.js @@ -0,0 +1,5 @@ +var globalShortcut; + +globalShortcut = process.atomBinding('global_shortcut').globalShortcut; + +module.exports = globalShortcut; diff --git a/atom/browser/api/lib/ipc-main.coffee b/atom/browser/api/lib/ipc-main.coffee deleted file mode 100644 index 8021544479d2..000000000000 --- a/atom/browser/api/lib/ipc-main.coffee +++ /dev/null @@ -1,3 +0,0 @@ -{EventEmitter} = require 'events' - -module.exports = new EventEmitter diff --git a/atom/browser/api/lib/ipc-main.js b/atom/browser/api/lib/ipc-main.js new file mode 100644 index 000000000000..e253e03eaabe --- /dev/null +++ b/atom/browser/api/lib/ipc-main.js @@ -0,0 +1,3 @@ +const EventEmitter = require('events').EventEmitter; + +module.exports = new EventEmitter; diff --git a/atom/browser/api/lib/ipc.coffee b/atom/browser/api/lib/ipc.coffee deleted file mode 100644 index 018cb6bb0437..000000000000 --- a/atom/browser/api/lib/ipc.coffee +++ /dev/null @@ -1,6 +0,0 @@ -{deprecate, ipcMain} = require 'electron' - -# This module is deprecated, we mirror everything from ipcMain. -deprecate.warn 'ipc module', 'require("electron").ipcMain' - -module.exports = ipcMain diff --git a/atom/browser/api/lib/ipc.js b/atom/browser/api/lib/ipc.js new file mode 100644 index 000000000000..6e9715154234 --- /dev/null +++ b/atom/browser/api/lib/ipc.js @@ -0,0 +1,7 @@ +const deprecate = require('electron').deprecate; +const ipcMain = require('electron').ipcMain; + +// This module is deprecated, we mirror everything from ipcMain. +deprecate.warn('ipc module', 'require("electron").ipcMain'); + +module.exports = ipcMain; diff --git a/atom/browser/api/lib/menu-item.coffee b/atom/browser/api/lib/menu-item.coffee deleted file mode 100644 index 242a48f54d88..000000000000 --- a/atom/browser/api/lib/menu-item.coffee +++ /dev/null @@ -1,73 +0,0 @@ -v8Util = process.atomBinding 'v8_util' - -nextCommandId = 0 - -# Maps role to methods of webContents -rolesMap = - undo: 'undo' - redo: 'redo' - cut: 'cut' - copy: 'copy' - paste: 'paste' - selectall: 'selectAll' - minimize: 'minimize' - close: 'close' - -# Maps methods that should be called directly on the BrowserWindow instance -methodInBrowserWindow = - minimize: true - close: true - -class MenuItem - @types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] - - constructor: (options) -> - {Menu} = require 'electron' - - {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked, @submenu} = options - - if @submenu? and @submenu.constructor isnt Menu - @submenu = Menu.buildFromTemplate @submenu - @type = 'submenu' if not @type? and @submenu? - throw new Error('Invalid submenu') if @type is 'submenu' and @submenu?.constructor isnt Menu - - @overrideReadOnlyProperty 'type', 'normal' - @overrideReadOnlyProperty 'role' - @overrideReadOnlyProperty 'accelerator' - @overrideReadOnlyProperty 'icon' - @overrideReadOnlyProperty 'submenu' - @overrideProperty 'label', '' - @overrideProperty 'sublabel', '' - @overrideProperty 'enabled', true - @overrideProperty 'visible', true - @overrideProperty 'checked', false - - throw new Error("Unknown menu type #{@type}") if MenuItem.types.indexOf(@type) is -1 - - @commandId = ++nextCommandId - @click = (focusedWindow) => - # Manually flip the checked flags when clicked. - @checked = !@checked if @type in ['checkbox', 'radio'] - - if @role and rolesMap[@role] and process.platform isnt 'darwin' and focusedWindow? - methodName = rolesMap[@role] - if methodInBrowserWindow[methodName] - focusedWindow[methodName]() - else - focusedWindow.webContents?[methodName]() - else if typeof click is 'function' - click this, focusedWindow - else if typeof @selector is 'string' - Menu.sendActionToFirstResponder @selector - - overrideProperty: (name, defaultValue=null) -> - this[name] ?= defaultValue - - overrideReadOnlyProperty: (name, defaultValue=null) -> - this[name] ?= defaultValue - Object.defineProperty this, name, - enumerable: true - writable: false - value: this[name] - -module.exports = MenuItem diff --git a/atom/browser/api/lib/menu-item.js b/atom/browser/api/lib/menu-item.js new file mode 100644 index 000000000000..80fdd46fd89b --- /dev/null +++ b/atom/browser/api/lib/menu-item.js @@ -0,0 +1,104 @@ +var MenuItem, methodInBrowserWindow, nextCommandId, rolesMap, v8Util; + +v8Util = process.atomBinding('v8_util'); + +nextCommandId = 0; + +// Maps role to methods of webContents +rolesMap = { + undo: 'undo', + redo: 'redo', + cut: 'cut', + copy: 'copy', + paste: 'paste', + selectall: 'selectAll', + minimize: 'minimize', + close: 'close' +}; + +// Maps methods that should be called directly on the BrowserWindow instance +methodInBrowserWindow = { + minimize: true, + close: true +}; + +MenuItem = (function() { + MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio']; + + function MenuItem(options) { + var click, ref; + const Menu = require('electron').Menu; + click = options.click, this.selector = options.selector, this.type = options.type, this.role = options.role, this.label = options.label, this.sublabel = options.sublabel, this.accelerator = options.accelerator, this.icon = options.icon, this.enabled = options.enabled, this.visible = options.visible, this.checked = options.checked, this.submenu = options.submenu; + if ((this.submenu != null) && this.submenu.constructor !== Menu) { + this.submenu = Menu.buildFromTemplate(this.submenu); + } + if ((this.type == null) && (this.submenu != null)) { + this.type = 'submenu'; + } + if (this.type === 'submenu' && ((ref = this.submenu) != null ? ref.constructor : void 0) !== Menu) { + throw new Error('Invalid submenu'); + } + this.overrideReadOnlyProperty('type', 'normal'); + this.overrideReadOnlyProperty('role'); + this.overrideReadOnlyProperty('accelerator'); + this.overrideReadOnlyProperty('icon'); + this.overrideReadOnlyProperty('submenu'); + this.overrideProperty('label', ''); + this.overrideProperty('sublabel', ''); + this.overrideProperty('enabled', true); + this.overrideProperty('visible', true); + this.overrideProperty('checked', false); + if (MenuItem.types.indexOf(this.type) === -1) { + throw new Error("Unknown menu type " + this.type); + } + this.commandId = ++nextCommandId; + this.click = (function(_this) { + return function(focusedWindow) { + + // Manually flip the checked flags when clicked. + var methodName, ref1, ref2; + if ((ref1 = _this.type) === 'checkbox' || ref1 === 'radio') { + _this.checked = !_this.checked; + } + if (_this.role && rolesMap[_this.role] && process.platform !== 'darwin' && (focusedWindow != null)) { + methodName = rolesMap[_this.role]; + if (methodInBrowserWindow[methodName]) { + return focusedWindow[methodName](); + } else { + return (ref2 = focusedWindow.webContents) != null ? ref2[methodName]() : void 0; + } + } else if (typeof click === 'function') { + return click(_this, focusedWindow); + } else if (typeof _this.selector === 'string') { + return Menu.sendActionToFirstResponder(_this.selector); + } + }; + })(this); + } + + MenuItem.prototype.overrideProperty = function(name, defaultValue) { + if (defaultValue == null) { + defaultValue = null; + } + return this[name] != null ? this[name] : this[name] = defaultValue; + }; + + MenuItem.prototype.overrideReadOnlyProperty = function(name, defaultValue) { + if (defaultValue == null) { + defaultValue = null; + } + if (this[name] == null) { + this[name] = defaultValue; + } + return Object.defineProperty(this, name, { + enumerable: true, + writable: false, + value: this[name] + }); + }; + + return MenuItem; + +})(); + +module.exports = MenuItem; diff --git a/atom/browser/api/lib/menu.coffee b/atom/browser/api/lib/menu.coffee deleted file mode 100644 index d81c345f43a0..000000000000 --- a/atom/browser/api/lib/menu.coffee +++ /dev/null @@ -1,178 +0,0 @@ -{BrowserWindow, MenuItem} = require 'electron' -{EventEmitter} = require 'events' - -v8Util = process.atomBinding 'v8_util' -bindings = process.atomBinding 'menu' - -# Automatically generated radio menu item's group id. -nextGroupId = 0 - -# Search between seperators to find a radio menu item and return its group id, -# otherwise generate a group id. -generateGroupId = (items, pos) -> - if pos > 0 - for i in [pos - 1..0] - item = items[i] - return item.groupId if item.type is 'radio' - break if item.type is 'separator' - else if pos < items.length - for i in [pos..items.length - 1] - item = items[i] - return item.groupId if item.type is 'radio' - break if item.type is 'separator' - ++nextGroupId - -# Returns the index of item according to |id|. -indexOfItemById = (items, id) -> - return i for item, i in items when item.id is id - -1 - -# Returns the index of where to insert the item according to |position|. -indexToInsertByPosition = (items, position) -> - return items.length unless position - - [query, id] = position.split '=' - insertIndex = indexOfItemById items, id - if insertIndex is -1 and query isnt 'endof' - console.warn "Item with id '#{id}' is not found" - return items.length - - switch query - when 'after' - insertIndex++ - when 'endof' - # If the |id| doesn't exist, then create a new group with the |id|. - if insertIndex is -1 - items.push id: id, type: 'separator' - insertIndex = items.length - 1 - - # Find the end of the group. - insertIndex++ - while insertIndex < items.length and items[insertIndex].type isnt 'separator' - insertIndex++ - - insertIndex - -Menu = bindings.Menu -Menu::__proto__ = EventEmitter.prototype - -Menu::_init = -> - @commandsMap = {} - @groupsMap = {} - @items = [] - @delegate = - isCommandIdChecked: (commandId) => @commandsMap[commandId]?.checked - isCommandIdEnabled: (commandId) => @commandsMap[commandId]?.enabled - isCommandIdVisible: (commandId) => @commandsMap[commandId]?.visible - getAcceleratorForCommandId: (commandId) => @commandsMap[commandId]?.accelerator - getIconForCommandId: (commandId) => @commandsMap[commandId]?.icon - executeCommand: (commandId) => - @commandsMap[commandId]?.click BrowserWindow.getFocusedWindow() - menuWillShow: => - # Make sure radio groups have at least one menu item seleted. - for id, group of @groupsMap - checked = false - for radioItem in group when radioItem.checked - checked = true - break - v8Util.setHiddenValue group[0], 'checked', true unless checked - -Menu::popup = (window, x, y) -> - unless window?.constructor is BrowserWindow - # Shift. - y = x - x = window - window = BrowserWindow.getFocusedWindow() - if x? and y? - @_popupAt(window, x, y) - else - @_popup window - -Menu::append = (item) -> - @insert @getItemCount(), item - -Menu::insert = (pos, item) -> - throw new TypeError('Invalid item') unless item?.constructor is MenuItem - - switch item.type - when 'normal' then @insertItem pos, item.commandId, item.label - when 'checkbox' then @insertCheckItem pos, item.commandId, item.label - when 'separator' then @insertSeparator pos - when 'submenu' then @insertSubMenu pos, item.commandId, item.label, item.submenu - when 'radio' - # Grouping radio menu items. - item.overrideReadOnlyProperty 'groupId', generateGroupId(@items, pos) - @groupsMap[item.groupId] ?= [] - @groupsMap[item.groupId].push item - - # Setting a radio menu item should flip other items in the group. - v8Util.setHiddenValue item, 'checked', item.checked - Object.defineProperty item, 'checked', - enumerable: true - get: -> v8Util.getHiddenValue item, 'checked' - set: (val) => - for otherItem in @groupsMap[item.groupId] when otherItem isnt item - v8Util.setHiddenValue otherItem, 'checked', false - v8Util.setHiddenValue item, 'checked', true - - @insertRadioItem pos, item.commandId, item.label, item.groupId - - @setSublabel pos, item.sublabel if item.sublabel? - @setIcon pos, item.icon if item.icon? - @setRole pos, item.role if item.role? - - # Make menu accessable to items. - item.overrideReadOnlyProperty 'menu', this - - # Remember the items. - @items.splice pos, 0, item - @commandsMap[item.commandId] = item - -# Force menuWillShow to be called -Menu::_callMenuWillShow = -> - @delegate?.menuWillShow() - item.submenu._callMenuWillShow() for item in @items when item.submenu? - -applicationMenu = null -Menu.setApplicationMenu = (menu) -> - throw new TypeError('Invalid menu') unless menu is null or menu.constructor is Menu - applicationMenu = menu # Keep a reference. - - if process.platform is 'darwin' - return if menu is null - menu._callMenuWillShow() - bindings.setApplicationMenu menu - else - windows = BrowserWindow.getAllWindows() - w.setMenu menu for w in windows - -Menu.getApplicationMenu = -> applicationMenu - -Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder - -Menu.buildFromTemplate = (template) -> - throw new TypeError('Invalid template for Menu') unless Array.isArray template - - positionedTemplate = [] - insertIndex = 0 - - for item in template - if item.position - insertIndex = indexToInsertByPosition positionedTemplate, item.position - else - # If no |position| is specified, insert after last item. - insertIndex++ - positionedTemplate.splice insertIndex, 0, item - - menu = new Menu - - for item in positionedTemplate - throw new TypeError('Invalid template for MenuItem') unless typeof item is 'object' - - menuItem = new MenuItem(item) - menuItem[key] ?= value for key, value of item - menu.append menuItem - - menu - -module.exports = Menu diff --git a/atom/browser/api/lib/menu.js b/atom/browser/api/lib/menu.js new file mode 100644 index 000000000000..69ad4fec051d --- /dev/null +++ b/atom/browser/api/lib/menu.js @@ -0,0 +1,332 @@ +const BrowserWindow = require('electron').BrowserWindow; +const MenuItem = require('electron').MenuItem; +const EventEmitter = require('events').EventEmitter; +const v8Util = process.atomBinding('v8_util'); +const bindings = process.atomBinding('menu'); + +// Automatically generated radio menu item's group id. +var nextGroupId = 0; + +// Search between seperators to find a radio menu item and return its group id, +// otherwise generate a group id. +var generateGroupId = function(items, pos) { + var i, item, j, k, ref1, ref2, ref3; + if (pos > 0) { + for (i = j = ref1 = pos - 1; ref1 <= 0 ? j <= 0 : j >= 0; i = ref1 <= 0 ? ++j : --j) { + item = items[i]; + if (item.type === 'radio') { + return item.groupId; + } + if (item.type === 'separator') { + break; + } + } + } else if (pos < items.length) { + for (i = k = ref2 = pos, ref3 = items.length - 1; ref2 <= ref3 ? k <= ref3 : k >= ref3; i = ref2 <= ref3 ? ++k : --k) { + item = items[i]; + if (item.type === 'radio') { + return item.groupId; + } + if (item.type === 'separator') { + break; + } + } + } + return ++nextGroupId; +}; + +// Returns the index of item according to |id|. +var indexOfItemById = function(items, id) { + var i, item, j, len; + for (i = j = 0, len = items.length; j < len; i = ++j) { + item = items[i]; + if (item.id === id) { + return i; + } + } + return -1; +}; + +// Returns the index of where to insert the item according to |position|. +var indexToInsertByPosition = function(items, position) { + var id, insertIndex, query, ref1; + if (!position) { + return items.length; + } + ref1 = position.split('='), query = ref1[0], id = ref1[1]; + insertIndex = indexOfItemById(items, id); + if (insertIndex === -1 && query !== 'endof') { + console.warn("Item with id '" + id + "' is not found"); + return items.length; + } + switch (query) { + case 'after': + insertIndex++; + break; + case 'endof': + + // If the |id| doesn't exist, then create a new group with the |id|. + if (insertIndex === -1) { + items.push({ + id: id, + type: 'separator' + }); + insertIndex = items.length - 1; + } + + // Find the end of the group. + insertIndex++; + while (insertIndex < items.length && items[insertIndex].type !== 'separator') { + insertIndex++; + } + } + return insertIndex; +}; + +const Menu = bindings.Menu; + +Menu.prototype.__proto__ = EventEmitter.prototype; + +Menu.prototype._init = function() { + this.commandsMap = {}; + this.groupsMap = {}; + this.items = []; + return this.delegate = { + isCommandIdChecked: (function(_this) { + return function(commandId) { + var ref1; + return (ref1 = _this.commandsMap[commandId]) != null ? ref1.checked : void 0; + }; + })(this), + isCommandIdEnabled: (function(_this) { + return function(commandId) { + var ref1; + return (ref1 = _this.commandsMap[commandId]) != null ? ref1.enabled : void 0; + }; + })(this), + isCommandIdVisible: (function(_this) { + return function(commandId) { + var ref1; + return (ref1 = _this.commandsMap[commandId]) != null ? ref1.visible : void 0; + }; + })(this), + getAcceleratorForCommandId: (function(_this) { + return function(commandId) { + var ref1; + return (ref1 = _this.commandsMap[commandId]) != null ? ref1.accelerator : void 0; + }; + })(this), + getIconForCommandId: (function(_this) { + return function(commandId) { + var ref1; + return (ref1 = _this.commandsMap[commandId]) != null ? ref1.icon : void 0; + }; + })(this), + executeCommand: (function(_this) { + return function(commandId) { + var ref1; + return (ref1 = _this.commandsMap[commandId]) != null ? ref1.click(BrowserWindow.getFocusedWindow()) : void 0; + }; + })(this), + menuWillShow: (function(_this) { + return function() { + + // Make sure radio groups have at least one menu item seleted. + var checked, group, id, j, len, radioItem, ref1, results; + ref1 = _this.groupsMap; + results = []; + for (id in ref1) { + group = ref1[id]; + checked = false; + for (j = 0, len = group.length; j < len; j++) { + radioItem = group[j]; + if (!radioItem.checked) { + continue; + } + checked = true; + break; + } + if (!checked) { + results.push(v8Util.setHiddenValue(group[0], 'checked', true)); + } else { + results.push(void 0); + } + } + return results; + }; + })(this) + }; +}; + +Menu.prototype.popup = function(window, x, y) { + if ((window != null ? window.constructor : void 0) !== BrowserWindow) { + // Shift. + y = x; + x = window; + window = BrowserWindow.getFocusedWindow(); + } + if ((x != null) && (y != null)) { + return this._popupAt(window, x, y); + } else { + return this._popup(window); + } +}; + +Menu.prototype.append = function(item) { + return this.insert(this.getItemCount(), item); +}; + +Menu.prototype.insert = function(pos, item) { + var base, name; + if ((item != null ? item.constructor : void 0) !== MenuItem) { + throw new TypeError('Invalid item'); + } + switch (item.type) { + case 'normal': + this.insertItem(pos, item.commandId, item.label); + break; + case 'checkbox': + this.insertCheckItem(pos, item.commandId, item.label); + break; + case 'separator': + this.insertSeparator(pos); + break; + case 'submenu': + this.insertSubMenu(pos, item.commandId, item.label, item.submenu); + break; + case 'radio': + // Grouping radio menu items. + item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos)); + if ((base = this.groupsMap)[name = item.groupId] == null) { + base[name] = []; + } + this.groupsMap[item.groupId].push(item); + + // Setting a radio menu item should flip other items in the group. + v8Util.setHiddenValue(item, 'checked', item.checked); + Object.defineProperty(item, 'checked', { + enumerable: true, + get: function() { + return v8Util.getHiddenValue(item, 'checked'); + }, + set: (function(_this) { + return function(val) { + var j, len, otherItem, ref1; + ref1 = _this.groupsMap[item.groupId]; + for (j = 0, len = ref1.length; j < len; j++) { + otherItem = ref1[j]; + if (otherItem !== item) { + v8Util.setHiddenValue(otherItem, 'checked', false); + } + } + return v8Util.setHiddenValue(item, 'checked', true); + }; + })(this) + }); + this.insertRadioItem(pos, item.commandId, item.label, item.groupId); + } + if (item.sublabel != null) { + this.setSublabel(pos, item.sublabel); + } + if (item.icon != null) { + this.setIcon(pos, item.icon); + } + if (item.role != null) { + this.setRole(pos, item.role); + } + + // Make menu accessable to items. + item.overrideReadOnlyProperty('menu', this); + + // Remember the items. + this.items.splice(pos, 0, item); + return this.commandsMap[item.commandId] = item; +}; + + +// Force menuWillShow to be called +Menu.prototype._callMenuWillShow = function() { + var item, j, len, ref1, ref2, results; + if ((ref1 = this.delegate) != null) { + ref1.menuWillShow(); + } + ref2 = this.items; + results = []; + for (j = 0, len = ref2.length; j < len; j++) { + item = ref2[j]; + if (item.submenu != null) { + results.push(item.submenu._callMenuWillShow()); + } + } + return results; +}; + +var applicationMenu = null; + +Menu.setApplicationMenu = function(menu) { + var j, len, results, w, windows; + if (!(menu === null || menu.constructor === Menu)) { + throw new TypeError('Invalid menu'); + } + + // Keep a reference. + applicationMenu = menu; + if (process.platform === 'darwin') { + if (menu === null) { + return; + } + menu._callMenuWillShow(); + return bindings.setApplicationMenu(menu); + } else { + windows = BrowserWindow.getAllWindows(); + results = []; + for (j = 0, len = windows.length; j < len; j++) { + w = windows[j]; + results.push(w.setMenu(menu)); + } + return results; + } +}; + +Menu.getApplicationMenu = function() { + return applicationMenu; +}; + +Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder; + +Menu.buildFromTemplate = function(template) { + var insertIndex, item, j, k, key, len, len1, menu, menuItem, positionedTemplate, value; + if (!Array.isArray(template)) { + throw new TypeError('Invalid template for Menu'); + } + positionedTemplate = []; + insertIndex = 0; + for (j = 0, len = template.length; j < len; j++) { + item = template[j]; + if (item.position) { + insertIndex = indexToInsertByPosition(positionedTemplate, item.position); + } else { + // If no |position| is specified, insert after last item. + insertIndex++; + } + positionedTemplate.splice(insertIndex, 0, item); + } + menu = new Menu; + for (k = 0, len1 = positionedTemplate.length; k < len1; k++) { + item = positionedTemplate[k]; + if (typeof item !== 'object') { + throw new TypeError('Invalid template for MenuItem'); + } + menuItem = new MenuItem(item); + for (key in item) { + value = item[key]; + if (menuItem[key] == null) { + menuItem[key] = value; + } + } + menu.append(menuItem); + } + return menu; +}; + +module.exports = Menu; diff --git a/atom/browser/api/lib/navigation-controller.coffee b/atom/browser/api/lib/navigation-controller.coffee deleted file mode 100644 index d0c539a99db5..000000000000 --- a/atom/browser/api/lib/navigation-controller.coffee +++ /dev/null @@ -1,122 +0,0 @@ -{ipcMain} = require 'electron' - -# The history operation in renderer is redirected to browser. -ipcMain.on 'ATOM_SHELL_NAVIGATION_CONTROLLER', (event, method, args...) -> - event.sender[method] args... - -ipcMain.on 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', (event, method, args...) -> - event.returnValue = event.sender[method] args... - -# JavaScript implementation of Chromium's NavigationController. -# Instead of relying on Chromium for history control, we compeletely do history -# control on user land, and only rely on WebContents.loadURL for navigation. -# This helps us avoid Chromium's various optimizations so we can ensure renderer -# process is restarted everytime. -class NavigationController - constructor: (@webContents) -> - @clearHistory() - - # webContents may have already navigated to a page. - if @webContents._getURL() - @currentIndex++ - @history.push @webContents._getURL() - - @webContents.on 'navigation-entry-commited', (event, url, inPage, replaceEntry) => - if @inPageIndex > -1 and not inPage - # Navigated to a new page, clear in-page mark. - @inPageIndex = -1 - else if @inPageIndex is -1 and inPage - # Started in-page navigations. - @inPageIndex = @currentIndex - - if @pendingIndex >= 0 # Go to index. - @currentIndex = @pendingIndex - @pendingIndex = -1 - @history[@currentIndex] = url - else if replaceEntry # Non-user initialized navigation. - @history[@currentIndex] = url - else # Normal navigation. - @history = @history.slice 0, @currentIndex + 1 # Clear history. - currentEntry = @history[@currentIndex] - if currentEntry?.url isnt url - @currentIndex++ - @history.push url - - loadURL: (url, options={}) -> - @pendingIndex = -1 - @webContents._loadURL url, options - @webContents.emit 'load-url', url, options - - getURL: -> - if @currentIndex is -1 - '' - else - @history[@currentIndex] - - stop: -> - @pendingIndex = -1 - @webContents._stop() - - reload: -> - @pendingIndex = @currentIndex - @webContents._loadURL @getURL(), {} - - reloadIgnoringCache: -> - @pendingIndex = @currentIndex - @webContents._loadURL @getURL(), {extraHeaders: "pragma: no-cache\n"} - - canGoBack: -> - @getActiveIndex() > 0 - - canGoForward: -> - @getActiveIndex() < @history.length - 1 - - canGoToIndex: (index) -> - index >=0 and index < @history.length - - canGoToOffset: (offset) -> - @canGoToIndex @currentIndex + offset - - clearHistory: -> - @history = [] - @currentIndex = -1 - @pendingIndex = -1 - @inPageIndex = -1 - - goBack: -> - return unless @canGoBack() - @pendingIndex = @getActiveIndex() - 1 - if @inPageIndex > -1 and @pendingIndex >= @inPageIndex - @webContents._goBack() - else - @webContents._loadURL @history[@pendingIndex], {} - - goForward: -> - return unless @canGoForward() - @pendingIndex = @getActiveIndex() + 1 - if @inPageIndex > -1 and @pendingIndex >= @inPageIndex - @webContents._goForward() - else - @webContents._loadURL @history[@pendingIndex], {} - - goToIndex: (index) -> - return unless @canGoToIndex index - @pendingIndex = index - @webContents._loadURL @history[@pendingIndex], {} - - goToOffset: (offset) -> - return unless @canGoToOffset offset - pendingIndex = @currentIndex + offset - if @inPageIndex > -1 and pendingIndex >= @inPageIndex - @pendingIndex = pendingIndex - @webContents._goToOffset offset - else - @goToIndex pendingIndex - - getActiveIndex: -> - if @pendingIndex is -1 then @currentIndex else @pendingIndex - - length: -> - @history.length - -module.exports = NavigationController diff --git a/atom/browser/api/lib/navigation-controller.js b/atom/browser/api/lib/navigation-controller.js new file mode 100644 index 000000000000..80756eb13e49 --- /dev/null +++ b/atom/browser/api/lib/navigation-controller.js @@ -0,0 +1,188 @@ +const ipcMain = require('electron').ipcMain; + +var slice = [].slice; + +// The history operation in renderer is redirected to browser. +ipcMain.on('ATOM_SHELL_NAVIGATION_CONTROLLER', function() { + var args, event, method, ref; + event = arguments[0], method = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + return (ref = event.sender)[method].apply(ref, args); +}); + +ipcMain.on('ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', function() { + var args, event, method, ref; + event = arguments[0], method = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + return event.returnValue = (ref = event.sender)[method].apply(ref, args); +}); + +// JavaScript implementation of Chromium's NavigationController. +// Instead of relying on Chromium for history control, we compeletely do history +// control on user land, and only rely on WebContents.loadURL for navigation. +// This helps us avoid Chromium's various optimizations so we can ensure renderer +// process is restarted everytime. +var NavigationController = (function() { + function NavigationController(webContents) { + this.webContents = webContents; + this.clearHistory(); + + // webContents may have already navigated to a page. + if (this.webContents._getURL()) { + this.currentIndex++; + this.history.push(this.webContents._getURL()); + } + this.webContents.on('navigation-entry-commited', (function(_this) { + return function(event, url, inPage, replaceEntry) { + var currentEntry; + if (_this.inPageIndex > -1 && !inPage) { + + // Navigated to a new page, clear in-page mark. + _this.inPageIndex = -1; + } else if (_this.inPageIndex === -1 && inPage) { + + // Started in-page navigations. + _this.inPageIndex = _this.currentIndex; + } + if (_this.pendingIndex >= 0) { + + // Go to index. + _this.currentIndex = _this.pendingIndex; + _this.pendingIndex = -1; + return _this.history[_this.currentIndex] = url; + } else if (replaceEntry) { + + // Non-user initialized navigation. + return _this.history[_this.currentIndex] = url; + } else { + + // Normal navigation. Clear history. + _this.history = _this.history.slice(0, _this.currentIndex + 1); + currentEntry = _this.history[_this.currentIndex]; + if ((currentEntry != null ? currentEntry.url : void 0) !== url) { + _this.currentIndex++; + return _this.history.push(url); + } + } + }; + })(this)); + } + + NavigationController.prototype.loadURL = function(url, options) { + if (options == null) { + options = {}; + } + this.pendingIndex = -1; + this.webContents._loadURL(url, options); + return this.webContents.emit('load-url', url, options); + }; + + NavigationController.prototype.getURL = function() { + if (this.currentIndex === -1) { + return ''; + } else { + return this.history[this.currentIndex]; + } + }; + + NavigationController.prototype.stop = function() { + this.pendingIndex = -1; + return this.webContents._stop(); + }; + + NavigationController.prototype.reload = function() { + this.pendingIndex = this.currentIndex; + return this.webContents._loadURL(this.getURL(), {}); + }; + + NavigationController.prototype.reloadIgnoringCache = function() { + this.pendingIndex = this.currentIndex; + return this.webContents._loadURL(this.getURL(), { + extraHeaders: "pragma: no-cache\n" + }); + }; + + NavigationController.prototype.canGoBack = function() { + return this.getActiveIndex() > 0; + }; + + NavigationController.prototype.canGoForward = function() { + return this.getActiveIndex() < this.history.length - 1; + }; + + NavigationController.prototype.canGoToIndex = function(index) { + return index >= 0 && index < this.history.length; + }; + + NavigationController.prototype.canGoToOffset = function(offset) { + return this.canGoToIndex(this.currentIndex + offset); + }; + + NavigationController.prototype.clearHistory = function() { + this.history = []; + this.currentIndex = -1; + this.pendingIndex = -1; + return this.inPageIndex = -1; + }; + + NavigationController.prototype.goBack = function() { + if (!this.canGoBack()) { + return; + } + this.pendingIndex = this.getActiveIndex() - 1; + if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { + return this.webContents._goBack(); + } else { + return this.webContents._loadURL(this.history[this.pendingIndex], {}); + } + }; + + NavigationController.prototype.goForward = function() { + if (!this.canGoForward()) { + return; + } + this.pendingIndex = this.getActiveIndex() + 1; + if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { + return this.webContents._goForward(); + } else { + return this.webContents._loadURL(this.history[this.pendingIndex], {}); + } + }; + + NavigationController.prototype.goToIndex = function(index) { + if (!this.canGoToIndex(index)) { + return; + } + this.pendingIndex = index; + return this.webContents._loadURL(this.history[this.pendingIndex], {}); + }; + + NavigationController.prototype.goToOffset = function(offset) { + var pendingIndex; + if (!this.canGoToOffset(offset)) { + return; + } + pendingIndex = this.currentIndex + offset; + if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) { + this.pendingIndex = pendingIndex; + return this.webContents._goToOffset(offset); + } else { + return this.goToIndex(pendingIndex); + } + }; + + NavigationController.prototype.getActiveIndex = function() { + if (this.pendingIndex === -1) { + return this.currentIndex; + } else { + return this.pendingIndex; + } + }; + + NavigationController.prototype.length = function() { + return this.history.length; + }; + + return NavigationController; + +})(); + +module.exports = NavigationController; diff --git a/atom/browser/api/lib/power-monitor.coffee b/atom/browser/api/lib/power-monitor.coffee deleted file mode 100644 index 54bf9391827c..000000000000 --- a/atom/browser/api/lib/power-monitor.coffee +++ /dev/null @@ -1,7 +0,0 @@ -{EventEmitter} = require 'events' - -{powerMonitor} = process.atomBinding 'power_monitor' - -powerMonitor.__proto__ = EventEmitter.prototype - -module.exports = powerMonitor diff --git a/atom/browser/api/lib/power-monitor.js b/atom/browser/api/lib/power-monitor.js new file mode 100644 index 000000000000..239eb3b4d47a --- /dev/null +++ b/atom/browser/api/lib/power-monitor.js @@ -0,0 +1,6 @@ +const EventEmitter = require('events').EventEmitter; +const powerMonitor = process.atomBinding('power_monitor').powerMonitor; + +powerMonitor.__proto__ = EventEmitter.prototype; + +module.exports = powerMonitor; diff --git a/atom/browser/api/lib/power-save-blocker.coffee b/atom/browser/api/lib/power-save-blocker.coffee deleted file mode 100644 index 58392bc9aa8b..000000000000 --- a/atom/browser/api/lib/power-save-blocker.coffee +++ /dev/null @@ -1,3 +0,0 @@ -{powerSaveBlocker} = process.atomBinding 'power_save_blocker' - -module.exports = powerSaveBlocker diff --git a/atom/browser/api/lib/power-save-blocker.js b/atom/browser/api/lib/power-save-blocker.js new file mode 100644 index 000000000000..c44e3e2b63a2 --- /dev/null +++ b/atom/browser/api/lib/power-save-blocker.js @@ -0,0 +1,5 @@ +var powerSaveBlocker; + +powerSaveBlocker = process.atomBinding('power_save_blocker').powerSaveBlocker; + +module.exports = powerSaveBlocker; diff --git a/atom/browser/api/lib/protocol.coffee b/atom/browser/api/lib/protocol.coffee deleted file mode 100644 index a1dbc7c17d75..000000000000 --- a/atom/browser/api/lib/protocol.coffee +++ /dev/null @@ -1,25 +0,0 @@ -{app} = require 'electron' - -throw new Error('Can not initialize protocol module before app is ready') unless app.isReady() - -{protocol} = process.atomBinding 'protocol' - -# Warn about removed APIs. -logAndThrow = (callback, message) -> - console.error message - if callback then callback(new Error(message)) else throw new Error(message) -protocol.registerProtocol = (scheme, handler, callback) -> - logAndThrow callback, - 'registerProtocol API has been replaced by the - register[File/Http/Buffer/String]Protocol API family, please - switch to the new APIs.' -protocol.isHandledProtocol = (scheme, callback) -> - logAndThrow callback, - 'isHandledProtocol API has been replaced by isProtocolHandled.' -protocol.interceptProtocol = (scheme, handler, callback) -> - logAndThrow callback, - 'interceptProtocol API has been replaced by the - intercept[File/Http/Buffer/String]Protocol API family, please - switch to the new APIs.' - -module.exports = protocol diff --git a/atom/browser/api/lib/protocol.js b/atom/browser/api/lib/protocol.js new file mode 100644 index 000000000000..41cb48db09b9 --- /dev/null +++ b/atom/browser/api/lib/protocol.js @@ -0,0 +1,31 @@ +const app = require('electron').app; + +if (!app.isReady()) { + throw new Error('Can not initialize protocol module before app is ready'); +} + +const protocol = process.atomBinding('protocol').protocol; + +// Warn about removed APIs. +var logAndThrow = function(callback, message) { + console.error(message); + if (callback) { + return callback(new Error(message)); + } else { + throw new Error(message); + } +}; + +protocol.registerProtocol = function(scheme, handler, callback) { + return logAndThrow(callback, 'registerProtocol API has been replaced by the register[File/Http/Buffer/String]Protocol API family, please switch to the new APIs.'); +}; + +protocol.isHandledProtocol = function(scheme, callback) { + return logAndThrow(callback, 'isHandledProtocol API has been replaced by isProtocolHandled.'); +}; + +protocol.interceptProtocol = function(scheme, handler, callback) { + return logAndThrow(callback, 'interceptProtocol API has been replaced by the intercept[File/Http/Buffer/String]Protocol API family, please switch to the new APIs.'); +}; + +module.exports = protocol; diff --git a/atom/browser/api/lib/screen.coffee b/atom/browser/api/lib/screen.coffee deleted file mode 100644 index 87c42f091df2..000000000000 --- a/atom/browser/api/lib/screen.coffee +++ /dev/null @@ -1,6 +0,0 @@ -{EventEmitter} = require 'events' -{screen} = process.atomBinding 'screen' - -screen.__proto__ = EventEmitter.prototype - -module.exports = screen diff --git a/atom/browser/api/lib/screen.js b/atom/browser/api/lib/screen.js new file mode 100644 index 000000000000..04965278a3c8 --- /dev/null +++ b/atom/browser/api/lib/screen.js @@ -0,0 +1,6 @@ +const EventEmitter = require('events').EventEmitter; +const screen = process.atomBinding('screen').screen; + +screen.__proto__ = EventEmitter.prototype; + +module.exports = screen; diff --git a/atom/browser/api/lib/session.coffee b/atom/browser/api/lib/session.coffee deleted file mode 100644 index 5c65aa29cf6a..000000000000 --- a/atom/browser/api/lib/session.coffee +++ /dev/null @@ -1,24 +0,0 @@ -{EventEmitter} = require 'events' - -bindings = process.atomBinding 'session' - -PERSIST_PERFIX = 'persist:' - -# Returns the Session from |partition| string. -exports.fromPartition = (partition='') -> - return exports.defaultSession if partition is '' - if partition.startsWith PERSIST_PERFIX - bindings.fromPartition partition.substr(PERSIST_PERFIX.length), false - else - bindings.fromPartition partition, true - -# Returns the default session. -Object.defineProperty exports, 'defaultSession', - enumerable: true - get: -> bindings.fromPartition '', false - -wrapSession = (session) -> - # session is an EventEmitter. - session.__proto__ = EventEmitter.prototype - -bindings._setWrapSession wrapSession diff --git a/atom/browser/api/lib/session.js b/atom/browser/api/lib/session.js new file mode 100644 index 000000000000..dc65264349fa --- /dev/null +++ b/atom/browser/api/lib/session.js @@ -0,0 +1,33 @@ +const EventEmitter = require('events').EventEmitter; +const bindings = process.atomBinding('session'); +const PERSIST_PERFIX = 'persist:'; + +// Returns the Session from |partition| string. +exports.fromPartition = function(partition) { + if (partition == null) { + partition = ''; + } + if (partition === '') { + return exports.defaultSession; + } + if (partition.startsWith(PERSIST_PERFIX)) { + return bindings.fromPartition(partition.substr(PERSIST_PERFIX.length), false); + } else { + return bindings.fromPartition(partition, true); + } +}; + +// Returns the default session. +Object.defineProperty(exports, 'defaultSession', { + enumerable: true, + get: function() { + return bindings.fromPartition('', false); + } +}); + +var wrapSession = function(session) { + // session is an EventEmitter. + return session.__proto__ = EventEmitter.prototype; +}; + +bindings._setWrapSession(wrapSession); diff --git a/atom/browser/api/lib/tray.coffee b/atom/browser/api/lib/tray.coffee deleted file mode 100644 index db26ab5b7ed1..000000000000 --- a/atom/browser/api/lib/tray.coffee +++ /dev/null @@ -1,19 +0,0 @@ -{deprecate} = require 'electron' -{EventEmitter} = require 'events' - -{Tray} = process.atomBinding 'tray' -Tray::__proto__ = EventEmitter.prototype - -Tray::_init = -> - # Deprecated. - deprecate.rename this, 'popContextMenu', 'popUpContextMenu' - deprecate.event this, 'clicked', 'click' - deprecate.event this, 'double-clicked', 'double-click' - deprecate.event this, 'right-clicked', 'right-click' - deprecate.event this, 'balloon-clicked', 'balloon-click' - -Tray::setContextMenu = (menu) -> - @_setContextMenu menu - @menu = menu # Keep a strong reference of menu. - -module.exports = Tray diff --git a/atom/browser/api/lib/tray.js b/atom/browser/api/lib/tray.js new file mode 100644 index 000000000000..342683552f62 --- /dev/null +++ b/atom/browser/api/lib/tray.js @@ -0,0 +1,23 @@ +const deprecate = require('electron').deprecate; +const EventEmitter = require('events').EventEmitter; +const Tray = process.atomBinding('tray').Tray; + +Tray.prototype.__proto__ = EventEmitter.prototype; + +Tray.prototype._init = function() { + // Deprecated. + deprecate.rename(this, 'popContextMenu', 'popUpContextMenu'); + deprecate.event(this, 'clicked', 'click'); + deprecate.event(this, 'double-clicked', 'double-click'); + deprecate.event(this, 'right-clicked', 'right-click'); + return deprecate.event(this, 'balloon-clicked', 'balloon-click'); +}; + +Tray.prototype.setContextMenu = function(menu) { + this._setContextMenu(menu); + + // Keep a strong reference of menu. + return this.menu = menu; +}; + +module.exports = Tray; diff --git a/atom/browser/api/lib/web-contents.coffee b/atom/browser/api/lib/web-contents.coffee deleted file mode 100644 index 2eda00f0689d..000000000000 --- a/atom/browser/api/lib/web-contents.coffee +++ /dev/null @@ -1,137 +0,0 @@ -{EventEmitter} = require 'events' -{deprecate, ipcMain, session, NavigationController, Menu} = require 'electron' - -binding = process.atomBinding 'web_contents' - -nextId = 0 -getNextId = -> ++nextId - -PDFPageSize = - A5: - custom_display_name: "A5" - height_microns: 210000 - name: "ISO_A5" - width_microns: 148000 - A4: - custom_display_name: "A4" - height_microns: 297000 - name: "ISO_A4" - is_default: "true" - width_microns: 210000 - A3: - custom_display_name: "A3" - height_microns: 420000 - name: "ISO_A3" - width_microns: 297000 - Legal: - custom_display_name: "Legal" - height_microns: 355600 - name: "NA_LEGAL" - width_microns: 215900 - Letter: - custom_display_name: "Letter" - height_microns: 279400 - name: "NA_LETTER" - width_microns: 215900 - Tabloid: - height_microns: 431800 - name: "NA_LEDGER" - width_microns: 279400 - custom_display_name: "Tabloid" - -wrapWebContents = (webContents) -> - # webContents is an EventEmitter. - webContents.__proto__ = EventEmitter.prototype - - # WebContents::send(channel, args..) - webContents.send = (channel, args...) -> - @_send channel, [args...] - - # Make sure webContents.executeJavaScript would run the code only when the - # web contents has been loaded. - webContents.executeJavaScript = (code, hasUserGesture=false) -> - if @getURL() and not @isLoading() - @_executeJavaScript code, hasUserGesture - else - webContents.once 'did-finish-load', @_executeJavaScript.bind(this, code, hasUserGesture) - - # The navigation controller. - controller = new NavigationController(webContents) - for name, method of NavigationController.prototype when method instanceof Function - do (name, method) -> - webContents[name] = -> method.apply controller, arguments - - # Dispatch IPC messages to the ipc module. - webContents.on 'ipc-message', (event, packed) -> - [channel, args...] = packed - ipcMain.emit channel, event, args... - webContents.on 'ipc-message-sync', (event, packed) -> - [channel, args...] = packed - Object.defineProperty event, 'returnValue', set: (value) -> event.sendReply JSON.stringify(value) - ipcMain.emit channel, event, args... - - # Handle context menu action request from pepper plugin. - webContents.on 'pepper-context-menu', (event, params) -> - menu = Menu.buildFromTemplate params.menu - menu.popup params.x, params.y - - # This error occurs when host could not be found. - webContents.on 'did-fail-provisional-load', (args...) -> - # Calling loadURL during this event might cause crash, so delay the event - # until next tick. - setImmediate => @emit 'did-fail-load', args... - - # Delays the page-title-updated event to next tick. - webContents.on '-page-title-updated', (args...) -> - setImmediate => @emit 'page-title-updated', args... - - # Deprecated. - deprecate.rename webContents, 'loadUrl', 'loadURL' - deprecate.rename webContents, 'getUrl', 'getURL' - deprecate.event webContents, 'page-title-set', 'page-title-updated', (args...) -> - @emit 'page-title-set', args... - - webContents.printToPDF = (options, callback) -> - printingSetting = - pageRage: [] - mediaSize: {} - landscape: false - color: 2 - headerFooterEnabled: false - marginsType: 0 - isFirstRequest: false - requestID: getNextId() - previewModifiable: true - printToPDF: true - printWithCloudPrint: false - printWithPrivet: false - printWithExtension: false - deviceName: "Save as PDF" - generateDraftData: true - fitToPageEnabled: false - duplex: 0 - copies: 1 - collate: true - shouldPrintBackgrounds: false - shouldPrintSelectionOnly: false - - if options.landscape - printingSetting.landscape = options.landscape - if options.marginsType - printingSetting.marginsType = options.marginsType - if options.printSelectionOnly - printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly - if options.printBackground - printingSetting.shouldPrintBackgrounds = options.printBackground - - if options.pageSize and PDFPageSize[options.pageSize] - printingSetting.mediaSize = PDFPageSize[options.pageSize] - else - printingSetting.mediaSize = PDFPageSize['A4'] - - @_printToPDF printingSetting, callback - -binding._setWrapWebContents wrapWebContents - -module.exports.create = (options={}) -> - binding.create(options) diff --git a/atom/browser/api/lib/web-contents.js b/atom/browser/api/lib/web-contents.js new file mode 100644 index 000000000000..2cce6e6cb9dc --- /dev/null +++ b/atom/browser/api/lib/web-contents.js @@ -0,0 +1,226 @@ +'use strict'; + +const EventEmitter = require('events').EventEmitter; +const deprecate = require('electron').deprecate; +const ipcMain = require('electron').ipcMain; +const session = require('electron').session; +const NavigationController = require('electron').NavigationController; +const Menu = require('electron').Menu; + +const binding = process.atomBinding('web_contents'); + +let slice = [].slice; +let nextId = 0; + +let getNextId = function() { + return ++nextId; +}; + +let PDFPageSize = { + A5: { + custom_display_name: "A5", + height_microns: 210000, + name: "ISO_A5", + width_microns: 148000 + }, + A4: { + custom_display_name: "A4", + height_microns: 297000, + name: "ISO_A4", + is_default: "true", + width_microns: 210000 + }, + A3: { + custom_display_name: "A3", + height_microns: 420000, + name: "ISO_A3", + width_microns: 297000 + }, + Legal: { + custom_display_name: "Legal", + height_microns: 355600, + name: "NA_LEGAL", + width_microns: 215900 + }, + Letter: { + custom_display_name: "Letter", + height_microns: 279400, + name: "NA_LETTER", + width_microns: 215900 + }, + Tabloid: { + height_microns: 431800, + name: "NA_LEDGER", + width_microns: 279400, + custom_display_name: "Tabloid" + } +}; + +// Following methods are mapped to webFrame. +const webFrameMethods = [ + 'executeJavaScript', + 'insertText', + 'setZoomFactor', + 'setZoomLevel', + 'setZoomLevelLimits', +]; + +let wrapWebContents = function(webContents) { + // webContents is an EventEmitter. + var controller, method, name, ref1; + webContents.__proto__ = EventEmitter.prototype; + + // Every remote callback from renderer process would add a listenter to the + // render-view-deleted event, so ignore the listenters warning. + webContents.setMaxListeners(0); + + // WebContents::send(channel, args..) + webContents.send = function() { + var args, channel; + channel = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + return this._send(channel, slice.call(args)); + }; + + // The navigation controller. + controller = new NavigationController(webContents); + ref1 = NavigationController.prototype; + for (name in ref1) { + method = ref1[name]; + if (method instanceof Function) { + (function(name, method) { + return webContents[name] = function() { + return method.apply(controller, arguments); + }; + })(name, method); + } + } + + // Mapping webFrame methods. + for (let method of webFrameMethods) { + webContents[method] = function() { + let args = Array.prototype.slice.call(arguments); + this.send('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args); + }; + } + + // Make sure webContents.executeJavaScript would run the code only when the + // webContents has been loaded. + const executeJavaScript = webContents.executeJavaScript; + webContents.executeJavaScript = function(code, hasUserGesture) { + if (this.getURL() && !this.isLoading()) + return executeJavaScript.call(this, code, hasUserGesture); + else + return this.once('did-finish-load', executeJavaScript.bind(this, code, hasUserGesture)); + }; + + // Dispatch IPC messages to the ipc module. + webContents.on('ipc-message', function(event, packed) { + var args, channel; + channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; + return ipcMain.emit.apply(ipcMain, [channel, event].concat(slice.call(args))); + }); + webContents.on('ipc-message-sync', function(event, packed) { + var args, channel; + channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; + Object.defineProperty(event, 'returnValue', { + set: function(value) { + return event.sendReply(JSON.stringify(value)); + } + }); + return ipcMain.emit.apply(ipcMain, [channel, event].concat(slice.call(args))); + }); + + // Handle context menu action request from pepper plugin. + webContents.on('pepper-context-menu', function(event, params) { + var menu; + menu = Menu.buildFromTemplate(params.menu); + return menu.popup(params.x, params.y); + }); + + // This error occurs when host could not be found. + webContents.on('did-fail-provisional-load', function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + + // Calling loadURL during this event might cause crash, so delay the event + // until next tick. + return setImmediate((function(_this) { + return function() { + return _this.emit.apply(_this, ['did-fail-load'].concat(slice.call(args))); + }; + })(this)); + }); + + // Delays the page-title-updated event to next tick. + webContents.on('-page-title-updated', function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return setImmediate((function(_this) { + return function() { + return _this.emit.apply(_this, ['page-title-updated'].concat(slice.call(args))); + }; + })(this)); + }); + + // Deprecated. + deprecate.rename(webContents, 'loadUrl', 'loadURL'); + deprecate.rename(webContents, 'getUrl', 'getURL'); + deprecate.event(webContents, 'page-title-set', 'page-title-updated', function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return this.emit.apply(this, ['page-title-set'].concat(slice.call(args))); + }); + return webContents.printToPDF = function(options, callback) { + var printingSetting; + printingSetting = { + pageRage: [], + mediaSize: {}, + landscape: false, + color: 2, + headerFooterEnabled: false, + marginsType: 0, + isFirstRequest: false, + requestID: getNextId(), + previewModifiable: true, + printToPDF: true, + printWithCloudPrint: false, + printWithPrivet: false, + printWithExtension: false, + deviceName: "Save as PDF", + generateDraftData: true, + fitToPageEnabled: false, + duplex: 0, + copies: 1, + collate: true, + shouldPrintBackgrounds: false, + shouldPrintSelectionOnly: false + }; + if (options.landscape) { + printingSetting.landscape = options.landscape; + } + if (options.marginsType) { + printingSetting.marginsType = options.marginsType; + } + if (options.printSelectionOnly) { + printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly; + } + if (options.printBackground) { + printingSetting.shouldPrintBackgrounds = options.printBackground; + } + if (options.pageSize && PDFPageSize[options.pageSize]) { + printingSetting.mediaSize = PDFPageSize[options.pageSize]; + } else { + printingSetting.mediaSize = PDFPageSize['A4']; + } + return this._printToPDF(printingSetting, callback); + }; +}; + +binding._setWrapWebContents(wrapWebContents); + +module.exports.create = function(options) { + if (options == null) { + options = {}; + } + return binding.create(options); +}; diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index eadd52ac44c8..a046f34287ee 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -21,6 +21,7 @@ #if defined(USE_X11) #include "chrome/browser/ui/libgtk2ui/gtk2_util.h" +#include "ui/events/devices/x11/touch_factory_x11.h" #endif namespace atom { @@ -116,6 +117,10 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { node_bindings_->PrepareMessageLoop(); node_bindings_->RunMessageLoop(); +#if defined(USE_X11) + ui::TouchFactory::SetTouchDeviceListFromCommandLine(); +#endif + // Start idle gc. gc_timer_.Start( FROM_HERE, base::TimeDelta::FromMinutes(1), diff --git a/atom/browser/lib/chrome-extension.coffee b/atom/browser/lib/chrome-extension.coffee deleted file mode 100644 index 931b8168961e..000000000000 --- a/atom/browser/lib/chrome-extension.coffee +++ /dev/null @@ -1,96 +0,0 @@ -electron = require 'electron' -fs = require 'fs' -path = require 'path' -url = require 'url' - -# Mapping between hostname and file path. -hostPathMap = {} -hostPathMapNextKey = 0 - -getHostForPath = (path) -> - key = "extension-#{++hostPathMapNextKey}" - hostPathMap[key] = path - key - -getPathForHost = (host) -> - hostPathMap[host] - -# Cache extensionInfo. -extensionInfoMap = {} - -getExtensionInfoFromPath = (srcDirectory) -> - manifest = JSON.parse fs.readFileSync(path.join(srcDirectory, 'manifest.json')) - unless extensionInfoMap[manifest.name]? - # We can not use 'file://' directly because all resources in the extension - # will be treated as relative to the root in Chrome. - page = url.format - protocol: 'chrome-extension' - slashes: true - hostname: getHostForPath srcDirectory - pathname: manifest.devtools_page - extensionInfoMap[manifest.name] = - startPage: page - name: manifest.name - srcDirectory: srcDirectory - exposeExperimentalAPIs: true - extensionInfoMap[manifest.name] - -# The loaded extensions cache and its persistent path. -loadedExtensions = null -loadedExtensionsPath = null - -# Persistent loaded extensions. -{app} = electron -app.on 'will-quit', -> - try - loadedExtensions = Object.keys(extensionInfoMap).map (key) -> extensionInfoMap[key].srcDirectory - try - fs.mkdirSync path.dirname(loadedExtensionsPath) - catch e - fs.writeFileSync loadedExtensionsPath, JSON.stringify(loadedExtensions) - catch e - -# We can not use protocol or BrowserWindow until app is ready. -app.once 'ready', -> - {protocol, BrowserWindow} = electron - - # Load persistented extensions. - loadedExtensionsPath = path.join app.getPath('userData'), 'DevTools Extensions' - - try - loadedExtensions = JSON.parse fs.readFileSync(loadedExtensionsPath) - loadedExtensions = [] unless Array.isArray loadedExtensions - # Preheat the extensionInfo cache. - getExtensionInfoFromPath srcDirectory for srcDirectory in loadedExtensions - catch e - - # The chrome-extension: can map a extension URL request to real file path. - chromeExtensionHandler = (request, callback) -> - parsed = url.parse request.url - return callback() unless parsed.hostname and parsed.path? - return callback() unless /extension-\d+/.test parsed.hostname - - directory = getPathForHost parsed.hostname - return callback() unless directory? - callback path.join(directory, parsed.path) - protocol.registerFileProtocol 'chrome-extension', chromeExtensionHandler, (error) -> - console.error 'Unable to register chrome-extension protocol' if error - - BrowserWindow::_loadDevToolsExtensions = (extensionInfoArray) -> - @devToolsWebContents?.executeJavaScript "DevToolsAPI.addExtensions(#{JSON.stringify(extensionInfoArray)});" - - BrowserWindow.addDevToolsExtension = (srcDirectory) -> - extensionInfo = getExtensionInfoFromPath srcDirectory - if extensionInfo - window._loadDevToolsExtensions [extensionInfo] for window in BrowserWindow.getAllWindows() - extensionInfo.name - - BrowserWindow.removeDevToolsExtension = (name) -> - delete extensionInfoMap[name] - - # Load persistented extensions when devtools is opened. - init = BrowserWindow::_init - BrowserWindow::_init = -> - init.call this - @on 'devtools-opened', -> - @_loadDevToolsExtensions Object.keys(extensionInfoMap).map (key) -> extensionInfoMap[key] diff --git a/atom/browser/lib/chrome-extension.js b/atom/browser/lib/chrome-extension.js new file mode 100644 index 000000000000..32459c3a20e2 --- /dev/null +++ b/atom/browser/lib/chrome-extension.js @@ -0,0 +1,142 @@ +const electron = require('electron'); +const app = electron.app; +const fs = require('fs'); +const path = require('path'); +const url = require('url'); + +// Mapping between hostname and file path. +var hostPathMap = {}; +var hostPathMapNextKey = 0; + +var getHostForPath = function(path) { + var key; + key = "extension-" + (++hostPathMapNextKey); + hostPathMap[key] = path; + return key; +}; + +var getPathForHost = function(host) { + return hostPathMap[host]; +}; + +// Cache extensionInfo. +var extensionInfoMap = {}; + +var getExtensionInfoFromPath = function(srcDirectory) { + var manifest, page; + manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json'))); + if (extensionInfoMap[manifest.name] == null) { + + // We can not use 'file://' directly because all resources in the extension + // will be treated as relative to the root in Chrome. + page = url.format({ + protocol: 'chrome-extension', + slashes: true, + hostname: getHostForPath(srcDirectory), + pathname: manifest.devtools_page + }); + extensionInfoMap[manifest.name] = { + startPage: page, + name: manifest.name, + srcDirectory: srcDirectory, + exposeExperimentalAPIs: true + }; + return extensionInfoMap[manifest.name]; + } +}; + +// The loaded extensions cache and its persistent path. +var loadedExtensions = null; +var loadedExtensionsPath = null; + +app.on('will-quit', function() { + var e, error1, error2; + try { + loadedExtensions = Object.keys(extensionInfoMap).map(function(key) { + return extensionInfoMap[key].srcDirectory; + }); + try { + fs.mkdirSync(path.dirname(loadedExtensionsPath)); + } catch (error1) { + e = error1; + } + return fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions)); + } catch (error2) { + e = error2; + } +}); + +// We can not use protocol or BrowserWindow until app is ready. +app.once('ready', function() { + var BrowserWindow, chromeExtensionHandler, e, error1, i, init, len, protocol, srcDirectory; + protocol = electron.protocol, BrowserWindow = electron.BrowserWindow; + + // Load persistented extensions. + loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions'); + try { + loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath)); + if (!Array.isArray(loadedExtensions)) { + loadedExtensions = []; + } + + // Preheat the extensionInfo cache. + for (i = 0, len = loadedExtensions.length; i < len; i++) { + srcDirectory = loadedExtensions[i]; + getExtensionInfoFromPath(srcDirectory); + } + } catch (error1) { + e = error1; + } + + // The chrome-extension: can map a extension URL request to real file path. + chromeExtensionHandler = function(request, callback) { + var directory, parsed; + parsed = url.parse(request.url); + if (!(parsed.hostname && (parsed.path != null))) { + return callback(); + } + if (!/extension-\d+/.test(parsed.hostname)) { + return callback(); + } + directory = getPathForHost(parsed.hostname); + if (directory == null) { + return callback(); + } + return callback(path.join(directory, parsed.path)); + }; + protocol.registerFileProtocol('chrome-extension', chromeExtensionHandler, function(error) { + if (error) { + return console.error('Unable to register chrome-extension protocol'); + } + }); + BrowserWindow.prototype._loadDevToolsExtensions = function(extensionInfoArray) { + var ref; + return (ref = this.devToolsWebContents) != null ? ref.executeJavaScript("DevToolsAPI.addExtensions(" + (JSON.stringify(extensionInfoArray)) + ");") : void 0; + }; + BrowserWindow.addDevToolsExtension = function(srcDirectory) { + var extensionInfo, j, len1, ref, window; + extensionInfo = getExtensionInfoFromPath(srcDirectory); + if (extensionInfo) { + ref = BrowserWindow.getAllWindows(); + for (j = 0, len1 = ref.length; j < len1; j++) { + window = ref[j]; + window._loadDevToolsExtensions([extensionInfo]); + } + return extensionInfo.name; + } + }; + BrowserWindow.removeDevToolsExtension = function(name) { + return delete extensionInfoMap[name]; + }; + + // Load persistented extensions when devtools is opened. + init = BrowserWindow.prototype._init; + return BrowserWindow.prototype._init = function() { + init.call(this); + return this.on('devtools-opened', function() { + return this._loadDevToolsExtensions(Object.keys(extensionInfoMap).map(function(key) { + return extensionInfoMap[key]; + })); + }); + }; +}); diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee deleted file mode 100644 index a7fb29ff76ed..000000000000 --- a/atom/browser/lib/desktop-capturer.coffee +++ /dev/null @@ -1,37 +0,0 @@ -{ipcMain} = require 'electron' -{desktopCapturer} = process.atomBinding 'desktop_capturer' - -deepEqual = (opt1, opt2) -> - return JSON.stringify(opt1) is JSON.stringify(opt2) - -# A queue for holding all requests from renderer process. -requestsQueue = [] - -ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, captureWindow, captureScreen, thumbnailSize, id) -> - request = id: id, options: {captureWindow, captureScreen, thumbnailSize}, webContents: event.sender - requestsQueue.push request - desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize if requestsQueue.length is 1 - # If the WebContents is destroyed before receiving result, just remove the - # reference from requestsQueue to make the module not send the result to it. - event.sender.once 'destroyed', -> - request.webContents = null - -desktopCapturer.emit = (event, name, sources) -> - # Receiving sources result from main process, now send them back to renderer. - handledRequest = requestsQueue.shift 0 - result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", result - - # Check the queue to see whether there is other same request. If has, handle - # it for reducing redunplicated `desktopCaptuer.startHandling` calls. - unhandledRequestsQueue = [] - for request in requestsQueue - if deepEqual handledRequest.options, request.options - request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", errorMessage, result - else - unhandledRequestsQueue.push request - requestsQueue = unhandledRequestsQueue - # If the requestsQueue is not empty, start a new request handling. - if requestsQueue.length > 0 - {captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options - desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize diff --git a/atom/browser/lib/desktop-capturer.js b/atom/browser/lib/desktop-capturer.js new file mode 100644 index 000000000000..da649df1ce0c --- /dev/null +++ b/atom/browser/lib/desktop-capturer.js @@ -0,0 +1,75 @@ +const ipcMain = require('electron').ipcMain; +const desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer; + +var deepEqual = function(opt1, opt2) { + return JSON.stringify(opt1) === JSON.stringify(opt2); +}; + +// A queue for holding all requests from renderer process. +var requestsQueue = []; + +ipcMain.on('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function(event, captureWindow, captureScreen, thumbnailSize, id) { + var request; + request = { + id: id, + options: { + captureWindow: captureWindow, + captureScreen: captureScreen, + thumbnailSize: thumbnailSize + }, + webContents: event.sender + }; + requestsQueue.push(request); + if (requestsQueue.length === 1) { + desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize); + } + + // If the WebContents is destroyed before receiving result, just remove the + // reference from requestsQueue to make the module not send the result to it. + return event.sender.once('destroyed', function() { + return request.webContents = null; + }); +}); + +desktopCapturer.emit = function(event, name, sources) { + // Receiving sources result from main process, now send them back to renderer. + var captureScreen, captureWindow, handledRequest, i, len, ref, ref1, ref2, request, result, source, thumbnailSize, unhandledRequestsQueue; + handledRequest = requestsQueue.shift(0); + result = (function() { + var i, len, results; + results = []; + for (i = 0, len = sources.length; i < len; i++) { + source = sources[i]; + results.push({ + id: source.id, + name: source.name, + thumbnail: source.thumbnail.toDataUrl() + }); + } + return results; + })(); + if ((ref = handledRequest.webContents) != null) { + ref.send("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + handledRequest.id, result); + } + + // Check the queue to see whether there is other same request. If has, handle + // it for reducing redunplicated `desktopCaptuer.startHandling` calls. + unhandledRequestsQueue = []; + for (i = 0, len = requestsQueue.length; i < len; i++) { + request = requestsQueue[i]; + if (deepEqual(handledRequest.options, request.options)) { + if ((ref1 = request.webContents) != null) { + ref1.send("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + request.id, errorMessage, result); + } + } else { + unhandledRequestsQueue.push(request); + } + } + requestsQueue = unhandledRequestsQueue; + + // If the requestsQueue is not empty, start a new request handling. + if (requestsQueue.length > 0) { + ref2 = requestsQueue[0].options, captureWindow = ref2.captureWindow, captureScreen = ref2.captureScreen, thumbnailSize = ref2.thumbnailSize; + return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize); + } +}; diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee deleted file mode 100644 index c99bf5757dc1..000000000000 --- a/atom/browser/lib/guest-view-manager.coffee +++ /dev/null @@ -1,174 +0,0 @@ -{ipcMain, webContents} = require 'electron' - -webViewManager = null # Doesn't exist in early initialization. - -supportedWebViewEvents = [ - 'load-commit' - 'did-finish-load' - 'did-fail-load' - 'did-frame-finish-load' - 'did-start-loading' - 'did-stop-loading' - 'did-get-response-details' - 'did-get-redirect-request' - 'dom-ready' - 'console-message' - 'devtools-opened' - 'devtools-closed' - 'devtools-focused' - 'new-window' - 'will-navigate' - 'did-navigate' - 'did-navigate-in-page' - 'close' - 'crashed' - 'gpu-crashed' - 'plugin-crashed' - 'destroyed' - 'page-title-updated' - 'page-favicon-updated' - 'enter-html-full-screen' - 'leave-html-full-screen' - 'media-started-playing' - 'media-paused' - 'found-in-page' - 'did-change-theme-color' -] - -nextInstanceId = 0 -guestInstances = {} -embedderElementsMap = {} -reverseEmbedderElementsMap = {} - -# Moves the last element of array to the first one. -moveLastToFirst = (list) -> - list.unshift list.pop() - -# 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, partition: params.partition, embedder} - guestInstances[id] = {guest, embedder} - - # Destroy guest when the embedder is gone or navigated. - destroyEvents = ['destroyed', 'crashed', 'did-navigate'] - destroy = -> - destroyGuest embedder, id if guestInstances[id]? - for event in destroyEvents - embedder.once event, destroy - # Users might also listen to the crashed event, so We must ensure the guest - # is destroyed before users' listener gets called. It is done by moving our - # listener to the first one in queue. - listeners = embedder._events[event] - moveLastToFirst listeners if Array.isArray listeners - guest.once 'destroyed', -> - embedder.removeListener event, destroy for event in destroyEvents - - # Init guest web view after attached. - guest.once 'did-attach', -> - params = @attachParams - delete @attachParams - - @viewInstanceId = params.instanceId - @setSize - normal: - width: params.elementWidth, height: params.elementHeight - enableAutoSize: params.autosize - min: - width: params.minwidth, height: params.minheight - max: - width: params.maxwidth, height: params.maxheight - - if params.src - opts = {} - opts.httpReferrer = params.httpreferrer if params.httpreferrer - opts.userAgent = params.useragent if params.useragent - @loadURL params.src, opts - - if params.allowtransparency? - @setAllowTransparency params.allowtransparency - - guest.allowPopups = params.allowpopups - - # 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... - - # Dispatch guest's IPC messages to embedder. - guest.on 'ipc-message-host', (_, packed) -> - [channel, args...] = packed - embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{guest.viewInstanceId}", channel, args... - - # Autosize. - guest.on 'size-changed', (_, args...) -> - embedder.send "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{guest.viewInstanceId}", args... - - id - -# Attach the guest to an element of embedder. -attachGuest = (embedder, elementInstanceId, guestInstanceId, params) -> - guest = guestInstances[guestInstanceId].guest - - # Destroy the old guest when attaching. - key = "#{embedder.getId()}-#{elementInstanceId}" - oldGuestInstanceId = embedderElementsMap[key] - if oldGuestInstanceId? - # Reattachment to the same guest is not currently supported. - return unless oldGuestInstanceId != guestInstanceId - - return unless guestInstances[oldGuestInstanceId]? - destroyGuest embedder, oldGuestInstanceId - - webPreferences = - guestInstanceId: guestInstanceId - nodeIntegration: params.nodeintegration ? false - plugins: params.plugins - webSecurity: !params.disablewebsecurity - webPreferences.preloadURL = params.preload if params.preload - webViewManager.addGuest guestInstanceId, elementInstanceId, embedder, guest, webPreferences - - guest.attachParams = params - embedderElementsMap[key] = guestInstanceId - reverseEmbedderElementsMap[guestInstanceId] = key - -# Destroy an existing guest instance. -destroyGuest = (embedder, id) -> - webViewManager.removeGuest embedder, id - guestInstances[id].guest.destroy() - delete guestInstances[id] - - key = reverseEmbedderElementsMap[id] - if key? - delete reverseEmbedderElementsMap[id] - delete embedderElementsMap[key] - -ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', (event, params, requestId) -> - event.sender.send "ATOM_SHELL_RESPONSE_#{requestId}", createGuest(event.sender, params) - -ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', (event, elementInstanceId, guestInstanceId, params) -> - attachGuest event.sender, elementInstanceId, guestInstanceId, params - -ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', (event, id) -> - destroyGuest event.sender, id - -ipcMain.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', (event, id, params) -> - guestInstances[id]?.guest.setSize params - -ipcMain.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 diff --git a/atom/browser/lib/guest-view-manager.js b/atom/browser/lib/guest-view-manager.js new file mode 100644 index 000000000000..bf92cf80706c --- /dev/null +++ b/atom/browser/lib/guest-view-manager.js @@ -0,0 +1,217 @@ +const ipcMain = require('electron').ipcMain; +const webContents = require('electron').webContents; + +var slice = [].slice; + +// Doesn't exist in early initialization. +var webViewManager = null; + +var supportedWebViewEvents = ['load-commit', 'did-finish-load', 'did-fail-load', 'did-frame-finish-load', 'did-start-loading', 'did-stop-loading', 'did-get-response-details', 'did-get-redirect-request', 'dom-ready', 'console-message', 'devtools-opened', 'devtools-closed', 'devtools-focused', 'new-window', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'close', 'crashed', 'gpu-crashed', 'plugin-crashed', 'destroyed', 'page-title-updated', 'page-favicon-updated', 'enter-html-full-screen', 'leave-html-full-screen', 'media-started-playing', 'media-paused', 'found-in-page', 'did-change-theme-color']; + +var nextInstanceId = 0; +var guestInstances = {}; +var embedderElementsMap = {}; +var reverseEmbedderElementsMap = {}; + +// Moves the last element of array to the first one. +var moveLastToFirst = function(list) { + return list.unshift(list.pop()); +}; + +// Generate guestInstanceId. +var getNextInstanceId = function(webContents) { + return ++nextInstanceId; +}; + +// Create a new guest instance. +var createGuest = function(embedder, params) { + var destroy, destroyEvents, event, fn, guest, i, id, j, len, len1, listeners; + if (webViewManager == null) { + webViewManager = process.atomBinding('web_view_manager'); + } + id = getNextInstanceId(embedder); + guest = webContents.create({ + isGuest: true, + partition: params.partition, + embedder: embedder + }); + guestInstances[id] = { + guest: guest, + embedder: embedder + }; + + // Destroy guest when the embedder is gone or navigated. + destroyEvents = ['will-destroy', 'crashed', 'did-navigate']; + destroy = function() { + if (guestInstances[id] != null) { + return destroyGuest(embedder, id); + } + }; + for (i = 0, len = destroyEvents.length; i < len; i++) { + event = destroyEvents[i]; + embedder.once(event, destroy); + + // Users might also listen to the crashed event, so We must ensure the guest + // is destroyed before users' listener gets called. It is done by moving our + // listener to the first one in queue. + listeners = embedder._events[event]; + if (Array.isArray(listeners)) { + moveLastToFirst(listeners); + } + } + guest.once('destroyed', function() { + var j, len1, results; + results = []; + for (j = 0, len1 = destroyEvents.length; j < len1; j++) { + event = destroyEvents[j]; + results.push(embedder.removeListener(event, destroy)); + } + return results; + }); + + // Init guest web view after attached. + guest.once('did-attach', function() { + var opts; + params = this.attachParams; + delete this.attachParams; + this.viewInstanceId = params.instanceId; + this.setSize({ + normal: { + width: params.elementWidth, + height: params.elementHeight + }, + enableAutoSize: params.autosize, + min: { + width: params.minwidth, + height: params.minheight + }, + max: { + width: params.maxwidth, + height: params.maxheight + } + }); + if (params.src) { + opts = {}; + if (params.httpreferrer) { + opts.httpReferrer = params.httpreferrer; + } + if (params.useragent) { + opts.userAgent = params.useragent; + } + this.loadURL(params.src, opts); + } + if (params.allowtransparency != null) { + this.setAllowTransparency(params.allowtransparency); + } + return guest.allowPopups = params.allowpopups; + }); + + // Dispatch events to embedder. + fn = function(event) { + return guest.on(event, function() { + var _, args; + _ = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + guest.viewInstanceId, event].concat(slice.call(args))); + }); + }; + for (j = 0, len1 = supportedWebViewEvents.length; j < len1; j++) { + event = supportedWebViewEvents[j]; + fn(event); + } + + // Dispatch guest's IPC messages to embedder. + guest.on('ipc-message-host', function(_, packed) { + var args, channel; + channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; + return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + guest.viewInstanceId, channel].concat(slice.call(args))); + }); + + // Autosize. + guest.on('size-changed', function() { + var _, args; + _ = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + guest.viewInstanceId].concat(slice.call(args))); + }); + return id; +}; + +// Attach the guest to an element of embedder. +var attachGuest = function(embedder, elementInstanceId, guestInstanceId, params) { + var guest, key, oldGuestInstanceId, ref1, webPreferences; + guest = guestInstances[guestInstanceId].guest; + + // Destroy the old guest when attaching. + key = (embedder.getId()) + "-" + elementInstanceId; + oldGuestInstanceId = embedderElementsMap[key]; + if (oldGuestInstanceId != null) { + + // Reattachment to the same guest is not currently supported. + if (oldGuestInstanceId === guestInstanceId) { + return; + } + if (guestInstances[oldGuestInstanceId] == null) { + return; + } + destroyGuest(embedder, oldGuestInstanceId); + } + webPreferences = { + guestInstanceId: guestInstanceId, + nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false, + plugins: params.plugins, + webSecurity: !params.disablewebsecurity + }; + if (params.preload) { + webPreferences.preloadURL = params.preload; + } + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences); + guest.attachParams = params; + embedderElementsMap[key] = guestInstanceId; + return reverseEmbedderElementsMap[guestInstanceId] = key; +}; + +// Destroy an existing guest instance. +var destroyGuest = function(embedder, id) { + var key; + webViewManager.removeGuest(embedder, id); + guestInstances[id].guest.destroy(); + delete guestInstances[id]; + key = reverseEmbedderElementsMap[id]; + if (key != null) { + delete reverseEmbedderElementsMap[id]; + return delete embedderElementsMap[key]; + } +}; + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', function(event, params, requestId) { + return event.sender.send("ATOM_SHELL_RESPONSE_" + requestId, createGuest(event.sender, params)); +}); + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', function(event, elementInstanceId, guestInstanceId, params) { + return attachGuest(event.sender, elementInstanceId, guestInstanceId, params); +}); + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', function(event, id) { + return destroyGuest(event.sender, id); +}); + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', function(event, id, params) { + var ref1; + return (ref1 = guestInstances[id]) != null ? ref1.guest.setSize(params) : void 0; +}); + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', function(event, id, allowtransparency) { + var ref1; + return (ref1 = guestInstances[id]) != null ? ref1.guest.setAllowTransparency(allowtransparency) : void 0; +}); + +// Returns WebContents from its guest id. +exports.getGuest = function(id) { + var ref1; + return (ref1 = guestInstances[id]) != null ? ref1.guest : void 0; +}; + +// Returns the embedder of the guest. +exports.getEmbedder = function(id) { + var ref1; + return (ref1 = guestInstances[id]) != null ? ref1.embedder : void 0; +}; diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee deleted file mode 100644 index af73db5c78b8..000000000000 --- a/atom/browser/lib/guest-window-manager.coffee +++ /dev/null @@ -1,86 +0,0 @@ -{ipcMain, BrowserWindow} = require 'electron' -v8Util = process.atomBinding 'v8_util' - -frameToGuest = {} - -# Copy attribute of |parent| to |child| if it is not defined in |child|. -mergeOptions = (child, parent) -> - for own key, value of parent when key not of child - if typeof value is 'object' - child[key] = mergeOptions {}, value - else - child[key] = value - child - -# Merge |options| with the |embedder|'s window's options. -mergeBrowserWindowOptions = (embedder, options) -> - if embedder.browserWindowOptions? - # Inherit the original options if it is a BrowserWindow. - mergeOptions options, embedder.browserWindowOptions - else - # Or only inherit web-preferences if it is a webview. - options.webPreferences ?= {} - mergeOptions options.webPreferences, embedder.getWebPreferences() - options - -# Create a new guest created by |embedder| with |options|. -createGuest = (embedder, url, frameName, options) -> - guest = frameToGuest[frameName] - if frameName and guest? - guest.loadURL url - return guest.id - - # Remember the embedder window's id. - options.webPreferences ?= {} - options.webPreferences.openerId = BrowserWindow.fromWebContents(embedder)?.id - - guest = new BrowserWindow(options) - guest.loadURL url - - # When |embedder| is destroyed we should also destroy attached guest, and if - # guest is closed by user then we should prevent |embedder| from double - # closing guest. - guestId = guest.id - closedByEmbedder = -> - guest.removeListener 'closed', closedByUser - guest.destroy() - closedByUser = -> - embedder.send "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{guestId}" - embedder.removeListener 'render-view-deleted', closedByEmbedder - embedder.once 'render-view-deleted', closedByEmbedder - guest.once 'closed', closedByUser - - if frameName - frameToGuest[frameName] = guest - guest.frameName = frameName - guest.once 'closed', -> - delete frameToGuest[frameName] - - guest.id - -# Routed window.open messages. -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, args...) -> - [url, frameName, options] = args - options = mergeBrowserWindowOptions event.sender, options - event.sender.emit 'new-window', event, url, frameName, 'new-window', options - if (event.sender.isGuest() and not event.sender.allowPopups) or event.defaultPrevented - event.returnValue = null - else - event.returnValue = createGuest event.sender, url, frameName, options - -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, guestId) -> - BrowserWindow.fromId(guestId)?.destroy() - -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, method, args...) -> - BrowserWindow.fromId(guestId)?[method] args... - -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> - sourceId = BrowserWindow.fromWebContents(event.sender)?.id - return unless sourceId? - - guestContents = BrowserWindow.fromId(guestId)?.webContents - if guestContents?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin - -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> - BrowserWindow.fromId(guestId)?.webContents?[method] args... diff --git a/atom/browser/lib/guest-window-manager.js b/atom/browser/lib/guest-window-manager.js new file mode 100644 index 000000000000..152167cccc0c --- /dev/null +++ b/atom/browser/lib/guest-window-manager.js @@ -0,0 +1,126 @@ +const ipcMain = require('electron').ipcMain; +const BrowserWindow = require('electron').BrowserWindow; + +const v8Util = process.atomBinding('v8_util'); + +var hasProp = {}.hasOwnProperty; +var slice = [].slice; +var frameToGuest = {}; + +// Copy attribute of |parent| to |child| if it is not defined in |child|. +var mergeOptions = function(child, parent) { + var key, value; + for (key in parent) { + if (!hasProp.call(parent, key)) continue; + value = parent[key]; + if (!(key in child)) { + if (typeof value === 'object') { + child[key] = mergeOptions({}, value); + } else { + child[key] = value; + } + } + } + return child; +}; + +// Merge |options| with the |embedder|'s window's options. +var mergeBrowserWindowOptions = function(embedder, options) { + if (embedder.browserWindowOptions != null) { + + // Inherit the original options if it is a BrowserWindow. + mergeOptions(options, embedder.browserWindowOptions); + } else { + + // Or only inherit web-preferences if it is a webview. + if (options.webPreferences == null) { + options.webPreferences = {}; + } + mergeOptions(options.webPreferences, embedder.getWebPreferences()); + } + return options; +}; + +// Create a new guest created by |embedder| with |options|. +var createGuest = function(embedder, url, frameName, options) { + var closedByEmbedder, closedByUser, guest, guestId, ref1; + guest = frameToGuest[frameName]; + if (frameName && (guest != null)) { + guest.loadURL(url); + return guest.id; + } + + // Remember the embedder window's id. + if (options.webPreferences == null) { + options.webPreferences = {}; + } + options.webPreferences.openerId = (ref1 = BrowserWindow.fromWebContents(embedder)) != null ? ref1.id : void 0; + guest = new BrowserWindow(options); + guest.loadURL(url); + + // When |embedder| is destroyed we should also destroy attached guest, and if + // guest is closed by user then we should prevent |embedder| from double + // closing guest. + guestId = guest.id; + closedByEmbedder = function() { + guest.removeListener('closed', closedByUser); + return guest.destroy(); + }; + closedByUser = function() { + embedder.send("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + guestId); + return embedder.removeListener('render-view-deleted', closedByEmbedder); + }; + embedder.once('render-view-deleted', closedByEmbedder); + guest.once('closed', closedByUser); + if (frameName) { + frameToGuest[frameName] = guest; + guest.frameName = frameName; + guest.once('closed', function() { + return delete frameToGuest[frameName]; + }); + } + return guest.id; +}; + +// Routed window.open messages. +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function() { + var args, event, frameName, options, url; + event = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + url = args[0], frameName = args[1], options = args[2]; + options = mergeBrowserWindowOptions(event.sender, options); + event.sender.emit('new-window', event, url, frameName, 'new-window', options); + if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) { + return event.returnValue = null; + } else { + return event.returnValue = createGuest(event.sender, url, frameName, options); + } +}); + +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function(event, guestId) { + var ref1; + return (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1.destroy() : void 0; +}); + +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function() { + var args, event, guestId, method, ref1; + event = arguments[0], guestId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; + return (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1[method].apply(ref1, args) : void 0; +}); + +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function(event, guestId, message, targetOrigin, sourceOrigin) { + var guestContents, ref1, ref2, sourceId; + sourceId = (ref1 = BrowserWindow.fromWebContents(event.sender)) != null ? ref1.id : void 0; + if (sourceId == null) { + return; + } + guestContents = (ref2 = BrowserWindow.fromId(guestId)) != null ? ref2.webContents : void 0; + if ((guestContents != null ? guestContents.getURL().indexOf(targetOrigin) : void 0) === 0 || targetOrigin === '*') { + return guestContents != null ? guestContents.send('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) : void 0; + } +}); + +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function() { + var args, event, guestId, method, ref1, ref2; + event = arguments[0], guestId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; + return (ref1 = BrowserWindow.fromId(guestId)) != null ? (ref2 = ref1.webContents) != null ? ref2[method].apply(ref2, args) : void 0 : void 0; +}); diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee deleted file mode 100644 index c3d56d9c7c82..000000000000 --- a/atom/browser/lib/init.coffee +++ /dev/null @@ -1,118 +0,0 @@ -fs = require 'fs' -path = require 'path' -util = require 'util' -Module = require 'module' - -# We modified the original process.argv to let node.js load the atom.js, -# we need to restore it here. -process.argv.splice 1, 1 - -# Clear search paths. -require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths') - -# Import common settings. -require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init') - -globalPaths = Module.globalPaths -unless process.env.ELECTRON_HIDE_INTERNAL_MODULES - globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') - -# Expose public APIs. -globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports') - -if process.platform is 'win32' - # Redirect node's console to use our own implementations, since node can not - # handle console output when running as GUI program. - consoleLog = (args...) -> - process.log util.format(args...) + "\n" - streamWrite = (chunk, encoding, callback) -> - chunk = chunk.toString(encoding) if Buffer.isBuffer chunk - process.log chunk - callback() if callback - true - console.log = console.error = console.warn = consoleLog - process.stdout.write = process.stderr.write = streamWrite - - # Always returns EOF for stdin stream. - Readable = require('stream').Readable - stdin = new Readable - stdin.push null - process.__defineGetter__ 'stdin', -> stdin - -# Don't quit on fatal error. -process.on 'uncaughtException', (error) -> - # Do nothing if the user has a custom uncaught exception handler. - if process.listeners('uncaughtException').length > 1 - return - - # Show error in GUI. - {dialog} = require 'electron' - stack = error.stack ? "#{error.name}: #{error.message}" - message = "Uncaught Exception:\n#{stack}" - dialog.showErrorBox 'A JavaScript error occurred in the main process', message - -# Emit 'exit' event on quit. -{app} = require 'electron' -app.on 'quit', (event, exitCode) -> - process.emit 'exit', exitCode - -# Map process.exit to app.exit, which quits gracefully. -process.exit = app.exit - -# Load the RPC server. -require './rpc-server' - -# Load the guest view manager. -require './guest-view-manager' -require './guest-window-manager' - -# Now we try to load app's package.json. -packageJson = null - -searchPaths = [ 'app', 'app.asar', 'default_app' ] -for packagePath in searchPaths - try - packagePath = path.join process.resourcesPath, packagePath - packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'))) - break - catch e - continue - -unless packageJson? - process.nextTick -> process.exit 1 - throw new Error("Unable to find a valid app") - -# Set application's version. -app.setVersion packageJson.version if packageJson.version? - -# Set application's name. -if packageJson.productName? - app.setName packageJson.productName -else if packageJson.name? - app.setName packageJson.name - -# Set application's desktop name. -if packageJson.desktopName? - app.setDesktopName packageJson.desktopName -else - app.setDesktopName "#{app.getName()}.desktop" - -# Chrome 42 disables NPAPI plugins by default, reenable them here -app.commandLine.appendSwitch 'enable-npapi' - -# Set the user path according to application's name. -app.setPath 'userData', path.join(app.getPath('appData'), app.getName()) -app.setPath 'userCache', path.join(app.getPath('cache'), app.getName()) -app.setAppPath packagePath - -# Load the chrome extension support. -require './chrome-extension' - -# Load internal desktop-capturer module. -require './desktop-capturer' - -# Set main startup script of the app. -mainStartupScript = packageJson.main or 'index.js' - -# Finally load app's main.js and transfer control to C++. -Module._load path.join(packagePath, mainStartupScript), Module, true diff --git a/atom/browser/lib/init.js b/atom/browser/lib/init.js new file mode 100644 index 000000000000..272cebbf875b --- /dev/null +++ b/atom/browser/lib/init.js @@ -0,0 +1,152 @@ +const fs = require('fs'); +const path = require('path'); +const util = require('util'); +const Module = require('module'); + +var slice = [].slice; + +// We modified the original process.argv to let node.js load the atom.js, +// we need to restore it here. +process.argv.splice(1, 1); + +// Clear search paths. +require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')); + +// Import common settings. +require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')); + +var globalPaths = Module.globalPaths; + +if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { + globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); +} + +// Expose public APIs. +globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib', 'exports')); + +if (process.platform === 'win32') { + // Redirect node's console to use our own implementations, since node can not + // handle console output when running as GUI program. + var consoleLog = function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return process.log(util.format.apply(util, args) + "\n"); + }; + var streamWrite = function(chunk, encoding, callback) { + if (Buffer.isBuffer(chunk)) { + chunk = chunk.toString(encoding); + } + process.log(chunk); + if (callback) { + callback(); + } + return true; + }; + console.log = console.error = console.warn = consoleLog; + process.stdout.write = process.stderr.write = streamWrite; + + // Always returns EOF for stdin stream. + var Readable = require('stream').Readable; + var stdin = new Readable; + stdin.push(null); + process.__defineGetter__('stdin', function() { + return stdin; + }); +} + +// Don't quit on fatal error. +process.on('uncaughtException', function(error) { + + // Do nothing if the user has a custom uncaught exception handler. + var dialog, message, ref, stack; + if (process.listeners('uncaughtException').length > 1) { + return; + } + + // Show error in GUI. + dialog = require('electron').dialog; + stack = (ref = error.stack) != null ? ref : error.name + ": " + error.message; + message = "Uncaught Exception:\n" + stack; + return dialog.showErrorBox('A JavaScript error occurred in the main process', message); +}); + +// Emit 'exit' event on quit. +var app = require('electron').app; + +app.on('quit', function(event, exitCode) { + return process.emit('exit', exitCode); +}); + +// Map process.exit to app.exit, which quits gracefully. +process.exit = app.exit; + +// Load the RPC server. +require('./rpc-server'); + +// Load the guest view manager. +require('./guest-view-manager'); + +require('./guest-window-manager'); + +// Now we try to load app's package.json. +var packageJson = null; +var searchPaths = ['app', 'app.asar', 'default_app']; +var i, len, packagePath; +for (i = 0, len = searchPaths.length; i < len; i++) { + packagePath = searchPaths[i]; + try { + packagePath = path.join(process.resourcesPath, packagePath); + packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'))); + break; + } catch (error) { + continue; + } +} + +if (packageJson == null) { + process.nextTick(function() { + return process.exit(1); + }); + throw new Error("Unable to find a valid app"); +} + +// Set application's version. +if (packageJson.version != null) { + app.setVersion(packageJson.version); +} + +// Set application's name. +if (packageJson.productName != null) { + app.setName(packageJson.productName); +} else if (packageJson.name != null) { + app.setName(packageJson.name); +} + +// Set application's desktop name. +if (packageJson.desktopName != null) { + app.setDesktopName(packageJson.desktopName); +} else { + app.setDesktopName((app.getName()) + ".desktop"); +} + +// Chrome 42 disables NPAPI plugins by default, reenable them here +app.commandLine.appendSwitch('enable-npapi'); + +// Set the user path according to application's name. +app.setPath('userData', path.join(app.getPath('appData'), app.getName())); + +app.setPath('userCache', path.join(app.getPath('cache'), app.getName())); + +app.setAppPath(packagePath); + +// Load the chrome extension support. +require('./chrome-extension'); + +// Load internal desktop-capturer module. +require('./desktop-capturer'); + +// Set main startup script of the app. +var mainStartupScript = packageJson.main || 'index.js'; + +// Finally load app's main.js and transfer control to C++. +Module._load(path.join(packagePath, mainStartupScript), Module, true); diff --git a/atom/browser/lib/objects-registry.coffee b/atom/browser/lib/objects-registry.coffee deleted file mode 100644 index 53b00ed7f82b..000000000000 --- a/atom/browser/lib/objects-registry.coffee +++ /dev/null @@ -1,67 +0,0 @@ -{EventEmitter} = require 'events' -v8Util = process.atomBinding 'v8_util' - -class ObjectsRegistry extends EventEmitter - constructor: -> - @setMaxListeners Number.MAX_VALUE - @nextId = 0 - - # Stores all objects by ref-counting. - # (id) => {object, count} - @storage = {} - - # Stores the IDs of objects referenced by WebContents. - # (webContentsId) => {(id) => (count)} - @owners = {} - - # Register a new object, the object would be kept referenced until you release - # it explicitly. - add: (webContentsId, obj) -> - id = @saveToStorage obj - # Remember the owner. - @owners[webContentsId] ?= {} - @owners[webContentsId][id] ?= 0 - @owners[webContentsId][id]++ - # Returns object's id - id - - # Get an object according to its ID. - get: (id) -> - @storage[id]?.object - - # Dereference an object according to its ID. - remove: (webContentsId, id) -> - @dereference id, 1 - # Also reduce the count in owner. - pointer = @owners[webContentsId] - return unless pointer? - --pointer[id] - delete pointer[id] if pointer[id] is 0 - - # Clear all references to objects refrenced by the WebContents. - clear: (webContentsId) -> - @emit "clear-#{webContentsId}" - return unless @owners[webContentsId]? - @dereference id, count for id, count of @owners[webContentsId] - delete @owners[webContentsId] - - # Private: Saves the object into storage and assigns an ID for it. - saveToStorage: (object) -> - id = v8Util.getHiddenValue object, 'atomId' - unless id - id = ++@nextId - @storage[id] = {count: 0, object} - v8Util.setHiddenValue object, 'atomId', id - ++@storage[id].count - id - - # Private: Dereference the object from store. - dereference: (id, count) -> - pointer = @storage[id] - return unless pointer? - pointer.count -= count - if pointer.count is 0 - v8Util.deleteHiddenValue pointer.object, 'atomId' - delete @storage[id] - -module.exports = new ObjectsRegistry diff --git a/atom/browser/lib/objects-registry.js b/atom/browser/lib/objects-registry.js new file mode 100644 index 000000000000..953b2f47c9fd --- /dev/null +++ b/atom/browser/lib/objects-registry.js @@ -0,0 +1,115 @@ +const EventEmitter = require('events').EventEmitter; +const v8Util = process.atomBinding('v8_util'); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; +var hasProp = {}.hasOwnProperty; + +var ObjectsRegistry = (function(superClass) { + extend(ObjectsRegistry, superClass); + + function ObjectsRegistry() { + this.setMaxListeners(Number.MAX_VALUE); + this.nextId = 0; + + // Stores all objects by ref-counting. + // (id) => {object, count} + this.storage = {}; + + // Stores the IDs of objects referenced by WebContents. + // (webContentsId) => {(id) => (count)} + this.owners = {}; + } + + // Register a new object, the object would be kept referenced until you release + // it explicitly. + ObjectsRegistry.prototype.add = function(webContentsId, obj) { + var base, base1, id; + id = this.saveToStorage(obj); + + // Remember the owner. + if ((base = this.owners)[webContentsId] == null) { + base[webContentsId] = {}; + } + if ((base1 = this.owners[webContentsId])[id] == null) { + base1[id] = 0; + } + this.owners[webContentsId][id]++; + + // Returns object's id + return id; + }; + + + // Get an object according to its ID. + ObjectsRegistry.prototype.get = function(id) { + var ref; + return (ref = this.storage[id]) != null ? ref.object : void 0; + }; + + + // Dereference an object according to its ID. + ObjectsRegistry.prototype.remove = function(webContentsId, id) { + var pointer; + this.dereference(id, 1); + + // Also reduce the count in owner. + pointer = this.owners[webContentsId]; + if (pointer == null) { + return; + } + --pointer[id]; + if (pointer[id] === 0) { + return delete pointer[id]; + } + }; + + // Clear all references to objects refrenced by the WebContents. + ObjectsRegistry.prototype.clear = function(webContentsId) { + var count, id, ref; + this.emit("clear-" + webContentsId); + if (this.owners[webContentsId] == null) { + return; + } + ref = this.owners[webContentsId]; + for (id in ref) { + count = ref[id]; + this.dereference(id, count); + } + return delete this.owners[webContentsId]; + }; + + // Private: Saves the object into storage and assigns an ID for it. + ObjectsRegistry.prototype.saveToStorage = function(object) { + var id; + id = v8Util.getHiddenValue(object, 'atomId'); + if (!id) { + id = ++this.nextId; + this.storage[id] = { + count: 0, + object: object + }; + v8Util.setHiddenValue(object, 'atomId', id); + } + ++this.storage[id].count; + return id; + }; + + // Private: Dereference the object from store. + ObjectsRegistry.prototype.dereference = function(id, count) { + var pointer; + pointer = this.storage[id]; + if (pointer == null) { + return; + } + pointer.count -= count; + if (pointer.count === 0) { + v8Util.deleteHiddenValue(pointer.object, 'atomId'); + return delete this.storage[id]; + } + }; + + return ObjectsRegistry; + +})(EventEmitter); + +module.exports = new ObjectsRegistry; diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee deleted file mode 100644 index 7b05fa3d14ca..000000000000 --- a/atom/browser/lib/rpc-server.coffee +++ /dev/null @@ -1,231 +0,0 @@ -path = require 'path' - -electron = require 'electron' -{ipcMain} = electron -objectsRegistry = require './objects-registry' - -v8Util = process.atomBinding 'v8_util' -{IDWeakMap} = process.atomBinding 'id_weak_map' - -# Convert a real value into meta data. -valueToMeta = (sender, value, optimizeSimpleObject=false) -> - meta = type: typeof value - - meta.type = 'buffer' if Buffer.isBuffer value - meta.type = 'value' if value is null - meta.type = 'array' if Array.isArray value - meta.type = 'error' if value instanceof Error - meta.type = 'date' if value instanceof Date - meta.type = 'promise' if value?.constructor.name is 'Promise' - - # Treat simple objects as value. - if optimizeSimpleObject and meta.type is 'object' and v8Util.getHiddenValue value, 'simple' - meta.type = 'value' - - # Treat the arguments object as array. - meta.type = 'array' if meta.type is 'object' and value.callee? and value.length? - - if meta.type is 'array' - meta.members = [] - meta.members.push valueToMeta(sender, el) for el in value - else if meta.type is 'object' or meta.type is 'function' - meta.name = value.constructor.name - - # Reference the original value if it's an object, because when it's - # passed to renderer we would assume the renderer keeps a reference of - # it. - meta.id = objectsRegistry.add sender.getId(), value - - meta.members = ({name, type: typeof field} for name, field of value) - else if meta.type is 'buffer' - meta.value = Array::slice.call value, 0 - else if meta.type is 'promise' - meta.then = valueToMeta sender, value.then.bind(value) - else if meta.type is 'error' - meta.members = plainObjectToMeta value - # Error.name is not part of own properties. - meta.members.push {name: 'name', value: value.name} - else if meta.type is 'date' - meta.value = value.getTime() - else - meta.type = 'value' - meta.value = value - - meta - -# Convert object to meta by value. -plainObjectToMeta = (obj) -> - Object.getOwnPropertyNames(obj).map (name) -> {name, value: obj[name]} - -# Convert Error into meta data. -exceptionToMeta = (error) -> - type: 'exception', message: error.message, stack: (error.stack || error) - -# Convert array of meta data from renderer into array of real values. -unwrapArgs = (sender, args) -> - metaToValue = (meta) -> - switch meta.type - when 'value' then meta.value - when 'remote-object' then objectsRegistry.get meta.id - when 'array' then unwrapArgs sender, meta.value - when 'buffer' then new Buffer(meta.value) - when 'date' then new Date(meta.value) - when 'promise' then Promise.resolve(then: metaToValue(meta.then)) - when 'object' - ret = v8Util.createObjectWithName meta.name - for member in meta.members - ret[member.name] = metaToValue(member.value) - ret - when 'function-with-return-value' - returnValue = metaToValue meta.value - -> returnValue - when 'function' - # Cache the callbacks in renderer. - unless sender.callbacks - sender.callbacks = new IDWeakMap - sender.on 'render-view-deleted', -> - sender.callbacks.clear() - return sender.callbacks.get meta.id if sender.callbacks.has meta.id - - rendererReleased = false - objectsRegistry.once "clear-#{sender.getId()}", -> - rendererReleased = true - - ret = -> - if rendererReleased - throw new Error("Attempting to call a function in a renderer window - that has been closed or released. Function provided here: #{meta.location}.") - sender.send 'ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, arguments) - v8Util.setDestructor ret, -> - return if rendererReleased - sender.callbacks.remove meta.id - sender.send 'ATOM_RENDERER_RELEASE_CALLBACK', meta.id - sender.callbacks.set meta.id, ret - ret - else throw new TypeError("Unknown type: #{meta.type}") - - args.map metaToValue - -# Call a function and send reply asynchronously if it's a an asynchronous -# style function and the caller didn't pass a callback. -callFunction = (event, func, caller, args) -> - funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') - funcPassedCallback = typeof args[args.length - 1] is 'function' - - try - if funcMarkedAsync and not funcPassedCallback - args.push (ret) -> - event.returnValue = valueToMeta event.sender, ret, true - func.apply caller, args - else - ret = func.apply caller, args - event.returnValue = valueToMeta event.sender, ret, true - catch e - # Catch functions thrown further down in function invocation and wrap - # them with the function name so it's easier to trace things like - # `Error processing argument -1.` - funcName = func.name ? "anonymous" - throw new Error("Could not call remote function `#{funcName}`. - Check that the function signature is correct. - Underlying error: #{e.message}") - -# Send by BrowserWindow when its render view is deleted. -process.on 'ATOM_BROWSER_RELEASE_RENDER_VIEW', (id) -> - objectsRegistry.clear id - -ipcMain.on 'ATOM_BROWSER_REQUIRE', (event, module) -> - try - event.returnValue = valueToMeta event.sender, process.mainModule.require(module) - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_GET_BUILTIN', (event, module) -> - try - event.returnValue = valueToMeta event.sender, electron[module] - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_GLOBAL', (event, name) -> - try - event.returnValue = valueToMeta event.sender, global[name] - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_CURRENT_WINDOW', (event) -> - try - event.returnValue = valueToMeta event.sender, event.sender.getOwnerBrowserWindow() - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_CURRENT_WEB_CONTENTS', (event) -> - event.returnValue = valueToMeta event.sender, event.sender - -ipcMain.on 'ATOM_BROWSER_CONSTRUCTOR', (event, id, args) -> - try - args = unwrapArgs event.sender, args - constructor = objectsRegistry.get id - # Call new with array of arguments. - # http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible - obj = new (Function::bind.apply(constructor, [null].concat(args))) - event.returnValue = valueToMeta event.sender, obj - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_FUNCTION_CALL', (event, id, args) -> - try - args = unwrapArgs event.sender, args - func = objectsRegistry.get id - callFunction event, func, global, args - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', (event, id, method, args) -> - try - args = unwrapArgs event.sender, args - constructor = objectsRegistry.get(id)[method] - # Call new with array of arguments. - obj = new (Function::bind.apply(constructor, [null].concat(args))) - event.returnValue = valueToMeta event.sender, obj - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_MEMBER_CALL', (event, id, method, args) -> - try - args = unwrapArgs event.sender, args - obj = objectsRegistry.get id - callFunction event, obj[method], obj, args - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_MEMBER_SET', (event, id, name, value) -> - try - obj = objectsRegistry.get id - obj[name] = value - event.returnValue = null - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_MEMBER_GET', (event, id, name) -> - try - obj = objectsRegistry.get id - event.returnValue = valueToMeta event.sender, obj[name] - catch e - event.returnValue = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_DEREFERENCE', (event, id) -> - objectsRegistry.remove event.sender.getId(), id - -ipcMain.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 = exceptionToMeta e - -ipcMain.on 'ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', (event, guestInstanceId, method, args...) -> - try - guestViewManager = require './guest-view-manager' - guest = guestViewManager.getGuest(guestInstanceId) - guest[method].apply(guest, args) - catch e - event.returnValue = exceptionToMeta e diff --git a/atom/browser/lib/rpc-server.js b/atom/browser/lib/rpc-server.js new file mode 100644 index 000000000000..e7fa56b9e41a --- /dev/null +++ b/atom/browser/lib/rpc-server.js @@ -0,0 +1,363 @@ +'use strict'; + +const path = require('path'); +const electron = require('electron'); +const ipcMain = electron.ipcMain; +const objectsRegistry = require('./objects-registry'); +const v8Util = process.atomBinding('v8_util'); +const IDWeakMap = process.atomBinding('id_weak_map').IDWeakMap; + +var slice = [].slice; + +// Convert a real value into meta data. +var valueToMeta = function(sender, value, optimizeSimpleObject) { + var el, field, i, len, meta, name; + if (optimizeSimpleObject == null) { + optimizeSimpleObject = false; + } + meta = { + type: typeof value + }; + if (Buffer.isBuffer(value)) { + meta.type = 'buffer'; + } + if (value === null) { + meta.type = 'value'; + } + if (Array.isArray(value)) { + meta.type = 'array'; + } + if (value instanceof Error) { + meta.type = 'error'; + } + if (value instanceof Date) { + meta.type = 'date'; + } + if ((value != null ? value.constructor.name : void 0) === 'Promise') { + meta.type = 'promise'; + } + + // Treat simple objects as value. + if (optimizeSimpleObject && meta.type === 'object' && v8Util.getHiddenValue(value, 'simple')) { + meta.type = 'value'; + } + + // Treat the arguments object as array. + if (meta.type === 'object' && (value.hasOwnProperty('callee')) && (value.length != null)) { + meta.type = 'array'; + } + if (meta.type === 'array') { + meta.members = []; + for (i = 0, len = value.length; i < len; i++) { + el = value[i]; + meta.members.push(valueToMeta(sender, el)); + } + } else if (meta.type === 'object' || meta.type === 'function') { + meta.name = value.constructor.name; + + // Reference the original value if it's an object, because when it's + // passed to renderer we would assume the renderer keeps a reference of + // it. + meta.id = objectsRegistry.add(sender.getId(), value); + meta.members = (function() { + var results; + results = []; + for (name in value) { + field = value[name]; + results.push({ + name: name, + type: typeof field + }); + } + return results; + })(); + } else if (meta.type === 'buffer') { + meta.value = Array.prototype.slice.call(value, 0); + } else if (meta.type === 'promise') { + meta.then = valueToMeta(sender, value.then.bind(value)); + } else if (meta.type === 'error') { + meta.members = plainObjectToMeta(value); + + // Error.name is not part of own properties. + meta.members.push({ + name: 'name', + value: value.name + }); + } else if (meta.type === 'date') { + meta.value = value.getTime(); + } else { + meta.type = 'value'; + meta.value = value; + } + return meta; +}; + +// Convert object to meta by value. +var plainObjectToMeta = function(obj) { + return Object.getOwnPropertyNames(obj).map(function(name) { + return { + name: name, + value: obj[name] + }; + }); +}; + +// Convert Error into meta data. +var exceptionToMeta = function(error) { + return { + type: 'exception', + message: error.message, + stack: error.stack || error + }; +}; + +// Convert array of meta data from renderer into array of real values. +var unwrapArgs = function(sender, args) { + var metaToValue; + metaToValue = function(meta) { + var i, len, member, ref, rendererReleased, returnValue; + switch (meta.type) { + case 'value': + return meta.value; + case 'remote-object': + return objectsRegistry.get(meta.id); + case 'array': + return unwrapArgs(sender, meta.value); + case 'buffer': + return new Buffer(meta.value); + case 'date': + return new Date(meta.value); + case 'promise': + return Promise.resolve({ + then: metaToValue(meta.then) + }); + case 'object': + let ret = v8Util.createObjectWithName(meta.name); + ref = meta.members; + for (i = 0, len = ref.length; i < len; i++) { + member = ref[i]; + ret[member.name] = metaToValue(member.value); + } + return ret; + case 'function-with-return-value': + returnValue = metaToValue(meta.value); + return function() { + return returnValue; + }; + case 'function': + // Cache the callbacks in renderer. + if (!sender.callbacks) { + sender.callbacks = new IDWeakMap; + sender.on('render-view-deleted', function() { + return this.callbacks.clear(); + }); + } + + if (sender.callbacks.has(meta.id)) + return sender.callbacks.get(meta.id); + + // Prevent the callback from being called when its page is gone. + rendererReleased = false; + sender.once('render-view-deleted', function() { + rendererReleased = true; + }); + + let callIntoRenderer = function(...args) { + if (rendererReleased) + throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`); + sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)); + }; + v8Util.setDestructor(callIntoRenderer, function() { + if (!rendererReleased) + sender.send('ATOM_RENDERER_RELEASE_CALLBACK', meta.id); + }); + sender.callbacks.set(meta.id, callIntoRenderer); + return callIntoRenderer; + default: + throw new TypeError("Unknown type: " + meta.type); + } + }; + return args.map(metaToValue); +}; + +// Call a function and send reply asynchronously if it's a an asynchronous +// style function and the caller didn't pass a callback. +var callFunction = function(event, func, caller, args) { + var e, error1, funcMarkedAsync, funcName, funcPassedCallback, ref, ret; + funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous'); + funcPassedCallback = typeof args[args.length - 1] === 'function'; + try { + if (funcMarkedAsync && !funcPassedCallback) { + args.push(function(ret) { + return event.returnValue = valueToMeta(event.sender, ret, true); + }); + return func.apply(caller, args); + } else { + ret = func.apply(caller, args); + return event.returnValue = valueToMeta(event.sender, ret, true); + } + } catch (error1) { + e = error1; + + // Catch functions thrown further down in function invocation and wrap + // them with the function name so it's easier to trace things like + // `Error processing argument -1.` + funcName = (ref = func.name) != null ? ref : "anonymous"; + throw new Error("Could not call remote function `" + funcName + "`. Check that the function signature is correct. Underlying error: " + e.message); + } +}; + +// Send by BrowserWindow when its render view is deleted. +process.on('ATOM_BROWSER_RELEASE_RENDER_VIEW', function(id) { + return objectsRegistry.clear(id); +}); + +ipcMain.on('ATOM_BROWSER_REQUIRE', function(event, module) { + var e, error1; + try { + return event.returnValue = valueToMeta(event.sender, process.mainModule.require(module)); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_GET_BUILTIN', function(event, module) { + var e, error1; + try { + return event.returnValue = valueToMeta(event.sender, electron[module]); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_GLOBAL', function(event, name) { + var e, error1; + try { + return event.returnValue = valueToMeta(event.sender, global[name]); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_CURRENT_WINDOW', function(event) { + var e, error1; + try { + return event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow()); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_CURRENT_WEB_CONTENTS', function(event) { + return event.returnValue = valueToMeta(event.sender, event.sender); +}); + +ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) { + var constructor, e, error1, obj; + try { + args = unwrapArgs(event.sender, args); + constructor = objectsRegistry.get(id); + + // Call new with array of arguments. + // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible + obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))); + return event.returnValue = valueToMeta(event.sender, obj); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) { + var e, error1, func; + try { + args = unwrapArgs(event.sender, args); + func = objectsRegistry.get(id); + return callFunction(event, func, global, args); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args) { + var constructor, e, error1, obj; + try { + args = unwrapArgs(event.sender, args); + constructor = objectsRegistry.get(id)[method]; + + // Call new with array of arguments. + obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))); + return event.returnValue = valueToMeta(event.sender, obj); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) { + var e, error1, obj; + try { + args = unwrapArgs(event.sender, args); + obj = objectsRegistry.get(id); + return callFunction(event, obj[method], obj, args); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) { + var e, error1, obj; + try { + obj = objectsRegistry.get(id); + obj[name] = value; + return event.returnValue = null; + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_MEMBER_GET', function(event, id, name) { + var e, error1, obj; + try { + obj = objectsRegistry.get(id); + return event.returnValue = valueToMeta(event.sender, obj[name]); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_DEREFERENCE', function(event, id) { + return objectsRegistry.remove(event.sender.getId(), id); +}); + +ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function(event, guestInstanceId) { + var e, error1, guestViewManager; + try { + guestViewManager = require('./guest-view-manager'); + return event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId)); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); + +ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function() { + var args, e, error1, event, guest, guestInstanceId, guestViewManager, method; + event = arguments[0], guestInstanceId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; + try { + guestViewManager = require('./guest-view-manager'); + guest = guestViewManager.getGuest(guestInstanceId); + return guest[method].apply(guest, args); + } catch (error1) { + e = error1; + return event.returnValue = exceptionToMeta(e); + } +}); diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index c60c0dd5f1d6..a11be26ff7d1 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -143,6 +143,7 @@ class NativeWindow : public base::SupportsUserData, virtual void SetMenu(ui::MenuModel* menu); virtual bool HasModalDialog(); virtual gfx::NativeWindow GetNativeWindow() = 0; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() = 0; // Taskbar/Dock APIs. virtual void SetProgressBar(double progress) = 0; diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 23f210d597f6..d1703ac944da 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -65,6 +65,7 @@ class NativeWindowMac : public NativeWindow { void SetIgnoreMouseEvents(bool ignore) override; bool HasModalDialog() override; gfx::NativeWindow GetNativeWindow() override; + gfx::AcceleratedWidget GetAcceleratedWidget() override; void SetProgressBar(double progress) override; void SetOverlayIcon(const gfx::Image& overlay, const std::string& description) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 3735772fe1df..8f7699140474 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -452,6 +452,11 @@ NativeWindowMac::NativeWindowMac( set_force_using_draggable_region(true); } + bool movable; + if (options.Get(options::kMovable, &movable)) { + [window_ setMovable:movable]; + } + // On OS X the initial window size doesn't include window frame. bool use_content_size = false; options.Get(options::kUseContentSize, &use_content_size); @@ -731,6 +736,10 @@ gfx::NativeWindow NativeWindowMac::GetNativeWindow() { return window_; } +gfx::AcceleratedWidget NativeWindowMac::GetAcceleratedWidget() { + return inspectable_web_contents()->GetView()->GetNativeView(); +} + void NativeWindowMac::SetProgressBar(double progress) { NSDockTile* dock_tile = [NSApp dockTile]; diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 6c47c74331d0..125cd4740831 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -90,7 +90,7 @@ class NativeWindowViews : public NativeWindow, void SetVisibleOnAllWorkspaces(bool visible) override; bool IsVisibleOnAllWorkspaces() override; - gfx::AcceleratedWidget GetAcceleratedWidget(); + gfx::AcceleratedWidget GetAcceleratedWidget() override; views::Widget* widget() const { return window_.get(); } diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 2f321fa8fc2d..f1de499afeda 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.36.2 + 0.36.4 CFBundleShortVersionString - 0.36.2 + 0.36.4 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 225d910bc3f7..5763cb7ba3c6 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,36,2,0 - PRODUCTVERSION 0,36,2,0 + FILEVERSION 0,36,4,0 + PRODUCTVERSION 0,36,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.36.2" + VALUE "FileVersion", "0.36.4" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.36.2" + VALUE "ProductVersion", "0.36.4" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/message_box.h b/atom/browser/ui/message_box.h index 92052d3de4a4..d2eb70bcd938 100644 --- a/atom/browser/ui/message_box.h +++ b/atom/browser/ui/message_box.h @@ -38,6 +38,7 @@ int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, int cancel_id, + int default_id, int options, const std::string& title, const std::string& message, @@ -47,6 +48,7 @@ int ShowMessageBox(NativeWindow* parent_window, void ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index de8d994e5bca..9615e958b275 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -29,6 +29,7 @@ class GtkMessageBox { GtkMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, const std::string& title, const std::string& message, @@ -60,9 +61,10 @@ class GtkMessageBox { // Add buttons. for (size_t i = 0; i < buttons.size(); ++i) { - gtk_dialog_add_button(GTK_DIALOG(dialog_), - TranslateToStock(i, buttons[i]), - i); + GtkWidget* button = gtk_dialog_add_button( + GTK_DIALOG(dialog_), TranslateToStock(i, buttons[i]), i); + if (static_cast(i) == default_id) + gtk_widget_grab_focus(button); } // Parent window. @@ -161,19 +163,21 @@ void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) { int ShowMessageBox(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, const std::string& message, const std::string& detail, const gfx::ImageSkia& icon) { - return GtkMessageBox(parent, type, buttons, cancel_id, title, message, detail, - icon).RunSynchronous(); + return GtkMessageBox(parent, type, buttons, default_id, cancel_id, + title, message, detail, icon).RunSynchronous(); } void ShowMessageBox(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, @@ -181,13 +185,13 @@ void ShowMessageBox(NativeWindow* parent, const std::string& detail, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - (new GtkMessageBox(parent, type, buttons, cancel_id, title, message, detail, - icon))->RunAsynchronous(callback); + (new GtkMessageBox(parent, type, buttons, default_id, cancel_id, + title, message, detail, icon))->RunAsynchronous(callback); } void ShowErrorBox(const base::string16& title, const base::string16& content) { if (Browser::Get()->is_ready()) { - GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, { "OK" }, 0, "Error", + GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, { "OK" }, -1, 0, "Error", base::UTF16ToUTF8(title).c_str(), base::UTF16ToUTF8(content).c_str(), gfx::ImageSkia()).RunSynchronous(); diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index e518af653da4..f9e9718ebf55 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -54,6 +54,7 @@ namespace { NSAlert* CreateNSAlert(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int default_id, const std::string& title, const std::string& message, const std::string& detail) { @@ -82,6 +83,15 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, [button setTag:i]; } + NSArray* ns_buttons = [alert buttons]; + if (default_id >= 0 && default_id < static_cast([ns_buttons count])) { + // Focus the button at default_id if the user opted to do so. + // The first button added gets set as the default selected. + // So remove that default, and make the requested button the default. + [[ns_buttons objectAtIndex:0] setKeyEquivalent:@""]; + [[ns_buttons objectAtIndex:default_id] setKeyEquivalent:@"\r"]; + } + return alert; } @@ -94,6 +104,7 @@ void SetReturnCode(int* ret_code, int result) { int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, @@ -101,7 +112,8 @@ int ShowMessageBox(NativeWindow* parent_window, const std::string& detail, const gfx::ImageSkia& icon) { NSAlert* alert = CreateNSAlert( - parent_window, type, buttons, title, message, detail); + parent_window, type, buttons, default_id, title, message, + detail); // Use runModal for synchronous alert without parent, since we don't have a // window to wait for. @@ -127,6 +139,7 @@ int ShowMessageBox(NativeWindow* parent_window, void ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, @@ -135,7 +148,8 @@ void ShowMessageBox(NativeWindow* parent_window, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { NSAlert* alert = CreateNSAlert( - parent_window, type, buttons, title, message, detail); + parent_window, type, buttons, default_id, title, message, + detail); ModalDelegate* delegate = [[ModalDelegate alloc] initWithCallback:callback andAlert:alert callEndModal:false]; diff --git a/atom/browser/ui/message_box_win.cc b/atom/browser/ui/message_box_win.cc index 656be9f10bb2..2847ae21a164 100644 --- a/atom/browser/ui/message_box_win.cc +++ b/atom/browser/ui/message_box_win.cc @@ -72,6 +72,7 @@ void MapToCommonID(const std::vector& buttons, int ShowMessageBoxUTF16(HWND parent, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const base::string16& title, @@ -88,6 +89,9 @@ int ShowMessageBoxUTF16(HWND parent, config.hInstance = GetModuleHandle(NULL); config.dwFlags = flags; + if (default_id > 0) + config.nDefaultButton = kIDStart + default_id; + // TaskDialogIndirect doesn't allow empty name, if we set empty title it // will show "electron.exe" in title. base::string16 app_name = base::UTF8ToUTF16(Browser::Get()->GetName()); @@ -156,6 +160,7 @@ void RunMessageBoxInNewThread(base::Thread* thread, NativeWindow* parent, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, @@ -163,8 +168,8 @@ void RunMessageBoxInNewThread(base::Thread* thread, const std::string& detail, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - int result = ShowMessageBox(parent, type, buttons, cancel_id, options, title, - message, detail, icon); + int result = ShowMessageBox(parent, type, buttons, default_id, + cancel_id, options, title, message, detail, icon); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); content::BrowserThread::DeleteSoon( @@ -176,6 +181,7 @@ void RunMessageBoxInNewThread(base::Thread* thread, int ShowMessageBox(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, @@ -194,6 +200,7 @@ int ShowMessageBox(NativeWindow* parent, return ShowMessageBoxUTF16(hwnd_parent, type, utf16_buttons, + default_id, cancel_id, options, base::UTF8ToUTF16(title), @@ -205,6 +212,7 @@ int ShowMessageBox(NativeWindow* parent, void ShowMessageBox(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, + int default_id, int cancel_id, int options, const std::string& title, @@ -224,13 +232,13 @@ void ShowMessageBox(NativeWindow* parent, unretained->message_loop()->PostTask( FROM_HERE, base::Bind(&RunMessageBoxInNewThread, base::Unretained(unretained), - parent, type, buttons, cancel_id, options, title, message, - detail, icon, callback)); + parent, type, buttons, default_id, cancel_id, options, title, + message, detail, icon, callback)); } void ShowErrorBox(const base::string16& title, const base::string16& content) { - ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, 0, 0, L"Error", title, - content, gfx::ImageSkia()); + ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error", + title, content, gfx::ImageSkia()); } } // namespace atom diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index 274e1f533eb3..eeb26614847b 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -30,20 +30,10 @@ IPC_SYNC_MESSAGE_ROUTED2_1(AtomViewHostMsg_Message_Sync, base::ListValue /* arguments */, base::string16 /* result (in JSON) */) -IPC_MESSAGE_ROUTED1(AtomViewHostMsg_ZoomLevelChanged, - double /* level */) - -IPC_MESSAGE_ROUTED1(AtomViewMsg_SetZoomLevel, - double /* level */) - IPC_MESSAGE_ROUTED2(AtomViewMsg_Message, base::string16 /* channel */, base::ListValue /* arguments */) -IPC_MESSAGE_ROUTED2(AtomViewMsg_ExecuteJavaScript, - base::string16 /* code */, - bool /* has user gesture */) - // Sent by the renderer when the draggable regions are updated. IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions, std::vector /* regions */) diff --git a/atom/common/api/lib/callbacks-registry.coffee b/atom/common/api/lib/callbacks-registry.coffee deleted file mode 100644 index c546df34f9a8..000000000000 --- a/atom/common/api/lib/callbacks-registry.coffee +++ /dev/null @@ -1,43 +0,0 @@ -v8Util = process.atomBinding 'v8_util' - -module.exports = -class CallbacksRegistry - constructor: -> - @nextId = 0 - @callbacks = {} - - add: (callback) -> - # The callback is already added. - id = v8Util.getHiddenValue callback, 'callbackId' - return id if id? - - id = ++@nextId - - # Capture the location of the function and put it in the ID string, - # so that release errors can be tracked down easily. - regexp = /at (.*)/gi - stackString = (new Error).stack - - while (match = regexp.exec(stackString)) isnt null - [x, location] = match - continue if location.indexOf('(native)') isnt -1 - continue if location.indexOf('atom.asar') isnt -1 - [x, filenameAndLine] = /([^/^\)]*)\)?$/gi.exec(location) - break - - @callbacks[id] = callback - v8Util.setHiddenValue callback, 'callbackId', id - v8Util.setHiddenValue callback, 'location', filenameAndLine - id - - get: (id) -> - @callbacks[id] ? -> - - call: (id, args...) -> - @get(id).call global, args... - - apply: (id, args...) -> - @get(id).apply global, args... - - remove: (id) -> - delete @callbacks[id] diff --git a/atom/common/api/lib/callbacks-registry.js b/atom/common/api/lib/callbacks-registry.js new file mode 100644 index 000000000000..e4cfe8161236 --- /dev/null +++ b/atom/common/api/lib/callbacks-registry.js @@ -0,0 +1,65 @@ +var CallbacksRegistry, v8Util, + slice = [].slice; + +v8Util = process.atomBinding('v8_util'); + +module.exports = CallbacksRegistry = (function() { + function CallbacksRegistry() { + this.nextId = 0; + this.callbacks = {}; + } + + CallbacksRegistry.prototype.add = function(callback) { + // The callback is already added. + var filenameAndLine, id, location, match, ref, regexp, stackString, x; + id = v8Util.getHiddenValue(callback, 'callbackId'); + if (id != null) { + return id; + } + id = ++this.nextId; + + // Capture the location of the function and put it in the ID string, + // so that release errors can be tracked down easily. + regexp = /at (.*)/gi; + stackString = (new Error).stack; + while ((match = regexp.exec(stackString)) !== null) { + x = match[0], location = match[1]; + if (location.indexOf('(native)') !== -1) { + continue; + } + if (location.indexOf('atom.asar') !== -1) { + continue; + } + ref = /([^\/^\)]*)\)?$/gi.exec(location), x = ref[0], filenameAndLine = ref[1]; + break; + } + this.callbacks[id] = callback; + v8Util.setHiddenValue(callback, 'callbackId', id); + v8Util.setHiddenValue(callback, 'location', filenameAndLine); + return id; + }; + + CallbacksRegistry.prototype.get = function(id) { + var ref; + return (ref = this.callbacks[id]) != null ? ref : function() {}; + }; + + CallbacksRegistry.prototype.call = function() { + var args, id, ref; + id = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + return (ref = this.get(id)).call.apply(ref, [global].concat(slice.call(args))); + }; + + CallbacksRegistry.prototype.apply = function() { + var args, id, ref; + id = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + return (ref = this.get(id)).apply.apply(ref, [global].concat(slice.call(args))); + }; + + CallbacksRegistry.prototype.remove = function(id) { + return delete this.callbacks[id]; + }; + + return CallbacksRegistry; + +})(); diff --git a/atom/common/api/lib/clipboard.coffee b/atom/common/api/lib/clipboard.coffee deleted file mode 100644 index a3a6d555fe5c..000000000000 --- a/atom/common/api/lib/clipboard.coffee +++ /dev/null @@ -1,5 +0,0 @@ -if process.platform is 'linux' and process.type is 'renderer' - # On Linux we could not access clipboard in renderer process. - module.exports = require('electron').remote.clipboard -else - module.exports = process.atomBinding 'clipboard' diff --git a/atom/common/api/lib/clipboard.js b/atom/common/api/lib/clipboard.js new file mode 100644 index 000000000000..cd1cb53e4001 --- /dev/null +++ b/atom/common/api/lib/clipboard.js @@ -0,0 +1,6 @@ +if (process.platform === 'linux' && process.type === 'renderer') { + // On Linux we could not access clipboard in renderer process. + module.exports = require('electron').remote.clipboard; +} else { + module.exports = process.atomBinding('clipboard'); +} diff --git a/atom/common/api/lib/crash-reporter.coffee b/atom/common/api/lib/crash-reporter.coffee deleted file mode 100644 index 544791b7d1c0..000000000000 --- a/atom/common/api/lib/crash-reporter.coffee +++ /dev/null @@ -1,69 +0,0 @@ -fs = require 'fs' -os = require 'os' -path = require 'path' -{spawn} = require 'child_process' - -electron = require 'electron' -binding = process.atomBinding 'crash_reporter' - -class CrashReporter - start: (options={}) -> - {@productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra} = options - - # Deprecated. - {deprecate} = electron - if options.submitUrl - submitURL ?= options.submitUrl - deprecate.warn 'submitUrl', 'submitURL' - - {app} = if process.type is 'browser' then electron else electron.remote - - @productName ?= app.getName() - autoSubmit ?= true - ignoreSystemCrashHandler ?= false - extra ?= {} - - extra._productName ?= @productName - extra._companyName ?= companyName - extra._version ?= app.getVersion() - - unless companyName? - deprecate.log('companyName is now a required option to crashReporter.start') - return - - unless submitURL? - deprecate.log('submitURL is now a required option to crashReporter.start') - return - - start = => binding.start @productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra - - if process.platform is 'win32' - args = [ - "--reporter-url=#{submitURL}" - "--application-name=#{@productName}" - "--v=1" - ] - env = ATOM_SHELL_INTERNAL_CRASH_SERVICE: 1 - - spawn process.execPath, args, {env, detached: true} - start() - - getLastCrashReport: -> - reports = this.getUploadedReports() - if reports.length > 0 then reports[0] else null - - getUploadedReports: -> - tmpdir = - if process.platform is 'win32' - os.tmpdir() - else - '/tmp' - log = - if process.platform is 'darwin' - path.join tmpdir, "#{@productName} Crashes" - else - path.join tmpdir, "#{@productName} Crashes", 'uploads.log' - binding._getUploadedReports log - -crashRepoter = new CrashReporter -module.exports = crashRepoter diff --git a/atom/common/api/lib/crash-reporter.js b/atom/common/api/lib/crash-reporter.js new file mode 100644 index 000000000000..8a3de9eaa14d --- /dev/null +++ b/atom/common/api/lib/crash-reporter.js @@ -0,0 +1,95 @@ +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const spawn = require('child_process').spawn; +const electron = require('electron'); +const binding = process.atomBinding('crash_reporter'); + +var CrashReporter = (function() { + function CrashReporter() {} + + CrashReporter.prototype.start = function(options) { + var app, args, autoSubmit, companyName, deprecate, env, extra, ignoreSystemCrashHandler, start, submitURL; + if (options == null) { + options = {}; + } + this.productName = options.productName, companyName = options.companyName, submitURL = options.submitURL, autoSubmit = options.autoSubmit, ignoreSystemCrashHandler = options.ignoreSystemCrashHandler, extra = options.extra; + + // Deprecated. + deprecate = electron.deprecate; + if (options.submitUrl) { + if (submitURL == null) { + submitURL = options.submitUrl; + } + deprecate.warn('submitUrl', 'submitURL'); + } + app = (process.type === 'browser' ? electron : electron.remote).app; + if (this.productName == null) { + this.productName = app.getName(); + } + if (autoSubmit == null) { + autoSubmit = true; + } + if (ignoreSystemCrashHandler == null) { + ignoreSystemCrashHandler = false; + } + if (extra == null) { + extra = {}; + } + if (extra._productName == null) { + extra._productName = this.productName; + } + if (extra._companyName == null) { + extra._companyName = companyName; + } + if (extra._version == null) { + extra._version = app.getVersion(); + } + if (companyName == null) { + deprecate.log('companyName is now a required option to crashReporter.start'); + return; + } + if (submitURL == null) { + deprecate.log('submitURL is now a required option to crashReporter.start'); + return; + } + start = (function(_this) { + return function() { + return binding.start(_this.productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra); + }; + })(this); + if (process.platform === 'win32') { + args = ["--reporter-url=" + submitURL, "--application-name=" + this.productName, "--v=1"]; + env = { + ATOM_SHELL_INTERNAL_CRASH_SERVICE: 1 + }; + spawn(process.execPath, args, { + env: env, + detached: true + }); + } + return start(); + }; + + CrashReporter.prototype.getLastCrashReport = function() { + var reports; + reports = this.getUploadedReports(); + if (reports.length > 0) { + return reports[0]; + } else { + return null; + } + }; + + CrashReporter.prototype.getUploadedReports = function() { + var log, tmpdir; + tmpdir = process.platform === 'win32' ? os.tmpdir() : '/tmp'; + log = process.platform === 'darwin' ? path.join(tmpdir, this.productName + " Crashes") : path.join(tmpdir, this.productName + " Crashes", 'uploads.log'); + return binding._getUploadedReports(log); + }; + + return CrashReporter; + +})(); + +module.exports = new CrashReporter; diff --git a/atom/common/api/lib/deprecate.coffee b/atom/common/api/lib/deprecate.coffee deleted file mode 100644 index 71c2ee5cabac..000000000000 --- a/atom/common/api/lib/deprecate.coffee +++ /dev/null @@ -1,68 +0,0 @@ -# Deprecate a method. -deprecate = (oldName, newName, fn) -> - warned = false - -> - unless warned or process.noDeprecation - warned = true - deprecate.warn oldName, newName - fn.apply this, arguments - -# The method is renamed. -deprecate.rename = (object, oldName, newName) -> - warned = false - newMethod = -> - unless warned or process.noDeprecation - warned = true - deprecate.warn oldName, newName - this[newName].apply this, arguments - if typeof object is 'function' - object.prototype[oldName] = newMethod - else - object[oldName] = newMethod - -# Forward the method to member. -deprecate.member = (object, method, member) -> - warned = false - object.prototype[method] = -> - unless warned or process.noDeprecation - warned = true - deprecate.warn method, "#{member}.#{method}" - this[member][method].apply this[member], arguments - -# Deprecate a property. -deprecate.property = (object, property, method) -> - Object.defineProperty object, property, - get: -> - warned = false - unless warned or process.noDeprecation - warned = true - deprecate.warn "#{property} property", "#{method} method" - this[method]() - -# Deprecate an event. -deprecate.event = (emitter, oldName, newName, fn) -> - warned = false - emitter.on newName, (args...) -> - if @listenerCount(oldName) > 0 # there is listeners for old API. - unless warned or process.noDeprecation - warned = true - deprecate.warn "'#{oldName}' event", "'#{newName}' event" - if fn? - fn.apply this, arguments - else - @emit oldName, args... - -# Print deprecation warning. -deprecate.warn = (oldName, newName) -> - deprecate.log "#{oldName} is deprecated. Use #{newName} instead." - -# Print deprecation message. -deprecate.log = (message) -> - if process.throwDeprecation - throw new Error(message) - else if process.traceDeprecation - console.trace message - else - console.warn "(electron) #{message}" - -module.exports = deprecate diff --git a/atom/common/api/lib/deprecate.js b/atom/common/api/lib/deprecate.js new file mode 100644 index 000000000000..dcaf5f6cf6f9 --- /dev/null +++ b/atom/common/api/lib/deprecate.js @@ -0,0 +1,102 @@ +// Deprecate a method. +var deprecate, + slice = [].slice; + +deprecate = function(oldName, newName, fn) { + var warned; + warned = false; + return function() { + if (!(warned || process.noDeprecation)) { + warned = true; + deprecate.warn(oldName, newName); + } + return fn.apply(this, arguments); + }; +}; + +// The method is renamed. +deprecate.rename = function(object, oldName, newName) { + var newMethod, warned; + warned = false; + newMethod = function() { + if (!(warned || process.noDeprecation)) { + warned = true; + deprecate.warn(oldName, newName); + } + return this[newName].apply(this, arguments); + }; + if (typeof object === 'function') { + return object.prototype[oldName] = newMethod; + } else { + return object[oldName] = newMethod; + } +}; + +// Forward the method to member. +deprecate.member = function(object, method, member) { + var warned; + warned = false; + return object.prototype[method] = function() { + if (!(warned || process.noDeprecation)) { + warned = true; + deprecate.warn(method, member + "." + method); + } + return this[member][method].apply(this[member], arguments); + }; +}; + +// Deprecate a property. +deprecate.property = function(object, property, method) { + return Object.defineProperty(object, property, { + get: function() { + var warned; + warned = false; + if (!(warned || process.noDeprecation)) { + warned = true; + deprecate.warn(property + " property", method + " method"); + } + return this[method](); + } + }); +}; + +// Deprecate an event. +deprecate.event = function(emitter, oldName, newName, fn) { + var warned; + warned = false; + return emitter.on(newName, function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + + // there is listeners for old API. + if (this.listenerCount(oldName) > 0) { + if (!(warned || process.noDeprecation)) { + warned = true; + deprecate.warn("'" + oldName + "' event", "'" + newName + "' event"); + } + if (fn != null) { + return fn.apply(this, arguments); + } else { + return this.emit.apply(this, [oldName].concat(slice.call(args))); + } + } + }); +}; + +// Print deprecation warning. +deprecate.warn = function(oldName, newName) { + return deprecate.log(oldName + " is deprecated. Use " + newName + " instead."); +}; + +// Print deprecation message. +deprecate.log = function(message) { + if (process.throwDeprecation) { + throw new Error(message); + } else if (process.traceDeprecation) { + return console.trace(message); + } else { + return console.warn("(electron) " + message); + } +}; + +module.exports = deprecate; diff --git a/atom/common/api/lib/exports/electron.coffee b/atom/common/api/lib/exports/electron.coffee deleted file mode 100644 index 7598f9ee32fd..000000000000 --- a/atom/common/api/lib/exports/electron.coffee +++ /dev/null @@ -1,29 +0,0 @@ -# Do not expose the internal modules to `require`. -exports.hideInternalModules = -> - {globalPaths} = require 'module' - if globalPaths.length is 3 - # Remove the "common/api/lib" and "browser-or-renderer/api/lib". - globalPaths.splice 0, 2 - -# Attaches properties to |exports|. -exports.defineProperties = (exports) -> - Object.defineProperties exports, - # Common modules, please sort with alphabet order. - clipboard: - # Must be enumerable, otherwise it woulde be invisible to remote module. - enumerable: true - get: -> require '../clipboard' - crashReporter: - enumerable: true - get: -> require '../crash-reporter' - nativeImage: - enumerable: true - get: -> require '../native-image' - shell: - enumerable: true - get: -> require '../shell' - # The internal modules, invisible unless you know their names. - CallbacksRegistry: - get: -> require '../callbacks-registry' - deprecate: - get: -> require '../deprecate' diff --git a/atom/common/api/lib/exports/electron.js b/atom/common/api/lib/exports/electron.js new file mode 100644 index 000000000000..5b93d286da61 --- /dev/null +++ b/atom/common/api/lib/exports/electron.js @@ -0,0 +1,55 @@ +// Do not expose the internal modules to `require`. +exports.hideInternalModules = function() { + var globalPaths = require('module').globalPaths; + if (globalPaths.length === 3) { + + // Remove the "common/api/lib" and "browser-or-renderer/api/lib". + return globalPaths.splice(0, 2); + } +}; + +// Attaches properties to |exports|. +exports.defineProperties = function(exports) { + return Object.defineProperties(exports, { + + // Common modules, please sort with alphabet order. + clipboard: { + + // Must be enumerable, otherwise it woulde be invisible to remote module. + enumerable: true, + get: function() { + return require('../clipboard'); + } + }, + crashReporter: { + enumerable: true, + get: function() { + return require('../crash-reporter'); + } + }, + nativeImage: { + enumerable: true, + get: function() { + return require('../native-image'); + } + }, + shell: { + enumerable: true, + get: function() { + return require('../shell'); + } + }, + + // The internal modules, invisible unless you know their names. + CallbacksRegistry: { + get: function() { + return require('../callbacks-registry'); + } + }, + deprecate: { + get: function() { + return require('../deprecate'); + } + } + }); +}; diff --git a/atom/common/api/lib/native-image.coffee b/atom/common/api/lib/native-image.coffee deleted file mode 100644 index 2bdfd494f44b..000000000000 --- a/atom/common/api/lib/native-image.coffee +++ /dev/null @@ -1,7 +0,0 @@ -{deprecate} = require 'electron' -nativeImage = process.atomBinding 'native_image' - -# Deprecated. -deprecate.rename nativeImage, 'createFromDataUrl', 'createFromDataURL' - -module.exports = nativeImage diff --git a/atom/common/api/lib/native-image.js b/atom/common/api/lib/native-image.js new file mode 100644 index 000000000000..6f9a9bd7d542 --- /dev/null +++ b/atom/common/api/lib/native-image.js @@ -0,0 +1,7 @@ +const deprecate = require('electron').deprecate; +const nativeImage = process.atomBinding('native_image'); + +// Deprecated. +deprecate.rename(nativeImage, 'createFromDataUrl', 'createFromDataURL'); + +module.exports = nativeImage; diff --git a/atom/common/api/lib/shell.coffee b/atom/common/api/lib/shell.coffee deleted file mode 100644 index 5fb935bacd21..000000000000 --- a/atom/common/api/lib/shell.coffee +++ /dev/null @@ -1 +0,0 @@ -module.exports = process.atomBinding 'shell' diff --git a/atom/common/api/lib/shell.js b/atom/common/api/lib/shell.js new file mode 100644 index 000000000000..08cc4e8eb41c --- /dev/null +++ b/atom/common/api/lib/shell.js @@ -0,0 +1 @@ +module.exports = process.atomBinding('shell'); diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index ebb80cc2c485..1e986719ac6e 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -129,6 +129,9 @@ Archive::Archive(const base::FilePath& path) } Archive::~Archive() { +#if defined(OS_WIN) + _close(fd_); +#endif } bool Archive::Init() { diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 9ad405faeb6c..838eca35dbc4 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 0 #define ATOM_MINOR_VERSION 36 -#define ATOM_PATCH_VERSION 2 +#define ATOM_PATCH_VERSION 4 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee deleted file mode 100644 index 5f690e9a8f6b..000000000000 --- a/atom/common/lib/asar.coffee +++ /dev/null @@ -1,386 +0,0 @@ -asar = process.binding 'atom_common_asar' -child_process = require 'child_process' -path = require 'path' -util = require 'util' - -# Cache asar archive objects. -cachedArchives = {} -getOrCreateArchive = (p) -> - archive = cachedArchives[p] - return archive if archive? - archive = asar.createArchive p - return false unless archive - cachedArchives[p] = archive - -# Clean cache on quit. -process.on 'exit', -> - archive.destroy() for own p, archive of cachedArchives - -# Separate asar package's path from full path. -splitPath = (p) -> - return [false] if process.noAsar # shortcut to disable asar. - return [false] if typeof p isnt 'string' - return [true, p, ''] if p.substr(-5) is '.asar' - p = path.normalize p - index = p.lastIndexOf ".asar#{path.sep}" - return [false] if index is -1 - [true, p.substr(0, index + 5), p.substr(index + 6)] - -# Convert asar archive's Stats object to fs's Stats object. -nextInode = 0 -uid = if process.getuid? then process.getuid() else 0 -gid = if process.getgid? then process.getgid() else 0 -fakeTime = new Date() -asarStatsToFsStats = (stats) -> - { - dev: 1, - ino: ++nextInode, - mode: 33188, - nlink: 1, - uid: uid, - gid: gid, - rdev: 0, - atime: stats.atime || fakeTime, - birthtime: stats.birthtime || fakeTime, - mtime: stats.mtime || fakeTime, - ctime: stats.ctime || fakeTime, - size: stats.size, - isFile: -> stats.isFile - isDirectory: -> stats.isDirectory - isSymbolicLink: -> stats.isLink - isBlockDevice: -> false - isCharacterDevice: -> false - isFIFO: -> false - isSocket: -> false - } - -# Create a ENOENT error. -notFoundError = (asarPath, filePath, callback) -> - error = new Error("ENOENT, #{filePath} not found in #{asarPath}") - error.code = "ENOENT" - error.errno = -2 - unless typeof callback is 'function' - throw error - process.nextTick -> callback error - -# Create a ENOTDIR error. -notDirError = (callback) -> - error = new Error('ENOTDIR, not a directory') - error.code = 'ENOTDIR' - error.errno = -20 - unless typeof callback is 'function' - throw error - process.nextTick -> callback error - -# Create invalid archive error. -invalidArchiveError = (asarPath, callback) -> - error = new Error("Invalid package #{asarPath}") - unless typeof callback is 'function' - throw error - process.nextTick -> callback error - -# Override APIs that rely on passing file path instead of content to C++. -overrideAPISync = (module, name, arg = 0) -> - old = module[name] - module[name] = -> - p = arguments[arg] - [isAsar, asarPath, filePath] = splitPath p - return old.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - invalidArchiveError asarPath unless archive - - newPath = archive.copyFileOut filePath - notFoundError asarPath, filePath unless newPath - - arguments[arg] = newPath - old.apply this, arguments - -overrideAPI = (module, name, arg = 0) -> - old = module[name] - module[name] = -> - p = arguments[arg] - [isAsar, asarPath, filePath] = splitPath p - return old.apply this, arguments unless isAsar - - callback = arguments[arguments.length - 1] - return overrideAPISync module, name, arg unless typeof callback is 'function' - - archive = getOrCreateArchive asarPath - return invalidArchiveError asarPath, callback unless archive - - newPath = archive.copyFileOut filePath - return notFoundError asarPath, filePath, callback unless newPath - - arguments[arg] = newPath - old.apply this, arguments - -# Override fs APIs. -exports.wrapFsWithAsar = (fs) -> - lstatSync = fs.lstatSync - fs.lstatSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return lstatSync p unless isAsar - - archive = getOrCreateArchive asarPath - invalidArchiveError asarPath unless archive - - stats = archive.stat filePath - notFoundError asarPath, filePath unless stats - - asarStatsToFsStats stats - - lstat = fs.lstat - fs.lstat = (p, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return lstat p, callback unless isAsar - - archive = getOrCreateArchive asarPath - return invalidArchiveError asarPath, callback unless archive - - stats = getOrCreateArchive(asarPath).stat filePath - return notFoundError asarPath, filePath, callback unless stats - - process.nextTick -> callback null, asarStatsToFsStats stats - - statSync = fs.statSync - fs.statSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return statSync p unless isAsar - - # Do not distinguish links for now. - fs.lstatSync p - - stat = fs.stat - fs.stat = (p, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return stat p, callback unless isAsar - - # Do not distinguish links for now. - process.nextTick -> fs.lstat p, callback - - statSyncNoException = fs.statSyncNoException - fs.statSyncNoException = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return statSyncNoException p unless isAsar - - archive = getOrCreateArchive asarPath - return false unless archive - stats = archive.stat filePath - return false unless stats - asarStatsToFsStats stats - - realpathSync = fs.realpathSync - fs.realpathSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return realpathSync.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - invalidArchiveError asarPath unless archive - - real = archive.realpath filePath - notFoundError asarPath, filePath if real is false - - path.join realpathSync(asarPath), real - - realpath = fs.realpath - fs.realpath = (p, cache, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return realpath.apply this, arguments unless isAsar - - if typeof cache is 'function' - callback = cache - cache = undefined - - archive = getOrCreateArchive asarPath - return invalidArchiveError asarPath, callback unless archive - - real = archive.realpath filePath - if real is false - return notFoundError asarPath, filePath, callback - - realpath asarPath, (err, p) -> - return callback err if err - callback null, path.join(p, real) - - exists = fs.exists - fs.exists = (p, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return exists p, callback unless isAsar - - archive = getOrCreateArchive asarPath - return invalidArchiveError asarPath, callback unless archive - - process.nextTick -> callback archive.stat(filePath) isnt false - - existsSync = fs.existsSync - fs.existsSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return existsSync p unless isAsar - - archive = getOrCreateArchive asarPath - return false unless archive - - archive.stat(filePath) isnt false - - open = fs.open - readFile = fs.readFile - fs.readFile = (p, options, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return readFile.apply this, arguments unless isAsar - - if typeof options is 'function' - callback = options - options = undefined - - archive = getOrCreateArchive asarPath - return invalidArchiveError asarPath, callback unless archive - - info = archive.getFileInfo filePath - return notFoundError asarPath, filePath, callback unless info - - if info.size is 0 - return process.nextTick -> callback null, new Buffer(0) - - if info.unpacked - realPath = archive.copyFileOut filePath - return fs.readFile realPath, options, callback - - if not options - options = encoding: null - else if util.isString options - options = encoding: options - else if not util.isObject options - throw new TypeError('Bad arguments') - - encoding = options.encoding - - buffer = new Buffer(info.size) - fd = archive.getFd() - return notFoundError asarPath, filePath, callback unless fd >= 0 - - fs.read fd, buffer, 0, info.size, info.offset, (error) -> - callback error, if encoding then buffer.toString encoding else buffer - - openSync = fs.openSync - readFileSync = fs.readFileSync - fs.readFileSync = (p, opts) -> - options = opts # this allows v8 to optimize this function - [isAsar, asarPath, filePath] = splitPath p - return readFileSync.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - invalidArchiveError asarPath unless archive - - info = archive.getFileInfo filePath - notFoundError asarPath, filePath unless info - - if info.size is 0 - return if options then '' else new Buffer(0) - - if info.unpacked - realPath = archive.copyFileOut filePath - return fs.readFileSync realPath, options - - if not options - options = encoding: null - else if util.isString options - options = encoding: options - else if not util.isObject options - throw new TypeError('Bad arguments') - - encoding = options.encoding - - buffer = new Buffer(info.size) - fd = archive.getFd() - notFoundError asarPath, filePath unless fd >= 0 - - fs.readSync fd, buffer, 0, info.size, info.offset - if encoding then buffer.toString encoding else buffer - - readdir = fs.readdir - fs.readdir = (p, callback) -> - [isAsar, asarPath, filePath] = splitPath p - return readdir.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - return invalidArchiveError asarPath, callback unless archive - - files = archive.readdir filePath - return notFoundError asarPath, filePath, callback unless files - - process.nextTick -> callback null, files - - readdirSync = fs.readdirSync - fs.readdirSync = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return readdirSync.apply this, arguments unless isAsar - - archive = getOrCreateArchive asarPath - invalidArchiveError asarPath unless archive - - files = archive.readdir filePath - notFoundError asarPath, filePath unless files - - files - - internalModuleReadFile = process.binding('fs').internalModuleReadFile - process.binding('fs').internalModuleReadFile = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return internalModuleReadFile p unless isAsar - - archive = getOrCreateArchive asarPath - return undefined unless archive - - info = archive.getFileInfo filePath - return undefined unless info - return '' if info.size is 0 - - if info.unpacked - realPath = archive.copyFileOut filePath - return fs.readFileSync realPath, encoding: 'utf8' - - buffer = new Buffer(info.size) - fd = archive.getFd() - return undefined unless fd >= 0 - - fs.readSync fd, buffer, 0, info.size, info.offset - buffer.toString 'utf8' - - internalModuleStat = process.binding('fs').internalModuleStat - process.binding('fs').internalModuleStat = (p) -> - [isAsar, asarPath, filePath] = splitPath p - return internalModuleStat p unless isAsar - - archive = getOrCreateArchive asarPath - return -34 unless archive # -ENOENT - - stats = archive.stat filePath - return -34 unless stats # -ENOENT - - if stats.isDirectory then return 1 else return 0 - - # Calling mkdir for directory inside asar archive should throw ENOTDIR - # error, but on Windows it throws ENOENT. - # This is to work around the recursive looping bug of mkdirp since it is - # widely used. - if process.platform is 'win32' - mkdir = fs.mkdir - fs.mkdir = (p, mode, callback) -> - callback = mode if typeof mode is 'function' - [isAsar, asarPath, filePath] = splitPath p - return notDirError callback if isAsar and filePath.length - mkdir p, mode, callback - - mkdirSync = fs.mkdirSync - fs.mkdirSync = (p, mode) -> - [isAsar, asarPath, filePath] = splitPath p - notDirError() if isAsar and filePath.length - mkdirSync p, mode - - overrideAPI fs, 'open' - overrideAPI child_process, 'execFile' - overrideAPISync process, 'dlopen', 1 - overrideAPISync require('module')._extensions, '.node', 1 - overrideAPISync fs, 'openSync' - overrideAPISync child_process, 'execFileSync' diff --git a/atom/common/lib/asar.js b/atom/common/lib/asar.js new file mode 100644 index 000000000000..6c8aa64e024a --- /dev/null +++ b/atom/common/lib/asar.js @@ -0,0 +1,587 @@ +(function () { +const asar = process.binding('atom_common_asar'); +const child_process = require('child_process'); +const path = require('path'); +const util = require('util'); + +var hasProp = {}.hasOwnProperty; + +// Cache asar archive objects. +var cachedArchives = {}; + +var getOrCreateArchive = function(p) { + var archive; + archive = cachedArchives[p]; + if (archive != null) { + return archive; + } + archive = asar.createArchive(p); + if (!archive) { + return false; + } + return cachedArchives[p] = archive; +}; + +// Clean cache on quit. +process.on('exit', function() { + var archive, p, results; + results = []; + for (p in cachedArchives) { + if (!hasProp.call(cachedArchives, p)) continue; + archive = cachedArchives[p]; + results.push(archive.destroy()); + } + return results; +}); + +// Separate asar package's path from full path. +var splitPath = function(p) { + var index; + + // shortcut to disable asar. + if (process.noAsar) { + return [false]; + } + + if (typeof p !== 'string') { + return [false]; + } + if (p.substr(-5) === '.asar') { + return [true, p, '']; + } + p = path.normalize(p); + index = p.lastIndexOf(".asar" + path.sep); + if (index === -1) { + return [false]; + } + return [true, p.substr(0, index + 5), p.substr(index + 6)]; +}; + +// Convert asar archive's Stats object to fs's Stats object. +var nextInode = 0; + +var uid = process.getuid != null ? process.getuid() : 0; + +var gid = process.getgid != null ? process.getgid() : 0; + +var fakeTime = new Date(); + +var asarStatsToFsStats = function(stats) { + return { + dev: 1, + ino: ++nextInode, + mode: 33188, + nlink: 1, + uid: uid, + gid: gid, + rdev: 0, + atime: stats.atime || fakeTime, + birthtime: stats.birthtime || fakeTime, + mtime: stats.mtime || fakeTime, + ctime: stats.ctime || fakeTime, + size: stats.size, + isFile: function() { + return stats.isFile; + }, + isDirectory: function() { + return stats.isDirectory; + }, + isSymbolicLink: function() { + return stats.isLink; + }, + isBlockDevice: function() { + return false; + }, + isCharacterDevice: function() { + return false; + }, + isFIFO: function() { + return false; + }, + isSocket: function() { + return false; + } + }; +}; + +// Create a ENOENT error. +var notFoundError = function(asarPath, filePath, callback) { + var error; + error = new Error("ENOENT, " + filePath + " not found in " + asarPath); + error.code = "ENOENT"; + error.errno = -2; + if (typeof callback !== 'function') { + throw error; + } + return process.nextTick(function() { + return callback(error); + }); +}; + +// Create a ENOTDIR error. +var notDirError = function(callback) { + var error; + error = new Error('ENOTDIR, not a directory'); + error.code = 'ENOTDIR'; + error.errno = -20; + if (typeof callback !== 'function') { + throw error; + } + return process.nextTick(function() { + return callback(error); + }); +}; + +// Create invalid archive error. +var invalidArchiveError = function(asarPath, callback) { + var error; + error = new Error("Invalid package " + asarPath); + if (typeof callback !== 'function') { + throw error; + } + return process.nextTick(function() { + return callback(error); + }); +}; + +// Override APIs that rely on passing file path instead of content to C++. +var overrideAPISync = function(module, name, arg) { + var old; + if (arg == null) { + arg = 0; + } + old = module[name]; + return module[name] = function() { + var archive, asarPath, filePath, isAsar, newPath, p, ref; + p = arguments[arg]; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return old.apply(this, arguments); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + invalidArchiveError(asarPath); + } + newPath = archive.copyFileOut(filePath); + if (!newPath) { + notFoundError(asarPath, filePath); + } + arguments[arg] = newPath; + return old.apply(this, arguments); + }; +}; + +var overrideAPI = function(module, name, arg) { + var old; + if (arg == null) { + arg = 0; + } + old = module[name]; + return module[name] = function() { + var archive, asarPath, callback, filePath, isAsar, newPath, p, ref; + p = arguments[arg]; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return old.apply(this, arguments); + } + callback = arguments[arguments.length - 1]; + if (typeof callback !== 'function') { + return overrideAPISync(module, name, arg); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return invalidArchiveError(asarPath, callback); + } + newPath = archive.copyFileOut(filePath); + if (!newPath) { + return notFoundError(asarPath, filePath, callback); + } + arguments[arg] = newPath; + return old.apply(this, arguments); + }; +}; + +// Override fs APIs. +exports.wrapFsWithAsar = function(fs) { + var exists, existsSync, internalModuleReadFile, internalModuleStat, lstat, lstatSync, mkdir, mkdirSync, open, openSync, readFile, readFileSync, readdir, readdirSync, realpath, realpathSync, stat, statSync, statSyncNoException; + lstatSync = fs.lstatSync; + fs.lstatSync = function(p) { + var archive, asarPath, filePath, isAsar, ref, stats; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return lstatSync(p); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + invalidArchiveError(asarPath); + } + stats = archive.stat(filePath); + if (!stats) { + notFoundError(asarPath, filePath); + } + return asarStatsToFsStats(stats); + }; + lstat = fs.lstat; + fs.lstat = function(p, callback) { + var archive, asarPath, filePath, isAsar, ref, stats; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return lstat(p, callback); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return invalidArchiveError(asarPath, callback); + } + stats = getOrCreateArchive(asarPath).stat(filePath); + if (!stats) { + return notFoundError(asarPath, filePath, callback); + } + return process.nextTick(function() { + return callback(null, asarStatsToFsStats(stats)); + }); + }; + statSync = fs.statSync; + fs.statSync = function(p) { + var asarPath, filePath, isAsar, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return statSync(p); + } + + // Do not distinguish links for now. + return fs.lstatSync(p); + }; + stat = fs.stat; + fs.stat = function(p, callback) { + var asarPath, filePath, isAsar, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return stat(p, callback); + } + + // Do not distinguish links for now. + return process.nextTick(function() { + return fs.lstat(p, callback); + }); + }; + statSyncNoException = fs.statSyncNoException; + fs.statSyncNoException = function(p) { + var archive, asarPath, filePath, isAsar, ref, stats; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return statSyncNoException(p); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return false; + } + stats = archive.stat(filePath); + if (!stats) { + return false; + } + return asarStatsToFsStats(stats); + }; + realpathSync = fs.realpathSync; + fs.realpathSync = function(p) { + var archive, asarPath, filePath, isAsar, real, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return realpathSync.apply(this, arguments); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + invalidArchiveError(asarPath); + } + real = archive.realpath(filePath); + if (real === false) { + notFoundError(asarPath, filePath); + } + return path.join(realpathSync(asarPath), real); + }; + realpath = fs.realpath; + fs.realpath = function(p, cache, callback) { + var archive, asarPath, filePath, isAsar, real, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return realpath.apply(this, arguments); + } + if (typeof cache === 'function') { + callback = cache; + cache = void 0; + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return invalidArchiveError(asarPath, callback); + } + real = archive.realpath(filePath); + if (real === false) { + return notFoundError(asarPath, filePath, callback); + } + return realpath(asarPath, function(err, p) { + if (err) { + return callback(err); + } + return callback(null, path.join(p, real)); + }); + }; + exists = fs.exists; + fs.exists = function(p, callback) { + var archive, asarPath, filePath, isAsar, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return exists(p, callback); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return invalidArchiveError(asarPath, callback); + } + return process.nextTick(function() { + return callback(archive.stat(filePath) !== false); + }); + }; + existsSync = fs.existsSync; + fs.existsSync = function(p) { + var archive, asarPath, filePath, isAsar, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return existsSync(p); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return false; + } + return archive.stat(filePath) !== false; + }; + open = fs.open; + readFile = fs.readFile; + fs.readFile = function(p, options, callback) { + var archive, asarPath, buffer, encoding, fd, filePath, info, isAsar, realPath, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return readFile.apply(this, arguments); + } + if (typeof options === 'function') { + callback = options; + options = void 0; + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return invalidArchiveError(asarPath, callback); + } + info = archive.getFileInfo(filePath); + if (!info) { + return notFoundError(asarPath, filePath, callback); + } + if (info.size === 0) { + return process.nextTick(function() { + return callback(null, new Buffer(0)); + }); + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath); + return fs.readFile(realPath, options, callback); + } + if (!options) { + options = { + encoding: null + }; + } else if (util.isString(options)) { + options = { + encoding: options + }; + } else if (!util.isObject(options)) { + throw new TypeError('Bad arguments'); + } + encoding = options.encoding; + buffer = new Buffer(info.size); + fd = archive.getFd(); + if (!(fd >= 0)) { + return notFoundError(asarPath, filePath, callback); + } + return fs.read(fd, buffer, 0, info.size, info.offset, function(error) { + return callback(error, encoding ? buffer.toString(encoding) : buffer); + }); + }; + openSync = fs.openSync; + readFileSync = fs.readFileSync; + fs.readFileSync = function(p, opts) { + + // this allows v8 to optimize this function + var archive, asarPath, buffer, encoding, fd, filePath, info, isAsar, options, realPath, ref; + options = opts; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return readFileSync.apply(this, arguments); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + invalidArchiveError(asarPath); + } + info = archive.getFileInfo(filePath); + if (!info) { + notFoundError(asarPath, filePath); + } + if (info.size === 0) { + if (options) { + return ''; + } else { + return new Buffer(0); + } + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath); + return fs.readFileSync(realPath, options); + } + if (!options) { + options = { + encoding: null + }; + } else if (util.isString(options)) { + options = { + encoding: options + }; + } else if (!util.isObject(options)) { + throw new TypeError('Bad arguments'); + } + encoding = options.encoding; + buffer = new Buffer(info.size); + fd = archive.getFd(); + if (!(fd >= 0)) { + notFoundError(asarPath, filePath); + } + fs.readSync(fd, buffer, 0, info.size, info.offset); + if (encoding) { + return buffer.toString(encoding); + } else { + return buffer; + } + }; + readdir = fs.readdir; + fs.readdir = function(p, callback) { + var archive, asarPath, filePath, files, isAsar, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return readdir.apply(this, arguments); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return invalidArchiveError(asarPath, callback); + } + files = archive.readdir(filePath); + if (!files) { + return notFoundError(asarPath, filePath, callback); + } + return process.nextTick(function() { + return callback(null, files); + }); + }; + readdirSync = fs.readdirSync; + fs.readdirSync = function(p) { + var archive, asarPath, filePath, files, isAsar, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return readdirSync.apply(this, arguments); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + invalidArchiveError(asarPath); + } + files = archive.readdir(filePath); + if (!files) { + notFoundError(asarPath, filePath); + } + return files; + }; + internalModuleReadFile = process.binding('fs').internalModuleReadFile; + process.binding('fs').internalModuleReadFile = function(p) { + var archive, asarPath, buffer, fd, filePath, info, isAsar, realPath, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return internalModuleReadFile(p); + } + archive = getOrCreateArchive(asarPath); + if (!archive) { + return void 0; + } + info = archive.getFileInfo(filePath); + if (!info) { + return void 0; + } + if (info.size === 0) { + return ''; + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath); + return fs.readFileSync(realPath, { + encoding: 'utf8' + }); + } + buffer = new Buffer(info.size); + fd = archive.getFd(); + if (!(fd >= 0)) { + return void 0; + } + fs.readSync(fd, buffer, 0, info.size, info.offset); + return buffer.toString('utf8'); + }; + internalModuleStat = process.binding('fs').internalModuleStat; + process.binding('fs').internalModuleStat = function(p) { + var archive, asarPath, filePath, isAsar, ref, stats; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (!isAsar) { + return internalModuleStat(p); + } + archive = getOrCreateArchive(asarPath); + + // -ENOENT + if (!archive) { + return -34; + } + stats = archive.stat(filePath); + + // -ENOENT + if (!stats) { + return -34; + } + if (stats.isDirectory) { + return 1; + } else { + return 0; + } + }; + + // Calling mkdir for directory inside asar archive should throw ENOTDIR + // error, but on Windows it throws ENOENT. + // This is to work around the recursive looping bug of mkdirp since it is + // widely used. + if (process.platform === 'win32') { + mkdir = fs.mkdir; + fs.mkdir = function(p, mode, callback) { + var asarPath, filePath, isAsar, ref; + if (typeof mode === 'function') { + callback = mode; + } + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (isAsar && filePath.length) { + return notDirError(callback); + } + return mkdir(p, mode, callback); + }; + mkdirSync = fs.mkdirSync; + fs.mkdirSync = function(p, mode) { + var asarPath, filePath, isAsar, ref; + ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; + if (isAsar && filePath.length) { + notDirError(); + } + return mkdirSync(p, mode); + }; + } + overrideAPI(fs, 'open'); + overrideAPI(child_process, 'execFile'); + overrideAPISync(process, 'dlopen', 1); + overrideAPISync(require('module')._extensions, '.node', 1); + overrideAPISync(fs, 'openSync'); + return overrideAPISync(child_process, 'execFileSync'); +}; +})() diff --git a/atom/common/lib/asar_init.coffee b/atom/common/lib/asar_init.coffee deleted file mode 100644 index 211c79ee9099..000000000000 --- a/atom/common/lib/asar_init.coffee +++ /dev/null @@ -1,22 +0,0 @@ -return (process, require, asarSource) -> - {createArchive} = process.binding 'atom_common_asar' - - # Make asar.coffee accessible via "require". - process.binding('natives').ATOM_SHELL_ASAR = asarSource - - # Monkey-patch the fs module. - require('ATOM_SHELL_ASAR').wrapFsWithAsar require('fs') - - # Make graceful-fs work with asar. - source = process.binding 'natives' - source['original-fs'] = source.fs - source['fs'] = """ - var src = '(function (exports, require, module, __filename, __dirname) { ' + - process.binding('natives')['original-fs'] + - ' });'; - var vm = require('vm'); - var fn = vm.runInThisContext(src, { filename: 'fs.js' }); - fn(exports, require, module); - var asar = require('ATOM_SHELL_ASAR'); - asar.wrapFsWithAsar(exports); - """ diff --git a/atom/common/lib/asar_init.js b/atom/common/lib/asar_init.js new file mode 100644 index 000000000000..52d93af86691 --- /dev/null +++ b/atom/common/lib/asar_init.js @@ -0,0 +1,17 @@ +(function () { +return function(process, require, asarSource) { + var createArchive, source; + createArchive = process.binding('atom_common_asar').createArchive; + + // Make asar.coffee accessible via "require". + process.binding('natives').ATOM_SHELL_ASAR = asarSource; + + // Monkey-patch the fs module. + require('ATOM_SHELL_ASAR').wrapFsWithAsar(require('fs')); + + // Make graceful-fs work with asar. + source = process.binding('natives'); + source['original-fs'] = source.fs; + return source['fs'] = "var src = '(function (exports, require, module, __filename, __dirname) { ' +\n process.binding('natives')['original-fs'] +\n ' });';\nvar vm = require('vm');\nvar fn = vm.runInThisContext(src, { filename: 'fs.js' });\nfn(exports, require, module);\nvar asar = require('ATOM_SHELL_ASAR');\nasar.wrapFsWithAsar(exports);"; +}; +})() diff --git a/atom/common/lib/init.coffee b/atom/common/lib/init.coffee deleted file mode 100644 index 735d9b0a31e5..000000000000 --- a/atom/common/lib/init.coffee +++ /dev/null @@ -1,36 +0,0 @@ -fs = require 'fs' -path = require 'path' -timers = require 'timers' -Module = require 'module' - -process.atomBinding = (name) -> - try - process.binding "atom_#{process.type}_#{name}" - catch e - process.binding "atom_common_#{name}" if /No such module/.test e.message - -unless process.env.ELECTRON_HIDE_INTERNAL_MODULES - # Add common/api/lib to module search paths. - Module.globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') - -# setImmediate and process.nextTick makes use of uv_check and uv_prepare to -# run the callbacks, however since we only run uv loop on requests, the -# callbacks wouldn't be called until something else activated the uv loop, -# which would delay the callbacks for arbitrary long time. So we should -# initiatively activate the uv loop once setImmediate and process.nextTick is -# called. -wrapWithActivateUvLoop = (func) -> - -> - process.activateUvLoop() - func.apply this, arguments -process.nextTick = wrapWithActivateUvLoop process.nextTick -global.setImmediate = wrapWithActivateUvLoop timers.setImmediate -global.clearImmediate = timers.clearImmediate - -if process.type is 'browser' - # setTimeout needs to update the polling timeout of the event loop, when - # called under Chromium's event loop the node's event loop won't get a chance - # to update the timeout, so we have to force the node's event loop to - # recalculate the timeout in browser process. - global.setTimeout = wrapWithActivateUvLoop timers.setTimeout - global.setInterval = wrapWithActivateUvLoop timers.setInterval diff --git a/atom/common/lib/init.js b/atom/common/lib/init.js new file mode 100644 index 000000000000..76fe1b45820a --- /dev/null +++ b/atom/common/lib/init.js @@ -0,0 +1,50 @@ +const fs = require('fs'); +const path = require('path'); +const timers = require('timers'); +const Module = require('module'); + +process.atomBinding = function(name) { + var e, error; + try { + return process.binding("atom_" + process.type + "_" + name); + } catch (error) { + e = error; + if (/No such module/.test(e.message)) { + return process.binding("atom_common_" + name); + } + } +}; + +if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { + // Add common/api/lib to module search paths. + Module.globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); +} + + +// setImmediate and process.nextTick makes use of uv_check and uv_prepare to +// run the callbacks, however since we only run uv loop on requests, the +// callbacks wouldn't be called until something else activated the uv loop, +// which would delay the callbacks for arbitrary long time. So we should +// initiatively activate the uv loop once setImmediate and process.nextTick is +// called. +var wrapWithActivateUvLoop = function(func) { + return function() { + process.activateUvLoop(); + return func.apply(this, arguments); + }; +}; + +process.nextTick = wrapWithActivateUvLoop(process.nextTick); + +global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate); + +global.clearImmediate = timers.clearImmediate; + +if (process.type === 'browser') { + // setTimeout needs to update the polling timeout of the event loop, when + // called under Chromium's event loop the node's event loop won't get a chance + // to update the timeout, so we have to force the node's event loop to + // recalculate the timeout in browser process. + global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout); + global.setInterval = wrapWithActivateUvLoop(timers.setInterval); +} diff --git a/atom/common/lib/reset-search-paths.coffee b/atom/common/lib/reset-search-paths.coffee deleted file mode 100644 index 7061103306aa..000000000000 --- a/atom/common/lib/reset-search-paths.coffee +++ /dev/null @@ -1,29 +0,0 @@ -path = require 'path' -Module = require 'module' - -# Clear Node's global search paths. -Module.globalPaths.length = 0 - -# Clear current and parent(init.coffee)'s search paths. -module.paths = [] -module.parent.paths = [] - -# Prevent Node from adding paths outside this app to search paths. -Module._nodeModulePaths = (from) -> - from = path.resolve from - - # If "from" is outside the app then we do nothing. - skipOutsidePaths = from.startsWith process.resourcesPath - - # Following logoic is copied from module.js. - splitRe = if process.platform is 'win32' then /[\/\\]/ else /\// - paths = [] - - parts = from.split splitRe - for part, tip in parts by -1 - continue if part is 'node_modules' - dir = parts.slice(0, tip + 1).join path.sep - break if skipOutsidePaths and not dir.startsWith process.resourcesPath - paths.push path.join(dir, 'node_modules') - - paths diff --git a/atom/common/lib/reset-search-paths.js b/atom/common/lib/reset-search-paths.js new file mode 100644 index 000000000000..abf671196735 --- /dev/null +++ b/atom/common/lib/reset-search-paths.js @@ -0,0 +1,36 @@ +const path = require('path'); +const Module = require('module'); + +// Clear Node's global search paths. +Module.globalPaths.length = 0; + +// Clear current and parent(init.coffee)'s search paths. +module.paths = []; + +module.parent.paths = []; + +// Prevent Node from adding paths outside this app to search paths. +Module._nodeModulePaths = function(from) { + var dir, i, part, parts, paths, skipOutsidePaths, splitRe, tip; + from = path.resolve(from); + + // If "from" is outside the app then we do nothing. + skipOutsidePaths = from.startsWith(process.resourcesPath); + + // Following logoic is copied from module.js. + splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//; + paths = []; + parts = from.split(splitRe); + for (tip = i = parts.length - 1; i >= 0; tip = i += -1) { + part = parts[tip]; + if (part === 'node_modules') { + continue; + } + dir = parts.slice(0, tip + 1).join(path.sep); + if (skipOutsidePaths && !dir.startsWith(process.resourcesPath)) { + break; + } + paths.push(path.join(dir, 'node_modules')); + } + return paths; +}; diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 6c4e8477cc4a..477fe6755faf 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -22,6 +22,7 @@ const char kMinHeight[] = "minHeight"; const char kMaxWidth[] = "maxWidth"; const char kMaxHeight[] = "maxHeight"; const char kResizable[] = "resizable"; +const char kMovable[] = "movable"; const char kFullscreen[] = "fullscreen"; // Whether the window should show in taskbar. diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index f038521673a0..78de53b825f9 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -23,6 +23,7 @@ extern const char kMinHeight[]; extern const char kMaxWidth[]; extern const char kMaxHeight[]; extern const char kResizable[]; +extern const char kMovable[]; extern const char kFullscreen[]; extern const char kSkipTaskbar[]; extern const char kKiosk[]; diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index 83d67a8b6635..c72882886b91 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -4,7 +4,7 @@ #include "atom/renderer/api/atom_api_web_frame.h" -#include "atom/common/api/api_messages.h" +#include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" @@ -15,6 +15,8 @@ #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/WebScopedUserGesture.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" #include "third_party/WebKit/public/web/WebSecurityPolicy.h" #include "third_party/WebKit/public/web/WebView.h" @@ -36,11 +38,9 @@ void WebFrame::SetName(const std::string& name) { } double WebFrame::SetZoomLevel(double level) { - auto render_view = content::RenderView::FromWebView(web_frame_->view()); - // Notify guests if any for zoom level change. - render_view->Send( - new AtomViewHostMsg_ZoomLevelChanged(MSG_ROUTING_NONE, level)); - return web_frame_->view()->setZoomLevel(level); + double ret = web_frame_->view()->setZoomLevel(level); + mate::EmitEvent(isolate(), GetWrapper(isolate()), "zoom-level-changed", ret); + return ret; } double WebFrame::GetZoomLevel() const { @@ -116,6 +116,19 @@ void WebFrame::RegisterURLSchemeAsPrivileged(const std::string& scheme) { privileged_scheme); } +void WebFrame::InsertText(const std::string& text) { + web_frame_->insertText(blink::WebString::fromUTF8(text)); +} + +void WebFrame::ExecuteJavaScript(const base::string16& code, + mate::Arguments* args) { + bool has_user_gesture = false; + args->GetNext(&has_user_gesture); + scoped_ptr gesture( + has_user_gesture ? new blink::WebScopedUserGesture : nullptr); + web_frame_->executeScriptAndReturnValue(blink::WebScriptSource(code)); +} + mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) @@ -136,7 +149,9 @@ mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( .SetMethod("registerURLSchemeAsBypassingCSP", &WebFrame::RegisterURLSchemeAsBypassingCSP) .SetMethod("registerURLSchemeAsPrivileged", - &WebFrame::RegisterURLSchemeAsPrivileged); + &WebFrame::RegisterURLSchemeAsPrivileged) + .SetMethod("insertText", &WebFrame::InsertText) + .SetMethod("executeJavaScript", &WebFrame::ExecuteJavaScript); } // static diff --git a/atom/renderer/api/atom_api_web_frame.h b/atom/renderer/api/atom_api_web_frame.h index 95a5a82a313d..d55b24fd25ea 100644 --- a/atom/renderer/api/atom_api_web_frame.h +++ b/atom/renderer/api/atom_api_web_frame.h @@ -60,6 +60,12 @@ class WebFrame : public mate::Wrappable { void RegisterURLSchemeAsBypassingCSP(const std::string& scheme); void RegisterURLSchemeAsPrivileged(const std::string& scheme); + // Editing. + void InsertText(const std::string& text); + + // Excecuting scripts. + void ExecuteJavaScript(const base::string16& code, mate::Arguments* args); + // mate::Wrappable: virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate); diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee deleted file mode 100644 index 7c7c8982491f..000000000000 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ /dev/null @@ -1,20 +0,0 @@ -{ipcRenderer, nativeImage} = require 'electron' - -nextId = 0 -getNextId = -> ++nextId - -# |options.type| can not be empty and has to include 'window' or 'screen'. -isValid = (options) -> - return options?.types? and Array.isArray options.types - -exports.getSources = (options, callback) -> - return callback new Error('Invalid options') unless isValid options - - captureWindow = 'window' in options.types - captureScreen = 'screen' in options.types - options.thumbnailSize ?= width: 150, height: 150 - - id = getNextId() - ipcRenderer.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id - ipcRenderer.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (event, sources) -> - callback null, ({id: source.id, name: source.name, thumbnail: nativeImage.createFromDataURL source.thumbnail} for source in sources) diff --git a/atom/renderer/api/lib/desktop-capturer.js b/atom/renderer/api/lib/desktop-capturer.js new file mode 100644 index 000000000000..c445bfecbe1b --- /dev/null +++ b/atom/renderer/api/lib/desktop-capturer.js @@ -0,0 +1,47 @@ +const ipcRenderer = require('electron').ipcRenderer; +const nativeImage = require('electron').nativeImage; + +var nextId = 0; +var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +var getNextId = function() { + return ++nextId; +}; + +// |options.type| can not be empty and has to include 'window' or 'screen'. +var isValid = function(options) { + return ((options != null ? options.types : void 0) != null) && Array.isArray(options.types); +}; + +exports.getSources = function(options, callback) { + var captureScreen, captureWindow, id; + if (!isValid(options)) { + return callback(new Error('Invalid options')); + } + captureWindow = indexOf.call(options.types, 'window') >= 0; + captureScreen = indexOf.call(options.types, 'screen') >= 0; + if (options.thumbnailSize == null) { + options.thumbnailSize = { + width: 150, + height: 150 + }; + } + id = getNextId(); + ipcRenderer.send('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id); + return ipcRenderer.once("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + id, function(event, sources) { + var source; + return callback(null, (function() { + var i, len, results; + results = []; + for (i = 0, len = sources.length; i < len; i++) { + source = sources[i]; + results.push({ + id: source.id, + name: source.name, + thumbnail: nativeImage.createFromDataURL(source.thumbnail) + }); + } + return results; + })()); + }); +}; diff --git a/atom/renderer/api/lib/exports/electron.coffee b/atom/renderer/api/lib/exports/electron.coffee deleted file mode 100644 index d0b3af3c5571..000000000000 --- a/atom/renderer/api/lib/exports/electron.coffee +++ /dev/null @@ -1,22 +0,0 @@ -common = require '../../../../common/api/lib/exports/electron' - -# Import common modules. -common.defineProperties exports - -Object.defineProperties exports, - # Renderer side modules, please sort with alphabet order. - desktopCapturer: - enumerable: true - get: -> require '../desktop-capturer' - ipcRenderer: - enumerable: true - get: -> require '../ipc-renderer' - remote: - enumerable: true - get: -> require '../remote' - screen: - enumerable: true - get: -> require '../screen' - webFrame: - enumerable: true - get: -> require '../web-frame' diff --git a/atom/renderer/api/lib/exports/electron.js b/atom/renderer/api/lib/exports/electron.js new file mode 100644 index 000000000000..3f0d3254cb8b --- /dev/null +++ b/atom/renderer/api/lib/exports/electron.js @@ -0,0 +1,38 @@ +const common = require('../../../../common/api/lib/exports/electron'); + +// Import common modules. +common.defineProperties(exports); + +Object.defineProperties(exports, { + // Renderer side modules, please sort with alphabet order. + desktopCapturer: { + enumerable: true, + get: function() { + return require('../desktop-capturer'); + } + }, + ipcRenderer: { + enumerable: true, + get: function() { + return require('../ipc-renderer'); + } + }, + remote: { + enumerable: true, + get: function() { + return require('../remote'); + } + }, + screen: { + enumerable: true, + get: function() { + return require('../screen'); + } + }, + webFrame: { + enumerable: true, + get: function() { + return require('../web-frame'); + } + } +}); diff --git a/atom/renderer/api/lib/ipc-renderer.coffee b/atom/renderer/api/lib/ipc-renderer.coffee deleted file mode 100644 index 0dd629e54f75..000000000000 --- a/atom/renderer/api/lib/ipc-renderer.coffee +++ /dev/null @@ -1,18 +0,0 @@ -{EventEmitter} = require 'events' - -binding = process.atomBinding 'ipc' -v8Util = process.atomBinding 'v8_util' - -# Created by init.coffee. -ipcRenderer = v8Util.getHiddenValue global, 'ipc' - -ipcRenderer.send = (args...) -> - binding.send 'ipc-message', [args...] - -ipcRenderer.sendSync = (args...) -> - JSON.parse binding.sendSync('ipc-message-sync', [args...]) - -ipcRenderer.sendToHost = (args...) -> - binding.send 'ipc-message-host', [args...] - -module.exports = ipcRenderer diff --git a/atom/renderer/api/lib/ipc-renderer.js b/atom/renderer/api/lib/ipc-renderer.js new file mode 100644 index 000000000000..5b3c0a4eb788 --- /dev/null +++ b/atom/renderer/api/lib/ipc-renderer.js @@ -0,0 +1,28 @@ +const EventEmitter = require('events').EventEmitter; +const binding = process.atomBinding('ipc'); +const v8Util = process.atomBinding('v8_util'); + +var slice = [].slice; + +// Created by init.coffee. +const ipcRenderer = v8Util.getHiddenValue(global, 'ipc'); + +ipcRenderer.send = function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return binding.send('ipc-message', slice.call(args)); +}; + +ipcRenderer.sendSync = function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return JSON.parse(binding.sendSync('ipc-message-sync', slice.call(args))); +}; + +ipcRenderer.sendToHost = function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return binding.send('ipc-message-host', slice.call(args)); +}; + +module.exports = ipcRenderer; diff --git a/atom/renderer/api/lib/ipc.coffee b/atom/renderer/api/lib/ipc.coffee deleted file mode 100644 index edd7d29b6f06..000000000000 --- a/atom/renderer/api/lib/ipc.coffee +++ /dev/null @@ -1,19 +0,0 @@ -{ipcRenderer, deprecate} = require 'electron' -{EventEmitter} = require 'events' - -# This module is deprecated, we mirror everything from ipcRenderer. -deprecate.warn 'ipc module', 'require("electron").ipcRenderer' - -# Routes events of ipcRenderer. -ipc = new EventEmitter -ipcRenderer.emit = (channel, event, args...) -> - ipc.emit channel, args... - EventEmitter::emit.apply ipcRenderer, arguments - -# Deprecated. -for method of ipcRenderer when method.startsWith 'send' - ipc[method] = ipcRenderer[method] -deprecate.rename ipc, 'sendChannel', 'send' -deprecate.rename ipc, 'sendChannelSync', 'sendSync' - -module.exports = ipc diff --git a/atom/renderer/api/lib/ipc.js b/atom/renderer/api/lib/ipc.js new file mode 100644 index 000000000000..e58cd0273c2b --- /dev/null +++ b/atom/renderer/api/lib/ipc.js @@ -0,0 +1,31 @@ +const ipcRenderer = require('electron').ipcRenderer; +const deprecate = require('electron').deprecate; +const EventEmitter = require('events').EventEmitter; + +var slice = [].slice; + +// This module is deprecated, we mirror everything from ipcRenderer. +deprecate.warn('ipc module', 'require("electron").ipcRenderer'); + +// Routes events of ipcRenderer. +var ipc = new EventEmitter; + +ipcRenderer.emit = function() { + var args, channel, event; + channel = arguments[0], event = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + ipc.emit.apply(ipc, [channel].concat(slice.call(args))); + return EventEmitter.prototype.emit.apply(ipcRenderer, arguments); +}; + +// Deprecated. +for (var method in ipcRenderer) { + if (method.startsWith('send')) { + ipc[method] = ipcRenderer[method]; + } +} + +deprecate.rename(ipc, 'sendChannel', 'send'); + +deprecate.rename(ipc, 'sendChannelSync', 'sendSync'); + +module.exports = ipc; diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee deleted file mode 100644 index b73fbf50b7c1..000000000000 --- a/atom/renderer/api/lib/remote.coffee +++ /dev/null @@ -1,199 +0,0 @@ -{ipcRenderer, CallbacksRegistry} = require 'electron' -v8Util = process.atomBinding 'v8_util' - -callbacksRegistry = new CallbacksRegistry - -# Check for circular reference. -isCircular = (field, visited) -> - if typeof field is 'object' - if field in visited - return true - visited.push field - return false - -# Convert the arguments object into an array of meta data. -wrapArgs = (args, visited=[]) -> - valueToMeta = (value) -> - if Array.isArray value - type: 'array', value: wrapArgs(value, visited) - else if Buffer.isBuffer value - type: 'buffer', value: Array::slice.call(value, 0) - else if value instanceof Date - type: 'date', value: value.getTime() - else if value?.constructor.name is 'Promise' - type: 'promise', then: valueToMeta(value.then.bind(value)) - else if value? and typeof value is 'object' and v8Util.getHiddenValue value, 'atomId' - type: 'remote-object', id: v8Util.getHiddenValue value, 'atomId' - else if value? and typeof value is 'object' - ret = type: 'object', name: value.constructor.name, members: [] - for prop, field of value - ret.members.push - name: prop - value: valueToMeta(if isCircular(field, visited) then null else field) - ret - else if typeof value is 'function' and v8Util.getHiddenValue value, 'returnValue' - type: 'function-with-return-value', value: valueToMeta(value()) - else if typeof value is 'function' - type: 'function', id: callbacksRegistry.add(value), location: v8Util.getHiddenValue value, 'location' - else - type: 'value', value: value - - Array::slice.call(args).map valueToMeta - -# Convert meta data from browser into real value. -metaToValue = (meta) -> - switch meta.type - when 'value' then meta.value - when 'array' then (metaToValue(el) for el in meta.members) - when 'buffer' then new Buffer(meta.value) - when 'promise' then Promise.resolve(then: metaToValue(meta.then)) - when 'error' then metaToPlainObject meta - when 'date' then new Date(meta.value) - when 'exception' - throw new Error("#{meta.message}\n#{meta.stack}") - else - if meta.type is 'function' - # A shadow class to represent the remote function object. - ret = - class RemoteFunction - constructor: -> - if @constructor == RemoteFunction - # Constructor call. - obj = ipcRenderer.sendSync 'ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments) - - # Returning object in constructor will replace constructed object - # with the returned object. - # http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this - return metaToValue obj - else - # Function call. - obj = ipcRenderer.sendSync 'ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments) - return metaToValue obj - else - ret = v8Util.createObjectWithName meta.name - - # Polulate delegate members. - for member in meta.members - if member.type is 'function' - ret[member.name] = createRemoteMemberFunction meta.id, member.name - else - Object.defineProperty ret, member.name, createRemoteMemberProperty(meta.id, member.name) - - # Track delegate object's life time, and tell the browser to clean up - # when the object is GCed. - v8Util.setDestructor ret, -> - ipcRenderer.send 'ATOM_BROWSER_DEREFERENCE', meta.id - - # Remember object's id. - v8Util.setHiddenValue ret, 'atomId', meta.id - - ret - -# Construct a plain object from the meta. -metaToPlainObject = (meta) -> - obj = switch meta.type - when 'error' then new Error - else {} - obj[name] = value for {name, value} in meta.members - obj - -# Create a RemoteMemberFunction instance. -# This function's content should not be inlined into metaToValue, otherwise V8 -# may consider it circular reference. -createRemoteMemberFunction = (metaId, name) -> - class RemoteMemberFunction - constructor: -> - if @constructor is RemoteMemberFunction - # Constructor call. - ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, name, wrapArgs(arguments) - return metaToValue ret - else - # Call member function. - ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CALL', metaId, name, wrapArgs(arguments) - return metaToValue ret - -# Create configuration for defineProperty. -# This function's content should not be inlined into metaToValue, otherwise V8 -# may consider it circular reference. -createRemoteMemberProperty = (metaId, name) -> - enumerable: true, - configurable: false, - set: (value) -> - # Set member data. - ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_SET', metaId, name, value - value - get: -> - # Get member data. - metaToValue ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, name) - -# Browser calls a callback in renderer. -ipcRenderer.on 'ATOM_RENDERER_CALLBACK', (event, id, args) -> - callbacksRegistry.apply id, metaToValue(args) - -# A callback in browser is released. -ipcRenderer.on 'ATOM_RENDERER_RELEASE_CALLBACK', (event, id) -> - callbacksRegistry.remove id - -# List all built-in modules in browser process. -browserModules = require '../../../browser/api/lib/exports/electron' -# And add a helper receiver for each one. -for name of browserModules - do (name) -> - Object.defineProperty exports, name, get: -> exports.getBuiltin name - -# Get remote module. -# (Just like node's require, the modules are cached permanently, note that this -# is safe leak since the object is not expected to get freed in browser) -moduleCache = {} -exports.require = (module) -> - return moduleCache[module] if moduleCache[module]? - - meta = ipcRenderer.sendSync 'ATOM_BROWSER_REQUIRE', module - moduleCache[module] = metaToValue meta - -# Optimize require('electron'). -moduleCache.electron = exports - -# Alias to remote.require('electron').xxx. -builtinCache = {} -exports.getBuiltin = (module) -> - return builtinCache[module] if builtinCache[module]? - - meta = ipcRenderer.sendSync 'ATOM_BROWSER_GET_BUILTIN', module - builtinCache[module] = metaToValue meta - -# Get current BrowserWindow object. -windowCache = null -exports.getCurrentWindow = -> - return windowCache if windowCache? - meta = ipcRenderer.sendSync 'ATOM_BROWSER_CURRENT_WINDOW' - windowCache = metaToValue meta - -# Get current WebContents object. -webContentsCache = null -exports.getCurrentWebContents = -> - return webContentsCache if webContentsCache? - meta = ipcRenderer.sendSync 'ATOM_BROWSER_CURRENT_WEB_CONTENTS' - webContentsCache = metaToValue meta - -# Get a global object in browser. -exports.getGlobal = (name) -> - meta = ipcRenderer.sendSync 'ATOM_BROWSER_GLOBAL', name - metaToValue meta - -# Get the process object in browser. -processCache = null -exports.__defineGetter__ 'process', -> - processCache = exports.getGlobal('process') unless processCache? - processCache - -# Create a funtion that will return the specifed value when called in browser. -exports.createFunctionWithReturnValue = (returnValue) -> - func = -> returnValue - v8Util.setHiddenValue func, 'returnValue', true - func - -# Get the guest WebContents from guestInstanceId. -exports.getGuestWebContents = (guestInstanceId) -> - meta = ipcRenderer.sendSync 'ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId - metaToValue meta diff --git a/atom/renderer/api/lib/remote.js b/atom/renderer/api/lib/remote.js new file mode 100644 index 000000000000..c9b823b3b397 --- /dev/null +++ b/atom/renderer/api/lib/remote.js @@ -0,0 +1,346 @@ +const ipcRenderer = require('electron').ipcRenderer; +const CallbacksRegistry = require('electron').CallbacksRegistry; +const v8Util = process.atomBinding('v8_util'); + +const callbacksRegistry = new CallbacksRegistry; + +var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +// Check for circular reference. +var isCircular = function(field, visited) { + if (typeof field === 'object') { + if (indexOf.call(visited, field) >= 0) { + return true; + } + visited.push(field); + } + return false; +}; + +// Convert the arguments object into an array of meta data. +var wrapArgs = function(args, visited) { + var valueToMeta; + if (visited == null) { + visited = []; + } + valueToMeta = function(value) { + var field, prop, ret; + if (Array.isArray(value)) { + return { + type: 'array', + value: wrapArgs(value, visited) + }; + } else if (Buffer.isBuffer(value)) { + return { + type: 'buffer', + value: Array.prototype.slice.call(value, 0) + }; + } else if (value instanceof Date) { + return { + type: 'date', + value: value.getTime() + }; + } else if ((value != null ? value.constructor.name : void 0) === 'Promise') { + return { + type: 'promise', + then: valueToMeta(value.then.bind(value)) + }; + } else if ((value != null) && typeof value === 'object' && v8Util.getHiddenValue(value, 'atomId')) { + return { + type: 'remote-object', + id: v8Util.getHiddenValue(value, 'atomId') + }; + } else if ((value != null) && typeof value === 'object') { + ret = { + type: 'object', + name: value.constructor.name, + members: [] + }; + for (prop in value) { + field = value[prop]; + ret.members.push({ + name: prop, + value: valueToMeta(isCircular(field, visited) ? null : field) + }); + } + return ret; + } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) { + return { + type: 'function-with-return-value', + value: valueToMeta(value()) + }; + } else if (typeof value === 'function') { + return { + type: 'function', + id: callbacksRegistry.add(value), + location: v8Util.getHiddenValue(value, 'location') + }; + } else { + return { + type: 'value', + value: value + }; + } + }; + return Array.prototype.slice.call(args).map(valueToMeta); +}; + +// Convert meta data from browser into real value. +var metaToValue = function(meta) { + var RemoteFunction, el, i, j, len, len1, member, ref1, ref2, results, ret; + switch (meta.type) { + case 'value': + return meta.value; + case 'array': + ref1 = meta.members; + results = []; + for (i = 0, len = ref1.length; i < len; i++) { + el = ref1[i]; + results.push(metaToValue(el)); + } + return results; + case 'buffer': + return new Buffer(meta.value); + case 'promise': + return Promise.resolve({ + then: metaToValue(meta.then) + }); + case 'error': + return metaToPlainObject(meta); + case 'date': + return new Date(meta.value); + case 'exception': + throw new Error(meta.message + "\n" + meta.stack); + break; + default: + if (meta.type === 'function') { + // A shadow class to represent the remote function object. + ret = RemoteFunction = (function() { + function RemoteFunction() { + var obj; + if (this.constructor === RemoteFunction) { + + // Constructor call. + obj = ipcRenderer.sendSync('ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments)); + + /* + Returning object in constructor will replace constructed object + with the returned object. + http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this + */ + return metaToValue(obj); + } else { + + // Function call. + obj = ipcRenderer.sendSync('ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments)); + return metaToValue(obj); + } + } + + return RemoteFunction; + + })(); + } else { + ret = v8Util.createObjectWithName(meta.name); + } + + // Polulate delegate members. + ref2 = meta.members; + for (j = 0, len1 = ref2.length; j < len1; j++) { + member = ref2[j]; + if (member.type === 'function') { + ret[member.name] = createRemoteMemberFunction(meta.id, member.name); + } else { + Object.defineProperty(ret, member.name, createRemoteMemberProperty(meta.id, member.name)); + } + } + + // Track delegate object's life time, and tell the browser to clean up + // when the object is GCed. + v8Util.setDestructor(ret, function() { + return ipcRenderer.send('ATOM_BROWSER_DEREFERENCE', meta.id); + }); + + // Remember object's id. + v8Util.setHiddenValue(ret, 'atomId', meta.id); + return ret; + } +}; + +// Construct a plain object from the meta. +var metaToPlainObject = function(meta) { + var i, len, name, obj, ref1, ref2, value; + obj = (function() { + switch (meta.type) { + case 'error': + return new Error; + default: + return {}; + } + })(); + ref1 = meta.members; + for (i = 0, len = ref1.length; i < len; i++) { + ref2 = ref1[i], name = ref2.name, value = ref2.value; + obj[name] = value; + } + return obj; +}; + +// Create a RemoteMemberFunction instance. +// This function's content should not be inlined into metaToValue, otherwise V8 +// may consider it circular reference. +var createRemoteMemberFunction = function(metaId, name) { + var RemoteMemberFunction; + return RemoteMemberFunction = (function() { + function RemoteMemberFunction() { + var ret; + if (this.constructor === RemoteMemberFunction) { + + // Constructor call. + ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, name, wrapArgs(arguments)); + return metaToValue(ret); + } else { + + // Call member function. + ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CALL', metaId, name, wrapArgs(arguments)); + return metaToValue(ret); + } + } + + return RemoteMemberFunction; + + })(); +}; + +// Create configuration for defineProperty. +// This function's content should not be inlined into metaToValue, otherwise V8 +// may consider it circular reference. +var createRemoteMemberProperty = function(metaId, name) { + return { + enumerable: true, + configurable: false, + set: function(value) { + + // Set member data. + ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_SET', metaId, name, value); + return value; + }, + get: function() { + + // Get member data. + return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, name)); + } + }; +}; + +// Browser calls a callback in renderer. +ipcRenderer.on('ATOM_RENDERER_CALLBACK', function(event, id, args) { + return callbacksRegistry.apply(id, metaToValue(args)); +}); + +// A callback in browser is released. +ipcRenderer.on('ATOM_RENDERER_RELEASE_CALLBACK', function(event, id) { + return callbacksRegistry.remove(id); +}); + +// List all built-in modules in browser process. +const browserModules = require('../../../browser/api/lib/exports/electron'); + +// And add a helper receiver for each one. +var fn = function(name) { + return Object.defineProperty(exports, name, { + get: function() { + return exports.getBuiltin(name); + } + }); +}; +for (var name in browserModules) { + fn(name); +} + +// Get remote module. +// (Just like node's require, the modules are cached permanently, note that this +// is safe leak since the object is not expected to get freed in browser) +var moduleCache = {}; + +exports.require = function(module) { + var meta; + if (moduleCache[module] != null) { + return moduleCache[module]; + } + meta = ipcRenderer.sendSync('ATOM_BROWSER_REQUIRE', module); + return moduleCache[module] = metaToValue(meta); +}; + +// Optimize require('electron'). +moduleCache.electron = exports; + +// Alias to remote.require('electron').xxx. +var builtinCache = {}; + +exports.getBuiltin = function(module) { + var meta; + if (builtinCache[module] != null) { + return builtinCache[module]; + } + meta = ipcRenderer.sendSync('ATOM_BROWSER_GET_BUILTIN', module); + return builtinCache[module] = metaToValue(meta); +}; + +// Get current BrowserWindow object. +var windowCache = null; + +exports.getCurrentWindow = function() { + var meta; + if (windowCache != null) { + return windowCache; + } + meta = ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WINDOW'); + return windowCache = metaToValue(meta); +}; + +// Get current WebContents object. +var webContentsCache = null; + +exports.getCurrentWebContents = function() { + var meta; + if (webContentsCache != null) { + return webContentsCache; + } + meta = ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WEB_CONTENTS'); + return webContentsCache = metaToValue(meta); +}; + +// Get a global object in browser. +exports.getGlobal = function(name) { + var meta; + meta = ipcRenderer.sendSync('ATOM_BROWSER_GLOBAL', name); + return metaToValue(meta); +}; + +// Get the process object in browser. +var processCache = null; + +exports.__defineGetter__('process', function() { + if (processCache == null) { + processCache = exports.getGlobal('process'); + } + return processCache; +}); + +// Create a funtion that will return the specifed value when called in browser. +exports.createFunctionWithReturnValue = function(returnValue) { + var func; + func = function() { + return returnValue; + }; + v8Util.setHiddenValue(func, 'returnValue', true); + return func; +}; + +// Get the guest WebContents from guestInstanceId. +exports.getGuestWebContents = function(guestInstanceId) { + var meta; + meta = ipcRenderer.sendSync('ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId); + return metaToValue(meta); +}; diff --git a/atom/renderer/api/lib/screen.coffee b/atom/renderer/api/lib/screen.coffee deleted file mode 100644 index 9eecd49dc5bf..000000000000 --- a/atom/renderer/api/lib/screen.coffee +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('electron').remote.screen diff --git a/atom/renderer/api/lib/screen.js b/atom/renderer/api/lib/screen.js new file mode 100644 index 000000000000..fb74d78f79d2 --- /dev/null +++ b/atom/renderer/api/lib/screen.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.screen; diff --git a/atom/renderer/api/lib/web-frame.coffee b/atom/renderer/api/lib/web-frame.coffee deleted file mode 100644 index 53564c615ca4..000000000000 --- a/atom/renderer/api/lib/web-frame.coffee +++ /dev/null @@ -1,9 +0,0 @@ -{deprecate} = require 'electron' -{webFrame} = process.atomBinding 'web_frame' - -# Deprecated. -deprecate.rename webFrame, 'registerUrlSchemeAsSecure', 'registerURLSchemeAsSecure' -deprecate.rename webFrame, 'registerUrlSchemeAsBypassingCSP', 'registerURLSchemeAsBypassingCSP' -deprecate.rename webFrame, 'registerUrlSchemeAsPrivileged', 'registerURLSchemeAsPrivileged' - -module.exports = webFrame diff --git a/atom/renderer/api/lib/web-frame.js b/atom/renderer/api/lib/web-frame.js new file mode 100644 index 000000000000..8ae35d7b8ac6 --- /dev/null +++ b/atom/renderer/api/lib/web-frame.js @@ -0,0 +1,19 @@ +'use strict'; + +const deprecate = require('electron').deprecate; +const EventEmitter = require('events').EventEmitter; + +const webFrame = process.atomBinding('web_frame').webFrame; + +// webFrame is an EventEmitter. +webFrame.__proto__ = EventEmitter.prototype; + +// Lots of webview would subscribe to webFrame's events. +webFrame.setMaxListeners(0); + +// Deprecated. +deprecate.rename(webFrame, 'registerUrlSchemeAsSecure', 'registerURLSchemeAsSecure'); +deprecate.rename(webFrame, 'registerUrlSchemeAsBypassingCSP', 'registerURLSchemeAsBypassingCSP'); +deprecate.rename(webFrame, 'registerUrlSchemeAsPrivileged', 'registerURLSchemeAsPrivileged'); + +module.exports = webFrame; diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index 931913dd75d0..cdbdb3d7c3ce 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -27,8 +27,6 @@ #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebKit.h" -#include "third_party/WebKit/public/web/WebScopedUserGesture.h" -#include "third_party/WebKit/public/web/WebScriptSource.h" #include "third_party/WebKit/public/web/WebView.h" #include "ui/base/resource/resource_bundle.h" #include "native_mate/dictionary.h" @@ -88,7 +86,7 @@ void AtomRenderViewObserver::DidCreateDocumentElement( // Read --zoom-factor from command line. std::string zoom_factor_str = base::CommandLine::ForCurrentProcess()-> - GetSwitchValueASCII(switches::kZoomFactor);; + GetSwitchValueASCII(switches::kZoomFactor); if (zoom_factor_str.empty()) return; double zoom_factor; @@ -115,8 +113,6 @@ bool AtomRenderViewObserver::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(AtomRenderViewObserver, message) IPC_MESSAGE_HANDLER(AtomViewMsg_Message, OnBrowserMessage) - IPC_MESSAGE_HANDLER(AtomViewMsg_ExecuteJavaScript, - OnJavaScriptExecuteRequest) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -152,22 +148,4 @@ void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel, } } -void AtomRenderViewObserver::OnJavaScriptExecuteRequest( - const base::string16& code, bool has_user_gesture) { - if (!document_created_) - return; - - if (!render_view()->GetWebView()) - return; - - scoped_ptr gesture( - has_user_gesture ? new blink::WebScopedUserGesture : nullptr); - - v8::Isolate* isolate = blink::mainThreadIsolate(); - v8::HandleScope handle_scope(isolate); - - blink::WebFrame* frame = render_view()->GetWebView()->mainFrame(); - frame->executeScriptAndReturnValue(blink::WebScriptSource(code)); -} - } // namespace atom diff --git a/atom/renderer/atom_render_view_observer.h b/atom/renderer/atom_render_view_observer.h index 85a8c159d97e..4b9d59f3fa08 100644 --- a/atom/renderer/atom_render_view_observer.h +++ b/atom/renderer/atom_render_view_observer.h @@ -32,8 +32,6 @@ class AtomRenderViewObserver : public content::RenderViewObserver { void OnBrowserMessage(const base::string16& channel, const base::ListValue& args); - void OnJavaScriptExecuteRequest(const base::string16& code, - bool has_user_gesture); // Weak reference to renderer client. AtomRendererClient* renderer_client_; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index d9c364c3733f..15165efa330a 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -57,22 +57,6 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { render_frame()->GetWebFrame(), context); } - bool OnMessageReceived(const IPC::Message& message) { - bool handled = true; - IPC_BEGIN_MESSAGE_MAP(AtomRenderFrameObserver, message) - IPC_MESSAGE_HANDLER(AtomViewMsg_SetZoomLevel, OnSetZoomLevel) - IPC_MESSAGE_UNHANDLED(handled = false) - IPC_END_MESSAGE_MAP() - - return handled; - } - - void OnSetZoomLevel(double level) { - auto view = render_frame()->GetWebFrame()->view(); - if (view) - view->setZoomLevel(level); - } - private: AtomRendererClient* renderer_client_; diff --git a/atom/renderer/lib/chrome-api.coffee b/atom/renderer/lib/chrome-api.coffee deleted file mode 100644 index 288afaf55457..000000000000 --- a/atom/renderer/lib/chrome-api.coffee +++ /dev/null @@ -1,10 +0,0 @@ -url = require 'url' - -chrome = window.chrome = window.chrome || {} -chrome.extension = - getURL: (path) -> - url.format - protocol: location.protocol - slashes: true - hostname: location.hostname - pathname: path diff --git a/atom/renderer/lib/chrome-api.js b/atom/renderer/lib/chrome-api.js new file mode 100644 index 000000000000..7ff7d2e87b99 --- /dev/null +++ b/atom/renderer/lib/chrome-api.js @@ -0,0 +1,13 @@ +const url = require('url'); +const chrome = window.chrome = window.chrome || {}; + +chrome.extension = { + getURL: function(path) { + return url.format({ + protocol: location.protocol, + slashes: true, + hostname: location.hostname, + pathname: path + }); + } +}; diff --git a/atom/renderer/lib/init.coffee b/atom/renderer/lib/init.coffee deleted file mode 100644 index b9028f2dd6a5..000000000000 --- a/atom/renderer/lib/init.coffee +++ /dev/null @@ -1,109 +0,0 @@ -events = require 'events' -path = require 'path' -url = require 'url' -Module = require 'module' - -# We modified the original process.argv to let node.js load the -# atom-renderer.js, we need to restore it here. -process.argv.splice 1, 1 - -# Clear search paths. -require path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths') - -# Import common settings. -require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init') - -globalPaths = Module.globalPaths -unless process.env.ELECTRON_HIDE_INTERNAL_MODULES - globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') - -# Expose public APIs. -globalPaths.push path.resolve(__dirname, '..', 'api', 'lib', 'exports') - -# The global variable will be used by ipc for event dispatching -v8Util = process.atomBinding 'v8_util' -v8Util.setHiddenValue global, 'ipc', new events.EventEmitter - -# 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) - else if arg.indexOf('--opener-id=') == 0 - # This is a guest BrowserWindow. - process.openerId = parseInt arg.substr(arg.indexOf('=') + 1) - else if arg.indexOf('--node-integration=') == 0 - nodeIntegration = arg.substr arg.indexOf('=') + 1 - else if arg.indexOf('--preload=') == 0 - preloadScript = arg.substr arg.indexOf('=') + 1 - -if location.protocol is 'chrome-devtools:' - # Override some inspector APIs. - require './inspector' - nodeIntegration = 'true' -else if location.protocol is 'chrome-extension:' - # Add implementations of chrome API. - require './chrome-api' - nodeIntegration = 'true' -else - # Override default web functions. - require './override' - # Load webview tag implementation. - unless process.guestInstanceId? - require './web-view/web-view' - require './web-view/web-view-attributes' - -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 is file: protocol. - if window.location.protocol is 'file:' - 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 = (message, filename, lineno, colno, 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 - # Delete Node's symbols after the Environment has been loaded. - process.once 'loaded', -> - delete global.process - delete global.setImmediate - delete global.clearImmediate - delete global.global - -# Load the script specfied by the "preload" attribute. -if preloadScript - try - require preloadScript - catch error - if error.code is 'MODULE_NOT_FOUND' - console.error "Unable to load preload script #{preloadScript}" - else - console.error(error) - console.error(error.stack) diff --git a/atom/renderer/lib/init.js b/atom/renderer/lib/init.js new file mode 100644 index 000000000000..556e9d617bdb --- /dev/null +++ b/atom/renderer/lib/init.js @@ -0,0 +1,140 @@ +'user strict'; + +const events = require('events'); +const path = require('path'); +const url = require('url'); +const Module = require('module'); + + +// We modified the original process.argv to let node.js load the +// atom-renderer.js, we need to restore it here. +process.argv.splice(1, 1); + + +// Clear search paths. +require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')); + + +// Import common settings. +require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')); + +var globalPaths = Module.globalPaths; + +if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { + globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); +} + +// Expose public APIs. +globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib', 'exports')); + +// The global variable will be used by ipc for event dispatching +var v8Util = process.atomBinding('v8_util'); + +v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter); + +// Use electron module after everything is ready. +const electron = require('electron'); + +// Call webFrame method. +electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => { + electron.webFrame[method].apply(electron.webFrame, args); +}); + +// Process command line arguments. +var nodeIntegration = 'false'; +var preloadScript = null; + +var ref = process.argv; +var i, len, arg; +for (i = 0, len = ref.length; i < len; i++) { + arg = ref[i]; + if (arg.indexOf('--guest-instance-id=') === 0) { + // This is a guest web view. + process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1)); + } else if (arg.indexOf('--opener-id=') === 0) { + // This is a guest BrowserWindow. + process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1)); + } else if (arg.indexOf('--node-integration=') === 0) { + nodeIntegration = arg.substr(arg.indexOf('=') + 1); + } else if (arg.indexOf('--preload=') === 0) { + preloadScript = arg.substr(arg.indexOf('=') + 1); + } +} + +if (location.protocol === 'chrome-devtools:') { + // Override some inspector APIs. + require('./inspector'); + nodeIntegration = 'true'; +} else if (location.protocol === 'chrome-extension:') { + // Add implementations of chrome API. + require('./chrome-api'); + nodeIntegration = 'true'; +} else { + // Override default web functions. + require('./override'); + + // Load webview tag implementation. + if (process.guestInstanceId == null) { + require('./web-view/web-view'); + require('./web-view/web-view-attributes'); + } +} + +if (nodeIntegration === 'true' || nodeIntegration === 'all' || nodeIntegration === 'except-iframe' || nodeIntegration === '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 is file: protocol. + if (window.location.protocol === 'file:') { + var pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : 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 = function(message, filename, lineno, colno, error) { + if (global.process.listeners('uncaughtException').length > 0) { + global.process.emit('uncaughtException', error); + return true; + } else { + return false; + } + }; + + // Emit the 'exit' event when page is unloading. + window.addEventListener('unload', function() { + return process.emit('exit'); + }); +} else { + // Delete Node's symbols after the Environment has been loaded. + process.once('loaded', function() { + delete global.process; + delete global.setImmediate; + delete global.clearImmediate; + return delete global.global; + }); +} + +// Load the script specfied by the "preload" attribute. +if (preloadScript) { + try { + require(preloadScript); + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + console.error("Unable to load preload script " + preloadScript); + } else { + console.error(error); + console.error(error.stack); + } + } +} diff --git a/atom/renderer/lib/inspector.coffee b/atom/renderer/lib/inspector.coffee deleted file mode 100644 index 364ccc39d42a..000000000000 --- a/atom/renderer/lib/inspector.coffee +++ /dev/null @@ -1,60 +0,0 @@ -window.onload = -> - # Use menu API to show context menu. - InspectorFrontendHost.showContextMenuAtPoint = createMenu - - # Use dialog API to override file chooser dialog. - WebInspector.createFileSelectorElement = createFileSelectorElement - -convertToMenuTemplate = (items) -> - template = [] - for item in items - do (item) -> - transformed = - if item.type is 'subMenu' - type: 'submenu' - label: item.label - enabled: item.enabled - submenu: convertToMenuTemplate item.subItems - else if item.type is 'separator' - type: 'separator' - else if item.type is 'checkbox' - type: 'checkbox' - label: item.label - enabled: item.enabled - checked: item.checked - else - type: 'normal' - label: item.label - enabled: item.enabled - if item.id? - transformed.click = -> - DevToolsAPI.contextMenuItemSelected item.id - DevToolsAPI.contextMenuCleared() - template.push transformed - template - -createMenu = (x, y, items, document) -> - {remote} = require 'electron' - {Menu} = remote - - menu = Menu.buildFromTemplate convertToMenuTemplate(items) - # The menu is expected to show asynchronously. - setTimeout -> menu.popup remote.getCurrentWindow() - -showFileChooserDialog = (callback) -> - {remote} = require 'electron' - {dialog} = remote - files = dialog.showOpenDialog {} - callback pathToHtml5FileObject files[0] if files? - -pathToHtml5FileObject = (path) -> - fs = require 'fs' - blob = new Blob([fs.readFileSync(path)]) - blob.name = path - blob - -createFileSelectorElement = (callback) -> - fileSelectorElement = document.createElement 'span' - fileSelectorElement.style.display = 'none' - fileSelectorElement.click = showFileChooserDialog.bind this, callback - return fileSelectorElement diff --git a/atom/renderer/lib/inspector.js b/atom/renderer/lib/inspector.js new file mode 100644 index 000000000000..9ef6440720ff --- /dev/null +++ b/atom/renderer/lib/inspector.js @@ -0,0 +1,81 @@ +window.onload = function() { + // Use menu API to show context menu. + InspectorFrontendHost.showContextMenuAtPoint = createMenu; + + // Use dialog API to override file chooser dialog. + return WebInspector.createFileSelectorElement = createFileSelectorElement; +}; + +var convertToMenuTemplate = function(items) { + var fn, i, item, len, template; + template = []; + fn = function(item) { + var transformed; + transformed = item.type === 'subMenu' ? { + type: 'submenu', + label: item.label, + enabled: item.enabled, + submenu: convertToMenuTemplate(item.subItems) + } : item.type === 'separator' ? { + type: 'separator' + } : item.type === 'checkbox' ? { + type: 'checkbox', + label: item.label, + enabled: item.enabled, + checked: item.checked + } : { + type: 'normal', + label: item.label, + enabled: item.enabled + }; + if (item.id != null) { + transformed.click = function() { + DevToolsAPI.contextMenuItemSelected(item.id); + return DevToolsAPI.contextMenuCleared(); + }; + } + return template.push(transformed); + }; + for (i = 0, len = items.length; i < len; i++) { + item = items[i]; + fn(item); + } + return template; +}; + +var createMenu = function(x, y, items, document) { + const remote = require('electron').remote; + const Menu = remote.Menu; + const menu = Menu.buildFromTemplate(convertToMenuTemplate(items)); + + // The menu is expected to show asynchronously. + return setTimeout(function() { + return menu.popup(remote.getCurrentWindow()); + }); +}; + +var showFileChooserDialog = function(callback) { + var dialog, files, remote; + remote = require('electron').remote; + dialog = remote.dialog; + files = dialog.showOpenDialog({}); + if (files != null) { + return callback(pathToHtml5FileObject(files[0])); + } +}; + +var pathToHtml5FileObject = function(path) { + var blob, fs; + fs = require('fs'); + blob = new Blob([fs.readFileSync(path)]); + blob.name = path; + return blob; +}; + +var createFileSelectorElement = function(callback) { + var fileSelectorElement; + fileSelectorElement = document.createElement('span'); + fileSelectorElement.style.display = 'none'; + fileSelectorElement.click = showFileChooserDialog.bind(this, callback); + return fileSelectorElement; +}; diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee deleted file mode 100644 index 5280f1927e80..000000000000 --- a/atom/renderer/lib/override.coffee +++ /dev/null @@ -1,129 +0,0 @@ -{ipcRenderer, remote} = require 'electron' - -# Helper function to resolve relative url. -a = window.top.document.createElement 'a' -resolveURL = (url) -> - a.href = url - a.href - -# Window object returned by "window.open". -class BrowserWindowProxy - @proxies: {} - - @getOrCreate: (guestId) -> - @proxies[guestId] ?= new BrowserWindowProxy(guestId) - - @remove: (guestId) -> - delete @proxies[guestId] - - constructor: (@guestId) -> - @closed = false - ipcRenderer.once "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{@guestId}", => - BrowserWindowProxy.remove(@guestId) - @closed = true - - close: -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', @guestId - - focus: -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'focus' - - blur: -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'blur' - - postMessage: (message, targetOrigin='*') -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', @guestId, message, targetOrigin, location.origin - - eval: (args...) -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', @guestId, 'executeJavaScript', args... - -unless process.guestInstanceId? - # Override default window.close. - window.close = -> - remote.getCurrentWindow().close() - -# Make the browser window or guest view emit "new-window" event. -window.open = (url, frameName='', features='') -> - options = {} - ints = [ 'x', 'y', 'width', 'height', 'min-width', 'max-width', 'min-height', 'max-height', 'zoom-factor' ] - # Make sure to get rid of excessive whitespace in the property name - for feature in features.split /,\s*/ - [name, value] = feature.split /\s*=/ - options[name] = - if value is 'yes' or value is '1' - true - else if value is 'no' or value is '0' - false - else - value - options.x ?= options.left if options.left - options.y ?= options.top if options.top - options.title ?= frameName - options.width ?= 800 - options.height ?= 600 - - # Resolve relative urls. - url = resolveURL url - - (options[name] = parseInt(options[name], 10) if options[name]?) for name in ints - - guestId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options - if guestId - BrowserWindowProxy.getOrCreate(guestId) - else - null - -# Use the dialog API to implement alert(). -window.alert = (message, title='') -> - buttons = ['OK'] - message = message.toString() - remote.dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons} - # Alert should always return undefined. - return - -# And the confirm(). -window.confirm = (message, title='') -> - buttons = ['OK', 'Cancel'] - cancelId = 1 - not remote.dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons, cancelId} - -# But we do not support prompt(). -window.prompt = -> - throw new Error('prompt() is and will not be supported.') - -if process.openerId? - window.opener = BrowserWindowProxy.getOrCreate process.openerId - -ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, sourceId, message, sourceOrigin) -> - # Manually dispatch event instead of using postMessage because we also need to - # set event.source. - event = document.createEvent 'Event' - event.initEvent 'message', false, false - event.data = message - event.origin = sourceOrigin - event.source = BrowserWindowProxy.getOrCreate(sourceId) - window.dispatchEvent event - -# Forward history operations to browser. -sendHistoryOperation = (args...) -> - ipcRenderer.send 'ATOM_SHELL_NAVIGATION_CONTROLLER', args... - -getHistoryOperation = (args...) -> - ipcRenderer.sendSync 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', args... - -window.history.back = -> sendHistoryOperation 'goBack' -window.history.forward = -> sendHistoryOperation 'goForward' -window.history.go = (offset) -> sendHistoryOperation 'goToOffset', offset -Object.defineProperty window.history, 'length', - get: -> - getHistoryOperation 'length' - -# Make document.hidden and document.visibilityState return the correct value. -Object.defineProperty document, 'hidden', - get: -> - currentWindow = remote.getCurrentWindow() - currentWindow.isMinimized() || !currentWindow.isVisible() - -Object.defineProperty document, 'visibilityState', - get: -> - if document.hidden then "hidden" else "visible" diff --git a/atom/renderer/lib/override.js b/atom/renderer/lib/override.js new file mode 100644 index 000000000000..6ff1e80785d8 --- /dev/null +++ b/atom/renderer/lib/override.js @@ -0,0 +1,230 @@ +const ipcRenderer = require('electron').ipcRenderer; +const remote = require('electron').remote; + +var slice = [].slice; + +// Helper function to resolve relative url. +var a = window.top.document.createElement('a'); + +var resolveURL = function(url) { + a.href = url; + return a.href; +}; + +// Window object returned by "window.open". +var BrowserWindowProxy = (function() { + BrowserWindowProxy.proxies = {}; + + BrowserWindowProxy.getOrCreate = function(guestId) { + var base; + return (base = this.proxies)[guestId] != null ? base[guestId] : base[guestId] = new BrowserWindowProxy(guestId); + }; + + BrowserWindowProxy.remove = function(guestId) { + return delete this.proxies[guestId]; + }; + + function BrowserWindowProxy(guestId1) { + this.guestId = guestId1; + this.closed = false; + ipcRenderer.once("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + this.guestId, (function(_this) { + return function() { + BrowserWindowProxy.remove(_this.guestId); + return _this.closed = true; + }; + })(this)); + } + + BrowserWindowProxy.prototype.close = function() { + return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId); + }; + + BrowserWindowProxy.prototype.focus = function() { + return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus'); + }; + + BrowserWindowProxy.prototype.blur = function() { + return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur'); + }; + + BrowserWindowProxy.prototype.postMessage = function(message, targetOrigin) { + if (targetOrigin == null) { + targetOrigin = '*'; + } + return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, location.origin); + }; + + BrowserWindowProxy.prototype["eval"] = function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript'].concat(slice.call(args))); + }; + + return BrowserWindowProxy; + +})(); + +if (process.guestInstanceId == null) { + // Override default window.close. + window.close = function() { + return remote.getCurrentWindow().close(); + }; +} + +// Make the browser window or guest view emit "new-window" event. +window.open = function(url, frameName, features) { + var feature, guestId, i, ints, j, len, len1, name, options, ref1, ref2, value; + if (frameName == null) { + frameName = ''; + } + if (features == null) { + features = ''; + } + options = {}; + ints = ['x', 'y', 'width', 'height', 'min-width', 'max-width', 'min-height', 'max-height', 'zoom-factor']; + + // Make sure to get rid of excessive whitespace in the property name + ref1 = features.split(/,\s*/); + for (i = 0, len = ref1.length; i < len; i++) { + feature = ref1[i]; + ref2 = feature.split(/\s*=/), name = ref2[0], value = ref2[1]; + options[name] = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value; + } + if (options.left) { + if (options.x == null) { + options.x = options.left; + } + } + if (options.top) { + if (options.y == null) { + options.y = options.top; + } + } + if (options.title == null) { + options.title = frameName; + } + if (options.width == null) { + options.width = 800; + } + if (options.height == null) { + options.height = 600; + } + + // Resolve relative urls. + url = resolveURL(url); + for (j = 0, len1 = ints.length; j < len1; j++) { + name = ints[j]; + if (options[name] != null) { + options[name] = parseInt(options[name], 10); + } + } + guestId = ipcRenderer.sendSync('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options); + if (guestId) { + return BrowserWindowProxy.getOrCreate(guestId); + } else { + return null; + } +}; + +// Use the dialog API to implement alert(). +window.alert = function(message, title) { + var buttons; + if (title == null) { + title = ''; + } + buttons = ['OK']; + message = message.toString(); + remote.dialog.showMessageBox(remote.getCurrentWindow(), { + message: message, + title: title, + buttons: buttons + }); + + // Alert should always return undefined. +}; + +// And the confirm(). +window.confirm = function(message, title) { + var buttons, cancelId; + if (title == null) { + title = ''; + } + buttons = ['OK', 'Cancel']; + cancelId = 1; + return !remote.dialog.showMessageBox(remote.getCurrentWindow(), { + message: message, + title: title, + buttons: buttons, + cancelId: cancelId + }); +}; + +// But we do not support prompt(). +window.prompt = function() { + throw new Error('prompt() is and will not be supported.'); +}; + +if (process.openerId != null) { + window.opener = BrowserWindowProxy.getOrCreate(process.openerId); +} + +ipcRenderer.on('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', function(event, sourceId, message, sourceOrigin) { + // Manually dispatch event instead of using postMessage because we also need to + // set event.source. + event = document.createEvent('Event'); + event.initEvent('message', false, false); + event.data = message; + event.origin = sourceOrigin; + event.source = BrowserWindowProxy.getOrCreate(sourceId); + return window.dispatchEvent(event); +}); + +// Forward history operations to browser. +var sendHistoryOperation = function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_NAVIGATION_CONTROLLER'].concat(slice.call(args))); +}; + +var getHistoryOperation = function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return ipcRenderer.sendSync.apply(ipcRenderer, ['ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER'].concat(slice.call(args))); +}; + +window.history.back = function() { + return sendHistoryOperation('goBack'); +}; + +window.history.forward = function() { + return sendHistoryOperation('goForward'); +}; + +window.history.go = function(offset) { + return sendHistoryOperation('goToOffset', offset); +}; + +Object.defineProperty(window.history, 'length', { + get: function() { + return getHistoryOperation('length'); + } +}); + +// Make document.hidden and document.visibilityState return the correct value. +Object.defineProperty(document, 'hidden', { + get: function() { + var currentWindow; + currentWindow = remote.getCurrentWindow(); + return currentWindow.isMinimized() || !currentWindow.isVisible(); + } +}); + +Object.defineProperty(document, 'visibilityState', { + get: function() { + if (document.hidden) { + return "hidden"; + } else { + return "visible"; + } + } +}); diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee deleted file mode 100644 index 30fe3d2a6e3d..000000000000 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ /dev/null @@ -1,89 +0,0 @@ -{ipcRenderer, webFrame} = require 'electron' - -requestId = 0 - -WEB_VIEW_EVENTS = - 'load-commit': ['url', 'isMainFrame'] - 'did-finish-load': [] - 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL'] - 'did-frame-finish-load': ['isMainFrame'] - 'did-start-loading': [] - 'did-stop-loading': [] - 'did-get-response-details': ['status', 'newURL', 'originalURL', - 'httpResponseCode', 'requestMethod', 'referrer', - 'headers'] - 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'] - 'dom-ready': [] - 'console-message': ['level', 'message', 'line', 'sourceId'] - 'devtools-opened': [] - 'devtools-closed': [] - 'devtools-focused': [] - 'new-window': ['url', 'frameName', 'disposition', 'options'] - 'will-navigate': ['url'] - 'did-navigate': ['url'] - 'did-navigate-in-page': ['url'] - 'close': [] - 'crashed': [] - 'gpu-crashed': [] - 'plugin-crashed': ['name', 'version'] - 'media-started-playing': [] - 'media-paused': [] - 'did-change-theme-color': ['themeColor'] - 'destroyed': [] - 'page-title-updated': ['title', 'explicitSet'] - 'page-favicon-updated': ['favicons'] - 'enter-html-full-screen': [] - 'leave-html-full-screen': [] - 'found-in-page': ['result'] - -DEPRECATED_EVENTS = - 'page-title-updated': 'page-title-set' - -dispatchEvent = (webView, eventName, eventKey, args...) -> - if DEPRECATED_EVENTS[eventName]? - dispatchEvent webView, DEPRECATED_EVENTS[eventName], eventKey, args... - domEvent = new Event(eventName) - for f, i in WEB_VIEW_EVENTS[eventKey] - domEvent[f] = args[i] - webView.dispatchEvent domEvent - webView.onLoadCommit domEvent if eventName is 'load-commit' - -module.exports = - registerEvents: (webView, viewInstanceId) -> - ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}", (event, eventName, args...) -> - dispatchEvent webView, eventName, eventName, args... - - ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}", (event, channel, args...) -> - domEvent = new Event('ipc-message') - domEvent.channel = channel - domEvent.args = [args...] - webView.dispatchEvent domEvent - - ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}", (event, args...) -> - domEvent = new Event('size-changed') - for f, i in ['oldWidth', 'oldHeight', 'newWidth', 'newHeight'] - domEvent[f] = args[i] - webView.onSizeChanged domEvent - - deregisterEvents: (viewInstanceId) -> - ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}" - ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}" - ipcRenderer.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}" - - createGuest: (params, callback) -> - requestId++ - ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId - ipcRenderer.once "ATOM_SHELL_RESPONSE_#{requestId}", callback - - attachGuest: (elementInstanceId, guestInstanceId, params) -> - ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params - webFrame.attachGuest elementInstanceId - - destroyGuest: (guestInstanceId) -> - ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId - - setSize: (guestInstanceId, params) -> - ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params - - setAllowTransparency: (guestInstanceId, allowtransparency) -> - ipcRenderer.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', guestInstanceId, allowtransparency diff --git a/atom/renderer/lib/web-view/guest-view-internal.js b/atom/renderer/lib/web-view/guest-view-internal.js new file mode 100644 index 000000000000..046c9643eadd --- /dev/null +++ b/atom/renderer/lib/web-view/guest-view-internal.js @@ -0,0 +1,112 @@ +const ipcRenderer = require('electron').ipcRenderer; +const webFrame = require('electron').webFrame; + +var slice = [].slice; +var requestId = 0; + +var WEB_VIEW_EVENTS = { + 'load-commit': ['url', 'isMainFrame'], + 'did-finish-load': [], + 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL'], + 'did-frame-finish-load': ['isMainFrame'], + 'did-start-loading': [], + 'did-stop-loading': [], + 'did-get-response-details': ['status', 'newURL', 'originalURL', 'httpResponseCode', 'requestMethod', 'referrer', 'headers'], + 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'], + 'dom-ready': [], + 'console-message': ['level', 'message', 'line', 'sourceId'], + 'devtools-opened': [], + 'devtools-closed': [], + 'devtools-focused': [], + 'new-window': ['url', 'frameName', 'disposition', 'options'], + 'will-navigate': ['url'], + 'did-navigate': ['url'], + 'did-navigate-in-page': ['url'], + 'close': [], + 'crashed': [], + 'gpu-crashed': [], + 'plugin-crashed': ['name', 'version'], + 'media-started-playing': [], + 'media-paused': [], + 'did-change-theme-color': ['themeColor'], + 'destroyed': [], + 'page-title-updated': ['title', 'explicitSet'], + 'page-favicon-updated': ['favicons'], + 'enter-html-full-screen': [], + 'leave-html-full-screen': [], + 'found-in-page': ['result'] +}; + +var DEPRECATED_EVENTS = { + 'page-title-updated': 'page-title-set' +}; + +var dispatchEvent = function() { + var args, domEvent, eventKey, eventName, f, i, j, len, ref1, webView; + webView = arguments[0], eventName = arguments[1], eventKey = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; + if (DEPRECATED_EVENTS[eventName] != null) { + dispatchEvent.apply(null, [webView, DEPRECATED_EVENTS[eventName], eventKey].concat(slice.call(args))); + } + domEvent = new Event(eventName); + ref1 = WEB_VIEW_EVENTS[eventKey]; + for (i = j = 0, len = ref1.length; j < len; i = ++j) { + f = ref1[i]; + domEvent[f] = args[i]; + } + webView.dispatchEvent(domEvent); + if (eventName === 'load-commit') { + return webView.onLoadCommit(domEvent); + } +}; + +module.exports = { + registerEvents: function(webView, viewInstanceId) { + ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId, function() { + var args, event, eventName; + event = arguments[0], eventName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + return dispatchEvent.apply(null, [webView, eventName, eventName].concat(slice.call(args))); + }); + ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId, function() { + var args, channel, domEvent, event; + event = arguments[0], channel = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + domEvent = new Event('ipc-message'); + domEvent.channel = channel; + domEvent.args = slice.call(args); + return webView.dispatchEvent(domEvent); + }); + return ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + viewInstanceId, function() { + var args, domEvent, event, f, i, j, len, ref1; + event = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + domEvent = new Event('size-changed'); + ref1 = ['oldWidth', 'oldHeight', 'newWidth', 'newHeight']; + for (i = j = 0, len = ref1.length; j < len; i = ++j) { + f = ref1[i]; + domEvent[f] = args[i]; + } + return webView.onSizeChanged(domEvent); + }); + }, + deregisterEvents: function(viewInstanceId) { + ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId); + ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId); + return ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + viewInstanceId); + }, + createGuest: function(params, callback) { + requestId++; + ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId); + return ipcRenderer.once("ATOM_SHELL_RESPONSE_" + requestId, callback); + }, + attachGuest: function(elementInstanceId, guestInstanceId, params) { + ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params); + return webFrame.attachGuest(elementInstanceId); + }, + destroyGuest: function(guestInstanceId) { + return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId); + }, + setSize: function(guestInstanceId, params) { + return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params); + }, + setAllowTransparency: function(guestInstanceId, allowtransparency) { + return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', guestInstanceId, allowtransparency); + } +}; diff --git a/atom/renderer/lib/web-view/web-view-attributes.coffee b/atom/renderer/lib/web-view/web-view-attributes.coffee deleted file mode 100644 index 7760b400ce17..000000000000 --- a/atom/renderer/lib/web-view/web-view-attributes.coffee +++ /dev/null @@ -1,230 +0,0 @@ -WebViewImpl = require './web-view' -guestViewInternal = require './guest-view-internal' -webViewConstants = require './web-view-constants' - -{remote} = require 'electron' - -# Helper function to resolve url set in attribute. -a = document.createElement 'a' -resolveURL = (url) -> - a.href = url - a.href - -# Attribute objects. -# Default implementation of a WebView attribute. -class WebViewAttribute - constructor: (name, webViewImpl) -> - @name = name - @webViewImpl = webViewImpl - @ignoreMutation = false - - @defineProperty() - - # Retrieves and returns the attribute's value. - getValue: -> @webViewImpl.webviewNode.getAttribute(@name) || '' - - # Sets the attribute's value. - setValue: (value) -> @webViewImpl.webviewNode.setAttribute(@name, value || '') - - # Changes the attribute's value without triggering its mutation handler. - setValueIgnoreMutation: (value) -> - @ignoreMutation = true - @setValue value - @ignoreMutation = false - - # Defines this attribute as a property on the webview node. - defineProperty: -> - Object.defineProperty @webViewImpl.webviewNode, @name, - get: => @getValue() - set: (value) => @setValue value - enumerable: true - - # Called when the attribute's value changes. - handleMutation: -> - -# An attribute that is treated as a Boolean. -class BooleanAttribute extends WebViewAttribute - constructor: (name, webViewImpl) -> - super name, webViewImpl - - getValue: -> @webViewImpl.webviewNode.hasAttribute @name - - setValue: (value) -> - unless value - @webViewImpl.webviewNode.removeAttribute @name - else - @webViewImpl.webviewNode.setAttribute @name, '' - -# Attribute that specifies whether transparency is allowed in the webview. -class AllowTransparencyAttribute extends BooleanAttribute - constructor: (webViewImpl) -> - super webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl - - handleMutation: (oldValue, newValue) -> - return unless @webViewImpl.guestInstanceId - guestViewInternal.setAllowTransparency @webViewImpl.guestInstanceId, @getValue() - -# Attribute used to define the demension limits of autosizing. -class AutosizeDimensionAttribute extends WebViewAttribute - constructor: (name, webViewImpl) -> - super name, webViewImpl - - getValue: -> parseInt(@webViewImpl.webviewNode.getAttribute(@name)) || 0 - - handleMutation: (oldValue, newValue) -> - return unless @webViewImpl.guestInstanceId - guestViewInternal.setSize @webViewImpl.guestInstanceId, - enableAutoSize: @webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() - min: - width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0 - height: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0 - max: - width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0 - height: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0 - -# Attribute that specifies whether the webview should be autosized. -class AutosizeAttribute extends BooleanAttribute - constructor: (webViewImpl) -> - super webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl - - handleMutation: AutosizeDimensionAttribute::handleMutation - -# Attribute representing the state of the storage partition. -class PartitionAttribute extends WebViewAttribute - constructor: (webViewImpl) -> - super webViewConstants.ATTRIBUTE_PARTITION, webViewImpl - @validPartitionId = true - - handleMutation: (oldValue, newValue) -> - newValue = newValue || '' - - # The partition cannot change if the webview has already navigated. - unless @webViewImpl.beforeFirstNavigation - window.console.error webViewConstants.ERROR_MSG_ALREADY_NAVIGATED - @setValueIgnoreMutation oldValue - return - - if newValue is 'persist:' - @validPartitionId = false - window.console.error webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE - -# Attribute that handles the location and navigation of the webview. -class SrcAttribute extends WebViewAttribute - constructor: (webViewImpl) -> - super webViewConstants.ATTRIBUTE_SRC, webViewImpl - @setupMutationObserver() - - getValue: -> - if @webViewImpl.webviewNode.hasAttribute @name - resolveURL @webViewImpl.webviewNode.getAttribute(@name) - else - '' - - setValueIgnoreMutation: (value) -> - WebViewAttribute::setValueIgnoreMutation.call(this, value) - # takeRecords() is needed to clear queued up src mutations. Without it, it - # is possible for this change to get picked up asyncronously by src's - # mutation observer |observer|, and then get handled even though we do not - # want to handle this mutation. - @observer.takeRecords() - - handleMutation: (oldValue, newValue) -> - # Once we have navigated, we don't allow clearing the src attribute. - # Once enters a navigated state, it cannot return to a - # placeholder state. - if not 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. - @setValueIgnoreMutation oldValue - return - @parse() - - # 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. - setupMutationObserver: -> - @observer = new MutationObserver (mutations) => - for mutation in mutations - oldValue = mutation.oldValue - newValue = @getValue() - return if oldValue isnt newValue - @handleMutation oldValue, newValue - params = - attributes: true, - attributeOldValue: true, - attributeFilter: [@name] - @observer.observe @webViewImpl.webviewNode, params - - parse: -> - if not @webViewImpl.elementAttached or - not @webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId or - not @.getValue() - return - - unless @webViewImpl.guestInstanceId? - if @webViewImpl.beforeFirstNavigation - @webViewImpl.beforeFirstNavigation = false - @webViewImpl.createGuest() - return - - # Navigate to |this.src|. - opts = {} - httpreferrer = @webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue() - if httpreferrer then opts.httpReferrer = httpreferrer - - useragent = @webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue() - if useragent then opts.userAgent = useragent - - guestContents = remote.getGuestWebContents(@webViewImpl.guestInstanceId) - guestContents.loadURL @getValue(), opts - -# Attribute specifies HTTP referrer. -class HttpReferrerAttribute extends WebViewAttribute - constructor: (webViewImpl) -> - super webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl - -# Attribute specifies user agent -class UserAgentAttribute extends WebViewAttribute - constructor: (webViewImpl) -> - super webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl - -# Attribute that set preload script. -class PreloadAttribute extends WebViewAttribute - constructor: (webViewImpl) -> - super webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl - - getValue: -> - return '' unless @webViewImpl.webviewNode.hasAttribute @name - preload = resolveURL @webViewImpl.webviewNode.getAttribute(@name) - protocol = preload.substr 0, 5 - unless protocol is 'file:' - console.error webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE - preload = '' - preload - -# Sets up all of the webview attributes. -WebViewImpl::setupWebViewAttributes = -> - @attributes = {} - - @attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] = new AllowTransparencyAttribute(this) - @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this) - @attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this) - @attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this) - @attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this) - @attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this) - @attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this) - @attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this) - @attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this) - @attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this) - @attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) - - autosizeAttributes = [ - webViewConstants.ATTRIBUTE_MAXHEIGHT - webViewConstants.ATTRIBUTE_MAXWIDTH - webViewConstants.ATTRIBUTE_MINHEIGHT - webViewConstants.ATTRIBUTE_MINWIDTH - ] - for attribute in autosizeAttributes - @attributes[attribute] = new AutosizeDimensionAttribute(attribute, this) diff --git a/atom/renderer/lib/web-view/web-view-attributes.js b/atom/renderer/lib/web-view/web-view-attributes.js new file mode 100644 index 000000000000..90ce9f1290fc --- /dev/null +++ b/atom/renderer/lib/web-view/web-view-attributes.js @@ -0,0 +1,363 @@ +const WebViewImpl = require('./web-view'); +const guestViewInternal = require('./guest-view-internal'); +const webViewConstants = require('./web-view-constants'); +const remote = require('electron').remote; + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + +var hasProp = {}.hasOwnProperty; + +// Helper function to resolve url set in attribute. +var a = document.createElement('a'); + +var resolveURL = function(url) { + a.href = url; + return a.href; +}; + +// Attribute objects. +// Default implementation of a WebView attribute. +var WebViewAttribute = (function() { + function WebViewAttribute(name, webViewImpl) { + this.name = name; + this.value = webViewImpl.webviewNode[name] || ''; + this.webViewImpl = webViewImpl; + this.ignoreMutation = false; + this.defineProperty(); + } + + // Retrieves and returns the attribute's value. + WebViewAttribute.prototype.getValue = function() { + return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value; + }; + + // Sets the attribute's value. + WebViewAttribute.prototype.setValue = function(value) { + return this.webViewImpl.webviewNode.setAttribute(this.name, value || ''); + }; + + // Changes the attribute's value without triggering its mutation handler. + WebViewAttribute.prototype.setValueIgnoreMutation = function(value) { + this.ignoreMutation = true; + this.setValue(value); + return this.ignoreMutation = false; + }; + + // Defines this attribute as a property on the webview node. + WebViewAttribute.prototype.defineProperty = function() { + return Object.defineProperty(this.webViewImpl.webviewNode, this.name, { + get: (function(_this) { + return function() { + return _this.getValue(); + }; + })(this), + set: (function(_this) { + return function(value) { + return _this.setValue(value); + }; + })(this), + enumerable: true + }); + }; + + // Called when the attribute's value changes. + WebViewAttribute.prototype.handleMutation = function() {}; + + return WebViewAttribute; + +})(); + +// An attribute that is treated as a Boolean. +var BooleanAttribute = (function(superClass) { + extend(BooleanAttribute, superClass); + + function BooleanAttribute(name, webViewImpl) { + BooleanAttribute.__super__.constructor.call(this, name, webViewImpl); + } + + BooleanAttribute.prototype.getValue = function() { + return this.webViewImpl.webviewNode.hasAttribute(this.name); + }; + + BooleanAttribute.prototype.setValue = function(value) { + if (!value) { + return this.webViewImpl.webviewNode.removeAttribute(this.name); + } else { + return this.webViewImpl.webviewNode.setAttribute(this.name, ''); + } + }; + + return BooleanAttribute; + +})(WebViewAttribute); + +// Attribute that specifies whether transparency is allowed in the webview. +var AllowTransparencyAttribute = (function(superClass) { + extend(AllowTransparencyAttribute, superClass); + + function AllowTransparencyAttribute(webViewImpl) { + AllowTransparencyAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl); + } + + AllowTransparencyAttribute.prototype.handleMutation = function(oldValue, newValue) { + if (!this.webViewImpl.guestInstanceId) { + return; + } + return guestViewInternal.setAllowTransparency(this.webViewImpl.guestInstanceId, this.getValue()); + }; + + return AllowTransparencyAttribute; + +})(BooleanAttribute); + +// Attribute used to define the demension limits of autosizing. +var AutosizeDimensionAttribute = (function(superClass) { + extend(AutosizeDimensionAttribute, superClass); + + function AutosizeDimensionAttribute(name, webViewImpl) { + AutosizeDimensionAttribute.__super__.constructor.call(this, name, webViewImpl); + } + + AutosizeDimensionAttribute.prototype.getValue = function() { + return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name)) || 0; + }; + + AutosizeDimensionAttribute.prototype.handleMutation = function(oldValue, newValue) { + if (!this.webViewImpl.guestInstanceId) { + return; + } + return guestViewInternal.setSize(this.webViewImpl.guestInstanceId, { + enableAutoSize: this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue(), + min: { + width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0), + height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() || 0) + }, + max: { + width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0), + height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0) + } + }); + }; + + return AutosizeDimensionAttribute; + +})(WebViewAttribute); + +// Attribute that specifies whether the webview should be autosized. +var AutosizeAttribute = (function(superClass) { + extend(AutosizeAttribute, superClass); + + function AutosizeAttribute(webViewImpl) { + AutosizeAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl); + } + + AutosizeAttribute.prototype.handleMutation = AutosizeDimensionAttribute.prototype.handleMutation; + + return AutosizeAttribute; + +})(BooleanAttribute); + +// Attribute representing the state of the storage partition. +var PartitionAttribute = (function(superClass) { + extend(PartitionAttribute, superClass); + + function PartitionAttribute(webViewImpl) { + PartitionAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_PARTITION, webViewImpl); + this.validPartitionId = true; + } + + PartitionAttribute.prototype.handleMutation = function(oldValue, newValue) { + newValue = newValue || ''; + + // The partition cannot change if the webview has already navigated. + if (!this.webViewImpl.beforeFirstNavigation) { + window.console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED); + this.setValueIgnoreMutation(oldValue); + return; + } + if (newValue === 'persist:') { + this.validPartitionId = false; + return window.console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE); + } + }; + + return PartitionAttribute; + +})(WebViewAttribute); + +// Attribute that handles the location and navigation of the webview. +var SrcAttribute = (function(superClass) { + extend(SrcAttribute, superClass); + + function SrcAttribute(webViewImpl) { + SrcAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_SRC, webViewImpl); + this.setupMutationObserver(); + } + + SrcAttribute.prototype.getValue = function() { + if (this.webViewImpl.webviewNode.hasAttribute(this.name)) { + return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)); + } else { + return this.value; + } + }; + + SrcAttribute.prototype.setValueIgnoreMutation = function(value) { + WebViewAttribute.prototype.setValueIgnoreMutation.call(this, value); + + // takeRecords() is needed to clear queued up src mutations. Without it, it + // is possible for this change to get picked up asyncronously by src's + // mutation observer |observer|, and then get handled even though we do not + // want to handle this mutation. + return this.observer.takeRecords(); + }; + + SrcAttribute.prototype.handleMutation = function(oldValue, newValue) { + + // Once we have navigated, we don't allow clearing the src attribute. + // Once enters a navigated state, it cannot return to a + // placeholder state. + if (!newValue && 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. + this.setValueIgnoreMutation(oldValue); + return; + } + return this.parse(); + }; + + + // 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. + SrcAttribute.prototype.setupMutationObserver = function() { + var params; + this.observer = new MutationObserver((function(_this) { + return function(mutations) { + var i, len, mutation, newValue, oldValue; + for (i = 0, len = mutations.length; i < len; i++) { + mutation = mutations[i]; + oldValue = mutation.oldValue; + newValue = _this.getValue(); + if (oldValue !== newValue) { + return; + } + _this.handleMutation(oldValue, newValue); + } + }; + })(this)); + params = { + attributes: true, + attributeOldValue: true, + attributeFilter: [this.name] + }; + return this.observer.observe(this.webViewImpl.webviewNode, params); + }; + + SrcAttribute.prototype.parse = function() { + var guestContents, httpreferrer, opts, useragent; + if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) { + return; + } + if (this.webViewImpl.guestInstanceId == null) { + if (this.webViewImpl.beforeFirstNavigation) { + this.webViewImpl.beforeFirstNavigation = false; + this.webViewImpl.createGuest(); + } + return; + } + + // Navigate to |this.src|. + opts = {}; + httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue(); + if (httpreferrer) { + opts.httpReferrer = httpreferrer; + } + useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue(); + if (useragent) { + opts.userAgent = useragent; + } + guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId); + return guestContents.loadURL(this.getValue(), opts); + }; + + return SrcAttribute; + +})(WebViewAttribute); + +// Attribute specifies HTTP referrer. +var HttpReferrerAttribute = (function(superClass) { + extend(HttpReferrerAttribute, superClass); + + function HttpReferrerAttribute(webViewImpl) { + HttpReferrerAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl); + } + + return HttpReferrerAttribute; + +})(WebViewAttribute); + +// Attribute specifies user agent +var UserAgentAttribute = (function(superClass) { + extend(UserAgentAttribute, superClass); + + function UserAgentAttribute(webViewImpl) { + UserAgentAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl); + } + + return UserAgentAttribute; + +})(WebViewAttribute); + +// Attribute that set preload script. +var PreloadAttribute = (function(superClass) { + extend(PreloadAttribute, superClass); + + function PreloadAttribute(webViewImpl) { + PreloadAttribute.__super__.constructor.call(this, webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl); + } + + PreloadAttribute.prototype.getValue = function() { + var preload, protocol; + if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) { + return this.value; + } + preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)); + protocol = preload.substr(0, 5); + if (protocol !== 'file:') { + console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE); + preload = ''; + } + return preload; + }; + + return PreloadAttribute; + +})(WebViewAttribute); + +// Sets up all of the webview attributes. +WebViewImpl.prototype.setupWebViewAttributes = function() { + var attribute, autosizeAttributes, i, len, results; + this.attributes = {}; + this.attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] = new AllowTransparencyAttribute(this); + this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this); + this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this); + this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this); + this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this); + this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this); + this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this); + this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this); + this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this); + this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this); + this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this); + autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]; + results = []; + for (i = 0, len = autosizeAttributes.length; i < len; i++) { + attribute = autosizeAttributes[i]; + results.push(this.attributes[attribute] = new AutosizeDimensionAttribute(attribute, this)); + } + return results; +}; diff --git a/atom/renderer/lib/web-view/web-view-constants.coffee b/atom/renderer/lib/web-view/web-view-constants.coffee deleted file mode 100644 index bfb9376fa7ea..000000000000 --- a/atom/renderer/lib/web-view/web-view-constants.coffee +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = - # Attributes. - ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency' - ATTRIBUTE_AUTOSIZE: 'autosize' - ATTRIBUTE_MAXHEIGHT: 'maxheight' - ATTRIBUTE_MAXWIDTH: 'maxwidth' - ATTRIBUTE_MINHEIGHT: 'minheight' - ATTRIBUTE_MINWIDTH: 'minwidth' - ATTRIBUTE_NAME: 'name' - ATTRIBUTE_PARTITION: 'partition' - ATTRIBUTE_SRC: 'src' - ATTRIBUTE_HTTPREFERRER: 'httpreferrer' - ATTRIBUTE_NODEINTEGRATION: 'nodeintegration' - ATTRIBUTE_PLUGINS: 'plugins' - ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity' - ATTRIBUTE_ALLOWPOPUPS: 'allowpopups' - ATTRIBUTE_PRELOAD: 'preload' - ATTRIBUTE_USERAGENT: 'useragent' - - # Internal attribute. - ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid' - - # Error messages. - ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.' - ERROR_MSG_CANNOT_INJECT_SCRIPT: ': ' + - 'Script cannot be injected into content until the page has loaded.' - ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.' - ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.' diff --git a/atom/renderer/lib/web-view/web-view-constants.js b/atom/renderer/lib/web-view/web-view-constants.js new file mode 100644 index 000000000000..418083061a26 --- /dev/null +++ b/atom/renderer/lib/web-view/web-view-constants.js @@ -0,0 +1,28 @@ +module.exports = { + // Attributes. + ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency', + ATTRIBUTE_AUTOSIZE: 'autosize', + ATTRIBUTE_MAXHEIGHT: 'maxheight', + ATTRIBUTE_MAXWIDTH: 'maxwidth', + ATTRIBUTE_MINHEIGHT: 'minheight', + ATTRIBUTE_MINWIDTH: 'minwidth', + ATTRIBUTE_NAME: 'name', + ATTRIBUTE_PARTITION: 'partition', + ATTRIBUTE_SRC: 'src', + ATTRIBUTE_HTTPREFERRER: 'httpreferrer', + ATTRIBUTE_NODEINTEGRATION: 'nodeintegration', + ATTRIBUTE_PLUGINS: 'plugins', + ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity', + ATTRIBUTE_ALLOWPOPUPS: 'allowpopups', + ATTRIBUTE_PRELOAD: 'preload', + ATTRIBUTE_USERAGENT: 'useragent', + + // Internal attribute. + ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid', + + // Error messages. + ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.', + ERROR_MSG_CANNOT_INJECT_SCRIPT: ': ' + 'Script cannot be injected into content until the page has loaded.', + ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.', + ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.' +}; diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee deleted file mode 100644 index fc01725746ef..000000000000 --- a/atom/renderer/lib/web-view/web-view.coffee +++ /dev/null @@ -1,342 +0,0 @@ -{deprecate, webFrame, remote, ipcRenderer} = require 'electron' -v8Util = process.atomBinding 'v8_util' - -guestViewInternal = require './guest-view-internal' -webViewConstants = require './web-view-constants' - -# ID generator. -nextId = 0 -getNextId = -> ++nextId - -# Represents the internal state of the WebView node. -class WebViewImpl - constructor: (@webviewNode) -> - v8Util.setHiddenValue @webviewNode, 'internal', this - @attached = false - @elementAttached = false - - @beforeFirstNavigation = true - - # on* Event handlers. - @on = {} - - @browserPluginNode = @createBrowserPluginNode() - shadowRoot = @webviewNode.createShadowRoot() - @setupWebViewAttributes() - @setupFocusPropagation() - - @viewInstanceId = getNextId() - - shadowRoot.appendChild @browserPluginNode - - createBrowserPluginNode: -> - # We create BrowserPlugin as a custom element in order to observe changes - # to attributes synchronously. - browserPluginNode = new WebViewImpl.BrowserPlugin() - v8Util.setHiddenValue browserPluginNode, 'internal', this - browserPluginNode - - # Resets some state upon reattaching element to the DOM. - reset: -> - # If guestInstanceId is defined then the 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 - @webContents = null - @guestInstanceId = undefined - @beforeFirstNavigation = true - @attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true - @internalInstanceId = 0 - - # Sets the .request property. - setRequestPropertyOnWebViewNode: (request) -> - Object.defineProperty @webviewNode, 'request', value: request, enumerable: true - - setupFocusPropagation: -> - unless @webviewNode.hasAttribute 'tabIndex' - # needs a tabIndex in order to be focusable. - # TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute - # to allow to be focusable. - # See http://crbug.com/231664. - @webviewNode.setAttribute 'tabIndex', -1 - @webviewNode.addEventListener 'focus', (e) => - # Focus the BrowserPlugin when the takes focus. - @browserPluginNode.focus() - @webviewNode.addEventListener 'blur', (e) => - # Blur the BrowserPlugin when the loses focus. - @browserPluginNode.blur() - - # This observer monitors mutations to attributes of the 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: (attributeName, oldValue, newValue) -> - if not @attributes[attributeName] or @attributes[attributeName].ignoreMutation - return - - # Let the changed attribute handle its own mutation; - @attributes[attributeName].handleMutation oldValue, newValue - - handleBrowserPluginAttributeMutation: (attributeName, oldValue, newValue) -> - if attributeName is webViewConstants.ATTRIBUTE_INTERNALINSTANCEID and !oldValue and !!newValue - @browserPluginNode.removeAttribute webViewConstants.ATTRIBUTE_INTERNALINSTANCEID - @internalInstanceId = parseInt newValue - - # Track when the element resizes using the element resize callback. - webFrame.registerElementResizeCallback @internalInstanceId, @onElementResize.bind(this) - - return unless @guestInstanceId - - guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams() - - 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 - # outside of current constraints. - maxWidth = @attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width - maxHeight = @attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width - minWidth = @attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width - minHeight = @attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width - - minWidth = Math.min minWidth, maxWidth - minHeight = Math.min minHeight, maxHeight - - if not @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() 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 has actually - # changed. - @dispatchEvent webViewEvent - - onElementResize: (newSize) -> - # Dispatch the 'resize' event. - resizeEvent = new Event('resize', bubbles: true) - resizeEvent.newWidth = newSize.width - resizeEvent.newHeight = newSize.height - @dispatchEvent resizeEvent - - if @guestInstanceId - guestViewInternal.setSize @guestInstanceId, normal: newSize - - createGuest: -> - guestViewInternal.createGuest @buildParams(), (event, guestInstanceId) => - @attachWindow guestInstanceId - - dispatchEvent: (webViewEvent) -> - @webviewNode.dispatchEvent webViewEvent - - # Adds an 'on' 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: (webViewEvent) -> - oldValue = @webviewNode.getAttribute webViewConstants.ATTRIBUTE_SRC - newValue = webViewEvent.url - if webViewEvent.isMainFrame and (oldValue != newValue) - # Touching the src attribute triggers a navigation. To avoid - # triggering a page reload on every guest-initiated navigation, - # we do not handle this mutation - @attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation newValue - - onAttach: (storagePartitionId) -> - @attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue storagePartitionId - - buildParams: -> - params = - instanceId: @viewInstanceId - userAgentOverride: @userAgentOverride - for own attributeName, attribute of @attributes - params[attributeName] = attribute.getValue() - # When the WebView is not participating in layout (display:none) - # then getBoundingClientRect() would report a width and height of 0. - # However, in the case where the WebView has a fixed size we can - # use that value to initially size the guest so as to avoid a relayout of - # the on display:block. - css = window.getComputedStyle @webviewNode, null - elementRect = @webviewNode.getBoundingClientRect() - params.elementWidth = parseInt(elementRect.width) || - parseInt(css.getPropertyValue('width')) - params.elementHeight = parseInt(elementRect.height) || - parseInt(css.getPropertyValue('height')) - params - - attachWindow: (guestInstanceId) -> - @guestInstanceId = guestInstanceId - @webContents = remote.getGuestWebContents @guestInstanceId - return true unless @internalInstanceId - - guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams() - -# Registers browser plugin custom element. -registerBrowserPluginElement = -> - proto = Object.create HTMLObjectElement.prototype - - proto.createdCallback = -> - @setAttribute 'type', 'application/browser-plugin' - @setAttribute 'id', 'browser-plugin-' + getNextId() - # The node fills in the container. - @style.display = 'block' - @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 - - WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement 'browserplugin', - extends: 'object', prototype: proto - - delete proto.createdCallback - delete proto.attachedCallback - delete proto.detachedCallback - delete proto.attributeChangedCallback - -# Registers custom element. -registerWebViewElement = -> - proto = Object.create HTMLObjectElement.prototype - - proto.createdCallback = -> - new WebViewImpl(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 - guestViewInternal.deregisterEvents internal.viewInstanceId - internal.elementAttached = false - internal.reset() - - proto.attachedCallback = -> - internal = v8Util.getHiddenValue this, 'internal' - return unless internal - unless internal.elementAttached - guestViewInternal.registerEvents internal, internal.viewInstanceId - internal.elementAttached = true - internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse() - - # Public-facing API methods. - methods = [ - 'getURL' - 'getTitle' - 'isLoading' - 'isWaitingForResponse' - 'stop' - 'reload' - 'reloadIgnoringCache' - 'canGoBack' - 'canGoForward' - 'canGoToOffset' - 'clearHistory' - 'goBack' - 'goForward' - 'goToIndex' - 'goToOffset' - 'isCrashed' - 'setUserAgent' - 'getUserAgent' - 'openDevTools' - 'closeDevTools' - 'isDevToolsOpened' - 'isDevToolsFocused' - 'inspectElement' - 'setAudioMuted' - 'isAudioMuted' - 'undo' - 'redo' - 'cut' - 'copy' - 'paste' - 'pasteAndMatchStyle' - 'delete' - 'selectAll' - 'unselect' - 'replace' - 'replaceMisspelling' - 'findInPage' - 'stopFindInPage' - 'getId' - 'downloadURL' - 'inspectServiceWorker' - 'print' - 'printToPDF' - ] - - nonblockMethods = [ - 'send', - 'sendInputEvent', - 'executeJavaScript', - 'insertCSS' - ] - - # Forward proto.foo* method calls to WebViewImpl.foo*. - createBlockHandler = (m) -> - (args...) -> - internal = v8Util.getHiddenValue this, 'internal' - internal.webContents[m] args... - proto[m] = createBlockHandler m for m in methods - - createNonBlockHandler = (m) -> - (args...) -> - internal = v8Util.getHiddenValue this, 'internal' - ipcRenderer.send('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m, args...) - - proto[m] = createNonBlockHandler m for m in nonblockMethods - - # Deprecated. - deprecate.rename proto, 'getUrl', 'getURL' - - 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 - -module.exports = WebViewImpl diff --git a/atom/renderer/lib/web-view/web-view.js b/atom/renderer/lib/web-view/web-view.js new file mode 100644 index 000000000000..1daf66e55aa3 --- /dev/null +++ b/atom/renderer/lib/web-view/web-view.js @@ -0,0 +1,460 @@ +'user strict'; + +const deprecate = require('electron').deprecate; +const webFrame = require('electron').webFrame; +const remote = require('electron').remote; +const ipcRenderer = require('electron').ipcRenderer; + +const v8Util = process.atomBinding('v8_util'); +const guestViewInternal = require('./guest-view-internal'); +const webViewConstants = require('./web-view-constants'); + +var hasProp = {}.hasOwnProperty; +var slice = [].slice; + +// ID generator. +var nextId = 0; + +var getNextId = function() { + return ++nextId; +}; + +// Represents the internal state of the WebView node. +var WebViewImpl = (function() { + function WebViewImpl(webviewNode) { + var shadowRoot; + this.webviewNode = webviewNode; + v8Util.setHiddenValue(this.webviewNode, 'internal', this); + this.attached = false; + this.elementAttached = false; + this.beforeFirstNavigation = true; + + // on* Event handlers. + this.on = {}; + this.browserPluginNode = this.createBrowserPluginNode(); + shadowRoot = this.webviewNode.createShadowRoot(); + this.setupWebViewAttributes(); + this.setupFocusPropagation(); + this.viewInstanceId = getNextId(); + shadowRoot.appendChild(this.browserPluginNode); + + // Subscribe to host's zoom level changes. + this.onZoomLevelChanged = (zoomLevel) => { + this.webviewNode.setZoomLevel(zoomLevel); + } + webFrame.on('zoom-level-changed', this.onZoomLevelChanged); + } + + WebViewImpl.prototype.createBrowserPluginNode = function() { + // We create BrowserPlugin as a custom element in order to observe changes + // to attributes synchronously. + var browserPluginNode; + browserPluginNode = new WebViewImpl.BrowserPlugin(); + v8Util.setHiddenValue(browserPluginNode, 'internal', this); + return browserPluginNode; + }; + + // Resets some state upon reattaching element to the DOM. + WebViewImpl.prototype.reset = function() { + // Unlisten the zoom-level-changed event. + webFrame.removeListener('zoom-level-changed', this.onZoomLevelChanged); + + // If guestInstanceId is defined then the 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 (this.guestInstanceId) { + guestViewInternal.destroyGuest(this.guestInstanceId); + this.webContents = null; + this.guestInstanceId = void 0; + this.beforeFirstNavigation = true; + this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true; + } + return this.internalInstanceId = 0; + }; + + // Sets the .request property. + WebViewImpl.prototype.setRequestPropertyOnWebViewNode = function(request) { + return Object.defineProperty(this.webviewNode, 'request', { + value: request, + enumerable: true + }); + }; + + WebViewImpl.prototype.setupFocusPropagation = function() { + if (!this.webviewNode.hasAttribute('tabIndex')) { + + // needs a tabIndex in order to be focusable. + // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute + // to allow to be focusable. + // See http://crbug.com/231664. + this.webviewNode.setAttribute('tabIndex', -1); + } + this.webviewNode.addEventListener('focus', (function(_this) { + return function(e) { + + // Focus the BrowserPlugin when the takes focus. + return _this.browserPluginNode.focus(); + }; + })(this)); + return this.webviewNode.addEventListener('blur', (function(_this) { + return function(e) { + + // Blur the BrowserPlugin when the loses focus. + return _this.browserPluginNode.blur(); + }; + })(this)); + }; + + + // This observer monitors mutations to attributes of the 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. + WebViewImpl.prototype.handleWebviewAttributeMutation = function(attributeName, oldValue, newValue) { + if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) { + return; + } + + // Let the changed attribute handle its own mutation; + return this.attributes[attributeName].handleMutation(oldValue, newValue); + }; + + WebViewImpl.prototype.handleBrowserPluginAttributeMutation = function(attributeName, oldValue, newValue) { + if (attributeName === webViewConstants.ATTRIBUTE_INTERNALINSTANCEID && !oldValue && !!newValue) { + this.browserPluginNode.removeAttribute(webViewConstants.ATTRIBUTE_INTERNALINSTANCEID); + this.internalInstanceId = parseInt(newValue); + + // Track when the element resizes using the element resize callback. + webFrame.registerElementResizeCallback(this.internalInstanceId, this.onElementResize.bind(this)); + if (!this.guestInstanceId) { + return; + } + return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()); + } + }; + + WebViewImpl.prototype.onSizeChanged = function(webViewEvent) { + var height, maxHeight, maxWidth, minHeight, minWidth, newHeight, newWidth, node, width; + newWidth = webViewEvent.newWidth; + newHeight = webViewEvent.newHeight; + node = this.webviewNode; + width = node.offsetWidth; + height = node.offsetHeight; + + // Check the current bounds to make sure we do not resize + // outside of current constraints. + maxWidth = this.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width; + maxHeight = this.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width; + minWidth = this.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width; + minHeight = this.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width; + minWidth = Math.min(minWidth, maxWidth); + minHeight = Math.min(minHeight, maxHeight); + if (!this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() || (newWidth >= minWidth && newWidth <= maxWidth && newHeight >= minHeight && newHeight <= maxHeight)) { + node.style.width = newWidth + 'px'; + node.style.height = newHeight + 'px'; + + // Only fire the DOM event if the size of the has actually + // changed. + return this.dispatchEvent(webViewEvent); + } + }; + + WebViewImpl.prototype.onElementResize = function(newSize) { + // Dispatch the 'resize' event. + var resizeEvent; + resizeEvent = new Event('resize', { + bubbles: true + }); + resizeEvent.newWidth = newSize.width; + resizeEvent.newHeight = newSize.height; + this.dispatchEvent(resizeEvent); + if (this.guestInstanceId) { + return guestViewInternal.setSize(this.guestInstanceId, { + normal: newSize + }); + } + }; + + WebViewImpl.prototype.createGuest = function() { + return guestViewInternal.createGuest(this.buildParams(), (function(_this) { + return function(event, guestInstanceId) { + return _this.attachWindow(guestInstanceId); + }; + })(this)); + }; + + WebViewImpl.prototype.dispatchEvent = function(webViewEvent) { + return this.webviewNode.dispatchEvent(webViewEvent); + }; + + // Adds an 'on' property on the webview, which can be used to set/unset + // an event handler. + WebViewImpl.prototype.setupEventProperty = function(eventName) { + var propertyName; + propertyName = 'on' + eventName.toLowerCase(); + return Object.defineProperty(this.webviewNode, propertyName, { + get: (function(_this) { + return function() { + return _this.on[propertyName]; + }; + })(this), + set: (function(_this) { + return function(value) { + if (_this.on[propertyName]) { + _this.webviewNode.removeEventListener(eventName, _this.on[propertyName]); + } + _this.on[propertyName] = value; + if (value) { + return _this.webviewNode.addEventListener(eventName, value); + } + }; + })(this), + enumerable: true + }); + }; + + // Updates state upon loadcommit. + WebViewImpl.prototype.onLoadCommit = function(webViewEvent) { + var newValue, oldValue; + oldValue = this.webviewNode.getAttribute(webViewConstants.ATTRIBUTE_SRC); + newValue = webViewEvent.url; + if (webViewEvent.isMainFrame && (oldValue !== newValue)) { + + // Touching the src attribute triggers a navigation. To avoid + // triggering a page reload on every guest-initiated navigation, + // we do not handle this mutation. + return this.attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue); + } + }; + + WebViewImpl.prototype.onAttach = function(storagePartitionId) { + return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId); + }; + + WebViewImpl.prototype.buildParams = function() { + var attribute, attributeName, css, elementRect, params, ref1; + params = { + instanceId: this.viewInstanceId, + userAgentOverride: this.userAgentOverride + }; + ref1 = this.attributes; + for (attributeName in ref1) { + if (!hasProp.call(ref1, attributeName)) continue; + attribute = ref1[attributeName]; + params[attributeName] = attribute.getValue(); + } + + // When the WebView is not participating in layout (display:none) + // then getBoundingClientRect() would report a width and height of 0. + // However, in the case where the WebView has a fixed size we can + // use that value to initially size the guest so as to avoid a relayout of + // the on display:block. + css = window.getComputedStyle(this.webviewNode, null); + elementRect = this.webviewNode.getBoundingClientRect(); + params.elementWidth = parseInt(elementRect.width) || parseInt(css.getPropertyValue('width')); + params.elementHeight = parseInt(elementRect.height) || parseInt(css.getPropertyValue('height')); + return params; + }; + + WebViewImpl.prototype.attachWindow = function(guestInstanceId) { + this.guestInstanceId = guestInstanceId; + this.webContents = remote.getGuestWebContents(this.guestInstanceId); + if (!this.internalInstanceId) { + return true; + } + return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()); + }; + + return WebViewImpl; + +})(); + +// Registers browser plugin custom element. +var registerBrowserPluginElement = function() { + var proto; + proto = Object.create(HTMLObjectElement.prototype); + proto.createdCallback = function() { + this.setAttribute('type', 'application/browser-plugin'); + this.setAttribute('id', 'browser-plugin-' + getNextId()); + + // The node fills in the container. + this.style.display = 'block'; + this.style.width = '100%'; + return this.style.height = '100%'; + }; + proto.attributeChangedCallback = function(name, oldValue, newValue) { + var internal; + internal = v8Util.getHiddenValue(this, 'internal'); + if (!internal) { + return; + } + return internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue); + }; + proto.attachedCallback = function() { + // Load the plugin immediately. + var unused; + return unused = this.nonExistentAttribute; + }; + WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement('browserplugin', { + "extends": 'object', + prototype: proto + }); + delete proto.createdCallback; + delete proto.attachedCallback; + delete proto.detachedCallback; + return delete proto.attributeChangedCallback; +}; + +// Registers custom element. +var registerWebViewElement = function() { + var createBlockHandler, createNonBlockHandler, i, j, len, len1, m, methods, nonblockMethods, proto; + proto = Object.create(HTMLObjectElement.prototype); + proto.createdCallback = function() { + return new WebViewImpl(this); + }; + proto.attributeChangedCallback = function(name, oldValue, newValue) { + var internal; + internal = v8Util.getHiddenValue(this, 'internal'); + if (!internal) { + return; + } + return internal.handleWebviewAttributeMutation(name, oldValue, newValue); + }; + proto.detachedCallback = function() { + var internal; + internal = v8Util.getHiddenValue(this, 'internal'); + if (!internal) { + return; + } + guestViewInternal.deregisterEvents(internal.viewInstanceId); + internal.elementAttached = false; + return internal.reset(); + }; + proto.attachedCallback = function() { + var internal; + internal = v8Util.getHiddenValue(this, 'internal'); + if (!internal) { + return; + } + if (!internal.elementAttached) { + guestViewInternal.registerEvents(internal, internal.viewInstanceId); + internal.elementAttached = true; + return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse(); + } + }; + + // Public-facing API methods. + methods = [ + 'getURL', + 'getTitle', + 'isLoading', + 'isWaitingForResponse', + 'stop', + 'reload', + 'reloadIgnoringCache', + 'canGoBack', + 'canGoForward', + 'canGoToOffset', + 'clearHistory', + 'goBack', + 'goForward', + 'goToIndex', + 'goToOffset', + 'isCrashed', + 'setUserAgent', + 'getUserAgent', + 'openDevTools', + 'closeDevTools', + 'isDevToolsOpened', + 'isDevToolsFocused', + 'inspectElement', + 'setAudioMuted', + 'isAudioMuted', + 'undo', + 'redo', + 'cut', + 'copy', + 'paste', + 'pasteAndMatchStyle', + 'delete', + 'selectAll', + 'unselect', + 'replace', + 'replaceMisspelling', + 'findInPage', + 'stopFindInPage', + 'getId', + 'downloadURL', + 'inspectServiceWorker', + 'print', + 'printToPDF' + ]; + nonblockMethods = [ + 'executeJavaScript', + 'insertCSS', + 'insertText', + 'send', + 'sendInputEvent', + 'setZoomFactor', + 'setZoomLevel', + 'setZoomLevelLimits', + ]; + + // Forward proto.foo* method calls to WebViewImpl.foo*. + createBlockHandler = function(m) { + return function() { + var args, internal, ref1; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + internal = v8Util.getHiddenValue(this, 'internal'); + return (ref1 = internal.webContents)[m].apply(ref1, args); + }; + }; + for (i = 0, len = methods.length; i < len; i++) { + m = methods[i]; + proto[m] = createBlockHandler(m); + } + createNonBlockHandler = function(m) { + return function() { + var args, internal; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + internal = v8Util.getHiddenValue(this, 'internal'); + return ipcRenderer.send.apply(ipcRenderer, ['ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m].concat(slice.call(args))); + }; + }; + for (j = 0, len1 = nonblockMethods.length; j < len1; j++) { + m = nonblockMethods[j]; + proto[m] = createNonBlockHandler(m); + } + + // Deprecated. + deprecate.rename(proto, 'getUrl', 'getURL'); + 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; + return delete proto.attributeChangedCallback; +}; + +var useCapture = true; + +var listener = function(event) { + if (document.readyState === 'loading') { + return; + } + registerBrowserPluginElement(); + registerWebViewElement(); + return window.removeEventListener(event.type, listener, useCapture); +}; + +window.addEventListener('readystatechange', listener, true); + +module.exports = WebViewImpl; diff --git a/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc b/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc index c2992abcc11d..98932a494bf8 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc +++ b/chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc @@ -8,6 +8,7 @@ #include "chrome/browser/renderer_host/pepper/pepper_broker_message_filter.h" #include "chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h" #include "chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h" +#include "chrome/browser/renderer_host/pepper/pepper_flash_drm_host.h" #include "chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.h" #include "content/public/browser/browser_ppapi_host.h" #include "ppapi/host/message_filter_host.h" @@ -65,6 +66,9 @@ scoped_ptr ChromeBrowserPepperHostFactory::CreateResourceHost( return scoped_ptr(new MessageFilterHost( host_->GetPpapiHost(), instance, resource, clipboard_filter)); } + case PpapiHostMsg_FlashDRM_Create::ID: + return scoped_ptr( + new chrome::PepperFlashDRMHost(host_, instance, resource)); } } diff --git a/chromium_src/chrome/browser/renderer_host/pepper/monitor_finder_mac.h b/chromium_src/chrome/browser/renderer_host/pepper/monitor_finder_mac.h new file mode 100644 index 000000000000..cc10ee85780c --- /dev/null +++ b/chromium_src/chrome/browser/renderer_host/pepper/monitor_finder_mac.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_RENDERER_HOST_PEPPER_MONITOR_FINDER_MAC_H_ +#define CHROME_BROWSER_RENDERER_HOST_PEPPER_MONITOR_FINDER_MAC_H_ + +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" + +namespace chrome { + +// MonitorFinder maps a RenderFrameHost to the display ID on which the widget +// is painting. This class operates on the IO thread while the RenderFrameHost +// is on the UI thread, so the value returned by GetMonitor() may be 0 until +// the information can be retrieved asynchronously. +class MonitorFinder : public base::RefCountedThreadSafe { + public: + MonitorFinder(int process_id, int render_frame_id); + + // Gets the native display ID for the tuple. + int64_t GetMonitor(); + + // Checks if the given |monitor_id| represents a built-in display. + static bool IsMonitorBuiltIn(int64_t monitor_id); + + private: + friend class base::RefCountedThreadSafe; + ~MonitorFinder(); + + // Method run on the UI thread to get the display information. + void FetchMonitorFromWidget(); + + const int process_id_; + const int render_frame_id_; + + base::Lock mutex_; // Protects the two members below. + // Whether one request to FetchMonitorFromWidget() has been made already. + bool request_sent_; + // The native display ID for the RenderFrameHost. + CGDirectDisplayID display_id_; + + DISALLOW_COPY_AND_ASSIGN(MonitorFinder); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_RENDERER_HOST_PEPPER_MONITOR_FINDER_H_ diff --git a/chromium_src/chrome/browser/renderer_host/pepper/monitor_finder_mac.mm b/chromium_src/chrome/browser/renderer_host/pepper/monitor_finder_mac.mm new file mode 100644 index 000000000000..31f6cfd41060 --- /dev/null +++ b/chromium_src/chrome/browser/renderer_host/pepper/monitor_finder_mac.mm @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/renderer_host/pepper/monitor_finder_mac.h" + +#import + +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" + +namespace chrome { + +MonitorFinder::MonitorFinder(int process_id, int render_frame_id) + : process_id_(process_id), + render_frame_id_(render_frame_id), + request_sent_(false), + display_id_(kCGNullDirectDisplay) {} + +MonitorFinder::~MonitorFinder() {} + +int64_t MonitorFinder::GetMonitor() { + { + // The plugin may call this method several times, so avoid spamming the UI + // thread with requests by only allowing one outstanding request at a time. + base::AutoLock lock(mutex_); + if (request_sent_) + return display_id_; + request_sent_ = true; + } + + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&MonitorFinder::FetchMonitorFromWidget, this)); + return display_id_; +} + +// static +bool MonitorFinder::IsMonitorBuiltIn(int64_t display_id) { + return CGDisplayIsBuiltin(display_id); +} + +void MonitorFinder::FetchMonitorFromWidget() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + content::RenderFrameHost* rfh = + content::RenderFrameHost::FromID(process_id_, render_frame_id_); + if (!rfh) + return; + + gfx::NativeView native_view = rfh->GetNativeView(); + NSWindow* window = [native_view window]; + NSScreen* screen = [window screen]; + CGDirectDisplayID display_id = + [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue]; + + base::AutoLock lock(mutex_); + request_sent_ = false; + display_id_ = display_id; +} + +} // namespace chrome diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.cc b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.cc new file mode 100644 index 000000000000..f94216461769 --- /dev/null +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.cc @@ -0,0 +1,217 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/renderer_host/pepper/pepper_flash_drm_host.h" + +#include + +#if defined(OS_WIN) +#include +#endif + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "content/public/browser/browser_ppapi_host.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/common/pepper_plugin_info.h" +#include "net/base/net_util.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/host/dispatch_host_message.h" +#include "ppapi/host/host_message_context.h" +#include "ppapi/host/ppapi_host.h" +#include "ppapi/proxy/ppapi_messages.h" + +#if defined(USE_AURA) +#include "ui/aura/window.h" +#include "ui/aura/window_tree_host.h" +#endif + +#if defined(OS_MACOSX) +#include "chrome/browser/renderer_host/pepper/monitor_finder_mac.h" +#endif + +using content::BrowserPpapiHost; + +namespace chrome { + +namespace { + +const char kVoucherFilename[] = "plugin.vch"; + +#if defined(OS_WIN) +bool GetSystemVolumeSerialNumber(std::string* number) { + // Find the system root path (e.g: C:\). + wchar_t system_path[MAX_PATH + 1]; + if (!GetSystemDirectoryW(system_path, MAX_PATH)) + return false; + + wchar_t* first_slash = wcspbrk(system_path, L"\\/"); + if (first_slash != NULL) + *(first_slash + 1) = 0; + + DWORD number_local = 0; + if (!GetVolumeInformationW(system_path, NULL, 0, &number_local, NULL, NULL, + NULL, 0)) + return false; + + *number = base::IntToString(std::abs(static_cast(number_local))); + return true; +} +#endif + +} + +#if defined(OS_WIN) +// Helper class to get the UI thread which monitor is showing the +// window associated with the instance's render view. Since we get +// called by the IO thread and we cannot block, the first answer is +// of GetMonitor() may be NULL, but eventually it will contain the +// right monitor. +class MonitorFinder : public base::RefCountedThreadSafe { + public: + MonitorFinder(int process_id, int render_frame_id) + : process_id_(process_id), + render_frame_id_(render_frame_id), + monitor_(NULL), + request_sent_(0) {} + + int64_t GetMonitor() { + // We use |request_sent_| as an atomic boolean so that we + // never have more than one task posted at a given time. We + // do this because we don't know how often our client is going + // to call and we can't cache the |monitor_| value. + if (InterlockedCompareExchange(&request_sent_, 1, 0) == 0) { + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&MonitorFinder::FetchMonitorFromWidget, this)); + } + return reinterpret_cast(monitor_); + } + + private: + friend class base::RefCountedThreadSafe; + ~MonitorFinder() {} + + void FetchMonitorFromWidget() { + InterlockedExchange(&request_sent_, 0); + content::RenderFrameHost* rfh = + content::RenderFrameHost::FromID(process_id_, render_frame_id_); + if (!rfh) + return; + gfx::NativeView native_view = rfh->GetNativeView(); +#if defined(USE_AURA) + aura::WindowTreeHost* host = native_view->GetHost(); + if (!host) + return; + HWND window = host->GetAcceleratedWidget(); +#else + HWND window = native_view; +#endif + HMONITOR monitor = ::MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + InterlockedExchangePointer(reinterpret_cast(&monitor_), + monitor); + } + + const int process_id_; + const int render_frame_id_; + volatile HMONITOR monitor_; + volatile long request_sent_; +}; +#elif !defined(OS_MACOSX) +// TODO(cpu): Support Linux someday. +class MonitorFinder : public base::RefCountedThreadSafe { + public: + MonitorFinder(int, int) {} + int64_t GetMonitor() { return 0; } + + private: + friend class base::RefCountedThreadSafe; + ~MonitorFinder() {} +}; +#endif + +PepperFlashDRMHost::PepperFlashDRMHost(BrowserPpapiHost* host, + PP_Instance instance, + PP_Resource resource) + : ppapi::host::ResourceHost(host->GetPpapiHost(), instance, resource), + weak_factory_(this) { + // Grant permissions to read the flash voucher file. + int render_process_id; + int render_frame_id; + bool success = host->GetRenderFrameIDsForInstance( + instance, &render_process_id, &render_frame_id); + base::FilePath plugin_dir = host->GetPluginPath().DirName(); + DCHECK(!plugin_dir.empty() && success); + base::FilePath voucher_file = plugin_dir.AppendASCII(kVoucherFilename); + content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( + render_process_id, voucher_file); + + monitor_finder_ = new MonitorFinder(render_process_id, render_frame_id); + monitor_finder_->GetMonitor(); +} + +PepperFlashDRMHost::~PepperFlashDRMHost() {} + +int32_t PepperFlashDRMHost::OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) { + PPAPI_BEGIN_MESSAGE_MAP(PepperFlashDRMHost, msg) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FlashDRM_GetDeviceID, + OnHostMsgGetDeviceID) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FlashDRM_GetHmonitor, + OnHostMsgGetHmonitor) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FlashDRM_MonitorIsExternal, + OnHostMsgMonitorIsExternal) + PPAPI_END_MESSAGE_MAP() + return PP_ERROR_FAILED; +} + +int32_t PepperFlashDRMHost::OnHostMsgGetDeviceID( + ppapi::host::HostMessageContext* context) { + static std::string id; +#if defined(OS_WIN) + if (id.empty() && !GetSystemVolumeSerialNumber(&id)) + id = net::GetHostName(); +#else + if (id.empty()) + id = net::GetHostName(); +#endif + context->reply_msg = PpapiPluginMsg_FlashDRM_GetDeviceIDReply(id); + return PP_OK; +} + +int32_t PepperFlashDRMHost::OnHostMsgGetHmonitor( + ppapi::host::HostMessageContext* context) { + int64_t monitor_id = monitor_finder_->GetMonitor(); + if (monitor_id) { + context->reply_msg = PpapiPluginMsg_FlashDRM_GetHmonitorReply(monitor_id); + return PP_OK; + } + return PP_ERROR_FAILED; +} + +int32_t PepperFlashDRMHost::OnHostMsgMonitorIsExternal( + ppapi::host::HostMessageContext* context) { + int64_t monitor_id = monitor_finder_->GetMonitor(); + if (!monitor_id) + return PP_ERROR_FAILED; + + PP_Bool is_external = PP_FALSE; +#if defined(OS_MACOSX) + if (!MonitorFinder::IsMonitorBuiltIn(monitor_id)) + is_external = PP_TRUE; +#endif + context->reply_msg = + PpapiPluginMsg_FlashDRM_MonitorIsExternalReply(is_external); + return PP_OK; +} + +} // namespace chrome diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.h b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.h new file mode 100644 index 000000000000..91bba9631e68 --- /dev/null +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.h @@ -0,0 +1,55 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_DRM_HOST_H_ +#define CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_DRM_HOST_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "ppapi/host/host_message_context.h" +#include "ppapi/host/resource_host.h" + +namespace content { +class BrowserPpapiHost; +} + +namespace IPC { +class Message; +} + +namespace chrome { +class MonitorFinder; + +class PepperFlashDRMHost : public ppapi::host::ResourceHost { + public: + PepperFlashDRMHost(content::BrowserPpapiHost* host, + PP_Instance instance, + PP_Resource resource); + ~PepperFlashDRMHost() override; + + // ResourceHost override. + int32_t OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) override; + + private: + // IPC message handler. + int32_t OnHostMsgGetDeviceID(ppapi::host::HostMessageContext* context); + int32_t OnHostMsgGetHmonitor(ppapi::host::HostMessageContext* context); + int32_t OnHostMsgMonitorIsExternal(ppapi::host::HostMessageContext* context); + + scoped_refptr monitor_finder_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PepperFlashDRMHost); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_DRM_HOST_H_ diff --git a/chromium_src/chrome/common/chrome_paths.h b/chromium_src/chrome/common/chrome_paths.h index 3a76e3295f46..581fdc06f7c1 100644 --- a/chromium_src/chrome/common/chrome_paths.h +++ b/chromium_src/chrome/common/chrome_paths.h @@ -17,7 +17,7 @@ class FilePath; namespace chrome { enum { - PATH_START = 2000, + PATH_START = 1000, DIR_APP = PATH_START, // Directory where dlls and data reside. DIR_LOGS, // Directory where logs should be written. diff --git a/docs-translations/jp/README.md b/docs-translations/jp/README.md index 32b14b0fdd2b..accde9739769 100644 --- a/docs-translations/jp/README.md +++ b/docs-translations/jp/README.md @@ -1,3 +1,33 @@ +使用している Electron のバージョンに応じたドキュメントを使うように確認してください。 +ドキュメントのバージョン番号はページの URL の一部になっています。 +そうでない場合、おそらくご使用の Electron のバージョンと互換性のない API 変更を含んだ development ブランチのドキュメントを使っているものと思われます。 +その場合、atom.io の [available versions](http://electron.atom.io/docs/) リストにある別のバージョンのドキュメントに切り替えることができます。また GitHub で閲覧している場合、"Switch branches/tags" ドロップダウンを開いて、バージョンに対応したタグを選ぶこともできます。 + +## FAQ + +頻繁に聞かれる質問がありますので、issueを作成する前にこれをチェックしてください。 + +* [Electron FAQ](faq/electron-faq.md) + +## ガイド + +* [サポートするプラットフォーム](tutorial/supported-platforms.md) +* [アプリケーションの配布](tutorial/application-distribution.md) +* [Mac App Store 提出ガイド](tutorial/mac-app-store-submission-guide.md) +* [アプリケーションのパッケージ化](tutorial/application-packaging.md) +* [ネイティブのNodeモジュールを使用する](tutorial/using-native-node-modules.md) +* [メインプロセスのデバッグ](tutorial/debugging-main-process.md) +* [Selenium と WebDriverを使用する](tutorial/using-selenium-and-webdriver.md) +* [DevTools エクステンション](tutorial/devtools-extension.md) +* [Pepper Flashプラグインを使用する](tutorial/using-pepper-flash-plugin.md) +* [Widevine CDMプラグインを使用する](tutorial/using-widevine-cdm-plugin.md) + # チュートリアル * [クイックスタート](tutorial/quick-start.md) +* [デスクトップ環境の統合](tutorial/desktop-environment-integration.md) +* [オンライン/オフライン イベントの検知](tutorial/online-offline-events.md) + +## API リファレンス + +* [概要](api/synopsis.md) diff --git a/docs-translations/jp/api/accelerator.md b/docs-translations/jp/api/accelerator.md new file mode 100644 index 000000000000..83a66447ed5f --- /dev/null +++ b/docs-translations/jp/api/accelerator.md @@ -0,0 +1,43 @@ +# Accelerator + +acceleratorは、キーボードショートカットを示す文字列です。複数の修飾語句とキーコードを `+` 文字で結合します。 + +例: + +* `Command+A` +* `Ctrl+Shift+Z` + +## プラットフォームの留意点 + + +OS Xでは`Command` キー、LinuxとWindowsでは`Control` キーを意味する`CommandOrControl`はいくつかのacceleratorを定義しますが、LinuxとWindowsでは、`Command` キーは何の効果もありません。 + + `Super` キーは、WindowsとLinuxでは `Windows` キーに、OS Xでは、`Cmd` キーに関連付けられます。 + +## 提供されている修飾語句 + +* `Command` (または、短く `Cmd`) +* `Control` (または、短く `Ctrl`) +* `CommandOrControl` (または、短く `CmdOrCtrl`) +* `Alt` +* `Shift` +* `Super` + +## 提供されているキーコード + +* `0` to `9` +* `A` to `Z` +* `F1` to `F24` +* `~`, `!`, `@`, `#`, `$`などの記号 +* `Plus` +* `Space` +* `Backspace` +* `Delete` +* `Insert` +* `Return` (またはエイリアスで `Enter`) +* `Up`と `Down`,`Left`、 `Right` +* `Home` と `End` +* `PageUp` と `PageDown` +* `Escape` (または、短く `Esc`) +* `VolumeUp`と `VolumeDown` 、 `VolumeMute` +* `MediaNextTrack`と `MediaPreviousTrack`、 `MediaStop` 、 `MediaPlayPause` diff --git a/docs-translations/jp/api/app.md b/docs-translations/jp/api/app.md new file mode 100644 index 000000000000..6e18f91d73c4 --- /dev/null +++ b/docs-translations/jp/api/app.md @@ -0,0 +1,410 @@ +# app + + `app` モジュールは、アプリケーションのライフサイクルコントロールを担います。 + +次の例は、最後のウィンドウが閉じたときにアプリケーションを終了させる方法を示しています。 + +```javascript +const app = require('electron').app; +app.on('window-all-closed', function() { + app.quit(); +}); +``` + +## イベント + +`app` オブジェクトは次のイベントを出力します。 + +### イベント: 'will-finish-launching' + +アプリケーションの基礎起動が終わったときに出力されます。Windows と Linuxでは、 `will-finish-launching` イベントと`ready`イベントは同じです。OS Xでは、`NSApplication`の `applicationWillFinishLaunching` 通知をに相当します。通常、`open-file`と`open-url` 用のリスナーの設定、クラッシュレポートの開始、自動アップデートをします。 + +ほとんどの場合、 `ready` イベントハンドラーですべてをするべきです。 + +### イベント: 'ready' + +Electronの初期化が終わった時に出力します。 + +### イベント: 'window-all-closed' + +全てのウィンドウを閉じたときに出力します。 + +このイベントは、アプリケーションが終了する予定ではないときのみ出力します。ユーザーが `Cmd + Q`を押したり、開発者が`app.quit()`をコールすると、Electronは最初にすべてのウィンドウをクローズしようとし、`will-quit`イベントを出力します。この場合、`window-all-closed`イベントは出力されません。 + +### イベント: 'before-quit' + +戻り値: + +* `event` Event + +アプリケーションがウィンドウをクローズし始める前に出力します。`event.preventDefault()`をコールすると、アプリケーションを終了させる既定の挙動を止めることができます。 + +### イベント: 'will-quit' + +戻り値: + +* `event` Event + +全てのウィンドウが閉じて、アプリケーションを終了するときに出力します。`event.preventDefault()`をコールすると、アプリケーションを終了させる既定の挙動を止めることができます。 + +詳細は、`will-quit`イベント と `window-all-closed` イベントの違いは、`window-all-closed` イベントの説明を見てください。 + +### イベント: 'quit' + +戻り値: + +* `event` Event +* `exitCode` Integer + +アプリケーションが終了したときに出力されます。 + +### イベント: 'open-file' _OS X_ + +戻り値: + +* `event` Event +* `path` String + +アプリケーションでファイルを開こうとしたときに出力します。アプリケーションがすでに起動し、OSがファイルを開くアプリケーションを再使用したいとき、`open-file`イベントは出力します。ファイルがdockにドロップアウトされ、アプリケーションがまだ起動していないときにも`open-file` は出力します。このケースを処理するために、アプリケーションの起動のかなり早い段階で、`open-file` イベントをリッスンして確認します(まだ `ready` イベントが出力する前に)。 + +このイベントをハンドルしたいときには `event.preventDefault()` をコールすべきです。 + +Windowsでは、ファイルパスを取得するために、 `process.argv` をパースする必要があります。 + +### イベント: 'open-url' _OS X_ + +戻り値: + +* `event` Event +* `url` String + +アプリケーションでURLを開こうとしたときに出力されます。URLスキーマーは、アプリケーションが開くように登録しなければなりません。 + +このイベントをハンドルしたい場合は、`event.preventDefault()`をコールすべきです。 + +### イベント: 'activate' _OS X_ + +戻り値: + +* `event` Event +* `hasVisibleWindows` Boolean + +アプリケーションがアクティブになったときに出力されます。通常は、アプリケーションのドックアイコンをクリックしたときに発生します。 + +### イベント: 'browser-window-blur' + +戻り値: + +* `event` Event +* `window` BrowserWindow + +[browserWindow](browser-window.md) からフォーカスが外れたときに出力されます。 + +### イベント: 'browser-window-focus' + +戻り値: + +* `event` Event +* `window` BrowserWindow + +[browserWindow](browser-window.md) にフォーカスが当たったとき出力されます。 + +### イベント: 'browser-window-created' + +戻り値: + +* `event` Event +* `window` BrowserWindow + +新しい [browserWindow](browser-window.md) が作成されたときに出力されます。 + +### イベント: 'certificate-error' + +戻り値: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` URL +* `error` String - The error code +* `certificate` Object + * `data` Buffer - PEM encoded data + * `issuerName` String +* `callback` Function + + `url` の `certificate` 検証に失敗したときに発生します。証明書を信頼するために`event.preventDefault()` と `callback(true)`をコールして既定の動作を止める必要があります。 + +```javascript +session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { + if (url == "https://github.com") { + // Verification logic. + event.preventDefault(); + callback(true); + } else { + callback(false); + } +}); +``` + +### イベント: 'select-client-certificate' + +戻り値: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - PEM encoded data + * `issuerName` String - Issuer's Common Name +* `callback` Function + +クライアント証明書が要求されたときに出力されます。 + +`url` は、クライアント証明書を要求するナビゲーションエントリーに対応し、`callback` リストからエントリをフィルターしてコールするのに必要です。`event.preventDefault()` を使用して、アプリケーションの証明書ストアから最初の証明書を使用するのを止めることができます。 + +```javascript +app.on('select-client-certificate', function(event, webContents, url, list, callback) { + event.preventDefault(); + callback(list[0]); +}) +``` + +### イベント: 'login' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +`webContents` がベーシック認証をしようとしたときに出力されます。 + +既定の動作ではすべての認証をキャンセルしたり、`event.preventDefault()` と `callback(username, password)` とを証明書でコールし既定の動作をとめてオーバーライドします。 + +```javascript +app.on('login', function(event, webContents, request, authInfo, callback) { + event.preventDefault(); + callback('username', 'secret'); +}) +``` + +### イベント: 'gpu-process-crashed' + +gpu プロセスがクラッシュしたときに出力されます。 + +## メソッド + +`app` オブジェクトは次のメソッドを持ちます。 + +**Note:** いくつかのメソッドは、特定のオペレーティングシステム向けに提供され、そのようにラベルで表示します。 + +### `app.quit()` + +全てのウィンドウを閉じようとします。`before-quit`イベントは、最初に出力されます。すべてのウィンドウを閉じることに成功したら、`will-quit`イベントが出力され、既定では、アプリケーションが終了します。 + +このメソッドは、全ての`beforeunload`と`unload`イベントハンドラは正確に発生することを保証されます。`beforeunload` イベントハンドラで、`false`を返すことでウィンドウの終了をキャンセルすることができます。 + +### `app.exit(exitCode)` + +* `exitCode` Integer + +`exitCode`で今すぐ終了します。 + +全てのウィンドウは、ユーザーに確認することなく、すぐに閉じ、`before-quit`と`will-quit` イベントは出力されません。 + +### `app.getAppPath()` + +減殺のアプリケーションディレクトリを戻します。 + +### `app.getPath(name)` + +* `name` String + +`name`に関連した特定のディレクトリやファイルへのパスを返します。失敗したら、`Error`をスローします。 + +`name`で次のパスをリクエストできます。 + +* `home` ユーザーのホームディレクトリ +* `appData` 既定で示すユーザーごとのアプリケーションディレクトリ + * `%APPDATA%` Windows上 + * `$XDG_CONFIG_HOME` or `~/.config` Linux上 + * `~/Library/Application Support` OS X上 +* `userData` アプリの設定ファイルを格納するディレクトリで、既定では`appData` ディレクトリ配下のアプリ名ディレクトリです +* `temp` 一時ディレクトリ +* `exe` 現在の実行ファイル +* `module` `libchromiumcontent` ライブラリ +* `desktop` 現在のユーザーのデスクトップディレクトリ +* `documents` ユーザーの "My Documents"用ディレクトリ +* `downloads` ユーザーのダウンロード用ディレクトリ +* `music` ユーザーのミュージック用ディレクトリ +* `pictures` ユーザーのピクチャー用ディレクトリ +* `videos` ユーザーのビデオ用ディレクトリ + +### `app.setPath(name, path)` + +* `name` String +* `path` String + +`name`に関連した特定のディレクトリやファイルへの`path` を上書きします。存在しないパスを指定した場合、このメソッドがディレクトリを作成します。失敗したら、`Error`をスローします。 + +`app.getPath`で、`name` で定義されたパスを上書きできます。 + +既定では、webページのクッキーとキャッシュは`userData`ディレクトリ配下に格納できます。ロケーションを変更したい場合、 `app` モジュールの `ready` イベントが出力される前に`userData`パスを上書きする必要があります。 + +### `app.getVersion()` + +ロードしたアプリケーションのバージョンを戻します。アプリケーションの `package.json`ファイルにversionが無ければ、現在のバンドルまたは実行ファイルのバージョンになります。 + +### `app.getName()` + +現在のアプリケーション名を戻し、`package.json` ファイルのnameです。 + +通常、 `package.json`の`name` フィールドは、短い小文字名で、npm module spec と一致します。通常、`productName`で、アプリケーションの大文字正式名を指定し、Electronでは`name`をそれで上書きます。 + +### `app.getLocale()` + +現在のアプリケーションのロケールを戻します。 + +### `app.addRecentDocument(path)` _OS X_ _Windows_ + +* `path` String + +最近のドキュメント一覧に`path`を追加します。 + +この一覧はOSが管理しています。Windowsではタスクバーからこの一覧を見れ、OS Xではdockメニューから見れます。 + +### `app.clearRecentDocuments()` _OS X_ _Windows_ + +最近のドキュメント一覧をクリアします。 + +### `app.setUserTasks(tasks)` _Windows_ + +* `tasks` Array - `Task` オブジェクトの配列 + +Windowsのジャンプリストの[Tasks][tasks]カテゴリに`tasks`を追加します。 + +`tasks` は`Task`オブジェクトの配列で、次のフォーマットになります。 + +`Task` Object: + +* `program` String - 実行するプログラムのパスで、通常はプログラムが開く`process.execPath`を指定します +* `arguments` String - `program` を実行するときのコマンドライン引数です +* `title` String - ジャンプリストに表示される文字列です +* `description` String - タスクの説明 +* `iconPath` String - ジャンプリストに表示するアイコンの絶対パスで、アイコンを含む任意のリソースファイルです。通常、プログラムのアイコンを表示する`process.execPath`を指定します。 +* `iconIndex` Integer - アイコンファイルのアイコンインデックスです。アイコンファイルに2つ以上のアイコンが含まれている場合、この値でアイコンを指定します。1つしかアイコンファイルに含まれていない場合は、この値は0です。 + +### `app.allowNTLMCredentialsForAllDomains(allow)` + +* `allow` Boolean + +HTTP NTLMまたはNegotiate認証用の照明を常に送信するかどうかを動的に設定できます。通常、Electronはローカルインターネットサイト(例えば、あなたと同じドメイン名のとき)に該当するURL用のNTLM/Kerberos証明書のみ送信します。しかし、この検知はコーポレートネットワークの設定が悪いときには、頻繁に失敗するので、この挙動を共通に行うことを選べば、全てのURLで有効にできます。 + +### `app.makeSingleInstance(callback)` + +* `callback` Function + +このメソッドは、アプリケーションをシングルインスタンスアプリケーションにします。アプリの実行を複数のインスタンスで実行することを許可せず、アプリケーション実行をシングルインスタンスのみにすることを保証し、ほかのインスタンスにはこのインスタンスの存在を知らせ終了さえます。 + +2つ目のインスタンスが起動したとき、`callback` は、`callback(argv, workingDirectory)` でコールします。`argv` は、2つ目のインスタンスのコマンドライン引数の配列で、`workingDirectory` は現在のワーキングディレクトリです。通常、アプリケーションはメインのウィンドウにフォーカスをあて最小化させないことで対応します。 + +The `callback` は、 `app`の`ready` イベントの出力後に実行することを保証します。 + +プロセスがアプリケーションのプライマリインスタンスでアプリがロードし続けるなら、このメソッドは `false`を戻します。プロセスがほかのインスタンスにパラメーターを送信し、`true`を戻すと、直ちに終了します。 + +OS Xは、ユーザーがFinderで2つ目のアプリインスタンスを開いたり、`open-file` 、 `open-url`イベントが出力しようとすると、システムが自動的にシングルインスタンスを強制します。しかし、コマンドラインでアプリを開始するとシステムのシングルインスタンスメカニズムは無視されるので、シングルインスタンスを強制するためには、このメソッドを使う必要があります。 + +2つ目のインスタンスを起動するとき、メインのインスタンスのウィンドウをアクティブにする例 + +```js +var myWindow = null; + +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // Someone tried to run a second instance, we should focus our window + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); + } + return true; +}); + +if (shouldQuit) { + app.quit(); + return; +} + +// Create myWindow, load the rest of the app, etc... +app.on('ready', function() { +}); +``` + +### `app.setAppUserModelId(id)` _Windows_ + +* `id` String + +[Application User Model ID][app-user-model-id] を `id`に変更します。 + +### `app.commandLine.appendSwitch(switch[, value])` + +Chromiumのコマンドラインにスイッチ( `value`をオプションにし)を追加します。 + +**Note:** これは、`process.argv`に影響せず、開発者が、Chromiumのローレベルな挙動をコントロールするのに使用します。 + +### `app.commandLine.appendArgument(value)` + +Chromiumのコマンドダインに引数を追加します。引数は正しく引用符で囲まれます。 + +**Note:** `process.argv`に影響しません。 + +### `app.dock.bounce([type])` _OS X_ + +* `type` String (optional) - `critical` または `informational`を指定できます。既定では、 `informational`です。 + +`critical`を渡すと、アプリケーションがアクティブ、もしくはリクエストがキャンセルされるまでは、dockアイコンは、バウンスします。 + +`informational` を渡すと、1秒dockアイコンはバウンスします。しかし、アプリケーションがアクティブ、もしくはリクエストがキャンセルされるまでは、リクエストは残ります。 + +リクエストを示すIDを戻します。 + +### `app.dock.cancelBounce(id)` _OS X_ + +* `id` Integer + +`id`のバウンスをキャンセルします。 + +### `app.dock.setBadge(text)` _OS X_ + +* `text` String + +dockのバッジエリアで表示する文字列を設定します。 + +### `app.dock.getBadge()` _OS X_ + +dockのバッジ文字列を戻します。 + +### `app.dock.hide()` _OS X_ + +dock アイコンを隠します。 + +### `app.dock.show()` _OS X_ + +dock アイコンを表示します。 + +### `app.dock.setMenu(menu)` _OS X_ + +* `menu` Menu + +アプリケーションの[dock menu][dock-menu]を設定します。 + +[dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 +[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/jp/api/auto-updater.md b/docs-translations/jp/api/auto-updater.md new file mode 100644 index 000000000000..818a75e019a0 --- /dev/null +++ b/docs-translations/jp/api/auto-updater.md @@ -0,0 +1,85 @@ +# autoUpdater + +このモジュールは、`Squirrel`オートアップデートフレームワークのインターフェイスを提供します。 + +## プラットフォーム留意点 + +`autoUpdater`は、異なるプラットフォーム用に統一したAPIを提供していますが、それぞれのプラットフォーム上で、まだ多少の差があります。 + +### OS X + +OS Xでは、 `autoUpdater` モジュールは、[Squirrel.Mac][squirrel-mac]上に構築されていて、動作させるのに特別な設定が不要であることを意味します。サーバーサイドの要件は、[Server Support][server-support]を読んでください。 + +### Windows + +Windowsでは、auto-updaterを使う前に、ユーザーのPCにアプリをインストールする必要があるので、Windows インストーラーを生成するために[grunt-electron-installer][installer]モジュールを使用することをお勧めします。 + +Squirrelで生成されたインストーラーは、`com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`のフォーマット(例えば、`com.squirrel.slack.Slack` と `com.squirrel.code.Code`)で[Application User Model ID][app-user-model-id]とショートカットアイコンを作成します。`app.setAppUserModelId`APIで同じIDを使う必要があります。同じIDでないと、Windowsはタスクバーに適切にピン止めすることができません。 + +サーバーサイドのセットアップは、OS Xと異なります。詳細は、[Squirrel.Windows][squirrel-windows] を参照してください。 + +### Linux + +Linuxでは、auot-updater用のサポートがビルトインされていないので、アプリをアップデートするためにディストリビューションのパッケージマネジャーを使用することをお勧めします。 + +## イベント + +`autoUpdater` オブジェクトは次のイベントを出力します。 + +### イベント: 'error' + +戻り値: + +* `error` Error + +アップデート中にエラーがあった場合に出力されます。 + +### イベント: 'checking-for-update' + +アップデートを開始したかチェックしたときに出力されます。 + +### イベント: 'update-available' + +アップデートが提供されているときに出力されます。アップデートは自動的にダウンロードされます。 + +### イベント: 'update-not-available' + +アップデートが提供されていないときに出力されます。 + +### イベント: 'update-downloaded' + +戻り値: + +* `event` Event +* `releaseNotes` String +* `releaseName` String +* `releaseDate` Date +* `updateURL` String + +アップデートをダウンロードしたときに出力されます。 + +Windowsでは、`releaseName` のみ提供されます。 + +## メソッド + +`autoUpdater` オブジェクトは次のメソッドを持っています。 + +### `autoUpdater.setFeedURL(url)` + +* `url` String + + `url`を設定し、自動アップデートを初期化します。 `url`は一度設定すると変更できません。 + +### `autoUpdater.checkForUpdates()` + +アップデートがあるかどうかサーバーに問い合わせます。APIを使う前に、`setFeedURL`をコールしなければなりません。 + +### `autoUpdater.quitAndInstall()` + +ダウンロード後、アプリを再起動して、アップデートをインストールします。`update-downloaded`が出力された後のみ、コールすべきです。 + +[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac +[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support +[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows +[installer]: https://github.com/atom/grunt-electron-installer +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/jp/api/synopsis.md b/docs-translations/jp/api/synopsis.md new file mode 100644 index 000000000000..a2f701807ea0 --- /dev/null +++ b/docs-translations/jp/api/synopsis.md @@ -0,0 +1,69 @@ +# 概要 + +Electron では全ての [Node.js のビルトインモジュール](http://nodejs.org/api/) 利用可能です。また、サードパーティの Node モジュール ([ネイティブモジュール](../tutorial/using-native-node-modules.md)も含む) も完全にサポートされています。 + +Electron はネイティブのデスクトップアプリケーション開発のための幾つかの追加のビルトインモジュールも提供しています。メインプロセスでだけ使えるモジュールもあれば、レンダラプロセス(ウェブページ)でだけ使えるモジュール、あるいはメインプロセス、レンダラプロセスどちらでも使えるモジュールもあります。 + +基本的なルールは:[GUI][gui]、または低レベルのシステムに関連するモジュールはメインモジュールでだけ利用できるべきです。これらのモジュールを使用できるようにするためには [メインプロセス対レンダラプロセス][main-process] スクリプトの概念を理解する必要があります。 + +メインプロセススクリプトは普通の Node.js スクリプトのようなものです: + +```javascript +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + +var window = null; + +app.on('ready', function() { + window = new BrowserWindow({width: 800, height: 600}); + window.loadURL('https://github.com'); +}); +``` + +レンダラプロセスは Node モジュールを使うための追加機能を除いて、通常のウェブページとなんら違いはありません: + +```html + + + + + + +``` + +アプリケーションを実行については、[アプリを実行する](../tutorial/quick-start.md#アプリを実行する)を参照してください。 + +## 分割代入 + +CoffeeScript か Babel を使っているなら、[分割代入][desctructuring-assignment]でビルトインモジュールの使用をより簡単にできます: + +```javascript +const {app, BrowserWindow} = require('electron') +``` + +しかし、素の JavaScript を使っている場合、Chrome が ES6 を完全サポートするまで待たなければいけません。 + +## Disable old styles of using built-in modules + +v0.35.0 以前は全てのビルトインモジュールは `require('module-name')` の形式で使われなければいけません。この形式は[多くの欠点][issue-387]がありますが、古いアプリケーションとの互換性のためにまだサポートしています。 + +古い形式を完全に無効にするために、環境変数 `ELECTRON_HIDE_INTERNAL_MODULES` を設定できます: + +```javascript +process.env.ELECTRON_HIDE_INTERNAL_MODULES = 'true' +``` + +もしくは `hideInternalModules` API を呼んでください: + +```javascript +require('electron').hideInternalModules() +``` + +[gui]: https://en.wikipedia.org/wiki/Graphical_user_interface +[main-process]: ../tutorial/quick-start.md#メインプロセス +[desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +[issue-387]: https://github.com/atom/electron/issues/387 diff --git a/docs-translations/jp/faq/electron-faq.md b/docs-translations/jp/faq/electron-faq.md new file mode 100644 index 000000000000..b73831974772 --- /dev/null +++ b/docs-translations/jp/faq/electron-faq.md @@ -0,0 +1,73 @@ +# Electron FAQ + +## Electronは、いつ最新のChromeにアップグレードされますか? + +ElectronのChromeバージョンは、通常、新しいChromeのstabeleバージョンがリリースされた後、1~2週間以内に上げられます。 + +また、Chromeのstableチャンネルのみを使用し、もし、重要な修正がbetaまたはdevチャンネルにある場合、それをバックポートします。 + +## Electronは、いつ最新のNode.jsにアップグレードされますか? + +Node.js の新しいバージョンがリリースされたとき、私たちは Electron の Node.js を更新するのを通常約1か月待ちます。そのようにして、とても頻繁に発生している、新しい Node.js バージョンによって取り込まれたバグによる影響を避けることができます。 + +通常、Node.js の新しい機能は V8 のアップグレードによってもたらされますが、Electron は Chrome ブラウザーに搭載されている V8 を使用しているので、新しい Node.js に入ったばかりのピカピカに新しい JavaScript 機能は Electron ではたいてい既に導入されています。 + +## 何分か経つと、アプリの Window/tray が消えてしまいます + +これは、Window/trayを格納するのに使用している変数がガベージコレクトされたときに発生します。 + +この問題に遭遇した時には、次のドキュメントを読むことをお勧めします。 + +* [Memory Management][memory-management] +* [Variable Scope][variable-scope] + +もし簡単に修正したい場合は、コードを以下のように修正して変数をグローバルにすると良いでしょう: + +変更前: + +```javascript +app.on('ready', function() { + var tray = new Tray('/path/to/icon.png'); +}) +``` + +変更後: + +```javascript +var tray = null; +app.on('ready', function() { + tray = new Tray('/path/to/icon.png'); +}) +``` + +## ElectronでjQuery/RequireJS/Meteor/AngularJSを使用できません + +Electronに組み込まれているNode.jsの影響で, `module`, `exports`, `require`のようなシンボルがDOMに追加されています。このため、いくつかのライブラリでは同名のシンボルを追加しようとして問題が発生することがあります。 + +これを解決するために、Electronに組み込まれているnodeを無効にすることができます。 + +```javascript +// In the main process. +var mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: false + } +}); +``` + +しかし、Node.jsとElectron APIを使用した機能を維持したい場合は、ほかのライブラリを読み込む前に、ページのシンボルをリネームする必要があります。 + +```html + + + + +``` + +[memory-management]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management +[variable-scope]: https://msdn.microsoft.com/library/bzt2dkta(v=vs.94).aspx diff --git a/docs-translations/jp/tutorial/application-distribution.md b/docs-translations/jp/tutorial/application-distribution.md new file mode 100644 index 000000000000..96dce25134aa --- /dev/null +++ b/docs-translations/jp/tutorial/application-distribution.md @@ -0,0 +1,108 @@ +# アプリケーションの配布 + +Electronでアプリケーションを配布するために、アプリケーションを`app` という名前のディレクトリにし、Electronのリソースディレクトリ(OS X では +`Electron.app/Contents/Resources/` 、Linux と Windows では `resources/`)配下に置くべきです, + +OS X: + +```text +electron/Electron.app/Contents/Resources/app/ +├── package.json +├── main.js +└── index.html +``` + +Windows と Linux: + +```text +electron/resources/app +├── package.json +├── main.js +└── index.html +``` + +`Electron.app` (または Linux上では、`electron`、Windows上では、 `electron.exe`)を実行すると、Electronはアプリケーションを開始します。`electron` ディレクトリを最終的なユーザーに提供するために配布します。 + +## ファイルにアプリケーションをパッケージングする + +すべてのソースコードをコピーすることでアプリケーションを提供する方法とは別に、アプリケーションのソースコードをユーザーに見えるのを避けるために、[asar](https://github.com/atom/asar) にアーカイブしてアプリケーションをパッケージ化することができます。 + +`app` フォルダの代わりに `asar` アーカイブを使用するためには、アーカイブファイルを `app.asar` という名前に変更し、Electron のリソースディレクトリに以下のように配置する必要があります。すると、Electron はアーカイブを読み込もうとし、それを開始します。 + +OS X: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +Windows と Linux: + +```text +electron/resources/ +└── app.asar +``` + +[Application packaging](application-packaging.md)で、詳細を確認できます。 + +## ダウンロードするバイナリのブランド名の変更 + +Electronにバンドルした後、ユーザーに配布する前に、 Electron名を変更したいでしょう。 + +### Windows + +`electron.exe`を任意の名前に変更でき、[rcedit](https://github.com/atom/rcedit) または +[ResEdit](http://www.resedit.net)のようなツールでアイコンやその他の情報を編集できます。 + +### OS X + +`Electron.app` を任意の名前に変更でき、次のファイルの `CFBundleDisplayName`と `CFBundleIdentifier`、 `CFBundleName`のフィールドの名前を変更する必要があります。 + +* `Electron.app/Contents/Info.plist` +* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` + +ヘルパーアプリケーションの名前を変更して、アクティビティモニタに `Electron Helper` と表示されないようにすることもできますが、その際はヘルパーアプリケーションの実行可能ファイルの名前を変更したことを確認してください。 + +名前変更後のアプリケーションの構成は以下の通りです: + +``` +MyApp.app/Contents +├── Info.plist +├── MacOS/ +│   └── MyApp +└── Frameworks/ + ├── MyApp Helper EH.app + | ├── Info.plist + | └── MacOS/ + |    └── MyApp Helper EH + ├── MyApp Helper NP.app + | ├── Info.plist + | └── MacOS/ + |    └── MyApp Helper NP + └── MyApp Helper.app + ├── Info.plist + └── MacOS/ +    └── MyApp Helper +``` + +### Linux + +`electron` を任意の名前に変更できます。 + +## ソースからElectronをリビルドしてブランド名を変更する + +プロダクト名を変更し、ソースからビルドすることで、Electronのブランド名を変更できます。これをするために、`atom.gyp` ファイルを変更し、クリーンリビルドする必要があります。 + +### grunt-build-atom-shell + +Electron のコードを手動チェックアウトして再構築するのには複雑な手順が必要ですが、これを自動的に扱うための Grunt タスクが作られています: +[grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). + +このタスクは自動的に `.gyp` ファイルの編集、ソースからのビルド、そして新しい実行可能ファイル名に一致するようにネイティブの Node モジュールを再構築します。 + +## パッケージングツール + +手動でアプリケーションをパッケージ化する以外の方法として、サードパーティのパッケジングツールを選ぶこともできます。 + +* [electron-packager](https://github.com/maxogden/electron-packager) +* [electron-builder](https://github.com/loopline-systems/electron-builder) diff --git a/docs-translations/jp/tutorial/application-packaging.md b/docs-translations/jp/tutorial/application-packaging.md new file mode 100644 index 000000000000..e5196a6f6262 --- /dev/null +++ b/docs-translations/jp/tutorial/application-packaging.md @@ -0,0 +1,149 @@ +# アプリケーションのパッケージ化 + +Windows上の長いパス名周りの[問題](https://github.com/joyent/node/issues/6960) を軽減したり、`require`を若干スピードアップしたり、簡単な調査からソースコードを隠したりするために、ソースコードを少々変更して、アプリケーションを [asar][asar] アーカイブとしてパッケージ化することもできます。 + +## `asar` アーカイブの生成 + +[asar][asar] アーカイブは、1つのファイルに連結されたtarライクのシンプルなフォーマットです。Electron はファイル全体をアンパックしなくても任意のファイルを読み込めます。 + +アプリを `asar` アーカイブにパッケージ化する手順: + +### 1. asar ユーティリティのインストール + +```bash +$ npm install -g asar +``` + +### 2. `asar pack`でパッケージ化する + +```bash +$ asar pack your-app app.asar +``` + +## `asar` アーカイブを使用する + +Electronには、2組のAPIがあります。Node.js により提供される Node API、そして Chromium により提供される Web API です。どちらの API も `asar` アーカイブからのファイル読み込みに対応しています。 + +### Node API + +Electronでは特定のパッチで、`fs.readFile` や `require` のようなNode APIは、`asar` アーカイブを仮想ディレクトリのように扱い、ファイルをファイルシステム上の通常のファイルのように扱います。 + +例えば、`/path/to` 配下に、`example.asar` アーカイブがあると仮定します: + +```bash +$ asar list /path/to/example.asar +/app.js +/file.txt +/dir/module.js +/static/index.html +/static/main.css +/static/jquery.min.js +``` + +`asar` アーカイブ内のファイルを読み込む: + +```javascript +const fs = require('fs'); +fs.readFileSync('/path/to/example.asar/file.txt'); +``` + +アーカイブのルート直下にあるすべてのファイルを一覧にする: + +```javascript +const fs = require('fs'); +fs.readdirSync('/path/to/example.asar'); +``` + +アーカイブからモジュールを使用する: + +```javascript +require('/path/to/example.asar/dir/module.js'); +``` + +`BrowserWindow` を使って `asar` アーカイブ内の Web ページを表示することもできます: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('file:///path/to/example.asar/static/index.html'); +``` + +### Web API + +Webページで、アーカイブ内のファイルを `file:` プロトコルでリクエストできます。 +Node API と同様、`asar` アーカイブはディレクトリのように扱われます。 + +例えば、 `$.get` でファイルを取得できます: + +```html + +``` + +### `asar` アーカイブを通常のファイルのように扱う + +アーカイブのチェックサムを検証する等の幾つかのケースでは、`asar` アーカイブをファイルとして読み込む必要があります。この目的のために、 `asar` サポートしないオリジナルの `fs` API を提供するビルトインの `original-fs` モジュールを使用できます。 + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` + +`process.noAssar` に `true` をセットしても `fs` モジュールの `asar` サポートを無効にすることができます: + +```javascript +process.noAsar = true; +fs.readFileSync('/path/to/example.asar'); +``` + +## Node API の制限 + +Node APIで、`asar` アーカイブが可能な限りディレクトリのように動作するよう懸命に試してますが、低レベル環境での Node API に起因した制限が幾つかあります。 + +### アーカイブは読み取り専用です + +アーカイブは修正できないため、ファイルを変更できる変更できる全ての Node API は `asar` アーカイブに対して動作しません。 + +### 作業ディレクトリは、アーカイブ内のディレクトリに設定できません + +`asar` アーカイブはディレクトリのように扱われるにも関わらず、ファイルシステム上には実際のディレクトリが存在しないため、`asar` アーカイブ内のディレクトリを作業ディレクトリとして設定することはできません。幾つかの API の `cwd` オプションにアーカイブ内のディレクトリを渡すのも同様にエラーの原因になります。 + +### いくつかのAPIで追加のアンパッキングがされます + +たいていの `fs` APIは、アンパッキングせずに、 `asar` アーカイブからファイルを読み込んだり、ファイル情報を取得できます。しかし、システムコールに実際のファイルパスを渡すようになっている幾つかの API では、Electron は必要なファイルを一時ファイルとして抽出し、API に一時ファイルのパスを渡して、API が動作するようにします。このため、当該 API には多少のオーバーヘッドがあります。 + +追加のアンパッキングに必要なAPIです: + +* `child_process.execFile` +* `child_process.execFileSync` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - ネイティブモジュール上の `require` で使用されます + +### `fs.stat` の偽の統計情報 + +`asar` アーカイブ内のファイルはファイルシステム上に存在しないので、`fs.stat` および `asar` アーカイブ内のファイルへの関連情報によって返される`Stats` オブジェクトは、推測して生成されます。ファイルサイズの取得とファイルタイプのチェックを除いて、 `Stats` オブジェクトを信頼すべきではありません。 + +### `asar` アーカイブ内のバイナリの実行 + +`child_process.exec` と `child_process.spawn` 、 `child_process.execFile` のようなバイナリを実行できるNode APIがあります。しかし、`execFile` だけが、`asar` アーカイブ内でのバイナリ実行をサポートしています。 + +なぜならば、`exec` と `spawn` は入力として `file` の代わりに `command` を受け取り、`command` はシェル配下で実行されるからです。コマンドが asar アーカイブ内のファイルを使うかどうかを決定するための信頼できる方法はありませんし、そうするとしてもコマンドで使うファイルパスを副作用なしに置き換えることができるかどうかを確認することはできません。 + +## `asar` アーカイブ内のファイルをアンパックして追加 + +上記のように、いくつかのNode APIが呼ばれると、ファイルシステム上にファイルをアンパックしますが,パフォーマンス問題は別として、ウィルススキャナーのアラートにつながる可能性があります。 + +これに対応するために、`--unpack` オプションを使用して、アーカイブを作成する際に、いくつかのファイルをアンパックできます。例えば、ネイティブモジュールの共有ライブラリを除く場合: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +このコマンドを実行した後、`app.asar` とは別に、アンパックされたファイルを含んだ`app.asar.unpacked` フォルダーが生成されます。ユーザーに提供するときには、`app.asar` と一緒にコピーしなければなりません。 + +[asar]: https://github.com/atom/asar diff --git a/docs-translations/jp/tutorial/debugging-main-process.md b/docs-translations/jp/tutorial/debugging-main-process.md new file mode 100644 index 000000000000..0839d351598a --- /dev/null +++ b/docs-translations/jp/tutorial/debugging-main-process.md @@ -0,0 +1,70 @@ +# メインプロセスのデバッグ + +ブラウザウィンドウ DevToolsのみ、レンダリングプロセススクリプトをデバッグすることができます。メインプロセスからスクリプトをデバッグする方法を提供するために、Electronは、`--debug` と `--debug-brk` スイッチを提供します。 + +## コマンドライン スイッチ + +Electronのメインプロセスをデバッグするために次のコマンドラインスイッチを使用します + +### `--debug=[port]` + +このスイッチを使ったとき、Electronは、 `port` 上でV8デバッガープロトコルメッセージをリッスンします。デフォルトの `port` は `5858`です。 + +### `--debug-brk=[port]` + +`--debug` のようですが、最初の行でスクリプトをポーズします。 + +## デバッグ用のnode-inspectorを使用する + +__Note:__ Electron は今のところ、明確にはnode-inspectorは動作せず、node-inspectorのコンソール下で、`process` オブジェクトを調査した場合、メインプロセスはクラッシュするでしょう。 + +### 1. インストールされた[node-gyp required tools][node-gyp-required-tools] を確認する + +### 2. [node-inspector][node-inspector]をインストールする + +```bash +$ npm install node-inspector +``` + +### 3. `node-pre-gyp`のパッチバージョンをインストールする + +```bash +$ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electron-runtime-in-find +``` + +### 4. Electron用の `node-inspector` `v8` モジュールをリコンパイルする(対象のElectronのバージョン番号を変更する) + +```bash +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +``` + +[How to install native modules](how-to-install-native-modules)を見る。 + +### 5. Electron用のデバッグモードを有効にする + +デバッグフラッグでElectronを開始する: + +```bash +$ electron --debug=5858 your/app +``` + +または、最初のライン上でスクリプトをポーズする: + +```bash +$ electron --debug-brk=5858 your/app +``` + +### 5. Electronを使用して、[node-inspector][node-inspector] サーバーを開始する + +```bash +$ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin/inspector.js +``` + +### 6. デバッグUIを読み込みます + +Chromeブラウザで、 http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 を開きます。エントリーラインを見るために、debug-brkを始めるには、ポーズをクリックします。 + +[node-inspector]: https://github.com/node-inspector/node-inspector +[node-gyp-required-tools]: https://github.com/nodejs/node-gyp#installation +[how-to-install-native-modules]: using-native-node-modules.md#how-to-install-native-modules diff --git a/docs-translations/jp/tutorial/desktop-environment-integration.md b/docs-translations/jp/tutorial/desktop-environment-integration.md new file mode 100644 index 000000000000..24d3e9d2aaa7 --- /dev/null +++ b/docs-translations/jp/tutorial/desktop-environment-integration.md @@ -0,0 +1,251 @@ +# デスクトップ環境の統合 + +異なるオペレーティングシステムは、それぞれのデスクトップ環境でデスクトップに統合されたアプリケーション用に異なる機能を提供します。例えば、Windows アプリケーションではタスクバーのジャンプバーリストにショートカットをおけ、Macではドックメニューにカスタムメニューをおけます。 + +このガイドでは、Electron APIでデスクトップ環境にアプリケーションを統合する方法を説明します。 + +## 通知 (Windows, Linux, OS X) + +3つのオペレーティングシステム全てで、アプリケーションからユーザーに通知を送る手段が提供されています。通知を表示するためにオペレーティングシステムのネイティブ通知APIを使用しする[HTML5 Notification API](https://notifications.spec.whatwg.org/)で、Electronは、開発者に通知を送ることができます。 + +```javascript +var myNotification = new Notification('Title', { + body: 'Lorem Ipsum Dolor Sit Amet' +}); + +myNotification.onclick = function () { + console.log('Notification clicked') +} +``` + +オペレーティングシステム間でコードとユーザ体験は似ていますが、細かい違いがあります。 + +### Windows + +* Windows 10では、通知はすぐに動作します。 +* Windows 8.1 と Windows 8では、[Application User +Model ID][app-user-model-id]で、アプリへのショートカットはスタートメニューにインストールされます。しかし、スタートメニューにピン止めをする必要がありません。 +* Windows 7以前は、通知はサポートされていません。 しかし、[Tray API](tray-balloon)を使用してバルーンヒントを送信することができます。 + +通知にイメージを使うために、通知オプションの `icon` プロパティにローカルのイメージファイル(`png`が望ましい)を設定します。 正しくない、または`http/https`の URLを設定した場合でも、通知は表示されますが、イメージは表示されません。 + +```javascript +new Notification('Title', { + body: 'Notification with icon', + icon: 'file:///C:/Users/feriese/Desktop/icon.png' +}); +``` + +その上で、bodyの最大サイズは250文字であることに留意してください。Windowsチームは、通知は200文字にすることを推奨しています。 + +### Linux + +通知は、`libnotify`を使用して送信されます。[デスクトップ通知仕様][notification-spec]に対応したデスクトップ環境上(Cinnamon、Enlightenment、Unity、GNOME、KDEなど)で通知を表示できます。 + +### OS X + +通知は、そのままOS Xに通知されます。しかし、[通知に関するAppleのヒューマンインターフェイスガイドライン(英語版)](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/NotificationCenter.html)を知っておくべきです。 + +通知は、256バイトサイズに制限されており、制限を超えていた場合、通知が破棄されます。 + +## 最近のドキュメント (Windows と OS X) + +Windows と OS Xは、ジャンプリストやドックメニュー経由で、アプリケーションが開いた最近のドキュメント一覧に簡単にアクセスできます。 + +__JumpList:__ + +![JumpList Recent Files](http://i.msdn.microsoft.com/dynimg/IC420538.png) + +__Application dock menu:__ + + + +最近のドキュメントにファイルを追加するために、[app.addRecentDocument][addrecentdocument] APIを使用できます: + +```javascript +app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); +``` + +[app.clearRecentDocuments][clearrecentdocuments] API を使用して、最近のドキュメント一覧を空にできます: + +```javascript +app.clearRecentDocuments(); +``` + +### Windows 留意点 + +Windows で、この機能を使用できるようにするために、アプリケーションにドキュメントのファイルタイプのハンドラーを登録すべきです。さもなければ、ジャンプリストに表示されません。[Application Registration][app-registration]で、登録しているアプリケーションをすべて見れます。 + +ユーザーがジャンプリストからファイルをクリックしたとき、アプリケーションの新しいインスタンスは、コマンドライン引数にファイルのパスを渡して開始します。 + +### OS X 留意点 + +ファイルが最近のドキュメントメニューからリクエストされた時、 `app` モジュールの `open-file` イベントが発行されます。 + +## ドックメニュー (OS X)のカスタマイズ + +通常アプリケーションで使用する共通機能用のショートカットを含める、ドック用のカスタムメニューをOS Xでは指定できます。 + +__Dock menu of Terminal.app:__ + + + +カスタムドックメニューを設定するために、OS Xのみに提供されている `app.dock.setMenu` APIを使用できます。 + +```javascript +const electron = require('electron'); +const app = electron.app; +const Menu = electron.Menu; + +var dockMenu = Menu.buildFromTemplate([ + { label: 'New Window', click: function() { console.log('New Window'); } }, + { label: 'New Window with Settings', submenu: [ + { label: 'Basic' }, + { label: 'Pro'} + ]}, + { label: 'New Command...'} +]); +app.dock.setMenu(dockMenu); +``` + +## ユーザータスク (Windows) + +Windowsでは、ジャンプリストの `Tasks` カテゴリでカスタムアクションを指定できます。 +MSDNから引用します。 + +>アプリケーションでは、プログラムの機能とユーザーがプログラムを使用して実行する可能性が最も高い主な操作の両方に基づいてタスクを定義します。タスクを実行するためにアプリケーションが起動している必要がないように、タスクは状況に依存しないものにする必要があります。また、タスクは、一般的なユーザーがアプリケーションで実行する操作の中で、統計上最も一般的な操作です。たとえば、メール プログラムでは電子メールの作成や予定表の表示、ワード プロセッサでは新しい文書の作成、特定のモードでのアプリケーションの起動、アプリケーションのサブコマンドを実行することなどです。一般的なユーザーが必要としない高度な機能や、登録などの 1 回限りの操作によって、メニューがわかりづらくなることがないようにしてください。アップグレードやキャンペーンなどの販売促進用の項目としてタスクを使用しないでください。 + +>タスク一覧は静的なものにすることを強くお勧めします。アプリケーションの状態や状況に関係なく、タスク一覧は同じ状態にすることをお勧めします。リストを動的に変更することも可能ですが、ユーザーはターゲット一覧のこの部分が変更されると考えていないので、ユーザーを混乱させる可能性があることを考慮してください。 + +__Internet Explorerのタスク:__ + +![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) + +実際のメニューであるOS Xのドックメニューとは異なり、ユーザーがタスクをクリックしたとき、Windowsではユーザータスクはアプリケーションのショートカットのように動作し、プログラムは指定された引数を実行します。 + +アプリケーション用のユーザータスクを設定するために、[app.setUserTasks][setusertaskstasks] APIを使用できます: + +```javascript +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window', + description: 'Create a new window' + } +]); +``` + +タスクリストをクリアするために、`app.setUserTasks` をコールし、配列を空にします。 + +```javascript +app.setUserTasks([]); +``` + +アプリケーションを閉じた後もユーザータスクは表示されていてるので、アプリケーションをアンインストールするまではタスクに指定したアイコンとプログラムのパスは存在し続けてる必要があります。 + +## サムネイルツールバー(縮小表示ツールバー) + +Windowsでは、アプリケーションウィンドウのタスクバーレイアウトに、指定のボタンを縮小表示ツールバーに追加できます。アプリケーションのウィンドウを元のサイズに戻したりアクティブ化したりすることなく、主要なコマンドにアクセスできるようにします。 + +MSDNからの引用: + +>このツール バーは、単純に、使い慣れた標準的なツール バー コモン コントロールです。最大で 7 個のボタンが配置されます。各ボタンの ID、イメージ、ツールヒント、および状態は構造体で定義され、その後タスク バーに渡されます。アプリケーションでは、現在の状態での必要に応じて、縮小表示ツール バーのボタンの表示、有効化、無効化、非表示を実行できます。 +>たとえば、Windows Media Player の縮小表示ツール バーでは、再生、一時停止、ミュート、停止などの、標準的なメディア トランスポート コントロールを提供できます。 + +__Windows Media Playerの縮小表示ツールバー:__ + +![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) + +アプリケーションに縮小表示ツールバーを設定するために、[BrowserWindow.setThumbarButtons][setthumbarbuttons]を使えます: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +const path = require('path'); + +var win = new BrowserWindow({ + width: 800, + height: 600 +}); +win.setThumbarButtons([ + { + tooltip: "button1", + icon: path.join(__dirname, 'button1.png'), + click: function() { console.log("button2 clicked"); } + }, + { + tooltip: "button2", + icon: path.join(__dirname, 'button2.png'), + flags:['enabled', 'dismissonclick'], + click: function() { console.log("button2 clicked."); } + } +]); +``` + +縮小表示ツールバーボタンをクリアするために、 `BrowserWindow.setThumbarButtons` をコールして配列を空にします: + +```javascript +win.setThumbarButtons([]); +``` + +## Unity ランチャーショートカット (Linux) + +Unityでは、`.desktop` ファイルの修正を経由してランチャーにカスタムエントリーを追加できます。[Adding Shortcuts to a Launcher][unity-launcher]を参照してください。 + +__Audaciousのランチャーショートカット:__ + +![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) + +## タスクバーの進行状況バー (Windows & Unity) + +Windowsでは、タスクバーボタンは、進行状況バーを表示するのに使えます。ウィンドウを切り替えることなくウィンドウの進行状況情報をユーザーに提供することができます。 + +Unity DEは、ランチャーに進行状況バーの表示をするのと同様の機能を持っています。 + +__タスクバーボタン上の進行状況バー:__ + +![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) + +__Unityランチャーでの進行状況バー:__ + +![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) + +ウィンドウに進行状況バーを設定するために、[BrowserWindow.setProgressBar][setprogressbar] APIを使えます: + +```javascript +var window = new BrowserWindow({...}); +window.setProgressBar(0.5); +``` + +## Windowのファイル表示 (OS X) + +OS Xでは、ウィンドウがrepresented fileを設定でき、タイトルバー上にファイルのアイコンを表示でき、タイトル上でCommand-クリックまたはControl-クリックをすると、パスがポップアップ表示されます。 + +ウィンドウの編集状態を設定できるように、このウィンドウのドキュメントが変更されたかどうかをファイルのアイコンで示せます。 + +__Represented file ポップアップメニュー:__ + + + +ウィンドウにrepresented fileを設定するために、[BrowserWindow.setRepresentedFilename][setrepresentedfilename] と [BrowserWindow.setDocumentEdited][setdocumentedited] APIsを使えます: + +```javascript +var window = new BrowserWindow({...}); +window.setRepresentedFilename('/etc/passwd'); +window.setDocumentEdited(true); +``` + +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks +[setprogressbar]: ../api/browser-window.md#browserwindowsetprogressbarprogress +[setrepresentedfilename]: ../api/browser-window.md#browserwindowsetrepresentedfilenamefilename +[setdocumentedited]: ../api/browser-window.md#browserwindowsetdocumenteditededited +[app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx +[unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher +[setthumbarbuttons]: ../api/browser-window.md#browserwindowsetthumbarbuttonsbuttons +[tray-balloon]: ../api/tray.md#traydisplayballoonoptions-windows +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[notification-spec]: https://developer.gnome.org/notification-spec/ diff --git a/docs-translations/jp/tutorial/devtools-extension.md b/docs-translations/jp/tutorial/devtools-extension.md new file mode 100644 index 000000000000..85eff391a43b --- /dev/null +++ b/docs-translations/jp/tutorial/devtools-extension.md @@ -0,0 +1,45 @@ +# DevTools エクステンション + +簡単にデバッグをするために、Electronは[Chrome DevTools Extension][devtools-extension]に基本的な対応しています。 + +DevToolsエクステンションのために、ソースコードをダウンローし、`BrowserWindow.addDevToolsExtension` APIを使って読み込みます。読み込んだエクステンションは維持されているので、ウィンドウを作成するとき、毎回APIをコールする必要はありません。 + +** NOTE: React DevTools は動作しません。issue https://github.com/atom/electron/issues/915 でフォローしています** + +例えば、[React DevTools Extension](https://github.com/facebook/react-devtools)を使うために、最初にソースコードをダウンロードする必要があります。 + +```bash +$ cd /some-directory +$ git clone --recursive https://github.com/facebook/react-devtools.git +``` + +エクステンションをビルドするために、[`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md)を参照してください。 + +複数のウィンドウで、DevToolsを開くために、Electronでエクステンションをロードし、DevToolsコンソールで次のコードを実行します。 + +```javascript +const BrowserWindow = require('electron').remote.BrowserWindow; +BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); +``` + +エクステンションをアンロードするために、名前と`BrowserWindow.removeDevToolsExtension` APIをコールすると、次回DevToolsを開いた時にはロードされません。 + +```javascript +BrowserWindow.removeDevToolsExtension('React Developer Tools'); +``` + +## DevTools エクステンションのフォーマット + +理想的には、Chromeブラウザー用に書かれたすべてのDevToolsエクステンションをElectronがロードできることですが、それらはプレーンディレクトリにある必要があります。`crx` エクステンションパッケージは、ディレクトリに解凍できる方法がなければ、Electronはロードする方法はありません。 + +## バックグラウンドページ + +今のところ、ElectronはChromエクステンションのバックグラウンドページ機能に対応していません。そのため、Electronでは、この機能に依存するDevToolsエクステンションは動作しません。 + +## `chrome.*` APIs + +いくつかのChromeエクステンションは、`chrome.*` APIsを使用しており、ElectronでそれらのAPIを実装するための努力をしていますが、すべては実装されていません。 + +すべては実装されていない`chrome.*` APIについて考えると、もしDevToolsエクステンションが `chrome.devtools.*` 以外のAPIを使用していると、エクステンションは動作しない可能性が非常に高いです。 + +[devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs-translations/jp/tutorial/mac-app-store-submission-guide.md b/docs-translations/jp/tutorial/mac-app-store-submission-guide.md new file mode 100644 index 000000000000..b2cf9d26d30d --- /dev/null +++ b/docs-translations/jp/tutorial/mac-app-store-submission-guide.md @@ -0,0 +1,103 @@ +# Mac App Store 提出ガイド + +v0.34.0から、ElectronはMac App Store (MAS)にパッケージ化したアプリを登録することができます。このガイドでは、MASビルド用の制限とアプリを登録する方法についての情報を提供します。 + +__Note:__ Mac App Storeにアプリを登録するには、費用が発生する[Apple Developer Program][developer-program]に登録する必要があります。 + +## アプリを登録する方法 + +次の手順は、Mac App Sotreにアプリを登録する簡単な手順を説明します。しかし、これらの手順はAppleによってAppが承認されることを保証するものではありません。Mac App Storeの要件については、Appleの[Submitting Your App][submitting-your-app]ガイドを読んでおく必要があります。 + +### 証明書の取得 + +Mac App StoreにAppを登録するには、最初にAppleから証明書を取得しなければありません。Web上で、[existing guides][nwjs-guide] を参照してください。 + +### アプリケーションの署名 + +Appleから証明書を取得した後、[Application Distribution](application-distribution.md)を参照してアプリをパッケージ化し、アプリに証明をします。この手順は、基本的にほかのプログラムと同じですが、Electronが依存するすべてに1つ1つサインします。 + +最初に、2つの資格ファイルを用意する必要があります。 + +`child.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + +``` + +`parent.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + + +``` + +次のスクリプトでアプリをサインします。 + +```bash +#!/bin/bash + +# Name of your app. +APP="YourApp" +# The path of you app to sign. +APP_PATH="/path/to/YouApp.app" +# The path to the location you want to put the signed package. +RESULT_PATH="~/Desktop/$APP.pkg" +# The name of certificates you requested. +APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" +INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" + +FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" + +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" +``` + +OS Xで、サンドボックスにアプリを新しく追加した場合、基本的な考え方は、Appleの[Enabling App Sandbox][enable-app-sandbox]を読み、エンタイトルメントファイルにアプリに必要なパーミッションキーを追加します。 + +### Appをアップロードし、レビューに登録する + +アプリに署名後、iTunes ConnectにアップロードするためにApplication Loaderを使用できます。アップロードする前に[created a record][create-record]を確認してください。そして[レビュー用にアプリを登録できます][submit-for-review]. + +## MAS Buildの制限 + +アプリのサンドボックスですべての要件を満たすために、MASビルドで次のモジュールを無効にしてください。 + +* `crash-reporter` +* `auto-updater` + +次の挙動を変更してください。 + +* ビデオキャプチャーはいくつかのマシンで動作しないかもしれません。 +* 一部のアクセシビリティ機能が動作しないことがあります。 +* アプリはDNSの変更を認識しません。 + +アプリのサンドボックスでの使用が原因で、アプリがアクセスできるリソースは厳密に制限されています。詳細は、 [App Sandboxing][app-sandboxing] を参照してください。 + +[developer-program]: https://developer.apple.com/support/compare-memberships/ +[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html +[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps +[enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html +[create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html +[submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html +[app-sandboxing]: https://developer.apple.com/app-sandboxing/ diff --git a/docs-translations/jp/tutorial/online-offline-events.md b/docs-translations/jp/tutorial/online-offline-events.md new file mode 100644 index 000000000000..92503b04dfcb --- /dev/null +++ b/docs-translations/jp/tutorial/online-offline-events.md @@ -0,0 +1,79 @@ +# オンライン/オフライン イベントの検知 + +オンラインとオフラインイベントの検知は、以下の例で示すように、標準のHTML 5 APIを使用してレンダラプロセスに実装することができます。 + +_main.js_ + +```javascript +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + +var onlineStatusWindow; +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` + +メインプロセスでこれらのイベントに応答したいことがあるかもしれません。しかし、メインプロセスは `navigator` オブジェクトを持たないため、直接これらのイベントを検知することができません。Electronの inter-process communication ユーティリティを使用して、オンライン・オフラインイベントをメインプロセスに転送し、必要に応じて扱うことができます。次の例を見てみましょう。 + +_main.js_ + +```javascript +const electron = require('electron'); +const app = electron.app; +const ipcMain = electron.ipcMain; +const BrowserWindow = electron.BrowserWindow; + +var onlineStatusWindow; +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); +}); + +ipcMain.on('online-status-changed', function(event, status) { + console.log(status); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` diff --git a/docs-translations/jp/tutorial/quick-start.md b/docs-translations/jp/tutorial/quick-start.md index 4bd816cb6a46..bfb6e2a51f76 100644 --- a/docs-translations/jp/tutorial/quick-start.md +++ b/docs-translations/jp/tutorial/quick-start.md @@ -1,34 +1,32 @@ # クイックスタート -## 導入 +Electron ではリッチなネイティブ API を持ったランタイムを提供することによってピュアな JavaScript でデスクトップアプリケーションをつくることができます。ウェブサーバーの代わりにデスクトップアプリケーションに焦点をあてた Node.js ランタイムであるといえばわかりやすいかもしれません。 -ElectronではリッチなネイティブAPIを持ったランタイムを提供することによってピュアなJavaScriptでデスクトップアプリケーションをつくることができます。ウェブサーバーの代わりにデスクトップアプリケーションに焦点をあてたio.jsランタイムであるといえばわかりやすいかもしれません。 - -ElectronはJavaScriptをGUIライブラリにバインディングしません。その代わりに、ElectronはウェブページをGUIとして使用します。なのでElectronはJavaScriptによってコントロールされる最小のChromiumブラウザでもあるともいえます。 +Electron は JavaScript を GUI ライブラリにバインディングしません。その代わりに、Electron はウェブページを GUI として使用します。なので Electron は JavaScript によってコントロールされる最小のChromium ブラウザでもあるともいえます。 ### メインプロセス -Electronでは、`package.json` の `main`で実行されるプロセスを __メインプロセス__ と呼びます。メインスクリプトではGUIにウェブページを表示することができるプロセスを実行します。 +Electron では、`package.json` の `main` スクリプトで実行されるプロセスを __メインプロセス__ と呼びます。メインプロセスで実行されるスクリプトがウェブページを作ることによって GUI を表示することができます。 ### レンダラープロセス -Electronはウェブページを表示させるためにChromiumを使用しているので、Chromiumのマルチプロセスアーキテクチャが使用されることになります。Electronで実行されるウェブページはそれぞれ自身のプロセスで実行されます。それを __レンダラープロセス__ と呼びます。 +Electron はウェブページを表示させるために Chromium を使用しているので、Chromium のマルチプロセスアーキテクチャも使用されることになります。Electron で実行されるウェブページはそれぞれ自身のプロセスで実行されます。それを __レンダラープロセス__ と呼びます。 -通常、ブラウザのウェブページはサンドボックス環境で実行されネイティブなリソースへのアクセスができません。Electronではウェブページからio.jsのAPIを使って、ネイティブリソースへの権限が与えられます。そのおかげでウェブページの中からJavaScriptを使って低レベルなオペレーティングシステムとのインタラクションが可能になります。 +通常のブラウザでは、ウェブページはサンドボックス環境で実行されネイティブなリソースへのアクセスができません。Electron ではウェブページから Node.js の API を使えるためオペレーティングシステムと低レベルなやりとりが可能です。 ### メインプロセスとレンダラープロセスの違い -メインプロセスは `BrowserWindow` インスタンスを作ることによってウェブページをつくります。それぞれの `BrowserWindow` インスタンスはそれ自身の レンダラープロセス上でウェブページを実行します。`BrowserWindow` インスタンスが破棄されると、対応するレンダラープロセスも終了されます。 +メインプロセスは `BrowserWindow` インスタンスを作ることによってウェブページをつくります。それぞれの `BrowserWindow` インスタンスはそれ自身のレンダラープロセス上でウェブページを実行します。`BrowserWindow` インスタンスが破棄されると、対応するレンダラープロセスも終了されます。 -メインプロセスはすべてのウェブページとそれに対応するレンダラープロセスを管理しています。それぞれのレンダラープロセスは分離しているのでウェブページで実行されていることだけを気に留めておいてください。 +メインプロセスはすべてのウェブページとそれに対応するレンダラープロセスを管理しています。それぞれのレンダラープロセスは隔離されているので、自身の中で実行されているウェブページの面倒だけをみます。 -ウェブページでは、GUI関連のAPIを呼ぶことはできません。なぜならば、ウェブページで管理しているネイティブのGUIリソースは非常に危険で簡単にリークしてしまうからです。もしウェブページ内でGUIを操作したい場合には、メインプロセスと通信をする必要があります。 +ウェブページでは、GUI 関連の API を呼ぶことはできません。なぜならば、ウェブページからネイティブ GUI リソースを扱うことは非常に危険であり、簡単にリソースをリークしてしまうからです。もしウェブページ内でGUI を操作したい場合には、ウェブページのレンダラープロセスはメインプロセスにそれらの操作をするように伝える必要があります。 -Electronでは、メインプロセスとレンダラープロセスとのコミュニケーションをするために[ipc](../api/ipc-renderer.md)モジュールを提供しています。またそれと、RPC形式の通信を行う[remote](../api/remote.md)モジュールもあります。 +Electron では、メインプロセスとレンダラープロセスとのコミュニケーションをするために [ipc](../api/ipc-renderer.md) モジュールを提供しています。またそれと、RPC 形式の通信を行う [remote](../api/remote.md) モジュールもあります。 ## Electronアプリを作成する -一般的に Electronアプリの構成は次のようになります: +一般的に Electron アプリの構成は次のようになります: ```text your-app/ @@ -37,7 +35,7 @@ your-app/ └── index.html ``` -`package.json`の形式はNodeモジュールとまったく同じです。 `main` フィールドでアプリを起動するためのスクリプトを特定し、メインプロセスで実行します。 `package.json`の例は次のようになります: +`package.json` の形式は Node モジュールとまったく同じです。 `main` フィールドで指定するスクリプトはアプリの起動スクリプトであり、メインプロセスを実行します。 `package.json` の例は次のようになります: ```json { @@ -47,25 +45,32 @@ your-app/ } ``` +__注記__: `package.json` に `main` が存在しない場合、Electron は `index.js` のロードを試みます。 + `main.js` ではウィンドウを作成してシステムイベントを管理します。典型的な例は次のようになります: ```javascript -var app = require('app'); // Module to control application life. -var BrowserWindow = require('browser-window'); // Module to create native browser window. +'use strict'; + +const electron = require('electron'); +const app = electron.app; // Module to control application life. +const BrowserWindow = electron.BrowserWindow; // Module to create native browser window. // Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the javascript object is GCed. +// be closed automatically when the JavaScript object is garbage collected. var mainWindow = null; // Quit when all windows are closed. app.on('window-all-closed', function() { + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q if (process.platform != 'darwin') { app.quit(); } }); -// This method will be called when Electron has done everything -// initialization and ready for creating browser windows. +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. app.on('ready', function() { // Create the browser window. mainWindow = new BrowserWindow({width: 800, height: 600}); @@ -73,8 +78,8 @@ app.on('ready', function() { // and load the index.html of the app. mainWindow.loadURL('file://' + __dirname + '/index.html'); - // Open the devtools. - mainWindow.openDevTools(); + // Open the DevTools. + mainWindow.webContents.openDevTools(); // Emitted when the window is closed. mainWindow.on('closed', function() { @@ -86,43 +91,81 @@ app.on('ready', function() { }); ``` -最後に表示するウェブページ`index.html`は次のようになります: - +最後に表示するウェブページ `index.html` は次のようになります: ```html + Hello World!

Hello World!

- We are using io.js - and Electron . + We are using node , + Chrome , + and Electron . ``` ## アプリを実行する -アプリケーションを作り終えたら、[Application distribution](./application-distribution.md)ガイドにしたがってディストリビューションを作成します、そしてパッケージされたアプリケーションとして配布することが可能です。またダウンロードしたElectronのバイナリをアプリケーション・ディレクトリを実行するために利用することもできます。 +最初の `main.js`、`index.html`、`package.json` を作ったら、手元でアプリを実行し、思った通りに動くのを確認したいでしょう。 -Windowsの場合: +### electron-prebuilt + +`electron-prebuilt` を `npm` でグローバルインストールしているなら、アプリのソースディレクトリ内で以下を実行するだけで済みます: + +```bash +electron . +``` + +ローカルにインストールしているなら以下を実行してください: + +```bash +./node_modules/.bin/electron . +``` + +### 手動ダウンロードした Electron バイナリを使う場合 + +もしも Electron を手動ダウンロードしているなら、同梱されているバイナリであなたのアプリを直接実行できます。 + +#### Windows ```bash $ .\electron\electron.exe your-app\ ``` -Linuxの場合: +#### Linux ```bash $ ./electron/electron your-app/ ``` -OS Xの場合: +#### OS X ```bash $ ./Electron.app/Contents/MacOS/Electron your-app/ ``` -`Electron.app` はElectronのリリースパッケージに含まれており、[ここ](https://github.com/atom/electron/releases) からダウンロードできます。 +`Electron.app` は Electron のリリースパッケージの一部で、[ここ](https://github.com/atom/electron/releases) からダウンロードできます。 + +### Run as a distribution + +アプリケーションを作り終えたら、[Application distribution](./application-distribution.md) ガイドにしたがってディストリビューションを作成し、そしてパッケージされたアプリケーションとして実行することが可能です。 + +### 試してみよう + +このチュートリアルのコードは [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) リポジトリから clone して実行できます。 + +**注記**:例を試すには、[Git](https://git-scm.com) と [Node.js](https://nodejs.org/en/download/) ([npm](https://npmjs.org) もこれに含まれています) が必要です。 + +```bash +# Clone the repository +$ git clone https://github.com/atom/electron-quick-start +# Go into the repository +$ cd electron-quick-start +# Install dependencies and run the app +$ npm install && npm start +``` diff --git a/docs-translations/jp/tutorial/supported-platforms.md b/docs-translations/jp/tutorial/supported-platforms.md new file mode 100644 index 000000000000..8482931ed21e --- /dev/null +++ b/docs-translations/jp/tutorial/supported-platforms.md @@ -0,0 +1,24 @@ +# サポートするプラットフォーム + +Electronでは次のプラットフォームをサポートします。 + +### OS X + +OS X用に提供しているバイナリは64bitのみで、サポートするOS Xのバージョンは、OS X 10.8 以降です。 + +### Windows + +Windows 7 以降をサポートしており、それ以前のオペレーティングシステムはサポートしていません(し、動作しません)。 + +`x86` と `amd64` (x64) の両方のバイナリが、Windows用に提供しています。 +ただし、`ARM` バージョンのWindowsは今のところサポートしていません。 + +### Linux + +`ia32`(`i686`) と `x64`(`amd64`) のビルド済みバイナリは、Ubuntu 12.04上でビルドされ、`arm` バイナリは、Debian Wheezy用のhard-float ABIとNEONのARM v7を対象にビルドしています。 + +Electronはビルドプラットフォームにリンクされているので、ディストリビューションに同梱されているライブラリに依存しているかどうかに関係なくディストリビューション上で動作します。そのため Ubuntu 12.04 のみを動作保証しますが、次のプラットフォームについてもビルド済みのElectronバイナリを実行できるか検証します。 + +* Ubuntu 12.04 以降 +* Fedora 21 +* Debian 8 diff --git a/docs-translations/jp/tutorial/using-native-node-modules.md b/docs-translations/jp/tutorial/using-native-node-modules.md new file mode 100644 index 000000000000..c0bce4fab8d8 --- /dev/null +++ b/docs-translations/jp/tutorial/using-native-node-modules.md @@ -0,0 +1,50 @@ +# ネイティブのNodeモジュールを使用する + +Electronは、ネイティブのNodeモジュールをサポートしていますが、Node公式とは異なるV8バージョンを使用しているので、ネイティブモジュールでビルドする時、Electronのヘッダーで指定する必要があります。 + +## ネイティブNodeモジュールとの互換性 + +Nodeが新しいV8バージョンを使用し始めた時、Nativeモジュールは動作しなくなるかもしれません。ElectronでNativeモジュールが動作するかどうかを確認するために、Electronで使用する内部のNodeバージョンがサポートしているかを確認すべきです。ElectronでNodeが使用しているバージョンを確認するには、[releases](https://github.com/atom/electron/releases)ページを見るか、`process.version` (例えば [Quick Start](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md)を見てください)を使用してください。 + +Nodeの複数バージョンを簡単にサポートできるので、あなたのモジュールに [NAN](https://github.com/nodejs/nan/) を使うことを検討してください。古いバージョンからElectronで動作するNodeの新しいバージョンへ移植するのに役立ちます。 + +## ネイティブモジュールのインストール方法 + +ネイティブモジュールをインストールするための3つの方法: + +### 簡単な方法 + +ヘッダーをダウンロードし、ネイティブモジュールをビルドする手順をマニュアルで管理する、[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) パッケージ経由で、ネイティブモジュールをリビルドするのが最も簡単な方法です。 + +```sh +npm install --save-dev electron-rebuild + +# Every time you run "npm install", run this +./node_modules/.bin/electron-rebuild + +# On Windows if you have trouble, try: +.\node_modules\.bin\electron-rebuild.cmd +``` + +### npmを使う方法 + +モジュールをインストールするために`npm` を使用できます。環境変数の設定を除いて、Nodeモジュールと完全に同じ手順です。 + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.33.1 +export npm_config_arch=x64 +export npm_config_runtime=electron +HOME=~/.electron-gyp npm install module-name +``` + +### node-gypを使う方法 + +ElectronのヘッダーでNodeモジュールをビルドするために、どこからヘッダーをダウンロードするかとどのバージョンを使用するかを選択して`node-gyp`を実行する必要があります。 + +```bash +$ cd /path-to-module/ +$ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell +``` + +開発ヘッダーを探し、 `HOME=~/.electron-gyp` を変更します。`--target=0.29.1`がElectronのバージョンです。 `--dist-url=...` で、どこからヘッダーをダウンロードするかを指定します。`--arch=x64`を使用して、64bit システム用にモジュールをビルドします。 diff --git a/docs-translations/jp/tutorial/using-pepper-flash-plugin.md b/docs-translations/jp/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 000000000000..841a505c1e1a --- /dev/null +++ b/docs-translations/jp/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,46 @@ +# Pepper Flash プラグインを使用する + +Electronは、Pepper Flashプラグインをサポートしています。ElectronでPepper Flashプラグインを使用するために、マニュアルでPepper Flashプラグインのパスを指定し、アプリケーションで有効化しなければなりません。 + +Electron now supports the Pepper Flash plugin. To use the Pepper Flash plugin in + +## Flash プラグインのコピー準備 + +OS XとLinuxでは、Pepper Flashプラグインの詳細は、Chromeブラウザーで、`chrome://plugins` にアクセスして確認できます。そこで表示されるパスとバージョンは、ElectronのPepper Flashサポートに役立ちます。それを別のパスにコピーすることができます。 + +## Electron スイッチの追加 + +直接、Electronコマンドラインに`--ppapi-flash-path` と `ppapi-flash-version` を追加するか、 アプリのreadyイベントの前に、`app.commandLine.appendSwitch`メソッドを使用します。`browser-window`の`plugins`スイッチを追加します。 + +例: + +```javascript +// Specify flash path. +// On Windows, it might be /path/to/pepflashplayer.dll +// On OS X, /path/to/PepperFlashPlayer.plugin +// On Linux, /path/to/libpepflashplayer.so +app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); + +// Specify flash version, for example, v17.0.0.169 +app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169'); + +app.on('ready', function() { + mainWindow = new BrowserWindow({ + 'width': 800, + 'height': 600, + 'web-preferences': { + 'plugins': true + } + }); + mainWindow.loadURL('file://' + __dirname + '/index.html'); + // Something else +}); +``` + +## `` タグでFlashプラグインを有効か + +`` タグに`plugins`属性を追加する + +```html + +``` diff --git a/docs-translations/jp/tutorial/using-selenium-and-webdriver.md b/docs-translations/jp/tutorial/using-selenium-and-webdriver.md new file mode 100644 index 000000000000..3202028ebf5f --- /dev/null +++ b/docs-translations/jp/tutorial/using-selenium-and-webdriver.md @@ -0,0 +1,119 @@ +# Selenium と WebDriverを使用する + +[ChromeDriver - WebDriver for Chrome][chrome-driver]より: + +> WebDriverは、複数のブラウザ間でWebアプリの自動テストをするためのオープンソースツールです。Webページの移動、ユーザー入力、JavaScriptの実行などを可能にします。ChromeDriverはChromium用のWebDriverのワイヤープロトコルで実装されたスタンドアロンサーバーです。ChromiumとWebDriverチームメンバーが開発しています。 + +Electronで `chromedriver` を使用するために、Electronがどこにあるかを示し、ElectronはChromeブラウザーであると思わせます。 + +## WebDriverJsを設定します + +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) は、web driver でテストするためのNodeパッケージを提供します。例のように使用します。 + +### 1. ChromeDriverを開始 + +最初に、 `chromedriver` バイナリをダウンロードし、実行します: + +```bash +$ ./chromedriver +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +ポート番号 `9515`を覚えておいてください、あとで使用します。 + +### 2. WebDriverJSのインストール + +```bash +$ npm install selenium-webdriver +``` + +### 3. ChromeDriverに接続する + +Electronでの `selenium-webdriver` 使用方法は、chrome driverへの接続方法とElectronバイナリがどこにあるかをマニュアルで指定する以外は、upstreamと基本的に同じです。 + +```javascript +const webdriver = require('selenium-webdriver'); + +var driver = new webdriver.Builder() + // The "9515" is the port opened by chrome driver. + .usingServer('http://localhost:9515') + .withCapabilities({ + chromeOptions: { + // Here is the path to your Electron binary. + binary: '/Path-to-Your-App.app/Contents/MacOS/Atom', + } + }) + .forBrowser('electron') + .build(); + +driver.get('http://www.google.com'); +driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); +driver.findElement(webdriver.By.name('btnG')).click(); +driver.wait(function() { + return driver.getTitle().then(function(title) { + return title === 'webdriver - Google Search'; + }); +}, 1000); + +driver.quit(); +``` + +## WebdriverIOのセットアップをする + +[WebdriverIO](http://webdriver.io/) は、web driverでテストするNodeパッケージを提供します。 + +### 1. ChromeDriverを開始する + +最初に、 `chromedriver` バイナリをダウンロードし、実行します。: + +```bash +$ chromedriver --url-base=wd/hub --port=9515 +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +ポート番号 `9515`を覚えておいてください、あとで使用します。 + +### 2. WebdriverIOをインストールする + +```bash +$ npm install webdriverio +``` + +### 3. chrome driverに接続する + +```javascript +const webdriverio = require('webdriverio'); +var options = { + host: "localhost", // Use localhost as chrome driver server + port: 9515, // "9515" is the port opened by chrome driver. + desiredCapabilities: { + browserName: 'chrome', + chromeOptions: { + binary: '/Path-to-Your-App/electron', // Path to your Electron binary. + args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/ + } + } +}; + +var client = webdriverio.remote(options); + +client + .init() + .url('http://google.com') + .setValue('#q', 'webdriverio') + .click('#btnG') + .getTitle().then(function(title) { + console.log('Title was: ' + title); + }) + .end(); +``` + +## ワークフロー + +Electronはリビルドせずにアプリケーションをテストするために、単純にElectronのリソースディレクトリでアプリのソースを[配置します](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md)。 + +もしくは、アプリのフォルダーを引数にしてElectronバイナリを実行します。これは、Electronのリソースディレクトリにアプリをコピー&ペーストする必要性を除きます。 + +[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ diff --git a/docs-translations/jp/tutorial/using-widevine-cdm-plugin.md b/docs-translations/jp/tutorial/using-widevine-cdm-plugin.md new file mode 100644 index 000000000000..5cee7f26dc66 --- /dev/null +++ b/docs-translations/jp/tutorial/using-widevine-cdm-plugin.md @@ -0,0 +1,57 @@ +# Widevine CDM Pluginを使用する + +Electronで、Chromeブラウザーに同梱される Widevine CDMプラグインを使用できます。 + +## プラグインを取得する + +Electronは、ライセンス的な理由でWidevine CDMプラグインは同梱されません。Widevine CDMプラグインを取得するために、最初に、使用するElectronビルドのChromバージョンとアーキテクチャを合わせた公式のChromeブラウザーをインストールする必要があります。 + +### Windows & OS X + +Chromeブラウザーで、`chrome://components/`を開き、 `WidevineCdm` を探し、それが最新であることを確認し、`APP_DATA/Google/Chrome/WidevineCDM/VERSION/_platform_specific/PLATFORM_ARCH/`ディレクトリからすべてのプラグインバイナリを探します。 + +`APP_DATA` は、アプリデータを格納するシステムロケーションです。Windowsでは`%LOCALAPPDATA%`、OS Xでは`~/Library/Application Support`です。`VERSION` は、Widevine CDM プラグインのバージョン文字列で、 `1.4.8.866`のような文字列が格納されます。`PLATFORM` は、 `mac` か `win`です。`ARCH` は `x86` か `x64`です。 + +Windowsでは、`widevinecdm.dll` と `widevinecdmadapter.dll`が必要で、OS Xでは、`libwidevinecdm.dylib` と `widevinecdmadapter.plugin`です。任意の場所にコピーできますが、一緒に配置する必要があります。 + +### Linux + +Linux上では、プラグインバイナリは、Chromeブラウザーにいっほに格納され、 `/opt/google/chrome` 配下にあり、ファイル名は `libwidevinecdm.so` と `libwidevinecdmadapter.so`です。 + +## プラグインを使用する + +プラグインファイルを取得した後、Electronoの`--widevine-cdm-path`コマンドラインスイッチで`widevinecdmadapter`のパスを指定し、`--widevine-cdm-version`スイッチでプラグインのバージョンを指定します。 + +__Note:__ `widevinecdmadapter` バイナリはElectronにパスが通っていても`widevinecdm` バイナリはそこに置く必要があります。 + +コマンドラインスイッチは、`app`モジュールの`ready`イベントが発火する前に通り、プラグインが使用するページは、プラグインを有効にしなければなりません。 + +サンプルコード: + +```javascript +// You have to pass the filename of `widevinecdmadapter` here, it is +// * `widevinecdmadapter.plugin` on OS X, +// * `libwidevinecdmadapter.so` on Linux, +// * `widevinecdmadapter.dll` on Windows. +app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin'); +// The version of plugin can be got from `chrome://plugins` page in Chrome. +app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866'); + +var mainWindow = null; +app.on('ready', function() { + mainWindow = new BrowserWindow({ + webPreferences: { + // The `plugins` have to be enabled. + plugins: true + } + }) +}); +``` + +## プラグインの検証 + +プラグインが動作するかどうかを検証するために、次の方法を使用できます。 + +* devtoolsを開き、Widevine CDMプラグインに`navigator.plugins`が含まれているかどうかを確認します。 +* https://shaka-player-demo.appspot.com/ を開き、`Widevine`で使用するマニフェストをロードします。 +* http://www.dash-player.com/demo/drm-test-area/を開き、`bitdash uses Widevine in your browser`をチェックし、ビデオを再生します。 diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index 473f521136bb..6f4a1c520f53 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -6,11 +6,18 @@ URL에 포함되어 있습니다. 만약 그렇지 않다면, 아마 현재 보 수 있습니다. 또한 GitHub 인터페이스의 "Switch branches/tags" 드롭다운 메뉴에서도 사용 중인 Electron 버전으로 변경할 수 있습니다. -**역주:** 한국어 번역 문서는 atom.io에 반영이 되어있지 않습니다. 따라서 번역 문서는 -GitHub 프로젝트내에서만 볼 수 있고 `master` 브랜치의 문서는 현재 개발중인 프로젝트의 +**역주:** 한국어 번역 문서는 `atom.io`에 반영되어 있지 않습니다. 따라서 번역 문서는 +GitHub 프로젝트내에서만 볼 수 있으며 `master` 브랜치의 문서는 현재 개발중인 프로젝트의 문서입니다. 한국어 번역 문서는 현재 `upstream` 원본 문서의 변경에 따라 최대한 문서의 버전을 맞추려고 노력하고 있지만 가끔 누락된 번역이 존재할 수 있습니다. +## FAQ + +Electron에 대해 자주 묻는 질문이 있습니다. 이슈를 생성하기 전에 다음 문서를 먼저 +확인해 보세요: + +* [Electron FAQ](faq/electron-faq.md) + ## 개발 가이드 * [지원하는 플랫폼](tutorial/supported-platforms.md) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index a278a5075241..43bcd4a19cfc 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -64,7 +64,7 @@ win.show(); 형태로 생성합니다. 기본값은 `true`입니다. * `acceptFirstMouse` Boolean - 윈도우가 비활성화 상태일 때 내부 컨텐츠 클릭 시 활성화 되는 동시에 단일 mouse-down 이벤트를 발생시킬지 여부. 기본값은 `false`입니다. -* `disableAutoHideCursor` Boolean - 파이핑중 자동으로 커서를 숨길지 여부. 기본값은 +* `disableAutoHideCursor` Boolean - 타이핑중 자동으로 커서를 숨길지 여부. 기본값은 `false`입니다. * `autoHideMenuBar` Boolean - `Alt`를 누르지 않는 한 어플리케이션 메뉴바를 숨길지 여부. 기본값은 `false`입니다. @@ -77,73 +77,82 @@ win.show(); 몇몇 GTK+3 데스크톱 환경에서만 작동합니다. 기본값은 `false`입니다. * `transparent` Boolean - 윈도우 창을 [투명화](frameless-window.md)합니다. 기본값은 `false`입니다. -* `type` String - 특정 플랫폼에만 적용되는 윈도우 창의 종류를 지정합니다. 기본적으로 - 이 속성이 `undefined`일 경우 표준 윈도우가 사용됩니다. 사용할 수 있는 창의 종류는 - 다음과 같습니다: - * Linux의 경우: `desktop`, `dock`, `toolbar`, `splash`, `notification` 종류를 - 사용할 수 있습니다. - * OS X의 경우: `desktop`, `textured` 종류를 사용할 수 있습니다. `textured` 종류는 - 창을 그라디언트 형태로 표현합니다 (`NSTexturedBackgroundWindowMask`) `desktop` - 종류는 데스크탑 배경 레벨에 윈도우를 배치합니다 (`kCGDesktopWindowLevel - 1`). - 참고로 이렇게 만들어진 윈도우는 포커스, 키보드, 마우스 이벤트를 받을 수 없습니다. - 하지만 편법으로 `globalShortcut`을 통해 키 입력을 받을 수 있습니다. +* `type` String - 특정 플랫폼에만 적용되는 윈도우 창의 종류를 지정합니다. 기본값은 + 일반 윈도우 입니다. 사용할 수 있는 창의 종류는 아래를 참고하세요. * `standardWindow` Boolean - OS X의 표준 윈도우를 텍스쳐 윈도우 대신 사용합니다. 기본 값은 `true`입니다. -* `titleBarStyle` String, OS X - 윈도우 타이틀 바 스타일을 지정합니다. 이 속성은 - OS X 10.10 Yosemite 이후 버전만 지원합니다. 다음 3가지 종류의 값을 사용할 수 - 있습니다: - * `default` 또는 미지정: 표준 Mac 회색 불투명 스타일을 사용합니다. - * `hidden`: 타이틀 바를 숨기고 컨텐츠 전체를 윈도우 크기에 맞춥니다. - 타이틀 바는 없어지지만 표준 창 컨트롤 ("신호등 버튼")은 왼쪽 상단에 유지됩니다. - * `hidden-inset`: `hidden` 타이틀 바 속성과 함께 신호등 버튼이 윈도우 모서리로부터 - 약간 더 안쪽으로 들어가도록합니다. +* `titleBarStyle` String, OS X - 윈도우 타이틀 바 스타일을 지정합니다. 자세한 사항은 + 아래를 참고하세요. * `webPreferences` Object - 웹 페이지 기능을 설정합니다. 사용할 수 있는 속성은 - 다음과 같습니다: - * `nodeIntegration` Boolean - node(node.js) 통합 여부. 기본값은 `true`입니다. - * `preload` String - 스크립트를 지정하면 페이지 내의 다른 스크립트가 작동하기 전에 - 로드됩니다. 여기서 지정한 스크립트는 node 통합 활성화 여부에 상관없이 언제나 모든 - node API에 접근할 수 있습니다. 이 속성의 스크립트 경로는 절대 경로로 지정해야 - 합니다. node 통합이 비활성화되어있을 경우, preload 스크립트는 node의 global - 심볼들을 다시 global 스코프로 다시 포함 시킬 수 있습니다. - [여기](process.md#event-loaded)의 예제를 참고하세요. + 아래를 참고하세요. + +`type` 속성에서 사용할 수 있는 값과 동작은 다음과 같으며, 플랫폼에 따라 다릅니다: + +* Linux의 경우, `desktop`, `dock`, `toolbar`, `splash`, `notification` 종류를 + 사용할 수 있습니다. +* OS X의 경우, `desktop`, `textured` 종류를 사용할 수 있습니다. + * `textured`는 창에 메탈 그라디언트 외관(`NSTexturedBackgroundWindowMask`)을 + 설정합니다. + * `desktop`은 데스크탑 배경 레벨(`kCGDesktopWindowLevel - 1`)에 윈도우를 + 배치합니다. 참고로 이렇게 만들어진 윈도우는 포커스, 키보드, 마우스 이벤트를 받을 + 수 없습니다. 하지만 편법으로 `globalShortcut`을 통해 키 입력을 받을 수 있습니다. + +`titleBarStyle` 속성은 OS X 10.10 Yosemite 이후 버전만 지원하며, 다음 3가지 종류의 +값을 사용할 수 있습니다: + +* `default` 또는 미지정: 표준 Mac 회색 불투명 스타일을 사용합니다. +* `hidden`: 타이틀 바를 숨기고 컨텐츠 전체를 윈도우 크기에 맞춥니다. + 타이틀 바는 없어지지만 표준 창 컨트롤 ("신호등 버튼")은 왼쪽 상단에 유지됩니다. +* `hidden-inset`: `hidden` 타이틀 바 속성과 함께 신호등 버튼이 윈도우 모서리로부터 + 약간 더 안쪽으로 들어가도록합니다. + +`webPreferences` 속성은 다음과 같은 속성을 가질 수 있습니다: + +* `nodeIntegration` Boolean - node(node.js) 통합 여부. 기본값은 `true`입니다. +* `preload` String - 스크립트를 지정하면 페이지 내의 다른 스크립트가 작동하기 전에 + 로드됩니다. 여기서 지정한 스크립트는 node 통합 활성화 여부에 상관없이 언제나 모든 + node API에 접근할 수 있습니다. 이 속성의 스크립트 경로는 절대 경로로 지정해야 + 합니다. node 통합이 비활성화되어있을 경우, preload 스크립트는 node의 global + 심볼들을 다시 global 스코프로 다시 포함 시킬 수 있습니다. + [여기](process.md#event-loaded)의 예제를 참고하세요. +* `session` [Session](session.md#class-session) - 페이지에서 사용할 세션을 + 지정합니다. Session 객체를 직접적으로 전달하는 대신, 파티션 문자열을 받는 + `partition` 옵션을 사용할 수도 있습니다. `session`과 `partition`이 같이 + 제공되었을 경우 `session`이 사용됩니다. 기본값은 기본 세션입니다. * `partition` String - 페이지에서 사용할 세션을 지정합니다. 만약 `partition`이 `persist:`로 시작하면 페이지는 지속성 세션을 사용하며 다른 모든 앱 내의 페이지에서 같은 `partition`을 사용할 수 있습니다. 만약 `persist:` 접두어로 시작하지 않으면 페이지는 인-메모리 세션을 사용합니다. 여러 페이지에서 같은 `partition`을 지정하면 같은 세션을 공유할 수 있습니다. `partition`을 지정하지 않으면 어플리케이션의 기본 세션이 사용됩니다. - * `zoomFactor` Number - 페이지의 기본 줌 값을 지정합니다. 예를 들어 `300%`를 - 표현하려면 `3.0`으로 지정합니다. 기본값은 `1.0`입니다. - * `javascript` Boolean - 자바스크립트를 활성화합니다. 기본값은 `false`입니다. - * `webSecurity` Boolean - `false`로 지정하면 same-origin 정책을 비활성화합니다. - (이 속성은 보통 사람들에 의해 웹 사이트를 테스트할 때 사용합니다) 그리고 - `allowDisplayingInsecureContent`와 `allowRunningInsecureContent` 두 속성을 - 사용자가 `true`로 지정되지 않은 경우 `true`로 지정합니다. 기본값은 - `true`입니다. - * `allowDisplayingInsecureContent` Boolean - https 페이지에서 http URL에서 - 로드한 이미지 같은 리소스를 표시할 수 있도록 허용합니다. 기본값은 `false`입니다. - * `allowRunningInsecureContent` Boolean - https 페이지에서 http URL에서 로드한 - JavaScript와 CSS 또는 플러그인을 실행시킬 수 있도록 허용합니다. 기본값은 - `false`입니다. - * `images` Boolean - 이미지 지원을 활성화합니다. 기본값은 `true`입니다. - * `textAreasAreResizable` Boolean - HTML TextArea 요소의 크기를 재조정을 - 허용합니다. 기본값은 `true`입니다. - * `webgl` Boolean - WebGL 지원을 활성화합니다. 기본값은 `true`입니다. - * `webaudio` Boolean - WebAudio 지원을 활성화합니다. 기본값은 `true`입니다. - * `plugins` Boolean - 플러그인 활성화 여부를 지정합니다. 기본값은 `false`입니다. - * `experimentalFeatures` Boolean - Chrome의 실험적인 기능을 활성화합니다. - 기본값은 `false`입니다. - * `experimentalCanvasFeatures` Boolean - Chrome의 실험적인 캔버스(canvas) 기능을 - 활성화합니다. 기본값은 `false`입니다. - * `overlayScrollbars` Boolean - 오버레이 스크롤바를 활성화합니다. 기본값은 - `false`입니다. - * `sharedWorker` Boolean - SharedWorker 기능을 활성화합니다. 기본값은 - `false`입니다. - * `directWrite` Boolean - Windows에서 폰트 랜더링을 위해 DirectWrite를 - 사용하는지를 지정합니다. 기본값은 `true`입니다. - * `pageVisibility` Boolean - 현재 윈도우의 가시성을 반영하는 대신 페이지가 - visible 또는 hidden 중 지정된 상태를 계속 유지하도록 합니다. 이 속성을 `true`로 - 지정하면 DOM 타이머의 스로틀링을 방지할 수 있습니다. 기본값은 `false`입니다. +* `zoomFactor` Number - 페이지의 기본 줌 값을 지정합니다. 예를 들어 `300%`를 + 표현하려면 `3.0`으로 지정합니다. 기본값은 `1.0`입니다. +* `javascript` Boolean - 자바스크립트를 활성화합니다. 기본값은 `false`입니다. +* `webSecurity` Boolean - `false`로 지정하면 same-origin 정책을 비활성화합니다. + (이 속성은 보통 사람들에 의해 웹 사이트를 테스트할 때 사용합니다) 그리고 + `allowDisplayingInsecureContent`와 `allowRunningInsecureContent` 두 속성을 + 사용자가 `true`로 지정되지 않은 경우 `true`로 지정합니다. 기본값은 + `true`입니다. +* `allowDisplayingInsecureContent` Boolean - https 페이지에서 http URL에서 + 로드한 이미지 같은 리소스를 표시할 수 있도록 허용합니다. 기본값은 `false`입니다. +* `allowRunningInsecureContent` Boolean - https 페이지에서 http URL에서 로드한 + JavaScript와 CSS 또는 플러그인을 실행시킬 수 있도록 허용합니다. 기본값은 + `false`입니다. +* `images` Boolean - 이미지 지원을 활성화합니다. 기본값은 `true`입니다. +* `textAreasAreResizable` Boolean - HTML TextArea 요소의 크기를 재조정을 + 허용합니다. 기본값은 `true`입니다. +* `webgl` Boolean - WebGL 지원을 활성화합니다. 기본값은 `true`입니다. +* `webaudio` Boolean - WebAudio 지원을 활성화합니다. 기본값은 `true`입니다. +* `plugins` Boolean - 플러그인 활성화 여부를 지정합니다. 기본값은 `false`입니다. +* `experimentalFeatures` Boolean - Chrome의 실험적인 기능을 활성화합니다. + 기본값은 `false`입니다. +* `experimentalCanvasFeatures` Boolean - Chrome의 실험적인 캔버스(canvas) 기능을 + 활성화합니다. 기본값은 `false`입니다. +* `directWrite` Boolean - Windows에서 폰트 랜더링을 위해 DirectWrite를 + 사용하는지를 지정합니다. 기본값은 `true`입니다. +* `blinkFeatures` String - `CSSVariables,KeyboardEventKey`같은 `,`로 구분된 + 기능 문자열들의 리스트입니다. 지원하는 전체 기능 문자열들은 + [setFeatureEnabledFromString][blink-feature-string] 함수에서 찾을 수 있습니다. ## Events @@ -282,7 +291,8 @@ someWindow.on('app-command', function(e, cmd) { ### `BrowserWindow.getFocusedWindow()` -어플리케이션에서 포커스된 윈도우를 반환합니다. +어플리케이션에서 포커스된 윈도우를 반환합니다. 포커스된 윈도우가 없을 경우 `null`을 +반환합니다. ### `BrowserWindow.fromWebContents(webContents)` @@ -559,12 +569,20 @@ Kiosk(키오스크) 모드를 설정합니다. 현재 윈도우가 kiosk 모드인지 여부를 반환합니다. +### `win.getNativeWindowHandle()` + +`Buffer` 상의 플랫폼에 따른 윈도우 핸들을 반환합니다. + +핸들의 타입에 따라 적절히 캐스팅됩니다. Windows의 `HWND`, OS X의 `NSView*`, Linux의 +`Window` (`unsigned long`)를 예로 들 수 있습니다. + ### `win.hookWindowMessage(message, callback)` _Windows_ * `message` Integer * `callback` Function -Windows 메시지 훅을 등록합니다. `callback`은 WndProc에서 메시지를 받았을 때 호출됩니다. +Windows 메시지 훅을 등록합니다. `callback`은 WndProc에서 메시지를 받았을 때 +호출됩니다. ### `win.isWindowMessageHooked(message)` _Windows_ @@ -747,3 +765,5 @@ Linux 플랫폼에선 Unity 데스크톱 환경만 지원합니다. 그리고 * `ignore` Boolean 윈도우에서 일어나는 모든 마우스 이벤트를 무시합니다. + +[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 diff --git a/docs-translations/ko-KR/api/chrome-command-line-switches.md b/docs-translations/ko-KR/api/chrome-command-line-switches.md index f13f37920aea..defa1e04841b 100644 --- a/docs-translations/ko-KR/api/chrome-command-line-switches.md +++ b/docs-translations/ko-KR/api/chrome-command-line-switches.md @@ -119,6 +119,14 @@ TLS fallback에서 사용할 SSL/TLS 최소 버전을 지정합니다. ("tls1", SSL 암호화를 비활성화할 대상 목록을 지정합니다. (`,`로 구분) +## --disable-renderer-backgrounding + +Chromium이 랜더러 프로세스의 보이지 않는 페이지의 우선순위를 낮추는 것을 방지합니다. + +이 플래그는 전역적이며 모든 랜더러 프로세스에 적용됩니다. 만약 하나의 윈도우창에만 +스로틀링을 비활성화하고 싶다면 [조용한 오디오를 재생하는][play-silent-audio] 핵을 사용할 +수 있습니다. + ## --enable-logging Chromium의 로그를 콘솔에 출력합니다. @@ -149,3 +157,4 @@ Chromium의 로그를 콘솔에 출력합니다. [app]: app.md [append-switch]: app.md#appcommandlineappendswitchswitch-value [ready]: app.md#event-ready +[play-silent-audio]: https://github.com/atom/atom/pull/9485/files diff --git a/docs-translations/ko-KR/api/dialog.md b/docs-translations/ko-KR/api/dialog.md index 3fff8683cd6d..9147f1c5bdca 100644 --- a/docs-translations/ko-KR/api/dialog.md +++ b/docs-translations/ko-KR/api/dialog.md @@ -78,7 +78,8 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' `filters`를 지정하면 유저가 저장 가능한 파일 형식을 지정할 수 있습니다. 사용 방법은 `dialog.showOpenDialog`의 `filters` 속성과 같습니다. -`callback`이 전달되면 메서드가 비동기로 작동되며 결과는 `callback(filename)`을 통해 전달됩니다. +`callback`이 전달되면 메서드가 비동기로 작동되며 결과는 `callback(filename)`을 통해 +전달됩니다. ### `dialog.showMessageBox([browserWindow, ]options[, callback])` @@ -88,6 +89,8 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' 하나를 사용할 수 있습니다. Windows에선 따로 `icon`을 설정하지 않은 이상 "question"과 "info"는 같은 아이콘으로 표시됩니다. * `buttons` Array - 버튼들의 라벨을 포함한 배열입니다. + * `defaultId` Integer - 메시지 박스가 열렸을 때 기본적으로 선택될 버튼 배열의 + 버튼 인덱스입니다. * `title` String - 대화 상자의 제목입니다. 몇몇 플랫폼에선 보이지 않을 수 있습니다. * `message` String - 대화 상자의 본문 내용입니다. * `detail` String - 메시지의 추가 정보입니다. diff --git a/docs-translations/ko-KR/api/frameless-window.md b/docs-translations/ko-KR/api/frameless-window.md index e13794e0fdfd..9fa8fafc4c4f 100644 --- a/docs-translations/ko-KR/api/frameless-window.md +++ b/docs-translations/ko-KR/api/frameless-window.md @@ -38,8 +38,8 @@ var win = new BrowserWindow({ transparent: true, frame: false }); ### API의 한계 * 투명한 영역을 통과하여 클릭할 수 없습니다. 우리는 이 문제를 해결하기 위해 API를 - 제공할 예정이었지만 현재로써는 [upstream 버그](https://code.google.com/p/chromium/issues/detail?id=387234)로 - 인해 중단된 상태입니다. + 제공할 예정이며 자세한 내용은 + [이슈](https://github.com/atom/electron/issues/1335)를 참고하세요. * 투명한 창은 크기를 조절할 수 없습니다. `resizable` 속성을 `true`로 할 경우 몇몇 플랫폼에선 크래시가 일어납니다. * `blur` 필터는 웹 페이지에서만 적용됩니다. 윈도우 아래 컨텐츠에는 블러 효과를 적용할 diff --git a/docs-translations/ko-KR/api/global-shortcut.md b/docs-translations/ko-KR/api/global-shortcut.md index 9617d463e87e..658f723692a6 100644 --- a/docs-translations/ko-KR/api/global-shortcut.md +++ b/docs-translations/ko-KR/api/global-shortcut.md @@ -1,6 +1,6 @@ -# global-shortcut +# globalSortcut -`global-shortcut` 모듈은 운영체제의 전역 키보드 단축키를 등록/해제 하는 방법을 +`globalShortcut` 모듈은 운영체제의 전역 키보드 단축키를 등록/해제 하는 방법을 제공합니다. 이 모듈을 사용하여 사용자가 다양한 작업을 편하게 할 수 있도록 단축키를 정의 할 수 있습니다. @@ -38,26 +38,29 @@ app.on('will-quit', function() { ## Methods -`global-shortcut` 모듈은 다음과 같은 메서드를 가지고 있습니다: +`globalShortcut` 모듈은 다음과 같은 메서드를 가지고 있습니다: ### `globalShortcut.register(accelerator, callback)` * `accelerator` [Accelerator](accelerator.md) * `callback` Function -`accelerator`로 표현된 전역 단축키를 등록합니다. 유저로부터 등록된 단축키가 눌렸을 -경우 `callback` 함수가 호출됩니다. `accelerator` 단축키가 등록되었을 경우 -`true`를 반환합니다. 그 외엔 `false`를 반환합니다. 예를 들어 지정한 -`accelerator`가 이미 다른 호출자 또는 네이티브 어플리케이션에서 등록된 상태를 -생각할 수 있습니다. +`accelerator`의 전역 단축키를 등록합니다. 유저로부터 등록된 단축키가 눌렸을 경우 +`callback` 함수가 호출됩니다. +accelerator가 이미 다른 어플리케이션에서 사용 중일 경우, 이 작업은 조용히 실패합니다. +이러한 동작은 어플리케이션이 전역 키보드 단축키를 가지고 충돌이 일어나지 않도록 하기 +위해 운영체제에 의해 예정된 동작입니다. ### `globalShortcut.isRegistered(accelerator)` * `accelerator` [Accelerator](accelerator.md) -지정된 `accelerator` 단축키가 등록되었는지 여부를 확인합니다. 반환값은 boolean값 -입니다. +지정된 `accelerator` 단축키가 등록되었는지 여부를 확인합니다. + +Accelerator가 이미 다른 어플리케이션에서 사용 중일 경우, 여전히 `false`를 반환합니다. +이러한 동작은 어플리케이션이 전역 키보드 단축키를 가지고 충돌이 일어나지 않도록 하기 +위해 운영체제에 의해 예정된 동작입니다. ### `globalShortcut.unregister(accelerator)` diff --git a/docs-translations/ko-KR/api/menu.md b/docs-translations/ko-KR/api/menu.md index f01021ae1082..61bf92bf76d5 100644 --- a/docs-translations/ko-KR/api/menu.md +++ b/docs-translations/ko-KR/api/menu.md @@ -195,7 +195,7 @@ if (process.platform == 'darwin') { ); } -menu = Menu.buildFromTemplate(template); +var menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); ``` diff --git a/docs-translations/ko-KR/api/remote.md b/docs-translations/ko-KR/api/remote.md index 935b09865130..3a09084acdef 100644 --- a/docs-translations/ko-KR/api/remote.md +++ b/docs-translations/ko-KR/api/remote.md @@ -34,6 +34,9 @@ win.loadURL('https://github.com'); 않습니다. 대신에 이 `BrowserWindow` 객체는 메인 프로세스에서 생성되며 랜더러 프로세스에 `win` 객체와 같이 이에 대응하는 remote 객체를 반환합니다. +참고로 remote를 통해선 [enumerable 속성](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)을 +가진 프로퍼티에만 접근할 수 있습니다. + ## Remote 객체의 생명 주기 Electron은 랜더러 프로세스의 remote 객체가 살아있는 한(다시 말해서 GC(garbage diff --git a/docs-translations/ko-KR/api/screen.md b/docs-translations/ko-KR/api/screen.md index dfd85fb446e1..6cc6535235b6 100644 --- a/docs-translations/ko-KR/api/screen.md +++ b/docs-translations/ko-KR/api/screen.md @@ -54,6 +54,24 @@ app.on('ready', function() { }); ``` +## `Display` 객체 + +`Display` 객체는 시스템에 연결된 물리적인 디스플레이를 표현합니다. 헤드레스(headless) +시스템에선 가짜 `Display` 객체가 보여지거나 리모트(remote), 가상 디스플레이에 +해당하게 됩니다. + +* `display` object + * `id` Integer - 디스플레이에 관련된 유일 식별자. + * `rotation` Integer - 값은 0, 1, 2, 3이 될 수 있고, 각 값은 시계 방향을 기준으로 + 0, 90, 180, 270도의 화면 회전 상태로 표현됩니다. + * `scaleFactor` Number - 기기의 픽셀 스케일 크기. + * `touchSupport` String - 터치 스크린의 여부, `available`, `unavailable`, + `unknown` 값으로 반환됩니다. + * `bounds` Object + * `size` Object + * `workArea` Object + * `workAreaSize` Object + ## Events `screen` 모듈은 다음과 같은 이벤트를 가지고 있습니다: diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index c857e111571f..5da0dd01276b 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -178,47 +178,48 @@ session.defaultSession.cookies.set(cookie, function(error) { 웹 스토리지의 데이터를 비웁니다. +#### `ses.flushStorageData()` + +디스크에 사용되지 않은 DOMStorage 데이터를 모두 덮어씌웁니다. + #### `ses.setProxy(config, callback)` -* `config` String +* `config` Object + * `pacScript` String - PAC 파일과 관련된 URL입니다. + * `proxyRules` String - 사용할 프록시의 규칙을 나타냅니다. * `callback` Function - 작업이 완료되면 호출됩니다. -세션에 사용할 프록시 `config`를 분석하고 프록시를 적용합니다. +프록시 설정을 적용합니다. -세션에 사용할 프록시는 `config`가 PAC 주소일 경우 그대로 적용하고, 다른 형식일 경우 -다음 규칙에 따라 적용합니다. +`pacScript`와 `proxyRules`이 같이 제공되면 `proxyRules` 옵션은 무시되며 `pacScript` +컨픽만 적용됩니다. + +`proxyRules`는 다음과 같은 규칙을 따릅니다: ``` -config = scheme-proxies[";"] -scheme-proxies = ["="] -url-scheme = "http" | "https" | "ftp" | "socks" -proxy-uri-list = [","] -proxy-uri = ["://"][":"] - - 예시: - "http=foopy:80;ftp=foopy2" -- http:// URL에 "foopy:80" HTTP 프록시를 - 사용합니다. "foopy2:80" 는 ftp:// URL에 - 사용됩니다. - "foopy:80" -- 모든 URL에 "foopy:80" 프록시를 사용합니다. - "foopy:80,bar,direct://" -- 모든 URL에 "foopy:80" HTTP 프록시를 - 사용합니다. 문제가 발생하여 "foopy:80"를 - 사용할 수 없는 경우 "bar"를 대신 사용하여 - 장애를 복구하며 그 다음 문제가 생긴 경우 - 프록시를 사용하지 않습니다. - "socks4://foopy" -- 모든 URL에 "foopy:1000" SOCKS v4 프록시를 - 사용합니다. - "http=foopy,socks5://bar.com -- http:// URL에 "foopy" HTTP 프록시를 - 사용합니다. 문제가 발생하여 "foopy"를 - 사용할 수 없는 경우 SOCKS5 "bar.com" - 프록시를 대신 사용합니다. - "http=foopy,direct:// -- http:// URL에 "foopy" HTTP 프록시를 - 사용합니다. 그리고 문제가 발생하여 "foopy"를 - 사용할 수 없는 경우 프록시를 사용하지 않습니다. - "http=foopy;socks=foopy2 -- http:// URL에 "foopy" HTTP 프록시를 - 사용합니다. 그리고 "socks4://foopy2" - 프록시를 다른 모든 URL에 사용합니다. +proxyRules = schemeProxies[";"] +schemeProxies = ["="] +urlScheme = "http" | "https" | "ftp" | "socks" +proxyURIList = [","] +proxyURL = ["://"][":"] ``` +예시: +* `http=foopy:80;ftp=foopy2` - http:// URL에 `foopy:80` HTTP 프록시를 사용합니다. + `foopy2:80` 는 ftp:// URL에 사용됩니다. +* `foopy:80` - 모든 URL에 `foopy:80` 프록시를 사용합니다. +* `foopy:80,bar,direct://` - 모든 URL에 `foopy:80` HTTP 프록시를 사용합니다. + 문제가 발생하여 `foopy:80`를 사용할 수 없는 경우 `bar`를 대신 사용하여 장애를 + 복구하며 그 다음 문제가 생긴 경우 프록시를 사용하지 않습니다. +* `socks4://foopy` - 모든 URL에 `foopy:1000` SOCKS v4 프록시를 사용합니다. +* `http=foopy,socks5://bar.com` - http:// URL에 `foopy` HTTP 프록시를 사용합니다. + 문제가 발생하여 `foopy`를 사용할 수 없는 경우 SOCKS5 `bar.com` 프록시를 대신 + 사용합니다. +* `http=foopy,direct://` - http:// URL에 `foopy` HTTP 프록시를 사용합니다. 그리고 + 문제가 발생하여 `foopy`를 사용할 수 없는 경우 프록시를 사용하지 않습니다. +* `http=foopy;socks=foopy2` - http:// URL에 `foopy` HTTP 프록시를 사용합니다. + 그리고 `socks4://foopy2` 프록시를 다른 모든 URL에 사용합니다. + ### `app.resolveProxy(url, callback)` * `url` URL diff --git a/docs-translations/ko-KR/api/web-frame.md b/docs-translations/ko-KR/api/web-frame.md index 8181c0f3bf21..f77ed68f9073 100644 --- a/docs-translations/ko-KR/api/web-frame.md +++ b/docs-translations/ko-KR/api/web-frame.md @@ -88,4 +88,20 @@ webFrame.setSpellCheckProvider("en-US", true, { `scheme`를 보안된 스킴으로 등록합니다. 리소스에 대해 보안 정책을 우회하며, ServiceWorker의 등록과 fetch API를 사용할 수 있도록 지원합니다. +### `webFrame.insertText(text)` + +* `text` String + +포커스된 요소에 `text`를 삽입합니다. + +### `webFrame.executeJavaScript(code[, userGesture])` + +* `code` String +* `userGesture` Boolean (optional) - 기본값은 `false` 입니다. + +페이지에서 `code`를 실행합니다. + +브라우저 윈도우에서 어떤 `requestFullScreen` 같은 HTML API는 사용자의 승인이 +필요합니다. `userGesture`를 `true`로 설정하면 이러한 제약을 제거할 수 있습니다. + [spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs-translations/ko-KR/api/web-view-tag.md b/docs-translations/ko-KR/api/web-view-tag.md index 591721752f30..a6a84a1ceeee 100644 --- a/docs-translations/ko-KR/api/web-view-tag.md +++ b/docs-translations/ko-KR/api/web-view-tag.md @@ -340,6 +340,12 @@ Service worker에 대한 개발자 도구를 엽니다. 페이지에서 `replaceMisspelling` 커맨드를 실행합니다. +### `.insertText(text)` + +* `text` String + +포커스된 요소에 `text`를 삽입합니다. + ### `webContents.findInPage(text[, options])` * `text` String - 찾을 컨텐츠, 반드시 공백이 아니여야 합니다. diff --git a/docs-translations/ko-KR/development/build-instructions-windows.md b/docs-translations/ko-KR/development/build-instructions-windows.md index a5b0de78bdfb..3c7932479ecd 100644 --- a/docs-translations/ko-KR/development/build-instructions-windows.md +++ b/docs-translations/ko-KR/development/build-instructions-windows.md @@ -5,7 +5,7 @@ ## 빌드전 요구 사항 * Windows 7 / Server 2008 R2 또는 최신 버전 -* Visual Studio 2013 Update 5 - [VS 2013 커뮤니티 에디션 무료 다운로드](http://www.visualstudio.com/products/visual-studio-community-vs) +* Visual Studio 2013 Update 4 - [VS 2013 커뮤니티 에디션 무료 다운로드](https://www.visualstudio.com/news/vs2013-community-vs) * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) * [Git](http://git-scm.com) diff --git a/docs-translations/ko-KR/faq/electron-faq.md b/docs-translations/ko-KR/faq/electron-faq.md new file mode 100644 index 000000000000..54ecf64e435c --- /dev/null +++ b/docs-translations/ko-KR/faq/electron-faq.md @@ -0,0 +1,81 @@ +# Electron FAQ + +## 언제 Electron이 최신 버전의 Chrome으로 업그레이드 되나요? + +Electron의 Chrome 버전은 보통 새로운 Chrome 안정 버전이 릴리즈 된 이후 1주 내지 2주 +내로 업데이트됩니다. + +또한 우리는 크롬의 안정된 채널만을 이용합니다, 만약 중요한 수정이 베타 또는 개발 채널인 +경우, 우리는 해당 버전 대신 이전 버전을 다시 사용합니다. + +## Electron은 언제 최신 버전의 Node.js로 업그레이드 하나요? + +새로운 버전의 Node.js가 릴리즈 되면, 우리는 보통 Electron을 업그레이드 하기 전에 한 +달 정도 대기합니다. 이렇게 하면 새로운 Node.js 버전을 업데이트 함으로써 발생하는 +버그들을 피할 수 있습니다. 이러한 상황은 자주 발생합니다. + +Node.js의 새로운 기능은 보통 V8 업그레이드에서 가져옵니다. Electron은 Chrome +브라우저에 탑재된 V8을 사용하고 있습니다. 눈부신 새로운 Node.js 버전의 자바스크립트 +기능은 보통 이미 Electron에 있습니다. + +## 제작한 어플리케이션의 윈도우/트레이가 몇 분 후에나 나타납니다. + +이러한 문제가 발생하는 이유는 보통 윈도우/트레이를 담은 변수에 가비지 컬렉션이 작동해서 +그럴 가능성이 높습니다. + +이러한 문제를 맞닥뜨린 경우 다음 문서를 읽어보는 것이 좋습니다: + +* [메모리 관리][memory-management] +* [변수 스코프][variable-scope] + +만약 빠르게 고치고 싶다면, 다음과 같이 변수를 전역 변수로 만드는 방법이 있습니다: + +```javascript +app.on('ready', function() { + var tray = new Tray('/path/to/icon.png'); +}) +``` + +를 이렇게: + +```javascript +var tray = null; +app.on('ready', function() { + tray = new Tray('/path/to/icon.png'); +}) +``` + +## Electron에서 jQuery/RequireJS/Meteor/AngularJS를 사용할 수 없습니다. + +Node.js가 Electron에 합쳐졌기 때문에, DOM에 `module`, `exports`, `require` 같은 +몇 가지 심볼들이 추가됬습니다. 따라서 같은 이름의 심볼을 사용하는 몇몇 라이브러리들과 +충돌이 발생할 수 있습니다. + +이러한 문제를 해결하려면, Electron에서 node 포함을 비활성화시켜야 합니다: + +```javascript +// 메인 프로세스에서. +var mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: false + } +}); +``` + +하지만 Node.js의 기능과 Electron API를 유지하고 싶다면 페이지에 다른 라이브러리를 +추가하기 전에 심볼들의 이름을 변경해야 합니다: + +```html + + + + +``` + +[memory-management]: https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_Management +[variable-scope]: https://msdn.microsoft.com/library/bzt2dkta(v=vs.94).aspx diff --git a/docs-translations/ko-KR/tutorial/application-distribution.md b/docs-translations/ko-KR/tutorial/application-distribution.md index 73839f248b5b..34eb8cee71f7 100644 --- a/docs-translations/ko-KR/tutorial/application-distribution.md +++ b/docs-translations/ko-KR/tutorial/application-distribution.md @@ -106,14 +106,6 @@ MyApp.app/Contents 아이콘은 [.desktop](https://developer.gnome.org/integration-guide/stable/desktop-files.html.en) 파일을 사용하여 지정할 수 있습니다. -### 역주-자동화 - -어플리케이션 배포시 Electron의 리소스를 일일이 수정하는 것은 매우 반복적이고 복잡합니다. -하지만 이 작업을 자동화 시킬 수 있는 몇가지 방법이 있습니다: - -* [electron-builder](https://github.com/loopline-systems/electron-builder) -* [electron-packager](https://github.com/maxogden/electron-packager) - ## Electron 소스코드를 다시 빌드하여 리소스 수정하기 또한 Electron 소스코드를 다시 빌드할 때 어플리케이션 이름을 변경할 수 있습니다. @@ -146,3 +138,11 @@ Electron의 소스코드를 수정하고 다시 빌드하는 작업은 상당히 이 툴을 사용하면 자동으로 `.gyp`파일을 수정하고 다시 빌드합니다. 그리고 어플리케이션의 네이티브 Node 모듈 또한 새로운 실행파일 이름으로 일치시킵니다. + +## 패키징 툴 + +어플리케이션을 일일이 수동으로 패키지로 만드는 대신, 서드 파티 패키징 툴을 사용하며 +이러한 작업을 자동화 시킬 수 있습니다: + +* [electron-packager](https://github.com/maxogden/electron-packager) +* [electron-builder](https://github.com/loopline-systems/electron-builder) diff --git a/docs-translations/ko-KR/tutorial/debugging-main-process.md b/docs-translations/ko-KR/tutorial/debugging-main-process.md index ee7e8432d420..bea8ce6ccc0c 100644 --- a/docs-translations/ko-KR/tutorial/debugging-main-process.md +++ b/docs-translations/ko-KR/tutorial/debugging-main-process.md @@ -23,13 +23,30 @@ __참고:__ Electron은 현재 node-inspector 유틸리티와 호환성 문제 node-inspector 콘솔 내에서 메인 프로세스의 `process` 객체를 탐색할 경우 크래시가 발생할 수 있습니다. -### 1. [node-inspector][node-inspector] 서버 시작 +### 1. [node-gyp 필수 도구][node-gyp-required-tools]를 설치했는지 확인 + +### 2. [node-inspector][node-inspector] 설치 ```bash -$ node-inspector +$ npm install node-inspector ``` -### 2. Electron용 디버그 모드 활성화 +### 3. 패치된 버전의 `node-pre-gyp` 설치 + +```bash +$ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electron-runtime-in-find +``` + +### 4. Electron용 `node-inspector` `v8` 모듈을 재 컴파일 (target을 사용하는 Electron의 버전에 맞춰 변경) + +```bash +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +``` + +또한 [네이티브 모듈을 사용하는 방법](how-to-install-native-modules) 문서도 참고해보세요. + +### 5. Electron 디버그 모드 활성화 다음과 같이 debung 플래그로 Electron을 실행할 수 있습니다: @@ -43,9 +60,18 @@ $ electron --debug=5858 your/app $ electron --debug-brk=5858 your/app ``` -### 3. 디버그 UI 로드 +### 5. Electron을 사용하는 [node-inspector][node-inspector] 시작 + +```bash +$ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin/inspector.js +``` + +### 6. 디버거 UI 로드 Chrome 브라우저에서 http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 주소에 -접속합니다. (기본포트 또는 지정한 포트로 접속) +접속합니다. (기본 포트 또는 지정한 포트로 접속) 엔트리의 라인이 debug-brk로 시작하는 +경우 일시정지 버튼을 클릭해야 할 수도 있습니다. [node-inspector]: https://github.com/node-inspector/node-inspector +[node-gyp-required-tools]: https://github.com/nodejs/node-gyp#installation +[how-to-install-native-modules]: using-native-node-modules.md#네이티브-모듈을-설치하는-방법 diff --git a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md index d2e50beb6c18..91fc59a8f2ef 100644 --- a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md +++ b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md @@ -34,21 +34,10 @@ myNotification.onclick = function () { 만들어 놔야 합니다. 이 바로가기는 반드시 시작 화면에 설치되어 있어야 합니다. 참고로 반드시 시작 화면에 고정 할 필요는 없습니다. * Windows 7과 그 이하 버전은 데스크톱 알림을 지원하지 않습니다. - 혹시 "풍선 팝업 알림" 기능을 찾는다면 [Tray API](tray-balloon)를 사용하세요. + 혹시 "풍선 팝업 알림" 기능을 찾는다면 [Tray API][tray-balloon]를 사용하세요. -이미지를 데스크톱 알림에 사용하려면 알림 옵션의 `icon` 속성에 로컬 이미지 파일 -(`png` 권장)을 지정하면 됩니다. 데스크톱 알림은 잘못된 경로를 지정하거나 `http/https` -기반의 URL을 지정해도 이미지가 보이지 않을 뿐 정상 작동합니다. - -```javascript -new Notification('Title', { - body: 'Notification with icon', - icon: 'file:///C:/Users/feriese/Desktop/icon.png' -}); -``` - -또한 본문의 최대 길이는 250자 입니다. Windows 개발팀에선 알림 문자열을 200자 이하로 -유지하는 것을 권장합니다. +또한 알림 본문의 최대 길이는 250자 입니다. Windows 개발팀에선 알림 문자열을 200자 +이하로 유지하는 것을 권장합니다. ### Linux diff --git a/docs-translations/zh-CN/tutorial/desktop-environment-integration.md b/docs-translations/zh-CN/tutorial/desktop-environment-integration.md index 6ded331627e9..614aeed708f6 100644 --- a/docs-translations/zh-CN/tutorial/desktop-environment-integration.md +++ b/docs-translations/zh-CN/tutorial/desktop-environment-integration.md @@ -31,7 +31,7 @@ app.clearRecentDocuments(); ## 自定义的鱼眼菜单(OS X) OS X 可以让开发者定制自己的菜单,通常会包含一些常用特性的快捷方式。 ### 菜单中的终端 -[Dock menu of Terminal.app][6] +![Dock menu of Terminal.app][6] 使用 `app.dock.setMenu` API 来设置你的菜单,这仅在 OS X 上可行: ```javascript diff --git a/docs-translations/zh-CN/tutorial/supported-platforms.md b/docs-translations/zh-CN/tutorial/supported-platforms.md index a819e3a0817c..d4d42ec03ff3 100644 --- a/docs-translations/zh-CN/tutorial/supported-platforms.md +++ b/docs-translations/zh-CN/tutorial/supported-platforms.md @@ -20,7 +20,7 @@ Ubuntu 12.04 下编译的,`arm` 版的二进制文件是在 ARM v7(硬浮点 Debian Wheezy 版本的 NEON)下完成的。 预编译二进制文件是否能够运行,取决于其中是否包括了编译平台链接的库,所以只有 Ubuntu 12.04 -可以保证正常工作,但是以下的平台也被正事可以运行 Electron的预编译版本: +可以保证正常工作,但是以下的平台也被证实可以运行 Electron的预编译版本: * Ubuntu 12.04 及更新 * Fedora 21 diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 8fb64b561224..62eefd498a65 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -47,6 +47,8 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `maxWidth` Integer - Window's maximum width. Default is no limit. * `maxHeight` Integer - Window's maximum height. Default is no limit. * `resizable` Boolean - Whether window is resizable. Default is `true`. + * `movable` Boolean - Whether window is movable. This is only implemented + on OS X. Default is `true`. * `alwaysOnTop` Boolean - Whether the window should always stay on top of other windows. Default is `false`. * `fullscreen` Boolean - Whether the window should show in fullscreen. When @@ -63,9 +65,10 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `frame` Boolean - Specify `false` to create a [Frameless Window](frameless-window.md). Default is `true`. * `acceptFirstMouse` Boolean - Whether the web view accepts a single - mouse-down event that simultaneously activates the window. Default is `false`. - * `disableAutoHideCursor` Boolean - Whether to hide cursor when typing. Default - is `false`. + mouse-down event that simultaneously activates the window. Default is + `false`. + * `disableAutoHideCursor` Boolean - Whether to hide cursor when typing. + Default is `false`. * `autoHideMenuBar` Boolean - Auto hide the menu bar unless the `Alt` key is pressed. Default is `false`. * `enableLargerThanScreen` Boolean - Enable the window to be resized larger @@ -78,11 +81,11 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `transparent` Boolean - Makes the window [transparent](frameless-window.md). Default is `false`. * `type` String - The type of window, default is normal window. See more about - this bellow. + this below. * `titleBarStyle` String - The style of window title bar. See more about this - bellow. + below. * `webPreferences` Object - Settings of web page's features. See more about - this bellow. + this below. The possible values and behaviors of `type` option are platform dependent, supported values are: @@ -119,12 +122,17 @@ The `webPreferences` option is an object that can have following properties: When node integration is turned off, the preload script can reintroduce Node global symbols back to the global scope. See example [here](process.md#event-loaded). -* `partition` String - Sets the session used by the page. If `partition` - starts with `persist:`, the page will use a persistent session available to - all pages in the app with the same `partition`. if there is no `persist:` - prefix, the page will use an in-memory session. By assigning the same - `partition`, multiple pages can share the same session. If the `partition` - is unset then default session of the app will be used. +* `session` [Session](session.md#class-session) - Sets the session used by the + page. Instead of passing the Session object directly, you can also choose to + use the `partition` option instead, which accepts a partition string. When + both `session` and `partition` are provided, `session` would be preferred. + Default is the default session. +* `partition` String - Sets the session used by the page according to the + session's partition string. If `partition` starts with `persist:`, the page + will use a persistent session available to all pages in the app with the + same `partition`. if there is no `persist:` prefix, the page will use an + in-memory session. By assigning the same `partition`, multiple pages can share + the same session. Default is the default session. * `zoomFactor` Number - The default zoom factor of the page, `3.0` represents `300%`. Default is `1.0`. * `javascript` Boolean - Enables JavaScript support. Default is `true`. @@ -157,7 +165,8 @@ The `webPreferences` option is an object that can have following properties: The `BrowserWindow` object emits the following events: -**Note:** Some events are only available on specific operating systems and are labeled as such. +**Note:** Some events are only available on specific operating systems and are +labeled as such. ### Event: 'page-title-updated' @@ -287,7 +296,7 @@ Returns an array of all opened browser windows. ### `BrowserWindow.getFocusedWindow()` -Returns the window that is focused in this application. +Returns the window that is focused in this application, otherwise returns `null`. ### `BrowserWindow.fromWebContents(webContents)` @@ -341,7 +350,8 @@ The unique ID of this window. Objects created with `new BrowserWindow` have the following instance methods: -**Note:** Some methods are only available on specific operating systems and are labeled as such. +**Note:** Some methods are only available on specific operating systems and are +labeled as such. ### `win.destroy()` @@ -571,6 +581,13 @@ Enters or leaves the kiosk mode. Returns whether the window is in kiosk mode. +### `win.getNativeWindowHandle()` + +Returns the platform-specific handle of the window as `Buffer`. + +The native type of the handle is `HWND` on Windows, `NSView*` on OS X, and +`Window` (`unsigned long`) on Linux. + ### `win.hookWindowMessage(message, callback)` _Windows_ * `message` Integer diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 80a5289e93f5..84a22ef692b3 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -87,6 +87,8 @@ will be passed via `callback(filename)` `"warning"`. On Windows, "question" displays the same icon as "info", unless you set an icon using the "icon" option. * `buttons` Array - Array of texts for buttons. + * `defaultId` Integer - Index of the button in the buttons array which will + be selected by default when the message box opens. * `title` String - Title of the message box, some platforms will not show it. * `message` String - Content of the message box. * `detail` String - Extra information of the message. diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index e70749f2894c..f9afa55af517 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -38,8 +38,7 @@ var win = new BrowserWindow({ transparent: true, frame: false }); ### Limitations * You can not click through the transparent area. We are going to introduce an - API to set window shape to solve this, but currently blocked at an - [upstream bug](https://code.google.com/p/chromium/issues/detail?id=387234). + API to set window shape to solve this, see [our issue](https://github.com/atom/electron/issues/1335) for details. * Transparent windows are not resizable. Setting `resizable` to `true` may make a transparent window stop working on some platforms. * The `blur` filter only applies to the web page, so there is no way to apply diff --git a/docs/api/ipc-main.md b/docs/api/ipc-main.md index bbaa3c0043f6..337d86be217b 100644 --- a/docs/api/ipc-main.md +++ b/docs/api/ipc-main.md @@ -1,8 +1,10 @@ # ipcMain -The `ipcMain` module, when used in the main process, handles asynchronous and -synchronous messages sent from a renderer process (web page). Messages sent from -a renderer will be emitted to this module. +The `ipcMain` module is an instance of the +[EventEmitter](https://nodejs.org/api/events.html) class. When used in the main +process, it handles asynchronous and synchronous messages sent from a renderer +process (web page). Messages sent from a renderer will be emitted to this +module. ## Sending Messages @@ -51,8 +53,30 @@ The `ipcMain` module has the following method to listen for events: * `channel` String - The event name. * `callback` Function -When the event occurs the `callback` is called with an `event` object and a -message, `arg`. +When the event occurs the `callback` is called with an `event` object and +arbitrary arguments. + +### `ipcMain.removeListener(channel, callback)` + +* `channel` String - The event name. +* `callback` Function - The reference to the same function that you used for + `ipcMain.on(channel, callback)` + +Once done listening for messages, if you no longer want to activate this +callback and for whatever reason can't merely stop sending messages on the +channel, this function will remove the callback handler for the specified +channel. + +### `ipcMain.removeAllListeners(channel)` + +* `channel` String - The event name. + +This removes *all* handlers to this ipc channel. + +### `ipcMain.once(channel, callback)` + +Use this in place of `ipcMain.on()` to fire handlers meant to occur only once, +as in, they won't be activated after one call of `callback` ## IPC Event diff --git a/docs/api/ipc-renderer.md b/docs/api/ipc-renderer.md index 01f0bb3a83dd..090fd1a9119d 100644 --- a/docs/api/ipc-renderer.md +++ b/docs/api/ipc-renderer.md @@ -1,8 +1,10 @@ # ipcRenderer -The `ipcRenderer` module provides a few methods so you can send synchronous and -asynchronous messages from the render process (web page) to the main process. -You can also receive replies from the main process. +The `ipcRenderer` module is an instance of the +[EventEmitter](https://nodejs.org/api/events.html) class. It provides a few +methods so you can send synchronous and asynchronous messages from the render +process (web page) to the main process. You can also receive replies from the +main process. See [ipcMain](ipc-main.md) for code examples. @@ -18,6 +20,28 @@ The `ipcRenderer` module has the following method to listen for events: When the event occurs the `callback` is called with an `event` object and arbitrary arguments. +### `ipcRenderer.removeListener(channel, callback)` + +* `channel` String - The event name. +* `callback` Function - The reference to the same function that you used for + `ipcRenderer.on(channel, callback)` + +Once done listening for messages, if you no longer want to activate this +callback and for whatever reason can't merely stop sending messages on the +channel, this function will remove the callback handler for the specified +channel. + +### `ipcRenderer.removeAllListeners(channel)` + +* `channel` String - The event name. + +This removes *all* handlers to this ipc channel. + +### `ipcMain.once(channel, callback)` + +Use this in place of `ipcMain.on()` to fire handlers meant to occur only once, +as in, they won't be activated after one call of `callback` + ## Sending Messages The `ipcRenderer` module has the following methods for sending messages: diff --git a/docs/api/menu.md b/docs/api/menu.md index b5f2fbe9516e..38069140ad30 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -198,7 +198,7 @@ if (process.platform == 'darwin') { ); } -menu = Menu.buildFromTemplate(template); +var menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); ``` diff --git a/docs/api/remote.md b/docs/api/remote.md index 2a5881d31ca6..7bdbe0362f60 100644 --- a/docs/api/remote.md +++ b/docs/api/remote.md @@ -36,6 +36,8 @@ process. Instead, it created a `BrowserWindow` object in the main process and returned the corresponding remote object in the renderer process, namely the `win` object. +Please note that only [enumerable properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties) are accessible via remote. + ## Lifetime of Remote Objects Electron makes sure that as long as the remote object in the renderer process diff --git a/docs/api/session.md b/docs/api/session.md index 09fa61e2112e..ffed58797c43 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -159,6 +159,13 @@ on complete. Removes the cookies matching `url` and `name`, `callback` will called with `callback()` on complete. +#### `ses.getCacheSize(callback)` + +* `callback` Function + * `size` Integer - Cache size used in bytes. + +Returns the session's current cache size. + #### `ses.clearCache(callback)` * `callback` Function - Called when operation is done @@ -179,44 +186,46 @@ Clears the session’s HTTP cache. Clears the data of web storages. +#### `ses.flushStorageData()` + +Writes any unwritten DOMStorage data to disk. + #### `ses.setProxy(config, callback)` -* `config` String +* `config` Object + * `pacScript` String - The URL associated with the PAC file. + * `proxyRules` String - Rules indicating which proxies to use. * `callback` Function - Called when operation is done. -If `config` is a PAC url, it is used directly otherwise -`config` is parsed based on the following rules indicating which -proxies to use for the session. +Sets the proxy settings. + +When `pacScript` and `proxyRules` are provided together, the `proxyRules` +option is ignored and `pacScript` configuration is applied. + +The `proxyRules` has to follow the rules bellow: ``` -config = scheme-proxies[";"] -scheme-proxies = ["="] -url-scheme = "http" | "https" | "ftp" | "socks" -proxy-uri-list = [","] -proxy-uri = ["://"][":"] - - For example: - "http=foopy:80;ftp=foopy2" -- use HTTP proxy "foopy:80" for http:// - URLs, and HTTP proxy "foopy2:80" for - ftp:// URLs. - "foopy:80" -- use HTTP proxy "foopy:80" for all URLs. - "foopy:80,bar,direct://" -- use HTTP proxy "foopy:80" for all URLs, - failing over to "bar" if "foopy:80" is - unavailable, and after that using no - proxy. - "socks4://foopy" -- use SOCKS v4 proxy "foopy:1080" for all - URLs. - "http=foopy,socks5://bar.com -- use HTTP proxy "foopy" for http URLs, - and fail over to the SOCKS5 proxy - "bar.com" if "foopy" is unavailable. - "http=foopy,direct:// -- use HTTP proxy "foopy" for http URLs, - and use no proxy if "foopy" is - unavailable. - "http=foopy;socks=foopy2 -- use HTTP proxy "foopy" for http URLs, - and use socks4://foopy2 for all other - URLs. +proxyRules = schemeProxies[";"] +schemeProxies = ["="] +urlScheme = "http" | "https" | "ftp" | "socks" +proxyURIList = [","] +proxyURL = ["://"][":"] ``` +For example: +* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and + HTTP proxy `foopy2:80` for `ftp://` URLs. +* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs. +* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing + over to `bar` if `foopy:80` is unavailable, and after that using no proxy. +* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs. +* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail + over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable. +* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no + proxy if `foopy` is unavailable. +* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use + `socks4://foopy2` for all other URLs. + ### `ses.resolveProxy(url, callback)` * `url` URL diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index b399e143aacc..48658a294595 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -472,6 +472,12 @@ Executes the editing command `replace` in web page. Executes the editing command `replaceMisspelling` in web page. +### `webContents.insertText(text)` + +* `text` String + +Inserts `text` to the focused element. + ### `webContents.findInPage(text[, options])` * `text` String - Content to be searched, must not be empty. diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 114afd041cd0..d9e02ac097e7 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -90,4 +90,21 @@ Content Security Policy. Registers the `scheme` as secure, bypasses content security policy for resources, allows registering ServiceWorker and supports fetch API. +### `webFrame.insertText(text)` + +* `text` String + +Inserts `text` to the focused element. + +### `webFrame.executeJavaScript(code[, userGesture])` + +* `code` String +* `userGesture` Boolean (optional) - Default is `false`. + +Evaluates `code` in page. + +In the browser window some HTML APIs like `requestFullScreen` can only be +invoked by a gesture from the user. Setting `userGesture` to `true` will remove +this limitation. + [spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 8b8f5ffd49ad..b3d94b85f715 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -352,6 +352,12 @@ Executes editing command `replace` in page. Executes editing command `replaceMisspelling` in page. +### `.insertText(text)` + +* `text` String + +Inserts `text` to the focused element. + ### `.findInPage(text[, options])` * `text` String - Content to be searched, must not be empty. diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 2342131a78d3..06ce696a9e3c 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -5,8 +5,8 @@ Follow the guidelines below for building Electron on Windows. ## Prerequisites * Windows 7 / Server 2008 R2 or higher -* Visual Studio 2013 with Update 5 - [download VS 2013 Community Edition for - free](https://www.visualstudio.com/downloads/download-visual-studio-vs). +* Visual Studio 2013 with Update 4 - [download VS 2013 Community Edition for + free](https://www.visualstudio.com/news/vs2013-community-vs). * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) * [Git](http://git-scm.com) diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index c5b5cd680ac1..7d7eb44ecb6e 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -35,21 +35,9 @@ are fine differences. Model ID][app-user-model-id], must be installed to the Start screen. Note, however, that it does not need to be pinned to the Start screen. * On Windows 7 and below, notifications are not supported. You can however send -"balloon notifications" using the [Tray API](tray-balloon). +"balloon notifications" using the [Tray API][tray-balloon]. -To use an image in your notification, pass a local image file (preferably `png`) -in the `icon` property of your notification's options. The notification will -still display if you submit an incorrect or `http/https`-based URL, but the -image will not be displayed. - -```javascript -new Notification('Title', { - body: 'Notification with icon', - icon: 'file:///C:/Users/feriese/Desktop/icon.png' -}); -``` - -Furthermore, keep in mind that the maximum length for the body is 250 characters, +Furthermore, the maximum length for the notification body is 250 characters, with the Windows team recommending that notifications should be kept to 200 characters. diff --git a/filenames.gypi b/filenames.gypi index d7eb8409261b..61aa3d43d4aa 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -7,64 +7,64 @@ 'bundle_sources': [ 'atom/browser/resources/mac/atom.icns', ], - 'coffee_sources': [ - 'atom/browser/api/lib/app.coffee', - 'atom/browser/api/lib/auto-updater.coffee', - 'atom/browser/api/lib/auto-updater/auto-updater-native.coffee', - 'atom/browser/api/lib/auto-updater/auto-updater-win.coffee', - 'atom/browser/api/lib/auto-updater/squirrel-update-win.coffee', - 'atom/browser/api/lib/browser-window.coffee', - 'atom/browser/api/lib/content-tracing.coffee', - 'atom/browser/api/lib/dialog.coffee', - 'atom/browser/api/lib/exports/electron.coffee', - 'atom/browser/api/lib/global-shortcut.coffee', - 'atom/browser/api/lib/ipc.coffee', - 'atom/browser/api/lib/ipc-main.coffee', - 'atom/browser/api/lib/menu.coffee', - 'atom/browser/api/lib/menu-item.coffee', - 'atom/browser/api/lib/navigation-controller.coffee', - 'atom/browser/api/lib/power-monitor.coffee', - 'atom/browser/api/lib/power-save-blocker.coffee', - 'atom/browser/api/lib/protocol.coffee', - 'atom/browser/api/lib/session.coffee', - 'atom/browser/api/lib/screen.coffee', - 'atom/browser/api/lib/tray.coffee', - 'atom/browser/api/lib/web-contents.coffee', - 'atom/browser/lib/chrome-extension.coffee', - 'atom/browser/lib/desktop-capturer.coffee', - 'atom/browser/lib/guest-view-manager.coffee', - 'atom/browser/lib/guest-window-manager.coffee', - 'atom/browser/lib/init.coffee', - 'atom/browser/lib/objects-registry.coffee', - 'atom/browser/lib/rpc-server.coffee', - 'atom/common/api/lib/callbacks-registry.coffee', - 'atom/common/api/lib/clipboard.coffee', - 'atom/common/api/lib/crash-reporter.coffee', - 'atom/common/api/lib/deprecate.coffee', - 'atom/common/api/lib/exports/electron.coffee', - 'atom/common/api/lib/native-image.coffee', - 'atom/common/api/lib/shell.coffee', - 'atom/common/lib/init.coffee', - 'atom/common/lib/reset-search-paths.coffee', - 'atom/renderer/lib/chrome-api.coffee', - 'atom/renderer/lib/init.coffee', - 'atom/renderer/lib/inspector.coffee', - 'atom/renderer/lib/override.coffee', - 'atom/renderer/lib/web-view/guest-view-internal.coffee', - 'atom/renderer/lib/web-view/web-view.coffee', - 'atom/renderer/lib/web-view/web-view-attributes.coffee', - 'atom/renderer/lib/web-view/web-view-constants.coffee', - 'atom/renderer/api/lib/desktop-capturer.coffee', - 'atom/renderer/api/lib/exports/electron.coffee', - 'atom/renderer/api/lib/ipc.coffee', - 'atom/renderer/api/lib/ipc-renderer.coffee', - 'atom/renderer/api/lib/remote.coffee', - 'atom/renderer/api/lib/screen.coffee', - 'atom/renderer/api/lib/web-frame.coffee', + 'js_sources': [ + 'atom/browser/api/lib/app.js', + 'atom/browser/api/lib/auto-updater.js', + 'atom/browser/api/lib/auto-updater/auto-updater-native.js', + 'atom/browser/api/lib/auto-updater/auto-updater-win.js', + 'atom/browser/api/lib/auto-updater/squirrel-update-win.js', + 'atom/browser/api/lib/browser-window.js', + 'atom/browser/api/lib/content-tracing.js', + 'atom/browser/api/lib/dialog.js', + 'atom/browser/api/lib/exports/electron.js', + 'atom/browser/api/lib/global-shortcut.js', + 'atom/browser/api/lib/ipc.js', + 'atom/browser/api/lib/ipc-main.js', + 'atom/browser/api/lib/menu.js', + 'atom/browser/api/lib/menu-item.js', + 'atom/browser/api/lib/navigation-controller.js', + 'atom/browser/api/lib/power-monitor.js', + 'atom/browser/api/lib/power-save-blocker.js', + 'atom/browser/api/lib/protocol.js', + 'atom/browser/api/lib/session.js', + 'atom/browser/api/lib/screen.js', + 'atom/browser/api/lib/tray.js', + 'atom/browser/api/lib/web-contents.js', + 'atom/browser/lib/chrome-extension.js', + 'atom/browser/lib/desktop-capturer.js', + 'atom/browser/lib/guest-view-manager.js', + 'atom/browser/lib/guest-window-manager.js', + 'atom/browser/lib/init.js', + 'atom/browser/lib/objects-registry.js', + 'atom/browser/lib/rpc-server.js', + 'atom/common/api/lib/callbacks-registry.js', + 'atom/common/api/lib/clipboard.js', + 'atom/common/api/lib/crash-reporter.js', + 'atom/common/api/lib/deprecate.js', + 'atom/common/api/lib/exports/electron.js', + 'atom/common/api/lib/native-image.js', + 'atom/common/api/lib/shell.js', + 'atom/common/lib/init.js', + 'atom/common/lib/reset-search-paths.js', + 'atom/renderer/lib/chrome-api.js', + 'atom/renderer/lib/init.js', + 'atom/renderer/lib/inspector.js', + 'atom/renderer/lib/override.js', + 'atom/renderer/lib/web-view/guest-view-internal.js', + 'atom/renderer/lib/web-view/web-view.js', + 'atom/renderer/lib/web-view/web-view-attributes.js', + 'atom/renderer/lib/web-view/web-view-constants.js', + 'atom/renderer/api/lib/desktop-capturer.js', + 'atom/renderer/api/lib/exports/electron.js', + 'atom/renderer/api/lib/ipc.js', + 'atom/renderer/api/lib/ipc-renderer.js', + 'atom/renderer/api/lib/remote.js', + 'atom/renderer/api/lib/screen.js', + 'atom/renderer/api/lib/web-frame.js', ], - 'coffee2c_sources': [ - 'atom/common/lib/asar.coffee', - 'atom/common/lib/asar_init.coffee', + 'js2c_sources': [ + 'atom/common/lib/asar.js', + 'atom/common/lib/asar_init.js', ], 'lib_sources': [ 'atom/app/atom_content_client.cc', @@ -410,12 +410,16 @@ 'chromium_src/chrome/browser/process_singleton.h', 'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc', 'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h', + 'chromium_src/chrome/browser/renderer_host/pepper/monitor_finder_mac.h', + 'chromium_src/chrome/browser/renderer_host/pepper/monitor_finder_mac.mm', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_broker_message_filter.cc', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_broker_message_filter.h', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.cc', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h', + 'chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.cc', + 'chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_drm_host.h', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.cc', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.h', 'chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc', diff --git a/package.json b/package.json index f7202f28d6a3..7f1bde090eb4 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,8 @@ { "name": "electron", - "version": "0.36.2", + "version": "0.36.4", "devDependencies": { "asar": "^0.9.0", - "coffee-script": "^1.9.2", - "coffeelint": "^1.9.4", "request": "*" }, "optionalDependencies": { diff --git a/script/cibuild b/script/cibuild index c0798dc7e2c3..1273945409e4 100755 --- a/script/cibuild +++ b/script/cibuild @@ -65,7 +65,6 @@ def main(): run_script('cpplint.py') if PLATFORM != 'win32': run_script('pylint.py') - run_script('coffeelint.py') if is_release: run_script('build.py', ['-c', 'R']) run_script('create-dist.py') diff --git a/script/coffeelint.json b/script/coffeelint.json deleted file mode 100644 index cc06d0d3c8ed..000000000000 --- a/script/coffeelint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "max_line_length": { - "value": 80, - "level": "ignore" - } -} diff --git a/script/coffeelint.py b/script/coffeelint.py deleted file mode 100755 index 29f60f93d6fa..000000000000 --- a/script/coffeelint.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python - -import glob -import os -import sys - -from lib.util import execute - - -SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) - - -def main(): - os.chdir(SOURCE_ROOT) - - coffeelint = os.path.join(SOURCE_ROOT, 'node_modules', '.bin', 'coffeelint') - if sys.platform in ['win32', 'cygwin']: - coffeelint += '.cmd' - settings = ['--quiet', '-f', os.path.join('script', 'coffeelint.json')] - files = glob.glob('atom/browser/api/lib/*.coffee') + \ - glob.glob('atom/renderer/api/lib/*.coffee') + \ - glob.glob('atom/common/api/lib/*.coffee') + \ - glob.glob('atom/browser/atom/*.coffee') - - execute([coffeelint] + settings + files) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/script/lib/config.py b/script/lib/config.py index 932562fdcbee..279fa7340a96 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '0fbded6cf3d9244389db05f0c022e474a06ad32a' +LIBCHROMIUMCONTENT_COMMIT = 'ad63d8ba890bcaad2f1b7e6de148b7992f4d3af7' PLATFORM = { 'cygwin': 'win32', diff --git a/spec/api-app-spec.coffee b/spec/api-app-spec.coffee deleted file mode 100644 index adb64d363eb1..000000000000 --- a/spec/api-app-spec.coffee +++ /dev/null @@ -1,77 +0,0 @@ -assert = require 'assert' -ChildProcess = require 'child_process' -path = require 'path' -{remote} = require 'electron' -{app, BrowserWindow} = remote.require 'electron' - -describe 'app module', -> - describe 'app.getVersion()', -> - it 'returns the version field of package.json', -> - assert.equal app.getVersion(), '0.1.0' - - describe 'app.setVersion(version)', -> - it 'overrides the version', -> - assert.equal app.getVersion(), '0.1.0' - app.setVersion 'test-version' - assert.equal app.getVersion(), 'test-version' - app.setVersion '0.1.0' - - describe 'app.getName()', -> - it 'returns the name field of package.json', -> - assert.equal app.getName(), 'Electron Test' - - describe 'app.setName(name)', -> - it 'overrides the name', -> - assert.equal app.getName(), 'Electron Test' - app.setName 'test-name' - assert.equal app.getName(), 'test-name' - app.setName 'Electron Test' - - describe 'app.getLocale()', -> - it 'should not be empty', -> - assert.notEqual app.getLocale(), '' - - describe 'app.exit(exitCode)', -> - appProcess = null - afterEach -> - appProcess?.kill() - - it 'emits a process exit event with the code', (done) -> - appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') - electronPath = remote.getGlobal('process').execPath - appProcess = ChildProcess.spawn(electronPath, [appPath]) - - output = '' - appProcess.stdout.on 'data', (data) -> output += data - appProcess.on 'close', (code) -> - assert.notEqual output.indexOf('Exit event with code: 123'), -1 - assert.equal code, 123 - done() - - describe 'BrowserWindow events', -> - w = null - afterEach -> - w.destroy() if w? - w = null - - it 'should emit browser-window-focus event when window is focused', (done) -> - app.once 'browser-window-focus', (e, window) -> - assert.equal w.id, window.id - done() - w = new BrowserWindow(show: false) - w.emit 'focus' - - it 'should emit browser-window-blur event when window is blured', (done) -> - app.once 'browser-window-blur', (e, window) -> - assert.equal w.id, window.id - done() - w = new BrowserWindow(show: false) - w.emit 'blur' - - it 'should emit browser-window-created event when window is created', (done) -> - app.once 'browser-window-created', (e, window) -> - setImmediate -> - assert.equal w.id, window.id - done() - w = new BrowserWindow(show: false) - w.emit 'blur' diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js new file mode 100644 index 000000000000..dafaf44a2d15 --- /dev/null +++ b/spec/api-app-spec.js @@ -0,0 +1,111 @@ +var BrowserWindow, ChildProcess, app, assert, path, ref, remote; + +assert = require('assert'); + +ChildProcess = require('child_process'); + +path = require('path'); + +remote = require('electron').remote; + +ref = remote.require('electron'), app = ref.app, BrowserWindow = ref.BrowserWindow; + +describe('app module', function() { + describe('app.getVersion()', function() { + return it('returns the version field of package.json', function() { + return assert.equal(app.getVersion(), '0.1.0'); + }); + }); + describe('app.setVersion(version)', function() { + return it('overrides the version', function() { + assert.equal(app.getVersion(), '0.1.0'); + app.setVersion('test-version'); + assert.equal(app.getVersion(), 'test-version'); + return app.setVersion('0.1.0'); + }); + }); + describe('app.getName()', function() { + return it('returns the name field of package.json', function() { + return assert.equal(app.getName(), 'Electron Test'); + }); + }); + describe('app.setName(name)', function() { + return it('overrides the name', function() { + assert.equal(app.getName(), 'Electron Test'); + app.setName('test-name'); + assert.equal(app.getName(), 'test-name'); + return app.setName('Electron Test'); + }); + }); + describe('app.getLocale()', function() { + return it('should not be empty', function() { + return assert.notEqual(app.getLocale(), ''); + }); + }); + describe('app.exit(exitCode)', function() { + var appProcess; + appProcess = null; + afterEach(function() { + return appProcess != null ? appProcess.kill() : void 0; + }); + return it('emits a process exit event with the code', function(done) { + var appPath, electronPath, output; + appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app'); + electronPath = remote.getGlobal('process').execPath; + appProcess = ChildProcess.spawn(electronPath, [appPath]); + output = ''; + appProcess.stdout.on('data', function(data) { + return output += data; + }); + return appProcess.on('close', function(code) { + if (process.platform !== 'win32') { + assert.notEqual(output.indexOf('Exit event with code: 123'), -1); + } + assert.equal(code, 123); + return done(); + }); + }); + }); + return describe('BrowserWindow events', function() { + var w; + w = null; + afterEach(function() { + if (w != null) { + w.destroy(); + } + return w = null; + }); + it('should emit browser-window-focus event when window is focused', function(done) { + app.once('browser-window-focus', function(e, window) { + assert.equal(w.id, window.id); + return done(); + }); + w = new BrowserWindow({ + show: false + }); + return w.emit('focus'); + }); + it('should emit browser-window-blur event when window is blured', function(done) { + app.once('browser-window-blur', function(e, window) { + assert.equal(w.id, window.id); + return done(); + }); + w = new BrowserWindow({ + show: false + }); + return w.emit('blur'); + }); + return it('should emit browser-window-created event when window is created', function(done) { + app.once('browser-window-created', function(e, window) { + return setImmediate(function() { + assert.equal(w.id, window.id); + return done(); + }); + }); + w = new BrowserWindow({ + show: false + }); + return w.emit('blur'); + }); + }); +}); diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee deleted file mode 100644 index 38c8217041e7..000000000000 --- a/spec/api-browser-window-spec.coffee +++ /dev/null @@ -1,333 +0,0 @@ -assert = require 'assert' -fs = require 'fs' -path = require 'path' -http = require 'http' -url = require 'url' -os = require 'os' - -{remote, screen} = require 'electron' -{ipcMain, BrowserWindow} = remote.require 'electron' - -isCI = remote.getGlobal('isCi') - -describe 'browser-window module', -> - fixtures = path.resolve __dirname, 'fixtures' - - w = null - beforeEach -> - w.destroy() if w? - w = new BrowserWindow(show: false, width: 400, height: 400) - afterEach -> - w.destroy() if w? - w = null - - describe 'BrowserWindow.close()', -> - it 'should emit unload handler', (done) -> - w.webContents.on 'did-finish-load', -> - w.close() - w.on 'closed', -> - test = path.join(fixtures, 'api', 'unload') - content = fs.readFileSync(test) - fs.unlinkSync(test) - assert.equal String(content), 'unload' - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'unload.html') - - it 'should emit beforeunload handler', (done) -> - w.on 'onbeforeunload', -> - done() - w.webContents.on 'did-finish-load', -> - w.close() - w.loadURL 'file://' + path.join(fixtures, 'api', 'beforeunload-false.html') - - describe 'window.close()', -> - it 'should emit unload handler', (done) -> - w.on 'closed', -> - test = path.join(fixtures, 'api', 'close') - content = fs.readFileSync(test) - fs.unlinkSync(test) - assert.equal String(content), 'close' - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'close.html') - - it 'should emit beforeunload handler', (done) -> - w.on 'onbeforeunload', -> - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html') - - describe 'BrowserWindow.loadURL(url)', -> - it 'should emit did-start-loading event', (done) -> - w.webContents.on 'did-start-loading', -> - done() - w.loadURL 'about:blank' - - it 'should emit did-fail-load event', (done) -> - w.webContents.on 'did-fail-load', -> - done() - w.loadURL 'file://a.txt' - - describe 'BrowserWindow.show()', -> - it 'should focus on window', -> - return if isCI - w.show() - assert w.isFocused() - - describe 'BrowserWindow.showInactive()', -> - it 'should not focus on window', -> - w.showInactive() - assert !w.isFocused() - - describe 'BrowserWindow.focus()', -> - it 'does not make the window become visible', -> - assert.equal w.isVisible(), false - w.focus() - assert.equal w.isVisible(), false - - describe 'BrowserWindow.capturePage(rect, callback)', -> - it 'calls the callback with a Buffer', (done) -> - w.capturePage {x: 0, y: 0, width: 100, height: 100}, (image) -> - assert.equal image.isEmpty(), true - done() - - describe 'BrowserWindow.setSize(width, height)', -> - it 'sets the window size', (done) -> - size = [300, 400] - w.once 'resize', -> - newSize = w.getSize() - assert.equal newSize[0], size[0] - assert.equal newSize[1], size[1] - done() - w.setSize size[0], size[1] - - describe 'BrowserWindow.setPosition(x, y)', -> - it 'sets the window position', (done) -> - pos = [10, 10] - w.once 'move', -> - newPos = w.getPosition() - assert.equal newPos[0], pos[0] - assert.equal newPos[1], pos[1] - done() - w.setPosition pos[0], pos[1] - - describe 'BrowserWindow.setContentSize(width, height)', -> - it 'sets the content size', -> - size = [400, 400] - w.setContentSize size[0], size[1] - after = w.getContentSize() - assert.equal after[0], size[0] - assert.equal after[1], size[1] - - it 'works for framless window', -> - w.destroy() - w = new BrowserWindow(show: false, frame: false, width: 400, height: 400) - size = [400, 400] - w.setContentSize size[0], size[1] - after = w.getContentSize() - assert.equal after[0], size[0] - assert.equal after[1], size[1] - - describe 'BrowserWindow.fromId(id)', -> - it 'returns the window with id', -> - assert.equal w.id, BrowserWindow.fromId(w.id).id - - describe 'BrowserWindow.setResizable(resizable)', -> - it 'does not change window size for frameless window', -> - w.destroy() - w = new BrowserWindow(show: true, frame: false) - s = w.getSize() - w.setResizable not w.isResizable() - assert.deepEqual s, w.getSize() - - describe '"useContentSize" option', -> - it 'make window created with content size when used', -> - w.destroy() - w = new BrowserWindow(show: false, width: 400, height: 400, useContentSize: true) - contentSize = w.getContentSize() - assert.equal contentSize[0], 400 - assert.equal contentSize[1], 400 - - it 'make window created with window size when not used', -> - size = w.getSize() - assert.equal size[0], 400 - assert.equal size[1], 400 - - it 'works for framless window', -> - w.destroy() - w = new BrowserWindow(show: false, frame: false, width: 400, height: 400, useContentSize: true) - contentSize = w.getContentSize() - assert.equal contentSize[0], 400 - assert.equal contentSize[1], 400 - size = w.getSize() - assert.equal size[0], 400 - assert.equal size[1], 400 - - describe '"title-bar-style" option', -> - return if process.platform isnt 'darwin' - return if parseInt(os.release().split('.')[0]) < 14 # only run these tests on Yosemite or newer - - it 'creates browser window with hidden title bar', -> - w.destroy() - w = new BrowserWindow(show: false, width: 400, height: 400, titleBarStyle: 'hidden') - contentSize = w.getContentSize() - assert.equal contentSize[1], 400 - - it 'creates browser window with hidden inset title bar', -> - w.destroy() - w = new BrowserWindow(show: false, width: 400, height: 400, titleBarStyle: 'hidden-inset') - contentSize = w.getContentSize() - assert.equal contentSize[1], 400 - - describe '"enableLargerThanScreen" option', -> - return if process.platform is 'linux' - - beforeEach -> - w.destroy() - w = new BrowserWindow(show: true, width: 400, height: 400, enableLargerThanScreen: true) - - it 'can move the window out of screen', -> - w.setPosition -10, -10 - after = w.getPosition() - assert.equal after[0], -10 - assert.equal after[1], -10 - - it 'can set the window larger than screen', -> - size = screen.getPrimaryDisplay().size - size.width += 100 - size.height += 100 - w.setSize size.width, size.height - after = w.getSize() - assert.equal after[0], size.width - assert.equal after[1], size.height - - describe '"web-preferences" option', -> - afterEach -> - ipcMain.removeAllListeners('answer') - - describe '"preload" option', -> - it 'loads the script before other scripts in window', (done) -> - preload = path.join fixtures, 'module', 'set-global.js' - ipcMain.once 'answer', (event, test) -> - assert.equal(test, 'preload') - done() - w.destroy() - w = new BrowserWindow - show: false - webPreferences: - preload: preload - w.loadURL 'file://' + path.join(fixtures, 'api', 'preload.html') - - describe '"node-integration" option', -> - it 'disables node integration when specified to false', (done) -> - preload = path.join fixtures, 'module', 'send-later.js' - ipcMain.once 'answer', (event, test) -> - assert.equal(test, 'undefined') - done() - w.destroy() - w = new BrowserWindow - show: false - webPreferences: - preload: preload - nodeIntegration: false - w.loadURL 'file://' + path.join(fixtures, 'api', 'blank.html') - - describe 'beforeunload handler', -> - it 'returning true would not prevent close', (done) -> - w.on 'closed', -> - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-true.html') - - it 'returning non-empty string would not prevent close', (done) -> - w.on 'closed', -> - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-string.html') - - it 'returning false would prevent close', (done) -> - w.on 'onbeforeunload', -> - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html') - - it 'returning empty string would prevent close', (done) -> - w.on 'onbeforeunload', -> - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html') - - describe 'new-window event', -> - return if isCI and process.platform is 'darwin' - it 'emits when window.open is called', (done) -> - w.webContents.once 'new-window', (e, url, frameName) -> - e.preventDefault() - assert.equal url, 'http://host/' - assert.equal frameName, 'host' - done() - w.loadURL "file://#{fixtures}/pages/window-open.html" - - it 'emits when link with target is called', (done) -> - @timeout 10000 - w.webContents.once 'new-window', (e, url, frameName) -> - e.preventDefault() - assert.equal url, 'http://host/' - assert.equal frameName, 'target' - done() - w.loadURL "file://#{fixtures}/pages/target-name.html" - - describe 'maximize event', -> - return if isCI - it 'emits when window is maximized', (done) -> - @timeout 10000 - w.once 'maximize', -> done() - w.show() - w.maximize() - - describe 'unmaximize event', -> - return if isCI - it 'emits when window is unmaximized', (done) -> - @timeout 10000 - w.once 'unmaximize', -> done() - w.show() - w.maximize() - w.unmaximize() - - describe 'minimize event', -> - return if isCI - it 'emits when window is minimized', (done) -> - @timeout 10000 - w.once 'minimize', -> done() - w.show() - w.minimize() - - xdescribe 'beginFrameSubscription method', -> - it 'subscribes frame updates', (done) -> - w.loadURL "file://#{fixtures}/api/blank.html" - w.webContents.beginFrameSubscription (data) -> - assert.notEqual data.length, 0 - w.webContents.endFrameSubscription() - done() - - describe 'save page', -> - savePageDir = path.join fixtures, 'save_page' - savePageHtmlPath = path.join savePageDir, 'save_page.html' - savePageJsPath = path.join savePageDir, 'save_page_files', 'test.js' - savePageCssPath = path.join savePageDir, 'save_page_files', 'test.css' - it 'should save page', (done) -> - w.webContents.on 'did-finish-load', -> - w.webContents.savePage savePageHtmlPath, 'HTMLComplete', (error) -> - assert.equal error, null - assert fs.existsSync savePageHtmlPath - assert fs.existsSync savePageJsPath - assert fs.existsSync savePageCssPath - fs.unlinkSync savePageCssPath - fs.unlinkSync savePageJsPath - fs.unlinkSync savePageHtmlPath - fs.rmdirSync path.join savePageDir, 'save_page_files' - fs.rmdirSync savePageDir - done() - - w.loadURL "file://#{fixtures}/pages/save_page/index.html" - - describe 'BrowserWindow options argument is optional', -> - it 'should create a window with default size (800x600)', -> - w.destroy() - w = new BrowserWindow() - size = w.getSize() - assert.equal size[0], 800 - assert.equal size[1], 600 diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js new file mode 100644 index 000000000000..50a98ed38ce6 --- /dev/null +++ b/spec/api-browser-window-spec.js @@ -0,0 +1,500 @@ +var BrowserWindow, assert, fs, http, ipcMain, isCI, os, path, ref, ref1, remote, screen, url; + +assert = require('assert'); + +fs = require('fs'); + +path = require('path'); + +http = require('http'); + +url = require('url'); + +os = require('os'); + +ref = require('electron'), remote = ref.remote, screen = ref.screen; + +ref1 = remote.require('electron'), ipcMain = ref1.ipcMain, BrowserWindow = ref1.BrowserWindow; + +isCI = remote.getGlobal('isCi'); + +describe('browser-window module', function() { + var fixtures, w; + fixtures = path.resolve(__dirname, 'fixtures'); + w = null; + beforeEach(function() { + if (w != null) { + w.destroy(); + } + return w = new BrowserWindow({ + show: false, + width: 400, + height: 400 + }); + }); + afterEach(function() { + if (w != null) { + w.destroy(); + } + return w = null; + }); + describe('BrowserWindow.close()', function() { + it('should emit unload handler', function(done) { + w.webContents.on('did-finish-load', function() { + return w.close(); + }); + w.on('closed', function() { + var content, test; + test = path.join(fixtures, 'api', 'unload'); + content = fs.readFileSync(test); + fs.unlinkSync(test); + assert.equal(String(content), 'unload'); + return done(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'unload.html')); + }); + return it('should emit beforeunload handler', function(done) { + w.on('onbeforeunload', function() { + return done(); + }); + w.webContents.on('did-finish-load', function() { + return w.close(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html')); + }); + }); + describe('window.close()', function() { + it('should emit unload handler', function(done) { + w.on('closed', function() { + var content, test; + test = path.join(fixtures, 'api', 'close'); + content = fs.readFileSync(test); + fs.unlinkSync(test); + assert.equal(String(content), 'close'); + return done(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'close.html')); + }); + return it('should emit beforeunload handler', function(done) { + w.on('onbeforeunload', function() { + return done(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')); + }); + }); + describe('BrowserWindow.destroy()', function() { + return it('prevents users to access methods of webContents', function() { + var webContents; + webContents = w.webContents; + w.destroy(); + return assert.throws((function() { + return webContents.getId(); + }), /Object has been destroyed/); + }); + }); + describe('BrowserWindow.loadURL(url)', function() { + it('should emit did-start-loading event', function(done) { + w.webContents.on('did-start-loading', function() { + return done(); + }); + return w.loadURL('about:blank'); + }); + return it('should emit did-fail-load event', function(done) { + w.webContents.on('did-fail-load', function() { + return done(); + }); + return w.loadURL('file://a.txt'); + }); + }); + describe('BrowserWindow.show()', function() { + return it('should focus on window', function() { + if (isCI) { + return; + } + w.show(); + return assert(w.isFocused()); + }); + }); + describe('BrowserWindow.showInactive()', function() { + return it('should not focus on window', function() { + w.showInactive(); + return assert(!w.isFocused()); + }); + }); + describe('BrowserWindow.focus()', function() { + return it('does not make the window become visible', function() { + assert.equal(w.isVisible(), false); + w.focus(); + return assert.equal(w.isVisible(), false); + }); + }); + describe('BrowserWindow.capturePage(rect, callback)', function() { + return it('calls the callback with a Buffer', function(done) { + return w.capturePage({ + x: 0, + y: 0, + width: 100, + height: 100 + }, function(image) { + assert.equal(image.isEmpty(), true); + return done(); + }); + }); + }); + describe('BrowserWindow.setSize(width, height)', function() { + return it('sets the window size', function(done) { + var size; + size = [300, 400]; + w.once('resize', function() { + var newSize; + newSize = w.getSize(); + assert.equal(newSize[0], size[0]); + assert.equal(newSize[1], size[1]); + return done(); + }); + return w.setSize(size[0], size[1]); + }); + }); + describe('BrowserWindow.setPosition(x, y)', function() { + return it('sets the window position', function(done) { + var pos; + pos = [10, 10]; + w.once('move', function() { + var newPos; + newPos = w.getPosition(); + assert.equal(newPos[0], pos[0]); + assert.equal(newPos[1], pos[1]); + return done(); + }); + return w.setPosition(pos[0], pos[1]); + }); + }); + describe('BrowserWindow.setContentSize(width, height)', function() { + it('sets the content size', function() { + var after, size; + size = [400, 400]; + w.setContentSize(size[0], size[1]); + after = w.getContentSize(); + assert.equal(after[0], size[0]); + return assert.equal(after[1], size[1]); + }); + return it('works for framless window', function() { + var after, size; + w.destroy(); + w = new BrowserWindow({ + show: false, + frame: false, + width: 400, + height: 400 + }); + size = [400, 400]; + w.setContentSize(size[0], size[1]); + after = w.getContentSize(); + assert.equal(after[0], size[0]); + return assert.equal(after[1], size[1]); + }); + }); + describe('BrowserWindow.fromId(id)', function() { + return it('returns the window with id', function() { + return assert.equal(w.id, BrowserWindow.fromId(w.id).id); + }); + }); + describe('BrowserWindow.setResizable(resizable)', function() { + return it('does not change window size for frameless window', function() { + var s; + w.destroy(); + w = new BrowserWindow({ + show: true, + frame: false + }); + s = w.getSize(); + w.setResizable(!w.isResizable()); + return assert.deepEqual(s, w.getSize()); + }); + }); + describe('"useContentSize" option', function() { + it('make window created with content size when used', function() { + var contentSize; + w.destroy(); + w = new BrowserWindow({ + show: false, + width: 400, + height: 400, + useContentSize: true + }); + contentSize = w.getContentSize(); + assert.equal(contentSize[0], 400); + return assert.equal(contentSize[1], 400); + }); + it('make window created with window size when not used', function() { + var size; + size = w.getSize(); + assert.equal(size[0], 400); + return assert.equal(size[1], 400); + }); + return it('works for framless window', function() { + var contentSize, size; + w.destroy(); + w = new BrowserWindow({ + show: false, + frame: false, + width: 400, + height: 400, + useContentSize: true + }); + contentSize = w.getContentSize(); + assert.equal(contentSize[0], 400); + assert.equal(contentSize[1], 400); + size = w.getSize(); + assert.equal(size[0], 400); + return assert.equal(size[1], 400); + }); + }); + describe('"title-bar-style" option', function() { + if (process.platform !== 'darwin') { + return; + } + if (parseInt(os.release().split('.')[0]) < 14) { + return; + } + it('creates browser window with hidden title bar', function() { + var contentSize; + w.destroy(); + w = new BrowserWindow({ + show: false, + width: 400, + height: 400, + titleBarStyle: 'hidden' + }); + contentSize = w.getContentSize(); + return assert.equal(contentSize[1], 400); + }); + return it('creates browser window with hidden inset title bar', function() { + var contentSize; + w.destroy(); + w = new BrowserWindow({ + show: false, + width: 400, + height: 400, + titleBarStyle: 'hidden-inset' + }); + contentSize = w.getContentSize(); + return assert.equal(contentSize[1], 400); + }); + }); + describe('"enableLargerThanScreen" option', function() { + if (process.platform === 'linux') { + return; + } + beforeEach(function() { + w.destroy(); + return w = new BrowserWindow({ + show: true, + width: 400, + height: 400, + enableLargerThanScreen: true + }); + }); + it('can move the window out of screen', function() { + var after; + w.setPosition(-10, -10); + after = w.getPosition(); + assert.equal(after[0], -10); + return assert.equal(after[1], -10); + }); + return it('can set the window larger than screen', function() { + var after, size; + size = screen.getPrimaryDisplay().size; + size.width += 100; + size.height += 100; + w.setSize(size.width, size.height); + after = w.getSize(); + assert.equal(after[0], size.width); + return assert.equal(after[1], size.height); + }); + }); + describe('"web-preferences" option', function() { + afterEach(function() { + return ipcMain.removeAllListeners('answer'); + }); + describe('"preload" option', function() { + return it('loads the script before other scripts in window', function(done) { + var preload; + preload = path.join(fixtures, 'module', 'set-global.js'); + ipcMain.once('answer', function(event, test) { + assert.equal(test, 'preload'); + return done(); + }); + w.destroy(); + w = new BrowserWindow({ + show: false, + webPreferences: { + preload: preload + } + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html')); + }); + }); + return describe('"node-integration" option', function() { + return it('disables node integration when specified to false', function(done) { + var preload; + preload = path.join(fixtures, 'module', 'send-later.js'); + ipcMain.once('answer', function(event, test) { + assert.equal(test, 'undefined'); + return done(); + }); + w.destroy(); + w = new BrowserWindow({ + show: false, + webPreferences: { + preload: preload, + nodeIntegration: false + } + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html')); + }); + }); + }); + describe('beforeunload handler', function() { + it('returning true would not prevent close', function(done) { + w.on('closed', function() { + return done(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-true.html')); + }); + it('returning non-empty string would not prevent close', function(done) { + w.on('closed', function() { + return done(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-string.html')); + }); + it('returning false would prevent close', function(done) { + w.on('onbeforeunload', function() { + return done(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')); + }); + return it('returning empty string would prevent close', function(done) { + w.on('onbeforeunload', function() { + return done(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html')); + }); + }); + describe('new-window event', function() { + if (isCI && process.platform === 'darwin') { + return; + } + it('emits when window.open is called', function(done) { + w.webContents.once('new-window', function(e, url, frameName) { + e.preventDefault(); + assert.equal(url, 'http://host/'); + assert.equal(frameName, 'host'); + return done(); + }); + return w.loadURL("file://" + fixtures + "/pages/window-open.html"); + }); + return it('emits when link with target is called', function(done) { + this.timeout(10000); + w.webContents.once('new-window', function(e, url, frameName) { + e.preventDefault(); + assert.equal(url, 'http://host/'); + assert.equal(frameName, 'target'); + return done(); + }); + return w.loadURL("file://" + fixtures + "/pages/target-name.html"); + }); + }); + describe('maximize event', function() { + if (isCI) { + return; + } + return it('emits when window is maximized', function(done) { + this.timeout(10000); + w.once('maximize', function() { + return done(); + }); + w.show(); + return w.maximize(); + }); + }); + describe('unmaximize event', function() { + if (isCI) { + return; + } + return it('emits when window is unmaximized', function(done) { + this.timeout(10000); + w.once('unmaximize', function() { + return done(); + }); + w.show(); + w.maximize(); + return w.unmaximize(); + }); + }); + describe('minimize event', function() { + if (isCI) { + return; + } + return it('emits when window is minimized', function(done) { + this.timeout(10000); + w.once('minimize', function() { + return done(); + }); + w.show(); + return w.minimize(); + }); + }); + xdescribe('beginFrameSubscription method', function() { + return it('subscribes frame updates', function(done) { + w.loadURL("file://" + fixtures + "/api/blank.html"); + return w.webContents.beginFrameSubscription(function(data) { + assert.notEqual(data.length, 0); + w.webContents.endFrameSubscription(); + return done(); + }); + }); + }); + + describe('savePage method', function() { + const savePageDir = path.join(fixtures, 'save_page'); + const savePageHtmlPath = path.join(savePageDir, 'save_page.html'); + const savePageJsPath = path.join(savePageDir, 'save_page_files', 'test.js'); + const savePageCssPath = path.join(savePageDir, 'save_page_files', 'test.css'); + + after(function() { + try { + fs.unlinkSync(savePageCssPath); + fs.unlinkSync(savePageJsPath); + fs.unlinkSync(savePageHtmlPath); + fs.rmdirSync(path.join(savePageDir, 'save_page_files')); + fs.rmdirSync(savePageDir); + } catch (e) { + } + }); + + it('should save page to disk', function(done) { + w.webContents.on('did-finish-load', function() { + w.webContents.savePage(savePageHtmlPath, 'HTMLComplete', function(error) { + assert.equal(error, null); + assert(fs.existsSync(savePageHtmlPath)); + assert(fs.existsSync(savePageJsPath)); + assert(fs.existsSync(savePageCssPath)); + done(); + }); + }); + w.loadURL("file://" + fixtures + "/pages/save_page/index.html"); + }); + }); + + describe('BrowserWindow options argument is optional', function() { + return it('should create a window with default size (800x600)', function() { + var size; + w.destroy(); + w = new BrowserWindow(); + size = w.getSize(); + assert.equal(size[0], 800); + return assert.equal(size[1], 600); + }); + }); +}); diff --git a/spec/api-clipboard-spec.coffee b/spec/api-clipboard-spec.coffee deleted file mode 100644 index 19da3fc75f1b..000000000000 --- a/spec/api-clipboard-spec.coffee +++ /dev/null @@ -1,52 +0,0 @@ -assert = require 'assert' -path = require 'path' - -{clipboard, nativeImage} = require 'electron' - -describe 'clipboard module', -> - fixtures = path.resolve __dirname, 'fixtures' - - describe 'clipboard.readImage()', -> - it 'returns NativeImage intance', -> - p = path.join fixtures, 'assets', 'logo.png' - i = nativeImage.createFromPath p - clipboard.writeImage p - assert.equal clipboard.readImage().toDataURL(), i.toDataURL() - - describe 'clipboard.readText()', -> - it 'returns unicode string correctly', -> - text = '千江有水千江月,万里无云万里天' - clipboard.writeText text - assert.equal clipboard.readText(), text - - describe 'clipboard.readHtml()', -> - it 'returns markup correctly', -> - text = 'Hi' - markup = - if process.platform is 'darwin' - 'Hi' - else if process.platform is 'linux' - 'Hi' - else - 'Hi' - clipboard.writeHtml text - assert.equal clipboard.readHtml(), markup - - describe 'clipboard.write()', -> - it 'returns data correctly', -> - text = 'test' - p = path.join fixtures, 'assets', 'logo.png' - i = nativeImage.createFromPath p - markup = - if process.platform is 'darwin' - 'Hi' - else if process.platform is 'linux' - 'Hi' - else - 'Hi' - clipboard.write {text: "test", html: 'Hi', image: p} - assert.equal clipboard.readText(), text - assert.equal clipboard.readHtml(), markup - assert.equal clipboard.readImage().toDataURL(), i.toDataURL() diff --git a/spec/api-clipboard-spec.js b/spec/api-clipboard-spec.js new file mode 100644 index 000000000000..6154181f0926 --- /dev/null +++ b/spec/api-clipboard-spec.js @@ -0,0 +1,55 @@ +var assert, clipboard, nativeImage, path, ref; + +assert = require('assert'); + +path = require('path'); + +ref = require('electron'), clipboard = ref.clipboard, nativeImage = ref.nativeImage; + +describe('clipboard module', function() { + var fixtures; + fixtures = path.resolve(__dirname, 'fixtures'); + describe('clipboard.readImage()', function() { + return it('returns NativeImage intance', function() { + var i, p; + p = path.join(fixtures, 'assets', 'logo.png'); + i = nativeImage.createFromPath(p); + clipboard.writeImage(p); + return assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()); + }); + }); + describe('clipboard.readText()', function() { + return it('returns unicode string correctly', function() { + var text; + text = '千江有水千江月,万里无云万里天'; + clipboard.writeText(text); + return assert.equal(clipboard.readText(), text); + }); + }); + describe('clipboard.readHtml()', function() { + return it('returns markup correctly', function() { + var markup, text; + text = 'Hi'; + markup = process.platform === 'darwin' ? 'Hi' : process.platform === 'linux' ? 'Hi' : 'Hi'; + clipboard.writeHtml(text); + return assert.equal(clipboard.readHtml(), markup); + }); + }); + return describe('clipboard.write()', function() { + return it('returns data correctly', function() { + var i, markup, p, text; + text = 'test'; + p = path.join(fixtures, 'assets', 'logo.png'); + i = nativeImage.createFromPath(p); + markup = process.platform === 'darwin' ? 'Hi' : process.platform === 'linux' ? 'Hi' : 'Hi'; + clipboard.write({ + text: "test", + html: 'Hi', + image: p + }); + assert.equal(clipboard.readText(), text); + assert.equal(clipboard.readHtml(), markup); + return assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()); + }); + }); +}); diff --git a/spec/api-crash-reporter-spec.coffee b/spec/api-crash-reporter-spec.coffee deleted file mode 100644 index 334956d3c06b..000000000000 --- a/spec/api-crash-reporter-spec.coffee +++ /dev/null @@ -1,66 +0,0 @@ -assert = require 'assert' -path = require 'path' -http = require 'http' -url = require 'url' -multiparty = require 'multiparty' - -{remote} = require 'electron' -{app, crashReporter, BrowserWindow} = remote.require 'electron' - -describe 'crash-reporter module', -> - fixtures = path.resolve __dirname, 'fixtures' - - w = null - beforeEach -> w = new BrowserWindow(show: false) - afterEach -> w.destroy() - - # It is not working for mas build. - return if process.mas - - # The crash-reporter test is not reliable on CI machine. - isCI = remote.getGlobal('isCi') - return if isCI - - it 'should send minidump when renderer crashes', (done) -> - @timeout 120000 - called = false - server = http.createServer (req, res) -> - server.close() - form = new multiparty.Form() - form.parse req, (error, fields, files) -> - # This callback can be called for twice sometimes. - return if called - called = true - - assert.equal fields['prod'], 'Electron' - assert.equal fields['ver'], process.versions['electron'] - assert.equal fields['process_type'], 'renderer' - assert.equal fields['platform'], process.platform - assert.equal fields['extra1'], 'extra1' - assert.equal fields['extra2'], 'extra2' - assert.equal fields['_productName'], 'Zombies' - assert.equal fields['_companyName'], 'Umbrella Corporation' - assert.equal fields['_version'], app.getVersion() - - res.end('abc-123-def') - done() - # Server port is generated randomly for the first run, it will be reused - # when page is refreshed. - port = remote.process.port - server.listen port, '127.0.0.1', -> - {port} = server.address() - remote.process.port = port - url = url.format - protocol: 'file' - pathname: path.join fixtures, 'api', 'crash.html' - search: "?port=#{port}" - if process.platform is 'darwin' - crashReporter.start - companyName: 'Umbrella Corporation' - submitURL: "http://127.0.0.1:#{port}" - w.loadURL url - - describe ".start(options)", -> - it 'requires that the companyName and submitURL options be specified', -> - assert.throws(-> crashReporter.start({companyName: 'Missing submitURL'})) - assert.throws(-> crashReporter.start({submitURL: 'Missing companyName'})) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js new file mode 100644 index 000000000000..88cbe0a666dc --- /dev/null +++ b/spec/api-crash-reporter-spec.js @@ -0,0 +1,94 @@ +var BrowserWindow, app, assert, crashReporter, http, multiparty, path, ref, remote, url; + +assert = require('assert'); + +path = require('path'); + +http = require('http'); + +url = require('url'); + +multiparty = require('multiparty'); + +remote = require('electron').remote; + +ref = remote.require('electron'), app = ref.app, crashReporter = ref.crashReporter, BrowserWindow = ref.BrowserWindow; + +describe('crash-reporter module', function() { + var fixtures, isCI, w; + fixtures = path.resolve(__dirname, 'fixtures'); + w = null; + beforeEach(function() { + return w = new BrowserWindow({ + show: false + }); + }); + afterEach(function() { + return w.destroy(); + }); + if (process.mas) { + return; + } + isCI = remote.getGlobal('isCi'); + if (isCI) { + return; + } + it('should send minidump when renderer crashes', function(done) { + var called, port, server; + this.timeout(120000); + called = false; + server = http.createServer(function(req, res) { + var form; + server.close(); + form = new multiparty.Form(); + return form.parse(req, function(error, fields, files) { + if (called) { + return; + } + called = true; + assert.equal(fields['prod'], 'Electron'); + assert.equal(fields['ver'], process.versions['electron']); + assert.equal(fields['process_type'], 'renderer'); + assert.equal(fields['platform'], process.platform); + assert.equal(fields['extra1'], 'extra1'); + assert.equal(fields['extra2'], 'extra2'); + assert.equal(fields['_productName'], 'Zombies'); + assert.equal(fields['_companyName'], 'Umbrella Corporation'); + assert.equal(fields['_version'], app.getVersion()); + res.end('abc-123-def'); + return done(); + }); + }); + port = remote.process.port; + return server.listen(port, '127.0.0.1', function() { + port = server.address().port; + remote.process.port = port; + url = url.format({ + protocol: 'file', + pathname: path.join(fixtures, 'api', 'crash.html'), + search: "?port=" + port + }); + if (process.platform === 'darwin') { + crashReporter.start({ + companyName: 'Umbrella Corporation', + submitURL: "http://127.0.0.1:" + port + }); + } + return w.loadURL(url); + }); + }); + return describe(".start(options)", function() { + return it('requires that the companyName and submitURL options be specified', function() { + assert.throws(function() { + return crashReporter.start({ + companyName: 'Missing submitURL' + }); + }); + return assert.throws(function() { + return crashReporter.start({ + submitURL: 'Missing companyName' + }); + }); + }); + }); +}); diff --git a/spec/api-desktop-capturer-spec.js b/spec/api-desktop-capturer-spec.js new file mode 100644 index 000000000000..3a03366d4545 --- /dev/null +++ b/spec/api-desktop-capturer-spec.js @@ -0,0 +1,17 @@ +var assert, desktopCapturer; + +assert = require('assert'); + +desktopCapturer = require('electron').desktopCapturer; + +describe('desktopCapturer', function() { + return it('should return a non-empty array of sources', function(done) { + return desktopCapturer.getSources({ + types: ['window', 'screen'] + }, function(error, sources) { + assert.equal(error, null); + assert.notEqual(sources.length, 0); + return done(); + }); + }); +}); diff --git a/spec/api-desktop-capturer.coffee b/spec/api-desktop-capturer.coffee deleted file mode 100644 index 6f1842dd145c..000000000000 --- a/spec/api-desktop-capturer.coffee +++ /dev/null @@ -1,9 +0,0 @@ -assert = require 'assert' -{desktopCapturer} = require 'electron' - -describe 'desktopCapturer', -> - it 'should returns something', (done) -> - desktopCapturer.getSources {types: ['window', 'screen']}, (error, sources) -> - assert.equal error, null - assert.notEqual sources.length, 0 - done() diff --git a/spec/api-ipc-spec.coffee b/spec/api-ipc-spec.coffee deleted file mode 100644 index 67ef717d5045..000000000000 --- a/spec/api-ipc-spec.coffee +++ /dev/null @@ -1,110 +0,0 @@ -assert = require 'assert' -path = require 'path' - -{ipcRenderer, remote} = require 'electron' -{ipcMain, BrowserWindow} = remote.require 'electron' - -comparePaths = (path1, path2) -> - if process.platform is 'win32' - # Paths in Windows are case insensitive. - path1 = path1.toLowerCase() - path2 = path2.toLowerCase() - assert.equal path1, path2 - -describe 'ipc module', -> - fixtures = path.join __dirname, 'fixtures' - - describe 'remote.require', -> - it 'should returns same object for the same module', -> - dialog1 = remote.require 'electron' - dialog2 = remote.require 'electron' - assert.equal dialog1, dialog2 - - it 'should work when object contains id property', -> - a = remote.require path.join(fixtures, 'module', 'id.js') - assert.equal a.id, 1127 - - it 'should search module from the user app', -> - comparePaths path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js') - comparePaths path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules') - - describe 'remote.createFunctionWithReturnValue', -> - it 'should be called in browser synchronously', -> - buf = new Buffer('test') - call = remote.require path.join(fixtures, 'module', 'call.js') - result = call.call remote.createFunctionWithReturnValue(buf) - assert.equal result.constructor.name, 'Buffer' - - describe 'remote object in renderer', -> - it 'can change its properties', -> - property = remote.require path.join(fixtures, 'module', 'property.js') - assert.equal property.property, 1127 - property.property = 1007 - assert.equal property.property, 1007 - property2 = remote.require path.join(fixtures, 'module', 'property.js') - assert.equal property2.property, 1007 - - # Restore. - property.property = 1127 - - it 'can construct an object from its member', -> - call = remote.require path.join(fixtures, 'module', 'call.js') - obj = new call.constructor - assert.equal obj.test, 'test' - - describe 'remote value in browser', -> - print = path.join(fixtures, 'module', 'print_name.js') - - it 'keeps its constructor name for objects', -> - buf = new Buffer('test') - print_name = remote.require print - assert.equal print_name.print(buf), 'Buffer' - - it 'supports instanceof Date', -> - now = new Date() - print_name = remote.require print - assert.equal print_name.print(now), 'Date' - assert.deepEqual print_name.echo(now), now - - describe 'remote promise', -> - it 'can be used as promise in each side', (done) -> - promise = remote.require path.join(fixtures, 'module', 'promise.js') - promise.twicePromise(Promise.resolve(1234)) - .then (value) => - assert.equal value, 2468 - done() - - describe 'ipc.sender.send', -> - it 'should work when sending an object containing id property', (done) -> - obj = id: 1, name: 'ly' - ipcRenderer.once 'message', (event, message) -> - assert.deepEqual message, obj - done() - ipcRenderer.send 'message', obj - - describe 'ipc.sendSync', -> - it 'can be replied by setting event.returnValue', -> - msg = ipcRenderer.sendSync 'echo', 'test' - assert.equal msg, 'test' - - it 'does not crash when reply is not sent and browser is destroyed', (done) -> - @timeout 10000 - w = new BrowserWindow(show: false) - ipcMain.once 'send-sync-message', (event) -> - event.returnValue = null - w.destroy() - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'send-sync-message.html') - - describe 'remote listeners', -> - w = null - afterEach -> - w.destroy() - - it 'can be added and removed correctly', -> - w = new BrowserWindow(show: false) - listener = -> - w.on 'test', listener - assert.equal w.listenerCount('test'), 1 - w.removeListener 'test', listener - assert.equal w.listenerCount('test'), 0 diff --git a/spec/api-ipc-spec.js b/spec/api-ipc-spec.js new file mode 100644 index 000000000000..fba5b6d68402 --- /dev/null +++ b/spec/api-ipc-spec.js @@ -0,0 +1,147 @@ +var BrowserWindow, assert, comparePaths, ipcMain, ipcRenderer, path, ref, ref1, remote; + +assert = require('assert'); + +path = require('path'); + +ref = require('electron'), ipcRenderer = ref.ipcRenderer, remote = ref.remote; + +ref1 = remote.require('electron'), ipcMain = ref1.ipcMain, BrowserWindow = ref1.BrowserWindow; + +comparePaths = function(path1, path2) { + if (process.platform === 'win32') { + path1 = path1.toLowerCase(); + path2 = path2.toLowerCase(); + } + return assert.equal(path1, path2); +}; + +describe('ipc module', function() { + var fixtures; + fixtures = path.join(__dirname, 'fixtures'); + describe('remote.require', function() { + it('should returns same object for the same module', function() { + var dialog1, dialog2; + dialog1 = remote.require('electron'); + dialog2 = remote.require('electron'); + return assert.equal(dialog1, dialog2); + }); + it('should work when object contains id property', function() { + var a; + a = remote.require(path.join(fixtures, 'module', 'id.js')); + return assert.equal(a.id, 1127); + }); + return it('should search module from the user app', function() { + comparePaths(path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js')); + return comparePaths(path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules')); + }); + }); + describe('remote.createFunctionWithReturnValue', function() { + return it('should be called in browser synchronously', function() { + var buf, call, result; + buf = new Buffer('test'); + call = remote.require(path.join(fixtures, 'module', 'call.js')); + result = call.call(remote.createFunctionWithReturnValue(buf)); + return assert.equal(result.constructor.name, 'Buffer'); + }); + }); + describe('remote object in renderer', function() { + it('can change its properties', function() { + var property, property2; + property = remote.require(path.join(fixtures, 'module', 'property.js')); + assert.equal(property.property, 1127); + property.property = 1007; + assert.equal(property.property, 1007); + property2 = remote.require(path.join(fixtures, 'module', 'property.js')); + assert.equal(property2.property, 1007); + return property.property = 1127; + }); + return it('can construct an object from its member', function() { + var call, obj; + call = remote.require(path.join(fixtures, 'module', 'call.js')); + obj = new call.constructor; + return assert.equal(obj.test, 'test'); + }); + }); + describe('remote value in browser', function() { + var print; + print = path.join(fixtures, 'module', 'print_name.js'); + it('keeps its constructor name for objects', function() { + var buf, print_name; + buf = new Buffer('test'); + print_name = remote.require(print); + return assert.equal(print_name.print(buf), 'Buffer'); + }); + return it('supports instanceof Date', function() { + var now, print_name; + now = new Date(); + print_name = remote.require(print); + assert.equal(print_name.print(now), 'Date'); + return assert.deepEqual(print_name.echo(now), now); + }); + }); + describe('remote promise', function() { + return it('can be used as promise in each side', function(done) { + var promise; + promise = remote.require(path.join(fixtures, 'module', 'promise.js')); + return promise.twicePromise(Promise.resolve(1234)).then((function(_this) { + return function(value) { + assert.equal(value, 2468); + return done(); + }; + })(this)); + }); + }); + describe('ipc.sender.send', function() { + return it('should work when sending an object containing id property', function(done) { + var obj; + obj = { + id: 1, + name: 'ly' + }; + ipcRenderer.once('message', function(event, message) { + assert.deepEqual(message, obj); + return done(); + }); + return ipcRenderer.send('message', obj); + }); + }); + describe('ipc.sendSync', function() { + it('can be replied by setting event.returnValue', function() { + var msg; + msg = ipcRenderer.sendSync('echo', 'test'); + return assert.equal(msg, 'test'); + }); + return it('does not crash when reply is not sent and browser is destroyed', function(done) { + var w; + this.timeout(10000); + w = new BrowserWindow({ + show: false + }); + ipcMain.once('send-sync-message', function(event) { + event.returnValue = null; + w.destroy(); + return done(); + }); + return w.loadURL('file://' + path.join(fixtures, 'api', 'send-sync-message.html')); + }); + }); + return describe('remote listeners', function() { + var w; + w = null; + afterEach(function() { + return w.destroy(); + }); + return it('can be added and removed correctly', function() { + var listener; + w = new BrowserWindow({ + show: false + }); + listener = function() {}; + w.on('test', listener); + assert.equal(w.listenerCount('test'), 1); + w.removeListener('test', listener); + return assert.equal(w.listenerCount('test'), 0); + }); + }); +}); diff --git a/spec/api-menu-spec.coffee b/spec/api-menu-spec.coffee deleted file mode 100644 index 2f1f6465898f..000000000000 --- a/spec/api-menu-spec.coffee +++ /dev/null @@ -1,172 +0,0 @@ -assert = require 'assert' - -{remote, ipcRenderer} = require 'electron' -{Menu, MenuItem} = remote.require 'electron' - -describe 'menu module', -> - describe 'Menu.buildFromTemplate', -> - it 'should be able to attach extra fields', -> - menu = Menu.buildFromTemplate [label: 'text', extra: 'field'] - assert.equal menu.items[0].extra, 'field' - - it 'does not modify the specified template', -> - template = ipcRenderer.sendSync 'eval', """ - var template = [{label: 'text', submenu: [{label: 'sub'}]}]; - require('electron').Menu.buildFromTemplate(template); - template; - """ - assert.deepStrictEqual template, [label: 'text', submenu: [label: 'sub']] - - describe 'Menu.buildFromTemplate should reorder based on item position specifiers', -> - it 'should position before existing item', -> - menu = Menu.buildFromTemplate [ - {label: '2', id: '2'} - {label: '3', id: '3'} - {label: '1', id: '1', position: 'before=2'} - ] - assert.equal menu.items[0].label, '1' - assert.equal menu.items[1].label, '2' - assert.equal menu.items[2].label, '3' - - it 'should position after existing item', -> - menu = Menu.buildFromTemplate [ - {label: '1', id: '1'} - {label: '3', id: '3'} - {label: '2', id: '2', position: 'after=1'} - ] - assert.equal menu.items[0].label, '1' - assert.equal menu.items[1].label, '2' - assert.equal menu.items[2].label, '3' - - it 'should position at endof existing separator groups', -> - menu = Menu.buildFromTemplate [ - {type: 'separator', id: 'numbers'} - {type: 'separator', id: 'letters'} - {label: 'a', id: 'a', position: 'endof=letters'} - {label: '1', id: '1', position: 'endof=numbers'} - {label: 'b', id: 'b', position: 'endof=letters'} - {label: '2', id: '2', position: 'endof=numbers'} - {label: 'c', id: 'c', position: 'endof=letters'} - {label: '3', id: '3', position: 'endof=numbers'} - ] - assert.equal menu.items[0].id, 'numbers' - assert.equal menu.items[1].label, '1' - assert.equal menu.items[2].label, '2' - assert.equal menu.items[3].label, '3' - assert.equal menu.items[4].id, 'letters' - assert.equal menu.items[5].label, 'a' - assert.equal menu.items[6].label, 'b' - assert.equal menu.items[7].label, 'c' - - it 'should create separator group if endof does not reference existing separator group', -> - menu = Menu.buildFromTemplate [ - {label: 'a', id: 'a', position: 'endof=letters'} - {label: '1', id: '1', position: 'endof=numbers'} - {label: 'b', id: 'b', position: 'endof=letters'} - {label: '2', id: '2', position: 'endof=numbers'} - {label: 'c', id: 'c', position: 'endof=letters'} - {label: '3', id: '3', position: 'endof=numbers'} - ] - - assert.equal menu.items[0].id, 'letters' - assert.equal menu.items[1].label, 'a' - assert.equal menu.items[2].label, 'b' - assert.equal menu.items[3].label, 'c' - assert.equal menu.items[4].id, 'numbers' - assert.equal menu.items[5].label, '1' - assert.equal menu.items[6].label, '2' - assert.equal menu.items[7].label, '3' - - it 'should continue inserting items at next index when no specifier is present', -> - menu = Menu.buildFromTemplate [ - {label: '4', id: '4'} - {label: '5', id: '5'} - {label: '1', id: '1', position: 'before=4'} - {label: '2', id: '2'} - {label: '3', id: '3'} - ] - assert.equal menu.items[0].label, '1' - assert.equal menu.items[1].label, '2' - assert.equal menu.items[2].label, '3' - assert.equal menu.items[3].label, '4' - assert.equal menu.items[4].label, '5' - - describe 'Menu.insert', -> - it 'should store item in @items by its index', -> - menu = Menu.buildFromTemplate [ - {label: '1'} - {label: '2'} - {label: '3'} - ] - item = new MenuItem(label: 'inserted') - menu.insert 1, item - - assert.equal menu.items[0].label, '1' - assert.equal menu.items[1].label, 'inserted' - assert.equal menu.items[2].label, '2' - assert.equal menu.items[3].label, '3' - - describe 'MenuItem.click', -> - it 'should be called with the item object passed', (done) -> - menu = Menu.buildFromTemplate [ - label: 'text' - click: (item) -> - assert.equal item.constructor.name, 'MenuItem' - assert.equal item.label, 'text' - done() - ] - menu.delegate.executeCommand menu.items[0].commandId - - describe 'MenuItem with checked property', -> - it 'clicking an checkbox item should flip the checked property', -> - menu = Menu.buildFromTemplate [ label: 'text', type: 'checkbox' ] - assert.equal menu.items[0].checked, false - menu.delegate.executeCommand menu.items[0].commandId - assert.equal menu.items[0].checked, true - - it 'clicking an radio item should always make checked property true', -> - menu = Menu.buildFromTemplate [ label: 'text', type: 'radio' ] - menu.delegate.executeCommand menu.items[0].commandId - assert.equal menu.items[0].checked, true - menu.delegate.executeCommand menu.items[0].commandId - assert.equal menu.items[0].checked, true - - it 'at least have one item checked in each group', -> - template = [] - template.push label: "#{i}", type: 'radio' for i in [0..10] - template.push type: 'separator' - template.push label: "#{i}", type: 'radio' for i in [12..20] - menu = Menu.buildFromTemplate template - menu.delegate.menuWillShow() - assert.equal menu.items[0].checked, true - assert.equal menu.items[12].checked, true - - it 'should assign groupId automatically', -> - template = [] - template.push label: "#{i}", type: 'radio' for i in [0..10] - template.push type: 'separator' - template.push label: "#{i}", type: 'radio' for i in [12..20] - menu = Menu.buildFromTemplate template - groupId = menu.items[0].groupId - assert.equal menu.items[i].groupId, groupId for i in [0..10] - assert.equal menu.items[i].groupId, groupId + 1 for i in [12..20] - - it "setting 'checked' should flip other items' 'checked' property", -> - template = [] - template.push label: "#{i}", type: 'radio' for i in [0..10] - template.push type: 'separator' - template.push label: "#{i}", type: 'radio' for i in [12..20] - menu = Menu.buildFromTemplate template - assert.equal menu.items[i].checked, false for i in [0..10] - menu.items[0].checked = true - assert.equal menu.items[0].checked, true - assert.equal menu.items[i].checked, false for i in [1..10] - menu.items[10].checked = true - assert.equal menu.items[10].checked, true - assert.equal menu.items[i].checked, false for i in [0..9] - assert.equal menu.items[i].checked, false for i in [12..20] - menu.items[12].checked = true - assert.equal menu.items[10].checked, true - assert.equal menu.items[i].checked, false for i in [0..9] - assert.equal menu.items[12].checked, true - assert.equal menu.items[i].checked, false for i in [13..20] diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js new file mode 100644 index 000000000000..5771358337e4 --- /dev/null +++ b/spec/api-menu-spec.js @@ -0,0 +1,349 @@ +var Menu, MenuItem, assert, ipcRenderer, ref, ref1, remote; + +assert = require('assert'); + +ref = require('electron'), remote = ref.remote, ipcRenderer = ref.ipcRenderer; + +ref1 = remote.require('electron'), Menu = ref1.Menu, MenuItem = ref1.MenuItem; + +describe('menu module', function() { + describe('Menu.buildFromTemplate', function() { + it('should be able to attach extra fields', function() { + var menu; + menu = Menu.buildFromTemplate([ + { + label: 'text', + extra: 'field' + } + ]); + return assert.equal(menu.items[0].extra, 'field'); + }); + it('does not modify the specified template', function() { + var template; + template = ipcRenderer.sendSync('eval', "var template = [{label: 'text', submenu: [{label: 'sub'}]}];\nrequire('electron').Menu.buildFromTemplate(template);\ntemplate;"); + return assert.deepStrictEqual(template, [ + { + label: 'text', + submenu: [ + { + label: 'sub' + } + ] + } + ]); + }); + return describe('Menu.buildFromTemplate should reorder based on item position specifiers', function() { + it('should position before existing item', function() { + var menu; + menu = Menu.buildFromTemplate([ + { + label: '2', + id: '2' + }, { + label: '3', + id: '3' + }, { + label: '1', + id: '1', + position: 'before=2' + } + ]); + assert.equal(menu.items[0].label, '1'); + assert.equal(menu.items[1].label, '2'); + return assert.equal(menu.items[2].label, '3'); + }); + it('should position after existing item', function() { + var menu; + menu = Menu.buildFromTemplate([ + { + label: '1', + id: '1' + }, { + label: '3', + id: '3' + }, { + label: '2', + id: '2', + position: 'after=1' + } + ]); + assert.equal(menu.items[0].label, '1'); + assert.equal(menu.items[1].label, '2'); + return assert.equal(menu.items[2].label, '3'); + }); + it('should position at endof existing separator groups', function() { + var menu; + menu = Menu.buildFromTemplate([ + { + type: 'separator', + id: 'numbers' + }, { + type: 'separator', + id: 'letters' + }, { + label: 'a', + id: 'a', + position: 'endof=letters' + }, { + label: '1', + id: '1', + position: 'endof=numbers' + }, { + label: 'b', + id: 'b', + position: 'endof=letters' + }, { + label: '2', + id: '2', + position: 'endof=numbers' + }, { + label: 'c', + id: 'c', + position: 'endof=letters' + }, { + label: '3', + id: '3', + position: 'endof=numbers' + } + ]); + assert.equal(menu.items[0].id, 'numbers'); + assert.equal(menu.items[1].label, '1'); + assert.equal(menu.items[2].label, '2'); + assert.equal(menu.items[3].label, '3'); + assert.equal(menu.items[4].id, 'letters'); + assert.equal(menu.items[5].label, 'a'); + assert.equal(menu.items[6].label, 'b'); + return assert.equal(menu.items[7].label, 'c'); + }); + it('should create separator group if endof does not reference existing separator group', function() { + var menu; + menu = Menu.buildFromTemplate([ + { + label: 'a', + id: 'a', + position: 'endof=letters' + }, { + label: '1', + id: '1', + position: 'endof=numbers' + }, { + label: 'b', + id: 'b', + position: 'endof=letters' + }, { + label: '2', + id: '2', + position: 'endof=numbers' + }, { + label: 'c', + id: 'c', + position: 'endof=letters' + }, { + label: '3', + id: '3', + position: 'endof=numbers' + } + ]); + assert.equal(menu.items[0].id, 'letters'); + assert.equal(menu.items[1].label, 'a'); + assert.equal(menu.items[2].label, 'b'); + assert.equal(menu.items[3].label, 'c'); + assert.equal(menu.items[4].id, 'numbers'); + assert.equal(menu.items[5].label, '1'); + assert.equal(menu.items[6].label, '2'); + return assert.equal(menu.items[7].label, '3'); + }); + return it('should continue inserting items at next index when no specifier is present', function() { + var menu; + menu = Menu.buildFromTemplate([ + { + label: '4', + id: '4' + }, { + label: '5', + id: '5' + }, { + label: '1', + id: '1', + position: 'before=4' + }, { + label: '2', + id: '2' + }, { + label: '3', + id: '3' + } + ]); + assert.equal(menu.items[0].label, '1'); + assert.equal(menu.items[1].label, '2'); + assert.equal(menu.items[2].label, '3'); + assert.equal(menu.items[3].label, '4'); + return assert.equal(menu.items[4].label, '5'); + }); + }); + }); + describe('Menu.insert', function() { + return it('should store item in @items by its index', function() { + var item, menu; + menu = Menu.buildFromTemplate([ + { + label: '1' + }, { + label: '2' + }, { + label: '3' + } + ]); + item = new MenuItem({ + label: 'inserted' + }); + menu.insert(1, item); + assert.equal(menu.items[0].label, '1'); + assert.equal(menu.items[1].label, 'inserted'); + assert.equal(menu.items[2].label, '2'); + return assert.equal(menu.items[3].label, '3'); + }); + }); + describe('MenuItem.click', function() { + return it('should be called with the item object passed', function(done) { + var menu; + menu = Menu.buildFromTemplate([ + { + label: 'text', + click: function(item) { + assert.equal(item.constructor.name, 'MenuItem'); + assert.equal(item.label, 'text'); + return done(); + } + } + ]); + return menu.delegate.executeCommand(menu.items[0].commandId); + }); + }); + return describe('MenuItem with checked property', function() { + it('clicking an checkbox item should flip the checked property', function() { + var menu; + menu = Menu.buildFromTemplate([ + { + label: 'text', + type: 'checkbox' + } + ]); + assert.equal(menu.items[0].checked, false); + menu.delegate.executeCommand(menu.items[0].commandId); + return assert.equal(menu.items[0].checked, true); + }); + it('clicking an radio item should always make checked property true', function() { + var menu; + menu = Menu.buildFromTemplate([ + { + label: 'text', + type: 'radio' + } + ]); + menu.delegate.executeCommand(menu.items[0].commandId); + assert.equal(menu.items[0].checked, true); + menu.delegate.executeCommand(menu.items[0].commandId); + return assert.equal(menu.items[0].checked, true); + }); + it('at least have one item checked in each group', function() { + var i, j, k, menu, template; + template = []; + for (i = j = 0; j <= 10; i = ++j) { + template.push({ + label: "" + i, + type: 'radio' + }); + } + template.push({ + type: 'separator' + }); + for (i = k = 12; k <= 20; i = ++k) { + template.push({ + label: "" + i, + type: 'radio' + }); + } + menu = Menu.buildFromTemplate(template); + menu.delegate.menuWillShow(); + assert.equal(menu.items[0].checked, true); + return assert.equal(menu.items[12].checked, true); + }); + it('should assign groupId automatically', function() { + var groupId, i, j, k, l, m, menu, results, template; + template = []; + for (i = j = 0; j <= 10; i = ++j) { + template.push({ + label: "" + i, + type: 'radio' + }); + } + template.push({ + type: 'separator' + }); + for (i = k = 12; k <= 20; i = ++k) { + template.push({ + label: "" + i, + type: 'radio' + }); + } + menu = Menu.buildFromTemplate(template); + groupId = menu.items[0].groupId; + for (i = l = 0; l <= 10; i = ++l) { + assert.equal(menu.items[i].groupId, groupId); + } + results = []; + for (i = m = 12; m <= 20; i = ++m) { + results.push(assert.equal(menu.items[i].groupId, groupId + 1)); + } + return results; + }); + return it("setting 'checked' should flip other items' 'checked' property", function() { + var i, j, k, l, m, menu, n, o, p, q, results, template; + template = []; + for (i = j = 0; j <= 10; i = ++j) { + template.push({ + label: "" + i, + type: 'radio' + }); + } + template.push({ + type: 'separator' + }); + for (i = k = 12; k <= 20; i = ++k) { + template.push({ + label: "" + i, + type: 'radio' + }); + } + menu = Menu.buildFromTemplate(template); + for (i = l = 0; l <= 10; i = ++l) { + assert.equal(menu.items[i].checked, false); + } + menu.items[0].checked = true; + assert.equal(menu.items[0].checked, true); + for (i = m = 1; m <= 10; i = ++m) { + assert.equal(menu.items[i].checked, false); + } + menu.items[10].checked = true; + assert.equal(menu.items[10].checked, true); + for (i = n = 0; n <= 9; i = ++n) { + assert.equal(menu.items[i].checked, false); + } + for (i = o = 12; o <= 20; i = ++o) { + assert.equal(menu.items[i].checked, false); + } + menu.items[12].checked = true; + assert.equal(menu.items[10].checked, true); + for (i = p = 0; p <= 9; i = ++p) { + assert.equal(menu.items[i].checked, false); + } + assert.equal(menu.items[12].checked, true); + results = []; + for (i = q = 13; q <= 20; i = ++q) { + results.push(assert.equal(menu.items[i].checked, false)); + } + return results; + }); + }); +}); diff --git a/spec/api-protocol-spec.coffee b/spec/api-protocol-spec.coffee deleted file mode 100644 index 77eb90259bb2..000000000000 --- a/spec/api-protocol-spec.coffee +++ /dev/null @@ -1,499 +0,0 @@ -assert = require 'assert' -http = require 'http' -path = require 'path' -qs = require 'querystring' - -{remote} = require 'electron' -{protocol} = remote.require 'electron' - -describe 'protocol module', -> - protocolName = 'sp' - text = 'valar morghulis' - postData = - name: 'post test' - type: 'string' - - afterEach (done) -> - protocol.unregisterProtocol protocolName, -> - protocol.uninterceptProtocol 'http', -> done() - - describe 'protocol.register(Any)Protocol', -> - emptyHandler = (request, callback) -> callback() - it 'throws error when scheme is already registered', (done) -> - protocol.registerStringProtocol protocolName, emptyHandler, (error) -> - assert.equal error, null - protocol.registerBufferProtocol protocolName, emptyHandler, (error) -> - assert.notEqual error, null - done() - - it 'does not crash when handler is called twice', (done) -> - doubleHandler = (request, callback) -> - try - callback(text) - callback() - catch - protocol.registerStringProtocol protocolName, doubleHandler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'sends error when callback is called with nothing', (done) -> - protocol.registerBufferProtocol protocolName, emptyHandler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - done('request succeeded but it should not') - error: (xhr, errorType, error) -> - assert.equal errorType, 'error' - done() - - it 'does not crash when callback is called in next tick', (done) -> - handler = (request, callback) -> - setImmediate -> callback(text) - protocol.registerStringProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - describe 'protocol.unregisterProtocol', -> - it 'returns error when scheme does not exist', (done) -> - protocol.unregisterProtocol 'not-exist', (error) -> - assert.notEqual error, null - done() - - describe 'protocol.registerStringProtocol', -> - it 'sends string as response', (done) -> - handler = (request, callback) -> callback(text) - protocol.registerStringProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'sets Access-Control-Allow-Origin', (done) -> - handler = (request, callback) -> callback(text) - protocol.registerStringProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data, status, request) -> - assert.equal data, text - assert.equal( - request.getResponseHeader('Access-Control-Allow-Origin'), - '*') - done() - error: (xhr, errorType, error) -> - done(error) - - it 'sends object as response', (done) -> - handler = (request, callback) -> callback(data: text, mimeType: 'text/html') - protocol.registerStringProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data, statux, request) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'fails when sending object other than string', (done) -> - handler = (request, callback) -> callback(new Date) - protocol.registerBufferProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - done('request succeeded but it should not') - error: (xhr, errorType, error) -> - assert.equal errorType, 'error' - done() - - describe 'protocol.registerBufferProtocol', -> - buffer = new Buffer(text) - - it 'sends Buffer as response', (done) -> - handler = (request, callback) -> callback(buffer) - protocol.registerBufferProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'sets Access-Control-Allow-Origin', (done) -> - handler = (request, callback) -> callback(buffer) - protocol.registerBufferProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data, status, request) -> - assert.equal data, text - assert.equal( - request.getResponseHeader('Access-Control-Allow-Origin'), - '*') - done() - error: (xhr, errorType, error) -> - done(error) - - it 'sends object as response', (done) -> - handler = (request, callback) -> callback(data: buffer, mimeType: 'text/html') - protocol.registerBufferProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data, statux, request) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'fails when sending string', (done) -> - handler = (request, callback) -> callback(text) - protocol.registerBufferProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - done('request succeeded but it should not') - error: (xhr, errorType, error) -> - assert.equal errorType, 'error' - done() - - describe 'protocol.registerFileProtocol', -> - filePath = path.join __dirname, 'fixtures', 'asar', 'a.asar', 'file1' - fileContent = require('fs').readFileSync(filePath) - - normalPath = path.join __dirname, 'fixtures', 'pages', 'a.html' - normalContent = require('fs').readFileSync(normalPath) - - it 'sends file path as response', (done) -> - handler = (request, callback) -> callback(filePath) - protocol.registerFileProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - assert.equal data, String(fileContent) - done() - error: (xhr, errorType, error) -> - done(error) - - it 'sets Access-Control-Allow-Origin', (done) -> - handler = (request, callback) -> callback(filePath) - protocol.registerFileProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data, status, request) -> - assert.equal data, String(fileContent) - assert.equal( - request.getResponseHeader('Access-Control-Allow-Origin'), - '*') - done() - error: (xhr, errorType, error) -> - done(error) - - it 'sends object as response', (done) -> - handler = (request, callback) -> callback(path: filePath) - protocol.registerFileProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data, statux, request) -> - assert.equal data, String(fileContent) - done() - error: (xhr, errorType, error) -> - done(error) - - it 'can send normal file', (done) -> - handler = (request, callback) -> callback(normalPath) - protocol.registerFileProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - assert.equal data, String(normalContent) - done() - error: (xhr, errorType, error) -> - done(error) - - it 'fails when sending unexist-file', (done) -> - fakeFilePath = path.join __dirname, 'fixtures', 'asar', 'a.asar', 'not-exist' - handler = (request, callback) -> callback(fakeFilePath) - protocol.registerBufferProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - done('request succeeded but it should not') - error: (xhr, errorType, error) -> - assert.equal errorType, 'error' - done() - - it 'fails when sending unsupported content', (done) -> - handler = (request, callback) -> callback(new Date) - protocol.registerBufferProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - done('request succeeded but it should not') - error: (xhr, errorType, error) -> - assert.equal errorType, 'error' - done() - - describe 'protocol.registerHttpProtocol', -> - it 'sends url as response', (done) -> - server = http.createServer (req, res) -> - assert.notEqual req.headers.accept, '' - res.end(text) - server.close() - server.listen 0, '127.0.0.1', -> - {port} = server.address() - url = "http://127.0.0.1:#{port}" - handler = (request, callback) -> callback({url}) - protocol.registerHttpProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'fails when sending invalid url', (done) -> - handler = (request, callback) -> callback({url: 'url'}) - protocol.registerHttpProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - done('request succeeded but it should not') - error: (xhr, errorType, error) -> - assert.equal errorType, 'error' - done() - - it 'fails when sending unsupported content', (done) -> - handler = (request, callback) -> callback(new Date) - protocol.registerHttpProtocol protocolName, handler, (error) -> - return done(error) if error - $.ajax - url: "#{protocolName}://fake-host" - success: (data) -> - done('request succeeded but it should not') - error: (xhr, errorType, error) -> - assert.equal errorType, 'error' - done() - - describe 'protocol.isProtocolHandled', -> - it 'returns true for file:', (done) -> - protocol.isProtocolHandled 'file', (result) -> - assert.equal result, true - done() - - it 'returns true for http:', (done) -> - protocol.isProtocolHandled 'http', (result) -> - assert.equal result, true - done() - - it 'returns true for https:', (done) -> - protocol.isProtocolHandled 'https', (result) -> - assert.equal result, true - done() - - it 'returns false when scheme is not registred', (done) -> - protocol.isProtocolHandled 'no-exist', (result) -> - assert.equal result, false - done() - - it 'returns true for custom protocol', (done) -> - emptyHandler = (request, callback) -> callback() - protocol.registerStringProtocol protocolName, emptyHandler, (error) -> - assert.equal error, null - protocol.isProtocolHandled protocolName, (result) -> - assert.equal result, true - done() - - it 'returns true for intercepted protocol', (done) -> - emptyHandler = (request, callback) -> callback() - protocol.interceptStringProtocol 'http', emptyHandler, (error) -> - assert.equal error, null - protocol.isProtocolHandled 'http', (result) -> - assert.equal result, true - done() - - describe 'protocol.intercept(Any)Protocol', -> - emptyHandler = (request, callback) -> callback() - - it 'throws error when scheme is already intercepted', (done) -> - protocol.interceptStringProtocol 'http', emptyHandler, (error) -> - assert.equal error, null - protocol.interceptBufferProtocol 'http', emptyHandler, (error) -> - assert.notEqual error, null - done() - - it 'does not crash when handler is called twice', (done) -> - doubleHandler = (request, callback) -> - try - callback(text) - callback() - catch - protocol.interceptStringProtocol 'http', doubleHandler, (error) -> - return done(error) if error - $.ajax - url: 'http://fake-host' - success: (data) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'sends error when callback is called with nothing', (done) -> - # Flaky on Travis. - return done() if process.env.TRAVIS is 'true' - - protocol.interceptBufferProtocol 'http', emptyHandler, (error) -> - return done(error) if error - $.ajax - url: 'http://fake-host' - success: (data) -> - done('request succeeded but it should not') - error: (xhr, errorType, error) -> - assert.equal errorType, 'error' - done() - - describe 'protocol.interceptStringProtocol', -> - it 'can intercept http protocol', (done) -> - handler = (request, callback) -> callback(text) - protocol.interceptStringProtocol 'http', handler, (error) -> - return done(error) if error - $.ajax - url: 'http://fake-host' - success: (data) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'can set content-type', (done) -> - handler = (request, callback) -> - callback({mimeType: 'application/json', data: '{"value": 1}'}) - protocol.interceptStringProtocol 'http', handler, (error) -> - return done(error) if error - $.ajax - url: 'http://fake-host' - success: (data) -> - assert.equal typeof(data), 'object' - assert.equal data.value, 1 - done() - error: (xhr, errorType, error) -> - done(error) - - it 'can receive post data', (done) -> - handler = (request, callback) -> - uploadData = request.uploadData[0].bytes.toString() - callback({data: uploadData}) - protocol.interceptStringProtocol 'http', handler, (error) -> - return done(error) if error - $.ajax - url: "http://fake-host" - type: "POST" - data: postData - success: (data) -> - assert.deepEqual qs.parse(data), postData - done() - error: (xhr, errorType, error) -> - done(error) - - describe 'protocol.interceptBufferProtocol', -> - it 'can intercept http protocol', (done) -> - handler = (request, callback) -> callback(new Buffer(text)) - protocol.interceptBufferProtocol 'http', handler, (error) -> - return done(error) if error - $.ajax - url: 'http://fake-host' - success: (data) -> - assert.equal data, text - done() - error: (xhr, errorType, error) -> - done(error) - - it 'can receive post data', (done) -> - handler = (request, callback) -> - uploadData = request.uploadData[0].bytes - callback(uploadData) - protocol.interceptBufferProtocol 'http', handler, (error) -> - return done(error) if error - $.ajax - url: "http://fake-host" - type: "POST" - data: postData - success: (data) -> - assert.equal data, $.param postData - done() - error: (xhr, errorType, error) -> - done(error) - - describe 'protocol.interceptHttpProtocol', -> - it 'can send POST request', (done) -> - server = http.createServer (req, res) -> - body = '' - req.on 'data', (chunk) -> - body += chunk - req.on 'end', -> - res.end body - server.close() - server.listen 0, '127.0.0.1', -> - {port} = server.address() - url = "http://127.0.0.1:#{port}" - handler = (request, callback) -> - data = - url: url - method: 'POST' - uploadData: - contentType: 'application/x-www-form-urlencoded' - data: request.uploadData[0].bytes.toString() - session: null - callback(data) - protocol.interceptHttpProtocol 'http', handler, (error) -> - return done(error) if error - $.ajax - url: "http://fake-host" - type: "POST" - data: postData - success: (data) -> - assert.deepEqual qs.parse(data), postData - done() - error: (xhr, errorType, error) -> - done(error) - - describe 'protocol.uninterceptProtocol', -> - it 'returns error when scheme does not exist', (done) -> - protocol.uninterceptProtocol 'not-exist', (error) -> - assert.notEqual error, null - done() - - it 'returns error when scheme is not intercepted', (done) -> - protocol.uninterceptProtocol 'http', (error) -> - assert.notEqual error, null - done() diff --git a/spec/api-protocol-spec.js b/spec/api-protocol-spec.js new file mode 100644 index 000000000000..f0f19ecc5fb7 --- /dev/null +++ b/spec/api-protocol-spec.js @@ -0,0 +1,820 @@ +var assert, http, path, protocol, qs, remote; + +assert = require('assert'); + +http = require('http'); + +path = require('path'); + +qs = require('querystring'); + +remote = require('electron').remote; + +protocol = remote.require('electron').protocol; + +describe('protocol module', function() { + var postData, protocolName, text; + protocolName = 'sp'; + text = 'valar morghulis'; + postData = { + name: 'post test', + type: 'string' + }; + afterEach(function(done) { + return protocol.unregisterProtocol(protocolName, function() { + return protocol.uninterceptProtocol('http', function() { + return done(); + }); + }); + }); + describe('protocol.register(Any)Protocol', function() { + var emptyHandler; + emptyHandler = function(request, callback) { + return callback(); + }; + it('throws error when scheme is already registered', function(done) { + return protocol.registerStringProtocol(protocolName, emptyHandler, function(error) { + assert.equal(error, null); + return protocol.registerBufferProtocol(protocolName, emptyHandler, function(error) { + assert.notEqual(error, null); + return done(); + }); + }); + }); + it('does not crash when handler is called twice', function(done) { + var doubleHandler; + doubleHandler = function(request, callback) { + var error1; + try { + callback(text); + return callback(); + } catch (error1) { + + } + }; + return protocol.registerStringProtocol(protocolName, doubleHandler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('sends error when callback is called with nothing', function(done) { + return protocol.registerBufferProtocol(protocolName, emptyHandler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + return done('request succeeded but it should not'); + }, + error: function(xhr, errorType, error) { + assert.equal(errorType, 'error'); + return done(); + } + }); + }); + }); + return it('does not crash when callback is called in next tick', function(done) { + var handler; + handler = function(request, callback) { + return setImmediate(function() { + return callback(text); + }); + }; + return protocol.registerStringProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + }); + describe('protocol.unregisterProtocol', function() { + return it('returns error when scheme does not exist', function(done) { + return protocol.unregisterProtocol('not-exist', function(error) { + assert.notEqual(error, null); + return done(); + }); + }); + }); + describe('protocol.registerStringProtocol', function() { + it('sends string as response', function(done) { + var handler; + handler = function(request, callback) { + return callback(text); + }; + return protocol.registerStringProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('sets Access-Control-Allow-Origin', function(done) { + var handler; + handler = function(request, callback) { + return callback(text); + }; + return protocol.registerStringProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data, status, request) { + assert.equal(data, text); + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('sends object as response', function(done) { + var handler; + handler = function(request, callback) { + return callback({ + data: text, + mimeType: 'text/html' + }); + }; + return protocol.registerStringProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data, statux, request) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + return it('fails when sending object other than string', function(done) { + var handler; + handler = function(request, callback) { + return callback(new Date); + }; + return protocol.registerBufferProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + return done('request succeeded but it should not'); + }, + error: function(xhr, errorType, error) { + assert.equal(errorType, 'error'); + return done(); + } + }); + }); + }); + }); + describe('protocol.registerBufferProtocol', function() { + var buffer; + buffer = new Buffer(text); + it('sends Buffer as response', function(done) { + var handler; + handler = function(request, callback) { + return callback(buffer); + }; + return protocol.registerBufferProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('sets Access-Control-Allow-Origin', function(done) { + var handler; + handler = function(request, callback) { + return callback(buffer); + }; + return protocol.registerBufferProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data, status, request) { + assert.equal(data, text); + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('sends object as response', function(done) { + var handler; + handler = function(request, callback) { + return callback({ + data: buffer, + mimeType: 'text/html' + }); + }; + return protocol.registerBufferProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data, statux, request) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + return it('fails when sending string', function(done) { + var handler; + handler = function(request, callback) { + return callback(text); + }; + return protocol.registerBufferProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + return done('request succeeded but it should not'); + }, + error: function(xhr, errorType, error) { + assert.equal(errorType, 'error'); + return done(); + } + }); + }); + }); + }); + describe('protocol.registerFileProtocol', function() { + var fileContent, filePath, normalContent, normalPath; + filePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'file1'); + fileContent = require('fs').readFileSync(filePath); + normalPath = path.join(__dirname, 'fixtures', 'pages', 'a.html'); + normalContent = require('fs').readFileSync(normalPath); + it('sends file path as response', function(done) { + var handler; + handler = function(request, callback) { + return callback(filePath); + }; + return protocol.registerFileProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + assert.equal(data, String(fileContent)); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('sets Access-Control-Allow-Origin', function(done) { + var handler; + handler = function(request, callback) { + return callback(filePath); + }; + return protocol.registerFileProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data, status, request) { + assert.equal(data, String(fileContent)); + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('sends object as response', function(done) { + var handler; + handler = function(request, callback) { + return callback({ + path: filePath + }); + }; + return protocol.registerFileProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data, statux, request) { + assert.equal(data, String(fileContent)); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('can send normal file', function(done) { + var handler; + handler = function(request, callback) { + return callback(normalPath); + }; + return protocol.registerFileProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + assert.equal(data, String(normalContent)); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('fails when sending unexist-file', function(done) { + var fakeFilePath, handler; + fakeFilePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'not-exist'); + handler = function(request, callback) { + return callback(fakeFilePath); + }; + return protocol.registerBufferProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + return done('request succeeded but it should not'); + }, + error: function(xhr, errorType, error) { + assert.equal(errorType, 'error'); + return done(); + } + }); + }); + }); + return it('fails when sending unsupported content', function(done) { + var handler; + handler = function(request, callback) { + return callback(new Date); + }; + return protocol.registerBufferProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + return done('request succeeded but it should not'); + }, + error: function(xhr, errorType, error) { + assert.equal(errorType, 'error'); + return done(); + } + }); + }); + }); + }); + describe('protocol.registerHttpProtocol', function() { + it('sends url as response', function(done) { + var server; + server = http.createServer(function(req, res) { + assert.notEqual(req.headers.accept, ''); + res.end(text); + return server.close(); + }); + return server.listen(0, '127.0.0.1', function() { + var handler, port, url; + port = server.address().port; + url = "http://127.0.0.1:" + port; + handler = function(request, callback) { + return callback({ + url: url + }); + }; + return protocol.registerHttpProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + }); + it('fails when sending invalid url', function(done) { + var handler; + handler = function(request, callback) { + return callback({ + url: 'url' + }); + }; + return protocol.registerHttpProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + return done('request succeeded but it should not'); + }, + error: function(xhr, errorType, error) { + assert.equal(errorType, 'error'); + return done(); + } + }); + }); + }); + return it('fails when sending unsupported content', function(done) { + var handler; + handler = function(request, callback) { + return callback(new Date); + }; + return protocol.registerHttpProtocol(protocolName, handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: protocolName + "://fake-host", + success: function(data) { + return done('request succeeded but it should not'); + }, + error: function(xhr, errorType, error) { + assert.equal(errorType, 'error'); + return done(); + } + }); + }); + }); + }); + describe('protocol.isProtocolHandled', function() { + it('returns true for file:', function(done) { + return protocol.isProtocolHandled('file', function(result) { + assert.equal(result, true); + return done(); + }); + }); + it('returns true for http:', function(done) { + return protocol.isProtocolHandled('http', function(result) { + assert.equal(result, true); + return done(); + }); + }); + it('returns true for https:', function(done) { + return protocol.isProtocolHandled('https', function(result) { + assert.equal(result, true); + return done(); + }); + }); + it('returns false when scheme is not registred', function(done) { + return protocol.isProtocolHandled('no-exist', function(result) { + assert.equal(result, false); + return done(); + }); + }); + it('returns true for custom protocol', function(done) { + var emptyHandler; + emptyHandler = function(request, callback) { + return callback(); + }; + return protocol.registerStringProtocol(protocolName, emptyHandler, function(error) { + assert.equal(error, null); + return protocol.isProtocolHandled(protocolName, function(result) { + assert.equal(result, true); + return done(); + }); + }); + }); + return it('returns true for intercepted protocol', function(done) { + var emptyHandler; + emptyHandler = function(request, callback) { + return callback(); + }; + return protocol.interceptStringProtocol('http', emptyHandler, function(error) { + assert.equal(error, null); + return protocol.isProtocolHandled('http', function(result) { + assert.equal(result, true); + return done(); + }); + }); + }); + }); + describe('protocol.intercept(Any)Protocol', function() { + var emptyHandler; + emptyHandler = function(request, callback) { + return callback(); + }; + it('throws error when scheme is already intercepted', function(done) { + return protocol.interceptStringProtocol('http', emptyHandler, function(error) { + assert.equal(error, null); + return protocol.interceptBufferProtocol('http', emptyHandler, function(error) { + assert.notEqual(error, null); + return done(); + }); + }); + }); + it('does not crash when handler is called twice', function(done) { + var doubleHandler; + doubleHandler = function(request, callback) { + var error1; + try { + callback(text); + return callback(); + } catch (error1) { + + } + }; + return protocol.interceptStringProtocol('http', doubleHandler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: 'http://fake-host', + success: function(data) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + return it('sends error when callback is called with nothing', function(done) { + if (process.env.TRAVIS === 'true') { + return done(); + } + return protocol.interceptBufferProtocol('http', emptyHandler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: 'http://fake-host', + success: function(data) { + return done('request succeeded but it should not'); + }, + error: function(xhr, errorType, error) { + assert.equal(errorType, 'error'); + return done(); + } + }); + }); + }); + }); + describe('protocol.interceptStringProtocol', function() { + it('can intercept http protocol', function(done) { + var handler; + handler = function(request, callback) { + return callback(text); + }; + return protocol.interceptStringProtocol('http', handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: 'http://fake-host', + success: function(data) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + it('can set content-type', function(done) { + var handler; + handler = function(request, callback) { + return callback({ + mimeType: 'application/json', + data: '{"value": 1}' + }); + }; + return protocol.interceptStringProtocol('http', handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: 'http://fake-host', + success: function(data) { + assert.equal(typeof data, 'object'); + assert.equal(data.value, 1); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + return it('can receive post data', function(done) { + var handler; + handler = function(request, callback) { + var uploadData; + uploadData = request.uploadData[0].bytes.toString(); + return callback({ + data: uploadData + }); + }; + return protocol.interceptStringProtocol('http', handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: "http://fake-host", + type: "POST", + data: postData, + success: function(data) { + assert.deepEqual(qs.parse(data), postData); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + }); + describe('protocol.interceptBufferProtocol', function() { + it('can intercept http protocol', function(done) { + var handler; + handler = function(request, callback) { + return callback(new Buffer(text)); + }; + return protocol.interceptBufferProtocol('http', handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: 'http://fake-host', + success: function(data) { + assert.equal(data, text); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + return it('can receive post data', function(done) { + var handler; + handler = function(request, callback) { + var uploadData; + uploadData = request.uploadData[0].bytes; + return callback(uploadData); + }; + return protocol.interceptBufferProtocol('http', handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: "http://fake-host", + type: "POST", + data: postData, + success: function(data) { + assert.equal(data, $.param(postData)); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + }); + describe('protocol.interceptHttpProtocol', function() { + return it('can send POST request', function(done) { + var server; + server = http.createServer(function(req, res) { + var body; + body = ''; + req.on('data', function(chunk) { + return body += chunk; + }); + req.on('end', function() { + return res.end(body); + }); + return server.close(); + }); + return server.listen(0, '127.0.0.1', function() { + var handler, port, url; + port = server.address().port; + url = "http://127.0.0.1:" + port; + handler = function(request, callback) { + var data; + data = { + url: url, + method: 'POST', + uploadData: { + contentType: 'application/x-www-form-urlencoded', + data: request.uploadData[0].bytes.toString() + }, + session: null + }; + return callback(data); + }; + return protocol.interceptHttpProtocol('http', handler, function(error) { + if (error) { + return done(error); + } + return $.ajax({ + url: "http://fake-host", + type: "POST", + data: postData, + success: function(data) { + assert.deepEqual(qs.parse(data), postData); + return done(); + }, + error: function(xhr, errorType, error) { + return done(error); + } + }); + }); + }); + }); + }); + return describe('protocol.uninterceptProtocol', function() { + it('returns error when scheme does not exist', function(done) { + return protocol.uninterceptProtocol('not-exist', function(error) { + assert.notEqual(error, null); + return done(); + }); + }); + return it('returns error when scheme is not intercepted', function(done) { + return protocol.uninterceptProtocol('http', function(error) { + assert.notEqual(error, null); + return done(); + }); + }); + }); +}); diff --git a/spec/api-screen-spec.coffee b/spec/api-screen-spec.coffee deleted file mode 100644 index 59ef49631cbc..000000000000 --- a/spec/api-screen-spec.coffee +++ /dev/null @@ -1,17 +0,0 @@ -assert = require 'assert' - -{screen} = require 'electron' - -describe 'screen module', -> - describe 'screen.getCursorScreenPoint()', -> - it 'returns a point object', -> - point = screen.getCursorScreenPoint() - assert.equal typeof(point.x), 'number' - assert.equal typeof(point.y), 'number' - - describe 'screen.getPrimaryDisplay()', -> - it 'returns a display object', -> - display = screen.getPrimaryDisplay() - assert.equal typeof(display.scaleFactor), 'number' - assert display.size.width > 0 - assert display.size.height > 0 diff --git a/spec/api-screen-spec.js b/spec/api-screen-spec.js new file mode 100644 index 000000000000..b393d4b99ee8 --- /dev/null +++ b/spec/api-screen-spec.js @@ -0,0 +1,25 @@ +var assert, screen; + +assert = require('assert'); + +screen = require('electron').screen; + +describe('screen module', function() { + describe('screen.getCursorScreenPoint()', function() { + return it('returns a point object', function() { + var point; + point = screen.getCursorScreenPoint(); + assert.equal(typeof point.x, 'number'); + return assert.equal(typeof point.y, 'number'); + }); + }); + return describe('screen.getPrimaryDisplay()', function() { + return it('returns a display object', function() { + var display; + display = screen.getPrimaryDisplay(); + assert.equal(typeof display.scaleFactor, 'number'); + assert(display.size.width > 0); + return assert(display.size.height > 0); + }); + }); +}); diff --git a/spec/api-session-spec.coffee b/spec/api-session-spec.coffee deleted file mode 100644 index 03c62c1f1db6..000000000000 --- a/spec/api-session-spec.coffee +++ /dev/null @@ -1,138 +0,0 @@ -assert = require 'assert' -http = require 'http' -path = require 'path' -fs = require 'fs' - -{ipcRenderer, remote} = require 'electron' -{app, ipcMain, session, BrowserWindow} = remote - -describe 'session module', -> - @timeout 10000 - fixtures = path.resolve __dirname, 'fixtures' - w = null - url = "http://127.0.0.1" - - beforeEach -> w = new BrowserWindow(show: false, width: 400, height: 400) - afterEach -> w.destroy() - - it 'should get cookies', (done) -> - server = http.createServer (req, res) -> - res.setHeader('Set-Cookie', ['0=0']) - res.end('finished') - server.close() - - server.listen 0, '127.0.0.1', -> - {port} = server.address() - w.loadURL "#{url}:#{port}" - w.webContents.on 'did-finish-load', -> - w.webContents.session.cookies.get {url: url}, (error, list) -> - return done(error) if error - for cookie in list when cookie.name is '0' - if cookie.value is '0' - return done() - else - return done("cookie value is #{cookie.value} while expecting 0") - done('Can not find cookie') - - it 'should over-write the existent cookie', (done) -> - session.defaultSession.cookies.set {url: url, name: '1', value: '1'}, (error) -> - return done(error) if error - session.defaultSession.cookies.get {url: url}, (error, list) -> - return done(error) if error - for cookie in list when cookie.name is '1' - if cookie.value is '1' - return done() - else - return done("cookie value is #{cookie.value} while expecting 1") - done('Can not find cookie') - - it 'should remove cookies', (done) -> - session.defaultSession.cookies.set {url: url, name: '2', value: '2'}, (error) -> - return done(error) if error - session.defaultSession.cookies.remove url, '2', -> - session.defaultSession.cookies.get {url: url}, (error, list) -> - return done(error) if error - for cookie in list when cookie.name is '2' - return done('Cookie not deleted') - done() - - describe 'session.clearStorageData(options)', -> - fixtures = path.resolve __dirname, 'fixtures' - it 'clears localstorage data', (done) -> - ipcMain.on 'count', (event, count) -> - ipcMain.removeAllListeners 'count' - assert not count - done() - w.loadURL 'file://' + path.join(fixtures, 'api', 'localstorage.html') - w.webContents.on 'did-finish-load', -> - options = - origin: "file://", - storages: ['localstorage'], - quotas: ['persistent'], - w.webContents.session.clearStorageData options, -> - w.webContents.send 'getcount' - - describe 'DownloadItem', -> - # A 5 MB mock pdf. - mockPDF = new Buffer 1024 * 1024 * 5 - contentDisposition = 'inline; filename="mock.pdf"' - downloadFilePath = path.join fixtures, 'mock.pdf' - downloadServer = http.createServer (req, res) -> - res.writeHead 200, { - 'Content-Length': mockPDF.length, - 'Content-Type': 'application/pdf', - 'Content-Disposition': contentDisposition - } - res.end mockPDF - downloadServer.close() - - assertDownload = (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) -> - assert.equal state, 'completed' - assert.equal filename, 'mock.pdf' - assert.equal url, "http://127.0.0.1:#{port}/" - assert.equal mimeType, 'application/pdf' - assert.equal receivedBytes, mockPDF.length - assert.equal totalBytes, mockPDF.length - assert.equal disposition, contentDisposition - assert fs.existsSync downloadFilePath - fs.unlinkSync downloadFilePath - - it 'can download using BrowserWindow.loadURL', (done) -> - downloadServer.listen 0, '127.0.0.1', -> - {port} = downloadServer.address() - ipcRenderer.sendSync 'set-download-option', false - w.loadURL "#{url}:#{port}" - ipcRenderer.once 'download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) -> - assertDownload event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port - done() - - it 'can download using WebView.downloadURL', (done) -> - downloadServer.listen 0, '127.0.0.1', -> - {port} = downloadServer.address() - ipcRenderer.sendSync 'set-download-option', false - - webview = new WebView - webview.src = "file://#{fixtures}/api/blank.html" - webview.addEventListener 'did-finish-load', -> - webview.downloadURL "#{url}:#{port}/" - - ipcRenderer.once 'download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) -> - assertDownload event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port - document.body.removeChild(webview) - done() - - document.body.appendChild webview - - it 'can cancel download', (done) -> - downloadServer.listen 0, '127.0.0.1', -> - {port} = downloadServer.address() - ipcRenderer.sendSync 'set-download-option', true - w.loadURL "#{url}:#{port}/" - ipcRenderer.once 'download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) -> - assert.equal state, 'cancelled' - assert.equal filename, 'mock.pdf' - assert.equal mimeType, 'application/pdf' - assert.equal receivedBytes, 0 - assert.equal totalBytes, mockPDF.length - assert.equal disposition, contentDisposition - done() diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js new file mode 100644 index 000000000000..4045d9e6fdac --- /dev/null +++ b/spec/api-session-spec.js @@ -0,0 +1,222 @@ +var BrowserWindow, app, assert, fs, http, ipcMain, ipcRenderer, path, ref, remote, session; + +assert = require('assert'); + +http = require('http'); + +path = require('path'); + +fs = require('fs'); + +ref = require('electron'), ipcRenderer = ref.ipcRenderer, remote = ref.remote; + +app = remote.app, ipcMain = remote.ipcMain, session = remote.session, BrowserWindow = remote.BrowserWindow; + +describe('session module', function() { + var fixtures, url, w; + this.timeout(10000); + fixtures = path.resolve(__dirname, 'fixtures'); + w = null; + url = "http://127.0.0.1"; + beforeEach(function() { + return w = new BrowserWindow({ + show: false, + width: 400, + height: 400 + }); + }); + afterEach(function() { + return w.destroy(); + }); + + describe('session.cookies', function() { + it('should get cookies', function(done) { + var server; + server = http.createServer(function(req, res) { + res.setHeader('Set-Cookie', ['0=0']); + res.end('finished'); + return server.close(); + }); + return server.listen(0, '127.0.0.1', function() { + var port; + port = server.address().port; + w.loadURL(url + ":" + port); + return w.webContents.on('did-finish-load', function() { + return w.webContents.session.cookies.get({ + url: url + }, function(error, list) { + var cookie, i, len; + if (error) { + return done(error); + } + for (i = 0, len = list.length; i < len; i++) { + cookie = list[i]; + if (cookie.name === '0') { + if (cookie.value === '0') { + return done(); + } else { + return done("cookie value is " + cookie.value + " while expecting 0"); + } + } + } + return done('Can not find cookie'); + }); + }); + }); + }); + it('should over-write the existent cookie', function(done) { + return session.defaultSession.cookies.set({ + url: url, + name: '1', + value: '1' + }, function(error) { + if (error) { + return done(error); + } + return session.defaultSession.cookies.get({ + url: url + }, function(error, list) { + var cookie, i, len; + if (error) { + return done(error); + } + for (i = 0, len = list.length; i < len; i++) { + cookie = list[i]; + if (cookie.name === '1') { + if (cookie.value === '1') { + return done(); + } else { + return done("cookie value is " + cookie.value + " while expecting 1"); + } + } + } + return done('Can not find cookie'); + }); + }); + }); + it('should remove cookies', function(done) { + return session.defaultSession.cookies.set({ + url: url, + name: '2', + value: '2' + }, function(error) { + if (error) { + return done(error); + } + return session.defaultSession.cookies.remove(url, '2', function() { + return session.defaultSession.cookies.get({ + url: url + }, function(error, list) { + var cookie, i, len; + if (error) { + return done(error); + } + for (i = 0, len = list.length; i < len; i++) { + cookie = list[i]; + if (cookie.name === '2') { + return done('Cookie not deleted'); + } + } + return done(); + }); + }); + }); + }); + }); + + describe('session.clearStorageData(options)', function() { + fixtures = path.resolve(__dirname, 'fixtures'); + return it('clears localstorage data', function(done) { + ipcMain.on('count', function(event, count) { + ipcMain.removeAllListeners('count'); + assert(!count); + return done(); + }); + w.loadURL('file://' + path.join(fixtures, 'api', 'localstorage.html')); + return w.webContents.on('did-finish-load', function() { + var options; + options = { + origin: "file://", + storages: ['localstorage'], + quotas: ['persistent'] + }; + return w.webContents.session.clearStorageData(options, function() { + return w.webContents.send('getcount'); + }); + }); + }); + }); + return describe('DownloadItem', function() { + var assertDownload, contentDisposition, downloadFilePath, downloadServer, mockPDF; + mockPDF = new Buffer(1024 * 1024 * 5); + contentDisposition = 'inline; filename="mock.pdf"'; + downloadFilePath = path.join(fixtures, 'mock.pdf'); + downloadServer = http.createServer(function(req, res) { + res.writeHead(200, { + 'Content-Length': mockPDF.length, + 'Content-Type': 'application/pdf', + 'Content-Disposition': contentDisposition + }); + res.end(mockPDF); + return downloadServer.close(); + }); + assertDownload = function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) { + assert.equal(state, 'completed'); + assert.equal(filename, 'mock.pdf'); + assert.equal(url, "http://127.0.0.1:" + port + "/"); + assert.equal(mimeType, 'application/pdf'); + assert.equal(receivedBytes, mockPDF.length); + assert.equal(totalBytes, mockPDF.length); + assert.equal(disposition, contentDisposition); + assert(fs.existsSync(downloadFilePath)); + return fs.unlinkSync(downloadFilePath); + }; + it('can download using BrowserWindow.loadURL', function(done) { + return downloadServer.listen(0, '127.0.0.1', function() { + var port; + port = downloadServer.address().port; + ipcRenderer.sendSync('set-download-option', false); + w.loadURL(url + ":" + port); + return ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port); + return done(); + }); + }); + }); + it('can download using WebView.downloadURL', function(done) { + return downloadServer.listen(0, '127.0.0.1', function() { + var port, webview; + port = downloadServer.address().port; + ipcRenderer.sendSync('set-download-option', false); + webview = new WebView; + webview.src = "file://" + fixtures + "/api/blank.html"; + webview.addEventListener('did-finish-load', function() { + return webview.downloadURL(url + ":" + port + "/"); + }); + ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port); + document.body.removeChild(webview); + return done(); + }); + return document.body.appendChild(webview); + }); + }); + return it('can cancel download', function(done) { + return downloadServer.listen(0, '127.0.0.1', function() { + var port; + port = downloadServer.address().port; + ipcRenderer.sendSync('set-download-option', true); + w.loadURL(url + ":" + port + "/"); + return ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assert.equal(state, 'cancelled'); + assert.equal(filename, 'mock.pdf'); + assert.equal(mimeType, 'application/pdf'); + assert.equal(receivedBytes, 0); + assert.equal(totalBytes, mockPDF.length); + assert.equal(disposition, contentDisposition); + return done(); + }); + }); + }); + }); +}); diff --git a/spec/api-web-frame-spec.coffee b/spec/api-web-frame-spec.coffee deleted file mode 100644 index cece329084e1..000000000000 --- a/spec/api-web-frame-spec.coffee +++ /dev/null @@ -1,18 +0,0 @@ -assert = require 'assert' -path = require 'path' - -{webFrame} = require 'electron' - -describe 'webFrame module', -> - fixtures = path.resolve __dirname, 'fixtures' - - describe 'webFrame.registerURLSchemeAsPrivileged', -> - it 'supports fetch api', (done) -> - webFrame.registerURLSchemeAsPrivileged 'file' - url = "file://#{fixtures}/assets/logo.png" - - fetch(url).then((response) -> - assert response.ok - done() - ).catch (err) -> - done('unexpected error : ' + err) diff --git a/spec/api-web-frame-spec.js b/spec/api-web-frame-spec.js new file mode 100644 index 000000000000..3d287a6acba9 --- /dev/null +++ b/spec/api-web-frame-spec.js @@ -0,0 +1,25 @@ +var assert, path, webFrame; + +assert = require('assert'); + +path = require('path'); + +webFrame = require('electron').webFrame; + +describe('webFrame module', function() { + var fixtures; + fixtures = path.resolve(__dirname, 'fixtures'); + return describe('webFrame.registerURLSchemeAsPrivileged', function() { + return it('supports fetch api', function(done) { + var url; + webFrame.registerURLSchemeAsPrivileged('file'); + url = "file://" + fixtures + "/assets/logo.png"; + return fetch(url).then(function(response) { + assert(response.ok); + return done(); + })["catch"](function(err) { + return done('unexpected error : ' + err); + }); + }); + }); +}); diff --git a/spec/api-web-request-spec.coffee b/spec/api-web-request-spec.coffee deleted file mode 100644 index 5c78ef1d30a6..000000000000 --- a/spec/api-web-request-spec.coffee +++ /dev/null @@ -1,243 +0,0 @@ -assert = require 'assert' -http = require 'http' - -{remote} = require 'electron' -{session} = remote - -describe 'webRequest module', -> - ses = session.defaultSession - server = http.createServer (req, res) -> - res.setHeader('Custom', ['Header']) - content = req.url - if req.headers.accept is '*/*;test/header' - content += 'header/received' - res.end content - defaultURL = null - - before (done) -> - server.listen 0, '127.0.0.1', -> - {port} = server.address() - defaultURL = "http://127.0.0.1:#{port}/" - done() - after -> - server.close() - - describe 'webRequest.onBeforeRequest', -> - afterEach -> - ses.webRequest.onBeforeRequest null - - it 'can cancel the request', (done) -> - ses.webRequest.onBeforeRequest (details, callback) -> - callback(cancel: true) - $.ajax - url: defaultURL - success: (data) -> done('unexpected success') - error: (xhr, errorType, error) -> done() - - it 'can filter URLs', (done) -> - filter = urls: ["#{defaultURL}filter/*"] - ses.webRequest.onBeforeRequest filter, (details, callback) -> - callback(cancel: true) - $.ajax - url: "#{defaultURL}nofilter/test" - success: (data) -> - assert.equal data, '/nofilter/test' - $.ajax - url: "#{defaultURL}filter/test" - success: (data) -> done('unexpected success') - error: (xhr, errorType, error) -> done() - error: (xhr, errorType, error) -> done(errorType) - - it 'receives details object', (done) -> - ses.webRequest.onBeforeRequest (details, callback) -> - assert.equal typeof details.id, 'number' - assert.equal typeof details.timestamp, 'number' - assert.equal details.url, defaultURL - assert.equal details.method, 'GET' - assert.equal details.resourceType, 'xhr' - callback({}) - $.ajax - url: defaultURL - success: (data) -> - assert.equal data, '/' - done() - error: (xhr, errorType, error) -> done(errorType) - - it 'can redirect the request', (done) -> - ses.webRequest.onBeforeRequest (details, callback) -> - if details.url is defaultURL - callback(redirectURL: "#{defaultURL}redirect") - else - callback({}) - $.ajax - url: defaultURL - success: (data) -> - assert.equal data, '/redirect' - done() - error: (xhr, errorType, error) -> done(errorType) - - describe 'webRequest.onBeforeSendHeaders', -> - afterEach -> - ses.webRequest.onBeforeSendHeaders null - - it 'receives details object', (done) -> - ses.webRequest.onBeforeSendHeaders (details, callback) -> - assert.equal typeof details.requestHeaders, 'object' - callback({}) - $.ajax - url: defaultURL - success: (data) -> - assert.equal data, '/' - done() - error: (xhr, errorType, error) -> done(errorType) - - it 'can change the request headers', (done) -> - ses.webRequest.onBeforeSendHeaders (details, callback) -> - {requestHeaders} = details - requestHeaders.Accept = '*/*;test/header' - callback({requestHeaders}) - $.ajax - url: defaultURL - success: (data, textStatus, request) -> - assert.equal data, '/header/received' - done() - error: (xhr, errorType, error) -> done(errorType) - - it 'resets the whole headers', (done) -> - requestHeaders = Test: 'header' - ses.webRequest.onBeforeSendHeaders (details, callback) -> - callback({requestHeaders}) - ses.webRequest.onSendHeaders (details) -> - assert.deepEqual details.requestHeaders, requestHeaders - done() - $.ajax - url: defaultURL - error: (xhr, errorType, error) -> done(errorType) - - describe 'webRequest.onSendHeaders', -> - afterEach -> - ses.webRequest.onSendHeaders null - - it 'receives details object', (done) -> - ses.webRequest.onSendHeaders (details) -> - assert.equal typeof details.requestHeaders, 'object' - $.ajax - url: defaultURL - success: (data) -> - assert.equal data, '/' - done() - error: (xhr, errorType, error) -> done(errorType) - - describe 'webRequest.onHeadersReceived', -> - afterEach -> - ses.webRequest.onHeadersReceived null - - it 'receives details object', (done) -> - ses.webRequest.onHeadersReceived (details, callback) -> - assert.equal details.statusLine, 'HTTP/1.1 200 OK' - assert.equal details.statusCode, 200 - assert.equal details.responseHeaders['Custom'], 'Header' - callback({}) - $.ajax - url: defaultURL - success: (data) -> - assert.equal data, '/' - done() - error: (xhr, errorType, error) -> done(errorType) - - it 'can change the response header', (done) -> - ses.webRequest.onHeadersReceived (details, callback) -> - {responseHeaders} = details - responseHeaders['Custom'] = ['Changed'] - callback({responseHeaders}) - $.ajax - url: defaultURL - success: (data, status, xhr) -> - assert.equal xhr.getResponseHeader('Custom'), 'Changed' - assert.equal data, '/' - done() - error: (xhr, errorType, error) -> done(errorType) - - it 'does not change header by default', (done) -> - ses.webRequest.onHeadersReceived (details, callback) -> - callback({}) - $.ajax - url: defaultURL - success: (data, status, xhr) -> - assert.equal xhr.getResponseHeader('Custom'), 'Header' - assert.equal data, '/' - done() - error: (xhr, errorType, error) -> done(errorType) - - describe 'webRequest.onResponseStarted', -> - afterEach -> - ses.webRequest.onResponseStarted null - - it 'receives details object', (done) -> - ses.webRequest.onResponseStarted (details) -> - assert.equal typeof details.fromCache, 'boolean' - assert.equal details.statusLine, 'HTTP/1.1 200 OK' - assert.equal details.statusCode, 200 - assert.equal details.responseHeaders['Custom'], 'Header' - $.ajax - url: defaultURL - success: (data, status, xhr) -> - assert.equal xhr.getResponseHeader('Custom'), 'Header' - assert.equal data, '/' - done() - error: (xhr, errorType, error) -> done(errorType) - - describe 'webRequest.onBeforeRedirect', -> - afterEach -> - ses.webRequest.onBeforeRedirect null - ses.webRequest.onBeforeRequest null - - it 'receives details object', (done) -> - redirectURL = "#{defaultURL}redirect" - ses.webRequest.onBeforeRequest (details, callback) -> - if details.url is defaultURL - callback({redirectURL}) - else - callback({}) - ses.webRequest.onBeforeRedirect (details) -> - assert.equal typeof details.fromCache, 'boolean' - assert.equal details.statusLine, 'HTTP/1.1 307 Internal Redirect' - assert.equal details.statusCode, 307 - assert.equal details.redirectURL, redirectURL - $.ajax - url: defaultURL - success: (data, status, xhr) -> - assert.equal data, '/redirect' - done() - error: (xhr, errorType, error) -> done(errorType) - - describe 'webRequest.onCompleted', -> - afterEach -> - ses.webRequest.onCompleted null - - it 'receives details object', (done) -> - ses.webRequest.onCompleted (details) -> - assert.equal typeof details.fromCache, 'boolean' - assert.equal details.statusLine, 'HTTP/1.1 200 OK' - assert.equal details.statusCode, 200 - $.ajax - url: defaultURL - success: (data, status, xhr) -> - assert.equal data, '/' - done() - error: (xhr, errorType, error) -> done(errorType) - - describe 'webRequest.onErrorOccurred', -> - afterEach -> - ses.webRequest.onErrorOccurred null - ses.webRequest.onBeforeRequest null - - it 'receives details object', (done) -> - ses.webRequest.onBeforeRequest (details, callback) -> - callback(cancel: true) - ses.webRequest.onErrorOccurred (details) -> - assert.equal details.error, 'net::ERR_BLOCKED_BY_CLIENT' - done() - $.ajax - url: defaultURL - success: (data) -> done('unexpected success') diff --git a/spec/api-web-request-spec.js b/spec/api-web-request-spec.js new file mode 100644 index 000000000000..854b28391213 --- /dev/null +++ b/spec/api-web-request-spec.js @@ -0,0 +1,372 @@ +var assert, http, remote, session; + +assert = require('assert'); + +http = require('http'); + +remote = require('electron').remote; + +session = remote.session; + +describe('webRequest module', function() { + var defaultURL, server, ses; + ses = session.defaultSession; + server = http.createServer(function(req, res) { + var content; + res.setHeader('Custom', ['Header']); + content = req.url; + if (req.headers.accept === '*/*;test/header') { + content += 'header/received'; + } + return res.end(content); + }); + defaultURL = null; + before(function(done) { + return server.listen(0, '127.0.0.1', function() { + var port; + port = server.address().port; + defaultURL = "http://127.0.0.1:" + port + "/"; + return done(); + }); + }); + after(function() { + return server.close(); + }); + describe('webRequest.onBeforeRequest', function() { + afterEach(function() { + return ses.webRequest.onBeforeRequest(null); + }); + it('can cancel the request', function(done) { + ses.webRequest.onBeforeRequest(function(details, callback) { + return callback({ + cancel: true + }); + }); + return $.ajax({ + url: defaultURL, + success: function(data) { + return done('unexpected success'); + }, + error: function(xhr, errorType, error) { + return done(); + } + }); + }); + it('can filter URLs', function(done) { + var filter; + filter = { + urls: [defaultURL + "filter/*"] + }; + ses.webRequest.onBeforeRequest(filter, function(details, callback) { + return callback({ + cancel: true + }); + }); + return $.ajax({ + url: defaultURL + "nofilter/test", + success: function(data) { + assert.equal(data, '/nofilter/test'); + return $.ajax({ + url: defaultURL + "filter/test", + success: function(data) { + return done('unexpected success'); + }, + error: function(xhr, errorType, error) { + return done(); + } + }); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + it('receives details object', function(done) { + ses.webRequest.onBeforeRequest(function(details, callback) { + assert.equal(typeof details.id, 'number'); + assert.equal(typeof details.timestamp, 'number'); + assert.equal(details.url, defaultURL); + assert.equal(details.method, 'GET'); + assert.equal(details.resourceType, 'xhr'); + return callback({}); + }); + return $.ajax({ + url: defaultURL, + success: function(data) { + assert.equal(data, '/'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + return it('can redirect the request', function(done) { + ses.webRequest.onBeforeRequest(function(details, callback) { + if (details.url === defaultURL) { + return callback({ + redirectURL: defaultURL + "redirect" + }); + } else { + return callback({}); + } + }); + return $.ajax({ + url: defaultURL, + success: function(data) { + assert.equal(data, '/redirect'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + }); + describe('webRequest.onBeforeSendHeaders', function() { + afterEach(function() { + return ses.webRequest.onBeforeSendHeaders(null); + }); + it('receives details object', function(done) { + ses.webRequest.onBeforeSendHeaders(function(details, callback) { + assert.equal(typeof details.requestHeaders, 'object'); + return callback({}); + }); + return $.ajax({ + url: defaultURL, + success: function(data) { + assert.equal(data, '/'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + it('can change the request headers', function(done) { + ses.webRequest.onBeforeSendHeaders(function(details, callback) { + var requestHeaders; + requestHeaders = details.requestHeaders; + requestHeaders.Accept = '*/*;test/header'; + return callback({ + requestHeaders: requestHeaders + }); + }); + return $.ajax({ + url: defaultURL, + success: function(data, textStatus, request) { + assert.equal(data, '/header/received'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + return it('resets the whole headers', function(done) { + var requestHeaders; + requestHeaders = { + Test: 'header' + }; + ses.webRequest.onBeforeSendHeaders(function(details, callback) { + return callback({ + requestHeaders: requestHeaders + }); + }); + ses.webRequest.onSendHeaders(function(details) { + assert.deepEqual(details.requestHeaders, requestHeaders); + return done(); + }); + return $.ajax({ + url: defaultURL, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + }); + describe('webRequest.onSendHeaders', function() { + afterEach(function() { + return ses.webRequest.onSendHeaders(null); + }); + return it('receives details object', function(done) { + ses.webRequest.onSendHeaders(function(details) { + return assert.equal(typeof details.requestHeaders, 'object'); + }); + return $.ajax({ + url: defaultURL, + success: function(data) { + assert.equal(data, '/'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + }); + describe('webRequest.onHeadersReceived', function() { + afterEach(function() { + return ses.webRequest.onHeadersReceived(null); + }); + it('receives details object', function(done) { + ses.webRequest.onHeadersReceived(function(details, callback) { + assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); + assert.equal(details.statusCode, 200); + assert.equal(details.responseHeaders['Custom'], 'Header'); + return callback({}); + }); + return $.ajax({ + url: defaultURL, + success: function(data) { + assert.equal(data, '/'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + it('can change the response header', function(done) { + ses.webRequest.onHeadersReceived(function(details, callback) { + var responseHeaders; + responseHeaders = details.responseHeaders; + responseHeaders['Custom'] = ['Changed']; + return callback({ + responseHeaders: responseHeaders + }); + }); + return $.ajax({ + url: defaultURL, + success: function(data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Changed'); + assert.equal(data, '/'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + return it('does not change header by default', function(done) { + ses.webRequest.onHeadersReceived(function(details, callback) { + return callback({}); + }); + return $.ajax({ + url: defaultURL, + success: function(data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Header'); + assert.equal(data, '/'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + }); + describe('webRequest.onResponseStarted', function() { + afterEach(function() { + return ses.webRequest.onResponseStarted(null); + }); + return it('receives details object', function(done) { + ses.webRequest.onResponseStarted(function(details) { + assert.equal(typeof details.fromCache, 'boolean'); + assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); + assert.equal(details.statusCode, 200); + return assert.equal(details.responseHeaders['Custom'], 'Header'); + }); + return $.ajax({ + url: defaultURL, + success: function(data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Header'); + assert.equal(data, '/'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + }); + describe('webRequest.onBeforeRedirect', function() { + afterEach(function() { + ses.webRequest.onBeforeRedirect(null); + return ses.webRequest.onBeforeRequest(null); + }); + return it('receives details object', function(done) { + var redirectURL; + redirectURL = defaultURL + "redirect"; + ses.webRequest.onBeforeRequest(function(details, callback) { + if (details.url === defaultURL) { + return callback({ + redirectURL: redirectURL + }); + } else { + return callback({}); + } + }); + ses.webRequest.onBeforeRedirect(function(details) { + assert.equal(typeof details.fromCache, 'boolean'); + assert.equal(details.statusLine, 'HTTP/1.1 307 Internal Redirect'); + assert.equal(details.statusCode, 307); + return assert.equal(details.redirectURL, redirectURL); + }); + return $.ajax({ + url: defaultURL, + success: function(data, status, xhr) { + assert.equal(data, '/redirect'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + }); + describe('webRequest.onCompleted', function() { + afterEach(function() { + return ses.webRequest.onCompleted(null); + }); + return it('receives details object', function(done) { + ses.webRequest.onCompleted(function(details) { + assert.equal(typeof details.fromCache, 'boolean'); + assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); + return assert.equal(details.statusCode, 200); + }); + return $.ajax({ + url: defaultURL, + success: function(data, status, xhr) { + assert.equal(data, '/'); + return done(); + }, + error: function(xhr, errorType, error) { + return done(errorType); + } + }); + }); + }); + return describe('webRequest.onErrorOccurred', function() { + afterEach(function() { + ses.webRequest.onErrorOccurred(null); + return ses.webRequest.onBeforeRequest(null); + }); + return it('receives details object', function(done) { + ses.webRequest.onBeforeRequest(function(details, callback) { + return callback({ + cancel: true + }); + }); + ses.webRequest.onErrorOccurred(function(details) { + assert.equal(details.error, 'net::ERR_BLOCKED_BY_CLIENT'); + return done(); + }); + return $.ajax({ + url: defaultURL, + success: function(data) { + return done('unexpected success'); + } + }); + }); + }); +}); diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee deleted file mode 100644 index 7642283cea4d..000000000000 --- a/spec/asar-spec.coffee +++ /dev/null @@ -1,578 +0,0 @@ -assert = require 'assert' -child_process = require 'child_process' -fs = require 'fs' -path = require 'path' - -{nativeImage, remote} = require 'electron' -{ipcMain, BrowserWindow} = remote.require 'electron' - -describe 'asar package', -> - fixtures = path.join __dirname, 'fixtures' - - describe 'node api', -> - describe 'fs.readFileSync', -> - it 'does not leak fd', -> - for i in [1..10000] - fs.readFileSync(path.join(process.resourcesPath, 'atom.asar', 'renderer', 'api', 'lib', 'ipc.js')) - - it 'reads a normal file', -> - file1 = path.join fixtures, 'asar', 'a.asar', 'file1' - assert.equal fs.readFileSync(file1).toString().trim(), 'file1' - file2 = path.join fixtures, 'asar', 'a.asar', 'file2' - assert.equal fs.readFileSync(file2).toString().trim(), 'file2' - file3 = path.join fixtures, 'asar', 'a.asar', 'file3' - assert.equal fs.readFileSync(file3).toString().trim(), 'file3' - - it 'reads from a empty file', -> - file = path.join fixtures, 'asar', 'empty.asar', 'file1' - buffer = fs.readFileSync(file) - assert.equal buffer.length, 0 - assert.equal buffer.toString(), '' - - it 'reads a linked file', -> - p = path.join fixtures, 'asar', 'a.asar', 'link1' - assert.equal fs.readFileSync(p).toString().trim(), 'file1' - - it 'reads a file from linked directory', -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'file1' - assert.equal fs.readFileSync(p).toString().trim(), 'file1' - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' - assert.equal fs.readFileSync(p).toString().trim(), 'file1' - - it 'throws ENOENT error when can not find file', -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - throws = -> fs.readFileSync p - assert.throws throws, /ENOENT/ - - it 'passes ENOENT error to callback when can not find file', -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - async = false - fs.readFile p, (e) -> - assert async - assert /ENOENT/.test e - async = true - - it 'reads a normal file with unpacked files', -> - p = path.join fixtures, 'asar', 'unpack.asar', 'a.txt' - assert.equal fs.readFileSync(p).toString().trim(), 'a' - - describe 'fs.readFile', -> - it 'reads a normal file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'file1' - fs.readFile p, (err, content) -> - assert.equal err, null - assert.equal String(content).trim(), 'file1' - done() - - it 'reads from a empty file', (done) -> - p = path.join fixtures, 'asar', 'empty.asar', 'file1' - fs.readFile p, (err, content) -> - assert.equal err, null - assert.equal String(content), '' - done() - - it 'reads a linked file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'link1' - fs.readFile p, (err, content) -> - assert.equal err, null - assert.equal String(content).trim(), 'file1' - done() - - it 'reads a file from linked directory', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' - fs.readFile p, (err, content) -> - assert.equal err, null - assert.equal String(content).trim(), 'file1' - done() - - it 'throws ENOENT error when can not find file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - fs.readFile p, (err, content) -> - assert.equal err.code, 'ENOENT' - done() - - describe 'fs.lstatSync', -> - it 'handles path with trailing slash correctly', -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' - fs.lstatSync p - fs.lstatSync p + '/' - - it 'returns information of root', -> - p = path.join fixtures, 'asar', 'a.asar' - stats = fs.lstatSync p - assert.equal stats.isFile(), false - assert.equal stats.isDirectory(), true - assert.equal stats.isSymbolicLink(), false - assert.equal stats.size, 0 - - it 'returns information of a normal file', -> - for file in ['file1', 'file2', 'file3', path.join('dir1', 'file1'), path.join('link2', 'file1')] - p = path.join fixtures, 'asar', 'a.asar', file - stats = fs.lstatSync p - assert.equal stats.isFile(), true - assert.equal stats.isDirectory(), false - assert.equal stats.isSymbolicLink(), false - assert.equal stats.size, 6 - - it 'returns information of a normal directory', -> - for file in ['dir1', 'dir2', 'dir3'] - p = path.join fixtures, 'asar', 'a.asar', file - stats = fs.lstatSync p - assert.equal stats.isFile(), false - assert.equal stats.isDirectory(), true - assert.equal stats.isSymbolicLink(), false - assert.equal stats.size, 0 - - it 'returns information of a linked file', -> - for file in ['link1', path.join('dir1', 'link1'), path.join('link2', 'link2')] - p = path.join fixtures, 'asar', 'a.asar', file - stats = fs.lstatSync p - assert.equal stats.isFile(), false - assert.equal stats.isDirectory(), false - assert.equal stats.isSymbolicLink(), true - assert.equal stats.size, 0 - - it 'returns information of a linked directory', -> - for file in ['link2', path.join('dir1', 'link2'), path.join('link2', 'link2')] - p = path.join fixtures, 'asar', 'a.asar', file - stats = fs.lstatSync p - assert.equal stats.isFile(), false - assert.equal stats.isDirectory(), false - assert.equal stats.isSymbolicLink(), true - assert.equal stats.size, 0 - - it 'throws ENOENT error when can not find file', -> - for file in ['file4', 'file5', path.join('dir1', 'file4')] - p = path.join fixtures, 'asar', 'a.asar', file - throws = -> fs.lstatSync p - assert.throws throws, /ENOENT/ - - describe 'fs.lstat', -> - it 'handles path with trailing slash correctly', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' - fs.lstat p + '/', done - - it 'returns information of root', (done) -> - p = path.join fixtures, 'asar', 'a.asar' - stats = fs.lstat p, (err, stats) -> - assert.equal err, null - assert.equal stats.isFile(), false - assert.equal stats.isDirectory(), true - assert.equal stats.isSymbolicLink(), false - assert.equal stats.size, 0 - done() - - it 'returns information of a normal file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'file1' - stats = fs.lstat p, (err, stats) -> - assert.equal err, null - assert.equal stats.isFile(), true - assert.equal stats.isDirectory(), false - assert.equal stats.isSymbolicLink(), false - assert.equal stats.size, 6 - done() - - it 'returns information of a normal directory', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'dir1' - stats = fs.lstat p, (err, stats) -> - assert.equal err, null - assert.equal stats.isFile(), false - assert.equal stats.isDirectory(), true - assert.equal stats.isSymbolicLink(), false - assert.equal stats.size, 0 - done() - - it 'returns information of a linked file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link1' - stats = fs.lstat p, (err, stats) -> - assert.equal err, null - assert.equal stats.isFile(), false - assert.equal stats.isDirectory(), false - assert.equal stats.isSymbolicLink(), true - assert.equal stats.size, 0 - done() - - it 'returns information of a linked directory', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2' - stats = fs.lstat p, (err, stats) -> - assert.equal err, null - assert.equal stats.isFile(), false - assert.equal stats.isDirectory(), false - assert.equal stats.isSymbolicLink(), true - assert.equal stats.size, 0 - done() - - it 'throws ENOENT error when can not find file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'file4' - stats = fs.lstat p, (err, stats) -> - assert.equal err.code, 'ENOENT' - done() - - describe 'fs.realpathSync', -> - it 'returns real path root', -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = 'a.asar' - r = fs.realpathSync path.join(parent, p) - assert.equal r, path.join(parent, p) - - it 'returns real path of a normal file', -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'file1' - r = fs.realpathSync path.join(parent, p) - assert.equal r, path.join(parent, p) - - it 'returns real path of a normal directory', -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'dir1' - r = fs.realpathSync path.join(parent, p) - assert.equal r, path.join(parent, p) - - it 'returns real path of a linked file', -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'link2', 'link1' - r = fs.realpathSync path.join(parent, p) - assert.equal r, path.join(parent, 'a.asar', 'file1') - - it 'returns real path of a linked directory', -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'link2', 'link2' - r = fs.realpathSync path.join(parent, p) - assert.equal r, path.join(parent, 'a.asar', 'dir1') - - it 'throws ENOENT error when can not find file', -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'not-exist' - throws = -> fs.realpathSync path.join(parent, p) - assert.throws throws, /ENOENT/ - - describe 'fs.realpath', -> - it 'returns real path root', (done) -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = 'a.asar' - fs.realpath path.join(parent, p), (err, r) -> - assert.equal err, null - assert.equal r, path.join(parent, p) - done() - - it 'returns real path of a normal file', (done) -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'file1' - fs.realpath path.join(parent, p), (err, r) -> - assert.equal err, null - assert.equal r, path.join(parent, p) - done() - - it 'returns real path of a normal directory', (done) -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'dir1' - fs.realpath path.join(parent, p), (err, r) -> - assert.equal err, null - assert.equal r, path.join(parent, p) - done() - - it 'returns real path of a linked file', (done) -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'link2', 'link1' - fs.realpath path.join(parent, p), (err, r) -> - assert.equal err, null - assert.equal r, path.join(parent, 'a.asar', 'file1') - done() - - it 'returns real path of a linked directory', (done) -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'link2', 'link2' - fs.realpath path.join(parent, p), (err, r) -> - assert.equal err, null - assert.equal r, path.join(parent, 'a.asar', 'dir1') - done() - - it 'throws ENOENT error when can not find file', (done) -> - parent = fs.realpathSync path.join(fixtures, 'asar') - p = path.join 'a.asar', 'not-exist' - fs.realpath path.join(parent, p), (err, stats) -> - assert.equal err.code, 'ENOENT' - done() - - describe 'fs.readdirSync', -> - it 'reads dirs from root', -> - p = path.join fixtures, 'asar', 'a.asar' - dirs = fs.readdirSync p - assert.deepEqual dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js'] - - it 'reads dirs from a normal dir', -> - p = path.join fixtures, 'asar', 'a.asar', 'dir1' - dirs = fs.readdirSync p - assert.deepEqual dirs, ['file1', 'file2', 'file3', 'link1', 'link2'] - - it 'reads dirs from a linked dir', -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2' - dirs = fs.readdirSync p - assert.deepEqual dirs, ['file1', 'file2', 'file3', 'link1', 'link2'] - - it 'throws ENOENT error when can not find file', -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - throws = -> fs.readdirSync p - assert.throws throws, /ENOENT/ - - describe 'fs.readdir', -> - it 'reads dirs from root', (done) -> - p = path.join fixtures, 'asar', 'a.asar' - dirs = fs.readdir p, (err, dirs) -> - assert.equal err, null - assert.deepEqual dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js'] - done() - - it 'reads dirs from a normal dir', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'dir1' - dirs = fs.readdir p, (err, dirs) -> - assert.equal err, null - assert.deepEqual dirs, ['file1', 'file2', 'file3', 'link1', 'link2'] - done() - - it 'reads dirs from a linked dir', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2' - dirs = fs.readdir p, (err, dirs) -> - assert.equal err, null - assert.deepEqual dirs, ['file1', 'file2', 'file3', 'link1', 'link2'] - done() - - it 'throws ENOENT error when can not find file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - fs.readdir p, (err, stats) -> - assert.equal err.code, 'ENOENT' - done() - - describe 'fs.openSync', -> - it 'opens a normal/linked/under-linked-directory file', -> - for file in ['file1', 'link1', path.join('link2', 'file1')] - p = path.join fixtures, 'asar', 'a.asar', file - fd = fs.openSync p, 'r' - buffer = new Buffer(6) - fs.readSync fd, buffer, 0, 6, 0 - assert.equal String(buffer).trim(), 'file1' - fs.closeSync fd - - it 'throws ENOENT error when can not find file', -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - throws = -> fs.openSync p - assert.throws throws, /ENOENT/ - - describe 'fs.open', -> - it 'opens a normal file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'file1' - fs.open p, 'r', (err, fd) -> - assert.equal err, null - buffer = new Buffer(6) - fs.read fd, buffer, 0, 6, 0, (err) -> - assert.equal err, null - assert.equal String(buffer).trim(), 'file1' - fs.close fd, done - - it 'throws ENOENT error when can not find file', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - fs.open p, 'r', (err, stats) -> - assert.equal err.code, 'ENOENT' - done() - - describe 'fs.mkdir', -> - it 'throws error when calling inside asar archive', (done) -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - fs.mkdir p, (err) -> - assert.equal err.code, 'ENOTDIR' - done() - - describe 'fs.mkdirSync', -> - it 'throws error when calling inside asar archive', -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - assert.throws (-> fs.mkdirSync p), new RegExp('ENOTDIR') - - describe 'child_process.fork', -> - child_process = require 'child_process' - - it 'opens a normal js file', (done) -> - child = child_process.fork path.join(fixtures, 'asar', 'a.asar', 'ping.js') - child.on 'message', (msg) -> - assert.equal msg, 'message' - done() - child.send 'message' - - it 'supports asar in the forked js', (done) -> - file = path.join fixtures, 'asar', 'a.asar', 'file1' - child = child_process.fork path.join(fixtures, 'module', 'asar.js') - child.on 'message', (content) -> - assert.equal content, fs.readFileSync(file).toString() - done() - child.send file - - describe 'child_process.execFile', -> - return unless process.platform is 'darwin' - - {execFile, execFileSync} = require 'child_process' - echo = path.join fixtures, 'asar', 'echo.asar', 'echo' - - it 'executes binaries', (done) -> - child = execFile echo, ['test'], (error, stdout) -> - assert.equal error, null - assert.equal stdout, 'test\n' - done() - - # execFileSync makes the test flaky after a refresh. - xit 'execFileSync executes binaries', -> - output = execFileSync echo, ['test'] - assert.equal String(output), 'test\n' - - describe 'internalModuleReadFile', -> - internalModuleReadFile = process.binding('fs').internalModuleReadFile - - it 'read a normal file', -> - file1 = path.join fixtures, 'asar', 'a.asar', 'file1' - assert.equal internalModuleReadFile(file1).toString().trim(), 'file1' - file2 = path.join fixtures, 'asar', 'a.asar', 'file2' - assert.equal internalModuleReadFile(file2).toString().trim(), 'file2' - file3 = path.join fixtures, 'asar', 'a.asar', 'file3' - assert.equal internalModuleReadFile(file3).toString().trim(), 'file3' - - it 'reads a normal file with unpacked files', -> - p = path.join fixtures, 'asar', 'unpack.asar', 'a.txt' - assert.equal internalModuleReadFile(p).toString().trim(), 'a' - - describe 'process.noAsar', -> - errorName = if process.platform is 'win32' then 'ENOENT' else 'ENOTDIR' - - beforeEach -> - process.noAsar = true - afterEach -> - process.noAsar = false - - it 'disables asar support in sync API', -> - file = path.join fixtures, 'asar', 'a.asar', 'file1' - dir = path.join fixtures, 'asar', 'a.asar', 'dir1' - assert.throws (-> fs.readFileSync file), new RegExp(errorName) - assert.throws (-> fs.lstatSync file), new RegExp(errorName) - assert.throws (-> fs.realpathSync file), new RegExp(errorName) - assert.throws (-> fs.readdirSync dir), new RegExp(errorName) - - it 'disables asar support in async API', (done) -> - file = path.join fixtures, 'asar', 'a.asar', 'file1' - dir = path.join fixtures, 'asar', 'a.asar', 'dir1' - fs.readFile file, (error) -> - assert.equal error.code, errorName - fs.lstat file, (error) -> - assert.equal error.code, errorName - fs.realpath file, (error) -> - assert.equal error.code, errorName - fs.readdir dir, (error) -> - assert.equal error.code, errorName - done() - - it 'treats *.asar as normal file', -> - originalFs = require 'original-fs' - asar = path.join fixtures, 'asar', 'a.asar' - content1 = fs.readFileSync asar - content2 = originalFs.readFileSync asar - assert.equal content1.compare(content2), 0 - assert.throws (-> fs.readdirSync asar), /ENOTDIR/ - - describe 'asar protocol', -> - url = require 'url' - - it 'can request a file in package', (done) -> - p = path.resolve fixtures, 'asar', 'a.asar', 'file1' - $.get "file://#{p}", (data) -> - assert.equal data.trim(), 'file1' - done() - - it 'can request a file in package with unpacked files', (done) -> - p = path.resolve fixtures, 'asar', 'unpack.asar', 'a.txt' - $.get "file://#{p}", (data) -> - assert.equal data.trim(), 'a' - done() - - it 'can request a linked file in package', (done) -> - p = path.resolve fixtures, 'asar', 'a.asar', 'link2', 'link1' - $.get "file://#{p}", (data) -> - assert.equal data.trim(), 'file1' - done() - - it 'can request a file in filesystem', (done) -> - p = path.resolve fixtures, 'asar', 'file' - $.get "file://#{p}", (data) -> - assert.equal data.trim(), 'file' - done() - - it 'gets 404 when file is not found', (done) -> - p = path.resolve fixtures, 'asar', 'a.asar', 'no-exist' - $.ajax - url: "file://#{p}" - error: (err) -> - assert.equal err.status, 404 - done() - - it 'sets __dirname correctly', (done) -> - after -> - w.destroy() - ipcMain.removeAllListeners 'dirname' - - w = new BrowserWindow(show: false, width: 400, height: 400) - p = path.resolve fixtures, 'asar', 'web.asar', 'index.html' - u = url.format protocol: 'file', slashed: true, pathname: p - ipcMain.once 'dirname', (event, dirname) -> - assert.equal dirname, path.dirname(p) - done() - w.loadURL u - - it 'loads script tag in html', (done) -> - after -> - w.destroy() - ipcMain.removeAllListeners 'ping' - - w = new BrowserWindow(show: false, width: 400, height: 400) - p = path.resolve fixtures, 'asar', 'script.asar', 'index.html' - u = url.format protocol: 'file', slashed: true, pathname: p - w.loadURL u - ipcMain.once 'ping', (event, message) -> - assert.equal message, 'pong' - done() - - describe 'original-fs module', -> - originalFs = require 'original-fs' - - it 'treats .asar as file', -> - file = path.join fixtures, 'asar', 'a.asar' - stats = originalFs.statSync file - assert stats.isFile() - - it 'is available in forked scripts', (done) -> - child = child_process.fork path.join(fixtures, 'module', 'original-fs.js') - child.on 'message', (msg) -> - assert.equal msg, 'object' - done() - child.send 'message' - - describe 'graceful-fs module', -> - gfs = require 'graceful-fs' - - it 'recognize asar archvies', -> - p = path.join fixtures, 'asar', 'a.asar', 'link1' - assert.equal gfs.readFileSync(p).toString().trim(), 'file1' - - it 'does not touch global fs object', -> - assert.notEqual fs.readdir, gfs.readdir - - describe 'mkdirp module', -> - mkdirp = require 'mkdirp' - - it 'throws error when calling inside asar archive', -> - p = path.join fixtures, 'asar', 'a.asar', 'not-exist' - assert.throws (-> mkdirp.sync p), new RegExp('ENOTDIR') - - describe 'native-image', -> - it 'reads image from asar archive', -> - p = path.join fixtures, 'asar', 'logo.asar', 'logo.png' - logo = nativeImage.createFromPath p - assert.deepEqual logo.getSize(), {width: 55, height: 55} - - it 'reads image from asar archive with unpacked files', -> - p = path.join fixtures, 'asar', 'unpack.asar', 'atom.png' - logo = nativeImage.createFromPath p - assert.deepEqual logo.getSize(), {width: 1024, height: 1024} diff --git a/spec/asar-spec.js b/spec/asar-spec.js new file mode 100644 index 000000000000..6ccc4b46a011 --- /dev/null +++ b/spec/asar-spec.js @@ -0,0 +1,805 @@ +var BrowserWindow, assert, child_process, fs, ipcMain, nativeImage, path, ref, ref1, remote; + +assert = require('assert'); + +child_process = require('child_process'); + +fs = require('fs'); + +path = require('path'); + +ref = require('electron'), nativeImage = ref.nativeImage, remote = ref.remote; + +ref1 = remote.require('electron'), ipcMain = ref1.ipcMain, BrowserWindow = ref1.BrowserWindow; + +describe('asar package', function() { + var fixtures; + fixtures = path.join(__dirname, 'fixtures'); + describe('node api', function() { + describe('fs.readFileSync', function() { + it('does not leak fd', function() { + var i, j, results; + results = []; + for (i = j = 1; j <= 10000; i = ++j) { + results.push(fs.readFileSync(path.join(process.resourcesPath, 'atom.asar', 'renderer', 'api', 'lib', 'ipc.js'))); + } + return results; + }); + it('reads a normal file', function() { + var file1, file2, file3; + file1 = path.join(fixtures, 'asar', 'a.asar', 'file1'); + assert.equal(fs.readFileSync(file1).toString().trim(), 'file1'); + file2 = path.join(fixtures, 'asar', 'a.asar', 'file2'); + assert.equal(fs.readFileSync(file2).toString().trim(), 'file2'); + file3 = path.join(fixtures, 'asar', 'a.asar', 'file3'); + return assert.equal(fs.readFileSync(file3).toString().trim(), 'file3'); + }); + it('reads from a empty file', function() { + var buffer, file; + file = path.join(fixtures, 'asar', 'empty.asar', 'file1'); + buffer = fs.readFileSync(file); + assert.equal(buffer.length, 0); + return assert.equal(buffer.toString(), ''); + }); + it('reads a linked file', function() { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'link1'); + return assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); + }); + it('reads a file from linked directory', function() { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1'); + assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); + return assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); + }); + it('throws ENOENT error when can not find file', function() { + var p, throws; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + throws = function() { + return fs.readFileSync(p); + }; + return assert.throws(throws, /ENOENT/); + }); + it('passes ENOENT error to callback when can not find file', function() { + var async, p; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + async = false; + fs.readFile(p, function(e) { + assert(async); + return assert(/ENOENT/.test(e)); + }); + return async = true; + }); + return it('reads a normal file with unpacked files', function() { + var p; + p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt'); + return assert.equal(fs.readFileSync(p).toString().trim(), 'a'); + }); + }); + describe('fs.readFile', function() { + it('reads a normal file', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'file1'); + return fs.readFile(p, function(err, content) { + assert.equal(err, null); + assert.equal(String(content).trim(), 'file1'); + return done(); + }); + }); + it('reads from a empty file', function(done) { + var p; + p = path.join(fixtures, 'asar', 'empty.asar', 'file1'); + return fs.readFile(p, function(err, content) { + assert.equal(err, null); + assert.equal(String(content), ''); + return done(); + }); + }); + it('reads a linked file', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'link1'); + return fs.readFile(p, function(err, content) { + assert.equal(err, null); + assert.equal(String(content).trim(), 'file1'); + return done(); + }); + }); + it('reads a file from linked directory', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); + return fs.readFile(p, function(err, content) { + assert.equal(err, null); + assert.equal(String(content).trim(), 'file1'); + return done(); + }); + }); + return it('throws ENOENT error when can not find file', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + return fs.readFile(p, function(err, content) { + assert.equal(err.code, 'ENOENT'); + return done(); + }); + }); + }); + describe('fs.lstatSync', function() { + it('handles path with trailing slash correctly', function() { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); + fs.lstatSync(p); + return fs.lstatSync(p + '/'); + }); + it('returns information of root', function() { + var p, stats; + p = path.join(fixtures, 'asar', 'a.asar'); + stats = fs.lstatSync(p); + assert.equal(stats.isFile(), false); + assert.equal(stats.isDirectory(), true); + assert.equal(stats.isSymbolicLink(), false); + return assert.equal(stats.size, 0); + }); + it('returns information of a normal file', function() { + var file, j, len, p, ref2, results, stats; + ref2 = ['file1', 'file2', 'file3', path.join('dir1', 'file1'), path.join('link2', 'file1')]; + results = []; + for (j = 0, len = ref2.length; j < len; j++) { + file = ref2[j]; + p = path.join(fixtures, 'asar', 'a.asar', file); + stats = fs.lstatSync(p); + assert.equal(stats.isFile(), true); + assert.equal(stats.isDirectory(), false); + assert.equal(stats.isSymbolicLink(), false); + results.push(assert.equal(stats.size, 6)); + } + return results; + }); + it('returns information of a normal directory', function() { + var file, j, len, p, ref2, results, stats; + ref2 = ['dir1', 'dir2', 'dir3']; + results = []; + for (j = 0, len = ref2.length; j < len; j++) { + file = ref2[j]; + p = path.join(fixtures, 'asar', 'a.asar', file); + stats = fs.lstatSync(p); + assert.equal(stats.isFile(), false); + assert.equal(stats.isDirectory(), true); + assert.equal(stats.isSymbolicLink(), false); + results.push(assert.equal(stats.size, 0)); + } + return results; + }); + it('returns information of a linked file', function() { + var file, j, len, p, ref2, results, stats; + ref2 = ['link1', path.join('dir1', 'link1'), path.join('link2', 'link2')]; + results = []; + for (j = 0, len = ref2.length; j < len; j++) { + file = ref2[j]; + p = path.join(fixtures, 'asar', 'a.asar', file); + stats = fs.lstatSync(p); + assert.equal(stats.isFile(), false); + assert.equal(stats.isDirectory(), false); + assert.equal(stats.isSymbolicLink(), true); + results.push(assert.equal(stats.size, 0)); + } + return results; + }); + it('returns information of a linked directory', function() { + var file, j, len, p, ref2, results, stats; + ref2 = ['link2', path.join('dir1', 'link2'), path.join('link2', 'link2')]; + results = []; + for (j = 0, len = ref2.length; j < len; j++) { + file = ref2[j]; + p = path.join(fixtures, 'asar', 'a.asar', file); + stats = fs.lstatSync(p); + assert.equal(stats.isFile(), false); + assert.equal(stats.isDirectory(), false); + assert.equal(stats.isSymbolicLink(), true); + results.push(assert.equal(stats.size, 0)); + } + return results; + }); + return it('throws ENOENT error when can not find file', function() { + var file, j, len, p, ref2, results, throws; + ref2 = ['file4', 'file5', path.join('dir1', 'file4')]; + results = []; + for (j = 0, len = ref2.length; j < len; j++) { + file = ref2[j]; + p = path.join(fixtures, 'asar', 'a.asar', file); + throws = function() { + return fs.lstatSync(p); + }; + results.push(assert.throws(throws, /ENOENT/)); + } + return results; + }); + }); + describe('fs.lstat', function() { + it('handles path with trailing slash correctly', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); + return fs.lstat(p + '/', done); + }); + it('returns information of root', function(done) { + var p, stats; + p = path.join(fixtures, 'asar', 'a.asar'); + return stats = fs.lstat(p, function(err, stats) { + assert.equal(err, null); + assert.equal(stats.isFile(), false); + assert.equal(stats.isDirectory(), true); + assert.equal(stats.isSymbolicLink(), false); + assert.equal(stats.size, 0); + return done(); + }); + }); + it('returns information of a normal file', function(done) { + var p, stats; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1'); + return stats = fs.lstat(p, function(err, stats) { + assert.equal(err, null); + assert.equal(stats.isFile(), true); + assert.equal(stats.isDirectory(), false); + assert.equal(stats.isSymbolicLink(), false); + assert.equal(stats.size, 6); + return done(); + }); + }); + it('returns information of a normal directory', function(done) { + var p, stats; + p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); + return stats = fs.lstat(p, function(err, stats) { + assert.equal(err, null); + assert.equal(stats.isFile(), false); + assert.equal(stats.isDirectory(), true); + assert.equal(stats.isSymbolicLink(), false); + assert.equal(stats.size, 0); + return done(); + }); + }); + it('returns information of a linked file', function(done) { + var p, stats; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link1'); + return stats = fs.lstat(p, function(err, stats) { + assert.equal(err, null); + assert.equal(stats.isFile(), false); + assert.equal(stats.isDirectory(), false); + assert.equal(stats.isSymbolicLink(), true); + assert.equal(stats.size, 0); + return done(); + }); + }); + it('returns information of a linked directory', function(done) { + var p, stats; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); + return stats = fs.lstat(p, function(err, stats) { + assert.equal(err, null); + assert.equal(stats.isFile(), false); + assert.equal(stats.isDirectory(), false); + assert.equal(stats.isSymbolicLink(), true); + assert.equal(stats.size, 0); + return done(); + }); + }); + return it('throws ENOENT error when can not find file', function(done) { + var p, stats; + p = path.join(fixtures, 'asar', 'a.asar', 'file4'); + return stats = fs.lstat(p, function(err, stats) { + assert.equal(err.code, 'ENOENT'); + return done(); + }); + }); + }); + describe('fs.realpathSync', function() { + it('returns real path root', function() { + var p, parent, r; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = 'a.asar'; + r = fs.realpathSync(path.join(parent, p)); + return assert.equal(r, path.join(parent, p)); + }); + it('returns real path of a normal file', function() { + var p, parent, r; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'file1'); + r = fs.realpathSync(path.join(parent, p)); + return assert.equal(r, path.join(parent, p)); + }); + it('returns real path of a normal directory', function() { + var p, parent, r; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'dir1'); + r = fs.realpathSync(path.join(parent, p)); + return assert.equal(r, path.join(parent, p)); + }); + it('returns real path of a linked file', function() { + var p, parent, r; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'link2', 'link1'); + r = fs.realpathSync(path.join(parent, p)); + return assert.equal(r, path.join(parent, 'a.asar', 'file1')); + }); + it('returns real path of a linked directory', function() { + var p, parent, r; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'link2', 'link2'); + r = fs.realpathSync(path.join(parent, p)); + return assert.equal(r, path.join(parent, 'a.asar', 'dir1')); + }); + return it('throws ENOENT error when can not find file', function() { + var p, parent, throws; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'not-exist'); + throws = function() { + return fs.realpathSync(path.join(parent, p)); + }; + return assert.throws(throws, /ENOENT/); + }); + }); + describe('fs.realpath', function() { + it('returns real path root', function(done) { + var p, parent; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = 'a.asar'; + return fs.realpath(path.join(parent, p), function(err, r) { + assert.equal(err, null); + assert.equal(r, path.join(parent, p)); + return done(); + }); + }); + it('returns real path of a normal file', function(done) { + var p, parent; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'file1'); + return fs.realpath(path.join(parent, p), function(err, r) { + assert.equal(err, null); + assert.equal(r, path.join(parent, p)); + return done(); + }); + }); + it('returns real path of a normal directory', function(done) { + var p, parent; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'dir1'); + return fs.realpath(path.join(parent, p), function(err, r) { + assert.equal(err, null); + assert.equal(r, path.join(parent, p)); + return done(); + }); + }); + it('returns real path of a linked file', function(done) { + var p, parent; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'link2', 'link1'); + return fs.realpath(path.join(parent, p), function(err, r) { + assert.equal(err, null); + assert.equal(r, path.join(parent, 'a.asar', 'file1')); + return done(); + }); + }); + it('returns real path of a linked directory', function(done) { + var p, parent; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'link2', 'link2'); + return fs.realpath(path.join(parent, p), function(err, r) { + assert.equal(err, null); + assert.equal(r, path.join(parent, 'a.asar', 'dir1')); + return done(); + }); + }); + return it('throws ENOENT error when can not find file', function(done) { + var p, parent; + parent = fs.realpathSync(path.join(fixtures, 'asar')); + p = path.join('a.asar', 'not-exist'); + return fs.realpath(path.join(parent, p), function(err, stats) { + assert.equal(err.code, 'ENOENT'); + return done(); + }); + }); + }); + describe('fs.readdirSync', function() { + it('reads dirs from root', function() { + var dirs, p; + p = path.join(fixtures, 'asar', 'a.asar'); + dirs = fs.readdirSync(p); + return assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); + }); + it('reads dirs from a normal dir', function() { + var dirs, p; + p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); + dirs = fs.readdirSync(p); + return assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); + }); + it('reads dirs from a linked dir', function() { + var dirs, p; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); + dirs = fs.readdirSync(p); + return assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); + }); + return it('throws ENOENT error when can not find file', function() { + var p, throws; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + throws = function() { + return fs.readdirSync(p); + }; + return assert.throws(throws, /ENOENT/); + }); + }); + describe('fs.readdir', function() { + it('reads dirs from root', function(done) { + var dirs, p; + p = path.join(fixtures, 'asar', 'a.asar'); + return dirs = fs.readdir(p, function(err, dirs) { + assert.equal(err, null); + assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); + return done(); + }); + }); + it('reads dirs from a normal dir', function(done) { + var dirs, p; + p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); + return dirs = fs.readdir(p, function(err, dirs) { + assert.equal(err, null); + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); + return done(); + }); + }); + it('reads dirs from a linked dir', function(done) { + var dirs, p; + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); + return dirs = fs.readdir(p, function(err, dirs) { + assert.equal(err, null); + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); + return done(); + }); + }); + return it('throws ENOENT error when can not find file', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + return fs.readdir(p, function(err, stats) { + assert.equal(err.code, 'ENOENT'); + return done(); + }); + }); + }); + describe('fs.openSync', function() { + it('opens a normal/linked/under-linked-directory file', function() { + var buffer, fd, file, j, len, p, ref2, results; + ref2 = ['file1', 'link1', path.join('link2', 'file1')]; + results = []; + for (j = 0, len = ref2.length; j < len; j++) { + file = ref2[j]; + p = path.join(fixtures, 'asar', 'a.asar', file); + fd = fs.openSync(p, 'r'); + buffer = new Buffer(6); + fs.readSync(fd, buffer, 0, 6, 0); + assert.equal(String(buffer).trim(), 'file1'); + results.push(fs.closeSync(fd)); + } + return results; + }); + return it('throws ENOENT error when can not find file', function() { + var p, throws; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + throws = function() { + return fs.openSync(p); + }; + return assert.throws(throws, /ENOENT/); + }); + }); + describe('fs.open', function() { + it('opens a normal file', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'file1'); + return fs.open(p, 'r', function(err, fd) { + var buffer; + assert.equal(err, null); + buffer = new Buffer(6); + return fs.read(fd, buffer, 0, 6, 0, function(err) { + assert.equal(err, null); + assert.equal(String(buffer).trim(), 'file1'); + return fs.close(fd, done); + }); + }); + }); + return it('throws ENOENT error when can not find file', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + return fs.open(p, 'r', function(err, stats) { + assert.equal(err.code, 'ENOENT'); + return done(); + }); + }); + }); + describe('fs.mkdir', function() { + return it('throws error when calling inside asar archive', function(done) { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + return fs.mkdir(p, function(err) { + assert.equal(err.code, 'ENOTDIR'); + return done(); + }); + }); + }); + describe('fs.mkdirSync', function() { + return it('throws error when calling inside asar archive', function() { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + return assert.throws((function() { + return fs.mkdirSync(p); + }), new RegExp('ENOTDIR')); + }); + }); + describe('child_process.fork', function() { + child_process = require('child_process'); + it('opens a normal js file', function(done) { + var child; + child = child_process.fork(path.join(fixtures, 'asar', 'a.asar', 'ping.js')); + child.on('message', function(msg) { + assert.equal(msg, 'message'); + return done(); + }); + return child.send('message'); + }); + return it('supports asar in the forked js', function(done) { + var child, file; + file = path.join(fixtures, 'asar', 'a.asar', 'file1'); + child = child_process.fork(path.join(fixtures, 'module', 'asar.js')); + child.on('message', function(content) { + assert.equal(content, fs.readFileSync(file).toString()); + return done(); + }); + return child.send(file); + }); + }); + describe('child_process.execFile', function() { + var echo, execFile, execFileSync, ref2; + if (process.platform !== 'darwin') { + return; + } + ref2 = require('child_process'), execFile = ref2.execFile, execFileSync = ref2.execFileSync; + echo = path.join(fixtures, 'asar', 'echo.asar', 'echo'); + it('executes binaries', function(done) { + var child; + return child = execFile(echo, ['test'], function(error, stdout) { + assert.equal(error, null); + assert.equal(stdout, 'test\n'); + return done(); + }); + }); + return xit('execFileSync executes binaries', function() { + var output; + output = execFileSync(echo, ['test']); + return assert.equal(String(output), 'test\n'); + }); + }); + describe('internalModuleReadFile', function() { + var internalModuleReadFile; + internalModuleReadFile = process.binding('fs').internalModuleReadFile; + it('read a normal file', function() { + var file1, file2, file3; + file1 = path.join(fixtures, 'asar', 'a.asar', 'file1'); + assert.equal(internalModuleReadFile(file1).toString().trim(), 'file1'); + file2 = path.join(fixtures, 'asar', 'a.asar', 'file2'); + assert.equal(internalModuleReadFile(file2).toString().trim(), 'file2'); + file3 = path.join(fixtures, 'asar', 'a.asar', 'file3'); + return assert.equal(internalModuleReadFile(file3).toString().trim(), 'file3'); + }); + return it('reads a normal file with unpacked files', function() { + var p; + p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt'); + return assert.equal(internalModuleReadFile(p).toString().trim(), 'a'); + }); + }); + return describe('process.noAsar', function() { + var errorName; + errorName = process.platform === 'win32' ? 'ENOENT' : 'ENOTDIR'; + beforeEach(function() { + return process.noAsar = true; + }); + afterEach(function() { + return process.noAsar = false; + }); + it('disables asar support in sync API', function() { + var dir, file; + file = path.join(fixtures, 'asar', 'a.asar', 'file1'); + dir = path.join(fixtures, 'asar', 'a.asar', 'dir1'); + assert.throws((function() { + return fs.readFileSync(file); + }), new RegExp(errorName)); + assert.throws((function() { + return fs.lstatSync(file); + }), new RegExp(errorName)); + assert.throws((function() { + return fs.realpathSync(file); + }), new RegExp(errorName)); + return assert.throws((function() { + return fs.readdirSync(dir); + }), new RegExp(errorName)); + }); + it('disables asar support in async API', function(done) { + var dir, file; + file = path.join(fixtures, 'asar', 'a.asar', 'file1'); + dir = path.join(fixtures, 'asar', 'a.asar', 'dir1'); + return fs.readFile(file, function(error) { + assert.equal(error.code, errorName); + return fs.lstat(file, function(error) { + assert.equal(error.code, errorName); + return fs.realpath(file, function(error) { + assert.equal(error.code, errorName); + return fs.readdir(dir, function(error) { + assert.equal(error.code, errorName); + return done(); + }); + }); + }); + }); + }); + return it('treats *.asar as normal file', function() { + var asar, content1, content2, originalFs; + originalFs = require('original-fs'); + asar = path.join(fixtures, 'asar', 'a.asar'); + content1 = fs.readFileSync(asar); + content2 = originalFs.readFileSync(asar); + assert.equal(content1.compare(content2), 0); + return assert.throws((function() { + return fs.readdirSync(asar); + }), /ENOTDIR/); + }); + }); + }); + describe('asar protocol', function() { + var url; + url = require('url'); + it('can request a file in package', function(done) { + var p; + p = path.resolve(fixtures, 'asar', 'a.asar', 'file1'); + return $.get("file://" + p, function(data) { + assert.equal(data.trim(), 'file1'); + return done(); + }); + }); + it('can request a file in package with unpacked files', function(done) { + var p; + p = path.resolve(fixtures, 'asar', 'unpack.asar', 'a.txt'); + return $.get("file://" + p, function(data) { + assert.equal(data.trim(), 'a'); + return done(); + }); + }); + it('can request a linked file in package', function(done) { + var p; + p = path.resolve(fixtures, 'asar', 'a.asar', 'link2', 'link1'); + return $.get("file://" + p, function(data) { + assert.equal(data.trim(), 'file1'); + return done(); + }); + }); + it('can request a file in filesystem', function(done) { + var p; + p = path.resolve(fixtures, 'asar', 'file'); + return $.get("file://" + p, function(data) { + assert.equal(data.trim(), 'file'); + return done(); + }); + }); + it('gets 404 when file is not found', function(done) { + var p; + p = path.resolve(fixtures, 'asar', 'a.asar', 'no-exist'); + return $.ajax({ + url: "file://" + p, + error: function(err) { + assert.equal(err.status, 404); + return done(); + } + }); + }); + it('sets __dirname correctly', function(done) { + var p, u, w; + after(function() { + w.destroy(); + return ipcMain.removeAllListeners('dirname'); + }); + w = new BrowserWindow({ + show: false, + width: 400, + height: 400 + }); + p = path.resolve(fixtures, 'asar', 'web.asar', 'index.html'); + u = url.format({ + protocol: 'file', + slashed: true, + pathname: p + }); + ipcMain.once('dirname', function(event, dirname) { + assert.equal(dirname, path.dirname(p)); + return done(); + }); + return w.loadURL(u); + }); + return it('loads script tag in html', function(done) { + var p, u, w; + after(function() { + w.destroy(); + return ipcMain.removeAllListeners('ping'); + }); + w = new BrowserWindow({ + show: false, + width: 400, + height: 400 + }); + p = path.resolve(fixtures, 'asar', 'script.asar', 'index.html'); + u = url.format({ + protocol: 'file', + slashed: true, + pathname: p + }); + w.loadURL(u); + return ipcMain.once('ping', function(event, message) { + assert.equal(message, 'pong'); + return done(); + }); + }); + }); + describe('original-fs module', function() { + var originalFs; + originalFs = require('original-fs'); + it('treats .asar as file', function() { + var file, stats; + file = path.join(fixtures, 'asar', 'a.asar'); + stats = originalFs.statSync(file); + return assert(stats.isFile()); + }); + return it('is available in forked scripts', function(done) { + var child; + child = child_process.fork(path.join(fixtures, 'module', 'original-fs.js')); + child.on('message', function(msg) { + assert.equal(msg, 'object'); + return done(); + }); + return child.send('message'); + }); + }); + describe('graceful-fs module', function() { + var gfs; + gfs = require('graceful-fs'); + it('recognize asar archvies', function() { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'link1'); + return assert.equal(gfs.readFileSync(p).toString().trim(), 'file1'); + }); + return it('does not touch global fs object', function() { + return assert.notEqual(fs.readdir, gfs.readdir); + }); + }); + describe('mkdirp module', function() { + var mkdirp; + mkdirp = require('mkdirp'); + return it('throws error when calling inside asar archive', function() { + var p; + p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); + return assert.throws((function() { + return mkdirp.sync(p); + }), new RegExp('ENOTDIR')); + }); + }); + return describe('native-image', function() { + it('reads image from asar archive', function() { + var logo, p; + p = path.join(fixtures, 'asar', 'logo.asar', 'logo.png'); + logo = nativeImage.createFromPath(p); + return assert.deepEqual(logo.getSize(), { + width: 55, + height: 55 + }); + }); + return it('reads image from asar archive with unpacked files', function() { + var logo, p; + p = path.join(fixtures, 'asar', 'unpack.asar', 'atom.png'); + logo = nativeImage.createFromPath(p); + return assert.deepEqual(logo.getSize(), { + width: 1024, + height: 1024 + }); + }); + }); +}); diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee deleted file mode 100644 index cc28f2f7ad47..000000000000 --- a/spec/chromium-spec.coffee +++ /dev/null @@ -1,275 +0,0 @@ -assert = require 'assert' -http = require 'http' -https = require 'https' -path = require 'path' -ws = require 'ws' - -{remote} = require 'electron' -{BrowserWindow, session} = remote.require 'electron' - -describe 'chromium feature', -> - fixtures = path.resolve __dirname, 'fixtures' - - listener = null - afterEach -> - if listener? - window.removeEventListener 'message', listener - listener = null - - xdescribe 'heap snapshot', -> - it 'does not crash', -> - process.atomBinding('v8_util').takeHeapSnapshot() - - describe 'sending request of http protocol urls', -> - it 'does not crash', (done) -> - @timeout 5000 - server = http.createServer (req, res) -> - res.end() - server.close() - done() - server.listen 0, '127.0.0.1', -> - {port} = server.address() - $.get "http://127.0.0.1:#{port}" - - describe 'document.hidden', -> - url = "file://#{fixtures}/pages/document-hidden.html" - w = null - - afterEach -> - w?.destroy() - - it 'is set correctly when window is not shown', (done) -> - w = new BrowserWindow(show:false) - w.webContents.on 'ipc-message', (event, args) -> - assert.deepEqual args, ['hidden', true] - done() - w.loadURL url - - it 'is set correctly when window is inactive', (done) -> - w = new BrowserWindow(show:false) - w.webContents.on 'ipc-message', (event, args) -> - assert.deepEqual args, ['hidden', false] - done() - w.showInactive() - w.loadURL url - - xdescribe 'navigator.webkitGetUserMedia', -> - it 'calls its callbacks', (done) -> - @timeout 5000 - navigator.webkitGetUserMedia audio: true, video: false, - -> done() - -> done() - - describe 'navigator.language', -> - it 'should not be empty', -> - assert.notEqual navigator.language, '' - - describe 'navigator.serviceWorker', -> - url = "file://#{fixtures}/pages/service-worker/index.html" - w = null - - afterEach -> - w?.destroy() - - it 'should register for file scheme', (done) -> - w = new BrowserWindow(show:false) - w.webContents.on 'ipc-message', (event, args) -> - if args[0] == 'reload' - w.webContents.reload() - else if args[0] == 'error' - done('unexpected error : ' + args[1]) - else if args[0] == 'response' - assert.equal args[1], 'Hello from serviceWorker!' - session.defaultSession.clearStorageData {storages: ['serviceworkers']}, -> - done() - w.loadURL url - - describe 'window.open', -> - @timeout 20000 - - it 'returns a BrowserWindowProxy object', -> - b = window.open 'about:blank', '', 'show=no' - assert.equal b.closed, false - assert.equal b.constructor.name, 'BrowserWindowProxy' - b.close() - - it 'accepts "node-integration" as feature', (done) -> - listener = (event) -> - assert.equal event.data, 'undefined' - b.close() - done() - window.addEventListener 'message', listener - b = window.open "file://#{fixtures}/pages/window-opener-node.html", '', 'nodeIntegration=no,show=no' - - it 'inherit options of parent window', (done) -> - listener = (event) -> - [width, height] = remote.getCurrentWindow().getSize() - assert.equal event.data, "size: #{width} #{height}" - b.close() - done() - window.addEventListener 'message', listener - b = window.open "file://#{fixtures}/pages/window-open-size.html", '', 'show=no' - - it 'does not override child options', (done) -> - size = {width: 350, height: 450} - listener = (event) -> - assert.equal event.data, "size: #{size.width} #{size.height}" - b.close() - done() - window.addEventListener 'message', listener - b = window.open "file://#{fixtures}/pages/window-open-size.html", '', "show=no,width=#{size.width},height=#{size.height}" - - describe 'window.opener', -> - @timeout 10000 - - url = "file://#{fixtures}/pages/window-opener.html" - w = null - - afterEach -> - w?.destroy() - - it 'is null for main window', (done) -> - w = new BrowserWindow(show: false) - w.webContents.on 'ipc-message', (event, args) -> - assert.deepEqual args, ['opener', null] - done() - w.loadURL url - - it 'is not null for window opened by window.open', (done) -> - listener = (event) -> - assert.equal event.data, 'object' - b.close() - done() - window.addEventListener 'message', listener - b = window.open url, '', 'show=no' - - describe 'window.postMessage', -> - it 'sets the source and origin correctly', (done) -> - sourceId = remote.getCurrentWindow().id - listener = (event) -> - window.removeEventListener 'message', listener - b.close() - message = JSON.parse(event.data) - assert.equal message.data, 'testing' - assert.equal message.origin, 'file://' - assert.equal message.sourceEqualsOpener, true - assert.equal message.sourceId, sourceId - assert.equal event.origin, 'file://' - done() - window.addEventListener 'message', listener - b = window.open "file://#{fixtures}/pages/window-open-postMessage.html", '', 'show=no' - BrowserWindow.fromId(b.guestId).webContents.once 'did-finish-load', -> - b.postMessage('testing', '*') - - describe 'window.opener.postMessage', -> - it 'sets source and origin correctly', (done) -> - listener = (event) -> - window.removeEventListener 'message', listener - b.close() - assert.equal event.source, b - assert.equal event.origin, 'file://' - done() - window.addEventListener 'message', listener - b = window.open "file://#{fixtures}/pages/window-opener-postMessage.html", '', 'show=no' - - describe 'creating a Uint8Array under browser side', -> - it 'does not crash', -> - RUint8Array = remote.getGlobal 'Uint8Array' - new RUint8Array - - describe 'webgl', -> - it 'can be get as context in canvas', -> - return if process.platform is 'linux' - webgl = document.createElement('canvas').getContext 'webgl' - assert.notEqual webgl, null - - describe 'web workers', -> - it 'Worker can work', (done) -> - worker = new Worker('../fixtures/workers/worker.js') - message = 'ping' - worker.onmessage = (event) -> - assert.equal event.data, message - worker.terminate() - done() - worker.postMessage message - - it 'SharedWorker can work', (done) -> - worker = new SharedWorker('../fixtures/workers/shared_worker.js') - message = 'ping' - worker.port.onmessage = (event) -> - assert.equal event.data, message - done() - worker.port.postMessage message - - describe 'iframe', -> - iframe = null - - beforeEach -> - iframe = document.createElement 'iframe' - - afterEach -> - document.body.removeChild iframe - - it 'does not have node integration', (done) -> - iframe.src = "file://#{fixtures}/pages/set-global.html" - document.body.appendChild iframe - iframe.onload = -> - assert.equal iframe.contentWindow.test, 'undefined undefined undefined' - done() - - describe 'storage', -> - it 'requesting persitent quota works', (done) -> - navigator.webkitPersistentStorage.requestQuota 1024 * 1024, (grantedBytes) -> - assert.equal grantedBytes, 1048576 - done() - - describe 'websockets', -> - wss = null - server = null - WebSocketServer = ws.Server - - afterEach -> - wss.close() - server.close() - - it 'has user agent', (done) -> - server = http.createServer() - server.listen 0, '127.0.0.1', -> - port = server.address().port - wss = new WebSocketServer(server: server) - wss.on 'error', done - wss.on 'connection', (ws) -> - if ws.upgradeReq.headers['user-agent'] - done() - else - done('user agent is empty') - websocket = new WebSocket("ws://127.0.0.1:#{port}") - - describe 'Promise', -> - it 'resolves correctly in Node.js calls', (done) -> - document.registerElement('x-element', { - prototype: Object.create(HTMLElement.prototype, { - createdCallback: { value: -> } - }) - }) - - setImmediate -> - called = false - Promise.resolve().then -> - done(if called then undefined else new Error('wrong sequence')) - document.createElement 'x-element' - called = true - - it 'resolves correctly in Electron calls', (done) -> - document.registerElement('y-element', { - prototype: Object.create(HTMLElement.prototype, { - createdCallback: { value: -> } - }) - }) - - remote.getGlobal('setImmediate') -> - called = false - Promise.resolve().then -> - done(if called then undefined else new Error('wrong sequence')) - document.createElement 'y-element' - called = true diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js new file mode 100644 index 000000000000..563eea742ffd --- /dev/null +++ b/spec/chromium-spec.js @@ -0,0 +1,368 @@ +var BrowserWindow, assert, http, https, path, ref, remote, session, ws; + +assert = require('assert'); + +http = require('http'); + +https = require('https'); + +path = require('path'); + +ws = require('ws'); + +remote = require('electron').remote; + +ref = remote.require('electron'), BrowserWindow = ref.BrowserWindow, session = ref.session; + +describe('chromium feature', function() { + var fixtures, listener; + fixtures = path.resolve(__dirname, 'fixtures'); + listener = null; + afterEach(function() { + if (listener != null) { + window.removeEventListener('message', listener); + } + return listener = null; + }); + xdescribe('heap snapshot', function() { + return it('does not crash', function() { + return process.atomBinding('v8_util').takeHeapSnapshot(); + }); + }); + describe('sending request of http protocol urls', function() { + return it('does not crash', function(done) { + var server; + this.timeout(5000); + server = http.createServer(function(req, res) { + res.end(); + server.close(); + return done(); + }); + return server.listen(0, '127.0.0.1', function() { + var port; + port = server.address().port; + return $.get("http://127.0.0.1:" + port); + }); + }); + }); + describe('document.hidden', function() { + var url, w; + url = "file://" + fixtures + "/pages/document-hidden.html"; + w = null; + afterEach(function() { + return w != null ? w.destroy() : void 0; + }); + it('is set correctly when window is not shown', function(done) { + w = new BrowserWindow({ + show: false + }); + w.webContents.on('ipc-message', function(event, args) { + assert.deepEqual(args, ['hidden', true]); + return done(); + }); + return w.loadURL(url); + }); + return it('is set correctly when window is inactive', function(done) { + w = new BrowserWindow({ + show: false + }); + w.webContents.on('ipc-message', function(event, args) { + assert.deepEqual(args, ['hidden', false]); + return done(); + }); + w.showInactive(); + return w.loadURL(url); + }); + }); + xdescribe('navigator.webkitGetUserMedia', function() { + return it('calls its callbacks', function(done) { + this.timeout(5000); + return navigator.webkitGetUserMedia({ + audio: true, + video: false + }, function() { + return done(); + }, function() { + return done(); + }); + }); + }); + describe('navigator.language', function() { + return it('should not be empty', function() { + return assert.notEqual(navigator.language, ''); + }); + }); + describe('navigator.serviceWorker', function() { + var url, w; + url = "file://" + fixtures + "/pages/service-worker/index.html"; + w = null; + afterEach(function() { + return w != null ? w.destroy() : void 0; + }); + return it('should register for file scheme', function(done) { + w = new BrowserWindow({ + show: false + }); + w.webContents.on('ipc-message', function(event, args) { + if (args[0] === 'reload') { + return w.webContents.reload(); + } else if (args[0] === 'error') { + return done('unexpected error : ' + args[1]); + } else if (args[0] === 'response') { + assert.equal(args[1], 'Hello from serviceWorker!'); + return session.defaultSession.clearStorageData({ + storages: ['serviceworkers'] + }, function() { + return done(); + }); + } + }); + return w.loadURL(url); + }); + }); + describe('window.open', function() { + this.timeout(20000); + it('returns a BrowserWindowProxy object', function() { + var b; + b = window.open('about:blank', '', 'show=no'); + assert.equal(b.closed, false); + assert.equal(b.constructor.name, 'BrowserWindowProxy'); + return b.close(); + }); + it('accepts "node-integration" as feature', function(done) { + var b; + listener = function(event) { + assert.equal(event.data, 'undefined'); + b.close(); + return done(); + }; + window.addEventListener('message', listener); + return b = window.open("file://" + fixtures + "/pages/window-opener-node.html", '', 'nodeIntegration=no,show=no'); + }); + it('inherit options of parent window', function(done) { + var b; + listener = function(event) { + var height, ref1, width; + ref1 = remote.getCurrentWindow().getSize(), width = ref1[0], height = ref1[1]; + assert.equal(event.data, "size: " + width + " " + height); + b.close(); + return done(); + }; + window.addEventListener('message', listener); + return b = window.open("file://" + fixtures + "/pages/window-open-size.html", '', 'show=no'); + }); + return it('does not override child options', function(done) { + var b, size; + size = { + width: 350, + height: 450 + }; + listener = function(event) { + assert.equal(event.data, "size: " + size.width + " " + size.height); + b.close(); + return done(); + }; + window.addEventListener('message', listener); + return b = window.open("file://" + fixtures + "/pages/window-open-size.html", '', "show=no,width=" + size.width + ",height=" + size.height); + }); + }); + describe('window.opener', function() { + var url, w; + this.timeout(10000); + url = "file://" + fixtures + "/pages/window-opener.html"; + w = null; + afterEach(function() { + return w != null ? w.destroy() : void 0; + }); + it('is null for main window', function(done) { + w = new BrowserWindow({ + show: false + }); + w.webContents.on('ipc-message', function(event, args) { + assert.deepEqual(args, ['opener', null]); + return done(); + }); + return w.loadURL(url); + }); + return it('is not null for window opened by window.open', function(done) { + var b; + listener = function(event) { + assert.equal(event.data, 'object'); + b.close(); + return done(); + }; + window.addEventListener('message', listener); + return b = window.open(url, '', 'show=no'); + }); + }); + describe('window.postMessage', function() { + return it('sets the source and origin correctly', function(done) { + var b, sourceId; + sourceId = remote.getCurrentWindow().id; + listener = function(event) { + var message; + window.removeEventListener('message', listener); + b.close(); + message = JSON.parse(event.data); + assert.equal(message.data, 'testing'); + assert.equal(message.origin, 'file://'); + assert.equal(message.sourceEqualsOpener, true); + assert.equal(message.sourceId, sourceId); + assert.equal(event.origin, 'file://'); + return done(); + }; + window.addEventListener('message', listener); + b = window.open("file://" + fixtures + "/pages/window-open-postMessage.html", '', 'show=no'); + return BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function() { + return b.postMessage('testing', '*'); + }); + }); + }); + describe('window.opener.postMessage', function() { + return it('sets source and origin correctly', function(done) { + var b; + listener = function(event) { + window.removeEventListener('message', listener); + b.close(); + assert.equal(event.source, b); + assert.equal(event.origin, 'file://'); + return done(); + }; + window.addEventListener('message', listener); + return b = window.open("file://" + fixtures + "/pages/window-opener-postMessage.html", '', 'show=no'); + }); + }); + describe('creating a Uint8Array under browser side', function() { + return it('does not crash', function() { + var RUint8Array; + RUint8Array = remote.getGlobal('Uint8Array'); + return new RUint8Array; + }); + }); + describe('webgl', function() { + return it('can be get as context in canvas', function() { + var webgl; + if (process.platform === 'linux') { + return; + } + webgl = document.createElement('canvas').getContext('webgl'); + return assert.notEqual(webgl, null); + }); + }); + describe('web workers', function() { + it('Worker can work', function(done) { + var message, worker; + worker = new Worker('../fixtures/workers/worker.js'); + message = 'ping'; + worker.onmessage = function(event) { + assert.equal(event.data, message); + worker.terminate(); + return done(); + }; + return worker.postMessage(message); + }); + return it('SharedWorker can work', function(done) { + var message, worker; + worker = new SharedWorker('../fixtures/workers/shared_worker.js'); + message = 'ping'; + worker.port.onmessage = function(event) { + assert.equal(event.data, message); + return done(); + }; + return worker.port.postMessage(message); + }); + }); + describe('iframe', function() { + var iframe; + iframe = null; + beforeEach(function() { + return iframe = document.createElement('iframe'); + }); + afterEach(function() { + return document.body.removeChild(iframe); + }); + return it('does not have node integration', function(done) { + iframe.src = "file://" + fixtures + "/pages/set-global.html"; + document.body.appendChild(iframe); + return iframe.onload = function() { + assert.equal(iframe.contentWindow.test, 'undefined undefined undefined'); + return done(); + }; + }); + }); + describe('storage', function() { + return it('requesting persitent quota works', function(done) { + return navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function(grantedBytes) { + assert.equal(grantedBytes, 1048576); + return done(); + }); + }); + }); + describe('websockets', function() { + var WebSocketServer, server, wss; + wss = null; + server = null; + WebSocketServer = ws.Server; + afterEach(function() { + wss.close(); + return server.close(); + }); + return it('has user agent', function(done) { + server = http.createServer(); + return server.listen(0, '127.0.0.1', function() { + var port, websocket; + port = server.address().port; + wss = new WebSocketServer({ + server: server + }); + wss.on('error', done); + wss.on('connection', function(ws) { + if (ws.upgradeReq.headers['user-agent']) { + return done(); + } else { + return done('user agent is empty'); + } + }); + return websocket = new WebSocket("ws://127.0.0.1:" + port); + }); + }); + }); + return describe('Promise', function() { + it('resolves correctly in Node.js calls', function(done) { + document.registerElement('x-element', { + prototype: Object.create(HTMLElement.prototype, { + createdCallback: { + value: function() {} + } + }) + }); + return setImmediate(function() { + var called; + called = false; + Promise.resolve().then(function() { + return done(called ? void 0 : new Error('wrong sequence')); + }); + document.createElement('x-element'); + return called = true; + }); + }); + return it('resolves correctly in Electron calls', function(done) { + document.registerElement('y-element', { + prototype: Object.create(HTMLElement.prototype, { + createdCallback: { + value: function() {} + } + }) + }); + return remote.getGlobal('setImmediate')(function() { + var called; + called = false; + Promise.resolve().then(function() { + return done(called ? void 0 : new Error('wrong sequence')); + }); + document.createElement('y-element'); + return called = true; + }); + }); + }); +}); diff --git a/spec/fixtures/pages/save_page/index.html b/spec/fixtures/pages/save_page/index.html index 829233bb86fb..61445bce23d1 100644 --- a/spec/fixtures/pages/save_page/index.html +++ b/spec/fixtures/pages/save_page/index.html @@ -1,6 +1,6 @@ - + diff --git a/spec/fixtures/pages/save_page/test.js b/spec/fixtures/pages/save_page/test.js index 3d79457383f5..c6035e5e3f33 100644 --- a/spec/fixtures/pages/save_page/test.js +++ b/spec/fixtures/pages/save_page/test.js @@ -1 +1 @@ -console.log('save_page'); +// do nothing diff --git a/spec/modules-spec.coffee b/spec/modules-spec.coffee deleted file mode 100644 index 1cdc6cf0ea5c..000000000000 --- a/spec/modules-spec.coffee +++ /dev/null @@ -1,38 +0,0 @@ -assert = require 'assert' -fs = require 'fs' -path = require 'path' -temp = require 'temp' - -describe 'third-party module', -> - fixtures = path.join __dirname, 'fixtures' - temp.track() - - # If the test is executed with the debug build on Windows, we will skip it - # because native modules don't work with the debug build (see issue #2558). - if process.platform isnt 'win32' or - process.execPath.toLowerCase().indexOf('\\out\\d\\') is -1 - describe 'runas', -> - it 'can be required in renderer', -> - require 'runas' - - it 'can be required in node binary', (done) -> - runas = path.join fixtures, 'module', 'runas.js' - child = require('child_process').fork runas - child.on 'message', (msg) -> - assert.equal msg, 'ok' - done() - - describe 'ffi', -> - it 'does not crash', -> - ffi = require 'ffi' - libm = ffi.Library('libm', ceil: [ 'double', [ 'double' ] ]) - assert.equal libm.ceil(1.5), 2 - - describe 'q', -> - Q = require 'q' - - describe 'Q.when', -> - it 'emits the fullfil callback', (done) -> - Q(true).then (val) -> - assert.equal val, true - done() diff --git a/spec/modules-spec.js b/spec/modules-spec.js new file mode 100644 index 000000000000..809aa547b1d9 --- /dev/null +++ b/spec/modules-spec.js @@ -0,0 +1,53 @@ +var assert, fs, path, temp; + +assert = require('assert'); + +fs = require('fs'); + +path = require('path'); + +temp = require('temp'); + +describe('third-party module', function() { + var fixtures; + fixtures = path.join(__dirname, 'fixtures'); + temp.track(); + if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { + describe('runas', function() { + it('can be required in renderer', function() { + return require('runas'); + }); + return it('can be required in node binary', function(done) { + var child, runas; + runas = path.join(fixtures, 'module', 'runas.js'); + child = require('child_process').fork(runas); + return child.on('message', function(msg) { + assert.equal(msg, 'ok'); + return done(); + }); + }); + }); + describe('ffi', function() { + return it('does not crash', function() { + var ffi, libm; + ffi = require('ffi'); + libm = ffi.Library('libm', { + ceil: ['double', ['double']] + }); + return assert.equal(libm.ceil(1.5), 2); + }); + }); + } + return describe('q', function() { + var Q; + Q = require('q'); + return describe('Q.when', function() { + return it('emits the fullfil callback', function(done) { + return Q(true).then(function(val) { + assert.equal(val, true); + return done(); + }); + }); + }); + }); +}); diff --git a/spec/node-spec.coffee b/spec/node-spec.coffee deleted file mode 100644 index e6b2aa15821c..000000000000 --- a/spec/node-spec.coffee +++ /dev/null @@ -1,156 +0,0 @@ -assert = require 'assert' -child_process = require 'child_process' -fs = require 'fs' -path = require 'path' -os = require 'os' - -{remote} = require 'electron' - -describe 'node feature', -> - fixtures = path.join __dirname, 'fixtures' - - describe 'child_process', -> - describe 'child_process.fork', -> - it 'works in current process', (done) -> - child = child_process.fork path.join(fixtures, 'module', 'ping.js') - child.on 'message', (msg) -> - assert.equal msg, 'message' - done() - child.send 'message' - - it 'preserves args', (done) -> - args = ['--expose_gc', '-test', '1'] - child = child_process.fork path.join(fixtures, 'module', 'process_args.js'), args - child.on 'message', (msg) -> - assert.deepEqual args, msg.slice(2) - done() - child.send 'message' - - it 'works in forked process', (done) -> - child = child_process.fork path.join(fixtures, 'module', 'fork_ping.js') - child.on 'message', (msg) -> - assert.equal msg, 'message' - done() - child.send 'message' - - it 'works in forked process when options.env is specifed', (done) -> - child = child_process.fork path.join(fixtures, 'module', 'fork_ping.js'), - [], - path: process.env['PATH'] - child.on 'message', (msg) -> - assert.equal msg, 'message' - done() - child.send 'message' - - it 'works in browser process', (done) -> - fork = remote.require('child_process').fork - child = fork path.join(fixtures, 'module', 'ping.js') - child.on 'message', (msg) -> - assert.equal msg, 'message' - done() - child.send 'message' - - it 'has String::localeCompare working in script', (done) -> - child = child_process.fork path.join(fixtures, 'module', 'locale-compare.js') - child.on 'message', (msg) -> - assert.deepEqual msg, [0, -1, 1] - done() - child.send 'message' - - it 'has setImmediate working in script', (done) -> - child = child_process.fork path.join(fixtures, 'module', 'set-immediate.js') - child.on 'message', (msg) -> - assert.equal msg, 'ok' - done() - child.send 'message' - - describe 'contexts', -> - describe 'setTimeout in fs callback', -> - return if process.env.TRAVIS is 'true' - it 'does not crash', (done) -> - fs.readFile __filename, -> - setTimeout done, 0 - - describe 'throw error in node context', -> - it 'gets caught', (done) -> - error = new Error('boo!') - lsts = process.listeners 'uncaughtException' - process.removeAllListeners 'uncaughtException' - process.on 'uncaughtException', (err) -> - process.removeAllListeners 'uncaughtException' - for lst in lsts - process.on 'uncaughtException', lst - done() - fs.readFile __filename, -> - throw error - - describe 'setTimeout called under Chromium event loop in browser process', -> - it 'can be scheduled in time', (done) -> - remote.getGlobal('setTimeout')(done, 0) - - describe 'setInterval called under Chromium event loop in browser process', -> - it 'can be scheduled in time', (done) -> - clear = -> - remote.getGlobal('clearInterval')(interval) - done() - interval = remote.getGlobal('setInterval')(clear, 10) - - describe 'message loop', -> - describe 'process.nextTick', -> - it 'emits the callback', (done) -> - process.nextTick done - - it 'works in nested calls', (done) -> - process.nextTick -> - process.nextTick -> - process.nextTick done - - describe 'setImmediate', -> - it 'emits the callback', (done) -> - setImmediate done - - it 'works in nested calls', (done) -> - setImmediate -> - setImmediate -> - setImmediate done - - describe 'net.connect', -> - return unless process.platform is 'darwin' - - it 'emit error when connect to a socket path without listeners', (done) -> - socketPath = path.join os.tmpdir(), 'atom-shell-test.sock' - script = path.join(fixtures, 'module', 'create_socket.js') - child = child_process.fork script, [socketPath] - child.on 'exit', (code) -> - assert.equal code, 0 - client = require('net').connect socketPath - client.on 'error', (error) -> - assert.equal error.code, 'ECONNREFUSED' - done() - - describe 'Buffer', -> - it 'can be created from WebKit external string', -> - p = document.createElement 'p' - p.innerText = '闲云潭影日悠悠,物换星移几度秋' - b = new Buffer(p.innerText) - assert.equal b.toString(), '闲云潭影日悠悠,物换星移几度秋' - assert.equal Buffer.byteLength(p.innerText), 45 - - it 'correctly parses external one-byte UTF8 string', -> - p = document.createElement 'p' - p.innerText = 'Jøhänñéß' - b = new Buffer(p.innerText) - assert.equal b.toString(), 'Jøhänñéß' - assert.equal Buffer.byteLength(p.innerText), 13 - - describe 'process.stdout', -> - it 'should not throw exception', -> - process.stdout - - # Not reliable on some machines - xit 'should have isTTY defined', -> - assert.equal typeof(process.stdout.isTTY), 'boolean' - - describe 'vm.createContext', -> - it 'should not crash', -> - require('vm').runInNewContext('') diff --git a/spec/node-spec.js b/spec/node-spec.js new file mode 100644 index 000000000000..fb9a40ea8d42 --- /dev/null +++ b/spec/node-spec.js @@ -0,0 +1,213 @@ +var assert, child_process, fs, os, path, remote; + +assert = require('assert'); + +child_process = require('child_process'); + +fs = require('fs'); + +path = require('path'); + +os = require('os'); + +remote = require('electron').remote; + +describe('node feature', function() { + var fixtures; + fixtures = path.join(__dirname, 'fixtures'); + describe('child_process', function() { + return describe('child_process.fork', function() { + it('works in current process', function(done) { + var child; + child = child_process.fork(path.join(fixtures, 'module', 'ping.js')); + child.on('message', function(msg) { + assert.equal(msg, 'message'); + return done(); + }); + return child.send('message'); + }); + it('preserves args', function(done) { + var args, child; + args = ['--expose_gc', '-test', '1']; + child = child_process.fork(path.join(fixtures, 'module', 'process_args.js'), args); + child.on('message', function(msg) { + assert.deepEqual(args, msg.slice(2)); + return done(); + }); + return child.send('message'); + }); + it('works in forked process', function(done) { + var child; + child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js')); + child.on('message', function(msg) { + assert.equal(msg, 'message'); + return done(); + }); + return child.send('message'); + }); + it('works in forked process when options.env is specifed', function(done) { + var child; + child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], { + path: process.env['PATH'] + }); + child.on('message', function(msg) { + assert.equal(msg, 'message'); + return done(); + }); + return child.send('message'); + }); + it('works in browser process', function(done) { + var child, fork; + fork = remote.require('child_process').fork; + child = fork(path.join(fixtures, 'module', 'ping.js')); + child.on('message', function(msg) { + assert.equal(msg, 'message'); + return done(); + }); + return child.send('message'); + }); + it('has String::localeCompare working in script', function(done) { + var child; + child = child_process.fork(path.join(fixtures, 'module', 'locale-compare.js')); + child.on('message', function(msg) { + assert.deepEqual(msg, [0, -1, 1]); + return done(); + }); + return child.send('message'); + }); + return it('has setImmediate working in script', function(done) { + var child; + child = child_process.fork(path.join(fixtures, 'module', 'set-immediate.js')); + child.on('message', function(msg) { + assert.equal(msg, 'ok'); + return done(); + }); + return child.send('message'); + }); + }); + }); + describe('contexts', function() { + describe('setTimeout in fs callback', function() { + if (process.env.TRAVIS === 'true') { + return; + } + return it('does not crash', function(done) { + return fs.readFile(__filename, function() { + return setTimeout(done, 0); + }); + }); + }); + describe('throw error in node context', function() { + return it('gets caught', function(done) { + var error, lsts; + error = new Error('boo!'); + lsts = process.listeners('uncaughtException'); + process.removeAllListeners('uncaughtException'); + process.on('uncaughtException', function(err) { + var i, len, lst; + process.removeAllListeners('uncaughtException'); + for (i = 0, len = lsts.length; i < len; i++) { + lst = lsts[i]; + process.on('uncaughtException', lst); + } + return done(); + }); + return fs.readFile(__filename, function() { + throw error; + }); + }); + }); + describe('setTimeout called under Chromium event loop in browser process', function() { + return it('can be scheduled in time', function(done) { + return remote.getGlobal('setTimeout')(done, 0); + }); + }); + return describe('setInterval called under Chromium event loop in browser process', function() { + return it('can be scheduled in time', function(done) { + var clear, interval; + clear = function() { + remote.getGlobal('clearInterval')(interval); + return done(); + }; + return interval = remote.getGlobal('setInterval')(clear, 10); + }); + }); + }); + describe('message loop', function() { + describe('process.nextTick', function() { + it('emits the callback', function(done) { + return process.nextTick(done); + }); + return it('works in nested calls', function(done) { + return process.nextTick(function() { + return process.nextTick(function() { + return process.nextTick(done); + }); + }); + }); + }); + return describe('setImmediate', function() { + it('emits the callback', function(done) { + return setImmediate(done); + }); + return it('works in nested calls', function(done) { + return setImmediate(function() { + return setImmediate(function() { + return setImmediate(done); + }); + }); + }); + }); + }); + describe('net.connect', function() { + if (process.platform !== 'darwin') { + return; + } + return it('emit error when connect to a socket path without listeners', function(done) { + var child, script, socketPath; + socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock'); + script = path.join(fixtures, 'module', 'create_socket.js'); + child = child_process.fork(script, [socketPath]); + return child.on('exit', function(code) { + var client; + assert.equal(code, 0); + client = require('net').connect(socketPath); + return client.on('error', function(error) { + assert.equal(error.code, 'ECONNREFUSED'); + return done(); + }); + }); + }); + }); + describe('Buffer', function() { + it('can be created from WebKit external string', function() { + var b, p; + p = document.createElement('p'); + p.innerText = '闲云潭影日悠悠,物换星移几度秋'; + b = new Buffer(p.innerText); + assert.equal(b.toString(), '闲云潭影日悠悠,物换星移几度秋'); + return assert.equal(Buffer.byteLength(p.innerText), 45); + }); + return it('correctly parses external one-byte UTF8 string', function() { + var b, p; + p = document.createElement('p'); + p.innerText = 'Jøhänñéß'; + b = new Buffer(p.innerText); + assert.equal(b.toString(), 'Jøhänñéß'); + return assert.equal(Buffer.byteLength(p.innerText), 13); + }); + }); + describe('process.stdout', function() { + it('should not throw exception', function() { + return process.stdout; + }); + return xit('should have isTTY defined', function() { + return assert.equal(typeof process.stdout.isTTY, 'boolean'); + }); + }); + return describe('vm.createContext', function() { + return it('should not crash', function() { + return require('vm').runInNewContext(''); + }); + }); +}); diff --git a/spec/static/index.html b/spec/static/index.html index f958e1b7ed01..e31fcffebb89 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -31,8 +31,6 @@ remote.getCurrentWindow().inspectElement(e.clientX, e.clientY); } - require('coffee-script/register'); // Supports .coffee tests. - // Rediret all output to browser. if (isCi) { global.__defineGetter__('console', function() { @@ -64,7 +62,7 @@ }); walker.on('file', function(file) { - if (/-spec.coffee$/.test(file)) + if (/-spec\.js$/.test(file)) mocha.addFile(file); }); diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee deleted file mode 100644 index 4754ea4348be..000000000000 --- a/spec/webview-spec.coffee +++ /dev/null @@ -1,517 +0,0 @@ -assert = require 'assert' -path = require 'path' -http = require 'http' -url = require 'url' - -describe ' tag', -> - @timeout 10000 - - fixtures = path.join __dirname, 'fixtures' - - webview = null - beforeEach -> - webview = new WebView - afterEach -> - document.body.removeChild(webview) if document.body.contains(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 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 - - it 'loads node symbols after POST navigation 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/post.html" - document.body.appendChild webview - - # If the test is executed with the debug build on Windows, we will skip it - # because native modules don't work with the debug build (see issue #2558). - if process.platform isnt 'win32' or - process.execPath.toLowerCase().indexOf('\\out\\d\\') is -1 - it 'loads native modules when navigation happens', (done) -> - listener = (e) -> - webview.removeEventListener 'did-finish-load', listener - listener2 = (e) -> - assert.equal e.message, 'function' - done() - webview.addEventListener 'console-message', listener2 - webview.reload() - webview.addEventListener 'did-finish-load', listener - webview.setAttribute 'nodeintegration', 'on' - webview.src = "file://#{fixtures}/pages/native-module.html" - document.body.appendChild webview - - describe 'preload attribute', -> - it 'loads the script before other scripts in window', (done) -> - listener = (e) -> - assert.equal e.message, 'function object object' - webview.removeEventListener 'console-message', listener - done() - webview.addEventListener 'console-message', listener - webview.setAttribute 'preload', "#{fixtures}/module/preload.js" - webview.src = "file://#{fixtures}/pages/e.html" - document.body.appendChild webview - - it 'preload script can still use "process" in required modules when nodeintegration is off', (done) -> - webview.addEventListener 'console-message', (e) -> - assert.equal e.message, 'object undefined object' - done() - webview.setAttribute 'preload', "#{fixtures}/module/preload-node-off.js" - webview.src = "file://#{fixtures}/api/blank.html" - document.body.appendChild webview - - it 'receives ipc message in preload script', (done) -> - message = 'boom!' - listener = (e) -> - assert.equal e.channel, 'pong' - assert.deepEqual e.args, [message] - webview.removeEventListener 'ipc-message', listener - done() - listener2 = (e) -> - webview.send 'ping', message - webview.removeEventListener 'did-finish-load', listener2 - webview.addEventListener 'ipc-message', listener - webview.addEventListener 'did-finish-load', listener2 - webview.setAttribute 'preload', "#{fixtures}/module/preload-ipc.js" - webview.src = "file://#{fixtures}/pages/e.html" - document.body.appendChild webview - - describe 'httpreferrer attribute', -> - it 'sets the referrer url', (done) -> - referrer = 'http://github.com/' - listener = (e) -> - assert.equal e.message, referrer - webview.removeEventListener 'console-message', listener - done() - webview.addEventListener 'console-message', listener - webview.setAttribute 'httpreferrer', referrer - webview.src = "file://#{fixtures}/pages/referrer.html" - document.body.appendChild webview - - describe 'useragent attribute', -> - it 'sets the user agent', (done) -> - referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko' - listener = (e) -> - assert.equal e.message, referrer - webview.removeEventListener 'console-message', listener - done() - webview.addEventListener 'console-message', listener - webview.setAttribute 'useragent', referrer - webview.src = "file://#{fixtures}/pages/useragent.html" - document.body.appendChild webview - - describe 'disablewebsecurity attribute', -> - it 'does not disable web security when not set', (done) -> - src = " - - - " - encoded = btoa(unescape(encodeURIComponent(src))) - listener = (e) -> - assert /Not allowed to load local resource/.test(e.message) - webview.removeEventListener 'console-message', listener - done() - webview.addEventListener 'console-message', listener - webview.src = "data:text/html;base64,#{encoded}" - document.body.appendChild webview - - it 'disables web security when set', (done) -> - src = " - - - " - encoded = btoa(unescape(encodeURIComponent(src))) - listener = (e) -> - assert.equal e.message, 'ok' - webview.removeEventListener 'console-message', listener - done() - webview.addEventListener 'console-message', listener - webview.setAttribute 'disablewebsecurity', '' - webview.src = "data:text/html;base64,#{encoded}" - document.body.appendChild webview - - describe 'partition attribute', -> - it 'inserts no node symbols when not set', (done) -> - webview.addEventListener 'console-message', (e) -> - assert.equal e.message, 'undefined undefined undefined undefined' - done() - webview.src = "file://#{fixtures}/pages/c.html" - webview.partition = 'test1' - 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" - webview.partition = 'test2' - document.body.appendChild webview - - it 'isolates storage for different id', (done) -> - listener = (e) -> - assert.equal e.message, " 0" - webview.removeEventListener 'console-message', listener - done() - window.localStorage.setItem 'test', 'one' - webview.addEventListener 'console-message', listener - webview.src = "file://#{fixtures}/pages/partition/one.html" - webview.partition = 'test3' - document.body.appendChild webview - - it 'uses current session storage when no id is provided', (done) -> - listener = (e) -> - assert.equal e.message, "one 1" - webview.removeEventListener 'console-message', listener - done() - window.localStorage.setItem 'test', 'one' - webview.addEventListener 'console-message', listener - webview.src = "file://#{fixtures}/pages/partition/one.html" - document.body.appendChild webview - - describe 'allowpopups attribute', -> - it 'can not open new window when not set', (done) -> - listener = (e) -> - assert.equal e.message, 'null' - webview.removeEventListener 'console-message', listener - done() - webview.addEventListener 'console-message', listener - webview.src = "file://#{fixtures}/pages/window-open-hide.html" - document.body.appendChild webview - - it 'can open new window when set', (done) -> - listener = (e) -> - assert.equal e.message, 'window' - webview.removeEventListener 'console-message', listener - done() - webview.addEventListener 'console-message', listener - webview.setAttribute 'allowpopups', 'on' - webview.src = "file://#{fixtures}/pages/window-open-hide.html" - document.body.appendChild webview - - describe 'new-window event', -> - it 'emits when window.open is called', (done) -> - webview.addEventListener 'new-window', (e) -> - assert.equal e.url, 'http://host/' - assert.equal e.frameName, 'host' - done() - webview.src = "file://#{fixtures}/pages/window-open.html" - document.body.appendChild webview - - it 'emits when link with target is called', (done) -> - webview.addEventListener 'new-window', (e) -> - assert.equal e.url, 'http://host/' - assert.equal e.frameName, 'target' - done() - webview.src = "file://#{fixtures}/pages/target-name.html" - document.body.appendChild webview - - describe 'ipc-message event', -> - it 'emits when guest sends a ipc message to browser', (done) -> - webview.addEventListener 'ipc-message', (e) -> - assert.equal e.channel, 'channel' - assert.deepEqual e.args, ['arg1', 'arg2'] - done() - webview.src = "file://#{fixtures}/pages/ipc-message.html" - webview.setAttribute 'nodeintegration', 'on' - document.body.appendChild webview - - describe 'page-title-set event', -> - it 'emits when title is set', (done) -> - webview.addEventListener 'page-title-set', (e) -> - assert.equal e.title, 'test' - assert e.explicitSet - done() - webview.src = "file://#{fixtures}/pages/a.html" - document.body.appendChild webview - - describe 'page-favicon-updated event', -> - it 'emits when favicon urls are received', (done) -> - webview.addEventListener 'page-favicon-updated', (e) -> - assert.equal e.favicons.length, 2 - pageUrl = - if process.platform is 'win32' - 'file:///C:/favicon.png' - else - 'file:///favicon.png' - assert.equal e.favicons[0], pageUrl - done() - webview.src = "file://#{fixtures}/pages/a.html" - document.body.appendChild webview - - describe 'will-navigate event', -> - it 'emits when a url that leads to oustide of the page is clicked', (done) -> - webview.addEventListener 'will-navigate', (e) -> - assert.equal e.url, "http://host/" - done() - - webview.src = "file://#{fixtures}/pages/webview-will-navigate.html" - document.body.appendChild webview - - describe 'did-navigate event', -> - p = path.join fixtures, 'pages', 'webview-will-navigate.html' - p = p.replace /\\/g, '/' - pageUrl = url.format protocol: 'file', slashes: true, pathname: p - - it 'emits when a url that leads to outside of the page is clicked', (done) -> - webview.addEventListener 'did-navigate', (e) -> - assert.equal e.url, pageUrl - done() - - webview.src = pageUrl - document.body.appendChild webview - - describe 'did-navigate-in-page event', -> - it 'emits when an anchor link is clicked', (done) -> - p = path.join fixtures, 'pages', 'webview-did-navigate-in-page.html' - p = p.replace /\\/g, '/' - pageUrl = url.format protocol: 'file', slashes: true, pathname: p - - webview.addEventListener 'did-navigate-in-page', (e) -> - assert.equal e.url, "#{pageUrl}#test_content" - done() - - webview.src = pageUrl - document.body.appendChild webview - - it 'emits when window.history.replaceState is called', (done) -> - webview.addEventListener 'did-navigate-in-page', (e) -> - assert.equal e.url, "http://host/" - done() - - webview.src = "file://#{fixtures}/pages/webview-did-navigate-in-page-with-history.html" - document.body.appendChild webview - - it 'emits when window.location.hash is changed', (done) -> - p = path.join fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html' - p = p.replace /\\/g, '/' - pageUrl = url.format protocol: 'file', slashes: true, pathname: p - - webview.addEventListener 'did-navigate-in-page', (e) -> - assert.equal e.url, "#{pageUrl}#test" - done() - - webview.src = pageUrl - document.body.appendChild webview - - describe 'close event', -> - it 'should fire when interior page calls window.close', (done) -> - webview.addEventListener 'close', -> - done() - - webview.src = "file://#{fixtures}/pages/close.html" - document.body.appendChild webview - - describe 'devtools-opened event', -> - it 'should fire when webview.openDevTools() is called', (done) -> - listener = -> - webview.removeEventListener 'devtools-opened', listener - webview.closeDevTools() - done() - - webview.addEventListener 'devtools-opened', listener - webview.addEventListener 'dom-ready', -> - webview.openDevTools() - - webview.src = "file://#{fixtures}/pages/base-page.html" - document.body.appendChild webview - - describe 'devtools-closed event', -> - it 'should fire when webview.closeDevTools() is called', (done) -> - listener2 = -> - webview.removeEventListener 'devtools-closed', listener2 - done() - - listener = -> - webview.removeEventListener 'devtools-opened', listener - webview.closeDevTools() - - webview.addEventListener 'devtools-opened', listener - webview.addEventListener 'devtools-closed', listener2 - webview.addEventListener 'dom-ready', -> - webview.openDevTools() - - webview.src = "file://#{fixtures}/pages/base-page.html" - document.body.appendChild webview - - describe 'devtools-focused event', -> - it 'should fire when webview.openDevTools() is called', (done) -> - listener = -> - webview.removeEventListener 'devtools-focused', listener - webview.closeDevTools() - done() - - webview.addEventListener 'devtools-focused', listener - webview.addEventListener 'dom-ready', -> - webview.openDevTools() - - webview.src = "file://#{fixtures}/pages/base-page.html" - document.body.appendChild webview - - describe '.reload()', -> - it 'should emit beforeunload handler', (done) -> - listener = (e) -> - assert.equal e.channel, 'onbeforeunload' - webview.removeEventListener 'ipc-message', listener - done() - listener2 = (e) -> - webview.reload() - webview.removeEventListener 'did-finish-load', listener2 - webview.addEventListener 'ipc-message', listener - webview.addEventListener 'did-finish-load', listener2 - webview.setAttribute 'nodeintegration', 'on' - webview.src = "file://#{fixtures}/pages/beforeunload-false.html" - document.body.appendChild webview - - describe '.clearHistory()', -> - it 'should clear the navigation history', (done) -> - listener = (e) -> - assert.equal e.channel, 'history' - assert.equal e.args[0], 2 - assert webview.canGoBack() - webview.clearHistory() - assert not webview.canGoBack() - webview.removeEventListener 'ipc-message', listener - done() - webview.addEventListener 'ipc-message', listener - webview.setAttribute 'nodeintegration', 'on' - webview.src = "file://#{fixtures}/pages/history.html" - document.body.appendChild webview - - describe 'basic auth', -> - auth = require 'basic-auth' - - it 'should authenticate with correct credentials', (done) -> - message = 'Authenticated' - server = http.createServer (req, res) -> - credentials = auth(req) - if credentials.name == 'test' and credentials.pass == 'test' - res.end(message) - else - res.end('failed') - server.close() - server.listen 0, '127.0.0.1', -> - {port} = server.address() - webview.addEventListener 'ipc-message', (e) -> - assert.equal e.channel, message - done() - webview.src = "file://#{fixtures}/pages/basic-auth.html?port=#{port}" - webview.setAttribute 'nodeintegration', 'on' - document.body.appendChild webview - - describe 'dom-ready event', -> - it 'emits when document is loaded', (done) -> - server = http.createServer (req) -> - # Never respond, so the page never finished loading. - server.listen 0, '127.0.0.1', -> - {port} = server.address() - webview.addEventListener 'dom-ready', -> - done() - webview.src = "file://#{fixtures}/pages/dom-ready.html?port=#{port}" - document.body.appendChild webview - - describe 'executeJavaScript', -> - return unless process.env.TRAVIS is 'true' - - it 'should support user gesture', (done) -> - listener = (e) -> - webview.removeEventListener 'enter-html-full-screen', listener - done() - listener2 = (e) -> - jsScript = 'document.getElementsByTagName("video")[0].webkitRequestFullScreen()' - webview.executeJavaScript jsScript, true - webview.removeEventListener 'did-finish-load', listener2 - webview.addEventListener 'enter-html-full-screen', listener - webview.addEventListener 'did-finish-load', listener2 - webview.src = "file://#{fixtures}/pages/fullscreen.html" - document.body.appendChild webview - - describe 'sendInputEvent', -> - it 'can send keyboard event', (done) -> - webview.addEventListener 'ipc-message', (e) -> - assert.equal e.channel, 'keyup' - assert.deepEqual e.args, [67, true, false] - done() - webview.addEventListener 'dom-ready', -> - webview.sendInputEvent type: 'keyup', keyCode: 'c', modifiers: ['shift'] - webview.src = "file://#{fixtures}/pages/onkeyup.html" - webview.setAttribute 'nodeintegration', 'on' - document.body.appendChild webview - - it 'can send mouse event', (done) -> - webview.addEventListener 'ipc-message', (e) -> - assert.equal e.channel, 'mouseup' - assert.deepEqual e.args, [10, 20, false, true] - done() - webview.addEventListener 'dom-ready', -> - webview.sendInputEvent type: 'mouseup', modifiers: ['ctrl'], x: 10, y: 20 - webview.src = "file://#{fixtures}/pages/onmouseup.html" - webview.setAttribute 'nodeintegration', 'on' - document.body.appendChild webview - - describe 'media-started-playing media-paused events', -> - it 'emits when audio starts and stops playing', (done) -> - audioPlayed = false - webview.addEventListener 'media-started-playing', -> - audioPlayed = true - webview.addEventListener 'media-paused', -> - assert audioPlayed - done() - webview.src = "file://#{fixtures}/pages/audio.html" - document.body.appendChild webview - - describe 'found-in-page event', -> - it 'emits when a request is made', (done) -> - requestId = null - listener = (e) -> - assert.equal e.result.requestId, requestId - if e.result.finalUpdate - assert.equal e.result.matches, 3 - webview.stopFindInPage "clearSelection" - done() - listener2 = (e) -> - requestId = webview.findInPage "virtual" - webview.addEventListener 'found-in-page', listener - webview.addEventListener 'did-finish-load', listener2 - webview.src = "file://#{fixtures}/pages/content.html" - document.body.appendChild webview - - xdescribe 'did-change-theme-color event', -> - it 'emits when theme color changes', (done) -> - webview.addEventListener 'did-change-theme-color', (e) -> - done() - webview.src = "file://#{fixtures}/pages/theme-color.html" - document.body.appendChild webview diff --git a/spec/webview-spec.js b/spec/webview-spec.js new file mode 100644 index 000000000000..660a960cf946 --- /dev/null +++ b/spec/webview-spec.js @@ -0,0 +1,639 @@ +var assert, http, path, url; + +assert = require('assert'); + +path = require('path'); + +http = require('http'); + +url = require('url'); + +describe(' tag', function() { + var fixtures, webview; + this.timeout(10000); + fixtures = path.join(__dirname, 'fixtures'); + webview = null; + beforeEach(function() { + return webview = new WebView; + }); + afterEach(function() { + if (document.body.contains(webview)) { + return document.body.removeChild(webview); + } + }); + describe('src attribute', function() { + it('specifies the page to load', function(done) { + webview.addEventListener('console-message', function(e) { + assert.equal(e.message, 'a'); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/a.html"; + return document.body.appendChild(webview); + }); + return it('navigates to new page when changed', function(done) { + var listener; + listener = function(e) { + webview.src = "file://" + fixtures + "/pages/b.html"; + webview.addEventListener('console-message', function(e) { + assert.equal(e.message, 'b'); + return done(); + }); + return webview.removeEventListener('did-finish-load', listener); + }; + webview.addEventListener('did-finish-load', listener); + webview.src = "file://" + fixtures + "/pages/a.html"; + return document.body.appendChild(webview); + }); + }); + describe('nodeintegration attribute', function() { + it('inserts no node symbols when not set', function(done) { + webview.addEventListener('console-message', function(e) { + assert.equal(e.message, 'undefined undefined undefined undefined'); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/c.html"; + return document.body.appendChild(webview); + }); + it('inserts node symbols when set', function(done) { + webview.addEventListener('console-message', function(e) { + assert.equal(e.message, 'function object object'); + return done(); + }); + webview.setAttribute('nodeintegration', 'on'); + webview.src = "file://" + fixtures + "/pages/d.html"; + return document.body.appendChild(webview); + }); + it('loads node symbols after POST navigation when set', function(done) { + webview.addEventListener('console-message', function(e) { + assert.equal(e.message, 'function object object'); + return done(); + }); + webview.setAttribute('nodeintegration', 'on'); + webview.src = "file://" + fixtures + "/pages/post.html"; + return document.body.appendChild(webview); + }); + if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { + return it('loads native modules when navigation happens', function(done) { + var listener; + listener = function(e) { + var listener2; + webview.removeEventListener('did-finish-load', listener); + listener2 = function(e) { + assert.equal(e.message, 'function'); + return done(); + }; + webview.addEventListener('console-message', listener2); + return webview.reload(); + }; + webview.addEventListener('did-finish-load', listener); + webview.setAttribute('nodeintegration', 'on'); + webview.src = "file://" + fixtures + "/pages/native-module.html"; + return document.body.appendChild(webview); + }); + } + }); + describe('preload attribute', function() { + it('loads the script before other scripts in window', function(done) { + var listener; + listener = function(e) { + assert.equal(e.message, 'function object object'); + webview.removeEventListener('console-message', listener); + return done(); + }; + webview.addEventListener('console-message', listener); + webview.setAttribute('preload', fixtures + "/module/preload.js"); + webview.src = "file://" + fixtures + "/pages/e.html"; + return document.body.appendChild(webview); + }); + it('preload script can still use "process" in required modules when nodeintegration is off', function(done) { + webview.addEventListener('console-message', function(e) { + assert.equal(e.message, 'object undefined object'); + return done(); + }); + webview.setAttribute('preload', fixtures + "/module/preload-node-off.js"); + webview.src = "file://" + fixtures + "/api/blank.html"; + return document.body.appendChild(webview); + }); + return it('receives ipc message in preload script', function(done) { + var listener, listener2, message; + message = 'boom!'; + listener = function(e) { + assert.equal(e.channel, 'pong'); + assert.deepEqual(e.args, [message]); + webview.removeEventListener('ipc-message', listener); + return done(); + }; + listener2 = function(e) { + webview.send('ping', message); + return webview.removeEventListener('did-finish-load', listener2); + }; + webview.addEventListener('ipc-message', listener); + webview.addEventListener('did-finish-load', listener2); + webview.setAttribute('preload', fixtures + "/module/preload-ipc.js"); + webview.src = "file://" + fixtures + "/pages/e.html"; + return document.body.appendChild(webview); + }); + }); + describe('httpreferrer attribute', function() { + return it('sets the referrer url', function(done) { + var listener, referrer; + referrer = 'http://github.com/'; + listener = function(e) { + assert.equal(e.message, referrer); + webview.removeEventListener('console-message', listener); + return done(); + }; + webview.addEventListener('console-message', listener); + webview.setAttribute('httpreferrer', referrer); + webview.src = "file://" + fixtures + "/pages/referrer.html"; + return document.body.appendChild(webview); + }); + }); + describe('useragent attribute', function() { + return it('sets the user agent', function(done) { + var listener, referrer; + referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko'; + listener = function(e) { + assert.equal(e.message, referrer); + webview.removeEventListener('console-message', listener); + return done(); + }; + webview.addEventListener('console-message', listener); + webview.setAttribute('useragent', referrer); + webview.src = "file://" + fixtures + "/pages/useragent.html"; + return document.body.appendChild(webview); + }); + }); + describe('disablewebsecurity attribute', function() { + it('does not disable web security when not set', function(done) { + var encoded, listener, src; + src = " "; + encoded = btoa(unescape(encodeURIComponent(src))); + listener = function(e) { + assert(/Not allowed to load local resource/.test(e.message)); + webview.removeEventListener('console-message', listener); + return done(); + }; + webview.addEventListener('console-message', listener); + webview.src = "data:text/html;base64," + encoded; + return document.body.appendChild(webview); + }); + return it('disables web security when set', function(done) { + var encoded, listener, src; + src = " "; + encoded = btoa(unescape(encodeURIComponent(src))); + listener = function(e) { + assert.equal(e.message, 'ok'); + webview.removeEventListener('console-message', listener); + return done(); + }; + webview.addEventListener('console-message', listener); + webview.setAttribute('disablewebsecurity', ''); + webview.src = "data:text/html;base64," + encoded; + return document.body.appendChild(webview); + }); + }); + describe('partition attribute', function() { + it('inserts no node symbols when not set', function(done) { + webview.addEventListener('console-message', function(e) { + assert.equal(e.message, 'undefined undefined undefined undefined'); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/c.html"; + webview.partition = 'test1'; + return document.body.appendChild(webview); + }); + it('inserts node symbols when set', function(done) { + webview.addEventListener('console-message', function(e) { + assert.equal(e.message, 'function object object'); + return done(); + }); + webview.setAttribute('nodeintegration', 'on'); + webview.src = "file://" + fixtures + "/pages/d.html"; + webview.partition = 'test2'; + return document.body.appendChild(webview); + }); + it('isolates storage for different id', function(done) { + var listener; + listener = function(e) { + assert.equal(e.message, " 0"); + webview.removeEventListener('console-message', listener); + return done(); + }; + window.localStorage.setItem('test', 'one'); + webview.addEventListener('console-message', listener); + webview.src = "file://" + fixtures + "/pages/partition/one.html"; + webview.partition = 'test3'; + return document.body.appendChild(webview); + }); + return it('uses current session storage when no id is provided', function(done) { + var listener; + listener = function(e) { + assert.equal(e.message, "one 1"); + webview.removeEventListener('console-message', listener); + return done(); + }; + window.localStorage.setItem('test', 'one'); + webview.addEventListener('console-message', listener); + webview.src = "file://" + fixtures + "/pages/partition/one.html"; + return document.body.appendChild(webview); + }); + }); + describe('allowpopups attribute', function() { + it('can not open new window when not set', function(done) { + var listener; + listener = function(e) { + assert.equal(e.message, 'null'); + webview.removeEventListener('console-message', listener); + return done(); + }; + webview.addEventListener('console-message', listener); + webview.src = "file://" + fixtures + "/pages/window-open-hide.html"; + return document.body.appendChild(webview); + }); + return it('can open new window when set', function(done) { + var listener; + listener = function(e) { + assert.equal(e.message, 'window'); + webview.removeEventListener('console-message', listener); + return done(); + }; + webview.addEventListener('console-message', listener); + webview.setAttribute('allowpopups', 'on'); + webview.src = "file://" + fixtures + "/pages/window-open-hide.html"; + return document.body.appendChild(webview); + }); + }); + describe('new-window event', function() { + it('emits when window.open is called', function(done) { + webview.addEventListener('new-window', function(e) { + assert.equal(e.url, 'http://host/'); + assert.equal(e.frameName, 'host'); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/window-open.html"; + return document.body.appendChild(webview); + }); + return it('emits when link with target is called', function(done) { + webview.addEventListener('new-window', function(e) { + assert.equal(e.url, 'http://host/'); + assert.equal(e.frameName, 'target'); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/target-name.html"; + return document.body.appendChild(webview); + }); + }); + describe('ipc-message event', function() { + return it('emits when guest sends a ipc message to browser', function(done) { + webview.addEventListener('ipc-message', function(e) { + assert.equal(e.channel, 'channel'); + assert.deepEqual(e.args, ['arg1', 'arg2']); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/ipc-message.html"; + webview.setAttribute('nodeintegration', 'on'); + return document.body.appendChild(webview); + }); + }); + describe('page-title-set event', function() { + return it('emits when title is set', function(done) { + webview.addEventListener('page-title-set', function(e) { + assert.equal(e.title, 'test'); + assert(e.explicitSet); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/a.html"; + return document.body.appendChild(webview); + }); + }); + describe('page-favicon-updated event', function() { + return it('emits when favicon urls are received', function(done) { + webview.addEventListener('page-favicon-updated', function(e) { + var pageUrl; + assert.equal(e.favicons.length, 2); + pageUrl = process.platform === 'win32' ? 'file:///C:/favicon.png' : 'file:///favicon.png'; + assert.equal(e.favicons[0], pageUrl); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/a.html"; + return document.body.appendChild(webview); + }); + }); + describe('will-navigate event', function() { + return it('emits when a url that leads to oustide of the page is clicked', function(done) { + webview.addEventListener('will-navigate', function(e) { + assert.equal(e.url, "http://host/"); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/webview-will-navigate.html"; + return document.body.appendChild(webview); + }); + }); + describe('did-navigate event', function() { + var p, pageUrl; + p = path.join(fixtures, 'pages', 'webview-will-navigate.html'); + p = p.replace(/\\/g, '/'); + pageUrl = url.format({ + protocol: 'file', + slashes: true, + pathname: p + }); + return it('emits when a url that leads to outside of the page is clicked', function(done) { + webview.addEventListener('did-navigate', function(e) { + assert.equal(e.url, pageUrl); + return done(); + }); + webview.src = pageUrl; + return document.body.appendChild(webview); + }); + }); + describe('did-navigate-in-page event', function() { + it('emits when an anchor link is clicked', function(done) { + var p, pageUrl; + p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html'); + p = p.replace(/\\/g, '/'); + pageUrl = url.format({ + protocol: 'file', + slashes: true, + pathname: p + }); + webview.addEventListener('did-navigate-in-page', function(e) { + assert.equal(e.url, pageUrl + "#test_content"); + return done(); + }); + webview.src = pageUrl; + return document.body.appendChild(webview); + }); + it('emits when window.history.replaceState is called', function(done) { + webview.addEventListener('did-navigate-in-page', function(e) { + assert.equal(e.url, "http://host/"); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/webview-did-navigate-in-page-with-history.html"; + return document.body.appendChild(webview); + }); + return it('emits when window.location.hash is changed', function(done) { + var p, pageUrl; + p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html'); + p = p.replace(/\\/g, '/'); + pageUrl = url.format({ + protocol: 'file', + slashes: true, + pathname: p + }); + webview.addEventListener('did-navigate-in-page', function(e) { + assert.equal(e.url, pageUrl + "#test"); + return done(); + }); + webview.src = pageUrl; + return document.body.appendChild(webview); + }); + }); + describe('close event', function() { + return it('should fire when interior page calls window.close', function(done) { + webview.addEventListener('close', function() { + return done(); + }); + webview.src = "file://" + fixtures + "/pages/close.html"; + return document.body.appendChild(webview); + }); + }); + describe('devtools-opened event', function() { + return it('should fire when webview.openDevTools() is called', function(done) { + var listener; + listener = function() { + webview.removeEventListener('devtools-opened', listener); + webview.closeDevTools(); + return done(); + }; + webview.addEventListener('devtools-opened', listener); + webview.addEventListener('dom-ready', function() { + return webview.openDevTools(); + }); + webview.src = "file://" + fixtures + "/pages/base-page.html"; + return document.body.appendChild(webview); + }); + }); + describe('devtools-closed event', function() { + return it('should fire when webview.closeDevTools() is called', function(done) { + var listener, listener2; + listener2 = function() { + webview.removeEventListener('devtools-closed', listener2); + return done(); + }; + listener = function() { + webview.removeEventListener('devtools-opened', listener); + return webview.closeDevTools(); + }; + webview.addEventListener('devtools-opened', listener); + webview.addEventListener('devtools-closed', listener2); + webview.addEventListener('dom-ready', function() { + return webview.openDevTools(); + }); + webview.src = "file://" + fixtures + "/pages/base-page.html"; + return document.body.appendChild(webview); + }); + }); + describe('devtools-focused event', function() { + return it('should fire when webview.openDevTools() is called', function(done) { + var listener; + listener = function() { + webview.removeEventListener('devtools-focused', listener); + webview.closeDevTools(); + return done(); + }; + webview.addEventListener('devtools-focused', listener); + webview.addEventListener('dom-ready', function() { + return webview.openDevTools(); + }); + webview.src = "file://" + fixtures + "/pages/base-page.html"; + return document.body.appendChild(webview); + }); + }); + describe('.reload()', function() { + return it('should emit beforeunload handler', function(done) { + var listener, listener2; + listener = function(e) { + assert.equal(e.channel, 'onbeforeunload'); + webview.removeEventListener('ipc-message', listener); + return done(); + }; + listener2 = function(e) { + webview.reload(); + return webview.removeEventListener('did-finish-load', listener2); + }; + webview.addEventListener('ipc-message', listener); + webview.addEventListener('did-finish-load', listener2); + webview.setAttribute('nodeintegration', 'on'); + webview.src = "file://" + fixtures + "/pages/beforeunload-false.html"; + return document.body.appendChild(webview); + }); + }); + describe('.clearHistory()', function() { + return it('should clear the navigation history', function(done) { + var listener; + listener = function(e) { + assert.equal(e.channel, 'history'); + assert.equal(e.args[0], 2); + assert(webview.canGoBack()); + webview.clearHistory(); + assert(!webview.canGoBack()); + webview.removeEventListener('ipc-message', listener); + return done(); + }; + webview.addEventListener('ipc-message', listener); + webview.setAttribute('nodeintegration', 'on'); + webview.src = "file://" + fixtures + "/pages/history.html"; + return document.body.appendChild(webview); + }); + }); + describe('basic auth', function() { + var auth; + auth = require('basic-auth'); + return it('should authenticate with correct credentials', function(done) { + var message, server; + message = 'Authenticated'; + server = http.createServer(function(req, res) { + var credentials; + credentials = auth(req); + if (credentials.name === 'test' && credentials.pass === 'test') { + res.end(message); + } else { + res.end('failed'); + } + return server.close(); + }); + return server.listen(0, '127.0.0.1', function() { + var port; + port = server.address().port; + webview.addEventListener('ipc-message', function(e) { + assert.equal(e.channel, message); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/basic-auth.html?port=" + port; + webview.setAttribute('nodeintegration', 'on'); + return document.body.appendChild(webview); + }); + }); + }); + describe('dom-ready event', function() { + return it('emits when document is loaded', function(done) { + var server; + server = http.createServer(function(req) {}); + return server.listen(0, '127.0.0.1', function() { + var port; + port = server.address().port; + webview.addEventListener('dom-ready', function() { + return done(); + }); + webview.src = "file://" + fixtures + "/pages/dom-ready.html?port=" + port; + return document.body.appendChild(webview); + }); + }); + }); + describe('executeJavaScript', function() { + if (process.env.TRAVIS !== 'true') { + return; + } + return it('should support user gesture', function(done) { + var listener, listener2; + listener = function(e) { + webview.removeEventListener('enter-html-full-screen', listener); + return done(); + }; + listener2 = function(e) { + var jsScript; + jsScript = 'document.getElementsByTagName("video")[0].webkitRequestFullScreen()'; + webview.executeJavaScript(jsScript, true); + return webview.removeEventListener('did-finish-load', listener2); + }; + webview.addEventListener('enter-html-full-screen', listener); + webview.addEventListener('did-finish-load', listener2); + webview.src = "file://" + fixtures + "/pages/fullscreen.html"; + return document.body.appendChild(webview); + }); + }); + describe('sendInputEvent', function() { + it('can send keyboard event', function(done) { + webview.addEventListener('ipc-message', function(e) { + assert.equal(e.channel, 'keyup'); + assert.deepEqual(e.args, [67, true, false]); + return done(); + }); + webview.addEventListener('dom-ready', function() { + return webview.sendInputEvent({ + type: 'keyup', + keyCode: 'c', + modifiers: ['shift'] + }); + }); + webview.src = "file://" + fixtures + "/pages/onkeyup.html"; + webview.setAttribute('nodeintegration', 'on'); + return document.body.appendChild(webview); + }); + return it('can send mouse event', function(done) { + webview.addEventListener('ipc-message', function(e) { + assert.equal(e.channel, 'mouseup'); + assert.deepEqual(e.args, [10, 20, false, true]); + return done(); + }); + webview.addEventListener('dom-ready', function() { + return webview.sendInputEvent({ + type: 'mouseup', + modifiers: ['ctrl'], + x: 10, + y: 20 + }); + }); + webview.src = "file://" + fixtures + "/pages/onmouseup.html"; + webview.setAttribute('nodeintegration', 'on'); + return document.body.appendChild(webview); + }); + }); + describe('media-started-playing media-paused events', function() { + return it('emits when audio starts and stops playing', function(done) { + var audioPlayed; + audioPlayed = false; + webview.addEventListener('media-started-playing', function() { + return audioPlayed = true; + }); + webview.addEventListener('media-paused', function() { + assert(audioPlayed); + return done(); + }); + webview.src = "file://" + fixtures + "/pages/audio.html"; + return document.body.appendChild(webview); + }); + }); + describe('found-in-page event', function() { + return it('emits when a request is made', function(done) { + var listener, listener2, requestId; + requestId = null; + listener = function(e) { + assert.equal(e.result.requestId, requestId); + if (e.result.finalUpdate) { + assert.equal(e.result.matches, 3); + webview.stopFindInPage("clearSelection"); + return done(); + } + }; + listener2 = function(e) { + return requestId = webview.findInPage("virtual"); + }; + webview.addEventListener('found-in-page', listener); + webview.addEventListener('did-finish-load', listener2); + webview.src = "file://" + fixtures + "/pages/content.html"; + return document.body.appendChild(webview); + }); + }); + return xdescribe('did-change-theme-color event', function() { + return it('emits when theme color changes', function(done) { + webview.addEventListener('did-change-theme-color', function(e) { + return done(); + }); + webview.src = "file://" + fixtures + "/pages/theme-color.html"; + return document.body.appendChild(webview); + }); + }); +}); diff --git a/tools/compile-coffee.py b/tools/compile-coffee.py deleted file mode 100755 index d7423428f489..000000000000 --- a/tools/compile-coffee.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python - -import os -import subprocess -import sys - - -SOURCE_ROOT = os.path.dirname(os.path.dirname(__file__)) -WINDOWS_NODE_PATHs = os.environ['PATH'].split(os.pathsep) + [ - 'C:/Program Files (x86)/nodejs', - 'C:/Program Files/nodejs', -] -NIX_NODE_PATHs = os.environ['PATH'].split(os.pathsep) + [ - '/usr/local/bin', - '/usr/bin', - '/bin', -] - -def main(): - input_file = sys.argv[1] - output_dir = os.path.dirname(sys.argv[2]) - - coffee = os.path.join(SOURCE_ROOT, 'node_modules', 'coffee-script', 'bin', - 'coffee') - - node = 'node' - if sys.platform in ['win32', 'cygwin']: - node = find_node(WINDOWS_NODE_PATHs, 'node.exe') - else: - node = find_node(NIX_NODE_PATHs, 'node') - - if not node: - print 'Node.js not found in PATH for building electron' - return 1 - - subprocess.check_call(['node', coffee, '-c', '-o', output_dir, input_file], - executable=node) - - -def find_node(paths, target): - for path in paths: - full_path = os.path.join(path, target) - if os.path.exists(full_path): - return full_path - return None - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/tools/coffee2asar.py b/tools/js2asar.py similarity index 68% rename from tools/coffee2asar.py rename to tools/js2asar.py index 9581715f0070..a17212419e1d 100755 --- a/tools/coffee2asar.py +++ b/tools/js2asar.py @@ -1,36 +1,31 @@ #!/usr/bin/env python +import errno import os import shutil import subprocess import sys import tempfile - SOURCE_ROOT = os.path.dirname(os.path.dirname(__file__)) def main(): archive = sys.argv[1] - coffee_source_files = sys.argv[2:] + js_source_files = sys.argv[2:] output_dir = tempfile.mkdtemp() - compile_coffee(coffee_source_files, output_dir) + copy_js(js_source_files, output_dir) call_asar(archive, output_dir) shutil.rmtree(output_dir) -def compile_coffee(coffee_source_files, output_dir): - for source_file in coffee_source_files: +def copy_js(js_source_files, output_dir): + for source_file in js_source_files: output_filename = os.path.splitext(source_file)[0] + '.js' output_path = os.path.join(output_dir, output_filename) - call_compile_coffee(source_file, output_path) - - -def call_compile_coffee(source_file, output_filename): - compile_coffee = os.path.join(SOURCE_ROOT, 'tools', 'compile-coffee.py') - subprocess.check_call([sys.executable, compile_coffee, source_file, - output_filename]) + safe_mkdir(os.path.dirname(output_path)) + shutil.copy2(source_file, output_path) def call_asar(archive, output_dir): @@ -52,5 +47,14 @@ def find_node(): return full_path return 'node' + +def safe_mkdir(path): + try: + os.makedirs(path) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + if __name__ == '__main__': sys.exit(main()) diff --git a/tools/coffee2c.py b/tools/js2c.py similarity index 50% rename from tools/coffee2c.py rename to tools/js2c.py index 1ca9a19334c6..394aa557e522 100755 --- a/tools/coffee2c.py +++ b/tools/js2c.py @@ -11,29 +11,11 @@ SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def main(): natives = os.path.abspath(sys.argv[1]) - coffee_source_files = sys.argv[2:] + js_source_files = sys.argv[2:] - output_dir = os.path.dirname(natives) - js_source_files = compile_coffee(coffee_source_files, output_dir) call_js2c(natives, js_source_files) -def compile_coffee(coffee_source_files, output_dir): - js_source_files = [] - for source_file in coffee_source_files: - output_filename = os.path.splitext(source_file)[0] + '.js' - output_path = os.path.join(output_dir, output_filename) - js_source_files.append(output_path) - call_compile_coffee(source_file, output_path) - return js_source_files - - -def call_compile_coffee(source_file, output_filename): - compile_coffee = os.path.join(SOURCE_ROOT, 'tools', 'compile-coffee.py') - subprocess.check_call([sys.executable, compile_coffee, source_file, - output_filename]) - - def call_js2c(natives, js_source_files): js2c = os.path.join(SOURCE_ROOT, 'vendor', 'node', 'tools', 'js2c.py') src_dir = os.path.dirname(js_source_files[0]) diff --git a/vendor/brightray b/vendor/brightray index 8550f2a032b3..8b6c24dce25e 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 8550f2a032b332d86bd8a7ec235685e22d028906 +Subproject commit 8b6c24dce25e743ceecd06d1c175c1cf8b91627b