diff --git a/atom.gyp b/atom.gyp index c768289f744..d6d9e2e2268 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.33.8', + 'version%': '0.34.0', }, 'includes': [ 'filenames.gypi', diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 639340bcf09..44f2048c091 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -21,6 +21,15 @@ namespace atom { +namespace { + +bool IsBrowserProcess(base::CommandLine* cmd) { + std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType); + return process_type.empty(); +} + +} // namespace + AtomMainDelegate::AtomMainDelegate() { } @@ -28,11 +37,14 @@ AtomMainDelegate::~AtomMainDelegate() { } bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { + auto command_line = base::CommandLine::ForCurrentProcess(); + logging::LoggingSettings settings; #if defined(OS_WIN) // On Windows the terminal returns immediately, so we add a new line to // prevent output in the same line as the prompt. - std::wcout << std::endl; + if (IsBrowserProcess(command_line)) + std::wcout << std::endl; #if defined(DEBUG) // Print logging to debug.log on Windows settings.logging_dest = logging::LOG_TO_ALL; @@ -47,7 +59,6 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { #endif // !defined(OS_WIN) // Only enable logging when --enable-logging is specified. - auto command_line = base::CommandLine::ForCurrentProcess(); if (!command_line->HasSwitch(switches::kEnableLogging)) { settings.logging_dest = logging::LOG_NONE; logging::SetMinLogLevel(logging::LOG_NUM_SEVERITIES); @@ -83,7 +94,7 @@ void AtomMainDelegate::PreSandboxStartup() { } // Only append arguments for browser process. - if (!process_type.empty()) + if (!IsBrowserProcess(command_line)) return; #if defined(OS_WIN) diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 7bc1a2153e3..3c92266be33 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -11,6 +11,7 @@ #include "atom/browser/api/atom_api_download_item.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/api/save_page_handler.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" @@ -237,6 +238,8 @@ Session::~Session() { void Session::OnDownloadCreated(content::DownloadManager* manager, content::DownloadItem* item) { auto web_contents = item->GetWebContents(); + if (SavePageHandler::IsSavePageTypes(item->GetMimeType())) + return; bool prevent_default = Emit( "will-download", DownloadItem::Create(isolate(), item), diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 0b8a9882cd1..5b3ef2f4aa7 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -162,6 +162,26 @@ struct Converter { } }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + content::SavePageType* out) { + std::string save_type; + if (!ConvertFromV8(isolate, val, &save_type)) + return false; + if (save_type == "HTMLOnly") { + *out = content::SAVE_PAGE_TYPE_AS_ONLY_HTML; + } else if (save_type == "HTMLComplete") { + *out = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; + } else if (save_type == "MHTML") { + *out = content::SAVE_PAGE_TYPE_AS_MHTML; + } else { + return false; + } + return true; + } +}; + } // namespace mate @@ -665,6 +685,13 @@ void WebContents::InsertCSS(const std::string& css) { web_contents()->InsertCSS(css); } +bool WebContents::SavePage(const base::FilePath& full_file_path, + const content::SavePageType& save_type, + const SavePageHandler::SavePageCallback& callback) { + auto handler = new SavePageHandler(web_contents(), callback); + return handler->Handle(full_file_path, save_type); +} + void WebContents::ExecuteJavaScript(const base::string16& code, bool has_user_gesture) { Send(new AtomViewMsg_ExecuteJavaScript(routing_id(), code, has_user_gesture)); @@ -976,6 +1003,7 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("setUserAgent", &WebContents::SetUserAgent) .SetMethod("getUserAgent", &WebContents::GetUserAgent) .SetMethod("insertCSS", &WebContents::InsertCSS) + .SetMethod("savePage", &WebContents::SavePage) .SetMethod("_executeJavaScript", &WebContents::ExecuteJavaScript) .SetMethod("openDevTools", &WebContents::OpenDevTools) .SetMethod("closeDevTools", &WebContents::CloseDevTools) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 01075c450a5..7fa8951106b 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -9,6 +9,7 @@ #include #include "atom/browser/api/frame_subscriber.h" +#include "atom/browser/api/save_page_handler.h" #include "atom/browser/api/trackable_object.h" #include "atom/browser/common_web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" @@ -73,6 +74,9 @@ class WebContents : public mate::TrackableObject, void SetUserAgent(const std::string& user_agent); std::string GetUserAgent(); void InsertCSS(const std::string& css); + bool SavePage(const base::FilePath& full_file_path, + const content::SavePageType& save_type, + const SavePageHandler::SavePageCallback& callback); void ExecuteJavaScript(const base::string16& code, bool has_user_gesture); void OpenDevTools(mate::Arguments* args); diff --git a/atom/browser/api/save_page_handler.cc b/atom/browser/api/save_page_handler.cc new file mode 100644 index 00000000000..42b93befddf --- /dev/null +++ b/atom/browser/api/save_page_handler.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/save_page_handler.h" + +#include + +#include "atom/browser/atom_browser_context.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "content/public/browser/web_contents.h" + +namespace atom { + +namespace api { + +SavePageHandler::SavePageHandler(content::WebContents* web_contents, + const SavePageCallback& callback) + : web_contents_(web_contents), + callback_(callback) { +} + +SavePageHandler::~SavePageHandler() { +} + +void SavePageHandler::OnDownloadCreated(content::DownloadManager* manager, + content::DownloadItem* item) { + // OnDownloadCreated is invoked during WebContents::SavePage, so the |item| + // here is the one stated by WebContents::SavePage. + item->AddObserver(this); +} + +bool SavePageHandler::Handle(const base::FilePath& full_path, + const content::SavePageType& save_type) { + auto download_manager = content::BrowserContext::GetDownloadManager( + web_contents_->GetBrowserContext()); + download_manager->AddObserver(this); + bool result = web_contents_->SavePage(full_path, + full_path.DirName(), + save_type); + download_manager->RemoveObserver(this); + // If initialization fails which means fail to create |DownloadItem|, we need + // to delete the |SavePageHandler| instance to avoid memory-leak. + if (!result) + delete this; + return result; +} + +void SavePageHandler::OnDownloadUpdated(content::DownloadItem* item) { + if (item->IsDone()) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + if (item->GetState() == content::DownloadItem::COMPLETE) { + callback_.Run(v8::Null(isolate)); + } else { + v8::Local error_message = v8::String::NewFromUtf8( + isolate, "Fail to save page"); + callback_.Run(v8::Exception::Error(error_message)); + } + Destroy(item); + } +} + +void SavePageHandler::Destroy(content::DownloadItem* item) { + item->RemoveObserver(this); + delete this; +} + +// static +bool SavePageHandler::IsSavePageTypes(const std::string& type) { + return type == "multipart/related" || type == "text/html"; +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/save_page_handler.h b/atom/browser/api/save_page_handler.h new file mode 100644 index 00000000000..dd1692a8bad --- /dev/null +++ b/atom/browser/api/save_page_handler.h @@ -0,0 +1,60 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_SAVE_PAGE_HANDLER_H_ +#define ATOM_BROWSER_API_SAVE_PAGE_HANDLER_H_ + +#include + +#include "content/public/browser/download_item.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/save_page_type.h" +#include "v8/include/v8.h" + +namespace base { +class FilePath; +} + +namespace content { +class WebContents; +} + +namespace atom { + +namespace api { + +// A self-destroyed class for handling save page request. +class SavePageHandler : public content::DownloadManager::Observer, + public content::DownloadItem::Observer { + public: + using SavePageCallback = base::Callback)>; + + SavePageHandler(content::WebContents* web_contents, + const SavePageCallback& callback); + ~SavePageHandler(); + + bool Handle(const base::FilePath& full_path, + const content::SavePageType& save_type); + + static bool IsSavePageTypes(const std::string& type); + + private: + void Destroy(content::DownloadItem* item); + + // content::DownloadManager::Observer: + void OnDownloadCreated(content::DownloadManager* manager, + content::DownloadItem* item) override; + + // content::DownloadItem::Observer: + void OnDownloadUpdated(content::DownloadItem* item) override; + + content::WebContents* web_contents_; // weak + SavePageCallback callback_; +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_SAVE_PAGE_HANDLER_H_ diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index 67630a1bd6f..9f92d700c73 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -18,6 +18,25 @@ require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init') globalPaths = Module.globalPaths globalPaths.push path.resolve(__dirname, '..', 'api', 'lib') +if process.platform is 'win32' + # Redirect node's console to use our own implementations, since node can not + # handle console output when running as GUI program. + consoleLog = (args...) -> + process.log util.format(args...) + "\n" + streamWrite = (chunk, encoding, callback) -> + chunk = chunk.toString(encoding) if Buffer.isBuffer chunk + process.log chunk + callback() if callback + true + console.log = console.error = console.warn = consoleLog + process.stdout.write = process.stderr.write = streamWrite + + # Always returns EOF for stdin stream. + Readable = require('stream').Readable + stdin = new Readable + stdin.push null + process.__defineGetter__ 'stdin', -> stdin + # Don't quit on fatal error. process.on 'uncaughtException', (error) -> # Do nothing if the user has a custom uncaught exception handler. diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index 149b208b409..a1e159a16a7 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -69,7 +69,9 @@ unwrapArgs = (sender, args) -> rendererReleased = true ret = -> - throw new Error('Calling a callback of released renderer view') if rendererReleased + if rendererReleased + throw new Error("Attempting to call a function in a renderer window + that has been closed or released. Function provided here: #{meta.id}.") sender.send 'ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, arguments) v8Util.setDestructor ret, -> return if rendererReleased diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index dd4694b3bd6..7f30aa00389 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -116,7 +116,8 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { } else if (options.Get(switches::kCenter, ¢er) && center) { Center(); } - extensions::SizeConstraints size_constraints; + // On Linux and Window we may already have maximum size defined. + extensions::SizeConstraints size_constraints(GetContentSizeConstraints()); int min_height = 0, min_width = 0; if (options.Get(switches::kMinHeight, &min_height) | options.Get(switches::kMinWidth, &min_width)) { diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 4c8d014b1ec..e3688235bd4 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -390,7 +390,10 @@ gfx::Size NativeWindowViews::GetContentSize() { void NativeWindowViews::SetContentSizeConstraints( const extensions::SizeConstraints& size_constraints) { NativeWindow::SetContentSizeConstraints(size_constraints); - window_->OnSizeConstraintsChanged(); + // widget_delegate() is only available after Init() is called, we make use of + // this to determine whether native widget has initialized. + if (window_ && window_->widget_delegate()) + window_->OnSizeConstraintsChanged(); #if defined(USE_X11) if (resizable_) old_size_constraints_ = size_constraints; diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index f4725715526..01c4bc7e628 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.33.8 + 0.34.0 CFBundleShortVersionString - 0.33.8 + 0.34.0 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 08af14987ee..5a25256831c 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,33,8,0 - PRODUCTVERSION 0,33,8,0 + FILEVERSION 0,34,0,0 + PRODUCTVERSION 0,34,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.33.8" + VALUE "FileVersion", "0.34.0" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.33.8" + VALUE "ProductVersion", "0.34.0" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 2856598c0bc..14e32353af6 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -26,7 +26,6 @@ namespace { const char* kWebRuntimeFeatures[] = { switches::kExperimentalFeatures, switches::kExperimentalCanvasFeatures, - switches::kSubpixelFontScaling, switches::kOverlayScrollbars, switches::kOverlayFullscreenVideo, switches::kSharedWorker, diff --git a/atom/common/api/lib/callbacks-registry.coffee b/atom/common/api/lib/callbacks-registry.coffee index 8f5eb62916c..d4c37f087b1 100644 --- a/atom/common/api/lib/callbacks-registry.coffee +++ b/atom/common/api/lib/callbacks-registry.coffee @@ -1,16 +1,25 @@ -savedGlobal = global # the "global.global" might be deleted later - module.exports = class CallbacksRegistry constructor: -> - @emptyFunc = -> throw new Error "Browser trying to call a non-exist callback - in renderer, this usually happens when renderer code forgot to release - a callback installed on objects in browser when renderer was going to be - unloaded or released." + @nextId = 0 @callbacks = {} add: (callback) -> - id = Math.random().toString() + id = ++@nextId + + # Capture the location of the function and put it in the ID string, + # so that release errors can be tracked down easily. + regexp = /at (.*)/gi + stackString = (new Error).stack + + while (match = regexp.exec(stackString)) isnt null + [x, location] = match + continue if location.indexOf('(native)') isnt -1 + continue if location.indexOf('atom.asar') isnt -1 + [x, filenameAndLine] = /([^/^\)]*)\)?$/gi.exec(location) + id = "#{filenameAndLine} (#{id})" + break + @callbacks[id] = callback id @@ -18,10 +27,10 @@ class CallbacksRegistry @callbacks[id] ? -> call: (id, args...) -> - @get(id).call savedGlobal, args... + @get(id).call global, args... apply: (id, args...) -> - @get(id).apply savedGlobal, args... + @get(id).apply global, args... remove: (id) -> delete @callbacks[id] diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 205d512b2b5..8351fff18ca 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -6,8 +6,8 @@ #define ATOM_VERSION_H #define ATOM_MAJOR_VERSION 0 -#define ATOM_MINOR_VERSION 33 -#define ATOM_PATCH_VERSION 8 +#define ATOM_MINOR_VERSION 34 +#define ATOM_PATCH_VERSION 0 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index be096da80e2..78bd196f6cc 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -11,6 +11,25 @@ #include "base/memory/singleton.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "content/public/common/result_codes.h" +#include "gin/public/debug.h" +#include "sandbox/win/src/nt_internals.h" + +#pragma intrinsic(_AddressOfReturnAddress) +#pragma intrinsic(_ReturnAddress) + +#ifdef _WIN64 +// See http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx +typedef struct _UNWIND_INFO { + unsigned char Version : 3; + unsigned char Flags : 5; + unsigned char SizeOfProlog; + unsigned char CountOfCodes; + unsigned char FrameRegister : 4; + unsigned char FrameOffset : 4; + ULONG ExceptionHandler; +} UNWIND_INFO, *PUNWIND_INFO; +#endif namespace crash_reporter { @@ -24,6 +43,94 @@ const MINIDUMP_TYPE kSmallDumpType = static_cast( const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent"; const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\$1 Crash Service"; +typedef NTSTATUS (WINAPI* NtTerminateProcessPtr)(HANDLE ProcessHandle, + NTSTATUS ExitStatus); +char* g_real_terminate_process_stub = NULL; + +void TerminateProcessWithoutDump() { + // Patched stub exists based on conditions (See InitCrashReporter). + // As a side note this function also gets called from + // WindowProcExceptionFilter. + if (g_real_terminate_process_stub == NULL) { + ::TerminateProcess(::GetCurrentProcess(), content::RESULT_CODE_KILLED); + } else { + NtTerminateProcessPtr real_terminate_proc = + reinterpret_cast( + static_cast(g_real_terminate_process_stub)); + real_terminate_proc(::GetCurrentProcess(), content::RESULT_CODE_KILLED); + } +} + +#ifdef _WIN64 +int CrashForExceptionInNonABICompliantCodeRange( + PEXCEPTION_RECORD ExceptionRecord, + ULONG64 EstablisherFrame, + PCONTEXT ContextRecord, + PDISPATCHER_CONTEXT DispatcherContext) { + EXCEPTION_POINTERS info = { ExceptionRecord, ContextRecord }; + if (!CrashReporter::GetInstance()) + return EXCEPTION_CONTINUE_SEARCH; + return static_cast(CrashReporter::GetInstance())-> + CrashForException(&info); +} + +struct ExceptionHandlerRecord { + RUNTIME_FUNCTION runtime_function; + UNWIND_INFO unwind_info; + unsigned char thunk[12]; +}; + +void RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes) { + ExceptionHandlerRecord* record = + reinterpret_cast(start); + + // We assume that the first page of the code range is executable and + // committed and reserved for breakpad. What could possibly go wrong? + + // All addresses are 32bit relative offsets to start. + record->runtime_function.BeginAddress = 0; + record->runtime_function.EndAddress = + base::checked_cast(size_in_bytes); + record->runtime_function.UnwindData = + offsetof(ExceptionHandlerRecord, unwind_info); + + // Create unwind info that only specifies an exception handler. + record->unwind_info.Version = 1; + record->unwind_info.Flags = UNW_FLAG_EHANDLER; + record->unwind_info.SizeOfProlog = 0; + record->unwind_info.CountOfCodes = 0; + record->unwind_info.FrameRegister = 0; + record->unwind_info.FrameOffset = 0; + record->unwind_info.ExceptionHandler = + offsetof(ExceptionHandlerRecord, thunk); + + // Hardcoded thunk. + // mov imm64, rax + record->thunk[0] = 0x48; + record->thunk[1] = 0xb8; + void* handler = &CrashForExceptionInNonABICompliantCodeRange; + memcpy(&record->thunk[2], &handler, 8); + + // jmp rax + record->thunk[10] = 0xff; + record->thunk[11] = 0xe0; + + // Protect reserved page against modifications. + DWORD old_protect; + CHECK(VirtualProtect( + start, sizeof(ExceptionHandlerRecord), PAGE_EXECUTE_READ, &old_protect)); + CHECK(RtlAddFunctionTable( + &record->runtime_function, 1, reinterpret_cast(start))); +} + +void UnregisterNonABICompliantCodeRange(void* start) { + ExceptionHandlerRecord* record = + reinterpret_cast(start); + + CHECK(RtlDeleteFunctionTable(&record->runtime_function)); +} +#endif // _WIN64 + } // namespace CrashReporterWin::CrashReporterWin() { @@ -63,26 +170,46 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, // to allow any previous handler to detach in the correct order. breakpad_.reset(); - int handler_types = google_breakpad::ExceptionHandler::HANDLER_EXCEPTION | - google_breakpad::ExceptionHandler::HANDLER_PURECALL; breakpad_.reset(new google_breakpad::ExceptionHandler( temp_dir.value(), FilterCallback, MinidumpCallback, this, - handler_types, + google_breakpad::ExceptionHandler::HANDLER_ALL, kSmallDumpType, pipe_name.c_str(), GetCustomInfo(product_name, version, company_name))); if (!breakpad_->IsOutOfProcess()) LOG(ERROR) << "Cannot initialize out-of-process crash handler"; + +#ifdef _WIN64 + // Hook up V8 to breakpad. + { + // gin::Debug::SetCodeRangeCreatedCallback only runs the callback when + // Isolate is just created, so we have to manually run following code here. + void* code_range = nullptr; + size_t size = 0; + v8::Isolate::GetCurrent()->GetCodeRange(&code_range, &size); + if (code_range && size) + RegisterNonABICompliantCodeRange(code_range, size); + } + gin::Debug::SetCodeRangeDeletedCallback(UnregisterNonABICompliantCodeRange); +#endif } void CrashReporterWin::SetUploadParameters() { upload_parameters_["platform"] = "win32"; } +int CrashReporterWin::CrashForException(EXCEPTION_POINTERS* info) { + if (breakpad_) { + breakpad_->WriteMinidumpForException(info); + TerminateProcessWithoutDump(); + } + return EXCEPTION_CONTINUE_SEARCH; +} + // static bool CrashReporterWin::FilterCallback(void* context, EXCEPTION_POINTERS* exinfo, diff --git a/atom/common/crash_reporter/crash_reporter_win.h b/atom/common/crash_reporter/crash_reporter_win.h index 72b9411d219..09c7ff4eaad 100644 --- a/atom/common/crash_reporter/crash_reporter_win.h +++ b/atom/common/crash_reporter/crash_reporter_win.h @@ -29,6 +29,9 @@ class CrashReporterWin : public CrashReporter { bool skip_system_crash_handler) override; void SetUploadParameters() override; + // Crashes the process after generating a dump for the provided exception. + int CrashForException(EXCEPTION_POINTERS* info); + private: friend struct DefaultSingletonTraits; diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index d315b0b9419..9b6ba7e03a6 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -311,7 +311,7 @@ bool CrashService::Initialize(const base::string16& application_name, // service is initialized. base::string16 wait_name = ReplaceStringPlaceholders( kWaitEventFormat, application_name, NULL); - HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, wait_name.c_str()); + HANDLE wait_event = ::CreateEventW(NULL, TRUE, TRUE, wait_name.c_str()); ::SetEvent(wait_event); return true; diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 46687becf84..e05768b5230 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -99,7 +99,6 @@ const char kClientCertificate[] = "client-certificate"; // Web runtime features. const char kExperimentalFeatures[] = "experimental-features"; const char kExperimentalCanvasFeatures[] = "experimental-canvas-features"; -const char kSubpixelFontScaling[] = "subpixel-font-scaling"; const char kOverlayScrollbars[] = "overlay-scrollbars"; const char kOverlayFullscreenVideo[] = "overlay-fullscreen-video"; const char kSharedWorker[] = "shared-worker"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 16046d19c82..c568804c4df 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -51,7 +51,6 @@ extern const char kClientCertificate[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; -extern const char kSubpixelFontScaling[]; extern const char kOverlayScrollbars[]; extern const char kOverlayFullscreenVideo[]; extern const char kSharedWorker[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index eeadabcba73..10dd2541b9c 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -234,8 +234,6 @@ void AtomRendererClient::EnableWebRuntimeFeatures() { blink::WebRuntimeFeatures::enableExperimentalFeatures(b); if (IsSwitchEnabled(command_line, switches::kExperimentalCanvasFeatures, &b)) blink::WebRuntimeFeatures::enableExperimentalCanvasFeatures(b); - if (IsSwitchEnabled(command_line, switches::kSubpixelFontScaling, &b)) - blink::WebRuntimeFeatures::enableSubpixelFontScaling(b); if (IsSwitchEnabled(command_line, switches::kOverlayScrollbars, &b)) blink::WebRuntimeFeatures::enableOverlayScrollbars(b); if (IsSwitchEnabled(command_line, switches::kOverlayFullscreenVideo, &b)) diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md index 74923a411c5..db88edaf77e 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -1,12 +1,12 @@ ## Guias -* [Distribuir Aplicação](tutorial/application-distribution.md) +* [Distribuição de Aplicações](tutorial/application-distribution.md) * [Empacotamento da aplicação](tutorial/application-packaging.md) -* [Usando módulos nativos](../../docs/tutorial/using-native-node-modules.md) -* [Depuração do processo principal](../../docs/tutorial/debugging-main-process.md) +* [Usando módulos nativos](tutorial/using-native-node-modules.md) +* [Depuração do processo principal](tutorial/debugging-main-process.md) * [Usando Selenium e WebDriver](../../docs/tutorial/using-selenium-and-webdriver.md) * [Extensão DevTools](../../docs/tutorial/devtools-extension.md) -* [Usando o plugin papper flash](../../docs/tutorial/using-pepper-flash-plugin.md) +* [Usando o plugin papper flash](tutorial/using-pepper-flash-plugin.md) ## Tutoriais diff --git a/docs-translations/pt-BR/tutorial/debugging-main-process.md b/docs-translations/pt-BR/tutorial/debugging-main-process.md new file mode 100644 index 00000000000..ee79a523e92 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/debugging-main-process.md @@ -0,0 +1,53 @@ +# Depurando o Processo Principal + +A janela do navegador, DevTools, pode somente depurar o processo de renderização +de scripts (por exemplo, as páginas da web). Para providenciar um modo de +depurar os scripts através do processo principal, o Electron criou as opções +`--debug` e `--debug-brk`. + +## Opções da Linha de Comando + +Use a seguinte opção na linha de comando para depurar o processo principal do +Electron: + +### `--debug=[porta]` + +Quando este comando é usado, o Electron irá executar o protocolo de depuração +V8 mandando as mensagens na `porta`. A `porta` padrão é `5858`. + +### `--debug-brk=[porta]` + +Semelhante ao `--debug`, porém pausa o script na primeira linha. + +## Usando node-inspector para depurar + +__Nota:__ O Electron usa a versão v0.11.13 do Node, a qual, atualmenta não +funciona muito bem com node-inspector, e o processo principal irá quebrar se +você inspecionar o objeto `process` pelo console do node-inspector. + +### 1. Inicie o servidor [node-inspector][node-inspector] + +```bash +$ node-inspector +``` + +### 2. Habilite o modo de depuração para o Electron + +Você pode também iniciar o Electron com um ponto de depuração, desta maneira: + +```bash +$ electron --debug=5858 sua/aplicacao +``` + +ou para pausar o script na primeira linha: + +```bash +$ electron --debug-brk=5858 sua/aplicacao +``` + +### 3. Carregue o debugger UI + +Abra este endereço http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 +usando o Chrome. + +[node-inspector]: https://github.com/node-inspector/node-inspector diff --git a/docs-translations/pt-BR/tutorial/using-native-node-modules.md b/docs-translations/pt-BR/tutorial/using-native-node-modules.md new file mode 100644 index 00000000000..c5e9eeddd61 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/using-native-node-modules.md @@ -0,0 +1,68 @@ +# Usando Módulos Nativos do Node + +Os módulos nativos do Node são suportados pelo Electron, desde que o Electron +esteja usando uma versão diferente da oficial V8 do Node, você tem de +especificar manualmente a localização das headers do Electron quando compilar os +módulos nativos. + +## Compatibilidade de Módulos Nativos do Node + +Módulos nativos podem quebrar quando utilizar a nova versão do Node, V8. +Para ter certeza que o módulo que você está interessado em trabalhar com o +Electron, você deve checar se a versão do Node utilizada é compatível com a +usada pelo Electron. +Você pode verificar qual versão do Node foi utilizada no Electron olhando na +página [releases](https://github.com/atom/electron/releases) ou usando +`process.version` (veja [Quick Start](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md) +por exemplo). + +Considere usar [NAN](https://github.com/nodejs/nan/) para seus próprios +módulos, caso isso facilite o suporte da múltiplas versões do Node. Isso é +também de grande ajuda para fazer a portabilidade dos módulos antigos para as +versões mais novas do Node, assim podendo trabalhar com o Electron. + +## Como Instalar os Módulos Nativos + +Existem três maneiras de instalar os módulos nativos: + +### O Modo Fácil + +O modo mais direto para recompilar os módulos é pelo pacote +[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild), +o que lida com passos manuais para baixar as headers e construir os módulos +nativos: + +```sh +npm install --save-dev electron-rebuild + +# Sempre que rodar npm install, execute também: +node ./node_modules/.bin/electron-rebuild +``` + +### Via npm + +Você pode usar também `npm` para instalar os módulos. Os passos são exatamente +os mesmos com os módulos Node, exceto que você precisa configurar algumas +variáveis da ambiente: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.33.1 +export npm_config_arch=x64 +export npm_config_runtime=electron +HOME=~/.electron-gyp npm install module-name +``` + +### O modo node-gyp + +Para compilar os módulos do Node com as headers do Electron, você precisa dizer +ao `node-gyp` onde baixar as headers e qual versão usar: + +```bash +$ cd /path-to-module/ +$ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell +``` + +A tag `HOME=~/.electron-gyp` altera onde encontrar as headers de desenvolvimento. +A tag `--target=0.29.1` é a versão do Electron. A tag `--dist-url=...` especifica +onde baixar as headers. A tag `--arch=x64` diz ao módulo que é feito em 64bit. diff --git a/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md b/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 00000000000..a92f61c78e2 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,68 @@ +# Usando o Plugin Pepper Flash + +Electron atualmente suporta o plugin Pepper Flash. Para usá-lo no Electron, +você deve especificar manualmente a localização do plugin e então ele será +habilitado em sua aplicação. + +## Prepare uma cópia do plugin Flash + +Tanto no OS X como no Linux, os detalhes do plugin Pepper Flash podem ser +encontrados navegando por `chrome://plugins` no navegador Chrome. Essa +localização e versão são úteis para o suporte do plugin Electron's Pepper Flash. +Você pode também copiar para outra localização. + +## Adicionando a opçao Electron + +Você pode adicionar diretamente `--ppapi-flash-path` e `ppapi-flash-version` +para a linha de comando do Electron ou usando o método +`app.commandLine.appendSwitch` após o evento pronto. Também, adicione a opção +`plugins` em `browser-window`. +Por exemplo: + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +// Informa os erros ao ao servidor. +require('crash-reporter').start(); + +// Mantém uma referência global da janela, se não manter, a janela irá fechar +// automaticamente quando o objeto javascript for GCed. +var mainWindow = null; + +// Sai assim que todas as janelas forem fechadas. +app.on('window-all-closed', function() { + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// Epecifica o caminho do flash. +// No Windows, deve ser /path/to/pepflashplayer.dll +// No Mac, /path/to/PepperFlashPlayer.plugin +// No Linux, /path/to/libpepflashplayer.so +app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); + +// Especifica a versão do flash, por exemplo, v17.0.0.169 +app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169'); + +app.on('ready', function() { + mainWindow = new BrowserWindow({ + 'width': 800, + 'height': 600, + 'web-preferences': { + 'plugins': true + } + }); + mainWindow.loadUrl('file://' + __dirname + '/index.html'); + // Algo mais +}); +``` + +## Ative o plugin Flash na tag `` + +Adicione o atributo `plugins` na tag ``. + +```html + +``` diff --git a/docs/README.md b/docs/README.md index eb6e9d6e36f..a0d91a5459c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,7 @@ * [Supported Platforms](tutorial/supported-platforms.md) * [Application Distribution](tutorial/application-distribution.md) +* [Mac App Store Submission Guide](tutorial/mac-app-store-submission-guide.md) * [Application Packaging](tutorial/application-packaging.md) * [Using Native Node Modules](tutorial/using-native-node-modules.md) * [Debugging Main Process](tutorial/debugging-main-process.md) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 1a2870f205a..fd25f8cdc98 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -111,7 +111,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `plugins` Boolean - Whether plugins should be enabled. * `experimental-features` Boolean * `experimental-canvas-features` Boolean - * `subpixel-font-scaling` Boolean * `overlay-scrollbars` Boolean * `overlay-fullscreen-video` Boolean * `shared-worker` Boolean diff --git a/docs/api/process.md b/docs/api/process.md index a2157886bc6..22fe452b0ca 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -8,6 +8,8 @@ upstream node: * `process.versions['electron']` String - Version of Electron. * `process.versions['chrome']` String - Version of Chromium. * `process.resourcesPath` String - Path to JavaScript source code. +* `process.mas` Boolean - For Mac App Store build, this value is `true`, for + other builds it is `undefined`. ## Events @@ -37,7 +39,7 @@ The `process` object has the following method: Causes the main thread of the current process hang. -### process.setFdLimit(maxDescriptors) _OS X_ _Linux_ +### `process.setFdLimit(maxDescriptors)` _OS X_ _Linux_ * `maxDescriptors` Integer diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 08670bad9df..006d7e3934b 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -631,3 +631,26 @@ Get the `WebContents` of DevTools for this `WebContents`. **Note:** Users should never store this object because it may become `null` when the DevTools has been closed. + +### `webContents.savePage(fullPath, saveType, callback)` + +* `fullPath` String - The full file path. +* `saveType` String - Specify the save type. + * `HTMLOnly` - Save only the HTML of the page. + * `HTMLComplete` - Save complete-html page. + * `MHTML` - Save complete-html page as MHTML. +* `callback` Function - `function(error) {}`. + * `error` Error + +Returns true if the process of saving page has been initiated successfully. + +```javascript +win.loadUrl('https://github.com'); + +win.webContents.on('did-finish-load', function() { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', function(error) { + if (!error) + console.log("Save page successfully"); + }); +}); +``` diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md new file mode 100644 index 00000000000..ccd0ead8584 --- /dev/null +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -0,0 +1,115 @@ +# Mac App Store Submission Guide + +Since v0.34.0, Electron allows submitting packaged apps to Mac App Store (MAS), +this guide provides information on how to submit your app, and the limitations +of the MAS build. + +## How to submit your app + +Following steps introduces a simple way to submit your app to Mac App Store, but +it doesn't make sure your app gets approved by Apple, you still have to read +apple's [Submitting Your App][submitting-your-app] guide on how to meet Mac +App Store's requirements. + +### Get certificate + +To submit your app to Mac App Store, you have to get a certificate from Apple +first, you can follow [existing guides][nwjs-guide] on web. + +### Sign your app + +After getting the certificate, you can package your app by following +[Application Distribution](application-distribution.md), and then sign your app. +The step is basically the same with other programs, the key is to sign every +dependency of Electron one by one. + +First you need to prepare two entitlements files. + +`child.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + +``` + +`parent.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + + +``` + +And then sign your app with following script: + +```bash +#!/bin/bash + +# Name of your app. +APP="YourApp" +# The path of you app to sign. +APP_PATH="/path/to/YouApp.app" +# The path to the location you want to put the signed package. +RESULT_PATH="~/Desktop/$APP.pkg" +# The name of certificates you requested. +APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" +INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" + +FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" + +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$APP_PATH" +``` + +If you are new to app sandboxing of OS X, you should also go through Apple's +[Enabling App Sandbox][enable-app-sandbox] to have a basic idea, and add keys +for the permissions needed by your app to the entitlements files. + +### Upload your app and submit for review + +After signing your app you can use Application Loader to upload it to iTunes +Connect for processing, make sure you have [created a record][create-record] +before uploading. Then you can [submit your app for review][submit-for-review]. + +## Limitations of MAS build + +In order to satisfy requirements for app sandboxing, following modules have been +disabled in MAS build: + +* `crash-reporter` +* `auto-updater` + +and following behaviors have been changed: + +* Video capture may not work for some machines. +* Certain accessibility features may not work. +* Apps will not be aware of DNS changes. + +Also due to the usage of app sandboxing, the resources can be accessed by the +app is strictly limited, you can read [App Sandboxing][app-sandboxing] for more. + +[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html +[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps +[enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html +[create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html +[submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html +[app-sandboxing]: https://developer.apple.com/app-sandboxing/ diff --git a/filenames.gypi b/filenames.gypi index 6418ba28721..aadc1a092f6 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -108,6 +108,8 @@ 'atom/browser/api/trackable_object.h', 'atom/browser/api/frame_subscriber.cc', 'atom/browser/api/frame_subscriber.h', + 'atom/browser/api/save_page_handler.cc', + 'atom/browser/api/save_page_handler.h', 'atom/browser/auto_updater.cc', 'atom/browser/auto_updater.h', 'atom/browser/auto_updater_delegate.h', diff --git a/script/lib/config.py b/script/lib/config.py index 30eedc13e96..83468f81994 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -7,8 +7,8 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ - 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '04523758cda2a96d2454f9056fb1fb9a1c1f95f1' + 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' +LIBCHROMIUMCONTENT_COMMIT = '78e54bc39a04b758ed5167cd980cc4d9951bd629' PLATFORM = { 'cygwin': 'win32', diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index 2e4a66b92b9..0f9b376784f 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -301,3 +301,15 @@ describe 'browser-window module', -> assert.notEqual data.length, 0 w.webContents.endFrameSubscription() done() + + describe 'save page', -> + savePagePath = path.join fixtures, 'save_page.html' + it 'should save page', (done) -> + w.webContents.on 'did-finish-load', -> + w.webContents.savePage savePagePath, 'HTMLComplete', (error) -> + assert.equal error, null + assert fs.existsSync savePagePath + fs.unlinkSync savePagePath + done() + + w.loadUrl "file://#{fixtures}/api/blank.html" diff --git a/spec/api-crash-reporter-spec.coffee b/spec/api-crash-reporter-spec.coffee index ef60e6ab9ff..9aedb0c8ea1 100644 --- a/spec/api-crash-reporter-spec.coffee +++ b/spec/api-crash-reporter-spec.coffee @@ -15,9 +15,6 @@ describe 'crash-reporter module', -> beforeEach -> w = new BrowserWindow(show: false) afterEach -> w.destroy() - # It is not working on 64bit Windows. - return if process.platform is 'win32' and process.arch is 'x64' - # It is not working for mas build. return if process.mas diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index 1e6ee691036..75f0cec63ad 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -450,10 +450,10 @@ describe 'asar package', -> w = new BrowserWindow(show: false, width: 400, height: 400) p = path.resolve fixtures, 'asar', 'web.asar', 'index.html' u = url.format protocol: 'file', slashed: true, pathname: p - w.loadUrl u ipc.once 'dirname', (event, dirname) -> assert.equal dirname, path.dirname(p) done() + w.loadUrl u it 'loads script tag in html', (done) -> after ->