From 3a5caa77be3d24e5aa71eecb68b506c0838a1390 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 24 Aug 2015 14:25:38 +0800 Subject: [PATCH 001/404] menu popup ported --- src/api/base/base.cc | 3 ++- src/api/base/base.h | 7 +++++- src/api/menu/menu.cc | 6 ++--- src/api/menu/menu.h | 13 +++++++--- src/api/menu/menu_views.cc | 32 ++++++++++++++++++------ src/api/menuitem/menuitem.cc | 3 ++- src/api/menuitem/menuitem.h | 3 ++- src/api/menuitem/menuitem_views.cc | 2 ++ src/api/nw_object_api.cc | 4 +-- src/api/nw_shell_api.cc | 6 ++--- src/api/object_manager.cc | 6 ++++- src/api/object_manager.h | 13 +++++++--- src/nw_custom_bindings.cc | 39 ++++++++++++++++++++++++++++++ src/nw_custom_bindings.h | 1 + src/resources/api_nw_menu.js | 17 ++++++++++--- 15 files changed, 124 insertions(+), 31 deletions(-) diff --git a/src/api/base/base.cc b/src/api/base/base.cc index e14f7834d2..ce748ce8bb 100644 --- a/src/api/base/base.cc +++ b/src/api/base/base.cc @@ -38,7 +38,8 @@ Base::~Base() { } -void Base::Call(const std::string& method, const base::ListValue& arguments) { +void Base::Call(const std::string& method, const base::ListValue& arguments, + content::RenderViewHost* rvh) { NOTREACHED() << "Uncatched call in Base" << " method:" << method << " arguments:" << arguments; diff --git a/src/api/base/base.h b/src/api/base/base.h index eb1ae1c7d6..0c54a7c031 100644 --- a/src/api/base/base.h +++ b/src/api/base/base.h @@ -31,6 +31,10 @@ class DictionaryValue; class ListValue; } +namespace content { +class RenderViewHost; +} + namespace nw { class ObjectManager; @@ -44,7 +48,8 @@ class Base { virtual ~Base(); virtual void Call(const std::string& method, - const base::ListValue& arguments); + const base::ListValue& arguments, + content::RenderViewHost* rvh = nullptr); virtual void CallSync(const std::string& method, const base::ListValue& arguments, base::ListValue* result); diff --git a/src/api/menu/menu.cc b/src/api/menu/menu.cc index d46498275c..fa9c975e27 100644 --- a/src/api/menu/menu.cc +++ b/src/api/menu/menu.cc @@ -39,7 +39,8 @@ Menu::~Menu() { } void Menu::Call(const std::string& method, - const base::ListValue& arguments) { + const base::ListValue& arguments, + content::RenderViewHost* rvh) { if (method == "Append") { int object_id = 0; arguments.GetInteger(0, &object_id); @@ -61,8 +62,7 @@ void Menu::Call(const std::string& method, arguments.GetInteger(0, &x); int y = 0; arguments.GetInteger(1, &y); - // Popup(x, y, content::Shell::FromRenderViewHost( - // object_manager()->render_view_host())); + Popup(x, y, rvh); } else if (method == "EnableShowEvent") { arguments.GetBoolean(0, &enable_show_event_); } else { diff --git a/src/api/menu/menu.h b/src/api/menu/menu.h index d3d9a100c1..954404022f 100644 --- a/src/api/menu/menu.h +++ b/src/api/menu/menu.h @@ -78,8 +78,13 @@ class NwMenuModel : public SimpleMenuModel { #endif +namespace aura { +class Window; +} + namespace content { -class Shell; +class RenderViewHost; +class RenderFrameHost; } namespace nw { @@ -95,11 +100,13 @@ class Menu : public Base { ~Menu() override; void Call(const std::string& method, - const base::ListValue& arguments) override; + const base::ListValue& arguments, + content::RenderViewHost* rvh = nullptr) override; #if defined(OS_WIN) || defined(OS_LINUX) void UpdateKeys(views::FocusManager *focus_manager); ui::NwMenuModel* model() { return menu_model_.get(); } + aura::Window* GetActiveNativeView(content::RenderFrameHost* rfh); #endif bool enable_show_event() { return enable_show_event_; } @@ -111,7 +118,7 @@ class Menu : public Base { void Append(MenuItem* menu_item); void Insert(MenuItem* menu_item, int pos); void Remove(MenuItem* menu_item, int pos); - //void Popup(int x, int y, content::Shell*); + void Popup(int x, int y, content::RenderViewHost*); #if defined(OS_LINUX) std::vector menu_items; diff --git a/src/api/menu/menu_views.cc b/src/api/menu/menu_views.cc index 335a9c0e8d..6b3c9f595d 100644 --- a/src/api/menu/menu_views.cc +++ b/src/api/menu/menu_views.cc @@ -24,6 +24,9 @@ #include "base/strings/utf_string_conversions.h" #include "content/nw/src/api/object_manager.h" #include "content/nw/src/api/menuitem/menuitem.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/app_window/app_window.h" #include "skia/ext/image_operations.h" @@ -157,8 +160,7 @@ void Menu::Remove(MenuItem* menu_item, int pos) { menu_item->menu_ = NULL; } -#if 0 //FIXME -void Menu::Popup(int x, int y, content::Shell* shell) { +void Menu::Popup(int x, int y, content::RenderViewHost* rvh) { // Rebuild(); // Map point from document to screen. @@ -166,18 +168,19 @@ void Menu::Popup(int x, int y, content::Shell* shell) { // Convert from content coordinates to window coordinates. // This code copied from chrome_web_contents_view_delegate_views.cc - aura::Window* web_contents_window = - shell->web_contents()->GetNativeView(); - aura::Window* root_window = web_contents_window->GetRootWindow(); + aura::Window* target_window = GetActiveNativeView(rvh->GetMainFrame()); + aura::Window* root_window = target_window->GetRootWindow(); + views::Widget* top_level_widget = + views::Widget::GetTopLevelWidgetForNativeView(target_window); aura::client::ScreenPositionClient* screen_position_client = aura::client::GetScreenPositionClient(root_window); if (screen_position_client) { - screen_position_client->ConvertPointToScreen(web_contents_window, + screen_position_client->ConvertPointToScreen(target_window, &screen_point); } views::MenuRunner runner(menu_model_.get(), views::MenuRunner::CONTEXT_MENU); if (views::MenuRunner::MENU_DELETED == - runner.RunMenuAt(static_cast(shell->window())->window(), + runner.RunMenuAt(top_level_widget, NULL, gfx::Rect(screen_point, gfx::Size()), views::MENU_ANCHOR_TOPRIGHT, @@ -185,7 +188,7 @@ void Menu::Popup(int x, int y, content::Shell* shell) { return; // menu_->RunMenuAt(screen_point, views::Menu2::ALIGN_TOPLEFT); } -#endif + #if defined(OS_WIN) void Menu::Rebuild(const HMENU *parent_menu) { if (is_menu_modified_) { @@ -269,4 +272,17 @@ void Menu::SetWindow(extensions::AppWindow* win) { } #endif +aura::Window* Menu::GetActiveNativeView(content::RenderFrameHost* rfh) { + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(rfh); + if (!web_contents) { + LOG(ERROR) << "Menu: couldn't find WebContents"; + return NULL; + } + return web_contents->GetFullscreenRenderWidgetHostView() + ? web_contents->GetFullscreenRenderWidgetHostView() + ->GetNativeView() + : web_contents->GetNativeView(); +} + } // namespace nw diff --git a/src/api/menuitem/menuitem.cc b/src/api/menuitem/menuitem.cc index 4cdb269929..183084824c 100644 --- a/src/api/menuitem/menuitem.cc +++ b/src/api/menuitem/menuitem.cc @@ -42,7 +42,8 @@ MenuItem::~MenuItem() { } void MenuItem::Call(const std::string& method, - const base::ListValue& arguments) { + const base::ListValue& arguments, + content::RenderViewHost* rvh) { if (method == "SetLabel") { std::string label; arguments.GetString(0, &label); diff --git a/src/api/menuitem/menuitem.h b/src/api/menuitem/menuitem.h index 2c4a83c1d1..5518d06d31 100644 --- a/src/api/menuitem/menuitem.h +++ b/src/api/menuitem/menuitem.h @@ -59,7 +59,8 @@ class MenuItem : public Base { ~MenuItem() override; void Call(const std::string& method, - const base::ListValue& arguments) override; + const base::ListValue& arguments, + content::RenderViewHost* rvh = nullptr) override; #if defined(OS_WIN) || defined(OS_LINUX) bool AcceleratorPressed(const ui::Accelerator& accelerator) override; diff --git a/src/api/menuitem/menuitem_views.cc b/src/api/menuitem/menuitem_views.cc index 99ff9d6f0c..b1bf23e972 100644 --- a/src/api/menuitem/menuitem_views.cc +++ b/src/api/menuitem/menuitem_views.cc @@ -22,6 +22,7 @@ #include "base/files/file_path.h" #include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" #include "base/values.h" #include "content/nw/src/api/object_manager.h" #include "content/nw/src/api/menu/menu.h" @@ -123,6 +124,7 @@ void MenuItem::SetLabel(const std::string& label) { } void MenuItem::SetIcon(const std::string& icon) { + base::ThreadRestrictions::ScopedAllowIO allow_io; is_modified_ = true; if (icon.empty()) { icon_ = gfx::Image(); diff --git a/src/api/nw_object_api.cc b/src/api/nw_object_api.cc index 603f1e785d..23fdc102bc 100644 --- a/src/api/nw_object_api.cc +++ b/src/api/nw_object_api.cc @@ -62,7 +62,7 @@ bool NwObjectCallObjectMethodFunction::RunNWSync(base::ListValue* response, std: EXTENSION_FUNCTION_VALIDATE(args_->GetList(3, &arguments)); nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallObjectMethod(id, type, method, *arguments); + manager->OnCallObjectMethod(render_view_host(), id, type, method, *arguments); return true; } @@ -82,7 +82,7 @@ bool NwObjectCallObjectMethodSyncFunction::RunNWSync(base::ListValue* response, EXTENSION_FUNCTION_VALIDATE(args_->GetList(3, &arguments)); nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallObjectMethodSync(id, type, method, *arguments, response); + manager->OnCallObjectMethodSync(render_view_host(), id, type, method, *arguments, response); return true; } diff --git a/src/api/nw_shell_api.cc b/src/api/nw_shell_api.cc index b0026263c1..3e536ee37b 100644 --- a/src/api/nw_shell_api.cc +++ b/src/api/nw_shell_api.cc @@ -19,7 +19,7 @@ NwShellOpenItemFunction::~NwShellOpenItemFunction() { bool NwShellOpenItemFunction::RunNWSync(base::ListValue* response, std::string* error) { nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallStaticMethod("Shell", "OpenItem", *args_); + manager->OnCallStaticMethod(render_view_host(), "Shell", "OpenItem", *args_); return true; } @@ -31,7 +31,7 @@ NwShellOpenExternalFunction::~NwShellOpenExternalFunction() { bool NwShellOpenExternalFunction::RunNWSync(base::ListValue* response, std::string* error) { nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallStaticMethod("Shell", "OpenExternal", *args_); + manager->OnCallStaticMethod(render_view_host(), "Shell", "OpenExternal", *args_); return true; } @@ -43,7 +43,7 @@ NwShellShowItemInFolderFunction::~NwShellShowItemInFolderFunction() { bool NwShellShowItemInFolderFunction::RunNWSync(base::ListValue* response, std::string* error) { nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallStaticMethod("Shell", "ShowItemInFolder", *args_); + manager->OnCallStaticMethod(render_view_host(), "Shell", "ShowItemInFolder", *args_); return true; } diff --git a/src/api/object_manager.cc b/src/api/object_manager.cc index d2f25800b0..f7c8a1283d 100644 --- a/src/api/object_manager.cc +++ b/src/api/object_manager.cc @@ -126,6 +126,7 @@ void ObjectManager::OnDeallocateObject(int object_id) { } void ObjectManager::OnCallObjectMethod( + content::RenderViewHost* rvh, int object_id, const std::string& type, const std::string& method, @@ -137,7 +138,7 @@ void ObjectManager::OnCallObjectMethod( Base* object = GetApiObject(object_id); if (object) - object->Call(method, arguments); + object->Call(method, arguments, rvh); else DLOG(WARNING) << "Unknown object: " << object_id << " type:" << type @@ -146,6 +147,7 @@ void ObjectManager::OnCallObjectMethod( } void ObjectManager::OnCallObjectMethodSync( + content::RenderViewHost* rvh, int object_id, const std::string& type, const std::string& method, @@ -167,6 +169,7 @@ void ObjectManager::OnCallObjectMethodSync( } void ObjectManager::OnCallStaticMethod( + content::RenderViewHost* rvh, const std::string& type, const std::string& method, const base::ListValue& arguments) { @@ -188,6 +191,7 @@ void ObjectManager::OnCallStaticMethod( } void ObjectManager::OnCallStaticMethodSync( + content::RenderViewHost* rvh, const std::string& type, const std::string& method, const base::ListValue& arguments, diff --git a/src/api/object_manager.h b/src/api/object_manager.h index a8a347863d..26d1a67959 100644 --- a/src/api/object_manager.h +++ b/src/api/object_manager.h @@ -42,6 +42,7 @@ class WebFrame; namespace content { class BrowserContext; +class RenderViewHost; } namespace nw { @@ -80,19 +81,23 @@ class ObjectManager : public KeyedService { const base::DictionaryValue& option, const std::string& extension_id); void OnDeallocateObject(int object_id); - void OnCallObjectMethod(int object_id, + void OnCallObjectMethod(content::RenderViewHost* rvh, + int object_id, const std::string& type, const std::string& method, const base::ListValue& arguments); - void OnCallObjectMethodSync(int object_id, + void OnCallObjectMethodSync(content::RenderViewHost* rvh, + int object_id, const std::string& type, const std::string& method, const base::ListValue& arguments, base::ListValue* result); - void OnCallStaticMethod(const std::string& type, + void OnCallStaticMethod(content::RenderViewHost* rvh, + const std::string& type, const std::string& method, const base::ListValue& arguments); - void OnCallStaticMethodSync(const std::string& type, + void OnCallStaticMethodSync(content::RenderViewHost* rvh, + const std::string& type, const std::string& method, const base::ListValue& arguments, base::ListValue* result); diff --git a/src/nw_custom_bindings.cc b/src/nw_custom_bindings.cc index ebcb4e7306..2f296278bf 100644 --- a/src/nw_custom_bindings.cc +++ b/src/nw_custom_bindings.cc @@ -45,6 +45,30 @@ using blink::WebFrame; namespace extensions { +namespace { +bool MakePathAbsolute(base::FilePath* file_path) { + DCHECK(file_path); + + base::FilePath current_directory; + if (!base::GetCurrentDirectory(¤t_directory)) + return false; + + if (file_path->IsAbsolute()) + return true; + + if (current_directory.empty()) { + *file_path = base::MakeAbsoluteFilePath(*file_path); + return true; + } + + if (!current_directory.IsAbsolute()) + return false; + + *file_path = current_directory.Append(*file_path); + return true; +} + +} // namespace NWCustomBindings::NWCustomBindings(ScriptContext* context) : ObjectBackedNativeHandler(context) { RouteFunction("crashRenderer", @@ -56,6 +80,9 @@ NWCustomBindings::NWCustomBindings(ScriptContext* context) RouteFunction("evalNWBin", base::Bind(&NWCustomBindings::EvalScript, base::Unretained(this))); + RouteFunction("getAbsolutePath", + base::Bind(&NWCustomBindings::GetAbsolutePath, + base::Unretained(this))); } void NWCustomBindings::CrashRenderer( @@ -141,4 +168,16 @@ void NWCustomBindings::EvalNWBin( return; } +void NWCustomBindings::GetAbsolutePath( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + base::FilePath path = base::FilePath::FromUTF8Unsafe(*v8::String::Utf8Value(args[0])); + MakePathAbsolute(&path); +#if defined(OS_POSIX) + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, path.value().c_str())); +#else + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, path.AsUTF8Unsafe().c_str())); +#endif +} + } // namespace extensions diff --git a/src/nw_custom_bindings.h b/src/nw_custom_bindings.h index 3564edb202..eb924f4925 100644 --- a/src/nw_custom_bindings.h +++ b/src/nw_custom_bindings.h @@ -19,6 +19,7 @@ class NWCustomBindings : public ObjectBackedNativeHandler { void CrashRenderer(const v8::FunctionCallbackInfo& args); void EvalScript(const v8::FunctionCallbackInfo& args); void EvalNWBin(const v8::FunctionCallbackInfo& args); + void GetAbsolutePath(const v8::FunctionCallbackInfo& args); }; } // namespace extensions diff --git a/src/resources/api_nw_menu.js b/src/resources/api_nw_menu.js index 8bec8585af..e33865e47e 100644 --- a/src/resources/api_nw_menu.js +++ b/src/resources/api_nw_menu.js @@ -1,6 +1,7 @@ var Binding = require('binding').Binding; var forEach = require('utils').forEach; var nw_binding = require('binding').Binding.create('nw.Menu'); +var nwNative = requireNative('nw_natives'); var sendRequest = require('sendRequest'); var contextMenuNatives = requireNative('context_menus'); var messagingNatives = requireNative('messaging_natives'); @@ -74,7 +75,7 @@ function MenuItem(id, option) { if (option.hasOwnProperty('icon')) { option.shadowIcon = String(option.icon); - option.icon = nw.getAbsolutePath(option.icon); + option.icon = nwNative.getAbsolutePath(option.icon); } if (option.hasOwnProperty('iconIsTemplate')) @@ -123,6 +124,16 @@ function MenuItem(id, option) { option.modifiers = ""; } +MenuItem.prototype.handleGetter = function(name) { + return privates(this).option[name]; +}; + +MenuItem.prototype.handleSetter = function(name, setter, type, value) { + value = type(value); + privates(this).option[name] = value; + nw.Object.callObjectMethod(this.id, 'MenuItem', setter, [ value ]); +}; + MenuItem.prototype.__defineGetter__('type', function() { return this.handleGetter('type'); }); @@ -144,8 +155,8 @@ MenuItem.prototype.__defineGetter__('icon', function() { }); MenuItem.prototype.__defineSetter__('icon', function(val) { - prvates(this).option.shadowIcon = String(val); - var real_path = val == '' ? '' : nw.getAbsolutePath(val); //FIXME + privates(this).option.shadowIcon = String(val); + var real_path = val == '' ? '' : nwNative.getAbsolutePath(val); //FIXME this.handleSetter('icon', 'SetIcon', String, real_path); }); From 70999af8635d969c55ba2eceef0d9881d6630e4d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 24 Aug 2015 14:26:24 +0800 Subject: [PATCH 002/404] [test] expand nwdir parameter --- test/testpy/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/testpy/__init__.py b/test/testpy/__init__.py index 1d2a7343ce..9f3043d1ab 100644 --- a/test/testpy/__init__.py +++ b/test/testpy/__init__.py @@ -41,8 +41,8 @@ class SimpleTestCase(test.TestCase): - def __init__(self, path, file, arch, mode, context, config, additional=[]): - super(SimpleTestCase, self).__init__(context, path, arch, mode) + def __init__(self, path, file, arch, mode, nwdir, context, config, additional=[]): + super(SimpleTestCase, self).__init__(context, path, arch, mode, nwdir) self.file = file self.config = config self.arch = arch @@ -108,7 +108,7 @@ def GetName(self): return self.path[-1] def GetCommand(self): - result = [self.config.context.GetVm(self.arch, self.mode)] + result = [self.config.context.GetVm(self.arch, self.mode, self.nwdir)] manifest = json.loads(open(os.path.join(self.file, 'package.json')).read(), 'utf-8') if manifest.get('expect_exit_code'): self.expected_exit_code = manifest['expect_exit_code'] @@ -137,13 +137,13 @@ def SelectTest(name): return os.path.isdir(os.path.join(path, name)) return [f[0:] for f in os.listdir(path) if SelectTest(f)] - def ListTests(self, current_path, path, arch, mode): + def ListTests(self, current_path, path, arch, mode, nwdir): all_tests = [current_path + [t] for t in self.Ls(join(self.root))] result = [] for test in all_tests: if self.Contains(path, test): file_path = join(self.root, reduce(join, test[1:], "")) - result.append(SimpleTestCase(test, file_path, arch, mode, self.context, + result.append(SimpleTestCase(test, file_path, arch, mode, nwdir, self.context, self, self.additional_flags)) return result @@ -160,9 +160,9 @@ def __init__(self, context, root, section, additional=[]): super(ParallelTestConfiguration, self).__init__(context, root, section, additional) - def ListTests(self, current_path, path, arch, mode): + def ListTests(self, current_path, path, arch, mode, nwdir): result = super(ParallelTestConfiguration, self).ListTests( - current_path, path, arch, mode) + current_path, path, arch, mode, nwdir) for test in result: test.parallel = True return result @@ -183,12 +183,12 @@ def SelectTest(name): result.append([subpath, f[:-3]]) return result - def ListTests(self, current_path, path, arch, mode): + def ListTests(self, current_path, path, arch, mode, nwdir): all_tests = [current_path + t for t in self.Ls(join(self.root))] result = [] for test in all_tests: if self.Contains(path, test): file_path = join(self.root, reduce(join, test[1:], "") + ".js") result.append( - SimpleTestCase(test, file_path, arch, mode, self.context, self)) + SimpleTestCase(test, file_path, arch, mode, nwdir, self.context, self)) return result From 1314bebd889eb66cf3df7377f1989e918f81d5e3 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 24 Aug 2015 14:48:00 +0800 Subject: [PATCH 003/404] skip uploading overlapped header files and win32 libs --- tools/aws_uploader.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/aws_uploader.py b/tools/aws_uploader.py index 030b15328b..11f50c2627 100755 --- a/tools/aws_uploader.py +++ b/tools/aws_uploader.py @@ -84,8 +84,18 @@ def aws_upload(upload_path, file_list): sys.stdout.flush() # use '/' for s3 path_prefix = '' - if builder_name.startswith("win64") and (f == 'nw.lib' or f == 'nw.exp') : - path_prefix = 'x64' + if (f == 'nw.lib' or f == 'nw.exp') : + if builder_name != 'nw13_win64' and builder_name != 'nw13_win32' : + continue + if builder_name == 'nw13_win64' : + path_prefix = 'x64' + + if f.startswith('nw-headers') and builder_name != 'nw13_mac64' : + continue + + if f.startswith('chromedriver') and 'sdk' not in builder_name : + continue + key = bucket.new_key(upload_path + '/' + path_prefix + '/' + f) key.set_contents_from_filename(filename=os.path.join(dist_dir, f), cb=print_progress, num_cb=50, replace=True) From 5c1c7050db4076d01dd7270267188e17d73aecef Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 24 Aug 2015 16:14:51 +0800 Subject: [PATCH 004/404] mac menu popup --- src/api/menu/menu_mac.mm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/menu/menu_mac.mm b/src/api/menu/menu_mac.mm index 4e1cb20239..ebf0f7de59 100644 --- a/src/api/menu/menu_mac.mm +++ b/src/api/menu/menu_mac.mm @@ -25,6 +25,8 @@ #include "base/values.h" #import #include "content/public/browser/web_contents.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/render_view_host.h" #include "content/nw/src/api/object_manager.h" #include "content/nw/src/api/menu/menu_delegate_mac.h" #include "content/nw/src/api/menuitem/menuitem.h" @@ -55,13 +57,12 @@ [menu_ removeItem:menu_item->menu_item_]; } -#if 0 -void Menu::Popup(int x, int y, content::Shell* shell) { +void Menu::Popup(int x, int y, content::RenderViewHost* rvh) { // Fake out a context menu event for our menu - NSWindow* window = - static_cast(shell->window())->window(); + NSView* web_view = + rvh->GetView()->GetNativeView(); + NSWindow* window = [web_view window]; NSEvent* currentEvent = [NSApp currentEvent]; - NSView* web_view = shell->web_contents()->GetNativeView(); NSPoint position = { x, web_view.bounds.size.height - y }; NSTimeInterval eventTime = [currentEvent timestamp]; NSEvent* clickEvent = [NSEvent mouseEventWithType:NSRightMouseDown @@ -91,6 +92,5 @@ forView:web_view]; } } -#endif } // namespace nw From 4210e1f6cb673c039fcf8b403b1847f00cc9cdab Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 24 Aug 2015 16:25:49 +0800 Subject: [PATCH 005/404] [WIN] fix menu popup --- src/nw_custom_bindings.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nw_custom_bindings.cc b/src/nw_custom_bindings.cc index 2f296278bf..6b1bf42777 100644 --- a/src/nw_custom_bindings.cc +++ b/src/nw_custom_bindings.cc @@ -9,6 +9,7 @@ #include #include "base/bind.h" +#include "base/files/file_util.h" #include "extensions/renderer/script_context.h" #include "v8/include/v8.h" From 00eaaee63f668eae4e65fb812ad3f28d3411393c Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 26 Aug 2015 15:21:34 +0800 Subject: [PATCH 006/404] fixup nw.Shell --- src/api/nw_shell.idl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/nw_shell.idl b/src/api/nw_shell.idl index 3008cc6cb4..d495efdd9d 100644 --- a/src/api/nw_shell.idl +++ b/src/api/nw_shell.idl @@ -2,8 +2,8 @@ [implemented_in="content/nw/src/api/nw_shell_api.h"] namespace nw.Shell { interface Functions { - [nocompile] static void openExternal(DOMString uri); - [nocompile] static void openItem(DOMString filePath); - [nocompile] static void showItemInFolder(DOMString filePath); + static void openExternal(DOMString uri); + static void openItem(DOMString filePath); + static void showItemInFolder(DOMString filePath); }; }; From bfc812668d975d6f6db8690b298c016b623d5e98 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 14 Sep 2015 14:26:39 +0800 Subject: [PATCH 007/404] support tray API --- nw.gypi | 3 +++ src/api/schemas.gypi | 1 + src/api/tray/tray.cc | 18 ++++++++++-------- src/api/tray/tray.h | 12 +++++++----- src/api/tray/tray_aura.cc | 19 +++++++++---------- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/nw.gypi b/nw.gypi index 010535a2cc..5b63cff499 100644 --- a/nw.gypi +++ b/nw.gypi @@ -670,6 +670,8 @@ 'src/api/menuitem/menuitem.h', 'src/api/shell/shell.cc', 'src/api/shell/shell.h', + 'src/api/tray/tray.cc', + 'src/api/tray/tray.h', 'src/nw_content.cc', 'src/nw_content.h', 'src/nw_custom_bindings.cc', @@ -678,6 +680,7 @@ 'conditions': [ ['OS=="win" or OS=="linux"', { 'sources': [ + 'src/api/tray/tray_aura.cc', 'src/api/menu/menu_delegate.cc', 'src/api/menu/menu_delegate.h', 'src/api/menu/menu_views.cc', diff --git a/src/api/schemas.gypi b/src/api/schemas.gypi index 4f7c79aec5..60d8d0dc96 100644 --- a/src/api/schemas.gypi +++ b/src/api/schemas.gypi @@ -14,6 +14,7 @@ 'nw_clipboard.idl', 'nw_menu.idl', 'nw_shell.idl', + 'nw_tray.idl', 'nw_current_window_internal.idl', 'nw_test.idl', ], diff --git a/src/api/tray/tray.cc b/src/api/tray/tray.cc index 0259984c6b..330b13fe41 100644 --- a/src/api/tray/tray.cc +++ b/src/api/tray/tray.cc @@ -22,15 +22,16 @@ #include "base/values.h" #include "chrome/browser/status_icons/status_tray.h" -#include "content/nw/src/api/dispatcher_host.h" +#include "content/nw/src/api/object_manager.h" #include "content/nw/src/api/menu/menu.h" -namespace nwapi { +namespace nw { Tray::Tray(int id, - const base::WeakPtr& dispatcher_host, - const base::DictionaryValue& option) - : Base(id, dispatcher_host, option) { + const base::WeakPtr& object_manager, + const base::DictionaryValue& option, + const std::string& extension_id) + : Base(id, object_manager, option, extension_id) { Create(option); std::string title; @@ -55,7 +56,7 @@ Tray::Tray(int id, int menu_id; if (option.GetInteger("menu", &menu_id)) - SetMenu(dispatcher_host->GetApiObject(menu_id)); + SetMenu(object_manager->GetApiObject(menu_id)); ShowAfterCreate(); } @@ -65,7 +66,8 @@ Tray::~Tray() { } void Tray::Call(const std::string& method, - const base::ListValue& arguments) { + const base::ListValue& arguments, + content::RenderFrameHost* rvh) { if (method == "SetTitle") { std::string title; arguments.GetString(0, &title); @@ -89,7 +91,7 @@ void Tray::Call(const std::string& method, } else if (method == "SetMenu") { int object_id = 0; arguments.GetInteger(0, &object_id); - SetMenu(dispatcher_host()->GetApiObject(object_id)); + SetMenu(object_manager()->GetApiObject(object_id)); } else if (method == "Remove") { Remove(); } else { diff --git a/src/api/tray/tray.h b/src/api/tray/tray.h index 095d9e7d34..b0c4cf7db9 100644 --- a/src/api/tray/tray.h +++ b/src/api/tray/tray.h @@ -42,7 +42,7 @@ class StatusIcon; class StatusTray; #endif // defined(OS_MACOSX) -namespace nwapi { +namespace nw { class Menu; class TrayObserver; @@ -50,12 +50,14 @@ class TrayObserver; class Tray : public Base { public: Tray(int id, - const base::WeakPtr& dispatcher_host, - const base::DictionaryValue& option); + const base::WeakPtr& object_manager, + const base::DictionaryValue& option, + const std::string& extension_id); ~Tray() override; void Call(const std::string& method, - const base::ListValue& arguments) override; + const base::ListValue& arguments, + content::RenderFrameHost* rvh = nullptr) override; private: // Platform-independent implementations @@ -100,6 +102,6 @@ class Tray : public Base { DISALLOW_COPY_AND_ASSIGN(Tray); }; -} // namespace nwapi +} // namespace nw #endif // CONTENT_NW_SRC_API_TRAY_TRAY_H_ diff --git a/src/api/tray/tray_aura.cc b/src/api/tray/tray_aura.cc index 7bd9623fe2..e144a33a5b 100644 --- a/src/api/tray/tray_aura.cc +++ b/src/api/tray/tray_aura.cc @@ -26,14 +26,15 @@ #include "chrome/browser/status_icons/status_icon.h" #include "chrome/browser/status_icons/status_icon_observer.h" #include "chrome/browser/status_icons/status_tray.h" -#include "content/nw/src/api/dispatcher_host.h" +#include "content/nw/src/api/object_manager.h" #include "content/nw/src/api/menu/menu.h" +#include "content/nw/src/nw_base.h" +#include "content/nw/src/nw_content.h" #include "content/nw/src/nw_package.h" -#include "content/nw/src/nw_shell.h" #include "ui/gfx/screen.h" #include "ui/gfx/image/image.h" -namespace nwapi { +namespace nw { StatusTray* Tray::status_tray_ = NULL; @@ -54,7 +55,7 @@ class TrayObserver : public StatusIconObserver { data->SetInteger("x", cursor_pos.x()); data->SetInteger("y", cursor_pos.y()); args.Append(data); - tray_->dispatcher_host()->SendEvent(tray_, "click", args); + tray_->object_manager()->SendEvent(tray_, "click", args); } private: @@ -63,7 +64,7 @@ class TrayObserver : public StatusIconObserver { void Tray::Create(const base::DictionaryValue& option) { if (!status_tray_) - status_tray_ = StatusTray::GetSingleton(); + status_tray_ = StatusTray::Create(); status_icon_ = status_tray_->CreateStatusIcon(StatusTray::NOTIFICATION_TRAY_ICON, gfx::ImageSkia(), base::string16()); @@ -84,10 +85,8 @@ void Tray::SetTitle(const std::string& title) { void Tray::SetIcon(const std::string& path) { gfx::Image icon; - content::Shell* shell = content::Shell::FromRenderViewHost( - dispatcher_host()->render_view_host()); - nw::Package* package = shell->GetPackage(); - package->GetImage(base::FilePath::FromUTF8Unsafe(path), &icon); + nw::Package* package = nw::InitNWPackage(); + nw::GetImage(package, base::FilePath::FromUTF8Unsafe(path), &icon); if (!icon.IsEmpty()) status_icon_->SetImage(*icon.ToImageSkia()); @@ -117,4 +116,4 @@ void Tray::SetAltIcon(const std::string& alticon_path) { void Tray::SetIconsAreTemplates(bool areTemplates) { } -} // namespace nwapi +} // namespace nw From 3e001d4e0fbe90eee7557fb0e8fc6c924e71f602 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 14 Sep 2015 14:26:57 +0800 Subject: [PATCH 008/404] rebase to Chromium 45 and Node.js v4.0.0 --- src/api/base/base.cc | 2 +- src/api/base/base.h | 4 ++-- src/api/menu/menu.cc | 2 +- src/api/menu/menu.h | 6 +++--- src/api/menu/menu_views.cc | 4 ++-- src/api/menuitem/menuitem.cc | 2 +- src/api/menuitem/menuitem.h | 2 +- src/api/nw_object_api.cc | 4 ++-- src/api/nw_shell_api.cc | 8 ++++---- src/api/nw_window_api.cc | 14 +++++++------- src/api/object_manager.cc | 12 ++++++------ src/api/object_manager.h | 10 +++++----- src/nw_content.cc | 6 +++--- src/nw_custom_bindings.cc | 12 ++++++------ src/nw_package.cc | 3 ++- 15 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/api/base/base.cc b/src/api/base/base.cc index ce748ce8bb..5260fa4700 100644 --- a/src/api/base/base.cc +++ b/src/api/base/base.cc @@ -39,7 +39,7 @@ Base::~Base() { void Base::Call(const std::string& method, const base::ListValue& arguments, - content::RenderViewHost* rvh) { + content::RenderFrameHost* rvh) { NOTREACHED() << "Uncatched call in Base" << " method:" << method << " arguments:" << arguments; diff --git a/src/api/base/base.h b/src/api/base/base.h index 0c54a7c031..9161a8e026 100644 --- a/src/api/base/base.h +++ b/src/api/base/base.h @@ -32,7 +32,7 @@ class ListValue; } namespace content { -class RenderViewHost; +class RenderFrameHost; } namespace nw { @@ -49,7 +49,7 @@ class Base { virtual void Call(const std::string& method, const base::ListValue& arguments, - content::RenderViewHost* rvh = nullptr); + content::RenderFrameHost* rvh = nullptr); virtual void CallSync(const std::string& method, const base::ListValue& arguments, base::ListValue* result); diff --git a/src/api/menu/menu.cc b/src/api/menu/menu.cc index fa9c975e27..4fd99d0003 100644 --- a/src/api/menu/menu.cc +++ b/src/api/menu/menu.cc @@ -40,7 +40,7 @@ Menu::~Menu() { void Menu::Call(const std::string& method, const base::ListValue& arguments, - content::RenderViewHost* rvh) { + content::RenderFrameHost* rvh) { if (method == "Append") { int object_id = 0; arguments.GetInteger(0, &object_id); diff --git a/src/api/menu/menu.h b/src/api/menu/menu.h index 954404022f..a17465a8cf 100644 --- a/src/api/menu/menu.h +++ b/src/api/menu/menu.h @@ -83,7 +83,7 @@ class Window; } namespace content { -class RenderViewHost; +class RenderFrameHost; class RenderFrameHost; } @@ -101,7 +101,7 @@ class Menu : public Base { void Call(const std::string& method, const base::ListValue& arguments, - content::RenderViewHost* rvh = nullptr) override; + content::RenderFrameHost* rvh = nullptr) override; #if defined(OS_WIN) || defined(OS_LINUX) void UpdateKeys(views::FocusManager *focus_manager); @@ -118,7 +118,7 @@ class Menu : public Base { void Append(MenuItem* menu_item); void Insert(MenuItem* menu_item, int pos); void Remove(MenuItem* menu_item, int pos); - void Popup(int x, int y, content::RenderViewHost*); + void Popup(int x, int y, content::RenderFrameHost*); #if defined(OS_LINUX) std::vector menu_items; diff --git a/src/api/menu/menu_views.cc b/src/api/menu/menu_views.cc index 6b3c9f595d..ae9a2f309c 100644 --- a/src/api/menu/menu_views.cc +++ b/src/api/menu/menu_views.cc @@ -160,7 +160,7 @@ void Menu::Remove(MenuItem* menu_item, int pos) { menu_item->menu_ = NULL; } -void Menu::Popup(int x, int y, content::RenderViewHost* rvh) { +void Menu::Popup(int x, int y, content::RenderFrameHost* rfh) { // Rebuild(); // Map point from document to screen. @@ -168,7 +168,7 @@ void Menu::Popup(int x, int y, content::RenderViewHost* rvh) { // Convert from content coordinates to window coordinates. // This code copied from chrome_web_contents_view_delegate_views.cc - aura::Window* target_window = GetActiveNativeView(rvh->GetMainFrame()); + aura::Window* target_window = GetActiveNativeView(rfh); aura::Window* root_window = target_window->GetRootWindow(); views::Widget* top_level_widget = views::Widget::GetTopLevelWidgetForNativeView(target_window); diff --git a/src/api/menuitem/menuitem.cc b/src/api/menuitem/menuitem.cc index 183084824c..236575343e 100644 --- a/src/api/menuitem/menuitem.cc +++ b/src/api/menuitem/menuitem.cc @@ -43,7 +43,7 @@ MenuItem::~MenuItem() { void MenuItem::Call(const std::string& method, const base::ListValue& arguments, - content::RenderViewHost* rvh) { + content::RenderFrameHost* rvh) { if (method == "SetLabel") { std::string label; arguments.GetString(0, &label); diff --git a/src/api/menuitem/menuitem.h b/src/api/menuitem/menuitem.h index 5518d06d31..43f3c007ed 100644 --- a/src/api/menuitem/menuitem.h +++ b/src/api/menuitem/menuitem.h @@ -60,7 +60,7 @@ class MenuItem : public Base { void Call(const std::string& method, const base::ListValue& arguments, - content::RenderViewHost* rvh = nullptr) override; + content::RenderFrameHost* rvh = nullptr) override; #if defined(OS_WIN) || defined(OS_LINUX) bool AcceleratorPressed(const ui::Accelerator& accelerator) override; diff --git a/src/api/nw_object_api.cc b/src/api/nw_object_api.cc index 23fdc102bc..1715caeb74 100644 --- a/src/api/nw_object_api.cc +++ b/src/api/nw_object_api.cc @@ -62,7 +62,7 @@ bool NwObjectCallObjectMethodFunction::RunNWSync(base::ListValue* response, std: EXTENSION_FUNCTION_VALIDATE(args_->GetList(3, &arguments)); nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallObjectMethod(render_view_host(), id, type, method, *arguments); + manager->OnCallObjectMethod(render_frame_host(), id, type, method, *arguments); return true; } @@ -82,7 +82,7 @@ bool NwObjectCallObjectMethodSyncFunction::RunNWSync(base::ListValue* response, EXTENSION_FUNCTION_VALIDATE(args_->GetList(3, &arguments)); nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallObjectMethodSync(render_view_host(), id, type, method, *arguments, response); + manager->OnCallObjectMethodSync(render_frame_host(), id, type, method, *arguments, response); return true; } diff --git a/src/api/nw_shell_api.cc b/src/api/nw_shell_api.cc index 3e536ee37b..6f8208eccd 100644 --- a/src/api/nw_shell_api.cc +++ b/src/api/nw_shell_api.cc @@ -4,7 +4,7 @@ #include "chrome/browser/extensions/devtools_util.h" #include "chrome/browser/extensions/extension_service.h" #include "content/nw/src/api/object_manager.h" -#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_system.h" #include "extensions/common/error_utils.h" @@ -19,7 +19,7 @@ NwShellOpenItemFunction::~NwShellOpenItemFunction() { bool NwShellOpenItemFunction::RunNWSync(base::ListValue* response, std::string* error) { nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallStaticMethod(render_view_host(), "Shell", "OpenItem", *args_); + manager->OnCallStaticMethod(render_frame_host(), "Shell", "OpenItem", *args_); return true; } @@ -31,7 +31,7 @@ NwShellOpenExternalFunction::~NwShellOpenExternalFunction() { bool NwShellOpenExternalFunction::RunNWSync(base::ListValue* response, std::string* error) { nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallStaticMethod(render_view_host(), "Shell", "OpenExternal", *args_); + manager->OnCallStaticMethod(render_frame_host(), "Shell", "OpenExternal", *args_); return true; } @@ -43,7 +43,7 @@ NwShellShowItemInFolderFunction::~NwShellShowItemInFolderFunction() { bool NwShellShowItemInFolderFunction::RunNWSync(base::ListValue* response, std::string* error) { nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnCallStaticMethod(render_view_host(), "Shell", "ShowItemInFolder", *args_); + manager->OnCallStaticMethod(render_frame_host(), "Shell", "ShowItemInFolder", *args_); return true; } diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 5f617554c7..3302d78c4b 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -60,9 +60,9 @@ NwCurrentWindowInternalShowDevToolsFunction::~NwCurrentWindowInternalShowDevTool } bool NwCurrentWindowInternalShowDevToolsFunction::RunAsync() { - content::RenderViewHost* rvh = render_view_host(); + content::RenderFrameHost* rfh = render_frame_host(); DevToolsWindow::OpenDevToolsWindow( - content::WebContents::FromRenderViewHost(rvh)); + content::WebContents::FromRenderFrameHost(rfh)); SendResponse(true); return true; } @@ -83,8 +83,8 @@ bool NwCurrentWindowInternalCapturePageInternalFunction::RunAsync() { image_details = ImageDetails::FromValue(*spec); } - content::RenderViewHost* rvh = render_view_host(); - WebContents* contents = content::WebContents::FromRenderViewHost(rvh); + content::RenderFrameHost* rfh = render_frame_host(); + WebContents* contents = content::WebContents::FromRenderFrameHost(rfh); if (!contents) return false; @@ -227,12 +227,12 @@ bool NwCurrentWindowInternalSetMenuFunction::RunAsync() { AppWindowRegistry* registry = AppWindowRegistry::Get(browser_context()); DCHECK(registry); - content::RenderViewHost* rvh = render_view_host(); - if (!rvh) + content::WebContents* web_contents = GetSenderWebContents(); + if (!web_contents) // No need to set an error, since we won't return to the caller anyway if // there's no RVH. return false; - AppWindow* window = registry->GetAppWindowForRenderViewHost(rvh); + AppWindow* window = registry->GetAppWindowForWebContents(web_contents); if (!window) { error_ = kNoAssociatedAppWindow; return false; diff --git a/src/api/object_manager.cc b/src/api/object_manager.cc index f7c8a1283d..dda1f56aab 100644 --- a/src/api/object_manager.cc +++ b/src/api/object_manager.cc @@ -126,7 +126,7 @@ void ObjectManager::OnDeallocateObject(int object_id) { } void ObjectManager::OnCallObjectMethod( - content::RenderViewHost* rvh, + content::RenderFrameHost* rvh, int object_id, const std::string& type, const std::string& method, @@ -147,7 +147,7 @@ void ObjectManager::OnCallObjectMethod( } void ObjectManager::OnCallObjectMethodSync( - content::RenderViewHost* rvh, + content::RenderFrameHost* rvh, int object_id, const std::string& type, const std::string& method, @@ -169,7 +169,7 @@ void ObjectManager::OnCallObjectMethodSync( } void ObjectManager::OnCallStaticMethod( - content::RenderViewHost* rvh, + content::RenderFrameHost* rvh, const std::string& type, const std::string& method, const base::ListValue& arguments) { @@ -191,7 +191,7 @@ void ObjectManager::OnCallStaticMethod( } void ObjectManager::OnCallStaticMethodSync( - content::RenderViewHost* rvh, + content::RenderFrameHost* rvh, const std::string& type, const std::string& method, const base::ListValue& arguments, @@ -203,7 +203,7 @@ void ObjectManager::OnCallStaticMethodSync( #if 0 if (type == "App") { content::Shell* shell = - content::Shell::FromRenderViewHost(render_view_host()); + content::Shell::FromRenderFrameHost(render_view_host()); nwapi::App::Call(shell, method, arguments, result, this); return; } else if (type == "Screen") { @@ -223,7 +223,7 @@ void ObjectManager::SendEvent(Base* object, return; scoped_ptr arguments(args.DeepCopy()); arguments->Insert(0, new base::FundamentalValue(object->id())); - scoped_ptr event(new Event("NWObject" + event_name, arguments.Pass())); + scoped_ptr event(new Event(extensions::events::UNKNOWN, "NWObject" + event_name, arguments.Pass())); event->restrict_to_browser_context = browser_context_; event->user_gesture = EventRouter::USER_GESTURE_ENABLED; event_router->DispatchEventToExtension(object->extension_id_, event.Pass()); diff --git a/src/api/object_manager.h b/src/api/object_manager.h index 26d1a67959..d49ef199a6 100644 --- a/src/api/object_manager.h +++ b/src/api/object_manager.h @@ -42,7 +42,7 @@ class WebFrame; namespace content { class BrowserContext; -class RenderViewHost; +class RenderFrameHost; } namespace nw { @@ -81,22 +81,22 @@ class ObjectManager : public KeyedService { const base::DictionaryValue& option, const std::string& extension_id); void OnDeallocateObject(int object_id); - void OnCallObjectMethod(content::RenderViewHost* rvh, + void OnCallObjectMethod(content::RenderFrameHost* rvh, int object_id, const std::string& type, const std::string& method, const base::ListValue& arguments); - void OnCallObjectMethodSync(content::RenderViewHost* rvh, + void OnCallObjectMethodSync(content::RenderFrameHost* rvh, int object_id, const std::string& type, const std::string& method, const base::ListValue& arguments, base::ListValue* result); - void OnCallStaticMethod(content::RenderViewHost* rvh, + void OnCallStaticMethod(content::RenderFrameHost* rvh, const std::string& type, const std::string& method, const base::ListValue& arguments); - void OnCallStaticMethodSync(content::RenderViewHost* rvh, + void OnCallStaticMethodSync(content::RenderFrameHost* rvh, const std::string& type, const std::string& method, const base::ListValue& arguments, diff --git a/src/nw_content.cc b/src/nw_content.cc index 3140e5723f..6e2dde6be6 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -94,7 +94,7 @@ static inline v8::Local v8_str(const char* x) { v8::Handle CallNWTickCallback(node::Environment* env, const v8::Handle ret) { blink::WebScopedMicrotaskSuppression suppression; - return node::CallTickCallback(env, ret); + return node::CallTickCallback(env); } v8::Handle CreateNW(ScriptContext* context, @@ -433,7 +433,7 @@ void LoadNWAppAsExtensionHook(base::DictionaryValue* manifest, std::string* erro } if (manifest->GetString(manifest_keys::kNWJSMain, &main_url)) { - if (EndsWith(main_url, ".js", false)) { + if (base::EndsWith(main_url, ".js", false)) { AmendManifestStringList(manifest, manifest_keys::kPlatformAppBackgroundScripts, main_url); manifest->SetString(manifest_keys::kNWJSInternalMainFilename, main_url); }else @@ -573,7 +573,7 @@ void CalcNewWinParams(content::WebContents* new_contents, void* params, scoped_ptr val; scoped_ptr manifest; std::string manifest_str = base::UTF16ToUTF8(nw::GetCurrentNewWinManifest()); - val.reset(base::JSONReader().ReadToValue(manifest_str)); + val = base::JSONReader().ReadToValue(manifest_str); if (val.get() && val->IsType(base::Value::TYPE_DICTIONARY)) manifest.reset(static_cast(val.release())); else diff --git a/src/nw_custom_bindings.cc b/src/nw_custom_bindings.cc index 6b1bf42777..c9360dab58 100644 --- a/src/nw_custom_bindings.cc +++ b/src/nw_custom_bindings.cc @@ -95,10 +95,10 @@ void NWCustomBindings::CrashRenderer( void NWCustomBindings::EvalScript( const v8::FunctionCallbackInfo& args) { - content::RenderView* render_view = context()->GetRenderView(); - if (!render_view) + content::RenderFrame* render_frame = context()->GetRenderFrame(); + if (!render_frame) return; - WebFrame* main_frame = render_view->GetWebView()->mainFrame(); + WebFrame* main_frame = render_frame->GetWebFrame(); v8::Handle result; v8::Handle frm = v8::Handle::Cast(args[0]); WebFrame* web_frame = NULL; @@ -123,8 +123,8 @@ void NWCustomBindings::EvalScript( void NWCustomBindings::EvalNWBin( const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); - content::RenderView* render_view = context()->GetRenderView(); - if (!render_view) + content::RenderFrame* render_frame = context()->GetRenderFrame(); + if (!render_frame) return; #if defined(OS_WIN) base::FilePath path((WCHAR*)*v8::String::Value(args[1])); @@ -140,7 +140,7 @@ void NWCustomBindings::EvalNWBin( raw_data.resize(size); uint8_t* data = reinterpret_cast(&(raw_data.front())); if (file.ReadAtCurrentPos((char*)data, size) == length) { - WebFrame* main_frame = render_view->GetWebView()->mainFrame(); + WebFrame* main_frame = render_frame->GetWebFrame(); v8::Handle source_string = v8::String::NewFromUtf8(isolate, ""); v8::ScriptCompiler::CachedData* cache; cache = new v8::ScriptCompiler::CachedData( diff --git a/src/nw_package.cc b/src/nw_package.cc index c7cb5903b9..a89884db88 100644 --- a/src/nw_package.cc +++ b/src/nw_package.cc @@ -28,6 +28,7 @@ #include "base/json/json_file_value_serializer.h" #include "base/json/json_string_value_serializer.h" #include "base/path_service.h" +#include "base/strings/pattern.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_tokenizer.h" #include "base/strings/string_util.h" @@ -150,7 +151,7 @@ void RelativePathToURI(FilePath root, base::DictionaryValue* manifest) { return; // Don't append path if there is already a prefix - if (MatchPattern(old, "*://*")) + if (base::MatchPattern(old, "*://*")) return; FilePath main_path = root.Append(FilePath::FromUTF8Unsafe(old)); From e86872e47d0d5d93ae358de46ea3c6943bff4dbb Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 15 Sep 2015 14:56:02 +0800 Subject: [PATCH 009/404] update webrtc_openssl.patch --- patch/patches/webrtc_openssl.patch | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/patch/patches/webrtc_openssl.patch b/patch/patches/webrtc_openssl.patch index a30eb6ac74..586d22cdc4 100644 --- a/patch/patches/webrtc_openssl.patch +++ b/patch/patches/webrtc_openssl.patch @@ -1,8 +1,8 @@ diff --git a/base/base.gyp b/base/base.gyp -index 76d85a7..a99893e 100644 ---- base/base.gyp -+++ base/base.gyp -@@ -558,7 +558,7 @@ +index 7bfa3e3..4685b0b 100644 +--- a/base/base.gyp ++++ b/base/base.gyp +@@ -548,7 +548,7 @@ 'conditions': [ # On some platforms, the rest of NSS is bundled. On others, # it's pulled from the system. From be0d62aa53d92ae8af584edc76970f55d2fb8c7a Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 25 Sep 2015 13:14:57 +0800 Subject: [PATCH 010/404] decouple node dll --- nw.gypi | 2 +- src/nw_content.cc | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/nw.gypi b/nw.gypi index 5b63cff499..8c7d8dc1ad 100644 --- a/nw.gypi +++ b/nw.gypi @@ -628,7 +628,7 @@ 'type': 'static_library', 'dependencies': [ '<(DEPTH)/base/base.gyp:base', - '<(DEPTH)/third_party/node/node.gyp:node', + #'<(DEPTH)/third_party/node/node.gyp:node', '<(DEPTH)/third_party/WebKit/public/blink.gyp:blink', '<(DEPTH)/third_party/WebKit/Source/wtf/wtf.gyp:wtf', '<(DEPTH)/third_party/zlib/zlib.gyp:minizip', diff --git a/src/nw_content.cc b/src/nw_content.cc index 6e2dde6be6..a08b046148 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -80,6 +80,18 @@ using blink::WebScriptSource; namespace manifest_keys = extensions::manifest_keys; +CallTickCallbackFn g_call_tick_callback_fn = nullptr; +SetupNWNodeFn g_setup_nwnode_fn = nullptr; +IsNodeInitializedFn g_is_node_initialized_fn = nullptr; +SetNWTickCallbackFn g_set_nw_tick_callback_fn = nullptr; +StartNWInstanceFn g_start_nw_instance_fn = nullptr; +GetNodeContextFn g_get_node_context_fn = nullptr; +SetNodeContextFn g_set_node_context_fn = nullptr; +GetNodeEnvFn g_get_node_env_fn = nullptr; +GetCurrentEnvironmentFn g_get_current_env_fn = nullptr; +EmitExitFn g_emit_exit_fn = nullptr; +RunAtExitFn g_run_at_exit_fn = nullptr; + namespace nw { @@ -92,9 +104,10 @@ static inline v8::Local v8_str(const char* x) { return v8::String::NewFromUtf8(isolate, x); } -v8::Handle CallNWTickCallback(node::Environment* env, const v8::Handle ret) { +v8::Handle CallNWTickCallback(void* env, const v8::Handle ret) { blink::WebScopedMicrotaskSuppression suppression; - return node::CallTickCallback(env); + g_call_tick_callback_fn(env); + return Undefined(v8::Isolate::GetCurrent()); } v8::Handle CreateNW(ScriptContext* context, @@ -248,12 +261,14 @@ void DocumentElementHook(blink::WebFrame* frame, void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { v8::Isolate* isolate = context->isolate(); - if (!node::is_node_initialized()) - node::SetupNWNode(0, nullptr); + if (!g_is_node_initialized_fn()) + g_setup_nwnode_fn(0, nullptr); bool mixed_context = false; context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSMixedContext, &mixed_context); - if (node::g_context.IsEmpty() || mixed_context) { + v8::Local node_context; + g_get_node_context_fn(&node_context); + if (node_context.IsEmpty() || mixed_context) { { int argc = 1; char argv0[] = "node"; @@ -271,15 +286,15 @@ void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { v8::HandleScope scope(isolate); blink::WebScopedMicrotaskSuppression suppression; - node::SetNWTickCallback(CallNWTickCallback); + g_set_nw_tick_callback_fn(&CallNWTickCallback); v8::Local dom_context = context->v8_context(); if (!mixed_context) - node::g_context.Reset(isolate, dom_context); + g_set_node_context_fn(isolate, &dom_context); dom_context->SetSecurityToken(v8::String::NewFromUtf8(isolate, "nw-token")); dom_context->Enter(); dom_context->SetEmbedderData(0, v8::String::NewFromUtf8(isolate, "node")); - node::StartNWInstance(argc, argv, dom_context); + g_start_nw_instance_fn(argc, argv, dom_context); { v8::Local script = v8::Script::Compile(v8::String::NewFromUtf8(isolate, "process.versions['nwjs'] = '" NW_VERSION_STRING "';" @@ -307,12 +322,14 @@ void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { } } + v8::Local node_context2; + g_get_node_context_fn(&node_context2); if (!mixed_context) { v8::Local g_context = - v8::Local::New(isolate, node::g_context); + v8::Local::New(isolate, node_context2); v8::Local node_global = g_context->Global(); - context->v8_context()->SetAlignedPointerInEmbedderData(NODE_CONTEXT_EMBEDDER_DATA_INDEX, node::g_env); + context->v8_context()->SetAlignedPointerInEmbedderData(NODE_CONTEXT_EMBEDDER_DATA_INDEX, g_get_node_env_fn()); context->v8_context()->SetSecurityToken(g_context->GetSecurityToken()); v8::Handle nw = AsObjectOrEmpty(CreateNW(context, node_global, g_context)); @@ -478,9 +495,9 @@ void RendererProcessTerminatedHook(content::RenderProcessHost* process, void OnRenderProcessShutdownHook(extensions::ScriptContext* context) { blink::WebScopedMicrotaskSuppression suppression; - node::Environment* env = node::GetCurrentEnvironment(context->v8_context()); - node::EmitExit(env); - node::RunAtExit(env); + void* env = g_get_current_env_fn(context->v8_context()); + g_emit_exit_fn(env); + g_run_at_exit_fn(env); } void willHandleNavigationPolicy(content::RenderView* rv, From c81c8828755ed7a365ee8b6bc302e4296b4e699b Mon Sep 17 00:00:00 2001 From: build Date: Fri, 9 Oct 2015 10:22:35 +0800 Subject: [PATCH 011/404] mac rebase fix --- patch/patches/webrtc_openssl.patch | 6 +++--- src/api/menu/menu_mac.mm | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/patch/patches/webrtc_openssl.patch b/patch/patches/webrtc_openssl.patch index 586d22cdc4..e697fc4da5 100644 --- a/patch/patches/webrtc_openssl.patch +++ b/patch/patches/webrtc_openssl.patch @@ -1,7 +1,7 @@ -diff --git a/base/base.gyp b/base/base.gyp +diff --git base/base.gyp base/base.gyp index 7bfa3e3..4685b0b 100644 ---- a/base/base.gyp -+++ b/base/base.gyp +--- base/base.gyp ++++ base/base.gyp @@ -548,7 +548,7 @@ 'conditions': [ # On some platforms, the rest of NSS is bundled. On others, diff --git a/src/api/menu/menu_mac.mm b/src/api/menu/menu_mac.mm index ebf0f7de59..aa56b5e208 100644 --- a/src/api/menu/menu_mac.mm +++ b/src/api/menu/menu_mac.mm @@ -26,7 +26,7 @@ #import #include "content/public/browser/web_contents.h" #include "content/public/browser/render_widget_host_view.h" -#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_frame_host.h" #include "content/nw/src/api/object_manager.h" #include "content/nw/src/api/menu/menu_delegate_mac.h" #include "content/nw/src/api/menuitem/menuitem.h" @@ -57,10 +57,9 @@ [menu_ removeItem:menu_item->menu_item_]; } -void Menu::Popup(int x, int y, content::RenderViewHost* rvh) { +void Menu::Popup(int x, int y, content::RenderFrameHost* rfh) { // Fake out a context menu event for our menu - NSView* web_view = - rvh->GetView()->GetNativeView(); + NSView* web_view = rfh->GetNativeView(); NSWindow* window = [web_view window]; NSEvent* currentEvent = [NSApp currentEvent]; NSPoint position = { x, web_view.bounds.size.height - y }; From b91be190c26096cc05bdd553c5e993d77a0c956f Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sat, 10 Oct 2015 09:24:47 +0800 Subject: [PATCH 012/404] bump version to 0.13.0-alpha4 --- src/nw_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nw_version.h b/src/nw_version.h index d242161596..a55b461031 100644 --- a/src/nw_version.h +++ b/src/nw_version.h @@ -38,7 +38,7 @@ #else # define NW_VERSION_STRING NW_STRINGIFY(NW_MAJOR_VERSION) "." \ NW_STRINGIFY(NW_MINOR_VERSION) "." \ - NW_STRINGIFY(NW_PATCH_VERSION) "-alpha3" + NW_STRINGIFY(NW_PATCH_VERSION) "-alpha4" #endif #define NW_VERSION "v" NW_VERSION_STRING From b2ac541449bde529b4cb677360395dcacaabab2f Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sat, 10 Oct 2015 09:37:23 +0800 Subject: [PATCH 013/404] add tray API files --- src/api/nw_tray.idl | 21 +++++++++++++++++++++ src/api/nw_tray_api.h | 24 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/api/nw_tray.idl create mode 100644 src/api/nw_tray_api.h diff --git a/src/api/nw_tray.idl b/src/api/nw_tray.idl new file mode 100644 index 0000000000..b4087cc84c --- /dev/null +++ b/src/api/nw_tray.idl @@ -0,0 +1,21 @@ +// nw Tray API +[implemented_in="content/nw/src/api/nw_tray_api.h"] +namespace nw.Tray { + callback EventCallback = void(); + [noinline_doc] dictionary Tray { + [nodoc] DOMString? title; + DOMString tooltip; + DOMString icon; + DOMString alticon; + boolean iconsAreTemplates; + object menu; + object click; + static void remove(); + static void on(DOMString event, + EventCallback callback); + }; + interface Functions { + [nocompile] static object create(optional object options); + [nocompile] static void destroy(long id); + }; +}; diff --git a/src/api/nw_tray_api.h b/src/api/nw_tray_api.h new file mode 100644 index 0000000000..a72aa18413 --- /dev/null +++ b/src/api/nw_tray_api.h @@ -0,0 +1,24 @@ +#ifndef NW_API_MENU_API_H_ +#define NW_API_MENU_API_H_ + +#include + +#include "extensions/browser/extension_function.h" + +namespace extensions { + +class NwMenuCreateItemFunction : public NWSyncExtensionFunction { + public: + NwMenuCreateItemFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwMenuCreateItemFunction() override; + + DECLARE_EXTENSION_FUNCTION("nw.Menu.createItem", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwMenuCreateItemFunction); +}; + +} // namespace extensions +#endif From 51ec2f39874c3c976a564e1fc7f6a8556aaa270c Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sat, 10 Oct 2015 14:10:40 +0800 Subject: [PATCH 014/404] relocate 'nw' target and ship node dll --- nw.gypi | 62 +++++++++++++++++++++++++++++++++++++-- tools/package_binaries.py | 3 ++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/nw.gypi b/nw.gypi index 8c7d8dc1ad..623eac3253 100644 --- a/nw.gypi +++ b/nw.gypi @@ -812,7 +812,7 @@ }, ], 'dependencies': [ - '<(DEPTH)/chrome/chrome.gyp:nw', + 'nw', '../breakpad/breakpad.gyp:dump_syms', ], }], @@ -839,7 +839,7 @@ }, ], 'dependencies': [ - '<(DEPTH)/chrome/chrome.gyp:nw', + 'nw', ], }], ['OS=="linux"', { @@ -865,9 +865,45 @@ 'message': 'Dumping breakpad symbols to <(_outputs)', 'process_outputs_as_sources': 1, }, + { + 'action_name': 'dump_symbol_and_strip_2', + 'inputs': [ + '<(DEPTH)/content/nw/tools/dump_app_syms', + '<(PRODUCT_DIR)/dump_syms', + '<(PRODUCT_DIR)/lib/libnw.so', + ], + 'outputs': [ + '<(PRODUCT_DIR)/nw.so.breakpad.<(target_arch)', + ], + 'action': ['<(DEPTH)/content/nw/tools/dump_app_syms', + '<(PRODUCT_DIR)/dump_syms', + '<(linux_strip_binary)', + '<(PRODUCT_DIR)/lib/libnw.so', + '<@(_outputs)'], + 'message': 'Dumping breakpad symbols to <(_outputs)', + 'process_outputs_as_sources': 1, + }, + { + 'action_name': 'dump_symbol_and_strip_3', + 'inputs': [ + '<(DEPTH)/content/nw/tools/dump_app_syms', + '<(PRODUCT_DIR)/dump_syms', + '<(PRODUCT_DIR)/lib/libnode.so', + ], + 'outputs': [ + '<(PRODUCT_DIR)/node.so.breakpad.<(target_arch)', + ], + 'action': ['<(DEPTH)/content/nw/tools/dump_app_syms', + '<(PRODUCT_DIR)/dump_syms', + '<(linux_strip_binary)', + '<(PRODUCT_DIR)/lib/libnode.so', + '<@(_outputs)'], + 'message': 'Dumping breakpad symbols to <(_outputs)', + 'process_outputs_as_sources': 1, + }, ], 'dependencies': [ - '<(DEPTH)/chrome/chrome.gyp:nw', + 'nw', '../breakpad/breakpad.gyp:dump_syms', ], }], @@ -898,6 +934,26 @@ }], ], }, + { + 'target_name': 'nw', + 'type': 'none', + 'dependencies': [ + '<(DEPTH)/chrome/chrome.gyp:chrome', + '<(DEPTH)/third_party/node/node.gyp:node', + ], + 'conditions': [ + [ 'OS=="mac"', { + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)/<(mac_product_name).app/Contents/Versions/<(version_full)/<(mac_product_name) Framework.framework/', + 'files': [ + '<(PRODUCT_DIR)/libnode.dylib', + ], + }, + ], + }], + ], + }, { 'target_name': 'dist', 'type': 'none', diff --git a/tools/package_binaries.py b/tools/package_binaries.py index 16362a64f7..c67c316337 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -155,6 +155,8 @@ def generate_target_nw(platform_name, arch, version): 'locales', 'snapshot_blob.bin', 'natives_blob.bin', + 'lib/libnw.so', + 'lib/libnode.so', ] if flavor in ['nacl','sdk'] : target['input'] += ['nacl_helper', 'nacl_helper_bootstrap', 'pnacl'] @@ -171,6 +173,7 @@ def generate_target_nw(platform_name, arch, version): 'libEGL.dll', 'libGLESv2.dll', 'nw.dll', + 'node.dll', 'nw_elf.dll', 'nw.exe', 'locales', From 51d5700963a99cbff59db80cd405cbd3f4db6567 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 12 Oct 2015 21:36:13 +0800 Subject: [PATCH 015/404] make lib in linux dist folder --- tools/package_binaries.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/package_binaries.py b/tools/package_binaries.py index c67c316337..6b3fd9b666 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -383,6 +383,8 @@ def make_packages(targets): # copy files into a folder then pack folder = os.path.join(dist_dir, t['output']) os.mkdir(folder) + if platform_name == 'linux': + os.mkdir(os.path.join(folder, 'lib')) for f in t['input']: src = os.path.join(binaries_location, f) dest = os.path.join(folder, f) From e9041de4c1dce8cf096b10610270478a82ddd7fb Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 13 Oct 2015 11:04:00 +0800 Subject: [PATCH 016/404] rename nw target to nwjs and strip nacl_helper --- nw.gypi | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/nw.gypi b/nw.gypi index 623eac3253..6d3920c92e 100644 --- a/nw.gypi +++ b/nw.gypi @@ -812,7 +812,7 @@ }, ], 'dependencies': [ - 'nw', + 'nwjs', '../breakpad/breakpad.gyp:dump_syms', ], }], @@ -839,7 +839,7 @@ }, ], 'dependencies': [ - 'nw', + 'nwjs', ], }], ['OS=="linux"', { @@ -903,10 +903,35 @@ }, ], 'dependencies': [ - 'nw', + 'nwjs', '../breakpad/breakpad.gyp:dump_syms', ], }], + ['OS=="linux" and disable_nacl==0', { + 'variables': { + 'linux_strip_binary': 1, + }, + 'actions': [ + { + 'action_name': 'dump_symbol_and_strip_4', + 'inputs': [ + '<(DEPTH)/content/nw/tools/dump_app_syms', + '<(PRODUCT_DIR)/dump_syms', + '<(PRODUCT_DIR)/nacl_helper', + ], + 'outputs': [ + '<(PRODUCT_DIR)/nacl_helper.breakpad.<(target_arch)', + ], + 'action': ['<(DEPTH)/content/nw/tools/dump_app_syms', + '<(PRODUCT_DIR)/dump_syms', + '<(linux_strip_binary)', + '<(PRODUCT_DIR)/nacl_helper', + '<@(_outputs)'], + 'message': 'Dumping breakpad symbols to <(_outputs)', + 'process_outputs_as_sources': 1, + }, + ], + }], ], }, { @@ -935,7 +960,7 @@ ], }, { - 'target_name': 'nw', + 'target_name': 'nwjs', 'type': 'none', 'dependencies': [ '<(DEPTH)/chrome/chrome.gyp:chrome', From 584109db76f811aaf707ebda8f736e5179aa35f9 Mon Sep 17 00:00:00 2001 From: Jefry Date: Mon, 12 Oct 2015 08:26:07 +0700 Subject: [PATCH 017/404] implement Menu.createMacBuiltin --- nw.gypi | 1 + src/api/nw_menu.idl | 4 +- src/api/nw_menu_api.cc | 15 +++-- src/api/nw_menu_api.h | 26 ++++++++ src/api/nw_menu_api_mac.mm | 75 +++++++++++++++++++++ src/resources/api_nw_menu.js | 125 ++++++++++++++++++++++++++++++++++- 6 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 src/api/nw_menu_api_mac.mm diff --git a/nw.gypi b/nw.gypi index 6d3920c92e..d651216da1 100644 --- a/nw.gypi +++ b/nw.gypi @@ -696,6 +696,7 @@ }], ['OS=="mac"', { 'sources': [ + 'src/api/nw_menu_api_mac.mm', 'src/api/menuitem/menuitem_mac.mm', 'src/api/menu/menu_mac.mm', 'src/api/menu/menu_delegate_mac.h', diff --git a/src/api/nw_menu.idl b/src/api/nw_menu.idl index 986bdc7905..ed66c5e78e 100644 --- a/src/api/nw_menu.idl +++ b/src/api/nw_menu.idl @@ -25,11 +25,13 @@ namespace nw.Menu { static void remove([instanceOf=MenuItem] object item); static void removeAt(long index); static void popup(long x, long y); - //static void createMacBuiltin(DOMString appname); + static void createMacBuiltin(DOMString appname); }; interface Functions { static object createItem(optional object options); [nocompile] static object createMenu(optional object options); [nocompile] static void destroy(long id); + static DOMString getNSStringWithFixup(DOMString msg); + static DOMString getNSStringFWithFixup(DOMString msg, DOMString appName); }; }; diff --git a/src/api/nw_menu_api.cc b/src/api/nw_menu_api.cc index c554fce8a4..47245e2010 100644 --- a/src/api/nw_menu_api.cc +++ b/src/api/nw_menu_api.cc @@ -1,15 +1,11 @@ #include "content/nw/src/api/nw_menu_api.h" -#include "chrome/browser/devtools/devtools_window.h" -#include "chrome/browser/extensions/devtools_util.h" #include "chrome/browser/extensions/extension_service.h" #include "content/nw/src/api/menuitem/menuitem.h" #include "content/nw/src/api/object_manager.h" #include "content/public/browser/render_view_host.h" -#include "content/public/browser/web_contents.h" #include "extensions/browser/extension_system.h" #include "extensions/common/error_utils.h" -#include "ui/base/clipboard/clipboard.h" using nw::MenuItem; @@ -33,4 +29,15 @@ bool NwMenuCreateItemFunction::RunNWSync(base::ListValue* response, std::string* return true; } +#ifndef OS_MACOSX +bool NwMenuGetNSStringWithFixupFunction::RunNWSync(base::ListValue* response, std::string* error) { + error_ = "NwMenuGetNSStringWithFixupFunction is only for OSX"; + return false; +} + +bool NwMenuGetNSStringFWithFixupFunction::RunNWSync(base::ListValue* response, std::string* error) { + error_ = "NwMenuGetNSStringFWithFixupFunction is only for OSX"; + return false; +} +#endif } // namespace extensions diff --git a/src/api/nw_menu_api.h b/src/api/nw_menu_api.h index a72aa18413..450c8dae30 100644 --- a/src/api/nw_menu_api.h +++ b/src/api/nw_menu_api.h @@ -20,5 +20,31 @@ class NwMenuCreateItemFunction : public NWSyncExtensionFunction { DISALLOW_COPY_AND_ASSIGN(NwMenuCreateItemFunction); }; +class NwMenuGetNSStringWithFixupFunction : public NWSyncExtensionFunction { + public: + NwMenuGetNSStringWithFixupFunction(){} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwMenuGetNSStringWithFixupFunction() override {} + + DECLARE_EXTENSION_FUNCTION("nw.Menu.getNSStringWithFixup", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwMenuGetNSStringWithFixupFunction); +}; + +class NwMenuGetNSStringFWithFixupFunction : public NWSyncExtensionFunction { + public: + NwMenuGetNSStringFWithFixupFunction() {} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwMenuGetNSStringFWithFixupFunction() override {} + + DECLARE_EXTENSION_FUNCTION("nw.Menu.getNSStringFWithFixup", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwMenuGetNSStringFWithFixupFunction); +}; + } // namespace extensions #endif diff --git a/src/api/nw_menu_api_mac.mm b/src/api/nw_menu_api_mac.mm new file mode 100644 index 0000000000..96048f03d4 --- /dev/null +++ b/src/api/nw_menu_api_mac.mm @@ -0,0 +1,75 @@ +#include "content/nw/src/api/nw_menu_api.h" +#include "base/containers/hash_tables.h" +#include "base/strings/sys_string_conversions.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/grit/generated_resources.h" +#include "ui/base/l10n/l10n_util_mac.h" + +namespace extensions { + +typedef struct { + std::string msgstr; + int msgid; +} MsgMapEntry; + +const MsgMapEntry msg_map[] = { + { "IDS_ABOUT_MAC", IDS_ABOUT_MAC }, + { "IDS_HIDE_APP_MAC", IDS_HIDE_APP_MAC}, + { "IDS_HIDE_OTHERS_MAC", IDS_HIDE_OTHERS_MAC}, + { "IDS_SHOW_ALL_MAC", IDS_SHOW_ALL_MAC }, + { "IDS_EXIT_MAC", IDS_EXIT_MAC }, + { "IDS_EDIT_MENU_MAC", IDS_EDIT_MENU_MAC }, + { "IDS_EDIT_UNDO_MAC", IDS_EDIT_UNDO_MAC }, + { "IDS_EDIT_REDO_MAC", IDS_EDIT_REDO_MAC }, + { "IDS_CUT_MAC", IDS_CUT_MAC }, + { "IDS_COPY_MAC", IDS_COPY_MAC }, + { "IDS_PASTE_MAC", IDS_PASTE_MAC }, + { "IDS_EDIT_DELETE_MAC", IDS_EDIT_DELETE_MAC }, + { "IDS_EDIT_SELECT_ALL_MAC", IDS_EDIT_SELECT_ALL_MAC }, + { "IDS_WINDOW_MENU_MAC", IDS_WINDOW_MENU_MAC }, + { "IDS_MINIMIZE_WINDOW_MAC", IDS_MINIMIZE_WINDOW_MAC }, + { "IDS_CLOSE_WINDOW_MAC", IDS_CLOSE_WINDOW_MAC }, + { "IDS_ALL_WINDOWS_FRONT_MAC", IDS_ALL_WINDOWS_FRONT_MAC }, +}; + +typedef base::hash_map MsgIDMap; +MsgIDMap g_msgid_map; +static bool g_msgid_inited = false; + +void InitMsgIDMap() { + g_msgid_map.clear(); + for (size_t i = 0; i < arraysize(msg_map); i++) { + g_msgid_map.insert(std::make_pair(msg_map[i].msgstr, msg_map[i].msgid)); + } + g_msgid_inited = true; +} + +bool NwMenuGetNSStringWithFixupFunction::RunNWSync(base::ListValue* response, std::string* error) { + if (!g_msgid_inited) InitMsgIDMap(); + std::string msgstr; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &msgstr)); + MsgIDMap::iterator it = g_msgid_map.find(msgstr); + if (it != g_msgid_map.end()) { + int msgid = it->second; + response->AppendString(base::SysNSStringToUTF16(l10n_util::GetNSStringWithFixup(msgid))); + return true; + } + return false; +} + +bool NwMenuGetNSStringFWithFixupFunction::RunNWSync(base::ListValue* response, std::string* error) { + if (!g_msgid_inited) InitMsgIDMap(); + std::string msgstr; + base::string16 appName; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &msgstr)); + EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &appName)); + MsgIDMap::iterator it = g_msgid_map.find(msgstr); + if (it != g_msgid_map.end()) { + int msgid = it->second; + response->AppendString(base::SysNSStringToUTF16(l10n_util::GetNSStringFWithFixup(msgid, appName))); + return true; + } + return false; +} + +} // namespace extensions diff --git a/src/resources/api_nw_menu.js b/src/resources/api_nw_menu.js index e33865e47e..3ae4f1efd5 100644 --- a/src/resources/api_nw_menu.js +++ b/src/resources/api_nw_menu.js @@ -52,6 +52,121 @@ Menu.prototype.popup = function(x, y) { nw.Object.callObjectMethod(this.id, 'Menu', 'Popup', [ x, y ]); } +Menu.prototype.createMacBuiltin = function (app_name, options) { + var appleMenu = nw.Menu.createMenu({type:'menubar'}), + options = options || {}; + + appleMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringFWithFixup("IDS_ABOUT_MAC", app_name), + selector: "orderFrontStandardAboutPanel:" + })); + appleMenu.append(nw.Menu.createItem({ + type: "separator" + })); + appleMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringFWithFixup("IDS_HIDE_APP_MAC", app_name), + selector: "hide:", + modifiers: "cmd", + key: "h" + })); + appleMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_HIDE_OTHERS_MAC"), + selector: "hideOtherApplications:", + key: "h", + modifiers: "cmd-alt" + })); + appleMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_SHOW_ALL_MAC"), + selector: "unhideAllApplications:", + })); + appleMenu.append(nw.Menu.createItem({ + type: "separator" + })); + appleMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringFWithFixup("IDS_EXIT_MAC", app_name), + selector: "closeAllWindowsQuit:", + modifiers: "cmd", + key: "q" + })); + this.append(nw.Menu.createItem({ label:'', submenu: appleMenu})); + + if (!options.hideEdit) { + var editMenu = nw.Menu.createMenu({type:'menubar'}); + editMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_EDIT_UNDO_MAC"), + selector: "undo:", + modifiers: "cmd", + key: "z" + })); + editMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_EDIT_REDO_MAC"), + selector: "redo:", + key: "z", + modifiers: "cmd-shift" + })); + editMenu.append(nw.Menu.createItem({ + type: "separator" + })); + editMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_CUT_MAC"), + selector: "cut:", + modifiers: "cmd", + key: "x" + })); + editMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_COPY_MAC"), + selector: "copy:", + modifiers: "cmd", + key: "c" + })); + editMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_PASTE_MAC"), + selector: "paste:", + modifiers: "cmd", + key: "v" + })); + editMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_EDIT_DELETE_MAC"), + selector: "delete:", + key: "" + })); + editMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_EDIT_SELECT_ALL_MAC"), + selector: "selectAll:", + modifiers: "cmd", + key: "a" + })); + this.append(nw.Menu.createItem({ label: nw.Menu.getNSStringWithFixup("IDS_EDIT_MENU_MAC"), + submenu: editMenu})); + } + + if (!options.hideWindow) { + var winMenu = nw.Menu.createMenu({type:'menubar'}); + winMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_MINIMIZE_WINDOW_MAC"), + selector: "performMiniaturize:", + modifiers: "cmd", + key: "m" + })); + winMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_CLOSE_WINDOW_MAC"), + selector: "performClose:", + modifiers: "cmd", + key: "w" + })); + winMenu.append(nw.Menu.createItem({ + type: "separator" + })); + winMenu.append(nw.Menu.createItem({ + label: nw.Menu.getNSStringWithFixup("IDS_ALL_WINDOWS_FRONT_MAC"), + selector: "arrangeInFront:", + })); + this.append(nw.Menu.createItem({ label: nw.Menu.getNSStringWithFixup("IDS_WINDOW_MENU_MAC"), + submenu: winMenu})); + } +} + + function MenuItem(id, option) { if (typeof option != 'object') throw new TypeError('Invalid option.'); @@ -236,7 +351,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { option.generatedId = id; var ret = new MenuItem(id, option); sendRequest.sendRequestSync('nw.Menu.createItem', [option], this.definition.parameters, {}); - messagingNatives.BindToGC(ret, nw.Menu.destroy.bind(undefined, id)); + messagingNatives.BindToGC(ret, nw.Menu.destroy.bind(undefined, id), -1); return ret; }); apiFunctions.setHandleRequest('destroy', function(id) { @@ -250,9 +365,15 @@ nw_binding.registerCustomHook(function(bindingsAPI) { option.generatedId = id; var ret = new Menu(id, option); sendRequest.sendRequestSync('nw.Object.create', [id, 'Menu', option], this.definition.parameters, {}); - messagingNatives.BindToGC(ret, nw.Menu.destroy.bind(undefined, id)); + messagingNatives.BindToGC(ret, nw.Menu.destroy.bind(undefined, id), -1); return ret; }); + apiFunctions.setHandleRequest('getNSStringWithFixup', function(msg) { + return sendRequest.sendRequestSync('nw.Menu.getNSStringWithFixup', [msg], this.definition.parameters, {})[0]; + }); + apiFunctions.setHandleRequest('getNSStringFWithFixup', function(msg, appName) { + return sendRequest.sendRequestSync('nw.Menu.getNSStringFWithFixup', [msg, appName], this.definition.parameters, {})[0]; + }); }); exports.binding = nw_binding.generate(); From 791f61ddf320a28252ea6482710e2e60c28d5165 Mon Sep 17 00:00:00 2001 From: Jefry Date: Wed, 14 Oct 2015 08:54:59 +0700 Subject: [PATCH 018/404] [Win Apis] implementation of 'setBadgeLabel', 'requestAttention', and 'setProgressBar' --- nw.gypi | 1 + src/api/nw_current_window_internal.idl | 3 + src/api/nw_window_api.cc | 227 ++++++++++++++++++++++++- src/api/nw_window_api.h | 40 +++++ src/api/nw_window_api_mac.mm | 131 ++++++++++++++ 5 files changed, 393 insertions(+), 9 deletions(-) create mode 100644 src/api/nw_window_api_mac.mm diff --git a/nw.gypi b/nw.gypi index d651216da1..840266749b 100644 --- a/nw.gypi +++ b/nw.gypi @@ -696,6 +696,7 @@ }], ['OS=="mac"', { 'sources': [ + 'src/api/nw_window_api_mac.mm', 'src/api/nw_menu_api_mac.mm', 'src/api/menuitem/menuitem_mac.mm', 'src/api/menu/menu_mac.mm', diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index 0a687fd9f3..be0472993c 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -14,6 +14,9 @@ namespace nw.currentWindowInternal { }; interface Functions { static void showDevTools(); + static void setBadgeLabel(DOMString badge); + static void requestAttention(long count); + static void setProgressBar(double progress); static void capturePageInternal(optional CapturePageOptions options, optional CapturePageCallback callback); static void clearMenu(); static void setMenu(long id); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 3302d78c4b..cc10162021 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -2,6 +2,7 @@ #include "base/base64.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/browser/render_widget_host.h" #include "chrome/browser/devtools/devtools_window.h" #include "chrome/browser/extensions/devtools_util.h" @@ -21,10 +22,19 @@ #include "ui/gfx/screen.h" #if defined(OS_WIN) +#include +#include + +#include "ui/gfx/canvas.h" +#include "ui/gfx/icon_util.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/platform_font.h" +#include "ui/gfx/win/dpi.h" #include "ui/views/win/hwnd_util.h" #endif #if defined(OS_LINUX) +#include "chrome/browser/ui/libgtk2ui/gtk2_ui.h" #include "content/nw/src/browser/menubar_view.h" #include "content/nw/src/browser/browser_view_layout.h" using nw::BrowserViewLayout; @@ -42,6 +52,22 @@ using nw::Menu; #if defined(OS_LINUX) using nw::MenuBarView; +static void SetDeskopEnvironment() { + static bool runOnce = false; + if (runOnce) return; + runOnce = true; + + scoped_ptr env(base::Environment::Create()); + std::string name; + //if (env->GetVar("CHROME_DESKTOP", &name) && !name.empty()) + // return; + + if (!env->GetVar("NW_DESKTOP", &name) || name.empty()) + name = "nw.desktop"; + + env->SetVar("CHROME_DESKTOP", name); +} + #endif namespace extensions { @@ -52,6 +78,28 @@ const char kNoAssociatedAppWindow[] = "associated app window."; } +static AppWindow* getAppWindow(AsyncExtensionFunction* func) { + AppWindowRegistry* registry = AppWindowRegistry::Get(func->browser_context()); + DCHECK(registry); + content::WebContents* web_contents = func->GetSenderWebContents(); + if (!web_contents) { + // No need to set an error, since we won't return to the caller anyway if + // there's no RVH. + return NULL; + } + return registry->GetAppWindowForWebContents(web_contents); +} + +#ifdef OS_WIN +static HWND getHWND(AppWindow* window) { + if (window == NULL) return NULL; + native_app_window::NativeAppWindowViews* native_app_window_views = + static_cast( + window->GetBaseWindow()); + return views::HWNDForWidget(native_app_window_views->widget()->GetTopLevelWidget()); +} +#endif + NwCurrentWindowInternalShowDevToolsFunction::NwCurrentWindowInternalShowDevToolsFunction() { } @@ -224,15 +272,7 @@ NwCurrentWindowInternalSetMenuFunction::~NwCurrentWindowInternalSetMenuFunction( bool NwCurrentWindowInternalSetMenuFunction::RunAsync() { int id = 0; EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &id)); - - AppWindowRegistry* registry = AppWindowRegistry::Get(browser_context()); - DCHECK(registry); - content::WebContents* web_contents = GetSenderWebContents(); - if (!web_contents) - // No need to set an error, since we won't return to the caller anyway if - // there's no RVH. - return false; - AppWindow* window = registry->GetAppWindowForWebContents(web_contents); + AppWindow* window = getAppWindow(this); if (!window) { error_ = kNoAssociatedAppWindow; return false; @@ -277,5 +317,174 @@ bool NwCurrentWindowInternalSetMenuFunction::RunAsync() { //FIXME menu->UpdateKeys( native_app_window_views->widget()->GetFocusManager() ); return true; } + +#if defined(OS_WIN) +static HICON createBadgeIcon(const HWND hWnd, const TCHAR *value, const int sizeX, const int sizeY) { + // canvas for the overlay icon + gfx::Canvas canvas(gfx::Size(sizeX, sizeY), 1, false); + + // drawing red circle + SkPaint paint; + paint.setColor(SK_ColorRED); + canvas.DrawCircle(gfx::Point(sizeX / 2, sizeY / 2), sizeX / 2, paint); + + // drawing the text + gfx::PlatformFont *platform_font = gfx::PlatformFont::CreateDefault(); + const int fontSize = sizeY * 0.65f; + gfx::Font font(platform_font->GetFontName(), fontSize); + platform_font->Release(); + platform_font = NULL; + const int yMargin = (sizeY - fontSize) / 2; + canvas.DrawStringRectWithFlags(value, gfx::FontList(font), SK_ColorWHITE, gfx::Rect(sizeX, fontSize + yMargin + 1), gfx::Canvas::TEXT_ALIGN_CENTER); + + // return the canvas as windows native icon handle + return IconUtil::CreateHICONFromSkBitmap(canvas.ExtractImageRep().sk_bitmap()); +} +#endif + +#ifndef OS_MACOSX +bool NwCurrentWindowInternalSetBadgeLabelFunction::RunAsync() { + EXTENSION_FUNCTION_VALIDATE(args_); + std::string badge; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &badge)); +#if defined(OS_WIN) + base::win::ScopedComPtr taskbar; + HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL, + CLSCTX_INPROC_SERVER); + + if (FAILED(result)) { + error_ = "Failed creating a TaskbarList3 object: "; + LOG(ERROR) << error_ << result; + return false; + } + + result = taskbar->HrInit(); + if (FAILED(result)) { + error_ = "Failed initializing an ITaskbarList3 interface."; + LOG(ERROR) << error_; + return false; + } + + HICON icon = NULL; + HWND hWnd = getHWND(getAppWindow(this)); + if (hWnd == NULL) { + error_ = kNoAssociatedAppWindow; + LOG(ERROR) << error_; + return false; + } + const float scale = gfx::GetDPIScale(); + if (badge.size()) + icon = createBadgeIcon(hWnd, base::UTF8ToUTF16(badge).c_str(), 16 * scale, 16 * scale); + + taskbar->SetOverlayIcon(hWnd, icon, L"Status"); + DestroyIcon(icon); +#elif defined(OS_LINUX) + views::LinuxUI* linuxUI = views::LinuxUI::instance(); + if (linuxUI == NULL) { + error_ = "LinuxUI::instance() is NULL"; + return false; + } + SetDeskopEnvironment(); + linuxUI->SetDownloadCount(atoi(badge.c_str())); +#else + error_ = "NwCurrentWindowInternalSetBadgeLabelFunction NOT Implemented" + NOTIMPLEMENTED() << error_; + return false; +#endif + return true; +} + +bool NwCurrentWindowInternalRequestAttentionFunction::RunAsync() { + EXTENSION_FUNCTION_VALIDATE(args_); + int count; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &count)); +#if defined(OS_WIN) + FLASHWINFO fwi; + fwi.cbSize = sizeof(fwi); + fwi.hwnd = getHWND(getAppWindow(this)); + if (fwi.hwnd == NULL) { + error_ = kNoAssociatedAppWindow; + LOG(ERROR) << error_; + return false; + } + if (count != 0) { + fwi.dwFlags = FLASHW_ALL; + fwi.uCount = count < 0 ? 4 : count; + fwi.dwTimeout = 0; + } + else { + fwi.dwFlags = FLASHW_STOP; + } + FlashWindowEx(&fwi); +#elif defined(OS_LINUX) + AppWindow* window = getAppWindow(this); + if (!window) { + error_ = kNoAssociatedAppWindow; + return false; + } + window->GetBaseWindow()->FlashFrame(count); +#else + error_ = "NwCurrentWindowInternalRequestAttentionFunction NOT Implemented" + NOTIMPLEMENTED() << error_; + return false; +#endif + return true; +} + +bool NwCurrentWindowInternalSetProgressBarFunction::RunAsync() { + EXTENSION_FUNCTION_VALIDATE(args_); + double progress; + EXTENSION_FUNCTION_VALIDATE(args_->GetDouble(0, &progress)); +#if defined(OS_WIN) + base::win::ScopedComPtr taskbar; + HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL, + CLSCTX_INPROC_SERVER); + + if (FAILED(result)) { + error_ = "Failed creating a TaskbarList3 object: "; + LOG(ERROR) << error_ << result; + return false; + } + + result = taskbar->HrInit(); + if (FAILED(result)) { + error_ = "Failed initializing an ITaskbarList3 interface."; + LOG(ERROR) << error_; + return false; + } + + HWND hWnd = getHWND(getAppWindow(this)); + if (hWnd == NULL) { + error_ = kNoAssociatedAppWindow; + LOG(ERROR) << error_; + return false; + } + TBPFLAG tbpFlag = TBPF_NOPROGRESS; + + if (progress > 1) { + tbpFlag = TBPF_INDETERMINATE; + } + else if (progress >= 0) { + tbpFlag = TBPF_NORMAL; + taskbar->SetProgressValue(hWnd, progress * 100, 100); + } + + taskbar->SetProgressState(hWnd, tbpFlag); +#elif defined(OS_LINUX) + views::LinuxUI* linuxUI = views::LinuxUI::instance(); + if (linuxUI == NULL) { + error_ = "LinuxUI::instance() is NULL"; + return false; + } + SetDeskopEnvironment(); + linuxUI->SetProgressFraction(progress); +#else + error_ = "NwCurrentWindowInternalSetProgressBarFunction NOT Implemented" + NOTIMPLEMENTED() << error_; + return false; +#endif + return true; +} +#endif } // namespace extensions diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 794319bf47..d292415fa5 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -94,5 +94,45 @@ class NwCurrentWindowInternalSetMenuFunction : public AsyncExtensionFunction { DISALLOW_COPY_AND_ASSIGN(NwCurrentWindowInternalSetMenuFunction); }; +class NwCurrentWindowInternalSetBadgeLabelFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalSetBadgeLabelFunction(){} + + protected: + ~NwCurrentWindowInternalSetBadgeLabelFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.setBadgeLabel", UNKNOWN) +}; + +class NwCurrentWindowInternalRequestAttentionFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalRequestAttentionFunction(){} + + protected: + ~NwCurrentWindowInternalRequestAttentionFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.requestAttention", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwCurrentWindowInternalRequestAttentionFunction); +}; + +class NwCurrentWindowInternalSetProgressBarFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalSetProgressBarFunction(){} + + protected: + ~NwCurrentWindowInternalSetProgressBarFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.setProgressBar", UNKNOWN) + private: + void Callback(); +}; + } // namespace extensions #endif diff --git a/src/api/nw_window_api_mac.mm b/src/api/nw_window_api_mac.mm new file mode 100644 index 0000000000..7fbc17f2c2 --- /dev/null +++ b/src/api/nw_window_api_mac.mm @@ -0,0 +1,131 @@ +#include "content/nw/src/api/nw_window_api.h" + +#include "base/strings/sys_string_conversions.h" + +#import +#import +#import +#import +#import +#import + +@interface NWProgressBar : NSProgressIndicator +@end + +@implementation NWProgressBar + +// override the drawing, so we can give color to the progress bar +- (void)drawRect:(NSRect)dirtyRect { + + [super drawRect:dirtyRect]; + + if(self.style != NSProgressIndicatorBarStyle) + return; + + NSRect sliceRect, remainderRect; + double progressFraction = ([self doubleValue] - [self minValue]) / + ([self maxValue] - [self minValue]); + + NSDivideRect(dirtyRect, &sliceRect, &remainderRect, + NSWidth(dirtyRect) * progressFraction, NSMinXEdge); + + const int kProgressBarCornerRadius = 3; + + if (progressFraction == 0.0) + return; + + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:sliceRect + xRadius:kProgressBarCornerRadius + yRadius:kProgressBarCornerRadius]; + // blue color with alpha blend 0.5 + [[NSColor colorWithCalibratedRed:0 green:0 blue:1 alpha:0.5] set]; + [path fill]; +} +@end + +namespace extensions { + +bool NwCurrentWindowInternalSetBadgeLabelFunction::RunAsync() { + EXTENSION_FUNCTION_VALIDATE(args_); + std::string badge; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &badge)); + [[NSApp dockTile] setBadgeLabel:base::SysUTF8ToNSString(badge)]; + return true; +} + +bool NwCurrentWindowInternalRequestAttentionFunction::RunAsync() { + EXTENSION_FUNCTION_VALIDATE(args_); + int count; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &count)); + + //static map from web_content to its attention_request_id + static base::hash_map attention_request_id; + content::WebContents* web_content = GetSenderWebContents(); + + if (count != 0) { + attention_request_id[web_content] = count < 0 ? [NSApp requestUserAttention:NSInformationalRequest] : + [NSApp requestUserAttention:NSCriticalRequest]; + } else { + [NSApp cancelUserAttentionRequest:attention_request_id[web_content]]; + attention_request_id.erase(web_content); + } + return true; +} + +bool NwCurrentWindowInternalSetProgressBarFunction::RunAsync() { + EXTENSION_FUNCTION_VALIDATE(args_); + double progress; + EXTENSION_FUNCTION_VALIDATE(args_->GetDouble(0, &progress)); + NSDockTile *dockTile = [NSApp dockTile]; + NWProgressBar *progressIndicator = NULL; + + if (dockTile.contentView == NULL && progress >= 0) { + + // create image view to draw application icon + NSImageView *iv = [[NSImageView alloc] init]; + [iv setImage:[NSApp applicationIconImage]]; + + // set dockTile content view to app icon + [dockTile setContentView:iv]; + + progressIndicator = [[NWProgressBar alloc] + initWithFrame:NSMakeRect(0.0f, 0.0f, dockTile.size.width, 15.)]; + + [progressIndicator setStyle:NSProgressIndicatorBarStyle]; + + [progressIndicator setBezeled:YES]; + [progressIndicator setMinValue:0]; + [progressIndicator setMaxValue:1]; + [progressIndicator setHidden:NO]; + [progressIndicator setUsesThreadedAnimation:false]; + + // add progress indicator to image view + [iv addSubview:progressIndicator]; + } + + progressIndicator = (NWProgressBar*)[dockTile.contentView.subviews objectAtIndex:0]; + + if(progress >= 0) { + [progressIndicator setIndeterminate:progress > 1]; + if(progress > 1) { + // progress Indicator is indeterminate + // [progressIndicator startAnimation:window_]; + [progressIndicator setDoubleValue:1]; + } + else { + //[progressIndicator stopAnimation:window_]; + [progressIndicator setDoubleValue:progress]; + } + } + else { + // progress indicator < 0, destroy it + [[dockTile.contentView.subviews objectAtIndex:0]release]; + [dockTile.contentView release]; + dockTile.contentView = NULL; + } + + [dockTile display]; + return true; +} + +} // namespace extensions From ec1d2f1ade89a445233583f4158e28cf188fda94 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 21 Oct 2015 14:03:02 +0800 Subject: [PATCH 019/404] [test] wait for new window --- test/remoting/new-win-policy/test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/remoting/new-win-policy/test.py b/test/remoting/new-win-policy/test.py index 6b19512085..293e5c2983 100644 --- a/test/remoting/new-win-policy/test.py +++ b/test/remoting/new-win-policy/test.py @@ -13,7 +13,8 @@ print link.text link.click() #raw_input("Press Enter to continue...") - #print driver.window_handles + time.sleep(2) + print driver.window_handles driver.switch_to_window(driver.window_handles[-1]) output = driver.find_element_by_id('output') assert(output.get_attribute('value') == '["inject-js-start","body-start","inject-js-end","onload-dom"]') From 4b6b5a5a3f36917eb45e29601647d149fa7bcadb Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 21 Oct 2015 14:10:37 +0800 Subject: [PATCH 020/404] [test] window-eval --- test/remoting/window-eval/iframe.html | 14 +++++++++++++ test/remoting/window-eval/index.html | 29 ++++++++++++++++++++++++++ test/remoting/window-eval/package.json | 5 +++++ test/remoting/window-eval/test.py | 17 +++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 test/remoting/window-eval/iframe.html create mode 100644 test/remoting/window-eval/index.html create mode 100644 test/remoting/window-eval/package.json create mode 100644 test/remoting/window-eval/test.py diff --git a/test/remoting/window-eval/iframe.html b/test/remoting/window-eval/iframe.html new file mode 100644 index 0000000000..ab1d2c0ffa --- /dev/null +++ b/test/remoting/window-eval/iframe.html @@ -0,0 +1,14 @@ + + + + + test + + +

iframe

+ + + + \ No newline at end of file diff --git a/test/remoting/window-eval/index.html b/test/remoting/window-eval/index.html new file mode 100644 index 0000000000..64272cf841 --- /dev/null +++ b/test/remoting/window-eval/index.html @@ -0,0 +1,29 @@ + + + + + window.eval test + + +

window.eval test

+ + + + + diff --git a/test/remoting/window-eval/package.json b/test/remoting/window-eval/package.json new file mode 100644 index 0000000000..a33ab28774 --- /dev/null +++ b/test/remoting/window-eval/package.json @@ -0,0 +1,5 @@ +{ + "name":"nw_1403254112", + "main":"index.html", + "dependencies":{} +} \ No newline at end of file diff --git a/test/remoting/window-eval/test.py b/test/remoting/window-eval/test.py new file mode 100644 index 0000000000..596e8a00e7 --- /dev/null +++ b/test/remoting/window-eval/test.py @@ -0,0 +1,17 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() From 5ef29d52a61621d64aa3667da9f9663da5408895 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 22 Oct 2015 13:12:46 +0800 Subject: [PATCH 021/404] [test] remote/tar --- test/remoting/tar/index.html | 62 ++ test/remoting/tar/node_modules/tar/.npmignore | 5 + .../remoting/tar/node_modules/tar/.travis.yml | 4 + test/remoting/tar/node_modules/tar/LICENSE | 12 + test/remoting/tar/node_modules/tar/README.md | 50 + .../node_modules/tar/examples/extracter.js | 19 + .../tar/node_modules/tar/examples/packer.js | 24 + .../tar/node_modules/tar/examples/reader.js | 36 + .../tar/node_modules/tar/lib/buffer-entry.js | 30 + .../tar/node_modules/tar/lib/entry-writer.js | 169 ++++ .../tar/node_modules/tar/lib/entry.js | 220 ++++ .../tar/lib/extended-header-writer.js | 191 ++++ .../node_modules/tar/lib/extended-header.js | 140 +++ .../tar/node_modules/tar/lib/extract.js | 94 ++ .../tar/lib/global-header-writer.js | 14 + .../tar/node_modules/tar/lib/header.js | 385 +++++++ .../remoting/tar/node_modules/tar/lib/pack.js | 236 +++++ .../tar/node_modules/tar/lib/parse.js | 275 +++++ .../tar/node_modules/block-stream/LICENCE | 25 + .../tar/node_modules/block-stream/LICENSE | 15 + .../tar/node_modules/block-stream/README.md | 14 + .../block-stream/bench/block-stream-pause.js | 70 ++ .../block-stream/bench/block-stream.js | 68 ++ .../block-stream/bench/dropper-pause.js | 70 ++ .../block-stream/bench/dropper.js | 68 ++ .../node_modules/block-stream/block-stream.js | 209 ++++ .../node_modules/block-stream/package.json | 55 + .../node_modules/block-stream/test/basic.js | 27 + .../block-stream/test/nopad-thorough.js | 68 ++ .../node_modules/block-stream/test/nopad.js | 57 ++ .../block-stream/test/pause-resume.js | 73 ++ .../block-stream/test/thorough.js | 68 ++ .../block-stream/test/two-stream.js | 59 ++ .../tar/node_modules/fstream/.npmignore | 5 + .../tar/node_modules/fstream/.travis.yml | 9 + .../tar/node_modules/fstream/LICENSE | 15 + .../tar/node_modules/fstream/README.md | 76 ++ .../fstream/examples/filter-pipe.js | 134 +++ .../tar/node_modules/fstream/examples/pipe.js | 118 +++ .../node_modules/fstream/examples/reader.js | 68 ++ .../fstream/examples/symlink-write.js | 27 + .../tar/node_modules/fstream/fstream.js | 35 + .../tar/node_modules/fstream/lib/abstract.js | 85 ++ .../tar/node_modules/fstream/lib/collect.js | 68 ++ .../node_modules/fstream/lib/dir-reader.js | 252 +++++ .../node_modules/fstream/lib/dir-writer.js | 174 ++++ .../node_modules/fstream/lib/file-reader.js | 150 +++ .../node_modules/fstream/lib/file-writer.js | 107 ++ .../tar/node_modules/fstream/lib/get-type.js | 33 + .../node_modules/fstream/lib/link-reader.js | 53 + .../node_modules/fstream/lib/link-writer.js | 95 ++ .../node_modules/fstream/lib/proxy-reader.js | 95 ++ .../node_modules/fstream/lib/proxy-writer.js | 111 ++ .../tar/node_modules/fstream/lib/reader.js | 255 +++++ .../node_modules/fstream/lib/socket-reader.js | 36 + .../tar/node_modules/fstream/lib/writer.js | 390 +++++++ .../fstream/node_modules/.bin/mkdirp | 1 + .../fstream/node_modules/.bin/rimraf | 1 + .../fstream/node_modules/graceful-fs/LICENSE | 15 + .../node_modules/graceful-fs/README.md | 36 + .../fstream/node_modules/graceful-fs/fs.js | 21 + .../node_modules/graceful-fs/graceful-fs.js | 251 +++++ .../graceful-fs/legacy-streams.js | 118 +++ .../node_modules/graceful-fs/package.json | 73 ++ .../node_modules/graceful-fs/polyfills.js | 252 +++++ .../fstream/node_modules/mkdirp/.travis.yml | 8 + .../fstream/node_modules/mkdirp/LICENSE | 21 + .../fstream/node_modules/mkdirp/bin/cmd.js | 33 + .../fstream/node_modules/mkdirp/bin/usage.txt | 12 + .../node_modules/mkdirp/examples/pow.js | 6 + .../fstream/node_modules/mkdirp/index.js | 98 ++ .../mkdirp/node_modules/minimist/.travis.yml | 4 + .../mkdirp/node_modules/minimist/LICENSE | 18 + .../node_modules/minimist/example/parse.js | 2 + .../mkdirp/node_modules/minimist/index.js | 187 ++++ .../mkdirp/node_modules/minimist/package.json | 67 ++ .../node_modules/minimist/readme.markdown | 73 ++ .../mkdirp/node_modules/minimist/test/dash.js | 24 + .../minimist/test/default_bool.js | 20 + .../node_modules/minimist/test/dotted.js | 16 + .../mkdirp/node_modules/minimist/test/long.js | 31 + .../node_modules/minimist/test/parse.js | 318 ++++++ .../minimist/test/parse_modified.js | 9 + .../node_modules/minimist/test/short.js | 67 ++ .../node_modules/minimist/test/whitespace.js | 8 + .../fstream/node_modules/mkdirp/package.json | 60 ++ .../node_modules/mkdirp/readme.markdown | 100 ++ .../fstream/node_modules/mkdirp/test/chmod.js | 41 + .../node_modules/mkdirp/test/clobber.js | 38 + .../node_modules/mkdirp/test/mkdirp.js | 28 + .../node_modules/mkdirp/test/opts_fs.js | 29 + .../node_modules/mkdirp/test/opts_fs_sync.js | 27 + .../fstream/node_modules/mkdirp/test/perm.js | 32 + .../node_modules/mkdirp/test/perm_sync.js | 36 + .../fstream/node_modules/mkdirp/test/race.js | 37 + .../fstream/node_modules/mkdirp/test/rel.js | 32 + .../node_modules/mkdirp/test/return.js | 25 + .../node_modules/mkdirp/test/return_sync.js | 24 + .../fstream/node_modules/mkdirp/test/root.js | 19 + .../fstream/node_modules/mkdirp/test/sync.js | 32 + .../fstream/node_modules/mkdirp/test/umask.js | 28 + .../node_modules/mkdirp/test/umask_sync.js | 32 + .../fstream/node_modules/rimraf/LICENSE | 15 + .../fstream/node_modules/rimraf/README.md | 38 + .../fstream/node_modules/rimraf/bin.js | 40 + .../rimraf/node_modules/glob/LICENSE | 15 + .../rimraf/node_modules/glob/README.md | 377 +++++++ .../rimraf/node_modules/glob/common.js | 245 +++++ .../rimraf/node_modules/glob/glob.js | 752 ++++++++++++++ .../glob/node_modules/inflight/.eslintrc | 17 + .../glob/node_modules/inflight/LICENSE | 15 + .../glob/node_modules/inflight/README.md | 37 + .../glob/node_modules/inflight/inflight.js | 44 + .../inflight/node_modules/wrappy/LICENSE | 15 + .../inflight/node_modules/wrappy/README.md | 36 + .../inflight/node_modules/wrappy/package.json | 52 + .../node_modules/wrappy/test/basic.js | 51 + .../inflight/node_modules/wrappy/wrappy.js | 33 + .../glob/node_modules/inflight/package.json | 61 ++ .../glob/node_modules/inflight/test.js | 97 ++ .../glob/node_modules/minimatch/LICENSE | 15 + .../glob/node_modules/minimatch/README.md | 216 ++++ .../glob/node_modules/minimatch/minimatch.js | 912 +++++++++++++++++ .../node_modules/brace-expansion/.npmignore | 3 + .../node_modules/brace-expansion/README.md | 122 +++ .../node_modules/brace-expansion/example.js | 8 + .../node_modules/brace-expansion/index.js | 191 ++++ .../node_modules/balanced-match/.npmignore | 2 + .../node_modules/balanced-match/.travis.yml | 4 + .../node_modules/balanced-match/Makefile | 6 + .../node_modules/balanced-match/README.md | 80 ++ .../node_modules/balanced-match/example.js | 5 + .../node_modules/balanced-match/index.js | 38 + .../node_modules/balanced-match/package.json | 73 ++ .../balanced-match/test/balanced.js | 56 ++ .../node_modules/concat-map/.travis.yml | 4 + .../node_modules/concat-map/LICENSE | 18 + .../node_modules/concat-map/README.markdown | 62 ++ .../node_modules/concat-map/example/map.js | 6 + .../node_modules/concat-map/index.js | 13 + .../node_modules/concat-map/package.json | 83 ++ .../node_modules/concat-map/test/map.js | 39 + .../node_modules/brace-expansion/package.json | 75 ++ .../glob/node_modules/minimatch/package.json | 60 ++ .../glob/node_modules/once/LICENSE | 15 + .../glob/node_modules/once/README.md | 51 + .../once/node_modules/wrappy/LICENSE | 15 + .../once/node_modules/wrappy/README.md | 36 + .../once/node_modules/wrappy/package.json | 52 + .../once/node_modules/wrappy/test/basic.js | 51 + .../once/node_modules/wrappy/wrappy.js | 33 + .../glob/node_modules/once/once.js | 21 + .../glob/node_modules/once/package.json | 60 ++ .../glob/node_modules/once/test/once.js | 23 + .../node_modules/path-is-absolute/index.js | 20 + .../node_modules/path-is-absolute/license | 21 + .../path-is-absolute/package.json | 70 ++ .../node_modules/path-is-absolute/readme.md | 51 + .../rimraf/node_modules/glob/package.json | 73 ++ .../rimraf/node_modules/glob/sync.js | 460 +++++++++ .../fstream/node_modules/rimraf/package.json | 62 ++ .../fstream/node_modules/rimraf/rimraf.js | 333 ++++++ .../tar/node_modules/fstream/package.json | 71 ++ .../tar/node_modules/inherits/LICENSE | 16 + .../tar/node_modules/inherits/README.md | 42 + .../tar/node_modules/inherits/inherits.js | 1 + .../node_modules/inherits/inherits_browser.js | 23 + .../tar/node_modules/inherits/package.json | 50 + .../tar/node_modules/inherits/test.js | 25 + .../tar/node_modules/tar/package.json | 69 ++ test/remoting/tar/node_modules/tar/tar.js | 173 ++++ .../tar/test/00-setup-fixtures.js | 53 + .../tar/test/cb-never-called-1.0.1.tgz | Bin 0 -> 4096 bytes .../tar/test/dir-normalization.js | 177 ++++ .../tar/test/dir-normalization.tar | Bin 0 -> 4608 bytes .../node_modules/tar/test/error-on-broken.js | 33 + .../tar/node_modules/tar/test/extract-move.js | 132 +++ .../tar/node_modules/tar/test/extract.js | 367 +++++++ .../tar/node_modules/tar/test/fixtures.tgz | Bin 0 -> 19352 bytes .../tar/node_modules/tar/test/header.js | 183 ++++ .../tar/test/pack-no-proprietary.js | 886 ++++++++++++++++ .../tar/node_modules/tar/test/pack.js | 952 ++++++++++++++++++ .../node_modules/tar/test/parse-discard.js | 29 + .../tar/node_modules/tar/test/parse.js | 359 +++++++ .../tar/node_modules/tar/test/zz-cleanup.js | 20 + test/remoting/tar/package.json | 5 + test/remoting/tar/test.py | 23 + test/remoting/tar/test.tar | Bin 0 -> 716800 bytes 188 files changed, 16880 insertions(+) create mode 100644 test/remoting/tar/index.html create mode 100644 test/remoting/tar/node_modules/tar/.npmignore create mode 100644 test/remoting/tar/node_modules/tar/.travis.yml create mode 100644 test/remoting/tar/node_modules/tar/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/README.md create mode 100644 test/remoting/tar/node_modules/tar/examples/extracter.js create mode 100644 test/remoting/tar/node_modules/tar/examples/packer.js create mode 100644 test/remoting/tar/node_modules/tar/examples/reader.js create mode 100644 test/remoting/tar/node_modules/tar/lib/buffer-entry.js create mode 100644 test/remoting/tar/node_modules/tar/lib/entry-writer.js create mode 100644 test/remoting/tar/node_modules/tar/lib/entry.js create mode 100644 test/remoting/tar/node_modules/tar/lib/extended-header-writer.js create mode 100644 test/remoting/tar/node_modules/tar/lib/extended-header.js create mode 100644 test/remoting/tar/node_modules/tar/lib/extract.js create mode 100644 test/remoting/tar/node_modules/tar/lib/global-header-writer.js create mode 100644 test/remoting/tar/node_modules/tar/lib/header.js create mode 100644 test/remoting/tar/node_modules/tar/lib/pack.js create mode 100644 test/remoting/tar/node_modules/tar/lib/parse.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/LICENCE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/block-stream-pause.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/block-stream.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/dropper-pause.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/dropper.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/block-stream.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/test/basic.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/test/nopad-thorough.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/test/nopad.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/test/pause-resume.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/test/thorough.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/block-stream/test/two-stream.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/.npmignore create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/.travis.yml create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/examples/filter-pipe.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/examples/pipe.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/examples/reader.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/examples/symlink-write.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/fstream.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/abstract.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/collect.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/dir-reader.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/dir-writer.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/file-reader.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/file-writer.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/get-type.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/link-reader.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/link-writer.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/proxy-reader.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/proxy-writer.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/reader.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/socket-reader.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/lib/writer.js create mode 120000 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/.bin/mkdirp create mode 120000 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/.bin/rimraf create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/fs.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/graceful-fs.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/legacy-streams.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/polyfills.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/.travis.yml create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/LICENSE create mode 100755 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/bin/cmd.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/bin/usage.txt create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/examples/pow.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/index.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/.travis.yml create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/example/parse.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/index.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/readme.markdown create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/dash.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/default_bool.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/dotted.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/long.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/parse.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/parse_modified.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/short.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/whitespace.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/readme.markdown create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/chmod.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/clobber.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/mkdirp.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/opts_fs.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/opts_fs_sync.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/perm.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/perm_sync.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/race.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/rel.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/return.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/return_sync.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/root.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/sync.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/umask.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/umask_sync.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/README.md create mode 100755 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/bin.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/common.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/glob.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/.eslintrc create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/inflight.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/test/basic.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/wrappy.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/test.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/minimatch.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/.npmignore create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/example.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/index.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/.npmignore create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/.travis.yml create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/Makefile create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/example.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/index.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/test/balanced.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/.travis.yml create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/README.markdown create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/example/map.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/index.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/test/map.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/test/basic.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/wrappy.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/once.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/test/once.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/index.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/license create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/readme.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/sync.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/rimraf.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/fstream/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/inherits/LICENSE create mode 100644 test/remoting/tar/node_modules/tar/node_modules/inherits/README.md create mode 100644 test/remoting/tar/node_modules/tar/node_modules/inherits/inherits.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/inherits/inherits_browser.js create mode 100644 test/remoting/tar/node_modules/tar/node_modules/inherits/package.json create mode 100644 test/remoting/tar/node_modules/tar/node_modules/inherits/test.js create mode 100644 test/remoting/tar/node_modules/tar/package.json create mode 100644 test/remoting/tar/node_modules/tar/tar.js create mode 100644 test/remoting/tar/node_modules/tar/test/00-setup-fixtures.js create mode 100644 test/remoting/tar/node_modules/tar/test/cb-never-called-1.0.1.tgz create mode 100644 test/remoting/tar/node_modules/tar/test/dir-normalization.js create mode 100644 test/remoting/tar/node_modules/tar/test/dir-normalization.tar create mode 100644 test/remoting/tar/node_modules/tar/test/error-on-broken.js create mode 100644 test/remoting/tar/node_modules/tar/test/extract-move.js create mode 100644 test/remoting/tar/node_modules/tar/test/extract.js create mode 100644 test/remoting/tar/node_modules/tar/test/fixtures.tgz create mode 100644 test/remoting/tar/node_modules/tar/test/header.js create mode 100644 test/remoting/tar/node_modules/tar/test/pack-no-proprietary.js create mode 100644 test/remoting/tar/node_modules/tar/test/pack.js create mode 100644 test/remoting/tar/node_modules/tar/test/parse-discard.js create mode 100644 test/remoting/tar/node_modules/tar/test/parse.js create mode 100644 test/remoting/tar/node_modules/tar/test/zz-cleanup.js create mode 100644 test/remoting/tar/package.json create mode 100644 test/remoting/tar/test.py create mode 100644 test/remoting/tar/test.tar diff --git a/test/remoting/tar/index.html b/test/remoting/tar/index.html new file mode 100644 index 0000000000..fbd5d8b0d1 --- /dev/null +++ b/test/remoting/tar/index.html @@ -0,0 +1,62 @@ + + + + + tar test + + +

tar test

+ + + + diff --git a/test/remoting/tar/node_modules/tar/.npmignore b/test/remoting/tar/node_modules/tar/.npmignore new file mode 100644 index 0000000000..c167ad5b1c --- /dev/null +++ b/test/remoting/tar/node_modules/tar/.npmignore @@ -0,0 +1,5 @@ +.*.swp +node_modules +examples/extract/ +test/tmp/ +test/fixtures/ diff --git a/test/remoting/tar/node_modules/tar/.travis.yml b/test/remoting/tar/node_modules/tar/.travis.yml new file mode 100644 index 0000000000..fca8ef0194 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.10 + - 0.11 diff --git a/test/remoting/tar/node_modules/tar/LICENSE b/test/remoting/tar/node_modules/tar/LICENSE new file mode 100644 index 0000000000..019b7e40ea --- /dev/null +++ b/test/remoting/tar/node_modules/tar/LICENSE @@ -0,0 +1,12 @@ +The ISC License +Copyright (c) Isaac Z. Schlueter and Contributors +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/README.md b/test/remoting/tar/node_modules/tar/README.md new file mode 100644 index 0000000000..cfda2ac180 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/README.md @@ -0,0 +1,50 @@ +# node-tar + +Tar for Node.js. + +[![NPM](https://nodei.co/npm/tar.png)](https://nodei.co/npm/tar/) + +## API + +See `examples/` for usage examples. + +### var tar = require('tar') + +Returns an object with `.Pack`, `.Extract` and `.Parse` methods. + +### tar.Pack([properties]) + +Returns a through stream. Use +[fstream](https://npmjs.org/package/fstream) to write files into the +pack stream and you will receive tar archive data from the pack +stream. + +This only works with directories, it does not work with individual files. + +The optional `properties` object are used to set properties in the tar +'Global Extended Header'. If the `fromBase` property is set to true, +the tar will contain files relative to the path passed, and not with +the path included. + +### tar.Extract([options]) + +Returns a through stream. Write tar data to the stream and the files +in the tarball will be extracted onto the filesystem. + +`options` can be: + +```js +{ + path: '/path/to/extract/tar/into', + strip: 0, // how many path segments to strip from the root when extracting +} +``` + +`options` also get passed to the `fstream.Writer` instance that `tar` +uses internally. + +### tar.Parse() + +Returns a writable stream. Write tar data to it and it will emit +`entry` events for each entry parsed from the tarball. This is used by +`tar.Extract`. diff --git a/test/remoting/tar/node_modules/tar/examples/extracter.js b/test/remoting/tar/node_modules/tar/examples/extracter.js new file mode 100644 index 0000000000..f6253a72c5 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/examples/extracter.js @@ -0,0 +1,19 @@ +var tar = require("../tar.js") + , fs = require("fs") + + +function onError(err) { + console.error('An error occurred:', err) +} + +function onEnd() { + console.log('Extracted!') +} + +var extractor = tar.Extract({path: __dirname + "/extract"}) + .on('error', onError) + .on('end', onEnd); + +fs.createReadStream(__dirname + "/../test/fixtures/c.tar") + .on('error', onError) + .pipe(extractor); diff --git a/test/remoting/tar/node_modules/tar/examples/packer.js b/test/remoting/tar/node_modules/tar/examples/packer.js new file mode 100644 index 0000000000..039969ce30 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/examples/packer.js @@ -0,0 +1,24 @@ +var tar = require("../tar.js") + , fstream = require("fstream") + , fs = require("fs") + +var dirDest = fs.createWriteStream('dir.tar') + + +function onError(err) { + console.error('An error occurred:', err) +} + +function onEnd() { + console.log('Packed!') +} + +var packer = tar.Pack({ noProprietary: true }) + .on('error', onError) + .on('end', onEnd); + +// This must be a "directory" +fstream.Reader({ path: __dirname, type: "Directory" }) + .on('error', onError) + .pipe(packer) + .pipe(dirDest) diff --git a/test/remoting/tar/node_modules/tar/examples/reader.js b/test/remoting/tar/node_modules/tar/examples/reader.js new file mode 100644 index 0000000000..39f3f0888a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/examples/reader.js @@ -0,0 +1,36 @@ +var tar = require("../tar.js") + , fs = require("fs") + +fs.createReadStream(__dirname + "/../test/fixtures/c.tar") + .pipe(tar.Parse()) + .on("extendedHeader", function (e) { + console.error("extended pax header", e.props) + e.on("end", function () { + console.error("extended pax fields:", e.fields) + }) + }) + .on("ignoredEntry", function (e) { + console.error("ignoredEntry?!?", e.props) + }) + .on("longLinkpath", function (e) { + console.error("longLinkpath entry", e.props) + e.on("end", function () { + console.error("value=%j", e.body.toString()) + }) + }) + .on("longPath", function (e) { + console.error("longPath entry", e.props) + e.on("end", function () { + console.error("value=%j", e.body.toString()) + }) + }) + .on("entry", function (e) { + console.error("entry", e.props) + e.on("data", function (c) { + console.error(" >>>" + c.toString().replace(/\n/g, "\\n")) + }) + e.on("end", function () { + console.error(" << 0 + return !this._needDrain +} + +EntryWriter.prototype.end = function (c) { + // console.error(".. ew end") + if (c) this._buffer.push(c) + this._buffer.push(EOF) + this._ended = true + this._process() + this._needDrain = this._buffer.length > 0 +} + +EntryWriter.prototype.pause = function () { + // console.error(".. ew pause") + this._paused = true + this.emit("pause") +} + +EntryWriter.prototype.resume = function () { + // console.error(".. ew resume") + this._paused = false + this.emit("resume") + this._process() +} + +EntryWriter.prototype.add = function (entry) { + // console.error(".. ew add") + if (!this.parent) return this.emit("error", new Error("no parent")) + + // make sure that the _header and such is emitted, and clear out + // the _currentEntry link on the parent. + if (!this._ended) this.end() + + return this.parent.add(entry) +} + +EntryWriter.prototype._header = function () { + // console.error(".. ew header") + if (this._didHeader) return + this._didHeader = true + + var headerBlock = TarHeader.encode(this.props) + + if (this.props.needExtended && !this._meta) { + var me = this + + ExtendedHeaderWriter = ExtendedHeaderWriter || + require("./extended-header-writer.js") + + ExtendedHeaderWriter(this.props) + .on("data", function (c) { + me.emit("data", c) + }) + .on("error", function (er) { + me.emit("error", er) + }) + .end() + } + + // console.error(".. .. ew headerBlock emitting") + this.emit("data", headerBlock) + this.emit("header") +} + +EntryWriter.prototype._process = function () { + // console.error(".. .. ew process") + if (!this._didHeader && !this._meta) { + this._header() + } + + if (this._paused || this._processing) { + // console.error(".. .. .. paused=%j, processing=%j", this._paused, this._processing) + return + } + + this._processing = true + + var buf = this._buffer + for (var i = 0; i < buf.length; i ++) { + // console.error(".. .. .. i=%d", i) + + var c = buf[i] + + if (c === EOF) this._stream.end() + else this._stream.write(c) + + if (this._paused) { + // console.error(".. .. .. paused mid-emission") + this._processing = false + if (i < buf.length) { + this._needDrain = true + this._buffer = buf.slice(i + 1) + } + return + } + } + + // console.error(".. .. .. emitted") + this._buffer.length = 0 + this._processing = false + + // console.error(".. .. .. emitting drain") + this.emit("drain") +} + +EntryWriter.prototype.destroy = function () {} diff --git a/test/remoting/tar/node_modules/tar/lib/entry.js b/test/remoting/tar/node_modules/tar/lib/entry.js new file mode 100644 index 0000000000..591202bd3b --- /dev/null +++ b/test/remoting/tar/node_modules/tar/lib/entry.js @@ -0,0 +1,220 @@ +// A passthrough read/write stream that sets its properties +// based on a header, extendedHeader, and globalHeader +// +// Can be either a file system object of some sort, or +// a pax/ustar metadata entry. + +module.exports = Entry + +var TarHeader = require("./header.js") + , tar = require("../tar") + , assert = require("assert").ok + , Stream = require("stream").Stream + , inherits = require("inherits") + , fstream = require("fstream").Abstract + +function Entry (header, extended, global) { + Stream.call(this) + this.readable = true + this.writable = true + + this._needDrain = false + this._paused = false + this._reading = false + this._ending = false + this._ended = false + this._remaining = 0 + this._abort = false + this._queue = [] + this._index = 0 + this._queueLen = 0 + + this._read = this._read.bind(this) + + this.props = {} + this._header = header + this._extended = extended || {} + + // globals can change throughout the course of + // a file parse operation. Freeze it at its current state. + this._global = {} + var me = this + Object.keys(global || {}).forEach(function (g) { + me._global[g] = global[g] + }) + + this._setProps() +} + +inherits(Entry, Stream) + +Entry.prototype.write = function (c) { + if (this._ending) this.error("write() after end()", null, true) + if (this._remaining === 0) { + this.error("invalid bytes past eof") + } + + // often we'll get a bunch of \0 at the end of the last write, + // since chunks will always be 512 bytes when reading a tarball. + if (c.length > this._remaining) { + c = c.slice(0, this._remaining) + } + this._remaining -= c.length + + // put it on the stack. + var ql = this._queueLen + this._queue.push(c) + this._queueLen ++ + + this._read() + + // either paused, or buffered + if (this._paused || ql > 0) { + this._needDrain = true + return false + } + + return true +} + +Entry.prototype.end = function (c) { + if (c) this.write(c) + this._ending = true + this._read() +} + +Entry.prototype.pause = function () { + this._paused = true + this.emit("pause") +} + +Entry.prototype.resume = function () { + // console.error(" Tar Entry resume", this.path) + this.emit("resume") + this._paused = false + this._read() + return this._queueLen - this._index > 1 +} + + // This is bound to the instance +Entry.prototype._read = function () { + // console.error(" Tar Entry _read", this.path) + + if (this._paused || this._reading || this._ended) return + + // set this flag so that event handlers don't inadvertently + // get multiple _read() calls running. + this._reading = true + + // have any data to emit? + while (this._index < this._queueLen && !this._paused) { + var chunk = this._queue[this._index ++] + this.emit("data", chunk) + } + + // check if we're drained + if (this._index >= this._queueLen) { + this._queue.length = this._queueLen = this._index = 0 + if (this._needDrain) { + this._needDrain = false + this.emit("drain") + } + if (this._ending) { + this._ended = true + this.emit("end") + } + } + + // if the queue gets too big, then pluck off whatever we can. + // this should be fairly rare. + var mql = this._maxQueueLen + if (this._queueLen > mql && this._index > 0) { + mql = Math.min(this._index, mql) + this._index -= mql + this._queueLen -= mql + this._queue = this._queue.slice(mql) + } + + this._reading = false +} + +Entry.prototype._setProps = function () { + // props = extended->global->header->{} + var header = this._header + , extended = this._extended + , global = this._global + , props = this.props + + // first get the values from the normal header. + var fields = tar.fields + for (var f = 0; fields[f] !== null; f ++) { + var field = fields[f] + , val = header[field] + if (typeof val !== "undefined") props[field] = val + } + + // next, the global header for this file. + // numeric values, etc, will have already been parsed. + ;[global, extended].forEach(function (p) { + Object.keys(p).forEach(function (f) { + if (typeof p[f] !== "undefined") props[f] = p[f] + }) + }) + + // no nulls allowed in path or linkpath + ;["path", "linkpath"].forEach(function (p) { + if (props.hasOwnProperty(p)) { + props[p] = props[p].split("\0")[0] + } + }) + + + // set date fields to be a proper date + ;["mtime", "ctime", "atime"].forEach(function (p) { + if (props.hasOwnProperty(p)) { + props[p] = new Date(props[p] * 1000) + } + }) + + // set the type so that we know what kind of file to create + var type + switch (tar.types[props.type]) { + case "OldFile": + case "ContiguousFile": + type = "File" + break + + case "GNUDumpDir": + type = "Directory" + break + + case undefined: + type = "Unknown" + break + + case "Link": + case "SymbolicLink": + case "CharacterDevice": + case "BlockDevice": + case "Directory": + case "FIFO": + default: + type = tar.types[props.type] + } + + this.type = type + this.path = props.path + this.size = props.size + + // size is special, since it signals when the file needs to end. + this._remaining = props.size +} + +// the parser may not call write if _abort is true. +// useful for skipping data from some files quickly. +Entry.prototype.abort = function(){ + this._abort = true +} + +Entry.prototype.warn = fstream.warn +Entry.prototype.error = fstream.error diff --git a/test/remoting/tar/node_modules/tar/lib/extended-header-writer.js b/test/remoting/tar/node_modules/tar/lib/extended-header-writer.js new file mode 100644 index 0000000000..1728c4583a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/lib/extended-header-writer.js @@ -0,0 +1,191 @@ + +module.exports = ExtendedHeaderWriter + +var inherits = require("inherits") + , EntryWriter = require("./entry-writer.js") + +inherits(ExtendedHeaderWriter, EntryWriter) + +var tar = require("../tar.js") + , path = require("path") + , TarHeader = require("./header.js") + +// props is the props of the thing we need to write an +// extended header for. +// Don't be shy with it. Just encode everything. +function ExtendedHeaderWriter (props) { + // console.error(">> ehw ctor") + var me = this + + if (!(me instanceof ExtendedHeaderWriter)) { + return new ExtendedHeaderWriter(props) + } + + me.fields = props + + var p = + { path : ("PaxHeader" + path.join("/", props.path || "")) + .replace(/\\/g, "/").substr(0, 100) + , mode : props.mode || 0666 + , uid : props.uid || 0 + , gid : props.gid || 0 + , size : 0 // will be set later + , mtime : props.mtime || Date.now() / 1000 + , type : "x" + , linkpath : "" + , ustar : "ustar\0" + , ustarver : "00" + , uname : props.uname || "" + , gname : props.gname || "" + , devmaj : props.devmaj || 0 + , devmin : props.devmin || 0 + } + + + EntryWriter.call(me, p) + // console.error(">> ehw props", me.props) + me.props = p + + me._meta = true +} + +ExtendedHeaderWriter.prototype.end = function () { + // console.error(">> ehw end") + var me = this + + if (me._ended) return + me._ended = true + + me._encodeFields() + + if (me.props.size === 0) { + // nothing to write! + me._ready = true + me._stream.end() + return + } + + me._stream.write(TarHeader.encode(me.props)) + me.body.forEach(function (l) { + me._stream.write(l) + }) + me._ready = true + + // console.error(">> ehw _process calling end()", me.props) + this._stream.end() +} + +ExtendedHeaderWriter.prototype._encodeFields = function () { + // console.error(">> ehw _encodeFields") + this.body = [] + if (this.fields.prefix) { + this.fields.path = this.fields.prefix + "/" + this.fields.path + this.fields.prefix = "" + } + encodeFields(this.fields, "", this.body, this.fields.noProprietary) + var me = this + this.body.forEach(function (l) { + me.props.size += l.length + }) +} + +function encodeFields (fields, prefix, body, nop) { + // console.error(">> >> ehw encodeFields") + // "%d %s=%s\n", , , + // The length is a decimal number, and includes itself and the \n + // Numeric values are decimal strings. + + Object.keys(fields).forEach(function (k) { + var val = fields[k] + , numeric = tar.numeric[k] + + if (prefix) k = prefix + "." + k + + // already including NODETAR.type, don't need File=true also + if (k === fields.type && val === true) return + + switch (k) { + // don't include anything that's always handled just fine + // in the normal header, or only meaningful in the context + // of nodetar + case "mode": + case "cksum": + case "ustar": + case "ustarver": + case "prefix": + case "basename": + case "dirname": + case "needExtended": + case "block": + case "filter": + return + + case "rdev": + if (val === 0) return + break + + case "nlink": + case "dev": // Truly a hero among men, Creator of Star! + case "ino": // Speak his name with reverent awe! It is: + k = "SCHILY." + k + break + + default: break + } + + if (val && typeof val === "object" && + !Buffer.isBuffer(val)) encodeFields(val, k, body, nop) + else if (val === null || val === undefined) return + else body.push.apply(body, encodeField(k, val, nop)) + }) + + return body +} + +function encodeField (k, v, nop) { + // lowercase keys must be valid, otherwise prefix with + // "NODETAR." + if (k.charAt(0) === k.charAt(0).toLowerCase()) { + var m = k.split(".")[0] + if (!tar.knownExtended[m]) k = "NODETAR." + k + } + + // no proprietary + if (nop && k.charAt(0) !== k.charAt(0).toLowerCase()) { + return [] + } + + if (typeof val === "number") val = val.toString(10) + + var s = new Buffer(" " + k + "=" + v + "\n") + , digits = Math.floor(Math.log(s.length) / Math.log(10)) + 1 + + // console.error("1 s=%j digits=%j s.length=%d", s.toString(), digits, s.length) + + // if adding that many digits will make it go over that length, + // then add one to it. For example, if the string is: + // " foo=bar\n" + // then that's 9 characters. With the "9", that bumps the length + // up to 10. However, this is invalid: + // "10 foo=bar\n" + // but, since that's actually 11 characters, since 10 adds another + // character to the length, and the length includes the number + // itself. In that case, just bump it up again. + if (s.length + digits >= Math.pow(10, digits)) digits += 1 + // console.error("2 s=%j digits=%j s.length=%d", s.toString(), digits, s.length) + + var len = digits + s.length + // console.error("3 s=%j digits=%j s.length=%d len=%d", s.toString(), digits, s.length, len) + var lenBuf = new Buffer("" + len) + if (lenBuf.length + s.length !== len) { + throw new Error("Bad length calculation\n"+ + "len="+len+"\n"+ + "lenBuf="+JSON.stringify(lenBuf.toString())+"\n"+ + "lenBuf.length="+lenBuf.length+"\n"+ + "digits="+digits+"\n"+ + "s="+JSON.stringify(s.toString())+"\n"+ + "s.length="+s.length) + } + + return [lenBuf, s] +} diff --git a/test/remoting/tar/node_modules/tar/lib/extended-header.js b/test/remoting/tar/node_modules/tar/lib/extended-header.js new file mode 100644 index 0000000000..74f432ceee --- /dev/null +++ b/test/remoting/tar/node_modules/tar/lib/extended-header.js @@ -0,0 +1,140 @@ +// An Entry consisting of: +// +// "%d %s=%s\n", , , +// +// The length is a decimal number, and includes itself and the \n +// \0 does not terminate anything. Only the length terminates the string. +// Numeric values are decimal strings. + +module.exports = ExtendedHeader + +var Entry = require("./entry.js") + , inherits = require("inherits") + , tar = require("../tar.js") + , numeric = tar.numeric + , keyTrans = { "SCHILY.dev": "dev" + , "SCHILY.ino": "ino" + , "SCHILY.nlink": "nlink" } + +function ExtendedHeader () { + Entry.apply(this, arguments) + this.on("data", this._parse) + this.fields = {} + this._position = 0 + this._fieldPos = 0 + this._state = SIZE + this._sizeBuf = [] + this._keyBuf = [] + this._valBuf = [] + this._size = -1 + this._key = "" +} + +inherits(ExtendedHeader, Entry) +ExtendedHeader.prototype._parse = parse + +var s = 0 + , states = ExtendedHeader.states = {} + , SIZE = states.SIZE = s++ + , KEY = states.KEY = s++ + , VAL = states.VAL = s++ + , ERR = states.ERR = s++ + +Object.keys(states).forEach(function (s) { + states[states[s]] = states[s] +}) + +states[s] = null + +// char code values for comparison +var _0 = "0".charCodeAt(0) + , _9 = "9".charCodeAt(0) + , point = ".".charCodeAt(0) + , a = "a".charCodeAt(0) + , Z = "Z".charCodeAt(0) + , a = "a".charCodeAt(0) + , z = "z".charCodeAt(0) + , space = " ".charCodeAt(0) + , eq = "=".charCodeAt(0) + , cr = "\n".charCodeAt(0) + +function parse (c) { + if (this._state === ERR) return + + for ( var i = 0, l = c.length + ; i < l + ; this._position++, this._fieldPos++, i++) { + // console.error("top of loop, size="+this._size) + + var b = c[i] + + if (this._size >= 0 && this._fieldPos > this._size) { + error(this, "field exceeds length="+this._size) + return + } + + switch (this._state) { + case ERR: return + + case SIZE: + // console.error("parsing size, b=%d, rest=%j", b, c.slice(i).toString()) + if (b === space) { + this._state = KEY + // this._fieldPos = this._sizeBuf.length + this._size = parseInt(new Buffer(this._sizeBuf).toString(), 10) + this._sizeBuf.length = 0 + continue + } + if (b < _0 || b > _9) { + error(this, "expected [" + _0 + ".." + _9 + "], got " + b) + return + } + this._sizeBuf.push(b) + continue + + case KEY: + // can be any char except =, not > size. + if (b === eq) { + this._state = VAL + this._key = new Buffer(this._keyBuf).toString() + if (keyTrans[this._key]) this._key = keyTrans[this._key] + this._keyBuf.length = 0 + continue + } + this._keyBuf.push(b) + continue + + case VAL: + // field must end with cr + if (this._fieldPos === this._size - 1) { + // console.error("finished with "+this._key) + if (b !== cr) { + error(this, "expected \\n at end of field") + return + } + var val = new Buffer(this._valBuf).toString() + if (numeric[this._key]) { + val = parseFloat(val) + } + this.fields[this._key] = val + + this._valBuf.length = 0 + this._state = SIZE + this._size = -1 + this._fieldPos = -1 + continue + } + this._valBuf.push(b) + continue + } + } +} + +function error (me, msg) { + msg = "invalid header: " + msg + + "\nposition=" + me._position + + "\nfield position=" + me._fieldPos + + me.error(msg) + me.state = ERR +} diff --git a/test/remoting/tar/node_modules/tar/lib/extract.js b/test/remoting/tar/node_modules/tar/lib/extract.js new file mode 100644 index 0000000000..fe1bb976eb --- /dev/null +++ b/test/remoting/tar/node_modules/tar/lib/extract.js @@ -0,0 +1,94 @@ +// give it a tarball and a path, and it'll dump the contents + +module.exports = Extract + +var tar = require("../tar.js") + , fstream = require("fstream") + , inherits = require("inherits") + , path = require("path") + +function Extract (opts) { + if (!(this instanceof Extract)) return new Extract(opts) + tar.Parse.apply(this) + + if (typeof opts !== "object") { + opts = { path: opts } + } + + // better to drop in cwd? seems more standard. + opts.path = opts.path || path.resolve("node-tar-extract") + opts.type = "Directory" + opts.Directory = true + + // similar to --strip or --strip-components + opts.strip = +opts.strip + if (!opts.strip || opts.strip <= 0) opts.strip = 0 + + this._fst = fstream.Writer(opts) + + this.pause() + var me = this + + // Hardlinks in tarballs are relative to the root + // of the tarball. So, they need to be resolved against + // the target directory in order to be created properly. + me.on("entry", function (entry) { + // if there's a "strip" argument, then strip off that many + // path components. + if (opts.strip) { + var p = entry.path.split("/").slice(opts.strip).join("/") + entry.path = entry.props.path = p + if (entry.linkpath) { + var lp = entry.linkpath.split("/").slice(opts.strip).join("/") + entry.linkpath = entry.props.linkpath = lp + } + } + if (entry.type === "Link") { + entry.linkpath = entry.props.linkpath = + path.join(opts.path, path.join("/", entry.props.linkpath)) + } + + if (entry.type === "SymbolicLink") { + var dn = path.dirname(entry.path) || "" + var linkpath = entry.props.linkpath + var target = path.resolve(opts.path, dn, linkpath) + if (target.indexOf(opts.path) !== 0) { + linkpath = path.join(opts.path, path.join("/", linkpath)) + } + entry.linkpath = entry.props.linkpath = linkpath + } + }) + + this._fst.on("ready", function () { + me.pipe(me._fst, { end: false }) + me.resume() + }) + + this._fst.on('error', function(err) { + me.emit('error', err) + }) + + this._fst.on('drain', function() { + me.emit('drain') + }) + + // this._fst.on("end", function () { + // console.error("\nEEEE Extract End", me._fst.path) + // }) + + this._fst.on("close", function () { + // console.error("\nEEEE Extract End", me._fst.path) + me.emit("finish") + me.emit("end") + me.emit("close") + }) +} + +inherits(Extract, tar.Parse) + +Extract.prototype._streamEnd = function () { + var me = this + if (!me._ended || me._entry) me.error("unexpected eof") + me._fst.end() + // my .end() is coming later. +} diff --git a/test/remoting/tar/node_modules/tar/lib/global-header-writer.js b/test/remoting/tar/node_modules/tar/lib/global-header-writer.js new file mode 100644 index 0000000000..0bfc7b80aa --- /dev/null +++ b/test/remoting/tar/node_modules/tar/lib/global-header-writer.js @@ -0,0 +1,14 @@ +module.exports = GlobalHeaderWriter + +var ExtendedHeaderWriter = require("./extended-header-writer.js") + , inherits = require("inherits") + +inherits(GlobalHeaderWriter, ExtendedHeaderWriter) + +function GlobalHeaderWriter (props) { + if (!(this instanceof GlobalHeaderWriter)) { + return new GlobalHeaderWriter(props) + } + ExtendedHeaderWriter.call(this, props) + this.props.type = "g" +} diff --git a/test/remoting/tar/node_modules/tar/lib/header.js b/test/remoting/tar/node_modules/tar/lib/header.js new file mode 100644 index 0000000000..05b237c0c7 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/lib/header.js @@ -0,0 +1,385 @@ +// parse a 512-byte header block to a data object, or vice-versa +// If the data won't fit nicely in a simple header, then generate +// the appropriate extended header file, and return that. + +module.exports = TarHeader + +var tar = require("../tar.js") + , fields = tar.fields + , fieldOffs = tar.fieldOffs + , fieldEnds = tar.fieldEnds + , fieldSize = tar.fieldSize + , numeric = tar.numeric + , assert = require("assert").ok + , space = " ".charCodeAt(0) + , slash = "/".charCodeAt(0) + , bslash = process.platform === "win32" ? "\\".charCodeAt(0) : null + +function TarHeader (block) { + if (!(this instanceof TarHeader)) return new TarHeader(block) + if (block) this.decode(block) +} + +TarHeader.prototype = + { decode : decode + , encode: encode + , calcSum: calcSum + , checkSum: checkSum + } + +TarHeader.parseNumeric = parseNumeric +TarHeader.encode = encode +TarHeader.decode = decode + +// note that this will only do the normal ustar header, not any kind +// of extended posix header file. If something doesn't fit comfortably, +// then it will set obj.needExtended = true, and set the block to +// the closest approximation. +function encode (obj) { + if (!obj && !(this instanceof TarHeader)) throw new Error( + "encode must be called on a TarHeader, or supplied an object") + + obj = obj || this + var block = obj.block = new Buffer(512) + + // if the object has a "prefix", then that's actually an extension of + // the path field. + if (obj.prefix) { + // console.error("%% header encoding, got a prefix", obj.prefix) + obj.path = obj.prefix + "/" + obj.path + // console.error("%% header encoding, prefixed path", obj.path) + obj.prefix = "" + } + + obj.needExtended = false + + if (obj.mode) { + if (typeof obj.mode === "string") obj.mode = parseInt(obj.mode, 8) + obj.mode = obj.mode & 0777 + } + + for (var f = 0; fields[f] !== null; f ++) { + var field = fields[f] + , off = fieldOffs[f] + , end = fieldEnds[f] + , ret + + switch (field) { + case "cksum": + // special, done below, after all the others + break + + case "prefix": + // special, this is an extension of the "path" field. + // console.error("%% header encoding, skip prefix later") + break + + case "type": + // convert from long name to a single char. + var type = obj.type || "0" + if (type.length > 1) { + type = tar.types[obj.type] + if (!type) type = "0" + } + writeText(block, off, end, type) + break + + case "path": + // uses the "prefix" field if > 100 bytes, but <= 255 + var pathLen = Buffer.byteLength(obj.path) + , pathFSize = fieldSize[fields.path] + , prefFSize = fieldSize[fields.prefix] + + // paths between 100 and 255 should use the prefix field. + // longer than 255 + if (pathLen > pathFSize && + pathLen <= pathFSize + prefFSize) { + // need to find a slash somewhere in the middle so that + // path and prefix both fit in their respective fields + var searchStart = pathLen - 1 - pathFSize + , searchEnd = prefFSize + , found = false + , pathBuf = new Buffer(obj.path) + + for ( var s = searchStart + ; (s <= searchEnd) + ; s ++ ) { + if (pathBuf[s] === slash || pathBuf[s] === bslash) { + found = s + break + } + } + + if (found !== false) { + prefix = pathBuf.slice(0, found).toString("utf8") + path = pathBuf.slice(found + 1).toString("utf8") + + ret = writeText(block, off, end, path) + off = fieldOffs[fields.prefix] + end = fieldEnds[fields.prefix] + // console.error("%% header writing prefix", off, end, prefix) + ret = writeText(block, off, end, prefix) || ret + break + } + } + + // paths less than 100 chars don't need a prefix + // and paths longer than 255 need an extended header and will fail + // on old implementations no matter what we do here. + // Null out the prefix, and fallthrough to default. + // console.error("%% header writing no prefix") + var poff = fieldOffs[fields.prefix] + , pend = fieldEnds[fields.prefix] + writeText(block, poff, pend, "") + // fallthrough + + // all other fields are numeric or text + default: + ret = numeric[field] + ? writeNumeric(block, off, end, obj[field]) + : writeText(block, off, end, obj[field] || "") + break + } + obj.needExtended = obj.needExtended || ret + } + + var off = fieldOffs[fields.cksum] + , end = fieldEnds[fields.cksum] + + writeNumeric(block, off, end, calcSum.call(this, block)) + + return block +} + +// if it's a negative number, or greater than will fit, +// then use write256. +var MAXNUM = { 12: 077777777777 + , 11: 07777777777 + , 8 : 07777777 + , 7 : 0777777 } +function writeNumeric (block, off, end, num) { + var writeLen = end - off + , maxNum = MAXNUM[writeLen] || 0 + + num = num || 0 + // console.error(" numeric", num) + + if (num instanceof Date || + Object.prototype.toString.call(num) === "[object Date]") { + num = num.getTime() / 1000 + } + + if (num > maxNum || num < 0) { + write256(block, off, end, num) + // need an extended header if negative or too big. + return true + } + + // god, tar is so annoying + // if the string is small enough, you should put a space + // between the octal string and the \0, but if it doesn't + // fit, then don't. + var numStr = Math.floor(num).toString(8) + if (num < MAXNUM[writeLen - 1]) numStr += " " + + // pad with "0" chars + if (numStr.length < writeLen) { + numStr = (new Array(writeLen - numStr.length).join("0")) + numStr + } + + if (numStr.length !== writeLen - 1) { + throw new Error("invalid length: " + JSON.stringify(numStr) + "\n" + + "expected: "+writeLen) + } + block.write(numStr, off, writeLen, "utf8") + block[end - 1] = 0 +} + +function write256 (block, off, end, num) { + var buf = block.slice(off, end) + var positive = num >= 0 + buf[0] = positive ? 0x80 : 0xFF + + // get the number as a base-256 tuple + if (!positive) num *= -1 + var tuple = [] + do { + var n = num % 256 + tuple.push(n) + num = (num - n) / 256 + } while (num) + + var bytes = tuple.length + + var fill = buf.length - bytes + for (var i = 1; i < fill; i ++) { + buf[i] = positive ? 0 : 0xFF + } + + // tuple is a base256 number, with [0] as the *least* significant byte + // if it's negative, then we need to flip all the bits once we hit the + // first non-zero bit. The 2's-complement is (0x100 - n), and the 1's- + // complement is (0xFF - n). + var zero = true + for (i = bytes; i > 0; i --) { + var byte = tuple[bytes - i] + if (positive) buf[fill + i] = byte + else if (zero && byte === 0) buf[fill + i] = 0 + else if (zero) { + zero = false + buf[fill + i] = 0x100 - byte + } else buf[fill + i] = 0xFF - byte + } +} + +function writeText (block, off, end, str) { + // strings are written as utf8, then padded with \0 + var strLen = Buffer.byteLength(str) + , writeLen = Math.min(strLen, end - off) + // non-ascii fields need extended headers + // long fields get truncated + , needExtended = strLen !== str.length || strLen > writeLen + + // write the string, and null-pad + if (writeLen > 0) block.write(str, off, writeLen, "utf8") + for (var i = off + writeLen; i < end; i ++) block[i] = 0 + + return needExtended +} + +function calcSum (block) { + block = block || this.block + assert(Buffer.isBuffer(block) && block.length === 512) + + if (!block) throw new Error("Need block to checksum") + + // now figure out what it would be if the cksum was " " + var sum = 0 + , start = fieldOffs[fields.cksum] + , end = fieldEnds[fields.cksum] + + for (var i = 0; i < fieldOffs[fields.cksum]; i ++) { + sum += block[i] + } + + for (var i = start; i < end; i ++) { + sum += space + } + + for (var i = end; i < 512; i ++) { + sum += block[i] + } + + return sum +} + + +function checkSum (block) { + var sum = calcSum.call(this, block) + block = block || this.block + + var cksum = block.slice(fieldOffs[fields.cksum], fieldEnds[fields.cksum]) + cksum = parseNumeric(cksum) + + return cksum === sum +} + +function decode (block) { + block = block || this.block + assert(Buffer.isBuffer(block) && block.length === 512) + + this.block = block + this.cksumValid = this.checkSum() + + var prefix = null + + // slice off each field. + for (var f = 0; fields[f] !== null; f ++) { + var field = fields[f] + , val = block.slice(fieldOffs[f], fieldEnds[f]) + + switch (field) { + case "ustar": + // if not ustar, then everything after that is just padding. + if (val.toString() !== "ustar\0") { + this.ustar = false + return + } else { + // console.error("ustar:", val, val.toString()) + this.ustar = val.toString() + } + break + + // prefix is special, since it might signal the xstar header + case "prefix": + var atime = parseNumeric(val.slice(131, 131 + 12)) + , ctime = parseNumeric(val.slice(131 + 12, 131 + 12 + 12)) + if ((val[130] === 0 || val[130] === space) && + typeof atime === "number" && + typeof ctime === "number" && + val[131 + 12] === space && + val[131 + 12 + 12] === space) { + this.atime = atime + this.ctime = ctime + val = val.slice(0, 130) + } + prefix = val.toString("utf8").replace(/\0+$/, "") + // console.error("%% header reading prefix", prefix) + break + + // all other fields are null-padding text + // or a number. + default: + if (numeric[field]) { + this[field] = parseNumeric(val) + } else { + this[field] = val.toString("utf8").replace(/\0+$/, "") + } + break + } + } + + // if we got a prefix, then prepend it to the path. + if (prefix) { + this.path = prefix + "/" + this.path + // console.error("%% header got a prefix", this.path) + } +} + +function parse256 (buf) { + // first byte MUST be either 80 or FF + // 80 for positive, FF for 2's comp + var positive + if (buf[0] === 0x80) positive = true + else if (buf[0] === 0xFF) positive = false + else return null + + // build up a base-256 tuple from the least sig to the highest + var zero = false + , tuple = [] + for (var i = buf.length - 1; i > 0; i --) { + var byte = buf[i] + if (positive) tuple.push(byte) + else if (zero && byte === 0) tuple.push(0) + else if (zero) { + zero = false + tuple.push(0x100 - byte) + } else tuple.push(0xFF - byte) + } + + for (var sum = 0, i = 0, l = tuple.length; i < l; i ++) { + sum += tuple[i] * Math.pow(256, i) + } + + return positive ? sum : -1 * sum +} + +function parseNumeric (f) { + if (f[0] & 0x80) return parse256(f) + + var str = f.toString("utf8").split("\0")[0].trim() + , res = parseInt(str, 8) + + return isNaN(res) ? null : res +} + diff --git a/test/remoting/tar/node_modules/tar/lib/pack.js b/test/remoting/tar/node_modules/tar/lib/pack.js new file mode 100644 index 0000000000..5a3bb95a12 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/lib/pack.js @@ -0,0 +1,236 @@ +// pipe in an fstream, and it'll make a tarball. +// key-value pair argument is global extended header props. + +module.exports = Pack + +var EntryWriter = require("./entry-writer.js") + , Stream = require("stream").Stream + , path = require("path") + , inherits = require("inherits") + , GlobalHeaderWriter = require("./global-header-writer.js") + , collect = require("fstream").collect + , eof = new Buffer(512) + +for (var i = 0; i < 512; i ++) eof[i] = 0 + +inherits(Pack, Stream) + +function Pack (props) { + // console.error("-- p ctor") + var me = this + if (!(me instanceof Pack)) return new Pack(props) + + if (props) me._noProprietary = props.noProprietary + else me._noProprietary = false + + me._global = props + + me.readable = true + me.writable = true + me._buffer = [] + // console.error("-- -- set current to null in ctor") + me._currentEntry = null + me._processing = false + + me._pipeRoot = null + me.on("pipe", function (src) { + if (src.root === me._pipeRoot) return + me._pipeRoot = src + src.on("end", function () { + me._pipeRoot = null + }) + me.add(src) + }) +} + +Pack.prototype.addGlobal = function (props) { + // console.error("-- p addGlobal") + if (this._didGlobal) return + this._didGlobal = true + + var me = this + GlobalHeaderWriter(props) + .on("data", function (c) { + me.emit("data", c) + }) + .end() +} + +Pack.prototype.add = function (stream) { + if (this._global && !this._didGlobal) this.addGlobal(this._global) + + if (this._ended) return this.emit("error", new Error("add after end")) + + collect(stream) + this._buffer.push(stream) + this._process() + this._needDrain = this._buffer.length > 0 + return !this._needDrain +} + +Pack.prototype.pause = function () { + this._paused = true + if (this._currentEntry) this._currentEntry.pause() + this.emit("pause") +} + +Pack.prototype.resume = function () { + this._paused = false + if (this._currentEntry) this._currentEntry.resume() + this.emit("resume") + this._process() +} + +Pack.prototype.end = function () { + this._ended = true + this._buffer.push(eof) + this._process() +} + +Pack.prototype._process = function () { + var me = this + if (me._paused || me._processing) { + return + } + + var entry = me._buffer.shift() + + if (!entry) { + if (me._needDrain) { + me.emit("drain") + } + return + } + + if (entry.ready === false) { + // console.error("-- entry is not ready", entry) + me._buffer.unshift(entry) + entry.on("ready", function () { + // console.error("-- -- ready!", entry) + me._process() + }) + return + } + + me._processing = true + + if (entry === eof) { + // need 2 ending null blocks. + me.emit("data", eof) + me.emit("data", eof) + me.emit("end") + me.emit("close") + return + } + + // Change the path to be relative to the root dir that was + // added to the tarball. + // + // XXX This should be more like how -C works, so you can + // explicitly set a root dir, and also explicitly set a pathname + // in the tarball to use. That way we can skip a lot of extra + // work when resolving symlinks for bundled dependencies in npm. + + var root = path.dirname((entry.root || entry).path); + if (me._global && me._global.fromBase && entry.root && entry.root.path) { + // user set 'fromBase: true' indicating tar root should be directory itself + root = entry.root.path; + } + + var wprops = {} + + Object.keys(entry.props || {}).forEach(function (k) { + wprops[k] = entry.props[k] + }) + + if (me._noProprietary) wprops.noProprietary = true + + wprops.path = path.relative(root, entry.path || '') + + // actually not a matter of opinion or taste. + if (process.platform === "win32") { + wprops.path = wprops.path.replace(/\\/g, "/") + } + + if (!wprops.type) + wprops.type = 'Directory' + + switch (wprops.type) { + // sockets not supported + case "Socket": + return + + case "Directory": + wprops.path += "/" + wprops.size = 0 + break + + case "Link": + var lp = path.resolve(path.dirname(entry.path), entry.linkpath) + wprops.linkpath = path.relative(root, lp) || "." + wprops.size = 0 + break + + case "SymbolicLink": + var lp = path.resolve(path.dirname(entry.path), entry.linkpath) + wprops.linkpath = path.relative(path.dirname(entry.path), lp) || "." + wprops.size = 0 + break + } + + // console.error("-- new writer", wprops) + // if (!wprops.type) { + // // console.error("-- no type?", entry.constructor.name, entry) + // } + + // console.error("-- -- set current to new writer", wprops.path) + var writer = me._currentEntry = EntryWriter(wprops) + + writer.parent = me + + // writer.on("end", function () { + // // console.error("-- -- writer end", writer.path) + // }) + + writer.on("data", function (c) { + me.emit("data", c) + }) + + writer.on("header", function () { + Buffer.prototype.toJSON = function () { + return this.toString().split(/\0/).join(".") + } + // console.error("-- -- writer header %j", writer.props) + if (writer.props.size === 0) nextEntry() + }) + writer.on("close", nextEntry) + + var ended = false + function nextEntry () { + if (ended) return + ended = true + + // console.error("-- -- writer close", writer.path) + // console.error("-- -- set current to null", wprops.path) + me._currentEntry = null + me._processing = false + me._process() + } + + writer.on("error", function (er) { + // console.error("-- -- writer error", writer.path) + me.emit("error", er) + }) + + // if it's the root, then there's no need to add its entries, + // or data, since they'll be added directly. + if (entry === me._pipeRoot) { + // console.error("-- is the root, don't auto-add") + writer.add = null + } + + entry.pipe(writer) +} + +Pack.prototype.destroy = function () {} +Pack.prototype.write = function () {} diff --git a/test/remoting/tar/node_modules/tar/lib/parse.js b/test/remoting/tar/node_modules/tar/lib/parse.js new file mode 100644 index 0000000000..600ad782f0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/lib/parse.js @@ -0,0 +1,275 @@ + +// A writable stream. +// It emits "entry" events, which provide a readable stream that has +// header info attached. + +module.exports = Parse.create = Parse + +var stream = require("stream") + , Stream = stream.Stream + , BlockStream = require("block-stream") + , tar = require("../tar.js") + , TarHeader = require("./header.js") + , Entry = require("./entry.js") + , BufferEntry = require("./buffer-entry.js") + , ExtendedHeader = require("./extended-header.js") + , assert = require("assert").ok + , inherits = require("inherits") + , fstream = require("fstream") + +// reading a tar is a lot like reading a directory +// However, we're actually not going to run the ctor, +// since it does a stat and various other stuff. +// This inheritance gives us the pause/resume/pipe +// behavior that is desired. +inherits(Parse, fstream.Reader) + +function Parse () { + var me = this + if (!(me instanceof Parse)) return new Parse() + + // doesn't apply fstream.Reader ctor? + // no, becasue we don't want to stat/etc, we just + // want to get the entry/add logic from .pipe() + Stream.apply(me) + + me.writable = true + me.readable = true + me._stream = new BlockStream(512) + me.position = 0 + me._ended = false + + me._stream.on("error", function (e) { + me.emit("error", e) + }) + + me._stream.on("data", function (c) { + me._process(c) + }) + + me._stream.on("end", function () { + me._streamEnd() + }) + + me._stream.on("drain", function () { + me.emit("drain") + }) +} + +// overridden in Extract class, since it needs to +// wait for its DirWriter part to finish before +// emitting "end" +Parse.prototype._streamEnd = function () { + var me = this + if (!me._ended || me._entry) me.error("unexpected eof") + me.emit("end") +} + +// a tar reader is actually a filter, not just a readable stream. +// So, you should pipe a tarball stream into it, and it needs these +// write/end methods to do that. +Parse.prototype.write = function (c) { + if (this._ended) { + // gnutar puts a LOT of nulls at the end. + // you can keep writing these things forever. + // Just ignore them. + for (var i = 0, l = c.length; i > l; i ++) { + if (c[i] !== 0) return this.error("write() after end()") + } + return + } + return this._stream.write(c) +} + +Parse.prototype.end = function (c) { + this._ended = true + return this._stream.end(c) +} + +// don't need to do anything, since we're just +// proxying the data up from the _stream. +// Just need to override the parent's "Not Implemented" +// error-thrower. +Parse.prototype._read = function () {} + +Parse.prototype._process = function (c) { + assert(c && c.length === 512, "block size should be 512") + + // one of three cases. + // 1. A new header + // 2. A part of a file/extended header + // 3. One of two or more EOF null blocks + + if (this._entry) { + var entry = this._entry + if(!entry._abort) entry.write(c) + else { + entry._remaining -= c.length + if(entry._remaining < 0) entry._remaining = 0 + } + if (entry._remaining === 0) { + entry.end() + this._entry = null + } + } else { + // either zeroes or a header + var zero = true + for (var i = 0; i < 512 && zero; i ++) { + zero = c[i] === 0 + } + + // eof is *at least* 2 blocks of nulls, and then the end of the + // file. you can put blocks of nulls between entries anywhere, + // so appending one tarball to another is technically valid. + // ending without the eof null blocks is not allowed, however. + if (zero) { + if (this._eofStarted) + this._ended = true + this._eofStarted = true + } else { + this._eofStarted = false + this._startEntry(c) + } + } + + this.position += 512 +} + +// take a header chunk, start the right kind of entry. +Parse.prototype._startEntry = function (c) { + var header = new TarHeader(c) + , self = this + , entry + , ev + , EntryType + , onend + , meta = false + + if (null === header.size || !header.cksumValid) { + var e = new Error("invalid tar file") + e.header = header + e.tar_file_offset = this.position + e.tar_block = this.position / 512 + return this.emit("error", e) + } + + switch (tar.types[header.type]) { + case "File": + case "OldFile": + case "Link": + case "SymbolicLink": + case "CharacterDevice": + case "BlockDevice": + case "Directory": + case "FIFO": + case "ContiguousFile": + case "GNUDumpDir": + // start a file. + // pass in any extended headers + // These ones consumers are typically most interested in. + EntryType = Entry + ev = "entry" + break + + case "GlobalExtendedHeader": + // extended headers that apply to the rest of the tarball + EntryType = ExtendedHeader + onend = function () { + self._global = self._global || {} + Object.keys(entry.fields).forEach(function (k) { + self._global[k] = entry.fields[k] + }) + } + ev = "globalExtendedHeader" + meta = true + break + + case "ExtendedHeader": + case "OldExtendedHeader": + // extended headers that apply to the next entry + EntryType = ExtendedHeader + onend = function () { + self._extended = entry.fields + } + ev = "extendedHeader" + meta = true + break + + case "NextFileHasLongLinkpath": + // set linkpath= in extended header + EntryType = BufferEntry + onend = function () { + self._extended = self._extended || {} + self._extended.linkpath = entry.body + } + ev = "longLinkpath" + meta = true + break + + case "NextFileHasLongPath": + case "OldGnuLongPath": + // set path= in file-extended header + EntryType = BufferEntry + onend = function () { + self._extended = self._extended || {} + self._extended.path = entry.body + } + ev = "longPath" + meta = true + break + + default: + // all the rest we skip, but still set the _entry + // member, so that we can skip over their data appropriately. + // emit an event to say that this is an ignored entry type? + EntryType = Entry + ev = "ignoredEntry" + break + } + + var global, extended + if (meta) { + global = extended = null + } else { + var global = this._global + var extended = this._extended + + // extendedHeader only applies to one entry, so once we start + // an entry, it's over. + this._extended = null + } + entry = new EntryType(header, extended, global) + entry.meta = meta + + // only proxy data events of normal files. + if (!meta) { + entry.on("data", function (c) { + me.emit("data", c) + }) + } + + if (onend) entry.on("end", onend) + + this._entry = entry + var me = this + + entry.on("pause", function () { + me.pause() + }) + + entry.on("resume", function () { + me.resume() + }) + + if (this.listeners("*").length) { + this.emit("*", ev, entry) + } + + this.emit(ev, entry) + + // Zero-byte entry. End immediately. + if (entry.props.size === 0) { + entry.end() + this._entry = null + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/LICENCE b/test/remoting/tar/node_modules/tar/node_modules/block-stream/LICENCE new file mode 100644 index 0000000000..74489e2e26 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/LICENCE @@ -0,0 +1,25 @@ +Copyright (c) Isaac Z. Schlueter +All rights reserved. + +The BSD License + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/block-stream/LICENSE new file mode 100644 index 0000000000..19129e315f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/README.md b/test/remoting/tar/node_modules/tar/node_modules/block-stream/README.md new file mode 100644 index 0000000000..c16e9c4688 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/README.md @@ -0,0 +1,14 @@ +# block-stream + +A stream of blocks. + +Write data into it, and it'll output data in buffer blocks the size you +specify, padding with zeroes if necessary. + +```javascript +var block = new BlockStream(512) +fs.createReadStream("some-file").pipe(block) +block.pipe(fs.createWriteStream("block-file")) +``` + +When `.end()` or `.flush()` is called, it'll pad the block with zeroes. diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/block-stream-pause.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/block-stream-pause.js new file mode 100644 index 0000000000..9328844aa6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/block-stream-pause.js @@ -0,0 +1,70 @@ +var BlockStream = require("../block-stream.js") + +var blockSizes = [16, 25, 1024] + , writeSizes = [4, 8, 15, 16, 17, 64, 100] + , writeCounts = [1, 10, 100] + , tap = require("tap") + +writeCounts.forEach(function (writeCount) { +blockSizes.forEach(function (blockSize) { +writeSizes.forEach(function (writeSize) { + tap.test("writeSize=" + writeSize + + " blockSize="+blockSize + + " writeCount="+writeCount, function (t) { + var f = new BlockStream(blockSize, {nopad: true }) + + var actualChunks = 0 + var actualBytes = 0 + var timeouts = 0 + + f.on("data", function (c) { + timeouts ++ + + actualChunks ++ + actualBytes += c.length + + // make sure that no data gets corrupted, and basic sanity + var before = c.toString() + // simulate a slow write operation + f.pause() + setTimeout(function () { + timeouts -- + + var after = c.toString() + t.equal(after, before, "should not change data") + + // now corrupt it, to find leaks. + for (var i = 0; i < c.length; i ++) { + c[i] = "x".charCodeAt(0) + } + f.resume() + }, 100) + }) + + f.on("end", function () { + // round up to the nearest block size + var expectChunks = Math.ceil(writeSize * writeCount * 2 / blockSize) + var expectBytes = writeSize * writeCount * 2 + t.equal(actualBytes, expectBytes, + "bytes=" + expectBytes + " writeSize=" + writeSize) + t.equal(actualChunks, expectChunks, + "chunks=" + expectChunks + " writeSize=" + writeSize) + + // wait for all the timeout checks to finish, then end the test + setTimeout(function WAIT () { + if (timeouts > 0) return setTimeout(WAIT) + t.end() + }, 100) + }) + + for (var i = 0; i < writeCount; i ++) { + var a = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) a[j] = "a".charCodeAt(0) + var b = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) b[j] = "b".charCodeAt(0) + f.write(a) + f.write(b) + } + f.end() + }) +}) }) }) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/block-stream.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/block-stream.js new file mode 100644 index 0000000000..1141f3a84c --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/block-stream.js @@ -0,0 +1,68 @@ +var BlockStream = require("../block-stream.js") + +var blockSizes = [16, 25, 1024] + , writeSizes = [4, 8, 15, 16, 17, 64, 100] + , writeCounts = [1, 10, 100] + , tap = require("tap") + +writeCounts.forEach(function (writeCount) { +blockSizes.forEach(function (blockSize) { +writeSizes.forEach(function (writeSize) { + tap.test("writeSize=" + writeSize + + " blockSize="+blockSize + + " writeCount="+writeCount, function (t) { + var f = new BlockStream(blockSize, {nopad: true }) + + var actualChunks = 0 + var actualBytes = 0 + var timeouts = 0 + + f.on("data", function (c) { + timeouts ++ + + actualChunks ++ + actualBytes += c.length + + // make sure that no data gets corrupted, and basic sanity + var before = c.toString() + // simulate a slow write operation + setTimeout(function () { + timeouts -- + + var after = c.toString() + t.equal(after, before, "should not change data") + + // now corrupt it, to find leaks. + for (var i = 0; i < c.length; i ++) { + c[i] = "x".charCodeAt(0) + } + }, 100) + }) + + f.on("end", function () { + // round up to the nearest block size + var expectChunks = Math.ceil(writeSize * writeCount * 2 / blockSize) + var expectBytes = writeSize * writeCount * 2 + t.equal(actualBytes, expectBytes, + "bytes=" + expectBytes + " writeSize=" + writeSize) + t.equal(actualChunks, expectChunks, + "chunks=" + expectChunks + " writeSize=" + writeSize) + + // wait for all the timeout checks to finish, then end the test + setTimeout(function WAIT () { + if (timeouts > 0) return setTimeout(WAIT) + t.end() + }, 100) + }) + + for (var i = 0; i < writeCount; i ++) { + var a = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) a[j] = "a".charCodeAt(0) + var b = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) b[j] = "b".charCodeAt(0) + f.write(a) + f.write(b) + } + f.end() + }) +}) }) }) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/dropper-pause.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/dropper-pause.js new file mode 100644 index 0000000000..93e4068eea --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/dropper-pause.js @@ -0,0 +1,70 @@ +var BlockStream = require("dropper") + +var blockSizes = [16, 25, 1024] + , writeSizes = [4, 8, 15, 16, 17, 64, 100] + , writeCounts = [1, 10, 100] + , tap = require("tap") + +writeCounts.forEach(function (writeCount) { +blockSizes.forEach(function (blockSize) { +writeSizes.forEach(function (writeSize) { + tap.test("writeSize=" + writeSize + + " blockSize="+blockSize + + " writeCount="+writeCount, function (t) { + var f = new BlockStream(blockSize, {nopad: true }) + + var actualChunks = 0 + var actualBytes = 0 + var timeouts = 0 + + f.on("data", function (c) { + timeouts ++ + + actualChunks ++ + actualBytes += c.length + + // make sure that no data gets corrupted, and basic sanity + var before = c.toString() + // simulate a slow write operation + f.pause() + setTimeout(function () { + timeouts -- + + var after = c.toString() + t.equal(after, before, "should not change data") + + // now corrupt it, to find leaks. + for (var i = 0; i < c.length; i ++) { + c[i] = "x".charCodeAt(0) + } + f.resume() + }, 100) + }) + + f.on("end", function () { + // round up to the nearest block size + var expectChunks = Math.ceil(writeSize * writeCount * 2 / blockSize) + var expectBytes = writeSize * writeCount * 2 + t.equal(actualBytes, expectBytes, + "bytes=" + expectBytes + " writeSize=" + writeSize) + t.equal(actualChunks, expectChunks, + "chunks=" + expectChunks + " writeSize=" + writeSize) + + // wait for all the timeout checks to finish, then end the test + setTimeout(function WAIT () { + if (timeouts > 0) return setTimeout(WAIT) + t.end() + }, 100) + }) + + for (var i = 0; i < writeCount; i ++) { + var a = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) a[j] = "a".charCodeAt(0) + var b = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) b[j] = "b".charCodeAt(0) + f.write(a) + f.write(b) + } + f.end() + }) +}) }) }) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/dropper.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/dropper.js new file mode 100644 index 0000000000..55fa133054 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/bench/dropper.js @@ -0,0 +1,68 @@ +var BlockStream = require("dropper") + +var blockSizes = [16, 25, 1024] + , writeSizes = [4, 8, 15, 16, 17, 64, 100] + , writeCounts = [1, 10, 100] + , tap = require("tap") + +writeCounts.forEach(function (writeCount) { +blockSizes.forEach(function (blockSize) { +writeSizes.forEach(function (writeSize) { + tap.test("writeSize=" + writeSize + + " blockSize="+blockSize + + " writeCount="+writeCount, function (t) { + var f = new BlockStream(blockSize, {nopad: true }) + + var actualChunks = 0 + var actualBytes = 0 + var timeouts = 0 + + f.on("data", function (c) { + timeouts ++ + + actualChunks ++ + actualBytes += c.length + + // make sure that no data gets corrupted, and basic sanity + var before = c.toString() + // simulate a slow write operation + setTimeout(function () { + timeouts -- + + var after = c.toString() + t.equal(after, before, "should not change data") + + // now corrupt it, to find leaks. + for (var i = 0; i < c.length; i ++) { + c[i] = "x".charCodeAt(0) + } + }, 100) + }) + + f.on("end", function () { + // round up to the nearest block size + var expectChunks = Math.ceil(writeSize * writeCount * 2 / blockSize) + var expectBytes = writeSize * writeCount * 2 + t.equal(actualBytes, expectBytes, + "bytes=" + expectBytes + " writeSize=" + writeSize) + t.equal(actualChunks, expectChunks, + "chunks=" + expectChunks + " writeSize=" + writeSize) + + // wait for all the timeout checks to finish, then end the test + setTimeout(function WAIT () { + if (timeouts > 0) return setTimeout(WAIT) + t.end() + }, 100) + }) + + for (var i = 0; i < writeCount; i ++) { + var a = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) a[j] = "a".charCodeAt(0) + var b = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) b[j] = "b".charCodeAt(0) + f.write(a) + f.write(b) + } + f.end() + }) +}) }) }) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/block-stream.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/block-stream.js new file mode 100644 index 0000000000..008de035c2 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/block-stream.js @@ -0,0 +1,209 @@ +// write data to it, and it'll emit data in 512 byte blocks. +// if you .end() or .flush(), it'll emit whatever it's got, +// padded with nulls to 512 bytes. + +module.exports = BlockStream + +var Stream = require("stream").Stream + , inherits = require("inherits") + , assert = require("assert").ok + , debug = process.env.DEBUG ? console.error : function () {} + +function BlockStream (size, opt) { + this.writable = this.readable = true + this._opt = opt || {} + this._chunkSize = size || 512 + this._offset = 0 + this._buffer = [] + this._bufferLength = 0 + if (this._opt.nopad) this._zeroes = false + else { + this._zeroes = new Buffer(this._chunkSize) + for (var i = 0; i < this._chunkSize; i ++) { + this._zeroes[i] = 0 + } + } +} + +inherits(BlockStream, Stream) + +BlockStream.prototype.write = function (c) { + // debug(" BS write", c) + if (this._ended) throw new Error("BlockStream: write after end") + if (c && !Buffer.isBuffer(c)) c = new Buffer(c + "") + if (c.length) { + this._buffer.push(c) + this._bufferLength += c.length + } + // debug("pushed onto buffer", this._bufferLength) + if (this._bufferLength >= this._chunkSize) { + if (this._paused) { + // debug(" BS paused, return false, need drain") + this._needDrain = true + return false + } + this._emitChunk() + } + return true +} + +BlockStream.prototype.pause = function () { + // debug(" BS pausing") + this._paused = true +} + +BlockStream.prototype.resume = function () { + // debug(" BS resume") + this._paused = false + return this._emitChunk() +} + +BlockStream.prototype.end = function (chunk) { + // debug("end", chunk) + if (typeof chunk === "function") cb = chunk, chunk = null + if (chunk) this.write(chunk) + this._ended = true + this.flush() +} + +BlockStream.prototype.flush = function () { + this._emitChunk(true) +} + +BlockStream.prototype._emitChunk = function (flush) { + // debug("emitChunk flush=%j emitting=%j paused=%j", flush, this._emitting, this._paused) + + // emit a chunk + if (flush && this._zeroes) { + // debug(" BS push zeroes", this._bufferLength) + // push a chunk of zeroes + var padBytes = (this._bufferLength % this._chunkSize) + if (padBytes !== 0) padBytes = this._chunkSize - padBytes + if (padBytes > 0) { + // debug("padBytes", padBytes, this._zeroes.slice(0, padBytes)) + this._buffer.push(this._zeroes.slice(0, padBytes)) + this._bufferLength += padBytes + // debug(this._buffer[this._buffer.length - 1].length, this._bufferLength) + } + } + + if (this._emitting || this._paused) return + this._emitting = true + + // debug(" BS entering loops") + var bufferIndex = 0 + while (this._bufferLength >= this._chunkSize && + (flush || !this._paused)) { + // debug(" BS data emission loop", this._bufferLength) + + var out + , outOffset = 0 + , outHas = this._chunkSize + + while (outHas > 0 && (flush || !this._paused) ) { + // debug(" BS data inner emit loop", this._bufferLength) + var cur = this._buffer[bufferIndex] + , curHas = cur.length - this._offset + // debug("cur=", cur) + // debug("curHas=%j", curHas) + // If it's not big enough to fill the whole thing, then we'll need + // to copy multiple buffers into one. However, if it is big enough, + // then just slice out the part we want, to save unnecessary copying. + // Also, need to copy if we've already done some copying, since buffers + // can't be joined like cons strings. + if (out || curHas < outHas) { + out = out || new Buffer(this._chunkSize) + cur.copy(out, outOffset, + this._offset, this._offset + Math.min(curHas, outHas)) + } else if (cur.length === outHas && this._offset === 0) { + // shortcut -- cur is exactly long enough, and no offset. + out = cur + } else { + // slice out the piece of cur that we need. + out = cur.slice(this._offset, this._offset + outHas) + } + + if (curHas > outHas) { + // means that the current buffer couldn't be completely output + // update this._offset to reflect how much WAS written + this._offset += outHas + outHas = 0 + } else { + // output the entire current chunk. + // toss it away + outHas -= curHas + outOffset += curHas + bufferIndex ++ + this._offset = 0 + } + } + + this._bufferLength -= this._chunkSize + assert(out.length === this._chunkSize) + // debug("emitting data", out) + // debug(" BS emitting, paused=%j", this._paused, this._bufferLength) + this.emit("data", out) + out = null + } + // debug(" BS out of loops", this._bufferLength) + + // whatever is left, it's not enough to fill up a block, or we're paused + this._buffer = this._buffer.slice(bufferIndex) + if (this._paused) { + // debug(" BS paused, leaving", this._bufferLength) + this._needsDrain = true + this._emitting = false + return + } + + // if flushing, and not using null-padding, then need to emit the last + // chunk(s) sitting in the queue. We know that it's not enough to + // fill up a whole block, because otherwise it would have been emitted + // above, but there may be some offset. + var l = this._buffer.length + if (flush && !this._zeroes && l) { + if (l === 1) { + if (this._offset) { + this.emit("data", this._buffer[0].slice(this._offset)) + } else { + this.emit("data", this._buffer[0]) + } + } else { + var outHas = this._bufferLength + , out = new Buffer(outHas) + , outOffset = 0 + for (var i = 0; i < l; i ++) { + var cur = this._buffer[i] + , curHas = cur.length - this._offset + cur.copy(out, outOffset, this._offset) + this._offset = 0 + outOffset += curHas + this._bufferLength -= curHas + } + this.emit("data", out) + } + // truncate + this._buffer.length = 0 + this._bufferLength = 0 + this._offset = 0 + } + + // now either drained or ended + // debug("either draining, or ended", this._bufferLength, this._ended) + // means that we've flushed out all that we can so far. + if (this._needDrain) { + // debug("emitting drain", this._bufferLength) + this._needDrain = false + this.emit("drain") + } + + if ((this._bufferLength === 0) && this._ended && !this._endEmitted) { + // debug("emitting end", this._bufferLength) + this._endEmitted = true + this.emit("end") + } + + this._emitting = false + + // debug(" BS no longer emitting", flush, this._paused, this._emitting, this._bufferLength, this._chunkSize) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/package.json b/test/remoting/tar/node_modules/tar/node_modules/block-stream/package.json new file mode 100644 index 0000000000..97d9d42aba --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/package.json @@ -0,0 +1,55 @@ +{ + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "name": "block-stream", + "description": "a stream of blocks", + "version": "0.0.8", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/block-stream.git" + }, + "engines": { + "node": "0.4 || >=0.5.8" + }, + "main": "block-stream.js", + "dependencies": { + "inherits": "~2.0.0" + }, + "devDependencies": { + "tap": "0.x" + }, + "scripts": { + "test": "tap test/" + }, + "license": "ISC", + "gitHead": "b35520314f4763af0788d65a846bb43d9c0a8f02", + "bugs": { + "url": "https://github.com/isaacs/block-stream/issues" + }, + "homepage": "https://github.com/isaacs/block-stream#readme", + "_id": "block-stream@0.0.8", + "_shasum": "0688f46da2bbf9cff0c4f68225a0cb95cbe8a46b", + "_from": "block-stream@*", + "_npmVersion": "2.10.0", + "_nodeVersion": "2.0.1", + "_npmUser": { + "name": "isaacs", + "email": "isaacs@npmjs.com" + }, + "dist": { + "shasum": "0688f46da2bbf9cff0c4f68225a0cb95cbe8a46b", + "tarball": "http://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "directories": {}, + "_resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/basic.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/basic.js new file mode 100644 index 0000000000..b4b930511e --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/basic.js @@ -0,0 +1,27 @@ +var tap = require("tap") + , BlockStream = require("../block-stream.js") + +tap.test("basic test", function (t) { + var b = new BlockStream(16) + var fs = require("fs") + var fstr = fs.createReadStream(__filename, {encoding: "utf8"}) + fstr.pipe(b) + + var stat + t.doesNotThrow(function () { + stat = fs.statSync(__filename) + }, "stat should not throw") + + var totalBytes = 0 + b.on("data", function (c) { + t.equal(c.length, 16, "chunks should be 16 bytes long") + t.type(c, Buffer, "chunks should be buffer objects") + totalBytes += c.length + }) + b.on("end", function () { + var expectedBytes = stat.size + (16 - stat.size % 16) + t.equal(totalBytes, expectedBytes, "Should be multiple of 16") + t.end() + }) + +}) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/nopad-thorough.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/nopad-thorough.js new file mode 100644 index 0000000000..7a8de88b5b --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/nopad-thorough.js @@ -0,0 +1,68 @@ +var BlockStream = require("../block-stream.js") + +var blockSizes = [16]//, 25]//, 1024] + , writeSizes = [4, 15, 16, 17, 64 ]//, 64, 100] + , writeCounts = [1, 10]//, 100] + , tap = require("tap") + +writeCounts.forEach(function (writeCount) { +blockSizes.forEach(function (blockSize) { +writeSizes.forEach(function (writeSize) { + tap.test("writeSize=" + writeSize + + " blockSize="+blockSize + + " writeCount="+writeCount, function (t) { + var f = new BlockStream(blockSize, {nopad: true }) + + var actualChunks = 0 + var actualBytes = 0 + var timeouts = 0 + + f.on("data", function (c) { + timeouts ++ + + actualChunks ++ + actualBytes += c.length + + // make sure that no data gets corrupted, and basic sanity + var before = c.toString() + // simulate a slow write operation + setTimeout(function () { + timeouts -- + + var after = c.toString() + t.equal(after, before, "should not change data") + + // now corrupt it, to find leaks. + for (var i = 0; i < c.length; i ++) { + c[i] = "x".charCodeAt(0) + } + }, 100) + }) + + f.on("end", function () { + // round up to the nearest block size + var expectChunks = Math.ceil(writeSize * writeCount * 2 / blockSize) + var expectBytes = writeSize * writeCount * 2 + t.equal(actualBytes, expectBytes, + "bytes=" + expectBytes + " writeSize=" + writeSize) + t.equal(actualChunks, expectChunks, + "chunks=" + expectChunks + " writeSize=" + writeSize) + + // wait for all the timeout checks to finish, then end the test + setTimeout(function WAIT () { + if (timeouts > 0) return setTimeout(WAIT) + t.end() + }, 100) + }) + + for (var i = 0; i < writeCount; i ++) { + var a = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) a[j] = "a".charCodeAt(0) + var b = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) b[j] = "b".charCodeAt(0) + f.write(a) + f.write(b) + } + f.end() + }) +}) }) }) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/nopad.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/nopad.js new file mode 100644 index 0000000000..6d38429fbc --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/nopad.js @@ -0,0 +1,57 @@ +var BlockStream = require("../") +var tap = require("tap") + + +tap.test("don't pad, small writes", function (t) { + var f = new BlockStream(16, { nopad: true }) + t.plan(1) + + f.on("data", function (c) { + t.equal(c.toString(), "abc", "should get 'abc'") + }) + + f.on("end", function () { t.end() }) + + f.write(new Buffer("a")) + f.write(new Buffer("b")) + f.write(new Buffer("c")) + f.end() +}) + +tap.test("don't pad, exact write", function (t) { + var f = new BlockStream(16, { nopad: true }) + t.plan(1) + + var first = true + f.on("data", function (c) { + if (first) { + first = false + t.equal(c.toString(), "abcdefghijklmnop", "first chunk") + } else { + t.fail("should only get one") + } + }) + + f.on("end", function () { t.end() }) + + f.end(new Buffer("abcdefghijklmnop")) +}) + +tap.test("don't pad, big write", function (t) { + var f = new BlockStream(16, { nopad: true }) + t.plan(2) + + var first = true + f.on("data", function (c) { + if (first) { + first = false + t.equal(c.toString(), "abcdefghijklmnop", "first chunk") + } else { + t.equal(c.toString(), "q") + } + }) + + f.on("end", function () { t.end() }) + + f.end(new Buffer("abcdefghijklmnopq")) +}) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/pause-resume.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/pause-resume.js new file mode 100644 index 0000000000..64d0d091da --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/pause-resume.js @@ -0,0 +1,73 @@ +var BlockStream = require("../block-stream.js") + +var blockSizes = [16] + , writeSizes = [15, 16, 17] + , writeCounts = [1, 10]//, 100] + , tap = require("tap") + +writeCounts.forEach(function (writeCount) { +blockSizes.forEach(function (blockSize) { +writeSizes.forEach(function (writeSize) { + tap.test("writeSize=" + writeSize + + " blockSize="+blockSize + + " writeCount="+writeCount, function (t) { + var f = new BlockStream(blockSize) + + var actualChunks = 0 + var actualBytes = 0 + var timeouts = 0 + var paused = false + + f.on("data", function (c) { + timeouts ++ + t.notOk(paused, "should not be paused when emitting data") + + actualChunks ++ + actualBytes += c.length + + // make sure that no data gets corrupted, and basic sanity + var before = c.toString() + // simulate a slow write operation + paused = true + f.pause() + process.nextTick(function () { + var after = c.toString() + t.equal(after, before, "should not change data") + + // now corrupt it, to find leaks. + for (var i = 0; i < c.length; i ++) { + c[i] = "x".charCodeAt(0) + } + paused = false + f.resume() + timeouts -- + }) + }) + + f.on("end", function () { + // round up to the nearest block size + var expectChunks = Math.ceil(writeSize * writeCount * 2 / blockSize) + var expectBytes = expectChunks * blockSize + t.equal(actualBytes, expectBytes, + "bytes=" + expectBytes + " writeSize=" + writeSize) + t.equal(actualChunks, expectChunks, + "chunks=" + expectChunks + " writeSize=" + writeSize) + + // wait for all the timeout checks to finish, then end the test + setTimeout(function WAIT () { + if (timeouts > 0) return setTimeout(WAIT) + t.end() + }, 200) + }) + + for (var i = 0; i < writeCount; i ++) { + var a = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) a[j] = "a".charCodeAt(0) + var b = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) b[j] = "b".charCodeAt(0) + f.write(a) + f.write(b) + } + f.end() + }) +}) }) }) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/thorough.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/thorough.js new file mode 100644 index 0000000000..1cc9ea08a3 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/thorough.js @@ -0,0 +1,68 @@ +var BlockStream = require("../block-stream.js") + +var blockSizes = [16]//, 25]//, 1024] + , writeSizes = [4, 15, 16, 17, 64 ]//, 64, 100] + , writeCounts = [1, 10]//, 100] + , tap = require("tap") + +writeCounts.forEach(function (writeCount) { +blockSizes.forEach(function (blockSize) { +writeSizes.forEach(function (writeSize) { + tap.test("writeSize=" + writeSize + + " blockSize="+blockSize + + " writeCount="+writeCount, function (t) { + var f = new BlockStream(blockSize) + + var actualChunks = 0 + var actualBytes = 0 + var timeouts = 0 + + f.on("data", function (c) { + timeouts ++ + + actualChunks ++ + actualBytes += c.length + + // make sure that no data gets corrupted, and basic sanity + var before = c.toString() + // simulate a slow write operation + setTimeout(function () { + timeouts -- + + var after = c.toString() + t.equal(after, before, "should not change data") + + // now corrupt it, to find leaks. + for (var i = 0; i < c.length; i ++) { + c[i] = "x".charCodeAt(0) + } + }, 100) + }) + + f.on("end", function () { + // round up to the nearest block size + var expectChunks = Math.ceil(writeSize * writeCount * 2 / blockSize) + var expectBytes = expectChunks * blockSize + t.equal(actualBytes, expectBytes, + "bytes=" + expectBytes + " writeSize=" + writeSize) + t.equal(actualChunks, expectChunks, + "chunks=" + expectChunks + " writeSize=" + writeSize) + + // wait for all the timeout checks to finish, then end the test + setTimeout(function WAIT () { + if (timeouts > 0) return setTimeout(WAIT) + t.end() + }, 100) + }) + + for (var i = 0; i < writeCount; i ++) { + var a = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) a[j] = "a".charCodeAt(0) + var b = new Buffer(writeSize); + for (var j = 0; j < writeSize; j ++) b[j] = "b".charCodeAt(0) + f.write(a) + f.write(b) + } + f.end() + }) +}) }) }) diff --git a/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/two-stream.js b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/two-stream.js new file mode 100644 index 0000000000..c6db79a43d --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/block-stream/test/two-stream.js @@ -0,0 +1,59 @@ +var log = console.log, + assert = require( 'assert' ), + BlockStream = require("../block-stream.js"), + isize = 0, tsize = 0, fsize = 0, psize = 0, i = 0, + filter = null, paper = null, stack = null, + +// a source data buffer +tsize = 1 * 1024; // <- 1K +stack = new Buffer( tsize ); +for ( ; i < tsize; i++) stack[i] = "x".charCodeAt(0); + +isize = 1 * 1024; // <- initial packet size with 4K no bug! +fsize = 2 * 1024 ; // <- first block-stream size +psize = Math.ceil( isize / 6 ); // <- second block-stream size + +fexpected = Math.ceil( tsize / fsize ); // <- packets expected for first +pexpected = Math.ceil( tsize / psize ); // <- packets expected for second + + +filter = new BlockStream( fsize, { nopad : true } ); +paper = new BlockStream( psize, { nopad : true } ); + + +var fcounter = 0; +filter.on( 'data', function (c) { + // verify that they're not null-padded + for (var i = 0; i < c.length; i ++) { + assert.strictEqual(c[i], "x".charCodeAt(0)) + } + ++fcounter; +} ); + +var pcounter = 0; +paper.on( 'data', function (c) { + // verify that they're not null-padded + for (var i = 0; i < c.length; i ++) { + assert.strictEqual(c[i], "x".charCodeAt(0)) + } + ++pcounter; +} ); + +filter.pipe( paper ); + +filter.on( 'end', function () { + log("fcounter: %s === %s", fcounter, fexpected) + assert.strictEqual( fcounter, fexpected ); +} ); + +paper.on( 'end', function () { + log("pcounter: %s === %s", pcounter, pexpected); + assert.strictEqual( pcounter, pexpected ); +} ); + + +for ( i = 0, j = isize; j <= tsize; j += isize ) { + filter.write( stack.slice( j - isize, j ) ); +} + +filter.end(); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/.npmignore b/test/remoting/tar/node_modules/tar/node_modules/fstream/.npmignore new file mode 100644 index 0000000000..494272a81a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/.npmignore @@ -0,0 +1,5 @@ +.*.swp +node_modules/ +examples/deep-copy/ +examples/path/ +examples/filter-copy/ diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/.travis.yml b/test/remoting/tar/node_modules/tar/node_modules/fstream/.travis.yml new file mode 100644 index 0000000000..a092c82b26 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: + - iojs + - 0.12 + - 0.10 + - 0.8 +before_install: + - "npm config set spin false" + - "npm install -g npm/npm" diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/LICENSE new file mode 100644 index 0000000000..19129e315f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/README.md new file mode 100644 index 0000000000..9d8cb77e5c --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/README.md @@ -0,0 +1,76 @@ +Like FS streams, but with stat on them, and supporting directories and +symbolic links, as well as normal files. Also, you can use this to set +the stats on a file, even if you don't change its contents, or to create +a symlink, etc. + +So, for example, you can "write" a directory, and it'll call `mkdir`. You +can specify a uid and gid, and it'll call `chown`. You can specify a +`mtime` and `atime`, and it'll call `utimes`. You can call it a symlink +and provide a `linkpath` and it'll call `symlink`. + +Note that it won't automatically resolve symbolic links. So, if you +call `fstream.Reader('/some/symlink')` then you'll get an object +that stats and then ends immediately (since it has no data). To follow +symbolic links, do this: `fstream.Reader({path:'/some/symlink', follow: +true })`. + +There are various checks to make sure that the bytes emitted are the +same as the intended size, if the size is set. + +## Examples + +```javascript +fstream + .Writer({ path: "path/to/file" + , mode: 0755 + , size: 6 + }) + .write("hello\n") + .end() +``` + +This will create the directories if they're missing, and then write +`hello\n` into the file, chmod it to 0755, and assert that 6 bytes have +been written when it's done. + +```javascript +fstream + .Writer({ path: "path/to/file" + , mode: 0755 + , size: 6 + , flags: "a" + }) + .write("hello\n") + .end() +``` + +You can pass flags in, if you want to append to a file. + +```javascript +fstream + .Writer({ path: "path/to/symlink" + , linkpath: "./file" + , SymbolicLink: true + , mode: "0755" // octal strings supported + }) + .end() +``` + +If isSymbolicLink is a function, it'll be called, and if it returns +true, then it'll treat it as a symlink. If it's not a function, then +any truish value will make a symlink, or you can set `type: +'SymbolicLink'`, which does the same thing. + +Note that the linkpath is relative to the symbolic link location, not +the parent dir or cwd. + +```javascript +fstream + .Reader("path/to/dir") + .pipe(fstream.Writer("path/to/other/dir")) +``` + +This will do like `cp -Rp path/to/dir path/to/other/dir`. If the other +dir exists and isn't a directory, then it'll emit an error. It'll also +set the uid, gid, mode, etc. to be identical. In this way, it's more +like `rsync -a` than simply a copy. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/filter-pipe.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/filter-pipe.js new file mode 100644 index 0000000000..83dadef8a6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/filter-pipe.js @@ -0,0 +1,134 @@ +var fstream = require('../fstream.js') +var path = require('path') + +var r = fstream.Reader({ + path: path.dirname(__dirname), + filter: function () { + return !this.basename.match(/^\./) && + !this.basename.match(/^node_modules$/) && + !this.basename.match(/^deep-copy$/) && + !this.basename.match(/^filter-copy$/) + } +}) + +// this writer will only write directories +var w = fstream.Writer({ + path: path.resolve(__dirname, 'filter-copy'), + type: 'Directory', + filter: function () { + return this.type === 'Directory' + } +}) + +var indent = '' + +r.on('entry', appears) +r.on('ready', function () { + console.error('ready to begin!', r.path) +}) + +function appears (entry) { + console.error(indent + 'a %s appears!', entry.type, entry.basename, typeof entry.basename) + if (foggy) { + console.error('FOGGY!') + var p = entry + do { + console.error(p.depth, p.path, p._paused) + p = p.parent + } while (p) + + throw new Error('\u001b[mshould not have entries while foggy') + } + indent += '\t' + entry.on('data', missile(entry)) + entry.on('end', runaway(entry)) + entry.on('entry', appears) +} + +var foggy +function missile (entry) { + function liftFog (who) { + if (!foggy) return + if (who) { + console.error('%s breaks the spell!', who && who.path) + } else { + console.error('the spell expires!') + } + console.error('\u001b[mthe fog lifts!\n') + clearTimeout(foggy) + foggy = null + if (entry._paused) entry.resume() + } + + if (entry.type === 'Directory') { + var ended = false + entry.once('end', function () { ended = true }) + return function (c) { + // throw in some pathological pause()/resume() behavior + // just for extra fun. + process.nextTick(function () { + if (!foggy && !ended) { // && Math.random() < 0.3) { + console.error(indent + '%s casts a spell', entry.basename) + console.error('\na slowing fog comes over the battlefield...\n\u001b[32m') + entry.pause() + entry.once('resume', liftFog) + foggy = setTimeout(liftFog, 1000) + } + }) + } + } + + return function (c) { + var e = Math.random() < 0.5 + console.error(indent + '%s %s for %d damage!', + entry.basename, + e ? 'is struck' : 'fires a chunk', + c.length) + } +} + +function runaway (entry) { + return function () { + var e = Math.random() < 0.5 + console.error(indent + '%s %s', + entry.basename, + e ? 'turns to flee' : 'is vanquished!') + indent = indent.slice(0, -1) + } +} + +w.on('entry', attacks) +// w.on('ready', function () { attacks(w) }) +function attacks (entry) { + console.error(indent + '%s %s!', entry.basename, + entry.type === 'Directory' ? 'calls for backup' : 'attacks') + entry.on('entry', attacks) +} + +var ended = false +var i = 1 +r.on('end', function () { + if (foggy) clearTimeout(foggy) + console.error("\u001b[mIT'S OVER!!") + console.error('A WINNAR IS YOU!') + + console.log('ok ' + (i++) + ' A WINNAR IS YOU') + ended = true + // now go through and verify that everything in there is a dir. + var p = path.resolve(__dirname, 'filter-copy') + var checker = fstream.Reader({ path: p }) + checker.checker = true + checker.on('child', function (e) { + var ok = e.type === 'Directory' + console.log((ok ? '' : 'not ') + 'ok ' + (i++) + + ' should be a dir: ' + + e.path.substr(checker.path.length + 1)) + }) +}) + +process.on('exit', function () { + console.log((ended ? '' : 'not ') + 'ok ' + (i) + ' ended') + console.log('1..' + i) +}) + +r.pipe(w) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/pipe.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/pipe.js new file mode 100644 index 0000000000..3de42ef32b --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/pipe.js @@ -0,0 +1,118 @@ +var fstream = require('../fstream.js') +var path = require('path') + +var r = fstream.Reader({ + path: path.dirname(__dirname), + filter: function () { + return !this.basename.match(/^\./) && + !this.basename.match(/^node_modules$/) && + !this.basename.match(/^deep-copy$/) + } +}) + +var w = fstream.Writer({ + path: path.resolve(__dirname, 'deep-copy'), + type: 'Directory' +}) + +var indent = '' + +r.on('entry', appears) +r.on('ready', function () { + console.error('ready to begin!', r.path) +}) + +function appears (entry) { + console.error(indent + 'a %s appears!', entry.type, entry.basename, typeof entry.basename, entry) + if (foggy) { + console.error('FOGGY!') + var p = entry + do { + console.error(p.depth, p.path, p._paused) + p = p.parent + } while (p) + + throw new Error('\u001b[mshould not have entries while foggy') + } + indent += '\t' + entry.on('data', missile(entry)) + entry.on('end', runaway(entry)) + entry.on('entry', appears) +} + +var foggy +function missile (entry) { + function liftFog (who) { + if (!foggy) return + if (who) { + console.error('%s breaks the spell!', who && who.path) + } else { + console.error('the spell expires!') + } + console.error('\u001b[mthe fog lifts!\n') + clearTimeout(foggy) + foggy = null + if (entry._paused) entry.resume() + } + + if (entry.type === 'Directory') { + var ended = false + entry.once('end', function () { ended = true }) + return function (c) { + // throw in some pathological pause()/resume() behavior + // just for extra fun. + process.nextTick(function () { + if (!foggy && !ended) { // && Math.random() < 0.3) { + console.error(indent + '%s casts a spell', entry.basename) + console.error('\na slowing fog comes over the battlefield...\n\u001b[32m') + entry.pause() + entry.once('resume', liftFog) + foggy = setTimeout(liftFog, 10) + } + }) + } + } + + return function (c) { + var e = Math.random() < 0.5 + console.error(indent + '%s %s for %d damage!', + entry.basename, + e ? 'is struck' : 'fires a chunk', + c.length) + } +} + +function runaway (entry) { + return function () { + var e = Math.random() < 0.5 + console.error(indent + '%s %s', + entry.basename, + e ? 'turns to flee' : 'is vanquished!') + indent = indent.slice(0, -1) + } +} + +w.on('entry', attacks) +// w.on('ready', function () { attacks(w) }) +function attacks (entry) { + console.error(indent + '%s %s!', entry.basename, + entry.type === 'Directory' ? 'calls for backup' : 'attacks') + entry.on('entry', attacks) +} + +var ended = false +r.on('end', function () { + if (foggy) clearTimeout(foggy) + console.error("\u001b[mIT'S OVER!!") + console.error('A WINNAR IS YOU!') + + console.log('ok 1 A WINNAR IS YOU') + ended = true +}) + +process.on('exit', function () { + console.log((ended ? '' : 'not ') + 'ok 2 ended') + console.log('1..2') +}) + +r.pipe(w) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/reader.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/reader.js new file mode 100644 index 0000000000..19affbe7e6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/reader.js @@ -0,0 +1,68 @@ +var fstream = require('../fstream.js') +var tap = require('tap') +var fs = require('fs') +var path = require('path') +var dir = path.dirname(__dirname) + +tap.test('reader test', function (t) { + var children = -1 + var gotReady = false + var ended = false + + var r = fstream.Reader({ + path: dir, + filter: function () { + // return this.parent === r + return this.parent === r || this === r + } + }) + + r.on('ready', function () { + gotReady = true + children = fs.readdirSync(dir).length + console.error('Setting expected children to ' + children) + t.equal(r.type, 'Directory', 'should be a directory') + }) + + r.on('entry', function (entry) { + children-- + if (!gotReady) { + t.fail('children before ready!') + } + t.equal(entry.dirname, r.path, 'basename is parent dir') + }) + + r.on('error', function (er) { + t.fail(er) + t.end() + process.exit(1) + }) + + r.on('end', function () { + t.equal(children, 0, 'should have seen all children') + ended = true + }) + + var closed = false + r.on('close', function () { + t.ok(ended, 'saw end before close') + t.notOk(closed, 'close should only happen once') + closed = true + t.end() + }) +}) + +tap.test('reader error test', function (t) { + // assumes non-root on a *nix system + var r = fstream.Reader({ path: '/etc/shadow' }) + + r.once('error', function (er) { + t.ok(true) + t.end() + }) + + r.on('end', function () { + t.fail('reader ended without error') + t.end() + }) +}) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/symlink-write.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/symlink-write.js new file mode 100644 index 0000000000..19e81eea9f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/examples/symlink-write.js @@ -0,0 +1,27 @@ +var fstream = require('../fstream.js') +var notOpen = false +process.chdir(__dirname) + +fstream + .Writer({ + path: 'path/to/symlink', + linkpath: './file', + isSymbolicLink: true, + mode: '0755' // octal strings supported + }) + .on('close', function () { + notOpen = true + var fs = require('fs') + var s = fs.lstatSync('path/to/symlink') + var isSym = s.isSymbolicLink() + console.log((isSym ? '' : 'not ') + 'ok 1 should be symlink') + var t = fs.readlinkSync('path/to/symlink') + var isTarget = t === './file' + console.log((isTarget ? '' : 'not ') + 'ok 2 should link to ./file') + }) + .end() + +process.on('exit', function () { + console.log((notOpen ? '' : 'not ') + 'ok 3 should be closed') + console.log('1..3') +}) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/fstream.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/fstream.js new file mode 100644 index 0000000000..c0eb3bea78 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/fstream.js @@ -0,0 +1,35 @@ +exports.Abstract = require('./lib/abstract.js') +exports.Reader = require('./lib/reader.js') +exports.Writer = require('./lib/writer.js') + +exports.File = { + Reader: require('./lib/file-reader.js'), + Writer: require('./lib/file-writer.js') +} + +exports.Dir = { + Reader: require('./lib/dir-reader.js'), + Writer: require('./lib/dir-writer.js') +} + +exports.Link = { + Reader: require('./lib/link-reader.js'), + Writer: require('./lib/link-writer.js') +} + +exports.Proxy = { + Reader: require('./lib/proxy-reader.js'), + Writer: require('./lib/proxy-writer.js') +} + +exports.Reader.Dir = exports.DirReader = exports.Dir.Reader +exports.Reader.File = exports.FileReader = exports.File.Reader +exports.Reader.Link = exports.LinkReader = exports.Link.Reader +exports.Reader.Proxy = exports.ProxyReader = exports.Proxy.Reader + +exports.Writer.Dir = exports.DirWriter = exports.Dir.Writer +exports.Writer.File = exports.FileWriter = exports.File.Writer +exports.Writer.Link = exports.LinkWriter = exports.Link.Writer +exports.Writer.Proxy = exports.ProxyWriter = exports.Proxy.Writer + +exports.collect = require('./lib/collect.js') diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/abstract.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/abstract.js new file mode 100644 index 0000000000..97c120e1d5 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/abstract.js @@ -0,0 +1,85 @@ +// the parent class for all fstreams. + +module.exports = Abstract + +var Stream = require('stream').Stream +var inherits = require('inherits') + +function Abstract () { + Stream.call(this) +} + +inherits(Abstract, Stream) + +Abstract.prototype.on = function (ev, fn) { + if (ev === 'ready' && this.ready) { + process.nextTick(fn.bind(this)) + } else { + Stream.prototype.on.call(this, ev, fn) + } + return this +} + +Abstract.prototype.abort = function () { + this._aborted = true + this.emit('abort') +} + +Abstract.prototype.destroy = function () {} + +Abstract.prototype.warn = function (msg, code) { + var self = this + var er = decorate(msg, code, self) + if (!self.listeners('warn')) { + console.error('%s %s\n' + + 'path = %s\n' + + 'syscall = %s\n' + + 'fstream_type = %s\n' + + 'fstream_path = %s\n' + + 'fstream_unc_path = %s\n' + + 'fstream_class = %s\n' + + 'fstream_stack =\n%s\n', + code || 'UNKNOWN', + er.stack, + er.path, + er.syscall, + er.fstream_type, + er.fstream_path, + er.fstream_unc_path, + er.fstream_class, + er.fstream_stack.join('\n')) + } else { + self.emit('warn', er) + } +} + +Abstract.prototype.info = function (msg, code) { + this.emit('info', msg, code) +} + +Abstract.prototype.error = function (msg, code, th) { + var er = decorate(msg, code, this) + if (th) throw er + else this.emit('error', er) +} + +function decorate (er, code, self) { + if (!(er instanceof Error)) er = new Error(er) + er.code = er.code || code + er.path = er.path || self.path + er.fstream_type = er.fstream_type || self.type + er.fstream_path = er.fstream_path || self.path + if (self._path !== self.path) { + er.fstream_unc_path = er.fstream_unc_path || self._path + } + if (self.linkpath) { + er.fstream_linkpath = er.fstream_linkpath || self.linkpath + } + er.fstream_class = er.fstream_class || self.constructor.name + er.fstream_stack = er.fstream_stack || + new Error().stack.split(/\n/).slice(3).map(function (s) { + return s.replace(/^ {4}at /, '') + }) + + return er +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/collect.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/collect.js new file mode 100644 index 0000000000..6245e6ce49 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/collect.js @@ -0,0 +1,68 @@ +module.exports = collect + +function collect (stream) { + if (stream._collected) return + + stream._collected = true + stream.pause() + + stream.on('data', save) + stream.on('end', save) + var buf = [] + function save (b) { + if (typeof b === 'string') b = new Buffer(b) + if (Buffer.isBuffer(b) && !b.length) return + buf.push(b) + } + + stream.on('entry', saveEntry) + var entryBuffer = [] + function saveEntry (e) { + collect(e) + entryBuffer.push(e) + } + + stream.on('proxy', proxyPause) + function proxyPause (p) { + p.pause() + } + + // replace the pipe method with a new version that will + // unlock the buffered stuff. if you just call .pipe() + // without a destination, then it'll re-play the events. + stream.pipe = (function (orig) { + return function (dest) { + // console.error(' === open the pipes', dest && dest.path) + + // let the entries flow through one at a time. + // Once they're all done, then we can resume completely. + var e = 0 + ;(function unblockEntry () { + var entry = entryBuffer[e++] + // console.error(" ==== unblock entry", entry && entry.path) + if (!entry) return resume() + entry.on('end', unblockEntry) + if (dest) dest.add(entry) + else stream.emit('entry', entry) + })() + + function resume () { + stream.removeListener('entry', saveEntry) + stream.removeListener('data', save) + stream.removeListener('end', save) + + stream.pipe = orig + if (dest) stream.pipe(dest) + + buf.forEach(function (b) { + if (b) stream.emit('data', b) + else stream.emit('end') + }) + + stream.resume() + } + + return dest + } + })(stream.pipe) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/dir-reader.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/dir-reader.js new file mode 100644 index 0000000000..820cdc85a8 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/dir-reader.js @@ -0,0 +1,252 @@ +// A thing that emits "entry" events with Reader objects +// Pausing it causes it to stop emitting entry events, and also +// pauses the current entry if there is one. + +module.exports = DirReader + +var fs = require('graceful-fs') +var inherits = require('inherits') +var path = require('path') +var Reader = require('./reader.js') +var assert = require('assert').ok + +inherits(DirReader, Reader) + +function DirReader (props) { + var self = this + if (!(self instanceof DirReader)) { + throw new Error('DirReader must be called as constructor.') + } + + // should already be established as a Directory type + if (props.type !== 'Directory' || !props.Directory) { + throw new Error('Non-directory type ' + props.type) + } + + self.entries = null + self._index = -1 + self._paused = false + self._length = -1 + + if (props.sort) { + this.sort = props.sort + } + + Reader.call(this, props) +} + +DirReader.prototype._getEntries = function () { + var self = this + + // race condition. might pause() before calling _getEntries, + // and then resume, and try to get them a second time. + if (self._gotEntries) return + self._gotEntries = true + + fs.readdir(self._path, function (er, entries) { + if (er) return self.error(er) + + self.entries = entries + + self.emit('entries', entries) + if (self._paused) self.once('resume', processEntries) + else processEntries() + + function processEntries () { + self._length = self.entries.length + if (typeof self.sort === 'function') { + self.entries = self.entries.sort(self.sort.bind(self)) + } + self._read() + } + }) +} + +// start walking the dir, and emit an "entry" event for each one. +DirReader.prototype._read = function () { + var self = this + + if (!self.entries) return self._getEntries() + + if (self._paused || self._currentEntry || self._aborted) { + // console.error('DR paused=%j, current=%j, aborted=%j', self._paused, !!self._currentEntry, self._aborted) + return + } + + self._index++ + if (self._index >= self.entries.length) { + if (!self._ended) { + self._ended = true + self.emit('end') + self.emit('close') + } + return + } + + // ok, handle this one, then. + + // save creating a proxy, by stat'ing the thing now. + var p = path.resolve(self._path, self.entries[self._index]) + assert(p !== self._path) + assert(self.entries[self._index]) + + // set this to prevent trying to _read() again in the stat time. + self._currentEntry = p + fs[ self.props.follow ? 'stat' : 'lstat' ](p, function (er, stat) { + if (er) return self.error(er) + + var who = self._proxy || self + + stat.path = p + stat.basename = path.basename(p) + stat.dirname = path.dirname(p) + var childProps = self.getChildProps.call(who, stat) + childProps.path = p + childProps.basename = path.basename(p) + childProps.dirname = path.dirname(p) + + var entry = Reader(childProps, stat) + + // console.error("DR Entry", p, stat.size) + + self._currentEntry = entry + + // "entry" events are for direct entries in a specific dir. + // "child" events are for any and all children at all levels. + // This nomenclature is not completely final. + + entry.on('pause', function (who) { + if (!self._paused && !entry._disowned) { + self.pause(who) + } + }) + + entry.on('resume', function (who) { + if (self._paused && !entry._disowned) { + self.resume(who) + } + }) + + entry.on('stat', function (props) { + self.emit('_entryStat', entry, props) + if (entry._aborted) return + if (entry._paused) { + entry.once('resume', function () { + self.emit('entryStat', entry, props) + }) + } else self.emit('entryStat', entry, props) + }) + + entry.on('ready', function EMITCHILD () { + // console.error("DR emit child", entry._path) + if (self._paused) { + // console.error(" DR emit child - try again later") + // pause the child, and emit the "entry" event once we drain. + // console.error("DR pausing child entry") + entry.pause(self) + return self.once('resume', EMITCHILD) + } + + // skip over sockets. they can't be piped around properly, + // so there's really no sense even acknowledging them. + // if someone really wants to see them, they can listen to + // the "socket" events. + if (entry.type === 'Socket') { + self.emit('socket', entry) + } else { + self.emitEntry(entry) + } + }) + + var ended = false + entry.on('close', onend) + entry.on('disown', onend) + function onend () { + if (ended) return + ended = true + self.emit('childEnd', entry) + self.emit('entryEnd', entry) + self._currentEntry = null + if (!self._paused) { + self._read() + } + } + + // XXX Remove this. Works in node as of 0.6.2 or so. + // Long filenames should not break stuff. + entry.on('error', function (er) { + if (entry._swallowErrors) { + self.warn(er) + entry.emit('end') + entry.emit('close') + } else { + self.emit('error', er) + } + }) + + // proxy up some events. + ;[ + 'child', + 'childEnd', + 'warn' + ].forEach(function (ev) { + entry.on(ev, self.emit.bind(self, ev)) + }) + }) +} + +DirReader.prototype.disown = function (entry) { + entry.emit('beforeDisown') + entry._disowned = true + entry.parent = entry.root = null + if (entry === this._currentEntry) { + this._currentEntry = null + } + entry.emit('disown') +} + +DirReader.prototype.getChildProps = function () { + return { + depth: this.depth + 1, + root: this.root || this, + parent: this, + follow: this.follow, + filter: this.filter, + sort: this.props.sort, + hardlinks: this.props.hardlinks + } +} + +DirReader.prototype.pause = function (who) { + var self = this + if (self._paused) return + who = who || self + self._paused = true + if (self._currentEntry && self._currentEntry.pause) { + self._currentEntry.pause(who) + } + self.emit('pause', who) +} + +DirReader.prototype.resume = function (who) { + var self = this + if (!self._paused) return + who = who || self + + self._paused = false + // console.error('DR Emit Resume', self._path) + self.emit('resume', who) + if (self._paused) { + // console.error('DR Re-paused', self._path) + return + } + + if (self._currentEntry) { + if (self._currentEntry.resume) self._currentEntry.resume(who) + } else self._read() +} + +DirReader.prototype.emitEntry = function (entry) { + this.emit('entry', entry) + this.emit('child', entry) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/dir-writer.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/dir-writer.js new file mode 100644 index 0000000000..ec50dca900 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/dir-writer.js @@ -0,0 +1,174 @@ +// It is expected that, when .add() returns false, the consumer +// of the DirWriter will pause until a "drain" event occurs. Note +// that this is *almost always going to be the case*, unless the +// thing being written is some sort of unsupported type, and thus +// skipped over. + +module.exports = DirWriter + +var Writer = require('./writer.js') +var inherits = require('inherits') +var mkdir = require('mkdirp') +var path = require('path') +var collect = require('./collect.js') + +inherits(DirWriter, Writer) + +function DirWriter (props) { + var self = this + if (!(self instanceof DirWriter)) { + self.error('DirWriter must be called as constructor.', null, true) + } + + // should already be established as a Directory type + if (props.type !== 'Directory' || !props.Directory) { + self.error('Non-directory type ' + props.type + ' ' + + JSON.stringify(props), null, true) + } + + Writer.call(this, props) +} + +DirWriter.prototype._create = function () { + var self = this + mkdir(self._path, Writer.dirmode, function (er) { + if (er) return self.error(er) + // ready to start getting entries! + self.ready = true + self.emit('ready') + self._process() + }) +} + +// a DirWriter has an add(entry) method, but its .write() doesn't +// do anything. Why a no-op rather than a throw? Because this +// leaves open the door for writing directory metadata for +// gnu/solaris style dumpdirs. +DirWriter.prototype.write = function () { + return true +} + +DirWriter.prototype.end = function () { + this._ended = true + this._process() +} + +DirWriter.prototype.add = function (entry) { + var self = this + + // console.error('\tadd', entry._path, '->', self._path) + collect(entry) + if (!self.ready || self._currentEntry) { + self._buffer.push(entry) + return false + } + + // create a new writer, and pipe the incoming entry into it. + if (self._ended) { + return self.error('add after end') + } + + self._buffer.push(entry) + self._process() + + return this._buffer.length === 0 +} + +DirWriter.prototype._process = function () { + var self = this + + // console.error('DW Process p=%j', self._processing, self.basename) + + if (self._processing) return + + var entry = self._buffer.shift() + if (!entry) { + // console.error("DW Drain") + self.emit('drain') + if (self._ended) self._finish() + return + } + + self._processing = true + // console.error("DW Entry", entry._path) + + self.emit('entry', entry) + + // ok, add this entry + // + // don't allow recursive copying + var p = entry + var pp + do { + pp = p._path || p.path + if (pp === self.root._path || pp === self._path || + (pp && pp.indexOf(self._path) === 0)) { + // console.error('DW Exit (recursive)', entry.basename, self._path) + self._processing = false + if (entry._collected) entry.pipe() + return self._process() + } + p = p.parent + } while (p) + + // console.error("DW not recursive") + + // chop off the entry's root dir, replace with ours + var props = { + parent: self, + root: self.root || self, + type: entry.type, + depth: self.depth + 1 + } + + pp = entry._path || entry.path || entry.props.path + if (entry.parent) { + pp = pp.substr(entry.parent._path.length + 1) + } + // get rid of any ../../ shenanigans + props.path = path.join(self.path, path.join('/', pp)) + + // if i have a filter, the child should inherit it. + props.filter = self.filter + + // all the rest of the stuff, copy over from the source. + Object.keys(entry.props).forEach(function (k) { + if (!props.hasOwnProperty(k)) { + props[k] = entry.props[k] + } + }) + + // not sure at this point what kind of writer this is. + var child = self._currentChild = new Writer(props) + child.on('ready', function () { + // console.error("DW Child Ready", child.type, child._path) + // console.error(" resuming", entry._path) + entry.pipe(child) + entry.resume() + }) + + // XXX Make this work in node. + // Long filenames should not break stuff. + child.on('error', function (er) { + if (child._swallowErrors) { + self.warn(er) + child.emit('end') + child.emit('close') + } else { + self.emit('error', er) + } + }) + + // we fire _end internally *after* end, so that we don't move on + // until any "end" listeners have had their chance to do stuff. + child.on('close', onend) + var ended = false + function onend () { + if (ended) return + ended = true + // console.error("* DW Child end", child.basename) + self._currentChild = null + self._processing = false + self._process() + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/file-reader.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/file-reader.js new file mode 100644 index 0000000000..baa01f4b3d --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/file-reader.js @@ -0,0 +1,150 @@ +// Basically just a wrapper around an fs.ReadStream + +module.exports = FileReader + +var fs = require('graceful-fs') +var inherits = require('inherits') +var Reader = require('./reader.js') +var EOF = {EOF: true} +var CLOSE = {CLOSE: true} + +inherits(FileReader, Reader) + +function FileReader (props) { + // console.error(" FR create", props.path, props.size, new Error().stack) + var self = this + if (!(self instanceof FileReader)) { + throw new Error('FileReader must be called as constructor.') + } + + // should already be established as a File type + // XXX Todo: preserve hardlinks by tracking dev+inode+nlink, + // with a HardLinkReader class. + if (!((props.type === 'Link' && props.Link) || + (props.type === 'File' && props.File))) { + throw new Error('Non-file type ' + props.type) + } + + self._buffer = [] + self._bytesEmitted = 0 + Reader.call(self, props) +} + +FileReader.prototype._getStream = function () { + var self = this + var stream = self._stream = fs.createReadStream(self._path, self.props) + + if (self.props.blksize) { + stream.bufferSize = self.props.blksize + } + + stream.on('open', self.emit.bind(self, 'open')) + + stream.on('data', function (c) { + // console.error('\t\t%d %s', c.length, self.basename) + self._bytesEmitted += c.length + // no point saving empty chunks + if (!c.length) { + return + } else if (self._paused || self._buffer.length) { + self._buffer.push(c) + self._read() + } else self.emit('data', c) + }) + + stream.on('end', function () { + if (self._paused || self._buffer.length) { + // console.error('FR Buffering End', self._path) + self._buffer.push(EOF) + self._read() + } else { + self.emit('end') + } + + if (self._bytesEmitted !== self.props.size) { + self.error("Didn't get expected byte count\n" + + 'expect: ' + self.props.size + '\n' + + 'actual: ' + self._bytesEmitted) + } + }) + + stream.on('close', function () { + if (self._paused || self._buffer.length) { + // console.error('FR Buffering Close', self._path) + self._buffer.push(CLOSE) + self._read() + } else { + // console.error('FR close 1', self._path) + self.emit('close') + } + }) + + stream.on('error', function (e) { + self.emit('error', e) + }) + + self._read() +} + +FileReader.prototype._read = function () { + var self = this + // console.error('FR _read', self._path) + if (self._paused) { + // console.error('FR _read paused', self._path) + return + } + + if (!self._stream) { + // console.error('FR _getStream calling', self._path) + return self._getStream() + } + + // clear out the buffer, if there is one. + if (self._buffer.length) { + // console.error('FR _read has buffer', self._buffer.length, self._path) + var buf = self._buffer + for (var i = 0, l = buf.length; i < l; i++) { + var c = buf[i] + if (c === EOF) { + // console.error('FR Read emitting buffered end', self._path) + self.emit('end') + } else if (c === CLOSE) { + // console.error('FR Read emitting buffered close', self._path) + self.emit('close') + } else { + // console.error('FR Read emitting buffered data', self._path) + self.emit('data', c) + } + + if (self._paused) { + // console.error('FR Read Re-pausing at '+i, self._path) + self._buffer = buf.slice(i) + return + } + } + self._buffer.length = 0 + } +// console.error("FR _read done") +// that's about all there is to it. +} + +FileReader.prototype.pause = function (who) { + var self = this + // console.error('FR Pause', self._path) + if (self._paused) return + who = who || self + self._paused = true + if (self._stream) self._stream.pause() + self.emit('pause', who) +} + +FileReader.prototype.resume = function (who) { + var self = this + // console.error('FR Resume', self._path) + if (!self._paused) return + who = who || self + self.emit('resume', who) + self._paused = false + if (self._stream) self._stream.resume() + self._read() +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/file-writer.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/file-writer.js new file mode 100644 index 0000000000..4c803d8d68 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/file-writer.js @@ -0,0 +1,107 @@ +module.exports = FileWriter + +var fs = require('graceful-fs') +var Writer = require('./writer.js') +var inherits = require('inherits') +var EOF = {} + +inherits(FileWriter, Writer) + +function FileWriter (props) { + var self = this + if (!(self instanceof FileWriter)) { + throw new Error('FileWriter must be called as constructor.') + } + + // should already be established as a File type + if (props.type !== 'File' || !props.File) { + throw new Error('Non-file type ' + props.type) + } + + self._buffer = [] + self._bytesWritten = 0 + + Writer.call(this, props) +} + +FileWriter.prototype._create = function () { + var self = this + if (self._stream) return + + var so = {} + if (self.props.flags) so.flags = self.props.flags + so.mode = Writer.filemode + if (self._old && self._old.blksize) so.bufferSize = self._old.blksize + + self._stream = fs.createWriteStream(self._path, so) + + self._stream.on('open', function () { + // console.error("FW open", self._buffer, self._path) + self.ready = true + self._buffer.forEach(function (c) { + if (c === EOF) self._stream.end() + else self._stream.write(c) + }) + self.emit('ready') + // give this a kick just in case it needs it. + self.emit('drain') + }) + + self._stream.on('error', function (er) { self.emit('error', er) }) + + self._stream.on('drain', function () { self.emit('drain') }) + + self._stream.on('close', function () { + // console.error('\n\nFW Stream Close', self._path, self.size) + self._finish() + }) +} + +FileWriter.prototype.write = function (c) { + var self = this + + self._bytesWritten += c.length + + if (!self.ready) { + if (!Buffer.isBuffer(c) && typeof c !== 'string') { + throw new Error('invalid write data') + } + self._buffer.push(c) + return false + } + + var ret = self._stream.write(c) + // console.error('\t-- fw wrote, _stream says', ret, self._stream._queue.length) + + // allow 2 buffered writes, because otherwise there's just too + // much stop and go bs. + if (ret === false && self._stream._queue) { + return self._stream._queue.length <= 2 + } else { + return ret + } +} + +FileWriter.prototype.end = function (c) { + var self = this + + if (c) self.write(c) + + if (!self.ready) { + self._buffer.push(EOF) + return false + } + + return self._stream.end() +} + +FileWriter.prototype._finish = function () { + var self = this + if (typeof self.size === 'number' && self._bytesWritten !== self.size) { + self.error( + 'Did not get expected byte count.\n' + + 'expect: ' + self.size + '\n' + + 'actual: ' + self._bytesWritten) + } + Writer.prototype._finish.call(self) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/get-type.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/get-type.js new file mode 100644 index 0000000000..19f6a657db --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/get-type.js @@ -0,0 +1,33 @@ +module.exports = getType + +function getType (st) { + var types = [ + 'Directory', + 'File', + 'SymbolicLink', + 'Link', // special for hardlinks from tarballs + 'BlockDevice', + 'CharacterDevice', + 'FIFO', + 'Socket' + ] + var type + + if (st.type && types.indexOf(st.type) !== -1) { + st[st.type] = true + return st.type + } + + for (var i = 0, l = types.length; i < l; i++) { + type = types[i] + var is = st[type] || st['is' + type] + if (typeof is === 'function') is = is.call(st) + if (is) { + st[type] = true + st.type = type + return type + } + } + + return null +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/link-reader.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/link-reader.js new file mode 100644 index 0000000000..fb4cc67a98 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/link-reader.js @@ -0,0 +1,53 @@ +// Basically just a wrapper around an fs.readlink +// +// XXX: Enhance this to support the Link type, by keeping +// a lookup table of {:}, so that hardlinks +// can be preserved in tarballs. + +module.exports = LinkReader + +var fs = require('graceful-fs') +var inherits = require('inherits') +var Reader = require('./reader.js') + +inherits(LinkReader, Reader) + +function LinkReader (props) { + var self = this + if (!(self instanceof LinkReader)) { + throw new Error('LinkReader must be called as constructor.') + } + + if (!((props.type === 'Link' && props.Link) || + (props.type === 'SymbolicLink' && props.SymbolicLink))) { + throw new Error('Non-link type ' + props.type) + } + + Reader.call(self, props) +} + +// When piping a LinkReader into a LinkWriter, we have to +// already have the linkpath property set, so that has to +// happen *before* the "ready" event, which means we need to +// override the _stat method. +LinkReader.prototype._stat = function (currentStat) { + var self = this + fs.readlink(self._path, function (er, linkpath) { + if (er) return self.error(er) + self.linkpath = self.props.linkpath = linkpath + self.emit('linkpath', linkpath) + Reader.prototype._stat.call(self, currentStat) + }) +} + +LinkReader.prototype._read = function () { + var self = this + if (self._paused) return + // basically just a no-op, since we got all the info we need + // from the _stat method + if (!self._ended) { + self.emit('end') + self.emit('close') + self._ended = true + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/link-writer.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/link-writer.js new file mode 100644 index 0000000000..af54284008 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/link-writer.js @@ -0,0 +1,95 @@ +module.exports = LinkWriter + +var fs = require('graceful-fs') +var Writer = require('./writer.js') +var inherits = require('inherits') +var path = require('path') +var rimraf = require('rimraf') + +inherits(LinkWriter, Writer) + +function LinkWriter (props) { + var self = this + if (!(self instanceof LinkWriter)) { + throw new Error('LinkWriter must be called as constructor.') + } + + // should already be established as a Link type + if (!((props.type === 'Link' && props.Link) || + (props.type === 'SymbolicLink' && props.SymbolicLink))) { + throw new Error('Non-link type ' + props.type) + } + + if (props.linkpath === '') props.linkpath = '.' + if (!props.linkpath) { + self.error('Need linkpath property to create ' + props.type) + } + + Writer.call(this, props) +} + +LinkWriter.prototype._create = function () { + // console.error(" LW _create") + var self = this + var hard = self.type === 'Link' || process.platform === 'win32' + var link = hard ? 'link' : 'symlink' + var lp = hard ? path.resolve(self.dirname, self.linkpath) : self.linkpath + + // can only change the link path by clobbering + // For hard links, let's just assume that's always the case, since + // there's no good way to read them if we don't already know. + if (hard) return clobber(self, lp, link) + + fs.readlink(self._path, function (er, p) { + // only skip creation if it's exactly the same link + if (p && p === lp) return finish(self) + clobber(self, lp, link) + }) +} + +function clobber (self, lp, link) { + rimraf(self._path, function (er) { + if (er) return self.error(er) + create(self, lp, link) + }) +} + +function create (self, lp, link) { + fs[link](lp, self._path, function (er) { + // if this is a hard link, and we're in the process of writing out a + // directory, it's very possible that the thing we're linking to + // doesn't exist yet (especially if it was intended as a symlink), + // so swallow ENOENT errors here and just soldier in. + // Additionally, an EPERM or EACCES can happen on win32 if it's trying + // to make a link to a directory. Again, just skip it. + // A better solution would be to have fs.symlink be supported on + // windows in some nice fashion. + if (er) { + if ((er.code === 'ENOENT' || + er.code === 'EACCES' || + er.code === 'EPERM') && process.platform === 'win32') { + self.ready = true + self.emit('ready') + self.emit('end') + self.emit('close') + self.end = self._finish = function () {} + } else return self.error(er) + } + finish(self) + }) +} + +function finish (self) { + self.ready = true + self.emit('ready') + if (self._ended && !self._finished) self._finish() +} + +LinkWriter.prototype.end = function () { + // console.error("LW finish in end") + this._ended = true + if (this.ready) { + this._finished = true + this._finish() + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/proxy-reader.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/proxy-reader.js new file mode 100644 index 0000000000..4f431c9d9e --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/proxy-reader.js @@ -0,0 +1,95 @@ +// A reader for when we don't yet know what kind of thing +// the thing is. + +module.exports = ProxyReader + +var Reader = require('./reader.js') +var getType = require('./get-type.js') +var inherits = require('inherits') +var fs = require('graceful-fs') + +inherits(ProxyReader, Reader) + +function ProxyReader (props) { + var self = this + if (!(self instanceof ProxyReader)) { + throw new Error('ProxyReader must be called as constructor.') + } + + self.props = props + self._buffer = [] + self.ready = false + + Reader.call(self, props) +} + +ProxyReader.prototype._stat = function () { + var self = this + var props = self.props + // stat the thing to see what the proxy should be. + var stat = props.follow ? 'stat' : 'lstat' + + fs[stat](props.path, function (er, current) { + var type + if (er || !current) { + type = 'File' + } else { + type = getType(current) + } + + props[type] = true + props.type = self.type = type + + self._old = current + self._addProxy(Reader(props, current)) + }) +} + +ProxyReader.prototype._addProxy = function (proxy) { + var self = this + if (self._proxyTarget) { + return self.error('proxy already set') + } + + self._proxyTarget = proxy + proxy._proxy = self + + ;[ + 'error', + 'data', + 'end', + 'close', + 'linkpath', + 'entry', + 'entryEnd', + 'child', + 'childEnd', + 'warn', + 'stat' + ].forEach(function (ev) { + // console.error('~~ proxy event', ev, self.path) + proxy.on(ev, self.emit.bind(self, ev)) + }) + + self.emit('proxy', proxy) + + proxy.on('ready', function () { + // console.error("~~ proxy is ready!", self.path) + self.ready = true + self.emit('ready') + }) + + var calls = self._buffer + self._buffer.length = 0 + calls.forEach(function (c) { + proxy[c[0]].apply(proxy, c[1]) + }) +} + +ProxyReader.prototype.pause = function () { + return this._proxyTarget ? this._proxyTarget.pause() : false +} + +ProxyReader.prototype.resume = function () { + return this._proxyTarget ? this._proxyTarget.resume() : false +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/proxy-writer.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/proxy-writer.js new file mode 100644 index 0000000000..a6544621bf --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/proxy-writer.js @@ -0,0 +1,111 @@ +// A writer for when we don't know what kind of thing +// the thing is. That is, it's not explicitly set, +// so we're going to make it whatever the thing already +// is, or "File" +// +// Until then, collect all events. + +module.exports = ProxyWriter + +var Writer = require('./writer.js') +var getType = require('./get-type.js') +var inherits = require('inherits') +var collect = require('./collect.js') +var fs = require('fs') + +inherits(ProxyWriter, Writer) + +function ProxyWriter (props) { + var self = this + if (!(self instanceof ProxyWriter)) { + throw new Error('ProxyWriter must be called as constructor.') + } + + self.props = props + self._needDrain = false + + Writer.call(self, props) +} + +ProxyWriter.prototype._stat = function () { + var self = this + var props = self.props + // stat the thing to see what the proxy should be. + var stat = props.follow ? 'stat' : 'lstat' + + fs[stat](props.path, function (er, current) { + var type + if (er || !current) { + type = 'File' + } else { + type = getType(current) + } + + props[type] = true + props.type = self.type = type + + self._old = current + self._addProxy(Writer(props, current)) + }) +} + +ProxyWriter.prototype._addProxy = function (proxy) { + // console.error("~~ set proxy", this.path) + var self = this + if (self._proxy) { + return self.error('proxy already set') + } + + self._proxy = proxy + ;[ + 'ready', + 'error', + 'close', + 'pipe', + 'drain', + 'warn' + ].forEach(function (ev) { + proxy.on(ev, self.emit.bind(self, ev)) + }) + + self.emit('proxy', proxy) + + var calls = self._buffer + calls.forEach(function (c) { + // console.error("~~ ~~ proxy buffered call", c[0], c[1]) + proxy[c[0]].apply(proxy, c[1]) + }) + self._buffer.length = 0 + if (self._needsDrain) self.emit('drain') +} + +ProxyWriter.prototype.add = function (entry) { + // console.error("~~ proxy add") + collect(entry) + + if (!this._proxy) { + this._buffer.push(['add', [entry]]) + this._needDrain = true + return false + } + return this._proxy.add(entry) +} + +ProxyWriter.prototype.write = function (c) { + // console.error('~~ proxy write') + if (!this._proxy) { + this._buffer.push(['write', [c]]) + this._needDrain = true + return false + } + return this._proxy.write(c) +} + +ProxyWriter.prototype.end = function (c) { + // console.error('~~ proxy end') + if (!this._proxy) { + this._buffer.push(['end', [c]]) + return false + } + return this._proxy.end(c) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/reader.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/reader.js new file mode 100644 index 0000000000..876021f92b --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/reader.js @@ -0,0 +1,255 @@ +module.exports = Reader + +var fs = require('graceful-fs') +var Stream = require('stream').Stream +var inherits = require('inherits') +var path = require('path') +var getType = require('./get-type.js') +var hardLinks = Reader.hardLinks = {} +var Abstract = require('./abstract.js') + +// Must do this *before* loading the child classes +inherits(Reader, Abstract) + +var LinkReader = require('./link-reader.js') + +function Reader (props, currentStat) { + var self = this + if (!(self instanceof Reader)) return new Reader(props, currentStat) + + if (typeof props === 'string') { + props = { path: props } + } + + if (!props.path) { + self.error('Must provide a path', null, true) + } + + // polymorphism. + // call fstream.Reader(dir) to get a DirReader object, etc. + // Note that, unlike in the Writer case, ProxyReader is going + // to be the *normal* state of affairs, since we rarely know + // the type of a file prior to reading it. + + var type + var ClassType + + if (props.type && typeof props.type === 'function') { + type = props.type + ClassType = type + } else { + type = getType(props) + ClassType = Reader + } + + if (currentStat && !type) { + type = getType(currentStat) + props[type] = true + props.type = type + } + + switch (type) { + case 'Directory': + ClassType = require('./dir-reader.js') + break + + case 'Link': + // XXX hard links are just files. + // However, it would be good to keep track of files' dev+inode + // and nlink values, and create a HardLinkReader that emits + // a linkpath value of the original copy, so that the tar + // writer can preserve them. + // ClassType = HardLinkReader + // break + + case 'File': + ClassType = require('./file-reader.js') + break + + case 'SymbolicLink': + ClassType = LinkReader + break + + case 'Socket': + ClassType = require('./socket-reader.js') + break + + case null: + ClassType = require('./proxy-reader.js') + break + } + + if (!(self instanceof ClassType)) { + return new ClassType(props) + } + + Abstract.call(self) + + self.readable = true + self.writable = false + + self.type = type + self.props = props + self.depth = props.depth = props.depth || 0 + self.parent = props.parent || null + self.root = props.root || (props.parent && props.parent.root) || self + + self._path = self.path = path.resolve(props.path) + if (process.platform === 'win32') { + self.path = self._path = self.path.replace(/\?/g, '_') + if (self._path.length >= 260) { + // how DOES one create files on the moon? + // if the path has spaces in it, then UNC will fail. + self._swallowErrors = true + // if (self._path.indexOf(" ") === -1) { + self._path = '\\\\?\\' + self.path.replace(/\//g, '\\') + // } + } + } + self.basename = props.basename = path.basename(self.path) + self.dirname = props.dirname = path.dirname(self.path) + + // these have served their purpose, and are now just noisy clutter + props.parent = props.root = null + + // console.error("\n\n\n%s setting size to", props.path, props.size) + self.size = props.size + self.filter = typeof props.filter === 'function' ? props.filter : null + if (props.sort === 'alpha') props.sort = alphasort + + // start the ball rolling. + // this will stat the thing, and then call self._read() + // to start reading whatever it is. + // console.error("calling stat", props.path, currentStat) + self._stat(currentStat) +} + +function alphasort (a, b) { + return a === b ? 0 + : a.toLowerCase() > b.toLowerCase() ? 1 + : a.toLowerCase() < b.toLowerCase() ? -1 + : a > b ? 1 + : -1 +} + +Reader.prototype._stat = function (currentStat) { + var self = this + var props = self.props + var stat = props.follow ? 'stat' : 'lstat' + // console.error("Reader._stat", self._path, currentStat) + if (currentStat) process.nextTick(statCb.bind(null, null, currentStat)) + else fs[stat](self._path, statCb) + + function statCb (er, props_) { + // console.error("Reader._stat, statCb", self._path, props_, props_.nlink) + if (er) return self.error(er) + + Object.keys(props_).forEach(function (k) { + props[k] = props_[k] + }) + + // if it's not the expected size, then abort here. + if (undefined !== self.size && props.size !== self.size) { + return self.error('incorrect size') + } + self.size = props.size + + var type = getType(props) + var handleHardlinks = props.hardlinks !== false + + // special little thing for handling hardlinks. + if (handleHardlinks && type !== 'Directory' && props.nlink && props.nlink > 1) { + var k = props.dev + ':' + props.ino + // console.error("Reader has nlink", self._path, k) + if (hardLinks[k] === self._path || !hardLinks[k]) { + hardLinks[k] = self._path + } else { + // switch into hardlink mode. + type = self.type = self.props.type = 'Link' + self.Link = self.props.Link = true + self.linkpath = self.props.linkpath = hardLinks[k] + // console.error("Hardlink detected, switching mode", self._path, self.linkpath) + // Setting __proto__ would arguably be the "correct" + // approach here, but that just seems too wrong. + self._stat = self._read = LinkReader.prototype._read + } + } + + if (self.type && self.type !== type) { + self.error('Unexpected type: ' + type) + } + + // if the filter doesn't pass, then just skip over this one. + // still have to emit end so that dir-walking can move on. + if (self.filter) { + var who = self._proxy || self + // special handling for ProxyReaders + if (!self.filter.call(who, who, props)) { + if (!self._disowned) { + self.abort() + self.emit('end') + self.emit('close') + } + return + } + } + + // last chance to abort or disown before the flow starts! + var events = ['_stat', 'stat', 'ready'] + var e = 0 + ;(function go () { + if (self._aborted) { + self.emit('end') + self.emit('close') + return + } + + if (self._paused && self.type !== 'Directory') { + self.once('resume', go) + return + } + + var ev = events[e++] + if (!ev) { + return self._read() + } + self.emit(ev, props) + go() + })() + } +} + +Reader.prototype.pipe = function (dest) { + var self = this + if (typeof dest.add === 'function') { + // piping to a multi-compatible, and we've got directory entries. + self.on('entry', function (entry) { + var ret = dest.add(entry) + if (ret === false) { + self.pause() + } + }) + } + + // console.error("R Pipe apply Stream Pipe") + return Stream.prototype.pipe.apply(this, arguments) +} + +Reader.prototype.pause = function (who) { + this._paused = true + who = who || this + this.emit('pause', who) + if (this._stream) this._stream.pause(who) +} + +Reader.prototype.resume = function (who) { + this._paused = false + who = who || this + this.emit('resume', who) + if (this._stream) this._stream.resume(who) + this._read() +} + +Reader.prototype._read = function () { + this.error('Cannot read unknown type: ' + this.type) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/socket-reader.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/socket-reader.js new file mode 100644 index 0000000000..e0456ba890 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/socket-reader.js @@ -0,0 +1,36 @@ +// Just get the stats, and then don't do anything. +// You can't really "read" from a socket. You "connect" to it. +// Mostly, this is here so that reading a dir with a socket in it +// doesn't blow up. + +module.exports = SocketReader + +var inherits = require('inherits') +var Reader = require('./reader.js') + +inherits(SocketReader, Reader) + +function SocketReader (props) { + var self = this + if (!(self instanceof SocketReader)) { + throw new Error('SocketReader must be called as constructor.') + } + + if (!(props.type === 'Socket' && props.Socket)) { + throw new Error('Non-socket type ' + props.type) + } + + Reader.call(self, props) +} + +SocketReader.prototype._read = function () { + var self = this + if (self._paused) return + // basically just a no-op, since we got all the info we have + // from the _stat method + if (!self._ended) { + self.emit('end') + self.emit('close') + self._ended = true + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/writer.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/writer.js new file mode 100644 index 0000000000..ca3396b5d1 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/lib/writer.js @@ -0,0 +1,390 @@ +module.exports = Writer + +var fs = require('graceful-fs') +var inherits = require('inherits') +var rimraf = require('rimraf') +var mkdir = require('mkdirp') +var path = require('path') +var umask = process.platform === 'win32' ? 0 : process.umask() +var getType = require('./get-type.js') +var Abstract = require('./abstract.js') + +// Must do this *before* loading the child classes +inherits(Writer, Abstract) + +Writer.dirmode = parseInt('0777', 8) & (~umask) +Writer.filemode = parseInt('0666', 8) & (~umask) + +var DirWriter = require('./dir-writer.js') +var LinkWriter = require('./link-writer.js') +var FileWriter = require('./file-writer.js') +var ProxyWriter = require('./proxy-writer.js') + +// props is the desired state. current is optionally the current stat, +// provided here so that subclasses can avoid statting the target +// more than necessary. +function Writer (props, current) { + var self = this + + if (typeof props === 'string') { + props = { path: props } + } + + if (!props.path) self.error('Must provide a path', null, true) + + // polymorphism. + // call fstream.Writer(dir) to get a DirWriter object, etc. + var type = getType(props) + var ClassType = Writer + + switch (type) { + case 'Directory': + ClassType = DirWriter + break + case 'File': + ClassType = FileWriter + break + case 'Link': + case 'SymbolicLink': + ClassType = LinkWriter + break + case null: + default: + // Don't know yet what type to create, so we wrap in a proxy. + ClassType = ProxyWriter + break + } + + if (!(self instanceof ClassType)) return new ClassType(props) + + // now get down to business. + + Abstract.call(self) + + // props is what we want to set. + // set some convenience properties as well. + self.type = props.type + self.props = props + self.depth = props.depth || 0 + self.clobber = props.clobber === false ? props.clobber : true + self.parent = props.parent || null + self.root = props.root || (props.parent && props.parent.root) || self + + self._path = self.path = path.resolve(props.path) + if (process.platform === 'win32') { + self.path = self._path = self.path.replace(/\?/g, '_') + if (self._path.length >= 260) { + self._swallowErrors = true + self._path = '\\\\?\\' + self.path.replace(/\//g, '\\') + } + } + self.basename = path.basename(props.path) + self.dirname = path.dirname(props.path) + self.linkpath = props.linkpath || null + + props.parent = props.root = null + + // console.error("\n\n\n%s setting size to", props.path, props.size) + self.size = props.size + + if (typeof props.mode === 'string') { + props.mode = parseInt(props.mode, 8) + } + + self.readable = false + self.writable = true + + // buffer until ready, or while handling another entry + self._buffer = [] + self.ready = false + + self.filter = typeof props.filter === 'function' ? props.filter : null + + // start the ball rolling. + // this checks what's there already, and then calls + // self._create() to call the impl-specific creation stuff. + self._stat(current) +} + +// Calling this means that it's something we can't create. +// Just assert that it's already there, otherwise raise a warning. +Writer.prototype._create = function () { + var self = this + fs[self.props.follow ? 'stat' : 'lstat'](self._path, function (er) { + if (er) { + return self.warn('Cannot create ' + self._path + '\n' + + 'Unsupported type: ' + self.type, 'ENOTSUP') + } + self._finish() + }) +} + +Writer.prototype._stat = function (current) { + var self = this + var props = self.props + var stat = props.follow ? 'stat' : 'lstat' + var who = self._proxy || self + + if (current) statCb(null, current) + else fs[stat](self._path, statCb) + + function statCb (er, current) { + if (self.filter && !self.filter.call(who, who, current)) { + self._aborted = true + self.emit('end') + self.emit('close') + return + } + + // if it's not there, great. We'll just create it. + // if it is there, then we'll need to change whatever differs + if (er || !current) { + return create(self) + } + + self._old = current + var currentType = getType(current) + + // if it's a type change, then we need to clobber or error. + // if it's not a type change, then let the impl take care of it. + if (currentType !== self.type) { + return rimraf(self._path, function (er) { + if (er) return self.error(er) + self._old = null + create(self) + }) + } + + // otherwise, just handle in the app-specific way + // this creates a fs.WriteStream, or mkdir's, or whatever + create(self) + } +} + +function create (self) { + // console.error("W create", self._path, Writer.dirmode) + + // XXX Need to clobber non-dirs that are in the way, + // unless { clobber: false } in the props. + mkdir(path.dirname(self._path), Writer.dirmode, function (er, made) { + // console.error("W created", path.dirname(self._path), er) + if (er) return self.error(er) + + // later on, we have to set the mode and owner for these + self._madeDir = made + return self._create() + }) +} + +function endChmod (self, want, current, path, cb) { + var wantMode = want.mode + var chmod = want.follow || self.type !== 'SymbolicLink' + ? 'chmod' : 'lchmod' + + if (!fs[chmod]) return cb() + if (typeof wantMode !== 'number') return cb() + + var curMode = current.mode & parseInt('0777', 8) + wantMode = wantMode & parseInt('0777', 8) + if (wantMode === curMode) return cb() + + fs[chmod](path, wantMode, cb) +} + +function endChown (self, want, current, path, cb) { + // Don't even try it unless root. Too easy to EPERM. + if (process.platform === 'win32') return cb() + if (!process.getuid || process.getuid() !== 0) return cb() + if (typeof want.uid !== 'number' && + typeof want.gid !== 'number') return cb() + + if (current.uid === want.uid && + current.gid === want.gid) return cb() + + var chown = (self.props.follow || self.type !== 'SymbolicLink') + ? 'chown' : 'lchown' + if (!fs[chown]) return cb() + + if (typeof want.uid !== 'number') want.uid = current.uid + if (typeof want.gid !== 'number') want.gid = current.gid + + fs[chown](path, want.uid, want.gid, cb) +} + +function endUtimes (self, want, current, path, cb) { + if (!fs.utimes || process.platform === 'win32') return cb() + + var utimes = (want.follow || self.type !== 'SymbolicLink') + ? 'utimes' : 'lutimes' + + if (utimes === 'lutimes' && !fs[utimes]) { + utimes = 'utimes' + } + + if (!fs[utimes]) return cb() + + var curA = current.atime + var curM = current.mtime + var meA = want.atime + var meM = want.mtime + + if (meA === undefined) meA = curA + if (meM === undefined) meM = curM + + if (!isDate(meA)) meA = new Date(meA) + if (!isDate(meM)) meA = new Date(meM) + + if (meA.getTime() === curA.getTime() && + meM.getTime() === curM.getTime()) return cb() + + fs[utimes](path, meA, meM, cb) +} + +// XXX This function is beastly. Break it up! +Writer.prototype._finish = function () { + var self = this + + if (self._finishing) return + self._finishing = true + + // console.error(" W Finish", self._path, self.size) + + // set up all the things. + // At this point, we're already done writing whatever we've gotta write, + // adding files to the dir, etc. + var todo = 0 + var errState = null + var done = false + + if (self._old) { + // the times will almost *certainly* have changed. + // adds the utimes syscall, but remove another stat. + self._old.atime = new Date(0) + self._old.mtime = new Date(0) + // console.error(" W Finish Stale Stat", self._path, self.size) + setProps(self._old) + } else { + var stat = self.props.follow ? 'stat' : 'lstat' + // console.error(" W Finish Stating", self._path, self.size) + fs[stat](self._path, function (er, current) { + // console.error(" W Finish Stated", self._path, self.size, current) + if (er) { + // if we're in the process of writing out a + // directory, it's very possible that the thing we're linking to + // doesn't exist yet (especially if it was intended as a symlink), + // so swallow ENOENT errors here and just soldier on. + if (er.code === 'ENOENT' && + (self.type === 'Link' || self.type === 'SymbolicLink') && + process.platform === 'win32') { + self.ready = true + self.emit('ready') + self.emit('end') + self.emit('close') + self.end = self._finish = function () {} + return + } else return self.error(er) + } + setProps(self._old = current) + }) + } + + return + + function setProps (current) { + todo += 3 + endChmod(self, self.props, current, self._path, next('chmod')) + endChown(self, self.props, current, self._path, next('chown')) + endUtimes(self, self.props, current, self._path, next('utimes')) + } + + function next (what) { + return function (er) { + // console.error(" W Finish", what, todo) + if (errState) return + if (er) { + er.fstream_finish_call = what + return self.error(errState = er) + } + if (--todo > 0) return + if (done) return + done = true + + // we may still need to set the mode/etc. on some parent dirs + // that were created previously. delay end/close until then. + if (!self._madeDir) return end() + else endMadeDir(self, self._path, end) + + function end (er) { + if (er) { + er.fstream_finish_call = 'setupMadeDir' + return self.error(er) + } + // all the props have been set, so we're completely done. + self.emit('end') + self.emit('close') + } + } + } +} + +function endMadeDir (self, p, cb) { + var made = self._madeDir + // everything *between* made and path.dirname(self._path) + // needs to be set up. Note that this may just be one dir. + var d = path.dirname(p) + + endMadeDir_(self, d, function (er) { + if (er) return cb(er) + if (d === made) { + return cb() + } + endMadeDir(self, d, cb) + }) +} + +function endMadeDir_ (self, p, cb) { + var dirProps = {} + Object.keys(self.props).forEach(function (k) { + dirProps[k] = self.props[k] + + // only make non-readable dirs if explicitly requested. + if (k === 'mode' && self.type !== 'Directory') { + dirProps[k] = dirProps[k] | parseInt('0111', 8) + } + }) + + var todo = 3 + var errState = null + fs.stat(p, function (er, current) { + if (er) return cb(errState = er) + endChmod(self, dirProps, current, p, next) + endChown(self, dirProps, current, p, next) + endUtimes(self, dirProps, current, p, next) + }) + + function next (er) { + if (errState) return + if (er) return cb(errState = er) + if (--todo === 0) return cb() + } +} + +Writer.prototype.pipe = function () { + this.error("Can't pipe from writable stream") +} + +Writer.prototype.add = function () { + this.error("Can't add to non-Directory type") +} + +Writer.prototype.write = function () { + return true +} + +function objectToString (d) { + return Object.prototype.toString.call(d) +} + +function isDate (d) { + return typeof d === 'object' && objectToString(d) === '[object Date]' +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/.bin/mkdirp b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/.bin/mkdirp new file mode 120000 index 0000000000..017896cebb --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/.bin/mkdirp @@ -0,0 +1 @@ +../mkdirp/bin/cmd.js \ No newline at end of file diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/.bin/rimraf b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/.bin/rimraf new file mode 120000 index 0000000000..4cd49a49dd --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/.bin/rimraf @@ -0,0 +1 @@ +../rimraf/bin.js \ No newline at end of file diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/LICENSE new file mode 100644 index 0000000000..9d2c803696 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter, Ben Noordhuis, and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/README.md new file mode 100644 index 0000000000..13a2e86050 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/README.md @@ -0,0 +1,36 @@ +# graceful-fs + +graceful-fs functions as a drop-in replacement for the fs module, +making various improvements. + +The improvements are meant to normalize behavior across different +platforms and environments, and to make filesystem access more +resilient to errors. + +## Improvements over [fs module](http://api.nodejs.org/fs.html) + +graceful-fs: + +* Queues up `open` and `readdir` calls, and retries them once + something closes if there is an EMFILE error from too many file + descriptors. +* fixes `lchmod` for Node versions prior to 0.6.2. +* implements `fs.lutimes` if possible. Otherwise it becomes a noop. +* ignores `EINVAL` and `EPERM` errors in `chown`, `fchown` or + `lchown` if the user isn't root. +* makes `lchmod` and `lchown` become noops, if not available. +* retries reading a file if `read` results in EAGAIN error. + +On Windows, it retries renaming a file for up to one second if `EACCESS` +or `EPERM` error occurs, likely because antivirus software has locked +the directory. + +## USAGE + +```javascript +// use just like fs +var fs = require('graceful-fs') + +// now go and do stuff with it... +fs.readFileSync('some-file-or-whatever') +``` diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/fs.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/fs.js new file mode 100644 index 0000000000..8ad4a38396 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/fs.js @@ -0,0 +1,21 @@ +'use strict' + +var fs = require('fs') + +module.exports = clone(fs) + +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj + + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/graceful-fs.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/graceful-fs.js new file mode 100644 index 0000000000..fe3b17cb60 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/graceful-fs.js @@ -0,0 +1,251 @@ +var fs = require('fs') +var polyfills = require('./polyfills.js') +var legacy = require('./legacy-streams.js') +var queue = [] + +var util = require('util') + +function noop () {} + +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } + +if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(queue) + require('assert').equal(queue.length, 0) + }) +} + +module.exports = patch(require('./fs.js')) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) { + module.exports = patch(fs) +} + +// Always patch fs.close/closeSync, because we want to +// retry() whenever a close happens *anywhere* in the program. +// This is essential when multiple graceful-fs instances are +// in play at the same time. +fs.close = (function (fs$close) { return function (fd, cb) { + return fs$close.call(fs, fd, function (err) { + if (!err) + retry() + + if (typeof cb === 'function') + cb.apply(this, arguments) + }) +}})(fs.close) + +fs.closeSync = (function (fs$closeSync) { return function (fd) { + // Note that graceful-fs also retries when fs.closeSync() fails. + // Looks like a bug to me, although it's probably a harmless one. + var rval = fs$closeSync.apply(fs, arguments) + retry() + return rval +}})(fs.closeSync) + +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + fs.FileReadStream = ReadStream; // Legacy name. + fs.FileWriteStream = WriteStream; // Legacy name. + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$readFile(path, options, cb) + + function go$readFile (path, options, cb) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$writeFile(path, data, options, cb) + + function go$writeFile (path, data, options, cb) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$appendFile(path, data, options, cb) + + function go$appendFile (path, data, options, cb) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + var fs$readdir = fs.readdir + fs.readdir = readdir + function readdir (path, cb) { + return go$readdir(path, cb) + + function go$readdir () { + return fs$readdir(path, function (err, files) { + if (files && files.sort) + files.sort(); // Backwards compatibility with graceful-fs. + + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readdir, [path, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } + + var fs$ReadStream = fs.ReadStream + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + + var fs$WriteStream = fs.WriteStream + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + + fs.ReadStream = ReadStream + fs.WriteStream = WriteStream + + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } + + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() + + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } + + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } + + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } + + function createReadStream (path, options) { + return new ReadStream(path, options) + } + + function createWriteStream (path, options) { + return new WriteStream(path, options) + } + + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null + + return go$open(path, flags, mode, cb) + + function go$open (path, flags, mode, cb) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + return fs +} + +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + queue.push(elem) +} + +function retry () { + var elem = queue.shift() + if (elem) { + debug('RETRY', elem[0].name, elem[1]) + elem[0].apply(null, elem[1]) + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/legacy-streams.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/legacy-streams.js new file mode 100644 index 0000000000..d617b50fc0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/legacy-streams.js @@ -0,0 +1,118 @@ +var Stream = require('stream').Stream + +module.exports = legacy + +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } + + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); + + Stream.call(this); + + var self = this; + + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; + + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.encoding) this.setEncoding(this.encoding); + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } + + if (this.start > this.end) { + throw new Error('start must be <= end'); + } + + this.pos = this.start; + } + + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } + + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } + + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } + + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); + + Stream.call(this); + + this.path = path; + this.fd = null; + this.writable = true; + + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } + + this.pos = this.start; + } + + this.busy = false; + this._queue = []; + + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/package.json new file mode 100644 index 0000000000..c9df61c72a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/package.json @@ -0,0 +1,73 @@ +{ + "name": "graceful-fs", + "description": "A drop-in replacement for fs, making various improvements.", + "version": "4.1.2", + "repository": { + "type": "git", + "url": "git+https://github.com/isaacs/node-graceful-fs.git" + }, + "main": "graceful-fs.js", + "engines": { + "node": ">=0.4.0" + }, + "directories": { + "test": "test" + }, + "scripts": { + "test": "node test.js | tap -" + }, + "keywords": [ + "fs", + "module", + "reading", + "retry", + "retries", + "queue", + "error", + "errors", + "handling", + "EMFILE", + "EAGAIN", + "EINVAL", + "EPERM", + "EACCESS" + ], + "license": "ISC", + "devDependencies": { + "mkdirp": "^0.5.0", + "rimraf": "^2.2.8", + "tap": "^1.2.0" + }, + "files": [ + "fs.js", + "graceful-fs.js", + "legacy-streams.js", + "polyfills.js" + ], + "gitHead": "c286080071b6be9aa9ba108b0bb9b44ff122926d", + "bugs": { + "url": "https://github.com/isaacs/node-graceful-fs/issues" + }, + "homepage": "https://github.com/isaacs/node-graceful-fs#readme", + "_id": "graceful-fs@4.1.2", + "_shasum": "fe2239b7574972e67e41f808823f9bfa4a991e37", + "_from": "graceful-fs@>=4.1.2 <5.0.0", + "_npmVersion": "3.0.0", + "_nodeVersion": "2.2.1", + "_npmUser": { + "name": "isaacs", + "email": "isaacs@npmjs.com" + }, + "dist": { + "shasum": "fe2239b7574972e67e41f808823f9bfa4a991e37", + "tarball": "http://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "_resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/polyfills.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/polyfills.js new file mode 100644 index 0000000000..5e4f480461 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/graceful-fs/polyfills.js @@ -0,0 +1,252 @@ +var fs = require('./fs.js') +var constants = require('constants') + +var origCwd = process.cwd +var cwd = null +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd +} +try { + process.cwd() +} catch (er) {} + +var chdir = process.chdir +process.chdir = function(d) { + cwd = null + chdir.call(process, d) +} + +module.exports = patch + +function patch (fs) { + // (re-)implement some things that are known busted or missing. + + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } + + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } + + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. + + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) + + fs.chmod = chownFix(fs.chmod) + fs.fchmod = chownFix(fs.fchmod) + fs.lchmod = chownFix(fs.lchmod) + + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) + + fs.chmodSync = chownFix(fs.chmodSync) + fs.fchmodSync = chownFix(fs.fchmodSync) + fs.lchmodSync = chownFix(fs.lchmodSync) + + // if lchmod/lchown do not exist, then make them no-ops + if (!fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (!fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + process.nextTick(cb) + } + fs.lchownSync = function () {} + } + + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 1 second. + if (process.platform === "win32") { + fs.rename = (function (fs$rename) { return function (from, to, cb) { + var start = Date.now() + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM") + && Date.now() - start < 1000) { + return fs$rename(from, to, CB) + } + if (cb) cb(er) + }) + }})(fs.rename) + } + + // if read() returns EAGAIN, then just try it again. + fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + }})(fs.read) + + fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } + } + }})(fs.readSync) +} + +function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + callback = callback || noop + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + callback(err || err2) + }) + }) + }) + } + + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } +} + +function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + cb = cb || noop + if (er) return cb(er) + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + return cb(er || er2) + }) + }) + }) + } + + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + + } else { + fs.lutimes = function (_a, _b, _c, cb) { process.nextTick(cb) } + fs.lutimesSync = function () {} + } +} + +function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er, res) { + if (chownErOk(er)) er = null + cb(er, res) + }) + } +} + +function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } +} + +// ENOSYS means that the fs doesn't support the op. Just ignore +// that, because it doesn't matter. +// +// if there's no getuid, or if getuid() is something other +// than 0, and the error is EINVAL or EPERM, then just ignore +// it. +// +// This specific case is a silent failure in cp, install, tar, +// and most other unix tools that manage permissions. +// +// When running as root, or if other types of errors are +// encountered, then it's strict. +function chownErOk (er) { + if (!er) + return true + + if (er.code === "ENOSYS") + return true + + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } + + return false +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/.travis.yml b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/.travis.yml new file mode 100644 index 0000000000..74c57bf15e --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - "0.8" + - "0.10" + - "0.12" + - "iojs" +before_install: + - npm install -g npm@~1.4.6 diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/LICENSE new file mode 100644 index 0000000000..432d1aeb01 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/LICENSE @@ -0,0 +1,21 @@ +Copyright 2010 James Halliday (mail@substack.net) + +This project is free software released under the MIT/X11 license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/bin/cmd.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/bin/cmd.js new file mode 100755 index 0000000000..d95de15ae9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/bin/cmd.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +var mkdirp = require('../'); +var minimist = require('minimist'); +var fs = require('fs'); + +var argv = minimist(process.argv.slice(2), { + alias: { m: 'mode', h: 'help' }, + string: [ 'mode' ] +}); +if (argv.help) { + fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout); + return; +} + +var paths = argv._.slice(); +var mode = argv.mode ? parseInt(argv.mode, 8) : undefined; + +(function next () { + if (paths.length === 0) return; + var p = paths.shift(); + + if (mode === undefined) mkdirp(p, cb) + else mkdirp(p, mode, cb) + + function cb (err) { + if (err) { + console.error(err.message); + process.exit(1); + } + else next(); + } +})(); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/bin/usage.txt b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/bin/usage.txt new file mode 100644 index 0000000000..f952aa2c7a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/bin/usage.txt @@ -0,0 +1,12 @@ +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories that + don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m, --mode If a directory needs to be created, set the mode as an octal + permission string. + diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/examples/pow.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/examples/pow.js new file mode 100644 index 0000000000..e6924212e6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/examples/pow.js @@ -0,0 +1,6 @@ +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', function (err) { + if (err) console.error(err) + else console.log('pow!') +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/index.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/index.js new file mode 100644 index 0000000000..6ce241b58c --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/index.js @@ -0,0 +1,98 @@ +var path = require('path'); +var fs = require('fs'); +var _0777 = parseInt('0777', 8); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, opts, f, made) { + if (typeof opts === 'function') { + f = opts; + opts = {}; + } + else if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 & (~process.umask()); + } + if (!made) made = null; + + var cb = f || function () {}; + p = path.resolve(p); + + xfs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + mkdirP(path.dirname(p), opts, function (er, made) { + if (er) cb(er, made); + else mkdirP(p, opts, cb, made); + }); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + xfs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original error be the failure reason. + if (er2 || !stat.isDirectory()) cb(er, made) + else cb(null, made); + }); + break; + } + }); +} + +mkdirP.sync = function sync (p, opts, made) { + if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 & (~process.umask()); + } + if (!made) made = null; + + p = path.resolve(p); + + try { + xfs.mkdirSync(p, mode); + made = made || p; + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + made = sync(path.dirname(p), opts, made); + sync(p, opts, made); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + var stat; + try { + stat = xfs.statSync(p); + } + catch (err1) { + throw err0; + } + if (!stat.isDirectory()) throw err0; + break; + } + } + + return made; +}; diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/.travis.yml b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/.travis.yml new file mode 100644 index 0000000000..cc4dba29d9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.8" + - "0.10" diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/LICENSE new file mode 100644 index 0000000000..ee27ba4b44 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/LICENSE @@ -0,0 +1,18 @@ +This software is released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/example/parse.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/example/parse.js new file mode 100644 index 0000000000..abff3e8ee8 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/example/parse.js @@ -0,0 +1,2 @@ +var argv = require('../')(process.argv.slice(2)); +console.dir(argv); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/index.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/index.js new file mode 100644 index 0000000000..584f551a6d --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/index.js @@ -0,0 +1,187 @@ +module.exports = function (args, opts) { + if (!opts) opts = {}; + + var flags = { bools : {}, strings : {} }; + + [].concat(opts['boolean']).filter(Boolean).forEach(function (key) { + flags.bools[key] = true; + }); + + [].concat(opts.string).filter(Boolean).forEach(function (key) { + flags.strings[key] = true; + }); + + var aliases = {}; + Object.keys(opts.alias || {}).forEach(function (key) { + aliases[key] = [].concat(opts.alias[key]); + aliases[key].forEach(function (x) { + aliases[x] = [key].concat(aliases[key].filter(function (y) { + return x !== y; + })); + }); + }); + + var defaults = opts['default'] || {}; + + var argv = { _ : [] }; + Object.keys(flags.bools).forEach(function (key) { + setArg(key, defaults[key] === undefined ? false : defaults[key]); + }); + + var notFlags = []; + + if (args.indexOf('--') !== -1) { + notFlags = args.slice(args.indexOf('--')+1); + args = args.slice(0, args.indexOf('--')); + } + + function setArg (key, val) { + var value = !flags.strings[key] && isNumber(val) + ? Number(val) : val + ; + setKey(argv, key.split('.'), value); + + (aliases[key] || []).forEach(function (x) { + setKey(argv, x.split('.'), value); + }); + } + + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + + if (/^--.+=/.test(arg)) { + // Using [\s\S] instead of . because js doesn't support the + // 'dotall' regex modifier. See: + // http://stackoverflow.com/a/1068308/13216 + var m = arg.match(/^--([^=]+)=([\s\S]*)$/); + setArg(m[1], m[2]); + } + else if (/^--no-.+/.test(arg)) { + var key = arg.match(/^--no-(.+)/)[1]; + setArg(key, false); + } + else if (/^--.+/.test(arg)) { + var key = arg.match(/^--(.+)/)[1]; + var next = args[i + 1]; + if (next !== undefined && !/^-/.test(next) + && !flags.bools[key] + && (aliases[key] ? !flags.bools[aliases[key]] : true)) { + setArg(key, next); + i++; + } + else if (/^(true|false)$/.test(next)) { + setArg(key, next === 'true'); + i++; + } + else { + setArg(key, flags.strings[key] ? '' : true); + } + } + else if (/^-[^-]+/.test(arg)) { + var letters = arg.slice(1,-1).split(''); + + var broken = false; + for (var j = 0; j < letters.length; j++) { + var next = arg.slice(j+2); + + if (next === '-') { + setArg(letters[j], next) + continue; + } + + if (/[A-Za-z]/.test(letters[j]) + && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { + setArg(letters[j], next); + broken = true; + break; + } + + if (letters[j+1] && letters[j+1].match(/\W/)) { + setArg(letters[j], arg.slice(j+2)); + broken = true; + break; + } + else { + setArg(letters[j], flags.strings[letters[j]] ? '' : true); + } + } + + var key = arg.slice(-1)[0]; + if (!broken && key !== '-') { + if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1]) + && !flags.bools[key] + && (aliases[key] ? !flags.bools[aliases[key]] : true)) { + setArg(key, args[i+1]); + i++; + } + else if (args[i+1] && /true|false/.test(args[i+1])) { + setArg(key, args[i+1] === 'true'); + i++; + } + else { + setArg(key, flags.strings[key] ? '' : true); + } + } + } + else { + argv._.push( + flags.strings['_'] || !isNumber(arg) ? arg : Number(arg) + ); + } + } + + Object.keys(defaults).forEach(function (key) { + if (!hasKey(argv, key.split('.'))) { + setKey(argv, key.split('.'), defaults[key]); + + (aliases[key] || []).forEach(function (x) { + setKey(argv, x.split('.'), defaults[key]); + }); + } + }); + + notFlags.forEach(function(key) { + argv._.push(key); + }); + + return argv; +}; + +function hasKey (obj, keys) { + var o = obj; + keys.slice(0,-1).forEach(function (key) { + o = (o[key] || {}); + }); + + var key = keys[keys.length - 1]; + return key in o; +} + +function setKey (obj, keys, value) { + var o = obj; + keys.slice(0,-1).forEach(function (key) { + if (o[key] === undefined) o[key] = {}; + o = o[key]; + }); + + var key = keys[keys.length - 1]; + if (o[key] === undefined || typeof o[key] === 'boolean') { + o[key] = value; + } + else if (Array.isArray(o[key])) { + o[key].push(value); + } + else { + o[key] = [ o[key], value ]; + } +} + +function isNumber (x) { + if (typeof x === 'number') return true; + if (/^0x[0-9a-f]+$/i.test(x)) return true; + return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x); +} + +function longest (xs) { + return Math.max.apply(null, xs.map(function (x) { return x.length })); +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/package.json new file mode 100644 index 0000000000..09e9ec4410 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/package.json @@ -0,0 +1,67 @@ +{ + "name": "minimist", + "version": "0.0.8", + "description": "parse argument options", + "main": "index.js", + "devDependencies": { + "tape": "~1.0.4", + "tap": "~0.4.0" + }, + "scripts": { + "test": "tap test/*.js" + }, + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/6..latest", + "ff/5", + "firefox/latest", + "chrome/10", + "chrome/latest", + "safari/5.1", + "safari/latest", + "opera/12" + ] + }, + "repository": { + "type": "git", + "url": "git://github.com/substack/minimist.git" + }, + "homepage": "https://github.com/substack/minimist", + "keywords": [ + "argv", + "getopt", + "parser", + "optimist" + ], + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/substack/minimist/issues" + }, + "_id": "minimist@0.0.8", + "dist": { + "shasum": "857fcabfc3397d2625b8228262e86aa7a011b05d", + "tarball": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + }, + "_from": "minimist@0.0.8", + "_npmVersion": "1.4.3", + "_npmUser": { + "name": "substack", + "email": "mail@substack.net" + }, + "maintainers": [ + { + "name": "substack", + "email": "mail@substack.net" + } + ], + "directories": {}, + "_shasum": "857fcabfc3397d2625b8228262e86aa7a011b05d", + "_resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/readme.markdown b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/readme.markdown new file mode 100644 index 0000000000..c25635323e --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/readme.markdown @@ -0,0 +1,73 @@ +# minimist + +parse argument options + +This module is the guts of optimist's argument parser without all the +fanciful decoration. + +[![browser support](https://ci.testling.com/substack/minimist.png)](http://ci.testling.com/substack/minimist) + +[![build status](https://secure.travis-ci.org/substack/minimist.png)](http://travis-ci.org/substack/minimist) + +# example + +``` js +var argv = require('minimist')(process.argv.slice(2)); +console.dir(argv); +``` + +``` +$ node example/parse.js -a beep -b boop +{ _: [], a: 'beep', b: 'boop' } +``` + +``` +$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz +{ _: [ 'foo', 'bar', 'baz' ], + x: 3, + y: 4, + n: 5, + a: true, + b: true, + c: true, + beep: 'boop' } +``` + +# methods + +``` js +var parseArgs = require('minimist') +``` + +## var argv = parseArgs(args, opts={}) + +Return an argument object `argv` populated with the array arguments from `args`. + +`argv._` contains all the arguments that didn't have an option associated with +them. + +Numeric-looking arguments will be returned as numbers unless `opts.string` or +`opts.boolean` is set for that argument name. + +Any arguments after `'--'` will not be parsed and will end up in `argv._`. + +options can be: + +* `opts.string` - a string or array of strings argument names to always treat as +strings +* `opts.boolean` - a string or array of strings to always treat as booleans +* `opts.alias` - an object mapping string names to strings or arrays of string +argument names to use as aliases +* `opts.default` - an object mapping string argument names to default values + +# install + +With [npm](https://npmjs.org) do: + +``` +npm install minimist +``` + +# license + +MIT diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/dash.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/dash.js new file mode 100644 index 0000000000..8b034b99a9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/dash.js @@ -0,0 +1,24 @@ +var parse = require('../'); +var test = require('tape'); + +test('-', function (t) { + t.plan(5); + t.deepEqual(parse([ '-n', '-' ]), { n: '-', _: [] }); + t.deepEqual(parse([ '-' ]), { _: [ '-' ] }); + t.deepEqual(parse([ '-f-' ]), { f: '-', _: [] }); + t.deepEqual( + parse([ '-b', '-' ], { boolean: 'b' }), + { b: true, _: [ '-' ] } + ); + t.deepEqual( + parse([ '-s', '-' ], { string: 's' }), + { s: '-', _: [] } + ); +}); + +test('-a -- b', function (t) { + t.plan(3); + t.deepEqual(parse([ '-a', '--', 'b' ]), { a: true, _: [ 'b' ] }); + t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] }); + t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/default_bool.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/default_bool.js new file mode 100644 index 0000000000..f0041ee40c --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/default_bool.js @@ -0,0 +1,20 @@ +var test = require('tape'); +var parse = require('../'); + +test('boolean default true', function (t) { + var argv = parse([], { + boolean: 'sometrue', + default: { sometrue: true } + }); + t.equal(argv.sometrue, true); + t.end(); +}); + +test('boolean default false', function (t) { + var argv = parse([], { + boolean: 'somefalse', + default: { somefalse: false } + }); + t.equal(argv.somefalse, false); + t.end(); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/dotted.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/dotted.js new file mode 100644 index 0000000000..ef0ae349bf --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/dotted.js @@ -0,0 +1,16 @@ +var parse = require('../'); +var test = require('tape'); + +test('dotted alias', function (t) { + var argv = parse(['--a.b', '22'], {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}}); + t.equal(argv.a.b, 22); + t.equal(argv.aa.bb, 22); + t.end(); +}); + +test('dotted default', function (t) { + var argv = parse('', {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}}); + t.equal(argv.a.b, 11); + t.equal(argv.aa.bb, 11); + t.end(); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/long.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/long.js new file mode 100644 index 0000000000..5d3a1e09d3 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/long.js @@ -0,0 +1,31 @@ +var test = require('tape'); +var parse = require('../'); + +test('long opts', function (t) { + t.deepEqual( + parse([ '--bool' ]), + { bool : true, _ : [] }, + 'long boolean' + ); + t.deepEqual( + parse([ '--pow', 'xixxle' ]), + { pow : 'xixxle', _ : [] }, + 'long capture sp' + ); + t.deepEqual( + parse([ '--pow=xixxle' ]), + { pow : 'xixxle', _ : [] }, + 'long capture eq' + ); + t.deepEqual( + parse([ '--host', 'localhost', '--port', '555' ]), + { host : 'localhost', port : 555, _ : [] }, + 'long captures sp' + ); + t.deepEqual( + parse([ '--host=localhost', '--port=555' ]), + { host : 'localhost', port : 555, _ : [] }, + 'long captures eq' + ); + t.end(); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/parse.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/parse.js new file mode 100644 index 0000000000..8a90646696 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/parse.js @@ -0,0 +1,318 @@ +var parse = require('../'); +var test = require('tape'); + +test('parse args', function (t) { + t.deepEqual( + parse([ '--no-moo' ]), + { moo : false, _ : [] }, + 'no' + ); + t.deepEqual( + parse([ '-v', 'a', '-v', 'b', '-v', 'c' ]), + { v : ['a','b','c'], _ : [] }, + 'multi' + ); + t.end(); +}); + +test('comprehensive', function (t) { + t.deepEqual( + parse([ + '--name=meowmers', 'bare', '-cats', 'woo', + '-h', 'awesome', '--multi=quux', + '--key', 'value', + '-b', '--bool', '--no-meep', '--multi=baz', + '--', '--not-a-flag', 'eek' + ]), + { + c : true, + a : true, + t : true, + s : 'woo', + h : 'awesome', + b : true, + bool : true, + key : 'value', + multi : [ 'quux', 'baz' ], + meep : false, + name : 'meowmers', + _ : [ 'bare', '--not-a-flag', 'eek' ] + } + ); + t.end(); +}); + +test('nums', function (t) { + var argv = parse([ + '-x', '1234', + '-y', '5.67', + '-z', '1e7', + '-w', '10f', + '--hex', '0xdeadbeef', + '789' + ]); + t.deepEqual(argv, { + x : 1234, + y : 5.67, + z : 1e7, + w : '10f', + hex : 0xdeadbeef, + _ : [ 789 ] + }); + t.deepEqual(typeof argv.x, 'number'); + t.deepEqual(typeof argv.y, 'number'); + t.deepEqual(typeof argv.z, 'number'); + t.deepEqual(typeof argv.w, 'string'); + t.deepEqual(typeof argv.hex, 'number'); + t.deepEqual(typeof argv._[0], 'number'); + t.end(); +}); + +test('flag boolean', function (t) { + var argv = parse([ '-t', 'moo' ], { boolean: 't' }); + t.deepEqual(argv, { t : true, _ : [ 'moo' ] }); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); +}); + +test('flag boolean value', function (t) { + var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], { + boolean: [ 't', 'verbose' ], + default: { verbose: true } + }); + + t.deepEqual(argv, { + verbose: false, + t: true, + _: ['moo'] + }); + + t.deepEqual(typeof argv.verbose, 'boolean'); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); +}); + +test('flag boolean default false', function (t) { + var argv = parse(['moo'], { + boolean: ['t', 'verbose'], + default: { verbose: false, t: false } + }); + + t.deepEqual(argv, { + verbose: false, + t: false, + _: ['moo'] + }); + + t.deepEqual(typeof argv.verbose, 'boolean'); + t.deepEqual(typeof argv.t, 'boolean'); + t.end(); + +}); + +test('boolean groups', function (t) { + var argv = parse([ '-x', '-z', 'one', 'two', 'three' ], { + boolean: ['x','y','z'] + }); + + t.deepEqual(argv, { + x : true, + y : false, + z : true, + _ : [ 'one', 'two', 'three' ] + }); + + t.deepEqual(typeof argv.x, 'boolean'); + t.deepEqual(typeof argv.y, 'boolean'); + t.deepEqual(typeof argv.z, 'boolean'); + t.end(); +}); + +test('newlines in params' , function (t) { + var args = parse([ '-s', "X\nX" ]) + t.deepEqual(args, { _ : [], s : "X\nX" }); + + // reproduce in bash: + // VALUE="new + // line" + // node program.js --s="$VALUE" + args = parse([ "--s=X\nX" ]) + t.deepEqual(args, { _ : [], s : "X\nX" }); + t.end(); +}); + +test('strings' , function (t) { + var s = parse([ '-s', '0001234' ], { string: 's' }).s; + t.equal(s, '0001234'); + t.equal(typeof s, 'string'); + + var x = parse([ '-x', '56' ], { string: 'x' }).x; + t.equal(x, '56'); + t.equal(typeof x, 'string'); + t.end(); +}); + +test('stringArgs', function (t) { + var s = parse([ ' ', ' ' ], { string: '_' })._; + t.same(s.length, 2); + t.same(typeof s[0], 'string'); + t.same(s[0], ' '); + t.same(typeof s[1], 'string'); + t.same(s[1], ' '); + t.end(); +}); + +test('empty strings', function(t) { + var s = parse([ '-s' ], { string: 's' }).s; + t.equal(s, ''); + t.equal(typeof s, 'string'); + + var str = parse([ '--str' ], { string: 'str' }).str; + t.equal(str, ''); + t.equal(typeof str, 'string'); + + var letters = parse([ '-art' ], { + string: [ 'a', 't' ] + }); + + t.equal(letters.a, ''); + t.equal(letters.r, true); + t.equal(letters.t, ''); + + t.end(); +}); + + +test('slashBreak', function (t) { + t.same( + parse([ '-I/foo/bar/baz' ]), + { I : '/foo/bar/baz', _ : [] } + ); + t.same( + parse([ '-xyz/foo/bar/baz' ]), + { x : true, y : true, z : '/foo/bar/baz', _ : [] } + ); + t.end(); +}); + +test('alias', function (t) { + var argv = parse([ '-f', '11', '--zoom', '55' ], { + alias: { z: 'zoom' } + }); + t.equal(argv.zoom, 55); + t.equal(argv.z, argv.zoom); + t.equal(argv.f, 11); + t.end(); +}); + +test('multiAlias', function (t) { + var argv = parse([ '-f', '11', '--zoom', '55' ], { + alias: { z: [ 'zm', 'zoom' ] } + }); + t.equal(argv.zoom, 55); + t.equal(argv.z, argv.zoom); + t.equal(argv.z, argv.zm); + t.equal(argv.f, 11); + t.end(); +}); + +test('nested dotted objects', function (t) { + var argv = parse([ + '--foo.bar', '3', '--foo.baz', '4', + '--foo.quux.quibble', '5', '--foo.quux.o_O', + '--beep.boop' + ]); + + t.same(argv.foo, { + bar : 3, + baz : 4, + quux : { + quibble : 5, + o_O : true + } + }); + t.same(argv.beep, { boop : true }); + t.end(); +}); + +test('boolean and alias with chainable api', function (t) { + var aliased = [ '-h', 'derp' ]; + var regular = [ '--herp', 'derp' ]; + var opts = { + herp: { alias: 'h', boolean: true } + }; + var aliasedArgv = parse(aliased, { + boolean: 'herp', + alias: { h: 'herp' } + }); + var propertyArgv = parse(regular, { + boolean: 'herp', + alias: { h: 'herp' } + }); + var expected = { + herp: true, + h: true, + '_': [ 'derp' ] + }; + + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +test('boolean and alias with options hash', function (t) { + var aliased = [ '-h', 'derp' ]; + var regular = [ '--herp', 'derp' ]; + var opts = { + alias: { 'h': 'herp' }, + boolean: 'herp' + }; + var aliasedArgv = parse(aliased, opts); + var propertyArgv = parse(regular, opts); + var expected = { + herp: true, + h: true, + '_': [ 'derp' ] + }; + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +test('boolean and alias using explicit true', function (t) { + var aliased = [ '-h', 'true' ]; + var regular = [ '--herp', 'true' ]; + var opts = { + alias: { h: 'herp' }, + boolean: 'h' + }; + var aliasedArgv = parse(aliased, opts); + var propertyArgv = parse(regular, opts); + var expected = { + herp: true, + h: true, + '_': [ ] + }; + + t.same(aliasedArgv, expected); + t.same(propertyArgv, expected); + t.end(); +}); + +// regression, see https://github.com/substack/node-optimist/issues/71 +test('boolean and --x=true', function(t) { + var parsed = parse(['--boool', '--other=true'], { + boolean: 'boool' + }); + + t.same(parsed.boool, true); + t.same(parsed.other, 'true'); + + parsed = parse(['--boool', '--other=false'], { + boolean: 'boool' + }); + + t.same(parsed.boool, true); + t.same(parsed.other, 'false'); + t.end(); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/parse_modified.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/parse_modified.js new file mode 100644 index 0000000000..21851b036e --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/parse_modified.js @@ -0,0 +1,9 @@ +var parse = require('../'); +var test = require('tape'); + +test('parse with modifier functions' , function (t) { + t.plan(1); + + var argv = parse([ '-b', '123' ], { boolean: 'b' }); + t.deepEqual(argv, { b: true, _: ['123'] }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/short.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/short.js new file mode 100644 index 0000000000..d513a1c252 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/short.js @@ -0,0 +1,67 @@ +var parse = require('../'); +var test = require('tape'); + +test('numeric short args', function (t) { + t.plan(2); + t.deepEqual(parse([ '-n123' ]), { n: 123, _: [] }); + t.deepEqual( + parse([ '-123', '456' ]), + { 1: true, 2: true, 3: 456, _: [] } + ); +}); + +test('short', function (t) { + t.deepEqual( + parse([ '-b' ]), + { b : true, _ : [] }, + 'short boolean' + ); + t.deepEqual( + parse([ 'foo', 'bar', 'baz' ]), + { _ : [ 'foo', 'bar', 'baz' ] }, + 'bare' + ); + t.deepEqual( + parse([ '-cats' ]), + { c : true, a : true, t : true, s : true, _ : [] }, + 'group' + ); + t.deepEqual( + parse([ '-cats', 'meow' ]), + { c : true, a : true, t : true, s : 'meow', _ : [] }, + 'short group next' + ); + t.deepEqual( + parse([ '-h', 'localhost' ]), + { h : 'localhost', _ : [] }, + 'short capture' + ); + t.deepEqual( + parse([ '-h', 'localhost', '-p', '555' ]), + { h : 'localhost', p : 555, _ : [] }, + 'short captures' + ); + t.end(); +}); + +test('mixed short bool and capture', function (t) { + t.same( + parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]), + { + f : true, p : 555, h : 'localhost', + _ : [ 'script.js' ] + } + ); + t.end(); +}); + +test('short and long', function (t) { + t.deepEqual( + parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]), + { + f : true, p : 555, h : 'localhost', + _ : [ 'script.js' ] + } + ); + t.end(); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/whitespace.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/whitespace.js new file mode 100644 index 0000000000..8a52a58cec --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/node_modules/minimist/test/whitespace.js @@ -0,0 +1,8 @@ +var parse = require('../'); +var test = require('tape'); + +test('whitespace should be whitespace' , function (t) { + t.plan(1); + var x = parse([ '-x', '\t' ]).x; + t.equal(x, '\t'); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/package.json new file mode 100644 index 0000000000..b48242c4dd --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/package.json @@ -0,0 +1,60 @@ +{ + "name": "mkdirp", + "description": "Recursively mkdir, like `mkdir -p`", + "version": "0.5.1", + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "main": "index.js", + "keywords": [ + "mkdir", + "directory" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/substack/node-mkdirp.git" + }, + "scripts": { + "test": "tap test/*.js" + }, + "dependencies": { + "minimist": "0.0.8" + }, + "devDependencies": { + "tap": "1", + "mock-fs": "2 >=2.7.0" + }, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "license": "MIT", + "gitHead": "d4eff0f06093aed4f387e88e9fc301cb76beedc7", + "bugs": { + "url": "https://github.com/substack/node-mkdirp/issues" + }, + "homepage": "https://github.com/substack/node-mkdirp#readme", + "_id": "mkdirp@0.5.1", + "_shasum": "30057438eac6cf7f8c4767f38648d6697d75c903", + "_from": "mkdirp@>=0.5.0 >=0.0.0 <1.0.0", + "_npmVersion": "2.9.0", + "_nodeVersion": "2.0.0", + "_npmUser": { + "name": "substack", + "email": "substack@gmail.com" + }, + "dist": { + "shasum": "30057438eac6cf7f8c4767f38648d6697d75c903", + "tarball": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + }, + "maintainers": [ + { + "name": "substack", + "email": "mail@substack.net" + } + ], + "directories": {}, + "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/readme.markdown b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/readme.markdown new file mode 100644 index 0000000000..3cc1315385 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/readme.markdown @@ -0,0 +1,100 @@ +# mkdirp + +Like `mkdir -p`, but in node.js! + +[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp) + +# example + +## pow.js + +```js +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', function (err) { + if (err) console.error(err) + else console.log('pow!') +}); +``` + +Output + +``` +pow! +``` + +And now /tmp/foo/bar/baz exists, huzzah! + +# methods + +```js +var mkdirp = require('mkdirp'); +``` + +## mkdirp(dir, opts, cb) + +Create a new directory and any necessary subdirectories at `dir` with octal +permission string `opts.mode`. If `opts` is a non-object, it will be treated as +the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0777 & (~process.umask())`. + +`cb(err, made)` fires with the error or the first directory `made` +that had to be created, if any. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdir(path, mode, cb)` and +`opts.fs.stat(path, cb)`. + +## mkdirp.sync(dir, opts) + +Synchronously create a new directory and any necessary subdirectories at `dir` +with octal permission string `opts.mode`. If `opts` is a non-object, it will be +treated as the `opts.mode`. + +If `opts.mode` isn't specified, it defaults to `0777 & (~process.umask())`. + +Returns the first directory that had to be created, if any. + +You can optionally pass in an alternate `fs` implementation by passing in +`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` and +`opts.fs.statSync(path)`. + +# usage + +This package also ships with a `mkdirp` command. + +``` +usage: mkdirp [DIR1,DIR2..] {OPTIONS} + + Create each supplied directory including any necessary parent directories that + don't yet exist. + + If the directory already exists, do nothing. + +OPTIONS are: + + -m, --mode If a directory needs to be created, set the mode as an octal + permission string. + +``` + +# install + +With [npm](http://npmjs.org) do: + +``` +npm install mkdirp +``` + +to get the library, or + +``` +npm install -g mkdirp +``` + +to get the command. + +# license + +MIT diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/chmod.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/chmod.js new file mode 100644 index 0000000000..6a404b932f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/chmod.js @@ -0,0 +1,41 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); +var _0744 = parseInt('0744', 8); + +var ps = [ '', 'tmp' ]; + +for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); +} + +var file = ps.join('/'); + +test('chmod-pre', function (t) { + var mode = _0744 + mkdirp(file, mode, function (er) { + t.ifError(er, 'should not error'); + fs.stat(file, function (er, stat) { + t.ifError(er, 'should exist'); + t.ok(stat && stat.isDirectory(), 'should be directory'); + t.equal(stat && stat.mode & _0777, mode, 'should be 0744'); + t.end(); + }); + }); +}); + +test('chmod', function (t) { + var mode = _0755 + mkdirp(file, mode, function (er) { + t.ifError(er, 'should not error'); + fs.stat(file, function (er, stat) { + t.ifError(er, 'should exist'); + t.ok(stat && stat.isDirectory(), 'should be directory'); + t.end(); + }); + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/clobber.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/clobber.js new file mode 100644 index 0000000000..2433b9ad54 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/clobber.js @@ -0,0 +1,38 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; +var _0755 = parseInt('0755', 8); + +var ps = [ '', 'tmp' ]; + +for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); +} + +var file = ps.join('/'); + +// a file in the way +var itw = ps.slice(0, 3).join('/'); + + +test('clobber-pre', function (t) { + console.error("about to write to "+itw) + fs.writeFileSync(itw, 'I AM IN THE WAY, THE TRUTH, AND THE LIGHT.'); + + fs.stat(itw, function (er, stat) { + t.ifError(er) + t.ok(stat && stat.isFile(), 'should be file') + t.end() + }) +}) + +test('clobber', function (t) { + t.plan(2); + mkdirp(file, _0755, function (err) { + t.ok(err); + t.equal(err.code, 'ENOTDIR'); + t.end(); + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/mkdirp.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/mkdirp.js new file mode 100644 index 0000000000..eaa8921c7f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/mkdirp.js @@ -0,0 +1,28 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var exists = fs.exists || path.exists; +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('woo', function (t) { + t.plan(5); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + mkdirp(file, _0755, function (err) { + t.ifError(err); + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, _0755); + t.ok(stat.isDirectory(), 'target not a directory'); + }) + }) + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/opts_fs.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/opts_fs.js new file mode 100644 index 0000000000..97186b62e0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/opts_fs.js @@ -0,0 +1,29 @@ +var mkdirp = require('../'); +var path = require('path'); +var test = require('tap').test; +var mockfs = require('mock-fs'); +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('opts.fs', function (t) { + t.plan(5); + + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/beep/boop/' + [x,y,z].join('/'); + var xfs = mockfs.fs(); + + mkdirp(file, { fs: xfs, mode: _0755 }, function (err) { + t.ifError(err); + xfs.exists(file, function (ex) { + t.ok(ex, 'created file'); + xfs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, _0755); + t.ok(stat.isDirectory(), 'target not a directory'); + }); + }); + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/opts_fs_sync.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/opts_fs_sync.js new file mode 100644 index 0000000000..6c370aa6e9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/opts_fs_sync.js @@ -0,0 +1,27 @@ +var mkdirp = require('../'); +var path = require('path'); +var test = require('tap').test; +var mockfs = require('mock-fs'); +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('opts.fs sync', function (t) { + t.plan(4); + + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/beep/boop/' + [x,y,z].join('/'); + var xfs = mockfs.fs(); + + mkdirp.sync(file, { fs: xfs, mode: _0755 }); + xfs.exists(file, function (ex) { + t.ok(ex, 'created file'); + xfs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, _0755); + t.ok(stat.isDirectory(), 'target not a directory'); + }); + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/perm.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/perm.js new file mode 100644 index 0000000000..fbce44b82b --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/perm.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var exists = fs.exists || path.exists; +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('async perm', function (t) { + t.plan(5); + var file = '/tmp/' + (Math.random() * (1<<30)).toString(16); + + mkdirp(file, _0755, function (err) { + t.ifError(err); + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, _0755); + t.ok(stat.isDirectory(), 'target not a directory'); + }) + }) + }); +}); + +test('async root perm', function (t) { + mkdirp('/tmp', _0755, function (err) { + if (err) t.fail(err); + t.end(); + }); + t.end(); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/perm_sync.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/perm_sync.js new file mode 100644 index 0000000000..398229fe54 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/perm_sync.js @@ -0,0 +1,36 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var exists = fs.exists || path.exists; +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('sync perm', function (t) { + t.plan(4); + var file = '/tmp/' + (Math.random() * (1<<30)).toString(16) + '.json'; + + mkdirp.sync(file, _0755); + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, _0755); + t.ok(stat.isDirectory(), 'target not a directory'); + }); + }); +}); + +test('sync root perm', function (t) { + t.plan(3); + + var file = '/tmp'; + mkdirp.sync(file, _0755); + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + t.ok(stat.isDirectory(), 'target not a directory'); + }) + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/race.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/race.js new file mode 100644 index 0000000000..b0b9e183c9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/race.js @@ -0,0 +1,37 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var exists = fs.exists || path.exists; +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('race', function (t) { + t.plan(10); + var ps = [ '', 'tmp' ]; + + for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); + } + var file = ps.join('/'); + + var res = 2; + mk(file); + + mk(file); + + function mk (file, cb) { + mkdirp(file, _0755, function (err) { + t.ifError(err); + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, _0755); + t.ok(stat.isDirectory(), 'target not a directory'); + }); + }) + }); + } +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/rel.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/rel.js new file mode 100644 index 0000000000..4ddb34276a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/rel.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var exists = fs.exists || path.exists; +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('rel', function (t) { + t.plan(5); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var cwd = process.cwd(); + process.chdir('/tmp'); + + var file = [x,y,z].join('/'); + + mkdirp(file, _0755, function (err) { + t.ifError(err); + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + process.chdir(cwd); + t.equal(stat.mode & _0777, _0755); + t.ok(stat.isDirectory(), 'target not a directory'); + }) + }) + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/return.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/return.js new file mode 100644 index 0000000000..bce68e5613 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/return.js @@ -0,0 +1,25 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('return value', function (t) { + t.plan(4); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + // should return the first dir created. + // By this point, it would be profoundly surprising if /tmp didn't + // already exist, since every other test makes things in there. + mkdirp(file, function (err, made) { + t.ifError(err); + t.equal(made, '/tmp/' + x); + mkdirp(file, function (err, made) { + t.ifError(err); + t.equal(made, null); + }); + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/return_sync.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/return_sync.js new file mode 100644 index 0000000000..7c222d3558 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/return_sync.js @@ -0,0 +1,24 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('return value', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + // should return the first dir created. + // By this point, it would be profoundly surprising if /tmp didn't + // already exist, since every other test makes things in there. + // Note that this will throw on failure, which will fail the test. + var made = mkdirp.sync(file); + t.equal(made, '/tmp/' + x); + + // making the same file again should have no effect. + made = mkdirp.sync(file); + t.equal(made, null); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/root.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/root.js new file mode 100644 index 0000000000..9e7d079d9f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/root.js @@ -0,0 +1,19 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; +var _0755 = parseInt('0755', 8); + +test('root', function (t) { + // '/' on unix, 'c:/' on windows. + var file = path.resolve('/'); + + mkdirp(file, _0755, function (err) { + if (err) throw err + fs.stat(file, function (er, stat) { + if (er) throw er + t.ok(stat.isDirectory(), 'target is a directory'); + t.end(); + }) + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/sync.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/sync.js new file mode 100644 index 0000000000..8c8dc938c8 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/sync.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var exists = fs.exists || path.exists; +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('sync', function (t) { + t.plan(4); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + try { + mkdirp.sync(file, _0755); + } catch (err) { + t.fail(err); + return t.end(); + } + + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, _0755); + t.ok(stat.isDirectory(), 'target not a directory'); + }); + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/umask.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/umask.js new file mode 100644 index 0000000000..2033c63a41 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/umask.js @@ -0,0 +1,28 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var exists = fs.exists || path.exists; +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('implicit mode from umask', function (t) { + t.plan(5); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + mkdirp(file, function (err) { + t.ifError(err); + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, _0777 & (~process.umask())); + t.ok(stat.isDirectory(), 'target not a directory'); + }); + }) + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/umask_sync.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/umask_sync.js new file mode 100644 index 0000000000..11a7614749 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/mkdirp/test/umask_sync.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var exists = fs.exists || path.exists; +var test = require('tap').test; +var _0777 = parseInt('0777', 8); +var _0755 = parseInt('0755', 8); + +test('umask sync modes', function (t) { + t.plan(4); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + try { + mkdirp.sync(file); + } catch (err) { + t.fail(err); + return t.end(); + } + + exists(file, function (ex) { + t.ok(ex, 'file created'); + fs.stat(file, function (err, stat) { + t.ifError(err); + t.equal(stat.mode & _0777, (_0777 & (~process.umask()))); + t.ok(stat.isDirectory(), 'target not a directory'); + }); + }); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/LICENSE new file mode 100644 index 0000000000..19129e315f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/README.md new file mode 100644 index 0000000000..18659f67fa --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/README.md @@ -0,0 +1,38 @@ +[![Build Status](https://travis-ci.org/isaacs/rimraf.svg?branch=master)](https://travis-ci.org/isaacs/rimraf) [![Dependency Status](https://david-dm.org/isaacs/rimraf.svg)](https://david-dm.org/isaacs/rimraf) [![devDependency Status](https://david-dm.org/isaacs/rimraf/dev-status.svg)](https://david-dm.org/isaacs/rimraf#info=devDependencies) + +The [UNIX command](http://en.wikipedia.org/wiki/Rm_(Unix)) `rm -rf` for node. + +Install with `npm install rimraf`, or just drop rimraf.js somewhere. + +## API + +`rimraf(f, callback)` + +The callback will be called with an error if there is one. Certain +errors are handled for you: + +* Windows: `EBUSY` and `ENOTEMPTY` - rimraf will back off a maximum of + `opts.maxBusyTries` times before giving up, adding 100ms of wait + between each attempt. The default `maxBusyTries` is 3. +* `ENOENT` - If the file doesn't exist, rimraf will return + successfully, since your desired outcome is already the case. +* `EMFILE` - Since `readdir` requires opening a file descriptor, it's + possible to hit `EMFILE` if too many file descriptors are in use. + In the sync case, there's nothing to be done for this. But in the + async case, rimraf will gradually back off with timeouts up to + `opts.emfileWait` ms, which defaults to 1000. + +## rimraf.sync + +It can remove stuff synchronously, too. But that's not so good. Use +the async API. It's better. + +## CLI + +If installed with `npm install rimraf -g` it can be used as a global +command `rimraf [ ...]` which is useful for cross platform support. + +## mkdirp + +If you need to create a directory recursively, check out +[mkdirp](https://github.com/substack/node-mkdirp). diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/bin.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/bin.js new file mode 100755 index 0000000000..1bd5a0d16a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/bin.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +var rimraf = require('./') + +var help = false +var dashdash = false +var args = process.argv.slice(2).filter(function(arg) { + if (dashdash) + return !!arg + else if (arg === '--') + dashdash = true + else if (arg.match(/^(-+|\/)(h(elp)?|\?)$/)) + help = true + else + return !!arg +}); + +if (help || args.length === 0) { + // If they didn't ask for help, then this is not a "success" + var log = help ? console.log : console.error + log('Usage: rimraf [ ...]') + log('') + log(' Deletes all files and folders at "path" recursively.') + log('') + log('Options:') + log('') + log(' -h, --help Display this usage info') + process.exit(help ? 0 : 1) +} else + go(0) + +function go (n) { + if (n >= args.length) + return + rimraf(args[n], function (er) { + if (er) + throw er + go(n+1) + }) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/LICENSE new file mode 100644 index 0000000000..19129e315f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/README.md new file mode 100644 index 0000000000..063cf950ac --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/README.md @@ -0,0 +1,377 @@ +[![Build Status](https://travis-ci.org/isaacs/node-glob.svg?branch=master)](https://travis-ci.org/isaacs/node-glob/) [![Dependency Status](https://david-dm.org/isaacs/node-glob.svg)](https://david-dm.org/isaacs/node-glob) [![devDependency Status](https://david-dm.org/isaacs/node-glob/dev-status.svg)](https://david-dm.org/isaacs/node-glob#info=devDependencies) [![optionalDependency Status](https://david-dm.org/isaacs/node-glob/optional-status.svg)](https://david-dm.org/isaacs/node-glob#info=optionalDependencies) + +# Glob + +Match files using the patterns the shell uses, like stars and stuff. + +This is a glob implementation in JavaScript. It uses the `minimatch` +library to do its matching. + +![](oh-my-glob.gif) + +## Usage + +```javascript +var glob = require("glob") + +// options is optional +glob("**/*.js", options, function (er, files) { + // files is an array of filenames. + // If the `nonull` option is set, and nothing + // was found, then files is ["**/*.js"] + // er is an error object or null. +}) +``` + +## Glob Primer + +"Globs" are the patterns you type when you do stuff like `ls *.js` on +the command line, or put `build/*` in a `.gitignore` file. + +Before parsing the path part patterns, braced sections are expanded +into a set. Braced sections start with `{` and end with `}`, with any +number of comma-delimited sections within. Braced sections may contain +slash characters, so `a{/b/c,bcd}` would expand into `a/b/c` and `abcd`. + +The following characters have special magic meaning when used in a +path portion: + +* `*` Matches 0 or more characters in a single path portion +* `?` Matches 1 character +* `[...]` Matches a range of characters, similar to a RegExp range. + If the first character of the range is `!` or `^` then it matches + any character not in the range. +* `!(pattern|pattern|pattern)` Matches anything that does not match + any of the patterns provided. +* `?(pattern|pattern|pattern)` Matches zero or one occurrence of the + patterns provided. +* `+(pattern|pattern|pattern)` Matches one or more occurrences of the + patterns provided. +* `*(a|b|c)` Matches zero or more occurrences of the patterns provided +* `@(pattern|pat*|pat?erN)` Matches exactly one of the patterns + provided +* `**` If a "globstar" is alone in a path portion, then it matches + zero or more directories and subdirectories searching for matches. + It does not crawl symlinked directories. + +### Dots + +If a file or directory path portion has a `.` as the first character, +then it will not match any glob pattern unless that pattern's +corresponding path part also has a `.` as its first character. + +For example, the pattern `a/.*/c` would match the file at `a/.b/c`. +However the pattern `a/*/c` would not, because `*` does not start with +a dot character. + +You can make glob treat dots as normal characters by setting +`dot:true` in the options. + +### Basename Matching + +If you set `matchBase:true` in the options, and the pattern has no +slashes in it, then it will seek for any file anywhere in the tree +with a matching basename. For example, `*.js` would match +`test/simple/basic.js`. + +### Negation + +The intent for negation would be for a pattern starting with `!` to +match everything that *doesn't* match the supplied pattern. However, +the implementation is weird, and for the time being, this should be +avoided. The behavior is deprecated in version 5, and will be removed +entirely in version 6. + +### Empty Sets + +If no matching files are found, then an empty array is returned. This +differs from the shell, where the pattern itself is returned. For +example: + + $ echo a*s*d*f + a*s*d*f + +To get the bash-style behavior, set the `nonull:true` in the options. + +### See Also: + +* `man sh` +* `man bash` (Search for "Pattern Matching") +* `man 3 fnmatch` +* `man 5 gitignore` +* [minimatch documentation](https://github.com/isaacs/minimatch) + +## glob.hasMagic(pattern, [options]) + +Returns `true` if there are any special characters in the pattern, and +`false` otherwise. + +Note that the options affect the results. If `noext:true` is set in +the options object, then `+(a|b)` will not be considered a magic +pattern. If the pattern has a brace expansion, like `a/{b/c,x/y}` +then that is considered magical, unless `nobrace:true` is set in the +options. + +## glob(pattern, [options], cb) + +* `pattern` {String} Pattern to be matched +* `options` {Object} +* `cb` {Function} + * `err` {Error | null} + * `matches` {Array} filenames found matching the pattern + +Perform an asynchronous glob search. + +## glob.sync(pattern, [options]) + +* `pattern` {String} Pattern to be matched +* `options` {Object} +* return: {Array} filenames found matching the pattern + +Perform a synchronous glob search. + +## Class: glob.Glob + +Create a Glob object by instantiating the `glob.Glob` class. + +```javascript +var Glob = require("glob").Glob +var mg = new Glob(pattern, options, cb) +``` + +It's an EventEmitter, and starts walking the filesystem to find matches +immediately. + +### new glob.Glob(pattern, [options], [cb]) + +* `pattern` {String} pattern to search for +* `options` {Object} +* `cb` {Function} Called when an error occurs, or matches are found + * `err` {Error | null} + * `matches` {Array} filenames found matching the pattern + +Note that if the `sync` flag is set in the options, then matches will +be immediately available on the `g.found` member. + +### Properties + +* `minimatch` The minimatch object that the glob uses. +* `options` The options object passed in. +* `aborted` Boolean which is set to true when calling `abort()`. There + is no way at this time to continue a glob search after aborting, but + you can re-use the statCache to avoid having to duplicate syscalls. +* `cache` Convenience object. Each field has the following possible + values: + * `false` - Path does not exist + * `true` - Path exists + * `'DIR'` - Path exists, and is not a directory + * `'FILE'` - Path exists, and is a directory + * `[file, entries, ...]` - Path exists, is a directory, and the + array value is the results of `fs.readdir` +* `statCache` Cache of `fs.stat` results, to prevent statting the same + path multiple times. +* `symlinks` A record of which paths are symbolic links, which is + relevant in resolving `**` patterns. +* `realpathCache` An optional object which is passed to `fs.realpath` + to minimize unnecessary syscalls. It is stored on the instantiated + Glob object, and may be re-used. + +### Events + +* `end` When the matching is finished, this is emitted with all the + matches found. If the `nonull` option is set, and no match was found, + then the `matches` list contains the original pattern. The matches + are sorted, unless the `nosort` flag is set. +* `match` Every time a match is found, this is emitted with the matched. +* `error` Emitted when an unexpected error is encountered, or whenever + any fs error occurs if `options.strict` is set. +* `abort` When `abort()` is called, this event is raised. + +### Methods + +* `pause` Temporarily stop the search +* `resume` Resume the search +* `abort` Stop the search forever + +### Options + +All the options that can be passed to Minimatch can also be passed to +Glob to change pattern matching behavior. Also, some have been added, +or have glob-specific ramifications. + +All options are false by default, unless otherwise noted. + +All options are added to the Glob object, as well. + +If you are running many `glob` operations, you can pass a Glob object +as the `options` argument to a subsequent operation to shortcut some +`stat` and `readdir` calls. At the very least, you may pass in shared +`symlinks`, `statCache`, `realpathCache`, and `cache` options, so that +parallel glob operations will be sped up by sharing information about +the filesystem. + +* `cwd` The current working directory in which to search. Defaults + to `process.cwd()`. +* `root` The place where patterns starting with `/` will be mounted + onto. Defaults to `path.resolve(options.cwd, "/")` (`/` on Unix + systems, and `C:\` or some such on Windows.) +* `dot` Include `.dot` files in normal matches and `globstar` matches. + Note that an explicit dot in a portion of the pattern will always + match dot files. +* `nomount` By default, a pattern starting with a forward-slash will be + "mounted" onto the root setting, so that a valid filesystem path is + returned. Set this flag to disable that behavior. +* `mark` Add a `/` character to directory matches. Note that this + requires additional stat calls. +* `nosort` Don't sort the results. +* `stat` Set to true to stat *all* results. This reduces performance + somewhat, and is completely unnecessary, unless `readdir` is presumed + to be an untrustworthy indicator of file existence. +* `silent` When an unusual error is encountered when attempting to + read a directory, a warning will be printed to stderr. Set the + `silent` option to true to suppress these warnings. +* `strict` When an unusual error is encountered when attempting to + read a directory, the process will just continue on in search of + other matches. Set the `strict` option to raise an error in these + cases. +* `cache` See `cache` property above. Pass in a previously generated + cache object to save some fs calls. +* `statCache` A cache of results of filesystem information, to prevent + unnecessary stat calls. While it should not normally be necessary + to set this, you may pass the statCache from one glob() call to the + options object of another, if you know that the filesystem will not + change between calls. (See "Race Conditions" below.) +* `symlinks` A cache of known symbolic links. You may pass in a + previously generated `symlinks` object to save `lstat` calls when + resolving `**` matches. +* `sync` DEPRECATED: use `glob.sync(pattern, opts)` instead. +* `nounique` In some cases, brace-expanded patterns can result in the + same file showing up multiple times in the result set. By default, + this implementation prevents duplicates in the result set. Set this + flag to disable that behavior. +* `nonull` Set to never return an empty set, instead returning a set + containing the pattern itself. This is the default in glob(3). +* `debug` Set to enable debug logging in minimatch and glob. +* `nobrace` Do not expand `{a,b}` and `{1..3}` brace sets. +* `noglobstar` Do not match `**` against multiple filenames. (Ie, + treat it as a normal `*` instead.) +* `noext` Do not match `+(a|b)` "extglob" patterns. +* `nocase` Perform a case-insensitive match. Note: on + case-insensitive filesystems, non-magic patterns will match by + default, since `stat` and `readdir` will not raise errors. +* `matchBase` Perform a basename-only match if the pattern does not + contain any slash characters. That is, `*.js` would be treated as + equivalent to `**/*.js`, matching all js files in all directories. +* `nodir` Do not match directories, only files. (Note: to match + *only* directories, simply put a `/` at the end of the pattern.) +* `ignore` Add a pattern or an array of patterns to exclude matches. +* `follow` Follow symlinked directories when expanding `**` patterns. + Note that this can result in a lot of duplicate references in the + presence of cyclic links. +* `realpath` Set to true to call `fs.realpath` on all of the results. + In the case of a symlink that cannot be resolved, the full absolute + path to the matched entry is returned (though it will usually be a + broken symlink) +* `nonegate` Suppress deprecated `negate` behavior. (See below.) + Default=true +* `nocomment` Suppress deprecated `comment` behavior. (See below.) + Default=true + +## Comparisons to other fnmatch/glob implementations + +While strict compliance with the existing standards is a worthwhile +goal, some discrepancies exist between node-glob and other +implementations, and are intentional. + +The double-star character `**` is supported by default, unless the +`noglobstar` flag is set. This is supported in the manner of bsdglob +and bash 4.3, where `**` only has special significance if it is the only +thing in a path part. That is, `a/**/b` will match `a/x/y/b`, but +`a/**b` will not. + +Note that symlinked directories are not crawled as part of a `**`, +though their contents may match against subsequent portions of the +pattern. This prevents infinite loops and duplicates and the like. + +If an escaped pattern has no matches, and the `nonull` flag is set, +then glob returns the pattern as-provided, rather than +interpreting the character escapes. For example, +`glob.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than +`"*a?"`. This is akin to setting the `nullglob` option in bash, except +that it does not resolve escaped pattern characters. + +If brace expansion is not disabled, then it is performed before any +other interpretation of the glob pattern. Thus, a pattern like +`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded +**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are +checked for validity. Since those two are valid, matching proceeds. + +### Comments and Negation + +**Note**: In version 5 of this module, negation and comments are +**disabled** by default. You can explicitly set `nonegate:false` or +`nocomment:false` to re-enable them. They are going away entirely in +version 6. + +The intent for negation would be for a pattern starting with `!` to +match everything that *doesn't* match the supplied pattern. However, +the implementation is weird. It is better to use the `ignore` option +to set a pattern or set of patterns to exclude from matches. If you +want the "everything except *x*" type of behavior, you can use `**` as +the main pattern, and set an `ignore` for the things to exclude. + +The comments feature is added in minimatch, primarily to more easily +support use cases like ignore files, where a `#` at the start of a +line makes the pattern "empty". However, in the context of a +straightforward filesystem globber, "comments" don't make much sense. + +## Windows + +**Please only use forward-slashes in glob expressions.** + +Though windows uses either `/` or `\` as its path separator, only `/` +characters are used by this glob implementation. You must use +forward-slashes **only** in glob expressions. Back-slashes will always +be interpreted as escape characters, not path separators. + +Results from absolute patterns such as `/foo/*` are mounted onto the +root setting using `path.join`. On windows, this will by default result +in `/foo/*` matching `C:\foo\bar.txt`. + +## Race Conditions + +Glob searching, by its very nature, is susceptible to race conditions, +since it relies on directory walking and such. + +As a result, it is possible that a file that exists when glob looks for +it may have been deleted or modified by the time it returns the result. + +As part of its internal implementation, this program caches all stat +and readdir calls that it makes, in order to cut down on system +overhead. However, this also makes it even more susceptible to races, +especially if the cache or statCache objects are reused between glob +calls. + +Users are thus advised not to use a glob result as a guarantee of +filesystem state in the face of rapid changes. For the vast majority +of operations, this is never a problem. + +## Contributing + +Any change to behavior (including bugfixes) must come with a test. + +Patches that fail tests or reduce performance will be rejected. + +``` +# to run tests +npm test + +# to re-generate test fixtures +npm run test-regen + +# to benchmark against bash/zsh +npm run bench + +# to profile javascript +npm run prof +``` diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/common.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/common.js new file mode 100644 index 0000000000..e36a631cab --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/common.js @@ -0,0 +1,245 @@ +exports.alphasort = alphasort +exports.alphasorti = alphasorti +exports.setopts = setopts +exports.ownProp = ownProp +exports.makeAbs = makeAbs +exports.finish = finish +exports.mark = mark +exports.isIgnored = isIgnored +exports.childrenIgnored = childrenIgnored + +function ownProp (obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field) +} + +var path = require("path") +var minimatch = require("minimatch") +var isAbsolute = require("path-is-absolute") +var Minimatch = minimatch.Minimatch + +function alphasorti (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()) +} + +function alphasort (a, b) { + return a.localeCompare(b) +} + +function setupIgnores (self, options) { + self.ignore = options.ignore || [] + + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore] + + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap) + } +} + +function ignoreMap (pattern) { + var gmatcher = null + if (pattern.slice(-3) === '/**') { + var gpattern = pattern.replace(/(\/\*\*)+$/, '') + gmatcher = new Minimatch(gpattern) + } + + return { + matcher: new Minimatch(pattern), + gmatcher: gmatcher + } +} + +function setopts (self, pattern, options) { + if (!options) + options = {} + + // base-matching: just use globstar for that. + if (options.matchBase && -1 === pattern.indexOf("/")) { + if (options.noglobstar) { + throw new Error("base matching requires globstar") + } + pattern = "**/" + pattern + } + + self.silent = !!options.silent + self.pattern = pattern + self.strict = options.strict !== false + self.realpath = !!options.realpath + self.realpathCache = options.realpathCache || Object.create(null) + self.follow = !!options.follow + self.dot = !!options.dot + self.mark = !!options.mark + self.nodir = !!options.nodir + if (self.nodir) + self.mark = true + self.sync = !!options.sync + self.nounique = !!options.nounique + self.nonull = !!options.nonull + self.nosort = !!options.nosort + self.nocase = !!options.nocase + self.stat = !!options.stat + self.noprocess = !!options.noprocess + + self.maxLength = options.maxLength || Infinity + self.cache = options.cache || Object.create(null) + self.statCache = options.statCache || Object.create(null) + self.symlinks = options.symlinks || Object.create(null) + + setupIgnores(self, options) + + self.changedCwd = false + var cwd = process.cwd() + if (!ownProp(options, "cwd")) + self.cwd = cwd + else { + self.cwd = options.cwd + self.changedCwd = path.resolve(options.cwd) !== cwd + } + + self.root = options.root || path.resolve(self.cwd, "/") + self.root = path.resolve(self.root) + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/") + + self.nomount = !!options.nomount + + // disable comments and negation unless the user explicitly + // passes in false as the option. + options.nonegate = options.nonegate === false ? false : true + options.nocomment = options.nocomment === false ? false : true + deprecationWarning(options) + + self.minimatch = new Minimatch(pattern, options) + self.options = self.minimatch.options +} + +// TODO(isaacs): remove entirely in v6 +// exported to reset in tests +exports.deprecationWarned +function deprecationWarning(options) { + if (!options.nonegate || !options.nocomment) { + if (process.noDeprecation !== true && !exports.deprecationWarned) { + var msg = 'glob WARNING: comments and negation will be disabled in v6' + if (process.throwDeprecation) + throw new Error(msg) + else if (process.traceDeprecation) + console.trace(msg) + else + console.error(msg) + + exports.deprecationWarned = true + } + } +} + +function finish (self) { + var nou = self.nounique + var all = nou ? [] : Object.create(null) + + for (var i = 0, l = self.matches.length; i < l; i ++) { + var matches = self.matches[i] + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + // do like the shell, and spit out the literal glob + var literal = self.minimatch.globSet[i] + if (nou) + all.push(literal) + else + all[literal] = true + } + } else { + // had matches + var m = Object.keys(matches) + if (nou) + all.push.apply(all, m) + else + m.forEach(function (m) { + all[m] = true + }) + } + } + + if (!nou) + all = Object.keys(all) + + if (!self.nosort) + all = all.sort(self.nocase ? alphasorti : alphasort) + + // at *some* point we statted all of these + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]) + } + if (self.nodir) { + all = all.filter(function (e) { + return !(/\/$/.test(e)) + }) + } + } + + if (self.ignore.length) + all = all.filter(function(m) { + return !isIgnored(self, m) + }) + + self.found = all +} + +function mark (self, p) { + var abs = makeAbs(self, p) + var c = self.cache[abs] + var m = p + if (c) { + var isDir = c === 'DIR' || Array.isArray(c) + var slash = p.slice(-1) === '/' + + if (isDir && !slash) + m += '/' + else if (!isDir && slash) + m = m.slice(0, -1) + + if (m !== p) { + var mabs = makeAbs(self, m) + self.statCache[mabs] = self.statCache[abs] + self.cache[mabs] = self.cache[abs] + } + } + + return m +} + +// lotta situps... +function makeAbs (self, f) { + var abs = f + if (f.charAt(0) === '/') { + abs = path.join(self.root, f) + } else if (isAbsolute(f) || f === '') { + abs = f + } else if (self.changedCwd) { + abs = path.resolve(self.cwd, f) + } else { + abs = path.resolve(f) + } + return abs +} + + +// Return true, if pattern ends with globstar '**', for the accompanying parent directory. +// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents +function isIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) + }) +} + +function childrenIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path)) + }) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/glob.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/glob.js new file mode 100644 index 0000000000..022d2ac8c6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/glob.js @@ -0,0 +1,752 @@ +// Approach: +// +// 1. Get the minimatch set +// 2. For each pattern in the set, PROCESS(pattern, false) +// 3. Store matches per-set, then uniq them +// +// PROCESS(pattern, inGlobStar) +// Get the first [n] items from pattern that are all strings +// Join these together. This is PREFIX. +// If there is no more remaining, then stat(PREFIX) and +// add to matches if it succeeds. END. +// +// If inGlobStar and PREFIX is symlink and points to dir +// set ENTRIES = [] +// else readdir(PREFIX) as ENTRIES +// If fail, END +// +// with ENTRIES +// If pattern[n] is GLOBSTAR +// // handle the case where the globstar match is empty +// // by pruning it out, and testing the resulting pattern +// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) +// // handle other cases. +// for ENTRY in ENTRIES (not dotfiles) +// // attach globstar + tail onto the entry +// // Mark that this entry is a globstar match +// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) +// +// else // not globstar +// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) +// Test ENTRY against pattern[n] +// If fails, continue +// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) +// +// Caveat: +// Cache all stats and readdirs results to minimize syscall. Since all +// we ever care about is existence and directory-ness, we can just keep +// `true` for files, and [children,...] for directories, or `false` for +// things that don't exist. + +module.exports = glob + +var fs = require('fs') +var minimatch = require('minimatch') +var Minimatch = minimatch.Minimatch +var inherits = require('inherits') +var EE = require('events').EventEmitter +var path = require('path') +var assert = require('assert') +var isAbsolute = require('path-is-absolute') +var globSync = require('./sync.js') +var common = require('./common.js') +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var inflight = require('inflight') +var util = require('util') +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored + +var once = require('once') + +function glob (pattern, options, cb) { + if (typeof options === 'function') cb = options, options = {} + if (!options) options = {} + + if (options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return globSync(pattern, options) + } + + return new Glob(pattern, options, cb) +} + +glob.sync = globSync +var GlobSync = glob.GlobSync = globSync.GlobSync + +// old api surface +glob.glob = glob + +glob.hasMagic = function (pattern, options_) { + var options = util._extend({}, options_) + options.noprocess = true + + var g = new Glob(pattern, options) + var set = g.minimatch.set + if (set.length > 1) + return true + + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== 'string') + return true + } + + return false +} + +glob.Glob = Glob +inherits(Glob, EE) +function Glob (pattern, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + + if (options && options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return new GlobSync(pattern, options) + } + + if (!(this instanceof Glob)) + return new Glob(pattern, options, cb) + + setopts(this, pattern, options) + this._didRealPath = false + + // process each pattern in the minimatch set + var n = this.minimatch.set.length + + // The matches are stored as {: true,...} so that + // duplicates are automagically pruned. + // Later, we do an Object.keys() on these. + // Keep them as a list so we can fill in when nonull is set. + this.matches = new Array(n) + + if (typeof cb === 'function') { + cb = once(cb) + this.on('error', cb) + this.on('end', function (matches) { + cb(null, matches) + }) + } + + var self = this + var n = this.minimatch.set.length + this._processing = 0 + this.matches = new Array(n) + + this._emitQueue = [] + this._processQueue = [] + this.paused = false + + if (this.noprocess) + return this + + if (n === 0) + return done() + + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false, done) + } + + function done () { + --self._processing + if (self._processing <= 0) + self._finish() + } +} + +Glob.prototype._finish = function () { + assert(this instanceof Glob) + if (this.aborted) + return + + if (this.realpath && !this._didRealpath) + return this._realpath() + + common.finish(this) + this.emit('end', this.found) +} + +Glob.prototype._realpath = function () { + if (this._didRealpath) + return + + this._didRealpath = true + + var n = this.matches.length + if (n === 0) + return this._finish() + + var self = this + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next) + + function next () { + if (--n === 0) + self._finish() + } +} + +Glob.prototype._realpathSet = function (index, cb) { + var matchset = this.matches[index] + if (!matchset) + return cb() + + var found = Object.keys(matchset) + var self = this + var n = found.length + + if (n === 0) + return cb() + + var set = this.matches[index] = Object.create(null) + found.forEach(function (p, i) { + // If there's a problem with the stat, then it means that + // one or more of the links in the realpath couldn't be + // resolved. just return the abs value in that case. + p = self._makeAbs(p) + fs.realpath(p, self.realpathCache, function (er, real) { + if (!er) + set[real] = true + else if (er.syscall === 'stat') + set[p] = true + else + self.emit('error', er) // srsly wtf right here + + if (--n === 0) { + self.matches[index] = set + cb() + } + }) + }) +} + +Glob.prototype._mark = function (p) { + return common.mark(this, p) +} + +Glob.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} + +Glob.prototype.abort = function () { + this.aborted = true + this.emit('abort') +} + +Glob.prototype.pause = function () { + if (!this.paused) { + this.paused = true + this.emit('pause') + } +} + +Glob.prototype.resume = function () { + if (this.paused) { + this.emit('resume') + this.paused = false + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0) + this._emitQueue.length = 0 + for (var i = 0; i < eq.length; i ++) { + var e = eq[i] + this._emitMatch(e[0], e[1]) + } + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0) + this._processQueue.length = 0 + for (var i = 0; i < pq.length; i ++) { + var p = pq[i] + this._processing-- + this._process(p[0], p[1], p[2], p[3]) + } + } + } +} + +Glob.prototype._process = function (pattern, index, inGlobStar, cb) { + assert(this instanceof Glob) + assert(typeof cb === 'function') + + if (this.aborted) + return + + this._processing++ + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]) + return + } + + //console.error('PROCESS %d', this._processing, pattern) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // see if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index, cb) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip _processing + if (childrenIgnored(this, read)) + return cb() + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) +} + +Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + +Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return cb() + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return cb() + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return cb() + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + this._process([e].concat(remain), index, inGlobStar, cb) + } + cb() +} + +Glob.prototype._emitMatch = function (index, e) { + if (this.aborted) + return + + if (this.matches[index][e]) + return + + if (isIgnored(this, e)) + return + + if (this.paused) { + this._emitQueue.push([index, e]) + return + } + + var abs = this._makeAbs(e) + + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } + + if (this.mark) + e = this._mark(e) + + this.matches[index][e] = true + + var st = this.statCache[abs] + if (st) + this.emit('stat', e, st) + + this.emit('match', e) +} + +Glob.prototype._readdirInGlobStar = function (abs, cb) { + if (this.aborted) + return + + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false, cb) + + var lstatkey = 'lstat\0' + abs + var self = this + var lstatcb = inflight(lstatkey, lstatcb_) + + if (lstatcb) + fs.lstat(abs, lstatcb) + + function lstatcb_ (er, lstat) { + if (er) + return cb() + + var isSym = lstat.isSymbolicLink() + self.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && !lstat.isDirectory()) { + self.cache[abs] = 'FILE' + cb() + } else + self._readdir(abs, false, cb) + } +} + +Glob.prototype._readdir = function (abs, inGlobStar, cb) { + if (this.aborted) + return + + cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) + if (!cb) + return + + //console.error('RD %j %j', +inGlobStar, abs) + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return cb() + + if (Array.isArray(c)) + return cb(null, c) + } + + var self = this + fs.readdir(abs, readdirCb(this, abs, cb)) +} + +function readdirCb (self, abs, cb) { + return function (er, entries) { + if (er) + self._readdirError(abs, er, cb) + else + self._readdirEntries(abs, entries, cb) + } +} + +Glob.prototype._readdirEntries = function (abs, entries, cb) { + if (this.aborted) + return + + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + return cb(null, entries) +} + +Glob.prototype._readdirError = function (f, er, cb) { + if (this.aborted) + return + + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + this.cache[this._makeAbs(f)] = 'FILE' + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) { + this.emit('error', er) + // If the error is handled, then we abort + // if not, we threw out of here + this.abort() + } + if (!this.silent) + console.error('glob error', er) + break + } + + return cb() +} + +Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + + +Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + //console.error('pgs2', prefix, remain[0], entries) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return cb() + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false, cb) + + var isSym = this.symlinks[abs] + var len = entries.length + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return cb() + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true, cb) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true, cb) + } + + cb() +} + +Glob.prototype._processSimple = function (prefix, index, cb) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var self = this + this._stat(prefix, function (er, exists) { + self._processSimple2(prefix, index, er, exists, cb) + }) +} +Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { + + //console.error('ps2', prefix, exists) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return cb() + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this._emitMatch(index, prefix) + cb() +} + +// Returns either 'DIR', 'FILE', or false +Glob.prototype._stat = function (f, cb) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return cb() + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return cb(null, c) + + if (needDir && c === 'FILE') + return cb() + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (stat !== undefined) { + if (stat === false) + return cb(null, stat) + else { + var type = stat.isDirectory() ? 'DIR' : 'FILE' + if (needDir && type === 'FILE') + return cb() + else + return cb(null, type, stat) + } + } + + var self = this + var statcb = inflight('stat\0' + abs, lstatcb_) + if (statcb) + fs.lstat(abs, statcb) + + function lstatcb_ (er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + // If it's a symlink, then treat it as the target, unless + // the target does not exist, then treat it as a file. + return fs.stat(abs, function (er, stat) { + if (er) + self._stat2(f, abs, null, lstat, cb) + else + self._stat2(f, abs, er, stat, cb) + }) + } else { + self._stat2(f, abs, er, lstat, cb) + } + } +} + +Glob.prototype._stat2 = function (f, abs, er, stat, cb) { + if (er) { + this.statCache[abs] = false + return cb() + } + + var needDir = f.slice(-1) === '/' + this.statCache[abs] = stat + + if (abs.slice(-1) === '/' && !stat.isDirectory()) + return cb(null, false, stat) + + var c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c !== 'DIR') + return cb() + + return cb(null, c, stat) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/.eslintrc b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/.eslintrc new file mode 100644 index 0000000000..b7a1550efc --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/.eslintrc @@ -0,0 +1,17 @@ +{ + "env" : { + "node" : true + }, + "rules" : { + "semi": [2, "never"], + "strict": 0, + "quotes": [1, "single", "avoid-escape"], + "no-use-before-define": 0, + "curly": 0, + "no-underscore-dangle": 0, + "no-lonely-if": 1, + "no-unused-vars": [2, {"vars" : "all", "args" : "after-used"}], + "no-mixed-requires": 0, + "space-infix-ops": 0 + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/LICENSE new file mode 100644 index 0000000000..05eeeb88c2 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/README.md new file mode 100644 index 0000000000..6dc8929171 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/README.md @@ -0,0 +1,37 @@ +# inflight + +Add callbacks to requests in flight to avoid async duplication + +## USAGE + +```javascript +var inflight = require('inflight') + +// some request that does some stuff +function req(key, callback) { + // key is any random string. like a url or filename or whatever. + // + // will return either a falsey value, indicating that the + // request for this key is already in flight, or a new callback + // which when called will call all callbacks passed to inflightk + // with the same key + callback = inflight(key, callback) + + // If we got a falsey value back, then there's already a req going + if (!callback) return + + // this is where you'd fetch the url or whatever + // callback is also once()-ified, so it can safely be assigned + // to multiple events etc. First call wins. + setTimeout(function() { + callback(null, key) + }, 100) +} + +// only assigns a single setTimeout +// when it dings, all cbs get called +req('foo', cb1) +req('foo', cb2) +req('foo', cb3) +req('foo', cb4) +``` diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/inflight.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/inflight.js new file mode 100644 index 0000000000..8bc96cbd37 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/inflight.js @@ -0,0 +1,44 @@ +var wrappy = require('wrappy') +var reqs = Object.create(null) +var once = require('once') + +module.exports = wrappy(inflight) + +function inflight (key, cb) { + if (reqs[key]) { + reqs[key].push(cb) + return null + } else { + reqs[key] = [cb] + return makeres(key) + } +} + +function makeres (key) { + return once(function RES () { + var cbs = reqs[key] + var len = cbs.length + var args = slice(arguments) + for (var i = 0; i < len; i++) { + cbs[i].apply(null, args) + } + if (cbs.length > len) { + // added more in the interim. + // de-zalgo, just in case, but don't call again. + cbs.splice(0, len) + process.nextTick(function () { + RES.apply(null, args) + }) + } else { + delete reqs[key] + } + }) +} + +function slice (args) { + var length = args.length + var array = [] + + for (var i = 0; i < length; i++) array[i] = args[i] + return array +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/LICENSE new file mode 100644 index 0000000000..19129e315f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/README.md new file mode 100644 index 0000000000..98eab2522b --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/README.md @@ -0,0 +1,36 @@ +# wrappy + +Callback wrapping utility + +## USAGE + +```javascript +var wrappy = require("wrappy") + +// var wrapper = wrappy(wrapperFunction) + +// make sure a cb is called only once +// See also: http://npm.im/once for this specific use case +var once = wrappy(function (cb) { + var called = false + return function () { + if (called) return + called = true + return cb.apply(this, arguments) + } +}) + +function printBoo () { + console.log('boo') +} +// has some rando property +printBoo.iAmBooPrinter = true + +var onlyPrintOnce = once(printBoo) + +onlyPrintOnce() // prints 'boo' +onlyPrintOnce() // does nothing + +// random property is retained! +assert.equal(onlyPrintOnce.iAmBooPrinter, true) +``` diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/package.json new file mode 100644 index 0000000000..5a07040a47 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/package.json @@ -0,0 +1,52 @@ +{ + "name": "wrappy", + "version": "1.0.1", + "description": "Callback wrapping utility", + "main": "wrappy.js", + "directories": { + "test": "test" + }, + "dependencies": {}, + "devDependencies": { + "tap": "^0.4.12" + }, + "scripts": { + "test": "tap test/*.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/npm/wrappy.git" + }, + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "license": "ISC", + "bugs": { + "url": "https://github.com/npm/wrappy/issues" + }, + "homepage": "https://github.com/npm/wrappy", + "gitHead": "006a8cbac6b99988315834c207896eed71fd069a", + "_id": "wrappy@1.0.1", + "_shasum": "1e65969965ccbc2db4548c6b84a6f2c5aedd4739", + "_from": "wrappy@>=1.0.0 <2.0.0", + "_npmVersion": "2.0.0", + "_nodeVersion": "0.10.31", + "_npmUser": { + "name": "isaacs", + "email": "i@izs.me" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "dist": { + "shasum": "1e65969965ccbc2db4548c6b84a6f2c5aedd4739", + "tarball": "http://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + }, + "_resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/test/basic.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/test/basic.js new file mode 100644 index 0000000000..5ed0fcdfd9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/test/basic.js @@ -0,0 +1,51 @@ +var test = require('tap').test +var wrappy = require('../wrappy.js') + +test('basic', function (t) { + function onceifier (cb) { + var called = false + return function () { + if (called) return + called = true + return cb.apply(this, arguments) + } + } + onceifier.iAmOnce = {} + var once = wrappy(onceifier) + t.equal(once.iAmOnce, onceifier.iAmOnce) + + var called = 0 + function boo () { + t.equal(called, 0) + called++ + } + // has some rando property + boo.iAmBoo = true + + var onlyPrintOnce = once(boo) + + onlyPrintOnce() // prints 'boo' + onlyPrintOnce() // does nothing + t.equal(called, 1) + + // random property is retained! + t.equal(onlyPrintOnce.iAmBoo, true) + + var logs = [] + var logwrap = wrappy(function (msg, cb) { + logs.push(msg + ' wrapping cb') + return function () { + logs.push(msg + ' before cb') + var ret = cb.apply(this, arguments) + logs.push(msg + ' after cb') + } + }) + + var c = logwrap('foo', function () { + t.same(logs, [ 'foo wrapping cb', 'foo before cb' ]) + }) + c() + t.same(logs, [ 'foo wrapping cb', 'foo before cb', 'foo after cb' ]) + + t.end() +}) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/wrappy.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/wrappy.js new file mode 100644 index 0000000000..bb7e7d6fcf --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/node_modules/wrappy/wrappy.js @@ -0,0 +1,33 @@ +// Returns a wrapper function that returns a wrapped callback +// The wrapper function should do some stuff, and return a +// presumably different callback function. +// This makes sure that own properties are retained, so that +// decorations and such are not lost along the way. +module.exports = wrappy +function wrappy (fn, cb) { + if (fn && cb) return wrappy(fn)(cb) + + if (typeof fn !== 'function') + throw new TypeError('need wrapper function') + + Object.keys(fn).forEach(function (k) { + wrapper[k] = fn[k] + }) + + return wrapper + + function wrapper() { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i] + } + var ret = fn.apply(this, args) + var cb = args[args.length-1] + if (typeof ret === 'function' && ret !== cb) { + Object.keys(cb).forEach(function (k) { + ret[k] = cb[k] + }) + } + return ret + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/package.json new file mode 100644 index 0000000000..fd0da2da38 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/package.json @@ -0,0 +1,61 @@ +{ + "name": "inflight", + "version": "1.0.4", + "description": "Add callbacks to requests in flight to avoid async duplication", + "main": "inflight.js", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + }, + "devDependencies": { + "tap": "^0.4.10" + }, + "scripts": { + "test": "tap test.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/isaacs/inflight.git" + }, + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "bugs": { + "url": "https://github.com/isaacs/inflight/issues" + }, + "homepage": "https://github.com/isaacs/inflight", + "license": "ISC", + "gitHead": "c7b5531d572a867064d4a1da9e013e8910b7d1ba", + "_id": "inflight@1.0.4", + "_shasum": "6cbb4521ebd51ce0ec0a936bfd7657ef7e9b172a", + "_from": "inflight@>=1.0.4 <2.0.0", + "_npmVersion": "2.1.3", + "_nodeVersion": "0.10.32", + "_npmUser": { + "name": "othiym23", + "email": "ogd@aoaioxxysz.net" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + }, + { + "name": "othiym23", + "email": "ogd@aoaioxxysz.net" + }, + { + "name": "iarna", + "email": "me@re-becca.org" + } + ], + "dist": { + "shasum": "6cbb4521ebd51ce0ec0a936bfd7657ef7e9b172a", + "tarball": "http://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/test.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/test.js new file mode 100644 index 0000000000..2bb75b3881 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/inflight/test.js @@ -0,0 +1,97 @@ +var test = require('tap').test +var inf = require('./inflight.js') + + +function req (key, cb) { + cb = inf(key, cb) + if (cb) setTimeout(function () { + cb(key) + cb(key) + }) + return cb +} + +test('basic', function (t) { + var calleda = false + var a = req('key', function (k) { + t.notOk(calleda) + calleda = true + t.equal(k, 'key') + if (calledb) t.end() + }) + t.ok(a, 'first returned cb function') + + var calledb = false + var b = req('key', function (k) { + t.notOk(calledb) + calledb = true + t.equal(k, 'key') + if (calleda) t.end() + }) + + t.notOk(b, 'second should get falsey inflight response') +}) + +test('timing', function (t) { + var expect = [ + 'method one', + 'start one', + 'end one', + 'two', + 'tick', + 'three' + ] + var i = 0 + + function log (m) { + t.equal(m, expect[i], m + ' === ' + expect[i]) + ++i + if (i === expect.length) + t.end() + } + + function method (name, cb) { + log('method ' + name) + process.nextTick(cb) + } + + var one = inf('foo', function () { + log('start one') + var three = inf('foo', function () { + log('three') + }) + if (three) method('three', three) + log('end one') + }) + + method('one', one) + + var two = inf('foo', function () { + log('two') + }) + if (two) method('one', two) + + process.nextTick(log.bind(null, 'tick')) +}) + +test('parameters', function (t) { + t.plan(8) + + var a = inf('key', function (first, second, third) { + t.equal(first, 1) + t.equal(second, 2) + t.equal(third, 3) + }) + t.ok(a, 'first returned cb function') + + var b = inf('key', function (first, second, third) { + t.equal(first, 1) + t.equal(second, 2) + t.equal(third, 3) + }) + t.notOk(b, 'second should get falsey inflight response') + + setTimeout(function () { + a(1, 2, 3) + }) +}) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/LICENSE new file mode 100644 index 0000000000..19129e315f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/README.md new file mode 100644 index 0000000000..d458bc2e0a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/README.md @@ -0,0 +1,216 @@ +# minimatch + +A minimal matching utility. + +[![Build Status](https://secure.travis-ci.org/isaacs/minimatch.png)](http://travis-ci.org/isaacs/minimatch) + + +This is the matching library used internally by npm. + +It works by converting glob expressions into JavaScript `RegExp` +objects. + +## Usage + +```javascript +var minimatch = require("minimatch") + +minimatch("bar.foo", "*.foo") // true! +minimatch("bar.foo", "*.bar") // false! +minimatch("bar.foo", "*.+(bar|foo)", { debug: true }) // true, and noisy! +``` + +## Features + +Supports these glob features: + +* Brace Expansion +* Extended glob matching +* "Globstar" `**` matching + +See: + +* `man sh` +* `man bash` +* `man 3 fnmatch` +* `man 5 gitignore` + +## Minimatch Class + +Create a minimatch object by instanting the `minimatch.Minimatch` class. + +```javascript +var Minimatch = require("minimatch").Minimatch +var mm = new Minimatch(pattern, options) +``` + +### Properties + +* `pattern` The original pattern the minimatch object represents. +* `options` The options supplied to the constructor. +* `set` A 2-dimensional array of regexp or string expressions. + Each row in the + array corresponds to a brace-expanded pattern. Each item in the row + corresponds to a single path-part. For example, the pattern + `{a,b/c}/d` would expand to a set of patterns like: + + [ [ a, d ] + , [ b, c, d ] ] + + If a portion of the pattern doesn't have any "magic" in it + (that is, it's something like `"foo"` rather than `fo*o?`), then it + will be left as a string rather than converted to a regular + expression. + +* `regexp` Created by the `makeRe` method. A single regular expression + expressing the entire pattern. This is useful in cases where you wish + to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled. +* `negate` True if the pattern is negated. +* `comment` True if the pattern is a comment. +* `empty` True if the pattern is `""`. + +### Methods + +* `makeRe` Generate the `regexp` member if necessary, and return it. + Will return `false` if the pattern is invalid. +* `match(fname)` Return true if the filename matches the pattern, or + false otherwise. +* `matchOne(fileArray, patternArray, partial)` Take a `/`-split + filename, and match it against a single row in the `regExpSet`. This + method is mainly for internal use, but is exposed so that it can be + used by a glob-walker that needs to avoid excessive filesystem calls. + +All other methods are internal, and will be called as necessary. + +## Functions + +The top-level exported function has a `cache` property, which is an LRU +cache set to store 100 items. So, calling these methods repeatedly +with the same pattern and options will use the same Minimatch object, +saving the cost of parsing it multiple times. + +### minimatch(path, pattern, options) + +Main export. Tests a path against the pattern using the options. + +```javascript +var isJS = minimatch(file, "*.js", { matchBase: true }) +``` + +### minimatch.filter(pattern, options) + +Returns a function that tests its +supplied argument, suitable for use with `Array.filter`. Example: + +```javascript +var javascripts = fileList.filter(minimatch.filter("*.js", {matchBase: true})) +``` + +### minimatch.match(list, pattern, options) + +Match against the list of +files, in the style of fnmatch or glob. If nothing is matched, and +options.nonull is set, then return a list containing the pattern itself. + +```javascript +var javascripts = minimatch.match(fileList, "*.js", {matchBase: true})) +``` + +### minimatch.makeRe(pattern, options) + +Make a regular expression object from the pattern. + +## Options + +All options are `false` by default. + +### debug + +Dump a ton of stuff to stderr. + +### nobrace + +Do not expand `{a,b}` and `{1..3}` brace sets. + +### noglobstar + +Disable `**` matching against multiple folder names. + +### dot + +Allow patterns to match filenames starting with a period, even if +the pattern does not explicitly have a period in that spot. + +Note that by default, `a/**/b` will **not** match `a/.d/b`, unless `dot` +is set. + +### noext + +Disable "extglob" style patterns like `+(a|b)`. + +### nocase + +Perform a case-insensitive match. + +### nonull + +When a match is not found by `minimatch.match`, return a list containing +the pattern itself if this option is set. When not set, an empty list +is returned if there are no matches. + +### matchBase + +If set, then patterns without slashes will be matched +against the basename of the path if it contains slashes. For example, +`a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`. + +### nocomment + +Suppress the behavior of treating `#` at the start of a pattern as a +comment. + +### nonegate + +Suppress the behavior of treating a leading `!` character as negation. + +### flipNegate + +Returns from negate expressions the same as if they were not negated. +(Ie, true on a hit, false on a miss.) + + +## Comparisons to other fnmatch/glob implementations + +While strict compliance with the existing standards is a worthwhile +goal, some discrepancies exist between minimatch and other +implementations, and are intentional. + +If the pattern starts with a `!` character, then it is negated. Set the +`nonegate` flag to suppress this behavior, and treat leading `!` +characters normally. This is perhaps relevant if you wish to start the +pattern with a negative extglob pattern like `!(a|B)`. Multiple `!` +characters at the start of a pattern will negate the pattern multiple +times. + +If a pattern starts with `#`, then it is treated as a comment, and +will not match anything. Use `\#` to match a literal `#` at the +start of a line, or set the `nocomment` flag to suppress this behavior. + +The double-star character `**` is supported by default, unless the +`noglobstar` flag is set. This is supported in the manner of bsdglob +and bash 4.1, where `**` only has special significance if it is the only +thing in a path part. That is, `a/**/b` will match `a/x/y/b`, but +`a/**b` will not. + +If an escaped pattern has no matches, and the `nonull` flag is set, +then minimatch.match returns the pattern as-provided, rather than +interpreting the character escapes. For example, +`minimatch.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than +`"*a?"`. This is akin to setting the `nullglob` option in bash, except +that it does not resolve escaped pattern characters. + +If brace expansion is not disabled, then it is performed before any +other interpretation of the glob pattern. Thus, a pattern like +`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded +**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are +checked for validity. Since those two are valid, matching proceeds. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/minimatch.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/minimatch.js new file mode 100644 index 0000000000..ec4c05c570 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/minimatch.js @@ -0,0 +1,912 @@ +module.exports = minimatch +minimatch.Minimatch = Minimatch + +var path = { sep: '/' } +try { + path = require('path') +} catch (er) {} + +var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} +var expand = require('brace-expansion') + +// any single thing other than / +// don't need to escape / when using new RegExp() +var qmark = '[^/]' + +// * => any number of characters +var star = qmark + '*?' + +// ** when dots are allowed. Anything goes, except .. and . +// not (^ or / followed by one or two dots followed by $ or /), +// followed by anything, any number of times. +var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' + +// not a ^ or / followed by a dot, +// followed by anything, any number of times. +var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' + +// characters that need to be escaped in RegExp. +var reSpecials = charSet('().*{}+?[]^$\\!') + +// "abc" -> { a:true, b:true, c:true } +function charSet (s) { + return s.split('').reduce(function (set, c) { + set[c] = true + return set + }, {}) +} + +// normalizes slashes. +var slashSplit = /\/+/ + +minimatch.filter = filter +function filter (pattern, options) { + options = options || {} + return function (p, i, list) { + return minimatch(p, pattern, options) + } +} + +function ext (a, b) { + a = a || {} + b = b || {} + var t = {} + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) + Object.keys(a).forEach(function (k) { + t[k] = a[k] + }) + return t +} + +minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return minimatch + + var orig = minimatch + + var m = function minimatch (p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)) + } + + m.Minimatch = function Minimatch (pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)) + } + + return m +} + +Minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return Minimatch + return minimatch.defaults(def).Minimatch +} + +function minimatch (p, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + + // shortcut: comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + return false + } + + // "" only matches "" + if (pattern.trim() === '') return p === '' + + return new Minimatch(pattern, options).match(p) +} + +function Minimatch (pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options) + } + + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + pattern = pattern.trim() + + // windows support: need to use /, not \ + if (path.sep !== '/') { + pattern = pattern.split(path.sep).join('/') + } + + this.options = options + this.set = [] + this.pattern = pattern + this.regexp = null + this.negate = false + this.comment = false + this.empty = false + + // make the set of regexps etc. + this.make() +} + +Minimatch.prototype.debug = function () {} + +Minimatch.prototype.make = make +function make () { + // don't do it more than once. + if (this._made) return + + var pattern = this.pattern + var options = this.options + + // empty patterns and comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + this.comment = true + return + } + if (!pattern) { + this.empty = true + return + } + + // step 1: figure out negation, etc. + this.parseNegate() + + // step 2: expand braces + var set = this.globSet = this.braceExpand() + + if (options.debug) this.debug = console.error + + this.debug(this.pattern, set) + + // step 3: now we have a set, so turn each one into a series of path-portion + // matching patterns. + // These will be regexps, except in the case of "**", which is + // set to the GLOBSTAR object for globstar behavior, + // and will not contain any / characters + set = this.globParts = set.map(function (s) { + return s.split(slashSplit) + }) + + this.debug(this.pattern, set) + + // glob --> regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + + this.debug(this.pattern, set) + + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) + + this.debug(this.pattern, set) + + this.set = set +} + +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + + if (options.nonegate) return + + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } + + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} + +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} + +Minimatch.prototype.braceExpand = braceExpand + +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } + + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new Error('undefined pattern') + } + + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] + } + + return expand(pattern) +} + +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + var options = this.options + + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' + + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var plType + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this + + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break + } + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false + } + } + + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue + } + + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false + + case '\\': + clearStateChar() + escaping = true + continue + + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } + + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue + + case '(': + if (inClass) { + re += '(' + continue + } + + if (!stateChar) { + re += '\\(' + continue + } + + plType = stateChar + patternListStack.push({ + type: plType, + start: i - 1, + reStart: re.length + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + re += ')' + var pl = patternListStack.pop() + plType = pl.type + // negation is (?:(?!js)[^/]*) + // The others are (?:) + switch (plType) { + case '!': + negativeLists.push(pl) + re += ')[^/]*?)' + pl.reEnd = re.length + break + case '?': + case '+': + case '*': + re += plType + break + case '@': break // the default anyway + } + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue + + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() + + if (inClass) { + re += '\\' + c + continue + } + + inClass = true + classStart = i + reClassStart = re.length + re += c + continue + + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } + + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } + + // finish up the class. + hasMagic = true + inClass = false + re += c + continue + + default: + // swallow any state char that wasn't consumed + clearStateChar() + + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } + + re += c + + } // switch + } // for + + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] + } + + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + 3) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2})*)(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } + + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail + } + + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' + } + + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true + } + + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe + } + + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re + } + + if (addPatternStart) { + re = patternStart + re + } + + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } + + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } + + var flags = options.nocase ? 'i' : '' + var regExp = new RegExp('^' + re + '$', flags) + + regExp._glob = pattern + regExp._src = re + + return regExp +} + +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} + +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set + + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options + + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' + + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') + + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' + + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' + + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} + +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} + +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' + + if (f === '/' && partial) return true + + var options = this.options + + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } + + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) + + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. + + var set = this.set + this.debug(this.pattern, 'set', set) + + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } + + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate + } + } + + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate +} + +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options + + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) + + this.debug('matchOne', file.length, pattern.length) + + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] + + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false + } + return true + } + + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] + + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } + + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ + } + } + + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true + } + return false + } + + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) + } + + if (!hit) return false + } + + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* + + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd + } + + // should be unreachable. + throw new Error('wtf?') +} + +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') +} + +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/.npmignore b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/.npmignore new file mode 100644 index 0000000000..353546af23 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/.npmignore @@ -0,0 +1,3 @@ +test +.gitignore +.travis.yml diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/README.md new file mode 100644 index 0000000000..179392978d --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/README.md @@ -0,0 +1,122 @@ +# brace-expansion + +[Brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html), +as known from sh/bash, in JavaScript. + +[![build status](https://secure.travis-ci.org/juliangruber/brace-expansion.svg)](http://travis-ci.org/juliangruber/brace-expansion) +[![downloads](https://img.shields.io/npm/dm/brace-expansion.svg)](https://www.npmjs.org/package/brace-expansion) + +[![testling badge](https://ci.testling.com/juliangruber/brace-expansion.png)](https://ci.testling.com/juliangruber/brace-expansion) + +## Example + +```js +var expand = require('brace-expansion'); + +expand('file-{a,b,c}.jpg') +// => ['file-a.jpg', 'file-b.jpg', 'file-c.jpg'] + +expand('-v{,,}') +// => ['-v', '-v', '-v'] + +expand('file{0..2}.jpg') +// => ['file0.jpg', 'file1.jpg', 'file2.jpg'] + +expand('file-{a..c}.jpg') +// => ['file-a.jpg', 'file-b.jpg', 'file-c.jpg'] + +expand('file{2..0}.jpg') +// => ['file2.jpg', 'file1.jpg', 'file0.jpg'] + +expand('file{0..4..2}.jpg') +// => ['file0.jpg', 'file2.jpg', 'file4.jpg'] + +expand('file-{a..e..2}.jpg') +// => ['file-a.jpg', 'file-c.jpg', 'file-e.jpg'] + +expand('file{00..10..5}.jpg') +// => ['file00.jpg', 'file05.jpg', 'file10.jpg'] + +expand('{{A..C},{a..c}}') +// => ['A', 'B', 'C', 'a', 'b', 'c'] + +expand('ppp{,config,oe{,conf}}') +// => ['ppp', 'pppconfig', 'pppoe', 'pppoeconf'] +``` + +## API + +```js +var expand = require('brace-expansion'); +``` + +### var expanded = expand(str) + +Return an array of all possible and valid expansions of `str`. If none are +found, `[str]` is returned. + +Valid expansions are: + +```js +/^(.*,)+(.+)?$/ +// {a,b,...} +``` + +A comma seperated list of options, like `{a,b}` or `{a,{b,c}}` or `{,a,}`. + +```js +/^-?\d+\.\.-?\d+(\.\.-?\d+)?$/ +// {x..y[..incr]} +``` + +A numeric sequence from `x` to `y` inclusive, with optional increment. +If `x` or `y` start with a leading `0`, all the numbers will be padded +to have equal length. Negative numbers and backwards iteration work too. + +```js +/^-?\d+\.\.-?\d+(\.\.-?\d+)?$/ +// {x..y[..incr]} +``` + +An alphabetic sequence from `x` to `y` inclusive, with optional increment. +`x` and `y` must be exactly one character, and if given, `incr` must be a +number. + +For compatibility reasons, the string `${` is not eligible for brace expansion. + +## Installation + +With [npm](https://npmjs.org) do: + +```bash +npm install brace-expansion +``` + +## Contributors + +- [Julian Gruber](https://github.com/juliangruber) +- [Isaac Z. Schlueter](https://github.com/isaacs) + +## License + +(MIT) + +Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/example.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/example.js new file mode 100644 index 0000000000..60ecfc74d4 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/example.js @@ -0,0 +1,8 @@ +var expand = require('./'); + +console.log(expand('http://any.org/archive{1996..1999}/vol{1..4}/part{a,b,c}.html')); +console.log(expand('http://www.numericals.com/file{1..100..10}.txt')); +console.log(expand('http://www.letters.com/file{a..z..2}.txt')); +console.log(expand('mkdir /usr/local/src/bash/{old,new,dist,bugs}')); +console.log(expand('chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}')); + diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/index.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/index.js new file mode 100644 index 0000000000..a23104e955 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/index.js @@ -0,0 +1,191 @@ +var concatMap = require('concat-map'); +var balanced = require('balanced-match'); + +module.exports = expandTop; + +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +var escClose = '\0CLOSE'+Math.random()+'\0'; +var escComma = '\0COMMA'+Math.random()+'\0'; +var escPeriod = '\0PERIOD'+Math.random()+'\0'; + +function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) + : str.charCodeAt(0); +} + +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} + +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} + + +// Basically just str.split(","), but handling cases +// where we have nested braced sections, which should be +// treated as individual members, like {a,{b,c},d} +function parseCommaParts(str) { + if (!str) + return ['']; + + var parts = []; + var m = balanced('{', '}', str); + + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length-1] += postParts.shift(); + p.push.apply(p, postParts); + } + + parts.push.apply(parts, p); + + return parts; +} + +function expandTop(str) { + if (!str) + return []; + + return expand(escapeBraces(str), true).map(unescapeBraces); +} + +function identity(e) { + return e; +} + +function embrace(str) { + return '{' + str + '}'; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} + +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} + +function expand(str, isTop) { + var expansions = []; + + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = /^(.*,)+(.+)?$/.test(m.body); + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,.*}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str); + } + return [str]; + } + + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } + } + + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. + + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.abs(numeric(n[2])) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { return expand(el, false) }); + } + + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } + } + + return expansions; +} + diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/.npmignore b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/.npmignore new file mode 100644 index 0000000000..fd4f2b066b --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/.npmignore @@ -0,0 +1,2 @@ +node_modules +.DS_Store diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/.travis.yml b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/.travis.yml new file mode 100644 index 0000000000..cc4dba29d9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.8" + - "0.10" diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/Makefile b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/Makefile new file mode 100644 index 0000000000..fa5da71a6d --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/Makefile @@ -0,0 +1,6 @@ + +test: + @node_modules/.bin/tape test/*.js + +.PHONY: test + diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/README.md new file mode 100644 index 0000000000..2aff0ebff4 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/README.md @@ -0,0 +1,80 @@ +# balanced-match + +Match balanced string pairs, like `{` and `}` or `` and ``. + +[![build status](https://secure.travis-ci.org/juliangruber/balanced-match.svg)](http://travis-ci.org/juliangruber/balanced-match) +[![downloads](https://img.shields.io/npm/dm/balanced-match.svg)](https://www.npmjs.org/package/balanced-match) + +[![testling badge](https://ci.testling.com/juliangruber/balanced-match.png)](https://ci.testling.com/juliangruber/balanced-match) + +## Example + +Get the first matching pair of braces: + +```js +var balanced = require('balanced-match'); + +console.log(balanced('{', '}', 'pre{in{nested}}post')); +console.log(balanced('{', '}', 'pre{first}between{second}post')); +``` + +The matches are: + +```bash +$ node example.js +{ start: 3, end: 14, pre: 'pre', body: 'in{nested}', post: 'post' } +{ start: 3, + end: 9, + pre: 'pre', + body: 'first', + post: 'between{second}post' } +``` + +## API + +### var m = balanced(a, b, str) + +For the first non-nested matching pair of `a` and `b` in `str`, return an +object with those keys: + +* **start** the index of the first match of `a` +* **end** the index of the matching `b` +* **pre** the preamble, `a` and `b` not included +* **body** the match, `a` and `b` not included +* **post** the postscript, `a` and `b` not included + +If there's no match, `undefined` will be returned. + +If the `str` contains more `a` than `b` / there are unmatched pairs, the first match that was closed will be used. For example, `{{a}` will match `['{', 'a', '']`. + +## Installation + +With [npm](https://npmjs.org) do: + +```bash +npm install balanced-match +``` + +## License + +(MIT) + +Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/example.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/example.js new file mode 100644 index 0000000000..c02ad348e6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/example.js @@ -0,0 +1,5 @@ +var balanced = require('./'); + +console.log(balanced('{', '}', 'pre{in{nested}}post')); +console.log(balanced('{', '}', 'pre{first}between{second}post')); + diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/index.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/index.js new file mode 100644 index 0000000000..d165ae8174 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/index.js @@ -0,0 +1,38 @@ +module.exports = balanced; +function balanced(a, b, str) { + var bal = 0; + var m = {}; + var ended = false; + + for (var i = 0; i < str.length; i++) { + if (a == str.substr(i, a.length)) { + if (!('start' in m)) m.start = i; + bal++; + } + else if (b == str.substr(i, b.length) && 'start' in m) { + ended = true; + bal--; + if (!bal) { + m.end = i; + m.pre = str.substr(0, m.start); + m.body = (m.end - m.start > 1) + ? str.substring(m.start + a.length, m.end) + : ''; + m.post = str.slice(m.end + b.length); + return m; + } + } + } + + // if we opened more than we closed, find the one we closed + if (bal && ended) { + var start = m.start + a.length; + m = balanced(a, b, str.substr(start)); + if (m) { + m.start += start; + m.end += start; + m.pre = str.slice(0, start) + m.pre; + } + return m; + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/package.json new file mode 100644 index 0000000000..ede6efefa0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/package.json @@ -0,0 +1,73 @@ +{ + "name": "balanced-match", + "description": "Match balanced character pairs, like \"{\" and \"}\"", + "version": "0.2.0", + "repository": { + "type": "git", + "url": "git://github.com/juliangruber/balanced-match.git" + }, + "homepage": "https://github.com/juliangruber/balanced-match", + "main": "index.js", + "scripts": { + "test": "make test" + }, + "dependencies": {}, + "devDependencies": { + "tape": "~1.1.1" + }, + "keywords": [ + "match", + "regexp", + "test", + "balanced", + "parse" + ], + "author": { + "name": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com" + }, + "license": "MIT", + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/8..latest", + "firefox/20..latest", + "firefox/nightly", + "chrome/25..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + }, + "gitHead": "ba40ed78e7114a4a67c51da768a100184dead39c", + "bugs": { + "url": "https://github.com/juliangruber/balanced-match/issues" + }, + "_id": "balanced-match@0.2.0", + "_shasum": "38f6730c03aab6d5edbb52bd934885e756d71674", + "_from": "balanced-match@>=0.2.0 <0.3.0", + "_npmVersion": "2.1.8", + "_nodeVersion": "0.10.32", + "_npmUser": { + "name": "juliangruber", + "email": "julian@juliangruber.com" + }, + "maintainers": [ + { + "name": "juliangruber", + "email": "julian@juliangruber.com" + } + ], + "dist": { + "shasum": "38f6730c03aab6d5edbb52bd934885e756d71674", + "tarball": "http://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/test/balanced.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/test/balanced.js new file mode 100644 index 0000000000..36bfd39954 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match/test/balanced.js @@ -0,0 +1,56 @@ +var test = require('tape'); +var balanced = require('..'); + +test('balanced', function(t) { + t.deepEqual(balanced('{', '}', 'pre{in{nest}}post'), { + start: 3, + end: 12, + pre: 'pre', + body: 'in{nest}', + post: 'post' + }); + t.deepEqual(balanced('{', '}', '{{{{{{{{{in}post'), { + start: 8, + end: 11, + pre: '{{{{{{{{', + body: 'in', + post: 'post' + }); + t.deepEqual(balanced('{', '}', 'pre{body{in}post'), { + start: 8, + end: 11, + pre: 'pre{body', + body: 'in', + post: 'post' + }); + t.deepEqual(balanced('{', '}', 'pre}{in{nest}}post'), { + start: 4, + end: 13, + pre: 'pre}', + body: 'in{nest}', + post: 'post' + }); + t.deepEqual(balanced('{', '}', 'pre{body}between{body2}post'), { + start: 3, + end: 8, + pre: 'pre', + body: 'body', + post: 'between{body2}post' + }); + t.notOk(balanced('{', '}', 'nope'), 'should be notOk'); + t.deepEqual(balanced('', '', 'preinnestpost'), { + start: 3, + end: 19, + pre: 'pre', + body: 'innest', + post: 'post' + }); + t.deepEqual(balanced('', '', 'preinnestpost'), { + start: 7, + end: 23, + pre: 'pre', + body: 'innest', + post: 'post' + }); + t.end(); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/.travis.yml b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/.travis.yml new file mode 100644 index 0000000000..f1d0f13c8a --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.4 + - 0.6 diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/LICENSE new file mode 100644 index 0000000000..ee27ba4b44 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/LICENSE @@ -0,0 +1,18 @@ +This software is released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/README.markdown b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/README.markdown new file mode 100644 index 0000000000..408f70a1be --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/README.markdown @@ -0,0 +1,62 @@ +concat-map +========== + +Concatenative mapdashery. + +[![browser support](http://ci.testling.com/substack/node-concat-map.png)](http://ci.testling.com/substack/node-concat-map) + +[![build status](https://secure.travis-ci.org/substack/node-concat-map.png)](http://travis-ci.org/substack/node-concat-map) + +example +======= + +``` js +var concatMap = require('concat-map'); +var xs = [ 1, 2, 3, 4, 5, 6 ]; +var ys = concatMap(xs, function (x) { + return x % 2 ? [ x - 0.1, x, x + 0.1 ] : []; +}); +console.dir(ys); +``` + +*** + +``` +[ 0.9, 1, 1.1, 2.9, 3, 3.1, 4.9, 5, 5.1 ] +``` + +methods +======= + +``` js +var concatMap = require('concat-map') +``` + +concatMap(xs, fn) +----------------- + +Return an array of concatenated elements by calling `fn(x, i)` for each element +`x` and each index `i` in the array `xs`. + +When `fn(x, i)` returns an array, its result will be concatenated with the +result array. If `fn(x, i)` returns anything else, that value will be pushed +onto the end of the result array. + +install +======= + +With [npm](http://npmjs.org) do: + +``` +npm install concat-map +``` + +license +======= + +MIT + +notes +===== + +This module was written while sitting high above the ground in a tree. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/example/map.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/example/map.js new file mode 100644 index 0000000000..33656217b6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/example/map.js @@ -0,0 +1,6 @@ +var concatMap = require('../'); +var xs = [ 1, 2, 3, 4, 5, 6 ]; +var ys = concatMap(xs, function (x) { + return x % 2 ? [ x - 0.1, x, x + 0.1 ] : []; +}); +console.dir(ys); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/index.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/index.js new file mode 100644 index 0000000000..b29a7812e5 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/index.js @@ -0,0 +1,13 @@ +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); + } + return res; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/package.json new file mode 100644 index 0000000000..b516138098 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/package.json @@ -0,0 +1,83 @@ +{ + "name": "concat-map", + "description": "concatenative mapdashery", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "git://github.com/substack/node-concat-map.git" + }, + "main": "index.js", + "keywords": [ + "concat", + "concatMap", + "map", + "functional", + "higher-order" + ], + "directories": { + "example": "example", + "test": "test" + }, + "scripts": { + "test": "tape test/*.js" + }, + "devDependencies": { + "tape": "~2.4.0" + }, + "license": "MIT", + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "testling": { + "files": "test/*.js", + "browsers": { + "ie": [ + 6, + 7, + 8, + 9 + ], + "ff": [ + 3.5, + 10, + 15 + ], + "chrome": [ + 10, + 22 + ], + "safari": [ + 5.1 + ], + "opera": [ + 12 + ] + } + }, + "bugs": { + "url": "https://github.com/substack/node-concat-map/issues" + }, + "homepage": "https://github.com/substack/node-concat-map", + "_id": "concat-map@0.0.1", + "dist": { + "shasum": "d8a96bd77fd68df7793a73036a3ba0d5405d477b", + "tarball": "http://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + }, + "_from": "concat-map@0.0.1", + "_npmVersion": "1.3.21", + "_npmUser": { + "name": "substack", + "email": "mail@substack.net" + }, + "maintainers": [ + { + "name": "substack", + "email": "mail@substack.net" + } + ], + "_shasum": "d8a96bd77fd68df7793a73036a3ba0d5405d477b", + "_resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/test/map.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/test/map.js new file mode 100644 index 0000000000..fdbd7022f6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/concat-map/test/map.js @@ -0,0 +1,39 @@ +var concatMap = require('../'); +var test = require('tape'); + +test('empty or not', function (t) { + var xs = [ 1, 2, 3, 4, 5, 6 ]; + var ixes = []; + var ys = concatMap(xs, function (x, ix) { + ixes.push(ix); + return x % 2 ? [ x - 0.1, x, x + 0.1 ] : []; + }); + t.same(ys, [ 0.9, 1, 1.1, 2.9, 3, 3.1, 4.9, 5, 5.1 ]); + t.same(ixes, [ 0, 1, 2, 3, 4, 5 ]); + t.end(); +}); + +test('always something', function (t) { + var xs = [ 'a', 'b', 'c', 'd' ]; + var ys = concatMap(xs, function (x) { + return x === 'b' ? [ 'B', 'B', 'B' ] : [ x ]; + }); + t.same(ys, [ 'a', 'B', 'B', 'B', 'c', 'd' ]); + t.end(); +}); + +test('scalars', function (t) { + var xs = [ 'a', 'b', 'c', 'd' ]; + var ys = concatMap(xs, function (x) { + return x === 'b' ? [ 'B', 'B', 'B' ] : x; + }); + t.same(ys, [ 'a', 'B', 'B', 'B', 'c', 'd' ]); + t.end(); +}); + +test('undefs', function (t) { + var xs = [ 'a', 'b', 'c', 'd' ]; + var ys = concatMap(xs, function () {}); + t.same(ys, [ undefined, undefined, undefined, undefined ]); + t.end(); +}); diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/package.json new file mode 100644 index 0000000000..4cb3e05d7c --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/package.json @@ -0,0 +1,75 @@ +{ + "name": "brace-expansion", + "description": "Brace expansion as known from sh/bash", + "version": "1.1.1", + "repository": { + "type": "git", + "url": "git://github.com/juliangruber/brace-expansion.git" + }, + "homepage": "https://github.com/juliangruber/brace-expansion", + "main": "index.js", + "scripts": { + "test": "tape test/*.js", + "gentest": "bash test/generate.sh" + }, + "dependencies": { + "balanced-match": "^0.2.0", + "concat-map": "0.0.1" + }, + "devDependencies": { + "tape": "^3.0.3" + }, + "keywords": [], + "author": { + "name": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com" + }, + "license": "MIT", + "testling": { + "files": "test/*.js", + "browsers": [ + "ie/8..latest", + "firefox/20..latest", + "firefox/nightly", + "chrome/25..latest", + "chrome/canary", + "opera/12..latest", + "opera/next", + "safari/5.1..latest", + "ipad/6.0..latest", + "iphone/6.0..latest", + "android-browser/4.2..latest" + ] + }, + "gitHead": "f50da498166d76ea570cf3b30179f01f0f119612", + "bugs": { + "url": "https://github.com/juliangruber/brace-expansion/issues" + }, + "_id": "brace-expansion@1.1.1", + "_shasum": "da5fb78aef4c44c9e4acf525064fb3208ebab045", + "_from": "brace-expansion@>=1.0.0 <2.0.0", + "_npmVersion": "2.6.1", + "_nodeVersion": "0.10.36", + "_npmUser": { + "name": "juliangruber", + "email": "julian@juliangruber.com" + }, + "maintainers": [ + { + "name": "juliangruber", + "email": "julian@juliangruber.com" + }, + { + "name": "isaacs", + "email": "isaacs@npmjs.com" + } + ], + "dist": { + "shasum": "da5fb78aef4c44c9e4acf525064fb3208ebab045", + "tarball": "http://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/package.json new file mode 100644 index 0000000000..4944eb0bcc --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/minimatch/package.json @@ -0,0 +1,60 @@ +{ + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me" + }, + "name": "minimatch", + "description": "a glob matcher in javascript", + "version": "3.0.0", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/minimatch.git" + }, + "main": "minimatch.js", + "scripts": { + "posttest": "standard minimatch.js test/*.js", + "test": "tap test/*.js" + }, + "engines": { + "node": "*" + }, + "dependencies": { + "brace-expansion": "^1.0.0" + }, + "devDependencies": { + "standard": "^3.7.2", + "tap": "^1.2.0" + }, + "license": "ISC", + "files": [ + "minimatch.js" + ], + "gitHead": "270dbea567f0af6918cb18103e98c612aa717a20", + "bugs": { + "url": "https://github.com/isaacs/minimatch/issues" + }, + "homepage": "https://github.com/isaacs/minimatch#readme", + "_id": "minimatch@3.0.0", + "_shasum": "5236157a51e4f004c177fb3c527ff7dd78f0ef83", + "_from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0", + "_npmVersion": "3.3.2", + "_nodeVersion": "4.0.0", + "_npmUser": { + "name": "isaacs", + "email": "isaacs@npmjs.com" + }, + "dist": { + "shasum": "5236157a51e4f004c177fb3c527ff7dd78f0ef83", + "tarball": "http://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "directories": {}, + "_resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/LICENSE new file mode 100644 index 0000000000..19129e315f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/README.md new file mode 100644 index 0000000000..a2981ea070 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/README.md @@ -0,0 +1,51 @@ +# once + +Only call a function once. + +## usage + +```javascript +var once = require('once') + +function load (file, cb) { + cb = once(cb) + loader.load('file') + loader.once('load', cb) + loader.once('error', cb) +} +``` + +Or add to the Function.prototype in a responsible way: + +```javascript +// only has to be done once +require('once').proto() + +function load (file, cb) { + cb = cb.once() + loader.load('file') + loader.once('load', cb) + loader.once('error', cb) +} +``` + +Ironically, the prototype feature makes this module twice as +complicated as necessary. + +To check whether you function has been called, use `fn.called`. Once the +function is called for the first time the return value of the original +function is saved in `fn.value` and subsequent calls will continue to +return this value. + +```javascript +var once = require('once') + +function load (cb) { + cb = once(cb) + var stream = createStream() + stream.once('data', cb) + stream.once('end', function () { + if (!cb.called) cb(new Error('not found')) + }) +} +``` diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/LICENSE new file mode 100644 index 0000000000..19129e315f --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/README.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/README.md new file mode 100644 index 0000000000..98eab2522b --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/README.md @@ -0,0 +1,36 @@ +# wrappy + +Callback wrapping utility + +## USAGE + +```javascript +var wrappy = require("wrappy") + +// var wrapper = wrappy(wrapperFunction) + +// make sure a cb is called only once +// See also: http://npm.im/once for this specific use case +var once = wrappy(function (cb) { + var called = false + return function () { + if (called) return + called = true + return cb.apply(this, arguments) + } +}) + +function printBoo () { + console.log('boo') +} +// has some rando property +printBoo.iAmBooPrinter = true + +var onlyPrintOnce = once(printBoo) + +onlyPrintOnce() // prints 'boo' +onlyPrintOnce() // does nothing + +// random property is retained! +assert.equal(onlyPrintOnce.iAmBooPrinter, true) +``` diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/package.json new file mode 100644 index 0000000000..5a07040a47 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/package.json @@ -0,0 +1,52 @@ +{ + "name": "wrappy", + "version": "1.0.1", + "description": "Callback wrapping utility", + "main": "wrappy.js", + "directories": { + "test": "test" + }, + "dependencies": {}, + "devDependencies": { + "tap": "^0.4.12" + }, + "scripts": { + "test": "tap test/*.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/npm/wrappy.git" + }, + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "license": "ISC", + "bugs": { + "url": "https://github.com/npm/wrappy/issues" + }, + "homepage": "https://github.com/npm/wrappy", + "gitHead": "006a8cbac6b99988315834c207896eed71fd069a", + "_id": "wrappy@1.0.1", + "_shasum": "1e65969965ccbc2db4548c6b84a6f2c5aedd4739", + "_from": "wrappy@>=1.0.0 <2.0.0", + "_npmVersion": "2.0.0", + "_nodeVersion": "0.10.31", + "_npmUser": { + "name": "isaacs", + "email": "i@izs.me" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "dist": { + "shasum": "1e65969965ccbc2db4548c6b84a6f2c5aedd4739", + "tarball": "http://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + }, + "_resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/test/basic.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/test/basic.js new file mode 100644 index 0000000000..5ed0fcdfd9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/test/basic.js @@ -0,0 +1,51 @@ +var test = require('tap').test +var wrappy = require('../wrappy.js') + +test('basic', function (t) { + function onceifier (cb) { + var called = false + return function () { + if (called) return + called = true + return cb.apply(this, arguments) + } + } + onceifier.iAmOnce = {} + var once = wrappy(onceifier) + t.equal(once.iAmOnce, onceifier.iAmOnce) + + var called = 0 + function boo () { + t.equal(called, 0) + called++ + } + // has some rando property + boo.iAmBoo = true + + var onlyPrintOnce = once(boo) + + onlyPrintOnce() // prints 'boo' + onlyPrintOnce() // does nothing + t.equal(called, 1) + + // random property is retained! + t.equal(onlyPrintOnce.iAmBoo, true) + + var logs = [] + var logwrap = wrappy(function (msg, cb) { + logs.push(msg + ' wrapping cb') + return function () { + logs.push(msg + ' before cb') + var ret = cb.apply(this, arguments) + logs.push(msg + ' after cb') + } + }) + + var c = logwrap('foo', function () { + t.same(logs, [ 'foo wrapping cb', 'foo before cb' ]) + }) + c() + t.same(logs, [ 'foo wrapping cb', 'foo before cb', 'foo after cb' ]) + + t.end() +}) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/wrappy.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/wrappy.js new file mode 100644 index 0000000000..bb7e7d6fcf --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/node_modules/wrappy/wrappy.js @@ -0,0 +1,33 @@ +// Returns a wrapper function that returns a wrapped callback +// The wrapper function should do some stuff, and return a +// presumably different callback function. +// This makes sure that own properties are retained, so that +// decorations and such are not lost along the way. +module.exports = wrappy +function wrappy (fn, cb) { + if (fn && cb) return wrappy(fn)(cb) + + if (typeof fn !== 'function') + throw new TypeError('need wrapper function') + + Object.keys(fn).forEach(function (k) { + wrapper[k] = fn[k] + }) + + return wrapper + + function wrapper() { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i] + } + var ret = fn.apply(this, args) + var cb = args[args.length-1] + if (typeof ret === 'function' && ret !== cb) { + Object.keys(cb).forEach(function (k) { + ret[k] = cb[k] + }) + } + return ret + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/once.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/once.js new file mode 100644 index 0000000000..2e1e721bfe --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/once.js @@ -0,0 +1,21 @@ +var wrappy = require('wrappy') +module.exports = wrappy(once) + +once.proto = once(function () { + Object.defineProperty(Function.prototype, 'once', { + value: function () { + return once(this) + }, + configurable: true + }) +}) + +function once (fn) { + var f = function () { + if (f.called) return f.value + f.called = true + return f.value = fn.apply(this, arguments) + } + f.called = false + return f +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/package.json new file mode 100644 index 0000000000..8f46e507c4 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/package.json @@ -0,0 +1,60 @@ +{ + "name": "once", + "version": "1.3.2", + "description": "Run a function exactly one time", + "main": "once.js", + "directories": { + "test": "test" + }, + "dependencies": { + "wrappy": "1" + }, + "devDependencies": { + "tap": "~0.3.0" + }, + "scripts": { + "test": "tap test/*.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/isaacs/once.git" + }, + "keywords": [ + "once", + "function", + "one", + "single" + ], + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "license": "ISC", + "gitHead": "e35eed5a7867574e2bf2260a1ba23970958b22f2", + "bugs": { + "url": "https://github.com/isaacs/once/issues" + }, + "homepage": "https://github.com/isaacs/once#readme", + "_id": "once@1.3.2", + "_shasum": "d8feeca93b039ec1dcdee7741c92bdac5e28081b", + "_from": "once@>=1.3.0 <2.0.0", + "_npmVersion": "2.9.1", + "_nodeVersion": "2.0.0", + "_npmUser": { + "name": "isaacs", + "email": "isaacs@npmjs.com" + }, + "dist": { + "shasum": "d8feeca93b039ec1dcdee7741c92bdac5e28081b", + "tarball": "http://registry.npmjs.org/once/-/once-1.3.2.tgz" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "_resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/test/once.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/test/once.js new file mode 100644 index 0000000000..c618360dfa --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/once/test/once.js @@ -0,0 +1,23 @@ +var test = require('tap').test +var once = require('../once.js') + +test('once', function (t) { + var f = 0 + function fn (g) { + t.equal(f, 0) + f ++ + return f + g + this + } + fn.ownProperty = {} + var foo = once(fn) + t.equal(fn.ownProperty, foo.ownProperty) + t.notOk(foo.called) + for (var i = 0; i < 1E3; i++) { + t.same(f, i === 0 ? 0 : 1) + var g = foo.call(1, 1) + t.ok(foo.called) + t.same(g, 3) + t.same(f, 1) + } + t.end() +}) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/index.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/index.js new file mode 100644 index 0000000000..19f103f908 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/index.js @@ -0,0 +1,20 @@ +'use strict'; + +function posix(path) { + return path.charAt(0) === '/'; +}; + +function win32(path) { + // https://github.com/joyent/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 + var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + var result = splitDeviceRe.exec(path); + var device = result[1] || ''; + var isUnc = !!device && device.charAt(1) !== ':'; + + // UNC paths are always absolute + return !!result[2] || isUnc; +}; + +module.exports = process.platform === 'win32' ? win32 : posix; +module.exports.posix = posix; +module.exports.win32 = win32; diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/license b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/license new file mode 100644 index 0000000000..654d0bfe94 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/package.json new file mode 100644 index 0000000000..39372636f3 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/package.json @@ -0,0 +1,70 @@ +{ + "name": "path-is-absolute", + "version": "1.0.0", + "description": "Node.js 0.12 path.isAbsolute() ponyfill", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/sindresorhus/path-is-absolute.git" + }, + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "node test.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "path", + "paths", + "file", + "dir", + "absolute", + "isabsolute", + "is-absolute", + "built-in", + "util", + "utils", + "core", + "ponyfill", + "polyfill", + "shim", + "is", + "detect", + "check" + ], + "gitHead": "7a76a0c9f2263192beedbe0a820e4d0baee5b7a1", + "bugs": { + "url": "https://github.com/sindresorhus/path-is-absolute/issues" + }, + "homepage": "https://github.com/sindresorhus/path-is-absolute", + "_id": "path-is-absolute@1.0.0", + "_shasum": "263dada66ab3f2fb10bf7f9d24dd8f3e570ef912", + "_from": "path-is-absolute@>=1.0.0 <2.0.0", + "_npmVersion": "2.5.1", + "_nodeVersion": "0.12.0", + "_npmUser": { + "name": "sindresorhus", + "email": "sindresorhus@gmail.com" + }, + "maintainers": [ + { + "name": "sindresorhus", + "email": "sindresorhus@gmail.com" + } + ], + "dist": { + "shasum": "263dada66ab3f2fb10bf7f9d24dd8f3e570ef912", + "tarball": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/readme.md b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/readme.md new file mode 100644 index 0000000000..cdf94f4309 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/node_modules/path-is-absolute/readme.md @@ -0,0 +1,51 @@ +# path-is-absolute [![Build Status](https://travis-ci.org/sindresorhus/path-is-absolute.svg?branch=master)](https://travis-ci.org/sindresorhus/path-is-absolute) + +> Node.js 0.12 [`path.isAbsolute()`](http://nodejs.org/api/path.html#path_path_isabsolute_path) ponyfill + +> Ponyfill: A polyfill that doesn't overwrite the native method + + +## Install + +``` +$ npm install --save path-is-absolute +``` + + +## Usage + +```js +var pathIsAbsolute = require('path-is-absolute'); + +// Linux +pathIsAbsolute('/home/foo'); +//=> true + +// Windows +pathIsAbsolute('C:/Users/'); +//=> true + +// Any OS +pathIsAbsolute.posix('/home/foo'); +//=> true +``` + + +## API + +See the [`path.isAbsolute()` docs](http://nodejs.org/api/path.html#path_path_isabsolute_path). + +### pathIsAbsolute(path) + +### pathIsAbsolute.posix(path) + +The Posix specific version. + +### pathIsAbsolute.win32(path) + +The Windows specific version. + + +## License + +MIT © [Sindre Sorhus](http://sindresorhus.com) diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/package.json new file mode 100644 index 0000000000..bc35810603 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/package.json @@ -0,0 +1,73 @@ +{ + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "name": "glob", + "description": "a little globber", + "version": "5.0.15", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/node-glob.git" + }, + "main": "glob.js", + "files": [ + "glob.js", + "sync.js", + "common.js" + ], + "engines": { + "node": "*" + }, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "devDependencies": { + "mkdirp": "0", + "rimraf": "^2.2.8", + "tap": "^1.1.4", + "tick": "0.0.6" + }, + "scripts": { + "prepublish": "npm run benchclean", + "profclean": "rm -f v8.log profile.txt", + "test": "tap test/*.js --cov", + "test-regen": "npm run profclean && TEST_REGEN=1 node test/00-setup.js", + "bench": "bash benchmark.sh", + "prof": "bash prof.sh && cat profile.txt", + "benchclean": "node benchclean.js" + }, + "license": "ISC", + "gitHead": "3a7e71d453dd80e75b196fd262dd23ed54beeceb", + "bugs": { + "url": "https://github.com/isaacs/node-glob/issues" + }, + "homepage": "https://github.com/isaacs/node-glob#readme", + "_id": "glob@5.0.15", + "_shasum": "1bc936b9e02f4a603fcc222ecf7633d30b8b93b1", + "_from": "glob@>=5.0.14 <6.0.0", + "_npmVersion": "3.3.2", + "_nodeVersion": "4.0.0", + "_npmUser": { + "name": "isaacs", + "email": "isaacs@npmjs.com" + }, + "dist": { + "shasum": "1bc936b9e02f4a603fcc222ecf7633d30b8b93b1", + "tarball": "http://registry.npmjs.org/glob/-/glob-5.0.15.tgz" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "directories": {}, + "_resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/sync.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/sync.js new file mode 100644 index 0000000000..09883d2ce0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/node_modules/glob/sync.js @@ -0,0 +1,460 @@ +module.exports = globSync +globSync.GlobSync = GlobSync + +var fs = require('fs') +var minimatch = require('minimatch') +var Minimatch = minimatch.Minimatch +var Glob = require('./glob.js').Glob +var util = require('util') +var path = require('path') +var assert = require('assert') +var isAbsolute = require('path-is-absolute') +var common = require('./common.js') +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var childrenIgnored = common.childrenIgnored + +function globSync (pattern, options) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + return new GlobSync(pattern, options).found +} + +function GlobSync (pattern, options) { + if (!pattern) + throw new Error('must provide pattern') + + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options) + + setopts(this, pattern, options) + + if (this.noprocess) + return this + + var n = this.minimatch.set.length + this.matches = new Array(n) + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false) + } + this._finish() +} + +GlobSync.prototype._finish = function () { + assert(this instanceof GlobSync) + if (this.realpath) { + var self = this + this.matches.forEach(function (matchset, index) { + var set = self.matches[index] = Object.create(null) + for (var p in matchset) { + try { + p = self._makeAbs(p) + var real = fs.realpathSync(p, self.realpathCache) + set[real] = true + } catch (er) { + if (er.syscall === 'stat') + set[self._makeAbs(p)] = true + else + throw er + } + } + }) + } + common.finish(this) +} + + +GlobSync.prototype._process = function (pattern, index, inGlobStar) { + assert(this instanceof GlobSync) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // See if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip processing + if (childrenIgnored(this, read)) + return + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar) +} + + +GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar) + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix.slice(-1) !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this.matches[index][e] = true + } + // This was the last one, and no stats were needed + return + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) + newPattern = [prefix, e] + else + newPattern = [e] + this._process(newPattern.concat(remain), index, inGlobStar) + } +} + + +GlobSync.prototype._emitMatch = function (index, e) { + var abs = this._makeAbs(e) + if (this.mark) + e = this._mark(e) + + if (this.matches[index][e]) + return + + if (this.nodir) { + var c = this.cache[this._makeAbs(e)] + if (c === 'DIR' || Array.isArray(c)) + return + } + + this.matches[index][e] = true + if (this.stat) + this._stat(e) +} + + +GlobSync.prototype._readdirInGlobStar = function (abs) { + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false) + + var entries + var lstat + var stat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + // lstat failed, doesn't exist + return null + } + + var isSym = lstat.isSymbolicLink() + this.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && !lstat.isDirectory()) + this.cache[abs] = 'FILE' + else + entries = this._readdir(abs, false) + + return entries +} + +GlobSync.prototype._readdir = function (abs, inGlobStar) { + var entries + + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return null + + if (Array.isArray(c)) + return c + } + + try { + return this._readdirEntries(abs, fs.readdirSync(abs)) + } catch (er) { + this._readdirError(abs, er) + return null + } +} + +GlobSync.prototype._readdirEntries = function (abs, entries) { + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + + // mark and cache dir-ness + return entries +} + +GlobSync.prototype._readdirError = function (f, er) { + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + this.cache[this._makeAbs(f)] = 'FILE' + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) + throw er + if (!this.silent) + console.error('glob error', er) + break + } +} + +GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { + + var entries = this._readdir(abs, inGlobStar) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false) + + var len = entries.length + var isSym = this.symlinks[abs] + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true) + } +} + +GlobSync.prototype._processSimple = function (prefix, index) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var exists = this._stat(prefix) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this.matches[index][prefix] = true +} + +// Returns either 'DIR', 'FILE', or false +GlobSync.prototype._stat = function (f) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return false + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return c + + if (needDir && c === 'FILE') + return false + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (!stat) { + var lstat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + return false + } + + if (lstat.isSymbolicLink()) { + try { + stat = fs.statSync(abs) + } catch (er) { + stat = lstat + } + } else { + stat = lstat + } + } + + this.statCache[abs] = stat + + var c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c !== 'DIR') + return false + + return c +} + +GlobSync.prototype._mark = function (p) { + return common.mark(this, p) +} + +GlobSync.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/package.json new file mode 100644 index 0000000000..064ebe06ba --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/package.json @@ -0,0 +1,62 @@ +{ + "name": "rimraf", + "version": "2.4.3", + "main": "rimraf.js", + "description": "A deep deletion module for node (like `rm -rf`)", + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "license": "ISC", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/rimraf.git" + }, + "scripts": { + "test": "tap test/*.js" + }, + "bin": { + "rimraf": "./bin.js" + }, + "dependencies": { + "glob": "^5.0.14" + }, + "files": [ + "LICENSE", + "README.md", + "bin.js", + "rimraf.js" + ], + "devDependencies": { + "mkdirp": "^0.5.1", + "tap": "^1.3.1" + }, + "gitHead": "ec7050f8ca14c931b847414f18466e601ca7c02e", + "bugs": { + "url": "https://github.com/isaacs/rimraf/issues" + }, + "homepage": "https://github.com/isaacs/rimraf#readme", + "_id": "rimraf@2.4.3", + "_shasum": "e5b51c9437a4c582adb955e9f28cf8d945e272af", + "_from": "rimraf@>=2.0.0 <3.0.0", + "_npmVersion": "3.2.2", + "_nodeVersion": "2.2.1", + "_npmUser": { + "name": "isaacs", + "email": "isaacs@npmjs.com" + }, + "dist": { + "shasum": "e5b51c9437a4c582adb955e9f28cf8d945e272af", + "tarball": "http://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "directories": {}, + "_resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/rimraf.js b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/rimraf.js new file mode 100644 index 0000000000..7771b530ca --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/node_modules/rimraf/rimraf.js @@ -0,0 +1,333 @@ +module.exports = rimraf +rimraf.sync = rimrafSync + +var assert = require("assert") +var path = require("path") +var fs = require("fs") +var glob = require("glob") + +var globOpts = { + nosort: true, + nocomment: true, + nonegate: true, + silent: true +} + +// for EMFILE handling +var timeout = 0 + +var isWindows = (process.platform === "win32") + +function defaults (options) { + var methods = [ + 'unlink', + 'chmod', + 'stat', + 'lstat', + 'rmdir', + 'readdir' + ] + methods.forEach(function(m) { + options[m] = options[m] || fs[m] + m = m + 'Sync' + options[m] = options[m] || fs[m] + }) + + options.maxBusyTries = options.maxBusyTries || 3 + options.emfileWait = options.emfileWait || 1000 + options.disableGlob = options.disableGlob || false +} + +function rimraf (p, options, cb) { + if (typeof options === 'function') { + cb = options + options = {} + } + + assert(p, 'rimraf: missing path') + assert.equal(typeof p, 'string', 'rimraf: path should be a string') + assert(options, 'rimraf: missing options') + assert.equal(typeof options, 'object', 'rimraf: options should be object') + assert.equal(typeof cb, 'function', 'rimraf: callback function required') + + defaults(options) + + var busyTries = 0 + var errState = null + var n = 0 + + if (options.disableGlob || !glob.hasMagic(p)) + return afterGlob(null, [p]) + + fs.lstat(p, function (er, stat) { + if (!er) + return afterGlob(null, [p]) + + glob(p, globOpts, afterGlob) + }) + + function next (er) { + errState = errState || er + if (--n === 0) + cb(errState) + } + + function afterGlob (er, results) { + if (er) + return cb(er) + + n = results.length + if (n === 0) + return cb() + + results.forEach(function (p) { + rimraf_(p, options, function CB (er) { + if (er) { + if (isWindows && (er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && + busyTries < options.maxBusyTries) { + busyTries ++ + var time = busyTries * 100 + // try again, with the same exact callback as this one. + return setTimeout(function () { + rimraf_(p, options, CB) + }, time) + } + + // this one won't happen if graceful-fs is used. + if (er.code === "EMFILE" && timeout < options.emfileWait) { + return setTimeout(function () { + rimraf_(p, options, CB) + }, timeout ++) + } + + // already gone + if (er.code === "ENOENT") er = null + } + + timeout = 0 + next(er) + }) + }) + } +} + +// Two possible strategies. +// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR +// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR +// +// Both result in an extra syscall when you guess wrong. However, there +// are likely far more normal files in the world than directories. This +// is based on the assumption that a the average number of files per +// directory is >= 1. +// +// If anyone ever complains about this, then I guess the strategy could +// be made configurable somehow. But until then, YAGNI. +function rimraf_ (p, options, cb) { + assert(p) + assert(options) + assert(typeof cb === 'function') + + // sunos lets the root user unlink directories, which is... weird. + // so we have to lstat here and make sure it's not a dir. + options.lstat(p, function (er, st) { + if (er && er.code === "ENOENT") + return cb(null) + + if (st && st.isDirectory()) + return rmdir(p, options, er, cb) + + options.unlink(p, function (er) { + if (er) { + if (er.code === "ENOENT") + return cb(null) + if (er.code === "EPERM") + return (isWindows) + ? fixWinEPERM(p, options, er, cb) + : rmdir(p, options, er, cb) + if (er.code === "EISDIR") + return rmdir(p, options, er, cb) + } + return cb(er) + }) + }) +} + +function fixWinEPERM (p, options, er, cb) { + assert(p) + assert(options) + assert(typeof cb === 'function') + if (er) + assert(er instanceof Error) + + options.chmod(p, 666, function (er2) { + if (er2) + cb(er2.code === "ENOENT" ? null : er) + else + options.stat(p, function(er3, stats) { + if (er3) + cb(er3.code === "ENOENT" ? null : er) + else if (stats.isDirectory()) + rmdir(p, options, er, cb) + else + options.unlink(p, cb) + }) + }) +} + +function fixWinEPERMSync (p, options, er) { + assert(p) + assert(options) + if (er) + assert(er instanceof Error) + + try { + options.chmodSync(p, 666) + } catch (er2) { + if (er2.code === "ENOENT") + return + else + throw er + } + + try { + var stats = options.statSync(p) + } catch (er3) { + if (er3.code === "ENOENT") + return + else + throw er + } + + if (stats.isDirectory()) + rmdirSync(p, options, er) + else + options.unlinkSync(p) +} + +function rmdir (p, options, originalEr, cb) { + assert(p) + assert(options) + if (originalEr) + assert(originalEr instanceof Error) + assert(typeof cb === 'function') + + // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) + // if we guessed wrong, and it's not a directory, then + // raise the original error. + options.rmdir(p, function (er) { + if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) + rmkids(p, options, cb) + else if (er && er.code === "ENOTDIR") + cb(originalEr) + else + cb(er) + }) +} + +function rmkids(p, options, cb) { + assert(p) + assert(options) + assert(typeof cb === 'function') + + options.readdir(p, function (er, files) { + if (er) + return cb(er) + var n = files.length + if (n === 0) + return options.rmdir(p, cb) + var errState + files.forEach(function (f) { + rimraf(path.join(p, f), options, function (er) { + if (errState) + return + if (er) + return cb(errState = er) + if (--n === 0) + options.rmdir(p, cb) + }) + }) + }) +} + +// this looks simpler, and is strictly *faster*, but will +// tie up the JavaScript thread and fail on excessively +// deep directory trees. +function rimrafSync (p, options) { + options = options || {} + defaults(options) + + assert(p, 'rimraf: missing path') + assert.equal(typeof p, 'string', 'rimraf: path should be a string') + assert(options, 'rimraf: missing options') + assert.equal(typeof options, 'object', 'rimraf: options should be object') + + var results + + if (options.disableGlob || !glob.hasMagic(p)) { + results = [p] + } else { + try { + fs.lstatSync(p) + results = [p] + } catch (er) { + results = glob.sync(p, globOpts) + } + } + + if (!results.length) + return + + for (var i = 0; i < results.length; i++) { + var p = results[i] + + try { + var st = options.lstatSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + } + + try { + // sunos lets the root user unlink directories, which is... weird. + if (st && st.isDirectory()) + rmdirSync(p, options, null) + else + options.unlinkSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "EPERM") + return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) + if (er.code !== "EISDIR") + throw er + rmdirSync(p, options, er) + } + } +} + +function rmdirSync (p, options, originalEr) { + assert(p) + assert(options) + if (originalEr) + assert(originalEr instanceof Error) + + try { + options.rmdirSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "ENOTDIR") + throw originalEr + if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM") + rmkidsSync(p, options) + } +} + +function rmkidsSync (p, options) { + assert(p) + assert(options) + options.readdirSync(p).forEach(function (f) { + rimrafSync(path.join(p, f), options) + }) + options.rmdirSync(p, options) +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/fstream/package.json b/test/remoting/tar/node_modules/tar/node_modules/fstream/package.json new file mode 100644 index 0000000000..3d7a9da8d3 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/fstream/package.json @@ -0,0 +1,71 @@ +{ + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "name": "fstream", + "description": "Advanced file system stream things", + "version": "1.0.8", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/fstream.git" + }, + "main": "fstream.js", + "engines": { + "node": ">=0.6" + }, + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "devDependencies": { + "standard": "^4.0.0", + "tap": "^1.2.0" + }, + "scripts": { + "test": "standard && tap examples/*.js" + }, + "license": "ISC", + "gitHead": "d9f81146c50e687f1df04c1a0e7e4c173eb3dae2", + "bugs": { + "url": "https://github.com/isaacs/fstream/issues" + }, + "homepage": "https://github.com/isaacs/fstream#readme", + "_id": "fstream@1.0.8", + "_shasum": "7e8d7a73abb3647ef36e4b8a15ca801dba03d038", + "_from": "fstream@>=1.0.2 <2.0.0", + "_npmVersion": "2.14.3", + "_nodeVersion": "2.2.2", + "_npmUser": { + "name": "zkat", + "email": "kat@sykosomatic.org" + }, + "dist": { + "shasum": "7e8d7a73abb3647ef36e4b8a15ca801dba03d038", + "tarball": "http://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz" + }, + "maintainers": [ + { + "name": "iarna", + "email": "me@re-becca.org" + }, + { + "name": "isaacs", + "email": "isaacs@npmjs.com" + }, + { + "name": "othiym23", + "email": "ogd@aoaioxxysz.net" + }, + { + "name": "zkat", + "email": "kat@sykosomatic.org" + } + ], + "directories": {}, + "_resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/inherits/LICENSE b/test/remoting/tar/node_modules/tar/node_modules/inherits/LICENSE new file mode 100644 index 0000000000..dea3013d67 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/inherits/LICENSE @@ -0,0 +1,16 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + diff --git a/test/remoting/tar/node_modules/tar/node_modules/inherits/README.md b/test/remoting/tar/node_modules/tar/node_modules/inherits/README.md new file mode 100644 index 0000000000..b1c5665855 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/inherits/README.md @@ -0,0 +1,42 @@ +Browser-friendly inheritance fully compatible with standard node.js +[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor). + +This package exports standard `inherits` from node.js `util` module in +node environment, but also provides alternative browser-friendly +implementation through [browser +field](https://gist.github.com/shtylman/4339901). Alternative +implementation is a literal copy of standard one located in standalone +module to avoid requiring of `util`. It also has a shim for old +browsers with no `Object.create` support. + +While keeping you sure you are using standard `inherits` +implementation in node.js environment, it allows bundlers such as +[browserify](https://github.com/substack/node-browserify) to not +include full `util` package to your client code if all you need is +just `inherits` function. It worth, because browser shim for `util` +package is large and `inherits` is often the single function you need +from it. + +It's recommended to use this package instead of +`require('util').inherits` for any code that has chances to be used +not only in node.js but in browser too. + +## usage + +```js +var inherits = require('inherits'); +// then use exactly as the standard one +``` + +## note on version ~1.0 + +Version ~1.0 had completely different motivation and is not compatible +neither with 2.0 nor with standard node.js `inherits`. + +If you are using version ~1.0 and planning to switch to ~2.0, be +careful: + +* new version uses `super_` instead of `super` for referencing + superclass +* new version overwrites current prototype while old one preserves any + existing fields on it diff --git a/test/remoting/tar/node_modules/tar/node_modules/inherits/inherits.js b/test/remoting/tar/node_modules/tar/node_modules/inherits/inherits.js new file mode 100644 index 0000000000..29f5e24f57 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/inherits/inherits.js @@ -0,0 +1 @@ +module.exports = require('util').inherits diff --git a/test/remoting/tar/node_modules/tar/node_modules/inherits/inherits_browser.js b/test/remoting/tar/node_modules/tar/node_modules/inherits/inherits_browser.js new file mode 100644 index 0000000000..c1e78a75e6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/inherits/inherits_browser.js @@ -0,0 +1,23 @@ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/inherits/package.json b/test/remoting/tar/node_modules/tar/node_modules/inherits/package.json new file mode 100644 index 0000000000..bb245d21b6 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/inherits/package.json @@ -0,0 +1,50 @@ +{ + "name": "inherits", + "description": "Browser-friendly inheritance fully compatible with standard node.js inherits()", + "version": "2.0.1", + "keywords": [ + "inheritance", + "class", + "klass", + "oop", + "object-oriented", + "inherits", + "browser", + "browserify" + ], + "main": "./inherits.js", + "browser": "./inherits_browser.js", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/inherits.git" + }, + "license": "ISC", + "scripts": { + "test": "node test" + }, + "bugs": { + "url": "https://github.com/isaacs/inherits/issues" + }, + "_id": "inherits@2.0.1", + "dist": { + "shasum": "b17d08d326b4423e568eff719f91b0b1cbdf69f1", + "tarball": "http://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "_from": "inherits@>=2.0.0 <3.0.0", + "_npmVersion": "1.3.8", + "_npmUser": { + "name": "isaacs", + "email": "i@izs.me" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "directories": {}, + "_shasum": "b17d08d326b4423e568eff719f91b0b1cbdf69f1", + "_resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "readme": "ERROR: No README data found!", + "homepage": "https://github.com/isaacs/inherits#readme" +} diff --git a/test/remoting/tar/node_modules/tar/node_modules/inherits/test.js b/test/remoting/tar/node_modules/tar/node_modules/inherits/test.js new file mode 100644 index 0000000000..fc53012d31 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/node_modules/inherits/test.js @@ -0,0 +1,25 @@ +var inherits = require('./inherits.js') +var assert = require('assert') + +function test(c) { + assert(c.constructor === Child) + assert(c.constructor.super_ === Parent) + assert(Object.getPrototypeOf(c) === Child.prototype) + assert(Object.getPrototypeOf(Object.getPrototypeOf(c)) === Parent.prototype) + assert(c instanceof Child) + assert(c instanceof Parent) +} + +function Child() { + Parent.call(this) + test(this) +} + +function Parent() {} + +inherits(Child, Parent) + +var c = new Child +test(c) + +console.log('ok') diff --git a/test/remoting/tar/node_modules/tar/package.json b/test/remoting/tar/node_modules/tar/package.json new file mode 100644 index 0000000000..0fd2a4d806 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/package.json @@ -0,0 +1,69 @@ +{ + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, + "name": "tar", + "description": "tar for node", + "version": "2.2.1", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/node-tar.git" + }, + "main": "tar.js", + "scripts": { + "test": "tap test/*.js" + }, + "dependencies": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + }, + "devDependencies": { + "graceful-fs": "^4.1.2", + "rimraf": "1.x", + "tap": "0.x", + "mkdirp": "^0.5.0" + }, + "license": "ISC", + "gitHead": "52237e39d2eb68d22a32d9a98f1d762189fe6a3d", + "bugs": { + "url": "https://github.com/isaacs/node-tar/issues" + }, + "homepage": "https://github.com/isaacs/node-tar#readme", + "_id": "tar@2.2.1", + "_shasum": "8e4d2a256c0e2185c6b18ad694aec968b83cb1d1", + "_from": "tar@latest", + "_npmVersion": "2.14.3", + "_nodeVersion": "2.2.2", + "_npmUser": { + "name": "zkat", + "email": "kat@sykosomatic.org" + }, + "dist": { + "shasum": "8e4d2a256c0e2185c6b18ad694aec968b83cb1d1", + "tarball": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz" + }, + "maintainers": [ + { + "name": "isaacs", + "email": "isaacs@npmjs.com" + }, + { + "name": "othiym23", + "email": "ogd@aoaioxxysz.net" + }, + { + "name": "soldair", + "email": "soldair@gmail.com" + }, + { + "name": "zkat", + "email": "kat@sykosomatic.org" + } + ], + "directories": {}, + "_resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "readme": "ERROR: No README data found!" +} diff --git a/test/remoting/tar/node_modules/tar/tar.js b/test/remoting/tar/node_modules/tar/tar.js new file mode 100644 index 0000000000..a81298b9a0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/tar.js @@ -0,0 +1,173 @@ +// field paths that every tar file must have. +// header is padded to 512 bytes. +var f = 0 + , fields = {} + , path = fields.path = f++ + , mode = fields.mode = f++ + , uid = fields.uid = f++ + , gid = fields.gid = f++ + , size = fields.size = f++ + , mtime = fields.mtime = f++ + , cksum = fields.cksum = f++ + , type = fields.type = f++ + , linkpath = fields.linkpath = f++ + , headerSize = 512 + , blockSize = 512 + , fieldSize = [] + +fieldSize[path] = 100 +fieldSize[mode] = 8 +fieldSize[uid] = 8 +fieldSize[gid] = 8 +fieldSize[size] = 12 +fieldSize[mtime] = 12 +fieldSize[cksum] = 8 +fieldSize[type] = 1 +fieldSize[linkpath] = 100 + +// "ustar\0" may introduce another bunch of headers. +// these are optional, and will be nulled out if not present. + +var ustar = fields.ustar = f++ + , ustarver = fields.ustarver = f++ + , uname = fields.uname = f++ + , gname = fields.gname = f++ + , devmaj = fields.devmaj = f++ + , devmin = fields.devmin = f++ + , prefix = fields.prefix = f++ + , fill = fields.fill = f++ + +// terminate fields. +fields[f] = null + +fieldSize[ustar] = 6 +fieldSize[ustarver] = 2 +fieldSize[uname] = 32 +fieldSize[gname] = 32 +fieldSize[devmaj] = 8 +fieldSize[devmin] = 8 +fieldSize[prefix] = 155 +fieldSize[fill] = 12 + +// nb: prefix field may in fact be 130 bytes of prefix, +// a null char, 12 bytes for atime, 12 bytes for ctime. +// +// To recognize this format: +// 1. prefix[130] === ' ' or '\0' +// 2. atime and ctime are octal numeric values +// 3. atime and ctime have ' ' in their last byte + +var fieldEnds = {} + , fieldOffs = {} + , fe = 0 +for (var i = 0; i < f; i ++) { + fieldOffs[i] = fe + fieldEnds[i] = (fe += fieldSize[i]) +} + +// build a translation table of field paths. +Object.keys(fields).forEach(function (f) { + if (fields[f] !== null) fields[fields[f]] = f +}) + +// different values of the 'type' field +// paths match the values of Stats.isX() functions, where appropriate +var types = + { 0: "File" + , "\0": "OldFile" // like 0 + , "": "OldFile" + , 1: "Link" + , 2: "SymbolicLink" + , 3: "CharacterDevice" + , 4: "BlockDevice" + , 5: "Directory" + , 6: "FIFO" + , 7: "ContiguousFile" // like 0 + // posix headers + , g: "GlobalExtendedHeader" // k=v for the rest of the archive + , x: "ExtendedHeader" // k=v for the next file + // vendor-specific stuff + , A: "SolarisACL" // skip + , D: "GNUDumpDir" // like 5, but with data, which should be skipped + , I: "Inode" // metadata only, skip + , K: "NextFileHasLongLinkpath" // data = link path of next file + , L: "NextFileHasLongPath" // data = path of next file + , M: "ContinuationFile" // skip + , N: "OldGnuLongPath" // like L + , S: "SparseFile" // skip + , V: "TapeVolumeHeader" // skip + , X: "OldExtendedHeader" // like x + } + +Object.keys(types).forEach(function (t) { + types[types[t]] = types[types[t]] || t +}) + +// values for the mode field +var modes = + { suid: 04000 // set uid on extraction + , sgid: 02000 // set gid on extraction + , svtx: 01000 // set restricted deletion flag on dirs on extraction + , uread: 0400 + , uwrite: 0200 + , uexec: 0100 + , gread: 040 + , gwrite: 020 + , gexec: 010 + , oread: 4 + , owrite: 2 + , oexec: 1 + , all: 07777 + } + +var numeric = + { mode: true + , uid: true + , gid: true + , size: true + , mtime: true + , devmaj: true + , devmin: true + , cksum: true + , atime: true + , ctime: true + , dev: true + , ino: true + , nlink: true + } + +Object.keys(modes).forEach(function (t) { + modes[modes[t]] = modes[modes[t]] || t +}) + +var knownExtended = + { atime: true + , charset: true + , comment: true + , ctime: true + , gid: true + , gname: true + , linkpath: true + , mtime: true + , path: true + , realtime: true + , security: true + , size: true + , uid: true + , uname: true } + + +exports.fields = fields +exports.fieldSize = fieldSize +exports.fieldOffs = fieldOffs +exports.fieldEnds = fieldEnds +exports.types = types +exports.modes = modes +exports.numeric = numeric +exports.headerSize = headerSize +exports.blockSize = blockSize +exports.knownExtended = knownExtended + +exports.Pack = require("./lib/pack.js") +exports.Parse = require("./lib/parse.js") +exports.Extract = require("./lib/extract.js") diff --git a/test/remoting/tar/node_modules/tar/test/00-setup-fixtures.js b/test/remoting/tar/node_modules/tar/test/00-setup-fixtures.js new file mode 100644 index 0000000000..1524ff7af0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/00-setup-fixtures.js @@ -0,0 +1,53 @@ +// the fixtures have some weird stuff that is painful +// to include directly in the repo for various reasons. +// +// So, unpack the fixtures with the system tar first. +// +// This means, of course, that it'll only work if you +// already have a tar implementation, and some of them +// will not properly unpack the fixtures anyway. +// +// But, since usually those tests will fail on Windows +// and other systems with less capable filesystems anyway, +// at least this way we don't cause inconveniences by +// merely cloning the repo or installing the package. + +var tap = require("tap") +, child_process = require("child_process") +, rimraf = require("rimraf") +, test = tap.test +, path = require("path") + +test("clean fixtures", function (t) { + rimraf(path.resolve(__dirname, "fixtures"), function (er) { + t.ifError(er, "rimraf ./fixtures/") + t.end() + }) +}) + +test("clean tmp", function (t) { + rimraf(path.resolve(__dirname, "tmp"), function (er) { + t.ifError(er, "rimraf ./tmp/") + t.end() + }) +}) + +test("extract fixtures", function (t) { + var c = child_process.spawn("tar" + ,["xzvf", "fixtures.tgz"] + ,{ cwd: __dirname }) + + c.stdout.on("data", errwrite) + c.stderr.on("data", errwrite) + function errwrite (chunk) { + process.stderr.write(chunk) + } + + c.on("exit", function (code) { + t.equal(code, 0, "extract fixtures should exit with 0") + if (code) { + t.comment("Note, all tests from here on out will fail because of this.") + } + t.end() + }) +}) diff --git a/test/remoting/tar/node_modules/tar/test/cb-never-called-1.0.1.tgz b/test/remoting/tar/node_modules/tar/test/cb-never-called-1.0.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..9e7014d85abe48a54cb6b4e1ec032df9a2073034 GIT binary patch literal 4096 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 literal 0 HcmV?d00001 diff --git a/test/remoting/tar/node_modules/tar/test/dir-normalization.js b/test/remoting/tar/node_modules/tar/test/dir-normalization.js new file mode 100644 index 0000000000..9719c42f35 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/dir-normalization.js @@ -0,0 +1,177 @@ +// Set the umask, so that it works the same everywhere. +process.umask(parseInt('22', 8)) + +var fs = require('fs') +var path = require('path') + +var fstream = require('fstream') +var test = require('tap').test + +var tar = require('../tar.js') +var file = path.resolve(__dirname, 'dir-normalization.tar') +var target = path.resolve(__dirname, 'tmp/dir-normalization-test') +var ee = 0 + +var expectEntries = [ + { path: 'fixtures/', + mode: '755', + type: '5', + linkpath: '' + }, + { path: 'fixtures/a/', + mode: '755', + type: '5', + linkpath: '' + }, + { path: 'fixtures/the-chumbler', + mode: '755', + type: '2', + linkpath: path.resolve(target, 'a/b/c/d/the-chumbler'), + }, + { path: 'fixtures/a/b/', + mode: '755', + type: '5', + linkpath: '' + }, + { path: 'fixtures/a/x', + mode: '644', + type: '0', + linkpath: '' + }, + { path: 'fixtures/a/b/c/', + mode: '755', + type: '5', + linkpath: '' + }, + { path: 'fixtures/a/b/c/y', + mode: '755', + type: '2', + linkpath: '../../x', + } +] + +var ef = 0 +var expectFiles = [ + { path: '', + mode: '40755', + type: 'Directory', + depth: 0, + linkpath: undefined + }, + { path: '/fixtures', + mode: '40755', + type: 'Directory', + depth: 1, + linkpath: undefined + }, + { path: '/fixtures/a', + mode: '40755', + type: 'Directory', + depth: 2, + linkpath: undefined + }, + { path: '/fixtures/a/b', + mode: '40755', + type: 'Directory', + depth: 3, + linkpath: undefined + }, + { path: '/fixtures/a/b/c', + mode: '40755', + type: 'Directory', + depth: 4, + linkpath: undefined + }, + { path: '/fixtures/a/b/c/y', + mode: '120755', + type: 'SymbolicLink', + depth: 5, + linkpath: '../../x' + }, + { path: '/fixtures/a/x', + mode: '100644', + type: 'File', + depth: 3, + linkpath: undefined + }, + { path: '/fixtures/the-chumbler', + mode: '120755', + type: 'SymbolicLink', + depth: 2, + linkpath: path.resolve(target, 'a/b/c/d/the-chumbler') + } +] + +test('preclean', function (t) { + require('rimraf').sync(path.join(__dirname, '/tmp/dir-normalization-test')) + t.pass('cleaned!') + t.end() +}) + +test('extract test', function (t) { + var extract = tar.Extract(target) + var inp = fs.createReadStream(file) + + inp.pipe(extract) + + extract.on('end', function () { + t.equal(ee, expectEntries.length, 'should see ' + expectEntries.length + ' entries') + + // should get no more entries after end + extract.removeAllListeners('entry') + extract.on('entry', function (e) { + t.fail('Should not get entries after end!') + }) + + next() + }) + + extract.on('entry', function (entry) { + var mode = entry.props.mode & (~parseInt('22', 8)) + var found = { + path: entry.path, + mode: mode.toString(8), + type: entry.props.type, + linkpath: entry.props.linkpath, + } + + var wanted = expectEntries[ee++] + t.equivalent(found, wanted, 'tar entry ' + ee + ' ' + (wanted && wanted.path)) + }) + + function next () { + var r = fstream.Reader({ + path: target, + type: 'Directory', + sort: 'alpha' + }) + + r.on('ready', function () { + foundEntry(r) + }) + + r.on('end', finish) + + function foundEntry (entry) { + var p = entry.path.substr(target.length) + var mode = entry.props.mode & (~parseInt('22', 8)) + var found = { + path: p, + mode: mode.toString(8), + type: entry.props.type, + depth: entry.props.depth, + linkpath: entry.props.linkpath + } + + var wanted = expectFiles[ef++] + t.equivalent(found, wanted, 'unpacked file ' + ef + ' ' + (wanted && wanted.path)) + + entry.on('entry', foundEntry) + } + + function finish () { + t.equal(ef, expectFiles.length, 'should have ' + ef + ' items') + t.end() + } + } +}) diff --git a/test/remoting/tar/node_modules/tar/test/dir-normalization.tar b/test/remoting/tar/node_modules/tar/test/dir-normalization.tar new file mode 100644 index 0000000000000000000000000000000000000000..3c4845356cecb20939b415830a5fa9717fb91341 GIT binary patch literal 4608 zcmeHJS&qUm4DC^J0f_Cy&3Qtn6-dKR7m2w&PN6j7t4Me>Xkt~0n?~MVp55+r8K=G- zY^5gv5SUE`972tqikXFmnFvea&It?*4!@B>h$+50` z-pt$IGqpQZU1J+8}SF0 zCIB7{5IUmDkd5|W8~bGygDd>M_5W1rH{-sT+lc?%|G~}t525gXDWB^4;D59iEk}ZX z?bE#9F#qrUKRCf3v;xZQ7XGjNZ*)4qOZ=A~MLp$ne2HwSHsX(c~XH?({YrM8z literal 0 HcmV?d00001 diff --git a/test/remoting/tar/node_modules/tar/test/error-on-broken.js b/test/remoting/tar/node_modules/tar/test/error-on-broken.js new file mode 100644 index 0000000000..e484920fd9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/error-on-broken.js @@ -0,0 +1,33 @@ +var fs = require('fs') +var path = require('path') +var zlib = require('zlib') + +var tap = require('tap') + +var tar = require('../tar.js') + +var file = path.join(__dirname, 'cb-never-called-1.0.1.tgz') +var target = path.join(__dirname, 'tmp/extract-test') + +tap.test('preclean', function (t) { + require('rimraf').sync(__dirname + '/tmp/extract-test') + t.pass('cleaned!') + t.end() +}) + +tap.test('extract test', function (t) { + var extract = tar.Extract(target) + var inp = fs.createReadStream(file) + + inp.pipe(zlib.createGunzip()).pipe(extract) + + extract.on('error', function (er) { + t.equal(er.message, 'unexpected eof', 'error noticed') + t.end() + }) + + extract.on('end', function () { + t.fail('shouldn\'t reach this point due to errors') + t.end() + }) +}) diff --git a/test/remoting/tar/node_modules/tar/test/extract-move.js b/test/remoting/tar/node_modules/tar/test/extract-move.js new file mode 100644 index 0000000000..45400cd9bb --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/extract-move.js @@ -0,0 +1,132 @@ +// Set the umask, so that it works the same everywhere. +process.umask(parseInt('22', 8)) + +var tap = require("tap") + , tar = require("../tar.js") + , fs = require("fs") + , gfs = require("graceful-fs") + , path = require("path") + , file = path.resolve(__dirname, "fixtures/dir.tar") + , target = path.resolve(__dirname, "tmp/extract-test") + , index = 0 + , fstream = require("fstream") + , rimraf = require("rimraf") + , mkdirp = require("mkdirp") + + , ee = 0 + , expectEntries = [ + { + "path" : "dir/", + "mode" : "750", + "type" : "5", + "depth" : undefined, + "size" : 0, + "linkpath" : "", + "nlink" : undefined, + "dev" : undefined, + "ino" : undefined + }, + { + "path" : "dir/sub/", + "mode" : "750", + "type" : "5", + "depth" : undefined, + "size" : 0, + "linkpath" : "", + "nlink" : undefined, + "dev" : undefined, + "ino" : undefined + } ] + +function slow (fs, method, t1, t2) { + var orig = fs[method] + if (!orig) return null + fs[method] = function () { + var args = [].slice.call(arguments) + console.error("slow", method, args[0]) + var cb = args.pop() + + setTimeout(function () { + orig.apply(fs, args.concat(function(er, data) { + setTimeout(function() { + cb(er, data) + }, t2) + })) + }, t1) + } +} + +// Make sure we get the graceful-fs that fstream is using. +var gfs2 +try { + gfs2 = require("fstream/node_modules/graceful-fs") +} catch (er) {} + +var slowMethods = ["chown", "chmod", "utimes", "lutimes"] +slowMethods.forEach(function (method) { + var t1 = 500 + var t2 = 0 + slow(fs, method, t1, t2) + slow(gfs, method, t1, t2) + if (gfs2) { + slow(gfs2, method, t1, t2) + } +}) + + + +// The extract class basically just pipes the input +// to a Reader, and then to a fstream.DirWriter + +// So, this is as much a test of fstream.Reader and fstream.Writer +// as it is of tar.Extract, but it sort of makes sense. + +tap.test("preclean", function (t) { + rimraf.sync(target) + /mkdirp.sync(target) + t.pass("cleaned!") + t.end() +}) + +tap.test("extract test", function (t) { + var extract = tar.Extract(target) + var inp = fs.createReadStream(file) + + // give it a weird buffer size to try to break in odd places + inp.bufferSize = 1234 + + inp.pipe(extract) + + extract.on("end", function () { + rimraf.sync(target) + + t.equal(ee, expectEntries.length, "should see "+ee+" entries") + + // should get no more entries after end + extract.removeAllListeners("entry") + extract.on("entry", function (e) { + t.fail("Should not get entries after end!") + }) + + t.end() + }) + + + extract.on("entry", function (entry) { + var found = + { path: entry.path + , mode: entry.props.mode.toString(8) + , type: entry.props.type + , depth: entry.props.depth + , size: entry.props.size + , linkpath: entry.props.linkpath + , nlink: entry.props.nlink + , dev: entry.props.dev + , ino: entry.props.ino + } + + var wanted = expectEntries[ee ++] + + t.equivalent(found, wanted, "tar entry " + ee + " " + wanted.path) + }) +}) diff --git a/test/remoting/tar/node_modules/tar/test/extract.js b/test/remoting/tar/node_modules/tar/test/extract.js new file mode 100644 index 0000000000..eca4e7cc96 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/extract.js @@ -0,0 +1,367 @@ +// Set the umask, so that it works the same everywhere. +process.umask(parseInt('22', 8)) + +var tap = require("tap") + , tar = require("../tar.js") + , fs = require("fs") + , path = require("path") + , file = path.resolve(__dirname, "fixtures/c.tar") + , target = path.resolve(__dirname, "tmp/extract-test") + , index = 0 + , fstream = require("fstream") + + , ee = 0 + , expectEntries = +[ { path: 'c.txt', + mode: '644', + type: '0', + depth: undefined, + size: 513, + linkpath: '', + nlink: undefined, + dev: undefined, + ino: undefined }, + { path: 'cc.txt', + mode: '644', + type: '0', + depth: undefined, + size: 513, + linkpath: '', + nlink: undefined, + dev: undefined, + ino: undefined }, + { path: 'r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: '644', + type: '0', + depth: undefined, + size: 100, + linkpath: '', + nlink: undefined, + dev: undefined, + ino: undefined }, + { path: 'Ω.txt', + mode: '644', + type: '0', + depth: undefined, + size: 2, + linkpath: '', + nlink: undefined, + dev: undefined, + ino: undefined }, + { path: 'Ω.txt', + mode: '644', + type: '0', + depth: undefined, + size: 2, + linkpath: '', + nlink: 1, + dev: 234881026, + ino: 51693379 }, + { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: '644', + type: '0', + depth: undefined, + size: 200, + linkpath: '', + nlink: 1, + dev: 234881026, + ino: 51681874 }, + { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: '644', + type: '0', + depth: undefined, + size: 201, + linkpath: '', + nlink: undefined, + dev: undefined, + ino: undefined }, + { path: '200LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL', + mode: '777', + type: '2', + depth: undefined, + size: 0, + linkpath: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + nlink: undefined, + dev: undefined, + ino: undefined }, + { path: '200-hard', + mode: '644', + type: '0', + depth: undefined, + size: 200, + linkpath: '', + nlink: 2, + dev: 234881026, + ino: 51681874 }, + { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: '644', + type: '1', + depth: undefined, + size: 0, + linkpath: path.resolve(target, '200-hard'), + nlink: 2, + dev: 234881026, + ino: 51681874 } ] + + , ef = 0 + , expectFiles = +[ { path: '', + mode: '40755', + type: 'Directory', + depth: 0, + linkpath: undefined }, + { path: '/200-hard', + mode: '100644', + type: 'File', + depth: 1, + size: 200, + linkpath: undefined, + nlink: 2 }, + { path: '/200LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL', + mode: '120777', + type: 'SymbolicLink', + depth: 1, + size: 200, + linkpath: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + nlink: 1 }, + { path: '/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: '100644', + type: 'Link', + depth: 1, + size: 200, + linkpath: path.join(target, '200-hard'), + nlink: 2 }, + { path: '/c.txt', + mode: '100644', + type: 'File', + depth: 1, + size: 513, + linkpath: undefined, + nlink: 1 }, + { path: '/cc.txt', + mode: '100644', + type: 'File', + depth: 1, + size: 513, + linkpath: undefined, + nlink: 1 }, + { path: '/r', + mode: '40755', + type: 'Directory', + depth: 1, + linkpath: undefined }, + { path: '/r/e', + mode: '40755', + type: 'Directory', + depth: 2, + linkpath: undefined }, + { path: '/r/e/a', + mode: '40755', + type: 'Directory', + depth: 3, + linkpath: undefined }, + { path: '/r/e/a/l', + mode: '40755', + type: 'Directory', + depth: 4, + linkpath: undefined }, + { path: '/r/e/a/l/l', + mode: '40755', + type: 'Directory', + depth: 5, + linkpath: undefined }, + { path: '/r/e/a/l/l/y', + mode: '40755', + type: 'Directory', + depth: 6, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-', + mode: '40755', + type: 'Directory', + depth: 7, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d', + mode: '40755', + type: 'Directory', + depth: 8, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e', + mode: '40755', + type: 'Directory', + depth: 9, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e', + mode: '40755', + type: 'Directory', + depth: 10, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p', + mode: '40755', + type: 'Directory', + depth: 11, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-', + mode: '40755', + type: 'Directory', + depth: 12, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f', + mode: '40755', + type: 'Directory', + depth: 13, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o', + mode: '40755', + type: 'Directory', + depth: 14, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l', + mode: '40755', + type: 'Directory', + depth: 15, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d', + mode: '40755', + type: 'Directory', + depth: 16, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e', + mode: '40755', + type: 'Directory', + depth: 17, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r', + mode: '40755', + type: 'Directory', + depth: 18, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-', + mode: '40755', + type: 'Directory', + depth: 19, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p', + mode: '40755', + type: 'Directory', + depth: 20, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a', + mode: '40755', + type: 'Directory', + depth: 21, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t', + mode: '40755', + type: 'Directory', + depth: 22, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h', + mode: '40755', + type: 'Directory', + depth: 23, + linkpath: undefined }, + { path: '/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: '100644', + type: 'File', + depth: 24, + size: 100, + linkpath: undefined, + nlink: 1 }, + { path: '/Ω.txt', + mode: '100644', + type: 'File', + depth: 1, + size: 2, + linkpath: undefined, + nlink: 1 } ] + + + +// The extract class basically just pipes the input +// to a Reader, and then to a fstream.DirWriter + +// So, this is as much a test of fstream.Reader and fstream.Writer +// as it is of tar.Extract, but it sort of makes sense. + +tap.test("preclean", function (t) { + require("rimraf").sync(__dirname + "/tmp/extract-test") + t.pass("cleaned!") + t.end() +}) + +tap.test("extract test", function (t) { + var extract = tar.Extract(target) + var inp = fs.createReadStream(file) + + // give it a weird buffer size to try to break in odd places + inp.bufferSize = 1234 + + inp.pipe(extract) + + extract.on("end", function () { + t.equal(ee, expectEntries.length, "should see "+ee+" entries") + + // should get no more entries after end + extract.removeAllListeners("entry") + extract.on("entry", function (e) { + t.fail("Should not get entries after end!") + }) + + next() + }) + + extract.on("entry", function (entry) { + var found = + { path: entry.path + , mode: entry.props.mode.toString(8) + , type: entry.props.type + , depth: entry.props.depth + , size: entry.props.size + , linkpath: entry.props.linkpath + , nlink: entry.props.nlink + , dev: entry.props.dev + , ino: entry.props.ino + } + + var wanted = expectEntries[ee ++] + + t.equivalent(found, wanted, "tar entry " + ee + " " + wanted.path) + }) + + function next () { + var r = fstream.Reader({ path: target + , type: "Directory" + // this is just to encourage consistency + , sort: "alpha" }) + + r.on("ready", function () { + foundEntry(r) + }) + + r.on("end", finish) + + function foundEntry (entry) { + var p = entry.path.substr(target.length) + var found = + { path: p + , mode: entry.props.mode.toString(8) + , type: entry.props.type + , depth: entry.props.depth + , size: entry.props.size + , linkpath: entry.props.linkpath + , nlink: entry.props.nlink + } + + var wanted = expectFiles[ef ++] + + t.has(found, wanted, "unpacked file " + ef + " " + wanted.path) + + entry.on("entry", foundEntry) + } + + function finish () { + t.equal(ef, expectFiles.length, "should have "+ef+" items") + t.end() + } + } +}) diff --git a/test/remoting/tar/node_modules/tar/test/fixtures.tgz b/test/remoting/tar/node_modules/tar/test/fixtures.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f1676023afa2bba2aa18b5508c92f03e1d0816c6 GIT binary patch literal 19352 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 literal 0 HcmV?d00001 diff --git a/test/remoting/tar/node_modules/tar/test/header.js b/test/remoting/tar/node_modules/tar/test/header.js new file mode 100644 index 0000000000..8ea6f79500 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/header.js @@ -0,0 +1,183 @@ +var tap = require("tap") +var TarHeader = require("../lib/header.js") +var tar = require("../tar.js") +var fs = require("fs") + + +var headers = + { "a.txt file header": + [ "612e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030303036343420003035373736312000303030303234200030303030303030303430312031313635313336303333332030313234353100203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000757374617200303069736161637300000000000000000000000000000000000000000000000000007374616666000000000000000000000000000000000000000000000000000000303030303030200030303030303020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , { cksumValid: true + , path: 'a.txt' + , mode: 420 + , uid: 24561 + , gid: 20 + , size: 257 + , mtime: 1319493851 + , cksum: 5417 + , type: '0' + , linkpath: '' + , ustar: 'ustar\0' + , ustarver: '00' + , uname: 'isaacs' + , gname: 'staff' + , devmaj: 0 + , devmin: 0 + , fill: '' } + ] + + , "omega pax": // the extended header from omega tar. + [ "5061784865616465722fcea92e74787400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030303036343420003035373736312000303030303234200030303030303030303137302031313534333731303631312030313530353100207800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000757374617200303069736161637300000000000000000000000000000000000000000000000000007374616666000000000000000000000000000000000000000000000000000000303030303030200030303030303020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , { cksumValid: true + , path: 'PaxHeader/Ω.txt' + , mode: 420 + , uid: 24561 + , gid: 20 + , size: 120 + , mtime: 1301254537 + , cksum: 6697 + , type: 'x' + , linkpath: '' + , ustar: 'ustar\0' + , ustarver: '00' + , uname: 'isaacs' + , gname: 'staff' + , devmaj: 0 + , devmin: 0 + , fill: '' } ] + + , "omega file header": + [ "cea92e7478740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030303036343420003035373736312000303030303234200030303030303030303030322031313534333731303631312030313330373200203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000757374617200303069736161637300000000000000000000000000000000000000000000000000007374616666000000000000000000000000000000000000000000000000000000303030303030200030303030303020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , { cksumValid: true + , path: 'Ω.txt' + , mode: 420 + , uid: 24561 + , gid: 20 + , size: 2 + , mtime: 1301254537 + , cksum: 5690 + , type: '0' + , linkpath: '' + , ustar: 'ustar\0' + , ustarver: '00' + , uname: 'isaacs' + , gname: 'staff' + , devmaj: 0 + , devmin: 0 + , fill: '' } ] + + , "foo.js file header": + [ "666f6f2e6a730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030303036343420003035373736312000303030303234200030303030303030303030342031313534333637303734312030313236313700203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000757374617200303069736161637300000000000000000000000000000000000000000000000000007374616666000000000000000000000000000000000000000000000000000000303030303030200030303030303020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , { cksumValid: true + , path: 'foo.js' + , mode: 420 + , uid: 24561 + , gid: 20 + , size: 4 + , mtime: 1301246433 + , cksum: 5519 + , type: '0' + , linkpath: '' + , ustar: 'ustar\0' + , ustarver: '00' + , uname: 'isaacs' + , gname: 'staff' + , devmaj: 0 + , devmin: 0 + , fill: '' } + ] + + , "b.txt file header": + [ "622e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030303036343420003035373736312000303030303234200030303030303030313030302031313635313336303637372030313234363100203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000757374617200303069736161637300000000000000000000000000000000000000000000000000007374616666000000000000000000000000000000000000000000000000000000303030303030200030303030303020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , { cksumValid: true + , path: 'b.txt' + , mode: 420 + , uid: 24561 + , gid: 20 + , size: 512 + , mtime: 1319494079 + , cksum: 5425 + , type: '0' + , linkpath: '' + , ustar: 'ustar\0' + , ustarver: '00' + , uname: 'isaacs' + , gname: 'staff' + , devmaj: 0 + , devmin: 0 + , fill: '' } + ] + + , "deep nested file": + [ "636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363633030303634342000303537373631200030303030323420003030303030303030313434203131363532313531353333203034333331340020300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075737461720030306973616163730000000000000000000000000000000000000000000000000000737461666600000000000000000000000000000000000000000000000000000030303030303020003030303030302000722f652f612f6c2f6c2f792f2d2f642f652f652f702f2d2f662f6f2f6c2f642f652f722f2d2f702f612f742f680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , { cksumValid: true, + path: 'r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc' + , mode: 420 + , uid: 24561 + , gid: 20 + , size: 100 + , mtime: 1319687003 + , cksum: 18124 + , type: '0' + , linkpath: '' + , ustar: 'ustar\0' + , ustarver: '00' + , uname: 'isaacs' + , gname: 'staff' + , devmaj: 0 + , devmin: 0 + , fill: '' } + ] + } + +tap.test("parsing", function (t) { + Object.keys(headers).forEach(function (name) { + var h = headers[name] + , header = new Buffer(h[0], "hex") + , expect = h[1] + , parsed = new TarHeader(header) + + // console.error(parsed) + t.has(parsed, expect, "parse " + name) + }) + t.end() +}) + +tap.test("encoding", function (t) { + Object.keys(headers).forEach(function (name) { + var h = headers[name] + , expect = new Buffer(h[0], "hex") + , encoded = TarHeader.encode(h[1]) + + // might have slightly different bytes, since the standard + // isn't very strict, but should have the same semantics + // checkSum will be different, but cksumValid will be true + + var th = new TarHeader(encoded) + delete h[1].block + delete h[1].needExtended + delete h[1].cksum + t.has(th, h[1], "fields "+name) + }) + t.end() +}) + +// test these manually. they're a bit rare to find in the wild +tap.test("parseNumeric tests", function (t) { + var parseNumeric = TarHeader.parseNumeric + , numbers = + { "303737373737373700": 2097151 + , "30373737373737373737373700": 8589934591 + , "303030303036343400": 420 + , "800000ffffffffffff": 281474976710655 + , "ffffff000000000001": -281474976710654 + , "ffffff000000000000": -281474976710655 + , "800000000000200000": 2097152 + , "8000000000001544c5": 1393861 + , "ffffffffffff1544c5": -15383354 } + Object.keys(numbers).forEach(function (n) { + var b = new Buffer(n, "hex") + t.equal(parseNumeric(b), numbers[n], n + " === " + numbers[n]) + }) + t.end() +}) diff --git a/test/remoting/tar/node_modules/tar/test/pack-no-proprietary.js b/test/remoting/tar/node_modules/tar/test/pack-no-proprietary.js new file mode 100644 index 0000000000..d4b03a1fe9 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/pack-no-proprietary.js @@ -0,0 +1,886 @@ +// This is exactly like test/pack.js, except that it's excluding +// any proprietary headers. +// +// This loses some information about the filesystem, but creates +// tarballs that are supported by more versions of tar, especially +// old non-spec-compliant copies of gnutar. + +// the symlink file is excluded from git, because it makes +// windows freak the hell out. +var fs = require("fs") + , path = require("path") + , symlink = path.resolve(__dirname, "fixtures/symlink") +try { fs.unlinkSync(symlink) } catch (e) {} +fs.symlinkSync("./hardlink-1", symlink) +process.on("exit", function () { + fs.unlinkSync(symlink) +}) + +var tap = require("tap") + , tar = require("../tar.js") + , pkg = require("../package.json") + , Pack = tar.Pack + , fstream = require("fstream") + , Reader = fstream.Reader + , Writer = fstream.Writer + , input = path.resolve(__dirname, "fixtures/") + , target = path.resolve(__dirname, "tmp/pack.tar") + , uid = process.getuid ? process.getuid() : 0 + , gid = process.getgid ? process.getgid() : 0 + + , entries = + + // the global header and root fixtures/ dir are going to get + // a different date each time, so omit that bit. + // Also, dev/ino values differ across machines, so that's not + // included. + [ [ 'entry', + { path: 'fixtures/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'extendedHeader', + { path: 'PaxHeader/fixtures/200cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: uid, + gid: gid, + type: 'x', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: 'fixtures/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + uid: uid, + gid: gid, + size: 200 } ] + + , [ 'entry', + { path: 'fixtures/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: uid, + gid: gid, + size: 200, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/a.txt', + mode: 420, + uid: uid, + gid: gid, + size: 257, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/b.txt', + mode: 420, + uid: uid, + gid: gid, + size: 512, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/c.txt', + mode: 420, + uid: uid, + gid: gid, + size: 513, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/cc.txt', + mode: 420, + uid: uid, + gid: gid, + size: 513, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/dir/', + mode: 488, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/dir/sub/', + mode: 488, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/foo.js', + mode: 420, + uid: uid, + gid: gid, + size: 4, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/hardlink-1', + mode: 420, + uid: uid, + gid: gid, + size: 200, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/hardlink-2', + mode: 420, + uid: uid, + gid: gid, + size: 0, + type: '1', + linkpath: 'fixtures/hardlink-1', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/omega.txt', + mode: 420, + uid: uid, + gid: gid, + size: 2, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/packtest/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/packtest/omega.txt', + mode: 420, + uid: uid, + gid: gid, + size: 2, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/packtest/star.4.html', + mode: 420, + uid: uid, + gid: gid, + size: 54081, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'extendedHeader', + { path: 'PaxHeader/fixtures/packtest/Ω.txt', + mode: 420, + uid: uid, + gid: gid, + type: 'x', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: 'fixtures/packtest/Ω.txt', + uid: uid, + gid: gid, + size: 2 } ] + + , [ 'entry', + { path: 'fixtures/packtest/Ω.txt', + mode: 420, + uid: uid, + gid: gid, + size: 2, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: uid, + gid: gid, + size: 100, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/symlink', + uid: uid, + gid: gid, + size: 0, + type: '2', + linkpath: 'hardlink-1', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'extendedHeader', + { path: 'PaxHeader/fixtures/Ω.txt', + mode: 420, + uid: uid, + gid: gid, + type: 'x', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: "fixtures/Ω.txt" + , uid: uid + , gid: gid + , size: 2 } ] + + , [ 'entry', + { path: 'fixtures/Ω.txt', + mode: 420, + uid: uid, + gid: gid, + size: 2, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + ] + + +// first, make sure that the hardlinks are actually hardlinks, or this +// won't work. Git has a way of replacing them with a copy. +var hard1 = path.resolve(__dirname, "fixtures/hardlink-1") + , hard2 = path.resolve(__dirname, "fixtures/hardlink-2") + , fs = require("fs") + +try { fs.unlinkSync(hard2) } catch (e) {} +fs.linkSync(hard1, hard2) + +tap.test("with global header", { timeout: 10000 }, function (t) { + runTest(t, true) +}) + +tap.test("without global header", { timeout: 10000 }, function (t) { + runTest(t, false) +}) + +function alphasort (a, b) { + return a === b ? 0 + : a.toLowerCase() > b.toLowerCase() ? 1 + : a.toLowerCase() < b.toLowerCase() ? -1 + : a > b ? 1 + : -1 +} + + +function runTest (t, doGH) { + var reader = Reader({ path: input + , filter: function () { + return !this.path.match(/\.(tar|hex)$/) + } + , sort: alphasort + }) + + var props = doGH ? pkg : {} + props.noProprietary = true + var pack = Pack(props) + var writer = Writer(target) + + // global header should be skipped regardless, since it has no content. + var entry = 0 + + t.ok(reader, "reader ok") + t.ok(pack, "pack ok") + t.ok(writer, "writer ok") + + pack.pipe(writer) + + var parse = tar.Parse() + t.ok(parse, "parser should be ok") + + pack.on("data", function (c) { + // console.error("PACK DATA") + if (c.length !== 512) { + // this one is too noisy, only assert if it'll be relevant + t.equal(c.length, 512, "parser should emit data in 512byte blocks") + } + parse.write(c) + }) + + pack.on("end", function () { + // console.error("PACK END") + t.pass("parser ends") + parse.end() + }) + + pack.on("error", function (er) { + t.fail("pack error", er) + }) + + parse.on("error", function (er) { + t.fail("parse error", er) + }) + + writer.on("error", function (er) { + t.fail("writer error", er) + }) + + reader.on("error", function (er) { + t.fail("reader error", er) + }) + + parse.on("*", function (ev, e) { + var wanted = entries[entry++] + if (!wanted) { + t.fail("unexpected event: "+ev) + return + } + t.equal(ev, wanted[0], "event type should be "+wanted[0]) + + if (ev !== wanted[0] || e.path !== wanted[1].path) { + console.error("wanted", wanted) + console.error([ev, e.props]) + e.on("end", function () { + console.error(e.fields) + throw "break" + }) + } + + t.has(e.props, wanted[1], "properties "+wanted[1].path) + if (wanted[2]) { + e.on("end", function () { + if (!e.fields) { + t.ok(e.fields, "should get fields") + } else { + t.has(e.fields, wanted[2], "should get expected fields") + } + }) + } + }) + + reader.pipe(pack) + + writer.on("close", function () { + t.equal(entry, entries.length, "should get all expected entries") + t.pass("it finished") + t.end() + }) + +} diff --git a/test/remoting/tar/node_modules/tar/test/pack.js b/test/remoting/tar/node_modules/tar/test/pack.js new file mode 100644 index 0000000000..0f16c07bb0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/pack.js @@ -0,0 +1,952 @@ + +// the symlink file is excluded from git, because it makes +// windows freak the hell out. +var fs = require("fs") + , path = require("path") + , symlink = path.resolve(__dirname, "fixtures/symlink") +try { fs.unlinkSync(symlink) } catch (e) {} +fs.symlinkSync("./hardlink-1", symlink) +process.on("exit", function () { + fs.unlinkSync(symlink) +}) + + +var tap = require("tap") + , tar = require("../tar.js") + , pkg = require("../package.json") + , Pack = tar.Pack + , fstream = require("fstream") + , Reader = fstream.Reader + , Writer = fstream.Writer + , input = path.resolve(__dirname, "fixtures/") + , target = path.resolve(__dirname, "tmp/pack.tar") + , uid = process.getuid ? process.getuid() : 0 + , gid = process.getgid ? process.getgid() : 0 + + , entries = + + // the global header and root fixtures/ dir are going to get + // a different date each time, so omit that bit. + // Also, dev/ino values differ across machines, so that's not + // included. + [ [ 'globalExtendedHeader', + { path: 'PaxHeader/', + mode: 438, + uid: 0, + gid: 0, + type: 'g', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' }, + { "NODETAR.author": pkg.author, + "NODETAR.name": pkg.name, + "NODETAR.description": pkg.description, + "NODETAR.version": pkg.version, + "NODETAR.repository.type": pkg.repository.type, + "NODETAR.repository.url": pkg.repository.url, + "NODETAR.main": pkg.main, + "NODETAR.scripts.test": pkg.scripts.test } ] + + , [ 'entry', + { path: 'fixtures/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'extendedHeader', + { path: 'PaxHeader/fixtures/200cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: uid, + gid: gid, + type: 'x', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: 'fixtures/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + 'NODETAR.depth': '1', + 'NODETAR.type': 'File', + nlink: 1, + uid: uid, + gid: gid, + size: 200, + 'NODETAR.blksize': '4096', + 'NODETAR.blocks': '8' } ] + + , [ 'entry', + { path: 'fixtures/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: uid, + gid: gid, + size: 200, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '', + 'NODETAR.depth': '1', + 'NODETAR.type': 'File', + nlink: 1, + 'NODETAR.blksize': '4096', + 'NODETAR.blocks': '8' } ] + + , [ 'entry', + { path: 'fixtures/a.txt', + mode: 420, + uid: uid, + gid: gid, + size: 257, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/b.txt', + mode: 420, + uid: uid, + gid: gid, + size: 512, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/c.txt', + mode: 420, + uid: uid, + gid: gid, + size: 513, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/cc.txt', + mode: 420, + uid: uid, + gid: gid, + size: 513, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/dir/', + mode: 488, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/dir/sub/', + mode: 488, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + + , [ 'entry', + { path: 'fixtures/foo.js', + mode: 420, + uid: uid, + gid: gid, + size: 4, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/hardlink-1', + mode: 420, + uid: uid, + gid: gid, + size: 200, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/hardlink-2', + mode: 420, + uid: uid, + gid: gid, + size: 0, + type: '1', + linkpath: 'fixtures/hardlink-1', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/omega.txt', + mode: 420, + uid: uid, + gid: gid, + size: 2, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/packtest/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/packtest/omega.txt', + mode: 420, + uid: uid, + gid: gid, + size: 2, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/packtest/star.4.html', + mode: 420, + uid: uid, + gid: gid, + size: 54081, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'extendedHeader', + { path: 'PaxHeader/fixtures/packtest/Ω.txt', + mode: 420, + uid: uid, + gid: gid, + type: 'x', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: 'fixtures/packtest/Ω.txt', + 'NODETAR.depth': '2', + 'NODETAR.type': 'File', + nlink: 1, + uid: uid, + gid: gid, + size: 2, + 'NODETAR.blksize': '4096', + 'NODETAR.blocks': '8' } ] + + , [ 'entry', + { path: 'fixtures/packtest/Ω.txt', + mode: 420, + uid: uid, + gid: gid, + size: 2, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '', + 'NODETAR.depth': '2', + 'NODETAR.type': 'File', + nlink: 1, + 'NODETAR.blksize': '4096', + 'NODETAR.blocks': '8' } ] + + , [ 'entry', + { path: 'fixtures/r/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/', + mode: 493, + uid: uid, + gid: gid, + size: 0, + type: '5', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: uid, + gid: gid, + size: 100, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'entry', + { path: 'fixtures/symlink', + uid: uid, + gid: gid, + size: 0, + type: '2', + linkpath: 'hardlink-1', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' } ] + + , [ 'extendedHeader', + { path: 'PaxHeader/fixtures/Ω.txt', + mode: 420, + uid: uid, + gid: gid, + type: 'x', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: "fixtures/Ω.txt" + , "NODETAR.depth": "1" + , "NODETAR.type": "File" + , nlink: 1 + , uid: uid + , gid: gid + , size: 2 + , "NODETAR.blksize": "4096" + , "NODETAR.blocks": "8" } ] + + , [ 'entry', + { path: 'fixtures/Ω.txt', + mode: 420, + uid: uid, + gid: gid, + size: 2, + type: '0', + linkpath: '', + ustar: 'ustar\u0000', + ustarver: '00', + uname: '', + gname: '', + devmaj: 0, + devmin: 0, + fill: '', + 'NODETAR.depth': '1', + 'NODETAR.type': 'File', + nlink: 1, + 'NODETAR.blksize': '4096', + 'NODETAR.blocks': '8' } ] + ] + + +// first, make sure that the hardlinks are actually hardlinks, or this +// won't work. Git has a way of replacing them with a copy. +var hard1 = path.resolve(__dirname, "fixtures/hardlink-1") + , hard2 = path.resolve(__dirname, "fixtures/hardlink-2") + , fs = require("fs") + +try { fs.unlinkSync(hard2) } catch (e) {} +fs.linkSync(hard1, hard2) + +tap.test("with global header", { timeout: 10000 }, function (t) { + runTest(t, true) +}) + +tap.test("without global header", { timeout: 10000 }, function (t) { + runTest(t, false) +}) + +tap.test("with from base", { timeout: 10000 }, function (t) { + runTest(t, true, true) +}) + +function alphasort (a, b) { + return a === b ? 0 + : a.toLowerCase() > b.toLowerCase() ? 1 + : a.toLowerCase() < b.toLowerCase() ? -1 + : a > b ? 1 + : -1 +} + + +function runTest (t, doGH, doFromBase) { + var reader = Reader({ path: input + , filter: function () { + return !this.path.match(/\.(tar|hex)$/) + } + , sort: alphasort + }) + + var props = doGH ? pkg : {} + if(doFromBase) props.fromBase = true; + + var pack = Pack(props) + var writer = Writer(target) + + // skip the global header if we're not doing that. + var entry = doGH ? 0 : 1 + + t.ok(reader, "reader ok") + t.ok(pack, "pack ok") + t.ok(writer, "writer ok") + + pack.pipe(writer) + + var parse = tar.Parse() + t.ok(parse, "parser should be ok") + + pack.on("data", function (c) { + // console.error("PACK DATA") + if (c.length !== 512) { + // this one is too noisy, only assert if it'll be relevant + t.equal(c.length, 512, "parser should emit data in 512byte blocks") + } + parse.write(c) + }) + + pack.on("end", function () { + // console.error("PACK END") + t.pass("parser ends") + parse.end() + }) + + pack.on("error", function (er) { + t.fail("pack error", er) + }) + + parse.on("error", function (er) { + t.fail("parse error", er) + }) + + writer.on("error", function (er) { + t.fail("writer error", er) + }) + + reader.on("error", function (er) { + t.fail("reader error", er) + }) + + parse.on("*", function (ev, e) { + var wanted = entries[entry++] + if (!wanted) { + t.fail("unexpected event: "+ev) + return + } + t.equal(ev, wanted[0], "event type should be "+wanted[0]) + + if(doFromBase) { + if(wanted[1].path.indexOf('fixtures/') && wanted[1].path.length == 100) + wanted[1].path = wanted[1].path.replace('fixtures/', '') + 'ccccccccc' + + if(wanted[1]) wanted[1].path = wanted[1].path.replace('fixtures/', '').replace('//', '/') + if(wanted[1].path == '') wanted[1].path = '/' + if(wanted[2] && wanted[2].path) wanted[2].path = wanted[2].path.replace('fixtures', '').replace(/^\//, '') + + wanted[1].linkpath = wanted[1].linkpath.replace('fixtures/', '') + } + + if (ev !== wanted[0] || e.path !== wanted[1].path) { + console.error("wanted", wanted) + console.error([ev, e.props]) + e.on("end", function () { + console.error(e.fields) + throw "break" + }) + } + + + t.has(e.props, wanted[1], "properties "+wanted[1].path) + if (wanted[2]) { + e.on("end", function () { + if (!e.fields) { + t.ok(e.fields, "should get fields") + } else { + t.has(e.fields, wanted[2], "should get expected fields") + } + }) + } + }) + + reader.pipe(pack) + + writer.on("close", function () { + t.equal(entry, entries.length, "should get all expected entries") + t.pass("it finished") + t.end() + }) + +} diff --git a/test/remoting/tar/node_modules/tar/test/parse-discard.js b/test/remoting/tar/node_modules/tar/test/parse-discard.js new file mode 100644 index 0000000000..da01a65ccc --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/parse-discard.js @@ -0,0 +1,29 @@ +var tap = require("tap") + , tar = require("../tar.js") + , fs = require("fs") + , path = require("path") + , file = path.resolve(__dirname, "fixtures/c.tar") + +tap.test("parser test", function (t) { + var parser = tar.Parse() + var total = 0 + var dataTotal = 0 + + parser.on("end", function () { + + t.equals(total-513,dataTotal,'should have discarded only c.txt') + + t.end() + }) + + fs.createReadStream(file) + .pipe(parser) + .on('entry',function(entry){ + if(entry.path === 'c.txt') entry.abort() + + total += entry.size; + entry.on('data',function(data){ + dataTotal += data.length + }) + }) +}) diff --git a/test/remoting/tar/node_modules/tar/test/parse.js b/test/remoting/tar/node_modules/tar/test/parse.js new file mode 100644 index 0000000000..f765a50129 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/parse.js @@ -0,0 +1,359 @@ +var tap = require("tap") + , tar = require("../tar.js") + , fs = require("fs") + , path = require("path") + , file = path.resolve(__dirname, "fixtures/c.tar") + , index = 0 + + , expect = +[ [ 'entry', + { path: 'c.txt', + mode: 420, + uid: 24561, + gid: 20, + size: 513, + mtime: new Date('Wed, 26 Oct 2011 01:10:58 GMT'), + cksum: 5422, + type: '0', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '' }, + undefined ], + [ 'entry', + { path: 'cc.txt', + mode: 420, + uid: 24561, + gid: 20, + size: 513, + mtime: new Date('Wed, 26 Oct 2011 01:11:02 GMT'), + cksum: 5525, + type: '0', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '' }, + undefined ], + [ 'entry', + { path: 'r/e/a/l/l/y/-/d/e/e/p/-/f/o/l/d/e/r/-/p/a/t/h/cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: 24561, + gid: 20, + size: 100, + mtime: new Date('Thu, 27 Oct 2011 03:43:23 GMT'), + cksum: 18124, + type: '0', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '' }, + undefined ], + [ 'entry', + { path: 'Ω.txt', + mode: 420, + uid: 24561, + gid: 20, + size: 2, + mtime: new Date('Thu, 27 Oct 2011 17:51:49 GMT'), + cksum: 5695, + type: '0', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '' }, + undefined ], + [ 'extendedHeader', + { path: 'PaxHeader/Ω.txt', + mode: 420, + uid: 24561, + gid: 20, + size: 120, + mtime: new Date('Thu, 27 Oct 2011 17:51:49 GMT'), + cksum: 6702, + type: 'x', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: 'Ω.txt', + ctime: 1319737909, + atime: 1319739061, + dev: 234881026, + ino: 51693379, + nlink: 1 } ], + [ 'entry', + { path: 'Ω.txt', + mode: 420, + uid: 24561, + gid: 20, + size: 2, + mtime: new Date('Thu, 27 Oct 2011 17:51:49 GMT'), + cksum: 5695, + type: '0', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '', + ctime: new Date('Thu, 27 Oct 2011 17:51:49 GMT'), + atime: new Date('Thu, 27 Oct 2011 18:11:01 GMT'), + dev: 234881026, + ino: 51693379, + nlink: 1 }, + undefined ], + [ 'extendedHeader', + { path: 'PaxHeader/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: 24561, + gid: 20, + size: 353, + mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'), + cksum: 14488, + type: 'x', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + ctime: 1319686868, + atime: 1319741254, + 'LIBARCHIVE.creationtime': '1319686852', + dev: 234881026, + ino: 51681874, + nlink: 1 } ], + [ 'entry', + { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: 24561, + gid: 20, + size: 200, + mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'), + cksum: 14570, + type: '0', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '', + ctime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'), + atime: new Date('Thu, 27 Oct 2011 18:47:34 GMT'), + 'LIBARCHIVE.creationtime': '1319686852', + dev: 234881026, + ino: 51681874, + nlink: 1 }, + undefined ], + [ 'longPath', + { path: '././@LongLink', + mode: 0, + uid: 0, + gid: 0, + size: 201, + mtime: new Date('Thu, 01 Jan 1970 00:00:00 GMT'), + cksum: 4976, + type: 'L', + linkpath: '', + ustar: false }, + '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc' ], + [ 'entry', + { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: 1000, + gid: 1000, + size: 201, + mtime: new Date('Thu, 27 Oct 2011 22:21:50 GMT'), + cksum: 14086, + type: '0', + linkpath: '', + ustar: false }, + undefined ], + [ 'longLinkpath', + { path: '././@LongLink', + mode: 0, + uid: 0, + gid: 0, + size: 201, + mtime: new Date('Thu, 01 Jan 1970 00:00:00 GMT'), + cksum: 4975, + type: 'K', + linkpath: '', + ustar: false }, + '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc' ], + [ 'longPath', + { path: '././@LongLink', + mode: 0, + uid: 0, + gid: 0, + size: 201, + mtime: new Date('Thu, 01 Jan 1970 00:00:00 GMT'), + cksum: 4976, + type: 'L', + linkpath: '', + ustar: false }, + '200LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL' ], + [ 'entry', + { path: '200LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL', + mode: 511, + uid: 1000, + gid: 1000, + size: 0, + mtime: new Date('Fri, 28 Oct 2011 23:05:17 GMT'), + cksum: 21603, + type: '2', + linkpath: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + ustar: false }, + undefined ], + [ 'extendedHeader', + { path: 'PaxHeader/200-hard', + mode: 420, + uid: 24561, + gid: 20, + size: 143, + mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'), + cksum: 6533, + type: 'x', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '' }, + { ctime: 1320617144, + atime: 1320617232, + 'LIBARCHIVE.creationtime': '1319686852', + dev: 234881026, + ino: 51681874, + nlink: 2 } ], + [ 'entry', + { path: '200-hard', + mode: 420, + uid: 24561, + gid: 20, + size: 200, + mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'), + cksum: 5526, + type: '0', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '', + ctime: new Date('Sun, 06 Nov 2011 22:05:44 GMT'), + atime: new Date('Sun, 06 Nov 2011 22:07:12 GMT'), + 'LIBARCHIVE.creationtime': '1319686852', + dev: 234881026, + ino: 51681874, + nlink: 2 }, + undefined ], + [ 'extendedHeader', + { path: 'PaxHeader/200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: 24561, + gid: 20, + size: 353, + mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'), + cksum: 14488, + type: 'x', + linkpath: '', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '' }, + { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + ctime: 1320617144, + atime: 1320617406, + 'LIBARCHIVE.creationtime': '1319686852', + dev: 234881026, + ino: 51681874, + nlink: 2 } ], + [ 'entry', + { path: '200ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + mode: 420, + uid: 24561, + gid: 20, + size: 0, + mtime: new Date('Thu, 27 Oct 2011 03:41:08 GMT'), + cksum: 15173, + type: '1', + linkpath: '200-hard', + ustar: 'ustar\0', + ustarver: '00', + uname: 'isaacs', + gname: 'staff', + devmaj: 0, + devmin: 0, + fill: '', + ctime: new Date('Sun, 06 Nov 2011 22:05:44 GMT'), + atime: new Date('Sun, 06 Nov 2011 22:10:06 GMT'), + 'LIBARCHIVE.creationtime': '1319686852', + dev: 234881026, + ino: 51681874, + nlink: 2 }, + undefined ] ] + + +tap.test("parser test", function (t) { + var parser = tar.Parse() + + parser.on("end", function () { + t.equal(index, expect.length, "saw all expected events") + t.end() + }) + + fs.createReadStream(file) + .pipe(parser) + .on("*", function (ev, entry) { + var wanted = expect[index] + if (!wanted) { + return t.fail("Unexpected event: " + ev) + } + var result = [ev, entry.props] + entry.on("end", function () { + result.push(entry.fields || entry.body) + + t.equal(ev, wanted[0], index + " event type") + t.equivalent(entry.props, wanted[1], wanted[1].path + " entry properties") + if (wanted[2]) { + t.equivalent(result[2], wanted[2], "metadata values") + } + index ++ + }) + }) +}) diff --git a/test/remoting/tar/node_modules/tar/test/zz-cleanup.js b/test/remoting/tar/node_modules/tar/test/zz-cleanup.js new file mode 100644 index 0000000000..a00ff7faa0 --- /dev/null +++ b/test/remoting/tar/node_modules/tar/test/zz-cleanup.js @@ -0,0 +1,20 @@ +// clean up the fixtures + +var tap = require("tap") +, rimraf = require("rimraf") +, test = tap.test +, path = require("path") + +test("clean fixtures", function (t) { + rimraf(path.resolve(__dirname, "fixtures"), function (er) { + t.ifError(er, "rimraf ./fixtures/") + t.end() + }) +}) + +test("clean tmp", function (t) { + rimraf(path.resolve(__dirname, "tmp"), function (er) { + t.ifError(er, "rimraf ./tmp/") + t.end() + }) +}) diff --git a/test/remoting/tar/package.json b/test/remoting/tar/package.json new file mode 100644 index 0000000000..6ce1ee4422 --- /dev/null +++ b/test/remoting/tar/package.json @@ -0,0 +1,5 @@ +{ + "name":"test-tar", + "main":"index.html", + "dependencies":{ "tar" : "latest" } +} diff --git a/test/remoting/tar/test.py b/test/remoting/tar/test.py new file mode 100644 index 0000000000..009fcace78 --- /dev/null +++ b/test/remoting/tar/test.py @@ -0,0 +1,23 @@ +import time +import os +import shutil + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +testdir = os.path.dirname(os.path.abspath(__file__)) +chrome_options.add_argument("nwapp=" + testdir) + +datadir = os.path.join(testdir, "extract") +if os.path.isdir(datadir) : + shutil.rmtree(datadir) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(2) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() diff --git a/test/remoting/tar/test.tar b/test/remoting/tar/test.tar new file mode 100644 index 0000000000000000000000000000000000000000..a7b9de77adbf6cef7603a00a4cc6f6209fe24ee2 GIT binary patch literal 716800 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 literal 0 HcmV?d00001 From d6ca311b0b33905fcf96de1375ffd424a56a2df4 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 23 Oct 2015 14:12:20 +0800 Subject: [PATCH 022/404] strip binary before test In 32bit system the test will fail since the binary exceeds 2GB --- nw.gypi | 1 + 1 file changed, 1 insertion(+) diff --git a/nw.gypi b/nw.gypi index 6d3920c92e..18559278bd 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1035,6 +1035,7 @@ 'type': 'none', 'dependencies': [ '<(DEPTH)/chrome/chrome.gyp:chromedriver', + 'dist', ], 'actions': [ { From 0282bd25b32659645371d8ff743aa0f334825991 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 23 Oct 2015 14:28:40 +0800 Subject: [PATCH 023/404] [test] set default timeout to 2 mins --- nw.gypi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nw.gypi b/nw.gypi index 18559278bd..479e93e4d3 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1050,7 +1050,7 @@ '<(PRODUCT_DIR)/run_tests.re', ], 'action': ['python', '<(test_script)', '-d', '<(PRODUCT_DIR)', - 'remoting'], + '-t', '120', 'remoting'], }, ], }, From 54076b3493b546ff7ea030cd9e3d481736dbbe9e Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 23 Oct 2015 14:45:22 +0800 Subject: [PATCH 024/404] Revert "strip binary before test" This reverts commit d6ca311b0b33905fcf96de1375ffd424a56a2df4. We are doing dist first in buildbot. --- nw.gypi | 1 - 1 file changed, 1 deletion(-) diff --git a/nw.gypi b/nw.gypi index 479e93e4d3..7e1c3568fb 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1035,7 +1035,6 @@ 'type': 'none', 'dependencies': [ '<(DEPTH)/chrome/chrome.gyp:chromedriver', - 'dist', ], 'actions': [ { From b5105c798b9c9bc1bcd73e6a8b99790d605991fe Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sat, 24 Oct 2015 06:59:15 +0800 Subject: [PATCH 025/404] [test] fix tar case --- test/remoting/tar/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/remoting/tar/test.py b/test/remoting/tar/test.py index 009fcace78..093ed0b284 100644 --- a/test/remoting/tar/test.py +++ b/test/remoting/tar/test.py @@ -15,7 +15,7 @@ driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) try: print driver.current_url - time.sleep(2) + time.sleep(5) result = driver.find_element_by_id('result') print result.get_attribute('innerHTML') assert("success" in result.get_attribute('innerHTML')) From de4e68526f080f52ea1060807300c82ec3a8381d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 25 Oct 2015 07:29:04 +0800 Subject: [PATCH 026/404] [README] update for 0.13.0-alpha4 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9c8facc379..9dcd0571e3 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,11 @@ It was created in the Intel Open Source Technology Center. * Windows: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-x64.zip) * Mac 10.7+: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-x64.zip) -* **v0.13.0-alpha2:** (Jun 23, 2015, based off of IO.js v1.5.1, Chromium 43.0.2357.45): [release notes](https://groups.google.com/d/msg/nwjs-general/zeC6SedUSFY/BlumrYJeVHYJ) +* **v0.13.0-alpha4:** (Jun 23, 2015, based off of Node.js v4.1.2, Chromium 45.0.2454.85): [release notes](https://groups.google.com/d/msg/nwjs-general/zzszAUlDSVM/0asr0DJPBQAJ) **NOTE** You might want the **SDK build**. Please read the release notes - * Linux: [32bit](http://dl.nwjs.io/v0.13.0/alpha2/nwjs-v0.13.0-alpha2-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0/alpha2/nwjs-v0.13.0-alpha2-linux-x64.tar.gz) - * Windows: [32bit](http://dl.nwjs.io/v0.13.0/alpha2/nwjs-v0.13.0-alpha2-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0/alpha2/nwjs-v0.13.0-alpha2-win-x64.zip) - * Mac 10.7+: [32bit](http://dl.nwjs.io/v0.13.0/alpha2/nwjs-v0.13.0-alpha2-osx-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0/alpha2/nwjs-v0.13.0-alpha2-osx-x64.zip) + * Linux: [32bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-linux-x64.tar.gz) + * Windows: [32bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-win-x64.zip) + * Mac 10.7+: [64bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-osx-x64.zip) * **0.8.6:** (Apr 18, 2014, based off of Node v0.10.22, Chrome 30.0.1599.66) **If your native Node module works only with Node v0.10, then you should use node-webkit v0.8.x, which is also a maintained branch. [More info](https://groups.google.com/d/msg/nwjs-general/2OJ1cEMPLlA/09BvpTagSA0J)** [release notes](https://groups.google.com/d/msg/nwjs-general/CLPkgfV-i7s/hwkkQuJ1kngJ) From 1ccd55f47c71f856a73f23ed96e6561ae50e5412 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 27 Oct 2015 12:50:46 +0800 Subject: [PATCH 027/404] rebase fix for chrome 46 --- nw.gypi | 4 +--- src/api/nw_window_api.cc | 10 +++++----- src/api/nw_window_api.h | 4 ++-- src/api/schemas.gypi | 1 + src/nw_content.cc | 15 ++++++++------- src/resources/api_nw_window.js | 2 +- src/resources/pages/nw_version.html | 2 +- test/remoting/capture_page/index.html | 13 +++++++------ tools/commit_id.py | 2 +- 9 files changed, 27 insertions(+), 26 deletions(-) diff --git a/nw.gypi b/nw.gypi index e467308f21..5b6668fc69 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1229,9 +1229,7 @@ '<(DEPTH)/content/nw/.git/index', '<(DEPTH)/.git/index', '<(DEPTH)/v8/.git/index', - '<(DEPTH)/third_party/node/.git/index', - '<(DEPTH)/third_party/WebKit/.git/index', - '<(DEPTH)/breakpad/src/.git/index' ], + '<(DEPTH)/third_party/node/.git/index'], 'outputs': [ '<(nw_id_header)' ], 'msvs_cygwin_shell': 0, 'action': diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index cc10162021..8a72e498fb 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -137,8 +137,8 @@ bool NwCurrentWindowInternalCapturePageInternalFunction::RunAsync() { return false; // The default format and quality setting used when encoding jpegs. - const core_api::extension_types::ImageFormat kDefaultFormat = - core_api::extension_types::IMAGE_FORMAT_JPEG; + const api::extension_types::ImageFormat kDefaultFormat = + api::extension_types::IMAGE_FORMAT_JPEG; const int kDefaultQuality = 90; image_format_ = kDefaultFormat; @@ -146,7 +146,7 @@ bool NwCurrentWindowInternalCapturePageInternalFunction::RunAsync() { if (image_details) { if (image_details->format != - core_api::extension_types::IMAGE_FORMAT_NONE) + api::extension_types::IMAGE_FORMAT_NONE) image_format_ = image_details->format; if (image_details->quality.get()) image_quality_ = *image_details->quality; @@ -197,7 +197,7 @@ void NwCurrentWindowInternalCapturePageInternalFunction::OnCaptureSuccess(const bool encoded = false; std::string mime_type; switch (image_format_) { - case core_api::extension_types::IMAGE_FORMAT_JPEG: + case api::extension_types::IMAGE_FORMAT_JPEG: encoded = gfx::JPEGCodec::Encode( reinterpret_cast(bitmap.getAddr32(0, 0)), gfx::JPEGCodec::FORMAT_SkBitmap, @@ -208,7 +208,7 @@ void NwCurrentWindowInternalCapturePageInternalFunction::OnCaptureSuccess(const &data); mime_type = kMimeTypeJpeg; break; - case core_api::extension_types::IMAGE_FORMAT_PNG: + case api::extension_types::IMAGE_FORMAT_PNG: encoded = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, // Discard transparency. diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index d292415fa5..8268505837 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -47,14 +47,14 @@ class NwCurrentWindowInternalCapturePageInternalFunction : public AsyncExtension DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.capturePageInternal", UNKNOWN) private: - typedef core_api::extension_types::ImageDetails ImageDetails; + typedef api::extension_types::ImageDetails ImageDetails; void CopyFromBackingStoreComplete(const SkBitmap& bitmap, content::ReadbackResponse response); void OnCaptureSuccess(const SkBitmap& bitmap); // The format (JPEG vs PNG) of the resulting image. Set in RunAsync(). - core_api::extension_types::ImageFormat image_format_; + api::extension_types::ImageFormat image_format_; // Quality setting to use when encoding jpegs. Set in RunAsync(). int image_quality_; diff --git a/src/api/schemas.gypi b/src/api/schemas.gypi index 60d8d0dc96..a462bbd403 100644 --- a/src/api/schemas.gypi +++ b/src/api/schemas.gypi @@ -26,6 +26,7 @@ 'chromium_code': 1, 'cc_dir': 'content/nw/src/api', 'root_namespace': 'extensions::nwapi::%(namespace)s', + 'bundle_name': 'nwjs', 'impl_dir_': 'content/nw/src/api', }, } diff --git a/src/nw_content.cc b/src/nw_content.cc index a08b046148..6d3497d61c 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -39,7 +39,7 @@ #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebScriptSource.h" -#include "chrome/common/chrome_version_info_values.h" +#include "chrome/common/chrome_constants.h" #include "ui/base/ui_base_types.h" #include "ui/gfx/image/image.h" @@ -296,11 +296,12 @@ void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { g_start_nw_instance_fn(argc, argv, dom_context); { - v8::Local script = v8::Script::Compile(v8::String::NewFromUtf8(isolate, - "process.versions['nwjs'] = '" NW_VERSION_STRING "';" - "process.versions['node-webkit'] = '" NW_VERSION_STRING "';" - "process.versions['nw-commit-id'] = '" NW_COMMIT_HASH "';" - "process.versions['chromium'] = '" PRODUCT_VERSION "';" + v8::Local script = + v8::Script::Compile(v8::String::NewFromUtf8(isolate, + (std::string("process.versions['nwjs'] = '" NW_VERSION_STRING "';") + + "process.versions['node-webkit'] = '" NW_VERSION_STRING "';" + "process.versions['nw-commit-id'] = '" NW_COMMIT_HASH "';" + "process.versions['chromium'] = '" + chrome::kChromeVersion + "';").c_str() )); script->Run(); } @@ -450,7 +451,7 @@ void LoadNWAppAsExtensionHook(base::DictionaryValue* manifest, std::string* erro } if (manifest->GetString(manifest_keys::kNWJSMain, &main_url)) { - if (base::EndsWith(main_url, ".js", false)) { + if (base::EndsWith(main_url, ".js", base::CompareCase::INSENSITIVE_ASCII)) { AmendManifestStringList(manifest, manifest_keys::kPlatformAppBackgroundScripts, main_url); manifest->SetString(manifest_keys::kNWJSInternalMainFilename, main_url); }else diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 29abb4bb15..8fd60e59f3 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -240,7 +240,7 @@ function onNavigation(frame, url, policy, context) { } function onLoadingStateChanged(status) { - // console.log("onLoadingStateChanged: " + status); + //console.log("onLoadingStateChanged: " + status); if (!currentNWWindow) return; dispatchEventIfExists(currentNWWindow, "LoadingStateChanged", [status]); diff --git a/src/resources/pages/nw_version.html b/src/resources/pages/nw_version.html index accc31af37..dac7924562 100644 --- a/src/resources/pages/nw_version.html +++ b/src/resources/pages/nw_version.html @@ -5,7 +5,7 @@ nw.js v
- io.js v
+ Node v
Chromium
commit hash:
diff --git a/test/remoting/capture_page/index.html b/test/remoting/capture_page/index.html index 8d0e02ef9b..8e0a96f15e 100644 --- a/test/remoting/capture_page/index.html +++ b/test/remoting/capture_page/index.html @@ -31,18 +31,19 @@

If you see a capture in popup window, this case passes Date: Wed, 21 Oct 2015 11:54:42 +0700 Subject: [PATCH 028/404] [Tray] fixes for nw13 --- nw.gypi | 1 + src/api/_api_features.json | 4 + src/api/nw_tray_api.h | 17 +--- src/api/object_manager.cc | 8 +- src/api/tray/tray_aura.cc | 4 +- src/api/tray/tray_mac.mm | 14 +-- src/nw_custom_bindings.cc | 6 ++ src/resources/api_nw_menu.js | 2 +- src/resources/api_nw_tray.js | 182 +++++++++++++++++++++++++++++++++++ 9 files changed, 210 insertions(+), 28 deletions(-) create mode 100644 src/resources/api_nw_tray.js diff --git a/nw.gypi b/nw.gypi index e467308f21..7535309098 100644 --- a/nw.gypi +++ b/nw.gypi @@ -704,6 +704,7 @@ 'src/api/menu/menu_delegate_mac.mm', 'src/api/menuitem/menuitem_delegate_mac.h', 'src/api/menuitem/menuitem_delegate_mac.mm', + 'src/api/tray/tray_mac.mm', 'src/nw_content_mac.h', 'src/nw_content_mac.mm', ], diff --git a/src/api/_api_features.json b/src/api/_api_features.json index 03c0877c33..3dd1cd72c3 100644 --- a/src/api/_api_features.json +++ b/src/api/_api_features.json @@ -35,6 +35,10 @@ "channel": "stable", "contexts": ["blessed_extension"] }, + "nw.Tray": { + "channel": "stable", + "contexts": ["blessed_extension"] + }, "nw.currentWindowInternal": { "noparent": true, "internal": true, diff --git a/src/api/nw_tray_api.h b/src/api/nw_tray_api.h index a72aa18413..5ecf533e9f 100644 --- a/src/api/nw_tray_api.h +++ b/src/api/nw_tray_api.h @@ -1,5 +1,5 @@ -#ifndef NW_API_MENU_API_H_ -#define NW_API_MENU_API_H_ +#ifndef NW_API_TRAY_API_H_ +#define NW_API_TRAY_API_H_ #include @@ -7,18 +7,5 @@ namespace extensions { -class NwMenuCreateItemFunction : public NWSyncExtensionFunction { - public: - NwMenuCreateItemFunction(); - bool RunNWSync(base::ListValue* response, std::string* error) override; - - protected: - ~NwMenuCreateItemFunction() override; - - DECLARE_EXTENSION_FUNCTION("nw.Menu.createItem", UNKNOWN) - private: - DISALLOW_COPY_AND_ASSIGN(NwMenuCreateItemFunction); -}; - } // namespace extensions #endif diff --git a/src/api/object_manager.cc b/src/api/object_manager.cc index dda1f56aab..d9269049f2 100644 --- a/src/api/object_manager.cc +++ b/src/api/object_manager.cc @@ -37,7 +37,7 @@ //#include "content/nw/src/api/screen/screen.h" #include "content/nw/src/api/shell/shell.h" //#include "content/nw/src/api/shortcut/shortcut.h" -//#include "content/nw/src/api/tray/tray.h" +#include "content/nw/src/api/tray/tray.h" #include "content/nw/src/common/shell_switches.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -96,11 +96,11 @@ void ObjectManager::OnAllocateObject(int object_id, } else if (type == "MenuItem") { objects_registry_.AddWithID( new MenuItem(object_id, weak_ptr_factory_.GetWeakPtr(), option, extension_id), object_id); + } else if (type == "Tray") { + objects_registry_.AddWithID(new Tray(object_id, weak_ptr_factory_.GetWeakPtr(), option, extension_id), object_id); } #if 0 - else if (type == "Tray") { - objects_registry_.AddWithID(new Tray(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); - } else if (type == "Clipboard") { + else if (type == "Clipboard") { objects_registry_.AddWithID( new Clipboard(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else if (type == "Window") { diff --git a/src/api/tray/tray_aura.cc b/src/api/tray/tray_aura.cc index e144a33a5b..6378715058 100644 --- a/src/api/tray/tray_aura.cc +++ b/src/api/tray/tray_aura.cc @@ -22,6 +22,7 @@ #include "base/files/file_path.h" #include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" #include "base/values.h" #include "chrome/browser/status_icons/status_icon.h" #include "chrome/browser/status_icons/status_icon_observer.h" @@ -55,7 +56,7 @@ class TrayObserver : public StatusIconObserver { data->SetInteger("x", cursor_pos.x()); data->SetInteger("y", cursor_pos.y()); args.Append(data); - tray_->object_manager()->SendEvent(tray_, "click", args); + tray_->object_manager()->SendEvent(tray_, "TrayClick", args); } private: @@ -86,6 +87,7 @@ void Tray::SetTitle(const std::string& title) { void Tray::SetIcon(const std::string& path) { gfx::Image icon; nw::Package* package = nw::InitNWPackage(); + base::ThreadRestrictions::ScopedAllowIO allowIO; nw::GetImage(package, base::FilePath::FromUTF8Unsafe(path), &icon); if (!icon.IsEmpty()) diff --git a/src/api/tray/tray_mac.mm b/src/api/tray/tray_mac.mm index 8c8575306d..762fb524d4 100644 --- a/src/api/tray/tray_mac.mm +++ b/src/api/tray/tray_mac.mm @@ -23,20 +23,20 @@ #include "base/values.h" #import #include "ui/gfx/screen.h" -#include "content/nw/src/api/dispatcher_host.h" #include "content/nw/src/api/menu/menu.h" +#include "content/nw/src/api/object_manager.h" @interface MacTrayObserver : NSObject { @private - nwapi::Tray* tray_; + nw::Tray* tray_; } -- (void)setBacking:(nwapi::Tray*)tray_; +- (void)setBacking:(nw::Tray*)tray_; - (void)onClick:(id)sender; @end @implementation MacTrayObserver -- (void)setBacking:(nwapi::Tray*)newTray { +- (void)setBacking:(nw::Tray*)newTray { tray_ = newTray; } - (void)onClick:(id)sender { @@ -50,11 +50,11 @@ - (void)onClick:(id)sender { data->SetInteger("x", pos.x); data->SetInteger("y", pos.y); args.Append(data); - tray_->dispatcher_host()->SendEvent(tray_,"click",args); + tray_->object_manager()->SendEvent(tray_,"TrayClick",args); } @end -namespace nwapi { +namespace nw { void Tray::Create(const base::DictionaryValue& option) { NSStatusBar *status_bar = [NSStatusBar systemStatusBar]; @@ -131,4 +131,4 @@ - (void)onClick:(id)sender { [[NSStatusBar systemStatusBar] removeStatusItem:status_item_]; } -} // namespace nwapi +} // namespace nw diff --git a/src/nw_custom_bindings.cc b/src/nw_custom_bindings.cc index c9360dab58..c761f405db 100644 --- a/src/nw_custom_bindings.cc +++ b/src/nw_custom_bindings.cc @@ -65,6 +65,12 @@ bool MakePathAbsolute(base::FilePath* file_path) { if (!current_directory.IsAbsolute()) return false; +#ifdef OS_LINUX + //linux might gives "/" as current_directory, return false + if (current_directory.value().length() <= 1) + return false; +#endif + *file_path = current_directory.Append(*file_path); return true; } diff --git a/src/resources/api_nw_menu.js b/src/resources/api_nw_menu.js index 3ae4f1efd5..dd31330398 100644 --- a/src/resources/api_nw_menu.js +++ b/src/resources/api_nw_menu.js @@ -9,7 +9,7 @@ var Event = require('event_bindings').Event; var menuItems = { objs : {}, clickEvent: {} }; -var Menu = function(id, option) { +var Menu = function Menu (id, option) { if (option.type != 'contextmenu' && option.type != 'menubar') throw new TypeError('Invalid menu type: ' + option.type); diff --git a/src/resources/api_nw_tray.js b/src/resources/api_nw_tray.js new file mode 100644 index 0000000000..d3bf66a3be --- /dev/null +++ b/src/resources/api_nw_tray.js @@ -0,0 +1,182 @@ +var Binding = require('binding').Binding; +var forEach = require('utils').forEach; +var nw_binding = require('binding').Binding.create('nw.Tray'); +var nwNative = requireNative('nw_natives'); +var sendRequest = require('sendRequest'); +var contextMenuNatives = requireNative('context_menus'); +var messagingNatives = requireNative('messaging_natives'); +var Event = require('event_bindings').Event; + +var trayEvents = { objs: {}, clickEvent: {} }; + +function Tray(id, option) { + if (typeof option != 'object') + throw new TypeError('Invalid option'); + + if (!option.hasOwnProperty('title') && !option.hasOwnProperty('icon')) + throw new TypeError("Must set 'title' or 'icon' field in option"); + + if (!option.hasOwnProperty('title')) + option.title = ''; + else + option.title = String(option.title); + + if (option.hasOwnProperty('icon')) { + option.shadowIcon = String(option.icon); + option.icon = nwNative.getAbsolutePath(option.icon); + } + + if (option.hasOwnProperty('alticon')) { + option.shadowAlticon = String(option.alticon); + option.alticon = nwNative.getAbsolutePath(option.alticon); + } + + if (option.hasOwnProperty('iconsAreTemplates')) + option.iconsAreTemplates = Boolean(option.iconsAreTemplates); + else + option.iconsAreTemplates = true; + + if (option.hasOwnProperty('tooltip')) + option.tooltip = String(option.tooltip); + + if (option.hasOwnProperty('click')) { + if (typeof option.click != 'function') { + throw new TypeError("'click' must be a valid Function"); + } else { + this.click = option.click; + } + } + + if (option.hasOwnProperty('menu')) { + if (option.menu.constructor.name != 'Menu') + throw new TypeError("'menu' must be a valid Menu"); + + // Transfer only object id + privates(this).menu = option.menu; + option.menu = option.menu.id; + } + + this.id = id; + privates(this).option = option; + + // All properties must be set after initialization. + if (!option.hasOwnProperty('icon')) + option.shadowIcon = ''; + if (!option.hasOwnProperty('alticon')) + option.shadowAlticon = ''; + if (!option.hasOwnProperty('tooltip')) + option.tooltip = ''; +} + +Tray.prototype.handleGetter = function(name) { + return privates(this).option[name]; +}; + +Tray.prototype.handleSetter = function(name, setter, type, value) { + value = type(value); + privates(this).option[name] = value; + nw.Object.callObjectMethod(this.id, 'Tray', setter, [ value ]); +}; + +Tray.prototype.__defineGetter__('title', function() { + return this.handleGetter('title'); +}); + +Tray.prototype.__defineSetter__('title', function(val) { + this.handleSetter('title', 'SetTitle', String, val); +}); + +Tray.prototype.__defineGetter__('icon', function() { + return this.handleGetter('shadowIcon'); +}); + +Tray.prototype.__defineGetter__('alticon', function() { + return this.handleGetter('shadowAlticon'); +}); + +Tray.prototype.__defineSetter__('icon', function(val) { + privates(this).option.shadowIcon = String(val); + var real_path = val == '' ? '' : nwNative.getAbsolutePath(val); + this.handleSetter('icon', 'SetIcon', String, real_path); +}); + +Tray.prototype.__defineSetter__('alticon', function(val) { + privates(this).option.shadowAlticon = String(val); + var real_path = val == '' ? '' : nwNative.getAbsolutePath(val); + this.handleSetter('alticon', 'SetAltIcon', String, real_path); +}); + +Tray.prototype.__defineGetter__('iconsAreTemplates', function() { + return this.handleGetter('iconsAreTemplates'); +}); + +Tray.prototype.__defineSetter__('iconsAreTemplates', function(val) { + this.handleSetter('iconsAreTemplates', 'SetIconsAreTemplates', Boolean, val); +}); + +Tray.prototype.__defineGetter__('tooltip', function() { + return this.handleGetter('tooltip'); +}); + +Tray.prototype.__defineSetter__('tooltip', function(val) { + this.handleSetter('tooltip', 'SetTooltip', String, val); +}); + +Tray.prototype.__defineGetter__('menu', function() { + return privates(this).menu; +}); + +Tray.prototype.__defineSetter__('menu', function(val) { + if (val.constructor.name != 'Menu') + throw new TypeError("'menu' property requries a valid Menu"); + + privates(this).menu = val; + nw.Object.callObjectMethod(this.id, 'Tray', 'SetMenu', [ val.id ]); +}); + +Tray.prototype.remove = function() { + if (trayEvents.objs[this.id]) + this.removeListener('click'); + nw.Object.callObjectMethod(this.id, 'Tray', 'Remove', []); +} + +Tray.prototype.on = function (event, callback) { + if (event == 'click') { + trayEvents.objs[this.id] = this; + this._onclick = callback; + } +} + +Tray.prototype.removeListener = function (event) { + if (event == 'click') { + delete trayEvents.objs[this.id]; + delete this._onclick; + } +} + +nw_binding.registerCustomHook(function(bindingsAPI) { + var apiFunctions = bindingsAPI.apiFunctions; + trayEvents.clickEvent = new Event("NWObjectTrayClick"); + trayEvents.clickEvent.addListener(function(id) { + if (!trayEvents.objs[id]) + return; + trayEvents.objs[id]._onclick(); + }); + apiFunctions.setHandleRequest('destroy', function(id) { + sendRequest.sendRequestSync('nw.Object.destroy', [id], this.definition.parameters, {}); + }); + apiFunctions.setHandleRequest('create', function(option) { + var id = contextMenuNatives.GetNextContextMenuId(); + if (typeof option != 'object' || !option) + option = { }; + + option.generatedId = id; + var ret = new Tray(id, option); + sendRequest.sendRequestSync('nw.Object.create', [id, 'Tray', option], this.definition.parameters, {}); + messagingNatives.BindToGC(ret, nw.Tray.destroy.bind(undefined, id), -1); + return ret; + }); +}); + +exports.binding = nw_binding.generate(); + From 802ea8dbc3b86918bbc23c542030527ee8c298d9 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 28 Oct 2015 12:10:15 +0800 Subject: [PATCH 029/404] bump version to 0.13.0-alpha5 --- src/nw_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nw_version.h b/src/nw_version.h index a55b461031..b89c8b118c 100644 --- a/src/nw_version.h +++ b/src/nw_version.h @@ -38,7 +38,7 @@ #else # define NW_VERSION_STRING NW_STRINGIFY(NW_MAJOR_VERSION) "." \ NW_STRINGIFY(NW_MINOR_VERSION) "." \ - NW_STRINGIFY(NW_PATCH_VERSION) "-alpha4" + NW_STRINGIFY(NW_PATCH_VERSION) "-alpha5" #endif #define NW_VERSION "v" NW_VERSION_STRING From 79f8a50318b44fa37ea8b85ded1b54ecd65ec819 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 1 Nov 2015 12:05:49 +0800 Subject: [PATCH 030/404] [test] set timeout to 240 --- nw.gypi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nw.gypi b/nw.gypi index 5b6668fc69..32671a39e6 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1051,7 +1051,7 @@ '<(PRODUCT_DIR)/run_tests.re', ], 'action': ['python', '<(test_script)', '-d', '<(PRODUCT_DIR)', - '-t', '120', 'remoting'], + '-t', '240', 'remoting'], }, ], }, From 9df083a737f968fc39e7d03647b30e696cef1636 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 2 Nov 2015 13:48:48 +0800 Subject: [PATCH 031/404] [README] update for 0.13.0-alpha5 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9dcd0571e3..e0aeb95b6c 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,11 @@ It was created in the Intel Open Source Technology Center. * Windows: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-x64.zip) * Mac 10.7+: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-x64.zip) -* **v0.13.0-alpha4:** (Jun 23, 2015, based off of Node.js v4.1.2, Chromium 45.0.2454.85): [release notes](https://groups.google.com/d/msg/nwjs-general/zzszAUlDSVM/0asr0DJPBQAJ) +* **v0.13.0-alpha5:** (Nov 2, 2015, based off of Node.js v5.0.0, Chromium 46.0.2490.80): [release notes](https://groups.google.com/d/msg/nwjs-general/YuwMHd_uvPM/pLFWG3vYBwAJ) **NOTE** You might want the **SDK build**. Please read the release notes - * Linux: [32bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-linux-x64.tar.gz) - * Windows: [32bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-win-x64.zip) - * Mac 10.7+: [64bit](http://dl.nwjs.io/v0.13.0-alpha4/nwjs-v0.13.0-alpha4-osx-x64.zip) + * Linux: [32bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-linux-x64.tar.gz) + * Windows: [32bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-win-x64.zip) + * Mac 10.7+: [64bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-osx-x64.zip) * **0.8.6:** (Apr 18, 2014, based off of Node v0.10.22, Chrome 30.0.1599.66) **If your native Node module works only with Node v0.10, then you should use node-webkit v0.8.x, which is also a maintained branch. [More info](https://groups.google.com/d/msg/nwjs-general/2OJ1cEMPLlA/09BvpTagSA0J)** [release notes](https://groups.google.com/d/msg/nwjs-general/CLPkgfV-i7s/hwkkQuJ1kngJ) From 50cecb56f98ada66efadc3ba00bd49c1bc318452 Mon Sep 17 00:00:00 2001 From: frankenbot Date: Mon, 2 Nov 2015 21:04:16 -0800 Subject: [PATCH 032/404] Update redirects --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e0aeb95b6c..de589204fc 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ native applications with all Web technologies. It was created in the Intel Open Source Technology Center. -[Introduction to node-webkit (slides)](https://speakerdeck.com/u/zcbenz/p/node-webkit-app-runtime-based-on-chromium-and-node-dot-js) -[Creating Desktop Applications With node-webkit](http://strongloop.com/strongblog/creating-desktop-applications-with-node-webkit/) +[Introduction to node-webkit (slides)](https://speakerdeck.com/zcbenz/node-webkit-app-runtime-based-on-chromium-and-node-dot-js) +[Creating Desktop Applications With node-webkit](https://strongloop.com/strongblog/creating-desktop-applications-with-node-webkit/) [WebApp to DesktopApp with node-webkit (slides)](http://oldgeeksguide.github.io/presentations/html5devconf2013/wtod.html) [Essay on the history and internals of the project](http://yedingding.com/2014/08/01/node-webkit-intro-en.html) ## Features * Apps written in modern HTML5, CSS3, JS and WebGL. -* Complete support for [Node.js APIs](http://nodejs.org/api/) and all its [third party modules](https://npmjs.org). +* Complete support for [Node.js APIs](https://nodejs.org/api/) and all its [third party modules](https://www.npmjs.com/). * Good performance: Node and WebKit run in the same thread: Function calls are made straightforward; objects are in the same heap and can just reference each other; * Easy to package and distribute apps. * Available on Linux, Mac OS X and Windows @@ -46,7 +46,7 @@ It was created in the Intel Open Source Technology Center. * **latest live build**: git tip version; build triggered from every git commit: http://dl.nwjs.io/live-build/ -* [Previous versions](https://github.com/rogerwang/node-webkit/wiki/Downloads-of-old-versions) +* [Previous versions](https://github.com/nwjs/nw.js/wiki/Downloads-of-old-versions) ###Demos and real apps You may also be interested in [our demos repository](https://github.com/zcbenz/nw-sample-apps) and the [List of apps and companies using nw.js](https://github.com/nwjs/nw.js/wiki/List-of-apps-and-companies-using-nw.js). @@ -92,11 +92,11 @@ Note: on OSX, the executable binary is in a hidden directory within the .app fil For more information on how to write/package/run apps, see: -* [How to run apps](https://github.com/rogerwang/node-webkit/wiki/How-to-run-apps) -* [How to package and distribute your apps](https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps) -* [How to use Node.js modules in node-webkit](https://github.com/rogerwang/node-webkit/wiki/Using-Node-modules) +* [How to run apps](https://github.com/nwjs/nw.js/wiki/How-to-run-apps) +* [How to package and distribute your apps](https://github.com/nwjs/nw.js/wiki/How-to-package-and-distribute-your-apps) +* [How to use Node.js modules in node-webkit](https://github.com/nwjs/nw.js/wiki/Using-Node-modules) -And our [Wiki](https://github.com/rogerwang/node-webkit/wiki) for much more. +And our [Wiki](https://github.com/nwjs/nw.js/wiki) for much more. ## Community @@ -109,10 +109,10 @@ Issues are being tracked here on GitHub. ## License -`node-webkit`'s code in this repo uses the MIT license, see our `LICENSE` file. To redistribute the binary, see [How to package and distribute your apps](https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps) +`node-webkit`'s code in this repo uses the MIT license, see our `LICENSE` file. To redistribute the binary, see [How to package and distribute your apps](https://github.com/nwjs/nw.js/wiki/How-to-package-and-distribute-your-apps) ## Sponsors The work is being sponsored by: -* [Intel](http://www.intel.com) +* [Intel](http://www.intel.com/content/www/us/en/homepage.html) * [Gnor Tech](http://gnor.net) From 8b12bfb11c0b97521b1a068b965a3d7149fffad7 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 3 Nov 2015 21:00:08 +0800 Subject: [PATCH 033/404] add nwjc back --- nw.gypi | 1 + tools/package_binaries.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/nw.gypi b/nw.gypi index 23637c7efc..fa87c80890 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1020,6 +1020,7 @@ ['nwjs_sdk==1', { 'dependencies': [ '<(DEPTH)/chrome/chrome.gyp:chromedriver', + '<(DEPTH)/v8/tools/gyp/v8.gyp:nwjc', ], }], ['OS == "linux"', { diff --git a/tools/package_binaries.py b/tools/package_binaries.py index 6b3fd9b666..760791e998 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -158,6 +158,8 @@ def generate_target_nw(platform_name, arch, version): 'lib/libnw.so', 'lib/libnode.so', ] + if flavor == 'sdk': + target['input'].append('nwjc') if flavor in ['nacl','sdk'] : target['input'] += ['nacl_helper', 'nacl_helper_bootstrap', 'pnacl'] if arch == 'x64': @@ -183,6 +185,8 @@ def generate_target_nw(platform_name, arch, version): 'nw_100_percent.pak', 'nw_200_percent.pak', ] + if flavor == 'sdk': + target['input'].append('nwjc.exe') if flavor in ['nacl','sdk'] : target['input'].append('pnacl') if arch == 'x64': @@ -194,6 +198,8 @@ def generate_target_nw(platform_name, arch, version): 'nwjs.app', 'credits.html', ] + if flavor == 'sdk': + target['input'].append('nwjc') else: print 'Unsupported platform: ' + platform_name exit(-1) From 0a217725f167f7026c32227fda2239f93133133c Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 5 Nov 2015 09:50:55 +0800 Subject: [PATCH 034/404] fix nwjc in cr46 --- src/nw_custom_bindings.cc | 2 +- src/resources/api_nw_window.js | 2 +- test/remoting/nwjc/index.html | 11 +++++++++++ test/remoting/nwjc/mytest.js | 3 +++ test/remoting/nwjc/package.json | 4 ++++ test/remoting/nwjc/test.py | 26 ++++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 test/remoting/nwjc/index.html create mode 100644 test/remoting/nwjc/mytest.js create mode 100644 test/remoting/nwjc/package.json create mode 100644 test/remoting/nwjc/test.py diff --git a/src/nw_custom_bindings.cc b/src/nw_custom_bindings.cc index c761f405db..4fbed1b809 100644 --- a/src/nw_custom_bindings.cc +++ b/src/nw_custom_bindings.cc @@ -85,7 +85,7 @@ NWCustomBindings::NWCustomBindings(ScriptContext* context) base::Bind(&NWCustomBindings::EvalScript, base::Unretained(this))); RouteFunction("evalNWBin", - base::Bind(&NWCustomBindings::EvalScript, + base::Bind(&NWCustomBindings::EvalNWBin, base::Unretained(this))); RouteFunction("getAbsolutePath", base::Bind(&NWCustomBindings::GetAbsolutePath, diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 8fd60e59f3..9c62fbce47 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -80,7 +80,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { nwNatives.evalScript(frame, script); }; NWWindow.prototype.evalNWBin = function (frame, path) { - nwNatives.evalScript(frame, path); + nwNatives.evalNWBin(frame, path); }; NWWindow.prototype.show = function () { this.appWindow.show(); diff --git a/test/remoting/nwjc/index.html b/test/remoting/nwjc/index.html new file mode 100644 index 0000000000..33fe685f03 --- /dev/null +++ b/test/remoting/nwjc/index.html @@ -0,0 +1,11 @@ + + snapshot demo + + + + + diff --git a/test/remoting/nwjc/mytest.js b/test/remoting/nwjc/mytest.js new file mode 100644 index 0000000000..7990ecd1ec --- /dev/null +++ b/test/remoting/nwjc/mytest.js @@ -0,0 +1,3 @@ +function mytest(a) { + document.write("

" + (a + 42) + "

"); +} diff --git a/test/remoting/nwjc/package.json b/test/remoting/nwjc/package.json new file mode 100644 index 0000000000..7cd4843f99 --- /dev/null +++ b/test/remoting/nwjc/package.json @@ -0,0 +1,4 @@ +{ + "name": "nw-demo", + "main": "index.html" +} diff --git a/test/remoting/nwjc/test.py b/test/remoting/nwjc/test.py new file mode 100644 index 0000000000..b5539647cb --- /dev/null +++ b/test/remoting/nwjc/test.py @@ -0,0 +1,26 @@ +import time +import os +import shutil +import subprocess + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +testdir = os.path.dirname(os.path.abspath(__file__)) +chrome_options.add_argument("nwapp=" + testdir) +binfile = os.path.join(testdir, "mytest.bin") +nwjc = os.path.join(os.path.dirname(os.environ['CHROMEDRIVER']), "nwjc.exe" if os.name == "nt" else "nwjc") +os.remove(binfile) +assert(False == os.path.isfile(binfile)) +subprocess.call([nwjc, "mytest.js", "mytest.bin"]) +assert(os.path.isfile(binfile)) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("44" == result.get_attribute('innerHTML')) +finally: + driver.quit() From b4013b59fb81b1b766ebc23385fcb4855df32896 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 5 Nov 2015 10:07:03 +0800 Subject: [PATCH 035/404] [test] fix nwjc test case --- test/remoting/nwjc/test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/remoting/nwjc/test.py b/test/remoting/nwjc/test.py index b5539647cb..82aa42aaf5 100644 --- a/test/remoting/nwjc/test.py +++ b/test/remoting/nwjc/test.py @@ -10,7 +10,10 @@ chrome_options.add_argument("nwapp=" + testdir) binfile = os.path.join(testdir, "mytest.bin") nwjc = os.path.join(os.path.dirname(os.environ['CHROMEDRIVER']), "nwjc.exe" if os.name == "nt" else "nwjc") -os.remove(binfile) +try: + os.remove(binfile) +except: + pass assert(False == os.path.isfile(binfile)) subprocess.call([nwjc, "mytest.js", "mytest.bin"]) assert(os.path.isfile(binfile)) From fd78759da3e8ef79949b948fd33e917357f3e4b4 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 5 Nov 2015 10:14:57 +0800 Subject: [PATCH 036/404] make nwjs depends on nwjc --- nw.gypi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nw.gypi b/nw.gypi index fa87c80890..6f9464e62a 100644 --- a/nw.gypi +++ b/nw.gypi @@ -968,6 +968,7 @@ 'dependencies': [ '<(DEPTH)/chrome/chrome.gyp:chrome', '<(DEPTH)/third_party/node/node.gyp:node', + '<(DEPTH)/v8/tools/gyp/v8.gyp:nwjc', ], 'conditions': [ [ 'OS=="mac"', { @@ -1020,7 +1021,6 @@ ['nwjs_sdk==1', { 'dependencies': [ '<(DEPTH)/chrome/chrome.gyp:chromedriver', - '<(DEPTH)/v8/tools/gyp/v8.gyp:nwjc', ], }], ['OS == "linux"', { From 83b82c9e1b131d0d52e9ef9b40a7fb81d70a3d0a Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 5 Nov 2015 10:20:26 +0800 Subject: [PATCH 037/404] [test] set working directory for nwjc --- test/remoting/nwjc/test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/remoting/nwjc/test.py b/test/remoting/nwjc/test.py index 82aa42aaf5..b5ea0a8f4e 100644 --- a/test/remoting/nwjc/test.py +++ b/test/remoting/nwjc/test.py @@ -10,6 +10,7 @@ chrome_options.add_argument("nwapp=" + testdir) binfile = os.path.join(testdir, "mytest.bin") nwjc = os.path.join(os.path.dirname(os.environ['CHROMEDRIVER']), "nwjc.exe" if os.name == "nt" else "nwjc") +os.chdir(testdir) try: os.remove(binfile) except: From 6ba21fd3d6f5cd57e4c932c5dc2ab93c39f9980d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 5 Nov 2015 10:47:58 +0800 Subject: [PATCH 038/404] [linux] strip nwjc binary --- nw.gypi | 1 + 1 file changed, 1 insertion(+) diff --git a/nw.gypi b/nw.gypi index 6f9464e62a..3bd452eb9f 100644 --- a/nw.gypi +++ b/nw.gypi @@ -947,6 +947,7 @@ 'action_name': 'strip_nw_binaries', 'inputs': [ '<(PRODUCT_DIR)/chromedriver', + '<(PRODUCT_DIR)/nwjc', ], 'outputs': [ '<(PRODUCT_DIR)/strip_binaries.stamp', From c8fe2da4ffcd8baa43196f52168e08c8e4a17d9c Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 5 Nov 2015 10:56:44 +0800 Subject: [PATCH 039/404] update live-build upload path --- tools/aws_uploader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/aws_uploader.py b/tools/aws_uploader.py index 11f50c2627..cd16e8d29b 100755 --- a/tools/aws_uploader.py +++ b/tools/aws_uploader.py @@ -53,7 +53,7 @@ # it's for S3, so always use '/' here #upload_path = ''.join(['/' + date, # '/' + builder_name + '-build-' + build_number + '-' + got_revision]) -upload_path = '/' + dlpath; +upload_path = '/live-build/' + dlpath; file_list = os.listdir(dist_dir) if len(file_list) == 0: From 6fa782a66d1e093d42b1445a0d64951248e47b78 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 5 Nov 2015 21:39:40 +0800 Subject: [PATCH 040/404] rename header file and add checksum for node-gyp --- tools/aws_uploader.py | 10 ++++++++-- tools/make-nw-headers.py | 2 +- tools/package_binaries.py | 8 +++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tools/aws_uploader.py b/tools/aws_uploader.py index cd16e8d29b..04095e3e76 100755 --- a/tools/aws_uploader.py +++ b/tools/aws_uploader.py @@ -6,6 +6,8 @@ import os import sys import time +import getnwisrelease +import getnwversion # Set timeout, for retry @@ -50,10 +52,14 @@ exit(-1) dist_dir = os.path.normpath(dist_dir) +nw_ver = getnwversion.nw_version +if getnwisrelease.release == 0: + nw_ver += getnwisrelease.postfix + # it's for S3, so always use '/' here #upload_path = ''.join(['/' + date, # '/' + builder_name + '-build-' + build_number + '-' + got_revision]) -upload_path = '/live-build/' + dlpath; +upload_path = '/live-build/' + dlpath + '/' + nw_ver; file_list = os.listdir(dist_dir) if len(file_list) == 0: @@ -90,7 +96,7 @@ def aws_upload(upload_path, file_list): if builder_name == 'nw13_win64' : path_prefix = 'x64' - if f.startswith('nw-headers') and builder_name != 'nw13_mac64' : + if (f.startswith('node-v') or f == 'SHASUMS256.txt') and builder_name != 'nw13_mac64' : continue if f.startswith('chromedriver') and 'sdk' not in builder_name : diff --git a/tools/make-nw-headers.py b/tools/make-nw-headers.py index 27fbde92da..e41f9fdbbd 100644 --- a/tools/make-nw-headers.py +++ b/tools/make-nw-headers.py @@ -35,7 +35,7 @@ def update_uvh(tmp_dir, header_files): ''' if '-t' in sys.argv: nw_version = sys.argv[sys.argv.index('-t') + 1] -tarname = 'nw-headers-v' + nw_version + '.tar.gz' +tarname = 'node-v' + nw_version + '.tar.gz' tarpath = os.path.join(tmp_dir, tarname) #make tmpdir diff --git a/tools/package_binaries.py b/tools/package_binaries.py index 760791e998..ae1214ce64 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -9,6 +9,7 @@ import sys import tarfile import zipfile +from hashlib import sha256 from subprocess import call @@ -272,11 +273,16 @@ def generate_target_headers(platform_name, arch, version): res = call(['python', make_nw_header]) if res == 0: print 'nw-headers generated' - nw_headers_name = 'nw-headers-v' + version + '.tar.gz' + nw_headers_name = 'node-v' + version + '.tar.gz' nw_headers_path = os.path.join(os.path.dirname(__file__), \ os.pardir, 'tmp', nw_headers_name) if os.path.isfile(os.path.join(binaries_location, nw_headers_name)): os.remove(os.path.join(binaries_location, nw_headers_name)) + + f = open(nw_headers_path, 'rb') + checksum_file = open(os.path.join(binaries_location, 'SHASUMS256.txt'), 'w') + with f, checksum_file: + checksum_file.write('%s %s' % (sha256(f.read()).hexdigest(), nw_headers_name)) shutil.move(nw_headers_path, binaries_location) target['input'].append(nw_headers_name) else: From 513d4ba07bd716fa1b960fb610d7900f1a86d35a Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sat, 7 Nov 2015 07:48:11 +0800 Subject: [PATCH 041/404] fix single-instance and port user-agent --- src/nw_content.cc | 40 ++++++++++++++++++++++++++++++++++++++-- src/nw_content.h | 4 ++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/nw_content.cc b/src/nw_content.cc index 6d3497d61c..bc92ec6686 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -17,6 +17,7 @@ #include "content/public/browser/web_contents.h" #include "content/public/common/content_switches.h" #include "content/public/common/result_codes.h" +#include "content/public/common/user_agent.h" #include "content/public/renderer/render_view.h" #include "content/nw/src/api/menu/menu.h" @@ -261,6 +262,13 @@ void DocumentElementHook(blink::WebFrame* frame, void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { v8::Isolate* isolate = context->isolate(); + + bool nodejs_enabled = true; + context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSEnableNode, &nodejs_enabled); + + if (!nodejs_enabled) + return; + if (!g_is_node_initialized_fn()) g_setup_nwnode_fn(0, nullptr); @@ -497,8 +505,10 @@ void RendererProcessTerminatedHook(content::RenderProcessHost* process, void OnRenderProcessShutdownHook(extensions::ScriptContext* context) { blink::WebScopedMicrotaskSuppression suppression; void* env = g_get_current_env_fn(context->v8_context()); - g_emit_exit_fn(env); - g_run_at_exit_fn(env); + if (g_is_node_initialized_fn()) { + g_emit_exit_fn(env); + g_run_at_exit_fn(env); + } } void willHandleNavigationPolicy(content::RenderView* rv, @@ -691,4 +701,30 @@ bool ExecuteAppCommandHook(int command_id, extensions::AppWindow* app_window) { #endif //OSX } +bool ProcessSingletonNotificationCallbackHook(const base::CommandLine& command_line, + const base::FilePath& current_directory) { + nw::Package* package = nw::package(); + bool single_instance = true; + package->root()->GetBoolean(switches::kmSingleInstance, &single_instance); + return single_instance; +} + +bool GetUserAgentFromManifest(std::string* agent) { + std::string user_agent; + nw::Package* package = nw::package(); + if (package->root()->GetString(switches::kmUserAgent, &user_agent)) { + std::string name, version; + package->root()->GetString(switches::kmName, &name); + package->root()->GetString("version", &version); + base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%name", name); + base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%ver", version); + base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%nwver", NW_VERSION_STRING); + base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%webkit_ver", content::GetWebKitVersion()); + base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%osinfo", content::BuildOSInfo()); + *agent = user_agent; + return true; + } + return false; +} + } //namespace nw diff --git a/src/nw_content.h b/src/nw_content.h index 0e0d4ed516..ec2d94f1e0 100644 --- a/src/nw_content.h +++ b/src/nw_content.h @@ -6,6 +6,7 @@ namespace base { class DictionaryValue; + class CommandLine; } namespace blink { @@ -55,6 +56,9 @@ void DocumentFinishHook(blink::WebFrame* frame, std::string* nw_inject_js_doc_end); bool GetImage(Package* package, const FilePath& icon_path, gfx::Image* image); bool ExecuteAppCommandHook(int command_id, extensions::AppWindow* app_window); + bool ProcessSingletonNotificationCallbackHook(const base::CommandLine& command_line, + const base::FilePath& current_directory); + bool GetUserAgentFromManifest(std::string* agent); } #endif From 66365d5219682b676a85e4f661d9c786aea6a161 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 8 Nov 2015 19:45:38 +0800 Subject: [PATCH 042/404] fix user-agent support --- src/nw_content.cc | 28 ++++++++++++++++++++-------- src/nw_content.h | 4 ++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/nw_content.cc b/src/nw_content.cc index bc92ec6686..927a2a105d 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -709,19 +709,31 @@ bool ProcessSingletonNotificationCallbackHook(const base::CommandLine& command_l return single_instance; } +static std::string g_user_agent; + +void SetUserAgentOverride(const std::string& agent, + const std::string& name, + const std::string& version) { + g_user_agent = agent; + base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%name", name); + base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%ver", version); + base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%nwver", NW_VERSION_STRING); + base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%webkit_ver", content::GetWebKitVersion()); + base::ReplaceSubstringsAfterOffset(&g_user_agent, 0, "%osinfo", content::BuildOSInfo()); +} + bool GetUserAgentFromManifest(std::string* agent) { - std::string user_agent; + if (!g_user_agent.empty()) { + *agent = g_user_agent; + return true; + } nw::Package* package = nw::package(); - if (package->root()->GetString(switches::kmUserAgent, &user_agent)) { + if (package->root()->GetString(switches::kmUserAgent, &g_user_agent)) { std::string name, version; package->root()->GetString(switches::kmName, &name); package->root()->GetString("version", &version); - base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%name", name); - base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%ver", version); - base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%nwver", NW_VERSION_STRING); - base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%webkit_ver", content::GetWebKitVersion()); - base::ReplaceSubstringsAfterOffset(&user_agent, 0, "%osinfo", content::BuildOSInfo()); - *agent = user_agent; + SetUserAgentOverride(g_user_agent, name, version); + *agent = g_user_agent; return true; } return false; diff --git a/src/nw_content.h b/src/nw_content.h index ec2d94f1e0..66349f9b5e 100644 --- a/src/nw_content.h +++ b/src/nw_content.h @@ -59,6 +59,10 @@ void DocumentFinishHook(blink::WebFrame* frame, bool ProcessSingletonNotificationCallbackHook(const base::CommandLine& command_line, const base::FilePath& current_directory); bool GetUserAgentFromManifest(std::string* agent); + void SetUserAgentOverride(const std::string& agent, + const std::string& name, + const std::string& version); + } #endif From e25bb7b57e1299f40b7306e0497b2d4f203ed49d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 8 Nov 2015 19:47:30 +0800 Subject: [PATCH 043/404] [test] user-agent --- test/remoting/user-agent/echo-user-agent.py | 27 +++++++++++++++++++++ test/remoting/user-agent/index.html | 11 +++++++++ test/remoting/user-agent/package.json | 5 ++++ test/remoting/user-agent/test.py | 25 +++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 test/remoting/user-agent/echo-user-agent.py create mode 100644 test/remoting/user-agent/index.html create mode 100644 test/remoting/user-agent/package.json create mode 100644 test/remoting/user-agent/test.py diff --git a/test/remoting/user-agent/echo-user-agent.py b/test/remoting/user-agent/echo-user-agent.py new file mode 100644 index 0000000000..79eb221e90 --- /dev/null +++ b/test/remoting/user-agent/echo-user-agent.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler + +class RequestHandler(BaseHTTPRequestHandler): + + def do_GET(self): + + request_path = self.path + + print(self.headers['User-Agent']) + + self.send_response(200) + self.send_header("Set-Cookie", "foo=bar") + + def log_request(self, code): + pass + +def main(): + port = 3456 + print('Listening on localhost:%s' % port) + server = HTTPServer(('', port), RequestHandler) + server.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/test/remoting/user-agent/index.html b/test/remoting/user-agent/index.html new file mode 100644 index 0000000000..3b3f9153e5 --- /dev/null +++ b/test/remoting/user-agent/index.html @@ -0,0 +1,11 @@ + + + + + test + + + + + + diff --git a/test/remoting/user-agent/package.json b/test/remoting/user-agent/package.json new file mode 100644 index 0000000000..7ce31cc155 --- /dev/null +++ b/test/remoting/user-agent/package.json @@ -0,0 +1,5 @@ +{ + "name":"test-user-agent", + "main":"index.html", + "user-agent": "test-agent" +} diff --git a/test/remoting/user-agent/test.py b/test/remoting/user-agent/test.py new file mode 100644 index 0000000000..b5998441a2 --- /dev/null +++ b/test/remoting/user-agent/test.py @@ -0,0 +1,25 @@ +import time +import os +import subprocess + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +testdir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(testdir) + +server = subprocess.Popen(['python', '-u', 'echo-user-agent.py'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) +print server.stdout.readline() + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + user_agent = server.stdout.readline() + print "user agent: " + user_agent + server.terminate() + assert("test-agent" in user_agent) +finally: + driver.quit() From 316c04ae2e34a5e2c4aa2afedcda9c614c579595 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 9 Nov 2015 10:38:58 +0800 Subject: [PATCH 044/404] fix crash in user agent --- src/nw_content.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nw_content.cc b/src/nw_content.cc index 927a2a105d..09fbe04c56 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -728,6 +728,8 @@ bool GetUserAgentFromManifest(std::string* agent) { return true; } nw::Package* package = nw::package(); + if (!package) + return false; if (package->root()->GetString(switches::kmUserAgent, &g_user_agent)) { std::string name, version; package->root()->GetString(switches::kmName, &name); From 86c812a628bc8c0cf9cc0c130122c9e48529adec Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 10 Nov 2015 10:44:53 +0800 Subject: [PATCH 045/404] add node-remote tests --- .../node-remote-negtive/http-server.py | 15 ++++++++ test/remoting/node-remote-negtive/index.html | 18 +++++++++ test/remoting/node-remote-negtive/test.py | 37 ++++++++++++++++++ test/remoting/node-remote/http-server.py | 15 ++++++++ test/remoting/node-remote/index.html | 18 +++++++++ test/remoting/node-remote/test.py | 38 +++++++++++++++++++ 6 files changed, 141 insertions(+) create mode 100644 test/remoting/node-remote-negtive/http-server.py create mode 100644 test/remoting/node-remote-negtive/index.html create mode 100644 test/remoting/node-remote-negtive/test.py create mode 100644 test/remoting/node-remote/http-server.py create mode 100644 test/remoting/node-remote/index.html create mode 100644 test/remoting/node-remote/test.py diff --git a/test/remoting/node-remote-negtive/http-server.py b/test/remoting/node-remote-negtive/http-server.py new file mode 100644 index 0000000000..76a2c2414d --- /dev/null +++ b/test/remoting/node-remote-negtive/http-server.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +import SimpleHTTPServer +import SocketServer +import sys + +PORT = int(sys.argv[1]) + +Handler = SimpleHTTPServer.SimpleHTTPRequestHandler + +httpd = SocketServer.TCPServer(("", PORT), Handler) + +print "serving at port", PORT +httpd.serve_forever() + diff --git a/test/remoting/node-remote-negtive/index.html b/test/remoting/node-remote-negtive/index.html new file mode 100644 index 0000000000..05c79be993 --- /dev/null +++ b/test/remoting/node-remote-negtive/index.html @@ -0,0 +1,18 @@ + + + + + node-remote negative test + + +

node-remote negative test

+ + + + diff --git a/test/remoting/node-remote-negtive/test.py b/test/remoting/node-remote-negtive/test.py new file mode 100644 index 0000000000..8d1ee5e75c --- /dev/null +++ b/test/remoting/node-remote-negtive/test.py @@ -0,0 +1,37 @@ +import time +import os +import subprocess + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common import utils + +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +testdir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(testdir) + +port = str(utils.free_port()) +server = subprocess.Popen(['python', 'http-server.py', port]) + +manifest = open('package.json', 'w') +manifest.write(''' +{ + "name":"test-node-remote", + "main":"http://localhost:%s/index.html" +} +''' % (port)) +manifest.close() + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options, service_log_path="log", service_args=["--verbose"]) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + server.terminate() + driver.quit() + diff --git a/test/remoting/node-remote/http-server.py b/test/remoting/node-remote/http-server.py new file mode 100644 index 0000000000..76a2c2414d --- /dev/null +++ b/test/remoting/node-remote/http-server.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +import SimpleHTTPServer +import SocketServer +import sys + +PORT = int(sys.argv[1]) + +Handler = SimpleHTTPServer.SimpleHTTPRequestHandler + +httpd = SocketServer.TCPServer(("", PORT), Handler) + +print "serving at port", PORT +httpd.serve_forever() + diff --git a/test/remoting/node-remote/index.html b/test/remoting/node-remote/index.html new file mode 100644 index 0000000000..9580ffd871 --- /dev/null +++ b/test/remoting/node-remote/index.html @@ -0,0 +1,18 @@ + + + + + node-remote test + + +

node-remote test

+ + + + diff --git a/test/remoting/node-remote/test.py b/test/remoting/node-remote/test.py new file mode 100644 index 0000000000..fb2d000b8f --- /dev/null +++ b/test/remoting/node-remote/test.py @@ -0,0 +1,38 @@ +import time +import os +import subprocess + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common import utils + +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +testdir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(testdir) + +port = str(utils.free_port()) +server = subprocess.Popen(['python', 'http-server.py', port]) + +manifest = open('package.json', 'w') +manifest.write(''' +{ + "name":"test-node-remote", + "node-remote":"", + "main":"http://localhost:%s/index.html" +} +''' % (port)) +manifest.close() + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options, service_log_path="log", service_args=["--verbose"]) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + server.terminate() + driver.quit() + From 34075f277139927f628b367d11b03df9e677949d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 10 Nov 2015 16:13:10 +0800 Subject: [PATCH 046/404] support dom_stoarge_quota and additional_trust_anchors --- nw.gypi | 2 ++ src/nw_content.cc | 45 +++++++++++++++++++++++ src/policy_cert_verifier.cc | 67 ++++++++++++++++++++++++++++++++++ src/policy_cert_verifier.h | 72 +++++++++++++++++++++++++++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 src/policy_cert_verifier.cc create mode 100644 src/policy_cert_verifier.h diff --git a/nw.gypi b/nw.gypi index 3bd452eb9f..d5b4a6a3b6 100644 --- a/nw.gypi +++ b/nw.gypi @@ -676,6 +676,8 @@ 'src/nw_content.h', 'src/nw_custom_bindings.cc', 'src/nw_custom_bindings.h', + 'src/policy_cert_verifier.cc', + 'src/policy_cert_verifier.h', ], 'conditions': [ ['OS=="win" or OS=="linux"', { diff --git a/src/nw_content.cc b/src/nw_content.cc index 09fbe04c56..360b94f362 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -4,12 +4,18 @@ #include "nw_base.h" #include "base/command_line.h" +#include "base/logging.h" #include "base/files/file_util.h" #include "base/json/json_reader.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" + +#include "chrome/browser/browser_process.h" +#include "chrome/browser/io_thread.h" + +#include "content/common/dom_storage/dom_storage_map.h" #include "content/nw/src/common/shell_switches.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" @@ -22,6 +28,7 @@ #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/api/object_manager.h" +#include "content/nw/src/policy_cert_verifier.h" #include "extensions/renderer/dispatcher.h" #include "extensions/renderer/script_context.h" @@ -31,6 +38,8 @@ #include "extensions/common/manifest.h" #include "extensions/common/manifest_constants.h" +#include "net/cert/x509_certificate.h" + #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" @@ -171,6 +180,42 @@ int MainPartsPreCreateThreadsHook() { base::FilePath path = package->path().NormalizePathSeparators(); command_line->AppendSwitchPath("nwapp", path); + int dom_storage_quota_mb; + if (package->root()->GetInteger("dom_storage_quota", &dom_storage_quota_mb)) { + content::DOMStorageMap::SetQuotaOverride(dom_storage_quota_mb * 1024 * 1024); + } + + const base::ListValue *additional_trust_anchors = NULL; + if (package->root()->GetList("additional_trust_anchors", &additional_trust_anchors)) { + net::CertificateList trust_anchors; + for (size_t i=0; iGetSize(); i++) { + std::string certificate_string; + if (!additional_trust_anchors->GetString(i, &certificate_string)) { + // LOG(WARNING) + // << "Could not get string from entry " << i; + continue; + } + + net::CertificateList loaded = + net::X509Certificate::CreateCertificateListFromBytes( + certificate_string.c_str(), certificate_string.size(), + net::X509Certificate::FORMAT_AUTO); + if (loaded.empty() && !certificate_string.empty()) { + // LOG(WARNING) + // << "Could not load certificate from entry " << i; + continue; + } + + trust_anchors.insert(trust_anchors.end(), loaded.begin(), loaded.end()); + } + if (!trust_anchors.empty()) { + // LOG(INFO) + // << "Added " << trust_anchors.size() << " certificates to trust anchors."; + PolicyCertVerifier* verifier = + (PolicyCertVerifier*)g_browser_process->io_thread()->globals()->cert_verifier.get(); + verifier->SetTrustAnchors(trust_anchors); + } + } } return content::RESULT_CODE_NORMAL_EXIT; } diff --git a/src/policy_cert_verifier.cc b/src/policy_cert_verifier.cc new file mode 100644 index 0000000000..ab241f81c2 --- /dev/null +++ b/src/policy_cert_verifier.cc @@ -0,0 +1,67 @@ +// 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. + +#include "content/nw/src/policy_cert_verifier.h" + +#include "base/logging.h" +#include "chrome/browser/browser_process.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" +#include "net/cert/cert_verify_proc.h" +#include "net/cert/multi_threaded_cert_verifier.h" + +namespace nw { + +PolicyCertVerifier::PolicyCertVerifier() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + InitializeOnIOThread(net::CertVerifyProc::CreateDefault()); +} + +PolicyCertVerifier::~PolicyCertVerifier() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); +} + +void PolicyCertVerifier::InitializeOnIOThread( + const scoped_refptr& verify_proc) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + if (!verify_proc->SupportsAdditionalTrustAnchors()) { + LOG(WARNING) + << "Additional trust anchors not supported on the current platform!"; + } + net::MultiThreadedCertVerifier* verifier = + new net::MultiThreadedCertVerifier(verify_proc.get()); + verifier->SetCertTrustAnchorProvider(this); + delegate_.reset(verifier); +} + +void PolicyCertVerifier::SetTrustAnchors( + const net::CertificateList& trust_anchors) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + trust_anchors_ = trust_anchors; +} + +int PolicyCertVerifier::Verify( + net::X509Certificate* cert, + const std::string& hostname, + const std::string& ocsp_response, + int flags, + net::CRLSet* crl_set, + net::CertVerifyResult* verify_result, + const net::CompletionCallback& completion_callback, + scoped_ptr* out_req, + const net::BoundNetLog& net_log) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + DCHECK(delegate_); + int error = + delegate_->Verify(cert, hostname, ocsp_response, flags, crl_set, + verify_result, completion_callback, out_req, net_log); + return error; +} + +const net::CertificateList& PolicyCertVerifier::GetAdditionalTrustAnchors() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + return trust_anchors_; +} + +} // namespace nw diff --git a/src/policy_cert_verifier.h b/src/policy_cert_verifier.h new file mode 100644 index 0000000000..6a5f0b8767 --- /dev/null +++ b/src/policy_cert_verifier.h @@ -0,0 +1,72 @@ +// 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 NW_POLICY_POLICY_CERT_VERIFIER_H_ +#define NW_POLICY_POLICY_CERT_VERIFIER_H_ + +#include + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/completion_callback.h" +#include "net/cert/cert_trust_anchor_provider.h" +#include "net/cert/cert_verifier.h" + +namespace net { +class CertVerifyProc; +class CertVerifyResult; +class X509Certificate; +typedef std::vector > CertificateList; +} + +namespace nw { + +// Wraps a MultiThreadedCertVerifier to make it use the additional trust anchors +// configured by the ONC user policy. +class PolicyCertVerifier : public net::CertVerifier, + public net::CertTrustAnchorProvider { + public: + // Except for tests, PolicyCertVerifier should only be created by + // PolicyCertService, which is the counterpart of this class on the UI thread. + // Except of the constructor, all methods and the destructor must be called on + // the IO thread. Calls |anchor_used_callback| on the IO thread everytime a + // certificate from the additional trust anchors (set with SetTrustAnchors) is + // used. + explicit PolicyCertVerifier(); + ~PolicyCertVerifier() override; + + void InitializeOnIOThread( + const scoped_refptr& verify_proc); + + // Sets the additional trust anchors. + void SetTrustAnchors(const net::CertificateList& trust_anchors); + + // CertVerifier: + // Note: |callback| can be null. + int Verify(net::X509Certificate* cert, + const std::string& hostname, + const std::string& ocsp_response, + int flags, + net::CRLSet* crl_set, + net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback, + scoped_ptr* out_req, + const net::BoundNetLog& net_log) override; + + // CertTrustAnchorProvider: + const net::CertificateList& GetAdditionalTrustAnchors() override; + + private: + net::CertificateList trust_anchors_; + scoped_ptr delegate_; + + DISALLOW_COPY_AND_ASSIGN(PolicyCertVerifier); +}; + +} // namespace nw + +#endif // NW_POLICY_POLICY_CERT_VERIFIER_H_ From 49e5830bb9d2fd3a6d6da2339e349aacd06c5da2 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 10 Nov 2015 16:19:45 +0800 Subject: [PATCH 047/404] bump version to 0.13.0-alpha6 --- src/nw_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nw_version.h b/src/nw_version.h index b89c8b118c..ffed25a050 100644 --- a/src/nw_version.h +++ b/src/nw_version.h @@ -38,7 +38,7 @@ #else # define NW_VERSION_STRING NW_STRINGIFY(NW_MAJOR_VERSION) "." \ NW_STRINGIFY(NW_MINOR_VERSION) "." \ - NW_STRINGIFY(NW_PATCH_VERSION) "-alpha5" + NW_STRINGIFY(NW_PATCH_VERSION) "-alpha6" #endif #define NW_VERSION "v" NW_VERSION_STRING From 03c04e157c37cee29a945b18cf3d34214814113e Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 10 Nov 2015 16:21:22 +0800 Subject: [PATCH 048/404] append 'v' for version in upload path --- tools/aws_uploader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/aws_uploader.py b/tools/aws_uploader.py index 04095e3e76..ec646cc965 100755 --- a/tools/aws_uploader.py +++ b/tools/aws_uploader.py @@ -59,7 +59,7 @@ # it's for S3, so always use '/' here #upload_path = ''.join(['/' + date, # '/' + builder_name + '-build-' + build_number + '-' + got_revision]) -upload_path = '/live-build/' + dlpath + '/' + nw_ver; +upload_path = '/live-build/' + dlpath + '/v' + nw_ver; file_list = os.listdir(dist_dir) if len(file_list) == 0: From 6e11196eb731d60af2aab55847199cf0aeb8290d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 10 Nov 2015 22:00:55 +0800 Subject: [PATCH 049/404] [test] add child_process --- test/remoting/child_process/index.html | 20 ++++++++++++++++++++ test/remoting/child_process/package.json | 4 ++++ test/remoting/child_process/test.py | 19 +++++++++++++++++++ test/remoting/child_process/worker.js | 1 + 4 files changed, 44 insertions(+) create mode 100644 test/remoting/child_process/index.html create mode 100644 test/remoting/child_process/package.json create mode 100644 test/remoting/child_process/test.py create mode 100644 test/remoting/child_process/worker.js diff --git a/test/remoting/child_process/index.html b/test/remoting/child_process/index.html new file mode 100644 index 0000000000..a2626e72e5 --- /dev/null +++ b/test/remoting/child_process/index.html @@ -0,0 +1,20 @@ + + + + + child_process test + + +

child_process test

+ + + + diff --git a/test/remoting/child_process/package.json b/test/remoting/child_process/package.json new file mode 100644 index 0000000000..6211ebbb61 --- /dev/null +++ b/test/remoting/child_process/package.json @@ -0,0 +1,4 @@ +{ + "name":"test-child-process", + "main":"index.html" +} diff --git a/test/remoting/child_process/test.py b/test/remoting/child_process/test.py new file mode 100644 index 0000000000..b51747f981 --- /dev/null +++ b/test/remoting/child_process/test.py @@ -0,0 +1,19 @@ +import time +import os +import shutil + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +testdir = os.path.dirname(os.path.abspath(__file__)) +chrome_options.add_argument("nwapp=" + testdir) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() diff --git a/test/remoting/child_process/worker.js b/test/remoting/child_process/worker.js new file mode 100644 index 0000000000..116d521aca --- /dev/null +++ b/test/remoting/child_process/worker.js @@ -0,0 +1 @@ +process.on('message', function(msg) { process.send('success: message from worker'); }); \ No newline at end of file From 2657e16e5b6b23fc83fa85510b1ef87337c430db Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 11 Nov 2015 15:56:50 +0800 Subject: [PATCH 050/404] add content verifying support --- nw.gypi | 2 + src/nw_content_verifier_delegate.cc | 159 ++++++++++++++++++++++++++++ src/nw_content_verifier_delegate.h | 44 ++++++++ 3 files changed, 205 insertions(+) create mode 100644 src/nw_content_verifier_delegate.cc create mode 100644 src/nw_content_verifier_delegate.h diff --git a/nw.gypi b/nw.gypi index d5b4a6a3b6..52e4327484 100644 --- a/nw.gypi +++ b/nw.gypi @@ -678,6 +678,8 @@ 'src/nw_custom_bindings.h', 'src/policy_cert_verifier.cc', 'src/policy_cert_verifier.h', + 'src/nw_content_verifier_delegate.cc', + 'src/nw_content_verifier_delegate.h', ], 'conditions': [ ['OS=="win" or OS=="linux"', { diff --git a/src/nw_content_verifier_delegate.cc b/src/nw_content_verifier_delegate.cc new file mode 100644 index 0000000000..1a9921f38f --- /dev/null +++ b/src/nw_content_verifier_delegate.cc @@ -0,0 +1,159 @@ +// Copyright 2015 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 "content/nw/src/nw_content_verifier_delegate.h" + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_util.h" +#include "base/version.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension_constants.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/management_policy.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_urls.h" +#include "extensions/common/extensions_client.h" +#include "extensions/common/manifest.h" +#include "extensions/common/manifest_url_handlers.h" +#include "net/base/escape.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/extensions/extension_assets_manager_chromeos.h" +#endif + +#include "chrome/browser/extensions/extension_error_reporter.h" + +namespace { + +const char kContentVerificationExperimentName[] = + "ExtensionContentVerification"; + +const uint8 kNWSignaturesPublicKey[] = { + 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, + 0x00, 0xcb, 0x26, 0xf3, 0xa3, 0xd7, 0x72, 0x57, 0x31, 0x1b, 0x7a, 0x19, 0xc0, 0x42, 0x4d, 0xbd, + 0x14, 0xfb, 0x52, 0xcf, 0x4c, 0x34, 0x2a, 0x54, 0x93, 0x03, 0x7a, 0x10, 0x0d, 0xac, 0xf4, 0x45, + 0xee, 0xa0, 0x12, 0x50, 0x4b, 0xdc, 0x33, 0xf6, 0x51, 0x0d, 0xea, 0x7f, 0xfb, 0x12, 0xc5, 0x3b, + 0xbb, 0xa9, 0x7d, 0xbc, 0x2a, 0xaf, 0x3f, 0x68, 0x17, 0x7c, 0x96, 0x4a, 0xe7, 0x68, 0xea, 0x76, + 0xbd, 0x4d, 0x7b, 0xa0, 0x4f, 0x5f, 0xd7, 0x8a, 0x4a, 0xc9, 0xe6, 0xdb, 0x92, 0xda, 0x79, 0x44, + 0xa4, 0x1a, 0x99, 0x4f, 0xe5, 0xea, 0x29, 0xbc, 0x2d, 0xb8, 0x7c, 0xf0, 0x6e, 0xc3, 0x53, 0x25, + 0x63, 0x97, 0x65, 0x1a, 0x74, 0xc3, 0x8b, 0xe3, 0x6b, 0x30, 0xff, 0x9b, 0x56, 0xfe, 0xdc, 0x69, + 0xd3, 0x12, 0xe2, 0xd1, 0x35, 0x38, 0x75, 0x83, 0x5c, 0xe8, 0x9a, 0xbe, 0xe7, 0xdb, 0x8f, 0x6c, + 0xf7, 0x48, 0x6e, 0x72, 0x44, 0x33, 0xe0, 0xa3, 0xf8, 0x55, 0xfb, 0x4b, 0x72, 0x13, 0x47, 0x09, + 0x2c, 0x08, 0x2b, 0x14, 0x39, 0x3f, 0x87, 0x7a, 0x49, 0xc0, 0x12, 0xec, 0xab, 0xac, 0x8b, 0x8f, + 0x12, 0x92, 0x2b, 0xda, 0x14, 0xa2, 0x14, 0xe1, 0x89, 0x7e, 0x70, 0x53, 0xb1, 0x85, 0x1d, 0x39, + 0x0a, 0xc0, 0xa2, 0x0c, 0xc0, 0x14, 0xff, 0x7c, 0x0d, 0xe6, 0x09, 0x99, 0x65, 0x9f, 0xc4, 0x9b, + 0x87, 0x10, 0xb9, 0x3b, 0x8a, 0x82, 0xc6, 0x5c, 0xf3, 0xd4, 0x59, 0x5e, 0x34, 0x54, 0xc2, 0x84, + 0x26, 0x9b, 0x61, 0x3b, 0x3b, 0xf9, 0x5e, 0x2b, 0xeb, 0xc8, 0xac, 0x67, 0x6b, 0x18, 0x8b, 0x39, + 0x69, 0x71, 0xc4, 0xe1, 0xde, 0xb1, 0xb0, 0xa3, 0x4e, 0x86, 0x5c, 0x29, 0x7c, 0x8b, 0xcd, 0x3d, + 0x27, 0xa4, 0x71, 0xcf, 0x9a, 0x62, 0xae, 0x54, 0x54, 0xaa, 0x0f, 0x1e, 0xb3, 0x78, 0x72, 0x67, + 0x91, 0x02, 0x03, 0x01, 0x00, 0x01}; + +const int kNWSignaturesPublicKeySize = + arraysize(kNWSignaturesPublicKey); + +} // namespace + +namespace extensions { + +// static +ContentVerifierDelegate::Mode NWContentVerifierDelegate::GetDefaultMode() { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + + Mode experiment_value = NONE; + const std::string group = + base::FieldTrialList::FindFullName(kContentVerificationExperimentName); + if (group == "EnforceStrict") + experiment_value = ContentVerifierDelegate::ENFORCE_STRICT; + else if (group == "Enforce") + experiment_value = ContentVerifierDelegate::ENFORCE; + else if (group == "Bootstrap") + experiment_value = ContentVerifierDelegate::BOOTSTRAP; + + Mode cmdline_value = NONE; + if (command_line->HasSwitch(switches::kExtensionContentVerification)) { + std::string switch_value = command_line->GetSwitchValueASCII( + switches::kExtensionContentVerification); + if (switch_value == switches::kExtensionContentVerificationBootstrap) + cmdline_value = ContentVerifierDelegate::BOOTSTRAP; + else if (switch_value == switches::kExtensionContentVerificationEnforce) + cmdline_value = ContentVerifierDelegate::ENFORCE; + else if (switch_value == + switches::kExtensionContentVerificationEnforceStrict) + cmdline_value = ContentVerifierDelegate::ENFORCE_STRICT; + else + // If no value was provided (or the wrong one), just default to enforce. + cmdline_value = ContentVerifierDelegate::ENFORCE; + } + + // We don't want to allow the command-line flags to eg disable enforcement + // if the experiment group says it should be on, or malware may just modify + // the command line flags. So return the more restrictive of the 2 values. + return std::max(experiment_value, cmdline_value); +} + +NWContentVerifierDelegate::NWContentVerifierDelegate( + content::BrowserContext* context) + : context_(context), default_mode_(GetDefaultMode()) { +} + +NWContentVerifierDelegate::~NWContentVerifierDelegate() { +} + +ContentVerifierDelegate::Mode NWContentVerifierDelegate::ShouldBeVerified( + const Extension& extension) { + + if (extension.is_nwjs_app() && !Manifest::IsComponentLocation(extension.location())) + return default_mode_; + if (!extension.is_extension() && !extension.is_legacy_packaged_app()) + return ContentVerifierDelegate::NONE; + if (!Manifest::IsAutoUpdateableLocation(extension.location())) + return ContentVerifierDelegate::NONE; + + return default_mode_; +} + +ContentVerifierKey NWContentVerifierDelegate::GetPublicKey() { + return ContentVerifierKey(kNWSignaturesPublicKey, + kNWSignaturesPublicKeySize); +} + +GURL NWContentVerifierDelegate::GetSignatureFetchUrl( + const std::string& extension_id, + const base::Version& version) { + return GURL(); +} + +std::set NWContentVerifierDelegate::GetBrowserImagePaths( + const extensions::Extension* extension) { + return ExtensionsClient::Get()->GetBrowserImagePaths(extension); +} + +void NWContentVerifierDelegate::VerifyFailed( + const std::string& extension_id, + ContentVerifyJob::FailureReason reason) { + ExtensionRegistry* registry = ExtensionRegistry::Get(context_); + const Extension* extension = + registry->enabled_extensions().GetByID(extension_id); + if (!extension) + return; + ExtensionSystem* system = ExtensionSystem::Get(context_); + Mode mode = ShouldBeVerified(*extension); + if (mode >= ContentVerifierDelegate::ENFORCE) { + system->extension_service()->DisableExtension(extension_id, + Extension::DISABLE_CORRUPTED); + ExtensionErrorReporter::GetInstance()-> + ReportLoadError(extension->path(), "Extension file corrupted", + system->extension_service()->profile(), true); + } +} + +} // namespace extensions diff --git a/src/nw_content_verifier_delegate.h b/src/nw_content_verifier_delegate.h new file mode 100644 index 0000000000..b88d60aa76 --- /dev/null +++ b/src/nw_content_verifier_delegate.h @@ -0,0 +1,44 @@ +// Copyright 2015 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 NW_CONTENT_VERIFIER_DELEGATE_H_ +#define NW_CONTENT_VERIFIER_DELEGATE_H_ + +#include "extensions/browser/content_verifier_delegate.h" + +namespace content { +class BrowserContext; +} + +namespace extensions { + +class NWContentVerifierDelegate : public ContentVerifierDelegate { + public: + static Mode GetDefaultMode(); + + explicit NWContentVerifierDelegate(content::BrowserContext* context); + + ~NWContentVerifierDelegate() override; + + // ContentVerifierDelegate: + Mode ShouldBeVerified(const Extension& extension) override; + ContentVerifierKey GetPublicKey() override; + GURL GetSignatureFetchUrl(const std::string& extension_id, + const base::Version& version) override; + std::set GetBrowserImagePaths( + const extensions::Extension* extension) override; + void VerifyFailed(const std::string& extension_id, + ContentVerifyJob::FailureReason reason) override; + + content::BrowserContext* context_; + ContentVerifierDelegate::Mode default_mode_; + + std::set would_be_disabled_ids_; + + DISALLOW_COPY_AND_ASSIGN(NWContentVerifierDelegate); +}; + +} // namespace extensions + +#endif // NW_CONTENT_VERIFIER_DELEGATE_H_ From 53cbc2cdf57b702c74522730b1ebad0e49897671 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 11 Nov 2015 21:40:31 +0800 Subject: [PATCH 051/404] Fix content verify message --- src/nw_content_verifier_delegate.cc | 7 ++++--- src/nw_content_verifier_delegate.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/nw_content_verifier_delegate.cc b/src/nw_content_verifier_delegate.cc index 1a9921f38f..efd4b19013 100644 --- a/src/nw_content_verifier_delegate.cc +++ b/src/nw_content_verifier_delegate.cc @@ -139,6 +139,7 @@ std::set NWContentVerifierDelegate::GetBrowserImagePaths( void NWContentVerifierDelegate::VerifyFailed( const std::string& extension_id, + const base::FilePath& relative_path, ContentVerifyJob::FailureReason reason) { ExtensionRegistry* registry = ExtensionRegistry::Get(context_); const Extension* extension = @@ -148,11 +149,11 @@ void NWContentVerifierDelegate::VerifyFailed( ExtensionSystem* system = ExtensionSystem::Get(context_); Mode mode = ShouldBeVerified(*extension); if (mode >= ContentVerifierDelegate::ENFORCE) { - system->extension_service()->DisableExtension(extension_id, - Extension::DISABLE_CORRUPTED); ExtensionErrorReporter::GetInstance()-> - ReportLoadError(extension->path(), "Extension file corrupted", + ReportLoadError(extension->path(), "Extension file corrupted: " + relative_path.AsUTF8Unsafe(), system->extension_service()->profile(), true); + system->extension_service()->DisableExtension(extension_id, + Extension::DISABLE_CORRUPTED); } } diff --git a/src/nw_content_verifier_delegate.h b/src/nw_content_verifier_delegate.h index b88d60aa76..974066cc3d 100644 --- a/src/nw_content_verifier_delegate.h +++ b/src/nw_content_verifier_delegate.h @@ -29,6 +29,7 @@ class NWContentVerifierDelegate : public ContentVerifierDelegate { std::set GetBrowserImagePaths( const extensions::Extension* extension) override; void VerifyFailed(const std::string& extension_id, + const base::FilePath& relative_path, ContentVerifyJob::FailureReason reason) override; content::BrowserContext* context_; From fa0fb1828c08a19bb1b768333159a8e13366c57a Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 12 Nov 2015 13:11:09 +0800 Subject: [PATCH 052/404] upload checksum of header --- tools/package_binaries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/package_binaries.py b/tools/package_binaries.py index ae1214ce64..6b10246643 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -285,6 +285,7 @@ def generate_target_headers(platform_name, arch, version): checksum_file.write('%s %s' % (sha256(f.read()).hexdigest(), nw_headers_name)) shutil.move(nw_headers_path, binaries_location) target['input'].append(nw_headers_name) + target['input'].append('SHASUMS256.txt') else: #TODO, handle err print 'nw-headers generate failed' From 0dc425737647ea4c8f5d01f053f85f0c5347e4fe Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 13 Nov 2015 12:49:50 +0800 Subject: [PATCH 053/404] verify-content argument --- src/common/shell_switches.cc | 1 + src/common/shell_switches.h | 2 ++ src/nw_content_verifier_delegate.cc | 6 ++++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/common/shell_switches.cc b/src/common/shell_switches.cc index 2093a57fc1..3e36302ca5 100644 --- a/src/common/shell_switches.cc +++ b/src/common/shell_switches.cc @@ -45,6 +45,7 @@ const char kSnapshot[] = "snapshot"; const char kDomStorageQuota[] = "ds-quota"; const char kNodejs[] = "nodejs"; const char kChromeExtension[] = "extension"; +const char kVerifyContent[] = "verify-content"; const char kmMain[] = "main"; const char kmName[] = "name"; diff --git a/src/common/shell_switches.h b/src/common/shell_switches.h index bba03710eb..0547108d9e 100644 --- a/src/common/shell_switches.h +++ b/src/common/shell_switches.h @@ -26,6 +26,8 @@ extern NW_EXPORT const char kSnapshot[]; extern NW_EXPORT const char kDomStorageQuota[]; extern NW_EXPORT const char kNodejs[]; extern NW_EXPORT const char kChromeExtension[]; +extern NW_EXPORT const char kVerifyContent[]; + // Manifest settings extern NW_EXPORT const char kmMain[]; extern NW_EXPORT const char kmName[]; diff --git a/src/nw_content_verifier_delegate.cc b/src/nw_content_verifier_delegate.cc index efd4b19013..60ebb1565a 100644 --- a/src/nw_content_verifier_delegate.cc +++ b/src/nw_content_verifier_delegate.cc @@ -25,6 +25,8 @@ #include "extensions/common/manifest_url_handlers.h" #include "net/base/escape.h" +#include "content/nw/src/common/shell_switches.h" + #if defined(OS_CHROMEOS) #include "chrome/browser/extensions/extension_assets_manager_chromeos.h" #endif @@ -79,9 +81,9 @@ ContentVerifierDelegate::Mode NWContentVerifierDelegate::GetDefaultMode() { experiment_value = ContentVerifierDelegate::BOOTSTRAP; Mode cmdline_value = NONE; - if (command_line->HasSwitch(switches::kExtensionContentVerification)) { + if (command_line->HasSwitch(switches::kVerifyContent)) { std::string switch_value = command_line->GetSwitchValueASCII( - switches::kExtensionContentVerification); + switches::kVerifyContent); if (switch_value == switches::kExtensionContentVerificationBootstrap) cmdline_value = ContentVerifierDelegate::BOOTSTRAP; else if (switch_value == switches::kExtensionContentVerificationEnforce) From ceebb52ab452b04597d2b93cd51d9cd8c8a01384 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 16 Nov 2015 13:18:53 +0800 Subject: [PATCH 054/404] move sandbox control to chromium --- src/nw_content.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nw_content.cc b/src/nw_content.cc index 360b94f362..964db5876c 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -174,7 +174,6 @@ bool GetPackageImage(nw::Package* package, int MainPartsPreCreateThreadsHook() { base::ThreadRestrictions::ScopedAllowIO allow_io; base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - command_line->AppendSwitch(switches::kNoSandbox); nw::Package* package = InitNWPackage(); if (package && !package->path().empty()) { base::FilePath path = package->path().NormalizePathSeparators(); From 87eecb2060742e2b0a061d3d58a6723a626161cd Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 17 Nov 2015 14:34:34 +0800 Subject: [PATCH 055/404] migrate App.argv and App.clearCache --- nw.gypi | 2 ++ src/api/api_registration.gyp | 11 ++++++++ src/api/nw_app.idl | 3 +++ src/api/nw_app_api.cc | 52 ++++++++++++++++++++++++++++++++++++ src/api/nw_app_api.h | 32 ++++++++++++++++++++++ src/resources/api_nw_app.js | 12 +++++++++ 6 files changed, 112 insertions(+) diff --git a/nw.gypi b/nw.gypi index 52e4327484..2943c1aaf9 100644 --- a/nw.gypi +++ b/nw.gypi @@ -637,6 +637,8 @@ '<(DEPTH)/content/nw/src/api/api.gyp:nw_api', '<(DEPTH)/content/nw/src/api/api_registration.gyp:nw_api_registration', '<(DEPTH)/extensions/browser/api/api_registration.gyp:extensions_api_registration', + '<(DEPTH)/components/components.gyp:policy', + '<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite', ], 'include_dirs': [ '<(DEPTH)/third_party/mojo/src', diff --git a/src/api/api_registration.gyp b/src/api/api_registration.gyp index 5af5283352..28bcd3f215 100644 --- a/src/api/api_registration.gyp +++ b/src/api/api_registration.gyp @@ -15,6 +15,17 @@ ], 'dependencies': [ 'api.gyp:nw_api', + # Different APIs include headers from these targets. + "<(DEPTH)/content/content.gyp:content_browser", + + # Different APIs include some headers from chrome/common that in turn + # include generated headers from these targets. + # TODO(brettw) this should be made unnecessary if possible. + '<(DEPTH)/components/components.gyp:component_metrics_proto', + '<(DEPTH)/components/components.gyp:copresence_proto', + '<(DEPTH)/skia/skia.gyp:skia', + '<(DEPTH)/sync/sync.gyp:sync', + '<(DEPTH)/ui/accessibility/accessibility.gyp:ax_gen', ], }, ], diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index 7ef2bde9f8..ef10b66e9c 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -5,5 +5,8 @@ namespace nw.App { // crash the renderer for testing [nocompile] static void crashRenderer(); static void quit(); + static void clearCache(); + [nocompile] static DOMString[] argv(); + static DOMString[] getArgvSync(); }; }; diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index 0cb60abac0..01efab6534 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -1,8 +1,11 @@ #include "content/nw/src/api/nw_app_api.h" +#include "base/command_line.h" +#include "chrome/browser/browsing_data/browsing_data_helper.h" #include "chrome/browser/devtools/devtools_window.h" #include "chrome/browser/extensions/devtools_util.h" #include "chrome/browser/extensions/extension_service.h" +#include "content/nw/src/nw_base.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_system.h" @@ -27,4 +30,53 @@ bool NwAppQuitFunction::RunAsync() { return true; } +NwAppGetArgvSyncFunction::NwAppGetArgvSyncFunction() { +} + +NwAppGetArgvSyncFunction::~NwAppGetArgvSyncFunction() { +} + +bool NwAppGetArgvSyncFunction::RunNWSync(base::ListValue* response, std::string* error) { + + nw::Package* package = nw::package(); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + base::CommandLine::StringVector args = command_line->GetArgs(); + base::CommandLine::StringVector argv = command_line->original_argv(); + + // Ignore first non-switch arg if it's not a standalone package. + bool ignore_arg = !package->self_extract(); + for (unsigned i = 1; i < argv.size(); ++i) { + if (ignore_arg && args.size() && argv[i] == args[0]) { + ignore_arg = false; + continue; + } + + response->AppendString(argv[i]); + } + return true; +} + +NwAppClearCacheFunction::NwAppClearCacheFunction() { +} + +NwAppClearCacheFunction::~NwAppClearCacheFunction() { +} + +bool NwAppClearCacheFunction::RunNWSync(base::ListValue* response, std::string* error) { + BrowsingDataRemover* remover = BrowsingDataRemover::CreateForUnboundedRange( + Profile::FromBrowserContext(context_)); + + remover->AddObserver(this); + remover->Remove(BrowsingDataRemover::REMOVE_CACHE, + BrowsingDataHelper::ALL); + // BrowsingDataRemover deletes itself. + run_loop_.Run(); + return true; +} + +void NwAppClearCacheFunction::OnBrowsingDataRemoverDone() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + run_loop_.Quit(); +} + } // namespace extensions diff --git a/src/api/nw_app_api.h b/src/api/nw_app_api.h index 638a094bac..7d5f44644b 100644 --- a/src/api/nw_app_api.h +++ b/src/api/nw_app_api.h @@ -3,6 +3,8 @@ #include +#include "base/run_loop.h" +#include "chrome/browser/browsing_data/browsing_data_remover.h" #include "extensions/browser/extension_function.h" namespace extensions { @@ -21,5 +23,35 @@ class NwAppQuitFunction : public AsyncExtensionFunction { void Callback(); }; +class NwAppGetArgvSyncFunction : public NWSyncExtensionFunction { + public: + NwAppGetArgvSyncFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwAppGetArgvSyncFunction() override; + + + DECLARE_EXTENSION_FUNCTION("nw.App.getArgvSync", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwAppGetArgvSyncFunction); +}; + +class NwAppClearCacheFunction : public NWSyncExtensionFunction, public BrowsingDataRemover::Observer { + public: + NwAppClearCacheFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + void OnBrowsingDataRemoverDone() override; + + protected: + ~NwAppClearCacheFunction() override; + + base::RunLoop run_loop_; + + DECLARE_EXTENSION_FUNCTION("nw.App.clearCache", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwAppClearCacheFunction); +}; + } // namespace extensions #endif diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index 165cf4615f..1ffb70e1a3 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -1,11 +1,23 @@ var nw_binding = require('binding').Binding.create('nw.App'); var nwNatives = requireNative('nw_natives'); +var sendRequest = require('sendRequest'); + +var argv = null; nw_binding.registerCustomHook(function(bindingsAPI) { var apiFunctions = bindingsAPI.apiFunctions; apiFunctions.setHandleRequest('crashRenderer', function() { nwNatives.crashRenderer(); }); + apiFunctions.setHandleRequest('argv', function() { + if (argv) + return argv; + argv = nw.App.getArgvSync(); + return argv; + }); + apiFunctions.setHandleRequest('getArgvSync', function() { + return sendRequest.sendRequestSync('nw.App.getArgvSync', [], this.definition.parameters, {}); + }); }); exports.binding = nw_binding.generate(); From 1a1108d2e5086320637cd255ebce1c7e9fef6105 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 17 Nov 2015 17:26:27 +0800 Subject: [PATCH 056/404] migrate App.setProxyConfig --- src/api/nw_app.idl | 1 + src/api/nw_app_api.cc | 45 +++++++++++++++++++++++++++++++++++++++++++ src/api/nw_app_api.h | 14 ++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index ef10b66e9c..9eb385ad16 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -6,6 +6,7 @@ namespace nw.App { [nocompile] static void crashRenderer(); static void quit(); static void clearCache(); + static void setProxyConfig(DOMString config); [nocompile] static DOMString[] argv(); static DOMString[] getArgvSync(); }; diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index 01efab6534..4274767892 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -6,10 +6,29 @@ #include "chrome/browser/extensions/devtools_util.h" #include "chrome/browser/extensions/extension_service.h" #include "content/nw/src/nw_base.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_system.h" #include "extensions/common/error_utils.h" +#include "net/proxy/proxy_config.h" +#include "net/proxy/proxy_config_service_fixed.h" +#include "net/proxy/proxy_service.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" + +namespace { +void SetProxyConfigCallback( + base::WaitableEvent* done, + net::URLRequestContextGetter* url_request_context_getter, + const net::ProxyConfig& proxy_config) { + net::ProxyService* proxy_service = + url_request_context_getter->GetURLRequestContext()->proxy_service(); + proxy_service->ResetConfigService( + new net::ProxyConfigServiceFixed(proxy_config)); + done->Signal(); +} +} // namespace namespace extensions { NwAppQuitFunction::NwAppQuitFunction() { @@ -79,4 +98,30 @@ void NwAppClearCacheFunction::OnBrowsingDataRemoverDone() { run_loop_.Quit(); } +NwAppSetProxyConfigFunction::NwAppSetProxyConfigFunction() { +} + +NwAppSetProxyConfigFunction::~NwAppSetProxyConfigFunction() { +} + +bool NwAppSetProxyConfigFunction::RunNWSync(base::ListValue* response, std::string* error) { + std::string proxy_config; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &proxy_config)); + + net::ProxyConfig config; + config.proxy_rules().ParseFromString(proxy_config); + content::RenderProcessHost* render_process_host = GetSenderWebContents()->GetRenderProcessHost(); + net::URLRequestContextGetter* context_getter = + render_process_host->GetBrowserContext()-> + GetRequestContextForRenderProcess(render_process_host->GetID()); + + base::WaitableEvent done(false, false); + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&SetProxyConfigCallback, &done, + make_scoped_refptr(context_getter), config)); + done.Wait(); + return true; +} + } // namespace extensions diff --git a/src/api/nw_app_api.h b/src/api/nw_app_api.h index 7d5f44644b..12a5c8f413 100644 --- a/src/api/nw_app_api.h +++ b/src/api/nw_app_api.h @@ -53,5 +53,19 @@ class NwAppClearCacheFunction : public NWSyncExtensionFunction, public BrowsingD DISALLOW_COPY_AND_ASSIGN(NwAppClearCacheFunction); }; +class NwAppSetProxyConfigFunction : public NWSyncExtensionFunction { + public: + NwAppSetProxyConfigFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwAppSetProxyConfigFunction() override; + + + DECLARE_EXTENSION_FUNCTION("nw.App.setProxyConfig", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwAppSetProxyConfigFunction); +}; + } // namespace extensions #endif From 0c1f7e1b51ee563698a17e4805f54dafedda833f Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 18 Nov 2015 09:51:52 +0800 Subject: [PATCH 057/404] port addOriginAccessWhitelistEntry and removeOriginAccessWhitelistEntry --- src/api/nw_app.idl | 2 ++ src/nw_custom_bindings.cc | 39 +++++++++++++++++++++++++++++++++++++ src/nw_custom_bindings.h | 2 ++ src/resources/api_nw_app.js | 8 +++++++- 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index 9eb385ad16..ea760663e2 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -7,6 +7,8 @@ namespace nw.App { static void quit(); static void clearCache(); static void setProxyConfig(DOMString config); + [nocompile] static void addOriginAccessWhitelistEntry(DOMString sourceOrigin, DOMString destinationProtocol, DOMString destinationHost, boolean allowDestinationSubdomains); + [nocompile] static void removeOriginAccessWhitelistEntry(DOMString sourceOrigin, DOMString destinationProtocol, DOMString destinationHost, boolean allowDestinationSubdomains); [nocompile] static DOMString[] argv(); static DOMString[] getArgvSync(); }; diff --git a/src/nw_custom_bindings.cc b/src/nw_custom_bindings.cc index 4fbed1b809..d5a21d02cb 100644 --- a/src/nw_custom_bindings.cc +++ b/src/nw_custom_bindings.cc @@ -35,6 +35,7 @@ using namespace blink; #define BLINK_IMPLEMENTATION 1 #include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebSecurityPolicy.h" #include "third_party/WebKit/Source/platform/heap/Handle.h" //#include "third_party/WebKit/Source/core/inspector/InspectorInstrumentation.h" //#include "third_party/WebKit/Source/core/inspector/InspectorResourceAgent.h" @@ -90,6 +91,12 @@ NWCustomBindings::NWCustomBindings(ScriptContext* context) RouteFunction("getAbsolutePath", base::Bind(&NWCustomBindings::GetAbsolutePath, base::Unretained(this))); + RouteFunction("addOriginAccessWhitelistEntry", + base::Bind(&NWCustomBindings::AddOriginAccessWhitelistEntry, + base::Unretained(this))); + RouteFunction("removeOriginAccessWhitelistEntry", + base::Bind(&NWCustomBindings::RemoveOriginAccessWhitelistEntry, + base::Unretained(this))); } void NWCustomBindings::CrashRenderer( @@ -187,4 +194,36 @@ void NWCustomBindings::GetAbsolutePath( #endif } +void NWCustomBindings::AddOriginAccessWhitelistEntry(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + + std::string sourceOrigin = *v8::String::Utf8Value(args[0]); + std::string destinationProtocol = *v8::String::Utf8Value(args[1]); + std::string destinationHost = *v8::String::Utf8Value(args[2]); + bool allowDestinationSubdomains = args[3]->ToBoolean()->Value(); + + blink::WebSecurityPolicy::addOriginAccessWhitelistEntry(GURL(sourceOrigin), + blink::WebString::fromUTF8(destinationProtocol), + blink::WebString::fromUTF8(destinationHost), + allowDestinationSubdomains); + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; +} + +void NWCustomBindings::RemoveOriginAccessWhitelistEntry(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + + std::string sourceOrigin = *v8::String::Utf8Value(args[0]); + std::string destinationProtocol = *v8::String::Utf8Value(args[1]); + std::string destinationHost = *v8::String::Utf8Value(args[2]); + bool allowDestinationSubdomains = args[3]->ToBoolean()->Value(); + + blink::WebSecurityPolicy::removeOriginAccessWhitelistEntry(GURL(sourceOrigin), + blink::WebString::fromUTF8(destinationProtocol), + blink::WebString::fromUTF8(destinationHost), + allowDestinationSubdomains); + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; +} + } // namespace extensions diff --git a/src/nw_custom_bindings.h b/src/nw_custom_bindings.h index eb924f4925..83fd0d1418 100644 --- a/src/nw_custom_bindings.h +++ b/src/nw_custom_bindings.h @@ -20,6 +20,8 @@ class NWCustomBindings : public ObjectBackedNativeHandler { void EvalScript(const v8::FunctionCallbackInfo& args); void EvalNWBin(const v8::FunctionCallbackInfo& args); void GetAbsolutePath(const v8::FunctionCallbackInfo& args); + void AddOriginAccessWhitelistEntry(const v8::FunctionCallbackInfo& args); + void RemoveOriginAccessWhitelistEntry(const v8::FunctionCallbackInfo& args); }; } // namespace extensions diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index 1ffb70e1a3..7d888a2d75 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -16,7 +16,13 @@ nw_binding.registerCustomHook(function(bindingsAPI) { return argv; }); apiFunctions.setHandleRequest('getArgvSync', function() { - return sendRequest.sendRequestSync('nw.App.getArgvSync', [], this.definition.parameters, {}); + return sendRequest.sendRequestSync('nw.App.getArgvSync', [], this.definition.parameters, {}); + }); + apiFunctions.setHandleRequest('addOriginAccessWhitelistEntry', function() { + nwNatives.addOriginAccessWhitelistEntry.apply(this, arguments); + }); + apiFunctions.setHandleRequest('removeOriginAccessWhitelistEntry', function() { + nwNatives.removeOriginAccessWhitelistEntry.apply(this, arguments); }); }); From bee9774fbc6441e8804ae4520f3fc7914e0fe23f Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 18 Nov 2015 16:08:14 +0800 Subject: [PATCH 058/404] port App.open event --- src/api/nw_app.idl | 9 +++++++++ src/nw_content.cc | 30 ++++++++++++++++++++++++++++++ src/resources/api_nw_app.js | 7 +++++++ 3 files changed, 46 insertions(+) diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index ea760663e2..be4bcfd5df 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -1,9 +1,15 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + // nw APP API [implemented_in="content/nw/src/api/nw_app_api.h"] namespace nw.App { + callback EventCallback = void(); interface Functions { // crash the renderer for testing [nocompile] static void crashRenderer(); + [nocompile] static void on(DOMString event, EventCallback callback); static void quit(); static void clearCache(); static void setProxyConfig(DOMString config); @@ -12,4 +18,7 @@ namespace nw.App { [nocompile] static DOMString[] argv(); static DOMString[] getArgvSync(); }; + interface Events { + static void onOpen(); + }; }; diff --git a/src/nw_content.cc b/src/nw_content.cc index 964db5876c..336b71ae57 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -14,6 +14,7 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/io_thread.h" +#include "chrome/browser/profiles/profile_manager.h" #include "content/common/dom_storage/dom_storage_map.h" #include "content/nw/src/common/shell_switches.h" @@ -30,6 +31,9 @@ #include "content/nw/src/api/object_manager.h" #include "content/nw/src/policy_cert_verifier.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" #include "extensions/renderer/dispatcher.h" #include "extensions/renderer/script_context.h" #include "extensions/common/extension.h" @@ -84,8 +88,12 @@ using content::RenderView; using content::RenderViewImpl; using extensions::ScriptContext; +using extensions::Extension; +using extensions::EventRouter; using extensions::Manifest; using extensions::Feature; +using extensions::ExtensionPrefs; +using extensions::ExtensionRegistry; using blink::WebScriptSource; namespace manifest_keys = extensions::manifest_keys; @@ -750,6 +758,28 @@ bool ProcessSingletonNotificationCallbackHook(const base::CommandLine& command_l nw::Package* package = nw::package(); bool single_instance = true; package->root()->GetBoolean(switches::kmSingleInstance, &single_instance); + if (single_instance) { + Profile* profile = ProfileManager::GetActiveUserProfile(); + const extensions::ExtensionSet& extensions = + ExtensionRegistry::Get(profile)->enabled_extensions(); + ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile); + + for (extensions::ExtensionSet::const_iterator it = extensions.begin(); + it != extensions.end(); ++it) { + const Extension* extension = it->get(); + if (extension_prefs->IsExtensionRunning(extension->id()) && + extension->location() == extensions::Manifest::COMMAND_LINE) { + scoped_ptr arguments(new base::ListValue()); + scoped_ptr event(new extensions::Event(extensions::events::UNKNOWN, + "nw.App.onOpen", + arguments.Pass())); + event->restrict_to_browser_context = profile; + EventRouter::Get(profile) + ->DispatchEventToExtension(extension->id(), event.Pass()); + } + } + } + return single_instance; } diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index 7d888a2d75..5afe4eca01 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -24,6 +24,13 @@ nw_binding.registerCustomHook(function(bindingsAPI) { apiFunctions.setHandleRequest('removeOriginAccessWhitelistEntry', function() { nwNatives.removeOriginAccessWhitelistEntry.apply(this, arguments); }); + apiFunctions.setHandleRequest('on', function(event, callback) { + switch (event) { + case 'open': + nw.App.onOpen.addListener(callback); + break; + } + }); }); exports.binding = nw_binding.generate(); From a5f0557ea6369d0c16ebfd5d1d077fd9af2060a4 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 18 Nov 2015 16:08:35 +0800 Subject: [PATCH 059/404] add license header to prevent build warning --- src/api/nw_clipboard.idl | 4 ++++ src/api/nw_menu.idl | 4 ++++ src/api/nw_object.idl | 4 ++++ src/api/nw_shell.idl | 4 ++++ src/api/nw_test.idl | 4 ++++ src/api/nw_tray.idl | 4 ++++ src/api/nw_window.idl | 4 ++++ 7 files changed, 28 insertions(+) diff --git a/src/api/nw_clipboard.idl b/src/api/nw_clipboard.idl index b1c512d8a8..bbddfe5da4 100644 --- a/src/api/nw_clipboard.idl +++ b/src/api/nw_clipboard.idl @@ -1,3 +1,7 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + // nw Clipboard API [implemented_in="content/nw/src/api/nw_clipboard_api.h"] namespace nw.Clipboard { diff --git a/src/api/nw_menu.idl b/src/api/nw_menu.idl index ed66c5e78e..d6994c673e 100644 --- a/src/api/nw_menu.idl +++ b/src/api/nw_menu.idl @@ -1,3 +1,7 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + // nw Menu API [implemented_in="content/nw/src/api/nw_menu_api.h"] namespace nw.Menu { diff --git a/src/api/nw_object.idl b/src/api/nw_object.idl index 12e57d01f8..58034c9a9b 100644 --- a/src/api/nw_object.idl +++ b/src/api/nw_object.idl @@ -1,3 +1,7 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + // nw Object API [implemented_in="content/nw/src/api/nw_object_api.h"] namespace nw.Object { diff --git a/src/api/nw_shell.idl b/src/api/nw_shell.idl index d495efdd9d..00ddda22ec 100644 --- a/src/api/nw_shell.idl +++ b/src/api/nw_shell.idl @@ -1,3 +1,7 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + // nw Shell API [implemented_in="content/nw/src/api/nw_shell_api.h"] namespace nw.Shell { diff --git a/src/api/nw_test.idl b/src/api/nw_test.idl index a72bb4bf98..c46462ca2e 100644 --- a/src/api/nw_test.idl +++ b/src/api/nw_test.idl @@ -1,3 +1,7 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + // nw APP API namespace nw.test { interface Functions { diff --git a/src/api/nw_tray.idl b/src/api/nw_tray.idl index b4087cc84c..6dd33e0262 100644 --- a/src/api/nw_tray.idl +++ b/src/api/nw_tray.idl @@ -1,3 +1,7 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + // nw Tray API [implemented_in="content/nw/src/api/nw_tray_api.h"] namespace nw.Tray { diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index ad683e9697..5ca34e300c 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -1,3 +1,7 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + // nw Window API namespace nw.Window { callback CreateWindowCallback = From 8d4dcb2d518441d7ec085892693163d40cf35315 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 19 Nov 2015 12:44:51 +0800 Subject: [PATCH 060/404] Ported App.getProxyForURL and fixed App.setProxyConfig --- src/api/nw_app.idl | 1 + src/nw_custom_bindings.cc | 24 ++++++++++++++++++++++++ src/nw_custom_bindings.h | 1 + src/resources/api_nw_app.js | 6 ++++++ 4 files changed, 32 insertions(+) diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index be4bcfd5df..06e9716da0 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -13,6 +13,7 @@ namespace nw.App { static void quit(); static void clearCache(); static void setProxyConfig(DOMString config); + [nocompile] static DOMString getProxyForURL(DOMString url); [nocompile] static void addOriginAccessWhitelistEntry(DOMString sourceOrigin, DOMString destinationProtocol, DOMString destinationHost, boolean allowDestinationSubdomains); [nocompile] static void removeOriginAccessWhitelistEntry(DOMString sourceOrigin, DOMString destinationProtocol, DOMString destinationHost, boolean allowDestinationSubdomains); [nocompile] static DOMString[] argv(); diff --git a/src/nw_custom_bindings.cc b/src/nw_custom_bindings.cc index d5a21d02cb..95bb399913 100644 --- a/src/nw_custom_bindings.cc +++ b/src/nw_custom_bindings.cc @@ -5,6 +5,7 @@ #include "content/nw/src/nw_custom_bindings.h" #include "content/renderer/render_view_impl.h" +#include "content/public/renderer/render_thread.h" #include @@ -97,6 +98,9 @@ NWCustomBindings::NWCustomBindings(ScriptContext* context) RouteFunction("removeOriginAccessWhitelistEntry", base::Bind(&NWCustomBindings::RemoveOriginAccessWhitelistEntry, base::Unretained(this))); + RouteFunction("getProxyForURL", + base::Bind(&NWCustomBindings::GetProxyForURL, + base::Unretained(this))); } void NWCustomBindings::CrashRenderer( @@ -226,4 +230,24 @@ void NWCustomBindings::RemoveOriginAccessWhitelistEntry(const v8::FunctionCallba return; } +void NWCustomBindings::GetProxyForURL(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + + std::string url = *v8::String::Utf8Value(args[0]); + GURL gurl(url); + if (!gurl.is_valid()) { + args.GetReturnValue().Set(isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(isolate, + "Invalid URL passed to App.getProxyForURL()")))); + return; + } + std::string proxy; + bool result = content::RenderThread::Get()->ResolveProxy(gurl, &proxy); + if (!result) { + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; + } + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, proxy.c_str())); + return; +} + } // namespace extensions diff --git a/src/nw_custom_bindings.h b/src/nw_custom_bindings.h index 83fd0d1418..81f751d384 100644 --- a/src/nw_custom_bindings.h +++ b/src/nw_custom_bindings.h @@ -22,6 +22,7 @@ class NWCustomBindings : public ObjectBackedNativeHandler { void GetAbsolutePath(const v8::FunctionCallbackInfo& args); void AddOriginAccessWhitelistEntry(const v8::FunctionCallbackInfo& args); void RemoveOriginAccessWhitelistEntry(const v8::FunctionCallbackInfo& args); + void GetProxyForURL(const v8::FunctionCallbackInfo& args); }; } // namespace extensions diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index 5afe4eca01..b27cb66d6d 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -18,6 +18,12 @@ nw_binding.registerCustomHook(function(bindingsAPI) { apiFunctions.setHandleRequest('getArgvSync', function() { return sendRequest.sendRequestSync('nw.App.getArgvSync', [], this.definition.parameters, {}); }); + apiFunctions.setHandleRequest('setProxyConfig', function() { + sendRequest.sendRequestSync('nw.App.setProxyConfig', arguments, this.definition.parameters, {}); + }); + apiFunctions.setHandleRequest('getProxyForURL', function() { + return nwNatives.getProxyForURL.apply(this, arguments); + }); apiFunctions.setHandleRequest('addOriginAccessWhitelistEntry', function() { nwNatives.addOriginAccessWhitelistEntry.apply(this, arguments); }); From b1f7d548c0bdecc50623684d76059855fbec76e4 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 19 Nov 2015 13:31:58 +0800 Subject: [PATCH 061/404] Added test case for App.getProxyForURL --- test/remoting/app-getproxyforurl/index.html | 20 +++++++++++++++++++ test/remoting/app-getproxyforurl/package.json | 4 ++++ test/remoting/app-getproxyforurl/test.py | 17 ++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 test/remoting/app-getproxyforurl/index.html create mode 100644 test/remoting/app-getproxyforurl/package.json create mode 100644 test/remoting/app-getproxyforurl/test.py diff --git a/test/remoting/app-getproxyforurl/index.html b/test/remoting/app-getproxyforurl/index.html new file mode 100644 index 0000000000..59b1cf661c --- /dev/null +++ b/test/remoting/app-getproxyforurl/index.html @@ -0,0 +1,20 @@ + + + + + + App.getProxyForURL + + + + + \ No newline at end of file diff --git a/test/remoting/app-getproxyforurl/package.json b/test/remoting/app-getproxyforurl/package.json new file mode 100644 index 0000000000..b016f89b77 --- /dev/null +++ b/test/remoting/app-getproxyforurl/package.json @@ -0,0 +1,4 @@ +{ + "name": "app-getproxyforurl", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/app-getproxyforurl/test.py b/test/remoting/app-getproxyforurl/test.py new file mode 100644 index 0000000000..596e8a00e7 --- /dev/null +++ b/test/remoting/app-getproxyforurl/test.py @@ -0,0 +1,17 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() From a8ef59a0bec6443a427e72179e092b0f58fd3039 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 19 Nov 2015 14:33:44 +0800 Subject: [PATCH 062/404] [test] add case for App.open event --- test/remoting/app-open-event/index.html | 16 ++++++++++++ test/remoting/app-open-event/package.json | 5 ++++ .../app-open-event/second_instance.py | 18 +++++++++++++ test/remoting/app-open-event/test.py | 26 +++++++++++++++++++ test/remoting/app-open-event/userdata/keep | 1 + 5 files changed, 66 insertions(+) create mode 100644 test/remoting/app-open-event/index.html create mode 100644 test/remoting/app-open-event/package.json create mode 100644 test/remoting/app-open-event/second_instance.py create mode 100644 test/remoting/app-open-event/test.py create mode 100644 test/remoting/app-open-event/userdata/keep diff --git a/test/remoting/app-open-event/index.html b/test/remoting/app-open-event/index.html new file mode 100644 index 0000000000..cb578f32f1 --- /dev/null +++ b/test/remoting/app-open-event/index.html @@ -0,0 +1,16 @@ + + + + + App.open event test + + +

App.open event test

+ + + + diff --git a/test/remoting/app-open-event/package.json b/test/remoting/app-open-event/package.json new file mode 100644 index 0000000000..f29bb8f1ea --- /dev/null +++ b/test/remoting/app-open-event/package.json @@ -0,0 +1,5 @@ +{ + "name":"test-app-open", + "main":"index.html", + "dependencies":{} +} diff --git a/test/remoting/app-open-event/second_instance.py b/test/remoting/app-open-event/second_instance.py new file mode 100644 index 0000000000..32cba16bdd --- /dev/null +++ b/test/remoting/app-open-event/second_instance.py @@ -0,0 +1,18 @@ +import time +import os +import subprocess + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() + +testdir = os.path.dirname(os.path.abspath(__file__)) +chrome_options.add_argument("nwapp=" + testdir) +chrome_options.add_argument("user-data-dir=" + os.path.join(testdir, 'userdata')) + +try: + driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options, service_log_path="log2", service_args=["--verbose", "--launch-timeout=5"]) +except: + pass + + diff --git a/test/remoting/app-open-event/test.py b/test/remoting/app-open-event/test.py new file mode 100644 index 0000000000..9d82ec69d8 --- /dev/null +++ b/test/remoting/app-open-event/test.py @@ -0,0 +1,26 @@ +import time +import os +import subprocess + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() + +testdir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(testdir) + +chrome_options.add_argument("nwapp=" + testdir) +chrome_options.add_argument("user-data-dir=" + os.path.join(testdir, 'userdata')) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options, service_log_path="log", service_args=["--verbose"]) +time.sleep(1) +try: + print driver.current_url + second_instance = subprocess.Popen(['python', 'second_instance.py']) + time.sleep(2) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() + #second_instance.kill() diff --git a/test/remoting/app-open-event/userdata/keep b/test/remoting/app-open-event/userdata/keep new file mode 100644 index 0000000000..1a19baadcf --- /dev/null +++ b/test/remoting/app-open-event/userdata/keep @@ -0,0 +1 @@ +keep this dir From c5922e76cb25fd4070e41ddefcc5d7919ce38dc8 Mon Sep 17 00:00:00 2001 From: Jefry Date: Fri, 20 Nov 2015 19:54:35 +0700 Subject: [PATCH 063/404] migrate App.dataPath --- src/api/nw_app.idl | 1 + src/api/nw_app_api.cc | 5 +++++ src/api/nw_app_api.h | 13 +++++++++++++ src/resources/api_nw_app.js | 10 ++++++++++ 4 files changed, 29 insertions(+) diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index 06e9716da0..0268aeed46 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -18,6 +18,7 @@ namespace nw.App { [nocompile] static void removeOriginAccessWhitelistEntry(DOMString sourceOrigin, DOMString destinationProtocol, DOMString destinationHost, boolean allowDestinationSubdomains); [nocompile] static DOMString[] argv(); static DOMString[] getArgvSync(); + static DOMString getDataPath(); }; interface Events { static void onOpen(); diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index 4274767892..491c82526d 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -124,4 +124,9 @@ bool NwAppSetProxyConfigFunction::RunNWSync(base::ListValue* response, std::stri return true; } +bool NwAppGetDataPathFunction::RunNWSync(base::ListValue* response, std::string* error) { + response->AppendString(browser_context()->GetPath().value()); + return true; +} + } // namespace extensions diff --git a/src/api/nw_app_api.h b/src/api/nw_app_api.h index 12a5c8f413..2c1b4df832 100644 --- a/src/api/nw_app_api.h +++ b/src/api/nw_app_api.h @@ -67,5 +67,18 @@ class NwAppSetProxyConfigFunction : public NWSyncExtensionFunction { DISALLOW_COPY_AND_ASSIGN(NwAppSetProxyConfigFunction); }; +class NwAppGetDataPathFunction : public NWSyncExtensionFunction { + public: + NwAppGetDataPathFunction(){} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwAppGetDataPathFunction() override {} + + DECLARE_EXTENSION_FUNCTION("nw.App.getDataPath", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwAppGetDataPathFunction); +}; + } // namespace extensions #endif diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index b27cb66d6d..0ad8f0abff 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -3,6 +3,7 @@ var nwNatives = requireNative('nw_natives'); var sendRequest = require('sendRequest'); var argv = null; +var dataPath; nw_binding.registerCustomHook(function(bindingsAPI) { var apiFunctions = bindingsAPI.apiFunctions; @@ -37,6 +38,15 @@ nw_binding.registerCustomHook(function(bindingsAPI) { break; } }); + apiFunctions.setHandleRequest('getDataPath', function() { + return sendRequest.sendRequestSync('nw.App.getDataPath', [], this.definition.parameters, {})[0]; + }); + bindingsAPI.compiledApi.__defineGetter__('dataPath', function() { + if (!dataPath) + dataPath = nw.App.getDataPath(); + return dataPath; + }); + }); exports.binding = nw_binding.generate(); From eef2f675ccf89f2af3f4f62160e70aa0601a3dcb Mon Sep 17 00:00:00 2001 From: Jefry Date: Fri, 20 Nov 2015 20:10:15 +0700 Subject: [PATCH 064/404] argv as getter --- src/api/nw_app.idl | 1 - src/resources/api_nw_app.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index 0268aeed46..7904738524 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -16,7 +16,6 @@ namespace nw.App { [nocompile] static DOMString getProxyForURL(DOMString url); [nocompile] static void addOriginAccessWhitelistEntry(DOMString sourceOrigin, DOMString destinationProtocol, DOMString destinationHost, boolean allowDestinationSubdomains); [nocompile] static void removeOriginAccessWhitelistEntry(DOMString sourceOrigin, DOMString destinationProtocol, DOMString destinationHost, boolean allowDestinationSubdomains); - [nocompile] static DOMString[] argv(); static DOMString[] getArgvSync(); static DOMString getDataPath(); }; diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index 0ad8f0abff..6ae7dec4b8 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -10,7 +10,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { apiFunctions.setHandleRequest('crashRenderer', function() { nwNatives.crashRenderer(); }); - apiFunctions.setHandleRequest('argv', function() { + bindingsAPI.compiledApi.__defineGetter__('argv', function() { if (argv) return argv; argv = nw.App.getArgvSync(); From dcf59ed35327abd293463989aadebd9f5521ff16 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sat, 21 Nov 2015 16:23:58 +0800 Subject: [PATCH 065/404] allow wait in App API --- src/api/nw_app_api.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index 4274767892..9889223760 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -108,6 +108,8 @@ bool NwAppSetProxyConfigFunction::RunNWSync(base::ListValue* response, std::stri std::string proxy_config; EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &proxy_config)); + base::ThreadRestrictions::ScopedAllowWait allow_wait; + net::ProxyConfig config; config.proxy_rules().ParseFromString(proxy_config); content::RenderProcessHost* render_process_host = GetSenderWebContents()->GetRenderProcessHost(); From ad92a9c2413eef1a8e8809c69278db4a6e3613bb Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 22 Nov 2015 00:51:41 +0800 Subject: [PATCH 066/404] disable field trial for nw_content_verifier_delegate --- src/nw_content_verifier_delegate.cc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/nw_content_verifier_delegate.cc b/src/nw_content_verifier_delegate.cc index 60ebb1565a..4ffce26fb4 100644 --- a/src/nw_content_verifier_delegate.cc +++ b/src/nw_content_verifier_delegate.cc @@ -71,14 +71,6 @@ ContentVerifierDelegate::Mode NWContentVerifierDelegate::GetDefaultMode() { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); Mode experiment_value = NONE; - const std::string group = - base::FieldTrialList::FindFullName(kContentVerificationExperimentName); - if (group == "EnforceStrict") - experiment_value = ContentVerifierDelegate::ENFORCE_STRICT; - else if (group == "Enforce") - experiment_value = ContentVerifierDelegate::ENFORCE; - else if (group == "Bootstrap") - experiment_value = ContentVerifierDelegate::BOOTSTRAP; Mode cmdline_value = NONE; if (command_line->HasSwitch(switches::kVerifyContent)) { From 059b49787d93ba6dc3373f8e5d1d461bbc11f43d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 22 Nov 2015 00:53:45 +0800 Subject: [PATCH 067/404] [test] set timeout to 80 --- nw.gypi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nw.gypi b/nw.gypi index 2943c1aaf9..c06fa945be 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1060,7 +1060,7 @@ '<(PRODUCT_DIR)/run_tests.re', ], 'action': ['python', '<(test_script)', '-d', '<(PRODUCT_DIR)', - '-t', '240', 'remoting'], + '-t', '80', 'remoting'], }, ], }, From 72df903803f9885d0195318978e24c94b0f01c81 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 23 Nov 2015 13:12:04 +0800 Subject: [PATCH 068/404] fix build warning for nw_content_verifier_delegate --- src/nw_content_verifier_delegate.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/nw_content_verifier_delegate.cc b/src/nw_content_verifier_delegate.cc index 4ffce26fb4..e0de839476 100644 --- a/src/nw_content_verifier_delegate.cc +++ b/src/nw_content_verifier_delegate.cc @@ -35,9 +35,6 @@ namespace { -const char kContentVerificationExperimentName[] = - "ExtensionContentVerification"; - const uint8 kNWSignaturesPublicKey[] = { 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, From e88d3a7b42a5daeb50a6aeb4581f6bab82fe956a Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 23 Nov 2015 13:12:34 +0800 Subject: [PATCH 069/404] support --nwapp in nw_package --- src/nw_package.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/nw_package.cc b/src/nw_package.cc index a89884db88..f2ce21c66e 100644 --- a/src/nw_package.cc +++ b/src/nw_package.cc @@ -190,6 +190,13 @@ Package::Package() // Then see if we have arguments and extract it. const base::CommandLine::StringVector& args = command_line->GetArgs(); + if (command_line->HasSwitch("nwapp")) { + path = command_line->GetSwitchValuePath("nwapp"); + self_extract_ = false; + if (InitFromPath(path)) + return; + } + if (args.size() > 0) { self_extract_ = false; path = FilePath(args[0]); From ed504ab15bd51dddd307e87cf967d2fd9d8492c2 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 23 Nov 2015 13:18:24 +0800 Subject: [PATCH 070/404] migrate localstorage data from nw12 apps --- src/nw_content.cc | 31 ++++++++++++++++- test/remoting/migrate-localstorage/index.html | 17 ++++++++++ .../migrate-localstorage/package.json | 4 +++ test/remoting/migrate-localstorage/test.py | 32 ++++++++++++++++++ .../Local Storage/file__0.localstorage | Bin 0 -> 3072 bytes .../file__0.localstorage-journal | Bin 0 -> 3608 bytes .../userdata_v10/Web Data | Bin 0 -> 30720 bytes .../userdata_v10/Web Data-journal | Bin 0 -> 512 bytes 8 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 test/remoting/migrate-localstorage/index.html create mode 100644 test/remoting/migrate-localstorage/package.json create mode 100644 test/remoting/migrate-localstorage/test.py create mode 100644 test/remoting/migrate-localstorage/userdata_v10/Local Storage/file__0.localstorage create mode 100644 test/remoting/migrate-localstorage/userdata_v10/Local Storage/file__0.localstorage-journal create mode 100644 test/remoting/migrate-localstorage/userdata_v10/Web Data create mode 100644 test/remoting/migrate-localstorage/userdata_v10/Web Data-journal diff --git a/src/nw_content.cc b/src/nw_content.cc index 336b71ae57..2e9c4e72da 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -4,9 +4,9 @@ #include "nw_base.h" #include "base/command_line.h" -#include "base/logging.h" #include "base/files/file_util.h" #include "base/json/json_reader.h" +#include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" @@ -15,7 +15,11 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/io_thread.h" #include "chrome/browser/profiles/profile_manager.h" +#include "chrome/common/chrome_paths.h" + +#include "components/crx_file/id_util.h" +#include "content/browser/dom_storage/dom_storage_area.h" #include "content/common/dom_storage/dom_storage_map.h" #include "content/nw/src/common/shell_switches.h" #include "content/public/browser/notification_service.h" @@ -84,6 +88,7 @@ #include "V8HTMLElement.h" #include "content/renderer/render_view_impl.h" +#include "base/logging.h" using content::RenderView; using content::RenderViewImpl; @@ -192,6 +197,30 @@ int MainPartsPreCreateThreadsHook() { content::DOMStorageMap::SetQuotaOverride(dom_storage_quota_mb * 1024 * 1024); } + base::FilePath user_data_dir; + std::string name; + package->root()->GetString("name", &name); + if (!name.empty() && PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { + base::FilePath old_dom_storage = user_data_dir + .Append(FILE_PATH_LITERAL("Local Storage")) + .Append(FILE_PATH_LITERAL("file__0.localstorage")); + if (base::PathExists(old_dom_storage)) { + std::string id = crx_file::id_util::GenerateId(name); + GURL origin("chrome-extension://" + id + "/"); + base::FilePath new_storage_dir = user_data_dir.Append(FILE_PATH_LITERAL("Default")) + .Append(FILE_PATH_LITERAL("Local Storage")); + base::CreateDirectory(new_storage_dir); + + base::FilePath new_dom_storage = new_storage_dir + .Append(content::DOMStorageArea::DatabaseFileNameFromOrigin(origin)); + base::FilePath new_dom_journal = new_dom_storage.ReplaceExtension(FILE_PATH_LITERAL("localstorage-journal")); + base::FilePath old_dom_journal = old_dom_storage.ReplaceExtension(FILE_PATH_LITERAL("localstorage-journal")); + base::Move(old_dom_journal, new_dom_journal); + base::Move(old_dom_storage, new_dom_storage); + LOG_IF(INFO, true) << "Migrate DOM storage from " << old_dom_storage.AsUTF8Unsafe() << " to " << new_dom_storage.AsUTF8Unsafe(); + } + } + const base::ListValue *additional_trust_anchors = NULL; if (package->root()->GetList("additional_trust_anchors", &additional_trust_anchors)) { net::CertificateList trust_anchors; diff --git a/test/remoting/migrate-localstorage/index.html b/test/remoting/migrate-localstorage/index.html new file mode 100644 index 0000000000..fd47d0c215 --- /dev/null +++ b/test/remoting/migrate-localstorage/index.html @@ -0,0 +1,17 @@ + + + + + + localstorage + + +

localstorage migration test

+ + + diff --git a/test/remoting/migrate-localstorage/package.json b/test/remoting/migrate-localstorage/package.json new file mode 100644 index 0000000000..da090583ec --- /dev/null +++ b/test/remoting/migrate-localstorage/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-localstorage", + "main": "index.html" +} diff --git a/test/remoting/migrate-localstorage/test.py b/test/remoting/migrate-localstorage/test.py new file mode 100644 index 0000000000..edc1270f64 --- /dev/null +++ b/test/remoting/migrate-localstorage/test.py @@ -0,0 +1,32 @@ +import time +import os +import shutil + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + +testdir = os.path.dirname(os.path.abspath(__file__)) +chrome_options = Options() +chrome_options.add_argument("nwapp=" + testdir) + +user_data_dir = os.path.join(testdir, 'userdata') +user_data_tmpl = os.path.join(testdir, 'userdata_v10') + +try: + shutil.rmtree(user_data_dir) +except: + pass +shutil.copytree(user_data_tmpl, user_data_dir) + +chrome_options.add_argument("user-data-dir=" + user_data_dir) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +time.sleep(1) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() diff --git a/test/remoting/migrate-localstorage/userdata_v10/Local Storage/file__0.localstorage b/test/remoting/migrate-localstorage/userdata_v10/Local Storage/file__0.localstorage new file mode 100644 index 0000000000000000000000000000000000000000..2a71b84bf074d337be6d75564b8933b37f2551ca GIT binary patch literal 3072 zcmeHHO-sW-5Ph>-6oh&a!INxm3VI0s0b@xa5E|Ii#C zo&CGqU;~$gd~lgDPMB|Sj1F6Md0K>wo|CQa*npwuzN6V-u%pA$SS}1+H*TynEc@n%IRSAZMQ;t?FAmpF&QKaLTRy zw;N3=(Qn}L;u-J^%r65gqJy=PBd*1plJkXIIe;(-z(s#0tX+nPnND*rheZC9Q_J> z*{&<_+XvL?AT7#~+)QVaVJZ7;2}Yt`nTqM06J?h!GJKL%2pbLTc^5*9AMcCFQe4 z#<(C}aE?Ax4Y(VIjGmxt_a2!N+6HB^9Mg~J$Jk@!k*_tzoP1()W?Y50GmGz&FXi(; xV~<=35xcn58|~Lnp}-xk$@hc@+>^Vo0_4myr!`$W&nv{q5mrRazuho>;0F<*NF)FN literal 0 HcmV?d00001 diff --git a/test/remoting/migrate-localstorage/userdata_v10/Web Data b/test/remoting/migrate-localstorage/userdata_v10/Web Data new file mode 100644 index 0000000000000000000000000000000000000000..c7e6a9ac49171d99710f1c29c089b60f46b64560 GIT binary patch literal 30720 zcmeI1-EQMV6vsVDYWI7!RF#EF(A-pQq@Ylt6@?+E46=I*Nc=_wg->sgms1MYR4ZrAMl;WrCR?pwjl67ik$ zhEZ)Av{^l?8Fa}YdQ7hej!mCb8|PnC8>OFjw9wvYf1yFMf8)6&8|DZMnFmTBAT^(}Xpaw_0HW8AcC+h;*Ai@;`m=Gx5VX4h+5U55{6x^|)$a7(Pf zN%@uOk50es*)c)j`P^}@&7`#L4P5RIQ&L+@W%fMV={U@$7xkv`&}h*5Rg>0RwHke3 zd|qwUn)Jgm^?c{raU-)+&CyY*x~b}|3Q-*4{)^yy;W{(JdY659mt27->BSIiy)0;& zet1Y;UCO+)eP%n{Y+Jq^#P8P=*@$aaCL$~q({tr~Q2=GiUi2N`;*L=CJeS`@(I+%R zX8AKk=#V*HeI|3;{;T9ZgR3xG-MB<~lWw}Qk zb-AJW$xEYe@|s>Ol2;pv)fr7zNMt{fjvbq&RXUjJj-#ImWv&E3QT(%_kJY-=%csX6 zSUf_mrl>Hci{Ydke*z?sl}eR@s-G8^B{vGkoUV?w4$<`eeKM*@8a>8E0=Sh+PzWW* zw`@pjSM`UJN5X9Rw(1v;T1MQbNdn`;OGi1eVeT(;EVN*rgx9>-atk}+lGFZDw130} z4Fo^{1V8`;K;W$<@Ln!|dRM;g$Ri7P#KGBo&ip{U8{fOPP4WkKr@in{pVM9|+H39K zw>Drn4gw$m0w4eaARq}Gc%! zpP_^!R1g3G5C8!X5CpLQqYOX*1V8`;Kp=Yru>WT-=ZF{tKmY_l00i*=KWYF3KmY_l z00go}0O$Yg Date: Tue, 24 Nov 2015 16:44:05 +0800 Subject: [PATCH 071/404] ship additional node.lib and rename headers back --- tools/package_binaries.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/package_binaries.py b/tools/package_binaries.py index 6b10246643..73af1d4852 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -89,8 +89,13 @@ if platform_name == 'win': libfile = os.path.join(binaries_location, 'nw.lib') expfile = os.path.join(binaries_location, 'nw.exp') + libfile2 = os.path.join(binaries_location, 'node.lib') + expfile2 = os.path.join(binaries_location, 'node.exp') + shutil.copy(os.path.join(binaries_location, 'nw.dll.lib'), libfile) shutil.copy(os.path.join(binaries_location, 'nw.dll.exp'), expfile) + shutil.copy(os.path.join(binaries_location, 'node.dll.lib'), libfile2) + shutil.copy(os.path.join(binaries_location, 'node.dll.exp'), expfile2) if platform_name == 'win': arch = 'ia32' @@ -273,7 +278,7 @@ def generate_target_headers(platform_name, arch, version): res = call(['python', make_nw_header]) if res == 0: print 'nw-headers generated' - nw_headers_name = 'node-v' + version + '.tar.gz' + nw_headers_name = 'nw-headers-v' + version + '.tar.gz' nw_headers_path = os.path.join(os.path.dirname(__file__), \ os.pardir, 'tmp', nw_headers_name) if os.path.isfile(os.path.join(binaries_location, nw_headers_name)): @@ -315,7 +320,7 @@ def generate_target_others(platform_name, arch, version): target['output'] = '' target['compress'] = None if platform_name == 'win': - target['input'] = ['nw.exp', 'nw.lib'] + target['input'] = ['nw.exp', 'nw.lib', 'node.exp', 'node.lib'] elif platform_name == 'linux' : target['input'] = [] else: From 747cb0adb49c911ff1c6038dac412850e4a01012 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 24 Nov 2015 16:54:36 +0800 Subject: [PATCH 072/404] rename nw headers back --- tools/aws_uploader.py | 2 +- tools/make-nw-headers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/aws_uploader.py b/tools/aws_uploader.py index ec646cc965..870d578c3f 100755 --- a/tools/aws_uploader.py +++ b/tools/aws_uploader.py @@ -96,7 +96,7 @@ def aws_upload(upload_path, file_list): if builder_name == 'nw13_win64' : path_prefix = 'x64' - if (f.startswith('node-v') or f == 'SHASUMS256.txt') and builder_name != 'nw13_mac64' : + if (f.startswith('node-v') or f.startswith('nw-header') or f == 'SHASUMS256.txt') and builder_name != 'nw13_sdk_mac64' : continue if f.startswith('chromedriver') and 'sdk' not in builder_name : diff --git a/tools/make-nw-headers.py b/tools/make-nw-headers.py index e41f9fdbbd..27fbde92da 100644 --- a/tools/make-nw-headers.py +++ b/tools/make-nw-headers.py @@ -35,7 +35,7 @@ def update_uvh(tmp_dir, header_files): ''' if '-t' in sys.argv: nw_version = sys.argv[sys.argv.index('-t') + 1] -tarname = 'node-v' + nw_version + '.tar.gz' +tarname = 'nw-headers-v' + nw_version + '.tar.gz' tarpath = os.path.join(tmp_dir, tarname) #make tmpdir From e4c5b68ca801ca8f8e25331943f6ff9786ad3779 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 24 Nov 2015 18:38:58 +0800 Subject: [PATCH 073/404] upload windows import libraries --- tools/aws_uploader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/aws_uploader.py b/tools/aws_uploader.py index 870d578c3f..7c6009990e 100755 --- a/tools/aws_uploader.py +++ b/tools/aws_uploader.py @@ -90,7 +90,7 @@ def aws_upload(upload_path, file_list): sys.stdout.flush() # use '/' for s3 path_prefix = '' - if (f == 'nw.lib' or f == 'nw.exp') : + if (f in ['nw.lib', 'nw.exp', 'node.lib', 'node.exp'] ) : if builder_name != 'nw13_win64' and builder_name != 'nw13_win32' : continue if builder_name == 'nw13_win64' : From c586b42c59cfb3228b099034dccbaa9bbd9d99c6 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 25 Nov 2015 14:24:51 +0800 Subject: [PATCH 074/404] tool and sample for app signing --- tools/sign/convertkey.py | 20 +++++++ tools/sign/index.html | 11 ++++ tools/sign/package.json | 5 ++ tools/sign/private_key.pem | 27 ++++++++++ tools/sign/sign.py | 86 +++++++++++++++++++++++++++++++ tools/sign/verified_contents.json | 24 +++++++++ 6 files changed, 173 insertions(+) create mode 100644 tools/sign/convertkey.py create mode 100644 tools/sign/index.html create mode 100644 tools/sign/package.json create mode 100644 tools/sign/private_key.pem create mode 100755 tools/sign/sign.py create mode 100644 tools/sign/verified_contents.json diff --git a/tools/sign/convertkey.py b/tools/sign/convertkey.py new file mode 100644 index 0000000000..34f9f80039 --- /dev/null +++ b/tools/sign/convertkey.py @@ -0,0 +1,20 @@ +from Crypto.PublicKey import RSA +import sys + +key = RSA.importKey(open("private_key.pem").read()) +der = key.publickey().exportKey("DER") + +i = 0 +total = len(der) +sys.stdout.write(''' +const uint8 kNWSignaturesPublicKey[] = { + ''') +for c in der: + sys.stdout.write("{0:#04x}".format(ord(c))) + i += 1 + if i < total: sys.stdout.write(", ") + if i % 10 == 0: + sys.stdout.write("\n ") +print ''' +}; +''' diff --git a/tools/sign/index.html b/tools/sign/index.html new file mode 100644 index 0000000000..6579ca3a5f --- /dev/null +++ b/tools/sign/index.html @@ -0,0 +1,11 @@ + + + + + sign test + + +

sign test

+ + + diff --git a/tools/sign/package.json b/tools/sign/package.json new file mode 100644 index 0000000000..dd8fa2dbc8 --- /dev/null +++ b/tools/sign/package.json @@ -0,0 +1,5 @@ +{ + "name":"sign-test", + "main":"index2.html", + "dependencies":{} +} diff --git a/tools/sign/private_key.pem b/tools/sign/private_key.pem new file mode 100644 index 0000000000..98d0b94e5d --- /dev/null +++ b/tools/sign/private_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyybzo9dyVzEbehnAQk29FPtSz0w0KlSTA3oQDaz0Re6gElBL +3DP2UQ3qf/sSxTu7qX28Kq8/aBd8lkrnaOp2vU17oE9f14pKyebbktp5RKQamU/l +6im8Lbh88G7DUyVjl2UadMOL42sw/5tW/txp0xLi0TU4dYNc6Jq+59uPbPdIbnJE +M+Cj+FX7S3ITRwksCCsUOT+HeknAEuyrrIuPEpIr2hSiFOGJfnBTsYUdOQrAogzA +FP98DeYJmWWfxJuHELk7ioLGXPPUWV40VMKEJpthOzv5XivryKxnaxiLOWlxxOHe +sbCjToZcKXyLzT0npHHPmmKuVFSqDx6zeHJnkQIDAQABAoIBAH30W5DUzm6i4s9U +UfCJ9Fai8BfYvmsUXsYEExn3hsgpCBPytuYDTY+5mg/ZYizpORQAAf9RAnYOQc+J +B2r0G8SI2sJtFBY2BuKhgLfPPurA+EDf2cPSNnr+bHBBrFbL0rCWHc4RQ5Uv64Q/ +ErQXgoE8r2ZYofWyCZOqBf0JEybSG56cpyKPlDI8f1kZbEUozdctd961++wzoK+w +qG+PH6nI4mC7CFagQzx09ZbTV78pce6KLDTpE+OflD0gzKaNxsu8ZWH7izKHz65t +y5/tyDM79VcSK8DyB0NE8+WgbQQjN+t1977I1wMESCm0t0MQ0c6E0OPheKuluv/Y +XbYPQMUCgYEA7EKe7CQHsRYBH6ugsqPhwUdx9KsQy4S5QH4QCANr+yrPuVo0CjpW +ROBuc4L1gIa0ZBJA6kVlJHuJM4NbQlNOCRL+CMondsE+1JUNtUqn513UCrLFMKCg +9TGUE2mju7lZknHBADUJZg5awVMJmlj8f+CWL6JCvc+5xe6RRH5AyTcCgYEA3CAu +Ty1Y5ZECWHRik//+pDfL/G4b0RU9JwXC04D0luRpIH2wQ15sowkF4Ei2xWmK4VjM +3rtNMjm26ZmXKL6i2+ZwFdl7lHhTEn1lMUBdqM4ezSrgS9yJDY2MeRxc4l9Umhlc +lpu/c9zQEq6Zsk6/ElUdGFHGldFyfu84Be4FmXcCgYBjzdzeunW5XCdLXrA65roG +cQz1o5IrtzyevuI80F08NCCeFznmnDA3VmuyRj85dS4dHAzqKjiIydrytOnHQfO0 +J57CzcsQAqBtIy4wSIJXXa6melCMsz5rde8sqDKvqaPqFj3GvaDjyOqTwmVLG45G +4vPu2WfCUU8UCyy4t3DczwKBgQDHR+8EyTX4pr3r3Hm+KPycKNNoVTqjn8m8ATAv +EEjeLiyqOH+RjfNl6e5C7TFiKTmM5zqZzhGGDc/1TZIWVffUgpsofLqvX/s8+v7Y +hsAD6Y7jCRUEOMRu2523qyC/47QQyjMTOi8qMlbBAwar8TRz4VA0yxuwWGyCVAlw +/Npe4wKBgQDS0utR4UTx9urhP8P76VwiY/F8DwgjOl6QYsiQzlIJlErdxxQwMcuL +ieyL073NR4u6jz+MSGcry36DYXat7A15sId1PgeXGRtrGYY+4oPo4Rt4UXwmMQfN +QC9fulNzn+UZppbVuIQdOiUBHt9e56CK4W6PdRQYqIUxHPGJyvBjZw== +-----END RSA PRIVATE KEY----- diff --git a/tools/sign/sign.py b/tools/sign/sign.py new file mode 100755 index 0000000000..502ce57cbd --- /dev/null +++ b/tools/sign/sign.py @@ -0,0 +1,86 @@ +import os +import os.path as osp +import hashlib +import base64 +import json + +# --- helpers --- + +def write(text): + """ helper for writing output, as a single point for replacement """ + print(text) + +def filehash(filepath): + blocksize = 4096 + sha = hashlib.sha256() + with open(filepath, 'rb') as fp: + while True: + data = fp.read(blocksize) + if not data: + break + sha.update(data) + return base64.b64encode(sha.digest()) + +def fixbase64(str): + return str.replace('/', '_').replace('+', '-').replace('=', '') + +def sign_data(private_key_loc, data): + from Crypto.PublicKey import RSA + from Crypto.Signature import PKCS1_v1_5 + from Crypto.Hash import SHA256 + from base64 import b64encode, b64decode + key = open(private_key_loc, "r").read() + rsakey = RSA.importKey(key) + signer = PKCS1_v1_5.new(rsakey) + digest = SHA256.new() + digest.update(data) + sign = signer.sign(digest) + return b64encode(sign) + +# --- /helpers --- + +hash = { + "block_size": 4096, + "hash_block_size": 4096, + "format": "treehash", + "files": [] } + +ROOT = '.' +for root, dirs, files in os.walk(ROOT): + for fpath in [osp.join(root, f) for f in files]: + size = osp.getsize(fpath) + sha = fixbase64(filehash(fpath)) + name = osp.relpath(fpath, ROOT) + hash['files'].append({"path": name, "root_hash": sha}) + +content_hashes = [ hash ] +payload = { "content_hashes" : content_hashes, "item_id": "abcdefghijklmnopabcdefghijklmnop", "item_version": "1.2.3"} + +payload_json = json.dumps(payload) +payload_encoded = fixbase64(base64.b64encode(payload_json)) +protected = fixbase64(base64.b64encode('{"alg":"RS256"}')) +signature_input = (protected + '.' + payload_encoded).replace("\n", "") + +signature = fixbase64(sign_data('private_key.pem', signature_input)) + +verfied_content = [ { + "description": "treehash per file", + "signed_content": { + "payload": payload_encoded, + "signatures": [ + { + "header": {"kid": "publisher"}, + "protected": protected, + "signature": "whatever" + }, + { + "header": {"kid": "nwjs"}, + "protected": protected, + "signature": signature + } + ] + } +}] + + +print json.dumps(verfied_content, indent=4) diff --git a/tools/sign/verified_contents.json b/tools/sign/verified_contents.json new file mode 100644 index 0000000000..1c38949774 --- /dev/null +++ b/tools/sign/verified_contents.json @@ -0,0 +1,24 @@ +[ + { + "signed_content": { + "signatures": [ + { + "header": { + "kid": "publisher" + }, + "protected": "eyJhbGciOiJSUzI1NiJ9", + "signature": "whatever" + }, + { + "header": { + "kid": "nwjs" + }, + "protected": "eyJhbGciOiJSUzI1NiJ9", + "signature": "e3M1YuCErlDgkqoK0dqn7hwZ8hcFGUv68LdzyVdIY7NqrzdDkCSTdbgvaxXCbIV8u56V9HdCzOT7ujohI_H-qhmIrt9B-VUDUThHU670gX9KuKTJE8pwXQVdzyyWO99yqXDCxK2kSlxkv3rwcxFUd6pqsZA0UG50IQpzUykcE657A77rAHQ2zYUcgrX5VwKbz8Sf8jFdJV7kkm3HVxKTOaj0ez1fsnT7reMWjLIKbxo3DJj8t_eHU2WPInkZpZ4Ceafn01aamGZeeixq26ok65gxzHEF4sUXxQzktIr_p4AwyNUJkXDaBdEbWORLdsU--1malKUx-9YbSzzqUUcoMw" + } + ], + "payload": "eyJjb250ZW50X2hhc2hlcyI6IFt7ImZpbGVzIjogW3sicGF0aCI6ICJjb252ZXJ0a2V5LnB5IiwgInJvb3RfaGFzaCI6ICJZcS1UclE0aXo1THI5dzh5N0d1TGx6TW1jemNla0JfRHVNQU9GRTBGaDdrIn0sIHsicGF0aCI6ICJwcml2YXRlX2tleS5wZW0iLCAicm9vdF9oYXNoIjogIjN4Ni13MEZZa3B5aE9MbkVyTXBndU5ZaUdISksxZ2NIQldGQ2pXdGV3dGMifSwgeyJwYXRoIjogInRlc3QucHkiLCAicm9vdF9oYXNoIjogImh0QU80N3Z4R3VhbE9iZFhDU2lHMWVZLVJrcjB6SEVMRkhJdjFyUkVGT00ifSwgeyJwYXRoIjogImNvbXB1dGVkX2hhc2hlcy5qc29uIiwgInJvb3RfaGFzaCI6ICJ4OUxCOHVsTlZkb01PNC1wcEhxcW1meENYYTgyLThXLXJNM09SSVd0TmdZIn0sIHsicGF0aCI6ICJpbmRleC5odG1sIiwgInJvb3RfaGFzaCI6ICJ2NU0wTm1yQ0VyZFRwMXBtSGZkMUZFVFlhVzZqZUhWRVJPLXZKSTNTZDBBIn0sIHsicGF0aCI6ICJoYXNoZGVlcC5weSIsICJyb290X2hhc2giOiAiZWtZQnhmWS12RmpIR2tITVFrT1N2UURkZTRMTXhpQ29MY0VSRG1XSDNVNCJ9LCB7InBhdGgiOiAicGFja2FnZS5qc29uIiwgInJvb3RfaGFzaCI6ICJIUE44Z1lRbllZaUJzcUZQdGpobjF6RXBqWEtsNWF5TElDbnhvVHdoZzd3In1dLCAiYmxvY2tfc2l6ZSI6IDQwOTYsICJoYXNoX2Jsb2NrX3NpemUiOiA0MDk2LCAiZm9ybWF0IjogInRyZWVoYXNoIn1dLCAiaXRlbV9pZCI6ICJhYmNkZWZnaGlqa2xtbm9wYWJjZGVmZ2hpamtsbW5vcCIsICJpdGVtX3ZlcnNpb24iOiAiMS4yLjMifQ" + }, + "description": "treehash per file" + } +] From bd23faad1404e944bf8d405068638be8a2b293fa Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 25 Nov 2015 14:43:20 +0800 Subject: [PATCH 075/404] [README] update for 0.13.0-alpha6 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index de589204fc..171c0abc39 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,11 @@ It was created in the Intel Open Source Technology Center. * Windows: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-x64.zip) * Mac 10.7+: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-x64.zip) -* **v0.13.0-alpha5:** (Nov 2, 2015, based off of Node.js v5.0.0, Chromium 46.0.2490.80): [release notes](https://groups.google.com/d/msg/nwjs-general/YuwMHd_uvPM/pLFWG3vYBwAJ) +* **v0.13.0-alpha6:** (Nov 25, 2015, based off of Node.js v5.0.0, Chromium 46.0.2490.80): [release notes](https://groups.google.com/d/msg/nwjs-general/l-vc2U9mSsA/qn4SqhR7AwAJ) **NOTE** You might want the **SDK build**. Please read the release notes - * Linux: [32bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-linux-x64.tar.gz) - * Windows: [32bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-win-x64.zip) - * Mac 10.7+: [64bit](http://dl.nwjs.io/v0.13.0-alpha5/nwjs-v0.13.0-alpha5-osx-x64.zip) + * Linux: [32bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-linux-x64.tar.gz) + * Windows: [32bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-win-x64.zip) + * Mac 10.7+: [64bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-osx-x64.zip) * **0.8.6:** (Apr 18, 2014, based off of Node v0.10.22, Chrome 30.0.1599.66) **If your native Node module works only with Node v0.10, then you should use node-webkit v0.8.x, which is also a maintained branch. [More info](https://groups.google.com/d/msg/nwjs-general/2OJ1cEMPLlA/09BvpTagSA0J)** [release notes](https://groups.google.com/d/msg/nwjs-general/CLPkgfV-i7s/hwkkQuJ1kngJ) From b1a425183bcb649c3063ca6eab5b82018e4fd6d6 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Sat, 21 Nov 2015 21:59:09 +0800 Subject: [PATCH 076/404] Ported nw.Screen API --- nw.gypi | 4 + src/api/_api_features.json | 4 + src/api/nw_screen.idl | 52 +++++ src/api/nw_screen_api.cc | 348 +++++++++++++++++++++++++++++++++ src/api/nw_screen_api.h | 79 ++++++++ src/api/schemas.gypi | 1 + src/resources/api_nw_screen.js | 76 +++++++ 7 files changed, 564 insertions(+) create mode 100644 src/api/nw_screen.idl create mode 100644 src/api/nw_screen_api.cc create mode 100644 src/api/nw_screen_api.h create mode 100644 src/resources/api_nw_screen.js diff --git a/nw.gypi b/nw.gypi index c06fa945be..d23120a9e1 100644 --- a/nw.gypi +++ b/nw.gypi @@ -639,8 +639,10 @@ '<(DEPTH)/extensions/browser/api/api_registration.gyp:extensions_api_registration', '<(DEPTH)/components/components.gyp:policy', '<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite', + '<(DEPTH)/third_party/webrtc/modules/modules.gyp:desktop_capture', ], 'include_dirs': [ + '<(DEPTH)/third_party', '<(DEPTH)/third_party/mojo/src', '<(SHARED_INTERMEDIATE_DIR)/blink', '<(SHARED_INTERMEDIATE_DIR)/blink/bindings/core/v8/', @@ -660,6 +662,8 @@ 'src/api/nw_object_api.h', 'src/api/nw_shell_api.cc', 'src/api/nw_shell_api.h', + 'src/api/nw_screen_api.cc', + 'src/api/nw_screen_api.h', 'src/api/object_manager.cc', 'src/api/object_manager.h', 'src/api/object_manager_factory.cc', diff --git a/src/api/_api_features.json b/src/api/_api_features.json index 3dd1cd72c3..cfe8e462c9 100644 --- a/src/api/_api_features.json +++ b/src/api/_api_features.json @@ -39,6 +39,10 @@ "channel": "stable", "contexts": ["blessed_extension"] }, + "nw.Screen": { + "channel": "stable", + "contexts": ["blessed_extension"] + }, "nw.currentWindowInternal": { "noparent": true, "internal": true, diff --git a/src/api/nw_screen.idl b/src/api/nw_screen.idl new file mode 100644 index 0000000000..22a307a436 --- /dev/null +++ b/src/api/nw_screen.idl @@ -0,0 +1,52 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// nw Screen API +[implemented_in="content/nw/src/api/nw_screen_api.h"] +namespace nw.Screen { + + dictionary DisplayGeometry { + long x; + long y; + long width; + long height; + }; + + dictionary Display { + long id; + double scaleFactor; + boolean isBuiltIn; + long rotation; + long touchSupport; + + DisplayGeometry bounds; + DisplayGeometry work_area; + }; + + callback ChooseDesktopMediaCallback = void (DOMString stream_id); + + interface Events { + static void onDisplayAdded(Display display); + static void onDisplayRemoved(Display display); + static void onDisplayBoundsChanged(Display display, long metrics); + + static void onSourceAdded(DOMString id, DOMString string, long order, DOMString type, optional boolean primary); + static void onSourceRemoved(long old_order); + static void onSourceOrderChanged(DOMString id, long new_order, long old_order); + static void onSourceNameChanged(DOMString id, DOMString name); + static void onSourceThumbnailChanged(DOMString id, DOMString thumbnail); + }; + + interface Functions { + static Display[] getScreens(); + static void initEventListeners(); + [nocompile] static long chooseDesktopMedia(DOMString[] sources, + ChooseDesktopMediaCallback callback); + [nocompile] static long cancelChooseDesktopMedia(long request_id); + static void startMonitor(boolean screens, boolean windows); + static void stopMonitor(); + static boolean isMonitorStarted(); + }; + +}; diff --git a/src/api/nw_screen_api.cc b/src/api/nw_screen_api.cc new file mode 100644 index 0000000000..203f330bea --- /dev/null +++ b/src/api/nw_screen_api.cc @@ -0,0 +1,348 @@ +#include "nw_screen_api.h" + +#include "base/lazy_instance.h" +#include "base/values.h" +#include "content/nw/src/api/nw_screen.h" +#include "extensions/browser/extensions_browser_client.h" +#include "ui/gfx/display_observer.h" +#include "ui/gfx/screen.h" + +// For desktop capture APIs +#include "base/base64.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/media/desktop_media_list_observer.h" +#include "chrome/browser/media/native_desktop_media_list.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" +#include "third_party/webrtc/modules/desktop_capture/window_capturer.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" + +using namespace extensions::nwapi::nw__screen; +using namespace content; + +namespace extensions { + + class NwDesktopCaptureMonitor : public DesktopMediaListObserver { + public: + static NwDesktopCaptureMonitor* GetInstance(); + + NwDesktopCaptureMonitor(); + void Start(bool screens, bool windows); + void Stop(); + bool IsStarted(); + + private: + int GetPrimaryMonitorIndex(); + // DesktopMediaListObserver implementation. + void OnSourceAdded(int index) override; + void OnSourceRemoved(int index) override; + void OnSourceMoved(int old_index, int new_index) override; + void OnSourceNameChanged(int index) override; + void OnSourceThumbnailChanged(int index) override; + + bool started_; + scoped_ptr media_list_; + + DISALLOW_COPY_AND_ASSIGN(NwDesktopCaptureMonitor); + }; + + class NwScreenDisplayObserver: public gfx::DisplayObserver { + public: + static NwScreenDisplayObserver* GetInstance(); + NwScreenDisplayObserver(); + + private: + ~NwScreenDisplayObserver() override; + // gfx::DisplayObserver implementation. + void OnDisplayMetricsChanged(const gfx::Display& display, uint32_t changed_metrics) override; + void OnDisplayAdded(const gfx::Display& new_display) override; + void OnDisplayRemoved(const gfx::Display& old_display) override; + + DISALLOW_COPY_AND_ASSIGN(NwScreenDisplayObserver); + }; + + namespace { + + // Helper function to convert gfx::Display to nwapi::nw__screen::Display + scoped_ptr ConvertGfxDisplay(const gfx::Display& gfx_display) { + scoped_ptr displayResult(new nwapi::nw__screen::Display); + + displayResult->id = gfx_display.id(); + displayResult->scale_factor = gfx_display.device_scale_factor(); + displayResult->is_built_in = gfx_display.IsInternal(); + displayResult->rotation = gfx_display.RotationAsDegree(); + displayResult->touch_support = gfx_display.touch_support(); + + gfx::Rect rect = gfx_display.bounds(); + DisplayGeometry& bounds = displayResult->bounds; + bounds.x = rect.x(); + bounds.y = rect.y(); + bounds.width = rect.width(); + bounds.height = rect.height(); + + rect = gfx_display.work_area(); + DisplayGeometry& work_area = displayResult->work_area; + work_area.x = rect.x(); + work_area.y = rect.y(); + work_area.width = rect.width(); + work_area.height = rect.height(); + + return displayResult.Pass(); + } + + void DispatchEvent( + events::HistogramValue histogram_value, + const std::string& event_name, + scoped_ptr args) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + ExtensionsBrowserClient::Get()->BroadcastEventToRenderers( + histogram_value, event_name, args.Pass()); + } + + // Lazy initialize screen event listeners until first call + base::LazyInstance::Leaky + g_display_observer = LAZY_INSTANCE_INITIALIZER; + + base::LazyInstance::Leaky + g_desktop_capture_monitor = LAZY_INSTANCE_INITIALIZER; + + } + + // static + NwScreenDisplayObserver* NwScreenDisplayObserver::GetInstance() { + return g_display_observer.Pointer(); + } + + NwScreenDisplayObserver::NwScreenDisplayObserver() { + gfx::Screen* screen = gfx::Screen::GetNativeScreen(); + if (screen) { + screen->AddObserver(this); + } + } + + NwScreenDisplayObserver::~NwScreenDisplayObserver() { + gfx::Screen* screen = gfx::Screen::GetNativeScreen(); + if (screen) { + screen->RemoveObserver(this); + } + } + + // Called when the |display|'s bound has changed. + void NwScreenDisplayObserver::OnDisplayMetricsChanged(const gfx::Display& display, + uint32_t changed_metrics) { + scoped_ptr args = + nwapi::nw__screen::OnDisplayBoundsChanged::Create(*ConvertGfxDisplay(display), + changed_metrics); + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__screen::OnDisplayBoundsChanged::kEventName, + args.Pass()); + } + + // Called when |new_display| has been added. + void NwScreenDisplayObserver::OnDisplayAdded(const gfx::Display& new_display) { + scoped_ptr args = + nwapi::nw__screen::OnDisplayAdded::Create(*ConvertGfxDisplay(new_display)); + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__screen::OnDisplayAdded::kEventName, + args.Pass()); + } + + // Called when |old_display| has been removed. + void NwScreenDisplayObserver::OnDisplayRemoved(const gfx::Display& old_display) { + scoped_ptr args = + nwapi::nw__screen::OnDisplayRemoved::Create(*ConvertGfxDisplay(old_display)); + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__screen::OnDisplayRemoved::kEventName, + args.Pass()); + } + + NwScreenGetScreensFunction::NwScreenGetScreensFunction() {} + + bool NwScreenGetScreensFunction::RunNWSync(base::ListValue* response, std::string* error) { + const std::vector& displays = gfx::Screen::GetNativeScreen()->GetAllDisplays(); + + for (size_t i=0; iAppend(ConvertGfxDisplay(displays[i])->ToValue()); + } + + return true; + } + + NwScreenInitEventListenersFunction::NwScreenInitEventListenersFunction() {} + + bool NwScreenInitEventListenersFunction::RunNWSync(base::ListValue* response, std::string* error) { + NwScreenDisplayObserver::GetInstance(); + return true; + } + + NwDesktopCaptureMonitor* NwDesktopCaptureMonitor::GetInstance() { + return g_desktop_capture_monitor.Pointer(); + } + + NwDesktopCaptureMonitor::NwDesktopCaptureMonitor() + : started_(false) + , media_list_(nullptr) { + } + + void NwDesktopCaptureMonitor::Start(bool screens, bool windows) { + if (started_) { + return; + } + + started_ = true; + + webrtc::DesktopCaptureOptions options = webrtc::DesktopCaptureOptions::CreateDefault(); + options.set_disable_effects(false); + scoped_ptr screenCapturer(screens ? webrtc::ScreenCapturer::Create(options) : nullptr); + scoped_ptr windowCapturer(windows ? webrtc::WindowCapturer::Create(options) : nullptr); + + media_list_.reset(new NativeDesktopMediaList(screenCapturer.Pass(), windowCapturer.Pass())); + + media_list_->StartUpdating(this); + } + + void NwDesktopCaptureMonitor::Stop() { + started_ = false; + media_list_.reset(); + } + + bool NwDesktopCaptureMonitor::IsStarted() { + return started_; + } + + int NwDesktopCaptureMonitor::GetPrimaryMonitorIndex() { + #ifdef _WIN32 + int count=0; + for (int i = 0;; ++i) { + DISPLAY_DEVICE device; + device.cb = sizeof(device); + BOOL ret = EnumDisplayDevices(NULL, i, &device, 0); + if(!ret) + break; + if (device.StateFlags & DISPLAY_DEVICE_ACTIVE){ + if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE){ + return count; + } + count++; + } + } + #endif + return -1; + } + + void NwDesktopCaptureMonitor::OnSourceAdded(int index) { + DesktopMediaList::Source src = media_list_->GetSource(index); + + std::string type; + switch(src.id.type) { + case content::DesktopMediaID::TYPE_WINDOW: + type = "window"; + break; + case content::DesktopMediaID::TYPE_SCREEN: + type = "screen"; + break; + case content::DesktopMediaID::TYPE_NONE: + type = "none"; + break; + default: + type = "unknown"; + break; + } + + scoped_ptr args = nwapi::nw__screen::OnSourceAdded::Create( + src.id.ToString(), + base::UTF16ToUTF8(src.name), + index, + type, + src.id.type == content::DesktopMediaID::TYPE_SCREEN && GetPrimaryMonitorIndex() == index); + + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__screen::OnSourceAdded::kEventName, + args.Pass()); + } + + void NwDesktopCaptureMonitor::OnSourceRemoved(int index) { + scoped_ptr args = nwapi::nw__screen::OnSourceRemoved::Create(index); + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__screen::OnSourceRemoved::kEventName, + args.Pass()); + } + + void NwDesktopCaptureMonitor::OnSourceMoved(int old_index, int new_index) { + DesktopMediaList::Source src = media_list_->GetSource(new_index); + scoped_ptr args = nwapi::nw__screen::OnSourceOrderChanged::Create( + src.id.ToString(), + new_index, + old_index); + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__screen::OnSourceOrderChanged::kEventName, + args.Pass()); + } + + void NwDesktopCaptureMonitor::OnSourceNameChanged(int index) { + DesktopMediaList::Source src = media_list_->GetSource(index); + scoped_ptr args = nwapi::nw__screen::OnSourceNameChanged::Create( + src.id.ToString(), + base::UTF16ToUTF8(src.name)); + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__screen::OnSourceNameChanged::kEventName, + args.Pass()); + } + + void NwDesktopCaptureMonitor::OnSourceThumbnailChanged(int index) { + std::string base64; + + DesktopMediaList::Source src = media_list_->GetSource(index); + SkBitmap bitmap = src.thumbnail.GetRepresentation(1).sk_bitmap(); + SkAutoLockPixels lock_image(bitmap); + std::vector data; + bool success = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &data); + if (success){ + base::StringPiece raw_str(reinterpret_cast(&data[0]), data.size()); + base::Base64Encode(raw_str, &base64); + } + + scoped_ptr args = nwapi::nw__screen::OnSourceThumbnailChanged::Create( + src.id.ToString(), + base64); + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__screen::OnSourceThumbnailChanged::kEventName, + args.Pass()); + } + + NwScreenStartMonitorFunction::NwScreenStartMonitorFunction() {} + + bool NwScreenStartMonitorFunction::RunNWSync(base::ListValue* response, std::string* error) { + bool screens, windows; + EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &screens)); + EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &windows)); + NwDesktopCaptureMonitor::GetInstance()->Start(screens, windows); + return true; + } + + NwScreenStopMonitorFunction::NwScreenStopMonitorFunction() {} + + bool NwScreenStopMonitorFunction::RunNWSync(base::ListValue* response, std::string* error) { + NwDesktopCaptureMonitor::GetInstance()->Stop(); + return true; + } + + NwScreenIsMonitorStartedFunction::NwScreenIsMonitorStartedFunction() {} + + bool NwScreenIsMonitorStartedFunction::RunNWSync(base::ListValue* response, std::string* error) { + response->AppendBoolean(NwDesktopCaptureMonitor::GetInstance()->IsStarted()); + return true; + } + +} // extensions diff --git a/src/api/nw_screen_api.h b/src/api/nw_screen_api.h new file mode 100644 index 0000000000..cd9bcacbee --- /dev/null +++ b/src/api/nw_screen_api.h @@ -0,0 +1,79 @@ +#ifndef NW_SRC_API_NW_SCREEN_API_H_ +#define NW_SRC_API_NW_SCREEN_API_H_ + +#include "extensions/browser/extension_function.h" + +namespace extensions { + + // implement nw.Screen.getScreens() + class NwScreenGetScreensFunction: public NWSyncExtensionFunction { + public: + NwScreenGetScreensFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwScreenGetScreensFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.Screen.getScreens", UNKNOWN) + + private: + DISALLOW_COPY_AND_ASSIGN(NwScreenGetScreensFunction); + }; + + // implement nw.Screen.initEventListeners() + class NwScreenInitEventListenersFunction: public NWSyncExtensionFunction { + public: + NwScreenInitEventListenersFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwScreenInitEventListenersFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.Screen.initEventListeners", UNKNOWN) + + private: + DISALLOW_COPY_AND_ASSIGN(NwScreenInitEventListenersFunction); + }; + + // implement nw.Screen.startMonitor() + class NwScreenStartMonitorFunction : public NWSyncExtensionFunction { + public: + NwScreenStartMonitorFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwScreenStartMonitorFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.Screen.startMonitor", UNKNOWN) + + private: + DISALLOW_COPY_AND_ASSIGN(NwScreenStartMonitorFunction); + }; + + // implement nw.Screen.stopMonitor() + class NwScreenStopMonitorFunction : public NWSyncExtensionFunction { + public: + NwScreenStopMonitorFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwScreenStopMonitorFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.Screen.stopMonitor", UNKNOWN) + + private: + DISALLOW_COPY_AND_ASSIGN(NwScreenStopMonitorFunction); + }; + + // implement nw.Screen.isMonitorStarted() + class NwScreenIsMonitorStartedFunction : public NWSyncExtensionFunction { + public: + NwScreenIsMonitorStartedFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwScreenIsMonitorStartedFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.Screen.isMonitorStarted", UNKNOWN) + + private: + DISALLOW_COPY_AND_ASSIGN(NwScreenIsMonitorStartedFunction); + }; +} // extensions + +#endif //NW_SRC_API_NW_SCREEN_API_H_ diff --git a/src/api/schemas.gypi b/src/api/schemas.gypi index a462bbd403..a2127ab231 100644 --- a/src/api/schemas.gypi +++ b/src/api/schemas.gypi @@ -14,6 +14,7 @@ 'nw_clipboard.idl', 'nw_menu.idl', 'nw_shell.idl', + 'nw_screen.idl', 'nw_tray.idl', 'nw_current_window_internal.idl', 'nw_test.idl', diff --git a/src/resources/api_nw_screen.js b/src/resources/api_nw_screen.js new file mode 100644 index 0000000000..a468beefeb --- /dev/null +++ b/src/resources/api_nw_screen.js @@ -0,0 +1,76 @@ +var nw_binding = require('binding').Binding.create('nw.Screen'); +var sendRequest = require('sendRequest'); +var EventEmitter = nw.require('events').EventEmitter; + +// Hook Sync API calls +nw_binding.registerCustomHook(function(bindingsAPI) { + var apiFunctions = bindingsAPI.apiFunctions; + ['getScreens', 'initEventListeners', 'startMonitor', 'stopMonitor', 'isMonitorStarted'].forEach(function(nwSyncAPIName) { + apiFunctions.setHandleRequest(nwSyncAPIName, function() { + return sendRequest.sendRequestSync(this.name, arguments, this.definition.parameters, {}); + }); + }); +}); + +var nwScreenBinding = nw_binding.generate(); +var inited = false; +var events = { + onDisplayAdded: 'displayAdded', + onDisplayRemoved: 'displayRemoved', + onDisplayBoundsChanged: 'displayBoundsChanged' +}; + +var sourceEvents = { + onSourceAdded: 'added', + onSourceRemoved: 'removed', + onSourceOrderChanged: 'orderchanged', + onSourceNameChanged: 'namechanged', + onSourceThumbnailChanged: 'thumbnailchanged' +}; + +var Screen = new EventEmitter(); + +Screen.Init = function() { + if (inited) return; + inited = true; + // init native event listeners + nwScreenBinding.initEventListeners(); + // bind native event listeners and re-emit event to JS + Object.keys(events).forEach(function(eventName) { + nwScreenBinding[eventName].addListener(function() { + var args = Array.prototype.concat.apply([events[eventName]], arguments); + Screen.emit.apply(Screen, args); + }); + }); + Object.keys(sourceEvents).forEach(function(eventName) { + nwScreenBinding[eventName].addListener(function() { + var args = Array.prototype.concat.apply([sourceEvents[eventName]], arguments); + Screen.DesktopCaptureMonitor.emit.apply(Screen.DesktopCaptureMonitor, args); + }); + }); + + return Screen; +}; + +Object.defineProperty(Screen, 'screens', { + get: function() { + return nwScreenBinding.getScreens(); + }, + enumerable: true +}); + +Screen.chooseDesktopMedia = chrome.desktopCapture.chooseDesktopMedia; +Screen.cancelChooseDesktopMedia = chrome.desktopCapture.cancelChooseDesktopMedia; + +Screen.DesktopCaptureMonitor = new EventEmitter(); +Screen.DesktopCaptureMonitor.start = nwScreenBinding.startMonitor; +Screen.DesktopCaptureMonitor.stop = nwScreenBinding.stopMonitor; + +Object.defineProperty(Screen.DesktopCaptureMonitor, 'started', { + get: function() { + return nwScreenBinding.isMonitorStarted()[0]; + }, + enumerable: true +}); + +exports.binding = Screen; \ No newline at end of file From b62fc02c5b2bd548c04b95d06971eeebc751247d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 26 Nov 2015 09:55:43 +0800 Subject: [PATCH 077/404] port Window.width and height --- src/api/nw_window.idl | 2 ++ src/resources/api_nw_window.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 5ca34e300c..e5a9c86438 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -61,6 +61,8 @@ namespace nw.Window { object menu; long x; long y; + long width; + long height; }; interface Functions { // get the current window diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 9c62fbce47..a3b83ee97c 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -145,6 +145,22 @@ nw_binding.registerCustomHook(function(bindingsAPI) { this.appWindow.outerBounds.top = y; } }); + Object.defineProperty(NWWindow.prototype, 'width', { + get: function() { + return this.appWindow.innerBounds.width; + }, + set: function(val) { + this.appWindow.innerBounds.width = val; + } + }); + Object.defineProperty(NWWindow.prototype, 'height', { + get: function() { + return this.appWindow.innerBounds.height; + }, + set: function(val) { + this.appWindow.innerBounds.height = val; + } + }); Object.defineProperty(NWWindow.prototype, 'menu', { get: function() { var ret = privates(this).menu || {}; From 14370e37217ec2ededbb27173f43ac65b4126705 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 26 Nov 2015 09:56:44 +0800 Subject: [PATCH 078/404] bump version to 0.13.0-alpha7 --- src/nw_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nw_version.h b/src/nw_version.h index ffed25a050..49e276057c 100644 --- a/src/nw_version.h +++ b/src/nw_version.h @@ -38,7 +38,7 @@ #else # define NW_VERSION_STRING NW_STRINGIFY(NW_MAJOR_VERSION) "." \ NW_STRINGIFY(NW_MINOR_VERSION) "." \ - NW_STRINGIFY(NW_PATCH_VERSION) "-alpha6" + NW_STRINGIFY(NW_PATCH_VERSION) "-alpha7" #endif #define NW_VERSION "v" NW_VERSION_STRING From 52b0c3a612adf56ad4aa52153bea4007cafdbbf4 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 26 Nov 2015 10:57:06 +0800 Subject: [PATCH 079/404] port nw.Window.title --- src/api/nw_window.idl | 1 + src/resources/api_nw_window.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index e5a9c86438..5640c5e554 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -63,6 +63,7 @@ namespace nw.Window { long y; long width; long height; + DOMString title; }; interface Functions { // get the current window diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index a3b83ee97c..3bdcaad718 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -161,6 +161,14 @@ nw_binding.registerCustomHook(function(bindingsAPI) { this.appWindow.innerBounds.height = val; } }); + Object.defineProperty(NWWindow.prototype, 'title', { + get: function() { + return this.appWindow.contentWindow.document.title; + }, + set: function(val) { + this.appWindow.contentWindow.document.title = val; + } + }); Object.defineProperty(NWWindow.prototype, 'menu', { get: function() { var ret = privates(this).menu || {}; From 227fecb32d0be4b309b86bcde91e3ee16077b8c3 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 26 Nov 2015 13:02:16 +0800 Subject: [PATCH 080/404] Added test cases for nw.Screen API --- .../screen-choosedekstopmedia/index.html | 71 +++++++++++++++++++ .../screen-choosedekstopmedia/package.json | 8 +++ .../screen-choosedekstopmedia/test.py | 17 +++++ .../screen-desktopcapturemonitor/index.html | 60 ++++++++++++++++ .../screen-desktopcapturemonitor/package.json | 4 ++ .../screen-desktopcapturemonitor/popup.html | 16 +++++ .../screen-desktopcapturemonitor/test.py | 57 +++++++++++++++ test/remoting/screen-events/index.html | 24 +++++++ test/remoting/screen-events/package.json | 4 ++ test/remoting/screen-events/test.py | 17 +++++ test/remoting/screen-property/index.html | 18 +++++ test/remoting/screen-property/package.json | 4 ++ test/remoting/screen-property/test.py | 17 +++++ test/remoting/screen/index.html | 18 +++++ test/remoting/screen/package.json | 4 ++ test/remoting/screen/test.py | 17 +++++ 16 files changed, 356 insertions(+) create mode 100644 test/remoting/screen-choosedekstopmedia/index.html create mode 100644 test/remoting/screen-choosedekstopmedia/package.json create mode 100644 test/remoting/screen-choosedekstopmedia/test.py create mode 100644 test/remoting/screen-desktopcapturemonitor/index.html create mode 100644 test/remoting/screen-desktopcapturemonitor/package.json create mode 100644 test/remoting/screen-desktopcapturemonitor/popup.html create mode 100644 test/remoting/screen-desktopcapturemonitor/test.py create mode 100644 test/remoting/screen-events/index.html create mode 100644 test/remoting/screen-events/package.json create mode 100644 test/remoting/screen-events/test.py create mode 100644 test/remoting/screen-property/index.html create mode 100644 test/remoting/screen-property/package.json create mode 100644 test/remoting/screen-property/test.py create mode 100644 test/remoting/screen/index.html create mode 100644 test/remoting/screen/package.json create mode 100644 test/remoting/screen/test.py diff --git a/test/remoting/screen-choosedekstopmedia/index.html b/test/remoting/screen-choosedekstopmedia/index.html new file mode 100644 index 0000000000..72e118acc3 --- /dev/null +++ b/test/remoting/screen-choosedekstopmedia/index.html @@ -0,0 +1,71 @@ + + + + + + Screen.chooseDesktopMedia + + + + + + \ No newline at end of file diff --git a/test/remoting/screen-choosedekstopmedia/package.json b/test/remoting/screen-choosedekstopmedia/package.json new file mode 100644 index 0000000000..c02c4ae325 --- /dev/null +++ b/test/remoting/screen-choosedekstopmedia/package.json @@ -0,0 +1,8 @@ +{ + "name": "screen-chooseDesktopMedia", + "main": "index.html", + "window": { + "width": 600, + "height": 600 + } +} \ No newline at end of file diff --git a/test/remoting/screen-choosedekstopmedia/test.py b/test/remoting/screen-choosedekstopmedia/test.py new file mode 100644 index 0000000000..cbf4d67e9b --- /dev/null +++ b/test/remoting/screen-choosedekstopmedia/test.py @@ -0,0 +1,17 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + driver.implicitly_wait(10) + print driver.current_url + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() diff --git a/test/remoting/screen-desktopcapturemonitor/index.html b/test/remoting/screen-desktopcapturemonitor/index.html new file mode 100644 index 0000000000..2b442580ce --- /dev/null +++ b/test/remoting/screen-desktopcapturemonitor/index.html @@ -0,0 +1,60 @@ + + + + + + Screen.DesktopCaptureMonitor + + + + + + + + \ No newline at end of file diff --git a/test/remoting/screen-desktopcapturemonitor/package.json b/test/remoting/screen-desktopcapturemonitor/package.json new file mode 100644 index 0000000000..d51f280290 --- /dev/null +++ b/test/remoting/screen-desktopcapturemonitor/package.json @@ -0,0 +1,4 @@ +{ + "name": "screen", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/screen-desktopcapturemonitor/popup.html b/test/remoting/screen-desktopcapturemonitor/popup.html new file mode 100644 index 0000000000..d92b2ed472 --- /dev/null +++ b/test/remoting/screen-desktopcapturemonitor/popup.html @@ -0,0 +1,16 @@ + + + + + + Popup + + + + + + \ No newline at end of file diff --git a/test/remoting/screen-desktopcapturemonitor/test.py b/test/remoting/screen-desktopcapturemonitor/test.py new file mode 100644 index 0000000000..dae96a47a3 --- /dev/null +++ b/test/remoting/screen-desktopcapturemonitor/test.py @@ -0,0 +1,57 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + +def assert_event_fired(event_name): + tmp_window_handle = None + try: + tmp_window_handle = driver.current_window_handle + except: + pass + driver.switch_to_window(base_window) + assert("success" in driver.find_element_by_id(event_name).get_attribute('innerHTML')) + print "%s triggered" % event_name + if tmp_window_handle is not None: + driver.switch_to_window(tmp_window_handle) + +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + driver.implicitly_wait(10); + base_window = driver.current_window_handle + print "startMonitor" + driver.find_element_by_id('startMonitor').click() + + print "newWindow" + driver.find_element_by_id('newWindow').click() + assert_event_fired('added') + driver.switch_to_window('mypopup'); + + print "trigger orderchanged" + time.sleep(2) # wait 2s for remembering current window order + driver.switch_to_window(base_window) + driver.execute_script('nw.Window.get().focus()') + assert_event_fired('orderchanged') + + print "trigger namechanged" + driver.switch_to_window('mypopup') + driver.find_element_by_id('changeTitle').click() + assert_event_fired('namechanged') + + print "trigger removed" + driver.close() + assert_event_fired('removed') + + print "get result" + driver.switch_to_window(base_window) + driver.find_element_by_id('stopMonitor').click() + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() \ No newline at end of file diff --git a/test/remoting/screen-events/index.html b/test/remoting/screen-events/index.html new file mode 100644 index 0000000000..703199560b --- /dev/null +++ b/test/remoting/screen-events/index.html @@ -0,0 +1,24 @@ + + + + + + Screen events + + + + + \ No newline at end of file diff --git a/test/remoting/screen-events/package.json b/test/remoting/screen-events/package.json new file mode 100644 index 0000000000..4e02cf8f9a --- /dev/null +++ b/test/remoting/screen-events/package.json @@ -0,0 +1,4 @@ +{ + "name": "screen-events", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/screen-events/test.py b/test/remoting/screen-events/test.py new file mode 100644 index 0000000000..596e8a00e7 --- /dev/null +++ b/test/remoting/screen-events/test.py @@ -0,0 +1,17 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() diff --git a/test/remoting/screen-property/index.html b/test/remoting/screen-property/index.html new file mode 100644 index 0000000000..6ef4f4cead --- /dev/null +++ b/test/remoting/screen-property/index.html @@ -0,0 +1,18 @@ + + + + + + Screen property + + + + + \ No newline at end of file diff --git a/test/remoting/screen-property/package.json b/test/remoting/screen-property/package.json new file mode 100644 index 0000000000..c11519cc79 --- /dev/null +++ b/test/remoting/screen-property/package.json @@ -0,0 +1,4 @@ +{ + "name": "screen-property", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/screen-property/test.py b/test/remoting/screen-property/test.py new file mode 100644 index 0000000000..596e8a00e7 --- /dev/null +++ b/test/remoting/screen-property/test.py @@ -0,0 +1,17 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() diff --git a/test/remoting/screen/index.html b/test/remoting/screen/index.html new file mode 100644 index 0000000000..0e621f2d50 --- /dev/null +++ b/test/remoting/screen/index.html @@ -0,0 +1,18 @@ + + + + + + Screen + + + + + \ No newline at end of file diff --git a/test/remoting/screen/package.json b/test/remoting/screen/package.json new file mode 100644 index 0000000000..d51f280290 --- /dev/null +++ b/test/remoting/screen/package.json @@ -0,0 +1,4 @@ +{ + "name": "screen", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/screen/test.py b/test/remoting/screen/test.py new file mode 100644 index 0000000000..596e8a00e7 --- /dev/null +++ b/test/remoting/screen/test.py @@ -0,0 +1,17 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + result = driver.find_element_by_id('result') + print result.get_attribute('innerHTML') + assert("success" in result.get_attribute('innerHTML')) +finally: + driver.quit() From 91c3eee29e43bb9b404607068c959a12c0f0a1aa Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 26 Nov 2015 16:31:05 +0800 Subject: [PATCH 081/404] port nw.Window.reload and nw.Window.reloadIgnoringCache --- src/api/nw_current_window_internal.idl | 1 + src/api/nw_window.idl | 2 ++ src/api/nw_window_api.cc | 7 +++++++ src/api/nw_window_api.h | 11 +++++++++++ src/resources/api_nw_window.js | 6 ++++++ 5 files changed, 27 insertions(+) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index be0472993c..e80f20a136 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -20,5 +20,6 @@ namespace nw.currentWindowInternal { static void capturePageInternal(optional CapturePageOptions options, optional CapturePageCallback callback); static void clearMenu(); static void setMenu(long id); + static void reloadIgnoringCache(); }; }; diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 5640c5e554..3dab8d6da2 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -52,6 +52,8 @@ namespace nw.Window { static void toggleFullscreen(); static void on(DOMString event, EventCallback callback); + static void reload(); + static void reloadIgnoringCache(); static void eval(object frame, DOMString script); static void evalNWBin(object frame, DOMString path); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 8a72e498fb..2366b834fc 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -487,4 +487,11 @@ bool NwCurrentWindowInternalSetProgressBarFunction::RunAsync() { } #endif +bool NwCurrentWindowInternalReloadIgnoringCacheFunction::RunAsync() { + content::WebContents* web_contents = GetSenderWebContents(); + web_contents->GetController().ReloadIgnoringCache(false); + SendResponse(true); + return true; +} + } // namespace extensions diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 8268505837..2ae3ab9538 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -134,5 +134,16 @@ class NwCurrentWindowInternalSetProgressBarFunction : public AsyncExtensionFunct void Callback(); }; +class NwCurrentWindowInternalReloadIgnoringCacheFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalReloadIgnoringCacheFunction() {} + + protected: + ~NwCurrentWindowInternalReloadIgnoringCacheFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.reloadIgnoringCache", UNKNOWN) +}; } // namespace extensions #endif diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 3bdcaad718..8864a965a4 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -76,6 +76,12 @@ nw_binding.registerCustomHook(function(bindingsAPI) { } currentNWWindowInternal.capturePageInternal(options, cb); }; + NWWindow.prototype.reload = function () { + this.appWindow.contentWindow.location.reload(); + }; + NWWindow.prototype.reloadIgnoringCache = function () { + currentNWWindowInternal.reloadIgnoringCache(); + }; NWWindow.prototype.eval = function (frame, script) { nwNatives.evalScript(frame, script); }; From 60a6147c02dadfc892f40b33e846293435ccde82 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 30 Nov 2015 12:28:27 +0800 Subject: [PATCH 082/404] [test] add document-start-end --- test/remoting/document-start-end/iframe.html | 15 ++++++++++ test/remoting/document-start-end/index.html | 30 +++++++++++++++++++ test/remoting/document-start-end/package.json | 4 +++ test/remoting/document-start-end/popup.html | 15 ++++++++++ test/remoting/document-start-end/test.py | 20 +++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 test/remoting/document-start-end/iframe.html create mode 100644 test/remoting/document-start-end/index.html create mode 100644 test/remoting/document-start-end/package.json create mode 100644 test/remoting/document-start-end/popup.html create mode 100644 test/remoting/document-start-end/test.py diff --git a/test/remoting/document-start-end/iframe.html b/test/remoting/document-start-end/iframe.html new file mode 100644 index 0000000000..738ec80e31 --- /dev/null +++ b/test/remoting/document-start-end/iframe.html @@ -0,0 +1,15 @@ + + + + + popup + + + +

iframe

+ + diff --git a/test/remoting/document-start-end/index.html b/test/remoting/document-start-end/index.html new file mode 100644 index 0000000000..db744d0159 --- /dev/null +++ b/test/remoting/document-start-end/index.html @@ -0,0 +1,30 @@ + + + + + +

document event test

+

document event test

+ + + diff --git a/test/remoting/document-start-end/package.json b/test/remoting/document-start-end/package.json new file mode 100644 index 0000000000..871dc6196b --- /dev/null +++ b/test/remoting/document-start-end/package.json @@ -0,0 +1,4 @@ +{ + "name":"test-doc-startend", + "main": "index.html" +} diff --git a/test/remoting/document-start-end/popup.html b/test/remoting/document-start-end/popup.html new file mode 100644 index 0000000000..ac6e18681d --- /dev/null +++ b/test/remoting/document-start-end/popup.html @@ -0,0 +1,15 @@ + + + + + popup + + + +

success from popup

+ + diff --git a/test/remoting/document-start-end/test.py b/test/remoting/document-start-end/test.py new file mode 100644 index 0000000000..58edb64ceb --- /dev/null +++ b/test/remoting/document-start-end/test.py @@ -0,0 +1,20 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +time.sleep(1) +try: + print driver.current_url + result = driver.find_element_by_id('result').get_attribute('innerHTML') + result2 = driver.find_element_by_id('result2').get_attribute('innerHTML') + print result + print result2 + assert(result == 'success from popup' and result2 == 'startiframe') +finally: + #time.sleep(50) + driver.quit() From 082603369f0c7ddc84b1a5586acc0e8de8543da5 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 30 Nov 2015 12:28:50 +0800 Subject: [PATCH 083/404] port document-start and document-end --- src/api/nw_window.idl | 2 ++ src/nw_content.cc | 19 +++++++++++++++++++ src/nw_content.h | 3 +++ src/resources/api_nw_window.js | 18 ++++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 3dab8d6da2..9826814ab4 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -79,5 +79,7 @@ namespace nw.Window { [nocompile] static void onNewWinPolicy(); [nocompile] static void onNavigation(); [nocompile] static void LoadingStateChanged(); + [nocompile] static void onDocumentStart(); + [nocompile] static void onDocumentEnd(); }; }; diff --git a/src/nw_content.cc b/src/nw_content.cc index 2e9c4e72da..0634eb8d81 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -99,6 +99,7 @@ using extensions::Manifest; using extensions::Feature; using extensions::ExtensionPrefs; using extensions::ExtensionRegistry; +using extensions::Dispatcher; using blink::WebScriptSource; namespace manifest_keys = extensions::manifest_keys; @@ -290,6 +291,24 @@ void DocumentFinishHook(blink::WebFrame* frame, } } +void DocumentHook2(bool start, content::RenderFrame* frame, Dispatcher* dispatcher) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + v8::Local v8_context = frame->GetRenderView() + ->GetWebView()->mainFrame()->mainWorldScriptContext(); + ScriptContext* script_context = + dispatcher->script_context_set().GetByV8Context(v8_context); + if (!script_context) + return; + std::vector > arguments; + blink::WebLocalFrame* web_frame = frame->GetWebFrame(); + v8::Local window = + web_frame->mainWorldScriptContext()->Global(); + arguments.push_back(v8::Boolean::New(isolate, start)); + arguments.push_back(window); + script_context->module_system()->CallModuleMethod("nw.Window", "onDocumentStartEnd", &arguments); +} + void DocumentElementHook(blink::WebFrame* frame, const extensions::Extension* extension, const GURL& effective_document_url) { diff --git a/src/nw_content.h b/src/nw_content.h index 66349f9b5e..17e75f88e2 100644 --- a/src/nw_content.h +++ b/src/nw_content.h @@ -17,6 +17,7 @@ namespace blink { } namespace content { + class RenderFrame; class RenderProcessHost; class NotificationDetails; class RenderView; @@ -24,6 +25,7 @@ namespace content { } namespace extensions { + class Dispatcher; class Extension; class ScriptContext; class Dispatcher; @@ -41,6 +43,7 @@ void DocumentElementHook(blink::WebFrame* frame, void DocumentFinishHook(blink::WebFrame* frame, const extensions::Extension* extension, const GURL& effective_document_url); + void DocumentHook2(bool start, content::RenderFrame* frame, extensions::Dispatcher* dispatcher); void RendererProcessTerminatedHook(content::RenderProcessHost* process, const content::NotificationDetails& details); void OnRenderProcessShutdownHook(extensions::ScriptContext* context); diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 8864a965a4..b2deccf4de 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -26,6 +26,8 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.onNewWinPolicy = new Event(); NWWindow.prototype.onNavigation = new Event(); NWWindow.prototype.LoadingStateChanged = new Event(); + NWWindow.prototype.onDocumentStart = new Event(); + NWWindow.prototype.onDocumentEnd = new Event(); NWWindow.prototype.on = function (event, callback) { switch (event) { @@ -52,6 +54,12 @@ nw_binding.registerCustomHook(function(bindingsAPI) { callback(frame, url, policy, context); }); break; + case 'document-start': + this.onDocumentStart.addListener(callback); + break; + case 'document-end': + this.onDocumentEnd.addListener(callback); + break; } }; NWWindow.prototype.capturePage = function (callback, options) { @@ -276,7 +284,17 @@ function onLoadingStateChanged(status) { dispatchEventIfExists(currentNWWindow, "LoadingStateChanged", [status]); } +function onDocumentStartEnd(start, frame) { + if (!currentNWWindow) + return; + if (start) + dispatchEventIfExists(currentNWWindow, "onDocumentStart", [frame]); + else + dispatchEventIfExists(currentNWWindow, "onDocumentEnd", [frame]); +} + exports.binding = nw_binding.generate(); exports.onNewWinPolicy = onNewWinPolicy; exports.onNavigation = onNavigation; exports.LoadingStateChanged = onLoadingStateChanged; +exports.onDocumentStartEnd = onDocumentStartEnd; From ad4137f2bc9455edc7c3ecd53f2651f7a172dbf9 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 30 Nov 2015 12:52:17 +0800 Subject: [PATCH 084/404] port nw.Window.cookies --- src/resources/api_nw_window.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index b2deccf4de..63132151b9 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -143,6 +143,8 @@ nw_binding.registerCustomHook(function(bindingsAPI) { }; NWWindow.prototype.setResizable = function (resizable) { }; + NWWindow.prototype.cookies = chrome.cookies; + Object.defineProperty(NWWindow.prototype, 'x', { get: function() { return this.appWindow.outerBounds.left; From 69703e9830066746a62ec740e5c3183ebd5a4b42 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 30 Nov 2015 14:48:21 +0800 Subject: [PATCH 085/404] port zoomLevel --- src/api/nw_current_window_internal.idl | 2 ++ src/api/nw_window.idl | 1 + src/api/nw_window_api.cc | 30 ++++++++++++++++++++++++++ src/api/nw_window_api.h | 21 ++++++++++++++++++ src/resources/api_nw_window.js | 24 +++++++++++++++++++-- 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index e80f20a136..fd18fde630 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -21,5 +21,7 @@ namespace nw.currentWindowInternal { static void clearMenu(); static void setMenu(long id); static void reloadIgnoringCache(); + static double getZoom(); + static void setZoom(double level); }; }; diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 9826814ab4..ac9ef8694a 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -65,6 +65,7 @@ namespace nw.Window { long y; long width; long height; + double zoomLevel; DOMString title; }; interface Functions { diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 2366b834fc..8534c62186 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -6,12 +6,14 @@ #include "content/public/browser/render_widget_host.h" #include "chrome/browser/devtools/devtools_window.h" #include "chrome/browser/extensions/devtools_util.h" +#include "components/ui/zoom/zoom_controller.h" #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/api/object_manager.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/app_window/app_window_registry.h" +#include "extensions/browser/extension_zoom_request_client.h" #include "extensions/components/native_app_window/native_app_window_views.h" #include "extensions/common/error_utils.h" #include "extensions/common/constants.h" @@ -47,6 +49,7 @@ using nw::BrowserViewLayout; using content::RenderWidgetHost; using content::RenderWidgetHostView; using content::WebContents; +using ui_zoom::ZoomController; using nw::Menu; @@ -494,4 +497,31 @@ bool NwCurrentWindowInternalReloadIgnoringCacheFunction::RunAsync() { return true; } +bool NwCurrentWindowInternalGetZoomFunction::RunNWSync(base::ListValue* response, std::string* error) { + content::WebContents* web_contents = GetSenderWebContents(); + if (!web_contents) + return false; + double zoom_level = + ZoomController::FromWebContents(web_contents)->GetZoomLevel(); + response->AppendDouble(zoom_level); + return true; +} + +bool NwCurrentWindowInternalSetZoomFunction::RunNWSync(base::ListValue* response, std::string* error) { + double zoom_level; + + EXTENSION_FUNCTION_VALIDATE(args_->GetDouble(0, &zoom_level)); + content::WebContents* web_contents = GetSenderWebContents(); + if (!web_contents) + return false; + ZoomController* zoom_controller = + ZoomController::FromWebContents(web_contents); + scoped_refptr client( + new ExtensionZoomRequestClient(extension())); + if (!zoom_controller->SetZoomLevelByClient(zoom_level, client)) { + return false; + } + return true; +} + } // namespace extensions diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 2ae3ab9538..21f0941f2d 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -145,5 +145,26 @@ class NwCurrentWindowInternalReloadIgnoringCacheFunction : public AsyncExtension bool RunAsync() override; DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.reloadIgnoringCache", UNKNOWN) }; + +class NwCurrentWindowInternalGetZoomFunction : public NWSyncExtensionFunction { + public: + NwCurrentWindowInternalGetZoomFunction() {} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwCurrentWindowInternalGetZoomFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.getZoom", UNKNOWN) +}; + +class NwCurrentWindowInternalSetZoomFunction : public NWSyncExtensionFunction { + public: + NwCurrentWindowInternalSetZoomFunction() {} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwCurrentWindowInternalSetZoomFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.setZoom", UNKNOWN) +}; + } // namespace extensions #endif diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 63132151b9..00009a888a 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -3,18 +3,30 @@ var nw_binding = require('binding').Binding.create('nw.Window'); var nwNatives = requireNative('nw_natives'); var forEach = require('utils').forEach; var Event = require('event_bindings').Event; +var sendRequest = require('sendRequest'); var currentNWWindow = null; var currentNWWindowInternal = null; +var nw_internal = require('binding').Binding.create('nw.currentWindowInternal'); + +nw_internal.registerCustomHook(function(bindingsAPI) { + var apiFunctions = bindingsAPI.apiFunctions; + apiFunctions.setHandleRequest('getZoom', function() { + return sendRequest.sendRequestSync('nw.currentWindowInternal.getZoom', arguments, this.definition.parameters, {})[0]; + }); + apiFunctions.setHandleRequest('setZoom', function() { + return sendRequest.sendRequestSync('nw.currentWindowInternal.setZoom', arguments, this.definition.parameters, {}); + }); +}); + nw_binding.registerCustomHook(function(bindingsAPI) { var apiFunctions = bindingsAPI.apiFunctions; apiFunctions.setHandleRequest('get', function() { if (currentNWWindow) return currentNWWindow; - currentNWWindowInternal = - Binding.create('nw.currentWindowInternal').generate(); + currentNWWindowInternal = nw_internal.generate(); var NWWindow = function() { this.appWindow = chrome.app.window.current(); privates(this).menu = null; @@ -185,6 +197,14 @@ nw_binding.registerCustomHook(function(bindingsAPI) { this.appWindow.contentWindow.document.title = val; } }); + Object.defineProperty(NWWindow.prototype, 'zoomLevel', { + get: function() { + return currentNWWindowInternal.getZoom(); + }, + set: function(val) { + currentNWWindowInternal.setZoom(val); + } + }); Object.defineProperty(NWWindow.prototype, 'menu', { get: function() { var ret = privates(this).menu || {}; From 6908d0b121dfa968c5fc1c7ed47f1e4daa27460b Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Fri, 27 Nov 2015 16:47:44 +0800 Subject: [PATCH 086/404] Ported nw.Shortcut API --- nw.gypi | 2 + src/api/_api_features.json | 4 + src/api/nw_shortcut.idl | 32 +++++ src/api/nw_shortcut_api.cc | 131 ++++++++++++++++++ src/api/nw_shortcut_api.h | 38 ++++++ src/api/schemas.gypi | 3 +- src/resources/api_nw_app.js | 6 + src/resources/api_nw_shortcut.js | 227 +++++++++++++++++++++++++++++++ 8 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 src/api/nw_shortcut.idl create mode 100644 src/api/nw_shortcut_api.cc create mode 100644 src/api/nw_shortcut_api.h create mode 100644 src/resources/api_nw_shortcut.js diff --git a/nw.gypi b/nw.gypi index d23120a9e1..38e38929fb 100644 --- a/nw.gypi +++ b/nw.gypi @@ -664,6 +664,8 @@ 'src/api/nw_shell_api.h', 'src/api/nw_screen_api.cc', 'src/api/nw_screen_api.h', + 'src/api/nw_shortcut_api.cc', + 'src/api/nw_shortcut_api.h', 'src/api/object_manager.cc', 'src/api/object_manager.h', 'src/api/object_manager_factory.cc', diff --git a/src/api/_api_features.json b/src/api/_api_features.json index cfe8e462c9..5077c76925 100644 --- a/src/api/_api_features.json +++ b/src/api/_api_features.json @@ -43,6 +43,10 @@ "channel": "stable", "contexts": ["blessed_extension"] }, + "nw.Shortcut": { + "channel": "stable", + "contexts": ["blessed_extension"] + }, "nw.currentWindowInternal": { "noparent": true, "internal": true, diff --git a/src/api/nw_shortcut.idl b/src/api/nw_shortcut.idl new file mode 100644 index 0000000000..2f0bcac88e --- /dev/null +++ b/src/api/nw_shortcut.idl @@ -0,0 +1,32 @@ +// Copyright 2015 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// nw Menu API +[implemented_in="content/nw/src/api/nw_shortcut_api.h"] +namespace nw.Shortcut { + + dictionary Modifiers { + boolean command; + boolean ctrl; + boolean alt; + boolean shift; + }; + + dictionary Accelerator { + DOMString key; + Modifiers modifiers; + }; + + interface Events { + static void onKeyPressed(Accelerator accelerator); + }; + + interface Functions { + + static boolean registerAccelerator(Accelerator accelerator); + static boolean unregisterAccelerator(Accelerator accelerator); + + }; + +}; \ No newline at end of file diff --git a/src/api/nw_shortcut_api.cc b/src/api/nw_shortcut_api.cc new file mode 100644 index 0000000000..1e6d25ac73 --- /dev/null +++ b/src/api/nw_shortcut_api.cc @@ -0,0 +1,131 @@ +#include "nw_shortcut_api.h" +#include "base/lazy_instance.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/extensions/global_shortcut_listener.h" +#include "content/nw/src/api/nw_shortcut.h" +#include "extensions/browser/extensions_browser_client.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event_constants.h" +#include "ui/events/keycodes/dom/keycode_converter.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/events/keycodes/keyboard_code_conversion.h" + +using namespace extensions::nwapi::nw__shortcut; +using namespace content; + +namespace extensions { + +class NWShortcutObserver : public GlobalShortcutListener::Observer { +public: + static NWShortcutObserver* GetInstance(); + + // ui::GlobalShortcutListener::Observer implementation. + void OnKeyPressed(const ui::Accelerator& uiAccelerator) override; + +}; + +namespace { + + scoped_ptr dictionaryToUIAccelerator(const base::DictionaryValue *acceleratorDict) { + nwapi::nw__shortcut::Accelerator accelerator; + nwapi::nw__shortcut::Accelerator::Populate(*acceleratorDict, &accelerator); + + // build keyboard code + ui::DomCode domCode = ui::KeycodeConverter::CodeStringToDomCode(accelerator.key.c_str()); + ui::KeyboardCode keyboardCode = ui::DomCodeToUsLayoutKeyboardCode(domCode); + + // build modifier + int modifiers = ui::EF_NONE; + if (accelerator.modifiers.alt) { + modifiers |= ui::EF_ALT_DOWN; + } + if (accelerator.modifiers.command) { + modifiers |= ui::EF_COMMAND_DOWN; + } + if (accelerator.modifiers.ctrl) { + modifiers |= ui::EF_CONTROL_DOWN; + } + if (accelerator.modifiers.shift) { + modifiers |= ui::EF_SHIFT_DOWN; + } + + scoped_ptr uiAccelerator(new ui::Accelerator(keyboardCode, modifiers)); + + return uiAccelerator.Pass(); + } + + scoped_ptr UIAcceleratorToAccelerator(const ui::Accelerator &uiAccelerator) { + scoped_ptr accelerator(new Accelerator()); + ui::DomCode domCode = ui::UsLayoutKeyboardCodeToDomCode(uiAccelerator.key_code()); + accelerator->key = ui::KeycodeConverter::DomCodeToCodeString(domCode); + int modifiers = uiAccelerator.modifiers(); + if (modifiers & ui::EF_ALT_DOWN) { + accelerator->modifiers.alt = true; + } + if (modifiers & ui::EF_COMMAND_DOWN) { + accelerator->modifiers.command = true; + } + if (modifiers & ui::EF_CONTROL_DOWN) { + accelerator->modifiers.ctrl = true; + } + if (modifiers & ui::EF_SHIFT_DOWN) { + accelerator->modifiers.shift = true; + } + return accelerator.Pass(); + } + + void DispatchEvent( + events::HistogramValue histogram_value, + const std::string& event_name, + scoped_ptr args) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + ExtensionsBrowserClient::Get()->BroadcastEventToRenderers( + histogram_value, event_name, args.Pass()); + } + + base::LazyInstance::Leaky + g_nw_shortcut_observer_ = LAZY_INSTANCE_INITIALIZER; + +} // + +// static +NWShortcutObserver* NWShortcutObserver::GetInstance() { + return g_nw_shortcut_observer_.Pointer(); +} + +void NWShortcutObserver::OnKeyPressed (const ui::Accelerator& uiAccelerator) { + scoped_ptr accelerator = UIAcceleratorToAccelerator(uiAccelerator); + scoped_ptr args = + nwapi::nw__shortcut::OnKeyPressed::Create(*accelerator); + DispatchEvent( + events::HistogramValue::UNKNOWN, + nwapi::nw__shortcut::OnKeyPressed::kEventName, + args.Pass()); +} + +bool NwShortcutRegisterAcceleratorFunction::RunNWSync(base::ListValue* response, std::string* error) { + const base::DictionaryValue *acceleratorDict = nullptr; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &acceleratorDict)); + scoped_ptr uiAccelerator = dictionaryToUIAccelerator(acceleratorDict); + + if (!GlobalShortcutListener::GetInstance()->RegisterAccelerator(*uiAccelerator, NWShortcutObserver::GetInstance())) { + response->AppendBoolean(false); + return true; + } + + response->AppendBoolean(true); + return true; +} + +bool NwShortcutUnregisterAcceleratorFunction::RunNWSync(base::ListValue* response, std::string* error) { + const base::DictionaryValue *acceleratorDict = nullptr; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &acceleratorDict)); + scoped_ptr uiAccelerator = dictionaryToUIAccelerator(acceleratorDict); + + GlobalShortcutListener::GetInstance()->UnregisterAccelerator(*uiAccelerator, NWShortcutObserver::GetInstance()); + response->AppendBoolean(true); + return true; +} + +} // extensions diff --git a/src/api/nw_shortcut_api.h b/src/api/nw_shortcut_api.h new file mode 100644 index 0000000000..a5522c774c --- /dev/null +++ b/src/api/nw_shortcut_api.h @@ -0,0 +1,38 @@ +#ifndef NW_SRC_API_NW_SHORTCUT_API_H +#define NW_SRC_API_NW_SHORTCUT_API_H + +#include "extensions/browser/extension_function.h" + +namespace extensions { + + class NwShortcutRegisterAcceleratorFunction : public NWSyncExtensionFunction + { + public: + NwShortcutRegisterAcceleratorFunction() {} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwShortcutRegisterAcceleratorFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.Shortcut.registerAccelerator", UNKNOWN) + + private: + DISALLOW_COPY_AND_ASSIGN(NwShortcutRegisterAcceleratorFunction); + }; + + class NwShortcutUnregisterAcceleratorFunction : public NWSyncExtensionFunction + { + public: + NwShortcutUnregisterAcceleratorFunction() {} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwShortcutUnregisterAcceleratorFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.Shortcut.unregisterAccelerator", UNKNOWN) + + private: + DISALLOW_COPY_AND_ASSIGN(NwShortcutUnregisterAcceleratorFunction); + }; + +} // extensions + +#endif // NW_SRC_API_NW_SHORTCUT_API_H diff --git a/src/api/schemas.gypi b/src/api/schemas.gypi index a2127ab231..77c72d4d73 100644 --- a/src/api/schemas.gypi +++ b/src/api/schemas.gypi @@ -13,8 +13,9 @@ 'nw_window.idl', 'nw_clipboard.idl', 'nw_menu.idl', - 'nw_shell.idl', 'nw_screen.idl', + 'nw_shell.idl', + 'nw_shortcut.idl', 'nw_tray.idl', 'nw_current_window_internal.idl', 'nw_test.idl', diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index 6ae7dec4b8..218e34f2e3 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -46,6 +46,12 @@ nw_binding.registerCustomHook(function(bindingsAPI) { dataPath = nw.App.getDataPath(); return dataPath; }); + bindingsAPI.compiledApi.registerGlobalHotkey = function() { + return nw.Shortcut.registerGlobalHotkey.apply(nw.Shortcut, arguments); + }; + bindingsAPI.compiledApi.unregisterGlobalHotkey = function() { + return nw.Shortcut.unregisterGlobalHotkey.apply(nw.Shortcut, arguments); + }; }); diff --git a/src/resources/api_nw_shortcut.js b/src/resources/api_nw_shortcut.js new file mode 100644 index 0000000000..4c2b6907a2 --- /dev/null +++ b/src/resources/api_nw_shortcut.js @@ -0,0 +1,227 @@ +var nw_binding = require('binding').Binding.create('nw.Shortcut'); +var sendRequest = require('sendRequest'); +var EventEmitter = nw.require('events').EventEmitter; + +var OPTION_INVALID = 'Invalid option.'; +var OPTION_KEY_REQUIRED = "Shortcut requires 'key' to specify key combinations."; +var OPTION_KEY_INVALID = "Invalid 'key' format."; +var OPTION_ACTIVE_INVALID = "'active' must be a valid function."; +var OPTION_FAILED_INVALID = "'failed' must be a valid function."; +var ARUGMENT_NOT_SHORTCUT = "'shortcut' argument is not an instance of nw.Shortcut"; +var UNABLE_REGISTER_HOTKEY = "Unable to register the hotkey"; +var UNABLE_UNREGISTER_HOTKEY = "Unable to unregister the hotkey"; + +// Build alias maps of all acceptable key code for nw.Shortcut API +// The list is polled from http://www.w3.org/TR/DOM-Level-3-Events-code/. And +// it also contains easy to type aliases. For example, you can use either +// "ctrl-`" or "ctrl-backquote" to register a shortcut. +// It is also backward compatible with NW12. +var ALIAS_MAP = (function() { + var map = { + '`' : 'Backquote', + '\\': 'Backslash', + '[' : 'BracketLeft', + ']' : 'BracketRight', + ',' : 'Comma', + '=' : 'Equal', + '.' : 'Period', + '\'': 'Quote', + ';' : 'Semicolon', + '/' : 'Slash', + '\n': 'Enter', + '\t': 'Tab', + // backward compatible with NW12 + 'medianexttrack': 'MediaTrackNext', + 'mediaprevtrack': 'MediaTrackPrevious' + }; + + // a~z, KeyA~KeyZ + var aCode = 'a'.charCodeAt(0); + var ACode = 'A'.charCodeAt(0); + for(var i = 0; i < 26; i++) { + var alpha = 'Key' + String.fromCharCode(ACode + i); + map[String.fromCharCode(aCode + i)] = alpha; + map[alpha.toLowerCase()] = alpha; + } + + // 0~9, Digit0~Digit9, Numpad0~Numpad9 + for(var i = 0; i <= 9; i++) { + var d = i.toString(10); + var digit = 'Digit' + d; + var numpad = 'Numpad' + d; + map[d] = digit; + map[digit.toLowerCase()] = digit; + map[numpad.toLowerCase()] = numpad; + } + + // F1~F24 + for(var i = 1; i <= 24; i++) { + var f = i.toString(10); + var func = 'F' + f; + map[func.toLowerCase()] = func; + } + + // (Arrow)Down, (Arrow)Left, (Arrow)Right, (Arrow)Up + 'Down|Left|Right|Up'.split('|').forEach(function(a) { + var arrow = 'Arrow' + arrow; + map[a.toLowerCase()] = arrow; + map[arrow.toLowerCase()] = arrow; + }); + + // + var keys = 'Backquote|Backslash|Backspace|BracketLeft|BracketRight|Comma|Equal|IntlBackslash|IntlHash|IntlRo|IntlYen|Minus|Period|Quote|Semicolon|Slash' + + '|Enter|Space|Tab' + + '|Delete|End|Help|Home|Insert|PageDown|PageUp' + + '|NumLock|NumpadAdd|NumpadBackspace|NumpadClear|NumpadClearEntry|NumpadComma|NumpadDecimal|NumpadDivide|NumpadEnter|NumpadEqual|NumpadHash|NumpadMemoryAdd|NumpadMemoryClear|NumpadMemoryRecall|NumpadMemoryStore|NumpadMemorySubtract|NumpadMultiply|NumpadParenLeft|NumpadParenRight|NumpadStar|NumpadSubtract' + + '|Escape|PrintScreen|Pause|ScrollLock' + + '|BrowserBack|BrowserFavorites|BrowserForward|BrowserHome|BrowserRefresh|BrowserSearch|BrowserStop|BrowserStop|BrowserStop|LaunchApp2|LaunchMail|MediaPlayPause|MediaSelect|MediaStop|MediaTrackNext|MediaTrackPrevious|Power|Sleep|VolumeDown|VolumeMute|VolumeUp|WakeUp'; + keys.split('|').forEach(function(k) { + map[k.toLowerCase()] = k; + }); + + return map; +}()); + +// modifiers +var MODIFIERS_REG = /^ctrl|alt|shift|command$/i; + +// Hook Sync API calls +nw_binding.registerCustomHook(function(bindingsAPI) { + var apiFunctions = bindingsAPI.apiFunctions; + ['registerAccelerator', 'unregisterAccelerator'].forEach(function(nwSyncAPIName) { + apiFunctions.setHandleRequest(nwSyncAPIName, function() { + return sendRequest.sendRequestSync(this.name, arguments, this.definition.parameters, {})[0]; + }); + }); +}); + +var nwShortcutBinding = nw_binding.generate(); +var handlers = {}; + +function keyToAccelerator(key) { + key = key.toString(); + var parts = key.split('-'); + var maybeKey = parts.pop(); + var maybeModifiers = parts; + + var modifiers = { + alt: false, + command: false, + ctrl: false, + shift: false + }; + + maybeKey = ALIAS_MAP[maybeKey.toLowerCase()]; + if (!maybeKey) { + throw new TypeError(OPTION_KEY_INVALID); + } + if (!maybeModifiers.every(function(m) { + return modifiers[m.toLowerCase()] = MODIFIERS_REG.test(m); + })) { + throw new TypeError(OPTION_KEY_INVALID); + } + + return {key: maybeKey, modifiers: modifiers}; +} + +function normalizeLocal(accelerator) { + var modifiers = accelerator.modifiers; + return [modifiers.alt, modifiers.command, modifiers.ctrl, modifiers.shift, accelerator.key].join('-'); +} + +function getRegistryLocal(accelerator) { + var localKey = normalizeLocal(accelerator); + return handlers[localKey]; +} + +function registerLocal(shortcut) { + var localKey = normalizeLocal(shortcut._accelerator); + handlers[localKey] = shortcut; +} + +function unregisterLocal(shortcut) { + var localKey = normalizeLocal(shortcut._accelerator); + delete handlers[localKey]; +} + +function Shortcut(option) { + if (!(this instanceof Shortcut)) { + return new Shortcut(option); + } + + EventEmitter.apply(this, arguments); + + if (typeof option != 'object') + throw new TypeError(OPTION_INVALID); + + if (!option.key) + throw new TypeError(OPTION_KEY_REQUIRED); + + if (option.hasOwnProperty('active')) { + if (typeof option.active != 'function') + throw new TypeError(OPTION_ACTIVE_INVALID); + } + + if (option.hasOwnProperty('failed')) { + if (typeof option.failed != 'function') + throw new TypeError(OPTION_FAILED_INVALID); + } + + var self = this; + + this.on('active', function() { + if (!self.active) return; + if (typeof self.active != 'function') + throw new TypeError(OPTION_ACTIVE_INVALID); + self.active.apply(self, arguments); + }); + + this.on('failed', function() { + if (!self.failed) return; + if (typeof self.failed != 'function') + throw new TypeError(OPTION_FAILED_INVALID); + self.failed.apply(self, arguments); + }); + + this.key = option.key; + this.active = option.active; + this.failed = option.failed; + + this._accelerator = keyToAccelerator(option.key); +} + +nw.require('util').inherits(Shortcut, EventEmitter); + +Shortcut.registerGlobalHotkey = function(shortcut) { + if (!(shortcut instanceof Shortcut)) { + throw new TypeError(ARUGMENT_NOT_SHORTCUT); + } + + if(nwShortcutBinding.registerAccelerator(shortcut._accelerator)) { + registerLocal(shortcut); + } else { + shortcut.emit('failed', new Error(UNABLE_REGISTER_HOTKEY)); + } +}; + +Shortcut.unregisterGlobalHotkey = function(shortcut) { + if (!(shortcut instanceof Shortcut)) { + throw new TypeError(ARUGMENT_NOT_SHORTCUT); + } + + var handler = getRegistryLocal(shortcut._accelerator); + if(handler && nwShortcutBinding.unregisterAccelerator(shortcut._accelerator)) { + unregisterLocal(shortcut); + } else { + shortcut.emit('failed', new Error(UNABLE_UNREGISTER_HOTKEY)); + } +}; + +nwShortcutBinding.onKeyPressed.addListener(function(accelerator) { + var handler = getRegistryLocal(accelerator); + if (handler) { + handler.emit('active'); + } +}); + +exports.binding = Shortcut; \ No newline at end of file From e004c2a7ed8c55020a0650f061e492ae12d6ead2 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Mon, 30 Nov 2015 19:50:14 +0800 Subject: [PATCH 087/404] Fixed nw.Shortcut separator --- src/resources/api_nw_shortcut.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/resources/api_nw_shortcut.js b/src/resources/api_nw_shortcut.js index 4c2b6907a2..c1b884bd67 100644 --- a/src/resources/api_nw_shortcut.js +++ b/src/resources/api_nw_shortcut.js @@ -14,7 +14,7 @@ var UNABLE_UNREGISTER_HOTKEY = "Unable to unregister the hotkey"; // Build alias maps of all acceptable key code for nw.Shortcut API // The list is polled from http://www.w3.org/TR/DOM-Level-3-Events-code/. And // it also contains easy to type aliases. For example, you can use either -// "ctrl-`" or "ctrl-backquote" to register a shortcut. +// "ctrl+`" or "ctrl+backquote" to register a shortcut. // It is also backward compatible with NW12. var ALIAS_MAP = (function() { var map = { @@ -24,6 +24,7 @@ var ALIAS_MAP = (function() { ']' : 'BracketRight', ',' : 'Comma', '=' : 'Equal', + '-' : 'Minus', '.' : 'Period', '\'': 'Quote', ';' : 'Semicolon', @@ -100,7 +101,7 @@ var handlers = {}; function keyToAccelerator(key) { key = key.toString(); - var parts = key.split('-'); + var parts = key.split('+'); var maybeKey = parts.pop(); var maybeModifiers = parts; From d1fa90707e763a2f5968b690f003242b86ac11b1 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 1 Dec 2015 10:59:51 +0800 Subject: [PATCH 088/404] port nw.Window events --- src/api/nw_window.idl | 1 + src/resources/api_nw_window.js | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index ac9ef8694a..db690ee0bd 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -82,5 +82,6 @@ namespace nw.Window { [nocompile] static void LoadingStateChanged(); [nocompile] static void onDocumentStart(); [nocompile] static void onDocumentEnd(); + [nocompile] static void onZoom(); }; }; diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 00009a888a..114b7ad34d 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -40,9 +40,37 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.LoadingStateChanged = new Event(); NWWindow.prototype.onDocumentStart = new Event(); NWWindow.prototype.onDocumentEnd = new Event(); + NWWindow.prototype.onZoom = new Event(); NWWindow.prototype.on = function (event, callback) { switch (event) { + case 'focus': + this.appWindow.contentWindow.onfocus = callback; + break; + case 'blur': + this.appWindow.contentWindow.onblur = callback; + break; + case 'minimize': + this.appWindow.onMinimized.addListener(callback); + break; + case 'maximize': + this.appWindow.onMaximized.addListener(callback); + break; + case 'restore': + this.appWindow.onRestored.addListener(callback); + break; + case 'resize': + this.appWindow.onResized.addListener(callback); + break; + case 'move': + this.appWindow.onMoved.addListener(callback); + break; + case 'enter-fullscreen': + this.appWindow.onFullscreened.addListener(callback); + break; + case 'zoom': + this.onZoom.addListener(callback); + break; case 'closed': this.appWindow.onClosed.addListener(callback); break; @@ -315,8 +343,15 @@ function onDocumentStartEnd(start, frame) { dispatchEventIfExists(currentNWWindow, "onDocumentEnd", [frame]); } +function updateAppWindowZoom(old_level, new_level) { + if (!currentNWWindow) + return; + dispatchEventIfExists(currentNWWindow, "onZoom", [new_level]); +} + exports.binding = nw_binding.generate(); exports.onNewWinPolicy = onNewWinPolicy; exports.onNavigation = onNavigation; exports.LoadingStateChanged = onLoadingStateChanged; exports.onDocumentStartEnd = onDocumentStartEnd; +exports.updateAppWindowZoom = updateAppWindowZoom; From 12ea9ba9fc775d260bf1cf28b0ddc197180f718e Mon Sep 17 00:00:00 2001 From: Chase Willden Date: Mon, 30 Nov 2015 22:20:32 -0800 Subject: [PATCH 089/404] port nw.Window.isFullScreen --- src/resources/api_nw_window.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 114b7ad34d..d5273ca067 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -173,6 +173,9 @@ nw_binding.registerCustomHook(function(bindingsAPI) { else this.appWindow.fullscreen(); }; + NWWindow.prototype.isFullscreen = function () { + return this.appWindow.isFullscreen(); + }; NWWindow.prototype.setMaximumSize = function (width, height) { this.appWindow.outerBounds.maxWidth = width; this.appWindow.outerBounds.maxHeight = height; From 57aeeb9557f0b91bf230ad7f85663477aebdb482 Mon Sep 17 00:00:00 2001 From: Chase Willden Date: Mon, 30 Nov 2015 22:30:57 -0800 Subject: [PATCH 090/404] ported nw.Window.resizeTo --- src/resources/api_nw_window.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index d5273ca067..4f14823666 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -184,6 +184,10 @@ nw_binding.registerCustomHook(function(bindingsAPI) { this.appWindow.outerBounds.minWidth = width; this.appWindow.outerBounds.minHeight = height; }; + NWWindow.prototype.resizeTo = function (width, height) { + this.appWindow.outerBounds.width = width; + this.appWindow.outerBounds.height = height; + }; NWWindow.prototype.setResizable = function (resizable) { }; NWWindow.prototype.cookies = chrome.cookies; From ba05a9a0d746b628b6bd45fd5c77113221fde681 Mon Sep 17 00:00:00 2001 From: Chase Willden Date: Mon, 30 Nov 2015 22:46:18 -0800 Subject: [PATCH 091/404] port nw.Window.moveTo, moveBy, resizeBy --- src/resources/api_nw_window.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 4f14823666..e9692bef9f 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -188,6 +188,18 @@ nw_binding.registerCustomHook(function(bindingsAPI) { this.appWindow.outerBounds.width = width; this.appWindow.outerBounds.height = height; }; + NWWindow.prototype.resizeBy = function (width, height) { + this.appWindow.outerBounds.width += width; + this.appWindow.outerBounds.height += height; + }; + NWWindow.prototype.moveTo = function (x, y) { + this.appWindow.outerBounds.top = x; + this.appWindow.outerBounds.left = y; + }; + NWWindow.prototype.moveBy = function (x, y) { + this.appWindow.outerBounds.top += x; + this.appWindow.outerBounds.left += y; + }; NWWindow.prototype.setResizable = function (resizable) { }; NWWindow.prototype.cookies = chrome.cookies; From af3c82f0bc8fff775e8668bf09debd5c0d4a3e87 Mon Sep 17 00:00:00 2001 From: Chase Willden Date: Mon, 30 Nov 2015 22:56:46 -0800 Subject: [PATCH 092/404] ported nw.Window.setAlwaysOnTop --- src/resources/api_nw_window.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index e9692bef9f..5b122f5c8f 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -173,6 +173,9 @@ nw_binding.registerCustomHook(function(bindingsAPI) { else this.appWindow.fullscreen(); }; + NWWindow.prototype.setAlwaysOnTop = function (top) { + this.appWindow.setAlwaysOnTop(top); + }; NWWindow.prototype.isFullscreen = function () { return this.appWindow.isFullscreen(); }; From 53f2e5044e31c5d232d4b0d234c4210e365d9382 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Wed, 2 Dec 2015 14:02:59 +0800 Subject: [PATCH 093/404] Added test cases for nw.Shortcut and fixed a typo --- src/resources/api_nw_shortcut.js | 2 +- test/remoting/shortcut-creation/index.html | 32 +++++++++++ test/remoting/shortcut-creation/package.json | 4 ++ test/remoting/shortcut-creation/test.py | 52 +++++++++++++++++ test/remoting/shortcut-event/index.html | 52 +++++++++++++++++ test/remoting/shortcut-event/package.json | 4 ++ test/remoting/shortcut-event/test.py | 54 ++++++++++++++++++ test/remoting/shortcut-failure/index.html | 47 ++++++++++++++++ test/remoting/shortcut-failure/package.json | 4 ++ test/remoting/shortcut-failure/test.py | 59 ++++++++++++++++++++ test/remoting/shortcut-normal/index.html | 52 +++++++++++++++++ test/remoting/shortcut-normal/package.json | 4 ++ test/remoting/shortcut-normal/test.py | 54 ++++++++++++++++++ 13 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 test/remoting/shortcut-creation/index.html create mode 100644 test/remoting/shortcut-creation/package.json create mode 100644 test/remoting/shortcut-creation/test.py create mode 100644 test/remoting/shortcut-event/index.html create mode 100644 test/remoting/shortcut-event/package.json create mode 100644 test/remoting/shortcut-event/test.py create mode 100644 test/remoting/shortcut-failure/index.html create mode 100644 test/remoting/shortcut-failure/package.json create mode 100644 test/remoting/shortcut-failure/test.py create mode 100644 test/remoting/shortcut-normal/index.html create mode 100644 test/remoting/shortcut-normal/package.json create mode 100644 test/remoting/shortcut-normal/test.py diff --git a/src/resources/api_nw_shortcut.js b/src/resources/api_nw_shortcut.js index c1b884bd67..b6f65b94f9 100644 --- a/src/resources/api_nw_shortcut.js +++ b/src/resources/api_nw_shortcut.js @@ -64,7 +64,7 @@ var ALIAS_MAP = (function() { // (Arrow)Down, (Arrow)Left, (Arrow)Right, (Arrow)Up 'Down|Left|Right|Up'.split('|').forEach(function(a) { - var arrow = 'Arrow' + arrow; + var arrow = 'Arrow' + a; map[a.toLowerCase()] = arrow; map[arrow.toLowerCase()] = arrow; }); diff --git a/test/remoting/shortcut-creation/index.html b/test/remoting/shortcut-creation/index.html new file mode 100644 index 0000000000..c00cbfcb15 --- /dev/null +++ b/test/remoting/shortcut-creation/index.html @@ -0,0 +1,32 @@ + + + + + + Shortcut + + + + + \ No newline at end of file diff --git a/test/remoting/shortcut-creation/package.json b/test/remoting/shortcut-creation/package.json new file mode 100644 index 0000000000..f3e2dc7c3c --- /dev/null +++ b/test/remoting/shortcut-creation/package.json @@ -0,0 +1,4 @@ +{ + "name": "shortcut-creation", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/shortcut-creation/test.py b/test/remoting/shortcut-creation/test.py new file mode 100644 index 0000000000..5587101639 --- /dev/null +++ b/test/remoting/shortcut-creation/test.py @@ -0,0 +1,52 @@ +import time +import os +import pyautogui + +from selenium import webdriver +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +def assert_dom(id, expect): + elem = driver.find_element_by_id(id) + print elem.get_attribute('innerHTML') + assert(expect in elem.get_attribute('innerHTML')) + +def test_shortcut_create(keys, expect="success"): + key = '+'.join(keys) + id = 'create-' + '-'.join(keys) + reg_script = 'createShortcut("%s", "%s")' % (id, key) + print reg_script + driver.execute_script(reg_script) + assert_dom(id, expect) + + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + driver.implicitly_wait(10) + + test_shortcut_create(['a']) + test_shortcut_create(['keyb']) + test_shortcut_create(['Numpad1']) + test_shortcut_create(['Escape']) + test_shortcut_create(['`']) + test_shortcut_create(['mediatracknext']) + test_shortcut_create(['MediaNextTrack']) + test_shortcut_create(['f1']) + test_shortcut_create(['f24']) + test_shortcut_create(['up']) + test_shortcut_create(['arrowleft']) + test_shortcut_create(['comma']) + test_shortcut_create(['.']) + + test_shortcut_create([], 'failure') + test_shortcut_create(['ctrl'], 'failure') + test_shortcut_create(['ctrl+alt'], 'failure') + test_shortcut_create(['command+alt'], 'failure') + +finally: + driver.quit() diff --git a/test/remoting/shortcut-event/index.html b/test/remoting/shortcut-event/index.html new file mode 100644 index 0000000000..973ed32541 --- /dev/null +++ b/test/remoting/shortcut-event/index.html @@ -0,0 +1,52 @@ + + + + + + Shortcut + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/remoting/shortcut-event/package.json b/test/remoting/shortcut-event/package.json new file mode 100644 index 0000000000..8b31760631 --- /dev/null +++ b/test/remoting/shortcut-event/package.json @@ -0,0 +1,4 @@ +{ + "name": "shortcut-event", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/shortcut-event/test.py b/test/remoting/shortcut-event/test.py new file mode 100644 index 0000000000..77afb99824 --- /dev/null +++ b/test/remoting/shortcut-event/test.py @@ -0,0 +1,54 @@ +import time +import os +import pyautogui + +from selenium import webdriver +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +def assert_dom(id, expect): + elem = driver.find_element_by_id(id) + print elem.get_attribute('innerHTML') + assert(expect in elem.get_attribute('innerHTML')) + +def test_reg(keys, pykeys=None, expect="success"): + key = '+'.join(keys) + id = 'reg-' + '-'.join(keys) + reg_script = 'reg("%s", "%s")' % (id, key) + print reg_script + driver.execute_script(reg_script) + if pykeys is not None: + pyautogui.hotkey(*pykeys) + if expect is not None: + assert_dom(id, expect) + +def test_unreg(keys, expect="success"): + key = '+'.join(keys) + id = 'unreg-' + '-'.join(keys) + unreg_script = 'unreg("%s", "%s")' % (id, key) + print unreg_script + driver.execute_script(unreg_script) + assert_dom(id, expect) + +def test_reg_unreg(keys, pykeys): + test_reg(keys, pykeys) + test_unreg(keys) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + driver.implicitly_wait(10) + test_reg_unreg(['a'], ['a']) + test_reg_unreg(['keyb'], ['b']) + test_reg_unreg(['KeyC'], ['c']) + test_reg_unreg(['ctrl', 'b'], ['ctrl', 'b']) + test_reg_unreg(['ctrl','shift','b'], ['ctrl','shift','b']) + test_reg_unreg(['ctrl','shift','alt','b'], ['ctrl','shift','alt','b']) + test_reg_unreg(['ctrl','shift','alt','`'], ['ctrl','shift','alt','`']) + test_reg_unreg(['ctrl','shift','alt','escape'], ['ctrl','shift','alt','escape']) +finally: + driver.quit() diff --git a/test/remoting/shortcut-failure/index.html b/test/remoting/shortcut-failure/index.html new file mode 100644 index 0000000000..d558ff8767 --- /dev/null +++ b/test/remoting/shortcut-failure/index.html @@ -0,0 +1,47 @@ + + + + + + Shortcut + + + + + + + \ No newline at end of file diff --git a/test/remoting/shortcut-failure/package.json b/test/remoting/shortcut-failure/package.json new file mode 100644 index 0000000000..137adea1a1 --- /dev/null +++ b/test/remoting/shortcut-failure/package.json @@ -0,0 +1,4 @@ +{ + "name": "shortcut-failure", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/shortcut-failure/test.py b/test/remoting/shortcut-failure/test.py new file mode 100644 index 0000000000..3c2e8a28f8 --- /dev/null +++ b/test/remoting/shortcut-failure/test.py @@ -0,0 +1,59 @@ +import time +import os +import pyautogui + +from selenium import webdriver +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +def assert_dom(id, expect): + elem = driver.find_element_by_id(id) + print elem.get_attribute('innerHTML') + assert(expect in elem.get_attribute('innerHTML')) + +def test_reg(keys, pykeys=None, expect="success"): + key = '+'.join(keys) + id = 'reg-' + '-'.join(keys) + reg_script = 'reg("%s", "%s")' % (id, key) + print reg_script + driver.execute_script(reg_script) + if pykeys is not None: + pyautogui.hotkey(*pykeys) + if expect is not None: + assert_dom(id, expect) + +def test_unreg(keys, expect="success"): + key = '+'.join(keys) + id = 'unreg-' + '-'.join(keys) + unreg_script = 'unreg("%s", "%s")' % (id, key) + print unreg_script + driver.execute_script(unreg_script) + assert_dom(id, expect) + +def test_reg_twice(keys1, keys2): + test_reg(keys1, expect=None) + test_reg(keys2, expect="failure") + test_unreg(keys1) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + driver.implicitly_wait(10) + + test_reg_twice(['a'], ['a']) + test_reg_twice(['a'], ['keya']) + test_reg_twice(['keya'], ['a']) + test_reg_twice(['1'], ['digit1']) + test_reg_twice(['comma'], [',']) + test_reg_twice(['up'], ['arrowup']) + test_reg_twice(['mediatracknext'], ['medianexttrack']) + test_reg_twice(['mediaprevtrack'], ['mediatrackprevious']) + test_reg_twice(['ctrl', 'shift', 'b'], ['shift', 'ctrl', 'b']) + + test_unreg(['ctrl','shift','p'], expect="failure") +finally: + driver.quit() diff --git a/test/remoting/shortcut-normal/index.html b/test/remoting/shortcut-normal/index.html new file mode 100644 index 0000000000..6e7cdd7a53 --- /dev/null +++ b/test/remoting/shortcut-normal/index.html @@ -0,0 +1,52 @@ + + + + + + Shortcut + + + + + + + + + + + + \ No newline at end of file diff --git a/test/remoting/shortcut-normal/package.json b/test/remoting/shortcut-normal/package.json new file mode 100644 index 0000000000..b5d94571d8 --- /dev/null +++ b/test/remoting/shortcut-normal/package.json @@ -0,0 +1,4 @@ +{ + "name": "shortcut-normal", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/shortcut-normal/test.py b/test/remoting/shortcut-normal/test.py new file mode 100644 index 0000000000..77afb99824 --- /dev/null +++ b/test/remoting/shortcut-normal/test.py @@ -0,0 +1,54 @@ +import time +import os +import pyautogui + +from selenium import webdriver +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +def assert_dom(id, expect): + elem = driver.find_element_by_id(id) + print elem.get_attribute('innerHTML') + assert(expect in elem.get_attribute('innerHTML')) + +def test_reg(keys, pykeys=None, expect="success"): + key = '+'.join(keys) + id = 'reg-' + '-'.join(keys) + reg_script = 'reg("%s", "%s")' % (id, key) + print reg_script + driver.execute_script(reg_script) + if pykeys is not None: + pyautogui.hotkey(*pykeys) + if expect is not None: + assert_dom(id, expect) + +def test_unreg(keys, expect="success"): + key = '+'.join(keys) + id = 'unreg-' + '-'.join(keys) + unreg_script = 'unreg("%s", "%s")' % (id, key) + print unreg_script + driver.execute_script(unreg_script) + assert_dom(id, expect) + +def test_reg_unreg(keys, pykeys): + test_reg(keys, pykeys) + test_unreg(keys) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + driver.implicitly_wait(10) + test_reg_unreg(['a'], ['a']) + test_reg_unreg(['keyb'], ['b']) + test_reg_unreg(['KeyC'], ['c']) + test_reg_unreg(['ctrl', 'b'], ['ctrl', 'b']) + test_reg_unreg(['ctrl','shift','b'], ['ctrl','shift','b']) + test_reg_unreg(['ctrl','shift','alt','b'], ['ctrl','shift','alt','b']) + test_reg_unreg(['ctrl','shift','alt','`'], ['ctrl','shift','alt','`']) + test_reg_unreg(['ctrl','shift','alt','escape'], ['ctrl','shift','alt','escape']) +finally: + driver.quit() From cd2edbd2cb5419124d793d5d315bbb2406883cf2 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 2 Dec 2015 10:19:04 +0800 Subject: [PATCH 094/404] port Window.setResizable --- src/resources/api_nw_window.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 114b7ad34d..91f7689996 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -182,6 +182,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { this.appWindow.outerBounds.minHeight = height; }; NWWindow.prototype.setResizable = function (resizable) { + this.appWindow.setResizable(resizable); }; NWWindow.prototype.cookies = chrome.cookies; From bc83762fe18eebf3b84fe678913c1704e5ea7340 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 2 Dec 2015 15:23:28 +0800 Subject: [PATCH 095/404] port nw.Window.close event --- src/api/nw_window.idl | 1 + src/resources/api_nw_window.js | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index db690ee0bd..19e5d0259b 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -83,5 +83,6 @@ namespace nw.Window { [nocompile] static void onDocumentStart(); [nocompile] static void onDocumentEnd(); [nocompile] static void onZoom(); + static void onClose(); }; }; diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 91f7689996..910c983ada 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -41,6 +41,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.onDocumentStart = new Event(); NWWindow.prototype.onDocumentEnd = new Event(); NWWindow.prototype.onZoom = new Event(); + NWWindow.prototype.onClose = new Event("nw.Window.onClose"); NWWindow.prototype.on = function (event, callback) { switch (event) { @@ -71,6 +72,9 @@ nw_binding.registerCustomHook(function(bindingsAPI) { case 'zoom': this.onZoom.addListener(callback); break; + case 'close': + this.onClose.addListener(callback); + break; case 'closed': this.appWindow.onClosed.addListener(callback); break; @@ -350,9 +354,16 @@ function updateAppWindowZoom(old_level, new_level) { dispatchEventIfExists(currentNWWindow, "onZoom", [new_level]); } +function onClose() { + if (!currentNWWindow) + return; + dispatchEventIfExists(currentNWWindow, "onClose", []); +} + exports.binding = nw_binding.generate(); exports.onNewWinPolicy = onNewWinPolicy; exports.onNavigation = onNavigation; exports.LoadingStateChanged = onLoadingStateChanged; exports.onDocumentStartEnd = onDocumentStartEnd; +exports.onClose = onClose; exports.updateAppWindowZoom = updateAppWindowZoom; From 38e18fbac6ab798a4285a8b79ec14c8fea763efa Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Wed, 2 Dec 2015 20:32:43 +0800 Subject: [PATCH 096/404] Fixed nw.Clipboard API --- src/api/nw_clipboard.idl | 1 + src/api/nw_clipboard_api.cc | 14 +++++++++ src/api/nw_clipboard_api.h | 13 +++++++++ src/resources/api_nw_clipboard.js | 48 +++++++++++++++++++------------ 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/api/nw_clipboard.idl b/src/api/nw_clipboard.idl index bbddfe5da4..03964723dc 100644 --- a/src/api/nw_clipboard.idl +++ b/src/api/nw_clipboard.idl @@ -13,5 +13,6 @@ namespace nw.Clipboard { [nocompile] static NWClipboard get(); static DOMString getSync(DOMString type); static void setSync(DOMString content, DOMString type); + static void clearSync(); }; }; diff --git a/src/api/nw_clipboard_api.cc b/src/api/nw_clipboard_api.cc index 54417e0bed..4a235a652a 100644 --- a/src/api/nw_clipboard_api.cc +++ b/src/api/nw_clipboard_api.cc @@ -48,4 +48,18 @@ bool NwClipboardSetSyncFunction::RunNWSync(base::ListValue* response, std::strin return true; } +NwClipboardClearSyncFunction::NwClipboardClearSyncFunction() { + +} + +NwClipboardClearSyncFunction::~NwClipboardClearSyncFunction() { + +} + +bool NwClipboardClearSyncFunction::RunNWSync(base::ListValue* response, std::string* error) { + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + clipboard->Clear(ui::CLIPBOARD_TYPE_COPY_PASTE); + return true; +} + } // namespace extensions diff --git a/src/api/nw_clipboard_api.h b/src/api/nw_clipboard_api.h index a8b4a71d5d..9e3f8753a0 100644 --- a/src/api/nw_clipboard_api.h +++ b/src/api/nw_clipboard_api.h @@ -34,5 +34,18 @@ class NwClipboardSetSyncFunction : public NWSyncExtensionFunction { DISALLOW_COPY_AND_ASSIGN(NwClipboardSetSyncFunction); }; +class NwClipboardClearSyncFunction : public NWSyncExtensionFunction { + public: + NwClipboardClearSyncFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwClipboardClearSyncFunction() override; + + DECLARE_EXTENSION_FUNCTION("nw.Clipboard.clearSync", UNKNOWN) + private: + DISALLOW_COPY_AND_ASSIGN(NwClipboardClearSyncFunction); +}; + } // namespace extensions #endif diff --git a/src/resources/api_nw_clipboard.js b/src/resources/api_nw_clipboard.js index 2403d1b4d0..4c91d3c106 100644 --- a/src/resources/api_nw_clipboard.js +++ b/src/resources/api_nw_clipboard.js @@ -3,28 +3,40 @@ var forEach = require('utils').forEach; var nw_binding = require('binding').Binding.create('nw.Clipboard'); var sendRequest = require('sendRequest'); -var clipboard = null; - nw_binding.registerCustomHook(function(bindingsAPI) { var apiFunctions = bindingsAPI.apiFunctions; - apiFunctions.setHandleRequest('get', function() { - if (clipboard) - return clipboard; - var NWClipboard = function() {}; - NWClipboard.prototype.get = function (type) { - return nw.Clipboard.getSync(type); - }; - NWClipboard.prototype.set = function (content, type) { - return nw.Clipboard.setSync(content, type); - }; - clipboard = new NWClipboard; - return clipboard; - }); - apiFunctions.setHandleRequest('getSync', function(type) { - return sendRequest.sendRequestSync('nw.Clipboard.getSync', [type], this.definition.parameters, {})[0]; + ['getSync', 'setSync', 'clearSync'].forEach(function(nwSyncAPIName) { + apiFunctions.setHandleRequest(nwSyncAPIName, function() { + return sendRequest.sendRequestSync(this.name, arguments, this.definition.parameters, {})[0]; + }); }); }); -exports.binding = nw_binding.generate(); +var nwClipboardBinding = nw_binding.generate(); + +function NWClipboard() { + +} + +var clipboard = null; + +NWClipboard.get = function() { + if (clipboard) + return clipboard; + clipboard = new NWClipboard(); + return clipboard; +}; + +NWClipboard.prototype.get = function (type) { + return nwClipboardBinding.getSync(type || 'text'); +}; +NWClipboard.prototype.set = function (content, type) { + return nwClipboardBinding.setSync(content, type || 'text'); +}; +NWClipboard.prototype.clear = function () { + return nwClipboardBinding.clearSync(); +}; + +exports.binding = NWClipboard; From 9597a645a0a2218e8530b699175e6d066281d7da Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 3 Dec 2015 10:36:24 +0800 Subject: [PATCH 097/404] rebase fix for chromium 47 --- nw.gypi | 2 -- src/api/nw_app_api.cc | 3 +-- src/api/nw_window_api.cc | 2 +- src/api/object_manager_factory.cc | 2 +- src/api/object_manager_factory.h | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/nw.gypi b/nw.gypi index 38e38929fb..910665ba4d 100644 --- a/nw.gypi +++ b/nw.gypi @@ -524,8 +524,6 @@ ['OS=="linux"', { 'dependencies': [ '<(DEPTH)/chrome/browser/ui/libgtk2ui/libgtk2ui.gyp:gtk2ui', - '<(DEPTH)/build/linux/system.gyp:gio', - '<(DEPTH)/build/linux/system.gyp:gtk', ], 'sources': [ 'src/browser/shell_download_manager_delegate_gtk.cc', diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index 9768a55fdc..0157a7f088 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -24,8 +24,7 @@ void SetProxyConfigCallback( const net::ProxyConfig& proxy_config) { net::ProxyService* proxy_service = url_request_context_getter->GetURLRequestContext()->proxy_service(); - proxy_service->ResetConfigService( - new net::ProxyConfigServiceFixed(proxy_config)); + proxy_service->ResetConfigService(make_scoped_ptr(new net::ProxyConfigServiceFixed(proxy_config))); done->Signal(); } } // namespace diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 8534c62186..38d71d7840 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -173,7 +173,7 @@ bool NwCurrentWindowInternalCapturePageInternalFunction::RunAsync() { const float scale = screen->GetDisplayNearestWindow(native_view).device_scale_factor(); if (scale > 1.0f) - bitmap_size = gfx::ToCeiledSize(gfx::ScaleSize(view_size, scale)); + bitmap_size = gfx::ScaleToCeiledSize(view_size, scale); host->CopyFromBackingStore( gfx::Rect(view_size), diff --git a/src/api/object_manager_factory.cc b/src/api/object_manager_factory.cc index f80a3cd5b5..b74d1aeb44 100644 --- a/src/api/object_manager_factory.cc +++ b/src/api/object_manager_factory.cc @@ -21,7 +21,7 @@ ObjectManager* ObjectManagerFactory::GetForBrowserContext( // static ObjectManagerFactory* ObjectManagerFactory::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } // static diff --git a/src/api/object_manager_factory.h b/src/api/object_manager_factory.h index cfbaaa0fdf..62a9ce98e2 100644 --- a/src/api/object_manager_factory.h +++ b/src/api/object_manager_factory.h @@ -26,7 +26,7 @@ class ObjectManagerFactory : public BrowserContextKeyedServiceFactory { content::BrowserContext* context); private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; ObjectManagerFactory(); ~ObjectManagerFactory() override; From 4a16dc57355fd98e91556eccc049ebdb585deb20 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 3 Dec 2015 10:39:40 +0800 Subject: [PATCH 098/404] Added test cases for nw.Clipboard --- test/remoting/clipboard/index.html | 55 +++++++++++++++++++++ test/remoting/clipboard/package.json | 4 ++ test/remoting/clipboard/test.py | 74 ++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 test/remoting/clipboard/index.html create mode 100644 test/remoting/clipboard/package.json create mode 100644 test/remoting/clipboard/test.py diff --git a/test/remoting/clipboard/index.html b/test/remoting/clipboard/index.html new file mode 100644 index 0000000000..d181124236 --- /dev/null +++ b/test/remoting/clipboard/index.html @@ -0,0 +1,55 @@ + + + + + + Screen + + + + + + + + + \ No newline at end of file diff --git a/test/remoting/clipboard/package.json b/test/remoting/clipboard/package.json new file mode 100644 index 0000000000..613effaf82 --- /dev/null +++ b/test/remoting/clipboard/package.json @@ -0,0 +1,4 @@ +{ + "name": "clipboard", + "main": "index.html" +} \ No newline at end of file diff --git a/test/remoting/clipboard/test.py b/test/remoting/clipboard/test.py new file mode 100644 index 0000000000..4ab5c0c5c7 --- /dev/null +++ b/test/remoting/clipboard/test.py @@ -0,0 +1,74 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +def assert_dom(id, expect): + elem = driver.find_element_by_id(id) + print elem.get_attribute('innerHTML') + assert(expect in elem.get_attribute('innerHTML')) + +def assert_dom_not(id, not_expect): + elem = driver.find_element_by_id(id) + print elem.get_attribute('innerHTML') + assert(not_expect not in elem.get_attribute('innerHTML')) + +def test_get_text(): + id = 'test-get-text' + script = 'getText("%s")' % id + print script + driver.execute_script(script) + assert_dom(id, 'success') + +def test_set_text(): + id = 'test-set-text-1' + value = 'abc' + script = 'setText("%s", "%s")' % (id, value) + print script + driver.execute_script(script) + assert_dom(id, 'success') + id = 'test-set-text-2' + script = 'getText("%s")' % id + print script + driver.execute_script(script) + assert_dom(id, 'success') + assert_dom(id, value) + +def test_clear(): + id = 'test-clear-1' + value = 'abc' + script = 'setText("%s", "%s")' % (id, value) + print script + driver.execute_script(script) + assert_dom(id, 'success') + id = 'test-clear-2' + script = 'getText("%s")' % id + print script + driver.execute_script(script) + assert_dom(id, 'success') + assert_dom(id, value) + id = 'test-clear-3' + script = 'clearClipboard("%s")' % id + print script + driver.execute_script(script) + assert_dom(id, 'success') + id = 'test-clear-4' + value = 'abc' + script = 'getText("%s")' % id + print script + driver.execute_script(script) + assert_dom_not(id, value) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +try: + print driver.current_url + time.sleep(1) + + test_get_text() + test_set_text() + test_clear() +finally: + driver.quit() From 9c36f78b40c93a33ecdcdbbdb86e696a373de984 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 3 Dec 2015 14:45:12 +0800 Subject: [PATCH 099/404] add 'nacl' dependency for Nacl build --- nw.gypi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nw.gypi b/nw.gypi index 910665ba4d..8a5618bc58 100644 --- a/nw.gypi +++ b/nw.gypi @@ -982,6 +982,11 @@ '<(DEPTH)/v8/tools/gyp/v8.gyp:nwjc', ], 'conditions': [ + ['disable_nacl==0', { + 'dependencies': [ + '<(DEPTH)/components/nacl.gyp:nacl', + ] + }], [ 'OS=="mac"', { 'copies': [ { From dd01710b47f5fb4495b821bf24956854ed17af33 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 3 Dec 2015 15:54:08 +0800 Subject: [PATCH 100/404] [test] avoid race condition in chromedriver --- test/remoting/capture_page/test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/remoting/capture_page/test.py b/test/remoting/capture_page/test.py index 7088c58d6d..f608fe06fe 100644 --- a/test/remoting/capture_page/test.py +++ b/test/remoting/capture_page/test.py @@ -7,6 +7,7 @@ chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +time.sleep(1) try: print driver.current_url time.sleep(5) From 29969cc923d42b9e371232ffcbc57fe7f16ccf30 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 3 Dec 2015 15:57:01 +0800 Subject: [PATCH 101/404] port devtools jail; add callback to replace devtools-open event --- src/api/nw_current_window_internal.idl | 3 ++- src/api/nw_window.idl | 2 +- src/api/nw_window_api.cc | 23 ++++++++++++++--------- src/api/nw_window_api.h | 10 +++++----- src/nw_custom_bindings.cc | 22 ++++++++++++++++++++++ src/nw_custom_bindings.h | 1 + src/resources/api_nw_window.js | 4 ++++ 7 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index fd18fde630..bf74c4fccf 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -7,13 +7,14 @@ [implemented_in="content/nw/src/api/nw_window_api.h"] namespace nw.currentWindowInternal { callback CapturePageCallback = void (DOMString dataUrl); + callback ShowDevToolsCallback = void (); [noinline_doc] dictionary CapturePageOptions { [nodoc] DOMString? format; [nodoc] DOMString? datatype; [nodoc] long? quality; }; interface Functions { - static void showDevTools(); + static void showDevToolsInternal(optional ShowDevToolsCallback callback); static void setBadgeLabel(DOMString badge); static void requestAttention(long count); static void setProgressBar(double progress); diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 19e5d0259b..3df8999bea 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -33,7 +33,7 @@ namespace nw.Window { [noinline_doc] dictionary NWWindow { // show devtools for the current window - static void showDevTools(); + static void showDevTools(optional any frm, optional boolean headless, optional EventCallback callback); static void capturePage(optional CapturePageCallback callback, optional CapturePageOptions options); static void show(); static void hide(); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 38d71d7840..225f1a09dc 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -103,18 +103,23 @@ static HWND getHWND(AppWindow* window) { } #endif -NwCurrentWindowInternalShowDevToolsFunction::NwCurrentWindowInternalShowDevToolsFunction() { - -} - -NwCurrentWindowInternalShowDevToolsFunction::~NwCurrentWindowInternalShowDevToolsFunction() { +void NwCurrentWindowInternalShowDevToolsInternalFunction::OnOpened() { + SendResponse(true); } -bool NwCurrentWindowInternalShowDevToolsFunction::RunAsync() { +bool NwCurrentWindowInternalShowDevToolsInternalFunction::RunAsync() { content::RenderFrameHost* rfh = render_frame_host(); - DevToolsWindow::OpenDevToolsWindow( - content::WebContents::FromRenderFrameHost(rfh)); - SendResponse(true); + content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(rfh); + scoped_refptr agent( + content::DevToolsAgentHost::GetOrCreateFor(web_contents)); + DevToolsWindow::OpenDevToolsWindow(web_contents); + DevToolsWindow* devtools_window = + DevToolsWindow::FindDevToolsWindow(agent.get()); + if (devtools_window) + devtools_window->SetLoadCompletedCallback(base::Bind(&NwCurrentWindowInternalShowDevToolsInternalFunction::OnOpened, this)); + else + OnOpened(); + return true; } diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 21f0941f2d..3789d90637 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -15,18 +15,18 @@ class WebContents; namespace extensions { -class NwCurrentWindowInternalShowDevToolsFunction : public AsyncExtensionFunction { +class NwCurrentWindowInternalShowDevToolsInternalFunction : public AsyncExtensionFunction { public: - NwCurrentWindowInternalShowDevToolsFunction(); + NwCurrentWindowInternalShowDevToolsInternalFunction() {}; protected: - ~NwCurrentWindowInternalShowDevToolsFunction() override; + ~NwCurrentWindowInternalShowDevToolsInternalFunction() override {}; // ExtensionFunction: bool RunAsync() override; - DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.showDevTools", UNKNOWN) + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.showDevToolsInternal", UNKNOWN) private: - void Callback(); + void OnOpened(); }; class NwCurrentWindowInternalCapturePageInternalFunction : public AsyncExtensionFunction { diff --git a/src/nw_custom_bindings.cc b/src/nw_custom_bindings.cc index 95bb399913..b7cd67a9ec 100644 --- a/src/nw_custom_bindings.cc +++ b/src/nw_custom_bindings.cc @@ -101,6 +101,9 @@ NWCustomBindings::NWCustomBindings(ScriptContext* context) RouteFunction("getProxyForURL", base::Bind(&NWCustomBindings::GetProxyForURL, base::Unretained(this))); + RouteFunction("setDevToolsJail", + base::Bind(&NWCustomBindings::SetDevToolsJail, + base::Unretained(this))); } void NWCustomBindings::CrashRenderer( @@ -250,4 +253,23 @@ void NWCustomBindings::GetProxyForURL(const v8::FunctionCallbackInfo& return; } +void NWCustomBindings::SetDevToolsJail(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + if (!args.Length()) + return; + v8::Handle frm = v8::Handle::Cast(args[0]); + content::RenderFrame* render_frame = context()->GetRenderFrame(); + if (!render_frame) + return; + WebFrame* main_frame = render_frame->GetWebFrame(); + if (frm->IsNull() || frm->IsUndefined()) { + main_frame->setDevtoolsJail(NULL); + }else{ + blink::HTMLIFrameElement* iframe = blink::V8HTMLIFrameElement::toImpl(frm); + main_frame->setDevtoolsJail(blink::WebFrame::fromFrame(iframe->contentFrame())); + } + args.GetReturnValue().Set(v8::Undefined(isolate)); + return; +} + } // namespace extensions diff --git a/src/nw_custom_bindings.h b/src/nw_custom_bindings.h index 81f751d384..71abc74a15 100644 --- a/src/nw_custom_bindings.h +++ b/src/nw_custom_bindings.h @@ -23,6 +23,7 @@ class NWCustomBindings : public ObjectBackedNativeHandler { void AddOriginAccessWhitelistEntry(const v8::FunctionCallbackInfo& args); void RemoveOriginAccessWhitelistEntry(const v8::FunctionCallbackInfo& args); void GetProxyForURL(const v8::FunctionCallbackInfo& args); + void SetDevToolsJail(const v8::FunctionCallbackInfo& args); }; } // namespace extensions diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 910c983ada..cc6f095198 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -106,6 +106,10 @@ nw_binding.registerCustomHook(function(bindingsAPI) { break; } }; + NWWindow.prototype.showDevTools = function(frm, headless, callback) { + nwNatives.setDevToolsJail(frm); + currentNWWindowInternal.showDevToolsInternal(callback); + }; NWWindow.prototype.capturePage = function (callback, options) { var cb = callback; if (!options) From d7bf782dbc407aecd6293e7edc20ebf75027751a Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 3 Dec 2015 16:30:50 +0800 Subject: [PATCH 102/404] port closeDevtools --- src/api/nw_current_window_internal.idl | 1 + src/api/nw_window_api.cc | 13 +++++++++++++ src/api/nw_window_api.h | 12 ++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index bf74c4fccf..d6f03b8cb6 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -15,6 +15,7 @@ namespace nw.currentWindowInternal { }; interface Functions { static void showDevToolsInternal(optional ShowDevToolsCallback callback); + static void closeDevTools(); static void setBadgeLabel(DOMString badge); static void requestAttention(long count); static void setProgressBar(double progress); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 225f1a09dc..7e5747973a 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -123,6 +123,19 @@ bool NwCurrentWindowInternalShowDevToolsInternalFunction::RunAsync() { return true; } +bool NwCurrentWindowInternalCloseDevToolsFunction::RunAsync() { + content::RenderFrameHost* rfh = render_frame_host(); + content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(rfh); + scoped_refptr agent( + content::DevToolsAgentHost::GetOrCreateFor(web_contents)); + DevToolsWindow* devtools_window = + DevToolsWindow::FindDevToolsWindow(agent.get()); + if (devtools_window) { + devtools_window->Close(); + } + return true; +} + NwCurrentWindowInternalCapturePageInternalFunction::NwCurrentWindowInternalCapturePageInternalFunction() { } diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 3789d90637..5afbdc571d 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -29,6 +29,18 @@ class NwCurrentWindowInternalShowDevToolsInternalFunction : public AsyncExtensio void OnOpened(); }; +class NwCurrentWindowInternalCloseDevToolsFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalCloseDevToolsFunction() {}; + + protected: + ~NwCurrentWindowInternalCloseDevToolsFunction() override {}; + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.closeDevTools", UNKNOWN) +}; + class NwCurrentWindowInternalCapturePageInternalFunction : public AsyncExtensionFunction { public: NwCurrentWindowInternalCapturePageInternalFunction(); From 93e5715b773b22cb94230015f6ed0ff7481e1677 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 4 Dec 2015 13:36:01 +0800 Subject: [PATCH 103/404] update mac symbol tool for ffmpegsumo change in upstream --- tools/dump_mac_syms | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/dump_mac_syms b/tools/dump_mac_syms index bea24badcc..e008b82547 100755 --- a/tools/dump_mac_syms +++ b/tools/dump_mac_syms @@ -82,7 +82,6 @@ SRC_NAMES=( "${SRC_APP_NAME} Framework.framework" "${SRC_APP_NAME} Helper.app" "crashpad_handler" - "ffmpegsumo.so" ) # PDF.plugin is optional. Only include it if present. From af740ecfc819186d2cd99a8e6f511982b1489c6f Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 4 Dec 2015 14:46:41 +0800 Subject: [PATCH 104/404] support 'force' argument in Window.close() --- src/api/nw_current_window_internal.idl | 1 + src/api/nw_window.idl | 2 +- src/api/nw_window_api.cc | 19 +++++++++++++++++++ src/api/nw_window_api.h | 13 +++++++++++++ src/resources/api_nw_window.js | 3 --- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index d6f03b8cb6..e921d05c21 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -14,6 +14,7 @@ namespace nw.currentWindowInternal { [nodoc] long? quality; }; interface Functions { + static void close(optional boolean force); static void showDevToolsInternal(optional ShowDevToolsCallback callback); static void closeDevTools(); static void setBadgeLabel(DOMString badge); diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 3df8999bea..10a595a4b1 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -39,7 +39,7 @@ namespace nw.Window { static void hide(); static void focus(); static void blur(); - static void close(); + static void close(optional boolean force); static void minimize(); static void maximize(); static void unmaximize(); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 7e5747973a..b8321f97a1 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -23,6 +23,8 @@ #include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/screen.h" +#include "content/nw/src/api/nw_current_window_internal.h" + #if defined(OS_WIN) #include #include @@ -103,6 +105,23 @@ static HWND getHWND(AppWindow* window) { } #endif + +bool NwCurrentWindowInternalCloseFunction::RunAsync() { + scoped_ptr params( + nwapi::nw_current_window_internal::Close::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + bool force = params->force.get() ? *params->force : false; + AppWindow* window = getAppWindow(this); + if (force) + window->GetBaseWindow()->Close(); + else if (window->NWCanClose()) + window->GetBaseWindow()->Close(); + + SendResponse(true); + return true; +} + void NwCurrentWindowInternalShowDevToolsInternalFunction::OnOpened() { SendResponse(true); } diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 5afbdc571d..27dce2d4cb 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -15,6 +15,19 @@ class WebContents; namespace extensions { +class NwCurrentWindowInternalCloseFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalCloseFunction() {}; + + protected: + ~NwCurrentWindowInternalCloseFunction() override {}; + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.close", UNKNOWN) +}; + + class NwCurrentWindowInternalShowDevToolsInternalFunction : public AsyncExtensionFunction { public: NwCurrentWindowInternalShowDevToolsInternalFunction() {}; diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index fab27f97cc..d4aef5c580 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -150,9 +150,6 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.hide = function () { this.appWindow.hide(); }; - NWWindow.prototype.close = function () { - this.appWindow.close(); - }; NWWindow.prototype.focus = function () { this.appWindow.focus(); }; From 476aec446a0aa6ad7ecd4ce0b8966eee873c480c Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sat, 5 Dec 2015 20:31:37 +0800 Subject: [PATCH 105/404] nw.App.manifest and closeAllWindows --- src/api/nw_app.idl | 1 + src/api/nw_app_api.cc | 19 +++++++++++++++++++ src/api/nw_app_api.h | 12 ++++++++++++ src/nw_content.cc | 2 ++ src/resources/api_nw_app.js | 3 +++ 5 files changed, 37 insertions(+) diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index 7904738524..489cb39d75 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -11,6 +11,7 @@ namespace nw.App { [nocompile] static void crashRenderer(); [nocompile] static void on(DOMString event, EventCallback callback); static void quit(); + static void closeAllWindows(); static void clearCache(); static void setProxyConfig(DOMString config); [nocompile] static DOMString getProxyForURL(DOMString url); diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index 0157a7f088..e714b0f668 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -9,6 +9,9 @@ #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" +#include "extensions/browser/app_window/app_window.h" +#include "extensions/browser/app_window/app_window_registry.h" +#include "extensions/browser/app_window/native_app_window.h" #include "extensions/browser/extension_system.h" #include "extensions/common/error_utils.h" #include "net/proxy/proxy_config.h" @@ -48,6 +51,22 @@ bool NwAppQuitFunction::RunAsync() { return true; } +bool NwAppCloseAllWindowsFunction::RunAsync() { + AppWindowRegistry* registry = AppWindowRegistry::Get(browser_context()); + if (!registry) + return false; + + AppWindowRegistry::AppWindowList windows = + registry->GetAppWindowsForApp(extension()->id()); + + for (AppWindow* window : windows) { + if (window->NWCanClose()) + window->GetBaseWindow()->Close(); + } + SendResponse(true); + return true; +} + NwAppGetArgvSyncFunction::NwAppGetArgvSyncFunction() { } diff --git a/src/api/nw_app_api.h b/src/api/nw_app_api.h index 2c1b4df832..a604bc7731 100644 --- a/src/api/nw_app_api.h +++ b/src/api/nw_app_api.h @@ -23,6 +23,18 @@ class NwAppQuitFunction : public AsyncExtensionFunction { void Callback(); }; +class NwAppCloseAllWindowsFunction : public AsyncExtensionFunction { + public: + NwAppCloseAllWindowsFunction() {} + + protected: + ~NwAppCloseAllWindowsFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.App.closeAllWindows", UNKNOWN) +}; + class NwAppGetArgvSyncFunction : public NWSyncExtensionFunction { public: NwAppGetArgvSyncFunction(); diff --git a/src/nw_content.cc b/src/nw_content.cc index 0634eb8d81..eb49b446ca 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -558,6 +558,8 @@ void LoadNWAppAsExtensionHook(base::DictionaryValue* manifest, std::string* erro return; } + manifest->MergeDictionary(package->root()); + if (manifest->GetString(manifest_keys::kNWJSMain, &main_url)) { if (base::EndsWith(main_url, ".js", base::CompareCase::INSENSITIVE_ASCII)) { AmendManifestStringList(manifest, manifest_keys::kPlatformAppBackgroundScripts, main_url); diff --git a/src/resources/api_nw_app.js b/src/resources/api_nw_app.js index 218e34f2e3..c6aa527154 100644 --- a/src/resources/api_nw_app.js +++ b/src/resources/api_nw_app.js @@ -16,6 +16,9 @@ nw_binding.registerCustomHook(function(bindingsAPI) { argv = nw.App.getArgvSync(); return argv; }); + bindingsAPI.compiledApi.__defineGetter__('manifest', function() { + return chrome.runtime.getManifest(); + }); apiFunctions.setHandleRequest('getArgvSync', function() { return sendRequest.sendRequestSync('nw.App.getArgvSync', [], this.definition.parameters, {}); }); From c66b62608a553dda39e62942729e37b1e574dbea Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 6 Dec 2015 10:44:55 +0800 Subject: [PATCH 106/404] port kiosk APIs --- src/api/nw_current_window_internal.idl | 3 +++ src/api/nw_window_api.cc | 25 +++++++++++++++++ src/api/nw_window_api.h | 37 ++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index e921d05c21..affc0c1672 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -20,6 +20,9 @@ namespace nw.currentWindowInternal { static void setBadgeLabel(DOMString badge); static void requestAttention(long count); static void setProgressBar(double progress); + static void enterKioskMode(); + static void leaveKioskMode(); + static void toggleKioskMode(); static void capturePageInternal(optional CapturePageOptions options, optional CapturePageCallback callback); static void clearMenu(); static void setMenu(long id); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index b8321f97a1..9a806eb925 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -561,4 +561,29 @@ bool NwCurrentWindowInternalSetZoomFunction::RunNWSync(base::ListValue* response return true; } +bool NwCurrentWindowInternalEnterKioskModeFunction::RunAsync() { + AppWindow* window = getAppWindow(this); + window->ForcedFullscreen(); + SendResponse(true); + return true; +} + +bool NwCurrentWindowInternalLeaveKioskModeFunction::RunAsync() { + AppWindow* window = getAppWindow(this); + window->Restore(); + SendResponse(true); + return true; +} + +bool NwCurrentWindowInternalToggleKioskModeFunction::RunAsync() { + AppWindow* window = getAppWindow(this); + if (window->IsFullscreen() || window->IsForcedFullscreen()) + window->Restore(); + else + window->ForcedFullscreen(); + SendResponse(true); + return true; +} + } // namespace extensions + diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 27dce2d4cb..e98b8f9dcf 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -191,5 +191,42 @@ class NwCurrentWindowInternalSetZoomFunction : public NWSyncExtensionFunction { DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.setZoom", UNKNOWN) }; +class NwCurrentWindowInternalEnterKioskModeFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalEnterKioskModeFunction() {} + + protected: + ~NwCurrentWindowInternalEnterKioskModeFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.enterKioskMode", UNKNOWN) +}; + +class NwCurrentWindowInternalLeaveKioskModeFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalLeaveKioskModeFunction() {} + + protected: + ~NwCurrentWindowInternalLeaveKioskModeFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.leaveKioskMode", UNKNOWN) +}; + +class NwCurrentWindowInternalToggleKioskModeFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalToggleKioskModeFunction() {} + + protected: + ~NwCurrentWindowInternalToggleKioskModeFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.toggleKioskMode", UNKNOWN) +}; + + } // namespace extensions #endif From 67f887f1293ab67bf1e939c4609bbe121db3ce3e Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 6 Dec 2015 11:00:49 +0800 Subject: [PATCH 107/404] port Window.isKioskMode --- src/api/nw_current_window_internal.idl | 1 + src/api/nw_window_api.cc | 12 +++++++++++- src/api/nw_window_api.h | 10 ++++++++++ src/resources/api_nw_window.js | 11 +++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index affc0c1672..921999136a 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -23,6 +23,7 @@ namespace nw.currentWindowInternal { static void enterKioskMode(); static void leaveKioskMode(); static void toggleKioskMode(); + static bool isKioskInternal(); static void capturePageInternal(optional CapturePageOptions options, optional CapturePageCallback callback); static void clearMenu(); static void setMenu(long id); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 9a806eb925..0bd6661a64 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -83,7 +83,7 @@ const char kNoAssociatedAppWindow[] = "associated app window."; } -static AppWindow* getAppWindow(AsyncExtensionFunction* func) { +static AppWindow* getAppWindow(UIThreadExtensionFunction* func) { AppWindowRegistry* registry = AppWindowRegistry::Get(func->browser_context()); DCHECK(registry); content::WebContents* web_contents = func->GetSenderWebContents(); @@ -585,5 +585,15 @@ bool NwCurrentWindowInternalToggleKioskModeFunction::RunAsync() { return true; } +bool NwCurrentWindowInternalIsKioskInternalFunction::RunNWSync(base::ListValue* response, std::string* error) { + AppWindow* window = getAppWindow(this); + if (window->IsFullscreen() || window->IsForcedFullscreen()) + response->AppendBoolean(true); + else + response->AppendBoolean(false); + return true; +} + + } // namespace extensions diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index e98b8f9dcf..209fd12b06 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -227,6 +227,16 @@ class NwCurrentWindowInternalToggleKioskModeFunction : public AsyncExtensionFunc DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.toggleKioskMode", UNKNOWN) }; +class NwCurrentWindowInternalIsKioskInternalFunction : public NWSyncExtensionFunction { + public: + NwCurrentWindowInternalIsKioskInternalFunction() {} + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwCurrentWindowInternalIsKioskInternalFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.isKioskInternal", UNKNOWN) +}; + } // namespace extensions #endif diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index d4aef5c580..fad95e10aa 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -261,6 +261,17 @@ nw_binding.registerCustomHook(function(bindingsAPI) { currentNWWindowInternal.setZoom(val); } }); + Object.defineProperty(NWWindow.prototype, 'isKioskMode', { + get: function() { + return currentNWWindowInternal.isKioskInternal(); + }, + set: function(val) { + if (val) + currentNWWindowInternal.enterKioskMode(); + else + currentNWWindowInternal.leaveKioskMode(); + } + }); Object.defineProperty(NWWindow.prototype, 'menu', { get: function() { var ret = privates(this).menu || {}; From 96ffe306d522f2f46657c421ab11a8b67a1985e0 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 6 Dec 2015 16:24:00 +0800 Subject: [PATCH 108/404] call nw.Window.onDocumentStartEnd in proper frames removes warning when opending CDT --- src/nw_content.cc | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/nw_content.cc b/src/nw_content.cc index eb49b446ca..14ae50db90 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -298,15 +298,18 @@ void DocumentHook2(bool start, content::RenderFrame* frame, Dispatcher* dispatch ->GetWebView()->mainFrame()->mainWorldScriptContext(); ScriptContext* script_context = dispatcher->script_context_set().GetByV8Context(v8_context); - if (!script_context) + if (!script_context || !script_context->extension()) return; - std::vector > arguments; - blink::WebLocalFrame* web_frame = frame->GetWebFrame(); - v8::Local window = + if (script_context->extension()->GetType() == Manifest::TYPE_NWJS_APP && + script_context->context_type() == Feature::BLESSED_EXTENSION_CONTEXT) { + std::vector > arguments; + blink::WebLocalFrame* web_frame = frame->GetWebFrame(); + v8::Local window = web_frame->mainWorldScriptContext()->Global(); - arguments.push_back(v8::Boolean::New(isolate, start)); - arguments.push_back(window); - script_context->module_system()->CallModuleMethod("nw.Window", "onDocumentStartEnd", &arguments); + arguments.push_back(v8::Boolean::New(isolate, start)); + arguments.push_back(window); + script_context->module_system()->CallModuleMethod("nw.Window", "onDocumentStartEnd", &arguments); + } } void DocumentElementHook(blink::WebFrame* frame, From 5ea8bc884e3e1a1fafa666342f5c762c04dcd72d Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Sun, 6 Dec 2015 16:25:00 +0800 Subject: [PATCH 109/404] port workspaces API for Window and isTransparent --- src/api/nw_window.idl | 2 ++ src/resources/api_nw_window.js | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 10a595a4b1..126e4004d2 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -50,6 +50,8 @@ namespace nw.Window { static void enterFullscreen(); static void leaveFullscreen(); static void toggleFullscreen(); + static void setVisibleOnAllWorkspaces(boolean all_visible); + static bool canSetVisibleOnAllWorkspaces(); static void on(DOMString event, EventCallback callback); static void reload(); diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index fad95e10aa..46d7a8c6c1 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -7,6 +7,7 @@ var sendRequest = require('sendRequest'); var currentNWWindow = null; var currentNWWindowInternal = null; +var canSetVisibleOnAllWorkspaces = /(darwin|linux)/.test(nw.require('os').platform()); var nw_internal = require('binding').Binding.create('nw.currentWindowInternal'); @@ -184,6 +185,12 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.isFullscreen = function () { return this.appWindow.isFullscreen(); }; + NWWindow.prototype.setVisibleOnAllWorkspaces = function(all_visible) { + this.appWindow.setVisibleOnAllWorkspaces(all_visible); + }; + NWWindow.prototype.canSetVisibleOnAllWorkspaces = function() { + return canSetVisibleOnAllWorkspaces; + }; NWWindow.prototype.setMaximumSize = function (width, height) { this.appWindow.outerBounds.maxWidth = width; this.appWindow.outerBounds.maxHeight = height; @@ -261,6 +268,11 @@ nw_binding.registerCustomHook(function(bindingsAPI) { currentNWWindowInternal.setZoom(val); } }); + Object.defineProperty(NWWindow.prototype, 'isTransparent', { + get: function() { + return this.appWindow.alphaEnabled; + } + }); Object.defineProperty(NWWindow.prototype, 'isKioskMode', { get: function() { return currentNWWindowInternal.isKioskInternal(); From da6662a94a05095416eae59a5c3dd11433102d30 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 7 Dec 2015 10:27:50 +0800 Subject: [PATCH 110/404] add shim for compatibility with nw12 --- src/nw_content.cc | 15 +++++++++++++++ src/resources/nw_pre13_shim.js | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/resources/nw_pre13_shim.js diff --git a/src/nw_content.cc b/src/nw_content.cc index 14ae50db90..a1815413f5 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -46,6 +46,8 @@ #include "extensions/common/manifest.h" #include "extensions/common/manifest_constants.h" +#include "extensions/grit/extensions_renderer_resources.h" + #include "net/cert/x509_certificate.h" #include "third_party/WebKit/public/web/WebDocument.h" @@ -59,6 +61,7 @@ #include "chrome/common/chrome_constants.h" +#include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_types.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia_rep.h" @@ -361,6 +364,18 @@ void DocumentElementHook(blink::WebFrame* frame, // v8::Handle result; frame->executeScriptAndReturnValue(WebScriptSource(jscript)); } + + ui::ResourceBundle* resource_bundle = &ResourceBundle::GetSharedInstance(); + base::StringPiece resource = + resource_bundle->GetRawDataResource(IDR_NW_PRE13_SHIM_JS); + if (resource.empty()) + return; + jscript = base::UTF8ToUTF16(resource.as_string()); + if (!v8_context.IsEmpty()) { + blink::WebScopedMicrotaskSuppression suppression; + v8::Context::Scope cscope(v8_context); + frame->executeScriptAndReturnValue(WebScriptSource(jscript)); + } } void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { diff --git a/src/resources/nw_pre13_shim.js b/src/resources/nw_pre13_shim.js new file mode 100644 index 0000000000..8b85d9cdf5 --- /dev/null +++ b/src/resources/nw_pre13_shim.js @@ -0,0 +1,22 @@ +(function() { + + // detect `nw` object of NW13 + if (!(self.nw && self.nw.require)) return; + + var realrequire = nw.require; + self.require = function() { + if (arguments[0] === 'nw.gui') { + return nw; + } else { + return realrequire.apply(self, [].slice.call(arguments, 0)); + } + }; + + // Following items exist when running with `--mixed-context`. + // Copy them from `nw` to browser context + if (!self.process) self.process = self.nw.process; + if (!self.Buffer) self.Buffer = self.nw.Buffer; + if (!self.global) self.global = self.nw.global; + if (!self.root) self.root = self.nw.root; + +}()); From ad8327570a276bfa656febd86b6b5c26854d04d1 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 7 Dec 2015 12:21:04 +0800 Subject: [PATCH 111/404] fix: 'nw' object not available --- src/nw_content.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nw_content.cc b/src/nw_content.cc index a1815413f5..4e28ba416e 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -576,7 +576,7 @@ void LoadNWAppAsExtensionHook(base::DictionaryValue* manifest, std::string* erro return; } - manifest->MergeDictionary(package->root()); + //manifest->MergeDictionary(package->root()); if (manifest->GetString(manifest_keys::kNWJSMain, &main_url)) { if (base::EndsWith(main_url, ".js", base::CompareCase::INSENSITIVE_ASCII)) { From b3f51e0bcfa425b41915394408c1e06c97daef34 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Mon, 7 Dec 2015 13:31:48 +0800 Subject: [PATCH 112/404] Fixed shim script injecting --- src/nw_content.cc | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/nw_content.cc b/src/nw_content.cc index 4e28ba416e..2aa5652557 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -348,6 +348,19 @@ void DocumentElementHook(blink::WebFrame* frame, RenderViewImpl* rv = RenderViewImpl::FromWebView(frame->view()); if (!rv) return; + + ui::ResourceBundle* resource_bundle = &ResourceBundle::GetSharedInstance(); + base::StringPiece resource = + resource_bundle->GetRawDataResource(IDR_NW_PRE13_SHIM_JS); + if (resource.empty()) + return; + base::string16 jscript = base::UTF8ToUTF16(resource.as_string()); + if (!v8_context.IsEmpty()) { + blink::WebScopedMicrotaskSuppression suppression; + v8::Context::Scope cscope(v8_context); + frame->executeScriptAndReturnValue(WebScriptSource(jscript)); + } + std::string js_fn = rv->renderer_preferences().nw_inject_js_doc_start; if (js_fn.empty()) return; @@ -357,25 +370,13 @@ void DocumentElementHook(blink::WebFrame* frame, //LOG(WARNING) << "Failed to load js script file: " << js_file.value(); return; } - base::string16 jscript = base::UTF8ToUTF16(content); + jscript = base::UTF8ToUTF16(content); if (!v8_context.IsEmpty()) { blink::WebScopedMicrotaskSuppression suppression; v8::Context::Scope cscope(v8_context); // v8::Handle result; frame->executeScriptAndReturnValue(WebScriptSource(jscript)); } - - ui::ResourceBundle* resource_bundle = &ResourceBundle::GetSharedInstance(); - base::StringPiece resource = - resource_bundle->GetRawDataResource(IDR_NW_PRE13_SHIM_JS); - if (resource.empty()) - return; - jscript = base::UTF8ToUTF16(resource.as_string()); - if (!v8_context.IsEmpty()) { - blink::WebScopedMicrotaskSuppression suppression; - v8::Context::Scope cscope(v8_context); - frame->executeScriptAndReturnValue(WebScriptSource(jscript)); - } } void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { From aca796d899fc01b4d7bd6e59f0b8da949482ed64 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Mon, 7 Dec 2015 15:58:17 +0800 Subject: [PATCH 113/404] Renamed `nw.Object` to `nw.Obj` to avoid conflict --- src/api/_api_features.json | 2 +- src/api/nw_object.idl | 2 +- src/api/nw_object_api.cc | 24 ++++++++++---------- src/api/nw_object_api.h | 40 +++++++++++++++++----------------- src/resources/api_nw_menu.js | 18 +++++++-------- src/resources/api_nw_object.js | 2 +- src/resources/api_nw_tray.js | 10 ++++----- 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/api/_api_features.json b/src/api/_api_features.json index 5077c76925..690879ef96 100644 --- a/src/api/_api_features.json +++ b/src/api/_api_features.json @@ -27,7 +27,7 @@ "channel": "stable", "contexts": ["blessed_extension"] }, - "nw.Object": { + "nw.Obj": { "channel": "stable", "contexts": ["blessed_extension"] }, diff --git a/src/api/nw_object.idl b/src/api/nw_object.idl index 58034c9a9b..63e98b074f 100644 --- a/src/api/nw_object.idl +++ b/src/api/nw_object.idl @@ -4,7 +4,7 @@ // nw Object API [implemented_in="content/nw/src/api/nw_object_api.h"] -namespace nw.Object { +namespace nw.Obj { interface Functions { static object create(long id, DOMString type, object options); static object destroy(long id); diff --git a/src/api/nw_object_api.cc b/src/api/nw_object_api.cc index 1715caeb74..22e63efd1e 100644 --- a/src/api/nw_object_api.cc +++ b/src/api/nw_object_api.cc @@ -12,13 +12,13 @@ namespace extensions { -NwObjectCreateFunction::NwObjectCreateFunction() { +NwObjCreateFunction::NwObjCreateFunction() { } -NwObjectCreateFunction::~NwObjectCreateFunction() { +NwObjCreateFunction::~NwObjCreateFunction() { } -bool NwObjectCreateFunction::RunNWSync(base::ListValue* response, std::string* error) { +bool NwObjCreateFunction::RunNWSync(base::ListValue* response, std::string* error) { base::DictionaryValue* options = nullptr; int id = 0; std::string type; @@ -31,13 +31,13 @@ bool NwObjectCreateFunction::RunNWSync(base::ListValue* response, std::string* e return true; } -NwObjectDestroyFunction::NwObjectDestroyFunction() { +NwObjDestroyFunction::NwObjDestroyFunction() { } -NwObjectDestroyFunction::~NwObjectDestroyFunction() { +NwObjDestroyFunction::~NwObjDestroyFunction() { } -bool NwObjectDestroyFunction::RunNWSync(base::ListValue* response, std::string* error) { +bool NwObjDestroyFunction::RunNWSync(base::ListValue* response, std::string* error) { int id = 0; EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &id)); @@ -46,13 +46,13 @@ bool NwObjectDestroyFunction::RunNWSync(base::ListValue* response, std::string* return true; } -NwObjectCallObjectMethodFunction::NwObjectCallObjectMethodFunction() { +NwObjCallObjectMethodFunction::NwObjCallObjectMethodFunction() { } -NwObjectCallObjectMethodFunction::~NwObjectCallObjectMethodFunction() { +NwObjCallObjectMethodFunction::~NwObjCallObjectMethodFunction() { } -bool NwObjectCallObjectMethodFunction::RunNWSync(base::ListValue* response, std::string* error) { +bool NwObjCallObjectMethodFunction::RunNWSync(base::ListValue* response, std::string* error) { base::ListValue* arguments = nullptr; int id = 0; std::string type, method; @@ -66,13 +66,13 @@ bool NwObjectCallObjectMethodFunction::RunNWSync(base::ListValue* response, std: return true; } -NwObjectCallObjectMethodSyncFunction::NwObjectCallObjectMethodSyncFunction() { +NwObjCallObjectMethodSyncFunction::NwObjCallObjectMethodSyncFunction() { } -NwObjectCallObjectMethodSyncFunction::~NwObjectCallObjectMethodSyncFunction() { +NwObjCallObjectMethodSyncFunction::~NwObjCallObjectMethodSyncFunction() { } -bool NwObjectCallObjectMethodSyncFunction::RunNWSync(base::ListValue* response, std::string* error) { +bool NwObjCallObjectMethodSyncFunction::RunNWSync(base::ListValue* response, std::string* error) { base::ListValue* arguments = nullptr; int id = 0; std::string type, method; diff --git a/src/api/nw_object_api.h b/src/api/nw_object_api.h index 743c06c918..1859eb0f03 100644 --- a/src/api/nw_object_api.h +++ b/src/api/nw_object_api.h @@ -7,56 +7,56 @@ namespace extensions { -class NwObjectCreateFunction : public NWSyncExtensionFunction { +class NwObjCreateFunction : public NWSyncExtensionFunction { public: - NwObjectCreateFunction(); + NwObjCreateFunction(); bool RunNWSync(base::ListValue* response, std::string* error) override; protected: - ~NwObjectCreateFunction() override; + ~NwObjCreateFunction() override; - DECLARE_EXTENSION_FUNCTION("nw.Object.create", UNKNOWN) + DECLARE_EXTENSION_FUNCTION("nw.Obj.create", UNKNOWN) private: - DISALLOW_COPY_AND_ASSIGN(NwObjectCreateFunction); + DISALLOW_COPY_AND_ASSIGN(NwObjCreateFunction); }; -class NwObjectDestroyFunction : public NWSyncExtensionFunction { +class NwObjDestroyFunction : public NWSyncExtensionFunction { public: - NwObjectDestroyFunction(); + NwObjDestroyFunction(); bool RunNWSync(base::ListValue* response, std::string* error) override; protected: - ~NwObjectDestroyFunction() override; + ~NwObjDestroyFunction() override; - DECLARE_EXTENSION_FUNCTION("nw.Object.destroy", UNKNOWN) + DECLARE_EXTENSION_FUNCTION("nw.Obj.destroy", UNKNOWN) private: - DISALLOW_COPY_AND_ASSIGN(NwObjectDestroyFunction); + DISALLOW_COPY_AND_ASSIGN(NwObjDestroyFunction); }; -class NwObjectCallObjectMethodFunction : public NWSyncExtensionFunction { +class NwObjCallObjectMethodFunction : public NWSyncExtensionFunction { public: - NwObjectCallObjectMethodFunction(); + NwObjCallObjectMethodFunction(); bool RunNWSync(base::ListValue* response, std::string* error) override; protected: - ~NwObjectCallObjectMethodFunction() override; + ~NwObjCallObjectMethodFunction() override; - DECLARE_EXTENSION_FUNCTION("nw.Object.callObjectMethod", UNKNOWN) + DECLARE_EXTENSION_FUNCTION("nw.Obj.callObjectMethod", UNKNOWN) private: - DISALLOW_COPY_AND_ASSIGN(NwObjectCallObjectMethodFunction); + DISALLOW_COPY_AND_ASSIGN(NwObjCallObjectMethodFunction); }; -class NwObjectCallObjectMethodSyncFunction : public NWSyncExtensionFunction { +class NwObjCallObjectMethodSyncFunction : public NWSyncExtensionFunction { public: - NwObjectCallObjectMethodSyncFunction(); + NwObjCallObjectMethodSyncFunction(); bool RunNWSync(base::ListValue* response, std::string* error) override; protected: - ~NwObjectCallObjectMethodSyncFunction() override; + ~NwObjCallObjectMethodSyncFunction() override; - DECLARE_EXTENSION_FUNCTION("nw.Object.callObjectMethodSync", UNKNOWN) + DECLARE_EXTENSION_FUNCTION("nw.Obj.callObjectMethodSync", UNKNOWN) private: - DISALLOW_COPY_AND_ASSIGN(NwObjectCallObjectMethodSyncFunction); + DISALLOW_COPY_AND_ASSIGN(NwObjCallObjectMethodSyncFunction); }; } // namespace extensions diff --git a/src/resources/api_nw_menu.js b/src/resources/api_nw_menu.js index dd31330398..d7981abc91 100644 --- a/src/resources/api_nw_menu.js +++ b/src/resources/api_nw_menu.js @@ -29,27 +29,27 @@ Menu.prototype.__defineSetter__('items', function(val) { Menu.prototype.append = function(menu_item) { privates(this).items.push(menu_item); - nw.Object.callObjectMethod(this.id, 'Menu', 'Append', [ menu_item.id ]); + nw.Obj.callObjectMethod(this.id, 'Menu', 'Append', [ menu_item.id ]); }; Menu.prototype.insert = function(menu_item, i) { privates(this).items.splice(i, 0, menu_item); - nw.Object.callObjectMethod(this.id, 'Menu', 'Insert', [ menu_item.id, i ]); + nw.Obj.callObjectMethod(this.id, 'Menu', 'Insert', [ menu_item.id, i ]); } Menu.prototype.remove = function(menu_item) { var pos_hint = privates(this).items.indexOf(menu_item); - nw.Object.callObjectMethod(this.id, 'Menu', 'Remove', [ menu_item.id, pos_hint ]); + nw.Obj.callObjectMethod(this.id, 'Menu', 'Remove', [ menu_item.id, pos_hint ]); privates(this).items.splice(pos_hint, 1); } Menu.prototype.removeAt = function(i) { - nw.Object.callObjectMethod(this.id, 'Menu', 'Remove', [ privates(this).items[i].id, i ]); + nw.Obj.callObjectMethod(this.id, 'Menu', 'Remove', [ privates(this).items[i].id, i ]); privates(this).items.splice(i, 1); } Menu.prototype.popup = function(x, y) { - nw.Object.callObjectMethod(this.id, 'Menu', 'Popup', [ x, y ]); + nw.Obj.callObjectMethod(this.id, 'Menu', 'Popup', [ x, y ]); } Menu.prototype.createMacBuiltin = function (app_name, options) { @@ -246,7 +246,7 @@ MenuItem.prototype.handleGetter = function(name) { MenuItem.prototype.handleSetter = function(name, setter, type, value) { value = type(value); privates(this).option[name] = value; - nw.Object.callObjectMethod(this.id, 'MenuItem', setter, [ value ]); + nw.Obj.callObjectMethod(this.id, 'MenuItem', setter, [ value ]); }; MenuItem.prototype.__defineGetter__('type', function() { @@ -335,7 +335,7 @@ MenuItem.prototype.__defineGetter__('submenu', function() { MenuItem.prototype.__defineSetter__('submenu', function(val) { privates(this).submenu = val; - nw.Object.callObjectMethod(this.id, 'MenuItem', 'SetSubmenu', [ val.id ]); + nw.Obj.callObjectMethod(this.id, 'MenuItem', 'SetSubmenu', [ val.id ]); }); nw_binding.registerCustomHook(function(bindingsAPI) { @@ -355,7 +355,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { return ret; }); apiFunctions.setHandleRequest('destroy', function(id) { - sendRequest.sendRequestSync('nw.Object.destroy', [id], this.definition.parameters, {}); + sendRequest.sendRequestSync('nw.Obj.destroy', [id], this.definition.parameters, {}); }); apiFunctions.setHandleRequest('createMenu', function(option) { var id = contextMenuNatives.GetNextContextMenuId(); @@ -364,7 +364,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { option.generatedId = id; var ret = new Menu(id, option); - sendRequest.sendRequestSync('nw.Object.create', [id, 'Menu', option], this.definition.parameters, {}); + sendRequest.sendRequestSync('nw.Obj.create', [id, 'Menu', option], this.definition.parameters, {}); messagingNatives.BindToGC(ret, nw.Menu.destroy.bind(undefined, id), -1); return ret; }); diff --git a/src/resources/api_nw_object.js b/src/resources/api_nw_object.js index 779196502a..3ceeff884f 100644 --- a/src/resources/api_nw_object.js +++ b/src/resources/api_nw_object.js @@ -1,6 +1,6 @@ var Binding = require('binding').Binding; var forEach = require('utils').forEach; -var nw_binding = require('binding').Binding.create('nw.Object'); +var nw_binding = require('binding').Binding.create('nw.Obj'); var sendRequest = require('sendRequest'); nw_binding.registerCustomHook(function(bindingsAPI) { diff --git a/src/resources/api_nw_tray.js b/src/resources/api_nw_tray.js index d3bf66a3be..87558ebb9c 100644 --- a/src/resources/api_nw_tray.js +++ b/src/resources/api_nw_tray.js @@ -75,7 +75,7 @@ Tray.prototype.handleGetter = function(name) { Tray.prototype.handleSetter = function(name, setter, type, value) { value = type(value); privates(this).option[name] = value; - nw.Object.callObjectMethod(this.id, 'Tray', setter, [ value ]); + nw.Obj.callObjectMethod(this.id, 'Tray', setter, [ value ]); }; Tray.prototype.__defineGetter__('title', function() { @@ -131,13 +131,13 @@ Tray.prototype.__defineSetter__('menu', function(val) { throw new TypeError("'menu' property requries a valid Menu"); privates(this).menu = val; - nw.Object.callObjectMethod(this.id, 'Tray', 'SetMenu', [ val.id ]); + nw.Obj.callObjectMethod(this.id, 'Tray', 'SetMenu', [ val.id ]); }); Tray.prototype.remove = function() { if (trayEvents.objs[this.id]) this.removeListener('click'); - nw.Object.callObjectMethod(this.id, 'Tray', 'Remove', []); + nw.Obj.callObjectMethod(this.id, 'Tray', 'Remove', []); } Tray.prototype.on = function (event, callback) { @@ -163,7 +163,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { trayEvents.objs[id]._onclick(); }); apiFunctions.setHandleRequest('destroy', function(id) { - sendRequest.sendRequestSync('nw.Object.destroy', [id], this.definition.parameters, {}); + sendRequest.sendRequestSync('nw.Obj.destroy', [id], this.definition.parameters, {}); }); apiFunctions.setHandleRequest('create', function(option) { var id = contextMenuNatives.GetNextContextMenuId(); @@ -172,7 +172,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { option.generatedId = id; var ret = new Tray(id, option); - sendRequest.sendRequestSync('nw.Object.create', [id, 'Tray', option], this.definition.parameters, {}); + sendRequest.sendRequestSync('nw.Obj.create', [id, 'Tray', option], this.definition.parameters, {}); messagingNatives.BindToGC(ret, nw.Tray.destroy.bind(undefined, id), -1); return ret; }); From 13424fb41b892377e0cf12a13e5b8ff8ff6e2f24 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 8 Dec 2015 13:08:15 +0800 Subject: [PATCH 114/404] use desktop icudtl.dat; fix chrome.i18n API IsLocalePartiallyPopulated will return false with the android data file --- nw.gypi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nw.gypi b/nw.gypi index 8a5618bc58..7a048dbc58 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1009,7 +1009,7 @@ 'icudat_path': '<(DEPTH)/third_party/icu/source/data/in/icudtl.dat', }, { 'package_mode': 'nosdk', - 'icudat_path': '<(DEPTH)/third_party/icu/android/icudtl.dat', + 'icudat_path': '<(DEPTH)/third_party/icu/source/data/in/icudtl.dat', }], ['disable_nacl==0 and nwjs_sdk==0', { 'package_mode': 'nacl', From 4697bec5162c3cb542a13fe1e74e9f9dcffd422a Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 8 Dec 2015 21:37:17 +0800 Subject: [PATCH 115/404] [README] update for 0.13.0-alpha7 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 171c0abc39..5ef4e236e3 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,11 @@ It was created in the Intel Open Source Technology Center. * Windows: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-win-x64.zip) * Mac 10.7+: [32bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-ia32.zip) / [64bit](http://dl.nwjs.io/v0.12.3/nwjs-v0.12.3-osx-x64.zip) -* **v0.13.0-alpha6:** (Nov 25, 2015, based off of Node.js v5.0.0, Chromium 46.0.2490.80): [release notes](https://groups.google.com/d/msg/nwjs-general/l-vc2U9mSsA/qn4SqhR7AwAJ) +* **v0.13.0-alpha7:** (Dec 8, 2015, based off of Node.js v5.1.0, Chromium 47.0.2526.73): [release notes](https://groups.google.com/d/msg/nwjs-general/HF7InrhFxqw/4v7SdZWOBwAJ) **NOTE** You might want the **SDK build**. Please read the release notes - * Linux: [32bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-linux-x64.tar.gz) - * Windows: [32bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-win-x64.zip) - * Mac 10.7+: [64bit](http://dl.nwjs.io/v0.13.0-alpha6/nwjs-v0.13.0-alpha6-osx-x64.zip) + * Linux: [32bit](http://dl.nwjs.io/v0.13.0-alpha7/nwjs-v0.13.0-alpha7-linux-ia32.tar.gz) / [64bit](http://dl.nwjs.io/v0.13.0-alpha7/nwjs-v0.13.0-alpha7-linux-x64.tar.gz) + * Windows: [32bit](http://dl.nwjs.io/v0.13.0-alpha7/nwjs-v0.13.0-alpha7-win-ia32.zip) / [64bit](http://dl.nwjs.io/v0.13.0-alpha7/nwjs-v0.13.0-alpha7-win-x64.zip) + * Mac 10.7+: [64bit](http://dl.nwjs.io/v0.13.0-alpha7/nwjs-v0.13.0-alpha7-osx-x64.zip) * **0.8.6:** (Apr 18, 2014, based off of Node v0.10.22, Chrome 30.0.1599.66) **If your native Node module works only with Node v0.10, then you should use node-webkit v0.8.x, which is also a maintained branch. [More info](https://groups.google.com/d/msg/nwjs-general/2OJ1cEMPLlA/09BvpTagSA0J)** [release notes](https://groups.google.com/d/msg/nwjs-general/CLPkgfV-i7s/hwkkQuJ1kngJ) From bc591dcb54d8b6e642d70558ebf43a1df43aceb1 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 9 Dec 2015 14:12:15 +0800 Subject: [PATCH 116/404] fix content verification: generated wrong hashes for files larger than 4096 bytes --- nw.gypi | 11 +++ tools/payload.cc | 220 +++++++++++++++++++++++++++++++++++++++++++++ tools/sign/sign.py | 20 +++-- 3 files changed, 243 insertions(+), 8 deletions(-) create mode 100644 tools/payload.cc diff --git a/nw.gypi b/nw.gypi index 7a048dbc58..5512c4d3b3 100644 --- a/nw.gypi +++ b/nw.gypi @@ -1050,6 +1050,17 @@ 'about_credits_nw', ], }, + { + 'target_name': 'payload', + 'type': 'executable', + 'sources': [ + 'tools/payload.cc', + ], + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/extensions/extensions.gyp:extensions_browser', + ], + }, { 'target_name': 'test', 'type': 'none', diff --git a/tools/payload.cc b/tools/payload.cc new file mode 100644 index 0000000000..d001051aed --- /dev/null +++ b/tools/payload.cc @@ -0,0 +1,220 @@ +#include + +#include "base/at_exit.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/i18n/icu_util.h" +#include "base/logging.h" +#include "base/process/memory.h" +#include "base/strings/string_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" +#include "extensions/browser/content_hash_tree.h" +#include "extensions/browser/computed_hashes.h" + +#include "base/base64.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/values.h" + + +using crypto::kSHA256Length; +using crypto::SecureHash; + + +namespace { +const char kBlockHashesKey[] = "root_hash"; +const char kBlockSizeKey[] = "block_size"; +const char kFileHashesKey[] = "files"; +const char kPathKey[] = "path"; +const char kVersionKey[] = "version"; +const int kVersion = 2; + +typedef std::set SortedFilePathSet; + +bool MakePathAbsolute(base::FilePath* file_path) { + DCHECK(file_path); + + base::FilePath current_directory; + if (!base::GetCurrentDirectory(¤t_directory)) + return false; + + if (file_path->IsAbsolute()) + return true; + + if (current_directory.empty()) { + *file_path = base::MakeAbsoluteFilePath(*file_path); + return true; + } + + if (!current_directory.IsAbsolute()) + return false; + +#ifdef OS_LINUX + //linux might gives "/" as current_directory, return false + if (current_directory.value().length() <= 1) + return false; +#endif + + *file_path = current_directory.Append(*file_path); + return true; +} + +} // namespace + +class MyComputedHashes { + public: + class Writer { + public: + Writer(); + ~Writer(); + + // Adds hashes for |relative_path|. Should not be called more than once + // for a given |relative_path|. + void AddHash(const base::FilePath& relative_path, + int block_size, + const std::string& hashes); + + bool WriteToFile(const base::FilePath& path); + + private: + // Each element of this list contains the path and block hashes for one + // file. + scoped_ptr file_list_; + }; + + // Computes the SHA256 hash of each |block_size| chunk in |contents|, placing + // the results into |hashes|. + static void ComputeHashesForContent(const std::string& contents, + size_t block_size, + std::vector* hashes); +}; + +MyComputedHashes::Writer::Writer() : file_list_(new base::ListValue) { +} + +MyComputedHashes::Writer::~Writer() { +} + +void MyComputedHashes::Writer::AddHash(const base::FilePath& relative_path, + int block_size, + const std::string& root) { + base::DictionaryValue* dict = new base::DictionaryValue(); + file_list_->Append(dict); + dict->SetString(kPathKey, + relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe()); + //dict->SetInteger(kBlockSizeKey, block_size); + std::string encoded; + base::Base64Encode(root, &encoded); + base::ReplaceChars(encoded, "=", "", &encoded); + base::ReplaceChars(encoded, "/", "_", &encoded); + base::ReplaceChars(encoded, "+", "-", &encoded); + dict->SetString(kBlockHashesKey, encoded); +} + +bool MyComputedHashes::Writer::WriteToFile(const base::FilePath& path) { + std::string json; + base::DictionaryValue top_dictionary; + top_dictionary.SetInteger("block_size", 4096); + top_dictionary.SetInteger("hash_block_size", 4096); + top_dictionary.SetString("format", "treehash"); + top_dictionary.Set(kFileHashesKey, file_list_.release()); + + if (!base::JSONWriter::Write(top_dictionary, &json)) + return false; + int written = base::WriteFile(path, json.data(), json.size()); + if (static_cast(written) != json.size()) { + LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe() + << " ; write result:" << written << " expected:" << json.size(); + return false; + } + return true; +} + +void MyComputedHashes::ComputeHashesForContent(const std::string& contents, + size_t block_size, + std::vector* hashes) { + size_t offset = 0; + // Even when the contents is empty, we want to output at least one hash + // block (the hash of the empty string). + do { + const char* block_start = contents.data() + offset; + DCHECK(offset <= contents.size()); + size_t bytes_to_read = std::min(contents.size() - offset, block_size); + scoped_ptr hash( + crypto::SecureHash::Create(crypto::SecureHash::SHA256)); + hash->Update(block_start, bytes_to_read); + + hashes->push_back(std::string()); + std::string* buffer = &(hashes->back()); + buffer->resize(crypto::kSHA256Length); + hash->Finish(string_as_array(buffer), buffer->size()); + + // If |contents| is empty, then we want to just exit here. + if (bytes_to_read == 0) + break; + + offset += bytes_to_read; + } while (offset < contents.size()); +} + +bool CreateHashes(const base::FilePath& hashes_file, const base::FilePath& work_path) { + // Make sure the directory exists. + if (!base::CreateDirectoryAndGetError(hashes_file.DirName(), NULL)) + return false; + + base::FilePath root_path(work_path); + MakePathAbsolute(&root_path); + base::FileEnumerator enumerator(root_path, + true, /* recursive */ + base::FileEnumerator::FILES); + // First discover all the file paths and put them in a sorted set. + SortedFilePathSet paths; + for (;;) { + base::FilePath full_path = enumerator.Next(); + if (full_path.empty()) + break; + paths.insert(full_path); + } + + // Now iterate over all the paths in sorted order and compute the block hashes + // for each one. + MyComputedHashes::Writer writer; + for (SortedFilePathSet::iterator i = paths.begin(); i != paths.end(); ++i) { + const base::FilePath& full_path = *i; + base::FilePath relative_path; + root_path.AppendRelativePath(full_path, &relative_path); + relative_path = relative_path.NormalizePathSeparatorsTo('/'); + + std::string contents; + if (!base::ReadFileToString(full_path, &contents)) { + LOG(ERROR) << "Could not read " << full_path.MaybeAsASCII(); + continue; + } + + // Iterate through taking the hash of each block of size (block_size_) of + // the file. + std::vector hashes; + MyComputedHashes::ComputeHashesForContent(contents, 4096, &hashes); + std::string root = + extensions::ComputeTreeHashRoot(hashes, 4096 / crypto::kSHA256Length); + writer.AddHash(relative_path, 4096, root); + } + bool result = writer.WriteToFile(hashes_file); + return result; +} + +#if defined(OS_WIN) +int wmain(int argc, wchar_t* argv[]) { +#else +int main(int argc, char* argv[]) { +#endif + base::AtExitManager exit_manager; + base::i18n::InitializeICU(); + + return CreateHashes(base::FilePath(FILE_PATH_LITERAL("payload.json")), + base::FilePath(FILE_PATH_LITERAL("."))) ? 1 : 0; +} diff --git a/tools/sign/sign.py b/tools/sign/sign.py index 502ce57cbd..2ef5f7990b 100755 --- a/tools/sign/sign.py +++ b/tools/sign/sign.py @@ -3,6 +3,7 @@ import hashlib import base64 import json +import sys # --- helpers --- @@ -45,23 +46,26 @@ def sign_data(private_key_loc, data): "format": "treehash", "files": [] } -ROOT = '.' -for root, dirs, files in os.walk(ROOT): - for fpath in [osp.join(root, f) for f in files]: - size = osp.getsize(fpath) - sha = fixbase64(filehash(fpath)) - name = osp.relpath(fpath, ROOT) - hash['files'].append({"path": name, "root_hash": sha}) +#ROOT = '.' +#for root, dirs, files in os.walk(ROOT): +# for fpath in [osp.join(root, f) for f in files]: +# size = osp.getsize(fpath) +# sha = fixbase64(filehash(fpath)) +# name = osp.relpath(fpath, ROOT) +# hash['files'].append({"path": name, "root_hash": sha}) +hash = json.loads(open('payload.json', "r").read()) content_hashes = [ hash ] payload = { "content_hashes" : content_hashes, "item_id": "abcdefghijklmnopabcdefghijklmnop", "item_version": "1.2.3"} +#sys.stderr.write(json.dumps(hash)) + payload_json = json.dumps(payload) payload_encoded = fixbase64(base64.b64encode(payload_json)) protected = fixbase64(base64.b64encode('{"alg":"RS256"}')) signature_input = (protected + '.' + payload_encoded).replace("\n", "") -signature = fixbase64(sign_data('private_key.pem', signature_input)) +signature = fixbase64(sign_data(osp.join(osp.dirname(__file__), 'private_key.pem'), signature_input)) verfied_content = [ { "description": "treehash per file", From 606f3f648b8404ba479db1caf3a2ff404e042f44 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 9 Dec 2015 14:13:08 +0800 Subject: [PATCH 117/404] add content verification support for node --- src/nw_content.cc | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/nw_content.cc b/src/nw_content.cc index 2aa5652557..f7df5c6d29 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -31,6 +31,7 @@ #include "content/public/common/user_agent.h" #include "content/public/renderer/render_view.h" +#include "content/nw/src/nw_content_verifier_delegate.h" #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/api/object_manager.h" #include "content/nw/src/policy_cert_verifier.h" @@ -103,6 +104,8 @@ using extensions::Feature; using extensions::ExtensionPrefs; using extensions::ExtensionRegistry; using extensions::Dispatcher; +using extensions::ContentVerifierDelegate; +using extensions::NWContentVerifierDelegate; using blink::WebScriptSource; namespace manifest_keys = extensions::manifest_keys; @@ -434,18 +437,28 @@ void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { } if (context->extension()->manifest()->GetString(manifest_keys::kNWJSInternalMainFilename, &main_fn)) { + v8::Local script = v8::Script::Compile(v8::String::NewFromUtf8(isolate, + ("global.__filename = '" + main_fn + "';").c_str())); + script->Run(); + } + + bool content_verification = false; + if (context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSContentVerifyFlag, + &content_verification) && content_verification) { std::string root_path = context->extension()->path().AsUTF8Unsafe(); #if defined(OS_WIN) base::ReplaceChars(root_path, "\\", "\\\\", &root_path); #endif base::ReplaceChars(root_path, "'", "\\'", &root_path); - v8::Local script = v8::Script::Compile(v8::String::NewFromUtf8(isolate, - ("global.__filename = '" + main_fn + "';" + - "global.__dirname = '" + root_path + "';" - ).c_str())); + v8::Local script = + v8::Script::Compile(v8::String::NewFromUtf8(isolate, + (std::string("global.__nwjs_cv = true;") + + "global.__dirname = '" + root_path + "';" + + "global.__nwjs_ext_id = '" + context->extension()->id() + "';").c_str())); + script->Run(); } - + dom_context->Exit(); } } @@ -493,6 +506,7 @@ void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { base::ReplaceChars(root_path, "'", "\\'", &root_path); v8::Local script = v8::Script::Compile(v8::String::NewFromUtf8(isolate, ( set_nw_script + + "nw.global.XMLHttpRequest = XMLHttpRequest;" + // Make node's relative modules work "if (typeof nw.process != 'undefined' && (!nw.process.mainModule.filename || nw.process.mainModule.filename === 'blank')) {" " var root = '" + root_path + "';" @@ -609,6 +623,9 @@ void LoadNWAppAsExtensionHook(base::DictionaryValue* manifest, std::string* erro //FIXME: node-remote spec different with kWebURLs AmendManifestStringList(manifest, manifest_keys::kWebURLs, node_remote); } + + if (NWContentVerifierDelegate::GetDefaultMode() == ContentVerifierDelegate::ENFORCE_STRICT) + manifest->SetBoolean(manifest_keys::kNWJSContentVerifyFlag, true); } void RendererProcessTerminatedHook(content::RenderProcessHost* process, From 8a49a5d68c7279487b42860b65a78d58ef1dffa0 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 9 Dec 2015 14:31:43 +0800 Subject: [PATCH 118/404] fix __dirname in previous commit --- src/nw_content.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/nw_content.cc b/src/nw_content.cc index f7df5c6d29..6c5cb669d7 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -441,19 +441,22 @@ void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { ("global.__filename = '" + main_fn + "';").c_str())); script->Run(); } - - bool content_verification = false; - if (context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSContentVerifyFlag, - &content_verification) && content_verification) { + { std::string root_path = context->extension()->path().AsUTF8Unsafe(); #if defined(OS_WIN) base::ReplaceChars(root_path, "\\", "\\\\", &root_path); #endif base::ReplaceChars(root_path, "'", "\\'", &root_path); + v8::Local script = v8::Script::Compile(v8::String::NewFromUtf8(isolate, + ("global.__dirname = '" + root_path + "';").c_str())); + script->Run(); + } + bool content_verification = false; + if (context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSContentVerifyFlag, + &content_verification) && content_verification) { v8::Local script = v8::Script::Compile(v8::String::NewFromUtf8(isolate, (std::string("global.__nwjs_cv = true;") + - "global.__dirname = '" + root_path + "';" + "global.__nwjs_ext_id = '" + context->extension()->id() + "';").c_str())); script->Run(); From ab2979470a58e4582f377d4de47ded618311f815 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 9 Dec 2015 14:31:59 +0800 Subject: [PATCH 119/404] ship payload tool in sdk build --- nw.gypi | 2 ++ tools/package_binaries.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/nw.gypi b/nw.gypi index 5512c4d3b3..03cac58b24 100644 --- a/nw.gypi +++ b/nw.gypi @@ -958,6 +958,7 @@ 'inputs': [ '<(PRODUCT_DIR)/chromedriver', '<(PRODUCT_DIR)/nwjc', + '<(PRODUCT_DIR)/payload', ], 'outputs': [ '<(PRODUCT_DIR)/strip_binaries.stamp', @@ -980,6 +981,7 @@ '<(DEPTH)/chrome/chrome.gyp:chrome', '<(DEPTH)/third_party/node/node.gyp:node', '<(DEPTH)/v8/tools/gyp/v8.gyp:nwjc', + 'payload', ], 'conditions': [ ['disable_nacl==0', { diff --git a/tools/package_binaries.py b/tools/package_binaries.py index 73af1d4852..0176d8168b 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -166,6 +166,7 @@ def generate_target_nw(platform_name, arch, version): ] if flavor == 'sdk': target['input'].append('nwjc') + target['input'].append('payload') if flavor in ['nacl','sdk'] : target['input'] += ['nacl_helper', 'nacl_helper_bootstrap', 'pnacl'] if arch == 'x64': @@ -193,6 +194,7 @@ def generate_target_nw(platform_name, arch, version): ] if flavor == 'sdk': target['input'].append('nwjc.exe') + target['input'].append('payload.exe') if flavor in ['nacl','sdk'] : target['input'].append('pnacl') if arch == 'x64': @@ -206,6 +208,7 @@ def generate_target_nw(platform_name, arch, version): ] if flavor == 'sdk': target['input'].append('nwjc') + target['input'].append('payload') else: print 'Unsupported platform: ' + platform_name exit(-1) From 191d33c402e9ac6bb7ee1c9b9b2df93b51d64a3e Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 9 Dec 2015 14:32:54 +0800 Subject: [PATCH 120/404] bump version to 0.13.0-alpha8 --- src/nw_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nw_version.h b/src/nw_version.h index 49e276057c..6f988cca64 100644 --- a/src/nw_version.h +++ b/src/nw_version.h @@ -38,7 +38,7 @@ #else # define NW_VERSION_STRING NW_STRINGIFY(NW_MAJOR_VERSION) "." \ NW_STRINGIFY(NW_MINOR_VERSION) "." \ - NW_STRINGIFY(NW_PATCH_VERSION) "-alpha7" + NW_STRINGIFY(NW_PATCH_VERSION) "-alpha8" #endif #define NW_VERSION "v" NW_VERSION_STRING From cba84a8188fe83ee9cda084da2b263b5a51c899c Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 9 Dec 2015 19:00:19 +0800 Subject: [PATCH 121/404] fix payload compilation warning --- tools/payload.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/payload.cc b/tools/payload.cc index d001051aed..b95079661c 100644 --- a/tools/payload.cc +++ b/tools/payload.cc @@ -125,7 +125,7 @@ bool MyComputedHashes::Writer::WriteToFile(const base::FilePath& path) { if (!base::JSONWriter::Write(top_dictionary, &json)) return false; - int written = base::WriteFile(path, json.data(), json.size()); + int written = (int)base::WriteFile(path, json.data(), json.size()); if (static_cast(written) != json.size()) { LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe() << " ; write result:" << written << " expected:" << json.size(); From c7d0dcf48a47021895c7a1c1916e921dbfdb3e78 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 9 Dec 2015 19:35:15 +0800 Subject: [PATCH 122/404] fix previous commit --- tools/payload.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/payload.cc b/tools/payload.cc index b95079661c..77b9caa86e 100644 --- a/tools/payload.cc +++ b/tools/payload.cc @@ -125,7 +125,7 @@ bool MyComputedHashes::Writer::WriteToFile(const base::FilePath& path) { if (!base::JSONWriter::Write(top_dictionary, &json)) return false; - int written = (int)base::WriteFile(path, json.data(), json.size()); + int written = base::WriteFile(path, json.data(), (int)json.size()); if (static_cast(written) != json.size()) { LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe() << " ; write result:" << written << " expected:" << json.size(); From 7e50515bca28b32a554c6884341bdd125e3f233c Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 9 Dec 2015 20:59:25 +0800 Subject: [PATCH 123/404] remove unused variable of payload.cc --- tools/payload.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/payload.cc b/tools/payload.cc index 77b9caa86e..49d64d13cd 100644 --- a/tools/payload.cc +++ b/tools/payload.cc @@ -27,11 +27,11 @@ using crypto::SecureHash; namespace { const char kBlockHashesKey[] = "root_hash"; -const char kBlockSizeKey[] = "block_size"; +//const char kBlockSizeKey[] = "block_size"; const char kFileHashesKey[] = "files"; const char kPathKey[] = "path"; -const char kVersionKey[] = "version"; -const int kVersion = 2; +//const char kVersionKey[] = "version"; +//const int kVersion = 2; typedef std::set SortedFilePathSet; From 72c5d7afe73c854e2f9c046ab683cb9db3c44dfb Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 10 Dec 2015 10:55:30 +0800 Subject: [PATCH 124/404] add 'enable-gcm' switch --- src/nw_base.cc | 6 ++++++ src/nw_base.h | 1 + 2 files changed, 7 insertions(+) diff --git a/src/nw_base.cc b/src/nw_base.cc index c0a4b5abfc..aa84382733 100644 --- a/src/nw_base.cc +++ b/src/nw_base.cc @@ -1,6 +1,7 @@ #include "nw_base.h" #include "nw_package.h" +#include "base/command_line.h" namespace nw { @@ -44,5 +45,10 @@ const base::string16& GetCurrentNewWinManifest() { return g_current_new_win_manifest; } +bool gcm_enabled() { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + return command_line->HasSwitch("enable-gcm"); +} + } //namespace nw diff --git a/src/nw_base.h b/src/nw_base.h index 45a521e2da..d444142b29 100644 --- a/src/nw_base.h +++ b/src/nw_base.h @@ -11,6 +11,7 @@ namespace nw { NW_EXPORT void SetExitCode(int); NW_EXPORT void SetCurrentNewWinManifest(const base::string16& manifest); NW_EXPORT const base::string16& GetCurrentNewWinManifest(); + NW_EXPORT bool gcm_enabled(); } #endif From 66513dee82f873c2eae46ff4109e250f44b25978 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 11 Dec 2015 10:22:45 +0800 Subject: [PATCH 125/404] port Window.setShowInTaskbar --- src/api/nw_current_window_internal.idl | 1 + src/api/nw_window_api.cc | 56 ++++++++++++++++++++++++++ src/api/nw_window_api.h | 11 +++++ src/nw_content_mac.h | 6 ++- src/nw_content_mac.mm | 8 ++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index 921999136a..3a4a582598 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -30,5 +30,6 @@ namespace nw.currentWindowInternal { static void reloadIgnoringCache(); static double getZoom(); static void setZoom(double level); + static void setShowInTaskbar(boolean show); }; }; diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 0bd6661a64..e24845a85a 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -29,6 +29,8 @@ #include #include +#include "base/win/windows_version.h" +#include "ui/base/win/hidden_window.h" #include "ui/gfx/canvas.h" #include "ui/gfx/icon_util.h" #include "ui/gfx/font_list.h" @@ -594,6 +596,60 @@ bool NwCurrentWindowInternalIsKioskInternalFunction::RunNWSync(base::ListValue* return true; } +bool NwCurrentWindowInternalSetShowInTaskbarFunction::RunAsync() { + EXTENSION_FUNCTION_VALIDATE(args_); + bool show; + EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &show)); +#if defined(OS_WIN) + AppWindow* window = getAppWindow(this); + + native_app_window::NativeAppWindowViews* native_app_window_views = + static_cast( + window->GetBaseWindow()); + + views::Widget* widget = native_app_window_views->widget()->GetTopLevelWidget(); + + if (show == false && base::win::GetVersion() < base::win::VERSION_VISTA) { + // Change the owner of native window. Only needed on Windows XP. + ::SetWindowLong(views::HWNDForWidget(widget), + GWLP_HWNDPARENT, + (LONG)ui::GetHiddenWindow()); + } + + base::win::ScopedComPtr taskbar; + HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(result)) { + VLOG(1) << "Failed creating a TaskbarList object: " << result; + SendResponse(true); + return true; + } + + result = taskbar->HrInit(); + if (FAILED(result)) { + LOG(ERROR) << "Failed initializing an ITaskbarList interface."; + SendResponse(true); + return true; + } + + if (show) + result = taskbar->AddTab(views::HWNDForWidget(widget)); + else + result = taskbar->DeleteTab(views::HWNDForWidget(widget)); + + if (FAILED(result)) { + LOG(ERROR) << "Failed to change the show in taskbar attribute"; + SendResponse(true); + return true; + } +#elif defined(OS_MACOSX) + AppWindow* app_window = getAppWindow(this); + extensions::NativeAppWindow* native_window = app_window->GetBaseWindow(); + NWSetNSWindowShowInTaskbar(native_window, show); +#endif + SendResponse(true); + return true; +} } // namespace extensions diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 209fd12b06..15cbbee1ca 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -237,6 +237,17 @@ class NwCurrentWindowInternalIsKioskInternalFunction : public NWSyncExtensionFun DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.isKioskInternal", UNKNOWN) }; +class NwCurrentWindowInternalSetShowInTaskbarFunction : public AsyncExtensionFunction { + public: + NwCurrentWindowInternalSetShowInTaskbarFunction() {} + + protected: + ~NwCurrentWindowInternalSetShowInTaskbarFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.setShowInTaskbar", UNKNOWN) +}; } // namespace extensions #endif diff --git a/src/nw_content_mac.h b/src/nw_content_mac.h index 38092f431d..1a032ac9bb 100644 --- a/src/nw_content_mac.h +++ b/src/nw_content_mac.h @@ -5,6 +5,10 @@ namespace nw { class Menu; } -void NWChangeAppMenu(nw::Menu* menu); +namespace extensions { +class NativeAppWindow; +} +void NWChangeAppMenu(nw::Menu* menu); +void NWSetNSWindowShowInTaskbar(extensions::NativeAppWindow* win, bool show); #endif diff --git a/src/nw_content_mac.mm b/src/nw_content_mac.mm index 8b14a77638..3424d35c06 100644 --- a/src/nw_content_mac.mm +++ b/src/nw_content_mac.mm @@ -3,7 +3,15 @@ #import #include "content/nw/src/api/menu/menu.h" +#include "extensions/browser/app_window/native_app_window.h" +#import "ui/gfx/mac/nswindow_frame_controls.h" void NWChangeAppMenu(nw::Menu* menu) { [NSApp setMainMenu:menu->menu_]; } + +void NWSetNSWindowShowInTaskbar(extensions::NativeAppWindow* win, bool show) { + NSWindow* nswin = win->GetNativeWindow(); + gfx::SetNSWindowShowInTaskbar(nswin, show); +} + From 0370e4440efef3684dfd0a648a33f1c5e0ace988 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 11 Dec 2015 10:36:55 +0800 Subject: [PATCH 126/404] port Window.setPosition --- src/api/nw_window.idl | 1 + src/resources/api_nw_window.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 126e4004d2..3d964bbc8f 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -44,6 +44,7 @@ namespace nw.Window { static void maximize(); static void unmaximize(); static void restore(); + static void setPosition(DOMString pos); static void setMaximumSize(long width, long height); static void setMinimumSize(long width, long height); static void setResizable(boolean resizable); diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 46d7a8c6c1..4ccdc3f850 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -182,6 +182,16 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.setAlwaysOnTop = function (top) { this.appWindow.setAlwaysOnTop(top); }; + NWWindow.prototype.setPosition = function (pos) { + if (pos == "center") { + var screenWidth = screen.availWidth; + var screenHeight = screen.availHeight; + var width = this.appWindow.outerBounds.width; + var height = this.appWindow.outerBounds.height; + this.appWindow.outerBounds.setPosition(Math.round((screenWidth-width)/2), + Math.round((screenHeight-height)/2)); + } + }; NWWindow.prototype.isFullscreen = function () { return this.appWindow.isFullscreen(); }; From 7be0f0928d82cb8805ceb5ac0ac97b94189dd4d2 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 3 Dec 2015 19:38:33 +0800 Subject: [PATCH 127/404] Fixed menubar on Windows --- src/api/menu/menu.h | 29 +------ src/api/menu/menu_views.cc | 121 ----------------------------- src/api/menuitem/menuitem_views.cc | 2 - src/api/nw_window_api.cc | 27 ++----- src/nw_content.cc | 3 - 5 files changed, 9 insertions(+), 173 deletions(-) diff --git a/src/api/menu/menu.h b/src/api/menu/menu.h index a17465a8cf..5f200e00af 100644 --- a/src/api/menu/menu.h +++ b/src/api/menu/menu.h @@ -28,10 +28,6 @@ #include #include -#if defined(OS_WIN) -#include "ui/views/controls/menu/native_menu_win.h" -#endif - #if defined(OS_MACOSX) #if __OBJC__ @class NSMenu; @@ -120,32 +116,13 @@ class Menu : public Base { void Remove(MenuItem* menu_item, int pos); void Popup(int x, int y, content::RenderFrameHost*); -#if defined(OS_LINUX) - std::vector menu_items; -#endif - #if defined(OS_MACOSX) NSMenu* menu_; NWMenuDelegate* menu_delegate_; -#elif defined(OS_LINUX) +#elif defined(OS_LINUX) || defined(OS_WIN) - views::FocusManager *focus_manager_; - std::vector menu_items_; - extensions::AppWindow* window_; - // Flag to indicate the menu has been modified since last show, so we should - // rebuild the menu before next show. - bool is_menu_modified_; - - scoped_ptr menu_delegate_; - scoped_ptr menu_model_; void UpdateStates(); -#elif defined(OS_WIN) - - void Rebuild(const HMENU *parent_menu = NULL); - void UpdateStates(); - void SetWindow(extensions::AppWindow* win); - //**Never Try to free this pointer** //We get it from top widget views::FocusManager *focus_manager_; @@ -157,10 +134,6 @@ class Menu : public Base { scoped_ptr menu_delegate_; scoped_ptr menu_model_; - scoped_ptr menu_; - - // A container for the handles of the icon bitmap. - std::vector icon_bitmaps_; #endif DISALLOW_COPY_AND_ASSIGN(Menu); diff --git a/src/api/menu/menu_views.cc b/src/api/menu/menu_views.cc index ae9a2f309c..b536900e0a 100644 --- a/src/api/menu/menu_views.cc +++ b/src/api/menu/menu_views.cc @@ -38,41 +38,6 @@ #include "ui/views/focus/focus_manager.h" #include "vector" -#if defined(OS_WIN) -#include "ui/gfx/gdi_util.h" -#include "ui/gfx/icon_util.h" -#include "ui/views/controls/menu/menu_2.h" -#endif - -namespace { - -#if defined(OS_WIN) - -HBITMAP GetNativeBitmapFromSkBitmap(const SkBitmap& bitmap) { - int width = bitmap.width(); - int height = bitmap.height(); - - BITMAPV4HEADER native_bitmap_header; - gfx::CreateBitmapV4Header(width, height, &native_bitmap_header); - - HDC dc = ::GetDC(NULL); - void* bits; - HBITMAP native_bitmap = ::CreateDIBSection(dc, - reinterpret_cast(&native_bitmap_header), - DIB_RGB_COLORS, - &bits, - NULL, - 0); - DCHECK(native_bitmap); - ::ReleaseDC(NULL, dc); - bitmap.copyPixelsTo(bits, width * height * 4, width * 4); - return native_bitmap; -} - -#endif -} // namespace - - namespace ui { NwMenuModel::NwMenuModel(Delegate* delegate) : SimpleMenuModel(delegate) { @@ -87,39 +52,20 @@ bool NwMenuModel::HasIcons() const { namespace nw { -#if defined(OS_WIN) -// The width of the icon for the menuitem -static const int kIconWidth = 16; -// The height of the icon for the menuitem -static const int kIconHeight = 16; -#endif - void Menu::Create(const base::DictionaryValue& option) { is_menu_modified_ = true; menu_delegate_.reset(new MenuDelegate(object_manager())); menu_model_.reset(new ui::NwMenuModel(menu_delegate_.get())); -#if defined(OS_WIN) - menu_.reset(new views::NativeMenuWin(menu_model_.get(), NULL)); -#endif focus_manager_ = NULL; window_ = NULL; std::string type; -#if defined(OS_WIN) - if (option.GetString("type", &type) && type == "menubar") - menu_->set_is_popup_menu(false); -#endif menu_items_.empty(); } void Menu::Destroy() { -#if defined(OS_WIN) - for (size_t index = 0; index < icon_bitmaps_.size(); ++index) { - ::DeleteObject(icon_bitmaps_[index]); - } -#endif } void Menu::Append(MenuItem* menu_item) { @@ -189,54 +135,6 @@ void Menu::Popup(int x, int y, content::RenderFrameHost* rfh) { // menu_->RunMenuAt(screen_point, views::Menu2::ALIGN_TOPLEFT); } -#if defined(OS_WIN) -void Menu::Rebuild(const HMENU *parent_menu) { - if (is_menu_modified_) { - // Refresh menu before show. - menu_->Rebuild(NULL); - menu_->UpdateStates(); -#if 0 - for (size_t index = 0; index < icon_bitmaps_.size(); ++index) { - ::DeleteObject(icon_bitmaps_[index]); - } - icon_bitmaps_.clear(); - - HMENU native_menu = parent_menu == NULL ? - menu_->GetNativeMenu() : *parent_menu; - - for (int model_index = 0; - model_index < menu_model_->GetItemCount(); - ++model_index) { - int command_id = menu_model_->GetCommandIdAt(model_index); - - if (menu_model_->GetTypeAt(model_index) == ui::MenuModel::TYPE_COMMAND || - menu_model_->GetTypeAt(model_index) == ui::MenuModel::TYPE_SUBMENU) { - gfx::Image icon; - menu_model_->GetIconAt(model_index, &icon); - if (!icon.IsEmpty()) { - SkBitmap resized_bitmap = - skia::ImageOperations::Resize(*icon.ToSkBitmap(), - skia::ImageOperations::RESIZE_GOOD, - kIconWidth, - kIconHeight); - HBITMAP icon_bitmap = GetNativeBitmapFromSkBitmap(resized_bitmap); - ::SetMenuItemBitmaps(native_menu, command_id, MF_BYCOMMAND, - icon_bitmap, icon_bitmap); - icon_bitmaps_.push_back(icon_bitmap); - } - } - - MenuItem* item = object_manager()->GetApiObject(command_id); - if (item != NULL && item->submenu_) { - item->submenu_->Rebuild(&native_menu); - } - } -#endif - is_menu_modified_ = false; - } -} -#endif - void Menu::UpdateKeys(views::FocusManager *focus_manager){ if (focus_manager == NULL){ return ; @@ -251,26 +149,7 @@ void Menu::UpdateKeys(views::FocusManager *focus_manager){ } void Menu::UpdateStates() { -#if defined(OS_WIN) - if (window_) - window_->menu_->menu_->UpdateStates(); -#endif -} - -#if defined(OS_WIN) -void Menu::SetWindow(extensions::AppWindow* win) { - window_ = win; - for (int model_index = 0; - model_index < menu_model_->GetItemCount(); - ++model_index) { - int command_id = menu_model_->GetCommandIdAt(model_index); - MenuItem* item = object_manager()->GetApiObject(command_id); - if (item != NULL && item->submenu_) { - item->submenu_->SetWindow(win); - } - } } -#endif aura::Window* Menu::GetActiveNativeView(content::RenderFrameHost* rfh) { content::WebContents* web_contents = diff --git a/src/api/menuitem/menuitem_views.cc b/src/api/menuitem/menuitem_views.cc index b1bf23e972..0b4ab3fdc0 100644 --- a/src/api/menuitem/menuitem_views.cc +++ b/src/api/menuitem/menuitem_views.cc @@ -117,10 +117,8 @@ void MenuItem::SetLabel(const std::string& label) { is_modified_ = true; label_ = base::UTF8ToUTF16(label); -#if 0//FIXME if (menu_) menu_->UpdateStates(); -#endif } void MenuItem::SetIcon(const std::string& icon) { diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index e24845a85a..7f7394cdec 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -41,6 +41,9 @@ #if defined(OS_LINUX) #include "chrome/browser/ui/libgtk2ui/gtk2_ui.h" +#endif + +#if defined(OS_LINUX) || defined(OS_WIN) #include "content/nw/src/browser/menubar_view.h" #include "content/nw/src/browser/browser_view_layout.h" using nw::BrowserViewLayout; @@ -57,8 +60,11 @@ using ui_zoom::ZoomController; using nw::Menu; -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_WIN) using nw::MenuBarView; +#endif + +#if defined(OS_LINUX) static void SetDeskopEnvironment() { static bool runOnce = false; if (runOnce) return; @@ -327,7 +333,7 @@ bool NwCurrentWindowInternalSetMenuFunction::RunAsync() { NWChangeAppMenu(menu); #endif -#if defined(OS_LINUX) +#if defined(OS_LINUX) || defined(OS_WIN) native_app_window::NativeAppWindowViews* native_app_window_views = static_cast( window->GetBaseWindow()); @@ -338,23 +344,6 @@ bool NwCurrentWindowInternalSetMenuFunction::RunAsync() { menubar->UpdateMenu(menu->model()); native_app_window_views->layout_(); native_app_window_views->SchedulePaint(); -#endif - // The menu is lazily built. -#if defined(OS_WIN) //FIXME - menu->Rebuild(); - menu->SetWindow(window); - - native_app_window::NativeAppWindowViews* native_app_window_views = - static_cast( - window->GetBaseWindow()); - - // menu is nwapi::Menu, menu->menu_ is NativeMenuWin, - BOOL ret = ::SetMenu(views::HWNDForWidget(native_app_window_views->widget()->GetTopLevelWidget()), menu->menu_->GetNativeMenu()); - if (!ret) - LOG(ERROR) << "error setting menu"; - - ::DrawMenuBar(views::HWNDForWidget(native_app_window_views->widget()->GetTopLevelWidget())); - native_app_window_views->SchedulePaint(); #endif //FIXME menu->UpdateKeys( native_app_window_views->widget()->GetFocusManager() ); return true; diff --git a/src/nw_content.cc b/src/nw_content.cc index 6c5cb669d7..4715256033 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -835,9 +835,6 @@ bool ExecuteAppCommandHook(int command_id, extensions::AppWindow* app_window) { if (!menu) return false; menu->menu_delegate_->ExecuteCommand(command_id, 0); -#if defined(OS_WIN) - menu->menu_->UpdateStates(); -#endif return true; #endif //OSX } From 435e4609906fc4bf65f63b7d0f55603107a48e35 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Wed, 9 Dec 2015 10:01:17 +0800 Subject: [PATCH 128/404] Fixed menubar hotkey on Windows --- src/api/menu/menu_delegate.cc | 7 +- src/api/menuitem/menuitem.h | 1 - src/api/menuitem/menuitem_views.cc | 214 ++++++++++++----------------- src/api/nw_window_api.cc | 2 +- 4 files changed, 95 insertions(+), 129 deletions(-) diff --git a/src/api/menu/menu_delegate.cc b/src/api/menu/menu_delegate.cc index 780fc0dcff..670ebc8563 100644 --- a/src/api/menu/menu_delegate.cc +++ b/src/api/menu/menu_delegate.cc @@ -71,7 +71,12 @@ base::string16 MenuDelegate::GetLabelForCommandId(int command_id) const { bool MenuDelegate::GetAcceleratorForCommandId( int command_id, ui::Accelerator* accelerator) { - return false; + MenuItem* item = object_manager_->GetApiObject(command_id); + if (!item) + return false; + + *accelerator = item->accelerator_; + return true; } bool MenuDelegate::GetIconForCommandId(int command_id, diff --git a/src/api/menuitem/menuitem.h b/src/api/menuitem/menuitem.h index 43f3c007ed..cc92e526ad 100644 --- a/src/api/menuitem/menuitem.h +++ b/src/api/menuitem/menuitem.h @@ -118,7 +118,6 @@ class MenuItem : public Base { Menu* submenu_; bool enable_shortcut_; - bool super_down_flag_; bool meta_down_flag_; #endif diff --git a/src/api/menuitem/menuitem_views.cc b/src/api/menuitem/menuitem_views.cc index 0b4ab3fdc0..d26648e2bd 100644 --- a/src/api/menuitem/menuitem_views.cc +++ b/src/api/menuitem/menuitem_views.cc @@ -21,6 +21,7 @@ #include "content/nw/src/api/menuitem/menuitem.h" #include "base/files/file_path.h" +#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" @@ -31,20 +32,79 @@ #include "content/nw/src/nw_package.h" #include "ui/base/accelerators/accelerator.h" #include "ui/events/event_constants.h"//for modifier key code +#include "ui/events/keycodes/dom/keycode_converter.h" #include "ui/events/keycodes/keyboard_codes.h"//for keycode +#include "ui/events/keycodes/keyboard_code_conversion.h" #include "base/logging.h" -ui::KeyboardCode GetKeycodeFromText(std::string text); - namespace nw { +namespace { + +typedef std::map KeyMap; + +static KeyMap keymap = { + {"`" , "Backquote"}, + {"\\" , "Backslash"}, + {"[" , "BracketLeft"}, + {"]" , "BracketRight"}, + {"," , "Comma"}, + {"=" , "Equal"}, + {"-" , "Minus"}, + {"." , "Period"}, + {"'" , "Quote"}, + {";" , "Semicolon"}, + {"/" , "Slash"}, + {"\n" , "Enter"}, + {"\t" , "Tab"}, + {"UP" , "ArrowUp"}, + {"DOWN" , "ArrowDown"}, + {"LEFT" , "ArrowLeft"}, + {"RIGHT", "ArrowRight"}, + {"ESC" , "Escape"}, + {"MEDIANEXTTRACK", "MediaTrackNext"}, + {"MEDIAPREVTRACK", "MediaTrackPrevious"} +}; + +ui::KeyboardCode GetKeycodeFromText(std::string text){ + ui::KeyboardCode retval = ui::VKEY_UNKNOWN; + if (text.size() != 0){ + std::string upperText = base::ToUpperASCII(text); + std::string keyName = text; + bool found = false; + if (upperText.size() == 1){ + char key = upperText[0]; + if (key>='0' && key<='9'){//handle digital + keyName = "Digit" + upperText; + found = true; + } else if (key>='A'&&key<='Z'){//handle alphabet + keyName = "Key" + upperText; + found = true; + } + } + + if (!found) { + KeyMap::iterator it = keymap.find(upperText); + if (it != keymap.end()) { + keyName = it->second; + found = true; + } + } + + // build keyboard code + ui::DomCode domCode = ui::KeycodeConverter::CodeStringToDomCode(keyName.c_str()); + retval = ui::DomCodeToUsLayoutKeyboardCode(domCode); + } + return retval; +} +} // namespace + void MenuItem::Create(const base::DictionaryValue& option) { is_modified_ = false; is_checked_ = false; is_enabled_ = true; type_ = "normal"; submenu_ = NULL; - super_down_flag_ = false; meta_down_flag_ = false; focus_manager_ = NULL; @@ -63,33 +123,32 @@ void MenuItem::Create(const base::DictionaryValue& option) { ui::KeyboardCode keyval = ui::VKEY_UNKNOWN; - - if (key.size() == 0){ + keyval = GetKeycodeFromText(key); + if (keyval == ui::VKEY_UNKNOWN){ enable_shortcut_ = false; } else { enable_shortcut_ = true; - keyval = ::GetKeycodeFromText(key); - } - - //only code for ctrl, shift, alt, super and meta modifiers - int modifiers_value = ui::EF_NONE; - if (modifiers.find("ctrl")!=std::string::npos){ - modifiers_value = modifiers_value | ui::EF_CONTROL_DOWN; - } - if (modifiers.find("shift")!=std::string::npos){ - modifiers_value = modifiers_value | ui::EF_SHIFT_DOWN ; - } - if (modifiers.find("alt")!=std::string::npos){ - modifiers_value = modifiers_value | ui::EF_ALT_DOWN; - } - if (modifiers.find("super")!=std::string::npos){ - super_down_flag_ = true; - } - if (modifiers.find("meta")!=std::string::npos){ - meta_down_flag_ = true; + //only code for ctrl, shift, alt, super and meta modifiers + int modifiers_value = ui::EF_NONE; + if (modifiers.find("ctrl")!=std::string::npos){ + modifiers_value |= ui::EF_CONTROL_DOWN; + } + if (modifiers.find("shift")!=std::string::npos){ + modifiers_value |= ui::EF_SHIFT_DOWN ; + } + if (modifiers.find("alt")!=std::string::npos){ + modifiers_value |= ui::EF_ALT_DOWN; + } + if (modifiers.find("super")!=std::string::npos + || modifiers.find("cmd")!=std::string::npos + || modifiers.find("command")!=std::string::npos){ + modifiers_value |= ui::EF_COMMAND_DOWN; + } + if (modifiers.find("meta")!=std::string::npos){ + meta_down_flag_ = true; + } + accelerator_ = ui::Accelerator(keyval,modifiers_value); } - accelerator_ = ui::Accelerator(keyval,modifiers_value); - std::string icon; if (option.GetString("icon", &icon) && !icon.empty()) @@ -137,6 +196,7 @@ void MenuItem::SetIconIsTemplate(bool isTemplate) { } void MenuItem::SetTooltip(const std::string& tooltip) { + is_modified_ = true; tooltip_ = base::UTF8ToUTF16(tooltip); if (menu_) menu_->UpdateStates(); @@ -177,16 +237,9 @@ void MenuItem::UpdateKeys(views::FocusManager *focus_manager){ #if defined(OS_WIN) || defined(OS_LINUX) bool MenuItem::AcceleratorPressed(const ui::Accelerator& accelerator) { - #if defined(OS_WIN) - if (super_down_flag_){ - if ( ( (::GetKeyState(VK_LWIN) & 0x8000) != 0x8000) - || ( (::GetKeyState(VK_LWIN) & 0x8000) != 0x8000) ){ - return true; - } - } - if (meta_down_flag_){ - if ( (::GetKeyState(VK_APPS) & 0x8000) != 0x8000 ){ + if (meta_down_flag_) { + if ((::GetKeyState(VK_APPS) & 0x8000) != 0x8000) { return true; } } @@ -202,94 +255,3 @@ bool MenuItem::CanHandleAccelerators() const { #endif } // namespace nwapi - - -ui::KeyboardCode GetKeycodeFromText(std::string text){ - ui::KeyboardCode retval = ui::VKEY_UNKNOWN; - if (text.size() != 0){ - for (unsigned int i=0;i='0' && key<='9'){//handle digital - retval = (ui::KeyboardCode)(ui::VKEY_0 + key - '0'); - } else if (key>='A'&&key<='Z'){//handle alphabet - retval = (ui::KeyboardCode)(ui::VKEY_A + key - 'A'); - } else if (key == '`'){//handle all special symbols - retval = ui::VKEY_OEM_3; - } else if (key == ','){ - retval = ui::VKEY_OEM_COMMA; - } else if (key == '.'){ - retval = ui::VKEY_OEM_PERIOD; - } else if (key == '/'){ - retval = ui::VKEY_OEM_2; - } else if (key == ';'){ - retval = ui::VKEY_OEM_1; - } else if (key == '\''){ - retval = ui::VKEY_OEM_7; - } else if (key == '['){ - retval = ui::VKEY_OEM_4; - } else if (key == ']'){ - retval = ui::VKEY_OEM_6; - } else if (key == '\\'){ - retval = ui::VKEY_OEM_5; - } else if (key == '-'){ - retval = ui::VKEY_OEM_MINUS; - } else if (key == '='){ - retval = ui::VKEY_OEM_PLUS; - } - } else {//handle long key name - if (!text.compare("ESC")){ - retval = ui::VKEY_ESCAPE; - } else if (!text.compare("BACKSPACE")){ - retval = ui::VKEY_BACK; - } else if (!text.compare("F1")){ - retval = ui::VKEY_F1; - } else if (!text.compare("F2")){ - retval = ui::VKEY_F2; - } else if (!text.compare("F3")){ - retval = ui::VKEY_F3; - } else if (!text.compare("F4")){ - retval = ui::VKEY_F4; - } else if (!text.compare("F5")){ - retval = ui::VKEY_F5; - } else if (!text.compare("F6")){ - retval = ui::VKEY_F6; - } else if (!text.compare("F7")){ - retval = ui::VKEY_F7; - } else if (!text.compare("F8")){ - retval = ui::VKEY_F8; - } else if (!text.compare("F9")){ - retval = ui::VKEY_F9; - } else if (!text.compare("F10")){ - retval = ui::VKEY_F10; - } else if (!text.compare("F11")){ - retval = ui::VKEY_F11; - } else if (!text.compare("F12")){ - retval = ui::VKEY_F12; - } else if (!text.compare("INSERT")){ - retval = ui::VKEY_INSERT; - } else if (!text.compare("HOME")){ - retval = ui::VKEY_HOME; - } else if (!text.compare("DELETE")){ - retval = ui::VKEY_DELETE; - } else if (!text.compare("END")){ - retval = ui::VKEY_END; - } else if (!text.compare("PAGEUP")){ - retval = ui::VKEY_PRIOR; - } else if (!text.compare("PAGEDOWN")){ - retval = ui::VKEY_NEXT; - } else if (!text.compare("UP")){ - retval = ui::VKEY_UP; - } else if (!text.compare("LEFT")){ - retval = ui::VKEY_LEFT; - } else if (!text.compare("DOWN")){ - retval = ui::VKEY_DOWN; - } else if (!text.compare("RIGHT")){ - retval = ui::VKEY_RIGHT; - } - } - } - return retval; -} diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index 7f7394cdec..47a04222dd 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -344,8 +344,8 @@ bool NwCurrentWindowInternalSetMenuFunction::RunAsync() { menubar->UpdateMenu(menu->model()); native_app_window_views->layout_(); native_app_window_views->SchedulePaint(); + menu->UpdateKeys( native_app_window_views->widget()->GetFocusManager() ); #endif - //FIXME menu->UpdateKeys( native_app_window_views->widget()->GetFocusManager() ); return true; } From d1abb5f5047fb00febe1e7f7c7cde278cffac280 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 10 Dec 2015 09:12:32 +0800 Subject: [PATCH 129/404] Fixed icon in menubar --- src/api/menuitem/menuitem_views.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/api/menuitem/menuitem_views.cc b/src/api/menuitem/menuitem_views.cc index d26648e2bd..59c04aa5ce 100644 --- a/src/api/menuitem/menuitem_views.cc +++ b/src/api/menuitem/menuitem_views.cc @@ -31,6 +31,7 @@ #include "content/nw/src/nw_content.h" #include "content/nw/src/nw_package.h" #include "ui/base/accelerators/accelerator.h" +#include "ui/gfx/image/image_skia_operations.h" #include "ui/events/event_constants.h"//for modifier key code #include "ui/events/keycodes/dom/keycode_converter.h" #include "ui/events/keycodes/keyboard_codes.h"//for keycode @@ -41,6 +42,9 @@ namespace nw { namespace { +static const int kIconWidth = 16; +static const int kIconHeight = 16; + typedef std::map KeyMap; static KeyMap keymap = { @@ -188,8 +192,15 @@ void MenuItem::SetIcon(const std::string& icon) { return; } + gfx::Image originImage; nw::Package* package = nw::InitNWPackage(); - nw::GetImage(package, base::FilePath::FromUTF8Unsafe(icon), &icon_); + if (nw::GetImage(package, base::FilePath::FromUTF8Unsafe(icon), &originImage)) { + const gfx::ImageSkia* originImageSkia = originImage.ToImageSkia(); + gfx::ImageSkia resizedImageSkia = gfx::ImageSkiaOperations::CreateResizedImage(*originImageSkia, + skia::ImageOperations::RESIZE_GOOD, + gfx::Size(kIconWidth, kIconHeight)); + icon_ = gfx::Image(resizedImageSkia); + } } void MenuItem::SetIconIsTemplate(bool isTemplate) { From dbb786349616071291544ca5de67a8d4e7b3d951 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Thu, 10 Dec 2015 20:58:16 +0800 Subject: [PATCH 130/404] Create `nw.MenuItem` object and make it an instance of `EventEmitter` --- src/api/_api_features.json | 4 + src/api/nw_menu.idl | 1 - src/api/nw_menu_api.cc | 18 --- src/api/nw_menu_api.h | 13 -- src/resources/api_nw_menu.js | 239 ++++--------------------------- src/resources/api_nw_menuitem.js | 204 ++++++++++++++++++++++++++ 6 files changed, 233 insertions(+), 246 deletions(-) create mode 100644 src/resources/api_nw_menuitem.js diff --git a/src/api/_api_features.json b/src/api/_api_features.json index 690879ef96..36223fa35a 100644 --- a/src/api/_api_features.json +++ b/src/api/_api_features.json @@ -23,6 +23,10 @@ "channel": "stable", "contexts": ["blessed_extension"] }, + "nw.MenuItem": { + "channel": "stable", + "contexts": ["blessed_extension"] + }, "nw.Shell": { "channel": "stable", "contexts": ["blessed_extension"] diff --git a/src/api/nw_menu.idl b/src/api/nw_menu.idl index d6994c673e..fe71b0c5a9 100644 --- a/src/api/nw_menu.idl +++ b/src/api/nw_menu.idl @@ -32,7 +32,6 @@ namespace nw.Menu { static void createMacBuiltin(DOMString appname); }; interface Functions { - static object createItem(optional object options); [nocompile] static object createMenu(optional object options); [nocompile] static void destroy(long id); static DOMString getNSStringWithFixup(DOMString msg); diff --git a/src/api/nw_menu_api.cc b/src/api/nw_menu_api.cc index 47245e2010..250a8f4226 100644 --- a/src/api/nw_menu_api.cc +++ b/src/api/nw_menu_api.cc @@ -11,24 +11,6 @@ using nw::MenuItem; namespace extensions { -NwMenuCreateItemFunction::NwMenuCreateItemFunction() { -} - -NwMenuCreateItemFunction::~NwMenuCreateItemFunction() { -} - -bool NwMenuCreateItemFunction::RunNWSync(base::ListValue* response, std::string* error) { - base::DictionaryValue* props = nullptr; - int id = 0; - EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &props)); - EXTENSION_FUNCTION_VALIDATE( - props->GetInteger("generatedId", &id)); - - nw::ObjectManager* manager = nw::ObjectManager::Get(browser_context()); - manager->OnAllocateObject(id, "MenuItem", *props, extension_id()); - return true; -} - #ifndef OS_MACOSX bool NwMenuGetNSStringWithFixupFunction::RunNWSync(base::ListValue* response, std::string* error) { error_ = "NwMenuGetNSStringWithFixupFunction is only for OSX"; diff --git a/src/api/nw_menu_api.h b/src/api/nw_menu_api.h index 450c8dae30..78243d4e51 100644 --- a/src/api/nw_menu_api.h +++ b/src/api/nw_menu_api.h @@ -7,19 +7,6 @@ namespace extensions { -class NwMenuCreateItemFunction : public NWSyncExtensionFunction { - public: - NwMenuCreateItemFunction(); - bool RunNWSync(base::ListValue* response, std::string* error) override; - - protected: - ~NwMenuCreateItemFunction() override; - - DECLARE_EXTENSION_FUNCTION("nw.Menu.createItem", UNKNOWN) - private: - DISALLOW_COPY_AND_ASSIGN(NwMenuCreateItemFunction); -}; - class NwMenuGetNSStringWithFixupFunction : public NWSyncExtensionFunction { public: NwMenuGetNSStringWithFixupFunction(){} diff --git a/src/resources/api_nw_menu.js b/src/resources/api_nw_menu.js index d7981abc91..64ca9ffd6d 100644 --- a/src/resources/api_nw_menu.js +++ b/src/resources/api_nw_menu.js @@ -7,8 +7,6 @@ var contextMenuNatives = requireNative('context_menus'); var messagingNatives = requireNative('messaging_natives'); var Event = require('event_bindings').Event; -var menuItems = { objs : {}, clickEvent: {} }; - var Menu = function Menu (id, option) { if (option.type != 'contextmenu' && option.type != 'menubar') throw new TypeError('Invalid menu type: ' + option.type); @@ -56,306 +54,120 @@ Menu.prototype.createMacBuiltin = function (app_name, options) { var appleMenu = nw.Menu.createMenu({type:'menubar'}), options = options || {}; - appleMenu.append(nw.Menu.createItem({ + appleMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringFWithFixup("IDS_ABOUT_MAC", app_name), selector: "orderFrontStandardAboutPanel:" })); - appleMenu.append(nw.Menu.createItem({ + appleMenu.append(new nw.MenuItem({ type: "separator" })); - appleMenu.append(nw.Menu.createItem({ + appleMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringFWithFixup("IDS_HIDE_APP_MAC", app_name), selector: "hide:", modifiers: "cmd", key: "h" })); - appleMenu.append(nw.Menu.createItem({ + appleMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_HIDE_OTHERS_MAC"), selector: "hideOtherApplications:", key: "h", modifiers: "cmd-alt" })); - appleMenu.append(nw.Menu.createItem({ + appleMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_SHOW_ALL_MAC"), selector: "unhideAllApplications:", })); - appleMenu.append(nw.Menu.createItem({ + appleMenu.append(new nw.MenuItem({ type: "separator" })); - appleMenu.append(nw.Menu.createItem({ + appleMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringFWithFixup("IDS_EXIT_MAC", app_name), selector: "closeAllWindowsQuit:", modifiers: "cmd", key: "q" })); - this.append(nw.Menu.createItem({ label:'', submenu: appleMenu})); + this.append(new nw.MenuItem({ label:'', submenu: appleMenu})); if (!options.hideEdit) { var editMenu = nw.Menu.createMenu({type:'menubar'}); - editMenu.append(nw.Menu.createItem({ + editMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_EDIT_UNDO_MAC"), selector: "undo:", modifiers: "cmd", key: "z" })); - editMenu.append(nw.Menu.createItem({ + editMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_EDIT_REDO_MAC"), selector: "redo:", key: "z", modifiers: "cmd-shift" })); - editMenu.append(nw.Menu.createItem({ + editMenu.append(new nw.MenuItem({ type: "separator" })); - editMenu.append(nw.Menu.createItem({ + editMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_CUT_MAC"), selector: "cut:", modifiers: "cmd", key: "x" })); - editMenu.append(nw.Menu.createItem({ + editMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_COPY_MAC"), selector: "copy:", modifiers: "cmd", key: "c" })); - editMenu.append(nw.Menu.createItem({ + editMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_PASTE_MAC"), selector: "paste:", modifiers: "cmd", key: "v" })); - editMenu.append(nw.Menu.createItem({ + editMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_EDIT_DELETE_MAC"), selector: "delete:", key: "" })); - editMenu.append(nw.Menu.createItem({ + editMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_EDIT_SELECT_ALL_MAC"), selector: "selectAll:", modifiers: "cmd", key: "a" })); - this.append(nw.Menu.createItem({ label: nw.Menu.getNSStringWithFixup("IDS_EDIT_MENU_MAC"), + this.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_EDIT_MENU_MAC"), submenu: editMenu})); } if (!options.hideWindow) { var winMenu = nw.Menu.createMenu({type:'menubar'}); - winMenu.append(nw.Menu.createItem({ + winMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_MINIMIZE_WINDOW_MAC"), selector: "performMiniaturize:", modifiers: "cmd", key: "m" })); - winMenu.append(nw.Menu.createItem({ + winMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_CLOSE_WINDOW_MAC"), selector: "performClose:", modifiers: "cmd", key: "w" })); - winMenu.append(nw.Menu.createItem({ + winMenu.append(new nw.MenuItem({ type: "separator" })); - winMenu.append(nw.Menu.createItem({ + winMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_ALL_WINDOWS_FRONT_MAC"), selector: "arrangeInFront:", })); - this.append(nw.Menu.createItem({ label: nw.Menu.getNSStringWithFixup("IDS_WINDOW_MENU_MAC"), + this.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_WINDOW_MENU_MAC"), submenu: winMenu})); } } - -function MenuItem(id, option) { - if (typeof option != 'object') - throw new TypeError('Invalid option.'); - - if (!option.hasOwnProperty('type')) - option.type = 'normal'; - - if (option.type != 'normal' && - option.type != 'checkbox' && - option.type != 'separator') - throw new TypeError('Invalid MenuItem type: ' + option.type); - - if (option.type == 'normal' || option.type == 'checkbox') { - if (option.type == 'checkbox') - option.checked = Boolean(option.checked); - - if (!option.hasOwnProperty('label')) - throw new TypeError('A normal MenuItem must have a label'); - else - option.label = String(option.label); - - if (option.hasOwnProperty('icon')) { - option.shadowIcon = String(option.icon); - option.icon = nwNative.getAbsolutePath(option.icon); - } - - if (option.hasOwnProperty('iconIsTemplate')) - option.iconIsTemplate = Boolean(option.iconIsTemplate); - else - option.iconIsTemplate = true; - - if (option.hasOwnProperty('tooltip')) - option.tooltip = String(option.tooltip); - - if (option.hasOwnProperty('enabled')) - option.enabled = Boolean(option.enabled); - - if (option.hasOwnProperty('submenu')) { - // Transfer only object id - privates(this).submenu = option.submenu; - option.submenu = option.submenu.id; - } - - if (option.hasOwnProperty('click')) { - if (typeof option.click != 'function') - throw new TypeError("'click' must be a valid Function"); - else - this.click = option.click; - } - } else if (option.type == 'separator') { - option = { - type: 'separator' - }; - } - - this.id = id; - privates(this).option = option; - - menuItems.objs[id] = this; - // All properties must be set after initialization. - if (!option.hasOwnProperty('icon')) - option.shadowIcon = ''; - if (!option.hasOwnProperty('tooltip')) - option.tooltip = ''; - if (!option.hasOwnProperty('enabled')) - option.enabled = true; - if (!option.hasOwnProperty('key')) - option.key = ""; - if (!option.hasOwnProperty('modifiers')) - option.modifiers = ""; -} - -MenuItem.prototype.handleGetter = function(name) { - return privates(this).option[name]; -}; - -MenuItem.prototype.handleSetter = function(name, setter, type, value) { - value = type(value); - privates(this).option[name] = value; - nw.Obj.callObjectMethod(this.id, 'MenuItem', setter, [ value ]); -}; - -MenuItem.prototype.__defineGetter__('type', function() { - return this.handleGetter('type'); -}); - -MenuItem.prototype.__defineSetter__('type', function() { - throw new Error("'type' is immutable at runtime"); -}); - -MenuItem.prototype.__defineGetter__('label', function() { - return this.handleGetter('label'); -}); - -MenuItem.prototype.__defineSetter__('label', function(val) { - this.handleSetter('label', 'SetLabel', String, val); -}); - -MenuItem.prototype.__defineGetter__('icon', function() { - return this.handleGetter('shadowIcon'); -}); - -MenuItem.prototype.__defineSetter__('icon', function(val) { - privates(this).option.shadowIcon = String(val); - var real_path = val == '' ? '' : nwNative.getAbsolutePath(val); //FIXME - this.handleSetter('icon', 'SetIcon', String, real_path); -}); - -MenuItem.prototype.__defineGetter__('iconIsTemplate', function() { - return this.handleGetter('iconIsTemplate'); -}); - -MenuItem.prototype.__defineSetter__('iconIsTemplate', function(val) { - this.handleSetter('iconIsTemplate', 'SetIconIsTemplate', Boolean, val); -}); - -MenuItem.prototype.__defineGetter__('tooltip', function() { - return this.handleGetter('tooltip'); -}); - -MenuItem.prototype.__defineSetter__('tooltip', function(val) { - this.handleSetter('tooltip', 'SetTooltip', String, val); -}); - -MenuItem.prototype.__defineGetter__('key', function() { - return this.handleGetter('key'); -}); - -MenuItem.prototype.__defineSetter__('key', function(val) { - this.handleSetter('key', 'SetKey', String, val); -}); - -MenuItem.prototype.__defineGetter__('modifiers', function() { - return this.handleGetter('modifiers'); -}); - -MenuItem.prototype.__defineSetter__('modifiers', function(val) { - this.handleSetter('modifiers', 'SetModifiers', String, val); -}); - -MenuItem.prototype.__defineGetter__('checked', function() { - if (this.type != 'checkbox') - return undefined; - - return this.handleGetter('checked'); -}); - -MenuItem.prototype.__defineSetter__('checked', function(val) { - if (this.type != 'checkbox') - throw new TypeError("'checked' property is only available for checkbox"); - - this.handleSetter('checked', 'SetChecked', Boolean, val); -}); - -MenuItem.prototype.__defineGetter__('enabled', function() { - return this.handleGetter('enabled'); -}); - -MenuItem.prototype.__defineSetter__('enabled', function(val) { - this.handleSetter('enabled', 'SetEnabled', Boolean, val); -}); - -MenuItem.prototype.__defineGetter__('submenu', function() { - return privates(this).submenu; -}); - -MenuItem.prototype.__defineSetter__('submenu', function(val) { - privates(this).submenu = val; - nw.Obj.callObjectMethod(this.id, 'MenuItem', 'SetSubmenu', [ val.id ]); -}); - nw_binding.registerCustomHook(function(bindingsAPI) { var apiFunctions = bindingsAPI.apiFunctions; - menuItems.clickEvent = new Event("NWObjectclick"); - menuItems.clickEvent.addListener(function(id) { - if (!menuItems.objs[id]) - return; - menuItems.objs[id].click(); - }); - apiFunctions.setHandleRequest('createItem', function(option) { - var id = contextMenuNatives.GetNextContextMenuId(); - option.generatedId = id; - var ret = new MenuItem(id, option); - sendRequest.sendRequestSync('nw.Menu.createItem', [option], this.definition.parameters, {}); - messagingNatives.BindToGC(ret, nw.Menu.destroy.bind(undefined, id), -1); - return ret; - }); apiFunctions.setHandleRequest('destroy', function(id) { - sendRequest.sendRequestSync('nw.Obj.destroy', [id], this.definition.parameters, {}); + sendRequest.sendRequestSync('nw.Obj.destroy', arguments, this.definition.parameters, {}); }); apiFunctions.setHandleRequest('createMenu', function(option) { var id = contextMenuNatives.GetNextContextMenuId(); @@ -369,12 +181,11 @@ nw_binding.registerCustomHook(function(bindingsAPI) { return ret; }); apiFunctions.setHandleRequest('getNSStringWithFixup', function(msg) { - return sendRequest.sendRequestSync('nw.Menu.getNSStringWithFixup', [msg], this.definition.parameters, {})[0]; + return sendRequest.sendRequestSync(this.name, arguments, this.definition.parameters, {})[0]; }); apiFunctions.setHandleRequest('getNSStringFWithFixup', function(msg, appName) { - return sendRequest.sendRequestSync('nw.Menu.getNSStringFWithFixup', [msg, appName], this.definition.parameters, {})[0]; + return sendRequest.sendRequestSync(this.name, arguments, this.definition.parameters, {})[0]; }); }); exports.binding = nw_binding.generate(); - diff --git a/src/resources/api_nw_menuitem.js b/src/resources/api_nw_menuitem.js new file mode 100644 index 0000000000..f83de547be --- /dev/null +++ b/src/resources/api_nw_menuitem.js @@ -0,0 +1,204 @@ +var Binding = require('binding').Binding; +var forEach = require('utils').forEach; +var nw_binding = require('binding').Binding.create('nw.Menu'); +var nwNative = requireNative('nw_natives'); +var sendRequest = require('sendRequest'); +var contextMenuNatives = requireNative('context_menus'); +var messagingNatives = requireNative('messaging_natives'); +var Event = require('event_bindings').Event; +var util = nw.require('util'); +var EventEmitter = nw.require('events').EventEmitter; + +var menuItems = { objs : {}, clickEvent: {} }; +menuItems.clickEvent = new Event("NWObjectclick"); +menuItems.clickEvent.addListener(function(id) { + if (!menuItems.objs[id]) + return; + menuItems.objs[id].click(); + menuItems.objs[id].emit('click'); +}); + +function MenuItem(option) { + if (!(this instanceof MenuItem)) { + return new MenuItem(option); + } + EventEmitter.apply(this); + + if (typeof option != 'object') + throw new TypeError('Invalid option.'); + + if (!option.hasOwnProperty('type')) + option.type = 'normal'; + + if (option.type != 'normal' && + option.type != 'checkbox' && + option.type != 'separator') + throw new TypeError('Invalid MenuItem type: ' + option.type); + + if (option.type == 'normal' || option.type == 'checkbox') { + if (option.type == 'checkbox') + option.checked = Boolean(option.checked); + + if (!option.hasOwnProperty('label')) + throw new TypeError('A normal MenuItem must have a label'); + else + option.label = String(option.label); + + if (option.hasOwnProperty('icon')) { + option.shadowIcon = String(option.icon); + option.icon = nwNative.getAbsolutePath(option.icon); + } + + if (option.hasOwnProperty('iconIsTemplate')) + option.iconIsTemplate = Boolean(option.iconIsTemplate); + else + option.iconIsTemplate = true; + + if (option.hasOwnProperty('tooltip')) + option.tooltip = String(option.tooltip); + + if (option.hasOwnProperty('enabled')) + option.enabled = Boolean(option.enabled); + + if (option.hasOwnProperty('submenu')) { + // Transfer only object id + privates(this).submenu = option.submenu; + option.submenu = option.submenu.id; + } + + if (option.hasOwnProperty('click')) { + if (typeof option.click != 'function') + throw new TypeError("'click' must be a valid Function"); + else + this.click = option.click; + } + } else if (option.type == 'separator') { + option = { + type: 'separator' + }; + } + + var id = contextMenuNatives.GetNextContextMenuId(); + this.id = id; + privates(this).option = option; + + menuItems.objs[id] = this; + // All properties must be set after initialization. + if (!option.hasOwnProperty('icon')) + option.shadowIcon = ''; + if (!option.hasOwnProperty('tooltip')) + option.tooltip = ''; + if (!option.hasOwnProperty('enabled')) + option.enabled = true; + if (!option.hasOwnProperty('key')) + option.key = ""; + if (!option.hasOwnProperty('modifiers')) + option.modifiers = ""; + + nw.Obj.create(id, 'MenuItem', option); + messagingNatives.BindToGC(this, nw.Menu.destroy.bind(undefined, id), -1); + +} + +util.inherits(MenuItem, EventEmitter); + +MenuItem.prototype.handleGetter = function(name) { + return privates(this).option[name]; +}; + +MenuItem.prototype.handleSetter = function(name, setter, type, value) { + value = type(value); + privates(this).option[name] = value; + nw.Obj.callObjectMethod(this.id, 'MenuItem', setter, [ value ]); +}; + +MenuItem.prototype.__defineGetter__('type', function() { + return this.handleGetter('type'); +}); + +MenuItem.prototype.__defineSetter__('type', function() { + throw new Error("'type' is immutable at runtime"); +}); + +MenuItem.prototype.__defineGetter__('label', function() { + return this.handleGetter('label'); +}); + +MenuItem.prototype.__defineSetter__('label', function(val) { + this.handleSetter('label', 'SetLabel', String, val); +}); + +MenuItem.prototype.__defineGetter__('icon', function() { + return this.handleGetter('shadowIcon'); +}); + +MenuItem.prototype.__defineSetter__('icon', function(val) { + privates(this).option.shadowIcon = String(val); + var real_path = val == '' ? '' : nwNative.getAbsolutePath(val); //FIXME + this.handleSetter('icon', 'SetIcon', String, real_path); +}); + +MenuItem.prototype.__defineGetter__('iconIsTemplate', function() { + return this.handleGetter('iconIsTemplate'); +}); + +MenuItem.prototype.__defineSetter__('iconIsTemplate', function(val) { + this.handleSetter('iconIsTemplate', 'SetIconIsTemplate', Boolean, val); +}); + +MenuItem.prototype.__defineGetter__('tooltip', function() { + return this.handleGetter('tooltip'); +}); + +MenuItem.prototype.__defineSetter__('tooltip', function(val) { + this.handleSetter('tooltip', 'SetTooltip', String, val); +}); + +MenuItem.prototype.__defineGetter__('key', function() { + return this.handleGetter('key'); +}); + +MenuItem.prototype.__defineSetter__('key', function(val) { + this.handleSetter('key', 'SetKey', String, val); +}); + +MenuItem.prototype.__defineGetter__('modifiers', function() { + return this.handleGetter('modifiers'); +}); + +MenuItem.prototype.__defineSetter__('modifiers', function(val) { + this.handleSetter('modifiers', 'SetModifiers', String, val); +}); + +MenuItem.prototype.__defineGetter__('checked', function() { + if (this.type != 'checkbox') + return undefined; + + return this.handleGetter('checked'); +}); + +MenuItem.prototype.__defineSetter__('checked', function(val) { + if (this.type != 'checkbox') + throw new TypeError("'checked' property is only available for checkbox"); + + this.handleSetter('checked', 'SetChecked', Boolean, val); +}); + +MenuItem.prototype.__defineGetter__('enabled', function() { + return this.handleGetter('enabled'); +}); + +MenuItem.prototype.__defineSetter__('enabled', function(val) { + this.handleSetter('enabled', 'SetEnabled', Boolean, val); +}); + +MenuItem.prototype.__defineGetter__('submenu', function() { + return privates(this).submenu; +}); + +MenuItem.prototype.__defineSetter__('submenu', function(val) { + privates(this).submenu = val; + nw.Obj.callObjectMethod(this.id, 'MenuItem', 'SetSubmenu', [ val.id ]); +}); + +exports.binding = MenuItem; \ No newline at end of file From 0f4d2ac0a2510b2e5f2a820b987d3163a084c601 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Fri, 11 Dec 2015 10:02:48 +0800 Subject: [PATCH 131/404] Rename `nw.Menu.createMenu` to `nw.Menu` --- src/api/nw_menu.idl | 1 - src/resources/api_nw_menu.js | 38 +++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/api/nw_menu.idl b/src/api/nw_menu.idl index fe71b0c5a9..9057ccd971 100644 --- a/src/api/nw_menu.idl +++ b/src/api/nw_menu.idl @@ -32,7 +32,6 @@ namespace nw.Menu { static void createMacBuiltin(DOMString appname); }; interface Functions { - [nocompile] static object createMenu(optional object options); [nocompile] static void destroy(long id); static DOMString getNSStringWithFixup(DOMString msg); static DOMString getNSStringFWithFixup(DOMString msg, DOMString appName); diff --git a/src/resources/api_nw_menu.js b/src/resources/api_nw_menu.js index 64ca9ffd6d..ffe42b304a 100644 --- a/src/resources/api_nw_menu.js +++ b/src/resources/api_nw_menu.js @@ -7,14 +7,26 @@ var contextMenuNatives = requireNative('context_menus'); var messagingNatives = requireNative('messaging_natives'); var Event = require('event_bindings').Event; -var Menu = function Menu (id, option) { +function Menu (option) { + if (!(this instanceof Menu)) { + return new Menu(option); + } + + if (typeof option != 'object' || !option) + option = { type: 'contextmenu' }; if (option.type != 'contextmenu' && option.type != 'menubar') throw new TypeError('Invalid menu type: ' + option.type); + var id = contextMenuNatives.GetNextContextMenuId(); + option.generatedId = id; + this.id = id; this.type = option.type; privates(this).items = []; privates(this).option = option; + + nw.Obj.create(id, 'Menu', option); + messagingNatives.BindToGC(this, nw.Menu.destroy.bind(undefined, id), -1); }; Menu.prototype.__defineGetter__('items', function() { @@ -51,7 +63,7 @@ Menu.prototype.popup = function(x, y) { } Menu.prototype.createMacBuiltin = function (app_name, options) { - var appleMenu = nw.Menu.createMenu({type:'menubar'}), + var appleMenu = new nw.Menu({type:'menubar'}), options = options || {}; appleMenu.append(new nw.MenuItem({ @@ -89,7 +101,7 @@ Menu.prototype.createMacBuiltin = function (app_name, options) { this.append(new nw.MenuItem({ label:'', submenu: appleMenu})); if (!options.hideEdit) { - var editMenu = nw.Menu.createMenu({type:'menubar'}); + var editMenu = new nw.Menu({type:'menubar'}); editMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_EDIT_UNDO_MAC"), selector: "undo:", @@ -139,7 +151,7 @@ Menu.prototype.createMacBuiltin = function (app_name, options) { } if (!options.hideWindow) { - var winMenu = nw.Menu.createMenu({type:'menubar'}); + var winMenu = new nw.Menu({type:'menubar'}); winMenu.append(new nw.MenuItem({ label: nw.Menu.getNSStringWithFixup("IDS_MINIMIZE_WINDOW_MAC"), selector: "performMiniaturize:", @@ -169,17 +181,6 @@ nw_binding.registerCustomHook(function(bindingsAPI) { apiFunctions.setHandleRequest('destroy', function(id) { sendRequest.sendRequestSync('nw.Obj.destroy', arguments, this.definition.parameters, {}); }); - apiFunctions.setHandleRequest('createMenu', function(option) { - var id = contextMenuNatives.GetNextContextMenuId(); - if (typeof option != 'object' || !option) - option = { type: 'contextmenu' }; - - option.generatedId = id; - var ret = new Menu(id, option); - sendRequest.sendRequestSync('nw.Obj.create', [id, 'Menu', option], this.definition.parameters, {}); - messagingNatives.BindToGC(ret, nw.Menu.destroy.bind(undefined, id), -1); - return ret; - }); apiFunctions.setHandleRequest('getNSStringWithFixup', function(msg) { return sendRequest.sendRequestSync(this.name, arguments, this.definition.parameters, {})[0]; }); @@ -188,4 +189,9 @@ nw_binding.registerCustomHook(function(bindingsAPI) { }); }); -exports.binding = nw_binding.generate(); +var nwMenuBinding = nw_binding.generate(); +Menu.destroy = nwMenuBinding.destroy; +Menu.getNSStringWithFixup = nwMenuBinding.getNSStringWithFixup; +Menu.getNSStringFWithFixup = nwMenuBinding.getNSStringFWithFixup; + +exports.binding = Menu; From 73757994435bb1a7b1cc2f4ae3abd2415507b926 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Fri, 11 Dec 2015 10:10:06 +0800 Subject: [PATCH 132/404] Removed deps to `nw.Menu.destroy` --- src/api/nw_menu.idl | 27 --------------------------- src/resources/api_nw_menu.js | 6 +----- src/resources/api_nw_menuitem.js | 2 +- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/src/api/nw_menu.idl b/src/api/nw_menu.idl index 9057ccd971..0fdae61de9 100644 --- a/src/api/nw_menu.idl +++ b/src/api/nw_menu.idl @@ -5,34 +5,7 @@ // nw Menu API [implemented_in="content/nw/src/api/nw_menu_api.h"] namespace nw.Menu { - callback EventCallback = void(); - [noinline_doc] dictionary MenuItem { - [nodoc] DOMString? type; - [nodoc] DOMString? title; - DOMString icon; - boolean iconIsTemplate; - DOMString tooltip; - boolean checked; - boolean enabled; - object submenu; - DOMString key; - DOMString modifiers; - object click; - static void on(DOMString event, - EventCallback callback); - }; - [noinline_doc] dictionary Menu { - [nodoc] MenuItem[] items; - - static void append([instanceOf=MenuItem] object item); - static void insert([instanceOf=MenuItem] object item, long index); - static void remove([instanceOf=MenuItem] object item); - static void removeAt(long index); - static void popup(long x, long y); - static void createMacBuiltin(DOMString appname); - }; interface Functions { - [nocompile] static void destroy(long id); static DOMString getNSStringWithFixup(DOMString msg); static DOMString getNSStringFWithFixup(DOMString msg, DOMString appName); }; diff --git a/src/resources/api_nw_menu.js b/src/resources/api_nw_menu.js index ffe42b304a..3a01250cee 100644 --- a/src/resources/api_nw_menu.js +++ b/src/resources/api_nw_menu.js @@ -26,7 +26,7 @@ function Menu (option) { privates(this).option = option; nw.Obj.create(id, 'Menu', option); - messagingNatives.BindToGC(this, nw.Menu.destroy.bind(undefined, id), -1); + messagingNatives.BindToGC(this, nw.Obj.destroy.bind(undefined, id), -1); }; Menu.prototype.__defineGetter__('items', function() { @@ -178,9 +178,6 @@ Menu.prototype.createMacBuiltin = function (app_name, options) { nw_binding.registerCustomHook(function(bindingsAPI) { var apiFunctions = bindingsAPI.apiFunctions; - apiFunctions.setHandleRequest('destroy', function(id) { - sendRequest.sendRequestSync('nw.Obj.destroy', arguments, this.definition.parameters, {}); - }); apiFunctions.setHandleRequest('getNSStringWithFixup', function(msg) { return sendRequest.sendRequestSync(this.name, arguments, this.definition.parameters, {})[0]; }); @@ -190,7 +187,6 @@ nw_binding.registerCustomHook(function(bindingsAPI) { }); var nwMenuBinding = nw_binding.generate(); -Menu.destroy = nwMenuBinding.destroy; Menu.getNSStringWithFixup = nwMenuBinding.getNSStringWithFixup; Menu.getNSStringFWithFixup = nwMenuBinding.getNSStringFWithFixup; diff --git a/src/resources/api_nw_menuitem.js b/src/resources/api_nw_menuitem.js index f83de547be..8a957697ea 100644 --- a/src/resources/api_nw_menuitem.js +++ b/src/resources/api_nw_menuitem.js @@ -96,7 +96,7 @@ function MenuItem(option) { option.modifiers = ""; nw.Obj.create(id, 'MenuItem', option); - messagingNatives.BindToGC(this, nw.Menu.destroy.bind(undefined, id), -1); + messagingNatives.BindToGC(this, nw.Obj.destroy.bind(undefined, id), -1); } From 682bf7df63664226b78500b0928c146cef7978af Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 11 Dec 2015 15:28:47 +0800 Subject: [PATCH 133/404] hide internal functions in nw.Window --- src/resources/api_nw_window.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 4ccdc3f850..e54656786e 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -33,7 +33,8 @@ nw_binding.registerCustomHook(function(bindingsAPI) { privates(this).menu = null; }; forEach(currentNWWindowInternal, function(key, value) { - NWWindow.prototype[key] = value; + if (!key.endsWith('Internal')) + NWWindow.prototype[key] = value; }); NWWindow.prototype.onNewWinPolicy = new Event(); From da7cdecfd8d7653c036afbb23791ce2ab70d128e Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 11 Dec 2015 15:29:02 +0800 Subject: [PATCH 134/404] port Window.requestAttention(Boolean) --- src/api/nw_current_window_internal.idl | 2 +- src/api/nw_window_api.cc | 8 ++------ src/api/nw_window_api.h | 10 +++++----- src/resources/api_nw_window.js | 5 +++++ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/api/nw_current_window_internal.idl b/src/api/nw_current_window_internal.idl index 3a4a582598..8df94e2992 100644 --- a/src/api/nw_current_window_internal.idl +++ b/src/api/nw_current_window_internal.idl @@ -18,7 +18,7 @@ namespace nw.currentWindowInternal { static void showDevToolsInternal(optional ShowDevToolsCallback callback); static void closeDevTools(); static void setBadgeLabel(DOMString badge); - static void requestAttention(long count); + static void requestAttentionInternal(long count); static void setProgressBar(double progress); static void enterKioskMode(); static void leaveKioskMode(); diff --git a/src/api/nw_window_api.cc b/src/api/nw_window_api.cc index e24845a85a..8864be6599 100644 --- a/src/api/nw_window_api.cc +++ b/src/api/nw_window_api.cc @@ -436,7 +436,7 @@ bool NwCurrentWindowInternalSetBadgeLabelFunction::RunAsync() { return true; } -bool NwCurrentWindowInternalRequestAttentionFunction::RunAsync() { +bool NwCurrentWindowInternalRequestAttentionInternalFunction::RunAsync() { EXTENSION_FUNCTION_VALIDATE(args_); int count; EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &count)); @@ -458,17 +458,13 @@ bool NwCurrentWindowInternalRequestAttentionFunction::RunAsync() { fwi.dwFlags = FLASHW_STOP; } FlashWindowEx(&fwi); -#elif defined(OS_LINUX) +#elif defined(OS_LINUX) || defined(OS_MACOSX) AppWindow* window = getAppWindow(this); if (!window) { error_ = kNoAssociatedAppWindow; return false; } window->GetBaseWindow()->FlashFrame(count); -#else - error_ = "NwCurrentWindowInternalRequestAttentionFunction NOT Implemented" - NOTIMPLEMENTED() << error_; - return false; #endif return true; } diff --git a/src/api/nw_window_api.h b/src/api/nw_window_api.h index 15cbbee1ca..5aba3b852d 100644 --- a/src/api/nw_window_api.h +++ b/src/api/nw_window_api.h @@ -131,18 +131,18 @@ class NwCurrentWindowInternalSetBadgeLabelFunction : public AsyncExtensionFuncti DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.setBadgeLabel", UNKNOWN) }; -class NwCurrentWindowInternalRequestAttentionFunction : public AsyncExtensionFunction { +class NwCurrentWindowInternalRequestAttentionInternalFunction : public AsyncExtensionFunction { public: - NwCurrentWindowInternalRequestAttentionFunction(){} + NwCurrentWindowInternalRequestAttentionInternalFunction(){} protected: - ~NwCurrentWindowInternalRequestAttentionFunction() override {} + ~NwCurrentWindowInternalRequestAttentionInternalFunction() override {} // ExtensionFunction: bool RunAsync() override; - DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.requestAttention", UNKNOWN) + DECLARE_EXTENSION_FUNCTION("nw.currentWindowInternal.requestAttentionInternal", UNKNOWN) private: - DISALLOW_COPY_AND_ASSIGN(NwCurrentWindowInternalRequestAttentionFunction); + DISALLOW_COPY_AND_ASSIGN(NwCurrentWindowInternalRequestAttentionInternalFunction); }; class NwCurrentWindowInternalSetProgressBarFunction : public AsyncExtensionFunction { diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index e54656786e..1129704824 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -229,6 +229,11 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.setResizable = function (resizable) { this.appWindow.setResizable(resizable); }; + NWWindow.prototype.requestAttention = function (flash) { + if (typeof flash == 'boolean') + flash = flash ? -1 : 0; + currentWindowInternal.requestAttentionInternal(flash); + }; NWWindow.prototype.cookies = chrome.cookies; Object.defineProperty(NWWindow.prototype, 'x', { From a5d7bbf931cb827d58b27c8089632871023b7154 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 11 Dec 2015 16:05:16 +0800 Subject: [PATCH 135/404] port App.crashBrowser --- src/api/nw_app.idl | 1 + src/api/nw_app_api.cc | 7 +++++++ src/api/nw_app_api.h | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/src/api/nw_app.idl b/src/api/nw_app.idl index 489cb39d75..9466bad4cd 100644 --- a/src/api/nw_app.idl +++ b/src/api/nw_app.idl @@ -19,6 +19,7 @@ namespace nw.App { [nocompile] static void removeOriginAccessWhitelistEntry(DOMString sourceOrigin, DOMString destinationProtocol, DOMString destinationHost, boolean allowDestinationSubdomains); static DOMString[] getArgvSync(); static DOMString getDataPath(); + static void crashBrowser(); }; interface Events { static void onOpen(); diff --git a/src/api/nw_app_api.cc b/src/api/nw_app_api.cc index e714b0f668..e9e570f020 100644 --- a/src/api/nw_app_api.cc +++ b/src/api/nw_app_api.cc @@ -149,4 +149,11 @@ bool NwAppGetDataPathFunction::RunNWSync(base::ListValue* response, std::string* return true; } +bool NwAppCrashBrowserFunction::RunAsync() { + int* ptr = nullptr; + *ptr = 1; + return true; +} + + } // namespace extensions diff --git a/src/api/nw_app_api.h b/src/api/nw_app_api.h index a604bc7731..a3b0325dfc 100644 --- a/src/api/nw_app_api.h +++ b/src/api/nw_app_api.h @@ -92,5 +92,17 @@ class NwAppGetDataPathFunction : public NWSyncExtensionFunction { DISALLOW_COPY_AND_ASSIGN(NwAppGetDataPathFunction); }; +class NwAppCrashBrowserFunction : public AsyncExtensionFunction { + public: + NwAppCrashBrowserFunction() {} + + protected: + ~NwAppCrashBrowserFunction() override {} + + // ExtensionFunction: + bool RunAsync() override; + DECLARE_EXTENSION_FUNCTION("nw.App.crashBrowser", UNKNOWN) +}; + } // namespace extensions #endif From 0526d4a4f78203d463757a908eedf8f87863ba14 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Fri, 11 Dec 2015 16:15:04 +0800 Subject: [PATCH 136/404] Fixed stale `item.checked` state --- src/api/menuitem/menuitem.cc | 11 +++++++++++ src/api/menuitem/menuitem.h | 7 ++++++- src/api/menuitem/menuitem_mac.mm | 4 ++++ src/api/menuitem/menuitem_views.cc | 4 ++++ src/resources/api_nw_menuitem.js | 2 +- 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/api/menuitem/menuitem.cc b/src/api/menuitem/menuitem.cc index 236575343e..270257b790 100644 --- a/src/api/menuitem/menuitem.cc +++ b/src/api/menuitem/menuitem.cc @@ -88,4 +88,15 @@ void MenuItem::Call(const std::string& method, } } +void MenuItem::CallSync(const std::string& method, + const base::ListValue& arguments, + base::ListValue* result) { + if (method == "GetChecked") { + result->AppendBoolean(GetChecked()); + } else { + NOTREACHED() << "Invalid call to MenuItem method:" << method + << " arguments:" << arguments; + } +} + } // namespace nwapi diff --git a/src/api/menuitem/menuitem.h b/src/api/menuitem/menuitem.h index cc92e526ad..2db66f343b 100644 --- a/src/api/menuitem/menuitem.h +++ b/src/api/menuitem/menuitem.h @@ -58,9 +58,12 @@ class MenuItem : public Base { const std::string& extension_id); ~MenuItem() override; - void Call(const std::string& method, + void Call(const std::string& method, const base::ListValue& arguments, content::RenderFrameHost* rvh = nullptr) override; + void CallSync(const std::string& method, + const base::ListValue& arguments, + base::ListValue* result) override; #if defined(OS_WIN) || defined(OS_LINUX) bool AcceleratorPressed(const ui::Accelerator& accelerator) override; @@ -85,6 +88,8 @@ class MenuItem : public Base { void SetChecked(bool checked); void SetSubmenu(Menu* sub_menu); + bool GetChecked(); + // Template icon works only on Mac OS X void SetIconIsTemplate(bool isTemplate); diff --git a/src/api/menuitem/menuitem_mac.mm b/src/api/menuitem/menuitem_mac.mm index c4443d211f..69243e00cb 100644 --- a/src/api/menuitem/menuitem_mac.mm +++ b/src/api/menuitem/menuitem_mac.mm @@ -174,4 +174,8 @@ [menu_item_ setSubmenu:sub_menu->menu_]; } +bool MenuItem::GetChecked() { + return menu_item_.state == NSOnState; +} + } // namespace nw diff --git a/src/api/menuitem/menuitem_views.cc b/src/api/menuitem/menuitem_views.cc index 59c04aa5ce..2265256985 100644 --- a/src/api/menuitem/menuitem_views.cc +++ b/src/api/menuitem/menuitem_views.cc @@ -229,6 +229,10 @@ void MenuItem::SetSubmenu(Menu* menu) { submenu_ = menu; } +bool MenuItem::GetChecked() { + return is_checked_; +} + void MenuItem::UpdateKeys(views::FocusManager *focus_manager){ if (focus_manager == NULL){ return ; diff --git a/src/resources/api_nw_menuitem.js b/src/resources/api_nw_menuitem.js index 8a957697ea..450edf55ac 100644 --- a/src/resources/api_nw_menuitem.js +++ b/src/resources/api_nw_menuitem.js @@ -174,7 +174,7 @@ MenuItem.prototype.__defineGetter__('checked', function() { if (this.type != 'checkbox') return undefined; - return this.handleGetter('checked'); + return nw.Obj.callObjectMethodSync(this.id, 'MenuItem', 'GetChecked', [])[0]; }); MenuItem.prototype.__defineSetter__('checked', function(val) { From a3f12dd5527e93218fcf7bf01920dcba9bda9699 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Fri, 11 Dec 2015 21:39:24 +0800 Subject: [PATCH 137/404] fix mac build error on requestAttention --- src/api/nw_window_api_mac.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/nw_window_api_mac.mm b/src/api/nw_window_api_mac.mm index 7fbc17f2c2..23ed1f55bb 100644 --- a/src/api/nw_window_api_mac.mm +++ b/src/api/nw_window_api_mac.mm @@ -53,7 +53,7 @@ - (void)drawRect:(NSRect)dirtyRect { return true; } -bool NwCurrentWindowInternalRequestAttentionFunction::RunAsync() { +bool NwCurrentWindowInternalRequestAttentionInternalFunction::RunAsync() { EXTENSION_FUNCTION_VALIDATE(args_); int count; EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &count)); From 993f25ff335b9b872282e78066e32dfeac445157 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 13 Dec 2015 15:40:31 -0700 Subject: [PATCH 138/404] fix requestAttention api --- src/resources/api_nw_window.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 1129704824..f03d027e96 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -232,7 +232,7 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.requestAttention = function (flash) { if (typeof flash == 'boolean') flash = flash ? -1 : 0; - currentWindowInternal.requestAttentionInternal(flash); + currentNWWindowInternal.requestAttentionInternal(flash); }; NWWindow.prototype.cookies = chrome.cookies; From eea97ad51459694264c90df53e91ce93a506dffa Mon Sep 17 00:00:00 2001 From: = Date: Sun, 13 Dec 2015 16:10:47 -0700 Subject: [PATCH 139/404] created isAlwaysOnTop() --- src/resources/api_nw_window.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index f03d027e96..ee17a67901 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -183,6 +183,9 @@ nw_binding.registerCustomHook(function(bindingsAPI) { NWWindow.prototype.setAlwaysOnTop = function (top) { this.appWindow.setAlwaysOnTop(top); }; + NWWindow.prototype.isAlwaysOnTop = function () { + return this.appWindow.isAlwaysOnTop(); + }; NWWindow.prototype.setPosition = function (pos) { if (pos == "center") { var screenWidth = screen.availWidth; From 35ea292e190b133edd24289f57d22ad4948ce71e Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Mon, 14 Dec 2015 09:38:27 +0800 Subject: [PATCH 140/404] [Mac] Added support for new key names on menubar --- src/api/menuitem/menuitem.cc | 90 ++++++++++++++++++++++++++++++ src/api/menuitem/menuitem.h | 5 ++ src/api/menuitem/menuitem_mac.mm | 13 ++++- src/api/menuitem/menuitem_views.cc | 60 -------------------- 4 files changed, 107 insertions(+), 61 deletions(-) diff --git a/src/api/menuitem/menuitem.cc b/src/api/menuitem/menuitem.cc index 270257b790..88c2a2280f 100644 --- a/src/api/menuitem/menuitem.cc +++ b/src/api/menuitem/menuitem.cc @@ -21,6 +21,7 @@ #include "content/nw/src/api/menuitem/menuitem.h" #include "base/logging.h" +#include "base/strings/string_util.h" #include "base/values.h" #include "content/nw/src/api/object_manager.h" #include "content/nw/src/api/menu/menu.h" @@ -29,6 +30,95 @@ namespace nw { +namespace { + +typedef std::map KeyMap; +/* +{ + {"`" , "Backquote"}, + {"\\" , "Backslash"}, + {"[" , "BracketLeft"}, + {"]" , "BracketRight"}, + {"," , "Comma"}, + {"=" , "Equal"}, + {"-" , "Minus"}, + {"." , "Period"}, + {"'" , "Quote"}, + {";" , "Semicolon"}, + {"/" , "Slash"}, + {"\n" , "Enter"}, + {"\t" , "Tab"}, + {"UP" , "ArrowUp"}, + {"DOWN" , "ArrowDown"}, + {"LEFT" , "ArrowLeft"}, + {"RIGHT", "ArrowRight"}, + {"ESC" , "Escape"}, + {"MEDIANEXTTRACK", "MediaTrackNext"}, + {"MEDIAPREVTRACK", "MediaTrackPrevious"} +}; +*/ + +static KeyMap InitKeyMap() { + KeyMap result; + result["`"] = "Backquote"; + result["\\"] = "Backslash"; + result["["] = "BracketLeft"; + result["]"] = "BracketRight"; + result[","] = "Comma"; + result["="] = "Equal"; + result["-"] = "Minus"; + result["."] = "Period"; + result["'"] = "Quote"; + result[";"] = "Semicolon"; + result["/"] = "Slash"; + result["\n"] = "Enter"; + result["\t"] = "Tab"; + result["UP"] = "ArrowUp"; + result["DOWN"] = "ArrowDown"; + result["LEFT"] = "ArrowLeft"; + result["RIGHT"] = "ArrowRight"; + result["ESC"] = "Escape"; + result["MEDIANEXTTRACK"] = "MediaTrackNext"; + result["MEDIAPREVTRACK"] = "MediaTrackPrevious"; + return result; +} + +static KeyMap keymap = InitKeyMap(); + +} + +ui::KeyboardCode GetKeycodeFromText(std::string text){ + ui::KeyboardCode retval = ui::VKEY_UNKNOWN; + if (text.size() != 0){ + std::string upperText = base::ToUpperASCII(text); + std::string keyName = text; + bool found = false; + if (upperText.size() == 1){ + char key = upperText[0]; + if (key>='0' && key<='9'){//handle digital + keyName = "Digit" + upperText; + found = true; + } else if (key>='A'&&key<='Z'){//handle alphabet + keyName = "Key" + upperText; + found = true; + } + } + + if (!found) { + KeyMap::iterator it = keymap.find(upperText); + if (it != keymap.end()) { + keyName = it->second; + found = true; + } + } + + // build keyboard code + ui::DomCode domCode = ui::KeycodeConverter::CodeStringToDomCode(keyName.c_str()); + retval = ui::DomCodeToUsLayoutKeyboardCode(domCode); + } + return retval; +} + MenuItem::MenuItem(int id, const base::WeakPtr& object_manager, const base::DictionaryValue& option, diff --git a/src/api/menuitem/menuitem.h b/src/api/menuitem/menuitem.h index 2db66f343b..e8289ece5e 100644 --- a/src/api/menuitem/menuitem.h +++ b/src/api/menuitem/menuitem.h @@ -23,6 +23,9 @@ #include "base/compiler_specific.h" #include "content/nw/src/api/base/base.h" +#include "ui/events/keycodes/dom/keycode_converter.h" +#include "ui/events/keycodes/keyboard_codes.h"//for keycode +#include "ui/events/keycodes/keyboard_code_conversion.h" #include @@ -43,6 +46,8 @@ class MenuItemDelegate; namespace nw { +ui::KeyboardCode GetKeycodeFromText(std::string text); + class Menu; #if defined(OS_WIN) || defined(OS_LINUX) diff --git a/src/api/menuitem/menuitem_mac.mm b/src/api/menuitem/menuitem_mac.mm index 69243e00cb..dae813bb1d 100644 --- a/src/api/menuitem/menuitem_mac.mm +++ b/src/api/menuitem/menuitem_mac.mm @@ -25,6 +25,7 @@ #include "content/nw/src/api/object_manager.h" #include "content/nw/src/api/menu/menu.h" #include "content/nw/src/api/menuitem/menuitem_delegate_mac.h" +#include "ui/events/keycodes/keyboard_code_conversion_mac.h" namespace nw{ @@ -118,7 +119,17 @@ } void MenuItem::SetKey(const std::string& key) { - [menu_item_ setKeyEquivalent:[NSString stringWithUTF8String:key.c_str()]]; + ui::KeyboardCode key_code = GetKeycodeFromText(key); + NSString* key_equivalent; + if (ui::VKEY_UNKNOWN == key_code) { // legacy key code support + key_equivalent = [NSString stringWithUTF8String:key.c_str()]; + } else { + unichar shifted_character; + int result = ui::MacKeyCodeForWindowsKeyCode(key_code, 0, &shifted_character, NULL); + DCHECK(result != -1); + key_equivalent = [NSString stringWithFormat:@"%C", shifted_character]; + } + [menu_item_ setKeyEquivalent:key_equivalent]; VLOG(1) << "setkey: " << key; } diff --git a/src/api/menuitem/menuitem_views.cc b/src/api/menuitem/menuitem_views.cc index 2265256985..044f5be5d9 100644 --- a/src/api/menuitem/menuitem_views.cc +++ b/src/api/menuitem/menuitem_views.cc @@ -21,7 +21,6 @@ #include "content/nw/src/api/menuitem/menuitem.h" #include "base/files/file_path.h" -#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" @@ -33,9 +32,6 @@ #include "ui/base/accelerators/accelerator.h" #include "ui/gfx/image/image_skia_operations.h" #include "ui/events/event_constants.h"//for modifier key code -#include "ui/events/keycodes/dom/keycode_converter.h" -#include "ui/events/keycodes/keyboard_codes.h"//for keycode -#include "ui/events/keycodes/keyboard_code_conversion.h" #include "base/logging.h" namespace nw { @@ -45,62 +41,6 @@ namespace { static const int kIconWidth = 16; static const int kIconHeight = 16; -typedef std::map KeyMap; - -static KeyMap keymap = { - {"`" , "Backquote"}, - {"\\" , "Backslash"}, - {"[" , "BracketLeft"}, - {"]" , "BracketRight"}, - {"," , "Comma"}, - {"=" , "Equal"}, - {"-" , "Minus"}, - {"." , "Period"}, - {"'" , "Quote"}, - {";" , "Semicolon"}, - {"/" , "Slash"}, - {"\n" , "Enter"}, - {"\t" , "Tab"}, - {"UP" , "ArrowUp"}, - {"DOWN" , "ArrowDown"}, - {"LEFT" , "ArrowLeft"}, - {"RIGHT", "ArrowRight"}, - {"ESC" , "Escape"}, - {"MEDIANEXTTRACK", "MediaTrackNext"}, - {"MEDIAPREVTRACK", "MediaTrackPrevious"} -}; - -ui::KeyboardCode GetKeycodeFromText(std::string text){ - ui::KeyboardCode retval = ui::VKEY_UNKNOWN; - if (text.size() != 0){ - std::string upperText = base::ToUpperASCII(text); - std::string keyName = text; - bool found = false; - if (upperText.size() == 1){ - char key = upperText[0]; - if (key>='0' && key<='9'){//handle digital - keyName = "Digit" + upperText; - found = true; - } else if (key>='A'&&key<='Z'){//handle alphabet - keyName = "Key" + upperText; - found = true; - } - } - - if (!found) { - KeyMap::iterator it = keymap.find(upperText); - if (it != keymap.end()) { - keyName = it->second; - found = true; - } - } - - // build keyboard code - ui::DomCode domCode = ui::KeycodeConverter::CodeStringToDomCode(keyName.c_str()); - retval = ui::DomCodeToUsLayoutKeyboardCode(domCode); - } - return retval; -} } // namespace void MenuItem::Create(const base::DictionaryValue& option) { From bec652c3ef29e424fe60690c506b4c26042af024 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Mon, 14 Dec 2015 12:29:30 +0800 Subject: [PATCH 141/404] fix crash dump for nw13 --- tools/dump_mac_syms | 1 + tools/dump_syms.exe | Bin 51200 -> 178688 bytes tools/package_binaries.py | 7 ++++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/dump_mac_syms b/tools/dump_mac_syms index e008b82547..5d79ee3473 100755 --- a/tools/dump_mac_syms +++ b/tools/dump_mac_syms @@ -82,6 +82,7 @@ SRC_NAMES=( "${SRC_APP_NAME} Framework.framework" "${SRC_APP_NAME} Helper.app" "crashpad_handler" + "libnode.dylib" ) # PDF.plugin is optional. Only include it if present. diff --git a/tools/dump_syms.exe b/tools/dump_syms.exe index 22e7398abdd2ff737541a4e50121c5872522a4bb..a06fb0440b778471cdf3714aa50881486b25d801 100644 GIT binary patch literal 178688 zcmeFadwf*Yx$r;9BxHcV9)xJ1C{d$C<0TrZiQ{DkW?*lak)TGg-l*7!M_Xw!!$rC2 z1ZB>4Jn2>2dXDxzo?~0vYHN?6_LKyW1W*WQ6|^e0T6a2HQCk7E%g{m&j=kDT!2NY5{Z zK6~t%qUf{7UNY~7wyOE9H(%fS#V=RQ{o+@?a&x@u+AmeLCcaX2!&j=pGcT_C^3B(M z>9i3ehE=;wfAf*YzR|n&mCoG%-@d=N^JVV8egCi41>=FXG04hMNt#N)Z{jv=0@lSVY>%IxtR=NVpH?(zJz z$m2PU|M^$Goj-nc8B|Z?y4d3xq8|1Cdi-EdkjFo-XU=W8N^V`A>PB)uQqFxMmmc%> zV$XGQv8ULxTRQ43_S76D4|4y!o=?iCv%bjFkb889-?|yap818(JBvK?f%kC#PK$qO zQJj}I{g_S#AQ`(mj$EB8k7v$lt=D}q{zZ?cV>3@_)AJ0!Kj*jLUl4LRO&9a*zJM1U zbUcpVWdi}AT<2-?bwM>o#_Fl0+`R{t3!c{6);gD03MYGbFGU_bRJkwRbTb77M=}E* zA3r}osN7_E`@jDV#(ABcR?o5QeNlU}Y4584 z&`c#9(=ImC&5mVn%62$Fxw3Qd1m~3hBYk$xG1C?AKys$ts#)2&=z5;) z&Q^>B-mu+i+Rw?ov-|;pARbV~S8j?Am%gZ%ZSr^M3U59t@EoS+#s8T3ylF=oRIOCm zV?~*9&K!k5HI`ZKSiZ>c&U{(7_buAX6sx-ilOD@VdsOx!)te*tL-f7z_qPfW>)w7EdS4yAEMYMXJDjAlW=dynK0TuF8qmd>x z9zU(o#Sdr7Oo9M@jz;qB|noOIaE9`(9MbFt2X8j~bj64Wl48E$&ir;m&nkB7>Q$1fVe#jN4{xqMj2czp3t{(RLJGFIO( zBx0=oYDvT%nqn?f(ds#=OR8swxoHY>Wrpc8m|9ZZQ2#QMS{F^1E)?oW-&MVWzS0*T zo4TpGhBEWRysmQ2`GLpNlDe9=9-6pr2m0@PQVu*9?Jw=|5%ox2!A;tNoy`*|xl&eMN$`9ic%;c?={#F1Oo$(5@ z;o0!0z0tC_I`t1RK=m5J8t&ZnU5^JEjHc?%)U6D#%@6GUR+1j6Ic|A%H^T^kb=@;eVw zI1`VwcgDY9CO`7ET@s~4Flv9*Zv|e68k65Qlly$FCz`B{I@3NW#>|$_1YpztwKJPm zP5W7A&AswVfv8NU%Pv|brzW)tg#s!s z!Xd6%&)yqCWOF8ctAM$&9)z>`T1#NE>=a$=pONwU1S;B%S6Awh+qd{F+cE9;8MYPJ zXWaf0!?tSoMFZO`WAc6$K;rc2>5?+DG2}OH7s*dE%v1;pgkx?jDe~0sFzqeQRiz$J zU%M)hJz(7V;~e?|-E9*EZ>IgCbNMIi`75*WIc9Ph)K3FvX+p1t&@;#JD)FY7S}cS1 z3qvjnu4>3I?5>UMiM8}1mCMcanA4(mw=?`ffre$ox=>5WD@$qp#*zkwTQCJGg^_}> zx;(BqaahWDpT2aIizJJ_kli!#vrCt zT7k~Cs;E6h&?i_v>SK+;xW?NQ!CNQ>$N58)%|_vEtZ940tElY`Jl{O`>t!<=*eOpH zS|M)O?F-I-C{gP$SXK1rwwoiq4G)91)nCt{Ex*5gHhO!Xfexg%v!M*=k(&YkK8+r~=+dKNl>PKLT^N*Fr_hq$OqYj* zCMT(LDmpq$ODvE{Wwq;9jqNg?6aactEdxDC1CAKNOvg{~>5KI`U{$_*6?fWcP$* zb-(1y;dLs&4sNDP-)=4TB+9t>fC~r$QgFdil>baPRcxlF^K_k-49&oFU&0q|EKYbI zEn0tO=ON`q&{|~KJHz%mGmQwIZK^cw89uYo^d&~x!y9Sf(IVNYuAl+^o2P+(cugA? zDcjUX#_gI>AeyX$RV@6x%bjQWTPx^kV~L;T-c`r|;iV;``k8|o3iA`6rE1DPtI3Bo z3BAybG#xLWo*q*awL6`A{-u!T(uY^G+d|9;n<)tl>H}u`@Iy?%#=CP9FmODIE$Sg9 z)zZxk={U8InJI?kg|Vu2HEZBh2>h^K+3p6x-vm>Oy6{^uZ>RI32>2pV9GY$h3?n^a z|GxwA7#HF^ymj?2LotX7B0xB&^4dh$RVo97gS?BDde6V{)QI%1n$R#VK2k*84rJXG z=Z^Cv_9Eh%h`9I3Qh^r-1;F7=_hq88~Y_B+p179leC9Q?TX@3LlC`G&G3=U`%#JLsmA&94~97)E*v6`SOHu`1*_gmimgm zM6^U6TT)%Al&^l#x(ww2wR2BYiJFL$J|lS>Ghy17R5v+6q17xbHnj+biYU@!2KFaL zo2ii8q8Y2dA%Et?VEc!0IzapPnt_JIkg!nLY$jo+FuUHdej|8CZ6cf5k18p2V>mLzP7vCs)#uNtd&HT&o(W0KUF| z6_qm(@+W5Sik{%8!%Vv2XT{>No2-sXsSqE@8bG14EAwp34mi5n$%FI1BdjSheIaiP-7jf9t)mi?EZ|GA)Bn4zdl-) zpe@LgpTm!Kh>TV+D#IL=C}m(7raQz?{n6B91(llecE3n05QGvSU?V||WW<_SYNR0D zLjIPubhW5Y$H~U@=R0#7le-@NEA;%RC_4X3&+ZEPUwRe}aq#N>e?`w1vCTRstQ~ibXKDdiyA2LKrq;_e9!v#D_LEjSm^$ z=**M_ik!(V12HsH#r!tP<(?;*;i91GkknLvW`=6f zvR}YHs~b8A5ta30_`T|tF)HFg8&5?LA2McAY{bv0f{9lO+KPCjK(c#`mE1VS3VdR$ z6nS1*2Tl9O)vJWSkr5Y(S=kg9N`u(Qd+yF!1JxIPi55#4z&m%5q z)^0b~T;HPq#q8aJy1jph+8rqNnMWAQO)wVwzG@sPOm7sqg-QJY^A?jjPpsDk z)9T76gY3ER@cpK}-D3XiEtqz*F97s7Xx;D+4?}jwC@)lxdCM4Ogq7Hgqw`r+>U=xq zhe!>7jJ{v&CsSk8aLjKsr%F}owruQk1x=-9Qb1Xa@&>P@7^huXN`6?fn)XoaejBrU z`{^xOyFF&_N3)A!gx>oHv+E-yw{=2trneVkRgH1CIB?DMVdX18IR8(2mMYXaccuJV_Lk}CQzl%PE;&Z*@SS34wV&a3qFK8wW^c3X z=beYu!&h-En69gW`AayaK!KHCf0o^6i3@uFp^#B!-2DmzYU#jbWZZo~jWLQN!G2cR6<&0z zS#?pgs)neZkd4+8xrHrOwC`B8n{&GBp4?(Ky?ex$vzLCifRL!Y&H|YNgj)BVyrcu( z51bn-(2KcnDSjY`Xqz)mSsZW|W*~;=EPJ!#Q%@9KZ0R2#JxJ4oTVtlHjDI>osw6)u zS~eu2MB((Y74>&9oJX1O zf3ABQd(cK0`Ih)0Da5E zYb#3PNEU6r;7~L9G+W|3t*@B&U!wL0?*42A{?c}-nNGZ&lm80Z{(NDtX9#-PRizm- z&>hztZKQo69Bqg&$Em^`g`>~97>f5ZZRS}@Z}FVMhm)$rm9Kx%M-KTSOZ@gk`%6H%QuQX`*4SOav{X?X4W7M>_LpFn(_6d!R z3^BaErNZLa*I1wu+L5Xhu84v$k9n0{Eyo_kcBQrj)>Xf6UYu%2iOU!Rub6vK^vz_K z-^^}K^ij2e8V8jhE#*sf`DB+*%D&^4l|?+DyjuS|gv%z^m04YXz*e+5W=z{2qu*n! z+8*=?v5b{Xw@WaVcp!40mFZ@&o|ha1 zkJLv3=!}na>!a(0QbDMslj-e(E;lE34{T;Y|9^+^==hhY#6VpFBeHj4f{)oACrrGa z*q4~FEBcIE@=+_uk2>}w`SyIPdx~tKSy!sAv{K>)LgYZntsT|R7!@NMw4odA$Ktc2 z1rc)O_fhqmp78_ci6iJ*#PE8yTlBvReN2tZ3Gyrf7gv&a@9rBf%6TR??JspMzMxcl$pf67#eol?CUZ3av%fa+~fJ&M1> z4f_O)M1TUKm0xA<>R(pERLmZ!5iMKTLA$5XUa737=r1CVTj@!++2_aXsZ}us6SH3u znyKAvb?q;St=zQig2MR_fg*1u?;-Rf)OhZewlT5v_$^j)J&R>i)~xM`dN*a*;(DmQ z{Mj(vDeS$_7xr$8;n3>ZN2{I78nyl;t6@NUk;d}P(%zWOEb9LDx}?2MDlUIE7I-N> z(Mr$oDSQ#H7BmB6O=Aj%1LzD#8|yV4+GhpUC0@rp^BM4A23|65e;?9!Cs@qYudP^) znPj4AU#zsrI_LioH)h(O6R~C60=fKn#jO}LUR8BssUUTj2s6bS^&2Dv1jRs>f$Z6J z&UdI4O%qpT*;k+&-3TSMHCXoVM83?+)NcG5W`0%;6}RXW+suZm_zvq;^fHNof+v^$ zny+wH3Oii{flDdwx?8?jRe58U##z-#ak=GwkHCwnr@GRU53Mf zb>OszNUN$yYRWTKddf3`Q`=YHX{Dy@tlb{2?c$cA4ft@yKYTHVn|V_JhpA`@M`UldbL=_u0zJB1}#=;nD*QxtRiY24KjG^oM6!)RYC z{UF#8ScDHeV(&pjH|}_jr_=25BSV3Y+b$2I+z`w(Yo-WlU9T7C_@`;ZVlIRno;GW@ z9K;A{XF&qAjrE1RC1V5KiPA{w%CUYc+bI$#4Ysb0+8{?YAZQyb23vt03qn)uB_r8b z*NIVMig(G#w|Q#Z`9+0U0b1MZZ(^Nth4A9WH=XoP7&Pyik3kb_{dl=q?%o)3(<(PQ z{AN$lk$uBe^OzcNfLYrjoqYolSND&dqQ2dsX?_gZ$L}G zl9qlXvo#kUke>83m(W8m3aq+>Gb|!zE?(i(4XF15^)d-zEk7Px2*y#SJ&nCrE5XDA zjQ~^c<9BGr;D=yGGIpgUHWpKru)oLBA3gY zC%KfsWI+=PZg33drJ+wd8Q09Xrs_YZYQIj4op~PI%*WJLXf`msp z7bplS?vs($$aLzWQ4uYbuo5I}LWM0?=#oF8q>5f9Z23q9V;^=f5An!BPXacBA4;M( zrl9Cdsp_6mC+%ex$T+0M`W427WBU&()D79YMXN={kg@9AH&Km{T_rrq4ohW00ZOY_x01X z{gH~4-$Ri59!xy|6_SbiWV-^YjG*mz>Ck?apmQgc0D>6BBrKi)-iXs5v2Cn+jBV+Td9*+-`ou z+q^sQoH30lrx^x}9bMx4C3ybuK&9d=-pqKXu{A##*Z!lPjB}VDP+pRnAXwCG;t0-# z#}j#bFi|FR;qmJHfeJ1V^)`J|^)7;_I~Xm85waS&_f^uyv{rop?TNOUw-O)H{d)pZJ!FDC5BsU{2@%W<*#<6`= zUcS{_n=?RIflUcO?Xlq8m}vL*9b~!9jIvNUe1S9CN@#9R)L!hHnyx-ew5N!XiX0WQ zx5R3p@osCcbCCH*q_B&^cC#^1zAKQ&gm0ntm!xc8DDX_{5etWh8=HK_*Sp#`;it9N zWkyERCYntTgIE{!D)H=Bv09?d2s)%sj2)Ym7=X~I?0!99{59jU&JT>`k?u(S%hA+T zFGlS|ca*I-xwq8gnVCLit=08ranx?!rtQUcWG`|ZI%>ES)wl-(W?9HyQtD4FJ*p*S zEZY=L#mXXq*Nr=V$t+J!ml!QZ+b&;=v21gy+3>ClY&7opKfDPCwzfrKijmr`h_^>7 zg^MD?5qcKXg=^P^ndtf*R4~)bcsfRmLhM~THaZ;WLI{eyTkA{3Mu%$m(P?IAsJ54q zyp5zwMnwX<+l~s?ZVcB(*6J7ao2WdxKASlYXv)HNY;Bk_!|emq4|(^~L?~V2lV{`Y zt)beUkarX1GX{~5G+V!^J}bQp>o=^?3#VP$UlGwQ9VtVuHIiQUCK}jd-1(u(hiUyXx)^<|=^!M#o#wvG(Wni6k3&E4iqIm>Q9pumF{?8oDD- zfdQBxdu%$k8lk1_T(KXdo4MY+unv>lYP0q|v+Dpxmno>Gia!bi{gL9G5FZhcu<%0Ybym~hx-w`nE2FRBoAX$gimpLcV_zOX#(-K4?(1z5|0 zjr}QX4~d|gCnfwbu)S?@s(E$o=2YydPeJVA1~E&)Hvq8*58{-=4FW(ycCUS5NeK1_ z*a~2GOqa1ae;RN|7^|@`$gCd@xL?4$RcucGtRD>AKdC~8m~}Z$K?v7YCvm+r6Pq9g z?(JBq#K5h#@7p%WuC81$Do#$$#>qcBgf=<3Bx-L$ZETYm-34BvbO|P^?+i6sY*qt9 zcI~3D&F=CUOAoR)K2Ze>&TS&YAZ>GAL(T+U2n*~GwLeYt6aUUUy?J)T55>Bnyeg4j^@J!T}C^7JMq01Gvk8+Hfv{wSsQLv=^enX->(cl{#O$&g0?q%?B#JQU5Yy69n^OP!Gzvulx`W z13z<$WmYr>(aeDVv^@NVz)vtflGp1c&hs}5_ct&xK`Q z4fwl+-b$Sp=F(c%TP3FVfR$Q?b?$e2Oz*2!>T0IutV0gq@bF{v7>NHo4Y~OLIs+(n zMyml7;{TOvK5YQtAA=DCxK;+hg}N^cguuYU^jqv)5-+G+p?mc3_Fl{IB&s)Eo-}cP z%t)6!?p`xP7*YD_7*;xCy#h1QJUeU2r@Gnu{--Y|AibKClCy{rueNVa~e?;8zc~F{^J_u zENOguTiC1}&bxywRAxVK!uSl}$68+a(H7@86-9++)hnFtioT1`@zs@p^^+xlwdSfk zSUGkDzaoYg({4)r9G+hh!z=u%NrkYw468}S@Cuu9A1n6dUJn?*8vIZp$|Ia>=cv=i zDLB7`Qp7EW?6br8)jK=eNq&4#(f>7&NIl{s+SG5q6X_ zUzomPxn;_V&0n!$PxA?PlTO7*E&Va3C}t)&V(z>loqhz&(Eu^m(LhgPymK8CqvQbq z={8ev!7FoHN-WT2v@fMi(K|zeLez}}A%POL4a9Il9e|Bu3h!dFRIFh9Ph#B)W=^&U zQ&us7pW}U00+WvrH+L|Kdf|o(#vS!U`M_`+$aEi1hhnM^Mgwf!i8~cQQ3*Mha~k=e z7BwpEDz8zniOIDJ#akUwcrS5{%)Xj-mJJPS*G)xU-|v4I|C6-UlsT0e+KHp#4Jvqf zql&uD97k@XI2>0{9FzzcGZ!(r%%2&L2T6xv5|z0>Gs3bL`C}-zD>=I35m$xqX$eTZ z280iZHqKX}>Wh4+=#BLfrXG+m^;=osM@AdZOiYw265zQd!&U(jwy80VfMNEygA#lX zQ^i~T#>yYK-EdIT-eRV{;P3l3PcTo?qON;{tFKev4H#vIo9@qiRYkMoYD=N6P4y>A zF}`Gu)gaH#ft(7+_}K6f2Btz^vhlm4kedv_C{umW#ySZPCni;G_B;^@f%6AbEBRwq zhE@gn|H8`9ie;4;z1AY6(+cMYd`3HGX%)S#ST3&~P_Kx#-*2qksKzma5L8YKT%u$5 zXZSZxk&^s2Z9tSD~(LO=6MEQ?3Q;53649GMZ zD_>Ck&{Snnf=x*rfPm62YF6$pF;1=D?HiF80fgsU9b2&}-1(1-Me?~-p-Mnwy-B>; zRw?K_@nJUGx0Pnp2%7Z>hU)=P3dWK1Hh#(oMyr7ZW~(TFQ$_hR&daGTV;HX18`fwI zb>eu169zI)gNf1jW7EC@6oBiM_hNX<1oK5StOEu$1dwTU@R%5u9@4i7;n_G%0{XW) z*IpqAV)ijYSGj?avOvVKZ)SVU)KmbJm6PH$imc_DP=WlU_@i>Y00sqxO(7t)MU;bV z^TP8EtuVf+jzL|F)V(8)-U|xL_sMe5Dyncx#WMzJwU4M;TE77$FwmS*+h0TEa~B@9 z!3jugUpGl7fprV7GdZHy?}*y_uDl;$%fYQzsBUjjeM*QNV#}CSIR!Rs_>1LK(qwfi z39tz+>IY(A6RQfq#xg;~QjrAAKs$r#u}A5EBBX{v0=4!5U=?Ma#>VU}%#PyejbdOM zZPo5HyM9+lu>7yqa*qpO0ArkCB1R)M@oAU(Tx(?Hx$?Ri) zDpQ>hg;d)sGJKPcD%d0*i`mZkuc)HpbJ68Ar{h9D#*_*_MD-Ka| zPn080V^rPM_cMivNBD~~EKtnp$P_@>k@v^Y z|Lpgp_I6HG_{=pY9DiK<8~44xC0gHOKJge^iHHBA#v_~9g@Q#q&0QB4TzlPXnMYg- z+*$TTHK=LU@tds1>2(W7NNN&oN;kc{9LIZ9?=GurcZp?}5QDTceu-sY=?DFvlbdNF zSO#V%_-^AAe;npJeskizOr0im#e78jWV+a|tSlx37Q>iJWrmK~I8_clZ0u965*QWD zovP}=vD7IrqyqR@suAgtdU7sI_5s!q+nt%Z%qPGhdE+Fp0>$iSsdFrN&Ggg~k1unI z8CWkn;W;FK5KY~t2E7Ni#o5L$@=v$J#j4Hesc10`ZFcsaEkK^$})rT`_JT;_!PiMre%?CeI_r zV9=SHioNxh@cz#zCow5^Zm^P1u7vZ;z5hezz{`H z3ZB%GnolJtsRluvoFgc?*y?=yEP?A>4Hr_x2BE2WIXAY-7^9Bcl0Ri|yy6&TS+jDX zL@EH06Cyc=^OT+ENAMQ=PqCS|iJFA@_oF;;o2*5ecwahWhzXsF3{MLz@>YnAA*U#iXJ3L4H7vVWh_N&hh z&)}^Fv9vp+l4}W`7PFHHuB!OI2EwRwBwn`zWX*R@64g5O)T1)q)KhEpPpAIr(LcTV zXQ%v#MN$~5=A|O`Vpc%oD>Acl{Og5Z=H$`+a`FzF9PdXtnVHf&X%AA@RPuk7(h_0h zG$@7vu8@fWhOmvX&ab4fy~PYnNbFH2&zz{)_aJW+VYq+PeotdCdb%rYFK1|=%?WC+ zclF7!&YS_QOVjLYB7gTy9n^Y0|C_zbDZW5pawA@k&K2w%4)zKF%Htz@!cgqG1Msm6 zd{iy{{xv{GSQGnm%Vo@J=wGO4(pkq^w zw!5dyRs@{7wW%d_Ymh7!4L&I`qsqBK8fHDX@9TJ9r4z8w%y4JAeyvDGI&Mh?2K6n& zRQ=0-sQm$={n#m?667VoALn=#_>XDAc}x?|W14VmMPjPdD=``UW18k3(=_*3ry?(V zh5V>et92>$AobWPd7gT#LwOJOYWcbPqJ~#ZMG(afv0q0sL#`j3FNKHeVEaH<;uf(0 z%`t7ZVPz{4F)mDm7k6`T;~p`UU>15~(TRZU#3dy0i681bcWl+YN}or$O71UeJ4%F3u4Ev={sv=@%!s)q) z9S(l>iMQaYM_D|)!o$}5sESoue6;h1%%qO+TKW|oRP-iP-M5;pi7IPwC{j(TtjTsP z;-MrDB{EIA-J@66RZdIlD#W$sH7&_=JQCcpK4Vx&=IOqbrKFY&7WE}C8nqJ;-8b%c%&*b<_ zWT%-t;IoV=8_8c{y60P-04x?q=_DF8R*&vzY4pdBW^l!3c2nYg1;YNHF;B=>w{Q%k zIJ0#?L+EJRKQ@yeXX97KY@;QVY$T6GeP?F0Xcj77n(>5^=Z4gHF2=qE@yIw;%lA)_ zzGJ|(bdQg~m)Op1h4%|Zp5(r4;@HA*g|Zv0WVg?nyMZW>;8(i_f@X|PXtmEK;0I!>|0GYkXppLoNrj@E!a%TNS&Z|UM@i`Y$3ID zx2A~QDr6GD3ZW!@Sy{H@2gFARecZ@I{EAU67X#f2D)a)ARB|)X_NFg`Uy*3lST%&A zIekIrqx}GRbuVDi5vT$ z6B@aQ=a(m!29Hc!-2c2M+p&TMcy)gN!=2ftAWu%=$xdOc6Ey%%wnG>)Z;r5IJ$AF> z-5%I#MBgJaXy!|*tliuz<}Q+Z$XJOb->gIWR4Y05JP-#3VTY+=koAP9_b3!J`|7gs z_WBdN;BIMIwkO&1NwyQgx1_emyQ}t9u@-_EphFPJMf&Y_w*k3Ffl6+eNB+ygxzzLN z=1L{jZxh@@ZCKrrwVHd}SM8T&ZI`}za*lJHuK4VGjLc|1hPfpxWt5;BQ)^U=GlPP) zj%1PzJcdo*!v4!(Cs)Zbom}O@tpZbzOvPohWSN1k`x9Uf2On9v#`Hh5jJ1B%Ox53K=Za@%$jw`3M<+;Js?mO2KNu^P-s3UaZPjq|TiFhH3xr^- z%JPH&wTDOnJD&ER9I8shx}B*Pp*n_BONosoN|Mkr=aL^v*S!p*%5lVAm|QwRujewS zGeYkq!)i$`?S^;8P%KBo47y>W9WMVQWEcL~Oi&l~_aSL#uH|-hwquSO!J8d+~S-$0v|;8eHyz0&+!VDmL!E88|LwNju%LwF6A zPPr_EkR%&+>~!ITKf6piaY_ER4YCr{Q4*CmTGopEQ}M>RWs;A8xUX7FR;-i%oanBT z_UcYSxBUk>iU1ct=~(RdBx;-)Og3T86P#sJd9Qcbh4b_-yL=jKyR+|nX^Sw3*#=Dr zb6ry16kJ7|wul!&(Z~NisA)2HusLW-T4_I941PO8wJTO)%5}$HaWt^ z!&tSR7!LWhG@6yR*`$1$>CzcA{A88jB7Il&3VxkNbcdGYFV&4FUTLP=)g}3cv+VEL zZgL$jQn`*}_z8jSo9eZ5+)uOF;OZhVRx+_$vK2qPL}pVBNv4{SJ9kV5dgF1(>n@Fd zruC&}fzO>GJ+1j{$Op;up>DqFAS)9i#~v+FK6&6yX<%2jqXt$2&t-3b#fhk>8C3g! zFkj9iLWk*!=ZPUyvr1U}qhOc>g@a^lrEG0Mcv#`#pQilFWxUCchqT6`>GI3#cd%q1 zrXlQCx70-Tcm)04vQobifyQR8yyqNT|v&yiON zF;T*vR*q0KYNVTLoG<2{?H3qqLrh-Deb>Z?@i%C!{GB|-^7Uj;8rVnMQB1b9(XMRl ztIc8pm5bTpTi9~GR2c6(C0(*2oF3y*E|<{WKXiRmY_IEbCU|=}pp;k;S-E4;S6h<% z>soK*Z{xx*8LPWU%-0+7u8RcLExA<8X=N>u#!zu$IzzXLIX6>GCy9r`DRz;vmSjUP zaV%N>e9J0A>EJ$jXhgC9Z~r)!o{`NQ+b!6T5fwZv=e8x;cum5HavD4xVk>^mxlAyS zpBj}SNy+K7d^)K-TSCB@Co>#>Zf9d!EOFSfW!iF!(eibKYKnM~1H&arnU#Xkf z8kbk9oQq|$HFme8YU(#32VPVJF)g0pEP!TRJ}?oDS?XOWjyVYpFUH!(7cAlf83*wb7h|dFcpCTTmVB+~6 zEu-Twx;ih$*sCCylvrl9j(t!GEhFmO`Y42Cn-tUR=bRSlEM4)GK$QAy)hi3CPt?_~ zqO!;&q&8RHqpWqu0b9 zqjD|G=|91x5>knL38Pdd0{Bu#O|h`5#br=YRS}1LeyLuOsrAvKK7$9s!YHq73Bt$b zA;&(aLi*?(Fxso{4)uYVH+J@5O7 z!VdrqlEsVtiu9sNS^pDqqVP1a{5Q9E{aEYO2XqASFi7O`=k%E%8uP{!)wmd2t4tiB8H z#5(a#Br^T}i6ZoVT3-R*iOP#^aX5%Y;itdb2AR7RjY2j)dOIH@* zaTtVH4nJdMC?EJt!R4EN*cjK z_U8zJAfXRCi{dA`@<=GThZM%o>L9lnmD!3JbP^`wN=hYOAMmP9_20YJM_cw@Lbecr z&fJ7g_Y1|5_Lq%z4fm;=R%NsKcrN4du2{N!GKE5rWM^`>k=!?I;aALi?QgDpa1kT( z{yO_xOd**o=x|Tshyz`rbEX%!#SjKUL(=DVXQr&^Dprrq;n6H#*Kf(U!K>lDocWp< z>T*gZQ%V{bcc3eCB!x4hq@{RivU_&Mo9u2%Zfuf9w-~q27fCODV^!$G;;Q)RIGfjF z)|-oo@AU(WGh&CQBRNQTx8PyQbU`*cHiFbqRE%&Cf$0B8Z4buaD&}q&(T$cpv&MOX zWdXCI08dZJJ^fdnV&(~v@Fl>eVcgydSr3qBcUcF?vyVfGTAmfJKWk{j3|T|^%saco zjX}&cg{GQo)*~~1X`#pS5B&cx{QornKg|D+xYvdEzu=khyS)7{J?~!lUJ#0bX?gmW zUEYBB>bM;5Ye272s{hgsPBV0H2pb%XSjK3b@7$Z;EN&`c@7a>IKju>xVLKBQ6Kt42 z^U%4dqdt){Pj-ko1(uIh$5SguuZZ{a2r%>WNLcn9WdyjSI-c#2y^G0M^&X6?@YPMO z*N+0F0GAX( zv_zSqj8)Tki3Yr?|6TJcw%)M4TUwl;sxDm-$1BW0Rmzd(6``D&$yjv&R>Yf|Yyvgd z@fI~+SshOt5!jX(84A3R7#a#ZZM5G*8TUM3CtdZjZ=9Hl?My{}li!ZT#@f8o8ND%^ zEo{xLbuYDkP}}S6N=0_3n&0TJbuYDcJFh%V43R!>=ui?f@>M2|^HQAfiCh z@lQku?QGqghs%CV0;F^%h4}fRU)d#Khtvk)owRof#)CQ<#4SLB59AxvgJLWquFxPU zw_=?%x+jWZ+N#}_XTc}Cr^L3SLgo$&8tp%TRQU`}WaS$nc!zPp4qNEbivuO6fUfx zqr?#)a1}2NUOFf8BI6I+krg~Cl_z7QXmWkIDQB=GK0!t#I7cOl6P0jNSk9=2Ijz1Z z#^*!`T-fH+U}wq|6N%(#&eA%gd6=Ue%@BSdWinoFNLM1qkTyAgJTtdOdBV{X<%tpf z2qq9)m95wqmJuADC8YjL)Mkb#PbvHz3L|{Ghs5WIlvmbo_3RidVaHU?$LG<-Mfecr z_GxBrvG1r1eE3pG;R7Gssrg_|ZrQ1hBD^O74e4gsZ;uk5(c^wPNid3jkgnK^WkN)b zM?SAa4#ZV%5xAQ>#c1H#jj3{Lf0f_3RhH-~r%RRO(oQY0AIqJ_Z7<9gX}Y;rER(@^ z^NjX=p^v3FM-~s0Hg+{6CIMp!P7M7`w;p#=b0nfn*`u6?SOYx}LwYqd9Qyz_$4u1@uE zGwEKOmmqlbpekUd#zXx7BLBYz$9T~>W)l)cDuQh&MpI(0eyG475=zf1?RSX00w7de zNZE%pg05$B7Zz@W_fRCc^m_I`k<%$iVQKVct+34`Gt(6(4_-9%B^}8&736 zntXU)I1<3={F~L8h%<8XMcB5+M~HJ}be2CeCM03|aSp(5kD_`h=c_&}@uGT@AX>~l zRN>0Y;lM@X+kE19CYFq-w2Rd1xMSHb5?dOdjpa_A1Ry@zO;OMGPQyA~^XXtWccb%D zxkoX`PsKTm5$dhZ*ev;}BwRJ|6U@np^O^cN*qSBU76;mZsc-x@_UOk;LkGQnOU=YM zUgzfYvrvYSQt{`XbDljtPaQvPf;zUd8aw(>pg#*S=y~8nLqzADK@~N>>5IoJkX9cp zO!-0?$wHfFD?6d6#L1yGrI3BLX}*X@>vAgqqPs*LO(A{jRws&OB6C7$&9MHG|6Wk? zWR>N0tMjx{42|{&3f_&>H0CagLduo1Jn|KTDf(o}IMMUmlPTm#%w02K6D2A@&F{FJ zJy$~hnKugc=M~#&2CjOs&kM(KR-GnDtoXN?n5W8}TlC#p?u5lS%RuoV7TmXy>Hv0I z|Kiy;kgXKw7SH1G&WWl`a}K@SL%Yt&w8^41bsOEZK|A7g6?W5I$Zq@;(d=f3F8mgS zsUR%pt$`g5$#rO~Qc(dMA9q5#l*%P2i#(RK>enCxAQs|(!^@#`ut2>#Z})>UGx!Wru%%g6X7az zr1Md&)K#?djTp3Y;1u1&TTV3v5VDA9iLG_+q-D%$(n-17#5Skcy~;W}@ytlaFFIS@ ztB0I7+$mh^yu_7Qfo|I%Td&@k``yrm@>V`}We)(ekKR}PC^fXarNy5|v2%hCQd319 z4G}N^KHZoBF>{C?0#`rVr7e_{k5cJ$HN49QJpHBGM2%ISAMljW0L5-b@zh=#Of5y) zx>`~KiPVLFv&H%DIB6SEDd3HJZ4qMnqhaJ5xyGt)pmxQSUY6}{#GX`SgB|&0D$>(0 zd-c-b2IsCdIoS)2feXLiBpOuv8)8?DRtpA5Un|+$kgRH)sLY^}z6R7s`g$7a>QNPd z01Se-d;kJQ3^{y!`9$HmLvr|31eQl=>M`aZGu7ECI#9ae&)6n1$a?y2t=x@seyQ)i zCU@n|Pxal6+$k|w6t_GCeryJN|KQxvf@uBSK*o#=&FuKyX6rNcFKb$au5E%8TWb~HIsDqWo$5f6i9r_ zECey$i%~`%$aT8nXNnfVMrV!2hP>4t*(T>t)iU02^*rfFh2W{4qzTh4ROf7?kQ#K9 zBPdw8CN@wOabDQBOCPx-CPE)#AXe$Al$pL)3Ux>JYM$Z7yV<4nz8smHuEl&-0g_-Y z*zLufc)wRBOtZj_X|TYLC#vo8a&4=TEtgud1AfYu`&fS)*GU_zbsMF+jYnw1-E!}N zo*pUOa(mfmtzUkv+WI>Gm0*uZF1-i-^@QGkSBp@jdncjGC6caH;;JNgg8i3q3%9vN zm%X1}R_l#jdbz)_m*2q5T=_(|zeV#r>d43*QYp!dRaLsBwY22EK?$moK0cQ6-I0kh ziQsX91X-{VHm}t&91@{2$|CBmB^RGES;zMo&P{i-H*l7AJ}{w~G`~gi*ZI!WBZaIZ zhTNEqSG{Gd6au%edWaI;P7QAvIaC|r+Y%DGDRooa*k^G0u+m4l=vM17njXBU^Vw z6uIuy>^iI&YtdW*t>XTOn0Vp3-)326Y9+=dg5uZwu9K*sb^EG!t-#guIhB*Xu#U&C zIamK0J$y!83dnTOiKt70Tcs`~mrm5K7Q=b=*h3;Xwl-Vwqk3UBhnw3}Ep*T8M})gd z>rh)x8B<{{W&QW7T@LI@yoaXAMZvyB5=NXJ(Iuo@f>XGI_5tKr0Fld2Yc|jA6+8uf zZwz4s&pGJUto!A1#zSX(Pn=^GtCxiAyz8Wt~@Hp%Y z;+Guw$!hqYL{Brq;2M|XL0tRI)T|mUas)e#%Qle@&g|t+aCdibj~ZaQsZ*GrbJtJu z-2Dq?`W{(!;@+xpp2IIL{_$J)iMWO^vCVm4KV@GgbcySBc550E(#j~U}S%K6}!oG>J#2jNY*0yR>g)Y%~gkPk6D&R2fef$`9Co}xeA zo+x_>G}I9nZKGATn`~#iGUn}cjztv0MyRn#u0euv&lDZbR3Gq=yq44W|F6WqtMe2C z&yqlZ`{?kAN&t7B7b%D7oy|m&tmymWB!+wC%bZ&mm2aP9bnnkw)F^`rGN}HPm7mf+ zo1Vvs4{Sg5x3(|m6I3?ipgx~{dEEzwzhW3BQ^*6K%?GA&awo7aF8RQiy-rAl2Yfa5 zW%Xe52gcQ1#_6F3;|{Ss>5%!`KK8INv<)MwT5MLH%n^mTadx(GX!2ziiexszCk?ul zSim1*nLa1WXSO$IRAwC`iId+Gt$j~r zAws26|2X5_Ng0yVs59Ot5!LHVI*J85BRyuMBnF7@YM8?DCbTlp8XGUu0O+eo%$OWQI5^ zj-
?L@2V6EB}k{k`$Hj+DW02leAQj;8ObUcsu0694i5zeE-~tC{}87E~YH(V;sQ zX|Za%L0P)w+*rC~<6-MqDu>ox3Mgkkts|0=3Sb)N@^uJ~cb{g)k+EPnT|x?~X?}jm zN;Smm*qzOiTn%x;<-hv_Z^VQu@Qh3Mg^#l&D z!&pe{9f3NDaS>d{I#A58Jn%@4M<~Vsf42qlq)yh%eUZPQu3I^_izb#<#jc zkPh?j8=#yE{fXko@dx>8e2-)`!!?_j6t>A;w}M$FzKZW3lf`mKj4XEIUzd*-mxTj6 zfUpPoJVq{h<#T^Qy%Gnzc8wn$vPWYN4a`#R^-U0~xYyk;8TUy}%&vH|oEQ50`q#Vg zuajJNztknF8?}qAxq!QqC1B$jEP9L8W4Rp4akf{kqCy8cQ@_NlpPmjkK z@?rca4j`k~!p7wk@_4)?#rhO7c9eJm^5+op=hPu&1XejS!t7qaS2pDcGUuoHBmw7& znFJVzoY;qjxGS+AT+I(uS|wOOKABQMjwP9_i)*6wJu10U%Nud<8%bDAfPIMAUB1Y~advk_ta-=$f=P+^g zA$wS(>DOPxeuc;g(FlE^#$l4?h}1W7Dxb1=CZLD)GG;Ps$X_cpng@f&+rNM0Rmzw> zp8)gOsEYVq$xll{Ar42w-En{@yF@-+k^Q2~Vy*eB$Ix*+{4Z|Ae0&~QD)B>PlAOqR zWGk6bTi><>fs9qQAC-ElD|-C8SmTtsg+nFhl=^f7W&D<1KO8$S}JtMKxvOyMR8 zhKk=;2>^X5^5>AquUs!hc=I*u2Q_k!YDCu@NzF1TeWshOKt=$`?nm;G z?~d^;@7t&2M6#4o<~qL56xd+g-oO)KMoq#27D+zUIe56Dwe0%aV}U)S>HDKqyMb?Z zA|nxbD<2d2wONZNWu0-yyUN9L6DGOzDd$9M|7-=GZ5xS8$x>eqbw&~r&sJK=SE1vr z60|E!%strU-f1aDHeD3wlGD_tn;tWnYph)Cyx$XzM{}&Hh;PmlSc)K_&+QH~dkyPpoM(JssHPyqMc z&x>f__x&CJZR34-4DHHSlLp`?cb5m-XqQ7#g`oE#T!4pjPm2XUS-2oq;c%T-2!aLK zG=V=1M4$OtJ6;bjiPpX;D2saE5)cG)eGdtX`Bb3aiC4G)f0?pikGcA#=o|&Z{p|4G z{uyd=TqaukG#M|%pylnv0k$c8!sZ%O_!T5$&LI|_G4LsNON_TjooFBN^!SKHier| zHYzO_M|v+O8*^*3dc@|Om!J8v(1Htc1zp%`S;FXmuxNApUu0Kf&p?L81bY*P@Dj<{ z4U3xxsC5D;``ZlpE~;_+9YPnZYLaykf~|t3v4%!#KVq+7aqrdQ_C`Lr%~opMah(*e zJrE6i*oIQlPl$(}nQVM4$ejcwv3|x(W`E*nIMnpUC@?Hcg_WHkr4_>at3o#--shDl zafH+!u)GJ9HnmS^LcUOl6kp>?@jv+oCB+-<-{++TNY~^+LZ2F~^{KZGA<@T`9PG{9 zBpSy6s+92aL&IDrFb`xf!k3gH0O`yz?O!|PxiW{-!W8|0YUNi@LPo)q@EtuIFwWUU znsl;bxp^NMLFnjxxp`A=3jgKvqYd;SnaY5$O9qaLzy>1OVA0S_l&pz<%x2}pXqLR6h~wumY}Y9{FPG+! zul3A+rfcX&nqS-su&ggSb`R%SfE(8PZb%zk~=%$&3F(fLE70e@@f` zh`mc=0bll|YOiwfblV2OpPaR4SXy=y8rsjX^%~3Dp4ZCR_2(Jcp=c;XO$^*@|!SU}Nbk`-?n9ZhBi3cMe+POeWwJX;5-NW;I-6wrOn4Vtgl;SO7`C=e++ewv9H;7M2 zl?LPoM{+4&O3aYg>Ua;#LN%rFRG@?E=qIcXQTui0spC}0@CYyU881dP@kZGrG9%^w z%iPnMm}YMOo?yw|XR%kLmy;ng>oab@lV=i?dPcRp+hYZ~+nS&B__+zJLww{*dcAll zT1QEl2(U{wL+lBeVlL8`R+)hpS}l2Zl8g(4o~v?er^oMOklBjMr1w)FsUgQi=%^v5HLLCRX_TdJ0=5m$MpG|+8CyE#g#<~>z< zCxx70$J1!4w2f=QVRc##C2UKb_Yd;&9cGc2m%1;@)(~Xs$s7-PKKzQ{u$;n zN@KLXzMsC+BhHoX(Bp!nHXMy+Q9(y0oho`to> zlV7E$T0W$dhb#9j-2$NcVnm=S%wkbSwR8WasH7C(Yc)=5~&cv_68G1oZ5n8EV~zD0Y?-|lfRu{ z-neeIZ@R_9oPz$N141-A7O6kto* z>ckm<&F;b107yu|^uoKrlGs6W;h3zakiDoJrlf#*4^uSX`EX9Wh?C~_nCfWiNZVn5 zjazf4`m@`RKGzaa(qVb8@da)yS@ve&M&I(?xTR&=Y66~YMa^)S+1x3dTbxlI^%77L zw*3S^vU-oQ(oN;&TykZvix1CYiCH%$6xh`I4u9J2!}3&(yp*!$MpyZFHVg|0#rTdukxwP_nE{etgR~ z%DP>QHm@QLj!sWzmm9OfWYA`MQithH`097i%p7dF=G+-1bl$`<11u6B#tptJ*jTje zIKmzqLnX_{g#*VfsR#$YShCy*B^$B{FZtp5j%0{Da(qa-YfRw9*8dFIl6o*sp09GZ zkCIiUH{#0-3ni;Oq2zHwxnL?NBQ}KtH;E`UyB}d{-oeyO)qZzX5Wv9puR^5$;QU@C zApJFbjZMfog-|d1nsR$jW@2`W&Sjq-^F7mEfe^v6EAKGpPVJO^=I&4EQ1!1wq-44=I5FoCsZt6y7ph2AWN``>OgqH|U$?+E*sc7#?&e=)F=5F3O zj!D~5d&O?4%R<0w6w3CBILHG}#sh%`X;TLU=J+Y!5E$hc0#xAMDtc`j=@cmtSIHea zD_>BZ;d9=RBflwC!c~z8BIuXF1W}u%NMMarWApILWU#B{k*#VWIFnCjtV*fQOug^C zcA>n>?>~;M%3}9Q>7IY*?$>SVxt?~lTf5J%(9Z?s42oU6K-O~3S1&u%5QdtK;~-lJ zc_MuCedt<9@{09j;{33%&M>Kyx>wPcD$p;xIe$D$y>mOGGP&H$^pC5XUq6KfaLjndAZ1Ve5HqBNKXIRL@%%669Per*soLO2O_3> zg^y4Y)i2UfRr-yw*@`WtvYq^YoV^QpRMqwOpJZmp00}1`fuNwHM2n_qBwi8&nnPw_ zMkWegX+_0GBZ?OaGh7Nn;v|~kIF?_vt+uw(*0#2_t!=FWq9g>9aKCs1wHmS2Gme)i zHUvcG|5liLR11w^yp~YuKu)d1U`U`%bGq`@nj+^ax{B#28aZ-f@HUX}EtFlo?2B3NxJkkZ0g<}dHjPU0`p>zTYc4?*m7*y2&xqdBmaX@o zYY#9mh4y787;!o8Q4_5$s)=Id8B_a#ShXAq2t=!G`!<##ikQ9$L~=_1_1RNm3w!EG~7Blj@iQ#@#sw zOk;&A6SuYZjW_8}c%wmX1_9H+WR;u2vg{oMZ+O+9UiuivZXA2Vuh7@0#6O3HV0e0r z``%?{_p}<}A>38XO;N2V!|Z~@7^5IhzC88sc5{jOpl)t`7z{rgC}|6ppiY)?lpCru z&NRCdgV1lEX?7WB1_q!{*dfZn@6061IX^Mj5Y8;;y#};wig-Z)A zyTqkST)M-5mLw_a3S(TW&dwP+F{3ZUQ{|<~%K-kAkVYsUw$DxUoNi;Nzgxmc%Zj3r zi@0+Fv7nnsd4s5m!x%bH`!!NZ5Q=qmtJME0Cf?hhsEH>S3j`&7a}O=h0VTs7m2&2uT+O*_su(KLt9(F{=|F=(@$n${2oNutE4M?r7OG1 zKsuN$lU#f09pnyL!*z1ph8Z0iepR|R@u2Qo*^8OZL{1-{_Yu($ge<5e7@}}sM5#r9g0_nWL6inI+vYT2rfec)&t!2+P z#<~7F3Tf21{53o@XKI}3Yr*|FaX+(W7k=y2tFMdCZV?cV=*cO-gfyxK)j`bNnV7z= z;&c&>j4t6G8;k7S$-=y&5PxeIt3G=eA86h;Lnrk(+-tL{9?cH9qBUKKJj>T5+fuKX z_TfO&UhC>j*aP9bV;6StzJhIt%_^RK{2@bGcWW#YUU^Hv+HL34v}l zQc4F$@QYfSB$mIQIEZnKS7hCHjk{UC@(;btSJaiG9k0`ldGZx?{a5^At{uwJ^{M`9 z(W!i>R zb%mzlP$x#+r0YPjN!u6I8FvroLZyw&;5y^37I>Ap zVFkI{O@xhg#;5$>y{pa$@8*ANosr=A`bLqt1R^a3iI)Hn6okfNDAtW`2oyu&AIAF5 zprcjXYYZ>4kk&*ly8~8LDNa#J#^poMjQD^IFNyedeHR{FZMT!$GvVRH! zKFudGpleD1$^e6EP5YS7*X0ohze&@o`aYXJHs* zNh?cdRU0|V`04EPG#BJ5&*4>}0TM0oZ-v|XX89c>(E}&TrXwx8E3_{|GnkT!{br<+ z-6f_;fRy0w9Y2D*>2}wLYLVM%R~Ct=ESM3M`9)1Me+rJ>sF2tu>?e^`;uuIEDpNux zZBn!!-Acdq(J%OM<5|s*-#ZJ!$|~RczVO~duBE{6<#67AyfJZSU~P8lkDJqW6KW5k zV8S1xqgYa_=AZ08xG(ZAD1dXh8T9{gJDf?4iO?tU_o2lcKon zFBpQMLCr(q-Nl+Hr#gf=sVC_a|Dgg$XRyy>ZI858@!ZRQm*N)S=b|&Z`R@(yP5hL! z{YCwvHkNA?b4EQgoy%8F~y6kl;e(TsaWcP5+1$y`JAJjuE zJTN5G+)TcE@fL1f;6KR!h5XOv|7!jN{GZ2vnEyNZznT9__@Bl94E}xmm-64Q{xr*H z(HV#8qBFi~o`Ic)=#2j+KIixI=#0ZS9u=1Z(HUQ+Jimy}I3i9xX}^Ohk6!3|M&6?? ziIquwFFj-zii}^~tGlX&Z-10sYx5fQhA5`8aNn!hT#sPYcjDCKDtn#P(rt%sl8`X; zt~d6u1XWbK#qJ=haJmb0ex ztioMr*|10Ks9j}VowlW>O%#yb&pXLZ1T4O8tgkA#1vfz+GOf3~(OYDGhVq0ULcHV8 zv%wU~Im-Sud`D`o9#l{e1ns{N-&hS@w%gv%Dh5u#XndC`6twQ_tf}~|2=u~J6_VjP zg04+ccu3HYMW>r92bj&yQ=lnOj&`A9Mq|08M*md4J^zh(2_N>^R(h_d)mEU|y(rEe>mGCFG#vkRGPDm@s z(aJjHc<5O6M zvVXsfCOM(sv}gHAE4}$PeR4@7X*?}~5b7s^tZiBLAL$~0mvq&gW!`Oi}03c0YA}}k* z5T&Xf4T=eMI*GoyS?bpMxzs?KbFk_gmj9iJA@&f`(`j*8v$y<~t`OVMn!S-dzmXJ!h6f!jI3!Sks%8Q zRy@+ni}B+&WcpQh+dvtc;nO0M9{!kB6j67>oluC;1hEtHCbYs1Qvkb|TI!&;rGj1~ z0cfQxyCoA83nYDc{z>gaV#rwD4;C+CMN2h;maCBH!Kmv39)7Fv;6C;(`Tyb9o`umU zt5+r^5_*X+%}F`}Cr*sCdIkIe^~ZS?Np7?E;J!+W7*oh0y~ac)E&YgwvG&n4b$3vP zt@g`2RJ#awe_ebgG3ljo_JX_RWz)F$WI8duMj>QG?2OH>!r|!f?d(;VPn4CkDMB5hBuuMbc&94!6HDnDHsRoM8RkU#5{LQWZr)QK(8~#-?Z69a5=n z+fp)NueC8QS95GD+f%j~a7lLj_u4~zN`voPNm-shRO0rn z_MevUt-7gjKJ`!07j$+R{g~WTp{33x`>g+B2$tiq2>{bu{KnLO$;2PQq1BX#UCzcv zI^ZM@%>VHC9Uqy1e(uBZv}l|g2`v~&Bf*KNTOD(foM>nxiFfA5kCkVym7yPhmfvmL z^!v}CEt(F%ZgTFz%U~|*<0UvM?z6hBH@sF{g|~k|+*OfD`(J}=HX3tjyAIbdxEEfs zyv^t5^4j#K(e#G&iIx9WLKFzsd?X+6tfCu{b?Xn>_gUpzu6%pd(iXJdmr{%iifsoO z(sG_x)@+uf`3t3g=1b^`+;C3fJ#qUU%EDyfe-o0yUh%&|{G++am&yGGmHuAs$@diX z-N|>b@;z?JtLzP}4O;I>gO2lC2jp3r$yt}cllUp4?M@jKvHU4K>@h+;V}-YCr|ZON z5KgOnq9xXCGzDtHXZVZ`*K=@#(lpvb?9Z8O=zXjaJd=h0rYz8OvBklbeleq{@6fT# zO&Pp<;y0+dF7uyN2>vVwB9r3JuBH1F=p35RrtvjTk|i5&9Kc!pTr&7S$STmi3NkQm zMt10H$Cbe0-G$Pgh_zb6XH>W@C&7g$GvF^;0IbTgk;7ExUxcHz&orjKZC1V=-gNv( z6)J~#LZ*e1O$g58IGw**cdhe3Lo_3$P(|?5IIm2|WL`|0ddRqyd1am7LC940JNmL# z%~vXl;j~!f*sSsYU-Ir>cR9&M-p|a<v`1 zz63f_qj-#QH~=nuC1}0G6^bdQb(hUl`8lDJ?eX(jYS?l-Nl^pV_dTYy#2+a6LR~;( z?c~&s<=rYZo42y=({rRTi*yC#%)%*L+2zn);L=IwVZA09Y6o$nE7#}cj%%4Yw}6Ht)bN`l@-Zc zZbz!VLx>SAP7999$&L%M!y4N(kbSu%EcwmpsA+^X1( z4oIt~a?Hp7$XkYJ5t=8m26A=kJ?JvFG8s_yHzFR^%$N@o!RB^ zmQbkc;_IoiYGaz*1+FaNIapnkRmF7Rr14g}#zjJ%c00ur93&Wz6c?fTG*f2}=NGVJ zV(lw-Ow0*ia?mVt%Z$3fswi1f(3D(0-gc4@8hj>72kBiFf|7QG4UQ2swk$CsU0Z!8bieo5<1;~iB2B!5rn=AOy)#tqbaEls-$-|@;fuUBYtB2%)jbc9!ho#7VN`jUY%4J^54ZX&T}r}i)v$M9zxiiydzJHk}AKJ{B=Li|sPEOlqg0N9Z%{KtFJ zaXajDUS+UF#xl~{IH$Nh{0fnF5{b@IHeCisDzn1_l7$foufpsMzjyrdpxC%HpSY~J zAax9%8bv0>w=-l|<8PP^({M7SvODEXZIP~!lK_r4gR%3kBFjKz;@ceVRm3TA!`g8Y z_F*=psfF9H-vnyYAqZ`l-o&J}a0*%W^TMhiHdT?sC%|8rj#%*3+=-?1oF}?_lwHWqZsu4@=9u8>i_MdM8%1OD$P~ z;E|Vc_V;g=ESuKAclo1DodoyC)E5K46aw#NjGyO^`sQQ;d5f!(g}-}O0tSmcR@U{mCvZB%rb$Tc!fb znXG5@_RYl}x=-w2%D&EC!JMUclte3ZAw7OwAsw8|lmvxNWw5 zIb&|Ed=YV6{Hr;_@}_v%Li+-&-lce1i?oIIIxLgZ+N>zswy`!$P|)#%lZzBi-g-lf z;%%`x$CGTRElOe=FH&2~Fp?=*TfmRn)u!fn-`rw*+f4Eh-L;FPutwvLrQ~z=NOf9D z!CmW%rtNFA%WQE8tGShQy|Fnvi}e7=a#onLkmoprD%2$1Sb&H zPHb_jJxWR(b*R%J(duH(6(tH&ZXMOd5~riuEsut2MLDQP^h}nuMbUY6s)p(){u!E8 z)L+I6lFmc3f~4z$c0!{6{`d5N-%Bwi4M1Z-L9HD|^!6k;=NcF(LI~M^|BgUU|GUk1n?xXpHAWonZT#tFe zvjQI8WyUVe5nT0Y0Z>oY7Uu3ngcfKu2)_nhnKh2aR*7i#RF&5oO}~>ksWOTr`X_vQS6*UTpL>DI zRmqRnwDts}S2z0_@qbXlKQG4xM9>+%=Mki@%}Z3qQ@bY4M1*O)*fH67@d5X|w}6Jf zYa|>d<%dxc3N*YEuZIV%!mIVLleA}9TTloz?jHa*i>0(La`p4E)N^L)=}$cer=H_< z_@==;P2${9aUP|;%Ng^=M4V*|Oadw=kP zVUL1w6)|;I$;REi@%Q1Aq{XJk-{Q@0jXD1rZi9S@ck^I-DUB3%5$_W$(p;bAtL!qZ zO=~IB3a|~52Vn{bV>AM4fM3{58CeORFzt=vzMHxtd-;7Q2L6 zdNHVBk7NhR_@nrvNN=VTl}qax7p%m)^~W=$X1DmX&vBR z9wT}x?P{*4y|d!8R1@F2{H@5L+l4P#At*wbc$xd(iP&OLL zRziJVvX%Mhjo+gR#H^RT_{2~*WHa~XnM%s(YwTHay>mvpw>VhJ1ty#Z9q*e?_4nD z>+K7ekA!=PJ8%%C`MOWsWDo|YBgr~^Rtw#+#BH)ip(_WQk@pY!D&O-Nm%k^s9?cWr zK>bKg}m#cvyE#j>J zN^3O9VMNuF=Smm37v##A8&A*lv}cD3zquV&c!EnmHIl!+Y_pVt*@WxtLmaonMi{5p?`S!&ne zDLRlW()Whuv2)^R_?L`6POJPF_>r_Er!HkuCwJDwR^-?=vI;d1!sl66uVN1aa)O-E zd~n|#W%9>jDjxOs*w>M~1WtyZQZ-1Rzo8&}>Lbif8Gyh!yX|kzjjPUZNJMxO54{ zh|h#6qKgBe^Z1<;8prP)p?rQ9g!>aJh4nUPRKa+7>W9C&z$I4G~B;7yWn0AMac3SziBn@ zt%Cr{sxV7N6^2=MswKjn5ma;OR5`$;E)%RWSMIdu5|3(7AXO8Q3m+TcQe0?X+>Jcjbjf&5cjjPC4Cb^7?BrwZ4LVdR4&M z8_L5{4Y%F9%w$&L^yH=P#rYtco0Rxk3HjJK(n(wDX>JtM=dhqA-?{# zgL$(bg}$%aFq2p7b#LtMPLbEYU`uaws?8uU3Ss&&$5JReYQMG3uA7ZyWtB|a$LS}3 z)Ya6I4MR1)U9cXmERqH6zo4S7!*pjMa2m+Cxhb#yrtL?BJ#@dWy|MuP)-E+R}i7 z#Rqz0ZpeZ^^H!95jbOPtMNSWudMhTKLHX`k0GRI>aEo+*9mav!HIXT3ywEA`C)@HIe9n#FpBfn)BLSjm5{>Di1UfnxOvy;nVZ^pezt$e~&wYEQ-!W#*i}a$- zgLwn`L`qm-yjblv*4eq_7nlBMuXZ=YrY41XiM0NA4JL^=kO(j@Sy)Qx<&ZdAVYwS& z;S@$vvhXxrr;1>-%y_mX(wh?=C`m>-s}gyUUPpM4{&F8*pNpR-jzOwJv)cRznea`SsU{`fH~C%F|zS^w&`^!i1cszq<9;V*RyW zeOdLCMmy}*4y(l><}yC#QQ1j$YjNKoGHN8@hrs(G!Lrs!AYr8>>gev|!0<@oT=z%e zy~UM?*n=kGDk44eF^wRSFngVj{(Or>=d5RmUgcxRbdOD5MCqqW7sTJuJN%bN#lPV9|!vq!qcTAgjY1P1k32 zDA1X_t*PdxF4}N2A3SasDi?Eo`XmL4HmHt>{AwO0&RQ#-WM89EoDuQqPNp2s1=Fn? z@IvJsO?Lr!UCZ^Gzerk<7Glu1@~{_Qm}&C=(rL%kA=TDC+SU%?Nn_lA-pa>b*?w{c zsuI^5zhxVbDq*T>N-bvY*`I08QY(KAA*4OG5uW%b3*)xcep{$O;bTa&Qti1$8h}Pc z0MZ8@5c!przfkf(rkJPBCr9#c;aj9*LW8R$yFm&#J^2+fhhk^kfytu}qVd!7Zr| z@T<#5QnE7LCUmI%lM$+aHpF>+?Ht}8lxueU7X<1rPmj5Nh)?Eh+;Y4#c>G_imxg1} z?XQ2xtf_PQA4;DdwAL4|rmX@NtOFqUE=m++BEU?uXv?|cbC@_m>j8DklHYo!_(?(| zv<5JZi5t;&Q17j_2xshXzeL8hm*oM62XwUZ0fp+Su{JdePfrq>2*83^Lj~H0Zf*yR z7wz-1=GmKxFN(YADaBn?n##>eokbV&ri^+YBV4i1YRpq+=R;7zQq~Md__`&}j z0^09wzGW)^&)NSRCc43zcQ9@vw@_N!If`Aw1k8Nfr$`9pTiLxFd`itbfD`TWapl<; ztQ3dDAX$n(K^SOtM~)_!xhfYpm$~f!;MH)6j3A>)uENIPo6~5zkdbLk&5R`D{xfh0 zR@@3JEA!W$i$omY9&;OR;Y_00-w_$PwMJa*8ufm8dC+>gm+yAp)a+n1e63xvDVa>E ze0SPU+$?h=?6PY!B?zA^xrdAT<-BOH25zKS!92lqf-0@fFHDTcKJwU;^lj&^Uw&DqY-N%ojNCx7j% zY^R-vgWu662M;#EkbSHqFP(OHyBOdxnqnknBJYc|5BF7GyzHs?LL7ot_}X-1p3S#z z%wpd9Rmr&j1RT5-wRy&Umr-s`@GL?futAM7#Ru~Bynsxtm(wb4`?L*6m}{Qrq9zQB zMMe@W7@JpMRxIo`ewd&k%*usd7(aYhn&e6r$(q_Tf)1ot&1+R4Hl2W?wEvJ!iutf`K6-Jj6JT0Dl@Voyn`REtdAu}eH( zku_QN@eG_xn0-_GrqOO_)yf)GC!rG3VdzdFToGg~48u;%?<%yZJ%^O-YLsTF0Sm9O z#1LxqSg}kBIhxI*|ISN>#KLV<Vh+RDuitqv5HP!x6fjGEwm3v|L{j5*g>jV2Ft~%ake?@;P zn*DW}#qHS*6*Ne_vS-N|!To3ej7F~N%Cf(QK;LgYRlG~86j{^7BP(>1prwjCZ~^L8 zB_S#v5ucF1;o_RhtGhx~4HsWgUEM`fC}%pnvDhX6HI@yS)KhiD4jPxL-a-F2y(mjD z(s_n{A~zK?a!($6Q~OlP?uFX#B?-~9&F(*y8r5QexhPc# z%!mX@*&oB~N$l<~e3juW_CLQKGM&iJzV4+HY3NI&p6#sB6rrN_+^+}k@fLhk z6`o^H$4e|-G#0lI5`o)H`I(;08QofJ`ijYxDEd+{+rbI4SFqfX?g2d~8&$v1OpI~a zglnCqk|@%Msz1KN$WAq%m~H789_`nb_lluG8R=>fjp%E<^>r5+TLkQqGJ5jN`2w{W zVCODpvb5>JvZQZ9PsK&c7uyfLAZhMG6j~arUChqD!x?lOwwLue`)mDq*0%>WeZ1W3 zukFA*Z@qnk^4%M#&GkF>+U4>E-A6QC%FQOLWifYj+6@C0Ez3)8mqeRBUeSq)=Gp9I z2ZX&@I1?yV+Bk^0vd`*_g6G)3VH0k@$PaqW+8n54&o7& zxUnc?*!L^0X)pa(M7{j6YsrS&?3O|LXd+WFmI5w#IWbO0$+NGLD77~p6F#WJ>yQKN zLr5J~X-*7bBC`^ZvFt!ZO{n>~e&x>KK!B7XDWb@98@**McKX=-45_J2+MUcX%;@I= zNJTm;T`F=&@g8bFg`WsMYO7g?XmF@kRr!(vt$?an>JH}}bVzF$jp*C6ties27Y`yZ zO#*M)-MD$lwd~;wK+#*S?<1v_+2u>_0%NJf5OgdO(yCd~u|{L9q=3r~`|Om<3|xZI zivz$FV%NCsY8kK50r4uFRs5b-cj#k6E$2 zh}QbaI@MZOT%rSYYmsgf2EA*>h0E=0RqCPTIg?5`vR8i+01 z4*NxBLgH%s6diJkjyF_)m8bGN?dy5kp8(&dr3(><6gzv6NF3bf*lHbuG_=d% zL7^wy_Fv=;nx`kvHvw*{`d=H>@X;z&XINn*Sj&n%-c#4BH6?f|V=%~vED!b<-NNlX zDeI>Yrs?*3IADXNTOwntu}hxTqUmzWlUfX{seVP#{MLD{yqfwQSK>+`&qBNKD z;fEATam_VZYJ32Y;0%ZT=69rUdnG<)S+5G4Sc)pmqY@}0fynCFvelKHixfYi+aN1+ zsx0n1Y}e;fTfJ1W@9}u8sv>K;({d;BgR#r_oKj?6>Sc%vo9^Qe6almVy0xXd@;1x47b_)1*NfC`P&zvwSi7qo4m){XX_%7pH2fvmw*dzhsA;**uSO^kel2unbk|u5z&%=EQPN;Vmt^7B`z4c zS}q%?Cs2+CYY*CQJxfBhm$_M2x~+-XGKlTYer}Ov+0LxcX#1IdZhEKjYjNr05pF<{ z*H@U^D2!d-FHSn0JNmilbiUh{j=9D`x!E)d75v_{GNfQp?Z*g_IN57Gp|7H1nAKAm zrQ8%f)jSz76udY-h^pK7A&u&5`^9|ewx{x;K{Um3m;HN6cT92DL9^+Z6s!*TDi-E4 zYe)a}UY0$!%yD&;9hmq&d!!P6tye{gtWgsI0+c+(TyrMNfVuVz`BggJaCt+ejF1qL zW=FUH!@&j$tA?esMeq?i$y+g)Qpr10Fpa`li@Lq zC~RCfMdpb|N_LX9pj>aY7fFO4NZVM`#<8_aY}1Mcnbiw0vKrq{Mv)b8vu9hmV>hbZ zi*fIE<><)@4Y8L$BctnbHYu$sUeaj%3*NdR!gtaJj`_DXEcHl@7v8`D(9+0acUEYa zJxgM8b#1VAhC3)*98_hT8xLJ1*P%T^5ac{F z+>@A?4yR)(f|v>VnnYZgQXN=Z+3x9x_D4)7;2lDcW(zwtNA$e&7%NrWga*vHPv%pQ z#@=c0qV;{wiK$@%Zv7Cuwjr{eyYnAZE0#Av7zgg{L8jbEQSmkWwTqngW&OR&zXMzG z2fTZ7>qCQjd|3rX)6aD1p?0x^M1~G4?00laatG{j$_?JcC%lQj^0UxI6*oFrh7ZzL z#|QpW+-k38KX4iHxQ*Ds+hy-Wrp736f{v_sI;YW>6>|0DxRG^^wXCmjSwO@m6-sw|%s+0T58` zP23DpzrdMrpexfw>1#MuD81I?*C6Un7M`+(T&07OyAoM8Qx?9L_Ou^T+PLOjQYd7G z&g5>qFBWgTR>krG=_16PD)FPPERmb5R^GPFa<%^u)tt3)>=yIjF7MbD@8DK%)b%$U zktpEi{C< zvSk+>Wi($l#JaB(Z=TVoL=JT=TBcAIRK#!i4uR%~a|l^l9h|~>X$bI3@}?lbmjfpt z&?W*woWzXoq30+uQv)&Gw<}yrL2fbktgZHcG>ywo{K?+4GYtf`J2J0w3(7ECGPnPp zan#Hp<5_kVdYGio!AEX#i*VvcFSA!Q0a{`;3?q3g39{5?6dY3DnCnggsQ@`TC>GeU zPk~=bMx9D)iUebfMYAQCebTWBk4)fT*S&3SlL-Ulii-s$g(}w4$ijb*tyfG6MTlzD2Nr7?t+ypsmtOpC^`Xf9z}>3Vq<^aD6@8Dm@}PzCFtx(pSpbU8r|Am)ATg;341Y$ZBD#zLMU4i#3UUYnJxe z4qgsJ5*iQfvp&>l=%?%0NE(f+31~j=A)dKbK+yqk4Z*0dSy0i9;%7Y9v34fjb!JIh z9VQ;Q5GOymO?wzG2Hjm>he$(~{zVAm#o;*`Bt1uiq)&7s8iVya4ZNz|0 z9FuyQ^?{^O|KNP3(I`rWHBx@L-RnEV zjT}mYrGKWv0G2+9mpF7ZODtHU3js%QGdpma4mgEIOi$P_9)EyeV5W;ePa_8q0d&@* z0-N?)cKi`OyvBxc0{05WSMmjV`rv&G2Nt58rz^-?(6SSErNPe*@Vh`8z39M37qeRz z6m@SVb6+E1RoU%;B&N&!fQkR85<36}@(geJh|sCVv%qAoUS)47R*6$uz_0CQ@+E$~ zm7gwqL%2{?Y{7YgT3*1`7p!8H(Rd!CRqEn4B45&wb!;MMDdvM0fBZ!8O~JjTlr+6= zmUN|6^VBg=XH(43g=`vbX85i0qT;?KSX=)Jl}8h^N=V<$LOARQVkpKiGj9lKES82# zIP0Jl3ZJd$Wc&)60MBLOS?6@Ph*o(67r}D-G+-<|ulxLQqHZW4AqA<__-!9sU*)txW|IK57n!kur!LYmBQ|4C z9W#HHwA>AeoBN>LmxIfaz=IDaE)7@{`-Y&s(3fgBqfss{^2NUEktXj4>{g8#|0TUB zZKZ+TI=SQF-MSAGlceJnynB;d(mF}?ri_b_8+5m~xHoYM1Vf&J$Q8_Yt$etA&{h!M z%narHe?)jUC70x?0j2TXz`9K1@}w26|3-8-_>`S#Z1*YOi0#gCv} zkom%IR3~13NRl)wzgqsShK}oH@`3W6CMw6M?`g<^(bz-;go4&VfvS5G4zKm8&vAy)V z{l_aQkmXyWTty~33y(DF`(5RU42zgpz~a{#E>t1cO82p&ktA*zNx+OIIl0*)g>iF#J7 z6QSyK*`?1*Oz3;(xGl2gDy{%Y7hRPFnNkY;{VmEw3et{EJ&RA1l7~j?l1qc7O8#f~ z(o|FH)s^IA2#ICuPW!;m)S52S894aUl))>Dwcx1rDl99uTA!Ql=mlI{ezjWRYgVV= z!2*qm35Ov7;rUO=n0&0b6!I75fgXYC!7k&8e<7A^`u zDs*db;zwd2kXt07Z{m=bXn>Ry#1})5q5koCJV`_oxw=U21>q5OW@K^BdEp^MaVpvu zpGF}3%2Iw4GJZWq{2b+H&&29r+t6~d+Wecyxu zA7Kw?r^y*Drgvp)*cBO^><9Wz?K3iRUgRF!^TGMfr19$x*B_T7>!2DPuE%(jW!Y=J zY3)N~(s1Ce&@Fd(tu3bYvf0pm+g-N=8a|(T%Uxm1_6~02)UJ2%M$>qBquFr442R~J z4WHd|SLin1;C&6B)tWV4@8Eq^#>4Gpn~h(4CB07AYA4mn^EK`XU2g4a*x!9g(r7r` zG5`riNyFjU19DK>HyoZP&yyN9K7Hs&GIH3n2%NT*OG?H$h20B=do9=bKL$h`=|m>x zI*ZptE^eptQq{;QZnNRtETh{n8xCa|v$K|Q^r+KGz-;{0;$3MkJ55SueHnEYL!#YM zC}Z7w0~bip9D=^~Fv;UGe8Kb%SJOS|23o`wd(@qycZm2V~P z(MjeA1Hu**`;L*1q`4PsfCq$X9AEQzdgO)!q!63@5~kc_A7rk32f}JLZ46y%Mh?{p zH=&OFvKY!X6PY62k+_tg8@wCSfM5Nw$&x zR{K2v*jBl?vx#AkJ{k_pD;*MyO*!gE?SpiVyO_5I4KIqt9kwi^Bx3EQAKl~>UrlQD zD{{tj27q}spgniI-*J>>cQv2h=1bPy!qaLyxc9`(_3w7ow=QnZyATyaHb*$@QfM$~ zDGpLDZ#ej7D$u-UlJ+~|CXO7%)PMCVhd|SSP%0BIfUVW|`2dyD3i4-=2feAFt$aKe%Y=w z9UEZ>Di%8G92=Xlerr2R-X`g~$V*3-?5u10c(MQZ7~DO&u#Bq)lPj~oJ>|)F!nDj# zu3AlSH{Q`0N<*tbIH93sj9hhkEs1lf0D`K_>$K4 z#b%WH%8h{GO_~S)bz)0#mvbH^;cN-lsJ6z>Yj{`2MWAHcVt0FP366<@x=*p^3lk^p z+i*xMTrDf}ui1I<%J+Wz^GzPVy6Y@gaM`W)C8&5cjIIIA`V(s;$L&CvgTV?r*q8t=2$(% zGIO>x8{X#2vzbNS@?f!_LtsqksuZiJWN899{$I=$9XDS&6O}#>g|rdawoF(Vd?Fl= z`8$2w)LYwae<;n27?9(*JxtD}Xyx_fJPA!uk8yc>q-7vB^I2Kt52z9t z54ZXq`}`$ea?3hw^j7ds7c*vFv$s~r>S#2*D3y;*%VGZ!a2CSy$%Jzg|ZR0R|C3dR7I< z$pmoB-Pl6_Z&tOl$5rujR?g7y`ifH4h}ev5zyTD&e*RH8%2Ce_EbD*kVdSNA$00KX zjk&bnDW0#H?g*~e`Y5>rU9MG?>CCFWWFBjj2rnV>TzsHVVuwxZvlm1nDvbF|k?2%$ zVZHtKbYei4Pq6F%nM_8<+4~?&)=P1f5(Wlre`HJ2U-?Gp#IhE#eMv99Y+(DpWS1Hr zkLx$(#Efwyh zN`QgZPB4>c!=`7beL5G6$p&f0;|$h@O;WZSH94vJC6JRG+k@O!~L zB)4Mt&^{=dAQ|!^g&koxqo+^XZheczCoYAfcjj?!NbKMhQgIobwOgg$j*dFGozg6E z(rZVuBXW;3YlTOM*3+Q;p?vT=5z z7qG|<75rBOmjO>&eWbeCu&(ED=90+=Wz5XWLUYn)T5o$RyN#N?^boUZhkcR_r43Q>xsA(aiM&?j#GzZuY%K@L)nLTQ5pi2^xcyhX81F{m!slOK^6ZGz5 zt&!d%%PK2hy63`1Yy<_8%BmD?vYy0;R;riv|T+ z1uJtd>O}3CY+E_FadY@Us`#NviGkntJ^7CIaZn$$tuG_m!;V9!Hxt{vGP~Rnrp8-W zVj3LL;KpVN`#Cc}k6#EzN=YIkRj^LNC{jLc_Z7m~f(o~Wqovj^7$>%9fIOR~*)cCbC(C!;d_nfQG+ zMf?_rku0>SmCLgdDN(m& zAk2L_GY?B^Y|Ohd9j(EfNBt@K74Z8G;MeMkTNbGK|Is?>Na_rCcM`JHAjC~}G!72& z@o69X*>&t^Z~xDjIZoSWU=ws?0U1|J?-p%Uf#~Z+454oD zFnYR^^#b)u<0kscD?9|z>?!+%me3Nmh#QxX3$m9x{Z=Gh?wcazS>ainC)6e192ya+ z5HoSN-!FsLk`(M6s+TQYnM??OzYuHab3#j^c4{ABp9Fn(w~xyy<5La?a`*~oG(@C44oKRD-9tfe?*r>0@zPT z;MhOLslZFeDFq3!ViK!jYPZmOL-w7+q|*6zSfxG0zEhr&6|_FX?Q&Y5Yqcc=m>%WB z$>>OjM{L{u9eSgO1@ehOD8c&mth>OYmHbB_uDKlil8IPz#w6!wKYUcfTQ% z{_Y~U^jP{k-FW)D|EOvIernV(+|{U&ARL~TVUzE-^sq^}>2U*OL5~}6cC)mXKu$C^ z!oGG{8YS$}xpEl@_^+SH)`1o7 zR3YqtM!X?*44^))-^!G!+>7=Jk0TNaytbe>kQSAyK9KnhUMVuE;YU<8JbW!HSki8V zXY6pz#RC6yX*5S0QTG~R9T1|(J!%{z5j{ zQb*P{3U(vBJ#~yN&+|v8IQ`N4JZzBHiiFL0uBGn4pRo71><${HP|$tGU&iJjolPo1o5&ArM^G*RB`>6G%str2s13#+*`;%sq*DK zGu$9I;tHFB`Gu#EAUrRbh)U>uo>Ep;z~*ca5Xsicw)y`B19A;(>O7A`xh8R%*LZev zw()FdBENF$f+1FGyEkv1C)sLNb}sw`6k^WG9M-8ftf@|Qc1KtTk?Wu%YMMvYkxQt9 zm~3Fwn)?v!Hvy|SrwG6LQ)wOC%Q=WR4_Rud1R5ucJSN__3kWCHFc_>yFktfnSYLS~{Vv{|IJa$T>77Zbl z*yOI`(qK~V~-U}&#}=hV*A5ZNSR7hV@@Nq>{~dC3or}2 zOi+~6+&>{R_aR!3tYL&D?$AfqfBq*W$$MPfWD>)MjrkV*o*+%QGExjUF)P9mdW+HL%CX4ZrBZQDPNUGU#;se)mQ6QRq3mB<%(UoTDNMp z+^fswJw%y@Q#S{uWV$_fY(jpk%Hxlk9m+cF(c~g(kL#_wAy`IJFK#d9OZ~xj{Xr}} zR6C=tmV2?NQ0Q-@Pr{#Kb`_UvKeh*iq+*9bU-GbJ8S{@g5HTtvu znUj^=nAmTpbD$bh>6asbNdHd?dcYN%cQ+qmf3*~_JNW47Z!?I`{UjJre$xT)|NG|7)|m4I(+ z`oEsAw(+|(v@7A_^o@l^OTiPXG=hxH7-Aba!2?@rg41T`g6O-W*rQHWT%!gMVO`d2 z+C=a6S<&CH-+_-W>yd5=v#M^jFF(wt)=P^8W2aw2msy{fN~6#D>WXsi?V*~c<5Tnk zrID{(^FNB+UzCHqxw6B!PdXJ1Ub4{tfF_Yn9E1*f#1O@Tn09=R-txz$6^YqZXwLnsZduHpHWpFDn27rQB^)aJA|E-A7n#Ob3hd@8Uxri8zY+> zv}!d=dd*ngCG~D+z|j#X*%{`Bg^f8?v$>=* zHxF8IKJ8oAYRr>HBjqxEsMN4M+xuGgm_lu<7vY2=)TNV}jxVFO7!*jt%4te<;SQhf0SR?^_j@JpZuq)jJ zEdf**HI9RUl25}=(a=)thEG-X&y0M^q58$aE>YhZOy zYX0XU2-fCiv>}FLcoh3yQH97R(Rh|Udb3-NEa!~Vv+9+wRaq04^l^B6;L4SGUlet{YwT86U$W3Zty;yUr`#Y$91eSZ2XtZ@ zhyQ{wrAG=ftmrrMf|zDLWba2XBH9vVImc*h7UE&8T1`8{DOXg0Wscq3B41oL$EIlh zN9k`YYdP`9C;+M!b|hl0A&8O<45ZO$@_~}x70wSv18z8uBk@x0lpV&_QZjy;%I8lE zcjom!)Y;;8A8R0+v zD}EX(R^gd;ZV=~{%K3l9IZuaf#3iw?p#tVQc_fet>B+ z{Rjxv-wHPU3ubW0=xINE2u8vg8e;RJ!2~Q25(6q;DS5m[a8-&uvPaVkR8|*d1&1x2Yfj=5<@FD| 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 STt^gUjvUMxjriS&?*9Oyeu(G* literal 51200 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 Date: Mon, 14 Dec 2015 13:08:34 +0800 Subject: [PATCH 142/404] wrap chromedriver into sdk build --- tools/package_binaries.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/package_binaries.py b/tools/package_binaries.py index eb5d3701e5..d63023cdec 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -13,7 +13,7 @@ from subprocess import call -steps = ['nw', 'chromedriver', 'symbol', 'headers', 'others'] +steps = ['nw', 'symbol', 'headers', 'others'] ################################ # Parse command line args parser = argparse.ArgumentParser(description='Package nw binaries.') @@ -167,6 +167,7 @@ def generate_target_nw(platform_name, arch, version): if flavor == 'sdk': target['input'].append('nwjc') target['input'].append('payload') + target['input'].append('chromedriver') if flavor in ['nacl','sdk'] : target['input'] += ['nacl_helper', 'nacl_helper_bootstrap', 'pnacl'] if arch == 'x64': @@ -195,6 +196,7 @@ def generate_target_nw(platform_name, arch, version): if flavor == 'sdk': target['input'].append('nwjc.exe') target['input'].append('payload.exe') + target['input'].append('chromedriver.exe') if flavor in ['nacl','sdk'] : target['input'].append('pnacl') if arch == 'x64': @@ -209,6 +211,7 @@ def generate_target_nw(platform_name, arch, version): if flavor == 'sdk': target['input'].append('nwjc') target['input'].append('payload') + target['input'].append('chromedriver') else: print 'Unsupported platform: ' + platform_name exit(-1) From 098d7303bb38ffc0794b77101f9b976b6e57a9a7 Mon Sep 17 00:00:00 2001 From: Jefry Date: Mon, 7 Dec 2015 08:55:12 +0700 Subject: [PATCH 143/404] [Tray] status tray should be gotten from browser process --- src/api/tray/tray_aura.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/tray/tray_aura.cc b/src/api/tray/tray_aura.cc index 6378715058..a844e83e42 100644 --- a/src/api/tray/tray_aura.cc +++ b/src/api/tray/tray_aura.cc @@ -24,6 +24,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/status_icons/status_icon.h" #include "chrome/browser/status_icons/status_icon_observer.h" #include "chrome/browser/status_icons/status_tray.h" @@ -65,7 +66,7 @@ class TrayObserver : public StatusIconObserver { void Tray::Create(const base::DictionaryValue& option) { if (!status_tray_) - status_tray_ = StatusTray::Create(); + status_tray_ = g_browser_process->status_tray(); status_icon_ = status_tray_->CreateStatusIcon(StatusTray::NOTIFICATION_TRAY_ICON, gfx::ImageSkia(), base::string16()); From 2e00931d3f6b191ee682bf63b9401dd086056718 Mon Sep 17 00:00:00 2001 From: Jefry Date: Mon, 14 Dec 2015 18:50:58 +0700 Subject: [PATCH 144/404] [Transparency] javascript / idl for all platform --- AUTHORS | 2 +- src/api/nw_window.idl | 1 + src/resources/api_nw_window.js | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 1f41d665ef..dd1e538b91 100644 --- a/AUTHORS +++ b/AUTHORS @@ -25,7 +25,7 @@ Fabrice Weinberg Lv Kaiyang Lukas Benes Lithare Emileit -Jefry Tedjokusumo +Jefry Tedjokusumo from V-cube Global Services ; Wu Haojian Bas Wegh Joachim Bauch diff --git a/src/api/nw_window.idl b/src/api/nw_window.idl index 3d964bbc8f..8443aadedf 100644 --- a/src/api/nw_window.idl +++ b/src/api/nw_window.idl @@ -29,6 +29,7 @@ namespace nw.Window { [nodoc] boolean? show; [nodoc] boolean? always_on_top; [nodoc] boolean? visible_on_all_workspaces; + [nodoc] boolean? transparent; }; [noinline_doc] dictionary NWWindow { diff --git a/src/resources/api_nw_window.js b/src/resources/api_nw_window.js index 1129704824..91764ba291 100644 --- a/src/resources/api_nw_window.js +++ b/src/resources/api_nw_window.js @@ -360,6 +360,8 @@ nw_binding.registerCustomHook(function(bindingsAPI) { options.alwaysOnTop = true; if (params['visible_on_all_workspaces'] === true) options.visibleOnAllWorkspaces = true; + if (params.transparent) + options.alphaEnabled = true; } chrome.app.window.create(url, options, function(appWin) { if (callback) { From 32fa4a0ce53dd85ee8d3ec0e726d859beba51a9d Mon Sep 17 00:00:00 2001 From: Jefry Date: Tue, 15 Dec 2015 12:12:21 +0700 Subject: [PATCH 145/404] fix debug build, nw.create fail the check, because it never returns an object --- src/api/nw_object.idl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/nw_object.idl b/src/api/nw_object.idl index 63e98b074f..819f8ed18f 100644 --- a/src/api/nw_object.idl +++ b/src/api/nw_object.idl @@ -6,8 +6,8 @@ [implemented_in="content/nw/src/api/nw_object_api.h"] namespace nw.Obj { interface Functions { - static object create(long id, DOMString type, object options); - static object destroy(long id); + static void create(long id, DOMString type, object options); + static void destroy(long id); static void callObjectMethod(long id, DOMString type, DOMString method, any[] arguments); static object callObjectMethodSync(long id, DOMString type, DOMString method, any[] arguments); }; From f0a327fab408bc1194602c5eb0086722e10509b3 Mon Sep 17 00:00:00 2001 From: Jefry Date: Tue, 15 Dec 2015 12:13:55 +0700 Subject: [PATCH 146/404] [Tray] change nw.Tray.create to 'new nw.Tray' cleanup unncessary idl nw.Tray now inherits event emitter --- src/api/nw_tray.idl | 25 ------------- src/api/nw_tray_api.h | 11 ------ src/api/schemas.gypi | 1 - src/resources/api_nw_tray.js | 69 +++++++++++++----------------------- 4 files changed, 25 insertions(+), 81 deletions(-) delete mode 100644 src/api/nw_tray.idl delete mode 100644 src/api/nw_tray_api.h diff --git a/src/api/nw_tray.idl b/src/api/nw_tray.idl deleted file mode 100644 index 6dd33e0262..0000000000 --- a/src/api/nw_tray.idl +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015 The NW.js Authors. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -// nw Tray API -[implemented_in="content/nw/src/api/nw_tray_api.h"] -namespace nw.Tray { - callback EventCallback = void(); - [noinline_doc] dictionary Tray { - [nodoc] DOMString? title; - DOMString tooltip; - DOMString icon; - DOMString alticon; - boolean iconsAreTemplates; - object menu; - object click; - static void remove(); - static void on(DOMString event, - EventCallback callback); - }; - interface Functions { - [nocompile] static object create(optional object options); - [nocompile] static void destroy(long id); - }; -}; diff --git a/src/api/nw_tray_api.h b/src/api/nw_tray_api.h deleted file mode 100644 index 5ecf533e9f..0000000000 --- a/src/api/nw_tray_api.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef NW_API_TRAY_API_H_ -#define NW_API_TRAY_API_H_ - -#include - -#include "extensions/browser/extension_function.h" - -namespace extensions { - -} // namespace extensions -#endif diff --git a/src/api/schemas.gypi b/src/api/schemas.gypi index 77c72d4d73..69a3c9af5e 100644 --- a/src/api/schemas.gypi +++ b/src/api/schemas.gypi @@ -16,7 +16,6 @@ 'nw_screen.idl', 'nw_shell.idl', 'nw_shortcut.idl', - 'nw_tray.idl', 'nw_current_window_internal.idl', 'nw_test.idl', ], diff --git a/src/resources/api_nw_tray.js b/src/resources/api_nw_tray.js index 87558ebb9c..a34301b68c 100644 --- a/src/resources/api_nw_tray.js +++ b/src/resources/api_nw_tray.js @@ -1,17 +1,30 @@ var Binding = require('binding').Binding; var forEach = require('utils').forEach; -var nw_binding = require('binding').Binding.create('nw.Tray'); var nwNative = requireNative('nw_natives'); var sendRequest = require('sendRequest'); var contextMenuNatives = requireNative('context_menus'); var messagingNatives = requireNative('messaging_natives'); var Event = require('event_bindings').Event; +var util = nw.require('util'); +var EventEmitter = nw.require('events').EventEmitter; var trayEvents = { objs: {}, clickEvent: {} }; -function Tray(id, option) { +trayEvents.clickEvent = new Event("NWObjectTrayClick"); +trayEvents.clickEvent.addListener(function(id) { + if (!trayEvents.objs[id]) + return; + trayEvents.objs[id].emit('click'); +}); + +function Tray(option) { + if (!(this instanceof Tray)) { + return new Tray(option); + } + EventEmitter.apply(this); + if (typeof option != 'object') - throw new TypeError('Invalid option'); + throw new TypeError('Invalid option.'); if (!option.hasOwnProperty('title') && !option.hasOwnProperty('icon')) throw new TypeError("Must set 'title' or 'icon' field in option"); @@ -56,6 +69,7 @@ function Tray(id, option) { option.menu = option.menu.id; } + var id = contextMenuNatives.GetNextContextMenuId(); this.id = id; privates(this).option = option; @@ -66,8 +80,14 @@ function Tray(id, option) { option.shadowAlticon = ''; if (!option.hasOwnProperty('tooltip')) option.tooltip = ''; + + nw.Obj.create(id, 'Tray', option); + messagingNatives.BindToGC(this, nw.Obj.destroy.bind(undefined, id), -1); + trayEvents.objs[this.id] = this; } +util.inherits(Tray, EventEmitter); + Tray.prototype.handleGetter = function(name) { return privates(this).option[name]; }; @@ -135,48 +155,9 @@ Tray.prototype.__defineSetter__('menu', function(val) { }); Tray.prototype.remove = function() { - if (trayEvents.objs[this.id]) - this.removeListener('click'); nw.Obj.callObjectMethod(this.id, 'Tray', 'Remove', []); + delete trayEvents.objs[this.id]; } -Tray.prototype.on = function (event, callback) { - if (event == 'click') { - trayEvents.objs[this.id] = this; - this._onclick = callback; - } -} - -Tray.prototype.removeListener = function (event) { - if (event == 'click') { - delete trayEvents.objs[this.id]; - delete this._onclick; - } -} - -nw_binding.registerCustomHook(function(bindingsAPI) { - var apiFunctions = bindingsAPI.apiFunctions; - trayEvents.clickEvent = new Event("NWObjectTrayClick"); - trayEvents.clickEvent.addListener(function(id) { - if (!trayEvents.objs[id]) - return; - trayEvents.objs[id]._onclick(); - }); - apiFunctions.setHandleRequest('destroy', function(id) { - sendRequest.sendRequestSync('nw.Obj.destroy', [id], this.definition.parameters, {}); - }); - apiFunctions.setHandleRequest('create', function(option) { - var id = contextMenuNatives.GetNextContextMenuId(); - if (typeof option != 'object' || !option) - option = { }; - - option.generatedId = id; - var ret = new Tray(id, option); - sendRequest.sendRequestSync('nw.Obj.create', [id, 'Tray', option], this.definition.parameters, {}); - messagingNatives.BindToGC(ret, nw.Tray.destroy.bind(undefined, id), -1); - return ret; - }); -}); - -exports.binding = nw_binding.generate(); +exports.binding = Tray; From 806a29c7fa92628d7218d37a6eb0ec627bb494b3 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Tue, 15 Dec 2015 16:30:14 +0800 Subject: [PATCH 147/404] [tools] fix regular build packaging --- tools/package_binaries.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/package_binaries.py b/tools/package_binaries.py index d63023cdec..5ed4959ea9 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -252,9 +252,11 @@ def generate_target_symbols(platform_name, arch, version): target['input'] = [ 'nw.breakpad.' + arch, 'node.so.breakpad.' + arch, - 'nw.so.breakpad.' + arch, - 'nacl_helper.breakpad.' + arch + 'nw.so.breakpad.' + arch ] + if flavor in ['sdk', 'nacl']: + target['input'].append('nacl_helper.breakpad.' + arch) + target['folder'] = True elif platform_name == 'win': target['compress'] = None From de349e4995d828bcc27be54e4333e7c969022062 Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Wed, 16 Dec 2015 14:05:58 +0800 Subject: [PATCH 148/404] support nwdisable --- src/nw_content.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/nw_content.cc b/src/nw_content.cc index 4715256033..8ab0501dc6 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -321,9 +321,12 @@ void DocumentHook2(bool start, content::RenderFrame* frame, Dispatcher* dispatch void DocumentElementHook(blink::WebFrame* frame, const extensions::Extension* extension, const GURL& effective_document_url) { + if (frame->isNwDisabledChildFrame()) + return; v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::HandleScope hscope(isolate); frame->document().securityOrigin().grantUniversalAccess(); + frame->setNodeJS(true); std::string path = effective_document_url.path(); v8::Local v8_context = frame->mainWorldScriptContext(); std::string root_path = extension->path().AsUTF8Unsafe(); @@ -385,6 +388,9 @@ void DocumentElementHook(blink::WebFrame* frame, void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { v8::Isolate* isolate = context->isolate(); + if (frame->isNwDisabledChildFrame()) + return; + bool nodejs_enabled = true; context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSEnableNode, &nodejs_enabled); From b7b5257a78776be75aac55ea1733d968012762fe Mon Sep 17 00:00:00 2001 From: Roger Wang Date: Thu, 17 Dec 2015 14:09:34 +0800 Subject: [PATCH 149/404] move nwdisable support to chromium repo --- src/nw_content.cc | 5 ----- test/remoting/iframe-nw/iframe.html | 14 ++++++++++++++ test/remoting/iframe-nw/index.html | 22 ++++++++++++++++++++++ test/remoting/iframe-nw/package.json | 5 +++++ test/remoting/iframe-nw/test.py | 20 ++++++++++++++++++++ 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 test/remoting/iframe-nw/iframe.html create mode 100644 test/remoting/iframe-nw/index.html create mode 100644 test/remoting/iframe-nw/package.json create mode 100644 test/remoting/iframe-nw/test.py diff --git a/src/nw_content.cc b/src/nw_content.cc index 8ab0501dc6..a29d62ade6 100644 --- a/src/nw_content.cc +++ b/src/nw_content.cc @@ -321,8 +321,6 @@ void DocumentHook2(bool start, content::RenderFrame* frame, Dispatcher* dispatch void DocumentElementHook(blink::WebFrame* frame, const extensions::Extension* extension, const GURL& effective_document_url) { - if (frame->isNwDisabledChildFrame()) - return; v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::HandleScope hscope(isolate); frame->document().securityOrigin().grantUniversalAccess(); @@ -388,9 +386,6 @@ void DocumentElementHook(blink::WebFrame* frame, void ContextCreationHook(blink::WebLocalFrame* frame, ScriptContext* context) { v8::Isolate* isolate = context->isolate(); - if (frame->isNwDisabledChildFrame()) - return; - bool nodejs_enabled = true; context->extension()->manifest()->GetBoolean(manifest_keys::kNWJSEnableNode, &nodejs_enabled); diff --git a/test/remoting/iframe-nw/iframe.html b/test/remoting/iframe-nw/iframe.html new file mode 100644 index 0000000000..7ee64374b3 --- /dev/null +++ b/test/remoting/iframe-nw/iframe.html @@ -0,0 +1,14 @@ + + + + + test + + +

iframe

+ + + + diff --git a/test/remoting/iframe-nw/index.html b/test/remoting/iframe-nw/index.html new file mode 100644 index 0000000000..08e76878da --- /dev/null +++ b/test/remoting/iframe-nw/index.html @@ -0,0 +1,22 @@ + + + + + iframe nw test + + +

iframe nw test

+

iframe nw test

+ + + + + + diff --git a/test/remoting/iframe-nw/package.json b/test/remoting/iframe-nw/package.json new file mode 100644 index 0000000000..1a8dc76012 --- /dev/null +++ b/test/remoting/iframe-nw/package.json @@ -0,0 +1,5 @@ +{ + "name":"test-iframe-nw", + "main":"index.html", + "dependencies":{} +} diff --git a/test/remoting/iframe-nw/test.py b/test/remoting/iframe-nw/test.py new file mode 100644 index 0000000000..122758d2f8 --- /dev/null +++ b/test/remoting/iframe-nw/test.py @@ -0,0 +1,20 @@ +import time +import os + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +chrome_options = Options() +chrome_options.add_argument("nwapp=" + os.path.dirname(os.path.abspath(__file__))) + +driver = webdriver.Chrome(executable_path=os.environ['CHROMEDRIVER'], chrome_options=chrome_options) +time.sleep(1) +try: + print driver.current_url + result = driver.find_element_by_id('result').get_attribute('innerHTML') + print result + assert("object" == result) + result2 = driver.find_element_by_id('result2').get_attribute('innerHTML') + print result2 + assert("undefined" == result2) +finally: + driver.quit() From 5f286bf2dc2c40ce810c4bc1dd6052a3642a043e Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Mon, 14 Dec 2015 15:43:40 +0800 Subject: [PATCH 150/404] Added docs for NW.js * For Developers/Writing Document * For Developers/Building NW.js * References/* --- docs/For Developers/Building NW.js.md | 106 +++ docs/For Developers/Contributing NW APIs.md | 0 docs/For Developers/Repositories.md | 0 .../Understanding Crash Dump.md | 0 docs/For Developers/Writing Documents.md | 88 ++ docs/For Users/Advanced/App Protocol.md | 0 docs/For Users/Advanced/Context.md | 0 docs/For Users/Advanced/Frames.md | 0 .../Advanced/Handle CLI Arguments.md | 0 .../Advanced/Test with ChromeDriver.md | 0 docs/For Users/Debug with DevTools.md | 0 docs/For Users/Getting Started.md | 0 docs/For Users/Migration/From 0.12 to 0.13.md | 0 .../Migration/From Chrome Apps to 0.13.md | 0 docs/For Users/Node API and NPM.md | 0 docs/For Users/Package and Redistribute.md | 0 docs/References/App.md | 222 +++++ docs/References/Clipboard.md | 64 ++ docs/References/File Dialogs.md | 139 ++++ docs/References/Frames.md | 69 ++ docs/References/Manifest Format.md | 347 ++++++++ docs/References/Menu.md | 148 ++++ docs/References/MenuItem.md | 183 +++++ docs/References/Screen.md | 252 ++++++ docs/References/Shell.md | 49 ++ docs/References/Shortcut.md | 138 ++++ docs/References/Tray.md | 128 +++ docs/References/Window.md | 773 ++++++++++++++++++ docs/index.md | 3 + mkdocs.yml | 23 + requirements.txt | 1 + 31 files changed, 2733 insertions(+) create mode 100644 docs/For Developers/Building NW.js.md create mode 100644 docs/For Developers/Contributing NW APIs.md create mode 100644 docs/For Developers/Repositories.md create mode 100644 docs/For Developers/Understanding Crash Dump.md create mode 100644 docs/For Developers/Writing Documents.md create mode 100644 docs/For Users/Advanced/App Protocol.md create mode 100644 docs/For Users/Advanced/Context.md create mode 100644 docs/For Users/Advanced/Frames.md create mode 100644 docs/For Users/Advanced/Handle CLI Arguments.md create mode 100644 docs/For Users/Advanced/Test with ChromeDriver.md create mode 100644 docs/For Users/Debug with DevTools.md create mode 100644 docs/For Users/Getting Started.md create mode 100644 docs/For Users/Migration/From 0.12 to 0.13.md create mode 100644 docs/For Users/Migration/From Chrome Apps to 0.13.md create mode 100644 docs/For Users/Node API and NPM.md create mode 100644 docs/For Users/Package and Redistribute.md create mode 100644 docs/References/App.md create mode 100644 docs/References/Clipboard.md create mode 100644 docs/References/File Dialogs.md create mode 100644 docs/References/Frames.md create mode 100644 docs/References/Manifest Format.md create mode 100644 docs/References/Menu.md create mode 100644 docs/References/MenuItem.md create mode 100644 docs/References/Screen.md create mode 100644 docs/References/Shell.md create mode 100644 docs/References/Shortcut.md create mode 100644 docs/References/Tray.md create mode 100644 docs/References/Window.md create mode 100644 docs/index.md create mode 100644 mkdocs.yml create mode 100644 requirements.txt diff --git a/docs/For Developers/Building NW.js.md b/docs/For Developers/Building NW.js.md new file mode 100644 index 0000000000..0b172cb00e --- /dev/null +++ b/docs/For Developers/Building NW.js.md @@ -0,0 +1,106 @@ +# Building NW.js {: .doctitle} +--- + +[TOC] + +!!! important + This document is written for latest **NW 0.13**. For legacy build instructions, please read the [wiki page](https://github.com/nwjs/nw.js/wiki/Building-nw.js) on GitHub. + +## Prerequisites + +NW.js use same build tools and similar steps as Chromium. Read the instructions according to your platform to install `depot_tools` and other prerequistes: + +* [Windows](http://www.chromium.org/developers/how-tos/build-instructions-windows) +* [Mac OS X](https://chromium.googlesource.com/chromium/src/+/master/docs/mac_build_instructions.md) +* [Linux](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md) + +!!! note "Windows" + As suggested by Chromium document, you need to run `set DEPOT_TOOLS_WIN_TOOLCHAIN=0` or set the variable in your global environment. + CLang is the build tool used by non-Windows platforms. On Windows, it's not supported yet. You need to run `set GYP_DEFINES="clang=0"` to disable CLang on Windows before going to next steps. + +!!! note "Xcode 7" + Mac SDK 10.11 as part of Xcode 7 is not supported yet. If you have upgraded to Xcode 7, either downgrade to Xcode 6 or copy Mac SDK 10.10 from other machines under ```xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs`` as suggested by [Chromium document](https://chromium.googlesource.com/chromium/src/+/master/docs/mac_build_instructions.md). + +## Get the Code + +1. Create a folder for holding NW.js source code, like `$HOME/nwjs`, and run following command in the folder to generate `.gclient` file: +```bash +mkdir -p $HOME/nwjs +cd $HOME/nwjs +gclient config --name=src https://github.com/nwjs/chromium.src.git@origin/nw13 +``` + +!!! tip "Faster Sync" + Generally if you are not interested in running Chromium tests, you don't have to sync the test cases and reference builds, which saves you lot of time. Just open the `.gclient` file you just created and put following code in `custom_deps` section: + ```python + "custom_deps" : { + "src/third_party/WebKit/LayoutTests": None, + "src/chrome_frame/tools/test/reference_build/chrome": None, + "src/chrome_frame/tools/test/reference_build/chrome_win": None, + "src/chrome/tools/test/reference_build/chrome": None, + "src/chrome/tools/test/reference_build/chrome_linux": None, + "src/chrome/tools/test/reference_build/chrome_mac": None, + "src/chrome/tools/test/reference_build/chrome_win": None, + } + ``` + +2. Run following command in your terminal: +``` +gclient sync --with_branch_heads +``` + +This usually downloads 20G+ from GitHub and Google's Git repos. Make sure you have a good network provider and be patient :stuck_out_tongue: + +When finished, you will see a `src` folder created. + +!!! note "Linux" + If you are building on Linux **for the first time**, you need to run `gclient sync --with_branch_heads --nohooks` and then run `./build/install-build-deps.sh` to install dependencies on Ubuntu. See [Chromium document](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md) for detailed instructions for installing prerequisites on other Linux distributions. + +!!! note "Windows" + On Windows, you have to install [DirectX SDK](https://www.microsoft.com/en-us/download/details.aspx?id=6812) and copy the files into the source folder manually using following bash command: + ```bash + mkdir -p $HOME/nwjs/src/third_party/directxsdk/files + cp -r /c/Program\ Files\ \(x86\)/Microsoft\ DirectX\ SDK\ \(June\ 2010\)/* \ + $HOME/nwjs/src/third_party/directxsdk/files/ + ``` + +## Build + +Build files are generated in `out/` folder during `gclient sync`. Run following command in your terminal will generate the Debug build of standard NW.js binaries in `out/Debug` folder: + +```bash +cd src +ninja -C out/Debug nwjs +``` + +!!! tip "Build Time" + Generally a full build takes hours of time depending on the performance of your machine. Recommended configuration is to build on a PC with multicore CPU (>=8 cores), SSD and large memory (>= 8G). And you can read [Build Faster](#build-faster) section below for some tips to speed up the build. + +To generate Release build, switch the second command to `ninja -C out/Release nwjs`. + +To build 32-bit/64-bit binaries or non-standard build flavors, you need to setup `GYP_DEFINES` variable in your environment and run `gclient runhooks --force` to generate build files. And then re-run the commands above to generate binaries. Continue to read following sections to find out how to setup `GYP_DEFINES`. + +### 32-bit/64-bit Build + +* Windows + - 32-bit: is the default build target + - 64-bit: `set GYP_DEFINES="target_arch=x64"` and rebuild in `out/Debug_x64` or `out/Release_x64` folder +* Linux + - 32-bit: **TODO: chroot** + - 64-bit: is the default build target +* Mac + - 32-bit: `export GYP_DEFINES="host_arch=ia32 target_arch=ia32"` and rebuild in `out/Debug` or `out/Release` folder + - 64-bit: is the default build target + +### Build Flavors + +* Standard: it's generated by default. Same as `GYP_DEFINES="nwjs_sdk=0 disable_nacl=1"`. +* SDK: `GYP_DEFINES="nwjs_sdk=1 disable_nacl=0"` +* NaCl: `GYP_DEFINES="disable_nacl=0"` + +## Build Faster + +From Google's website, there are a few tips to speed up your build. Open the links below to see the tips for your platform: + +* [Mac Build Instructions: Faster builds](https://chromium.googlesource.com/chromium/src/+/master/docs/mac_build_instructions.md#Faster-builds) +* [Tips for improving build speed on Linux](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_faster_builds.md) diff --git a/docs/For Developers/Contributing NW APIs.md b/docs/For Developers/Contributing NW APIs.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Developers/Repositories.md b/docs/For Developers/Repositories.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Developers/Understanding Crash Dump.md b/docs/For Developers/Understanding Crash Dump.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Developers/Writing Documents.md b/docs/For Developers/Writing Documents.md new file mode 100644 index 0000000000..9f9ffa77d1 --- /dev/null +++ b/docs/For Developers/Writing Documents.md @@ -0,0 +1,88 @@ +# Writing Documents for NW.js {: .doctitle} + +[TOC] + +## Read Before Writing New Document + +!!! important + Github provides nice representation of Markdown online with its own syntax GFM (Github Flavored Markdown). And many of the developers are reading documents directly on GitHub. To enable the best documentation of NW.js for any developers, **make sure the document is readable on GitHub before submitting your PR**. + + The documentation site of NW.js is generated by [MkDocs](http://www.mkdocs.org/) which supports a slightly different Markdown syntax than GFM. So the page may be broken on GitHub while works under MkDocs, or vice versa. + + A bad example is not having `.md` suffix in internal links, like `[Build NW.js](Build NW.js)`. On GitHub, the link is broken because the file `Build NW.js` without `.md` does not exist. Always add `.md` suffix to your internal links. + +## View the Document Offline +To view the well-formatted documents on your own laptop, you need [Python](https://www.python.org/) and install following dependencies: +```bash +pip install mkdocs pygments pymdown-extensions +``` +Run following commands in the root of NW.js repo: +```bash +mkdocs serve +``` +Then open your browser and navigate to http://localhost:8000/ . + +## Template for API Reference Page + +Here is a minimal template for writing a page for NW.js API reference: +````markdown +# Module Name {: .doctitle} + +--- + +!!! important "Available" + Since x.y.z + +[TOC] + +Describe the usage and other details of the module here. + +## Module.method(arg1, arg2) + +!!! important "Available" + Since x.y.z + +* `arg1` `{String}` description of `arg1` +* `arg2` `{Object}` description of `arg2` + * `isSet` `{Boolean}` description of the field +* Returns `{Type}` description of the return value + +Details about the the method. + +```javascript +// example code here +var ret = Module.method('hello', {isSet: false}); +``` +```` + +* **[MUST]** use `#` for page title and `##` or `###` headers for sections. **DO NOT** use `=======` or `-------`. +* **[MUST]** add `{: .doctitle}` right to the page title. This is the CSS class name for the document title. +* **[MUST]** add a horizontal ruler with `---` after the page title. +* **[MUST]** use `!!! important "Available"` admonition for the initial NW.js version that supports this feature. And use `!!! warning "Deprecated"` for deprecated features. See [reStructuredText Directives Admonitions](http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions) for full list of supported admonitions. +* **[MUST]** add a `[TOC]` after the version info. +* **[MUST]** quote any argument / variable / class with a back tick (`` ` ``) in the document unless it's in the headers. +* **[MUST]** specify the language for the code blocks. + +## Markdown Extensions +Currently the documentation site of NW.js enables following extensions for generating documents. See `mkdocs.yml` in the root for detailed configuration. + +* markdown.extensions.toc +* markdown.extensions.admonition +* markdown.extensions.smarty +* markdown.extensions.nl2br +* markdown.extensions.codehilite +* pymdownx.extra +* pymdownx.inlinehilite +* pymdownx.magiclink +* pymdownx.tilde +* pymdownx.caret +* pymdownx.smartsymbols +* pymdownx.githubemoji +* pymdownx.tasklist +* pymdownx.progressbar +* pymdownx.headeranchor +* pymdownx.arithmatex +* pymdownx.mark +* pymdownx.critic + + diff --git a/docs/For Users/Advanced/App Protocol.md b/docs/For Users/Advanced/App Protocol.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Advanced/Context.md b/docs/For Users/Advanced/Context.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Advanced/Frames.md b/docs/For Users/Advanced/Frames.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Advanced/Handle CLI Arguments.md b/docs/For Users/Advanced/Handle CLI Arguments.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Advanced/Test with ChromeDriver.md b/docs/For Users/Advanced/Test with ChromeDriver.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Debug with DevTools.md b/docs/For Users/Debug with DevTools.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Getting Started.md b/docs/For Users/Getting Started.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Migration/From 0.12 to 0.13.md b/docs/For Users/Migration/From 0.12 to 0.13.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Migration/From Chrome Apps to 0.13.md b/docs/For Users/Migration/From Chrome Apps to 0.13.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Node API and NPM.md b/docs/For Users/Node API and NPM.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/For Users/Package and Redistribute.md b/docs/For Users/Package and Redistribute.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/References/App.md b/docs/References/App.md new file mode 100644 index 0000000000..620697d058 --- /dev/null +++ b/docs/References/App.md @@ -0,0 +1,222 @@ +# App {: .doctitle} + +--- + +!!! important "Available" + Since 0.3.1 + +[TOC] + +## App.argv + +!!! important "Available" + Since 0.3.1 + +Get the command line arguments when starting the app. + +## App.fullArgv + +!!! important "Available" + Since 0.3.1 + +Get all the command line arguments when starting the app. Because NW.js itself used switches like `--no-sandbox` and `--process-per-tab`, it would confuse the app when the switches were meant to be given to NW.js, so `App.argv` just filtered such switches (arguments' precedence were kept). You can get the switches to be filtered with `App.filteredArgv`. + +## App.dataPath + +!!! important "Available" + Since 0.6.1 + +Get the application's data path in user's directory. + +* Windows: `%LOCALAPPDATA%/` +* Linux: `~/.config/` +* OSX: `~/Library/Application Support/` + +`` is the field in the manifest. + +## App.manifest + +!!! important "Available" + Since 0.7.0 + +Get the JSON object of the manifest file. + +## App.clearCache() + +!!! important "Available" + Since 0.6.0 + +Clear the HTTP cache in memory and the one on disk. This method call is synchronized. + +## App.closeAllWindows() + +!!! important "Available" + Since 0.3.2 + +Send the `close` event to all windows of current app, if no window is blocking the `close` event, then the app will quit after all windows have done shutdown. Use this method to quit an app will give windows a chance to save data. + +## App.crashBrowser() +## App.crashRenderer() + +!!! important "Available" + Since 0.8.0 + +These 2 functions crashes the browser process and the renderer process respectively, to test the [Crash dump](../For Developers/Understanding Crash Dump.md) feature. + +## App.getProxyForURL(url) + +!!! important "Available" + Since 0.6.3 + +* `url` `{String}` the URL to query for proxy + +Query the proxy to be used for loading `url` in DOM. The return value is in the same format used in [PAC](http://en.wikipedia.org/wiki/Proxy_auto-config) (e.g. "DIRECT", "PROXY localhost:8080"). + +## App.setProxyConfig(config) + +!!! important "Available" + Since 0.11.1 + +* `config` `{String}` Proxy rules + +Set the proxy config which the web engine will be used to request network resources. + +Rule (copied from [`net/proxy/proxy_config.h`](https://github.com/nwjs/chromium.src/blob/nw13/net/proxy/proxy_config.h)) + +``` + // Parses the rules from a string, indicating which proxies to use. + // + // proxy-uri = ["://"][":"] + // + // proxy-uri-list = [","] + // + // url-scheme = "http" | "https" | "ftp" | "socks" + // + // scheme-proxies = ["="] + // + // proxy-rules = scheme-proxies[";"] + // + // Thus, the proxy-rules string should be a semicolon-separated list of + // ordered proxies that apply to a particular URL scheme. Unless specified, + // the proxy scheme for proxy-uris is assumed to be http. + // + // Some special cases: + // * If the scheme is omitted from the first proxy list, that list applies + // to all URL schemes and subsequent lists are ignored. + // * If a scheme is omitted from any proxy list after a list where a scheme + // has been provided, the list without a scheme is ignored. + // * If the url-scheme is set to 'socks', that sets a fallback list that + // to all otherwise unspecified url-schemes, however the default proxy- + // scheme for proxy urls in the 'socks' list is understood to be + // socks4:// if unspecified. + // + // 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=foop,socks5://bar.com -- use HTTP proxy "foopy" for http URLs, + // and fail over to the SOCKS5 proxy + // "bar.com" if "foop" 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. +``` + +## App.quit() + +!!! important "Available" + Since 0.3.1 + +Quit current app. This method will **not** send `close` event to windows and app will just quit quietly. + +## App.setCrashDumpDir(dir) + +!!! warning "Deprecated" + Since 0.11.0 + +* `dir` `{String}` path to generate the crash dump + +Set the directory where the minidump file will be saved on crash. For more information, see [Crash dump](../For Developers/Understanding Crash Dump.md). + +This API was **deprecated** since 0.11.0. + +## App.addOriginAccessWhitelistEntry(sourceOrigin, destinationProtocol, destinationHost, allowDestinationSubdomains) + +!!! important "Available" + Since 0.10.0 + +* `sourceOrigin` `{String}` The source origin. e.g. `http://github.com/` +* `destinationProtocol` `{String}` The destination protocol where the `sourceOrigin` can access to. e.g. `app` +* `destinationHost` `{String}` The destination host where the `sourceOrigin` can access to. e.g. `myapp` +* `allowDestinationSubdomains` `{Boolean}` If set to true, the `sourceOrigin` is allowed to access subdomains of destinations. + +Add an entry to the whitelist used for controlling cross-origin access. Suppose you want to allow HTTP redirecting from `github.com` to the page of your app, use something like this with the [App protocol](../For Users/Advanced/App Protocol.md): + +```javascript +App.addOriginAccessWhitelistEntry('http://github.com/', 'app', 'myapp', true); +``` + +Use `App.removeOriginAccessWhitelistEntry` with exactly the same arguments to do the contrary. + +## App.removeOriginAccessWhitelistEntry(sourceOrigin, destinationProtocol, destinationHost, allowDestinationSubdomains) + +!!! important "Available" + Since 0.10.0 + +* `sourceOrigin` `{String}` The source origin. e.g. `http://github.com/` +* `destinationProtocol` `{String}` The destination protocol where the `sourceOrigin` can access to. e.g. `app` +* `destinationHost` `{String}` The destination host where the `sourceOrigin` can access to. e.g. `myapp` +* `allowDestinationSubdomains` `{Boolean}` If set to true, the `sourceOrigin` is allowed to access subdomains of destinations. + +Remove an entry from the whitelist used for controlling cross-origin access. See `addOriginAccessWhitelistEntry` above. + +## App.registerGlobalHotKey(shortcut) + +!!! important "Available" + Since 0.10.0 + +* `shortcut` `{Shortcut}` the `Shortcut` object to register. + +Register a global keyboard shortcut (also known as system-wide hot key) to the system. + +See [Shortcut](Shortcut.md) for more information. + +## App.unregisterGlobalHotKey(shortcut) + +!!! important "Available" + Since 0.10.0 + +* `shortcut` `{Shortcut}` the `Shortcut` object to unregister. + +Unregisters a global keyboard shortcut. + +See [Shortcut](Shortcut.md) for more information. + +## Event: open(args) + +!!! important "Available" + Since 0.3.2 + +* `args` `{String}` the full command line of the program. + +Emitted when users opened a file with your app. For more on this, see [Handle CLI Arguments](../For Users/Advanced/Handle CLI Arguments.md). + +!!! note + Before 0.7.0, `args` is the argument in the command line and the event is sent multiple times for each of the arguments. + +## Event: reopen + +!!! important "Available" + Since 0.7.3 + +This is a Mac specific feature. This event is sent when the user clicks the dock icon for an already running application. \ No newline at end of file diff --git a/docs/References/Clipboard.md b/docs/References/Clipboard.md new file mode 100644 index 0000000000..5c91d6a1b2 --- /dev/null +++ b/docs/References/Clipboard.md @@ -0,0 +1,64 @@ +# Clipboard {: .doctitle} + +--- + +!!! important "Available" + Since 0.3.0 + +[TOC] + +`Clipboard` is an abstraction of clipboard for Windows, Linux and Mac. + +## Synopsis + +```javascript +// get the system clipboard +var clipboard = nw.Clipboard.get(); + +// Read from clipboard +var text = clipboard.get('text'); +console.log(text); + +// Or write something +clipboard.set('I love NW.js :)', 'text'); + +// And clear it! +clipboard.clear(); +``` + +## Clipboard.get() + +!!! important "Available" + Since 0.3.0 + +* Returns `{Clipboard}` the clipboard object + +!!! note + The Selection Clipboard in X11 is not supported. + +## clip.set(data, [type]) + +!!! important "Available" + Since 0.3.0 + +* `data` `{String}` the data to write to the clipboard +* `type` `{String}` _Optional_ the type of the data. Currently only `"text"` (plain text) is support. By default, `type` is set to `"text"`. + +Write `data` of `type` to the clipboard. + +## clip.get([type]) + +!!! important "Available" + Since 0.3.0 + +* `type` `{String}` _Optional_ the type of the data. Currently only `"text"` (plain text) is support. By default, `type` is set to `"text"`. +* Returns `{String}` the data retrieved from the clipboard + +Get the data of `type` from clipboard. + +## clip.clear() + +!!! important "Available" + Since 0.3.0 + +Clear the clipboard. diff --git a/docs/References/File Dialogs.md b/docs/References/File Dialogs.md new file mode 100644 index 0000000000..a04996196a --- /dev/null +++ b/docs/References/File Dialogs.md @@ -0,0 +1,139 @@ +# File Dialogs {: .doctitle} +--- + +!!! important "Available" + Since 0.2.5 + +[TOC] + +HTML5 does provided limited support for file dialogs with `` element. NW.js extended the file input to better support native apps. + +!!! note + NW.js extended features are only enabled in Node frames for security reasons. See [Frames](Frames.md) for the differences of Node and normal frame. + +## Features Inherited from Chromium + +### Attribute: `multiple` + +This Boolean attribute indicates whether the user can select more than one files. + +```html + +``` + +And the `fileinput.value` will return all selected files's paths separated by `;`. See [fileinput.value](#fileinputvalue) for NW.js extensions. + +### Attribute: `accept` + +This attribute indicates the types of files that the server accepts; otherwise it is ignored. The value must be a comma-separated list of unique content type specifiers: + +* A file extension starting with the STOP character (U+002E). (E.g.: ".jpg,.png,.doc") +* A valid MIME type with no extensions +* audio/* representing sound files +* video/* representing video files +* image/* representing image files + +For example, with following code you can filter to show only MS Word files: + +```html + +``` + +### Attribute: `webkitdirectory` + +This attribute enables users to select a directory in "Select folder" dialog. + +```html + +``` + +The `fileinput.value` is not the path of directory we selected, but paths of all files under the directory. If you want the path of the selected directory, you can use [`nwdirectory` Attribute](#attribute-nwdirectory). + +## Featured Extended by NW.js + +### fileinput.value + +!!! important "Available" + Since 0.2.5 + +The property contains native path of the local file. + +For example, you can read the file selected by user with Node.js API: + +```javascript +// Get the native path of the file selected by user +var fileinput = document.querySelector('input[type=file]'); +var path = fileinput.value; + +// Read file with Node.js API +var fs = nw.require('fs'); +fs.readFile(path, 'utf8', function(err, txt) { + if (err) { + console.error(err); + return; + } + + console.log(txt); +}); +``` + +### fileitem.path + +!!! important "Available" + Since 0.2.5 + +HTML5 provides a `files` attribute to return all files selected in a `` tag. NW.js provided an extra property `fileitem.path` to each file item in `files`, which is the native path of the selected file. + +```javascript +var fileinput = document.querySelector('input[type=file]'); +var files = fileinput.files; + +for (var i = 0; i < files.length; ++i) { + console.log(files[i].path); +} +``` + +### Attribute: `nwdirectory` + +!!! important "Available" + Since 0.2.5 + +`nwdirectory` is a bit similar to `webkitdirectory`, but it returns the path of directory. + +For example: + +```html + +``` + +### Attribute: `nwsaveas` + +!!! important "Available" + Since 0.2.5 + +`nwsaveas` will open a 'Save as' dialog, which lets user enter the path of a file. It's possible to select a non-existing file, which is different from the default file input tag. + +For example: + +```html + +``` + +Starting from v0.7.3 you can specify a value for the default file name to save: + +```html + +``` + +### Attribute: `nwworkingdir` + +!!! important "Available" + Since 0.5.0 + +With `nwworkingdir`, the file dialog starts in the given directory when the element is activated. + +For example, following code enables the file dialog opening in `/home/path/` by default: + +```html + +``` diff --git a/docs/References/Frames.md b/docs/References/Frames.md new file mode 100644 index 0000000000..ae4b3589c9 --- /dev/null +++ b/docs/References/Frames.md @@ -0,0 +1,69 @@ +# Frames {: .doctitle} +--- + +[TOC] + +NW.js extended Chromium for easier developing native apps. Thoses features enables apps to bypass the restrictions of Sandbox and Same Origin Policy. However in some cases, you want to embed the 3rd-party web page within your app, but do not want the 3rd-party web page to bypass these security settings. NW.js provided two ways to achieve this: using `` tag provided by Chrome App or `