From c9fbde321c5595430fa869774f4bb942470e9221 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Fri, 2 Oct 2015 17:50:10 +0800 Subject: [PATCH 001/411] Implement desktop capture API on OS X. --- atom.gyp | 4 + atom/browser/api/atom_api_desktop_capturer.cc | 127 ++++++ atom/browser/api/atom_api_desktop_capturer.h | 51 +++ atom/browser/api/atom_api_screen.cc | 18 +- atom/browser/api/atom_api_screen.h | 5 + atom/browser/api/lib/app.coffee | 14 +- atom/common/node_bindings.cc | 1 + .../chrome/browser/media/desktop_media_list.h | 60 +++ .../media/desktop_media_list_observer.h | 22 ++ .../media/native_desktop_media_list.cc | 366 ++++++++++++++++++ .../browser/media/native_desktop_media_list.h | 100 +++++ filenames.gypi | 6 + 12 files changed, 768 insertions(+), 6 deletions(-) create mode 100644 atom/browser/api/atom_api_desktop_capturer.cc create mode 100644 atom/browser/api/atom_api_desktop_capturer.h create mode 100644 chromium_src/chrome/browser/media/desktop_media_list.h create mode 100644 chromium_src/chrome/browser/media/desktop_media_list_observer.h create mode 100644 chromium_src/chrome/browser/media/native_desktop_media_list.cc create mode 100644 chromium_src/chrome/browser/media/native_desktop_media_list.h diff --git a/atom.gyp b/atom.gyp index 3a46f242a2..ed885743c6 100644 --- a/atom.gyp +++ b/atom.gyp @@ -245,6 +245,10 @@ 'vendor/node/deps/cares/include', # The `third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h` is using `platform/PlatformExport.h`. '<(libchromiumcontent_src_dir)/third_party/WebKit/Source', + # The 'third_party/libyuv/include/libyuv/scale_argb.h' is using 'libyuv/basic_types.h'. + '<(libchromiumcontent_src_dir)/third_party/libyuv/include', + # The 'third_party/webrtc/modules/desktop_capture/desktop_frame.h' is using 'webrtc/base/scoped_ptr.h'. + '<(libchromiumcontent_src_dir)/third_party/', ], 'direct_dependent_settings': { 'include_dirs': [ diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc new file mode 100644 index 0000000000..c3a5737157 --- /dev/null +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -0,0 +1,127 @@ +// 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/atom_api_desktop_capturer.h" + +#include "atom/common/api/atom_api_native_image.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/node_includes.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/media/desktop_media_list.h" +#include "native_mate/dictionary.h" +#include "native_mate/handle.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" + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const DesktopMediaList::Source& source) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + content::DesktopMediaID id = source.id; + dict.Set("name", base::UTF16ToUTF8(source.name)); + dict.Set("id", id.ToString()); + dict.Set("thumbnail", + atom::api::NativeImage::Create(isolate, gfx::Image(source.thumbnail))); + return ConvertToV8(isolate, dict); + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +namespace { +// The wrapDesktopCapturer funtion which is implemented in JavaScript +using WrapDesktopCapturerCallback = base::Callback)>; +WrapDesktopCapturerCallback g_wrap_desktop_capturer; + +const int kThumbnailWidth = 150; +const int kThumbnailHeight = 150; +} // namespace + +DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) { + scoped_ptr screen_capturer( + show_screens ? webrtc::ScreenCapturer::Create() : nullptr); + scoped_ptr window_capturer( + show_windows ? webrtc::WindowCapturer::Create() : nullptr); + media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), + window_capturer.Pass())); + media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight)); + media_list_->StartUpdating(this); +} + +DesktopCapturer::~DesktopCapturer() { +} + +const DesktopMediaList::Source& DesktopCapturer::GetSource(int index) { + return media_list_->GetSource(index); +} + +void DesktopCapturer::OnSourceAdded(int index) { + Emit("source-added", index); +} + +void DesktopCapturer::OnSourceRemoved(int index) { + Emit("source-removed", index); +} + +void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { + Emit("source-moved", old_index, new_index); +} + +void DesktopCapturer::OnSourceNameChanged(int index) { + Emit("source-name-changed", index); +} + +void DesktopCapturer::OnSourceThumbnailChanged(int index) { + Emit("source-thumbnail-changed", index); +} + +mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return mate::ObjectTemplateBuilder(isolate) + .SetMethod("getSource", &DesktopCapturer::GetSource); +} + +void SetWrapDesktopCapturer(const WrapDesktopCapturerCallback& callback) { + g_wrap_desktop_capturer = callback; +} + +void ClearWrapDesktopCapturer() { + g_wrap_desktop_capturer.Reset(); +} + +// static +mate::Handle DesktopCapturer::Create(v8::Isolate* isolate, + bool show_screens, bool show_windows) { + auto handle = mate::CreateHandle(isolate, + new DesktopCapturer(show_screens, show_windows)); + g_wrap_desktop_capturer.Run(handle.ToV8()); + return handle; +} + +} // namespace api + +} // namespace atom + +namespace { + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.SetMethod("_setWrapDesktopCapturer", &atom::api::SetWrapDesktopCapturer); + dict.SetMethod("_clearWrapDesktopCapturer", + &atom::api::ClearWrapDesktopCapturer); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_desktop_capturer, Initialize); diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h new file mode 100644 index 0000000000..aae0a8268b --- /dev/null +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -0,0 +1,51 @@ +// 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_ATOM_API_DESKTOP_CAPTURER_ +#define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ + +#include "base/memory/scoped_ptr.h" +#include "atom/browser/api/event_emitter.h" +#include "chrome/browser/media/desktop_media_list_observer.h" +#include "chrome/browser/media/native_desktop_media_list.h" +#include "native_mate/handle.h" + +namespace atom { + +namespace api { + +class DesktopCapturer: public mate::EventEmitter, + public DesktopMediaListObserver { + public: + static mate::Handle Create(v8::Isolate* isolate, + bool show_screens, bool show_windows); + + const DesktopMediaList::Source& GetSource(int index); + + protected: + DesktopCapturer(bool show_screens, bool show_windows); + ~DesktopCapturer(); + + // DesktopMediaListObserver overrides. + 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; + + private: + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + + scoped_ptr media_list_; + + DISALLOW_COPY_AND_ASSIGN(DesktopCapturer); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index b73bda9ced..761ef28ef9 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -56,6 +56,21 @@ Screen::~Screen() { screen_->RemoveObserver(this); } +mate::Handle Screen::GetDesktopCapturer( + const std::vector& sources) { + bool show_screens = false; + bool show_windows = false; + for (const auto& source_type : sources) { + if (source_type == "screen") { + show_screens = true; + } else if (source_type == "window") { + show_windows = true; + } + } + + return DesktopCapturer::Create(isolate(), show_screens, show_windows); +} + gfx::Point Screen::GetCursorScreenPoint() { return screen_->GetCursorScreenPoint(); } @@ -107,7 +122,8 @@ mate::ObjectTemplateBuilder Screen::GetObjectTemplateBuilder( .SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay) .SetMethod("getAllDisplays", &Screen::GetAllDisplays) .SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint) - .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching); + .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching) + .SetMethod("getDesktopCapturer", &Screen::GetDesktopCapturer); } // static diff --git a/atom/browser/api/atom_api_screen.h b/atom/browser/api/atom_api_screen.h index f724130fa7..619f6c8b3f 100644 --- a/atom/browser/api/atom_api_screen.h +++ b/atom/browser/api/atom_api_screen.h @@ -6,7 +6,9 @@ #define ATOM_BROWSER_API_ATOM_API_SCREEN_H_ #include +#include +#include "atom/browser/api/atom_api_desktop_capturer.h" #include "atom/browser/api/event_emitter.h" #include "native_mate/handle.h" #include "ui/gfx/display_observer.h" @@ -36,6 +38,9 @@ class Screen : public mate::EventEmitter, gfx::Display GetDisplayNearestPoint(const gfx::Point& point); gfx::Display GetDisplayMatching(const gfx::Rect& match_rect); + mate::Handle GetDesktopCapturer( + const std::vector& sources); + // gfx::DisplayObserver: void OnDisplayAdded(const gfx::Display& new_display) override; void OnDisplayRemoved(const gfx::Display& old_display) override; diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index 18c80dc2b1..9ff568cf8d 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -3,17 +3,18 @@ EventEmitter = require('events').EventEmitter bindings = process.atomBinding 'app' sessionBindings = process.atomBinding 'session' downloadItemBindings = process.atomBinding 'download_item' +desktopCapturerBindings = process.atomBinding 'desktop_capturer' app = bindings.app app.__proto__ = EventEmitter.prototype -wrapSession = (session) -> - # session is an Event Emitter. - session.__proto__ = EventEmitter.prototype +wrapToEventListener = (item) -> + # item is an Event Emitter. + item.__proto__ = EventEmitter.prototype wrapDownloadItem = (download_item) -> # download_item is an Event Emitter. - download_item.__proto__ = EventEmitter.prototype + wrapToEventListener download_item # Be compatible with old APIs. download_item.url = download_item.getUrl() download_item.filename = download_item.getFilename() @@ -58,11 +59,14 @@ app.resolveProxy = -> @defaultSession.resolveProxy.apply @defaultSession, argume app.on 'activate', (event, hasVisibleWindows) -> @emit 'activate-with-no-open-windows' if not hasVisibleWindows # Session wrapper. -sessionBindings._setWrapSession wrapSession +sessionBindings._setWrapSession wrapToEventListener process.once 'exit', sessionBindings._clearWrapSession downloadItemBindings._setWrapDownloadItem wrapDownloadItem process.once 'exit', downloadItemBindings._clearWrapDownloadItem +desktopCapturerBindings._setWrapDesktopCapturer wrapToEventListener +process.once 'exit', desktopCapturerBindings._clearWrapDesktopCapturer + # Only one App object pemitted. module.exports = app diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 2da68854ad..9d2004deec 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -34,6 +34,7 @@ REFERENCE_MODULE(atom_browser_app); REFERENCE_MODULE(atom_browser_auto_updater); REFERENCE_MODULE(atom_browser_content_tracing); REFERENCE_MODULE(atom_browser_dialog); +REFERENCE_MODULE(atom_browser_desktop_capturer); REFERENCE_MODULE(atom_browser_download_item); REFERENCE_MODULE(atom_browser_menu); REFERENCE_MODULE(atom_browser_power_monitor); diff --git a/chromium_src/chrome/browser/media/desktop_media_list.h b/chromium_src/chrome/browser/media/desktop_media_list.h new file mode 100644 index 0000000000..fa0dbd8157 --- /dev/null +++ b/chromium_src/chrome/browser/media/desktop_media_list.h @@ -0,0 +1,60 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ +#define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ + +#include "base/basictypes.h" +#include "base/time/time.h" +#include "content/public/browser/desktop_media_id.h" +#include "ui/gfx/image/image_skia.h" + +class DesktopMediaListObserver; + +// DesktopMediaList provides the list of desktop media source (screens, windows, +// tabs), and their thumbnails, to the desktop media picker dialog. It +// transparently updates the list in the background, and notifies the desktop +// media picker when something changes. +class DesktopMediaList { + public: + // Struct used to represent each entry in the list. + struct Source { + // Id of the source. + content::DesktopMediaID id; + + // Name of the source that should be shown to the user. + base::string16 name; + + // The thumbnail for the source. + gfx::ImageSkia thumbnail; + }; + + virtual ~DesktopMediaList() {} + + // Sets time interval between updates. By default list of sources and their + // thumbnail are updated once per second. If called after StartUpdating() then + // it will take effect only after the next update. + virtual void SetUpdatePeriod(base::TimeDelta period) = 0; + + // Sets size to which the thumbnails should be scaled. If called after + // StartUpdating() then some thumbnails may be still scaled to the old size + // until they are updated. + virtual void SetThumbnailSize(const gfx::Size& thumbnail_size) = 0; + + // Sets ID of the hosting desktop picker dialog. The window with this ID will + // be filtered out from the list of sources. + virtual void SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id) = 0; + + // Starts updating the model. The model is initially empty, so OnSourceAdded() + // notifications will be generated for each existing source as it is + // enumerated. After the initial enumeration the model will be refreshed based + // on the update period, and notifications generated only for changes in the + // model. + virtual void StartUpdating(DesktopMediaListObserver* observer) = 0; + + virtual int GetSourceCount() const = 0; + virtual const Source& GetSource(int index) const = 0; +}; + +#endif // CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ diff --git a/chromium_src/chrome/browser/media/desktop_media_list_observer.h b/chromium_src/chrome/browser/media/desktop_media_list_observer.h new file mode 100644 index 0000000000..1988f52b5b --- /dev/null +++ b/chromium_src/chrome/browser/media/desktop_media_list_observer.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_ +#define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_ + +// Interface implemented by the desktop media picker dialog to receive +// notifications about changes in DesktopMediaList. +class DesktopMediaListObserver { + public: + virtual void OnSourceAdded(int index) = 0; + virtual void OnSourceRemoved(int index) = 0; + virtual void OnSourceMoved(int old_index, int new_index) = 0; + virtual void OnSourceNameChanged(int index) = 0; + virtual void OnSourceThumbnailChanged(int index) = 0; + + protected: + virtual ~DesktopMediaListObserver() {} +}; + +#endif // CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_ diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc new file mode 100644 index 0000000000..1db22ab72d --- /dev/null +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -0,0 +1,366 @@ +// Copyright 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 "chrome/browser/media/native_desktop_media_list.h" + +#include +#include +#include + +#include "base/hash.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/sequenced_worker_pool.h" +#include "chrome/browser/media/desktop_media_list_observer.h" +#include "content/public/browser/browser_thread.h" +#include "media/base/video_util.h" +#include "third_party/libyuv/include/libyuv/scale_argb.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" +#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" +#include "third_party/webrtc/modules/desktop_capture/window_capturer.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/skia_util.h" + +using content::BrowserThread; +using content::DesktopMediaID; + +namespace { + +// Update the list every second. +const int kDefaultUpdatePeriod = 1000; + +// Returns a hash of a DesktopFrame content to detect when image for a desktop +// media source has changed. +uint32 GetFrameHash(webrtc::DesktopFrame* frame) { + int data_size = frame->stride() * frame->size().height(); + return base::SuperFastHash(reinterpret_cast(frame->data()), data_size); +} + +gfx::ImageSkia ScaleDesktopFrame(scoped_ptr frame, + gfx::Size size) { + gfx::Rect scaled_rect = media::ComputeLetterboxRegion( + gfx::Rect(0, 0, size.width(), size.height()), + gfx::Size(frame->size().width(), frame->size().height())); + + SkBitmap result; + result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true); + result.lockPixels(); + + uint8* pixels_data = reinterpret_cast(result.getPixels()); + libyuv::ARGBScale(frame->data(), frame->stride(), + frame->size().width(), frame->size().height(), + pixels_data, result.rowBytes(), + scaled_rect.width(), scaled_rect.height(), + libyuv::kFilterBilinear); + + // Set alpha channel values to 255 for all pixels. + // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and + // remove this code. Currently screen/window capturers (at least some + // implementations) only capture R, G and B channels and set Alpha to 0. + // crbug.com/264424 + for (int y = 0; y < result.height(); ++y) { + for (int x = 0; x < result.width(); ++x) { + pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] = + 0xff; + } + } + + result.unlockPixels(); + + return gfx::ImageSkia::CreateFrom1xBitmap(result); +} + +} // namespace + +NativeDesktopMediaList::SourceDescription::SourceDescription( + DesktopMediaID id, + const base::string16& name) + : id(id), + name(name) { +} + +class NativeDesktopMediaList::Worker + : public webrtc::DesktopCapturer::Callback { + public: + Worker(base::WeakPtr media_list, + scoped_ptr screen_capturer, + scoped_ptr window_capturer); + ~Worker() override; + + void Refresh(const gfx::Size& thumbnail_size, + content::DesktopMediaID::Id view_dialog_id); + + private: + typedef std::map ImageHashesMap; + + // webrtc::DesktopCapturer::Callback interface. + webrtc::SharedMemory* CreateSharedMemory(size_t size) override; + void OnCaptureCompleted(webrtc::DesktopFrame* frame) override; + + base::WeakPtr media_list_; + + scoped_ptr screen_capturer_; + scoped_ptr window_capturer_; + + scoped_ptr current_frame_; + + ImageHashesMap image_hashes_; + + DISALLOW_COPY_AND_ASSIGN(Worker); +}; + +NativeDesktopMediaList::Worker::Worker( + base::WeakPtr media_list, + scoped_ptr screen_capturer, + scoped_ptr window_capturer) + : media_list_(media_list), + screen_capturer_(screen_capturer.Pass()), + window_capturer_(window_capturer.Pass()) { + if (screen_capturer_) + screen_capturer_->Start(this); + if (window_capturer_) + window_capturer_->Start(this); +} + +NativeDesktopMediaList::Worker::~Worker() {} + +void NativeDesktopMediaList::Worker::Refresh( + const gfx::Size& thumbnail_size, + content::DesktopMediaID::Id view_dialog_id) { + std::vector sources; + + if (screen_capturer_) { + webrtc::ScreenCapturer::ScreenList screens; + if (screen_capturer_->GetScreenList(&screens)) { + bool mutiple_screens = screens.size() > 1; + base::string16 title; + for (size_t i = 0; i < screens.size(); ++i) { + if (mutiple_screens) { + title = base::UTF8ToUTF16("Screen " + base::IntToString(i+1)); + } else { + title = base::UTF8ToUTF16("Entire screen"); + } + sources.push_back(SourceDescription(DesktopMediaID( + DesktopMediaID::TYPE_SCREEN, screens[i].id), title)); + } + } + } + + if (window_capturer_) { + webrtc::WindowCapturer::WindowList windows; + if (window_capturer_->GetWindowList(&windows)) { + for (webrtc::WindowCapturer::WindowList::iterator it = windows.begin(); + it != windows.end(); ++it) { + // Skip the picker dialog window. + if (it->id != view_dialog_id) { + sources.push_back(SourceDescription( + DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id), + base::UTF8ToUTF16(it->title))); + } + } + } + } + // Update list of windows before updating thumbnails. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::OnSourcesList, + media_list_, sources)); + + ImageHashesMap new_image_hashes; + + // Get a thumbnail for each source. + for (size_t i = 0; i < sources.size(); ++i) { + SourceDescription& source = sources[i]; + switch (source.id.type) { + case DesktopMediaID::TYPE_SCREEN: + if (!screen_capturer_->SelectScreen(source.id.id)) + continue; + screen_capturer_->Capture(webrtc::DesktopRegion()); + break; + + case DesktopMediaID::TYPE_WINDOW: + if (!window_capturer_->SelectWindow(source.id.id)) + continue; + window_capturer_->Capture(webrtc::DesktopRegion()); + break; + + default: + NOTREACHED(); + } + + // Expect that DesktopCapturer to always captures frames synchronously. + // |current_frame_| may be NULL if capture failed (e.g. because window has + // been closed). + if (current_frame_) { + uint32 frame_hash = GetFrameHash(current_frame_.get()); + new_image_hashes[source.id] = frame_hash; + + // Scale the image only if it has changed. + ImageHashesMap::iterator it = image_hashes_.find(source.id); + if (it == image_hashes_.end() || it->second != frame_hash) { + gfx::ImageSkia thumbnail = + ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::OnSourceThumbnail, + media_list_, i, thumbnail)); + } + } + } + + image_hashes_.swap(new_image_hashes); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::OnRefreshFinished, media_list_)); +} + +webrtc::SharedMemory* NativeDesktopMediaList::Worker::CreateSharedMemory( + size_t size) { + return NULL; +} + +void NativeDesktopMediaList::Worker::OnCaptureCompleted( + webrtc::DesktopFrame* frame) { + current_frame_.reset(frame); +} + +NativeDesktopMediaList::NativeDesktopMediaList( + scoped_ptr screen_capturer, + scoped_ptr window_capturer) + : screen_capturer_(screen_capturer.Pass()), + window_capturer_(window_capturer.Pass()), + update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)), + thumbnail_size_(100, 100), + view_dialog_id_(-1), + observer_(NULL), + weak_factory_(this) { + base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool(); + capture_task_runner_ = worker_pool->GetSequencedTaskRunner( + worker_pool->GetSequenceToken()); +} + +NativeDesktopMediaList::~NativeDesktopMediaList() { + capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release()); +} + +void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) { + DCHECK(!observer_); + update_period_ = period; +} + +void NativeDesktopMediaList::SetThumbnailSize( + const gfx::Size& thumbnail_size) { + thumbnail_size_ = thumbnail_size; +} + +void NativeDesktopMediaList::SetViewDialogWindowId( + content::DesktopMediaID::Id dialog_id) { + view_dialog_id_ = dialog_id; +} + +void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) { + DCHECK(!observer_); + DCHECK(screen_capturer_ || window_capturer_); + + observer_ = observer; + + worker_.reset(new Worker(weak_factory_.GetWeakPtr(), + screen_capturer_.Pass(), window_capturer_.Pass())); + Refresh(); +} + +int NativeDesktopMediaList::GetSourceCount() const { + return sources_.size(); +} + +const DesktopMediaList::Source& NativeDesktopMediaList::GetSource( + int index) const { + return sources_[index]; +} + +void NativeDesktopMediaList::Refresh() { + capture_task_runner_->PostTask( + FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()), + thumbnail_size_, view_dialog_id_)); +} + +void NativeDesktopMediaList::OnSourcesList( + const std::vector& new_sources) { + typedef std::set SourceSet; + SourceSet new_source_set; + for (size_t i = 0; i < new_sources.size(); ++i) { + new_source_set.insert(new_sources[i].id); + } + // Iterate through the old sources to find the removed sources. + for (size_t i = 0; i < sources_.size(); ++i) { + if (new_source_set.find(sources_[i].id) == new_source_set.end()) { + sources_.erase(sources_.begin() + i); + observer_->OnSourceRemoved(i); + --i; + } + } + // Iterate through the new sources to find the added sources. + if (new_sources.size() > sources_.size()) { + SourceSet old_source_set; + for (size_t i = 0; i < sources_.size(); ++i) { + old_source_set.insert(sources_[i].id); + } + + for (size_t i = 0; i < new_sources.size(); ++i) { + if (old_source_set.find(new_sources[i].id) == old_source_set.end()) { + sources_.insert(sources_.begin() + i, Source()); + sources_[i].id = new_sources[i].id; + sources_[i].name = new_sources[i].name; + observer_->OnSourceAdded(i); + } + } + } + DCHECK_EQ(new_sources.size(), sources_.size()); + + // Find the moved/changed sources. + size_t pos = 0; + while (pos < sources_.size()) { + if (!(sources_[pos].id == new_sources[pos].id)) { + // Find the source that should be moved to |pos|, starting from |pos + 1| + // of |sources_|, because entries before |pos| should have been sorted. + size_t old_pos = pos + 1; + for (; old_pos < sources_.size(); ++old_pos) { + if (sources_[old_pos].id == new_sources[pos].id) + break; + } + DCHECK(sources_[old_pos].id == new_sources[pos].id); + + // Move the source from |old_pos| to |pos|. + Source temp = sources_[old_pos]; + sources_.erase(sources_.begin() + old_pos); + sources_.insert(sources_.begin() + pos, temp); + + observer_->OnSourceMoved(old_pos, pos); + } + + if (sources_[pos].name != new_sources[pos].name) { + sources_[pos].name = new_sources[pos].name; + observer_->OnSourceNameChanged(pos); + } + ++pos; + } +} + +void NativeDesktopMediaList::OnSourceThumbnail( + int index, + const gfx::ImageSkia& image) { + DCHECK_LT(index, static_cast(sources_.size())); + sources_[index].thumbnail = image; + observer_->OnSourceThumbnailChanged(index); +} + +void NativeDesktopMediaList::OnRefreshFinished() { + BrowserThread::PostDelayedTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::Refresh, + weak_factory_.GetWeakPtr()), + update_period_); +} diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.h b/chromium_src/chrome/browser/media/native_desktop_media_list.h new file mode 100644 index 0000000000..81bd321528 --- /dev/null +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.h @@ -0,0 +1,100 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ +#define CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" +#include "chrome/browser/media/desktop_media_list.h" +#include "content/public/browser/desktop_media_id.h" +#include "ui/gfx/image/image_skia.h" + +namespace webrtc { +class ScreenCapturer; +class WindowCapturer; +} + +// Implementation of DesktopMediaList that shows native screens and +// native windows. +class NativeDesktopMediaList : public DesktopMediaList { + public: + // Caller may pass NULL for either of the arguments in case when only some + // types of sources the model should be populated with (e.g. it will only + // contain windows, if |screen_capturer| is NULL). + NativeDesktopMediaList( + scoped_ptr screen_capturer, + scoped_ptr window_capturer); + ~NativeDesktopMediaList() override; + + // DesktopMediaList interface. + void SetUpdatePeriod(base::TimeDelta period) override; + void SetThumbnailSize(const gfx::Size& thumbnail_size) override; + void StartUpdating(DesktopMediaListObserver* observer) override; + int GetSourceCount() const override; + const Source& GetSource(int index) const override; + void SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id) override; + + private: + class Worker; + friend class Worker; + + // Struct used to represent sources list the model gets from the Worker. + struct SourceDescription { + SourceDescription(content::DesktopMediaID id, const base::string16& name); + + content::DesktopMediaID id; + base::string16 name; + }; + + // Order comparator for sources. Used to sort list of sources. + static bool CompareSources(const SourceDescription& a, + const SourceDescription& b); + + // Post a task for the |worker_| to update list of windows and get thumbnails. + void Refresh(); + + // Called by |worker_| to refresh the model. First it posts tasks for + // OnSourcesList() with the fresh list of sources, then follows with + // OnSourceThumbnail() for each changed thumbnail and then calls + // OnRefreshFinished() at the end. + void OnSourcesList(const std::vector& sources); + void OnSourceThumbnail(int index, const gfx::ImageSkia& thumbnail); + void OnRefreshFinished(); + + // Capturers specified in SetCapturers() and passed to the |worker_| later. + scoped_ptr screen_capturer_; + scoped_ptr window_capturer_; + + // Time interval between mode updates. + base::TimeDelta update_period_; + + // Size of thumbnails generated by the model. + gfx::Size thumbnail_size_; + + // ID of the hosting dialog. + content::DesktopMediaID::Id view_dialog_id_; + + // The observer passed to StartUpdating(). + DesktopMediaListObserver* observer_; + + // Task runner used for the |worker_|. + scoped_refptr capture_task_runner_; + + // An object that does all the work of getting list of sources on a background + // thread (see |capture_task_runner_|). Destroyed on |capture_task_runner_| + // after the model is destroyed. + scoped_ptr worker_; + + // Current list of sources. + std::vector sources_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaList); +}; + +#endif // CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ diff --git a/filenames.gypi b/filenames.gypi index cb6a2273ea..81d4d87d3a 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -71,6 +71,8 @@ 'atom/browser/api/atom_api_content_tracing.cc', 'atom/browser/api/atom_api_cookies.cc', 'atom/browser/api/atom_api_cookies.h', + 'atom/browser/api/atom_api_desktop_capturer.cc', + 'atom/browser/api/atom_api_desktop_capturer.h', 'atom/browser/api/atom_api_download_item.cc', 'atom/browser/api/atom_api_download_item.h', 'atom/browser/api/atom_api_dialog.cc', @@ -349,6 +351,10 @@ 'chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h', + 'chromium_src/chrome/browser/media/desktop_media_list.h', + 'chromium_src/chrome/browser/media/desktop_media_list_observer.h', + 'chromium_src/chrome/browser/media/native_desktop_media_list.cc', + 'chromium_src/chrome/browser/media/native_desktop_media_list.h', 'chromium_src/chrome/browser/printing/print_job.cc', 'chromium_src/chrome/browser/printing/print_job.h', 'chromium_src/chrome/browser/printing/print_job_manager.cc', From 48fbd4741645f2518fc8138d886bddded4ba363e Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sat, 3 Oct 2015 10:41:08 +0800 Subject: [PATCH 002/411] Make desktop capture API work on Windows. --- atom.gyp | 11 +++++++++++ atom/browser/api/atom_api_desktop_capturer.cc | 17 +++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/atom.gyp b/atom.gyp index ed885743c6..77ad2e22ae 100644 --- a/atom.gyp +++ b/atom.gyp @@ -275,8 +275,14 @@ '-lcomctl32.lib', '-lcomdlg32.lib', '-lwininet.lib', + '-lwinmm.lib', ], }, + 'defines': [ + # The usage of "webrtc/modules/desktop_capture/desktop_capture_options.h" + # is required to see this macro. + 'WEBRTC_WIN', + ], 'dependencies': [ # Node is built as static_library on Windows, so we also need to # include its dependencies here. @@ -290,6 +296,11 @@ ], }], # OS=="win" ['OS=="mac"', { + 'defines': [ + # The usage of "webrtc/modules/desktop_capture/desktop_capture_options.h" + # is required to see this macro. + 'WEBRTC_MAC', + ], 'dependencies': [ 'vendor/crashpad/client/client.gyp:crashpad_client', 'vendor/crashpad/handler/handler.gyp:crashpad_handler', diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index c3a5737157..96786d0989 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -47,10 +47,23 @@ const int kThumbnailHeight = 150; } // namespace DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) { + webrtc::DesktopCaptureOptions options = + webrtc::DesktopCaptureOptions::CreateDefault(); + +#if defined(OS_WIN) + // On windows, desktop effects (e.g. Aero) will be disabled when the Desktop + // capture API is active by default. + // We keep the desktop effects in most times. Howerver, the screen still + // fickers when the API is capturing the window due to limitation of current + // implemetation. This is a known and wontFix issue in webrtc (see: + // http://code.google.com/p/webrtc/issues/detail?id=3373) + options.set_disable_effects(false); +#endif + scoped_ptr screen_capturer( - show_screens ? webrtc::ScreenCapturer::Create() : nullptr); + show_screens ? webrtc::ScreenCapturer::Create(options) : nullptr); scoped_ptr window_capturer( - show_windows ? webrtc::WindowCapturer::Create() : nullptr); + show_windows ? webrtc::WindowCapturer::Create(options) : nullptr); media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight)); From 1e69ef79de55fd4d26161fbb808edef8574c778c Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sun, 4 Oct 2015 09:35:00 +0800 Subject: [PATCH 003/411] Refine: make desktop-capturer as a renderer module. --- atom/browser/api/atom_api_desktop_capturer.cc | 67 +++++----- atom/browser/api/atom_api_desktop_capturer.h | 17 +-- atom/browser/api/atom_api_screen.cc | 18 +-- atom/browser/api/atom_api_screen.h | 5 - atom/browser/api/lib/app.coffee | 14 +-- atom/browser/lib/desktop-capturer.coffee | 42 +++++++ atom/browser/lib/init.coffee | 3 + atom/renderer/api/lib/desktop-capturer.coffee | 22 ++++ .../media/native_desktop_media_list.cc | 2 +- docs/README.md | 1 + docs/api/desktop-capturer.md | 118 ++++++++++++++++++ filenames.gypi | 2 + 12 files changed, 237 insertions(+), 74 deletions(-) create mode 100644 atom/browser/lib/desktop-capturer.coffee create mode 100644 atom/renderer/api/lib/desktop-capturer.coffee create mode 100644 docs/api/desktop-capturer.md diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index 96786d0989..f3e05486e9 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -5,7 +5,6 @@ #include "atom/browser/api/atom_api_desktop_capturer.h" #include "atom/common/api/atom_api_native_image.h" -#include "atom/common/native_mate_converters/callback.h" #include "atom/common/node_includes.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/media/desktop_media_list.h" @@ -25,7 +24,8 @@ struct Converter { content::DesktopMediaID id = source.id; dict.Set("name", base::UTF16ToUTF8(source.name)); dict.Set("id", id.ToString()); - dict.Set("thumbnail", + dict.Set( + "thumbnail", atom::api::NativeImage::Create(isolate, gfx::Image(source.thumbnail))); return ConvertToV8(isolate, dict); } @@ -38,15 +38,29 @@ namespace atom { namespace api { namespace { -// The wrapDesktopCapturer funtion which is implemented in JavaScript -using WrapDesktopCapturerCallback = base::Callback)>; -WrapDesktopCapturerCallback g_wrap_desktop_capturer; - const int kThumbnailWidth = 150; const int kThumbnailHeight = 150; } // namespace -DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) { +DesktopCapturer::DesktopCapturer() { +} + +DesktopCapturer::~DesktopCapturer() { +} + +void DesktopCapturer::StartUpdating(const std::vector& sources) { + bool show_screens = false; + bool show_windows = false; + for (const auto& source_type : sources) { + if (source_type == "screen") + show_screens = true; + else if (source_type == "window") + show_windows = true; + } + + if (!show_windows && !show_screens) + return; + webrtc::DesktopCaptureOptions options = webrtc::DesktopCaptureOptions::CreateDefault(); @@ -70,54 +84,39 @@ DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) { media_list_->StartUpdating(this); } -DesktopCapturer::~DesktopCapturer() { -} - -const DesktopMediaList::Source& DesktopCapturer::GetSource(int index) { - return media_list_->GetSource(index); +void DesktopCapturer::StopUpdating() { + media_list_.reset(); } void DesktopCapturer::OnSourceAdded(int index) { - Emit("source-added", index); + Emit("source-added", media_list_->GetSource(index)); } void DesktopCapturer::OnSourceRemoved(int index) { - Emit("source-removed", index); + Emit("source-removed", media_list_->GetSource(index)); } void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { - Emit("source-moved", old_index, new_index); } void DesktopCapturer::OnSourceNameChanged(int index) { - Emit("source-name-changed", index); + Emit("source-name-changed", media_list_->GetSource(index)); } void DesktopCapturer::OnSourceThumbnailChanged(int index) { - Emit("source-thumbnail-changed", index); + Emit("source-thumbnail-changed", media_list_->GetSource(index)); } mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) - .SetMethod("getSource", &DesktopCapturer::GetSource); -} - -void SetWrapDesktopCapturer(const WrapDesktopCapturerCallback& callback) { - g_wrap_desktop_capturer = callback; -} - -void ClearWrapDesktopCapturer() { - g_wrap_desktop_capturer.Reset(); + .SetMethod("startUpdating", &DesktopCapturer::StartUpdating) + .SetMethod("stopUpdating", &DesktopCapturer::StopUpdating); } // static -mate::Handle DesktopCapturer::Create(v8::Isolate* isolate, - bool show_screens, bool show_windows) { - auto handle = mate::CreateHandle(isolate, - new DesktopCapturer(show_screens, show_windows)); - g_wrap_desktop_capturer.Run(handle.ToV8()); - return handle; +mate::Handle DesktopCapturer::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new DesktopCapturer); } } // namespace api @@ -130,9 +129,7 @@ void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); - dict.SetMethod("_setWrapDesktopCapturer", &atom::api::SetWrapDesktopCapturer); - dict.SetMethod("_clearWrapDesktopCapturer", - &atom::api::ClearWrapDesktopCapturer); + dict.Set("desktopCapturer", atom::api::DesktopCapturer::Create(isolate)); } } // namespace diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index aae0a8268b..f4be410ed6 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -2,8 +2,11 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ -#define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ +#ifndef ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ +#define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ + +#include +#include #include "base/memory/scoped_ptr.h" #include "atom/browser/api/event_emitter.h" @@ -18,13 +21,13 @@ namespace api { class DesktopCapturer: public mate::EventEmitter, public DesktopMediaListObserver { public: - static mate::Handle Create(v8::Isolate* isolate, - bool show_screens, bool show_windows); + static mate::Handle Create(v8::Isolate* isolate); - const DesktopMediaList::Source& GetSource(int index); + void StartUpdating(const std::vector& sources); + void StopUpdating(); protected: - DesktopCapturer(bool show_screens, bool show_windows); + DesktopCapturer(); ~DesktopCapturer(); // DesktopMediaListObserver overrides. @@ -48,4 +51,4 @@ class DesktopCapturer: public mate::EventEmitter, } // namespace atom -#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_ +#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index 761ef28ef9..b73bda9ced 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -56,21 +56,6 @@ Screen::~Screen() { screen_->RemoveObserver(this); } -mate::Handle Screen::GetDesktopCapturer( - const std::vector& sources) { - bool show_screens = false; - bool show_windows = false; - for (const auto& source_type : sources) { - if (source_type == "screen") { - show_screens = true; - } else if (source_type == "window") { - show_windows = true; - } - } - - return DesktopCapturer::Create(isolate(), show_screens, show_windows); -} - gfx::Point Screen::GetCursorScreenPoint() { return screen_->GetCursorScreenPoint(); } @@ -122,8 +107,7 @@ mate::ObjectTemplateBuilder Screen::GetObjectTemplateBuilder( .SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay) .SetMethod("getAllDisplays", &Screen::GetAllDisplays) .SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint) - .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching) - .SetMethod("getDesktopCapturer", &Screen::GetDesktopCapturer); + .SetMethod("getDisplayMatching", &Screen::GetDisplayMatching); } // static diff --git a/atom/browser/api/atom_api_screen.h b/atom/browser/api/atom_api_screen.h index 619f6c8b3f..f724130fa7 100644 --- a/atom/browser/api/atom_api_screen.h +++ b/atom/browser/api/atom_api_screen.h @@ -6,9 +6,7 @@ #define ATOM_BROWSER_API_ATOM_API_SCREEN_H_ #include -#include -#include "atom/browser/api/atom_api_desktop_capturer.h" #include "atom/browser/api/event_emitter.h" #include "native_mate/handle.h" #include "ui/gfx/display_observer.h" @@ -38,9 +36,6 @@ class Screen : public mate::EventEmitter, gfx::Display GetDisplayNearestPoint(const gfx::Point& point); gfx::Display GetDisplayMatching(const gfx::Rect& match_rect); - mate::Handle GetDesktopCapturer( - const std::vector& sources); - // gfx::DisplayObserver: void OnDisplayAdded(const gfx::Display& new_display) override; void OnDisplayRemoved(const gfx::Display& old_display) override; diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index 9ff568cf8d..18c80dc2b1 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -3,18 +3,17 @@ EventEmitter = require('events').EventEmitter bindings = process.atomBinding 'app' sessionBindings = process.atomBinding 'session' downloadItemBindings = process.atomBinding 'download_item' -desktopCapturerBindings = process.atomBinding 'desktop_capturer' app = bindings.app app.__proto__ = EventEmitter.prototype -wrapToEventListener = (item) -> - # item is an Event Emitter. - item.__proto__ = EventEmitter.prototype +wrapSession = (session) -> + # session is an Event Emitter. + session.__proto__ = EventEmitter.prototype wrapDownloadItem = (download_item) -> # download_item is an Event Emitter. - wrapToEventListener download_item + download_item.__proto__ = EventEmitter.prototype # Be compatible with old APIs. download_item.url = download_item.getUrl() download_item.filename = download_item.getFilename() @@ -59,14 +58,11 @@ app.resolveProxy = -> @defaultSession.resolveProxy.apply @defaultSession, argume app.on 'activate', (event, hasVisibleWindows) -> @emit 'activate-with-no-open-windows' if not hasVisibleWindows # Session wrapper. -sessionBindings._setWrapSession wrapToEventListener +sessionBindings._setWrapSession wrapSession process.once 'exit', sessionBindings._clearWrapSession downloadItemBindings._setWrapDownloadItem wrapDownloadItem process.once 'exit', downloadItemBindings._clearWrapDownloadItem -desktopCapturerBindings._setWrapDesktopCapturer wrapToEventListener -process.once 'exit', desktopCapturerBindings._clearWrapDesktopCapturer - # Only one App object pemitted. module.exports = app diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee new file mode 100644 index 0000000000..29bb1ae605 --- /dev/null +++ b/atom/browser/lib/desktop-capturer.coffee @@ -0,0 +1,42 @@ +ipc = require 'ipc' +BrowserWindow = require 'browser-window' +EventEmitter = require('events').EventEmitter + +# The browser module manages all desktop-capturer moduels in renderer process. +desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer +desktopCapturer.__proto__ = EventEmitter.prototype + +getWebContentsFromId = (id) -> + windows = BrowserWindow.getAllWindows() + return window.webContents for window in windows when window.webContents?.getId() == id + +# The set for tracking id of webContents. +webContentsIds = new Set + +stopDesktopCapture = (id) -> + webContentsIds.delete id + if webContentsIds.size is 0 + desktopCapturer.stopUpdating() + +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED', (event) -> + id = event.sender.getId() + # Remove the tracked webContents when it is destroyed. + getWebContentsFromId(id).on 'destroyed', ()-> + stopDesktopCapture id + event.returnValue = 'done' + +# Handle `desktopCapturer.startUpdating` API. +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) -> + id = event.sender.getId() + webContentsIds.add id + desktopCapturer.startUpdating args + +# Handle `desktopCapturer.stopUpdating` API. +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) -> + stopDesktopCapture event.sender.getId() + +for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"] + do (event_name) -> + desktopCapturer.on event_name, (event, source) -> + webContentsIds.forEach (id) -> + getWebContentsFromId(id).send event_name, { id: source.id, name: source.name, dataUrl: source.thumbnail.toDataUrl() } diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index 1299364d2f..6caaaf4bf0 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -92,6 +92,9 @@ app.setAppPath packagePath # Load the chrome extension support. require './chrome-extension' +# Load internal desktop-capturer module. +require './desktop-capturer' + # Set main startup script of the app. mainStartupScript = packageJson.main or 'index.js' diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee new file mode 100644 index 0000000000..bf2d27597a --- /dev/null +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -0,0 +1,22 @@ +ipc = require 'ipc' +remote = require 'remote' +NativeImage = require 'native-image' + +EventEmitter = require('events').EventEmitter +desktopCapturer = new EventEmitter + +# Tells main process the renderer is requiring 'desktop-capture' module. +ipc.sendSync 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED' + +desktopCapturer.startUpdating = (args) -> + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args + +desktopCapturer.stopUpdating = () -> + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING' + +for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"] + do (event_name) -> + ipc.on event_name, (source) -> + desktopCapturer.emit event_name, { id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.dataUrl } + +module.exports = desktopCapturer diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index 1db22ab72d..050c5e1d5a 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -297,8 +297,8 @@ void NativeDesktopMediaList::OnSourcesList( // Iterate through the old sources to find the removed sources. for (size_t i = 0; i < sources_.size(); ++i) { if (new_source_set.find(sources_[i].id) == new_source_set.end()) { - sources_.erase(sources_.begin() + i); observer_->OnSourceRemoved(i); + sources_.erase(sources_.begin() + i); --i; } } diff --git a/docs/README.md b/docs/README.md index b2f95fe4b5..cc25e9d0ff 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,6 +46,7 @@ ### Modules for the Renderer Process (Web Page): +* [desktop-capturer](api/desktop-capturer.md) * [ipc (renderer)](api/ipc-renderer.md) * [remote](api/remote.md) * [web-frame](api/web-frame.md) diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md new file mode 100644 index 0000000000..8862762dda --- /dev/null +++ b/docs/api/desktop-capturer.md @@ -0,0 +1,118 @@ +# desktop-capturer + +The `desktop-capturer` is a renderer module used to capture the content of +screen and individual app windows. + +```javascript +// In the renderer process. +var desktopCapturer = require('desktop-capturer'); + +desktopCapturer.on('source-added', function(source) { + console.log("source " + source.name + " is added."); + // source.thumbnail is not ready to use here and webkitGetUserMedia neither. +}); + +desktopCapturer.on('source-thumbnail-changed', function(source) { + if (source.name == "Electron") { + // stopUpdating since we have found the window that we want to capture. + desktopCapturer.stopUpdating(); + + // It's ready to use webkitGetUserMedia right now. + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: source.id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } + } + }, gotStream, getUserMediaError); + } +}); + +// Let's start updating after setting all event of `desktopCapturer` +desktopCapturer.startUpdating(); + +function gotStream(stream) { + document.querySelector('video').src = URL.createObjectURL(stream); +} + +function getUserMediaError(e) { + console.log('getUserMediaError'); +} +``` + +## Events + +### Event: 'source-added' + +* `source` Source + +Emits when there is a new source added, usually a new window is created, +a new screen is attached. + +**Note:** The thumbnail of the source is not ready for use when 'source-added' +event is emitted, and `navigator.webkitGetUserMedia` neither. + +### Event: 'source-removed' + +* `source` Source + +Emits when there is a source removed. + +### Event: 'source-name-changed' + +* `source` Source + +Emits when the name of source is changed. + +### Event: 'source-thumbnail-changed' + +* `source` Source + +Emits when the thumbnail of source is changed. `desktopCapturer` will refresh +all sources every second. + +## Methods + +The `desktopCapturer` module has the following methods: + +### `desktopCapturer.startUpdating(options)` + +* `options` Array - An array of String that enums the types of desktop sources. + * `screen` String - Screen + * `window` String - Individual window + +Starts updating desktopCapturer. The events of `desktopCapturer` will only be +emitted after `startUpdating` API is invoked. + +**Note:** At beginning, the desktopCapturer is initially empty, so the +`source-added` event will be emitted for each existing source as it is +enumrated. +On Windows, you will see the screen ficker when desktopCapturer starts updating. +This is normal because the desktop effects(e.g. Aero) will be disabled when +desktop capturer is working. The ficker will disappear once +`desktopCapturer.stopUpdating()` is invoked. + +### `desktopCapturer.stopUpdating()` + +Stops updating desktopCapturer. The events of `desktopCapturer` will not be +emitted after the API is invoked. + +**Note:** It is a good practice to call `stopUpdating` when you do not need +getting any infomation of sources, usually after passing a id of source to +`navigator.webkitGetUserMedia`. + +## Source + +`Source` is an object represents a captured screen or individual window. It has +following properties: + +* `id` String - The id of the capturing window or screen used in + `navigator.webkitGetUserMedia`. +* `name` String - The descriped name of the capturing screen or window. +* `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. diff --git a/filenames.gypi b/filenames.gypi index 81d4d87d3a..46e06bebd1 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -26,6 +26,7 @@ 'atom/browser/api/lib/tray.coffee', 'atom/browser/api/lib/web-contents.coffee', 'atom/browser/lib/chrome-extension.coffee', + 'atom/browser/lib/desktop-capturer.coffee', 'atom/browser/lib/guest-view-manager.coffee', 'atom/browser/lib/guest-window-manager.coffee', 'atom/browser/lib/init.coffee', @@ -45,6 +46,7 @@ 'atom/renderer/lib/web-view/web-view.coffee', 'atom/renderer/lib/web-view/web-view-attributes.coffee', 'atom/renderer/lib/web-view/web-view-constants.coffee', + 'atom/renderer/api/lib/desktop-capturer.coffee', 'atom/renderer/api/lib/ipc.coffee', 'atom/renderer/api/lib/remote.coffee', 'atom/renderer/api/lib/screen.coffee', From 36c0ad7fda2abf9476fd90c54580c2ed71edbffd Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Mon, 5 Oct 2015 11:32:12 +0800 Subject: [PATCH 004/411] Refine more about desktop capturer API. * Simplify the coffeescript code. * Add more options in desktopCapturer.startUpdating. --- atom.gyp | 10 --- atom/browser/api/atom_api_desktop_capturer.cc | 64 +++++++++++------- atom/browser/api/atom_api_desktop_capturer.h | 10 ++- atom/browser/lib/desktop-capturer.coffee | 26 ++++---- atom/renderer/api/lib/desktop-capturer.coffee | 11 ++-- .../media/desktop_media_list_observer.h | 1 + .../media/native_desktop_media_list.cc | 1 + docs/api/desktop-capturer.md | 65 +++++++++++-------- 8 files changed, 104 insertions(+), 84 deletions(-) diff --git a/atom.gyp b/atom.gyp index 77ad2e22ae..b2e55e9be3 100644 --- a/atom.gyp +++ b/atom.gyp @@ -278,11 +278,6 @@ '-lwinmm.lib', ], }, - 'defines': [ - # The usage of "webrtc/modules/desktop_capture/desktop_capture_options.h" - # is required to see this macro. - 'WEBRTC_WIN', - ], 'dependencies': [ # Node is built as static_library on Windows, so we also need to # include its dependencies here. @@ -296,11 +291,6 @@ ], }], # OS=="win" ['OS=="mac"', { - 'defines': [ - # The usage of "webrtc/modules/desktop_capture/desktop_capture_options.h" - # is required to see this macro. - 'WEBRTC_MAC', - ], 'dependencies': [ 'vendor/crashpad/client/client.gyp:crashpad_client', 'vendor/crashpad/handler/handler.gyp:crashpad_handler', diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index f3e05486e9..a6556f6a89 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -14,30 +14,13 @@ #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" #include "third_party/webrtc/modules/desktop_capture/window_capturer.h" -namespace mate { - -template<> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const DesktopMediaList::Source& source) { - mate::Dictionary dict(isolate, v8::Object::New(isolate)); - content::DesktopMediaID id = source.id; - dict.Set("name", base::UTF16ToUTF8(source.name)); - dict.Set("id", id.ToString()); - dict.Set( - "thumbnail", - atom::api::NativeImage::Create(isolate, gfx::Image(source.thumbnail))); - return ConvertToV8(isolate, dict); - } -}; - -} // namespace mate - namespace atom { namespace api { namespace { +// Refresh every second. +const int kUpdatePeriod = 1000; const int kThumbnailWidth = 150; const int kThumbnailHeight = 150; } // namespace @@ -48,7 +31,11 @@ DesktopCapturer::DesktopCapturer() { DesktopCapturer::~DesktopCapturer() { } -void DesktopCapturer::StartUpdating(const std::vector& sources) { +void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { + std::vector sources; + if (!args.Get("types", &sources)) + return; + bool show_screens = false; bool show_windows = false; for (const auto& source_type : sources) { @@ -80,7 +67,16 @@ void DesktopCapturer::StartUpdating(const std::vector& sources) { show_windows ? webrtc::WindowCapturer::Create(options) : nullptr); media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); - media_list_->SetThumbnailSize(gfx::Size(kThumbnailWidth, kThumbnailHeight)); + + int update_period = kUpdatePeriod; + int thumbnail_width = kThumbnailWidth, thumbnail_height = kThumbnailHeight; + args.Get("updatePeriod", &update_period); + args.Get("thumbnailWidth", &thumbnail_width); + args.Get("thumbnailHeight", &thumbnail_height); + + media_list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds( + update_period)); + media_list_->SetThumbnailSize(gfx::Size(thumbnail_width, thumbnail_height)); media_list_->StartUpdating(this); } @@ -89,22 +85,40 @@ void DesktopCapturer::StopUpdating() { } void DesktopCapturer::OnSourceAdded(int index) { - Emit("source-added", media_list_->GetSource(index)); + EmitDesktopCapturerEvent("source-added", index, false); } void DesktopCapturer::OnSourceRemoved(int index) { - Emit("source-removed", media_list_->GetSource(index)); + EmitDesktopCapturerEvent("source-removed", index, false); } +// Ignore this event. void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { } void DesktopCapturer::OnSourceNameChanged(int index) { - Emit("source-name-changed", media_list_->GetSource(index)); + EmitDesktopCapturerEvent("source-removed", index, false); } void DesktopCapturer::OnSourceThumbnailChanged(int index) { - Emit("source-thumbnail-changed", media_list_->GetSource(index)); + EmitDesktopCapturerEvent("source-thumbnail-changed", index, true); +} + +void DesktopCapturer::OnRefreshFinished() { + Emit("refresh-finished"); +} + +void DesktopCapturer::EmitDesktopCapturerEvent( + const std::string& event_name, int index, bool with_thumbnail) { + const DesktopMediaList::Source& source = media_list_->GetSource(index); + content::DesktopMediaID id = source.id; + if (!with_thumbnail) + Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name)); + else { + Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name), + atom::api::NativeImage::Create(isolate(), + gfx::Image(source.thumbnail))); + } } mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index f4be410ed6..f0620c8e9c 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -14,6 +14,10 @@ #include "chrome/browser/media/native_desktop_media_list.h" #include "native_mate/handle.h" +namespace mate { +class Dictionary; +} + namespace atom { namespace api { @@ -23,7 +27,8 @@ class DesktopCapturer: public mate::EventEmitter, public: static mate::Handle Create(v8::Isolate* isolate); - void StartUpdating(const std::vector& sources); + void StartUpdating(const mate::Dictionary& args); + void StopUpdating(); protected: @@ -36,8 +41,11 @@ class DesktopCapturer: public mate::EventEmitter, void OnSourceMoved(int old_index, int new_index) override; void OnSourceNameChanged(int index) override; void OnSourceThumbnailChanged(int index) override; + void OnRefreshFinished() override; private: + void EmitDesktopCapturerEvent( + const std::string& event_name, int index, bool with_thumbnail); // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 29bb1ae605..0177d9721b 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -1,10 +1,8 @@ ipc = require 'ipc' BrowserWindow = require 'browser-window' -EventEmitter = require('events').EventEmitter # The browser module manages all desktop-capturer moduels in renderer process. desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer -desktopCapturer.__proto__ = EventEmitter.prototype getWebContentsFromId = (id) -> windows = BrowserWindow.getAllWindows() @@ -15,28 +13,26 @@ webContentsIds = new Set stopDesktopCapture = (id) -> webContentsIds.delete id + # Stop updating if no renderer process listens the desktop capturer. if webContentsIds.size is 0 desktopCapturer.stopUpdating() -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED', (event) -> - id = event.sender.getId() - # Remove the tracked webContents when it is destroyed. - getWebContentsFromId(id).on 'destroyed', ()-> - stopDesktopCapture id - event.returnValue = 'done' - # Handle `desktopCapturer.startUpdating` API. ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) -> id = event.sender.getId() + if not webContentsIds.has id + # Stop sending desktop capturer events to the destroyed webContents. + event.sender.on 'destroyed', ()-> + stopDesktopCapture id + # Start updating the desktopCapturer if it doesn't. + if webContentsIds.size is 0 + desktopCapturer.startUpdating args webContentsIds.add id - desktopCapturer.startUpdating args # Handle `desktopCapturer.stopUpdating` API. ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) -> stopDesktopCapture event.sender.getId() -for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"] - do (event_name) -> - desktopCapturer.on event_name, (event, source) -> - webContentsIds.forEach (id) -> - getWebContentsFromId(id).send event_name, { id: source.id, name: source.name, dataUrl: source.thumbnail.toDataUrl() } +desktopCapturer.emit = (event_name, event, desktopId, name, thumbnail) -> + webContentsIds.forEach (id) -> + getWebContentsFromId(id).send 'ATOM_RENDERER_DESKTOP_CAPTURER', event_name, desktopId, name, thumbnail?.toDataUrl() diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index bf2d27597a..ae5e054c2a 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -5,18 +5,15 @@ NativeImage = require 'native-image' EventEmitter = require('events').EventEmitter desktopCapturer = new EventEmitter -# Tells main process the renderer is requiring 'desktop-capture' module. -ipc.sendSync 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED' - desktopCapturer.startUpdating = (args) -> ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args desktopCapturer.stopUpdating = () -> ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING' -for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"] - do (event_name) -> - ipc.on event_name, (source) -> - desktopCapturer.emit event_name, { id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.dataUrl } +ipc.on 'ATOM_RENDERER_DESKTOP_CAPTURER', (event_name, id, name, thumbnail) -> + if not thumbnail + return desktopCapturer.emit event_name, id, name + desktopCapturer.emit event_name, id, name, NativeImage.createFromDataUrl thumbnail module.exports = desktopCapturer diff --git a/chromium_src/chrome/browser/media/desktop_media_list_observer.h b/chromium_src/chrome/browser/media/desktop_media_list_observer.h index 1988f52b5b..eccaa407d2 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list_observer.h +++ b/chromium_src/chrome/browser/media/desktop_media_list_observer.h @@ -14,6 +14,7 @@ class DesktopMediaListObserver { virtual void OnSourceMoved(int old_index, int new_index) = 0; virtual void OnSourceNameChanged(int index) = 0; virtual void OnSourceThumbnailChanged(int index) = 0; + virtual void OnRefreshFinished() = 0; protected: virtual ~DesktopMediaListObserver() {} diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index 050c5e1d5a..a326ded91b 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -358,6 +358,7 @@ void NativeDesktopMediaList::OnSourceThumbnail( } void NativeDesktopMediaList::OnRefreshFinished() { + observer_->OnRefreshFinished(); BrowserThread::PostDelayedTask( BrowserThread::UI, FROM_HERE, base::Bind(&NativeDesktopMediaList::Refresh, diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 8862762dda..6dc971cd20 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -7,13 +7,13 @@ screen and individual app windows. // In the renderer process. var desktopCapturer = require('desktop-capturer'); -desktopCapturer.on('source-added', function(source) { - console.log("source " + source.name + " is added."); - // source.thumbnail is not ready to use here and webkitGetUserMedia neither. +desktopCapturer.on('source-added', function(id, name) { + console.log("source " + name + " is added."); + // navigator.webkitGetUserMedia is not ready for use now. }); -desktopCapturer.on('source-thumbnail-changed', function(source) { - if (source.name == "Electron") { +desktopCapturer.on('source-thumbnail-changed', function(id, name, thumbnail) { + if (name == "Electron") { // stopUpdating since we have found the window that we want to capture. desktopCapturer.stopUpdating(); @@ -23,7 +23,7 @@ desktopCapturer.on('source-thumbnail-changed', function(source) { video: { mandatory: { chromeMediaSource: 'desktop', - chromeMediaSourceId: source.id, + chromeMediaSourceId: id, minWidth: 1280, maxWidth: 1280, minHeight: 720, @@ -50,42 +50,65 @@ function getUserMediaError(e) { ### Event: 'source-added' -* `source` Source +* `id` String - The id of the captured window or screen used in + `navigator.webkitGetUserMedia`. The format looks like 'window:XX' or + 'screen:XX' where XX is a random generated number. +* `name` String - The descriped name of the capturing screen or window. If the + source is a screen, the name will be 'Entire Screen' or 'Screen '; if + it is a window, the name will be the window's title. -Emits when there is a new source added, usually a new window is created, -a new screen is attached. +Emits when there is a new source added, usually a new window is created or a new +screen is attached. -**Note:** The thumbnail of the source is not ready for use when 'source-added' -event is emitted, and `navigator.webkitGetUserMedia` neither. +**Note:** `navigator.webkitGetUserMedia` is not ready for use in this event. ### Event: 'source-removed' -* `source` Source +* `id` String - The id of the captured window or screen used in + `navigator.webkitGetUserMedia`. +* `name` String - The descriped name of the capturing screen or window. Emits when there is a source removed. ### Event: 'source-name-changed' -* `source` Source +* `id` String - The id of the captured window or screen used in + `navigator.webkitGetUserMedia`. +* `name` String - The descriped name of the capturing screen or window. Emits when the name of source is changed. ### Event: 'source-thumbnail-changed' -* `source` Source +* `id` String - The id of the captured window or screen used in + `navigator.webkitGetUserMedia`. +* `name` String - The descriped name of the capturing screen or window. +* `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. Emits when the thumbnail of source is changed. `desktopCapturer` will refresh all sources every second. +### Event: 'refresh-finished' + +Emits when `desktopCapturer` finishes a refresh. + ## Methods The `desktopCapturer` module has the following methods: ### `desktopCapturer.startUpdating(options)` -* `options` Array - An array of String that enums the types of desktop sources. +`options` Object, properties: + +* `types` Array - An array of String that enums the types of desktop sources. * `screen` String - Screen * `window` String - Individual window +* `updatePeriod` Integer (optional) - The update period in milliseconds. By + default, `desktopCapturer` updates every second. +* `thumbnailWidth` Integer (optional) - The width of thumbnail. By default, it + is 150px. +* `thumbnailHeight` Integer (optional) - The height of thumbnail. By default, it + is 150px. Starts updating desktopCapturer. The events of `desktopCapturer` will only be emitted after `startUpdating` API is invoked. @@ -104,15 +127,5 @@ Stops updating desktopCapturer. The events of `desktopCapturer` will not be emitted after the API is invoked. **Note:** It is a good practice to call `stopUpdating` when you do not need -getting any infomation of sources, usually after passing a id of source to +getting any infomation of sources, usually after passing a id to `navigator.webkitGetUserMedia`. - -## Source - -`Source` is an object represents a captured screen or individual window. It has -following properties: - -* `id` String - The id of the capturing window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. -* `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. From dcb457e76ecc7ec530f9ececa5af2974ab5d17a5 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Tue, 6 Oct 2015 14:34:54 +0800 Subject: [PATCH 005/411] Refine API design: desktopCapturer.getSources. --- atom/browser/api/atom_api_desktop_capturer.cc | 69 ++++----- atom/browser/api/atom_api_desktop_capturer.h | 8 +- atom/browser/lib/desktop-capturer.coffee | 58 +++---- atom/renderer/api/lib/desktop-capturer.coffee | 21 +-- .../media/desktop_media_list_observer.h | 2 +- .../media/native_desktop_media_list.cc | 15 +- docs/api/desktop-capturer.md | 145 ++++++------------ 7 files changed, 124 insertions(+), 194 deletions(-) diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index a6556f6a89..116b0f4deb 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -6,6 +6,7 @@ #include "atom/common/api/atom_api_native_image.h" #include "atom/common/node_includes.h" +#include "atom/common/native_mate_converters/gfx_converter.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/media/desktop_media_list.h" #include "native_mate/dictionary.h" @@ -14,13 +15,30 @@ #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" #include "third_party/webrtc/modules/desktop_capture/window_capturer.h" +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const DesktopMediaList::Source& source) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + content::DesktopMediaID id = source.id; + dict.Set("name", base::UTF16ToUTF8(source.name)); + dict.Set("id", id.ToString()); + dict.Set( + "thumbnail", + atom::api::NativeImage::Create(isolate, gfx::Image(source.thumbnail))); + return ConvertToV8(isolate, dict); + } +}; + +} // namespace mate + namespace atom { namespace api { namespace { -// Refresh every second. -const int kUpdatePeriod = 1000; const int kThumbnailWidth = 150; const int kThumbnailHeight = 150; } // namespace @@ -31,7 +49,7 @@ DesktopCapturer::DesktopCapturer() { DesktopCapturer::~DesktopCapturer() { } -void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { +void DesktopCapturer::StartHandling(const mate::Dictionary& args) { std::vector sources; if (!args.Get("types", &sources)) return; @@ -68,64 +86,41 @@ void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); - int update_period = kUpdatePeriod; - int thumbnail_width = kThumbnailWidth, thumbnail_height = kThumbnailHeight; - args.Get("updatePeriod", &update_period); - args.Get("thumbnailWidth", &thumbnail_width); - args.Get("thumbnailHeight", &thumbnail_height); + gfx::Size thumbnail_size(kThumbnailWidth, kThumbnailHeight); + args.Get("thumbnailSize", &thumbnail_size); - media_list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds( - update_period)); - media_list_->SetThumbnailSize(gfx::Size(thumbnail_width, thumbnail_height)); + media_list_->SetThumbnailSize(thumbnail_size); media_list_->StartUpdating(this); } -void DesktopCapturer::StopUpdating() { - media_list_.reset(); -} - void DesktopCapturer::OnSourceAdded(int index) { - EmitDesktopCapturerEvent("source-added", index, false); } void DesktopCapturer::OnSourceRemoved(int index) { - EmitDesktopCapturerEvent("source-removed", index, false); } -// Ignore this event. void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { } void DesktopCapturer::OnSourceNameChanged(int index) { - EmitDesktopCapturerEvent("source-removed", index, false); } void DesktopCapturer::OnSourceThumbnailChanged(int index) { - EmitDesktopCapturerEvent("source-thumbnail-changed", index, true); } -void DesktopCapturer::OnRefreshFinished() { - Emit("refresh-finished"); -} - -void DesktopCapturer::EmitDesktopCapturerEvent( - const std::string& event_name, int index, bool with_thumbnail) { - const DesktopMediaList::Source& source = media_list_->GetSource(index); - content::DesktopMediaID id = source.id; - if (!with_thumbnail) - Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name)); - else { - Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name), - atom::api::NativeImage::Create(isolate(), - gfx::Image(source.thumbnail))); - } +bool DesktopCapturer::OnRefreshFinished() { + std::vector sources; + for (int i = 0; i < media_list_->GetSourceCount(); ++i) + sources.push_back(media_list_->GetSource(i)); + media_list_.reset(); + Emit("refresh-finished", sources); + return false; } mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) - .SetMethod("startUpdating", &DesktopCapturer::StartUpdating) - .SetMethod("stopUpdating", &DesktopCapturer::StopUpdating); + .SetMethod("startHandling", &DesktopCapturer::StartHandling); } // static diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index f0620c8e9c..32687abf73 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -27,9 +27,7 @@ class DesktopCapturer: public mate::EventEmitter, public: static mate::Handle Create(v8::Isolate* isolate); - void StartUpdating(const mate::Dictionary& args); - - void StopUpdating(); + void StartHandling(const mate::Dictionary& args); protected: DesktopCapturer(); @@ -41,11 +39,9 @@ class DesktopCapturer: public mate::EventEmitter, void OnSourceMoved(int old_index, int new_index) override; void OnSourceNameChanged(int index) override; void OnSourceThumbnailChanged(int index) override; - void OnRefreshFinished() override; + bool OnRefreshFinished() override; private: - void EmitDesktopCapturerEvent( - const std::string& event_name, int index, bool with_thumbnail); // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 0177d9721b..daef854cb7 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -1,38 +1,38 @@ ipc = require 'ipc' -BrowserWindow = require 'browser-window' # The browser module manages all desktop-capturer moduels in renderer process. desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer -getWebContentsFromId = (id) -> - windows = BrowserWindow.getAllWindows() - return window.webContents for window in windows when window.webContents?.getId() == id +isOptionsEqual = (opt1, opt2) -> + return JSON.stringify opt1 is JSON.stringify opt2 -# The set for tracking id of webContents. -webContentsIds = new Set +# A queue for holding all requests from renderer process. +requestsQueue = [] -stopDesktopCapture = (id) -> - webContentsIds.delete id - # Stop updating if no renderer process listens the desktop capturer. - if webContentsIds.size is 0 - desktopCapturer.stopUpdating() +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options) -> + request = { options: options, webContents: event.sender } + desktopCapturer.startHandling options if requestsQueue.length is 0 + requestsQueue.push request + # If the WebContents is destroyed before receiving result, just remove the + # reference from requestsQueue to make the module not send the result to it. + event.sender.once 'destroyed', () -> + request.webContents = null -# Handle `desktopCapturer.startUpdating` API. -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) -> - id = event.sender.getId() - if not webContentsIds.has id - # Stop sending desktop capturer events to the destroyed webContents. - event.sender.on 'destroyed', ()-> - stopDesktopCapture id - # Start updating the desktopCapturer if it doesn't. - if webContentsIds.size is 0 - desktopCapturer.startUpdating args - webContentsIds.add id +desktopCapturer.emit = (event_name, event, sources) -> + # Receiving sources result from main process, now send them back to renderer. + handledRequest = requestsQueue.shift 0 + result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) + handledRequest.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result -# Handle `desktopCapturer.stopUpdating` API. -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) -> - stopDesktopCapture event.sender.getId() - -desktopCapturer.emit = (event_name, event, desktopId, name, thumbnail) -> - webContentsIds.forEach (id) -> - getWebContentsFromId(id).send 'ATOM_RENDERER_DESKTOP_CAPTURER', event_name, desktopId, name, thumbnail?.toDataUrl() + # Check the queue to see whether there is other same request. If has, handle + # it for reducing redunplicated `desktopCaptuer.startHandling` calls. + unhandledRequestsQueue = [] + for request in requestsQueue + if isOptionsEqual handledRequest.options, request.options + request.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result + else + unhandledRequestsQueue.push request + requestsQueue = unhandledRequestsQueue + # If the requestsQueue is not empty, start a new request handling. + if requestsQueue.length > 0 + desktopCapturer.startHandling requestsQueue[0].options diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index ae5e054c2a..7ac5a4024c 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,19 +1,10 @@ ipc = require 'ipc' -remote = require 'remote' NativeImage = require 'native-image' -EventEmitter = require('events').EventEmitter -desktopCapturer = new EventEmitter +getSources = (options, callback) -> + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options + ipc.once 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', (sources) -> + callback ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) -desktopCapturer.startUpdating = (args) -> - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args - -desktopCapturer.stopUpdating = () -> - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING' - -ipc.on 'ATOM_RENDERER_DESKTOP_CAPTURER', (event_name, id, name, thumbnail) -> - if not thumbnail - return desktopCapturer.emit event_name, id, name - desktopCapturer.emit event_name, id, name, NativeImage.createFromDataUrl thumbnail - -module.exports = desktopCapturer +module.exports = + getSources: getSources diff --git a/chromium_src/chrome/browser/media/desktop_media_list_observer.h b/chromium_src/chrome/browser/media/desktop_media_list_observer.h index eccaa407d2..34cd626bf6 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list_observer.h +++ b/chromium_src/chrome/browser/media/desktop_media_list_observer.h @@ -14,7 +14,7 @@ class DesktopMediaListObserver { virtual void OnSourceMoved(int old_index, int new_index) = 0; virtual void OnSourceNameChanged(int index) = 0; virtual void OnSourceThumbnailChanged(int index) = 0; - virtual void OnRefreshFinished() = 0; + virtual bool OnRefreshFinished() = 0; protected: virtual ~DesktopMediaListObserver() {} diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index a326ded91b..4d15b068b7 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -358,10 +358,13 @@ void NativeDesktopMediaList::OnSourceThumbnail( } void NativeDesktopMediaList::OnRefreshFinished() { - observer_->OnRefreshFinished(); - BrowserThread::PostDelayedTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&NativeDesktopMediaList::Refresh, - weak_factory_.GetWeakPtr()), - update_period_); + // Give a chance to the observer to stop the refresh work. + bool is_continue = observer_->OnRefreshFinished(); + if (is_continue) { + BrowserThread::PostDelayedTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::Refresh, + weak_factory_.GetWeakPtr()), + update_period_); + } } diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 6dc971cd20..916538a4e9 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -7,36 +7,27 @@ screen and individual app windows. // In the renderer process. var desktopCapturer = require('desktop-capturer'); -desktopCapturer.on('source-added', function(id, name) { - console.log("source " + name + " is added."); - // navigator.webkitGetUserMedia is not ready for use now. -}); - -desktopCapturer.on('source-thumbnail-changed', function(id, name, thumbnail) { - if (name == "Electron") { - // stopUpdating since we have found the window that we want to capture. - desktopCapturer.stopUpdating(); - - // It's ready to use webkitGetUserMedia right now. - navigator.webkitGetUserMedia({ - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: id, - minWidth: 1280, - maxWidth: 1280, - minHeight: 720, - maxHeight: 720 +desktopCapturer.getSources({types: ['window', 'screen']}, function(sources) { + for (var i = 0; i < sources.length; ++i) { + if (sources[i].name == "Electron") { + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sources[i].id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } } - } - }, gotStream, getUserMediaError); + }, gotStream, getUserMediaError); + return; + } } }); -// Let's start updating after setting all event of `desktopCapturer` -desktopCapturer.startUpdating(); - function gotStream(stream) { document.querySelector('video').src = URL.createObjectURL(stream); } @@ -46,9 +37,35 @@ function getUserMediaError(e) { } ``` -## Events +## Methods -### Event: 'source-added' +The `desktopCapturer` module has the following methods: + +### `desktopCapturer.getSources(options, callback)` + +`options` Object, properties: +* `types` Array - An array of String that enums the types of desktop sources. + * `screen` String - Screen + * `window` String - Individual window +* `thumnailSize` Object (optional) - The suggested size that thumbnail should be + scaled. + * `width` Integer - The width of thumbnail. By default, it is 150px. + * `height` Integer - The height of thumbnail. By default, it is 150px. + +`callback` Function - `function(sources) {}` + +* `Sources` Array - An array of Source + +Gets all desktop sources. + +**Note:** There is no garuantee that the size of `source.thumbnail` is always +the same as the `thumnailSize` in `options`. It also depends on the scale of the +screen or window. + +## Source + +`Source` is an object represents a captured screen or individual window. It has +following properties: * `id` String - The id of the captured window or screen used in `navigator.webkitGetUserMedia`. The format looks like 'window:XX' or @@ -56,76 +73,4 @@ function getUserMediaError(e) { * `name` String - The descriped name of the capturing screen or window. If the source is a screen, the name will be 'Entire Screen' or 'Screen '; if it is a window, the name will be the window's title. - -Emits when there is a new source added, usually a new window is created or a new -screen is attached. - -**Note:** `navigator.webkitGetUserMedia` is not ready for use in this event. - -### Event: 'source-removed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. - -Emits when there is a source removed. - -### Event: 'source-name-changed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. - -Emits when the name of source is changed. - -### Event: 'source-thumbnail-changed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. * `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. - -Emits when the thumbnail of source is changed. `desktopCapturer` will refresh -all sources every second. - -### Event: 'refresh-finished' - -Emits when `desktopCapturer` finishes a refresh. - -## Methods - -The `desktopCapturer` module has the following methods: - -### `desktopCapturer.startUpdating(options)` - -`options` Object, properties: - -* `types` Array - An array of String that enums the types of desktop sources. - * `screen` String - Screen - * `window` String - Individual window -* `updatePeriod` Integer (optional) - The update period in milliseconds. By - default, `desktopCapturer` updates every second. -* `thumbnailWidth` Integer (optional) - The width of thumbnail. By default, it - is 150px. -* `thumbnailHeight` Integer (optional) - The height of thumbnail. By default, it - is 150px. - -Starts updating desktopCapturer. The events of `desktopCapturer` will only be -emitted after `startUpdating` API is invoked. - -**Note:** At beginning, the desktopCapturer is initially empty, so the -`source-added` event will be emitted for each existing source as it is -enumrated. -On Windows, you will see the screen ficker when desktopCapturer starts updating. -This is normal because the desktop effects(e.g. Aero) will be disabled when -desktop capturer is working. The ficker will disappear once -`desktopCapturer.stopUpdating()` is invoked. - -### `desktopCapturer.stopUpdating()` - -Stops updating desktopCapturer. The events of `desktopCapturer` will not be -emitted after the API is invoked. - -**Note:** It is a good practice to call `stopUpdating` when you do not need -getting any infomation of sources, usually after passing a id to -`navigator.webkitGetUserMedia`. From 214f8477b32e7d4067742ed5b49a933dfe4dcffb Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sat, 17 Oct 2015 19:28:14 +0800 Subject: [PATCH 006/411] Fix some typos. --- atom/browser/lib/desktop-capturer.coffee | 4 ++-- atom/renderer/api/lib/desktop-capturer.coffee | 2 +- docs/api/desktop-capturer.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index daef854cb7..fc3cdd5fea 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -22,14 +22,14 @@ desktopCapturer.emit = (event_name, event, sources) -> # Receiving sources result from main process, now send them back to renderer. handledRequest = requestsQueue.shift 0 result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result + handledRequest.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', result # Check the queue to see whether there is other same request. If has, handle # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue if isOptionsEqual handledRequest.options, request.options - request.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result + request.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', result else unhandledRequestsQueue.push request requestsQueue = unhandledRequestsQueue diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index 7ac5a4024c..a82eb69e65 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -3,7 +3,7 @@ NativeImage = require 'native-image' getSources = (options, callback) -> ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options - ipc.once 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', (sources) -> + ipc.once 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', (sources) -> callback ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) module.exports = diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 916538a4e9..8002451c46 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -47,8 +47,8 @@ The `desktopCapturer` module has the following methods: * `types` Array - An array of String that enums the types of desktop sources. * `screen` String - Screen * `window` String - Individual window -* `thumnailSize` Object (optional) - The suggested size that thumbnail should be - scaled. +* `thumbnailSize` Object (optional) - The suggested size that thumbnail should + be scaled. * `width` Integer - The width of thumbnail. By default, it is 150px. * `height` Integer - The height of thumbnail. By default, it is 150px. @@ -59,8 +59,8 @@ The `desktopCapturer` module has the following methods: Gets all desktop sources. **Note:** There is no garuantee that the size of `source.thumbnail` is always -the same as the `thumnailSize` in `options`. It also depends on the scale of the -screen or window. +the same as the `thumnbailSize` in `options`. It also depends on the scale of +the screen or window. ## Source From fb4efec55da118628263f21744a0ec8f6fce193a Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Mon, 19 Oct 2015 10:54:12 +0800 Subject: [PATCH 007/411] Add options check. This patch avoids main process never response back to renderer if the options is invalid. --- atom/browser/api/atom_api_desktop_capturer.cc | 37 ++++++++++++------- atom/browser/lib/desktop-capturer.coffee | 9 +++-- atom/renderer/api/lib/desktop-capturer.coffee | 5 ++- docs/api/desktop-capturer.md | 8 ++-- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index 116b0f4deb..21e98fe47c 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -41,6 +41,24 @@ namespace api { namespace { const int kThumbnailWidth = 150; const int kThumbnailHeight = 150; + +bool GetCapturerTypes(const mate::Dictionary& args, + bool* show_windows, + bool* show_screens) { + *show_windows = false; + *show_screens = false; + std::vector sources; + if (!args.Get("types", &sources)) + return false; + for (const auto& source_type : sources) { + if (source_type == "screen") + *show_screens = true; + else if (source_type == "window") + *show_windows = true; + } + return !show_windows && !show_screens ? false : true; +} + } // namespace DesktopCapturer::DesktopCapturer() { @@ -50,21 +68,14 @@ DesktopCapturer::~DesktopCapturer() { } void DesktopCapturer::StartHandling(const mate::Dictionary& args) { - std::vector sources; - if (!args.Get("types", &sources)) - return; - bool show_screens = false; bool show_windows = false; - for (const auto& source_type : sources) { - if (source_type == "screen") - show_screens = true; - else if (source_type == "window") - show_windows = true; - } - - if (!show_windows && !show_screens) + if (!GetCapturerTypes(args, &show_windows, &show_screens)) { + Emit("handling-finished", + "Invalid options.", + std::vector()); return; + } webrtc::DesktopCaptureOptions options = webrtc::DesktopCaptureOptions::CreateDefault(); @@ -113,7 +124,7 @@ bool DesktopCapturer::OnRefreshFinished() { for (int i = 0; i < media_list_->GetSourceCount(); ++i) sources.push_back(media_list_->GetSource(i)); media_list_.reset(); - Emit("refresh-finished", sources); + Emit("handling-finished", "", sources); return false; } diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index fc3cdd5fea..916eda7b68 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -11,25 +11,26 @@ requestsQueue = [] ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options) -> request = { options: options, webContents: event.sender } - desktopCapturer.startHandling options if requestsQueue.length is 0 requestsQueue.push request + desktopCapturer.startHandling options if requestsQueue.length is 1 # If the WebContents is destroyed before receiving result, just remove the # reference from requestsQueue to make the module not send the result to it. event.sender.once 'destroyed', () -> request.webContents = null -desktopCapturer.emit = (event_name, event, sources) -> +desktopCapturer.emit = (event_name, event, error_message, sources) -> # Receiving sources result from main process, now send them back to renderer. handledRequest = requestsQueue.shift 0 + error = if error_message then Error error_message else null result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', result + handledRequest.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', error_message, result # Check the queue to see whether there is other same request. If has, handle # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue if isOptionsEqual handledRequest.options, request.options - request.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', result + request.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', error_message, result else unhandledRequestsQueue.push request requestsQueue = unhandledRequestsQueue diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index a82eb69e65..5c8caf076a 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -3,8 +3,9 @@ NativeImage = require 'native-image' getSources = (options, callback) -> ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options - ipc.once 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', (sources) -> - callback ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) + ipc.once 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', (error_message, sources) -> + error = if error_message then Error error_message else null + callback error, ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) module.exports = getSources: getSources diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 8002451c46..2d805771ce 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -7,7 +7,8 @@ screen and individual app windows. // In the renderer process. var desktopCapturer = require('desktop-capturer'); -desktopCapturer.getSources({types: ['window', 'screen']}, function(sources) { +desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) { + if (error) throw error; for (var i = 0; i < sources.length; ++i) { if (sources[i].name == "Electron") { navigator.webkitGetUserMedia({ @@ -52,9 +53,10 @@ The `desktopCapturer` module has the following methods: * `width` Integer - The width of thumbnail. By default, it is 150px. * `height` Integer - The height of thumbnail. By default, it is 150px. -`callback` Function - `function(sources) {}` +`callback` Function - `function(error, sources) {}` -* `Sources` Array - An array of Source +* `error` Error +* `sources` Array - An array of Source Gets all desktop sources. From 9c861b9ad37a9c6335dde2e59d3005742fe75150 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Mon, 19 Oct 2015 18:07:35 +0800 Subject: [PATCH 008/411] Fix always passing the first result to renderer when the API is called multiple time at once. --- atom/browser/lib/desktop-capturer.coffee | 10 +++++----- atom/renderer/api/lib/desktop-capturer.coffee | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 916eda7b68..29d952595c 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -4,13 +4,13 @@ ipc = require 'ipc' desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer isOptionsEqual = (opt1, opt2) -> - return JSON.stringify opt1 is JSON.stringify opt2 + return JSON.stringify(opt1) is JSON.stringify(opt2) # A queue for holding all requests from renderer process. requestsQueue = [] -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options) -> - request = { options: options, webContents: event.sender } +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options, id) -> + request = { id: id, options: options, webContents: event.sender } requestsQueue.push request desktopCapturer.startHandling options if requestsQueue.length is 1 # If the WebContents is destroyed before receiving result, just remove the @@ -23,14 +23,14 @@ desktopCapturer.emit = (event_name, event, error_message, sources) -> handledRequest = requestsQueue.shift 0 error = if error_message then Error error_message else null result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', error_message, result + handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", error_message, result # Check the queue to see whether there is other same request. If has, handle # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue if isOptionsEqual handledRequest.options, request.options - request.webContents?.send 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', error_message, result + request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", error_message, result else unhandledRequestsQueue.push request requestsQueue = unhandledRequestsQueue diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index 5c8caf076a..95e67ff999 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,9 +1,13 @@ ipc = require 'ipc' NativeImage = require 'native-image' +nextId = 0 +getNextId = -> ++nextId + getSources = (options, callback) -> - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options - ipc.once 'ATOM_RENDERER_DESKTOP_CAPTURER_RESULT', (error_message, sources) -> + id = getNextId() + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, id + ipc.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (error_message, sources) -> error = if error_message then Error error_message else null callback error, ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) From ecdce2b21493dab19d9bf5602935dfae7aa0197a Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sat, 21 Nov 2015 07:51:52 +0900 Subject: [PATCH 009/411] Update as upstream --- docs-translations/ko-KR/api/app.md | 8 -- docs-translations/ko-KR/api/browser-window.md | 12 +- docs-translations/ko-KR/api/session.md | 129 ++++++++++++------ docs-translations/ko-KR/api/web-contents.md | 32 ++--- 4 files changed, 108 insertions(+), 73 deletions(-) diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index 6dbec14ba6..29f21547af 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -294,14 +294,6 @@ npm 모듈 규칙에 따라 대부분의 경우 `package.json`의 `name` 필드 현재 어플리케이션의 [로케일](https://ko.wikipedia.org/wiki/%EB%A1%9C%EC%BC%80%EC%9D%BC)을 반환합니다. -### `app.resolveProxy(url, callback)` - -* `url` URL -* `callback` Function - -`url`의 프록시 정보를 해석합니다. `callback`은 요청이 수행되었을 때 -`callback(proxy)` 형태로 호출됩니다. - ### `app.addRecentDocument(path)` _OS X_ _Windows_ * `path` String diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index ef1f270a01..85357d8ba6 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -68,9 +68,15 @@ win.show(); * `darkTheme` Boolean - 설정에 상관 없이 무조건 어두운 윈도우 테마를 사용합니다. 몇몇 GTK+3 데스크톱 환경에서만 작동합니다. * `transparent` Boolean - 윈도우 창을 [투명화](frameless-window.md)합니다. -* `type` String - 윈도우 창 종류를 지정합니다. - 사용할 수 있는 창 종류는 `desktop`, `dock`, `toolbar`, `splash`, - `notification`와 같습니다. 이 속성은 Linux에서만 작동합니다. +* `type` String - 특정 플랫폼에만 적용되는 윈도우 창의 종류를 지정합니다. 사용할 수 + 있는 창의 종류는 다음과 같습니다: + * Linux의 경우: `desktop`, `dock`, `toolbar`, `splash`, `notification` 종류를 + 사용할 수 있습니다. + * OS X의 경우: `desktop`, `textured` 종류를 사용할 수 있습니다. `textured` 종류는 + 창을 그라디언트 형태로 표현합니다 (`NSTexturedBackgroundWindowMask`) `desktop` + 종류는 데스크탑 배경 레벨에 윈도우를 배치합니다 (`kCGDesktopWindowLevel - 1`). + 참고로 이렇게 만들어진 윈도우는 포커스, 키보드, 마우스 이벤트를 받을 수 없습니다. + 하지만 편법으로 `globalShortcut`을 통해 키 입력을 받을 수 있습니다. * `standardWindow` Boolean - OS X의 표준 윈도우를 텍스쳐 윈도우 대신 사용합니다. 기본 값은 `true`입니다. * `titleBarStyle` String, OS X - 윈도우 타이틀 바 스타일을 지정합니다. 이 속성은 diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index aa5ab2ed3e..3fda5747f6 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -1,8 +1,9 @@ # session -`session` 객체는 [`BrowserWindow`](browser-window.md)의 -[`webContents`](web-contents.md)의 프로퍼티입니다. 다음과 같이 `BrowserWindow` -인스턴스에서 접근할 수 있습니다: +`session` 모듈은 새로운 `Session` 객체를 만드는데 사용할 수 있습니다. + +또한 존재하는 [`BrowserWindow`](browser-window.md)의 +[`webContents`](web-contents.md)에서 `session` 속성으로 접근할 수도 있습니다. ```javascript var BrowserWindow = require('browser-window'); @@ -10,12 +11,47 @@ var BrowserWindow = require('browser-window'); var win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL("http://github.com"); -var session = win.webContents.session +var ses = win.webContents.session ``` -## Events +## Methods -### Event: 'will-download' +`session` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### session.fromPartition(partition) + +* `partition` String + +`partition` 문자열로 부터 새로운 `Session` 인스턴스를 만들어 반환합니다. + +`partition`이 `persist:`로 시작하면 페이지는 지속성 세션을 사용하며 다른 모든 앱 내의 +페이지에서 같은 `partition`을 사용할 수 있습니다. 만약 `persist:` 접두어로 시작하지 +않으면 페이지는 인-메모리 세션을 사용합니다. `partition`을 지정하지 않으면 어플리케이션의 +기본 세션이 반환됩니다. + +## Properties + +`session` 모듈은 다음과 같은 속성을 가지고 있습니다: + +### session.defaultSession + +어플리케이션의 기본 세션 객체를 반환합니다. + +## Class: Session + +`session` 모듈을 사용하여 `Session` 객체를 생성할 수 있습니다: + +```javascript +const session = require('electron').session; + +var ses = session.fromPartition('persist:name'); + ``` + +### Instance Events + +`Session` 객체는 다음과 같은 이벤트를 가지고 있습니다: + +#### Event: 'will-download' * `event` Event * `item` [DownloadItem](download-item.md) @@ -34,36 +70,11 @@ session.on('will-download', function(event, item, webContents) { }); ``` -### Event: 'untrusted-certificate' +### Instance Methods -* `event` Event -* `hostname` String -* `certificate` Object - * `data` Buffer - PEM encoded data - * `issuerName` String -* `callback` Function +`Session` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: -`hostname`에 대한 `certificate`의 유효성 검증이 실패했을 때 발생하는 이벤트 입니다. -인증서를 신뢰한다면 `event.preventDefault()` 와 `callback(true)`를 호출하여 기본 -동작을 방지해야 합니다. - -```javascript -session.on('verify-certificate', function(event, hostname, certificate, callback) { - if (hostname == "github.com") { - // verification logic. - event.preventDefault(); - callback(true); - } else { - callback(false); - } -}); -``` - -## Methods - -`session` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: - -### `session.cookies` +#### `ses.cookies` `cookies` 속성은 쿠키를 조작하는 방법을 제공합니다. 예를 들어 다음과 같이 할 수 있습니다: @@ -100,9 +111,9 @@ win.webContents.on('did-finish-load', function() { }); ``` -### `session.cookies.get(details, callback)` +#### `ses.cookies.get(details, callback)` -`details` Object, properties: +`details` Object * `url` String - `url`에 관련된 쿠키를 가져옵니다. 이 속성을 비워두면 모든 url의 쿠키를 가져옵니다. @@ -128,9 +139,9 @@ win.webContents.on('did-finish-load', function() { * `expirationDate` Double - (Option) UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 시간. 세션 쿠키는 지원되지 않음. -### `session.cookies.set(details, callback)` +#### `ses.cookies.set(details, callback)` -`details` Object, properties: +`details` Object * `url` String - `url`에 관련된 쿠키를 가져옵니다. * `name` String - 쿠키의 이름입니다. 기본적으로 비워두면 생략됩니다. @@ -147,7 +158,7 @@ win.webContents.on('did-finish-load', function() { * `callback` Function - function(error) * `error` Error -### `session.cookies.remove(details, callback)` +#### `ses.cookies.remove(details, callback)` * `details` Object, proprties: * `url` String - 쿠키와 관련된 URL입니다. @@ -155,13 +166,13 @@ win.webContents.on('did-finish-load', function() { * `callback` Function - function(error) * `error` Error -### `session.clearCache(callback)` +#### `ses.clearCache(callback)` * `callback` Function - 작업이 완료되면 호출됩니다. 세션의 HTTP 캐시를 비웁니다. -### `session.clearStorageData([options, ]callback)` +#### `ses.clearStorageData([options, ]callback)` * `options` Object (optional), proprties: * `origin` String - `scheme://host:port`와 같은 `window.location.origin` 규칙을 @@ -175,7 +186,7 @@ win.webContents.on('did-finish-load', function() { 웹 스토리지의 데이터를 비웁니다. -### `session.setProxy(config, callback)` +#### `ses.setProxy(config, callback)` * `config` String * `callback` Function - 작업이 완료되면 호출됩니다. @@ -216,14 +227,22 @@ proxy-uri = ["://"][":"] 프록시를 다른 모든 URL에 사용합니다. ``` -### `session.setDownloadPath(path)` +### `app.resolveProxy(url, callback)` + +* `url` URL +* `callback` Function + +`url`의 프록시 정보를 해석합니다. `callback`은 요청이 수행되었을 때 +`callback(proxy)` 형태로 호출됩니다. + +#### `ses.setDownloadPath(path)` * `path` String - 다운로드 위치 다운로드 저장 위치를 지정합니다. 기본 다운로드 위치는 각 어플리케이션 데이터 디렉터리의 `Downloads` 폴더입니다. -### `session.enableNetworkEmulation(options)` +#### `ses.enableNetworkEmulation(options)` * `options` Object * `offline` Boolean - 네트워크의 오프라인 상태 여부 @@ -245,6 +264,26 @@ window.webContents.session.enableNetworkEmulation({ window.webContents.session.enableNetworkEmulation({offline: true}); ``` -### `session.disableNetworkEmulation` +#### `ses.disableNetworkEmulation()` 활성화된 `session`의 에뮬레이션을 비활성화합니다. 기본 네트워크 설정으로 돌아갑니다. + +#### `ses.setCertificateVerifyProc(proc)` + + * `proc` Function + +`session`에 인증서의 유효성을 확인하는 프로세스(proc)를 등록합니다. `proc`은 서버 +인증서 유효성 검증 요청이 들어왔을 때 언제나 `proc(hostname, certificate, callback)` +형식으로 호출됩니다. `callback(true)`을 호출하면 인증을 승인하고 `callback(false)`를 +호출하면 인증을 거부합니다. + +`setCertificateVerifyProc(null)`을 호출하면 기본 검증 프로세스로 되돌립니다. + +```javascript +myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, callback) { + if (hostname == 'github.com') + callback(true); + else + callback(false); +}); +``` diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index edec4d472b..d2255eb6d9 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -190,12 +190,6 @@ Returns: `webContents`객체는 다음과 같은 인스턴스 메서드들을 가지고 있습니다. -### `webContents.session` - -webContents에서 사용되는 `session`객체를 반환합니다. - -[session 문서](session.md)에서 이 객체의 메서드들을 확인할 수 있습니다. - ### `webContents.loadURL(url[, options])` * `url` URL @@ -645,17 +639,6 @@ Input `event`를 웹 페이지로 전송합니다. 프레임 프레젠테이션 이벤트들에 대한 구독을 중지합니다. -## Instance Properties - -`WebContents`객체들은 다음 속성들을 가지고 있습니다: - -### `webContents.devToolsWebContents` - -이 `WebContents`에 대한 개발자 도구의 `WebContents`를 가져옵니다. - -**참고:** 사용자가 절대로 이 객체를 저장해서는 안 됩니다. 개발자 도구가 닫혔을 때, -`null`이 반환될 수 있습니다. - ### `webContents.savePage(fullPath, saveType, callback)` * `fullPath` String - 전체 파일 경로. @@ -678,3 +661,18 @@ win.webContents.on('did-finish-load', function() { }); }); ``` + +## Instance Properties + +`WebContents`객체들은 다음 속성들을 가지고 있습니다: + +### `webContents.session` + +이 webContents에서 사용하는 [session](session.md) 객체를 반환합니다. + +### `webContents.devToolsWebContents` + +이 `WebContents`에 대한 개발자 도구의 `WebContents`를 가져옵니다. + +**참고:** 사용자가 절대로 이 객체를 저장해서는 안 됩니다. 개발자 도구가 닫혔을 때, +`null`이 반환될 수 있습니다. From 9341b9d6407447803731acf019976dc7b0859ea1 Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sat, 21 Nov 2015 02:26:59 +0200 Subject: [PATCH 010/411] Make links to docs, point to the correct version - Links to docs in the default app, pointed to the docs in the master branch. I changed them to point to the docs that match Electron's version. - Added Electron's version to the header of the default app, so it will be easier to figure out what version is currently running. --- atom/browser/default_app/index.html | 20 +++++++++++++++++--- atom/browser/default_app/main.js | 12 ++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/atom/browser/default_app/index.html b/atom/browser/default_app/index.html index e55cdf77b7..ec16a38bc4 100644 --- a/atom/browser/default_app/index.html +++ b/atom/browser/default_app/index.html @@ -76,7 +76,11 @@ }; -

Welcome to Electron

+

+ +

To run your app with Electron, execute the following command under your @@ -87,8 +91,18 @@

The path-to-your-app should be the path to your own Electron - app, you can read the quick start - guide in Electron's docs + app, you can read the + + guide in Electron's + on how to write one.

diff --git a/atom/browser/default_app/main.js b/atom/browser/default_app/main.js index 3916cfb288..49016c05ad 100644 --- a/atom/browser/default_app/main.js +++ b/atom/browser/default_app/main.js @@ -148,7 +148,11 @@ app.once('ready', function() { }, { label: 'Documentation', - click: function() { shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme') } + click: function() { + shell.openExternal( + `https://github.com/atom/electron/tree/v${process.versions.electron}/docs#readme` + ) + } }, { label: 'Community Discussions', @@ -249,7 +253,11 @@ if (option.file && !option.webdriver) { } catch(e) { if (e.code == 'MODULE_NOT_FOUND') { app.focus(); - dialog.showErrorBox('Error opening app', 'The app provided is not a valid electron app, please read the docs on how to write one:\nhttps://github.com/atom/electron/tree/master/docs\n\n' + e.toString()); + dialog.showErrorBox( + 'Error opening app', + 'The app provided is not a valid Electron app, please read the docs on how to write one:\n' + + `https://github.com/atom/electron/tree/v${process.versions.electron}/docs\n\n${e.toString()}` + ); process.exit(1); } else { console.error('App threw an error when running', e); From 4d4bb0a73e2fd01347dfe569d177e56572ed23da Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sat, 21 Nov 2015 02:32:53 +0200 Subject: [PATCH 011/411] Added ctags cache files to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b8a221c9e5..eb9aedb4e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +.tags* /.idea/ /build/ /dist/ From 5730d588d0debf86d25781d540c7e6c8aee5bb0d Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sat, 21 Nov 2015 03:31:52 +0200 Subject: [PATCH 012/411] Add a note to readme about docs versioning Added a note to readme about using the correct docs version. Hopefully this will reduce users confusion. --- docs/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/README.md b/docs/README.md index 208ff8bf47..a1c8526e6e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,10 @@ +Please make sure that you use the documents that match your Electron version. +The version number should be a part of the URL. If it's not, you are probably +using the documentation of a development branch which may contain API changes +that are not compatible with your Electron version. +When in doubt, run Electron without supplying an app path, and click on the +`docs` link. + ## Guides * [Supported Platforms](tutorial/supported-platforms.md) From df8cc85d2c207e0e5f33de59e32079f4e9f5d7f1 Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sat, 21 Nov 2015 04:22:16 +0200 Subject: [PATCH 013/411] Rephrased the note --- docs/README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index a1c8526e6e..9b6372524b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,11 @@ Please make sure that you use the documents that match your Electron version. -The version number should be a part of the URL. If it's not, you are probably -using the documentation of a development branch which may contain API changes -that are not compatible with your Electron version. -When in doubt, run Electron without supplying an app path, and click on the -`docs` link. +The version number should be a part of the page URL. If it's not, you are +probably using the documentation of a development branch which may contain API +changes that are not compatible with your Electron version. If that's the case, +you can switch to a different version of the documentation at the +[available versions](http://electron.atom.io/docs/) list on atom.io, or if +you're using the GitHub interface, open the "Switch branches/tags" dropdown and +select the tag that matches your version. ## Guides From 1cf3216f50fde3a33e0fdb8aa8ab6e5a4db61cb3 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sat, 21 Nov 2015 13:15:37 +0900 Subject: [PATCH 014/411] Add missed translation --- docs-translations/ko-KR/api/app.md | 31 ++++++++++++++++++- docs-translations/ko-KR/api/web-contents.md | 33 +++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index 29f21547af..d41ce1ab37 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -139,6 +139,35 @@ Returns: 새로운 [browserWindow](browser-window.md)가 생성되었을 때 발생하는 이벤트 입니다. +### Event: 'certificate-error' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` URL +* `error` String - 에러 코드 +* `certificate` Object + * `data` Buffer - PEM 인코딩된 데이터 + * `issuerName` String +* `callback` Function + +`url`에 대한 `certificate` 인증서의 유효성 검증에 실패했을 때 발생하는 이벤트입니다. +인증서를 신뢰한다면 `event.preventDefault()` 와 `callback(true)`를 호출하여 +기본 동작을 방지하고 인증을 승인할 수 있습니다. + +```javascript +session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { + if (url == "https://github.com") { + // Verification logic. + event.preventDefault(); + callback(true); + } else { + callback(false); + } +}); +``` + ### Event: 'select-client-certificate' Returns: @@ -151,7 +180,7 @@ Returns: * `issuerName` String - 발급자의 공통 이름 * `callback` Function -사용자 인증이 요청되었을 때 발생하는 이벤트 입니다. +클라이언트 인증이 요청되었을 때 발생하는 이벤트 입니다. `url`은 클라이언트 인증서를 요청하는 탐색 항목에 해당합니다. 그리고 `callback`은 목록에서 필터링된 항목과 함께 호출될 필요가 있습니다. diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index d2255eb6d9..ac98afe7c8 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -165,6 +165,39 @@ Returns: 개발자 도구에 포커스가 가거나 개발자 도구가 열렸을 때 발생되는 이벤트입니다. +### Event: 'certificate-error' + +Returns: + +* `event` Event +* `url` URL +* `error` String - 에러 코드 +* `certificate` Object + * `data` Buffer - PEM 인코딩된 데이터 + * `issuerName` String +* `callback` Function + +`url`에 대한 `certificate` 인증서의 유효성 검증에 실패했을 때 발생하는 이벤트입니다. + +사용법은 [`app`의 `certificate-error` 이벤트](app.md#event-certificate-error)와 +같습니다. + +### Event: 'select-client-certificate' + +Returns: + +* `event` Event +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - PEM 인코딩된 데이터 + * `issuerName` String - 인증서 발급자 이름 +* `callback` Function + +클라이언트 인증이 요청되었을 때 발생하는 이벤트 입니다. + +사용법은 [`app`의 `select-client-certificate` 이벤트](app.md#event-select-client-certificate)와 +같습니다. + ### Event: 'login' Returns: From 18de28c3ff4340e88bf01265604d7e78c84cef27 Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sat, 21 Nov 2015 06:22:19 +0200 Subject: [PATCH 015/411] Make BrowserWindow options argument optional Resolves #3473 --- atom/browser/api/atom_api_window.cc | 14 ++++++++++++-- atom/browser/api/atom_api_window.h | 3 +-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 7f5b78a797..a5aa4a1266 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -261,13 +261,23 @@ void Window::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) { #endif // static -mate::Wrappable* Window::New(v8::Isolate* isolate, - const mate::Dictionary& options) { +mate::Wrappable* Window::New(v8::Isolate* isolate, mate::Arguments* args) { if (!Browser::Get()->is_ready()) { isolate->ThrowException(v8::Exception::Error(mate::StringToV8( isolate, "Cannot create BrowserWindow before app is ready"))); return nullptr; } + + if (args->Length() > 1) { + args->ThrowError(); + return nullptr; + } + + mate::Dictionary options; + if (!(args->Length() == 1 && args->GetNext(&options))) { + options = mate::Dictionary::CreateEmpty(isolate); + } + return new Window(isolate, options); } diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 4161584206..757abd205b 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -38,8 +38,7 @@ class WebContents; class Window : public mate::TrackableObject, public NativeWindowObserver { public: - static mate::Wrappable* New(v8::Isolate* isolate, - const mate::Dictionary& options); + static mate::Wrappable* New(v8::Isolate* isolate, mate::Arguments* args); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); From ab693ca571fce1682f43092f34e7bea157d79e92 Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sat, 21 Nov 2015 06:42:40 +0200 Subject: [PATCH 016/411] Update docs --- docs/api/browser-window.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 52b1bb8d5c..f86f6e6856 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -25,17 +25,17 @@ You can also create a window without chrome by using It creates a new `BrowserWindow` with native properties as set by the `options`. -### `new BrowserWindow(options)` +### `new BrowserWindow([options])` -`options` Object, properties: +`options` Object (optional), properties: -* `width` Integer - Window's width. -* `height` Integer - Window's height. +* `width` Integer - Window's width. Default is 800 pixels. +* `height` Integer - Window's height. Default is 600 pixels. * `x` Integer - Window's left offset from screen. * `y` Integer - Window's top offset from screen. * `useContentSize` Boolean - The `width` and `height` would be used as web page's size, which means the actual window's size will include window - frame's size and be slightly larger. + frame's size and be slightly larger. Default is `false`. * `center` Boolean - Show window in the center of the screen. * `minWidth` Integer - Window's minimum width. * `minHeight` Integer - Window's minimum height. @@ -51,7 +51,8 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `title` String - Default window title. * `icon` [NativeImage](native-image.md) - The window icon, when omitted on Windows the executable's icon would be used as window icon. -* `show` Boolean - Whether window should be shown when created. +* `show` Boolean - Whether window should be shown when created. Default is +`true`. * `frame` Boolean - Specify `false` to create a [Frameless Window](frameless-window.md). * `acceptFirstMouse` Boolean - Whether the web view accepts a single From 4027d04662542b94ed5196a6bb190539c516171f Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sat, 21 Nov 2015 06:58:17 +0200 Subject: [PATCH 017/411] Add test --- spec/api-browser-window-spec.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index 00437ae412..8df4c9a947 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -322,3 +322,11 @@ describe 'browser-window module', -> done() w.loadURL "file://#{fixtures}/pages/save_page/index.html" + + describe 'BrowserWindow options argument is optional', -> + it 'should create a window with default size (800x600)', -> + w.destroy() + w = new BrowserWindow() + size = w.getSize() + assert.equal size[0], 800 + assert.equal size[1], 600 From 651254424d1427d21a7446e827d366f2820d2e92 Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sat, 21 Nov 2015 21:50:23 +0200 Subject: [PATCH 018/411] Expand the descriptions of options with defaults Resolves #3367 --- docs/api/browser-window.md | 93 ++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index f86f6e6856..15135d5eb4 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -29,46 +29,54 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. `options` Object (optional), properties: -* `width` Integer - Window's width. Default is 800 pixels. -* `height` Integer - Window's height. Default is 600 pixels. -* `x` Integer - Window's left offset from screen. -* `y` Integer - Window's top offset from screen. +* `width` Integer - Window's width in pixels. Default is `800`. +* `height` Integer - Window's height in pixels. Default is `600`. +* `x` Integer - Window's left offset from screen. Default is to center the +window. +* `y` Integer - Window's top offset from screen. Default is to center the +window. * `useContentSize` Boolean - The `width` and `height` would be used as web page's size, which means the actual window's size will include window frame's size and be slightly larger. Default is `false`. * `center` Boolean - Show window in the center of the screen. -* `minWidth` Integer - Window's minimum width. -* `minHeight` Integer - Window's minimum height. -* `maxWidth` Integer - Window's maximum width. -* `maxHeight` Integer - Window's maximum height. -* `resizable` Boolean - Whether window is resizable. +* `minWidth` Integer - Window's minimum width. Default is `0`. +* `minHeight` Integer - Window's minimum height. Default is `0`. +* `maxWidth` Integer - Window's maximum width. Default is no limit. +* `maxHeight` Integer - Window's maximum height. Default is no limit. +* `resizable` Boolean - Whether window is resizable. Default is `true`. * `alwaysOnTop` Boolean - Whether the window should always stay on top of - other windows. + other windows. Default is `false`. * `fullscreen` Boolean - Whether the window should show in fullscreen. When set to `false` the fullscreen button will be hidden or disabled on OS X. -* `skipTaskbar` Boolean - Whether to show the window in taskbar. -* `kiosk` Boolean - The kiosk mode. -* `title` String - Default window title. + Default is `false`. +* `skipTaskbar` Boolean - Whether to show the window in taskbar. Default is +`false`. +* `kiosk` Boolean - The kiosk mode. Default is `false`. +* `title` String - Default window title. Default is `"Electron"`. * `icon` [NativeImage](native-image.md) - The window icon, when omitted on Windows the executable's icon would be used as window icon. * `show` Boolean - Whether window should be shown when created. Default is `true`. * `frame` Boolean - Specify `false` to create a -[Frameless Window](frameless-window.md). +[Frameless Window](frameless-window.md). Default is `true`. * `acceptFirstMouse` Boolean - Whether the web view accepts a single - mouse-down event that simultaneously activates the window. -* `disableAutoHideCursor` Boolean - Whether to hide cursor when typing. + mouse-down event that simultaneously activates the window. Default is `false`. +* `disableAutoHideCursor` Boolean - Whether to hide cursor when typing. Default +is `false`. * `autoHideMenuBar` Boolean - Auto hide the menu bar unless the `Alt` - key is pressed. + key is pressed. Default is `false`. * `enableLargerThanScreen` Boolean - Enable the window to be resized larger - than screen. + than screen. Default is `false`. * `backgroundColor` String - Window's background color as Hexadecimal value, like `#66CD00` or `#FFF`. This is only implemented on Linux and Windows. + Default is `#000` (black). * `darkTheme` Boolean - Forces using dark theme for the window, only works on - some GTK+3 desktop environments. + some GTK+3 desktop environments. Default is `false`. * `transparent` Boolean - Makes the window [transparent](frameless-window.md). +Default is `false`. * `type` String - Specifies the type of the window, which applies - additional platform-specific properties. + additional platform-specific properties. By default it's undefined and you'll + get a regular app window. Supported values: * On Linux, possible types are `desktop`, `dock`, `toolbar`, `splash`, `notification`. * On OS X, possible types are `desktop`, `textured`. The `textured` type adds @@ -80,7 +88,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `titleBarStyle` String, OS X - specifies the style of window title bar. This option is supported on OS X 10.10 Yosemite and newer. There are three possible values: - * `default` or not specified results in the standard gray opaque Mac title + * `default` or not specified, results in the standard gray opaque Mac title bar. * `hidden` results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls ("traffic lights") in @@ -105,33 +113,38 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. `partition`, multiple pages can share the same session. If the `partition` is unset then default session of the app will be used. * `zoomFactor` Number - The default zoom factor of the page, `3.0` represents - `300%`. - * `javascript` Boolean + `300%`. Default is `1.0`. + * `javascript` Boolean - Enables JavaScript support. Default is `true`. * `webSecurity` Boolean - When setting `false`, it will disable the same-origin policy (Usually using testing websites by people), and set `allowDisplayingInsecureContent` and `allowRunningInsecureContent` to - `true` if these two options are not set by user. + `true` if these two options are not set by user. Default is `true`. * `allowDisplayingInsecureContent` Boolean - Allow an https page to display - content like images from http URLs. + content like images from http URLs. Default is `false`. * `allowRunningInsecureContent` Boolean - Allow a https page to run - JavaScript, CSS or plugins from http URLs. - * `images` Boolean - * `java` Boolean - * `textAreasAreResizable` Boolean - * `webgl` Boolean - * `webaudio` Boolean - * `plugins` Boolean - Whether plugins should be enabled. - * `experimentalFeatures` Boolean - * `experimentalCanvasFeatures` Boolean - * `overlayScrollbars` Boolean - * `overlayFullscreenVideo` Boolean - * `sharedWorker` Boolean - * `directWrite` Boolean - Whether the DirectWrite font rendering system on - Windows is enabled. + JavaScript, CSS or plugins from http URLs. Default is `false`. + * `images` Boolean - Enables image support. Default is `true`. + * `java` Boolean - Enables Java support. Default is `false`. + * `textAreasAreResizable` Boolean - Make TextArea elements resizable. Default + is `true`. + * `webgl` Boolean - Enables WebGL support. Default is `true`. + * `webaudio` Boolean - Enables WebAudio support. Default is `true`. + * `plugins` Boolean - Whether plugins should be enabled. Default is `false`. + * `experimentalFeatures` Boolean - Enables Chromium's experimental features. + Default is `false`. + * `experimentalCanvasFeatures` Boolean - Enables Chromium's experimental + canvas features. Default is `false`. + * `overlayScrollbars` Boolean - Enables overlay scrollbars. Default is + `false`. + * `overlayFullscreenVideo` Boolean - Enables overlay fullscreen video. Default + is `false` + * `sharedWorker` Boolean - Enables Shared Worker support. Default is `false`. + * `directWrite` Boolean - Enables DirectWrite font rendering system on + Windows. Default is `true`. * `pageVisibility` Boolean - Page would be forced to be always in visible or hidden state once set, instead of reflecting current window's visibility. Users can set it to `true` to prevent throttling of DOM - timers. + timers. Default is `false`. ## Events From 6db6842c14c3cd179c31581a42c51c7f71ae7f69 Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sun, 22 Nov 2015 01:13:57 +0200 Subject: [PATCH 019/411] Fix menu-item using deprecated API Some of the roles in menu-item use methods on BrowserWindow instead of WebContents which outputs a deprecation warning. I changed it to use the correct methods. --- atom/browser/api/lib/menu-item.coffee | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/lib/menu-item.coffee b/atom/browser/api/lib/menu-item.coffee index 92e2283b41..57beb6ffda 100644 --- a/atom/browser/api/lib/menu-item.coffee +++ b/atom/browser/api/lib/menu-item.coffee @@ -13,6 +13,11 @@ rolesMap = minimize: 'minimize' close: 'close' +# Maps methods that should be called directly on the BrowserWindow instance +methodInBrowserWindow = + minimize: true + close: true + class MenuItem @types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] @@ -42,8 +47,12 @@ class MenuItem # Manually flip the checked flags when clicked. @checked = !@checked if @type in ['checkbox', 'radio'] - if @role and rolesMap[@role] and process.platform isnt 'darwin' - focusedWindow?[rolesMap[@role]]() + if @role and rolesMap[@role] and process.platform isnt 'darwin' and focusedWindow? + methodName = rolesMap[@role] + if methodInBrowserWindow[methodName] + focusedWindow[methodName]() + else + focusedWindow.webContents?[methodName]() else if typeof click is 'function' click this, focusedWindow else if typeof @selector is 'string' From b406774055577b5c18bb069fc05aa3dc72757eea Mon Sep 17 00:00:00 2001 From: Dongjoon Hyun Date: Sun, 22 Nov 2015 19:50:52 +0900 Subject: [PATCH 020/411] Fix a typo in Korean quick-start tutotial --- docs-translations/ko-KR/tutorial/quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index 8946f44716..b69a31b792 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -70,7 +70,7 @@ your-app/ __알림__: 만약 `main` 필드가 `package.json`에 설정되어 있지 않으면 Electron은 자동으로 같은 디렉터리의 `index.js`를 로드합니다. -반드시 `main.js`에서 창을 만들고 시스템 이밴트를 처리해야합니다. 대표적인 예제로 +반드시 `main.js`에서 창을 만들고 시스템 이벤트를 처리해야합니다. 대표적인 예제로 다음과 같이 작성할 수 있습니다: ```javascript From 10a7ecee464b0955af2c8abdedd72e3f86af6fd0 Mon Sep 17 00:00:00 2001 From: Dongjoon Hyun Date: Sun, 22 Nov 2015 19:55:36 +0900 Subject: [PATCH 021/411] Add space, too --- docs-translations/ko-KR/tutorial/quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index b69a31b792..cfdc19f00e 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -70,7 +70,7 @@ your-app/ __알림__: 만약 `main` 필드가 `package.json`에 설정되어 있지 않으면 Electron은 자동으로 같은 디렉터리의 `index.js`를 로드합니다. -반드시 `main.js`에서 창을 만들고 시스템 이벤트를 처리해야합니다. 대표적인 예제로 +반드시 `main.js`에서 창을 만들고 시스템 이벤트를 처리해야 합니다. 대표적인 예제로 다음과 같이 작성할 수 있습니다: ```javascript From df570269e36899d52617f3f0cd57796ca61247bd Mon Sep 17 00:00:00 2001 From: Dongjoon Hyun Date: Sun, 22 Nov 2015 23:16:56 +0900 Subject: [PATCH 022/411] Fix typos in Korean api/ipc-main.md --- docs-translations/ko-KR/api/ipc-main.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs-translations/ko-KR/api/ipc-main.md b/docs-translations/ko-KR/api/ipc-main.md index 2dffcaf79a..6eb0d55275 100644 --- a/docs-translations/ko-KR/api/ipc-main.md +++ b/docs-translations/ko-KR/api/ipc-main.md @@ -19,12 +19,12 @@ ```javascript // 메인 프로세스 const ipcMain = require('electron').ipcMain; -ipc.on('asynchronous-message', function(event, arg) { +ipcMain.on('asynchronous-message', function(event, arg) { console.log(arg); // "ping" 출력 event.sender.send('asynchronous-reply', 'pong'); }); -ipc.on('synchronous-message', function(event, arg) { +ipcMain.on('synchronous-message', function(event, arg) { console.log(arg); // "ping" 출력 event.returnValue = 'pong'; }); @@ -35,17 +35,17 @@ ipc.on('synchronous-message', function(event, arg) { const ipcRenderer = require('electron').ipcRenderer; console.log(ipc.sendSync('synchronous-message', 'ping')); // "pong" 출력 -ipc.on('asynchronous-reply', function(arg) { +ipcRenderer.on('asynchronous-reply', function(arg) { console.log(arg); // "pong" 출력 }); -ipc.send('asynchronous-message', 'ping'); +ipcRenderer.send('asynchronous-message', 'ping'); ``` ## 메시지 리스닝 `ipcMain`은 다음과 같은 이벤트 리스닝 메서드를 가지고 있습니다: -### `ipc.on(channel, callback)` +### `ipcMain.on(channel, callback)` * `channel` String - 이벤트 이름 * `callback` Function @@ -56,11 +56,11 @@ ipc.send('asynchronous-message', 'ping'); `callback`에서 전달된 `event` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: -### `Event.returnValue` +### `event.returnValue` 이 메시지를 지정하면 동기 메시지를 전달합니다. -### `Event.sender` +### `event.sender` 메시지를 보낸 `webContents` 객체를 반환합니다. `event.sender.send` 메서드를 통해 비동기로 메시지를 전달할 수 있습니다. 자세한 내용은 From fbdef9e1121b66463da8251168b7864ca51c9bf8 Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Sun, 22 Nov 2015 09:08:35 -0800 Subject: [PATCH 023/411] Remove trailing colons from default menu roles `hideothers` and `unhide` had trailing colons which prevented them from being enabled / working in the default app. #3543 --- atom/browser/default_app/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/default_app/main.js b/atom/browser/default_app/main.js index 3916cfb288..01a2a0d876 100644 --- a/atom/browser/default_app/main.js +++ b/atom/browser/default_app/main.js @@ -189,11 +189,11 @@ app.once('ready', function() { { label: 'Hide Others', accelerator: 'Command+Shift+H', - role: 'hideothers:' + role: 'hideothers' }, { label: 'Show All', - role: 'unhide:' + role: 'unhide' }, { type: 'separator' From 167f11e797227283effc765ef064ac5249367250 Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 21 Nov 2015 02:20:51 +0530 Subject: [PATCH 024/411] protocol: handle http responses with no content --- atom/browser/net/url_request_fetch_job.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index a8a16e286b..24a7222660 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -181,6 +181,11 @@ void URLRequestFetchJob::Kill() { bool URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, int dest_size, int* bytes_read) { + if (GetResponseCode() == 204) { + *bytes_read = 0; + request()->set_received_response_content_length(prefilter_bytes_read()); + return true; + } pending_buffer_ = dest; pending_buffer_size_ = dest_size; SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); @@ -207,6 +212,14 @@ int URLRequestFetchJob::GetResponseCode() const { } void URLRequestFetchJob::OnURLFetchComplete(const net::URLFetcher* source) { + if (!response_info_) { + // Since we notify header completion only after first write there will be + // no response object constructed for http respones with no content 204. + // We notify header completion here. + HeadersCompleted(); + return; + } + pending_buffer_ = nullptr; pending_buffer_size_ = 0; NotifyDone(fetcher_->GetStatus()); From baab0486f05d447bb93aaaef663ff4ba3e13d1be Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sun, 22 Nov 2015 23:44:20 +0200 Subject: [PATCH 025/411] Add documentation for --proxy-bypass-list Depends on atom/brightray#179 --- docs/api/chrome-command-line-switches.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index c1adf3c425..ad7fafbe14 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -1,9 +1,9 @@ # Supported Chrome command line switches -This page lists the command line switches used by the Chrome browser that are also supported by -Electron. You can use [app.commandLine.appendSwitch][append-switch] to append -them in your app's main script before the [ready][ready] event of [app][app] -module is emitted: +This page lists the command line switches used by the Chrome browser that are +also supported by Electron. You can use +[app.commandLine.appendSwitch][append-switch] to append them in your app's main +script before the [ready][ready] event of [app][app] module is emitted: ```javascript const app = require('electron').app; @@ -47,6 +47,20 @@ only affects requests with HTTP protocol, including HTTPS and WebSocket requests. It is also noteworthy that not all proxy servers support HTTPS and WebSocket requests. +## --proxy-bypass-list=`hosts` + +Instructs Electron to bypass the proxy server for the given semi-colon-separated +list of hosts. This flag has an effect only if used in tandem with +`--proxy-server`. + +For example: + +`app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678')` + +Will use the proxy server for all hosts except for local addresses (localhost, +127.0.0.1 etc.), google.com subdomains, hosts that contain the suffix foo.com +and anything at 1.2.3.4:5678. + ## --proxy-pac-url=`url` Uses the PAC script at the specified `url`. From 6d46c3a75e984be04ee502f0c03018f9f8d6845c Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 23 Nov 2015 13:40:23 +0800 Subject: [PATCH 026/411] docs: Fix deprecated usage tips --- docs/api/menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/menu.md b/docs/api/menu.md index 1d81968216..b5f2fbe951 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -225,7 +225,7 @@ will be set as each window's top menu. Sends the `action` to the first responder of application. This is used for emulating default Cocoa menu behaviors, usually you would just use the -`selector` property of `MenuItem`. +`role` property of `MenuItem`. ### `Menu.buildFromTemplate(template)` From ba8b448c366b796869437bd65491026117eb18cb Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 23 Nov 2015 16:59:15 +0800 Subject: [PATCH 027/411] docs: Add indent for items in list Some markdown renderers require it to be able to render the list correctly. --- docs/api/browser-window.md | 25 ++++++++++++------------- docs/api/web-contents.md | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 15135d5eb4..a987e29246 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -32,9 +32,9 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `width` Integer - Window's width in pixels. Default is `800`. * `height` Integer - Window's height in pixels. Default is `600`. * `x` Integer - Window's left offset from screen. Default is to center the -window. + window. * `y` Integer - Window's top offset from screen. Default is to center the -window. + window. * `useContentSize` Boolean - The `width` and `height` would be used as web page's size, which means the actual window's size will include window frame's size and be slightly larger. Default is `false`. @@ -50,19 +50,19 @@ window. set to `false` the fullscreen button will be hidden or disabled on OS X. Default is `false`. * `skipTaskbar` Boolean - Whether to show the window in taskbar. Default is -`false`. + `false`. * `kiosk` Boolean - The kiosk mode. Default is `false`. * `title` String - Default window title. Default is `"Electron"`. * `icon` [NativeImage](native-image.md) - The window icon, when omitted on Windows the executable's icon would be used as window icon. * `show` Boolean - Whether window should be shown when created. Default is -`true`. + `true`. * `frame` Boolean - Specify `false` to create a -[Frameless Window](frameless-window.md). Default is `true`. + [Frameless Window](frameless-window.md). Default is `true`. * `acceptFirstMouse` Boolean - Whether the web view accepts a single mouse-down event that simultaneously activates the window. Default is `false`. * `disableAutoHideCursor` Boolean - Whether to hide cursor when typing. Default -is `false`. + is `false`. * `autoHideMenuBar` Boolean - Auto hide the menu bar unless the `Alt` key is pressed. Default is `false`. * `enableLargerThanScreen` Boolean - Enable the window to be resized larger @@ -73,7 +73,7 @@ is `false`. * `darkTheme` Boolean - Forces using dark theme for the window, only works on some GTK+3 desktop environments. Default is `false`. * `transparent` Boolean - Makes the window [transparent](frameless-window.md). -Default is `false`. + Default is `false`. * `type` String - Specifies the type of the window, which applies additional platform-specific properties. By default it's undefined and you'll get a regular app window. Supported values: @@ -102,7 +102,6 @@ Default is `false`. scripts run in the page. This script will always have access to node APIs no matter whether node integration is turned on or off. The value should be the absolute file path to the script. - When node integration is turned off, the preload script can reintroduce Node global symbols back to the global scope. See example [here](process.md#event-loaded). @@ -126,18 +125,18 @@ Default is `false`. * `images` Boolean - Enables image support. Default is `true`. * `java` Boolean - Enables Java support. Default is `false`. * `textAreasAreResizable` Boolean - Make TextArea elements resizable. Default - is `true`. + is `true`. * `webgl` Boolean - Enables WebGL support. Default is `true`. * `webaudio` Boolean - Enables WebAudio support. Default is `true`. * `plugins` Boolean - Whether plugins should be enabled. Default is `false`. * `experimentalFeatures` Boolean - Enables Chromium's experimental features. - Default is `false`. + Default is `false`. * `experimentalCanvasFeatures` Boolean - Enables Chromium's experimental - canvas features. Default is `false`. + canvas features. Default is `false`. * `overlayScrollbars` Boolean - Enables overlay scrollbars. Default is - `false`. + `false`. * `overlayFullscreenVideo` Boolean - Enables overlay fullscreen video. Default - is `false` + is `false` * `sharedWorker` Boolean - Enables Shared Worker support. Default is `false`. * `directWrite` Boolean - Enables DirectWrite font rendering system on Windows. Default is `true`. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index a716bdc593..2612d62122 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -349,7 +349,7 @@ this limitation. ### `webContents.setAudioMuted(muted)` -+ `muted` Boolean +* `muted` Boolean Mute the audio on the current web page. From 6636effb1defbb811e565eac6fe8b55c5ec898b2 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 24 Nov 2015 10:21:30 +0900 Subject: [PATCH 028/411] Update as upstream * Update as upstream * Add caution of translation version --- docs-translations/ko-KR/README.md | 15 ++- docs-translations/ko-KR/api/browser-window.md | 118 ++++++++++-------- docs-translations/ko-KR/api/menu.md | 2 +- docs-translations/ko-KR/api/web-contents.md | 2 +- 4 files changed, 81 insertions(+), 56 deletions(-) diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index d70894f8a9..e6ea629c1e 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -1,4 +1,17 @@ -## 개발 가이드 +반드시 사용하는 Electron 버전과 문서 버전을 일치시켜야 합니다. 버전 숫자는 문서 페이지 +URL에 포함되어 있습니다. 만약 그렇지 않다면, 아마 현재 보고 있는 문서는 개발 중인 +브랜치의 문서를 보고 있을 가능성이 있으며 해당 문서는 추후 API의 변경 가능성이 있고 +현재 사용하고 있는 Electron의 버전과 호환되지 않을 수 있습니다. 이 경우 atom.io의 +[사용할 수 있는 버전](http://electron.atom.io/docs/) 목록에서 다른 버전으로 변경할 +수 있습니다. 또한 GitHub 인터페이스의 "Switch branches/tags" 드롭다운 메뉴에서도 +사용 중인 Electron 버전으로 변경할 수 있습니다. + +**역주:** 한국어 번역 문서는 atom.io에 반영이 되어있지 않습니다. 따라서 번역 문서는 +GitHub 프로젝트내에서만 볼 수 있고 `master` 브랜치의 문서는 현재 개발중인 프로젝트의 +문서입니다. 한국어 번역 문서는 현재 `upstream` 원본 문서의 변경에 따라 최대한 문서의 +버전을 맞추려고 노력하고 있지만 가끔 누락된 번역이 존재할 수 있습니다. + +## 개발 가이드 * [지원하는 플랫폼](tutorial/supported-platforms.md) * [어플리케이션 배포](tutorial/application-distribution.md) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 85357d8ba6..73b14e7db9 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -27,49 +27,55 @@ win.show(); `BrowserWindow`는 `options`를 통해 네이티브 속성을 포함한 새로운 윈도우 창을 생성합니다. -### `new BrowserWindow(options)` +### `new BrowserWindow([options])` -`options` 객체에서 사용할 수 있는 속성들: +`options` 객체 (optional), 사용할 수 있는 속성들: -* `width` Integer - 윈도우 창의 가로 너비. -* `height` Integer - 윈도우 창의 세로 높이. -* `x` Integer - 화면을 기준으로 창 좌측을 오프셋 한 위치. -* `y` Integer - 화면을 기준으로 창 상단을 오프셋 한 위치. +* `width` Integer - 윈도우 창의 가로 너비. 기본값은 `800`입니다. +* `height` Integer - 윈도우 창의 세로 높이. 기본값은 `600`입니다. +* `x` Integer - 화면을 기준으로 창 좌측을 오프셋 한 위치. 기본값은 화면 중앙입니다. +* `y` Integer - 화면을 기준으로 창 상단을 오프셋 한 위치. 기본값은 화면 중앙입니다. * `useContentSize` Boolean - `width`와 `height`를 웹 페이지의 크기로 사용합니다. 이 속성을 사용하면 웹 페이지의 크기에 윈도우 프레임 크기가 추가되므로 실제 창은 조금 - 더 커질 수 있습니다. + 더 커질 수 있습니다. 기본값은 `false`입니다. * `center` Boolean - 윈도우 창을 화면 정 중앙에 위치시킵니다. -* `minWidth` Integer - 윈도우 창의 최소 가로 너비. -* `minHeight` Integer - 윈도우 창의 최소 세로 높이. -* `maxWidth` Integer - 윈도우 창의 최대 가로 너비. -* `maxHeight` Integer - 윈도우 창의 최대 세로 높이. -* `resizable` Boolean - 윈도우 창의 크기를 재조정 할 수 있는지 여부. +* `minWidth` Integer - 윈도우 창의 최소 가로 너비. 기본값은 `0`입니다. +* `minHeight` Integer - 윈도우 창의 최소 세로 높이. 기본값은 `0`입니다. +* `maxWidth` Integer - 윈도우 창의 최대 가로 너비. 기본값은 `제한없음`입니다. +* `maxHeight` Integer - 윈도우 창의 최대 세로 높이. 기본값은 `제한없음`입니다. +* `resizable` Boolean - 윈도우 창의 크기를 재조정 할 수 있는지 여부. 기본값은 `true` + 입니다. * `alwaysOnTop` Boolean - 윈도우 창이 언제나 다른 창들 위에 유지되는지 여부. -* `fullscreen` Boolean - 윈도우 창의 전체화면 활성화 여부. + 기본값은 `false`입니다. +* `fullscreen` Boolean - 윈도우 창의 전체화면 활성화 여부. 기본값은 `false` 입니다. `false`로 지정했을 경우 OS X에선 전체화면 버튼이 숨겨지거나 비활성화됩니다. -* `skipTaskbar` Boolean - 작업표시줄 어플리케이션 아이콘 표시 여부. -* `kiosk` Boolean - Kiosk(키오스크) 모드. -* `title` String - 기본 윈도우 창 제목. +* `skipTaskbar` Boolean - 작업표시줄 어플리케이션 아이콘 표시 스킵 여부. 기본값은 + `false`입니다. +* `kiosk` Boolean - Kiosk(키오스크) 모드. 기본값은 `false`입니다. +* `title` String - 기본 윈도우 창 제목. 기본값은 `"Electron"`입니다. * `icon` [NativeImage](native-image.md) - 윈도우 아이콘, 생략하면 실행 파일의 아이콘이 대신 사용됩니다. -* `show` Boolean - 윈도우가 생성되면 보여줄지 여부. +* `show` Boolean - 윈도우가 생성되면 보여줄지 여부. 기본값은 `true`입니다. * `frame` Boolean - `false`로 지정하면 창을 [Frameless Window](frameless-window.md) - 형태로 생성합니다. + 형태로 생성합니다. 기본값은 `true`입니다. * `acceptFirstMouse` Boolean - 윈도우가 비활성화 상태일 때 내부 컨텐츠 클릭 시 - 활성화 되는 동시에 단일 mouse-down 이벤트를 발생시킬지 여부. -* `disableAutoHideCursor` Boolean - 파이핑중 자동으로 커서를 숨길지 여부. + 활성화 되는 동시에 단일 mouse-down 이벤트를 발생시킬지 여부. 기본값은 `false`입니다. +* `disableAutoHideCursor` Boolean - 파이핑중 자동으로 커서를 숨길지 여부. 기본값은 + `false`입니다. * `autoHideMenuBar` Boolean - `Alt`를 누르지 않는 한 어플리케이션 메뉴바를 숨길지 - 여부. + 여부. 기본값은 `false`입니다. * `enableLargerThanScreen` Boolean - 윈도우 창 크기가 화면 크기보다 크게 재조정 될 - 수 있는지 여부. + 수 있는지 여부. 기본값은 `false`입니다. * `backgroundColor` String - 16진수로 표현된 윈도우의 배경 색. `#66CD00` 또는 - `#FFF`가 사용될 수 있습니다. - 이 속성은 Linux와 Windows에만 구현되어 있습니다. + `#FFF`가 사용될 수 있습니다. 이 속성은 Linux와 Windows에만 구현되어 있습니다. + 기본값은 `#000`(검정)입니다. * `darkTheme` Boolean - 설정에 상관 없이 무조건 어두운 윈도우 테마를 사용합니다. - 몇몇 GTK+3 데스크톱 환경에서만 작동합니다. -* `transparent` Boolean - 윈도우 창을 [투명화](frameless-window.md)합니다. -* `type` String - 특정 플랫폼에만 적용되는 윈도우 창의 종류를 지정합니다. 사용할 수 - 있는 창의 종류는 다음과 같습니다: + 몇몇 GTK+3 데스크톱 환경에서만 작동합니다. 기본값은 `false`입니다. +* `transparent` Boolean - 윈도우 창을 [투명화](frameless-window.md)합니다. 기본값은 + `false`입니다. +* `type` String - 특정 플랫폼에만 적용되는 윈도우 창의 종류를 지정합니다. 기본적으로 + 이 속성이 `undefined`일 경우 표준 윈도우가 사용됩니다. 사용할 수 있는 창의 종류는 + 다음과 같습니다: * Linux의 경우: `desktop`, `dock`, `toolbar`, `splash`, `notification` 종류를 사용할 수 있습니다. * OS X의 경우: `desktop`, `textured` 종류를 사용할 수 있습니다. `textured` 종류는 @@ -93,11 +99,9 @@ win.show(); * `preload` String - 스크립트를 지정하면 페이지 내의 다른 스크립트가 작동하기 전에 로드됩니다. 여기서 지정한 스크립트는 node 통합 활성화 여부에 상관없이 언제나 모든 node API에 접근할 수 있습니다. 이 속성의 스크립트 경로는 절대 경로로 지정해야 - 합니다. - - note 통합이 비활성화되어있을 경우, preload 스크립트는 Node의 global 심볼들을 - 다시 global 스코프로 다시 포함 시킬 수 있습니다. [여기](process.md#event-loaded) - 의 예제를 참고하세요. + 합니다. node 통합이 비활성화되어있을 경우, preload 스크립트는 node의 global + 심볼들을 다시 global 스코프로 다시 포함 시킬 수 있습니다. + [여기](process.md#event-loaded)의 예제를 참고하세요. * `partition` String - 페이지에서 사용할 세션을 지정합니다. 만약 `partition`이 `persist:`로 시작하면 페이지는 지속성 세션을 사용하며 다른 모든 앱 내의 페이지에서 같은 `partition`을 사용할 수 있습니다. 만약 `persist:` 접두어로 @@ -105,32 +109,40 @@ win.show(); `partition`을 지정하면 같은 세션을 공유할 수 있습니다. `partition`을 지정하지 않으면 어플리케이션의 기본 세션이 사용됩니다. * `zoomFactor` Number - 페이지의 기본 줌 값을 지정합니다. 예를 들어 `300%`를 - 표현하려면 `3.0`으로 지정합니다. - * `javascript` Boolean + 표현하려면 `3.0`으로 지정합니다. 기본값은 `1.0`입니다. + * `javascript` Boolean - 자바스크립트를 활성화합니다. 기본값은 `false`입니다. * `webSecurity` Boolean - `false`로 지정하면 same-origin 정책을 비활성화합니다. - (이 속성은 보통 사람에 의해 웹 사이트를 테스트할 때 사용합니다) 그리고 - `allowDisplayingInsecureContent`와 `allowRunningInsecureContent`이 - 사용자로부터 `true`로 지정되지 않은 경우 `true`로 지정합니다. + (이 속성은 보통 사람들에 의해 웹 사이트를 테스트할 때 사용합니다) 그리고 + `allowDisplayingInsecureContent`와 `allowRunningInsecureContent` 두 속성을 + 사용자가 `true`로 지정되지 않은 경우 `true`로 지정합니다. 기본값은 + `true`입니다. * `allowDisplayingInsecureContent` Boolean - https 페이지에서 http URL에서 - 로드한 이미지 같은 리소스를 표시할 수 있도록 허용합니다. + 로드한 이미지 같은 리소스를 표시할 수 있도록 허용합니다. 기본값은 `false`입니다. * `allowRunningInsecureContent` Boolean - https 페이지에서 http URL에서 로드한 - JavaScript와 CSS 또는 플러그인을 실행시킬 수 있도록 허용합니다. - * `images` Boolean - * `java` Boolean - * `textAreasAreResizable` Boolean - * `webgl` Boolean - * `webaudio` Boolean - * `plugins` Boolean - 어떤 플러그인이 활성화되어야 하는지 지정합니다. - * `experimentalFeatures` Boolean - * `experimentalCanvasFeatures` Boolean - * `overlayScrollbars` Boolean - * `overlayFullscreenVideo` Boolean - * `sharedWorker` Boolean + JavaScript와 CSS 또는 플러그인을 실행시킬 수 있도록 허용합니다. 기본값은 + `false`입니다. + * `images` Boolean - 이미지 지원을 활성화합니다. 기본값은 `true`입니다. + * `java` Boolean - Java 지원을 활성화합니다. 기본값은 `false`입니다. + * `textAreasAreResizable` Boolean - HTML TextArea 요소의 크기를 재조정을 + 허용합니다. 기본값은 `true`입니다. + * `webgl` Boolean - WebGL 지원을 활성화합니다. 기본값은 `true`입니다. + * `webaudio` Boolean - WebAudio 지원을 활성화합니다. 기본값은 `true`입니다. + * `plugins` Boolean - 플러그인 활성화 여부를 지정합니다. 기본값은 `false`입니다. + * `experimentalFeatures` Boolean - Chrome의 실험적인 기능을 활성화합니다. + 기본값은 `false`입니다. + * `experimentalCanvasFeatures` Boolean - Chrome의 실험적인 캔버스(canvas) 기능을 + 활성화합니다. 기본값은 `false`입니다. + * `overlayScrollbars` Boolean - 오버레이 스크롤바를 활성화합니다. 기본값은 + `false`입니다. + * `overlayFullscreenVideo` Boolean - 오버레이 전체화면 비디오 기능을 활성화합니다. + 기본값은 `false`입니다. + * `sharedWorker` Boolean - SharedWorker 기능을 활성화합니다. 기본값은 + `false`입니다. * `directWrite` Boolean - Windows에서 폰트 랜더링을 위해 DirectWrite를 - 사용하는지를 지정합니다. + 사용하는지를 지정합니다. 기본값은 `true`입니다. * `pageVisibility` Boolean - 현재 윈도우의 가시성을 반영하는 대신 페이지가 visible 또는 hidden 중 지정된 상태를 계속 유지하도록 합니다. 이 속성을 `true`로 - 지정하면 DOM 타이머의 스로틀링을 방지할 수 있습니다. + 지정하면 DOM 타이머의 스로틀링을 방지할 수 있습니다. 기본값은 `false`입니다. ## Events diff --git a/docs-translations/ko-KR/api/menu.md b/docs-translations/ko-KR/api/menu.md index 3c5740de53..f01021ae10 100644 --- a/docs-translations/ko-KR/api/menu.md +++ b/docs-translations/ko-KR/api/menu.md @@ -221,7 +221,7 @@ Linux에선 각 창의 상단에 표시됩니다. * `action` String `action`을 어플리케이션의 first responder에 전달합니다. 이 메서드는 Cocoa 메뉴 -동작을 에뮬레이트 하는데 사용되며 보통 `MenuItem`의 `selector` 속성에 사용됩니다. +동작을 에뮬레이트 하는데 사용되며 보통 `MenuItem`의 `role` 속성에 사용됩니다. **참고:** 이 메서드는 OS X에서만 사용할 수 있습니다. diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index ac98afe7c8..c92149ce44 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -346,7 +346,7 @@ CSS 코드를 현재 웹 페이지에 삽입합니다. ### `webContents.setAudioMuted(muted)` -+ `muted` Boolean +* `muted` Boolean 현재 웹 페이지의 소리를 음소거합니다. From 6e6cee3ac671156307371dac43f2722f79a38746 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 24 Nov 2015 10:23:52 +0900 Subject: [PATCH 029/411] Small fixes --- docs-translations/ko-KR/api/browser-window.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 73b14e7db9..6ff934afa0 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -33,8 +33,8 @@ win.show(); * `width` Integer - 윈도우 창의 가로 너비. 기본값은 `800`입니다. * `height` Integer - 윈도우 창의 세로 높이. 기본값은 `600`입니다. -* `x` Integer - 화면을 기준으로 창 좌측을 오프셋 한 위치. 기본값은 화면 중앙입니다. -* `y` Integer - 화면을 기준으로 창 상단을 오프셋 한 위치. 기본값은 화면 중앙입니다. +* `x` Integer - 화면을 기준으로 창 좌측을 오프셋 한 위치. 기본값은 `화면중앙`입니다. +* `y` Integer - 화면을 기준으로 창 상단을 오프셋 한 위치. 기본값은 `화면중앙`입니다. * `useContentSize` Boolean - `width`와 `height`를 웹 페이지의 크기로 사용합니다. 이 속성을 사용하면 웹 페이지의 크기에 윈도우 프레임 크기가 추가되므로 실제 창은 조금 더 커질 수 있습니다. 기본값은 `false`입니다. From a4a14a5f0ef0ff3c1423720005f461a42bf4cd25 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 24 Nov 2015 15:14:48 +0800 Subject: [PATCH 030/411] Update brightray for #3458 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index d4fab33427..77eca8fcbc 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit d4fab33427eb728a553896527f1931887ce6d9d9 +Subproject commit 77eca8fcbc18d2244b356045295f4472492cedb7 From 24e892dd17e51fbc984b828fcd9d2c91ee4cfeae Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 24 Nov 2015 18:39:16 +0800 Subject: [PATCH 031/411] Update brightray for #3550 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 77eca8fcbc..8893a27943 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 77eca8fcbc18d2244b356045295f4472492cedb7 +Subproject commit 8893a279435af82e3c2d4b4afe4d9d069f8a2820 From b9fd095b046856f07f58adbbf1dbee074b9bc0d7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 24 Nov 2015 18:40:50 +0800 Subject: [PATCH 032/411] docs: Mark code blocks --- docs/api/chrome-command-line-switches.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index ad7fafbe14..edeba0c9ad 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -55,11 +55,13 @@ list of hosts. This flag has an effect only if used in tandem with For example: -`app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678')` +```javascript +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678')` +``` -Will use the proxy server for all hosts except for local addresses (localhost, -127.0.0.1 etc.), google.com subdomains, hosts that contain the suffix foo.com -and anything at 1.2.3.4:5678. +Will use the proxy server for all hosts except for local addresses (`localhost`, +`127.0.0.1` etc.), `google.com` subdomains, hosts that contain the suffix +`foo.com` and anything at `1.2.3.4:5678`. ## --proxy-pac-url=`url` From 4acbd3e03f95d1a908d6d2a191d150f01def37a4 Mon Sep 17 00:00:00 2001 From: laiso Date: Tue, 24 Nov 2015 21:02:14 +0900 Subject: [PATCH 033/411] s/loadUrl/loadURL/g in docs-translations/ --- docs-translations/es/api/synopsis.md | 2 +- docs-translations/es/tutorial/application-packaging.md | 2 +- docs-translations/es/tutorial/online-offline-events.md | 4 ++-- docs-translations/es/tutorial/using-pepper-flash-plugin.md | 2 +- docs-translations/pt-BR/tutorial/application-packaging.md | 4 ++-- docs-translations/pt-BR/tutorial/online-offline-events.md | 4 ++-- .../pt-BR/tutorial/using-pepper-flash-plugin.md | 2 +- docs-translations/zh-CN/tutorial/online-offline-events.md | 6 +++--- docs-translations/zh-TW/api/synopsis.md | 2 +- docs-translations/zh-TW/tutorial/online-offline-events.md | 4 ++-- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs-translations/es/api/synopsis.md b/docs-translations/es/api/synopsis.md index eb4fcb39f6..534fafcf2f 100644 --- a/docs-translations/es/api/synopsis.md +++ b/docs-translations/es/api/synopsis.md @@ -25,7 +25,7 @@ var window = null; app.on('ready', function() { window = new BrowserWindow({width: 800, height: 600}); - window.loadUrl('https://github.com'); + window.loadURL('https://github.com'); }); ``` diff --git a/docs-translations/es/tutorial/application-packaging.md b/docs-translations/es/tutorial/application-packaging.md index 56698c1aae..c0cca7ecd9 100644 --- a/docs-translations/es/tutorial/application-packaging.md +++ b/docs-translations/es/tutorial/application-packaging.md @@ -70,7 +70,7 @@ También puedes mostrar una página web contenida en un `asar` utilizando `Brows ```javascript var BrowserWindow = require('browser-window'); var win = new BrowserWindow({width: 800, height: 600}); -win.loadUrl('file:///path/to/example.asar/static/index.html'); +win.loadURL('file:///path/to/example.asar/static/index.html'); ``` ### API Web diff --git a/docs-translations/es/tutorial/online-offline-events.md b/docs-translations/es/tutorial/online-offline-events.md index 0e43f9b161..450702e2f8 100644 --- a/docs-translations/es/tutorial/online-offline-events.md +++ b/docs-translations/es/tutorial/online-offline-events.md @@ -12,7 +12,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ``` @@ -50,7 +50,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ipc.on('online-status-changed', function(event, status) { diff --git a/docs-translations/es/tutorial/using-pepper-flash-plugin.md b/docs-translations/es/tutorial/using-pepper-flash-plugin.md index 53d2d024c1..4e45524fb6 100644 --- a/docs-translations/es/tutorial/using-pepper-flash-plugin.md +++ b/docs-translations/es/tutorial/using-pepper-flash-plugin.md @@ -46,7 +46,7 @@ app.on('ready', function() { 'plugins': true } }); - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Something else }); ``` diff --git a/docs-translations/pt-BR/tutorial/application-packaging.md b/docs-translations/pt-BR/tutorial/application-packaging.md index f55cbb2f7d..209188b2f5 100644 --- a/docs-translations/pt-BR/tutorial/application-packaging.md +++ b/docs-translations/pt-BR/tutorial/application-packaging.md @@ -71,7 +71,7 @@ Você também pode renderizar uma página web apartir de um arquivo `asar` utili ```javascript var BrowserWindow = require('browser-window'); var win = new BrowserWindow({width: 800, height: 600}); -win.loadUrl('file:///path/to/example.asar/static/index.html'); +win.loadURL('file:///path/to/example.asar/static/index.html'); ``` ### API Web @@ -155,4 +155,4 @@ Depois de executar o comando, além do `app.asar`, há também `app.asar.unpacked` pasta gerada que contém os arquivos descompactados, você deve copiá-lo juntamente com `app.asar` quando enviá-lo para os usuários. -Mais informações no repositório [asar](https://github.com/atom/asar) \ No newline at end of file +Mais informações no repositório [asar](https://github.com/atom/asar) diff --git a/docs-translations/pt-BR/tutorial/online-offline-events.md b/docs-translations/pt-BR/tutorial/online-offline-events.md index 294a62e7a8..8cdc1a6647 100644 --- a/docs-translations/pt-BR/tutorial/online-offline-events.md +++ b/docs-translations/pt-BR/tutorial/online-offline-events.md @@ -13,7 +13,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ``` @@ -53,7 +53,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ipc.on('online-status-changed', function(event, status) { diff --git a/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md b/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md index dfcca01a5c..e5222ba077 100644 --- a/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md +++ b/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md @@ -54,7 +54,7 @@ app.on('ready', function() { 'plugins': true } }); - mainWindow.loadUrl('file://' + __dirname + '/index.html'); + mainWindow.loadURL('file://' + __dirname + '/index.html'); // Algo mais }); ``` diff --git a/docs-translations/zh-CN/tutorial/online-offline-events.md b/docs-translations/zh-CN/tutorial/online-offline-events.md index de292490f0..764b81aa5d 100644 --- a/docs-translations/zh-CN/tutorial/online-offline-events.md +++ b/docs-translations/zh-CN/tutorial/online-offline-events.md @@ -9,7 +9,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ```` @@ -43,7 +43,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ipc.on('online-status-changed', function(event, status) { @@ -69,4 +69,4 @@ ipc.on('online-status-changed', function(event, status) { -```` \ No newline at end of file +```` diff --git a/docs-translations/zh-TW/api/synopsis.md b/docs-translations/zh-TW/api/synopsis.md index 484f5e3538..e28d1fd8cb 100644 --- a/docs-translations/zh-TW/api/synopsis.md +++ b/docs-translations/zh-TW/api/synopsis.md @@ -20,7 +20,7 @@ var window = null; app.on('ready', function() { window = new BrowserWindow({width: 800, height: 600}); - window.loadUrl('https://github.com'); + window.loadURL('https://github.com'); }); ``` diff --git a/docs-translations/zh-TW/tutorial/online-offline-events.md b/docs-translations/zh-TW/tutorial/online-offline-events.md index a4366f88a0..234a02710e 100644 --- a/docs-translations/zh-TW/tutorial/online-offline-events.md +++ b/docs-translations/zh-TW/tutorial/online-offline-events.md @@ -12,7 +12,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ``` @@ -50,7 +50,7 @@ var onlineStatusWindow; app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); - onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); + onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); ipc.on('online-status-changed', function(event, status) { From 0f5a3baff4f6d1bcfb1ff9efbb05eff9d5e14ebc Mon Sep 17 00:00:00 2001 From: Ryan Prichard Date: Tue, 24 Nov 2015 16:49:52 -0600 Subject: [PATCH 034/411] Fix https://github.com/atom/electron/issues/3565 by adding a typeof --- atom/browser/lib/rpc-server.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index ce63d91896..d9b1047bc8 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -107,7 +107,7 @@ unwrapArgs = (sender, args) -> # style function and the caller didn't pass a callback. callFunction = (event, func, caller, args) -> funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') - funcPassedCallback = args[args.length - 1] is 'function' + funcPassedCallback = typeof args[args.length - 1] is 'function' try if funcMarkedAsync and not funcPassedCallback From 34aecc9327755e4e904444128e7d26152371f434 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Wed, 25 Nov 2015 08:55:04 +0900 Subject: [PATCH 035/411] Update as upstream --- .../ko-KR/api/chrome-command-line-switches.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs-translations/ko-KR/api/chrome-command-line-switches.md b/docs-translations/ko-KR/api/chrome-command-line-switches.md index 3c825b1abb..443a8f2187 100644 --- a/docs-translations/ko-KR/api/chrome-command-line-switches.md +++ b/docs-translations/ko-KR/api/chrome-command-line-switches.md @@ -49,6 +49,21 @@ $ electron --js-flags="--harmony_proxies --harmony_collections" your-app 그리고 WebSocket 요청에만 적용됩니다. 그리고 모든 프록시 서버가 HTTPS가 WebSocket 요청을 지원하지 않고 있을 수 있으므로 사용시 주의해야 합니다. +## --proxy-bypass-list=`hosts` + +Electron이 세미콜론으로 구분된 호스트 리스트에서 지정한 프록시 서버를 건너뛰도록 +지시합니다. + +예시: + +```javascript +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678')` +``` + +위 예시는 로컬 주소(`localhost`, `127.0.0.1`, 등)와 `google.com`의 서브도메인, +`foo.com`을 접미사로 가지는 호스트, `1.2.3.4:5678` 호스트를 제외한 모든 연결에서 +프록시 서버를 사용합니다. + ## --proxy-pac-url=`url` 지정한 `url`의 PAC 스크립트를 사용합니다. From 789380dfad09ca59950bb8228f3edb3ac5dc039d Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 25 Nov 2015 15:54:30 -0800 Subject: [PATCH 036/411] Ensure calling webview.send will not block the renderer When the browser process is busy, calling webview.send (a method that appears on its face to be non-blocking) will actually block, because most webview methods are remoted to a guest view instance in the browser. Instead, define a few methods which will instead send its call over an async IPC message. --- atom/browser/lib/rpc-server.coffee | 8 ++++++++ atom/renderer/lib/web-view/web-view.coffee | 24 +++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index ce63d91896..8a67d2112e 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -221,3 +221,11 @@ ipcMain.on 'ATOM_BROWSER_GUEST_WEB_CONTENTS', (event, guestInstanceId) -> ipcMain.on 'ATOM_BROWSER_LIST_MODULES', (event) -> event.returnValue = (name for name of electron) + +ipcMain.on 'ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', (event, guestInstanceId, method, args...) -> + try + guestViewManager = require './guest-view-manager' + guest = guestViewManager.getGuest(guestInstanceId) + guest[method].apply(guest, args) + catch e + event.returnValue = exceptionToMeta e diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee index 5e3f7d6bae..720e6e84c5 100644 --- a/atom/renderer/lib/web-view/web-view.coffee +++ b/atom/renderer/lib/web-view/web-view.coffee @@ -1,4 +1,4 @@ -{deprecate, webFrame, remote} = require 'electron' +{deprecate, webFrame, remote, ipcRenderer} = require 'electron' v8Util = process.atomBinding 'v8_util' guestViewInternal = require './guest-view-internal' @@ -270,8 +270,6 @@ registerWebViewElement = -> 'isCrashed' 'setUserAgent' 'getUserAgent' - 'executeJavaScript' - 'insertCSS' 'openDevTools' 'closeDevTools' 'isDevToolsOpened' @@ -289,20 +287,32 @@ registerWebViewElement = -> 'unselect' 'replace' 'replaceMisspelling' - 'send' 'getId' 'inspectServiceWorker' 'print' 'printToPDF' - 'sendInputEvent' + ] + + nonblockMethods = [ + 'send', + 'sendInputEvent', + 'executeJavaScript', + 'insertCSS' ] # Forward proto.foo* method calls to WebViewImpl.foo*. - createHandler = (m) -> + createBlockHandler = (m) -> (args...) -> internal = v8Util.getHiddenValue this, 'internal' internal.webContents[m] args... - proto[m] = createHandler m for m in methods + proto[m] = createBlockHandler m for m in methods + + createNonBlockHandler = (m) -> + (args...) -> + internal = v8Util.getHiddenValue this, 'internal' + ipcRenderer.send('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m, args...) + + proto[m] = createNonBlockHandler m for m in nonblockMethods # Deprecated. deprecate.rename proto, 'getUrl', 'getURL' From 9c62be8fc998856fb6f5dbf032f85cacd3818659 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 11:06:56 +0800 Subject: [PATCH 037/411] Improve the deprecation notice for ipc module Close #3577. --- atom/browser/api/lib/ipc.coffee | 2 +- atom/renderer/api/lib/ipc.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/lib/ipc.coffee b/atom/browser/api/lib/ipc.coffee index 8019a385dd..018cb6bb04 100644 --- a/atom/browser/api/lib/ipc.coffee +++ b/atom/browser/api/lib/ipc.coffee @@ -1,6 +1,6 @@ {deprecate, ipcMain} = require 'electron' # This module is deprecated, we mirror everything from ipcMain. -deprecate.warn 'ipc module', 'ipcMain module' +deprecate.warn 'ipc module', 'require("electron").ipcMain' module.exports = ipcMain diff --git a/atom/renderer/api/lib/ipc.coffee b/atom/renderer/api/lib/ipc.coffee index b0e951d70e..edd7d29b6f 100644 --- a/atom/renderer/api/lib/ipc.coffee +++ b/atom/renderer/api/lib/ipc.coffee @@ -2,7 +2,7 @@ {EventEmitter} = require 'events' # This module is deprecated, we mirror everything from ipcRenderer. -deprecate.warn 'ipc module', 'ipcRenderer module' +deprecate.warn 'ipc module', 'require("electron").ipcRenderer' # Routes events of ipcRenderer. ipc = new EventEmitter From 6c1878d15b8c34f1441565ebacaaf34247276b30 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 13:55:59 +0800 Subject: [PATCH 038/411] mac: Clears the delegate when window is going to be closed Since EL Capitan it is possible that the methods of delegate would get called after the window has been closed. Refs atom/atom#9584. --- atom/browser/native_window_mac.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 7959eb04f5..42894c107d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -192,6 +192,11 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)windowWillClose:(NSNotification*)notification { shell_->NotifyWindowClosed(); + + // Clears the delegate when window is going to be closed, since EL Capitan it + // is possible that the methods of delegate would get called after the window + // has been closed. + [shell_->GetNativeWindow() setDelegate:nil]; } - (BOOL)windowShouldClose:(id)window { From b0d4aa211df8778d97047268c3e86e576adc7d39 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 13:57:48 +0800 Subject: [PATCH 039/411] Fix compatibility with activate-with-no-open-windows event --- atom/browser/api/lib/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index a2fdb847e1..4d1803a029 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -54,7 +54,7 @@ deprecate.event app, 'finish-launching', 'ready', -> setImmediate => # give default app a chance to setup default menu. @emit 'finish-launching' deprecate.event app, 'activate-with-no-open-windows', 'activate', (event, hasVisibleWindows) -> - @emit 'activate-with-no-open-windows' if not hasVisibleWindows + @emit 'activate-with-no-open-windows', event if not hasVisibleWindows deprecate.event app, 'select-certificate', 'select-client-certificate' # Wrappers for native classes. From fe4638ef332295e5a8b9e3b901060c37e2a96627 Mon Sep 17 00:00:00 2001 From: Vincent Quagliaro Date: Thu, 26 Nov 2015 09:05:02 +0100 Subject: [PATCH 040/411] :memo: open-file and open-url events are only available on OS X --- docs/api/app.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 00aade7c54..cf4284586d 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -66,7 +66,7 @@ the `will-quit` and `window-all-closed` events. Emitted when the application is quitting. -### Event: 'open-file' +### Event: 'open-file' _OS X_ Returns: @@ -82,7 +82,9 @@ handle this case (even before the `ready` event is emitted). You should call `event.preventDefault()` if you want to handle this event. -### Event: 'open-url' +On Windows, you have to parse `process.argv` to get the filepath. + +### Event: 'open-url' _OS X_ Returns: From 5ee9e6144560294eaf6a2451e67f906ea4821e53 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 19:10:43 +0800 Subject: [PATCH 041/411] Update brightray for #1369 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 8893a27943..57842edb81 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 8893a279435af82e3c2d4b4afe4d9d069f8a2820 +Subproject commit 57842edb817cc1ae38a02fd7f266dd6aa3afbb45 From b1e6d4f64c6aa841650340c87d743b794572bb24 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 20:15:35 +0800 Subject: [PATCH 042/411] Check ELECTRON_RUN_AS_NODE env var --- atom/app/atom_main.cc | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index 26dcb94212..88bb137bae 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -36,10 +36,27 @@ #include "base/at_exit.h" #include "base/i18n/icu_util.h" -#if defined(OS_WIN) - namespace { +const char* kRunAsNode = "ELECTRON_RUN_AS_NODE"; +const char* kOldRunAsNode = "ATOM_SHELL_INTERNAL_RUN_AS_NODE"; + +bool IsEnvSet(const char* name) { +#if defined(OS_WIN) + size_t required_size; + getenv_s(&required_size, nullptr, 0, name); + return required_size != 0; +#else + char* indicator = getenv(name); + return indicator && indicator[0] != '\0'; +#endif +} + +bool IsRunAsNode() { + return IsEnvSet(kRunAsNode) || IsEnvSet(kOldRunAsNode); +} + +#if defined(OS_WIN) // Win8.1 supports monitor-specific DPI scaling. bool SetProcessDpiAwarenessWrapper(PROCESS_DPI_AWARENESS value) { typedef HRESULT(WINAPI *SetProcessDpiAwarenessPtr)(PROCESS_DPI_AWARENESS); @@ -77,9 +94,11 @@ void EnableHighDPISupport() { SetProcessDPIAwareWrapper(); } } +#endif } // namespace +#if defined(OS_WIN) int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { int argc = 0; wchar_t** wargv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); @@ -131,16 +150,12 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { } } - std::string node_indicator, crash_service_indicator; - if (env->GetVar("ATOM_SHELL_INTERNAL_RUN_AS_NODE", &node_indicator) && - node_indicator == "1") { + if (IsRunAsNode()) { // Now that argv conversion is done, we can finally start. base::AtExitManager atexit_manager; base::i18n::InitializeICU(); return atom::NodeMain(argc, argv); - } else if (env->GetVar("ATOM_SHELL_INTERNAL_CRASH_SERVICE", - &crash_service_indicator) && - crash_service_indicator == "1") { + } else if (IsEnvSet("ATOM_SHELL_INTERNAL_CRASH_SERVICE")) { return crash_service::Main(cmd); } @@ -164,8 +179,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { #elif defined(OS_LINUX) // defined(OS_WIN) int main(int argc, const char* argv[]) { - char* node_indicator = getenv("ATOM_SHELL_INTERNAL_RUN_AS_NODE"); - if (node_indicator != NULL && strcmp(node_indicator, "1") == 0) { + if (IsRunAsNode()) { base::i18n::InitializeICU(); base::AtExitManager atexit_manager; return atom::NodeMain(argc, const_cast(argv)); @@ -182,8 +196,7 @@ int main(int argc, const char* argv[]) { #else // defined(OS_LINUX) int main(int argc, const char* argv[]) { - char* node_indicator = getenv("ATOM_SHELL_INTERNAL_RUN_AS_NODE"); - if (node_indicator != NULL && strcmp(node_indicator, "1") == 0) { + if (IsRunAsNode()) { return AtomInitializeICUandStartNode(argc, const_cast(argv)); } From 6534a0e616471c6bd577282f84e750c16ff699cf Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 20:37:48 +0800 Subject: [PATCH 043/411] docs: Add Environment Variables --- docs/README.md | 1 + docs/api/environment-variables.md | 32 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 docs/api/environment-variables.md diff --git a/docs/README.md b/docs/README.md index 9b6372524b..917f4050b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -30,6 +30,7 @@ select the tag that matches your version. * [Synopsis](api/synopsis.md) * [Process Object](api/process.md) * [Supported Chrome Command Line Switches](api/chrome-command-line-switches.md) +* [Environment Variables](api/environment-variables.md) ### Custom DOM Elements: diff --git a/docs/api/environment-variables.md b/docs/api/environment-variables.md new file mode 100644 index 0000000000..76cf4304e5 --- /dev/null +++ b/docs/api/environment-variables.md @@ -0,0 +1,32 @@ +# Environment variables + +Some behaviors of Electron are controlled by environment variables, because they +are initialized earlier than command line and the app's code. + +## `ELECTRON_RUN_AS_NODE` + +Starts the process as a normal Node.js process. + +## `ELECTRON_ENABLE_LOGGING` + +Prints Chrome's internal logging to console. + +## `ELECTRON_ENABLE_STACK_DUMPING` + +When Electron crashed, prints the stack trace to console. + +This environment variable will not work if `crashReporter` is started. + +## `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ + +Shows Windows's crash dialog when Electron crashed. + +This environment variable will not work if `crashReporter` is started. + +## `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ + +Don't use global menu bar on Linux. + +## `ELECTRON_HIDE_INTERNAL_MODULES` + +Turns off compatibility mode for old built-in modules like `require('ipc')`. From 682b48095a3110a6ed891f076cb1983c809a3ddc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 20:44:07 +0800 Subject: [PATCH 044/411] docs: Add example --- docs/api/environment-variables.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/api/environment-variables.md b/docs/api/environment-variables.md index 76cf4304e5..b5d3ff3f6f 100644 --- a/docs/api/environment-variables.md +++ b/docs/api/environment-variables.md @@ -3,6 +3,20 @@ Some behaviors of Electron are controlled by environment variables, because they are initialized earlier than command line and the app's code. +Examples on POSIX shells: + +```bash +$ export ELECTRON_ENABLE_LOGGING=true +$ electron +``` + +on Windows console: + +```powershell +> set ELECTRON_ENABLE_LOGGING=true +> electron +``` + ## `ELECTRON_RUN_AS_NODE` Starts the process as a normal Node.js process. From 59402eb23fb1cd87bd92e8bea618c2f2321fe105 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 21:02:55 +0800 Subject: [PATCH 045/411] Add ELECTRON_NO_ATTACH_CONSOLE env var Close #1556. --- atom/app/atom_main.cc | 13 +++++++------ docs/api/environment-variables.md | 4 ++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index 88bb137bae..5b5df448df 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -5,7 +5,6 @@ #include "atom/app/atom_main.h" #include -#include #if defined(OS_WIN) #include @@ -57,6 +56,12 @@ bool IsRunAsNode() { } #if defined(OS_WIN) +bool IsCygwin() { + std::string os; + scoped_ptr env(base::Environment::Create()); + return env->GetVar("OS", &os) && os == "cygwin"; +} + // Win8.1 supports monitor-specific DPI scaling. bool SetProcessDpiAwarenessWrapper(PROCESS_DPI_AWARENESS value) { typedef HRESULT(WINAPI *SetProcessDpiAwarenessPtr)(PROCESS_DPI_AWARENESS); @@ -103,17 +108,13 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { int argc = 0; wchar_t** wargv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - scoped_ptr env(base::Environment::Create()); - // Make output work in console if we are not in cygiwn. - std::string os; - if (env->GetVar("OS", &os) && os != "cygwin") { + if (!IsCygwin() && !IsEnvSet("ELECTRON_NO_ATTACH_CONSOLE")) { AttachConsole(ATTACH_PARENT_PROCESS); FILE* dontcare; freopen_s(&dontcare, "CON", "w", stdout); freopen_s(&dontcare, "CON", "w", stderr); - freopen_s(&dontcare, "CON", "r", stdin); } // Convert argv to to UTF8 diff --git a/docs/api/environment-variables.md b/docs/api/environment-variables.md index b5d3ff3f6f..6b000aaa10 100644 --- a/docs/api/environment-variables.md +++ b/docs/api/environment-variables.md @@ -37,6 +37,10 @@ Shows Windows's crash dialog when Electron crashed. This environment variable will not work if `crashReporter` is started. +## `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ + +Don't attach to current console session. + ## `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ Don't use global menu bar on Linux. From 932cd92bf6dded7756196254f5d7593fc5d2e14d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 27 Nov 2015 10:30:51 +0800 Subject: [PATCH 046/411] Fix wrong deprecation wrappers of BrowserWindow --- atom/browser/api/lib/browser-window.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee index 4cdffae87a..d693a6d934 100644 --- a/atom/browser/api/lib/browser-window.coffee +++ b/atom/browser/api/lib/browser-window.coffee @@ -90,16 +90,18 @@ deprecate.member BrowserWindow, 'copy', 'webContents' deprecate.member BrowserWindow, 'paste', 'webContents' deprecate.member BrowserWindow, 'selectAll', 'webContents' deprecate.member BrowserWindow, 'reloadIgnoringCache', 'webContents' -deprecate.member BrowserWindow, 'getPageTitle', 'webContents' deprecate.member BrowserWindow, 'isLoading', 'webContents' deprecate.member BrowserWindow, 'isWaitingForResponse', 'webContents' deprecate.member BrowserWindow, 'stop', 'webContents' deprecate.member BrowserWindow, 'isCrashed', 'webContents' -deprecate.member BrowserWindow, 'executeJavaScriptInDevTools', 'webContents' deprecate.member BrowserWindow, 'print', 'webContents' deprecate.member BrowserWindow, 'printToPDF', 'webContents' deprecate.rename BrowserWindow, 'restart', 'reload' deprecate.rename BrowserWindow, 'loadUrl', 'loadURL' deprecate.rename BrowserWindow, 'getUrl', 'getURL' +BrowserWindow::executeJavaScriptInDevTools = deprecate 'executeJavaScriptInDevTools', 'devToolsWebContents.executeJavaScript', (code) -> + @devToolsWebContents?.executeJavaScript code +BrowserWindow::getPageTitle = deprecate 'getPageTitle', 'webContents.getTitle', -> + @webContents?.getTitle() module.exports = BrowserWindow From 585ff9062cbf92ca65fcc50607e8f2b751f9d05e Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 26 Nov 2015 08:48:47 -0400 Subject: [PATCH 047/411] :bug: Fix missing execution permission bit in execFile override Consider an electron application that uses `execFile` to run a script that lives within the application code base: ```coffee child_process = require 'child_process' child_process.execFile __dirname + '/script.sh', (error) -> throw error if error? ``` An application like this will fail when being packaged in an `asar` with an following error: ``` Error: spawn EACCES ``` Electron overrides certain `fs` functions to make them work within an `asar` package. In the case of `execFile`, the file to be executed is extracted from the `asar` package into a temporary file and ran from there. The problem is that during the extraction, the original permissions of the file are lost. We workaround this by: 1. Extending `asar.stat` to return whether a file is executable or not, which is information that's already saved in the `asar` header. 2. Setting execution permissions on the extracted file if the above property holds true. Fixes: https://github.com/atom/electron/issues/3512 --- atom/common/api/atom_api_asar.cc | 1 + atom/common/asar/archive.cc | 3 +++ atom/common/asar/archive.h | 1 + atom/common/lib/asar.coffee | 11 +++++++++++ 4 files changed, 16 insertions(+) diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index 4ea7d8c5c3..489118bd70 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -54,6 +54,7 @@ class Archive : public mate::Wrappable { mate::Dictionary dict(isolate, v8::Object::New(isolate)); dict.Set("size", stats.size); dict.Set("offset", stats.offset); + dict.Set("executable", stats.executable); dict.Set("isFile", stats.is_file); dict.Set("isDirectory", stats.is_directory); dict.Set("isLink", stats.is_link); diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index 969f958956..61b22e9013 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -107,6 +107,9 @@ bool FillFileInfoWithNode(Archive::FileInfo* info, return false; info->offset += header_size; + info->executable = false; + node->GetBoolean("executable", &info->executable); + return true; } diff --git a/atom/common/asar/archive.h b/atom/common/asar/archive.h index f2ff2f76d6..fb52c6265d 100644 --- a/atom/common/asar/archive.h +++ b/atom/common/asar/archive.h @@ -27,6 +27,7 @@ class Archive { struct FileInfo { FileInfo() : size(0), offset(0) {} bool unpacked; + bool executable; uint32 size; uint64 offset; }; diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index f7eeceb3f3..fba3faed8c 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -1,5 +1,6 @@ asar = process.binding 'atom_common_asar' child_process = require 'child_process' +fs = require 'fs' path = require 'path' util = require 'util' @@ -83,6 +84,11 @@ overrideAPISync = (module, name, arg = 0) -> newPath = archive.copyFileOut filePath notFoundError asarPath, filePath unless newPath + stat = archive.stat filePath + + if stat.executable + fs.chmodSync(newPath, 0o755) + arguments[arg] = newPath old.apply this, arguments @@ -102,6 +108,11 @@ overrideAPI = (module, name, arg = 0) -> newPath = archive.copyFileOut filePath return notFoundError asarPath, filePath, callback unless newPath + stat = archive.stat filePath + + if stat.executable + fs.chmodSync(newPath, 0o755) + arguments[arg] = newPath old.apply this, arguments From 0b245b96e4c42ef90f98809ede7e4deaf942f3c2 Mon Sep 17 00:00:00 2001 From: Stephen Niedzielski Date: Thu, 26 Nov 2015 22:35:22 -0700 Subject: [PATCH 048/411] Update docs for globalShortcut.register Document return value for globalShortcut.register and common failure scenario. Fix: #3600 --- docs/api/global-shortcut.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index a0f069d7f1..ab6123bd87 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -46,7 +46,10 @@ The `global-shortcut` module has the following methods: * `callback` Function Registers a global shortcut of `accelerator`. The `callback` is called when -the registered shortcut is pressed by the user. +the registered shortcut is pressed by the user. Returns `true` if the shortcut +`accelerator` was registered, `false` otherwise. For example, the specified +`accelerator` has already been registered by another caller or other native +applications. ### `globalShortcut.isRegistered(accelerator)` From 62c65280a177a50ba9d3aac8c2de4ee8a9908564 Mon Sep 17 00:00:00 2001 From: Stephen Niedzielski Date: Thu, 26 Nov 2015 23:16:00 -0700 Subject: [PATCH 049/411] Update docs for webContents.addWorkSpace Specify that webContents.addWorkSpace cannot be called before DevTools creation and include example. Fix: #3536 --- docs/api/web-contents.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 2612d62122..e976b0ddb8 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -499,7 +499,14 @@ win.webContents.on("did-finish-load", function() { * `path` String -Adds the specified path to DevTools workspace. +Adds the specified path to DevTools workspace. Must be used after DevTools +creation: + +```javascript +mainWindow.webContents.on('devtools-opened', function() { + mainWindow.webContents.addWorkSpace(__dirname); +}); +``` ### `webContents.removeWorkSpace(path)` From a96c408df59d16c5ac5eb2b5ee134674f2cb8509 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 27 Nov 2015 17:28:09 +0900 Subject: [PATCH 050/411] Update as upstream --- docs-translations/ko-KR/README.md | 1 + docs-translations/ko-KR/api/app.md | 6 ++- .../ko-KR/api/environment-variables.md | 49 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 docs-translations/ko-KR/api/environment-variables.md diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index e6ea629c1e..e7aae9e6d6 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -34,6 +34,7 @@ GitHub 프로젝트내에서만 볼 수 있고 `master` 브랜치의 문서는 * [개요](api/synopsis.md) * [Process 객체](api/process.md) * [크롬 명령줄 스위치 지원](api/chrome-command-line-switches.md) +* [환경 변수](api/environment-variables.md) ### 커스텀 DOM elements: diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index d41ce1ab37..095e7b39c6 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -70,7 +70,7 @@ Returns: 어플리케이션이 종료될 때 발생하는 이벤트입니다. -### Event: 'open-file' +### Event: 'open-file' _OS X_ Returns: @@ -87,7 +87,9 @@ Returns: 이 이벤트를 처리할 땐 반드시 `event.preventDefault()`를 호출해야 합니다. -### Event: 'open-url' +Windows에선 `process.argv`를 통해 파일 경로를 얻을 수 있습니다. + +### Event: 'open-url' _OS X_ Returns: diff --git a/docs-translations/ko-KR/api/environment-variables.md b/docs-translations/ko-KR/api/environment-variables.md new file mode 100644 index 0000000000..11d3506921 --- /dev/null +++ b/docs-translations/ko-KR/api/environment-variables.md @@ -0,0 +1,49 @@ +# 환경 변수 + +Electron의 몇몇 동작은 명령 줄과 어플리케이션의 코드보다 먼저 초기화되어야 하므로 환경 변수에 의해 작동합니다. + +POSIX 쉘의 예시입니다: + +```bash +$ export ELECTRON_ENABLE_LOGGING=true +$ electron +``` + +Windows 콘솔의 예시입니다: + +```powershell +> set ELECTRON_ENABLE_LOGGING=true +> electron +``` + +## `ELECTRON_RUN_AS_NODE` + +프로세스를 일반 Node.js 프로세스처럼 시작합니다. (electron 모듈 제외) + +## `ELECTRON_ENABLE_LOGGING` + +Chrome의 내부 로그를 콘솔에 출력합니다. + +## `ELECTRON_ENABLE_STACK_DUMPING` + +Electron이 크래시되면, 콘솔에 stack trace를 출력합니다. + +이 환경 변수는 `crashReporter`가 시작되지 않았을 경우 작동하지 않습니다. + +## `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ + +Electron이 크래시되면, 크래시 정보 창을 표시합니다. + +이 환경 변수는 `crashReporter`가 시작되지 않았을 경우 작동하지 않습니다. + +## `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ + +현재 콘솔 세션에 소속시키지 않습니다. + +## `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ + +Linux의 글로벌 메뉴 막대를 사용하지 않습니다. + +## `ELECTRON_HIDE_INTERNAL_MODULES` + +`require('ipc')`같은 예전 방식의 빌트인 모듈을 비활성화합니다. From a1fdc701eeab630b9652e1c992eaf24164612e84 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 27 Nov 2015 21:29:31 +0800 Subject: [PATCH 051/411] docs: Add notes on spawn and exec --- docs/tutorial/application-packaging.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index 45973e49ea..c6e0ae3c41 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -132,6 +132,7 @@ work. This adds a little overhead for those APIs. APIs that requires extra unpacking are: * `child_process.execFile` +* `child_process.execFileSync` * `fs.open` * `fs.openSync` * `process.dlopen` - Used by `require` on native modules @@ -143,6 +144,17 @@ archives is generated by guessing, because those files do not exist on the filesystem. So you should not trust the `Stats` object except for getting file size and checking file type. +### Executing Binaries Inside `asar` Archive + +There are Node APIs that can execute binaries like `child_process.exec`, +`child_process.spawn` and `child_process.execFile`, but only `execFile` is +supported to execute binaries inside `asar` archive. + +This is because `exec` and `spawn` accept `command` instead of `file` as input, +and `command`s are executed under shell. There is no reliable way to determine +whether a command uses a file in asar archive, and even if we do, we can not be +sure whether we can replace the path in command without side effects. + ## Adding Unpacked Files in `asar` Archive As stated above, some Node APIs will unpack the file to filesystem when From 6ef6a830425ecab31ca94a48d12aea30f14e274d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 27 Nov 2015 21:38:43 +0800 Subject: [PATCH 052/411] spec: Test execFile and execFileSync --- spec/asar-spec.coffee | 16 ++++++++++++++++ spec/fixtures/asar/echo.asar | Bin 0 -> 18112 bytes 2 files changed, 16 insertions(+) create mode 100644 spec/fixtures/asar/echo.asar diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index ad480a5825..af39fa3ec5 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -392,6 +392,22 @@ describe 'asar package', -> done() child.send file + describe 'child_process.execFile', -> + return unless process.platform is 'darwin' + + {execFile, execFileSync} = require 'child_process' + echo = path.join fixtures, 'asar', 'echo.asar', 'echo' + + it 'executes binaries', (done) -> + child = execFile echo, ['test'], (error, stdout) -> + assert.equal error, null + assert.equal stdout, 'test\n' + done() + + it 'execFileSync executes binaries', -> + output = execFileSync echo, ['test'] + assert.equal String(output), 'test\n' + describe 'internalModuleReadFile', -> internalModuleReadFile = process.binding('fs').internalModuleReadFile diff --git a/spec/fixtures/asar/echo.asar b/spec/fixtures/asar/echo.asar new file mode 100644 index 0000000000000000000000000000000000000000..4d72f7a92a7bbd6ccc205942348801eea307742e GIT binary patch literal 18112 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 literal 0 HcmV?d00001 From cfdcfcbd80c43fa124b6926ebd6a355f4c6cc5ce Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 27 Nov 2015 22:06:37 +0800 Subject: [PATCH 053/411] Add executable permission in CopyFileOut --- atom/common/api/atom_api_asar.cc | 1 - atom/common/asar/archive.cc | 10 ++++++++-- atom/common/asar/archive.h | 2 +- atom/common/lib/asar.coffee | 11 ----------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index 489118bd70..4ea7d8c5c3 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -54,7 +54,6 @@ class Archive : public mate::Wrappable { mate::Dictionary dict(isolate, v8::Object::New(isolate)); dict.Set("size", stats.size); dict.Set("offset", stats.offset); - dict.Set("executable", stats.executable); dict.Set("isFile", stats.is_file); dict.Set("isDirectory", stats.is_directory); dict.Set("isLink", stats.is_link); diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index 61b22e9013..ab93e301b1 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -13,6 +13,7 @@ #include "atom/common/asar/scoped_temporary_file.h" #include "base/files/file.h" +#include "base/files/file_util.h" #include "base/logging.h" #include "base/pickle.h" #include "base/json/json_reader.h" @@ -96,7 +97,6 @@ bool FillFileInfoWithNode(Archive::FileInfo* info, return false; info->size = static_cast(size); - info->unpacked = false; if (node->GetBoolean("unpacked", &info->unpacked) && info->unpacked) return true; @@ -107,7 +107,6 @@ bool FillFileInfoWithNode(Archive::FileInfo* info, return false; info->offset += header_size; - info->executable = false; node->GetBoolean("executable", &info->executable); return true; @@ -276,6 +275,13 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { if (!temp_file->InitFromFile(&file_, info.offset, info.size)) return false; +#if defined(OS_POSIX) + if (info.executable) { + // chmod a+x temp_file; + base::SetPosixFilePermissions(temp_file->path(), 0755); + } +#endif + *out = temp_file->path(); external_files_.set(path, temp_file.Pass()); return true; diff --git a/atom/common/asar/archive.h b/atom/common/asar/archive.h index fb52c6265d..de5b9de605 100644 --- a/atom/common/asar/archive.h +++ b/atom/common/asar/archive.h @@ -25,7 +25,7 @@ class ScopedTemporaryFile; class Archive { public: struct FileInfo { - FileInfo() : size(0), offset(0) {} + FileInfo() : unpacked(false), executable(false), size(0), offset(0) {} bool unpacked; bool executable; uint32 size; diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index fba3faed8c..f7eeceb3f3 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -1,6 +1,5 @@ asar = process.binding 'atom_common_asar' child_process = require 'child_process' -fs = require 'fs' path = require 'path' util = require 'util' @@ -84,11 +83,6 @@ overrideAPISync = (module, name, arg = 0) -> newPath = archive.copyFileOut filePath notFoundError asarPath, filePath unless newPath - stat = archive.stat filePath - - if stat.executable - fs.chmodSync(newPath, 0o755) - arguments[arg] = newPath old.apply this, arguments @@ -108,11 +102,6 @@ overrideAPI = (module, name, arg = 0) -> newPath = archive.copyFileOut filePath return notFoundError asarPath, filePath, callback unless newPath - stat = archive.stat filePath - - if stat.executable - fs.chmodSync(newPath, 0o755) - arguments[arg] = newPath old.apply this, arguments From 62add3abccf7b192e222c54071202c0ddf934470 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 27 Nov 2015 22:23:19 +0800 Subject: [PATCH 054/411] Bump v0.35.2 --- atom.gyp | 2 +- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atom.gyp b/atom.gyp index 9d66741052..202a414003 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.35.1', + 'version%': '0.35.2', }, 'includes': [ 'filenames.gypi', diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 481945b8e8..fb4a233233 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.35.1 + 0.35.2 CFBundleShortVersionString - 0.35.1 + 0.35.2 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 875727cdaf..773871fd55 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,35,1,0 - PRODUCTVERSION 0,35,1,0 + FILEVERSION 0,35,2,0 + PRODUCTVERSION 0,35,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.35.1" + VALUE "FileVersion", "0.35.2" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.35.1" + VALUE "ProductVersion", "0.35.2" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 093da7b5c6..b5cc0982d0 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 0 #define ATOM_MINOR_VERSION 35 -#define ATOM_PATCH_VERSION 1 +#define ATOM_PATCH_VERSION 2 #define ATOM_VERSION_IS_RELEASE 1 From eeb1143debf7f6eab6592e6ff9e8328d560efab9 Mon Sep 17 00:00:00 2001 From: Ivan Kulikov Date: Fri, 27 Nov 2015 22:32:45 +0300 Subject: [PATCH 055/411] init russian lang docs --- README.md | 1 + docs-translations/ru-RU/README.md | 82 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 docs-translations/ru-RU/README.md diff --git a/README.md b/README.md index beb96b4e5c..db82fac88e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ contains documents describing how to build and contribute to Electron. - [Spanish](https://github.com/atom/electron/tree/master/docs-translations/es) - [Simplified Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) - [Traditional Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) +- [Russian](https://github.com/atom/electron/tree/master/docs-translations/ru-RU) ## Quick Start diff --git a/docs-translations/ru-RU/README.md b/docs-translations/ru-RU/README.md new file mode 100644 index 0000000000..07e50028df --- /dev/null +++ b/docs-translations/ru-RU/README.md @@ -0,0 +1,82 @@ +Пожалуйста, убедитесь, что вы используете документацию, которые соответствует вашей версии Electron. +Номер версии должен быть частью адреса страницы. Если это не так, вы +возможно,используете документицию ветки разработки, которая может содержать изменения api, +которые не совместимы с вашей версией Electron. Если это так, +Вы можете переключиться на другую версию документации в списке +[доступные версии](http://electron.atom.io/docs/) на atom.io, или +если вы используете интерфейс GitHub, откройте список "переключение ветки/тега" и +выберите тег, который соответствует вашей версии. + +## Руководства + +* [Поддерживаемые платформы](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) +* [Отладка главного процесса](tutorial/debugging-main-process.md) +* [Использование Selenium и WebDriver](tutorial/using-selenium-and-webdriver.md) +* [DevTools Extension](tutorial/devtools-extension.md) +* [Использование Pepper Flash Plugin](tutorial/using-pepper-flash-plugin.md) + +## Учебники + +* [Быстрый старт](tutorial/quick-start.md) +* [Desktop Environment Integration](tutorial/desktop-environment-integration.md) +* [Online/Offline Event Detection](tutorial/online-offline-events.md) + +## API References + +* [Краткий обзор](api/synopsis.md) +* [Process Object](api/process.md) +* [Поддерживаемые параметры командной строки Chrome](api/chrome-command-line-switches.md) + +### Пользовательские элементы DOM: + +* [`File` Object](api/file-object.md) +* [`` Tag](api/web-view-tag.md) +* [`window.open` Function](api/window-open.md) + +### Modules for the Main Process: + +* [app](api/app.md) +* [autoUpdater](api/auto-updater.md) +* [BrowserWindow](api/browser-window.md) +* [contentTracing](api/content-tracing.md) +* [dialog](api/dialog.md) +* [globalShortcut](api/global-shortcut.md) +* [ipcMain](api/ipc-main.md) +* [Menu](api/menu.md) +* [MenuItem](api/menu-item.md) +* [powerMonitor](api/power-monitor.md) +* [powerSaveBlocker](api/power-save-blocker.md) +* [protocol](api/protocol.md) +* [session](api/session.md) +* [webContents](api/web-contents.md) +* [Tray](api/tray.md) + +### Модули для Renderer Process (Web Page): + +* [ipcRenderer](api/ipc-renderer.md) +* [remote](api/remote.md) +* [webFrame](api/web-frame.md) + +### Modules for Both Processes: + +* [clipboard](api/clipboard.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.md) + +## Разработка + +* [Стиль кодирования](development/coding-style.md) +* [Source Code Directory Structure](development/source-code-directory-structure.md) +* [Technical Differences to NW.js (formerly node-webkit)](development/atom-shell-vs-node-webkit.md) +* [Обзор системы сборки](development/build-system-overview.md) +* [Инструкции по сборке (OS X)](development/build-instructions-osx.md) +* [Инструкции по сборке (Windows)](development/build-instructions-windows.md) +* [Инструкции по сборке (Linux)](development/build-instructions-linux.md) +* [Настройка сервера символов для отладчика](development/setting-up-symbol-server.md) + From 5bbc46930fef4d9445ce148cebac4e94560572c3 Mon Sep 17 00:00:00 2001 From: "howard.zuo" Date: Sun, 29 Nov 2015 12:14:49 +0800 Subject: [PATCH 056/411] add packaging translation for ZH-CN --- .../zh-CN/tutorial/application-packaging.md | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 docs-translations/zh-CN/tutorial/application-packaging.md diff --git a/docs-translations/zh-CN/tutorial/application-packaging.md b/docs-translations/zh-CN/tutorial/application-packaging.md new file mode 100644 index 0000000000..ee4b7bf63b --- /dev/null +++ b/docs-translations/zh-CN/tutorial/application-packaging.md @@ -0,0 +1,141 @@ +# 应用打包 + +为舒缓Windows下路径名过长的问题[issues](https://github.com/joyent/node/issues/6960), 也略对`require`加速以及简单隐匿你的源代码, 你可以通过极小的源代码改动将你的应用打包成[asar][asar]. + +## 生成`asar`包 + +[asar][asar]是一种将多个文件合并成一个文件的类tar风格的归档格式。 Electron可以无需解压,即从其中读取任意文件内容。 + +参照如下步骤将你的应用打包成`asar`: + +### 1. 安装asar + +```bash +$ npm install -g asar +``` + +### 2. 用`asar pack`打包 + +```bash +$ asar pack your-app app.asar +``` + +## 使用`asar`包 + +在Electron中有两类APIs:Node.js提供的Node APIs和Chromium提供的Web APIs。这两种APIs都支持从`asar`包中读取文件。 + +### Node API + +由于Electron中打了特别补丁, Node APIs中如`fs.readFile`或者`require`之类的方法可以将`asar`视之为虚拟文件夹,读取`asar`里面的文件就和从真实的文件系统中读取一样。 + +例如,假设我们在`/path/to`文件夹下有个`example.asar`包: + +```bash +$ asar list /path/to/example.asar +/app.js +/file.txt +/dir/module.js +/static/index.html +/static/main.css +/static/jquery.min.js +``` + +从`asar`包读取一个文件: + +```javascript +const fs = require('fs'); +fs.readFileSync('/path/to/example.asar/file.txt'); +``` + +列出`asar`包中根目录下的所有文件: + +```javascript +const fs = require('fs'); +fs.readdirSync('/path/to/example.asar'); +``` + +使用`asar`包中的一个模块: + +```javascript +require('/path/to/example.asar/dir/module.js'); +``` + +你也可以使用`BrowserWindow`来显示一个`asar`包里的web页面: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('file:///path/to/example.asar/static/index.html'); +``` + +### Web API + +在Web页面里,用`file:`协议可以获取`asar`包中文件。和Node API一样,视`asar`包如虚拟文件夹。 + +例如,用`$.get`获取文件: + +```html + +``` + +### 像“文件”那样处理`asar`包 + +有些场景,如:核查`asar`包的校验和,我们需要像读取“文件”那样读取`asar`包的内容(而不是当成虚拟文件夹)。你可以使用内置的`original-fs`(提供和`fs`一样的APIs)模块来读取`asar`包的真实信息。 + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` + +## Node API缺陷 + +尽管我们已经尽了最大努力使得`asar`包在Node API下的应用尽可能的趋向于真实的目录结构,但仍有一些底层Node API我们无法保证其正常工作。 + +### `asar`包是只读的 + +`asar`包中的内容不可更改,所以Node APIs里那些可以用来修改文件的方法在对待`asar`包时都无法正常工作。 + +### Working Directory在`asar`包中无效 + +尽管`asar`包是虚拟文件夹,但其实并没有真实的目录架构对应在文件系统里,所以你不可能将working Directory设置成`asar`包里的一个文件夹。将`asar`中的文件夹以`cwd`形式作为参数传入一些API中也会报错。 + +### API中的额外“开箱” + +大部分`fs`API可以无需解压即从`asar`包中读取文件或者文件的信息,但是在处理一些依赖真实文件路径的底层系统方法时,Electron会将所需文件解压到临时目录下,然后将临时目录下的真实文件路径传给底层系统方法使其正常工作。 对于这类API,耗费会略多一些。 + +以下是一些需要额外解压的APIs: + +* `child_process.execFile` +* `child_process.execFileSync` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - `require`native模块时用到 + +### `fs.stat`获取的stat信息不可靠 + +对`asar`包中的文件取`fs.stat`,返回的`Stats`对象不是精确值,因为这些文件不是真实存在于文件系统里。所以除了文件大小和文件类型以外,你不应该依赖`Stats`对象的值。 + +### 执行`asar`包中的程序 + +Node中有一些可以执行程序的API,如`child_process.exec`,`child_process.spawn`和`child_process.execFile`等,但只有`execFile`可以执行`asar`包中的程序。 + +因为`exec`和`spawn`允许`command`替代`file`作为输入,而`command`是需要在shell下执行的,目前没有可靠的方法来判断`command`中是否在操作一个`asar`包中的文件,而且即便可以判断,我们依旧无法保证可以在无任何副作用的情况下替换`command`中的文件路径。 + +## 打包时排除文件 + +如上所述,一些Node API会在调用时将文件解压到文件系统中,除了效率问题外,也有可能引起杀毒软件的注意! + +为解决这个问题,你可以在生成`asar`包时使用`--unpack`选项来排除一些文件,使其不打包到`asar`包中,下面是如何排除一些用作共享用途的native模块的方法: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +经过上述命令后,除了生成的`app.asar`包以外,还有一个包含了排除文件的`app.asar.unpacked`文件夹,你需要将这个文件夹一起拷贝,提供给用户。 + +[asar]: https://github.com/atom/asar From dab7058aa29c035aa844526e122cfd842e754e40 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sun, 29 Nov 2015 23:03:31 +0900 Subject: [PATCH 057/411] Update as upstream --- .../ko-KR/api/global-shortcut.md | 6 +++++- docs-translations/ko-KR/api/web-contents.md | 9 ++++++++- .../ko-KR/tutorial/application-packaging.md | 19 ++++++++++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/docs-translations/ko-KR/api/global-shortcut.md b/docs-translations/ko-KR/api/global-shortcut.md index 1b8363bcbe..9617d463e8 100644 --- a/docs-translations/ko-KR/api/global-shortcut.md +++ b/docs-translations/ko-KR/api/global-shortcut.md @@ -46,7 +46,11 @@ app.on('will-quit', function() { * `callback` Function `accelerator`로 표현된 전역 단축키를 등록합니다. 유저로부터 등록된 단축키가 눌렸을 -경우 `callback` 함수가 호출됩니다. +경우 `callback` 함수가 호출됩니다. `accelerator` 단축키가 등록되었을 경우 +`true`를 반환합니다. 그 외엔 `false`를 반환합니다. 예를 들어 지정한 +`accelerator`가 이미 다른 호출자 또는 네이티브 어플리케이션에서 등록된 상태를 +생각할 수 있습니다. + ### `globalShortcut.isRegistered(accelerator)` diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index c92149ce44..82d426c1ce 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -497,7 +497,14 @@ win.webContents.on("did-finish-load", function() { * `path` String -특정 경로를 개발자 도구의 워크스페이스에 추가합니다. +특정 경로를 개발자 도구의 워크스페이스에 추가합니다. 반드시 개발자 도구의 생성이 완료된 +이후에 사용해야 합니다. + +```javascript +mainWindow.webContents.on('devtools-opened', function() { + mainWindow.webContents.addWorkSpace(__dirname); +}); +``` ### `webContents.removeWorkSpace(path)` diff --git a/docs-translations/ko-KR/tutorial/application-packaging.md b/docs-translations/ko-KR/tutorial/application-packaging.md index 7a91419763..0e958fe862 100644 --- a/docs-translations/ko-KR/tutorial/application-packaging.md +++ b/docs-translations/ko-KR/tutorial/application-packaging.md @@ -129,16 +129,29 @@ API가 원할하게 작동할 수 있도록 임시 경로에 해당되는 파일 위 예외에 해당하는 API 메서드는 다음과 같습니다: * `child_process.execFile` +* `child_process.execFileSync` * `fs.open` * `fs.openSync` * `process.dlopen` - Used by `require` on native modules -### `fs.stat`의 잘못된 스테이터스 정보 +### `fs.stat`의 모조된(예측) 스테이터스 정보 `fs.stat` 로부터 반환되는 `Stats` 객체와 비슷한 API들은 `asar` 아카이브를 타겟으로 할 경우 예측된 디렉터리 파일 정보를 가집니다. 왜냐하면 아카이브의 디렉터리 경로는 실제 -파일 시스템에 존재하지 않기 때문입니다. 그러한 이유로 파일 크기와 -파일 타입 등을 확인할 때 `Stats` 객체를 신뢰해선 안됩니다. +파일 시스템에 존재하지 않기 때문입니다. 그러한 이유로 파일 크기와 파일 타입 등을 확인할 +때 `Stats` 객체를 신뢰해선 안됩니다. + +### `asar` 아카이브 내부의 바이너리 실행 + +Node API에는 `child_process.exec`, `child_process.spawn` 그리고 +`child_process.execFile`와 같은 바이너리를 실행시킬 수 있는 API가 있습니다. 하지만 +`asar` 아카이브 내에선 `execFile` API만 사용할 수 있습니다. + +이 한계가 존재하는 이유는 `exec`와 `spawn`은 `file` 대신 `command`를 인수로 허용하고 +있고 `command`는 shell에서 작동하기 때문입니다. Electron은 어떤 커맨드가 `asar` +아카이브 내의 파일을 사용하는지 결정하는데 적절한 방법을 가지고 있지 않으며, 심지어 +그게 가능하다고 해도 부작용 없이 명령 경로를 대체할 수 있는지에 대해 확실히 알 수 +있는 방법이 없습니다. ## `asar` 아카이브에 미리 압축 해제된 파일 추가하기 From 22dbf5e9fc7dabd9e271026669056b4c1b8de58d Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sun, 29 Nov 2015 23:06:01 +0900 Subject: [PATCH 058/411] Small fixes --- docs-translations/ko-KR/tutorial/application-packaging.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-translations/ko-KR/tutorial/application-packaging.md b/docs-translations/ko-KR/tutorial/application-packaging.md index 0e958fe862..97054110ce 100644 --- a/docs-translations/ko-KR/tutorial/application-packaging.md +++ b/docs-translations/ko-KR/tutorial/application-packaging.md @@ -150,8 +150,8 @@ Node API에는 `child_process.exec`, `child_process.spawn` 그리고 이 한계가 존재하는 이유는 `exec`와 `spawn`은 `file` 대신 `command`를 인수로 허용하고 있고 `command`는 shell에서 작동하기 때문입니다. Electron은 어떤 커맨드가 `asar` 아카이브 내의 파일을 사용하는지 결정하는데 적절한 방법을 가지고 있지 않으며, 심지어 -그게 가능하다고 해도 부작용 없이 명령 경로를 대체할 수 있는지에 대해 확실히 알 수 -있는 방법이 없습니다. +그게 가능하다고 해도 부작용 없이 명령 경로를 대체할 수 있는지에 대해 확실히 알 수 있는 +방법이 없습니다. ## `asar` 아카이브에 미리 압축 해제된 파일 추가하기 From df9ecefe015913fe0053e79cb7a3c448dc8deb7b Mon Sep 17 00:00:00 2001 From: Luke Westby Date: Sun, 29 Nov 2015 12:22:53 -0600 Subject: [PATCH 059/411] fix typo in "rotation" string --- atom/browser/api/atom_api_screen.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index b73bda9ced..407a71f0cc 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -41,7 +41,7 @@ std::vector MetricsToArray(uint32_t metrics) { if (metrics & gfx::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR) array.push_back("scaleFactor"); if (metrics & gfx::DisplayObserver::DISPLAY_METRIC_ROTATION) - array.push_back("rotaion"); + array.push_back("rotation"); return array; } From c7420e17ab0e74dfcfbcf5a53f0c13f35104d107 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sun, 29 Nov 2015 14:04:57 -0500 Subject: [PATCH 060/411] Suggest [ci skip] in documentation-only commits --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ca3ea5d2f..31e31edf1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,6 +57,7 @@ possible with your report. If you can, please include: * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") * Limit the first line to 72 characters or less * Reference issues and pull requests liberally +* When only changing documentation, include [ci skip] in the commit description * Consider starting the commit message with an applicable emoji: * :art: `:art:` when improving the format/structure of the code * :racehorse: `:racehorse:` when improving performance From 9c0afcd9017966125eb8605f683e202d70a062de Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sun, 29 Nov 2015 14:57:48 -0500 Subject: [PATCH 061/411] Wrap [ci skip] in backticks --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31e31edf1f..630b8b0ba0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,7 +57,7 @@ possible with your report. If you can, please include: * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") * Limit the first line to 72 characters or less * Reference issues and pull requests liberally -* When only changing documentation, include [ci skip] in the commit description +* When only changing documentation, include `[ci skip]` in the commit description * Consider starting the commit message with an applicable emoji: * :art: `:art:` when improving the format/structure of the code * :racehorse: `:racehorse:` when improving performance From 7d827c2f3e1734db241d37f04101c9cdde449b8e Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Sun, 29 Nov 2015 22:56:34 -0200 Subject: [PATCH 062/411] Remove 'Google Translator'-like translation --- .../pt-BR/development/coding-style.md | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/docs-translations/pt-BR/development/coding-style.md b/docs-translations/pt-BR/development/coding-style.md index f3b4d6d58f..b0068d5689 100644 --- a/docs-translations/pt-BR/development/coding-style.md +++ b/docs-translations/pt-BR/development/coding-style.md @@ -2,28 +2,25 @@ Estas são as diretrizes de estilo para codificar no Electron. -## C++ and Python +## C++ e Python -Para C ++ e Python, seguimos os padrões do projeto Chromium [Estilo de Codificação](http://www.chromium.org/developers/coding-style). Há também um +Para C++ e Python, seguimos o [Estilo de Codificação](http://www.chromium.org/developers/coding-style) do projeto Chromium. Há também um script `script/cpplint.py` para verificar se todos os arquivos estão em conformidade. A versão Python que estamos usando agora é a Python 2.7. -O código C ++ usa do Chromium's um monte de tipos e abstrações, por isso é recomendada para se familiarizar com eles. Um bom lugar para começar com a documentação do Chromium's [Important Abstractions and Data Structures](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures). O documento menciona alguns tipos especiais, com escopo tipos (que automaticamente libera sua memória quando sai do escopo), registrando mecanismos etc. +O código C++ usa várias abstrações e tipos do Chromium, por isso é recomendado familiarizar-se com eles. Um bom lugar para começar é com a documentação do Chromium [Important Abstractions and Data Structures](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures). O documento menciona alguns tipos especiais, *scoped types* (que automaticamente liberam sua memória ao sair do escopo), mecanismos de *log* etc. ## CoffeeScript -For CoffeeScript, we follow GitHub's [Style -Guide](https://github.com/styleguide/javascript) and the following rules: +Para CoffeeScript, seguimos o [Guia de Estilo] (https://github.com/styleguide/javascript) do GitHub com as seguintes regras: -Para CoffeeScript, seguimos o estilo do GitHub [Guia de Estilo] (https://github.com/styleguide/javascript) com as seguintes regras: +* Os arquivos **NÃO DEVEM** terminar com uma nova linha, porque queremos corresponder aos padrões de estilo Google. -* Os arquivos devem **NÃO DEVEM** com nova linha no final, porque queremos corresponder aos padrões de estilo Google. - -* Os nomes dos arquivos devem ser concatenados com o `-` em vez de`_`, por exemplo, `file-name.coffee` em vez de`file_name.coffee`, porque no [github/atom](https://github.com/github/atom) os nomes dos módulos são geralmente em o formulário `module-name`. Esta regra só se aplica aos arquivos com extensão `.coffee`. +* Os nomes dos arquivos devem ser concatenados com `-` em vez de `_`, por exemplo, `file-name.coffee` em vez de `file_name.coffee`, porque no [github/atom](https://github.com/github/atom) os nomes dos módulos são geralmente da forma `module-name`. Esta regra só se aplica aos arquivos com extensão `.coffee`. * -## API Names +## Nomes de APIs -Ao criar uma nova API, devemos preferencialmente utilizar métodos getters e setters em vez de -estilo de uma função do jQuery. Por exemplo, `.getText()` e `.setText(text)` utilize `.text([text])`. Existe uma +Ao criar uma nova API, devemos preferencialmente utilizar métodos getters e setters em vez do +estilo de uma função única do jQuery. Por exemplo, `.getText()` e `.setText(text)` são preferenciais a `.text([text])`. Existe uma [discussão](https://github.com/atom/electron/issues/46) sobre este assunto. From 45e9ed5f0c61fe8fbd4a3c2ff4c559c0ce0b08f2 Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Sun, 29 Nov 2015 23:25:16 -0200 Subject: [PATCH 063/411] =?UTF-8?q?Update=20'Estilo=20de=20c=C3=B3digo'=20?= =?UTF-8?q?link.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'Estilo de código' in 'Desenvolvimento' section now points to the translated coding-style.md --- docs-translations/pt-BR/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md index db88edaf77..1641f23807 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -61,7 +61,7 @@ Módulos de ambos os processos: ## Desenvolvimento -* [Estilo de código](../../docs/development/coding-style.md) +* [Estilo de código](development/coding-style.md) * [Estrutura de diretórios padrão](../../docs/development/source-code-directory-structure.md) * [Diferenças técnicas do NW.js (antigo node-webkit)](../../docs/development/atom-shell-vs-node-webkit.md) * [Visão geral do build](../../docs/development/build-system-overview.md) From bf4b94561ef38c29ebace0c557686795c03d29d3 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 30 Nov 2015 15:21:39 +0800 Subject: [PATCH 064/411] docs: Mention submitting to MAC costs money Close #3617. --- docs/tutorial/mac-app-store-submission-guide.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md index 43b2d9a0a4..036676fe85 100644 --- a/docs/tutorial/mac-app-store-submission-guide.md +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -4,6 +4,9 @@ Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store (MAS). This guide provides information on: how to submit your app and the limitations of the MAS build. +__Note:__ Submitting an app to Mac App Store requires enrolling [Apple Developer +Program][developer-program], which costs money. + ## How to Submit Your App The following steps introduce a simple way to submit your app to Mac App Store. @@ -108,6 +111,7 @@ Also, due to the usage of app sandboxing, the resources which can be accessed by the app are strictly limited; you can read [App Sandboxing][app-sandboxing] for more information. +[developer-program]: https://developer.apple.com/support/compare-memberships/ [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html [nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps [enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html From 9fa6527460149d4b1251f099d6582186a11ff0ca Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Mon, 30 Nov 2015 17:24:43 +0900 Subject: [PATCH 065/411] Update as upstream --- .../ko-KR/tutorial/mac-app-store-submission-guide.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md b/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md index 16a74d03ed..d316d16da8 100644 --- a/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md +++ b/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md @@ -4,6 +4,10 @@ Electron은 v0.34.0 버전부터 앱 패키지를 Mac App Store(MAS)에 제출 되었습니다. 이 가이드는 어플리케이션을 앱 스토어에 등록하는 방법과 빌드의 한계에 대한 설명을 제공합니다. +__참고:__ Mac App Store에 어플리케이션을 등록하려면 +[Apple Developer Program][developer-program]에 등록되어 있어야 하며 비용이 발생할 +수 있습니다. + ## 앱 스토어에 어플리케이션을 등록하는 방법 다음 몇 가지 간단한 절차에 따라 앱 스토어에 어플리케이션을 등록하는 방법을 알아봅니다. @@ -109,6 +113,7 @@ productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RES **역주:** [Mac 앱 배포 가이드 공식 문서](https://developer.apple.com/osx/distribution/kr/) +[developer-program]: https://developer.apple.com/support/compare-memberships/ [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html [nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps [enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html From 4d5028fb4c8e4643a16ea866ea318821f7c10994 Mon Sep 17 00:00:00 2001 From: Smite Chow Date: Mon, 30 Nov 2015 17:16:35 +0800 Subject: [PATCH 066/411] :art: add miss charactor --- .../zh-CN/development/atom-shell-vs-node-webkit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md b/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md index 9774580eeb..beea91e3ff 100644 --- a/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md +++ b/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md @@ -12,7 +12,7 @@ __1. 应用的入口__ 在 Electron 中,入口是一个 JavaScript 脚本。不同于直接提供一个URL,你需要手动创建一个浏览器窗口,然后通过 API 加载 HTML 文件。你还可以监听窗口事件,决定何时让应用退出。 -Electron 的工作方式更像 Node.js 运行时。 Electron 的 APIs 更加底层,因此你可以它替代 [PhantomJS](http://phantomjs.org/) 做浏览器测试。 +Electron 的工作方式更像 Node.js 运行时。 Electron 的 APIs 更加底层,因此你可以用它替代 [PhantomJS](http://phantomjs.org/) 做浏览器测试。 __2. 构建系统__ From 44e24ebf7adc5a6242085b35ed4150950b4eb560 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 30 Nov 2015 22:29:01 +0800 Subject: [PATCH 067/411] Delete BridgeTaskRunner when main message loop is ready --- atom/browser/atom_browser_main_parts.cc | 3 ++- atom/browser/bridge_task_runner.cc | 5 ----- atom/browser/bridge_task_runner.h | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index 0a8c16ca22..3bfe3748f1 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -114,7 +114,8 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { 1000)); brightray::BrowserMainParts::PreMainMessageLoopRun(); - BridgeTaskRunner::MessageLoopIsReady(); + bridge_task_runner_->MessageLoopIsReady(); + bridge_task_runner_ = nullptr; #if defined(USE_X11) libgtk2ui::GtkInitFromCommandLine(*base::CommandLine::ForCurrentProcess()); diff --git a/atom/browser/bridge_task_runner.cc b/atom/browser/bridge_task_runner.cc index 882a3050de..36c8d17359 100644 --- a/atom/browser/bridge_task_runner.cc +++ b/atom/browser/bridge_task_runner.cc @@ -8,11 +8,6 @@ namespace atom { -// static -std::vector BridgeTaskRunner::tasks_; -std::vector BridgeTaskRunner::non_nestable_tasks_; - -// static void BridgeTaskRunner::MessageLoopIsReady() { auto message_loop = base::MessageLoop::current(); CHECK(message_loop); diff --git a/atom/browser/bridge_task_runner.h b/atom/browser/bridge_task_runner.h index 12508f009d..b69b33b295 100644 --- a/atom/browser/bridge_task_runner.h +++ b/atom/browser/bridge_task_runner.h @@ -20,7 +20,7 @@ class BridgeTaskRunner : public base::SingleThreadTaskRunner { ~BridgeTaskRunner() override {} // Called when message loop is ready. - static void MessageLoopIsReady(); + void MessageLoopIsReady(); // base::SingleThreadTaskRunner: bool PostDelayedTask(const tracked_objects::Location& from_here, @@ -35,8 +35,8 @@ class BridgeTaskRunner : public base::SingleThreadTaskRunner { private: using TaskPair = base::Tuple< tracked_objects::Location, base::Closure, base::TimeDelta>; - static std::vector tasks_; - static std::vector non_nestable_tasks_; + std::vector tasks_; + std::vector non_nestable_tasks_; DISALLOW_COPY_AND_ASSIGN(BridgeTaskRunner); }; From 7622bb40a95801d5cca594925e83b2c540cb957a Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 23 Nov 2015 23:29:49 -0800 Subject: [PATCH 068/411] Enable all origins via CORS header for custom schemes This PR disables CORS for custom schemes, which allows you to serve Font resources from custom schemes after using registerCustomSchemeAsSecure --- atom/browser/net/url_request_buffer_job.cc | 3 +++ atom/browser/net/url_request_buffer_job.h | 2 ++ atom/browser/net/url_request_string_job.cc | 3 +++ 3 files changed, 8 insertions(+) diff --git a/atom/browser/net/url_request_buffer_job.cc b/atom/browser/net/url_request_buffer_job.cc index affc3dd37d..c4936ba156 100644 --- a/atom/browser/net/url_request_buffer_job.cc +++ b/atom/browser/net/url_request_buffer_job.cc @@ -50,6 +50,9 @@ void URLRequestBufferJob::GetResponseInfo(net::HttpResponseInfo* info) { status.append("\0\0", 2); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + std::string cors("Access-Control-Allow-Origin: *"); + headers->AddHeader(cors); + if (!mime_type_.empty()) { std::string content_type_header(net::HttpRequestHeaders::kContentType); content_type_header.append(": "); diff --git a/atom/browser/net/url_request_buffer_job.h b/atom/browser/net/url_request_buffer_job.h index ab8de7e8f0..8de5d82e0d 100644 --- a/atom/browser/net/url_request_buffer_job.h +++ b/atom/browser/net/url_request_buffer_job.h @@ -12,6 +12,8 @@ #include "net/http/http_status_code.h" #include "net/url_request/url_request_simple_job.h" +const std::string kCorsHeader("Access-Control-Allow-Origin: *"); + namespace atom { class URLRequestBufferJob : public JsAsker { diff --git a/atom/browser/net/url_request_string_job.cc b/atom/browser/net/url_request_string_job.cc index 4a631b813e..5d7a7dd017 100644 --- a/atom/browser/net/url_request_string_job.cc +++ b/atom/browser/net/url_request_string_job.cc @@ -32,6 +32,9 @@ void URLRequestStringJob::GetResponseInfo(net::HttpResponseInfo* info) { std::string status("HTTP/1.1 200 OK"); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + std::string cors("Access-Control-Allow-Origin: *"); + headers->AddHeader(cors); + if (!mime_type_.empty()) { std::string content_type_header(net::HttpRequestHeaders::kContentType); content_type_header.append(": "); From 65cb1488b0e32034d17ba3f8753b7dca015828aa Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 25 Nov 2015 15:00:34 -0800 Subject: [PATCH 069/411] Fix CORS header code to be cleaner --- atom/browser/net/js_asker.cc | 2 ++ atom/browser/net/js_asker.h | 1 + atom/browser/net/url_request_buffer_job.cc | 3 +-- atom/browser/net/url_request_string_job.cc | 3 +-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/atom/browser/net/js_asker.cc b/atom/browser/net/js_asker.cc index 8f0d1d2b95..0e232feffb 100644 --- a/atom/browser/net/js_asker.cc +++ b/atom/browser/net/js_asker.cc @@ -11,6 +11,8 @@ namespace atom { +const std::string kCorsHeader("Access-Control-Allow-Origin: *"); + namespace internal { namespace { diff --git a/atom/browser/net/js_asker.h b/atom/browser/net/js_asker.h index 8ec245ee8c..cdc0417f36 100644 --- a/atom/browser/net/js_asker.h +++ b/atom/browser/net/js_asker.h @@ -17,6 +17,7 @@ #include "v8/include/v8.h" namespace atom { +extern const std::string kCorsHeader; using JavaScriptHandler = base::Callback)>; diff --git a/atom/browser/net/url_request_buffer_job.cc b/atom/browser/net/url_request_buffer_job.cc index c4936ba156..55603e77e0 100644 --- a/atom/browser/net/url_request_buffer_job.cc +++ b/atom/browser/net/url_request_buffer_job.cc @@ -50,8 +50,7 @@ void URLRequestBufferJob::GetResponseInfo(net::HttpResponseInfo* info) { status.append("\0\0", 2); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); - std::string cors("Access-Control-Allow-Origin: *"); - headers->AddHeader(cors); + headers->AddHeader(kCorsHeader); if (!mime_type_.empty()) { std::string content_type_header(net::HttpRequestHeaders::kContentType); diff --git a/atom/browser/net/url_request_string_job.cc b/atom/browser/net/url_request_string_job.cc index 5d7a7dd017..6a12026b2d 100644 --- a/atom/browser/net/url_request_string_job.cc +++ b/atom/browser/net/url_request_string_job.cc @@ -32,8 +32,7 @@ void URLRequestStringJob::GetResponseInfo(net::HttpResponseInfo* info) { std::string status("HTTP/1.1 200 OK"); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); - std::string cors("Access-Control-Allow-Origin: *"); - headers->AddHeader(cors); + headers->AddHeader(kCorsHeader); if (!mime_type_.empty()) { std::string content_type_header(net::HttpRequestHeaders::kContentType); From 7cce3987eb0a91ae481c53568c378ea3a115a720 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 25 Nov 2015 15:00:47 -0800 Subject: [PATCH 070/411] Add CORS header to file jobs --- atom/browser/net/url_request_async_asar_job.cc | 9 +++++++++ atom/browser/net/url_request_async_asar_job.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/atom/browser/net/url_request_async_asar_job.cc b/atom/browser/net/url_request_async_asar_job.cc index 303bfc0170..86d92071e3 100644 --- a/atom/browser/net/url_request_async_asar_job.cc +++ b/atom/browser/net/url_request_async_asar_job.cc @@ -34,4 +34,13 @@ void URLRequestAsyncAsarJob::StartAsync(scoped_ptr options) { } } + +void URLRequestAsyncAsarJob::GetResponseInfo(net::HttpResponseInfo* info) { + std::string status("HTTP/1.1 200 OK"); + net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + + headers->AddHeader(kCorsHeader); + info->headers = headers; +} + } // namespace atom diff --git a/atom/browser/net/url_request_async_asar_job.h b/atom/browser/net/url_request_async_asar_job.h index df1aed350b..d65142f0bd 100644 --- a/atom/browser/net/url_request_async_asar_job.h +++ b/atom/browser/net/url_request_async_asar_job.h @@ -18,6 +18,9 @@ class URLRequestAsyncAsarJob : public JsAsker { // JsAsker: void StartAsync(scoped_ptr options) override; + // URLRequestJob: + void GetResponseInfo(net::HttpResponseInfo* info) override; + private: DISALLOW_COPY_AND_ASSIGN(URLRequestAsyncAsarJob); }; From 15b8d7680ecbfb86168acdbdfe218d9ab962273d Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 25 Nov 2015 15:20:50 -0800 Subject: [PATCH 071/411] Add tests to verify behavior --- spec/api-protocol-spec.coffee | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/spec/api-protocol-spec.coffee b/spec/api-protocol-spec.coffee index d160512cd7..5332b2b17c 100644 --- a/spec/api-protocol-spec.coffee +++ b/spec/api-protocol-spec.coffee @@ -81,6 +81,21 @@ describe 'protocol module', -> error: (xhr, errorType, error) -> done(error) + it 'sets Access-Control-Allow-Origin', (done) -> + handler = (request, callback) -> callback(text) + protocol.registerStringProtocol protocolName, handler, (error) -> + return done(error) if error + $.ajax + url: "#{protocolName}://fake-host" + success: (data, status, request) -> + assert.equal data, text + assert.equal( + request.getResponseHeader('Access-Control-Allow-Origin'), + '*') + done() + error: (xhr, errorType, error) -> + done(error) + it 'sends object as response', (done) -> handler = (request, callback) -> callback(data: text, mimeType: 'text/html') protocol.registerStringProtocol protocolName, handler, (error) -> @@ -120,6 +135,21 @@ describe 'protocol module', -> error: (xhr, errorType, error) -> done(error) + it 'sets Access-Control-Allow-Origin', (done) -> + handler = (request, callback) -> callback(buffer) + protocol.registerBufferProtocol protocolName, handler, (error) -> + return done(error) if error + $.ajax + url: "#{protocolName}://fake-host" + success: (data, status, request) -> + assert.equal data, text + assert.equal( + request.getResponseHeader('Access-Control-Allow-Origin'), + '*') + done() + error: (xhr, errorType, error) -> + done(error) + it 'sends object as response', (done) -> handler = (request, callback) -> callback(data: buffer, mimeType: 'text/html') protocol.registerBufferProtocol protocolName, handler, (error) -> @@ -163,6 +193,21 @@ describe 'protocol module', -> error: (xhr, errorType, error) -> done(error) + it 'sets Access-Control-Allow-Origin', (done) -> + handler = (request, callback) -> callback(filePath) + protocol.registerFileProtocol protocolName, handler, (error) -> + return done(error) if error + $.ajax + url: "#{protocolName}://fake-host" + success: (data, status, request) -> + assert.equal data, String(fileContent) + assert.equal( + request.getResponseHeader('Access-Control-Allow-Origin'), + '*') + done() + error: (xhr, errorType, error) -> + done(error) + it 'sends object as response', (done) -> handler = (request, callback) -> callback(path: filePath) protocol.registerFileProtocol protocolName, handler, (error) -> From 549da7fd91325b2370716fadfa2bc1a177593fe6 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 30 Nov 2015 11:08:22 -0800 Subject: [PATCH 072/411] Linting --- atom/browser/net/js_asker.h | 2 ++ atom/browser/net/url_request_buffer_job.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/net/js_asker.h b/atom/browser/net/js_asker.h index cdc0417f36..b353b98fa7 100644 --- a/atom/browser/net/js_asker.h +++ b/atom/browser/net/js_asker.h @@ -5,6 +5,8 @@ #ifndef ATOM_BROWSER_NET_JS_ASKER_H_ #define ATOM_BROWSER_NET_JS_ASKER_H_ +#include + #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" diff --git a/atom/browser/net/url_request_buffer_job.h b/atom/browser/net/url_request_buffer_job.h index 8de5d82e0d..ab8de7e8f0 100644 --- a/atom/browser/net/url_request_buffer_job.h +++ b/atom/browser/net/url_request_buffer_job.h @@ -12,8 +12,6 @@ #include "net/http/http_status_code.h" #include "net/url_request/url_request_simple_job.h" -const std::string kCorsHeader("Access-Control-Allow-Origin: *"); - namespace atom { class URLRequestBufferJob : public JsAsker { From c83976dfdc1444d1aa2a1d90bab873186d6c533a Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Mon, 30 Nov 2015 19:14:33 -0200 Subject: [PATCH 073/411] :memo: [ci skip] Fix typos --- .../pt-BR/tutorial/quick-start.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs-translations/pt-BR/tutorial/quick-start.md b/docs-translations/pt-BR/tutorial/quick-start.md index f9883144c8..eecf67878f 100644 --- a/docs-translations/pt-BR/tutorial/quick-start.md +++ b/docs-translations/pt-BR/tutorial/quick-start.md @@ -4,7 +4,7 @@ Electron permite criar aplicações desktop com puro JavaScript através de um runtime com APIs ricas e nativas. Você pode ver isso como uma variação do runtime do io.js que é focado em aplicações desktop em vez de web servers. -Isso não significa que o Electron é uma ligação em JavaScript para blibliotécas +Isso não significa que o Electron é uma ligação em JavaScript para bibliotecas de interface gráfica (GUI). Em vez disso, Electron usa páginas web como interface gráfica, então você pode ver isso também como um navegador Chromium mínimo, controlado por JavaScript. @@ -17,13 +17,13 @@ mostrar uma GUI criando páginas web. ### Processo Renderizador -Desde que o Electron usa o Chromium para mostrar as páginas web, a arquitetura +Já que o Electron usa o Chromium para mostrar as páginas web, a arquitetura multi-processo do Chromium também é usada. Cada página web no Electron roda em seu próprio processo, o que é chamado de __processo renderizador__. Em navegadores comuns, as páginas web normalmente rodam em um ambiente em sandbox -e não tem permissão de acesso para recursos nativos. Usuários Electron, entretanto, -tem o poder de usar as APIs do io.js nas páginas web, permitindo interações de baixo +e não têm permissão de acesso para recursos nativos. Usuários Electron, entretanto, +têm o poder de usar as APIs do io.js nas páginas web, permitindo interações de baixo nível no sistema operacional. ### Diferenças Entre o Processo Principal e o Processo Renderizador @@ -33,12 +33,12 @@ Cada instância de `BrowserWindow` roda a página web em seu próprio processo r Quando uma instância de `BrowserWindow` é destruída, o processo renderizador correspondente também é finalizado. -O processo principal gerência todas as páginas web de seus processos renderizadores +O processo principal gerencia todas as páginas web de seus processos renderizadores correspondentes. Cada processo renderizador é isolado e toma conta de sua respectiva página web. Nas páginas web, chamar APIs nativas relacionadas à GUI não é permitido porque -gerênciar recursos de GUI em páginas web é muito perigoso e torna fácil o vazamento de +gerenciar recursos de GUI em páginas web é muito perigoso e torna fácil o vazamento de recursos. Se você quer realizar operações com GUI em páginas web, o processo renderizador da página web deve se comunicar com o processo principal para requisitar que o processo principal realize estas operações. @@ -52,26 +52,26 @@ módulo [remoto](../../../docs/api/remote.md) para comunicação RPC. Geralmente, um app Electron é estruturado assim: ```text -your-app/ +seu-app/ ├── package.json ├── main.js └── index.html ``` -O formato de `package.json` é exatamente o mesmo que os dos módulos do Node, e +O formato de `package.json` é exatamente o mesmo que o dos módulos do Node, e o script especificado pelo campo `main` é o script de inicialização do seu app, que irá executar o processo principal. Um exemplo do seu `package.json` deve parecer com isso: ```json { - "name" : "your-app", + "name" : "seu-app", "version" : "0.1.0", "main" : "main.js" } ``` -__Nota__: Se o campo `main` não estiver presente no `package.jso`, o Electron irá +__Nota__: Se o campo `main` não estiver presente no `package.json`, o Electron irá tentar carregar um `index.js` O `main.js` deve criar as janelas e os manipuladores de eventos do sistema, um típico @@ -140,8 +140,8 @@ Finalmente o `index.html` é a página web que você quer mostrar: ## Execute seu App -Uma vez que você criou seus arquivos `main.js`, `index.html, e `package.json` iniciais, -você provavelmente vai querer tentar executar seu app localmente para testa-lo a ter +Uma vez que você criou seus arquivos `main.js`, `index.html`, e `package.json` iniciais, +você provavelmente vai querer tentar executar seu app localmente para testa-lo e ter certeza que funciona como você espera. ### electron-prebuilt @@ -167,19 +167,19 @@ executar seu app diretamente. #### Windows ```bash -$ .\electron\electron.exe your-app\ +$ .\electron\electron.exe seu-app\ ``` #### Linux ```bash -$ ./electron/electron your-app/ +$ ./electron/electron seu-app/ ``` #### OS X ```bash -$ ./Electron.app/Contents/MacOS/Electron your-app/ +$ ./Electron.app/Contents/MacOS/Electron seu-app/ ``` `Electron.app` aqui é uma parte do pacote de lançamento do Electron, você pode baixa-lo @@ -188,5 +188,5 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ ### Executar como uma distribuição Depois de terminar seu app, você pode criar uma distribuição seguindo o guia -[Application Distribution](./application-distribution.md) e então executar o app +[Distribuição de aplicações](./application-distribution.md) e então executar o app empacotado. From 97f535251b215a515db7af2a1c79453926a025d9 Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Mon, 30 Nov 2015 19:43:19 -0200 Subject: [PATCH 074/411] :memo: [ci skip] Update to match english docs. --- docs-translations/pt-BR/README.md | 77 ++++++++++++++++++------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md index 1641f23807..61202f8ca4 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -1,71 +1,82 @@ +Por favor, certifique-se de que está utilizando a documentação que corresponde à sua versão do Electron. +O número da versão deve ser uma parte da URL da página. Se não for, você provavelmente está utilizando +a documentação de um branch de desenvolvimento que pode conter mudanças na API que não são compatíveis +com a sua versão do Electron. Se este for o caso, você pode mudar para uma versão diferente da +documentação na lista de [versões disponíveis](http://electron.atom.io/docs/) em atom.io, +ou se você estiver usando a interface do GitHub, abra o *dropdown* "Switch branches/tags" e +selecione a *tag* que corresponde à sua versão. + ## Guias +* [Platformas Suportadas](../../tutorial/supported-platforms.md) * [Distribuição de Aplicações](tutorial/application-distribution.md) -* [Empacotamento da aplicação](tutorial/application-packaging.md) -* [Usando módulos nativos](tutorial/using-native-node-modules.md) -* [Depuração do processo principal](tutorial/debugging-main-process.md) +* [Guia de Submissão da Mac App Store](../../tutorial/mac-app-store-submission-guide.md) +* [Empacotamento da Aplicação](tutorial/application-packaging.md) +* [Usando Módulos Nativos do Node](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](tutorial/using-pepper-flash-plugin.md) +* [Usando o Plugin Pepper Flash](tutorial/using-pepper-flash-plugin.md) ## Tutoriais * [Introdução](tutorial/quick-start.md) -* [A integração com o ambiente de desenvolvimento](tutorial/desktop-environment-integration.md) -* [Evento de detecção on-line/off-line](tutorial/online-offline-events.md) +* [Integração com o Ambiente de Desenvolvimento](tutorial/desktop-environment-integration.md) +* [Evento de Detecção Online/Offline](tutorial/online-offline-events.md) -## API - Referencias +## API - Referências * [Sinopse](../../docs/api/synopsis.md) * [Processos](api/process.md) * [Aceleradores (Teclas de Atalho)](api/accelerator.md) * [Parâmetros CLI suportados (Chrome)](../../docs/api/chrome-command-line-switches.md) +* [Variáveis de Ambiente](../../docs/api/environment-variables.md) -DOM elementos personalizados: +### Elementos DOM Personalizados: * [Objeto `File`](../../docs/api/file-object.md) * [Tag ``](../../docs/api/web-view-tag.md) * [Função `window.open`](../../docs/api/window-open.md) -Os principais módulos: +### Módulos para o Processo Principal: * [app](../../docs/api/app.md) -* [auto-updater](../../docs/api/auto-updater.md) -* [browser-window](../../docs/api/browser-window.md) -* [content-tracing](../../docs/api/content-tracing.md) +* [autoUpdater](../../docs/api/auto-updater.md) +* [BrowserWindow](../../docs/api/browser-window.md) +* [contentTracing](../../docs/api/content-tracing.md) * [dialog](../../docs/api/dialog.md) -* [global-shortcut](../../docs/api/global-shortcut.md) -* [ipc (main process)](../../docs/api/ipc-main-process.md) -* [menu](../../docs/api/menu.md) -* [menu-item](../../docs/api/menu-item.md) -* [power-monitor](../../docs/api/power-monitor.md) -* [power-save-blocker](../../docs/api/power-save-blocker.md) +* [globalShortcut](../../docs/api/global-shortcut.md) +* [ipcMain](../../docs/api/ipc-main-process.md) +* [Menu](../../docs/api/menu.md) +* [MenuItem](../../docs/api/menu-item.md) +* [powerMonitor](../../docs/api/power-monitor.md) +* [powerSaveBlocker](../../docs/api/power-save-blocker.md) * [protocol](../../docs/api/protocol.md) * [session](../../docs/api/session.md) * [webContents](../../docs/api/web-contents.md) -* [tray](../../docs/api/tray.md) +* [Tray](../../docs/api/tray.md) -Módulos do renderizador (web page): +### Módulos para o Processo Renderizador: -* [ipc (renderer)](../../docs/api/ipc-renderer.md) +* [ipcRenderer](../../docs/api/ipc-renderer.md) * [remote](../../docs/api/remote.md) -* [web-frame](../../docs/api/web-frame.md) +* [webFrame](../../docs/api/web-frame.md) -Módulos de ambos os processos: +### Módulos para ambos os processos: * [clipboard](../../docs/api/clipboard.md) -* [crash-reporter](../../docs/api/crash-reporter.md) -* [native-image](../../docs/api/native-image.md) +* [crashReporter](../../docs/api/crash-reporter.md) +* [nativeImage](../../docs/api/native-image.md) * [screen](../../docs/api/screen.md) * [shell](api/shell.md) ## Desenvolvimento -* [Estilo de código](development/coding-style.md) -* [Estrutura de diretórios padrão](../../docs/development/source-code-directory-structure.md) -* [Diferenças técnicas do NW.js (antigo node-webkit)](../../docs/development/atom-shell-vs-node-webkit.md) -* [Visão geral do build](../../docs/development/build-system-overview.md) -* [Instrução de build (Mac)](../../docs/development/build-instructions-osx.md) -* [Instrução de build (Windows)](../../docs/development/build-instructions-windows.md) -* [Instrução de build (Linux)](../../docs/development/build-instructions-linux.md) -* [Configurando um symbol server no debugger](../../docs/development/setting-up-symbol-server.md) +* [Estilo de Código](development/coding-style.md) +* [Estrutura de Diretórios de Código Fonte](../../docs/development/source-code-directory-structure.md) +* [Diferenças Técnicas do NW.js (antigo node-webkit)](../../docs/development/atom-shell-vs-node-webkit.md) +* [Visão Geral do Build](../../docs/development/build-system-overview.md) +* [Instrução de Build (Mac)](../../docs/development/build-instructions-osx.md) +* [Instrução de Build (Windows)](../../docs/development/build-instructions-windows.md) +* [Instrução de Build (Linux)](../../docs/development/build-instructions-linux.md) +* [Configurando um Symbol Server no Debugger](../../docs/development/setting-up-symbol-server.md) From 7da78a9fa8ffdd0d9300bb12892683de6a154a5d Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Mon, 30 Nov 2015 21:56:19 -0200 Subject: [PATCH 075/411] :memo: [ci skip] Add translation to app.md --- docs-translations/pt-BR/api/app.md | 452 +++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 docs-translations/pt-BR/api/app.md diff --git a/docs-translations/pt-BR/api/app.md b/docs-translations/pt-BR/api/app.md new file mode 100644 index 0000000000..85cfaade58 --- /dev/null +++ b/docs-translations/pt-BR/api/app.md @@ -0,0 +1,452 @@ +# app + +O módulo `app` é responsável por controlar o ciclo de vida do aplicativo. + +O exemplo a seguir mostra como fechar o aplicativo quando a última janela é fechada: + +```javascript +const app = require('electron').app; +app.on('window-all-closed', function() { + app.quit(); +}); +``` + +## Eventos + +O objeto `app` emite os seguintes eventos: + +### Evento: 'will-finish-launching' + +Emitido quando o aplicativo finaliza a inicialização básica. No Windows e no Linux, +o evento `will-finish-launching` é o mesmo que o evento `ready`; No OS X, +esse evento representa a notificação `applicationWillFinishLaunching` do `NSApplication`. +Normalmente aqui seriam criados *listeners* para os eventos `open-file` e `open-url`, e inicializar o *crash reporter* e atualizador automático. + +Na maioria dos casos, você deve fazer tudo no manipulador de eventos do `ready`. + +### Evento: 'ready' + +Emitido quando o Electron finaliza a inicialização. + +### Evento: 'window-all-closed' + +Emitido quando todas as janelas forem fechadas. + +Este evento só é emitido quando o aplicativo não for fechar. Se o +usuário pressionou`Cmd + Q`, ou o desenvolvedor chamou `app.quit()`, +o Electron tentará primeiro fechar todas as janelas e então emitir o +evento `will-quit`, e neste caso o evento `window-all-closed` não +seria emitido. + +### Evento: 'before-quit' + +Retorna: + +* `event` Event + +Emitido antes que o aplicativo comece a fechar suas janelas. +Chamar `event.preventDefault()` irá impedir o comportamento padrão, +que é terminar o aplicativo. + +### Evento: 'will-quit' + +Retorna: + +* `event` Event + +Emitido quando todas as janelas foram fechadas e o aplicativo irá finalizar. +Chamar `event.preventDefault()` irá impedir o comportamento padrão, +que é terminar o aplicativo. + +Veja a descrição do evento `window-all-closed` para as diferenças entre o +evento `will-quit` e `window-all-closed`. + +### Evento: 'quit' + +Emitido quando o aplicativo está finalizando. + +### Evento: 'open-file' _OS X_ + +Retorna: + +* `event` Event +* `path` String + +Emitido quando o usuário deseja abrir um arquivo com o aplicativo. O evento +`open-file` normalmente é emitido quando o aplicativo já está aberto e o S.O. +quer reutilizar o aplicativo para abrir o arquivo. `open-file` também é emitido +quando um arquivo é jogado no *dock* e o aplicativo ainda não está rodando. +Certifique-se de utilizar um *listener* para o evento `open-file` cedo na +inicialização do seu aplicativo para cuidar deste caso (antes mesmo do evento +`ready` ser emitido). + +Você deve chamar `event.preventDefault()` se quiser cuidar deste caso. + +No Windows, você deve fazer o *parse* do `process.argv` para pegar o +endereço do arquivo. + +### Evento: 'open-url' _OS X_ + +Retorna: + +* `event` Event +* `url` String + +Emitido quando o usuário deseja abrir uma URL com o aplicativo. O esquema deve +ser registrado para ser aberto pelo seu aplicativo. + +Você deve chamar `event.preventDefault()` se quiser cuidar deste caso. + +### Evento: 'activate' _OS X_ + +Retorna: + +* `event` Event +* `hasVisibleWindows` Boolean + +Emitido quando o aplicativo é ativado, que normalmente acontece quando o ícone +do aplicativo no *dock* é clicado. + +### Evento: 'browser-window-blur' + +Retorna: + +* `event` Event +* `window` BrowserWindow + +Emitido quando uma [browserWindow](../../../docs/api/browser-window.md) fica embaçada. + +### Evento: 'browser-window-focus' + +Retorna: + +* `event` Event +* `window` BrowserWindow + +Emitido quando uma [browserWindow](../../../docs/api/browser-window.md) é focada. + +### Evento: 'browser-window-created' + +Retorna: + +* `event` Event +* `window` BrowserWindow + +Emitido quando uma nova [browserWindow](../../../docs/api/browser-window.md) é criada. + +### Evento: 'certificate-error' + +Returns: + +* `event` Event +* `webContents` [WebContents](../../../docs/api/web-contents.md) +* `url` URL +* `error` String - O código de erro +* `certificate` Object + * `data` Buffer - dados codificados PEM + * `issuerName` String +* `callback` Function + +Emitido quando há uma falha na verificação do `certificate` para a `url`, +para confiar no certificado, você deve impedir o comportamento padrão com +`event.preventDefault()` e chamar `callback(true)`. + +```javascript +session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { + if (url == "https://github.com") { + // Lógica de verificação. + event.preventDefault(); + callback(true); + } else { + callback(false); + } +}); +``` + +### Evento: 'select-client-certificate' + +Retorna: + +* `event` Event +* `webContents` [WebContents](../../../docs/api/web-contents.md) +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - dados codificados PEM + * `issuerName` String - Nome Comum do Emissor +* `callback` Function + +Emitido quando um certificado de cliente é requisitado. + +A `url` corresponde à entrada de navegação requisitando o certificado do +cliente e `callback` precisa ser chamada com uma entrada filtrada da lista. +Usar `event.preventDefault()` impede o aplicativo de usar o primeiro certificado +da memória. + +```javascript +app.on('select-client-certificate', function(event, webContents, url, list, callback) { + event.preventDefault(); + callback(list[0]); +}) +``` + +### Evento: 'login' + +Retorna: + +* `event` Event +* `webContents` [WebContents](../../../docs/api/web-contents.md) +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +Emitido quando `webContents` deseja fazer autenticação básica. + +O comportamento padrão é cancelar todas as autenticações, para sobrescrever +isto, você deve impedir esse comportamento com `event.preventDefault()` e +chamar `callback(username, password)` com as credenciais. + +```javascript +app.on('login', function(event, webContents, request, authInfo, callback) { + event.preventDefault(); + callback('username', 'secret'); +}) +``` + +### Evento: 'gpu-process-crashed' + +Emitido quando o processo da gpu falha. + +## Métodos + +O objeto `app` possui os seguintes métodos: + +**Nota:** Alguns métodos só estão disponíveis em sistemas operacionais específicos e estão rotulados como tal. + +### `app.quit()` + +Tente fechar todas as janelas. O evento `before-quit` será emitido primeiro. Se todas +as janelas fecharem com sucesso, o evento `will-quit` será emitido e por padrão o +aplicativo irá terminar. + +Este método garante que todos os manipuladores de evento `beforeunload` e `unload` +sejam corretamente executados. É possível que uma janela cancele o processo de +encerramento ao retornar `false` no manipulador de evento `beforeunload`. + +### `app.exit(exitCode)` + +* `exitCode` Integer + +Finaliza imediatamente com `exitCode`. + +Todas as janelas serão fechadas imediatamente sem perguntar ao usuário, e os eventos +`before-quit` e `will-quit` não serão emitidos. + +### `app.getAppPath()` + +Retorna o atual diretório do aplicativo. + +### `app.getPath(name)` + +* `name` String + +Retorna um endereço para um diretório especial ou arquivo associado com `nome`. +Numa falha um `Error` é lançado. + +Você pode requisitar os seguintes endereços pelo nome: + +* `home` Diretório *home* do usuário. +* `appData` Diretório de dados do aplicativo por usuário, que por padrão aponta para: + * `%APPDATA%` no Windows + * `$XDG_CONFIG_HOME` ou `~/.config` no Linux + * `~/Library/Application Support` no OS X +* `userData` O diretório para guardar os arquivos de configuração do seu aplicativo, que por padrão é o diretório `appData` concatenado com o nome do seu aplicativo. +* `temp` Diretório temporário. +* `exe` O arquivo executável atual. +* `module` A biblioteca `libchromiumcontent`. +* `desktop` O diretório *Desktop* do usuário atual. +* `documents` Diretório "Meus Documentos" do usuário. +* `downloads` Diretório dos downloads do usuário. +* `music` Diretório de músicas do usuário. +* `pictures` Diretório de imagens do usuário. +* `videos` Diretório de vídeos do usuário. + +### `app.setPath(name, path)` + +* `name` String +* `path` String + +Sobrescreve o `path` para um diretório especial ou arquivo associado com `name`. +Se o endereço especifica um diretório não existente, o diretório será criado por +este método. Numa falha um `Error` é lançado. + +Você pode sobrescrever apenas endereços com um `name` definido em `app.getPath`. + +Por padrão, *cookies* e *caches* de páginas web serão guardadas no diretório `userData`. Se você quiser mudar esta localização, você deve sobrescrever o +endereço `userData` antes que o evento `ready` do módulo `app` seja emitido. + +### `app.getVersion()` + +Retorna a versão do aplicativo carregado. Se nenhuma versão for encontrada no +arquivo `package.json` do aplicativo, a versão do pacote ou executável atual é +retornada. + +### `app.getName()` + +Retorna o nome do aplicativo atual, que é o nome no arquivo `package.json` do +aplicativo. + +Normalmente o campo `name` do `package.json` é um nome curto em letras minúsculas, +de acordo com as especificações de módulos npm. Normalmente você deve também +especificar um campo `productName`, que é o nome completo em letras maiúsculas do +seu aplicativo, e que será preferido ao `name` pelo Electron. + +### `app.getLocale()` + +Retorna a localidade atual do aplicativo. + +### `app.addRecentDocument(path)` _OS X_ _Windows_ + +* `path` String + +Adiciona `path` à lista de documentos recentes. + +Esta lista é gerenciada pelo S.O.. No Windows você pode visitar a lista pela +barra de tarefas, e no OS X você pode visita-la pelo *dock*. + +### `app.clearRecentDocuments()` _OS X_ _Windows_ + +Limpa a lista de documentos recentes. + +### `app.setUserTasks(tasks)` _Windows_ + +* `tasks` Array - Vetor de objetos `Task` + +Adiciona `tasks` à categoria [Tasks][tasks] do JumpList no Windows. + +`tasks` é um vetor de objetos `Task` no seguinte formato: + +`Task` Object +* `program` String - Endereço do programa a ser executado, normalmente você deve especificar `process.execPath` que abre o programa atual. +* `arguments` String - Os argumentos de linha de comando quando `program` é executado. +* `title` String - A string a ser exibida em uma JumpList. +* `description` String - Descrição desta *task*. +* `iconPath` String - O endereço absoluto para um ícone a ser exibido em uma JumpList, que pode ser um arquivo arbitrário que contém um ícone. Normalmente você pode especificar `process.execPath` para mostrar o ícone do programa. +* `iconIndex` Integer - O índice do ícone do arquivo do icone. Se um arquivo de ícone consiste de dois ou mais ícones, defina este valor para identificar o ícone. Se o arquivo de ícone consiste de um ícone apenas, este valor é 0. + +### `app.allowNTLMCredentialsForAllDomains(allow)` + +* `allow` Boolean + +Define dinamicamente se sempre envia credenciais para HTTP NTLM ou autenticação *Negotiate* - normalmente, o Electron irá mandar apenas credenciais NTLM/Kerberos para URLs que se enquadram em sites "Intranet Local" (estão no mesmo domínio que você). +Entretanto, esta detecção frequentemente falha quando redes corporativas são mal configuradas, então isso permite optar por esse comportamento e habilitá-lo para todas as URLs. + +### `app.makeSingleInstance(callback)` + +* `callback` Function + +Este método faz da sua aplicação uma Aplicação de Instância Única - invés de permitir múltiplas instâncias do seu aplicativo rodarem, isto irá assegurar que apenas uma única instância do seu aplicativo rodará, e outras instâncias sinalizam esta instância e finalizam. + +`callback` será chamado com `callback(argv, workingDirectory)` quando uma segunda instância tenha sido executada. `argv` é um vetor de argumentos de linha de comando da segunda instância, e `workingDirectory` é o atual endereço de seu diretório. +Normalmente aplicativos respondem à isso não minimizando sua janela primária e dando foco à ela. + +É garantida a execução do `callback` após o evento `ready` do `app` ser emitido. + +Este método retorna `false` caso seu processo seja a instância primária do aplicativo e seu aplicativo deve continuar carregando. E retorna `true` caso seu processo tenha enviado seus parâmetros para outra instância, e você deve imediatamente finalizar. + +No OS X o sistema enforça instância única automaticamente quando usuários tentam abrir uma segunda instância do seu aplicativo no *Finder*, e os eventos `open-file` e `open-url` serão emitidos para isso. Entretanto, quando usuários inicializam seu aplicativo na linha de comando, o mecanismo de instância única do sistema será ignorado e você terá de utilizar esse método para assegurar-se de ter uma instância única. + +Um exemplo de ativação da janela de primeira instância quando uma segunda instância inicializa: + +```js +var myWindow = null; + +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // Alguém tentou rodar uma segunda instância, devemos focar nossa janela + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); + } + return true; +}); + +if (shouldQuit) { + app.quit(); + return; +} + +// Cria myWindow, carrega o resto do aplicativo, etc... +app.on('ready', function() { +}); +``` + +### `app.setAppUserModelId(id)` _Windows_ + +* `id` String + +Muda o [Application User Model ID][app-user-model-id] para `id`. + +### `app.commandLine.appendSwitch(switch[, value])` + +Adiciona uma opção (com `value` opcional) à linha de comando do Chromium. + +**Nota:** Isto não irá afetar `process.argv`, e é utilizado principalmente por desenvolvedores para controlar alguns comportamentos de baixo nível do Chromium. + +### `app.commandLine.appendArgument(value)` + +Adiciona um argumento à linha de comando do Chromium. O argumento será passado com aspas corretamente. + +**Nota:** Isto não irá afetar `process.argv`. + +### `app.dock.bounce([type])` _OS X_ + +* `type` String (opcional) - Pode ser `critical` ou `informational`. O padrão é + `informational` + +Quando `critical` é passado, o ícone do *dock* irá pular até que o aplicativo se torne ativo ou a requisição seja cancelada. + +Quando `informational` é passado, o ícone do *dock* irá pular por um segundo. +Entretanto, a requisição se mantém ativa até que o aplicativo se torne ativo ou a requisição seja cancelada. + +Retorna um ID representando a requisição. + +### `app.dock.cancelBounce(id)` _OS X_ + +* `id` Integer + +Cancela o salto do `id`. + +### `app.dock.setBadge(text)` _OS X_ + +* `text` String + +Define a string a ser exibida na área de *badging* do *dock*. + +### `app.dock.getBadge()` _OS X_ + +Retorna a string da *badge* do *dock*. + +### `app.dock.hide()` _OS X_ + +Esconde o ícone do *dock*. + +### `app.dock.show()` _OS X_ + +Exibe o ícone do *dock*. + +### `app.dock.setMenu(menu)` _OS X_ + +* `menu` Menu + +Define o [menu do dock][dock-menu] do aplicativo. + +[dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 +[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx From 7fd1db192b1d0f9da0150e01f34c64bf596eb875 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 30 Nov 2015 18:12:00 -0800 Subject: [PATCH 076/411] Lint harder --- atom/browser/net/url_request_async_asar_job.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/atom/browser/net/url_request_async_asar_job.cc b/atom/browser/net/url_request_async_asar_job.cc index 86d92071e3..46065259fc 100644 --- a/atom/browser/net/url_request_async_asar_job.cc +++ b/atom/browser/net/url_request_async_asar_job.cc @@ -4,6 +4,8 @@ #include "atom/browser/net/url_request_async_asar_job.h" +#include + namespace atom { URLRequestAsyncAsarJob::URLRequestAsyncAsarJob( From e3ec1fe8abbad5c78dcf785fa64dfc2e10c751dd Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 13:09:37 +0800 Subject: [PATCH 077/411] Add process.noAsar to turn off asar support --- atom/common/lib/asar.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index f7eeceb3f3..2373385f75 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -18,6 +18,7 @@ process.on 'exit', -> # Separate asar package's path from full path. splitPath = (p) -> + return [false] if process.noAsar # shortcut to disable asar. return [false] if typeof p isnt 'string' return [true, p, ''] if p.substr(-5) is '.asar' p = path.normalize p From 900dc78a47598cec6bbfc0d6220ce69016919e82 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 13:09:50 +0800 Subject: [PATCH 078/411] spec: process.noAsar --- spec/asar-spec.coffee | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index af39fa3ec5..b7a62f8eea 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -423,6 +423,41 @@ describe 'asar package', -> p = path.join fixtures, 'asar', 'unpack.asar', 'a.txt' assert.equal internalModuleReadFile(p).toString().trim(), 'a' + describe 'process.noAsar', -> + beforeEach -> + process.noAsar = true + afterEach -> + process.noAsar = false + + it 'disables asar support in sync API', -> + file = path.join fixtures, 'asar', 'a.asar', 'file1' + dir = path.join fixtures, 'asar', 'a.asar', 'dir1' + assert.throws (-> fs.readFileSync file), /ENOTDIR/ + assert.throws (-> fs.lstatSync file), /ENOTDIR/ + assert.throws (-> fs.realpathSync file), /ENOTDIR/ + assert.throws (-> fs.readdirSync dir), /ENOTDIR/ + + it 'disables asar support in async API', (done) -> + file = path.join fixtures, 'asar', 'a.asar', 'file1' + dir = path.join fixtures, 'asar', 'a.asar', 'dir1' + fs.readFile file, (error) -> + assert.equal error.code, 'ENOTDIR' + fs.lstat file, (error) -> + assert.equal error.code, 'ENOTDIR' + fs.realpath file, (error) -> + assert.equal error.code, 'ENOTDIR' + fs.readdir dir, (error) -> + assert.equal error.code, 'ENOTDIR' + done() + + it 'treats *.asar as normal file', -> + originalFs = require 'original-fs' + asar = path.join fixtures, 'asar', 'a.asar' + content1 = fs.readFileSync asar + content2 = originalFs.readFileSync asar + assert.equal content1.compare(content2), 0 + assert.throws (-> fs.readdirSync asar), /ENOTDIR/ + describe 'asar protocol', -> url = require 'url' From 165b464a1503e8a03e761e8a0549848724b2b699 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 13:43:52 +0800 Subject: [PATCH 079/411] docs: process.noAsar --- docs/api/process.md | 9 ++++++++- docs/tutorial/application-packaging.md | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/api/process.md b/docs/api/process.md index 22fe452b0c..620ad6dc93 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -31,11 +31,18 @@ process.once('loaded', function() { }); ``` +## Properties + +### `process.noAsar` + +Setting this to `true` can disable the support for `asar` archives in Node's +built-in modules. + ## Methods The `process` object has the following method: -### `process.hang` +### `process.hang()` Causes the main thread of the current process hang. diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index c6e0ae3c41..b42a2f9298 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -103,6 +103,14 @@ var originalFs = require('original-fs'); originalFs.readFileSync('/path/to/example.asar'); ``` +You can also set `process.noAsar` to `true` to disable the support for `asar` in +the `fs` module: + +```javascript +process.noAsar = true; +fs.readFileSync('/path/to/example.asar'); +``` + ## Limitations on Node API Even though we tried hard to make `asar` archives in the Node API work like From 0f17a0163d16f8cdc2a032ebd96a3034525c0271 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 16:21:15 +0800 Subject: [PATCH 080/411] Put common constants in atom_constants --- atom/browser/net/js_asker.cc | 2 -- atom/browser/net/js_asker.h | 3 --- atom/browser/net/url_request_async_asar_job.cc | 5 +++-- atom/browser/net/url_request_buffer_job.cc | 3 ++- atom/browser/net/url_request_string_job.cc | 3 ++- atom/common/atom_constants.cc | 11 +++++++++++ atom/common/atom_constants.h | 15 +++++++++++++++ filenames.gypi | 2 ++ 8 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 atom/common/atom_constants.cc create mode 100644 atom/common/atom_constants.h diff --git a/atom/browser/net/js_asker.cc b/atom/browser/net/js_asker.cc index 0e232feffb..8f0d1d2b95 100644 --- a/atom/browser/net/js_asker.cc +++ b/atom/browser/net/js_asker.cc @@ -11,8 +11,6 @@ namespace atom { -const std::string kCorsHeader("Access-Control-Allow-Origin: *"); - namespace internal { namespace { diff --git a/atom/browser/net/js_asker.h b/atom/browser/net/js_asker.h index b353b98fa7..8ec245ee8c 100644 --- a/atom/browser/net/js_asker.h +++ b/atom/browser/net/js_asker.h @@ -5,8 +5,6 @@ #ifndef ATOM_BROWSER_NET_JS_ASKER_H_ #define ATOM_BROWSER_NET_JS_ASKER_H_ -#include - #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" @@ -19,7 +17,6 @@ #include "v8/include/v8.h" namespace atom { -extern const std::string kCorsHeader; using JavaScriptHandler = base::Callback)>; diff --git a/atom/browser/net/url_request_async_asar_job.cc b/atom/browser/net/url_request_async_asar_job.cc index 46065259fc..3578f3b797 100644 --- a/atom/browser/net/url_request_async_asar_job.cc +++ b/atom/browser/net/url_request_async_asar_job.cc @@ -6,6 +6,8 @@ #include +#include "atom/common/atom_constants.h" + namespace atom { URLRequestAsyncAsarJob::URLRequestAsyncAsarJob( @@ -36,12 +38,11 @@ void URLRequestAsyncAsarJob::StartAsync(scoped_ptr options) { } } - void URLRequestAsyncAsarJob::GetResponseInfo(net::HttpResponseInfo* info) { std::string status("HTTP/1.1 200 OK"); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); - headers->AddHeader(kCorsHeader); + headers->AddHeader(kCORSHeader); info->headers = headers; } diff --git a/atom/browser/net/url_request_buffer_job.cc b/atom/browser/net/url_request_buffer_job.cc index 55603e77e0..aa273bf816 100644 --- a/atom/browser/net/url_request_buffer_job.cc +++ b/atom/browser/net/url_request_buffer_job.cc @@ -6,6 +6,7 @@ #include +#include "atom/common/atom_constants.h" #include "base/strings/string_number_conversions.h" #include "net/base/net_errors.h" @@ -50,7 +51,7 @@ void URLRequestBufferJob::GetResponseInfo(net::HttpResponseInfo* info) { status.append("\0\0", 2); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); - headers->AddHeader(kCorsHeader); + headers->AddHeader(kCORSHeader); if (!mime_type_.empty()) { std::string content_type_header(net::HttpRequestHeaders::kContentType); diff --git a/atom/browser/net/url_request_string_job.cc b/atom/browser/net/url_request_string_job.cc index 6a12026b2d..606781142d 100644 --- a/atom/browser/net/url_request_string_job.cc +++ b/atom/browser/net/url_request_string_job.cc @@ -6,6 +6,7 @@ #include +#include "atom/common/atom_constants.h" #include "net/base/net_errors.h" namespace atom { @@ -32,7 +33,7 @@ void URLRequestStringJob::GetResponseInfo(net::HttpResponseInfo* info) { std::string status("HTTP/1.1 200 OK"); net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); - headers->AddHeader(kCorsHeader); + headers->AddHeader(kCORSHeader); if (!mime_type_.empty()) { std::string content_type_header(net::HttpRequestHeaders::kContentType); diff --git a/atom/common/atom_constants.cc b/atom/common/atom_constants.cc new file mode 100644 index 0000000000..dacda3c816 --- /dev/null +++ b/atom/common/atom_constants.cc @@ -0,0 +1,11 @@ +// 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/common/atom_constants.h" + +namespace atom { + +const char* kCORSHeader = "Access-Control-Allow-Origin: *"; + +} // namespace atom diff --git a/atom/common/atom_constants.h b/atom/common/atom_constants.h new file mode 100644 index 0000000000..e0d42e83ee --- /dev/null +++ b/atom/common/atom_constants.h @@ -0,0 +1,15 @@ +// 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_COMMON_ATOM_CONSTANTS_H_ +#define ATOM_COMMON_ATOM_CONSTANTS_H_ + +namespace atom { + +// Header to ignore CORS. +extern const char* kCORSHeader; + +} // namespace atom + +#endif // ATOM_COMMON_ATOM_CONSTANTS_H_ diff --git a/filenames.gypi b/filenames.gypi index 7157079178..151a69ff1c 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -286,6 +286,8 @@ 'atom/common/asar/scoped_temporary_file.h', 'atom/common/atom_command_line.cc', 'atom/common/atom_command_line.h', + 'atom/common/atom_constants.cc', + 'atom/common/atom_constants.h', 'atom/common/common_message_generator.cc', 'atom/common/common_message_generator.h', 'atom/common/crash_reporter/crash_reporter.cc', From 1b3eb1cc5df74ed2dac3f8e7e0fea17497ddda28 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 16:55:52 +0800 Subject: [PATCH 081/411] Delay the did-fail-provisional-load event to next tick Chrome is doing some stuff after the DidFailProvisionalLoad event, if we call LoadURL at this time crash would happen. --- atom/browser/api/atom_api_web_contents.cc | 5 ++--- atom/browser/api/lib/web-contents.coffee | 6 ++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 066ca9cc7b..339be85fe9 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -460,14 +460,13 @@ void WebContents::DidFinishLoad(content::RenderFrameHost* render_frame_host, Emit("did-finish-load"); } -// this error occurs when host could not be found void WebContents::DidFailProvisionalLoad( content::RenderFrameHost* render_frame_host, - const GURL& validated_url, + const GURL& url, int error_code, const base::string16& error_description, bool was_ignored_by_handler) { - Emit("did-fail-load", error_code, error_description, validated_url); + Emit("did-fail-provisional-load", error_code, error_description, url); } void WebContents::DidFailLoad(content::RenderFrameHost* render_frame_host, diff --git a/atom/browser/api/lib/web-contents.coffee b/atom/browser/api/lib/web-contents.coffee index 335928dcea..45545fe248 100644 --- a/atom/browser/api/lib/web-contents.coffee +++ b/atom/browser/api/lib/web-contents.coffee @@ -70,6 +70,12 @@ wrapWebContents = (webContents) -> menu = Menu.buildFromTemplate params.menu menu.popup params.x, params.y + # This error occurs when host could not be found. + webContents.on 'did-fail-provisional-load', (args...) -> + # Calling loadURL during this event might cause crash, so delay the event + # until next tick. + setImmediate => @emit 'did-fail-load', args... + # Deprecated. deprecate.rename webContents, 'loadUrl', 'loadURL' deprecate.rename webContents, 'getUrl', 'getURL' From 8d20dda6d77370b962eddae38e4fa0c42bad08ba Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 17:51:09 +0800 Subject: [PATCH 082/411] No need to override TitleWasSet in NativeWindow --- atom/browser/api/atom_api_window.cc | 5 ----- atom/browser/api/atom_api_window.h | 2 -- atom/browser/api/lib/browser-window.coffee | 5 +++++ atom/browser/native_window.cc | 11 ----------- atom/browser/native_window.h | 1 - atom/browser/native_window_observer.h | 4 ---- 6 files changed, 5 insertions(+), 23 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index a5aa4a1266..5696405544 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -161,11 +161,6 @@ Window::~Window() { Destroy(); } -void Window::OnPageTitleUpdated(bool* prevent_default, - const std::string& title) { - *prevent_default = Emit("page-title-updated", title); -} - void Window::WillCloseWindow(bool* prevent_default) { *prevent_default = Emit("close"); } diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 757abd205b..a04e00cfb4 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -54,8 +54,6 @@ class Window : public mate::TrackableObject, virtual ~Window(); // NativeWindowObserver: - void OnPageTitleUpdated(bool* prevent_default, - const std::string& title) override; void WillCloseWindow(bool* prevent_default) override; void OnWindowClosed() override; void OnWindowBlur() override; diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee index d693a6d934..84c7690867 100644 --- a/atom/browser/api/lib/browser-window.coffee +++ b/atom/browser/api/lib/browser-window.coffee @@ -31,6 +31,11 @@ BrowserWindow::_init = -> @webContents.on 'crashed', => @emit 'crashed' + # Change window title to page title. + @webContents.on 'page-title-set', (event, title, explicitSet) => + @emit 'page-title-updated', event, title + @setTitle title unless event.defaultPrevented + # Sometimes the webContents doesn't get focus when window is shown, so we have # to force focusing on webContents in this case. The safest way is to focus it # when we first start to load URL, if we do it earlier it won't have effect, diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index ad7a6c4b06..a3df240e4d 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -505,17 +505,6 @@ void NativeWindow::BeforeUnloadDialogCancelled() { window_unresposive_closure_.Cancel(); } -void NativeWindow::TitleWasSet(content::NavigationEntry* entry, - bool explicit_set) { - bool prevent_default = false; - std::string text = entry ? base::UTF16ToUTF8(entry->GetTitle()) : ""; - FOR_EACH_OBSERVER(NativeWindowObserver, - observers_, - OnPageTitleUpdated(&prevent_default, text)); - if (!prevent_default && !is_closed_) - SetTitle(text); -} - bool NativeWindow::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(NativeWindow, message) diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index e32b948118..0c918d92df 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -262,7 +262,6 @@ class NativeWindow : public base::SupportsUserData, // content::WebContentsObserver: void RenderViewCreated(content::RenderViewHost* render_view_host) override; void BeforeUnloadDialogCancelled() override; - void TitleWasSet(content::NavigationEntry* entry, bool explicit_set) override; bool OnMessageReceived(const IPC::Message& message) override; private: diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 54004a300d..ce2d41c6fa 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -21,10 +21,6 @@ class NativeWindowObserver { public: virtual ~NativeWindowObserver() {} - // Called when the web page of the window has updated it's document title. - virtual void OnPageTitleUpdated(bool* prevent_default, - const std::string& title) {} - // Called when the web page in window wants to create a popup window. virtual void WillCreatePopupWindow(const base::string16& frame_name, const GURL& target_url, From 83ee78451ad9d861deb0f14634f6c3fc2300e5d1 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 17:52:13 +0800 Subject: [PATCH 083/411] Emit event when title becomes empty --- atom/browser/api/atom_api_web_contents.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 339be85fe9..0c8a5ff3df 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -519,9 +519,10 @@ void WebContents::DidNavigateMainFrame( void WebContents::TitleWasSet(content::NavigationEntry* entry, bool explicit_set) { - // Back/Forward navigation may have pruned entries. if (entry) Emit("page-title-set", entry->GetTitle(), explicit_set); + else + Emit("page-title-set", "", explicit_set); } void WebContents::DidUpdateFaviconURL( From c95117fb227f438b8dc622f124fb9096f6a915ed Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 18:34:58 +0800 Subject: [PATCH 084/411] Delay the page-title-set event to next tick --- atom/browser/api/atom_api_web_contents.cc | 4 ++-- atom/browser/api/lib/web-contents.coffee | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 0c8a5ff3df..b12b3fea2b 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -520,9 +520,9 @@ void WebContents::DidNavigateMainFrame( void WebContents::TitleWasSet(content::NavigationEntry* entry, bool explicit_set) { if (entry) - Emit("page-title-set", entry->GetTitle(), explicit_set); + Emit("-page-title-set", entry->GetTitle(), explicit_set); else - Emit("page-title-set", "", explicit_set); + Emit("-page-title-set", "", explicit_set); } void WebContents::DidUpdateFaviconURL( diff --git a/atom/browser/api/lib/web-contents.coffee b/atom/browser/api/lib/web-contents.coffee index 45545fe248..dbc9a15fc2 100644 --- a/atom/browser/api/lib/web-contents.coffee +++ b/atom/browser/api/lib/web-contents.coffee @@ -76,6 +76,10 @@ wrapWebContents = (webContents) -> # until next tick. setImmediate => @emit 'did-fail-load', args... + # Delays the page-title-set event to next tick. + webContents.on '-page-title-set', (args...) -> + setImmediate => @emit 'page-title-set', args... + # Deprecated. deprecate.rename webContents, 'loadUrl', 'loadURL' deprecate.rename webContents, 'getUrl', 'getURL' From e5974e44ed5584db59eac5318b5d0b2dbd1dba7e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 1 Dec 2015 18:50:56 +0800 Subject: [PATCH 085/411] Deprecate the page-title-set event We have two names for the same event, page-title-updated wins. --- atom/browser/api/atom_api_web_contents.cc | 4 ++-- atom/browser/api/lib/browser-window.coffee | 2 +- atom/browser/api/lib/web-contents.coffee | 8 +++++--- atom/browser/lib/guest-view-manager.coffee | 2 +- .../lib/web-view/guest-view-internal.coffee | 20 +++++++++++-------- docs/api/web-view-tag.md | 6 +++--- 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index b12b3fea2b..f377e8fda9 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -520,9 +520,9 @@ void WebContents::DidNavigateMainFrame( void WebContents::TitleWasSet(content::NavigationEntry* entry, bool explicit_set) { if (entry) - Emit("-page-title-set", entry->GetTitle(), explicit_set); + Emit("-page-title-updated", entry->GetTitle(), explicit_set); else - Emit("-page-title-set", "", explicit_set); + Emit("-page-title-updated", "", explicit_set); } void WebContents::DidUpdateFaviconURL( diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee index 84c7690867..07b4191d7c 100644 --- a/atom/browser/api/lib/browser-window.coffee +++ b/atom/browser/api/lib/browser-window.coffee @@ -32,7 +32,7 @@ BrowserWindow::_init = -> @emit 'crashed' # Change window title to page title. - @webContents.on 'page-title-set', (event, title, explicitSet) => + @webContents.on 'page-title-updated', (event, title, explicitSet) => @emit 'page-title-updated', event, title @setTitle title unless event.defaultPrevented diff --git a/atom/browser/api/lib/web-contents.coffee b/atom/browser/api/lib/web-contents.coffee index dbc9a15fc2..dacbc919d6 100644 --- a/atom/browser/api/lib/web-contents.coffee +++ b/atom/browser/api/lib/web-contents.coffee @@ -76,13 +76,15 @@ wrapWebContents = (webContents) -> # until next tick. setImmediate => @emit 'did-fail-load', args... - # Delays the page-title-set event to next tick. - webContents.on '-page-title-set', (args...) -> - setImmediate => @emit 'page-title-set', args... + # Delays the page-title-updated event to next tick. + webContents.on '-page-title-updated', (args...) -> + setImmediate => @emit 'page-title-updated', args... # Deprecated. deprecate.rename webContents, 'loadUrl', 'loadURL' deprecate.rename webContents, 'getUrl', 'getURL' + deprecate.event webContents, 'page-title-set', 'page-title-updated', (args...) -> + @emit 'page-title-set', args... webContents.printToPDF = (options, callback) -> printingSetting = diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index e6be05a907..d4bf55158f 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -19,7 +19,7 @@ supportedWebViewEvents = [ 'gpu-crashed' 'plugin-crashed' 'destroyed' - 'page-title-set' + 'page-title-updated' 'page-favicon-updated' 'enter-html-full-screen' 'leave-html-full-screen' diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 61a93d8cb6..b28fec23ed 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -21,23 +21,27 @@ WEB_VIEW_EVENTS = 'gpu-crashed': [] 'plugin-crashed': ['name', 'version'] 'destroyed': [] - 'page-title-set': ['title', 'explicitSet'] + 'page-title-updated': ['title', 'explicitSet'] 'page-favicon-updated': ['favicons'] 'enter-html-full-screen': [] 'leave-html-full-screen': [] -dispatchEvent = (webView, event, args...) -> - throw new Error("Unknown event #{event}") unless WEB_VIEW_EVENTS[event]? - domEvent = new Event(event) - for f, i in WEB_VIEW_EVENTS[event] +DEPRECATED_EVENTS = + 'page-title-updated': 'page-title-set' + +dispatchEvent = (webView, eventName, eventKey, args...) -> + if DEPRECATED_EVENTS[eventName]? + dispatchEvent webView, DEPRECATED_EVENTS[eventName], eventKey, args... + domEvent = new Event(eventName) + for f, i in WEB_VIEW_EVENTS[eventKey] domEvent[f] = args[i] webView.dispatchEvent domEvent - webView.onLoadCommit domEvent if event == 'load-commit' + webView.onLoadCommit domEvent if eventName is 'load-commit' module.exports = registerEvents: (webView, viewInstanceId) -> - ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}", (event, domEvent, args...) -> - dispatchEvent webView, domEvent, args... + ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-#{viewInstanceId}", (event, eventName, args...) -> + dispatchEvent webView, eventName, eventName, args... ipcRenderer.on "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}", (event, channel, args...) -> domEvent = new Event('ipc-message') diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 47a3050a04..56909a52e3 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -452,15 +452,15 @@ Fired when a redirect was received while requesting a resource. Fired when document in the given frame is loaded. -### Event: 'page-title-set' +### Event: 'page-title-updated' Returns: * `title` String * `explicitSet` Boolean -Fired when page title is set during navigation. `explicitSet` is false when title is synthesised from file -url. +Fired when page title is set during navigation. `explicitSet` is false when +title is synthesised from file url. ### Event: 'page-favicon-updated' From a99c193cf2d783308a0777e6c6ee606590bfa263 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Tue, 1 Dec 2015 11:57:32 -0400 Subject: [PATCH 086/411] :checkered_flag: Preserve file extension when extracting from asar Currently, when calling `copyFileOut`, the original extension from the file is lost, and a generic `*.tmp` is added instead. This becomes problematic in the scenario where we use `child_process.execFile` on a Windows Batch script that lives inside the `asar` package. Windows relies on the extension being present in order to interpret the script accordingly, which results in the following bug because the operating system doesn't know what do to with this `*.tmp` file: ``` Error: spawn UNKNOWN ``` Steps to reproduce: 1. Create a dummy batch script (test.bat): ``` @echo off echo "Hello world" ``` 2. Create an electron app that attemps to call this script with `child_process.execFile`: ```js var child_process = require('child_process'); var path = require('path'); child_process.execFile(path.join(__dirname, 'test.bat'), function(error, stdout) { if (error) throw error; console.log(stdout); }); ``` 3. Package this small application as an asar archive: ```sh > asar pack mytestapp app.asar ``` 4. Execute the application: ```sh > electron.exe app.asar ``` --- atom/common/asar/archive.cc | 3 ++- atom/common/asar/scoped_temporary_file.cc | 14 +++++++++++--- atom/common/asar/scoped_temporary_file.h | 8 +++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index ab93e301b1..ebb80cc2c4 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -272,7 +272,8 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { } scoped_ptr temp_file(new ScopedTemporaryFile); - if (!temp_file->InitFromFile(&file_, info.offset, info.size)) + base::FilePath::StringType ext = path.Extension(); + if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size)) return false; #if defined(OS_POSIX) diff --git a/atom/common/asar/scoped_temporary_file.cc b/atom/common/asar/scoped_temporary_file.cc index 6fccc9434f..2cc51991e1 100644 --- a/atom/common/asar/scoped_temporary_file.cc +++ b/atom/common/asar/scoped_temporary_file.cc @@ -28,20 +28,28 @@ ScopedTemporaryFile::~ScopedTemporaryFile() { } } -bool ScopedTemporaryFile::Init() { +bool ScopedTemporaryFile::Init(const base::FilePath::StringType ext) { if (!path_.empty()) return true; base::ThreadRestrictions::ScopedAllowIO allow_io; - return base::CreateTemporaryFile(&path_); + + base::FilePath temporaryPath_; + if (!base::CreateTemporaryFile(&temporaryPath_)) { + return false; + } + + path_ = temporaryPath_.AddExtension(ext); + return base::Move(temporaryPath_, path_); } bool ScopedTemporaryFile::InitFromFile(base::File* src, + const base::FilePath::StringType ext, uint64 offset, uint64 size) { if (!src->IsValid()) return false; - if (!Init()) + if (!Init(ext)) return false; std::vector buf(size); diff --git a/atom/common/asar/scoped_temporary_file.h b/atom/common/asar/scoped_temporary_file.h index ffaee22e51..c0804a4e6e 100644 --- a/atom/common/asar/scoped_temporary_file.h +++ b/atom/common/asar/scoped_temporary_file.h @@ -22,11 +22,13 @@ class ScopedTemporaryFile { ScopedTemporaryFile(); virtual ~ScopedTemporaryFile(); - // Init an empty temporary file. - bool Init(); + // Init an empty temporary file with a certain extension. + bool Init(const base::FilePath::StringType ext); // Init an temporary file and fill it with content of |path|. - bool InitFromFile(base::File* src, uint64 offset, uint64 size); + bool InitFromFile(base::File* src, + const base::FilePath::StringType ext, + uint64 offset, uint64 size); base::FilePath path() const { return path_; } From 1b1c4bec4e4634087bb17fb96a8e60098329dabc Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Tue, 1 Dec 2015 21:09:20 -0200 Subject: [PATCH 087/411] :memo: [ci skip] Add translation to auto-updater, change links --- docs-translations/pt-BR/README.md | 4 +- docs-translations/pt-BR/api/auto-updater.md | 85 +++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 docs-translations/pt-BR/api/auto-updater.md diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md index 61202f8ca4..f458e51da8 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -40,8 +40,8 @@ selecione a *tag* que corresponde à sua versão. ### Módulos para o Processo Principal: -* [app](../../docs/api/app.md) -* [autoUpdater](../../docs/api/auto-updater.md) +* [app](api/app.md) +* [autoUpdater](api/auto-updater.md) * [BrowserWindow](../../docs/api/browser-window.md) * [contentTracing](../../docs/api/content-tracing.md) * [dialog](../../docs/api/dialog.md) diff --git a/docs-translations/pt-BR/api/auto-updater.md b/docs-translations/pt-BR/api/auto-updater.md new file mode 100644 index 0000000000..1c27a4b779 --- /dev/null +++ b/docs-translations/pt-BR/api/auto-updater.md @@ -0,0 +1,85 @@ +# autoUpdater + +Este módulo oferece uma interface para o framework de atualização automática `Squirrel`. + +## Notificações de Plataforma + +Embora o `autoUpdater` ofereça uma API uniforme para diferentes plataformas, existem diferenças sutis em cada plataforma. + +### OS X + +No OS X, o módulo `autoUpdater` é construído sobre o [Squirrel.Mac][squirrel-mac], o que significa que você não precisa de nenhuma configuração especial para fazê-lo funcionar. Para requerimentos de servidor, você pode ler [Server Support][server-support]. + +### Windows + +No Windows, você deve instalar seu aplicativo na máquina de um usuário antes que possa usar o auto-updater, então é recomendado utilizar o módulo [grunt-electron-installer][installer] para gerar um instalador do Windows. + +O instalador gerado com Squirrel irá criar um ícone de atalho com um [Application User Model ID][app-user-model-id] no formato `com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`, por exemplo: `com.squirrel.slack.Slack` e `com.squirrel.code.Code`. Você precisa usar o mesmo ID para seu aplicativo a API `app.setAppUserModelId`, senão o Windows não conseguirá fixar seu aplicativo corretamente na barra de tarefas. + +A configuração do servidor também é diferente do OS X. Você pode ler a documentação do [Squirrel.Windows][squirrel-windows] para mais detalhes. + +### Linux + +Não há suporte nativo do auto-updater para Linux, então é recomendado utilizar o gerenciador de pacotes da distribuição para atualizar seu aplicativo. + +## Eventos + +O objeto `autoUpdater` emite os seguintes eventos: + +### Evento: 'error' + +Retorna: + +* `error` Error + +Emitido quando há um erro durante a atualização. + +### Evento: 'checking-for-update' + +Emitido quando está verificando se uma atualização foi inicializada. + +### Evento: 'update-available' + +Emitido quando há uma atualização disponível. A autalização é baixada automaticamente. + +### Evento: 'update-not-available' + +Emitido quando não há uma atualização disponível. + +### Evento: 'update-downloaded' + +Retorna: + +* `event` Event +* `releaseNotes` String +* `releaseName` String +* `releaseDate` Date +* `updateURL` String + +Emitido quando uma atualização foi baixada. + +No Windows apenas `releaseName` está disponível. + +## Métodos + +O objeto `autoUpdater` possui os seguintes métodos: + +### `autoUpdater.setFeedURL(url)` + +* `url` String + +Define a `url` e inicializa o auto-updater. A `url` não pode ser alterada uma vez que foi definida. + +### `autoUpdater.checkForUpdates()` + +Pergunta ao servidor se há uma atualização. Você deve chamar `setFeedURL` antes de usar esta API. + +### `autoUpdater.quitAndInstall()` + +Reinicia o aplicativo e instala a atualização após esta ter sido baixada. Só deve ser chamado após o `update-downloaded` ter sido emitido. + +[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac +[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support +[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows +[installer]: https://github.com/atom/grunt-electron-installer +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx From 229dc02a41b06f47285746f6de3350795d36e8f3 Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Tue, 1 Dec 2015 21:34:35 -0200 Subject: [PATCH 088/411] :memo: [ci skip] Update to match english docs, fix typos --- docs-translations/pt-BR/api/process.md | 40 +++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/docs-translations/pt-BR/api/process.md b/docs-translations/pt-BR/api/process.md index 3da0dc5839..1c20e2df11 100644 --- a/docs-translations/pt-BR/api/process.md +++ b/docs-translations/pt-BR/api/process.md @@ -1,22 +1,48 @@ # process -O objeto `process` no Electron tem as seguintes diferenças de um upstream node: +O objeto `process` no Electron tem as seguintes diferenças do objeto no upstream node: -* `process.type` String - Tipo de processo, pode ser `browser` (i.e. main process) +* `process.type` String - Tipo de processo, pode ser `browser` (processo principal) ou `renderer`. * `process.versions['electron']` String - Versão do Electron. * `process.versions['chrome']` String - Versão do Chromium. -* `process.resourcesPath` String - Caminho para os códigos fontes JavaScript. +* `process.resourcesPath` String - Caminho para o código fonte JavaScript. +* `process.mas` Boolean - Para build da Mac App Store, este valor é `true`, para outros builds é `undefined`. + +## Eventos + +### Evento: 'loaded' + +Emitido quando o Electron carregou seu script de inicialização interno e está começando a carregar a página web ou o script principal. + +Pode ser utilizado pelo script pré-carregamento (preload.js abaixo) para adicionar símbolos globais do Node removidos para o escopo global quando a integração do node é desligada: + +```js +// preload.js +var _setImmediate = setImmediate; +var _clearImmediate = clearImmediate; +process.once('loaded', function() { + global.setImmediate = _setImmediate; + global.clearImmediate = _clearImmediate; +}); +``` + +## Propriedades + +### `process.noAsar` + +Definir isto para `true` pode desabilitar o suporte para arquivos `asar` nos módulos nativos do Node. # Métodos -O objeto `process` tem os seguintes método: + +O objeto `process` tem os seguintes métodos: ### `process.hang` -Afeta a thread principal do processo atual. +Faz com que o *thread* principal do processo congele. -## process.setFdLimit(MaxDescritores) _OS X_ _Linux_ +### `process.setFdLimit(maxDescriptors)` _OS X_ _Linux_ * `maxDescriptors` Integer Define o limite do arquivo descritor para `maxDescriptors` ou para o limite do OS, -o que for menor para o processo atual. \ No newline at end of file +o que for menor para o processo atual. From d76e21853b6a35552dec7d933dbf8e9aa4cfebeb Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Tue, 1 Dec 2015 21:38:25 -0200 Subject: [PATCH 089/411] Change header --- docs-translations/pt-BR/api/auto-updater.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/pt-BR/api/auto-updater.md b/docs-translations/pt-BR/api/auto-updater.md index 1c27a4b779..27c3ef3c07 100644 --- a/docs-translations/pt-BR/api/auto-updater.md +++ b/docs-translations/pt-BR/api/auto-updater.md @@ -2,7 +2,7 @@ Este módulo oferece uma interface para o framework de atualização automática `Squirrel`. -## Notificações de Plataforma +## Avisos sobre Plataformas Embora o `autoUpdater` ofereça uma API uniforme para diferentes plataformas, existem diferenças sutis em cada plataforma. From 9a93ecc3cfe74f6d953751dfc75059fcb81b9feb Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Tue, 1 Dec 2015 21:46:13 -0200 Subject: [PATCH 090/411] :memo: [ci skip] fix typos --- docs-translations/pt-BR/api/accelerator.md | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs-translations/pt-BR/api/accelerator.md b/docs-translations/pt-BR/api/accelerator.md index 87987258b1..92d70c844e 100644 --- a/docs-translations/pt-BR/api/accelerator.md +++ b/docs-translations/pt-BR/api/accelerator.md @@ -1,7 +1,7 @@ # Acelerador (teclas de atalhos) -Um acelerador é uma string que representa um atalho de tecla. Isso pode conter -multiplos modificadores e códigos chaves, combinado pelo caracter `+`. +Um acelerador é uma string que representa um atalho de tecla. Ele pode conter +múltiplos modificadores e códigos chaves, combinados pelo caractere `+`. Exemplos: @@ -11,13 +11,13 @@ Exemplos: ## Aviso sobre plataformas No Linux e no Windows a tecla `Command` não tem nenhum efeito, -então use `CommandOrControl` que representa a tecla `Command` existente no OSX e +então use `CommandOrControl` que representa a tecla `Command` existente no OS X e `Control` no Linux e no Windows para definir aceleradores (atalhos). A chave `Super` está mapeada para a tecla `Windows` para Windows e Linux, -e para a tecla `Cmd` para OSX. +e para a tecla `Cmd` para OS X. -## Modificadores disponiveis +## Modificadores disponíveis * `Command` (ou `Cmd` abreviado) * `Control` (ou `Ctrl` abreviado) @@ -26,21 +26,21 @@ e para a tecla `Cmd` para OSX. * `Shift` * `Super` -## Códigos chaves disponiveis +## Códigos chaves disponíveis -* `0` to `9` -* `A` to `Z` -* `F1` to `F24` -* Punctuations like `~`, `!`, `@`, `#`, `$`, etc. +* `0` até `9` +* `A` até `Z` +* `F1` até `F24` +* Pontuações como `~`, `!`, `@`, `#`, `$`, etc. * `Plus` * `Space` * `Backspace` * `Delete` * `Insert` -* `Return` (or `Enter` as alias) -* `Up`, `Down`, `Left` and `Right` -* `Home` and `End` -* `PageUp` and `PageDown` -* `Escape` (or `Esc` for short) -* `VolumeUp`, `VolumeDown` and `VolumeMute` -* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` and `MediaPlayPause` \ No newline at end of file +* `Return` (ou `Enter` como pseudônimo) +* `Up`, `Down`, `Left` e `Right` +* `Home` e `End` +* `PageUp` e `PageDown` +* `Escape` (ou `Esc` abreviado) +* `VolumeUp`, `VolumeDown` e `VolumeMute` +* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` e `MediaPlayPause` From 5cae8397ccaf6116be4606dc81da498fa529bc9d Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Tue, 1 Dec 2015 21:57:07 -0200 Subject: [PATCH 091/411] :memo: [ci skip] fix more typos --- docs-translations/pt-BR/api/shell.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs-translations/pt-BR/api/shell.md b/docs-translations/pt-BR/api/shell.md index 7c3f24ade5..65e0a2d426 100644 --- a/docs-translations/pt-BR/api/shell.md +++ b/docs-translations/pt-BR/api/shell.md @@ -1,11 +1,11 @@ # shell -O módulo `shell` fornece funções relacionadas intereções com o OS do usuário. +O módulo `shell` fornece funções relacionadas à integração com o desktop. Um exemplo para abrir uma URL no browser padrão do usuário: ```javascript -var shell = require('shell'); +const shell = require('shell'); shell.openExternal('https://github.com'); ``` @@ -17,26 +17,26 @@ O módulo `shell` tem os seguintes métodos: * `fullPath` String -Exibe o arquivo no gerenciador de arquivos padrão do sistema. Se possivel, seleciona o arquivo automaticamente. +Exibe o arquivo num gerenciador de arquivos. Se possivel, seleciona o arquivo. ### `shell.openItem(fullPath)` * `fullPath` String -Abre o arquivo em seu programa padrão. +Abre o arquivo de maneira padrão do desktop. ### `shell.openExternal(url)` * `url` String -Abre o arquivo seguido de um protocol em seu programa padrão. (Por -exemplo, mailto:foo@bar.com.) +Abre a URL de protocolo externo de maneira padrão do desktop. (Por +exemplo, mailto: URLs no programa de email padrão do usuário) ### `shell.moveItemToTrash(fullPath)` * `fullPath` String -Move o arquivo para a lixeira e retorna um boolean com o resultado da operação. +Move o arquivo para a lixeira e retorna um status boolean com o resultado da operação. ### `shell.beep()` From c691094aa103ee7d036ba524623edd280371dc72 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 2 Dec 2015 11:00:28 +0800 Subject: [PATCH 092/411] spec: Fix failing tests on win32 --- spec/asar-spec.coffee | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index b7a62f8eea..495d89734e 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -424,6 +424,8 @@ describe 'asar package', -> assert.equal internalModuleReadFile(p).toString().trim(), 'a' describe 'process.noAsar', -> + errorName = if process.platform is 'win32' then 'ENOENT' else 'ENOTDIR' + beforeEach -> process.noAsar = true afterEach -> @@ -432,22 +434,22 @@ describe 'asar package', -> it 'disables asar support in sync API', -> file = path.join fixtures, 'asar', 'a.asar', 'file1' dir = path.join fixtures, 'asar', 'a.asar', 'dir1' - assert.throws (-> fs.readFileSync file), /ENOTDIR/ - assert.throws (-> fs.lstatSync file), /ENOTDIR/ - assert.throws (-> fs.realpathSync file), /ENOTDIR/ - assert.throws (-> fs.readdirSync dir), /ENOTDIR/ + assert.throws (-> fs.readFileSync file), new RegExp(errorName) + assert.throws (-> fs.lstatSync file), new RegExp(errorName) + assert.throws (-> fs.realpathSync file), new RegExp(errorName) + assert.throws (-> fs.readdirSync dir), new RegExp(errorName) it 'disables asar support in async API', (done) -> file = path.join fixtures, 'asar', 'a.asar', 'file1' dir = path.join fixtures, 'asar', 'a.asar', 'dir1' fs.readFile file, (error) -> - assert.equal error.code, 'ENOTDIR' + assert.equal error.code, errorName fs.lstat file, (error) -> - assert.equal error.code, 'ENOTDIR' + assert.equal error.code, errorName fs.realpath file, (error) -> - assert.equal error.code, 'ENOTDIR' + assert.equal error.code, errorName fs.readdir dir, (error) -> - assert.equal error.code, 'ENOTDIR' + assert.equal error.code, errorName done() it 'treats *.asar as normal file', -> From c3645e3f9532f71fea15dd5c6dad603954978afe Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 2 Dec 2015 11:04:47 +0800 Subject: [PATCH 093/411] Don't call Move if there is no need to move --- atom/common/asar/scoped_temporary_file.cc | 17 ++++++++++------- atom/common/asar/scoped_temporary_file.h | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/atom/common/asar/scoped_temporary_file.cc b/atom/common/asar/scoped_temporary_file.cc index 2cc51991e1..14f2ba8ef9 100644 --- a/atom/common/asar/scoped_temporary_file.cc +++ b/atom/common/asar/scoped_temporary_file.cc @@ -28,23 +28,26 @@ ScopedTemporaryFile::~ScopedTemporaryFile() { } } -bool ScopedTemporaryFile::Init(const base::FilePath::StringType ext) { +bool ScopedTemporaryFile::Init(const base::FilePath::StringType& ext) { if (!path_.empty()) return true; base::ThreadRestrictions::ScopedAllowIO allow_io; - base::FilePath temporaryPath_; - if (!base::CreateTemporaryFile(&temporaryPath_)) { + base::FilePath temp_path; + if (!base::CreateTemporaryFile(&temp_path)) return false; - } - path_ = temporaryPath_.AddExtension(ext); - return base::Move(temporaryPath_, path_); + if (ext.empty()) + return true; + + // Keep the original extension. + path_ = temp_path.AddExtension(ext); + return base::Move(temp_path, path_); } bool ScopedTemporaryFile::InitFromFile(base::File* src, - const base::FilePath::StringType ext, + const base::FilePath::StringType& ext, uint64 offset, uint64 size) { if (!src->IsValid()) return false; diff --git a/atom/common/asar/scoped_temporary_file.h b/atom/common/asar/scoped_temporary_file.h index c0804a4e6e..23660a2390 100644 --- a/atom/common/asar/scoped_temporary_file.h +++ b/atom/common/asar/scoped_temporary_file.h @@ -23,11 +23,11 @@ class ScopedTemporaryFile { virtual ~ScopedTemporaryFile(); // Init an empty temporary file with a certain extension. - bool Init(const base::FilePath::StringType ext); + bool Init(const base::FilePath::StringType& ext); // Init an temporary file and fill it with content of |path|. bool InitFromFile(base::File* src, - const base::FilePath::StringType ext, + const base::FilePath::StringType& ext, uint64 offset, uint64 size); base::FilePath path() const { return path_; } From c493bec0898b2cdc3ae20b2d41b19cdf3041f599 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 2 Dec 2015 11:36:29 +0800 Subject: [PATCH 094/411] Make sure temp file will be cleaned up when base::Move fails --- atom/common/asar/scoped_temporary_file.cc | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/atom/common/asar/scoped_temporary_file.cc b/atom/common/asar/scoped_temporary_file.cc index 14f2ba8ef9..6dd12782d8 100644 --- a/atom/common/asar/scoped_temporary_file.cc +++ b/atom/common/asar/scoped_temporary_file.cc @@ -33,17 +33,20 @@ bool ScopedTemporaryFile::Init(const base::FilePath::StringType& ext) { return true; base::ThreadRestrictions::ScopedAllowIO allow_io; - - base::FilePath temp_path; - if (!base::CreateTemporaryFile(&temp_path)) + if (!base::CreateTemporaryFile(&path_)) return false; - if (ext.empty()) - return true; - +#if defined(OS_WIN) // Keep the original extension. - path_ = temp_path.AddExtension(ext); - return base::Move(temp_path, path_); + if (!ext.empty()) { + base::FilePath new_path = path_.AddExtension(ext); + if (!base::Move(path_, new_path)) + return false; + path_ = new_path; + } +#endif + + return true; } bool ScopedTemporaryFile::InitFromFile(base::File* src, From 202475f5a98cf4530845b80c87cd6c218059dc82 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 2 Dec 2015 17:29:58 +0800 Subject: [PATCH 095/411] Deprecating a property with method of same name causes trouble Close #3511. --- atom/browser/api/lib/app.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index 4d1803a029..d0ec41c4d2 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -65,7 +65,6 @@ wrapDownloadItem = (downloadItem) -> deprecate.property downloadItem, 'url', 'getURL' deprecate.property downloadItem, 'filename', 'getFilename' deprecate.property downloadItem, 'mimeType', 'getMimeType' - deprecate.property downloadItem, 'hasUserGesture', 'hasUserGesture' deprecate.rename downloadItem, 'getUrl', 'getURL' downloadItemBindings._setWrapDownloadItem wrapDownloadItem From 95675996989e9675acd06d3fcc4fa30b615eadfc Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 2 Dec 2015 10:23:21 +0000 Subject: [PATCH 096/411] Update debugging-main-process.md --- docs/tutorial/debugging-main-process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index 38c6e61ffc..aa95ae312b 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -19,7 +19,7 @@ Like `--debug` but pauses the script on the first line. ## Use node-inspector for Debugging -__Note:__ Electron uses node v0.11.13, which currently doesn't work very well +__Note:__ Electron doesn't currently work very well with node-inspector, and the main process will crash if you inspect the `process` object under node-inspector's console. From 2fba05b5e70e5d71f89c350dd42d096bb0f71016 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 2 Dec 2015 18:43:11 +0800 Subject: [PATCH 097/411] Add `menu` parameter for Tray.popUpContextMenu --- atom/browser/api/atom_api_tray.cc | 4 +++- atom/browser/ui/tray_icon.cc | 3 ++- atom/browser/ui/tray_icon.h | 4 +++- atom/browser/ui/tray_icon_cocoa.h | 3 ++- atom/browser/ui/tray_icon_cocoa.mm | 3 ++- atom/browser/ui/win/notify_icon.cc | 5 +++-- atom/browser/ui/win/notify_icon.h | 3 ++- docs/api/tray.md | 8 ++++++-- 8 files changed, 23 insertions(+), 10 deletions(-) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 5e32657f00..1e1c7c304d 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -137,9 +137,11 @@ void Tray::DisplayBalloon(mate::Arguments* args, } void Tray::PopUpContextMenu(mate::Arguments* args) { + Menu* menu = nullptr; + args->GetNext(&menu); gfx::Point pos; args->GetNext(&pos); - tray_icon_->PopUpContextMenu(pos); + tray_icon_->PopUpContextMenu(pos, menu ? menu->model() : nullptr); } void Tray::SetContextMenu(mate::Arguments* args, Menu* menu) { diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 1696aab276..60923c2ad0 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -26,7 +26,8 @@ void TrayIcon::DisplayBalloon(const gfx::Image& icon, const base::string16& contents) { } -void TrayIcon::PopUpContextMenu(const gfx::Point& pos) { +void TrayIcon::PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) { } void TrayIcon::NotifyClicked(const gfx::Rect& bounds, int modifiers) { diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index bc29acd8a2..c80ff08d6a 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -47,7 +47,9 @@ class TrayIcon { const base::string16& title, const base::string16& contents); - virtual void PopUpContextMenu(const gfx::Point& pos); + // Popups the menu. + virtual void PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model); // Set the context menu for this icon. virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0; diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index 7781c93a1c..59e2241aa4 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -29,7 +29,8 @@ class TrayIconCocoa : public TrayIcon, void SetToolTip(const std::string& tool_tip) override; void SetTitle(const std::string& title) override; void SetHighlightMode(bool highlight) override; - void PopUpContextMenu(const gfx::Point& pos) override; + void PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; protected: diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index e25f8ab5c2..5005234ab1 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -338,7 +338,8 @@ void TrayIconCocoa::SetHighlightMode(bool highlight) { [status_item_view_ setHighlight:highlight]; } -void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos) { +void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) { [status_item_view_ popUpContextMenu]; } diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index b2ca4bceed..133ab068da 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -66,7 +66,7 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, return; } else if (!double_button_click) { // single right click if (menu_model_) - PopUpContextMenu(cursor_pos); + PopUpContextMenu(cursor_pos, menu_model_); else NotifyRightClicked(gfx::Rect(rect), modifiers); } @@ -142,7 +142,8 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, LOG(WARNING) << "Unable to create status tray balloon."; } -void NotifyIcon::PopUpContextMenu(const gfx::Point& pos) { +void NotifyIcon::PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) { // Returns if context menu isn't set. if (!menu_model_) return; diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index d368dec713..8ee600033e 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -52,7 +52,8 @@ class NotifyIcon : public TrayIcon { void DisplayBalloon(const gfx::Image& icon, const base::string16& title, const base::string16& contents) override; - void PopUpContextMenu(const gfx::Point& pos) override; + void PopUpContextMenu(const gfx::Point& pos, + ui::SimpleMenuModel* menu_model) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; private: diff --git a/docs/api/tray.md b/docs/api/tray.md index f230c324ec..08a43638be 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -187,12 +187,16 @@ when the tray icon is clicked. Defaults to true. Displays a tray balloon. -### `Tray.popUpContextMenu([position])` _OS X_ _Windows_ +### `Tray.popUpContextMenu([menu, position])` _OS X_ _Windows_ -* `position` Object (optional)- The pop up position. +* `menu` Menu (optional) +* `position` Object (optional) - The pop up position. * `x` Integer * `y` Integer +Popups the context menu of tray icon. When `menu` is passed, the `menu` will +showed instead of the tray's context menu. + The `position` is only available on Windows, and it is (0, 0) by default. ### `Tray.setContextMenu(menu)` From 3cdd0f35c7d501572164a13051c2bb7ea6ac808f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 2 Dec 2015 19:05:22 +0800 Subject: [PATCH 098/411] mac: Implement menu parameter --- atom/browser/api/atom_api_tray.cc | 4 ++-- atom/browser/ui/tray_icon_cocoa.mm | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 1e1c7c304d..3b1a3a6321 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -137,11 +137,11 @@ void Tray::DisplayBalloon(mate::Arguments* args, } void Tray::PopUpContextMenu(mate::Arguments* args) { - Menu* menu = nullptr; + mate::Handle menu; args->GetNext(&menu); gfx::Point pos; args->GetNext(&pos); - tray_icon_->PopUpContextMenu(pos, menu ? menu->model() : nullptr); + tray_icon_->PopUpContextMenu(pos, menu.IsEmpty() ? nullptr : menu->model()); } void Tray::SetContextMenu(mate::Arguments* args, Menu* menu) { diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 5005234ab1..997ac6fd31 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -23,6 +23,7 @@ const CGFloat kVerticalTitleMargin = 2; atom::TrayIconCocoa* trayIcon_; // weak AtomMenuController* menuController_; // weak BOOL isHighlightEnable_; + BOOL forceHighlight_; BOOL inMouseEventSequence_; base::scoped_nsobject image_; base::scoped_nsobject alternateImage_; @@ -39,6 +40,8 @@ const CGFloat kVerticalTitleMargin = 2; image_.reset([image copy]); trayIcon_ = icon; isHighlightEnable_ = YES; + forceHighlight_ = NO; + inMouseEventSequence_ = NO; if ((self = [super initWithFrame: CGRectZero])) { // Setup the image view. @@ -238,7 +241,19 @@ const CGFloat kVerticalTitleMargin = 2; [self setNeedsDisplay:YES]; } -- (void)popUpContextMenu { +- (void)popUpContextMenu:(ui::SimpleMenuModel*)menu_model { + // Show a custom menu. + if (menu_model) { + base::scoped_nsobject menuController( + [[AtomMenuController alloc] initWithModel:menu_model]); + forceHighlight_ = YES; // Should highlight when showing menu. + [self setNeedsDisplay:YES]; + [statusItem_ popUpStatusItemMenu:[menuController menu]]; + forceHighlight_ = NO; + [self setNeedsDisplay:YES]; + return; + } + if (menuController_ && ![menuController_ isMenuOpen]) { // Redraw the dray icon to show highlight if it is enabled. [self setNeedsDisplay:YES]; @@ -288,6 +303,8 @@ const CGFloat kVerticalTitleMargin = 2; } - (BOOL)shouldHighlight { + if (isHighlightEnable_ && forceHighlight_) + return true; BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen]; return isHighlightEnable_ && (inMouseEventSequence_ || isMenuOpen); } @@ -340,7 +357,7 @@ void TrayIconCocoa::SetHighlightMode(bool highlight) { void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos, ui::SimpleMenuModel* menu_model) { - [status_item_view_ popUpContextMenu]; + [status_item_view_ popUpContextMenu:menu_model]; } void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) { From 615ce45849db3d0c2db785d3dce8e2450d9e197c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 2 Dec 2015 19:58:10 +0800 Subject: [PATCH 099/411] win: Implement menu parameter --- atom/browser/ui/win/notify_icon.cc | 21 +++++++++++---------- atom/browser/ui/win/notify_icon.h | 3 +-- atom/browser/ui/win/notify_icon_host.cc | 4 ---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 133ab068da..1ac29f1360 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -13,6 +13,7 @@ #include "ui/gfx/image/image.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" +#include "ui/gfx/screen.h" #include "ui/views/controls/menu/menu_runner.h" namespace atom { @@ -45,8 +46,7 @@ NotifyIcon::~NotifyIcon() { Shell_NotifyIcon(NIM_DELETE, &icon_data); } -void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, - int modifiers, +void NotifyIcon::HandleClickEvent(int modifiers, bool left_mouse_click, bool double_button_click) { NOTIFYICONIDENTIFIER icon_id; @@ -66,7 +66,7 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, return; } else if (!double_button_click) { // single right click if (menu_model_) - PopUpContextMenu(cursor_pos, menu_model_); + PopUpContextMenu(gfx::Point(), menu_model_); else NotifyRightClicked(gfx::Rect(rect), modifiers); } @@ -145,22 +145,23 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, void NotifyIcon::PopUpContextMenu(const gfx::Point& pos, ui::SimpleMenuModel* menu_model) { // Returns if context menu isn't set. - if (!menu_model_) + if (!menu_model) return; // Set our window as the foreground window, so the context menu closes when // we click away from it. if (!SetForegroundWindow(window_)) return; + // Show menu at mouse's position by default. + gfx::Rect rect(pos, gfx::Size()); + if (pos.IsOrigin()) + rect.set_origin(gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); + views::MenuRunner menu_runner( - menu_model_, + menu_model, views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); ignore_result(menu_runner.RunMenuAt( - NULL, - NULL, - gfx::Rect(pos, gfx::Size()), - views::MENU_ANCHOR_TOPLEFT, - ui::MENU_SOURCE_MOUSE)); + NULL, NULL, rect, views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_MOUSE)); } void NotifyIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index 8ee600033e..23608c7c7a 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -33,8 +33,7 @@ class NotifyIcon : public TrayIcon { // Handles a click event from the user - if |left_button_click| is true and // there is a registered observer, passes the click event to the observer, // otherwise displays the context menu if there is one. - void HandleClickEvent(const gfx::Point& cursor_pos, - int modifiers, + void HandleClickEvent(int modifiers, bool left_button_click, bool double_button_click); diff --git a/atom/browser/ui/win/notify_icon_host.cc b/atom/browser/ui/win/notify_icon_host.cc index 2c84837e71..a0d4287ff6 100644 --- a/atom/browser/ui/win/notify_icon_host.cc +++ b/atom/browser/ui/win/notify_icon_host.cc @@ -15,7 +15,6 @@ #include "base/win/win_util.h" #include "base/win/wrapped_window_proc.h" #include "ui/events/event_constants.h" -#include "ui/gfx/screen.h" #include "ui/gfx/win/hwnd_util.h" namespace atom { @@ -172,10 +171,7 @@ LRESULT CALLBACK NotifyIconHost::WndProc(HWND hwnd, case WM_CONTEXTMENU: // Walk our icons, find which one was clicked on, and invoke its // HandleClickEvent() method. - gfx::Point cursor_pos( - gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); win_icon->HandleClickEvent( - cursor_pos, GetKeyboardModifers(), (lparam == WM_LBUTTONDOWN || lparam == WM_LBUTTONDBLCLK), (lparam == WM_LBUTTONDBLCLK || lparam == WM_RBUTTONDBLCLK)); From 75c28ff993c24f5a463a918259259fb007311eed Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Thu, 3 Dec 2015 00:21:34 +0900 Subject: [PATCH 100/411] Update as upstream --- docs-translations/ko-KR/api/process.md | 7 +++++++ docs-translations/ko-KR/api/tray.md | 8 ++++++-- docs-translations/ko-KR/api/web-view-tag.md | 4 ++-- docs-translations/ko-KR/tutorial/application-packaging.md | 8 ++++++++ .../ko-KR/tutorial/debugging-main-process.md | 6 +++--- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/docs-translations/ko-KR/api/process.md b/docs-translations/ko-KR/api/process.md index afbc6afd79..de24b5ac17 100644 --- a/docs-translations/ko-KR/api/process.md +++ b/docs-translations/ko-KR/api/process.md @@ -30,6 +30,13 @@ process.once('loaded', function() { }); ``` +## Properties + +### `process.noAsar` + +이 속성을 `true`로 지정하면 Node 빌트인 모듈의 `asar` 아카이브 지원을 비활성화 시킬 +수 있습니다. + ## Methods `process` 객체는 다음과 같은 메서드를 가지고 있습니다: diff --git a/docs-translations/ko-KR/api/tray.md b/docs-translations/ko-KR/api/tray.md index 97db97f426..e182f6471e 100644 --- a/docs-translations/ko-KR/api/tray.md +++ b/docs-translations/ko-KR/api/tray.md @@ -184,12 +184,16 @@ __주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. 트레이에 풍선 팝업을 생성합니다. -### `Tray.popContextMenu([position])` _OS X_ _Windows_ +### `Tray.popUpContextMenu([menu, position])` _OS X_ _Windows_ -* `position` Object (optional) - 팝업 메뉴 위치 +* `menu` Menu (optional) +* `position` Object (optional) - 팝업 메뉴의 위치 * `x` Integer * `y` Integer +트레이 아이콘의 컨텍스트 메뉴를 팝업시킵니다. `menu`가 전달되면, `menu`가 트레이 +메뉴의 컨텍스트 메뉴 대신 표시됩니다. + `position`은 Windows에서만 사용할 수 있으며 기본값은 (0, 0)입니다. ### `Tray.setContextMenu(menu)` diff --git a/docs-translations/ko-KR/api/web-view-tag.md b/docs-translations/ko-KR/api/web-view-tag.md index 4ef7f47c85..531b19adce 100644 --- a/docs-translations/ko-KR/api/web-view-tag.md +++ b/docs-translations/ko-KR/api/web-view-tag.md @@ -441,7 +441,7 @@ Returns: 프레임 문서의 로드가 끝나면 발생하는 이벤트입니다. -### Event: 'page-title-set' +### Event: 'page-title-updated' Returns: @@ -449,7 +449,7 @@ Returns: * `explicitSet` Boolean 탐색하는 동안에 페이지의 제목이 설정되면 발생하는 이벤트입니다. `explicitSet`는 파일 -URL에서 종합(synthesised)된 제목인 경우 false로 표시됩니다. +URL에서 합성(synthesised)된 제목인 경우 false로 표시됩니다. ### Event: 'page-favicon-updated' diff --git a/docs-translations/ko-KR/tutorial/application-packaging.md b/docs-translations/ko-KR/tutorial/application-packaging.md index 97054110ce..9183f0eafe 100644 --- a/docs-translations/ko-KR/tutorial/application-packaging.md +++ b/docs-translations/ko-KR/tutorial/application-packaging.md @@ -103,6 +103,14 @@ var originalFs = require('original-fs'); originalFs.readFileSync('/path/to/example.asar'); ``` +또한 `process.noAsar`를 `true`로 지정하면 `fs` 모듈의 `asar` 지원을 비활성화 시킬 수 +있습니다. + +```javascript +process.noAsar = true; +fs.readFileSync('/path/to/example.asar'); +``` + ## Node API의 한계 `asar` 아카이브를 Node API가 최대한 디렉터리 구조로 작동하도록 노력해왔지만 여전히 diff --git a/docs-translations/ko-KR/tutorial/debugging-main-process.md b/docs-translations/ko-KR/tutorial/debugging-main-process.md index 32a6bd00f7..ee7e8432d4 100644 --- a/docs-translations/ko-KR/tutorial/debugging-main-process.md +++ b/docs-translations/ko-KR/tutorial/debugging-main-process.md @@ -19,9 +19,9 @@ ## node-inspector로 디버깅 하기 -__참고:__ Electron은 node v0.11.13 버전을 사용합니다. 그리고 현재 node-inspector -유틸리티와 호환성 문제가 있습니다. 추가로 node-inspector 콘솔 내에서 메인 프로세스의 -`process` 객체를 탐색할 경우 크래시가 발생할 수 있습니다. +__참고:__ Electron은 현재 node-inspector 유틸리티와 호환성 문제가 있습니다. 따라서 +node-inspector 콘솔 내에서 메인 프로세스의 `process` 객체를 탐색할 경우 크래시가 +발생할 수 있습니다. ### 1. [node-inspector][node-inspector] 서버 시작 From 0553b9c672f3fdb51b54fb181813e460e15b15ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9verton=20Heming?= Date: Wed, 2 Dec 2015 15:27:23 +0000 Subject: [PATCH 101/411] Add electron-br --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index beb96b4e5c..63c8aae69f 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ locations: forums - `#atom-shell` channel on Freenode - [`Atom`](http://atom-slack.herokuapp.com/) channel on Slack +- [`electron-br`](https://electron-br.slack.com) *(Brazilian Portuguese)* Check out [awesome-electron](https://github.com/sindresorhus/awesome-electron) for a community maintained list of useful example apps, tools and resources. From 32e949efed094d8212d8aafd2e6d0e7f7ffb46c0 Mon Sep 17 00:00:00 2001 From: Jeff Wear Date: Wed, 2 Dec 2015 11:50:44 -0800 Subject: [PATCH 102/411] Read window size properly in `window.open` test `BrowserWindow#getSize` returns `[width, height]`, not `{width, height}`. --- spec/chromium-spec.coffee | 4 ++-- spec/fixtures/pages/window-open-size.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 435f7bbbe3..7aecde218d 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -75,8 +75,8 @@ describe 'chromium feature', -> it 'inherit options of parent window', (done) -> listener = (event) -> - size = remote.getCurrentWindow().getSize() - assert.equal event.data, "size: #{size.width} #{size.height}" + [width, height] = remote.getCurrentWindow().getSize() + assert.equal event.data, "size: #{width} #{height}" b.close() done() window.addEventListener 'message', listener diff --git a/spec/fixtures/pages/window-open-size.html b/spec/fixtures/pages/window-open-size.html index b32e39889a..93b5039f79 100644 --- a/spec/fixtures/pages/window-open-size.html +++ b/spec/fixtures/pages/window-open-size.html @@ -2,7 +2,7 @@ From 225fe72d0337410117ad8f29647fc5ccd72e78c1 Mon Sep 17 00:00:00 2001 From: Jeff Wear Date: Wed, 2 Dec 2015 11:54:52 -0800 Subject: [PATCH 103/411] Ensure that `window.open` does not override the child options Fixes https://github.com/atom/electron/issues/3652. --- atom/browser/lib/guest-window-manager.coffee | 2 +- spec/chromium-spec.coffee | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index c311e01cf4..9b4490c4e0 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -5,7 +5,7 @@ frameToGuest = {} # Copy attribute of |parent| to |child| if it is not defined in |child|. mergeOptions = (child, parent) -> - for own key, value of parent when key not in child + for own key, value of parent when key not in Object.keys child if typeof value is 'object' child[key] = mergeOptions {}, value else diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 7aecde218d..211de34684 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -82,6 +82,15 @@ describe 'chromium feature', -> window.addEventListener 'message', listener b = window.open "file://#{fixtures}/pages/window-open-size.html", '', 'show=no' + it 'does not override child options', (done) -> + size = {width: 350, height: 450} + listener = (event) -> + assert.equal event.data, "size: #{size.width} #{size.height}" + b.close() + done() + window.addEventListener 'message', listener + b = window.open "file://#{fixtures}/pages/window-open-size.html", '', "show=no,width=#{size.width},height=#{size.height}" + describe 'window.opener', -> @timeout 10000 From c311c6cf1bb139a9b980297a3ae78015367dfc76 Mon Sep 17 00:00:00 2001 From: Charlie Hess Date: Wed, 2 Dec 2015 13:49:30 -0800 Subject: [PATCH 104/411] Add a DownloadURL method on WebContents. --- atom/browser/api/atom_api_web_contents.cc | 10 ++++++++++ atom/browser/api/atom_api_web_contents.h | 1 + 2 files changed, 11 insertions(+) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index f377e8fda9..116bf03b1d 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -634,6 +634,15 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { web_contents()->GetController().LoadURLWithParams(params); } +void WebContents::DownloadURL(const GURL& url) { + auto browser_context = web_contents()->GetBrowserContext(); + auto download_manager = + content::BrowserContext::GetDownloadManager(browser_context); + + download_manager->DownloadUrl( + content::DownloadUrlParameters::FromWebContents(web_contents(), url)); +} + GURL WebContents::GetURL() const { return web_contents()->GetURL(); } @@ -996,6 +1005,7 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("getId", &WebContents::GetID) .SetMethod("equal", &WebContents::Equal) .SetMethod("_loadURL", &WebContents::LoadURL) + .SetMethod("downloadURL", &WebContents::DownloadURL) .SetMethod("_getURL", &WebContents::GetURL) .SetMethod("getTitle", &WebContents::GetTitle) .SetMethod("isLoading", &WebContents::IsLoading) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 568a563e2c..07cb79dfab 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -60,6 +60,7 @@ class WebContents : public mate::TrackableObject, int GetID() const; bool Equal(const WebContents* web_contents) const; void LoadURL(const GURL& url, const mate::Dictionary& options); + void DownloadURL(const GURL& url); GURL GetURL() const; base::string16 GetTitle() const; bool IsLoading() const; From 5839df66e4fc22d9b8d55455837211387f0a6bec Mon Sep 17 00:00:00 2001 From: Charlie Hess Date: Wed, 2 Dec 2015 13:49:42 -0800 Subject: [PATCH 105/411] Document the new method. --- docs/api/web-contents.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index e976b0ddb8..c35bbc9ae3 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -242,6 +242,13 @@ const options = {"extraHeaders" : "pragma: no-cache\n"} webContents.loadURL(url, options) ``` +### `webContents.downloadURL(url)` + +* `url` URL + +Initiates a download of the resource at `url` without navigating. The +`will-download` event of `session` will be triggered. + ### `webContents.getURL()` Returns URL of the current web page. From 0d30a8d70cf82610a300552ea19d405913971433 Mon Sep 17 00:00:00 2001 From: Charlie Hess Date: Wed, 2 Dec 2015 18:40:02 -0800 Subject: [PATCH 106/411] Make downloadURL available on the webview tag. --- atom/renderer/lib/web-view/web-view.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee index 720e6e84c5..da36886f6f 100644 --- a/atom/renderer/lib/web-view/web-view.coffee +++ b/atom/renderer/lib/web-view/web-view.coffee @@ -288,6 +288,7 @@ registerWebViewElement = -> 'replace' 'replaceMisspelling' 'getId' + 'downloadURL' 'inspectServiceWorker' 'print' 'printToPDF' From b369d4991e56c8c1290c742b098797a9b2742fcc Mon Sep 17 00:00:00 2001 From: Charlie Hess Date: Wed, 2 Dec 2015 18:40:39 -0800 Subject: [PATCH 107/411] Write a spec for downloading from the webview tag. --- spec/api-session-spec.coffee | 40 +++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/spec/api-session-spec.coffee b/spec/api-session-spec.coffee index 6d0a8ac167..98e47e357a 100644 --- a/spec/api-session-spec.coffee +++ b/spec/api-session-spec.coffee @@ -87,23 +87,43 @@ describe 'session module', -> res.end mockPDF downloadServer.close() - it 'can download successfully', (done) -> + assertDownload = (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) -> + assert.equal state, 'completed' + assert.equal filename, 'mock.pdf' + assert.equal url, "http://127.0.0.1:#{port}/" + assert.equal mimeType, 'application/pdf' + assert.equal receivedBytes, mockPDF.length + assert.equal totalBytes, mockPDF.length + assert.equal disposition, contentDisposition + assert fs.existsSync downloadFilePath + fs.unlinkSync downloadFilePath + + it 'can download using BrowserWindow.loadURL', (done) -> downloadServer.listen 0, '127.0.0.1', -> {port} = downloadServer.address() ipcRenderer.sendSync 'set-download-option', false w.loadURL "#{url}:#{port}" ipcRenderer.once 'download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) -> - assert.equal state, 'completed' - assert.equal filename, 'mock.pdf' - assert.equal url, "http://127.0.0.1:#{port}/" - assert.equal mimeType, 'application/pdf' - assert.equal receivedBytes, mockPDF.length - assert.equal totalBytes, mockPDF.length - assert.equal disposition, contentDisposition - assert fs.existsSync downloadFilePath - fs.unlinkSync downloadFilePath + assertDownload event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port done() + it 'can download using WebView.downloadURL', (done) -> + downloadServer.listen 0, '127.0.0.1', -> + {port} = downloadServer.address() + ipcRenderer.sendSync 'set-download-option', false + + webview = new WebView + webview.src = "file://#{fixtures}/api/blank.html" + webview.addEventListener 'did-finish-load', -> + webview.downloadURL "#{url}:#{port}/" + + ipcRenderer.once 'download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) -> + assertDownload event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port + document.body.removeChild(webview) + done() + + document.body.appendChild webview + it 'can cancel download', (done) -> downloadServer.listen 0, '127.0.0.1', -> {port} = downloadServer.address() From b1391270ed1b71b156cb4f5b8d84d2a6859b2a90 Mon Sep 17 00:00:00 2001 From: Charlie Hess Date: Wed, 2 Dec 2015 18:40:57 -0800 Subject: [PATCH 108/411] This spec is a little unreliable; up the timeout. --- spec/api-browser-window-spec.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index 8df4c9a947..86beb1fdc6 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -262,6 +262,7 @@ describe 'browser-window module', -> w.loadURL "file://#{fixtures}/pages/window-open.html" it 'emits when link with target is called', (done) -> + @timeout 10000 w.webContents.once 'new-window', (e, url, frameName) -> e.preventDefault() assert.equal url, 'http://host/' From e5358d405ab2ba464005fa7189ff912e0d44b90a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 3 Dec 2015 11:24:33 +0800 Subject: [PATCH 109/411] Make sure V8 Function passed to native code are destroyed on UI thread --- .../common/native_mate_converters/callback.cc | 74 +++++++++++++++---- atom/common/native_mate_converters/callback.h | 30 ++++---- 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/atom/common/native_mate_converters/callback.cc b/atom/common/native_mate_converters/callback.cc index 87faa3df3c..cd9838c1ed 100644 --- a/atom/common/native_mate_converters/callback.cc +++ b/atom/common/native_mate_converters/callback.cc @@ -5,6 +5,9 @@ #include "atom/common/native_mate_converters/callback.h" #include "atom/browser/atom_browser_main_parts.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; namespace mate { @@ -56,31 +59,72 @@ v8::Local BindFunctionWith(v8::Isolate* isolate, } // namespace +// Destroy the class on UI thread when possible. +struct DeleteOnUIThread { + template + static void Destruct(const T* x) { + if (Locker::IsBrowserProcess() && + !BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, x); + } else { + delete x; + } + } +}; + +// Like v8::Global, but ref-counted. +template +class RefCountedGlobal : public base::RefCountedThreadSafe, + DeleteOnUIThread> { + public: + RefCountedGlobal(v8::Isolate* isolate, v8::Local value) + : handle_(isolate, v8::Local::Cast(value)), + weak_factory_(this) { + // In browser process, we need to ensure the V8 handle is destroyed before + // the JavaScript env gets destroyed. + if (Locker::IsBrowserProcess() && atom::AtomBrowserMainParts::Get()) { + atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( + base::Bind(&RefCountedGlobal::FreeHandle, + weak_factory_.GetWeakPtr())); + } + } + + bool IsAlive() const { + return !handle_.IsEmpty(); + } + + v8::Local NewHandle(v8::Isolate* isolate) const { + return v8::Local::New(isolate, handle_); + } + + private: + void FreeHandle() { + handle_.Reset(); + } + + v8::Global handle_; + base::WeakPtrFactory> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedGlobal); +}; + SafeV8Function::SafeV8Function(v8::Isolate* isolate, v8::Local value) - : v8_function_(new RefCountedPersistent(isolate, value)), - weak_factory_(this) { - Init(); + : v8_function_(new RefCountedGlobal(isolate, value)) { } SafeV8Function::SafeV8Function(const SafeV8Function& other) - : v8_function_(other.v8_function_), - weak_factory_(this) { - Init(); + : v8_function_(other.v8_function_) { } -v8::Local SafeV8Function::NewHandle() const { - return v8_function_->NewHandle(); +SafeV8Function::~SafeV8Function() { } -void SafeV8Function::Init() { - if (Locker::IsBrowserProcess() && atom::AtomBrowserMainParts::Get()) - atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( - base::Bind(&SafeV8Function::FreeHandle, weak_factory_.GetWeakPtr())); +bool SafeV8Function::IsAlive() const { + return v8_function_.get() && v8_function_->IsAlive(); } -void SafeV8Function::FreeHandle() { - Locker locker(v8_function_->isolate()); - v8_function_ = nullptr; +v8::Local SafeV8Function::NewHandle(v8::Isolate* isolate) const { + return v8_function_->NewHandle(isolate); } v8::Local CreateFunctionFromTranslater( diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h index 5dd9d3cec9..3cba2b32a8 100644 --- a/atom/common/native_mate_converters/callback.h +++ b/atom/common/native_mate_converters/callback.h @@ -19,23 +19,21 @@ namespace mate { namespace internal { -// Manages the V8 function with RAII, and automatically cleans the handle when -// JavaScript context is destroyed, even when the class is not destroyed. +template +class RefCountedGlobal; + +// Manages the V8 function with RAII. class SafeV8Function { public: SafeV8Function(v8::Isolate* isolate, v8::Local value); SafeV8Function(const SafeV8Function& other); + ~SafeV8Function(); - bool is_alive() const { return v8_function_.get(); } - - v8::Local NewHandle() const; + bool IsAlive() const; + v8::Local NewHandle(v8::Isolate* isolate) const; private: - void Init(); - void FreeHandle(); - - scoped_refptr> v8_function_; - base::WeakPtrFactory weak_factory_; + scoped_refptr> v8_function_; }; // Helper to invoke a V8 function with C++ parameters. @@ -49,12 +47,12 @@ struct V8FunctionInvoker(ArgTypes...)> { ArgTypes... raw) { Locker locker(isolate); v8::EscapableHandleScope handle_scope(isolate); - if (!function.is_alive()) + if (!function.IsAlive()) return v8::Null(isolate); scoped_ptr script_scope( Locker::IsBrowserProcess() ? nullptr : new blink::WebScopedRunV8Script(isolate)); - v8::Local holder = function.NewHandle(); + v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); std::vector> args = { ConvertToV8(isolate, raw)... }; @@ -70,12 +68,12 @@ struct V8FunctionInvoker { ArgTypes... raw) { Locker locker(isolate); v8::HandleScope handle_scope(isolate); - if (!function.is_alive()) + if (!function.IsAlive()) return; scoped_ptr script_scope( Locker::IsBrowserProcess() ? nullptr : new blink::WebScopedRunV8Script(isolate)); - v8::Local holder = function.NewHandle(); + v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); std::vector> args = { ConvertToV8(isolate, raw)... }; @@ -91,12 +89,12 @@ struct V8FunctionInvoker { Locker locker(isolate); v8::HandleScope handle_scope(isolate); ReturnType ret = ReturnType(); - if (!function.is_alive()) + if (!function.IsAlive()) return ret; scoped_ptr script_scope( Locker::IsBrowserProcess() ? nullptr : new blink::WebScopedRunV8Script(isolate)); - v8::Local holder = function.NewHandle(); + v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); std::vector> args = { ConvertToV8(isolate, raw)... }; From 6795bd1d9695634786badc3b2082019ec905c297 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 3 Dec 2015 15:38:43 +0800 Subject: [PATCH 110/411] Do not manually manage native resources We should rely on the destructor to cleanup everything, instead of putting them in the Destroy method. --- atom/browser/api/atom_api_download_item.cc | 26 +++++-------- atom/browser/api/atom_api_download_item.h | 3 -- atom/browser/api/atom_api_global_shortcut.cc | 3 -- atom/browser/api/atom_api_global_shortcut.h | 3 -- atom/browser/api/atom_api_menu.cc | 9 +---- atom/browser/api/atom_api_menu.h | 4 -- atom/browser/api/atom_api_menu_mac.h | 1 - atom/browser/api/atom_api_menu_mac.mm | 5 --- atom/browser/api/atom_api_power_monitor.cc | 3 -- atom/browser/api/atom_api_power_monitor.h | 3 -- .../api/atom_api_power_save_blocker.cc | 5 --- .../browser/api/atom_api_power_save_blocker.h | 3 -- atom/browser/api/atom_api_session.cc | 10 +---- atom/browser/api/atom_api_session.h | 4 -- atom/browser/api/atom_api_tray.cc | 11 +----- atom/browser/api/atom_api_tray.h | 6 --- atom/browser/api/atom_api_web_contents.cc | 37 +++++++------------ atom/browser/api/atom_api_web_contents.h | 4 -- atom/browser/api/atom_api_window.cc | 34 ++++++----------- atom/browser/api/atom_api_window.h | 6 --- atom/browser/api/trackable_object.cc | 15 +++++++- atom/browser/api/trackable_object.h | 11 ++++-- vendor/native_mate | 2 +- 23 files changed, 59 insertions(+), 149 deletions(-) diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index 7dd271304a..fe63d70043 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -70,29 +70,20 @@ DownloadItem::DownloadItem(content::DownloadItem* download_item) : } DownloadItem::~DownloadItem() { - Destroy(); -} - -void DownloadItem::Destroy() { - if (download_item_) { - download_item_->RemoveObserver(this); - auto iter = g_download_item_objects.find(download_item_->GetId()); - if (iter != g_download_item_objects.end()) - g_download_item_objects.erase(iter); - download_item_ = nullptr; - } -} - -bool DownloadItem::IsDestroyed() const { - return download_item_ == nullptr; + if (download_item_) + OnDownloadDestroyed(download_item_); } void DownloadItem::OnDownloadUpdated(content::DownloadItem* item) { download_item_->IsDone() ? Emit("done", item->GetState()) : Emit("updated"); } -void DownloadItem::OnDownloadDestroyed(content::DownloadItem* download) { - Destroy(); +void DownloadItem::OnDownloadDestroyed(content::DownloadItem* download_item) { + download_item_->RemoveObserver(this); + auto iter = g_download_item_objects.find(download_item_->GetId()); + if (iter != g_download_item_objects.end()) + g_download_item_objects.erase(iter); + download_item_ = nullptr; } int64 DownloadItem::GetReceivedBytes() { @@ -147,6 +138,7 @@ void DownloadItem::Cancel() { mate::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) + .MakeDestroyable() .SetMethod("pause", &DownloadItem::Pause) .SetMethod("resume", &DownloadItem::Resume) .SetMethod("cancel", &DownloadItem::Cancel) diff --git a/atom/browser/api/atom_api_download_item.h b/atom/browser/api/atom_api_download_item.h index 955801cd99..412b6ce9bf 100644 --- a/atom/browser/api/atom_api_download_item.h +++ b/atom/browser/api/atom_api_download_item.h @@ -56,9 +56,6 @@ class DownloadItem : public mate::EventEmitter, // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; - bool IsDestroyed() const override; - - void Destroy(); content::DownloadItem* download_item_; diff --git a/atom/browser/api/atom_api_global_shortcut.cc b/atom/browser/api/atom_api_global_shortcut.cc index 6ab4fa4b61..f5a03e4abf 100644 --- a/atom/browser/api/atom_api_global_shortcut.cc +++ b/atom/browser/api/atom_api_global_shortcut.cc @@ -23,9 +23,6 @@ GlobalShortcut::GlobalShortcut() { } GlobalShortcut::~GlobalShortcut() { -} - -void GlobalShortcut::Destroy() { UnregisterAll(); } diff --git a/atom/browser/api/atom_api_global_shortcut.h b/atom/browser/api/atom_api_global_shortcut.h index 93eb7853ae..d7057b0003 100644 --- a/atom/browser/api/atom_api_global_shortcut.h +++ b/atom/browser/api/atom_api_global_shortcut.h @@ -27,9 +27,6 @@ class GlobalShortcut : public extensions::GlobalShortcutListener::Observer, GlobalShortcut(); ~GlobalShortcut() override; - // mate::TrackableObject: - void Destroy() override; - // mate::Wrappable implementations: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 1f16a428da..96cba3fe99 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -27,14 +27,6 @@ Menu::Menu() Menu::~Menu() { } -void Menu::Destroy() { - model_.reset(); -} - -bool Menu::IsDestroyed() const { - return !model_; -} - void Menu::AfterInit(v8::Isolate* isolate) { mate::Dictionary wrappable(isolate, GetWrapper(isolate)); mate::Dictionary delegate; @@ -159,6 +151,7 @@ bool Menu::IsVisibleAt(int index) const { void Menu::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) + .MakeDestroyable() .SetMethod("insertItem", &Menu::InsertItemAt) .SetMethod("insertCheckItem", &Menu::InsertCheckItemAt) .SetMethod("insertRadioItem", &Menu::InsertRadioItemAt) diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 545dd18e38..17bb9073a7 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -39,11 +39,7 @@ class Menu : public mate::TrackableObject, Menu(); ~Menu() override; - // mate::TrackableObject: - void Destroy() override; - // mate::Wrappable: - bool IsDestroyed() const override; void AfterInit(v8::Isolate* isolate) override; // ui::SimpleMenuModel::Delegate: diff --git a/atom/browser/api/atom_api_menu_mac.h b/atom/browser/api/atom_api_menu_mac.h index baa2aff349..5a086776a6 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -19,7 +19,6 @@ class MenuMac : public Menu { protected: MenuMac(); - void Destroy() override; void Popup(Window* window) override; void PopupAt(Window* window, int x, int y) override; diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index 5936e0439f..071753218d 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -18,11 +18,6 @@ namespace api { MenuMac::MenuMac() { } -void MenuMac::Destroy() { - menu_controller_.reset(); - Menu::Destroy(); -} - void MenuMac::Popup(Window* window) { NativeWindow* native_window = window->window(); if (!native_window) diff --git a/atom/browser/api/atom_api_power_monitor.cc b/atom/browser/api/atom_api_power_monitor.cc index eeb9475c28..31b35e10ce 100644 --- a/atom/browser/api/atom_api_power_monitor.cc +++ b/atom/browser/api/atom_api_power_monitor.cc @@ -19,9 +19,6 @@ PowerMonitor::PowerMonitor() { } PowerMonitor::~PowerMonitor() { -} - -void PowerMonitor::Destroy() { base::PowerMonitor::Get()->RemoveObserver(this); } diff --git a/atom/browser/api/atom_api_power_monitor.h b/atom/browser/api/atom_api_power_monitor.h index 9303b3ab28..8fb52eeec9 100644 --- a/atom/browser/api/atom_api_power_monitor.h +++ b/atom/browser/api/atom_api_power_monitor.h @@ -23,9 +23,6 @@ class PowerMonitor : public mate::TrackableObject, PowerMonitor(); ~PowerMonitor() override; - // mate::TrackableObject: - void Destroy() override; - // base::PowerObserver implementations: void OnPowerStateChange(bool on_battery_power) override; void OnSuspend() override; diff --git a/atom/browser/api/atom_api_power_save_blocker.cc b/atom/browser/api/atom_api_power_save_blocker.cc index f77979ae41..58983e6c84 100644 --- a/atom/browser/api/atom_api_power_save_blocker.cc +++ b/atom/browser/api/atom_api_power_save_blocker.cc @@ -45,11 +45,6 @@ PowerSaveBlocker::PowerSaveBlocker() PowerSaveBlocker::~PowerSaveBlocker() { } -void PowerSaveBlocker::Destroy() { - power_save_blocker_types_.clear(); - power_save_blocker_.reset(); -} - void PowerSaveBlocker::UpdatePowerSaveBlocker() { if (power_save_blocker_types_.empty()) { power_save_blocker_.reset(); diff --git a/atom/browser/api/atom_api_power_save_blocker.h b/atom/browser/api/atom_api_power_save_blocker.h index e7ce97878f..a698d746ce 100644 --- a/atom/browser/api/atom_api_power_save_blocker.h +++ b/atom/browser/api/atom_api_power_save_blocker.h @@ -28,9 +28,6 @@ class PowerSaveBlocker : public mate::TrackableObject { PowerSaveBlocker(); ~PowerSaveBlocker() override; - // mate::TrackableObject: - void Destroy() override; - // mate::Wrappable implementations: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 27e1521f3d..495a4c46dd 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -253,7 +253,6 @@ Session::Session(AtomBrowserContext* browser_context) Session::~Session() { content::BrowserContext::GetDownloadManager(browser_context())-> RemoveObserver(this); - Destroy(); } void Session::OnDownloadCreated(content::DownloadManager* manager, @@ -271,14 +270,6 @@ void Session::OnDownloadCreated(content::DownloadManager* manager, } } -bool Session::IsDestroyed() const { - return !browser_context_; -} - -void Session::Destroy() { - browser_context_ = nullptr; -} - void Session::ResolveProxy(const GURL& url, ResolveProxyCallback callback) { new ResolveProxyHelper(browser_context(), url, callback); } @@ -379,6 +370,7 @@ v8::Local Session::Cookies(v8::Isolate* isolate) { mate::ObjectTemplateBuilder Session::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) + .MakeDestroyable() .SetMethod("resolveProxy", &Session::ResolveProxy) .SetMethod("clearCache", &Session::ClearCache) .SetMethod("clearStorageData", &Session::ClearStorageData) diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 01dc0a408a..104a8e40cc 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -59,12 +59,8 @@ class Session: public mate::TrackableObject, // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; - bool IsDestroyed() const override; private: - // mate::TrackableObject: - void Destroy() override; - void ResolveProxy(const GURL& url, ResolveProxyCallback callback); void ClearCache(const net::CompletionCallback& callback); void ClearStorageData(mate::Arguments* args); diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 3b1a3a6321..5f351f4850 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -94,14 +94,6 @@ void Tray::OnDragEnded() { Emit("drag-end"); } -bool Tray::IsDestroyed() const { - return !tray_icon_; -} - -void Tray::Destroy() { - tray_icon_.reset(); -} - void Tray::SetImage(mate::Arguments* args, const gfx::Image& image) { tray_icon_->SetImage(image); } @@ -162,8 +154,7 @@ v8::Local Tray::ModifiersToObject(v8::Isolate* isolate, void Tray::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) - .SetMethod("destroy", &Tray::Destroy, true) - .SetMethod("isDestroyed", &Tray::IsDestroyed, true) + .MakeDestroyable() .SetMethod("setImage", &Tray::SetImage) .SetMethod("setPressedImage", &Tray::SetPressedImage) .SetMethod("setToolTip", &Tray::SetToolTip) diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index d8d6dcead1..0e0d153ad0 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -54,12 +54,6 @@ class Tray : public mate::TrackableObject, void OnDragExited() override; void OnDragEnded() override; - // mate::Wrappable: - bool IsDestroyed() const override; - - // mate::TrackableObject: - void Destroy() override; - void SetImage(mate::Arguments* args, const gfx::Image& image); void SetPressedImage(mate::Arguments* args, const gfx::Image& image); void SetToolTip(mate::Arguments* args, const std::string& tool_tip); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index f377e8fda9..2f8353e273 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -290,7 +290,15 @@ WebContents::WebContents(v8::Isolate* isolate, } WebContents::~WebContents() { - Destroy(); + if (type_ == WEB_VIEW && managed_web_contents()) { + // When force destroying the "destroyed" event is not emitted. + WebContentsDestroyed(); + + guest_delegate_->Destroy(); + + Observe(nullptr); + DestroyWebContents(); + } } bool WebContents::AddMessageToConsole(content::WebContents* source, @@ -591,19 +599,6 @@ void WebContents::NavigationEntryCommitted( details.is_in_page, details.did_replace_entry); } -void WebContents::Destroy() { - session_.Reset(); - if (type_ == WEB_VIEW && managed_web_contents()) { - // When force destroying the "destroyed" event is not emitted. - WebContentsDestroyed(); - - guest_delegate_->Destroy(); - - Observe(nullptr); - DestroyWebContents(); - } -} - int WebContents::GetID() const { return web_contents()->GetRenderProcessHost()->GetID(); } @@ -991,8 +986,7 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( v8::Isolate* isolate) { if (template_.IsEmpty()) template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) - .SetMethod("destroy", &WebContents::Destroy, true) - .SetMethod("isDestroyed", &WebContents::IsDestroyed, true) + .MakeDestroyable() .SetMethod("getId", &WebContents::GetID) .SetMethod("equal", &WebContents::Equal) .SetMethod("_loadURL", &WebContents::LoadURL) @@ -1034,7 +1028,7 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("replaceMisspelling", &WebContents::ReplaceMisspelling) .SetMethod("focus", &WebContents::Focus) .SetMethod("tabTraverse", &WebContents::TabTraverse) - .SetMethod("_send", &WebContents::SendIPCMessage, true) + .SetMethod("_send", &WebContents::SendIPCMessage) .SetMethod("sendInputEvent", &WebContents::SendInputEvent) .SetMethod("beginFrameSubscription", &WebContents::BeginFrameSubscription) @@ -1052,19 +1046,14 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("_printToPDF", &WebContents::PrintToPDF) .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) - .SetProperty("session", &WebContents::Session, true) - .SetProperty("devToolsWebContents", - &WebContents::DevToolsWebContents, true) + .SetProperty("session", &WebContents::Session) + .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents) .Build()); return mate::ObjectTemplateBuilder( isolate, v8::Local::New(isolate, template_)); } -bool WebContents::IsDestroyed() const { - return !web_contents(); -} - AtomBrowserContext* WebContents::GetBrowserContext() const { return static_cast(web_contents()->GetBrowserContext()); } diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 568a563e2c..9462e926ee 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -54,9 +54,6 @@ class WebContents : public mate::TrackableObject, static mate::Handle Create( v8::Isolate* isolate, const mate::Dictionary& options); - // mate::TrackableObject: - void Destroy() override; - int GetID() const; bool Equal(const WebContents* web_contents) const; void LoadURL(const GURL& url, const mate::Dictionary& options); @@ -152,7 +149,6 @@ class WebContents : public mate::TrackableObject, // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; - bool IsDestroyed() const override; // content::WebContentsDelegate: bool AddMessageToConsole(content::WebContents* source, diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 5696405544..79c91db3fa 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -157,8 +157,8 @@ Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { } Window::~Window() { - if (window_) - Destroy(); + if (!window_->IsClosed()) + window_->CloseContents(nullptr); } void Window::WillCloseWindow(bool* prevent_default) { @@ -166,19 +166,19 @@ void Window::WillCloseWindow(bool* prevent_default) { } void Window::OnWindowClosed() { - if (api_web_contents_) { - api_web_contents_->DestroyWebContents(); - api_web_contents_ = nullptr; - web_contents_.Reset(); - } + api_web_contents_->DestroyWebContents(); RemoveFromWeakMap(); window_->RemoveObserver(this); + // We can not call Destroy here because we need to call Emit first, but we + // also do not want any method to be used, so just mark as destroyed here. + MarkDestroyed(); + Emit("closed"); - // Clean up the resources after window has been closed. - base::MessageLoop::current()->DeleteSoon(FROM_HERE, window_.release()); + // Destroy the native class when window is closed. + base::MessageLoop::current()->PostTask(FROM_HERE, GetDestroyClosure()); } void Window::OnWindowBlur() { @@ -276,15 +276,6 @@ mate::Wrappable* Window::New(v8::Isolate* isolate, mate::Arguments* args) { return new Window(isolate, options); } -bool Window::IsDestroyed() const { - return !window_ || window_->IsClosed(); -} - -void Window::Destroy() { - if (window_) - window_->CloseContents(nullptr); -} - void Window::Close() { window_->Close(); } @@ -622,8 +613,7 @@ v8::Local Window::WebContents(v8::Isolate* isolate) { void Window::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) - .SetMethod("destroy", &Window::Destroy, true) - .SetMethod("isDestroyed", &Window::IsDestroyed, true) + .MakeDestroyable() .SetMethod("close", &Window::Close) .SetMethod("focus", &Window::Focus) .SetMethod("isFocused", &Window::IsFocused) @@ -695,8 +685,8 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("showDefinitionForSelection", &Window::ShowDefinitionForSelection) #endif - .SetProperty("id", &Window::ID, true) - .SetProperty("webContents", &Window::WebContents, true); + .SetProperty("id", &Window::ID) + .SetProperty("webContents", &Window::WebContents); } // static diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index a04e00cfb4..6d5ce22f43 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -77,13 +77,7 @@ class Window : public mate::TrackableObject, void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; #endif - // mate::Wrappable: - bool IsDestroyed() const override; - private: - // mate::TrackableObject: - void Destroy() override; - // APIs for NativeWindow. void Close(); void Focus(); diff --git a/atom/browser/api/trackable_object.cc b/atom/browser/api/trackable_object.cc index 50bfa59e6a..5b1c6b8240 100644 --- a/atom/browser/api/trackable_object.cc +++ b/atom/browser/api/trackable_object.cc @@ -30,8 +30,7 @@ class IDUserData : public base::SupportsUserData::Data { TrackableObjectBase::TrackableObjectBase() : weak_map_id_(0), wrapped_(nullptr), weak_factory_(this) { - RegisterDestructionCallback( - base::Bind(&TrackableObjectBase::Destroy, weak_factory_.GetWeakPtr())); + RegisterDestructionCallback(GetDestroyClosure()); } TrackableObjectBase::~TrackableObjectBase() { @@ -42,6 +41,18 @@ void TrackableObjectBase::AfterInit(v8::Isolate* isolate) { AttachAsUserData(wrapped_); } +void TrackableObjectBase::MarkDestroyed() { + GetWrapper(isolate())->SetAlignedPointerInInternalField(0, nullptr); +} + +base::Closure TrackableObjectBase::GetDestroyClosure() { + return base::Bind(&TrackableObjectBase::Destroy, weak_factory_.GetWeakPtr()); +} + +void TrackableObjectBase::Destroy() { + delete this; +} + void TrackableObjectBase::AttachAsUserData(base::SupportsUserData* wrapped) { if (weak_map_id_ != 0) { wrapped->SetUserData(kTrackedObjectKey, new IDUserData(weak_map_id_)); diff --git a/atom/browser/api/trackable_object.h b/atom/browser/api/trackable_object.h index 8ff7d1c040..975bb130b6 100644 --- a/atom/browser/api/trackable_object.h +++ b/atom/browser/api/trackable_object.h @@ -30,15 +30,18 @@ class TrackableObjectBase : public mate::EventEmitter { // Wrap TrackableObject into a class that SupportsUserData. void AttachAsUserData(base::SupportsUserData* wrapped); - // Subclasses should implement this to destroy their native types. - virtual void Destroy() = 0; - protected: ~TrackableObjectBase() override; // mate::Wrappable: void AfterInit(v8::Isolate* isolate) override; + // Mark the JS object as destroyed. + void MarkDestroyed(); + + // Returns a closure that can destroy the native class. + base::Closure GetDestroyClosure(); + // Get the weak_map_id from SupportsUserData. static int32_t GetIDFromWrappedClass(base::SupportsUserData* wrapped); @@ -50,6 +53,8 @@ class TrackableObjectBase : public mate::EventEmitter { base::SupportsUserData* wrapped_; private: + void Destroy(); + base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(TrackableObjectBase); diff --git a/vendor/native_mate b/vendor/native_mate index 9398494100..e859228db1 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit 93984941005bab194a2d47aff655d525c064efcb +Subproject commit e859228db163c27410fb200c2df0715478fdf0d7 From a15f9fab5b76b3619d7411826beb6456850cbdd6 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 3 Dec 2015 16:04:46 +0800 Subject: [PATCH 111/411] Use BuildPrototype to build prototype This saves the step of manually keeping the global template object, which is easy to forget then leak. --- atom/browser/api/atom_api_cookies.cc | 17 +-- atom/browser/api/atom_api_cookies.h | 12 +- atom/browser/api/atom_api_download_item.cc | 7 +- atom/browser/api/atom_api_download_item.h | 10 +- atom/browser/api/atom_api_session.cc | 31 ++--- atom/browser/api/atom_api_session.h | 8 +- atom/browser/api/atom_api_web_contents.cc | 138 ++++++++++----------- atom/browser/api/atom_api_web_contents.h | 8 +- atom/browser/api/trackable_object.h | 18 +++ atom/common/api/atom_api_asar.cc | 25 ++-- 10 files changed, 148 insertions(+), 126 deletions(-) diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index a3b2c37c9a..3b1bd499be 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -322,14 +322,6 @@ void Cookies::OnSetCookies(const CookiesCallback& callback, callback)); } -mate::ObjectTemplateBuilder Cookies::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetMethod("get", &Cookies::Get) - .SetMethod("remove", &Cookies::Remove) - .SetMethod("set", &Cookies::Set); -} - net::CookieStore* Cookies::GetCookieStore() { return request_context_getter_->GetURLRequestContext()->cookie_store(); } @@ -341,6 +333,15 @@ mate::Handle Cookies::Create( return mate::CreateHandle(isolate, new Cookies(browser_context)); } +// static +void Cookies::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("get", &Cookies::Get) + .SetMethod("remove", &Cookies::Remove) + .SetMethod("set", &Cookies::Set); +} + } // namespace api } // namespace atom diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h index 0c309b3f18..5afa1bd23c 100644 --- a/atom/browser/api/atom_api_cookies.h +++ b/atom/browser/api/atom_api_cookies.h @@ -7,8 +7,8 @@ #include +#include "atom/browser/api/trackable_object.h" #include "base/callback.h" -#include "native_mate/wrappable.h" #include "native_mate/handle.h" #include "net/cookies/canonical_cookie.h" @@ -33,7 +33,7 @@ namespace atom { namespace api { -class Cookies : public mate::Wrappable { +class Cookies : public mate::TrackableObject { public: // node.js style callback function(error, result) typedef base::Callback, v8::Local)> @@ -42,6 +42,10 @@ class Cookies : public mate::Wrappable { static mate::Handle Create(v8::Isolate* isolate, content::BrowserContext* browser_context); + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: explicit Cookies(content::BrowserContext* browser_context); ~Cookies(); @@ -70,10 +74,6 @@ class Cookies : public mate::Wrappable { void OnSetCookies(const CookiesCallback& callback, bool set_success); - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - private: // Must be called on IO thread. net::CookieStore* GetCookieStore(); diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index fe63d70043..f186821e7d 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -135,9 +135,10 @@ void DownloadItem::Cancel() { download_item_->Cancel(true); } -mate::ObjectTemplateBuilder DownloadItem::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) +// static +void DownloadItem::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) .MakeDestroyable() .SetMethod("pause", &DownloadItem::Pause) .SetMethod("resume", &DownloadItem::Resume) diff --git a/atom/browser/api/atom_api_download_item.h b/atom/browser/api/atom_api_download_item.h index 412b6ce9bf..471913c226 100644 --- a/atom/browser/api/atom_api_download_item.h +++ b/atom/browser/api/atom_api_download_item.h @@ -17,7 +17,7 @@ namespace atom { namespace api { -class DownloadItem : public mate::EventEmitter, +class DownloadItem : public mate::TrackableObject, public content::DownloadItem::Observer { public: class SavePathData : public base::SupportsUserData::Data { @@ -32,6 +32,10 @@ class DownloadItem : public mate::EventEmitter, content::DownloadItem* item); static void* UserDataKey(); + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: explicit DownloadItem(content::DownloadItem* download_item); ~DownloadItem(); @@ -53,10 +57,6 @@ class DownloadItem : public mate::EventEmitter, void SetSavePath(const base::FilePath& path); private: - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - content::DownloadItem* download_item_; DISALLOW_COPY_AND_ASSIGN(DownloadItem); diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 495a4c46dd..f39073825c 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -367,21 +367,6 @@ v8::Local Session::Cookies(v8::Isolate* isolate) { return v8::Local::New(isolate, cookies_); } -mate::ObjectTemplateBuilder Session::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .MakeDestroyable() - .SetMethod("resolveProxy", &Session::ResolveProxy) - .SetMethod("clearCache", &Session::ClearCache) - .SetMethod("clearStorageData", &Session::ClearStorageData) - .SetMethod("setProxy", &Session::SetProxy) - .SetMethod("setDownloadPath", &Session::SetDownloadPath) - .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) - .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) - .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) - .SetProperty("cookies", &Session::Cookies); -} - // static mate::Handle Session::CreateFrom( v8::Isolate* isolate, AtomBrowserContext* browser_context) { @@ -402,6 +387,22 @@ mate::Handle Session::FromPartition( static_cast(browser_context.get())); } +// static +void Session::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .MakeDestroyable() + .SetMethod("resolveProxy", &Session::ResolveProxy) + .SetMethod("clearCache", &Session::ClearCache) + .SetMethod("clearStorageData", &Session::ClearStorageData) + .SetMethod("setProxy", &Session::SetProxy) + .SetMethod("setDownloadPath", &Session::SetDownloadPath) + .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) + .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) + .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) + .SetProperty("cookies", &Session::Cookies); +} + void ClearWrapSession() { g_wrap_session.Reset(); } diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 104a8e40cc..e800a992d4 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -48,6 +48,10 @@ class Session: public mate::TrackableObject, AtomBrowserContext* browser_context() const { return browser_context_.get(); } + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: explicit Session(AtomBrowserContext* browser_context); ~Session(); @@ -56,10 +60,6 @@ class Session: public mate::TrackableObject, void OnDownloadCreated(content::DownloadManager* manager, content::DownloadItem* item) override; - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - private: void ResolveProxy(const GURL& url, ResolveProxyCallback callback); void ClearCache(const net::CompletionCallback& callback); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 2f8353e273..da28dcf60c 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -194,8 +194,6 @@ namespace api { namespace { -v8::Persistent template_; - // The wrapWebContents function which is implemented in JavaScript using WrapWebContentsCallback = base::Callback)>; WrapWebContentsCallback g_wrap_web_contents; @@ -982,76 +980,72 @@ v8::Local WebContents::DevToolsWebContents(v8::Isolate* isolate) { return v8::Local::New(isolate, devtools_web_contents_); } -mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - if (template_.IsEmpty()) - template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) - .MakeDestroyable() - .SetMethod("getId", &WebContents::GetID) - .SetMethod("equal", &WebContents::Equal) - .SetMethod("_loadURL", &WebContents::LoadURL) - .SetMethod("_getURL", &WebContents::GetURL) - .SetMethod("getTitle", &WebContents::GetTitle) - .SetMethod("isLoading", &WebContents::IsLoading) - .SetMethod("isWaitingForResponse", &WebContents::IsWaitingForResponse) - .SetMethod("_stop", &WebContents::Stop) - .SetMethod("_goBack", &WebContents::GoBack) - .SetMethod("_goForward", &WebContents::GoForward) - .SetMethod("_goToOffset", &WebContents::GoToOffset) - .SetMethod("isCrashed", &WebContents::IsCrashed) - .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) - .SetMethod("isDevToolsOpened", &WebContents::IsDevToolsOpened) - .SetMethod("enableDeviceEmulation", - &WebContents::EnableDeviceEmulation) - .SetMethod("disableDeviceEmulation", - &WebContents::DisableDeviceEmulation) - .SetMethod("toggleDevTools", &WebContents::ToggleDevTools) - .SetMethod("inspectElement", &WebContents::InspectElement) - .SetMethod("setAudioMuted", &WebContents::SetAudioMuted) - .SetMethod("isAudioMuted", &WebContents::IsAudioMuted) - .SetMethod("undo", &WebContents::Undo) - .SetMethod("redo", &WebContents::Redo) - .SetMethod("cut", &WebContents::Cut) - .SetMethod("copy", &WebContents::Copy) - .SetMethod("paste", &WebContents::Paste) - .SetMethod("pasteAndMatchStyle", &WebContents::PasteAndMatchStyle) - .SetMethod("delete", &WebContents::Delete) - .SetMethod("selectAll", &WebContents::SelectAll) - .SetMethod("unselect", &WebContents::Unselect) - .SetMethod("replace", &WebContents::Replace) - .SetMethod("replaceMisspelling", &WebContents::ReplaceMisspelling) - .SetMethod("focus", &WebContents::Focus) - .SetMethod("tabTraverse", &WebContents::TabTraverse) - .SetMethod("_send", &WebContents::SendIPCMessage) - .SetMethod("sendInputEvent", &WebContents::SendInputEvent) - .SetMethod("beginFrameSubscription", - &WebContents::BeginFrameSubscription) - .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) - .SetMethod("setSize", &WebContents::SetSize) - .SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency) - .SetMethod("isGuest", &WebContents::IsGuest) - .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) - .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) - .SetMethod("hasServiceWorker", &WebContents::HasServiceWorker) - .SetMethod("unregisterServiceWorker", - &WebContents::UnregisterServiceWorker) - .SetMethod("inspectServiceWorker", &WebContents::InspectServiceWorker) - .SetMethod("print", &WebContents::Print) - .SetMethod("_printToPDF", &WebContents::PrintToPDF) - .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) - .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) - .SetProperty("session", &WebContents::Session) - .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents) - .Build()); - - return mate::ObjectTemplateBuilder( - isolate, v8::Local::New(isolate, template_)); +// static +void WebContents::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .MakeDestroyable() + .SetMethod("getId", &WebContents::GetID) + .SetMethod("equal", &WebContents::Equal) + .SetMethod("_loadURL", &WebContents::LoadURL) + .SetMethod("_getURL", &WebContents::GetURL) + .SetMethod("getTitle", &WebContents::GetTitle) + .SetMethod("isLoading", &WebContents::IsLoading) + .SetMethod("isWaitingForResponse", &WebContents::IsWaitingForResponse) + .SetMethod("_stop", &WebContents::Stop) + .SetMethod("_goBack", &WebContents::GoBack) + .SetMethod("_goForward", &WebContents::GoForward) + .SetMethod("_goToOffset", &WebContents::GoToOffset) + .SetMethod("isCrashed", &WebContents::IsCrashed) + .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) + .SetMethod("isDevToolsOpened", &WebContents::IsDevToolsOpened) + .SetMethod("enableDeviceEmulation", + &WebContents::EnableDeviceEmulation) + .SetMethod("disableDeviceEmulation", + &WebContents::DisableDeviceEmulation) + .SetMethod("toggleDevTools", &WebContents::ToggleDevTools) + .SetMethod("inspectElement", &WebContents::InspectElement) + .SetMethod("setAudioMuted", &WebContents::SetAudioMuted) + .SetMethod("isAudioMuted", &WebContents::IsAudioMuted) + .SetMethod("undo", &WebContents::Undo) + .SetMethod("redo", &WebContents::Redo) + .SetMethod("cut", &WebContents::Cut) + .SetMethod("copy", &WebContents::Copy) + .SetMethod("paste", &WebContents::Paste) + .SetMethod("pasteAndMatchStyle", &WebContents::PasteAndMatchStyle) + .SetMethod("delete", &WebContents::Delete) + .SetMethod("selectAll", &WebContents::SelectAll) + .SetMethod("unselect", &WebContents::Unselect) + .SetMethod("replace", &WebContents::Replace) + .SetMethod("replaceMisspelling", &WebContents::ReplaceMisspelling) + .SetMethod("focus", &WebContents::Focus) + .SetMethod("tabTraverse", &WebContents::TabTraverse) + .SetMethod("_send", &WebContents::SendIPCMessage) + .SetMethod("sendInputEvent", &WebContents::SendInputEvent) + .SetMethod("beginFrameSubscription", + &WebContents::BeginFrameSubscription) + .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) + .SetMethod("setSize", &WebContents::SetSize) + .SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency) + .SetMethod("isGuest", &WebContents::IsGuest) + .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) + .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) + .SetMethod("hasServiceWorker", &WebContents::HasServiceWorker) + .SetMethod("unregisterServiceWorker", + &WebContents::UnregisterServiceWorker) + .SetMethod("inspectServiceWorker", &WebContents::InspectServiceWorker) + .SetMethod("print", &WebContents::Print) + .SetMethod("_printToPDF", &WebContents::PrintToPDF) + .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) + .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) + .SetProperty("session", &WebContents::Session) + .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents); } AtomBrowserContext* WebContents::GetBrowserContext() const { diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 9462e926ee..637785aace 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -141,15 +141,15 @@ class WebContents : public mate::TrackableObject, v8::Local Session(v8::Isolate* isolate); v8::Local DevToolsWebContents(v8::Isolate* isolate); + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + protected: explicit WebContents(content::WebContents* web_contents); WebContents(v8::Isolate* isolate, const mate::Dictionary& options); ~WebContents(); - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - // content::WebContentsDelegate: bool AddMessageToConsole(content::WebContents* source, int32 level, diff --git a/atom/browser/api/trackable_object.h b/atom/browser/api/trackable_object.h index 975bb130b6..562ab5ba4f 100644 --- a/atom/browser/api/trackable_object.h +++ b/atom/browser/api/trackable_object.h @@ -12,6 +12,7 @@ #include "base/bind.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" +#include "native_mate/object_template_builder.h" namespace base { class SupportsUserData; @@ -120,16 +121,33 @@ class TrackableObject : public TrackableObjectBase { } private: + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override { + if (template_.IsEmpty()) { + auto templ = v8::ObjectTemplate::New(isolate); + T::BuildPrototype(isolate, templ); + template_.Reset(isolate, templ); + } + + return ObjectTemplateBuilder( + isolate, v8::Local::New(isolate, template_)); + } + // Releases all weak references in weak map, called when app is terminating. static void ReleaseAllWeakReferences() { weak_map_.reset(); } + static v8::Persistent template_; static scoped_ptr weak_map_; DISALLOW_COPY_AND_ASSIGN(TrackableObject); }; +template +v8::Persistent TrackableObject::template_; + template scoped_ptr TrackableObject::weak_map_; diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index 4ea7d8c5c3..7aee71fc32 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -18,6 +18,8 @@ namespace { +v8::Persistent template_; + class Archive : public mate::Wrappable { public: static v8::Local Create(v8::Isolate* isolate, @@ -101,15 +103,20 @@ class Archive : public mate::Wrappable { // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetValue("path", archive_->path()) - .SetMethod("getFileInfo", &Archive::GetFileInfo) - .SetMethod("stat", &Archive::Stat) - .SetMethod("readdir", &Archive::Readdir) - .SetMethod("realpath", &Archive::Realpath) - .SetMethod("copyFileOut", &Archive::CopyFileOut) - .SetMethod("getFd", &Archive::GetFD) - .SetMethod("destroy", &Archive::Destroy); + if (template_.IsEmpty()) + template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) + .SetValue("path", archive_->path()) + .SetMethod("getFileInfo", &Archive::GetFileInfo) + .SetMethod("stat", &Archive::Stat) + .SetMethod("readdir", &Archive::Readdir) + .SetMethod("realpath", &Archive::Realpath) + .SetMethod("copyFileOut", &Archive::CopyFileOut) + .SetMethod("getFd", &Archive::GetFD) + .SetMethod("destroy", &Archive::Destroy) + .Build()); + + return mate::ObjectTemplateBuilder( + isolate, v8::Local::New(isolate, template_)); } private: From 117b7462dea71db40ee4f43039d9893ac2cad719 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 3 Dec 2015 16:17:10 +0800 Subject: [PATCH 112/411] window.id is no longer available when window is closed --- atom/browser/lib/guest-window-manager.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index c311e01cf4..2394b582ec 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -39,11 +39,12 @@ createGuest = (embedder, url, frameName, options) -> # When |embedder| is destroyed we should also destroy attached guest, and if # guest is closed by user then we should prevent |embedder| from double # closing guest. + guestId = guest.id closedByEmbedder = -> guest.removeListener 'closed', closedByUser guest.destroy() closedByUser = -> - embedder.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', guest.id + embedder.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', guestId embedder.removeListener 'render-view-deleted', closedByEmbedder embedder.once 'render-view-deleted', closedByEmbedder guest.once 'closed', closedByUser From 1e7c8c9fda4072d9a0672d0f9125c4edec60f272 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 3 Dec 2015 16:33:57 +0800 Subject: [PATCH 113/411] It is fine to leak a V8 handle on exit --- atom/common/native_mate_converters/callback.cc | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/atom/common/native_mate_converters/callback.cc b/atom/common/native_mate_converters/callback.cc index cd9838c1ed..8bf5c459b0 100644 --- a/atom/common/native_mate_converters/callback.cc +++ b/atom/common/native_mate_converters/callback.cc @@ -4,7 +4,6 @@ #include "atom/common/native_mate_converters/callback.h" -#include "atom/browser/atom_browser_main_parts.h" #include "content/public/browser/browser_thread.h" using content::BrowserThread; @@ -78,15 +77,7 @@ class RefCountedGlobal : public base::RefCountedThreadSafe, DeleteOnUIThread> { public: RefCountedGlobal(v8::Isolate* isolate, v8::Local value) - : handle_(isolate, v8::Local::Cast(value)), - weak_factory_(this) { - // In browser process, we need to ensure the V8 handle is destroyed before - // the JavaScript env gets destroyed. - if (Locker::IsBrowserProcess() && atom::AtomBrowserMainParts::Get()) { - atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( - base::Bind(&RefCountedGlobal::FreeHandle, - weak_factory_.GetWeakPtr())); - } + : handle_(isolate, v8::Local::Cast(value)) { } bool IsAlive() const { @@ -98,12 +89,7 @@ class RefCountedGlobal : public base::RefCountedThreadSafe, } private: - void FreeHandle() { - handle_.Reset(); - } - v8::Global handle_; - base::WeakPtrFactory> weak_factory_; DISALLOW_COPY_AND_ASSIGN(RefCountedGlobal); }; From eb8426269f7ed36c043dc41cde35a7f3717602e6 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 3 Dec 2015 17:04:40 +0800 Subject: [PATCH 114/411] Remove itself from the cleanup list when it is destroyed --- atom/browser/api/trackable_object.cc | 9 +++++---- atom/browser/api/trackable_object.h | 3 ++- atom/browser/atom_browser_main_parts.cc | 17 ++++++++++++++--- atom/browser/atom_browser_main_parts.h | 5 +++-- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/atom/browser/api/trackable_object.cc b/atom/browser/api/trackable_object.cc index 5b1c6b8240..77a936cde0 100644 --- a/atom/browser/api/trackable_object.cc +++ b/atom/browser/api/trackable_object.cc @@ -30,10 +30,11 @@ class IDUserData : public base::SupportsUserData::Data { TrackableObjectBase::TrackableObjectBase() : weak_map_id_(0), wrapped_(nullptr), weak_factory_(this) { - RegisterDestructionCallback(GetDestroyClosure()); + cleanup_ = RegisterDestructionCallback(GetDestroyClosure()); } TrackableObjectBase::~TrackableObjectBase() { + cleanup_.Run(); } void TrackableObjectBase::AfterInit(v8::Isolate* isolate) { @@ -74,9 +75,9 @@ int32_t TrackableObjectBase::GetIDFromWrappedClass(base::SupportsUserData* w) { } // static -void TrackableObjectBase::RegisterDestructionCallback( - const base::Closure& closure) { - atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback(closure); +base::Closure TrackableObjectBase::RegisterDestructionCallback( + const base::Closure& c) { + return atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback(c); } } // namespace mate diff --git a/atom/browser/api/trackable_object.h b/atom/browser/api/trackable_object.h index 562ab5ba4f..8aad497034 100644 --- a/atom/browser/api/trackable_object.h +++ b/atom/browser/api/trackable_object.h @@ -48,7 +48,7 @@ class TrackableObjectBase : public mate::EventEmitter { // Register a callback that should be destroyed before JavaScript environment // gets destroyed. - static void RegisterDestructionCallback(const base::Closure& closure); + static base::Closure RegisterDestructionCallback(const base::Closure& c); int32_t weak_map_id_; base::SupportsUserData* wrapped_; @@ -56,6 +56,7 @@ class TrackableObjectBase : public mate::EventEmitter { private: void Destroy(); + base::Closure cleanup_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(TrackableObjectBase); diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index 3bfe3748f1..fd72f5e4ae 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -25,6 +25,11 @@ namespace atom { +template +void Erase(T* container, typename T::iterator iter) { + container->erase(iter); +} + // static AtomBrowserMainParts* AtomBrowserMainParts::self_ = NULL; @@ -56,9 +61,10 @@ bool AtomBrowserMainParts::SetExitCode(int code) { return true; } -void AtomBrowserMainParts::RegisterDestructionCallback( +base::Closure AtomBrowserMainParts::RegisterDestructionCallback( const base::Closure& callback) { - destruction_callbacks_.push_back(callback); + auto iter = destructors_.insert(destructors_.end(), callback); + return base::Bind(&Erase>, &destructors_, iter); } void AtomBrowserMainParts::PreEarlyInitialization() { @@ -150,8 +156,13 @@ void AtomBrowserMainParts::PostMainMessageLoopRun() { // Make sure destruction callbacks are called before message loop is // destroyed, otherwise some objects that need to be deleted on IO thread // won't be freed. - for (const auto& callback : destruction_callbacks_) + // We don't use ranged for loop because iterators are getting invalided when + // the callback runs. + for (auto iter = destructors_.begin(); iter != destructors_.end();) { + base::Closure& callback = *iter; + ++iter; callback.Run(); + } // Destroy JavaScript environment immediately after running destruction // callbacks. diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index bb4b204669..fbc59f7f81 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -36,7 +36,8 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { // Register a callback that should be destroyed before JavaScript environment // gets destroyed. - void RegisterDestructionCallback(const base::Closure& callback); + // Returns a closure that can be used to remove |callback| from the list. + base::Closure RegisterDestructionCallback(const base::Closure& callback); Browser* browser() { return browser_.get(); } @@ -82,7 +83,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { base::Timer gc_timer_; // List of callbacks should be executed before destroying JS env. - std::list destruction_callbacks_; + std::list destructors_; static AtomBrowserMainParts* self_; From 48a11bd237df77953d6136958a28a9e824c30551 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 3 Dec 2015 17:10:14 +0800 Subject: [PATCH 115/411] Weak map only needs to be deleted for once --- atom/browser/api/trackable_object.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/trackable_object.h b/atom/browser/api/trackable_object.h index 8aad497034..7c4ed03fe0 100644 --- a/atom/browser/api/trackable_object.h +++ b/atom/browser/api/trackable_object.h @@ -98,11 +98,6 @@ class TrackableObject : public TrackableObjectBase { return std::vector>(); } - TrackableObject() { - RegisterDestructionCallback( - base::Bind(&TrackableObject::ReleaseAllWeakReferences)); - } - // Removes this instance from the weak map. void RemoveFromWeakMap() { if (weak_map_ && weak_map_->Has(weak_map_id())) @@ -110,13 +105,17 @@ class TrackableObject : public TrackableObjectBase { } protected: + TrackableObject() {} ~TrackableObject() override { RemoveFromWeakMap(); } void AfterInit(v8::Isolate* isolate) override { - if (!weak_map_) + if (!weak_map_) { weak_map_.reset(new atom::IDWeakMap); + RegisterDestructionCallback( + base::Bind(&TrackableObject::ReleaseAllWeakReferences)); + } weak_map_id_ = weak_map_->Add(isolate, GetWrapper(isolate)); TrackableObjectBase::AfterInit(isolate); } From dec714bda49f9af82dc923ae73be7c40360ac0ea Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 3 Dec 2015 08:40:47 -0800 Subject: [PATCH 116/411] Add newline before example code block --- docs/api/web-view-tag.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 56909a52e3..e317ef8eab 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -164,6 +164,7 @@ The `webview` tag has the following methods: **Note:** The webview element must be loaded before using the methods. **Example** + ```javascript webview.addEventListener("dom-ready", function() { webview.openDevTools(); From c412b9b892fae6d6606b23018fd89f4e9888f76d Mon Sep 17 00:00:00 2001 From: Charlie Hess Date: Thu, 3 Dec 2015 12:14:08 -0800 Subject: [PATCH 117/411] Revert accidental change to this submodule. --- vendor/native_mate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/native_mate b/vendor/native_mate index 9398494100..e859228db1 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit 93984941005bab194a2d47aff655d525c064efcb +Subproject commit e859228db163c27410fb200c2df0715478fdf0d7 From d583cf7d8cbe8886e689c3018744cdcbf5f16ebb Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 4 Dec 2015 07:54:09 +0900 Subject: [PATCH 118/411] Update as upstream [ci skip] --- docs-translations/ko-KR/api/web-view-tag.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs-translations/ko-KR/api/web-view-tag.md b/docs-translations/ko-KR/api/web-view-tag.md index 531b19adce..935976b6bd 100644 --- a/docs-translations/ko-KR/api/web-view-tag.md +++ b/docs-translations/ko-KR/api/web-view-tag.md @@ -158,6 +158,7 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 **참고:** 태그 객체의 메서드는 페이지 로드가 끝난 뒤에만 사용할 수 있습니다. **예제** + ```javascript webview.addEventListener("dom-ready", function() { webview.openDevTools(); From 149859c97b5eec2dafdf715d1218ee8c50fef302 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 3 Dec 2015 18:06:18 -0600 Subject: [PATCH 119/411] add CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..444ce0b4c8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,24 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery +- Personal attacks +- Trolling or insulting/derogatory comments +- Public or private harassment +- Publishing other's private information, such as physical or electronic addresses, without explicit permission +- Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. + +This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/ From 7951226f5c3d88e047594fbe1cee4d5a7b129d52 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 3 Dec 2015 18:06:58 -0600 Subject: [PATCH 120/411] link to CoC from README and CONTRIBUTING --- CONTRIBUTING.md | 5 +++-- README.md | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 630b8b0ba0..67c1323205 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,9 @@ :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: -This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0). -By participating, you are expected to uphold this code. Please report unacceptable behavior to atom@github.com. +This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable +behavior to atom@github.com. The following is a set of guidelines for contributing to Electron. These are just guidelines, not rules, use your best judgment and feel free to diff --git a/README.md b/README.md index db82fac88e..8052e705bc 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ editor](https://github.com/atom/atom). Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important announcements. -This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0/). -By participating, you are expected to uphold this code. Please report -unacceptable behavior to atom@github.com. +This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable +behavior to atom@github.com. ## Downloads From 53350d26ae1b9f109636bde17f59f8c6c2f33891 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 3 Dec 2015 18:08:10 -0600 Subject: [PATCH 121/411] add placeholder CoC content to Korean README --- README-ko.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README-ko.md b/README-ko.md index 606d4e973e..7546f8c9f9 100644 --- a/README-ko.md +++ b/README-ko.md @@ -16,9 +16,7 @@ Cross-Platform 데스크톱 어플리케이션을 개발할 수 있도록 해주 Electron에 대한 중요한 알림을 받고 싶다면 Twitter에서 [@ElectronJS](https://twitter.com/electronjs)를 팔로우 하세요. -이 프로젝트는 [기여자 규약 1.2](http://contributor-covenant.org/version/1/2/0/)을 -준수합니다. 따라서 이 프로젝트의 개발에 참여하려면 이 계약을 지켜야 합니다. 받아들일 수 -없는 행위를 발견했을 경우 atom@github.com로 보고 하십시오. +[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) ## 다운로드 From 06a9ad3984cc388005b7f7ab3d062da8e89e202c Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 4 Dec 2015 10:06:25 +0900 Subject: [PATCH 122/411] Add contributing guide [ci skip] --- CONTRIBUTING-ko.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 CONTRIBUTING-ko.md diff --git a/CONTRIBUTING-ko.md b/CONTRIBUTING-ko.md new file mode 100644 index 0000000000..6747ad4690 --- /dev/null +++ b/CONTRIBUTING-ko.md @@ -0,0 +1,80 @@ +# Electron에 기여하기 + +:+1::tada: 먼저, 이 프로젝트에 기여해주셔서 감사합니다! :tada::+1: + +이 프로젝트는 기여자 규약 [행동 강령](CODE_OF_CONDUCT.md)을 +준수합니다. 따라서 이 프로젝트의 개발에 참여하려면 이 규약을 지켜야 합니다. 받아들일 수 +없는 행위를 발견했을 경우 atom@github.com로 보고 하십시오. + +다음 항목들은 Electron에 기여하는 가이드라인을 제시합니다. +참고로 이 항목들은 그저 가이드라인에 불과하며 규칙이 아닙니다. 따라서 스스로의 적절한 +판단에 따라 이 문서의 변경을 제안할 수 있으며 변경시 pull request를 넣으면 됩니다. + +## 이슈 제출 + +* [여기](https://github.com/atom/electron/issues/new)에서 새로운 이슈를 만들 수 +있습니다. 하지만 이슈를 작성하기 전에 아래의 항목들을 숙지하고 가능한한 이슈 보고에 +대해 최대한 많은 정보와 자세한 설명을 포함해야 합니다. 가능하다면 다음 항목을 포함해야 +합니다: + * 사용하고 있는 Electron의 버전 + * 현재 사용중인 운영체제 + * 가능하다면 무엇을 하려고 했고, 어떤 결과를 예측했으며, 어떤 것이 예측된대로 + 작동하지 않았는지에 대해 서술해야 합니다. +* 추가로 다음 사항을 준수하면 이슈를 해결하는데 큰 도움이 됩니다: + * 스크린샷 또는 GIF 애니메이션 이미지들 + * 터미널에 출력된 에러의 내용 또는 개발자 도구, 알림창에 뜬 내용 + * [Cursory search](https://github.com/atom/electron/issues?utf8=✓&q=is%3Aissue+)를 + 통해 이미 비슷한 내용의 이슈가 등록되어있는지 확인 + +## Pull Request 하기 + +* 가능한한 스크린샷과 GIF 애니메이션 이미지를 pull request에 추가 +* CoffeeScript, JavaScript, C++과 Python등 +[참조문서에 정의된 코딩스타일](/docs-translations/ko-KR/development/coding-style.md)을 +준수 +* [문서 스타일 가이드](/docs-translations/ko-KR/styleguide.md)에 따라 문서를 +[Markdown](https://daringfireball.net/projects/markdown) 형식으로 작성. +* 짧은, 현재 시제 커밋 메시지 사용. [커밋 메시지 스타일 가이드](#Git-커밋-메시지)를 +참고하세요 + +## 스타일 가이드 + +### 공통 코드 + +* 파일 마지막에 공백 라인(newline) 추가 +* 다음 순서에 맞춰서 require 코드 작성: + * Node 빌트인 모듈 (`path` 같은) + * Electron 모듈 (`ipc`, `app` 같은) + * 로컬 모듈 (상대 경로상에 있는) +* 다음 순서에 맞춰서 클래스 속성 지정: + * 클래스 메서드와 속성 (메서드는 `@`로 시작) + * 인스턴스 메서드와 속성 +* 플랫폼 종속적인 코드 자제: + * 파일 이름 결합시 `path.join()`을 사용. + * 임시 디렉터리가 필요할 땐 `/tmp` 대신 `os.tmpdir()`을 통해 접근. +* 명시적인 함수 종료가 필요할 땐 `return` 만 사용. + * `return null`, `return undefined`, `null`, 또는 `undefined` 사용 X + +### Git 커밋 메시지 + +* 현재 시제 사용 ("Added feature" 대신 "Add feature" 사용) +* 필수적 분위기(imperative mood) 사용 ("Moves cursor to..." 대신 "Move cursor to..." 사용) +* 첫 줄은 72자에 맞추거나 그 보다 적게 제한 +* 자유롭게 필요에 따라 이슈나 PR링크를 참조 +* 단순한 문서 변경일 경우 `[ci skip]`을 커밋 메시지에 추가 +* 커밋 메시지의 도입부에 의미있는 이모티콘 사용: + * :art: `:art:` 코드의 포맷이나 구조를 개선(추가)했을 때 + * :racehorse: `:racehorse:` 성능을 개선했을 때 + * :non-potable_water: `:non-potable_water:` 메모리 누수를 연결했을 때 + * :memo: `:memo:` 문서를 작성했을 때 + * :penguin: `:penguin:` Linux에 대한 패치를 했을 때 + * :apple: `:apple:` Mac OS에 대한 패치를 했을 때 + * :checkered_flag: `:checkered_flag:` Windows에 대한 패치를 했을 때 + * :bug: `:bug:` 버그를 고쳤을 때 + * :fire: `:fire:` 코드 또는 파일을 삭제했을 때 + * :green_heart: `:green_heart:` CI 빌드를 고쳤을 때 + * :white_check_mark: `:white_check_mark:` 테스트를 추가했을 때 + * :lock: `:lock:` 보안 문제를 해결했을 때 + * :arrow_up: `:arrow_up:` 종속성 라이브러리를 업데이트 했을 때 + * :arrow_down: `:arrow_down:` 종속성 라이브러리를 다운그레이드 했을 때 + * :shirt: `:shirt:` linter(코드 검사기)의 경고를 제거했을 때 From 2c6d23225460be0102ab7aeb0da0c6c14b09f838 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 4 Dec 2015 11:02:55 +0800 Subject: [PATCH 123/411] Don't add too much listeners in BrowserWindowProxy --- atom/browser/lib/guest-window-manager.coffee | 2 +- atom/renderer/lib/override.coffee | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 35ef7b89bb..eea26462b6 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -44,7 +44,7 @@ createGuest = (embedder, url, frameName, options) -> guest.removeListener 'closed', closedByUser guest.destroy() closedByUser = -> - embedder.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', guestId + embedder.send "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{guestId}" embedder.removeListener 'render-view-deleted', closedByEmbedder embedder.once 'render-view-deleted', closedByEmbedder guest.once 'closed', closedByUser diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 0b60ce0d66..cb4fb8fbac 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -10,9 +10,8 @@ resolveURL = (url) -> class BrowserWindowProxy constructor: (@guestId) -> @closed = false - ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', (event, guestId) => - if guestId is @guestId - @closed = true + ipcRenderer.once "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{@guestId}", => + @closed = true close: -> ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', @guestId From 973ae06f214122686c501ec28ff5f55e60fd4f0e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 4 Dec 2015 11:35:04 +0800 Subject: [PATCH 124/411] Destroy the native window in next tick It fixes a possible crash when native code is iterating all windows while the JavaScript code decides to destroy a window. --- atom/browser/api/atom_api_window.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 79c91db3fa..84e5c53ebe 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -159,6 +159,10 @@ Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { Window::~Window() { if (!window_->IsClosed()) window_->CloseContents(nullptr); + + // Destroy the native window in next tick because the native code might be + // iterating all windows. + base::MessageLoop::current()->DeleteSoon(FROM_HERE, window_.release()); } void Window::WillCloseWindow(bool* prevent_default) { From 5e5ae81c536948d4bd39fe78de26880dea3e46cb Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 4 Dec 2015 11:40:35 +0800 Subject: [PATCH 125/411] 'key of' is better than Object.keys --- atom/browser/lib/guest-window-manager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index eea26462b6..53bbb735b0 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -5,7 +5,7 @@ frameToGuest = {} # Copy attribute of |parent| to |child| if it is not defined in |child|. mergeOptions = (child, parent) -> - for own key, value of parent when key not in Object.keys child + for own key, value of parent when key not of child if typeof value is 'object' child[key] = mergeOptions {}, value else From 13c737823bee91f7f486f7d341b3d5712f6d5733 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 4 Dec 2015 11:52:34 +0800 Subject: [PATCH 126/411] spec: Suppress execFileSync test It somehow makes the test flaky after refresh. --- spec/asar-spec.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index 495d89734e..479443292b 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -404,7 +404,8 @@ describe 'asar package', -> assert.equal stdout, 'test\n' done() - it 'execFileSync executes binaries', -> + # execFileSync makes the test flaky after a refresh. + xit 'execFileSync executes binaries', -> output = execFileSync echo, ['test'] assert.equal String(output), 'test\n' From 01f9107f0057df8fb66be6f0d9a9bf4f16665ea4 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 4 Dec 2015 13:48:38 +0900 Subject: [PATCH 127/411] Update as upstream [ci skip] --- CONTRIBUTING-ko.md | 6 +++--- README-ko.md | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING-ko.md b/CONTRIBUTING-ko.md index 6747ad4690..52887c8d38 100644 --- a/CONTRIBUTING-ko.md +++ b/CONTRIBUTING-ko.md @@ -2,9 +2,9 @@ :+1::tada: 먼저, 이 프로젝트에 기여해주셔서 감사합니다! :tada::+1: -이 프로젝트는 기여자 규약 [행동 강령](CODE_OF_CONDUCT.md)을 -준수합니다. 따라서 이 프로젝트의 개발에 참여하려면 이 규약을 지켜야 합니다. 받아들일 수 -없는 행위를 발견했을 경우 atom@github.com로 보고 하십시오. +이 프로젝트는 기여자 규약 [행동강령](CODE_OF_CONDUCT.md)을 준수합니다. 따라서 이 +프로젝트의 개발에 참여하려면 이 규약을 지켜야 합니다. 받아들일 수 없는 행위를 발견했을 +경우 atom@github.com로 보고 하십시오. 다음 항목들은 Electron에 기여하는 가이드라인을 제시합니다. 참고로 이 항목들은 그저 가이드라인에 불과하며 규칙이 아닙니다. 따라서 스스로의 적절한 diff --git a/README-ko.md b/README-ko.md index 7546f8c9f9..2bd24dfe58 100644 --- a/README-ko.md +++ b/README-ko.md @@ -16,7 +16,9 @@ Cross-Platform 데스크톱 어플리케이션을 개발할 수 있도록 해주 Electron에 대한 중요한 알림을 받고 싶다면 Twitter에서 [@ElectronJS](https://twitter.com/electronjs)를 팔로우 하세요. -[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) +이 프로젝트는 기여자 규약 [행동강령](CODE_OF_CONDUCT.md)을 준수합니다. 따라서 이 +프로젝트의 개발에 참여하려면 이 규약을 지켜야 합니다. 받아들일 수 없는 행위를 발견했을 +경우 atom@github.com로 보고 하십시오. ## 다운로드 From 0bcc23d8fe17d5da943c92e036d2439411d51033 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 4 Dec 2015 13:52:03 +0900 Subject: [PATCH 128/411] Update as upstream [ci skip] --- docs-translations/ko-KR/api/web-contents.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index 82d426c1ce..4667ba7339 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -240,6 +240,13 @@ const options = {"extraHeaders" : "pragma: no-cache\n"} webContents.loadURL(url, options) ``` +### `webContents.downloadURL(url)` + +* `url` URL + +`url`의 리소스를 탐색 없이 다운로드를 시작합니다. `session`의 `will-download` +이벤트가 발생합니다. + ### `webContents.getURL()` 현재 웹 페이지의 URL을 반환합니다. From 7c1ea0b0f46334c31c59928731efb776bdbc7cc2 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 4 Dec 2015 12:25:46 +0800 Subject: [PATCH 129/411] spec: Suppress flaky tests on Travis --- spec/api-protocol-spec.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/api-protocol-spec.coffee b/spec/api-protocol-spec.coffee index 5332b2b17c..034b976592 100644 --- a/spec/api-protocol-spec.coffee +++ b/spec/api-protocol-spec.coffee @@ -365,6 +365,9 @@ describe 'protocol module', -> done(error) it 'sends error when callback is called with nothing', (done) -> + # Flaky on Travis. + return done() if process.env.TRAVIS is 'true' + protocol.interceptBufferProtocol 'http', emptyHandler, (error) -> return done(error) if error $.ajax From c4f2d946e135b3a66572e685cd1856c9ed30b4d5 Mon Sep 17 00:00:00 2001 From: "Howard.Zuo" Date: Fri, 4 Dec 2015 15:18:55 +0800 Subject: [PATCH 130/411] add translation of using-native-node-modules for ZH-CN --- .../tutorial/using-native-node-modules.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs-translations/zh-CN/tutorial/using-native-node-modules.md diff --git a/docs-translations/zh-CN/tutorial/using-native-node-modules.md b/docs-translations/zh-CN/tutorial/using-native-node-modules.md new file mode 100644 index 0000000000..e03e7b8948 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/using-native-node-modules.md @@ -0,0 +1,53 @@ +# 使用原生模块 + +Electron同样也支持原生模块,但由于和官方的Node相比使用了不同的V8引擎,如果你想编译原生模块,则需要手动设置Electron的headers的位置。 + +## 原生Node模块的兼容性 + +当Node开始换新的V8引擎版本时,原生模块可能“坏”掉。为确保一切工作正常,你需要检查你想要使用的原生模块是否被Electron内置的Node支持。你可以在[这里](https://github.com/atom/electron/releases)查看Electron内置的Node版本,或者使用`process.version`(参考:[快速入门](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md))查看。 + +考虑到[NAN](https://github.com/nodejs/nan/)可以使你的开发更容易对多版本Node的支持,建议使用它来开发你自己的模块。你也可以使用[NAN](https://github.com/nodejs/nan/)来移植旧的模块到新的Node版本,以使它们可以在新的Electron下良好工作。 + +## 如何安装原生模块 + +如下三种方法教你安装原生模块: + +### 最简单方式 + +最简单的方式就是通过[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild)包重新编译原生模块,它帮你自动完成了下载headers、编译原生模块等步骤: + +```sh +npm install --save-dev electron-rebuild + +# 每次运行"npm install"时,也运行这条命令 +./node_modules/.bin/electron-rebuild + +# 在windows下如果上述命令遇到了问题,尝试这个: +.\node_modules\.bin\electron-rebuild.cmd +``` + +### 通过npm安装 + +你当然也可以通过`npm`安装原生模块。大部分步骤和安装普通模块时一样,除了以下一些系统环境变量你需要自己操作: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.33.1 +export npm_config_arch=x64 +export npm_config_runtime=electron +HOME=~/.electron-gyp npm install module-name +``` + +### 通过node-gyp安装 + +你需要告诉`node-gyp`去哪下载Electron的headers,以及下载什么版本: + +```bash +$ cd /path-to-module/ +$ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell +``` + +`HOME=~/.electron-gyp`设置了去哪找开发时的headers。 +`--target=0.29.1`设置了Electron的版本 +`--dist-url=...`设置了Electron的headers的下载地址 +`--arch=x64`设置了该模块为适配64bit操作系统而编译 From c8e2be7b281d55bab8036058df9db52d90d14faa Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 4 Dec 2015 16:43:23 +0800 Subject: [PATCH 131/411] Bump v0.35.3 --- atom.gyp | 2 +- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atom.gyp b/atom.gyp index 202a414003..2f97e20674 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.35.2', + 'version%': '0.35.3', }, 'includes': [ 'filenames.gypi', diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index fb4a233233..dbb1f50826 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.35.2 + 0.35.3 CFBundleShortVersionString - 0.35.2 + 0.35.3 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 773871fd55..aaceb3420c 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,35,2,0 - PRODUCTVERSION 0,35,2,0 + FILEVERSION 0,35,3,0 + PRODUCTVERSION 0,35,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.35.2" + VALUE "FileVersion", "0.35.3" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.35.2" + VALUE "ProductVersion", "0.35.3" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index b5cc0982d0..c1593120fd 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 0 #define ATOM_MINOR_VERSION 35 -#define ATOM_PATCH_VERSION 2 +#define ATOM_PATCH_VERSION 3 #define ATOM_VERSION_IS_RELEASE 1 From d14f15c33ae831704abc72f8f4d973ef46908b8d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 4 Dec 2015 19:23:30 +0800 Subject: [PATCH 132/411] Update native_mate: isDestroy => isDestroyed --- vendor/native_mate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/native_mate b/vendor/native_mate index e859228db1..5e70868fd0 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit e859228db163c27410fb200c2df0715478fdf0d7 +Subproject commit 5e70868fd0c005dc2c43bea15ca6e93da0b68741 From e1d7ef7e24affd8f4ede3689c5354ef11036bf62 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 4 Dec 2015 19:23:48 +0800 Subject: [PATCH 133/411] Bump v0.35.4 --- atom.gyp | 2 +- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atom.gyp b/atom.gyp index 2f97e20674..9f1ffe5825 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.35.3', + 'version%': '0.35.4', }, 'includes': [ 'filenames.gypi', diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index dbb1f50826..179f26db03 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.35.3 + 0.35.4 CFBundleShortVersionString - 0.35.3 + 0.35.4 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index aaceb3420c..05fb3f336f 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,35,3,0 - PRODUCTVERSION 0,35,3,0 + FILEVERSION 0,35,4,0 + PRODUCTVERSION 0,35,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.35.3" + VALUE "FileVersion", "0.35.4" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.35.3" + VALUE "ProductVersion", "0.35.4" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index c1593120fd..a4930d0edb 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 0 #define ATOM_MINOR_VERSION 35 -#define ATOM_PATCH_VERSION 3 +#define ATOM_PATCH_VERSION 4 #define ATOM_VERSION_IS_RELEASE 1 From 220d05a39866caa147588c96f7377fcd160ba5c0 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Fri, 4 Dec 2015 21:07:33 +0000 Subject: [PATCH 134/411] Docs: Fix link to webcontents send channel --- docs/api/ipc-main.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/ipc-main.md b/docs/api/ipc-main.md index cdbc0ce34e..7c37a26c03 100644 --- a/docs/api/ipc-main.md +++ b/docs/api/ipc-main.md @@ -68,4 +68,4 @@ Returns the `webContents` that sent the message, you can call `event.sender.send` to reply to the asynchronous message, see [webContents.send][webcontents-send] for more information. -[webcontents-send]: web-contents.md#webcontentssendchannel-args +[webcontents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- From dcfbd294bb5219403a2778b46b8e4bb18c3aec74 Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Fri, 4 Dec 2015 21:55:03 -0200 Subject: [PATCH 135/411] :memo: [ci skip] browser window translation --- docs-translations/pt-BR/api/browser-window.md | 632 ++++++++++++++++++ 1 file changed, 632 insertions(+) create mode 100644 docs-translations/pt-BR/api/browser-window.md diff --git a/docs-translations/pt-BR/api/browser-window.md b/docs-translations/pt-BR/api/browser-window.md new file mode 100644 index 0000000000..143b6368bc --- /dev/null +++ b/docs-translations/pt-BR/api/browser-window.md @@ -0,0 +1,632 @@ +# BrowserWindow + +A classe `BrowserWindow` lhe dá a habilidade de criar uma janela do browser. Por exemplo: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600, show: false }); +win.on('closed', function() { + win = null; +}); + +win.loadURL('https://github.com'); +win.show(); +``` + +Você também pode criar uma janela sem o chrome utilizando a API [Frameless Window](../../../docs/api/frameless-window.md). + +## Classe: BrowserWindow + +`BrowserWindow` é um [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +Ela cria uma nova `BrowserWindow` com propriedades nativas definidas pelo `options`. + +### `new BrowserWindow([options])` + +`options` Objeto (opcional), propriedades: + +* `width` Integer - Largura da janela em pixels. O padrão é `800`. +* `height` Integer - Altura da janela em pixels. O padrão é `600`. +* `x` Integer - Deslocamento da janela da esquerda da tela. O padrão é centralizar a janela. +* `y` Integer - Deslocamento da janela do topo da tela. O padrão é centralizar a janela. +* `useContentSize` Boolean - `width` e `height` seriam utilizados como o tamanho da página web, o que significa que o tamanho real da janela irá incluir o tamanho da moldura da janela e será um pouco maior. O padrão é `false`. +* `center` Boolean - Mostra a janela no centro da tela. +* `minWidth` Integer - Largura mínima da janela. O padrão é `0`. +* `minHeight` Integer - Altura mínima da janela. O padrão é `0`. +* `maxWidth` Integer - Largura máxima da janela. O padrão é sem limites. +* `maxHeight` Integer - Altura máxima da janela. O padrão é sem limites. +* `resizable` Boolean - Se é possível modificar o tamanho da janela. O padrão é `true`. +* `alwaysOnTop` Boolean - Se a janela deve sempre ficar à frente de outras janelas. O padrão é `false`. +* `fullscreen` Boolean - Se a janela deve estar em tela cheia. Quando definido como `false`, o botão de tela cheia estará escondido ou desabilitado no OS X. O padrão é `false`. +* `skipTaskbar` Boolean - Se deve mostrar a janela na barra de tarefas. O padrão é `false`. +* `kiosk` Boolean - Modo *kiosk*. O padrão é `false`. +* `title` String - Título padrão da janela. O padrão é `"Electron"`. +* `icon` [NativeImage](../../../docs/api/native-image.md) - O ícone da janela, quando omitido no Windows, o ícone do executável é utilizado como o ícone da janela. +* `show` Boolean - Se a janela deve ser exibida quando criada. O padrão é `true`. +* `frame` Boolean - Defina como `false` para criar uma [Frameless Window](../../../docs/api/frameless-window.md). O padrão é `true`. +* `acceptFirstMouse` Boolean - Se o *web view* aceita um evento *mouse-down* que ativa a janela simultaneamente. O padrão é `false`. +* `disableAutoHideCursor` Boolean - Se deve esconder o cursor quando estiver digitando. O padrão é `false`. +* `autoHideMenuBar` Boolean - Esconde a barra de menu automaticamente, a não ser que a tecla `Alt` seja pressionada. O padrão é `false`. +* `enableLargerThanScreen` Boolean - Possibilita que o tamanho da janela seja maior que a tela. O padrão é `false`. +* `backgroundColor` String - Cor de fundo da janela em hexadecimal, como `#66CD00` ou `#FFF`. Só é implementado no Linux e Windows. O padrão é `#000` (preto). +* `darkTheme` Boolean - Força a utilização do tema *dark* na janela, só funciona em alguns ambientes de desktop GTK+3. O padrão é `false`. +* `transparent` Boolean - Torna a janela [transparente](../../../docs/api/frameless-window.md). O padrão é `false`. +* `type` String - Define o tipo da janela, que aplica propriedades adicionais específicas da plataforma. Por padrão é indefinido e será criada uma janela de aplicativo comum. Possíveis valores: + * No Linux, os tipos possíveis são `desktop`, `dock`, `toolbar`, `splash`, + `notification`. + * No OS X, os tipos possíveis são `desktop`, `textured`. O tipo `textured` adiciona a aparência degradê metálica (`NSTexturedBackgroundWindowMask`). O tipo `desktop` coloca a janela no nível do fundo de tela do desktop (`kCGDesktopWindowLevel - 1`). Note que a janela `desktop` não irá receber foco, eventos de teclado ou mouse, mas você pode usar `globalShortcut` para receber entrada de dados ocasionalmente. +* `titleBarStyle` String, OS X - Define o estilo da barra de título da janela. Esta opção está suportada a partir da versão OS X 10.10 Yosemite. Há três possíveis valores: + * `default` ou não definido, resulta na barra de título cinza opaca padrão do Mac. + * `hidden` resulta numa barra de título oculta e a janela de conteúdo no tamanho máximo, porém a barra de título ainda possui os controles padrões de janela ("semáforo") no canto superior esquerdo. + * `hidden-inset` resulta numa barra de título oculta com uma aparência alternativa onde os botões de semáforo estão ligeiramente mais longe do canto da janela. +* `webPreferences` Object - Configurações das características da página web, propriedades: + * `nodeIntegration` Boolean - Se a integração com node está habilitada. O padrão é `true`. + * `preload` String - Define um *script* que será carregado antes que outros scripts rodem na página. Este script sempre terá acesso às APIs do node, não importa se `nodeIntegration` esteja habilitada ou não. O valor deve ser o endereço absoluto do scriot. Quando `nodeIntegration` não está habilitada, o script `preload` pode reintroduzir símbolos globais Node de volta ao escopo global. Veja um exemplo [aqui](process.md#evento-loaded). + * `partition` String - Define a sessão utilizada pela página. Se `partition` começa com `persist:`, a página irá utilizar uma sessão persistente disponível para todas as páginas do aplicativo com a mesma `partition`. Se não houver o prefixo `persist:`, a página irá utilizar uma sessão em memória. Ao utilizar a mesma `partition`, várias páginas podem compartilhar a mesma sessão. Se a `partition` for indefinida, então a sessão padrão do aplicativo será utilizada. + * `zoomFactor` Number - O fator de *zoom* da página, `3.0` representa `300%`. O padrão é `1.0`. + * `javascript` Boolean - Habilita suporte à JavaScript. O padrão é `true`. + * `webSecurity` Boolean - Quando definido como `false`, irá desabilitar a política de mesma origem (Geralmente usando sites de teste por pessoas), e definir `allowDisplayingInsecureContent` e `allowRunningInsecureContent` como + `true` se estas duas opções não tiverem sido definidas pelo usuário. O padrão é `true`. + * `allowDisplayingInsecureContent` Boolean - Permite que uma página https exiba conteúdo como imagens de URLs http. O padrão é `false`. + * `allowRunningInsecureContent` Boolean - Permite que uma página https rode JavaScript, CSS ou plugins de URLs http. O padrão é `false`. + * `images` Boolean - Habilita suporte a imagens. O padrão é `true`. + * `java` Boolean - Habilita suporte a Java. O padrão é `false`. + * `textAreasAreResizable` Boolean - Faz com que os elementos *TextArea* elements tenham tamanho variável. O padrão é `true`. + * `webgl` Boolean - Habiltia suporte a WebGL. O padrão é `true`. + * `webaudio` Boolean - Habilita suporte a WebAudio. O padrão é `true`. + * `plugins` Boolean - Se plugins devem ser habilitados. O padrão é `false`. + * `experimentalFeatures` Boolean - Habilita as características experimentais do Chromium. O padrão é `false`. + * `experimentalCanvasFeatures` Boolean - Habilita as características experimentais de canvas do Chromium. O padrão é `false`. + * `overlayScrollbars` Boolean - Habilita sobreposição das barras de rolagem. O padrão é `false`. + * `overlayFullscreenVideo` Boolean - Habilita sobreposição do vídeo em tela cheia. O padrão é `false`. + * `sharedWorker` Boolean - Habilita suporte a *Shared Worker*. O padrão é `false`. + * `directWrite` Boolean - Habilita o sistema de renderização de fontes *DirectWrite* no Windows. O padrão é `true`. + * `pageVisibility` Boolean - A página é forçada a permanecer visível ou oculta quando definido, em vez de refletir a visibilidade atual da janela. Usuários podem definir como `true` para evitar que os temporizadores do *DOM* sejam suprimidos. O padrão é `false`. + +## Eventos + +O objeto `BrowserWindow` emite os seguintes eventos: + +**Nota:** Alguns eventos só estão disponíveis em sistemas operacionais específicos e estão rotulados como tal. + +### Evento: 'page-title-updated' + +Retorna: + +* `event` Evento + +Emitido quando o documento muda seu título, chamar `event.preventDefault()` previne que o título nativo da janela mude. + +### Evento: 'close' + +Retorna: + +* `event` Evento + +Emitido quando a janela for fechar. É emitido antes dos eventos `beforeunload` e `unload` do DOM. Chamar `event.preventDefault()` cancela o fechamento. + +Normalmente você utiliza o manipulador de eventos do `beforeunload` para decidir se a janela deve ser fechada, que também será chamado quando a janela é recarregada. No Electron, retornar uma string vazia ou `false` cancela o fechamento. Por exemplo: + +```javascript +window.onbeforeunload = function(e) { + console.log('Não quero ser fechada'); + + // Diferente de navegadores comuns, nos quais uma string deve ser retornada e + // o usuário deve confirmar se a janela será fechada, o Electron dá mais opções + // aos desenvolvedores. Retornar uma string vazia ou false cancela o fechamento. + // Você também pode usar a API de diálogo para deixar que o usuário confirme o + // fechamento do aplicativo. + e.returnValue = false; +}; +``` + +### Evento: 'closed' + +Emitido quando a janela é fechada. Após você ter recebido este evento, você deve remover a referência da janela e evitar utilizá-la. + +### Evento: 'unresponsive' + +Emitido quando a página web para de responder. + +### Evento: 'responsive' + +Emitido quando a página web que não respondia volta a responder novamente. + +### Evento: 'blur' + +Emitido quando a janela perde foco. + +### Evento: 'focus' + +Emitido quando a janela ganha foco. + +### Evento: 'maximize' + +Emitido quando a janela é maximizada. + +### Evento: 'unmaximize' + +Emitido quando a janela sai do estado maximizado. + +### Evento: 'minimize' + +Emitido quando a janela é minimizada. + +### Evento: 'restore' + +Emitido quando a janela é restaurada do estado minimizado. + +### Evento: 'resize' + +Emitido quando o tamanho da janela está sendo alterado. + +### Evento: 'move' + +Emitido quando está sendo movida para uma nova posição. + +__Note__: No OS X este evento é apenas um apelido de `moved`. + +### Evento: 'moved' _OS X_ + +Emitido uma vez quando a janela é movida para uma nova posição. + +### Evento: 'enter-full-screen' + +Emitido quando a janela entra no estado tela cheia. + +### Evento: 'leave-full-screen' + +Emitido quando a janela sai do estado de tela cheia. + +### Evento: 'enter-html-full-screen' + +Emitido quando a janela entra no estado tela cheia, ocasionado por uma api de html. + +### Evento: 'leave-html-full-screen' + +Emitido quando a janela sai do estado de tela cheia, ocasionado por uma api de html. + +### Evento: 'app-command' _Windows_ + +Emitido quando um [App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx) é invocado. Estes estão tipicamente relacionado às teclas de mídia do teclado, ou comandos do browser, assim como o botão "Voltar" existente em alguns modelos de mouse no Windows. + +```js +someWindow.on('app-command', function(e, cmd) { + // Navega a janela 'para trás' quando o usuário pressiona o botão voltar do mouse + if (cmd === 'browser-backward' && someWindow.webContents.canGoBack()) { + someWindow.webContents.goBack(); + } +}); +``` + +## Métodos + +O objeto `BrowserWindow` possui os seguintes métodos: + +### `BrowserWindow.getAllWindows()` + +Retorna um array de todas as janelas abertas. + +### `BrowserWindow.getFocusedWindow()` + +Retorna a janela que está focada no aplicativo. + +### `BrowserWindow.fromWebContents(webContents)` + +* `webContents` [WebContents](../../../docs/api/web-contents.md) + +Acha uma janela de acordo com os `webContents` que ela possui. + +### `BrowserWindow.fromId(id)` + +* `id` Integer + +Acha uma janela de acordo com o seu ID. + +### `BrowserWindow.addDevToolsExtension(path)` + +* `path` String + +Adiciona a extenção DevTools localizada no endereço `path`, e retorna o nome da extenção. + +A extenção será lembrada, então você só precisa chamar esta API uma vez, esta API não é para uso de programação. + +### `BrowserWindow.removeDevToolsExtension(name)` + +* `name` String + +Remove a extenção DevTools cujo nome é `name`. + +## Propriedades de Instância + +Objetos criados com `new BrowserWindow` possuem as seguintes propriedades: + +```javascript +// Neste exemplo `win` é a nossa instância +var win = new BrowserWindow({ width: 800, height: 600 }); +``` + +### `win.webContents` + +Todas os eventos e operações relacionados à pagina web serão feitos através do objeto `WebContents` que a esta janela possui. + +Veja a [documentação do `webContents`](../../../docs/api/web-contents.md) para seus métodos e eventos. + +### `win.id` + +O ID único desta janela. + +## Métodos de instância + +Objetos criados com `new BrowserWindow` possuem os seguintes métodos de instância: + +**Nota:** Alguns métodos só estão disponíveis em sistemas operacionais específicos e estão rotulados como tal. + +### `win.destroy()` + +Força o fechamento da janela, os eventos `unload` e `beforeunload` não serão emitidos para a página web, e o evento `close` também não será emitido para esta janela, mas garante que o evento `closed` será emitido. + +Você só deve utilizar este método quando o processo renderizador (página web) congelar. + +### `win.close()` + +Tenta fechar a janela, tem o mesmo efeito que o usuário manualmente clicar o botão de fechar a janela. Entretanto, a página web pode cancelar o fechamento, veja o [evento close](#evento-close). + +### `win.focus()` + +Foca na janela. + +### `win.isFocused()` + +Retorna um boolean, indicando se a janela está com foco. + +### `win.show()` + +Exibe e dá foco à janela. + +### `win.showInactive()` + +Exibe a janela, porém não dá foco à ela. + +### `win.hide()` + +Esconde a janela. + +### `win.isVisible()` + +Retorna um boolean, indicando se a janela está visível para o usuário. + +### `win.maximize()` + +Maximiza a janela. + +### `win.unmaximize()` + +Sai do estado maximizado da janela. + +### `win.isMaximized()` + +Retorna um boolean, indicando se a janela está maximizada. + +### `win.minimize()` + +Minimiza a janela. Em algumas plataformas, a janela minimizada será exibida no *Dock*. + +### `win.restore()` + +Restaura a janela do estado minimizado para o estado anterior. + +### `win.isMinimized()` + +Retorna um boolean, indicando se a janela está minimizada. + +### `win.setFullScreen(flag)` + +* `flag` Boolean + +Define se a janela deve estar em modo tela cheia. + +### `win.isFullScreen()` + +Retorna um boolean, indicando se a janela está em modo tela cheia. + +### `win.setAspectRatio(aspectRatio[, extraSize])` _OS X_ + +* `aspectRatio` A proporção que queremos manter para uma porção do conteúdo da *view*. +* `extraSize` Object (opcional) - O tamanho extra não incluído enquanto a proporção é mantida. Propriedades: + * `width` Integer + * `height` Integer + +Isto fará com que a janela mantenha sua proporção. O `extraSize` permite que o desenvolvedor tenha espaço, definido em pixels, não incluídos no cálculo da proporção. Esta API já leva em consideração a diferença entre o tamanho da janela e o tamanho de seu conteúdo. + +Suponha que exista uma janela normal com um *player* de vídeo HD e seus controles associados. Talvez tenha 15 pixels de controles na borda esquerda, 25 pixels de controles na borda direita e 50 abaixo do player. Para que seja mantida a proporção de 16:9 (proporção padrão para HD em 1920x1080) no próprio player, nós chamaríamos esta função com os argumentos 16/9 e [ 40, 50 ]. Para o segundo argumento, não interessa onde a largura e altura extras estão no conteúdo da view--apenas que elas existam. Apenas some qualquer área de largura e altura extras que você tem dentro da view do conteúdo. + +### `win.setBounds(options)` + +`options` Object, propriedades: + +* `x` Integer +* `y` Integer +* `width` Integer +* `height` Integer + +Redefine o tamanho e move a janela para `width`, `height`, `x`, `y`. + +### `win.getBounds()` + +Retorna um objeto que contém a largura, altura, posição x e y da janela. + +### `win.setSize(width, height)` + +* `width` Integer +* `height` Integer + +Redefine o tamanho da janela para largura `width` e altura `height`. + +### `win.getSize()` + +Retorna um array que contém a largura e altura da janela. + +### `win.setContentSize(width, height)` + +* `width` Integer +* `height` Integer + +Redefine a área de cliente da janela (a página web) para largura `width` e altura `height`. + +### `win.getContentSize()` + +Retorna um array que contém a largura e altura da área de cliente da janela. + +### `win.setMinimumSize(width, height)` + +* `width` Integer +* `height` Integer + +Define o tamanho mínimo da janela para largura `width` e altura `height`. + +### `win.getMinimumSize()` + +Retorna uma array que contém o tamanho mínimo da largura e altura da janela. + +### `win.setMaximumSize(width, height)` + +* `width` Integer +* `height` Integer + +Define o tamanho máximo da janela para largura `width` e altura `height`. + +### `win.getMaximumSize()` + +Retorna uma array que contém o tamanho máximo da largura e altura da janela. + +### `win.setResizable(resizable)` + +* `resizable` Boolean + +Define se a janela pode ter seu tamanho redefinido manualmente pelo usuário. + +### `win.isResizable()` + +Retorna um boolean indicando se a janela pode ter seu tamanho redefinido manualmente pelo usuário. + +### `win.setAlwaysOnTop(flag)` + +* `flag` Boolean + +Define se a janela deve estar sempre em cima de outras janelas. Após definir isso, a janela continua sendo uma janela normal, não uma janela *toolbox* que não pode receber foco. + +### `win.isAlwaysOnTop()` + +Retorna um boolean indicando se a janela está sempre em cima de outras janelas. + +### `win.center()` + +Move a janela para o centro da tela. + +### `win.setPosition(x, y)` + +* `x` Integer +* `y` Integer + +Move a janela para `x` e `y`. + +### `win.getPosition()` + +Retorna um array com a posição atual da janela. + +### `win.setTitle(title)` + +* `title` String + +Muda o título da janela nativa para `title`. + +### `win.getTitle()` + +Retorna o título da janela nativa. + +**Nota:** O título da página web pode ser diferente do título da janela nativa. + +### `win.flashFrame(flag)` + +* `flag` Boolean + +Inicia ou para de piscar a janela para atrair a atenção do usuário. + +### `win.setSkipTaskbar(skip)` + +* `skip` Boolean + +Faz com que a janela não apareça na barra de tarefas. + +### `win.setKiosk(flag)` + +* `flag` Boolean + +Entra ou sai do modo *kiosk*. + +### `win.isKiosk()` + +Retorna um boolean indicando se janela está no modo *kiosk*. + +### `win.hookWindowMessage(message, callback)` _Windows_ + +* `message` Integer +* `callback` Function + +Engancha uma mensagem de janela. O `callback` é chamado quando a mensagem é recebida no WndProc. + +### `win.isWindowMessageHooked(message)` _Windows_ + +* `message` Integer + +Retorna `true` ou `false` indicando se a mensagem está enganchada ou não. + +### `win.unhookWindowMessage(message)` _Windows_ + +* `message` Integer + +Desengancha a mensagem de janela. + +### `win.unhookAllWindowMessages()` _Windows_ + +Desengancha todas as mensagens de janela. + +### `win.setRepresentedFilename(filename)` _OS X_ + +* `filename` String + +Define o endereço do arquivo que a janela representa, e o ícone do arquivo será exibido na barra de título da janela. + +### `win.getRepresentedFilename()` _OS X_ + +Retorna o endereço do arquivo que a janela representa. + +### `win.setDocumentEdited(edited)` _OS X_ + +* `edited` Boolean + +Define se o documento da janela foi editado, e o ícone na barra de título se torna cinza quando definido como `true`. + +### `win.isDocumentEdited()` _OS X_ + +Retorna um boolean indicando se o documento da janela foi editado. + +### `win.focusOnWebView()` + +### `win.blurWebView()` + +### `win.capturePage([rect, ]callback)` + +* `rect` Object (opcional)- A área da página a ser capturada. Propriedades: + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer +* `callback` Function + +Captura uma imagem da página dentro do `rect`. Após completar, `callback` será chamada com `callback(imagem)`. `imagem` é uma instância de [NativeImage](../../../docs/api/native-image.md) que guarda dados sobre a imagem. Omitir `rect` captura toda a página visível. + +### `win.print([options])` + +Igual a `webContents.print([options])` + +### `win.printToPDF(options, callback)` + +Igual a `webContents.printToPDF(options, callback)` + +### `win.loadURL(url[, options])` + +Igual a `webContents.loadURL(url[, options])`. + +### `win.reload()` + +Igual a `webContents.reload`. + +### `win.setMenu(menu)` _Linux_ _Windows_ + +* `menu` Menu + +Define `menu` como a barra de menu da janela, definir como `null` remove a barra de menu. + +### `win.setProgressBar(progress)` + +* `progress` Double + +Define o valor do progresso na barra de progresso. Extensão válida é [0, 1.0]. + +Remove a barra de progresso quando `progress` < 0. +Muda para o modo indeterminado quando `progress` > 1. + +Na plataforma Linux, apenas suporta o ambiente de desktop Unity, você precisa definir o nome do arquivo como `*.desktop` no campo `desktopName` no `package.json`. Por padrão, irá assumir `app.getName().desktop`. + +### `win.setOverlayIcon(overlay, description)` _Windows 7+_ + +* `overlay` [NativeImage](../../../docs/api/native-image.md) - o ícone a ser exibido no canto inferior direito da barra de tarefas. Se este parâmetro for `null`, a sobreposição é eliminada. +* `description` String - uma descrição que será providenciada a leitores de tela de acessibilidade. + +Define uma sobreposição de 16px sobre o ícone da barra de tarefas atual, geralmente utilizado para indicar algum tipo de status de aplicação, ou notificar passivamente o usuário. + +### `win.setThumbarButtons(buttons)` _Windows 7+_ + +`buttons` Array de objetos `button`: + +`button` Object, propriedades: + +* `icon` [NativeImage](../../../docs/api/native-image.md) - O ícone exibido na barra de ferramentas miniatura. +* `tooltip` String (opcional) - O texto do balão de dica do botão. +* `flags` Array (opcional) - Controla estados e comportamentos específicos do botão. Utiliza `enabled` por padrão. Pode incluir as seguintes strings: + * `enabled` - O botão está ativo e disponível para o usuário. + * `disabled` - O botão está desabilitado. Está presente, mas possui um estado visual indicando que não irá responder às ações do usuário. + * `dismissonclick` - Quando o botão é clicado, o *flyout* do botão da barra de tarefas fecha imediatamente. + * `nobackground` - Não desenhe a borda do botão, apenas utilize a imagem. + * `hidden` - O botão não é exibido para o usuário. + * `noninteractive` - O botão está habilitado, mas não interage; Não é exibido o estado de botão pressionado. Este valor está destinado a instâncias nas quais o botão é utilizado em uma notificação. +* `click` - Função + +Adiciona uma barra de ferramentes miniatura com um conjunto de botões específicos à imagem miniatura de uma janela no layout de um botão de barra de tarefas. Retorna um objeto `Boolean` indicando se a miniatura foi adicionada com sucesso. + +O número de botões na barra de ferramentas miniatura não deve ser maior que 7 devido ao espaço limitado. Uma vez que você define a barra de ferramentas miniatura, ela não pode ser removida por causa da limitação da plataforma. Mas você pode chamar a API com um array vazio para limpar todos os botões. + +### `win.showDefinitionForSelection()` _OS X_ + +Mostra um dicionário *pop-up* que procura a palavra selecionada na página. + +### `win.setAutoHideMenuBar(hide)` + +* `hide` Boolean + +Define se a barra de menu da janela deve se esconder automaticamente. Uma vez que for definida, a barra de menu só será exibida quando usuários pressionarem a tecla `Alt`. + +Se a barra de menu já estiver visível, chamar `setAutoHideMenuBar(true)` não irá escondê-la imediatamente. + +### `win.isMenuBarAutoHide()` + +Retorna um boolean indicando se a barra de menu se esconde automaticamente. + +### `win.setMenuBarVisibility(visible)` + +* `visible` Boolean + +Define se a barra de menu deve ser visível. Se a barra de menu se esconde automaticamente, os usuários ainda podem exibí-la ao pressionar a tecla `Alt`. + +### `win.isMenuBarVisible()` + +Retorna um boolean indicando se a barra de menu está visível. + +### `win.setVisibleOnAllWorkspaces(visible)` + +* `visible` Boolean + +Define se a janela deve estar visível em todos os *workspaces*. + +**Nota:** Esta API não faz nada no Windows. + +### `win.isVisibleOnAllWorkspaces()` + +Retorna um boolean indicando se a janela está visível em todos os *workspaces*. + +**Nota:** Esta API sempre retorna `false` no Windows. + + From c6167bdf0a058808d32b2f2ea3bc32e64ce8cf76 Mon Sep 17 00:00:00 2001 From: Clark Feusier Date: Fri, 4 Dec 2015 21:51:33 -0800 Subject: [PATCH 136/411] Fix broken link in Docs > Synopsis --- docs/api/synopsis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/synopsis.md b/docs/api/synopsis.md index fcb7da2c1c..6886c6d78c 100644 --- a/docs/api/synopsis.md +++ b/docs/api/synopsis.md @@ -11,7 +11,7 @@ both processes. The basic rule is: if a module is [GUI][gui] or low-level system related, then it should be only available in the main process. You need to be familiar with -the concept of [main process vs. renderer process][mai-process] scripts to be +the concept of [main process vs. renderer process][main-process] scripts to be able to use those modules. The main process script is just like a normal Node.js script: From a1154ad8168958f63e5b932a44b743dd911df458 Mon Sep 17 00:00:00 2001 From: "howard.zuo" Date: Sat, 5 Dec 2015 14:49:01 +0800 Subject: [PATCH 137/411] add ZH-CN translation for using-selenium-and-webdriver --- .../tutorial/using-selenium-and-webdriver.md | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md diff --git a/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md new file mode 100644 index 0000000000..c05a3190ee --- /dev/null +++ b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md @@ -0,0 +1,119 @@ +# 使用Selenium和WebDriver + +引自[ChromeDriver - WebDriver for Chrome][chrome-driver]: + +> WebDriver是一款开源的支持多浏览器的自动化测试工具。它提供了操作网页、用户输入、JavaScript执行等能力。ChromeDriver是一个实现了WebDriver与Chromium联接协议的独立服务。它也是由开发了Chromium和WebDriver的团队开发的。 + +为了能够使`chromedriver`和Electron一起正常工作,我们需要告诉它Electron在哪,并且让它相信Electron就是Chrome浏览器。 + +## 通过WebDriverJs配置 + +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) 是一个可以配合WebDriver做测试的node模块,我们会用它来做个演示。 + +### 1. 启动ChromeDriver + +首先,你要下载`chromedriver`,然后运行以下命令: + +```bash +$ ./chromedriver +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +记住`9515`这个端口号,我们后面会用到 + +### 2. 安装WebDriverJS + +```bash +$ npm install selenium-webdriver +``` + +### 3. 联接到ChromeDriver + +在Electron下使用`selenium-webdriver`和其平时的用法并没有大的差异,只是你需要手动设置连接ChromeDriver,以及Electron的路径: + +```javascript +const webdriver = require('selenium-webdriver'); + +var driver = new webdriver.Builder() + // "9515" 是ChromeDriver使用的端口 + .usingServer('http://localhost:9515') + .withCapabilities({ + chromeOptions: { + // 这里设置Electron的路径 + binary: '/Path-to-Your-App.app/Contents/MacOS/Atom', + } + }) + .forBrowser('electron') + .build(); + +driver.get('http://www.google.com'); +driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); +driver.findElement(webdriver.By.name('btnG')).click(); +driver.wait(function() { + return driver.getTitle().then(function(title) { + return title === 'webdriver - Google Search'; + }); +}, 1000); + +driver.quit(); +``` + +## 通过WebdriverIO配置 + +[WebdriverIO](http://webdriver.io/)也是一个配合WebDriver用来测试的node模块 + +### 1. 启动ChromeDriver + +首先,下载`chromedriver`,然后运行以下命令: + +```bash +$ chromedriver --url-base=wd/hub --port=9515 +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +记住`9515`端口,后面会用到 + +### 2. 安装WebdriverIO + +```bash +$ npm install webdriverio +``` + +### 3. 连接到ChromeDriver + +```javascript +const webdriverio = require('webdriverio'); +var options = { + host: "localhost", // 使用localhost作为ChromeDriver服务器 + port: 9515, // "9515"是ChromeDriver使用的端口 + desiredCapabilities: { + browserName: 'chrome', + chromeOptions: { + binary: '/Path-to-Your-App/electron', // Electron的路径 + args: [/* cli arguments */] // 可选参数,类似:'app=' + /path/to/your/app/ + } + } +}; + +var client = webdriverio.remote(options); + +client + .init() + .url('http://google.com') + .setValue('#q', 'webdriverio') + .click('#btnG') + .getTitle().then(function(title) { + console.log('Title was: ' + title); + }) + .end(); +``` + +## 工作流程 + +无需重新编译Electron,只要把app的源码放到[Electron的资源目录](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md)里就可直接开始测试了。 + +当然,你也可以在运行Electron时传入参数指定你app的所在文件夹。这步可以免去你拷贝-粘贴你的app到Electron的资源目录。 + +[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ From e6adf36d9856b365d090a8342ab0b70ef87ed0e9 Mon Sep 17 00:00:00 2001 From: Artur de Oliveira Tsuda Date: Sat, 5 Dec 2015 10:39:03 -0200 Subject: [PATCH 138/411] :memo: [ci skip] translation build instructions --- .../development/build-instructions-linux.md | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 docs-translations/pt-BR/development/build-instructions-linux.md diff --git a/docs-translations/pt-BR/development/build-instructions-linux.md b/docs-translations/pt-BR/development/build-instructions-linux.md new file mode 100644 index 0000000000..532892a408 --- /dev/null +++ b/docs-translations/pt-BR/development/build-instructions-linux.md @@ -0,0 +1,142 @@ +# Instruções de Build (Linux) + +Siga as orientações abaixo pra fazer o build do Electron no Linux. + +## Pré-requisitos + +* Python 2.7.x. Algumas distribuições como CentOS ainda usam Python 2.6.x, + então você precisa checar a sua versão do Python com `python -V`. +* Node.js v0.12.x. Há várias maneiras de instalar o Node. Você pode baixar o + código fonte do [Node.js](http://nodejs.org) e compilar a partir dele. + Fazer isto permite que você instale o Node no seu próprio diretório home + como um usuário comum. + Ou tente repositórios como [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories). +* Clang 3.4 ou mais recente. +* Cabeçalhos de desenvolvimento do GTK+ e libnotify. + +No Ubuntu, instale as seguintes bibliotecas: + +```bash +$ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ + libnotify-dev libgnome-keyring-dev libgconf2-dev \ + libasound2-dev libcap-dev libcups2-dev libxtst-dev \ + libxss1 libnss3-dev gcc-multilib g++-multilib +``` + +No Fedora, instale as seguintes bibliotecas: + +```bash +$ sudo yum install clang dbus-devel gtk2-devel libnotify-devel libgnome-keyring-devel \ + xorg-x11-server-utils libcap-devel cups-devel libXtst-devel \ + alsa-lib-devel libXrandr-devel GConf2-devel nss-devel +``` + +Outras distribuições podem oferecer pacotes similares para instalação através +do gerenciador de pacotes como o pacman. Ou você pode compilar a partir do +código fonte. + +## Se Você Utilizar Máquinas Virtuais Para Fazer O Build + +Se você planeja fazer o build do Electron numa máquina virtual, você vai precisar +de um container de tamanho fixo de pelo menos 25 gigabytes. + +## Baixando o Código + +```bash +$ git clone https://github.com/atom/electron.git +``` + +## Bootstrapping + +O script de *boostrap* irá baixar todas as dependências de build necessárias +e criar os arquivos de projeto do build. Você deve ter o Python 2.7.x para +executar o script com sucesso. +Baixar certos arquivos pode demorar bastante. Note que estamos utilizando +`ninja` para fazer o build do Electron, então não há um `Makefile` gerado. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +### Compilação Cruzada + +Se você quer fazer o build para `arm`, você também deve instalar as seguintes +dependências: + +```bash +$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \ + g++-arm-linux-gnueabihf +``` + +E para fazer a compilação cruzada para `arm` ou `ia32`, você deve passar +o parâmetro `--target_arch` para o script `bootstrap.py`: + +```bash +$ ./script/bootstrap.py -v --target_arch=arm +``` + +## Building + +Se você quiser fazer o build para `Release` e `Debug`: + +```bash +$ ./script/build.py +``` + +Este script irá fazer com que um executável bem pesado do Electron seja +criado no diretório `out/R`. O arquivo possui mais de 1.3 gigabytes. +Isso acontece por que o binário do Release contém símbolos de debugging. +Para reduzir o tamanho do arquivo, rode o script `create-dist.py`: + +```bash +$ ./script/create-dist.py +``` + +Isso irá colocar uma distribuição funcional com arquivos muito menores +no diretório `dist`. Depois de rodar o script `create-dist.py`, talvez +você queira remover o binário de 1.3+ gigabytes que ainda está em `out/R`. + +Você também pode fazer apenas o build de `Debug`: + +```bash +$ ./script/build.py -c D +``` + +Depois de completar o build, você pode encontrar o binário de debug do `electron` +em `out/D`. + +## Limpando + +Para limpar os arquivos de build: + +```bash +$ ./script/clean.py +``` + +## Troubleshooting + +Certifique-se de que você tenha instalado todas as dependências do build. + +### Error While Loading Shared Libraries: libtinfo.so.5 + +O `clang` prebuilt irá tentar fazer o link com `libtinfo.so.5`. Dependendo +da arquitetura do host, faça um link simbólico para o `libncurses` apropriado: + +```bash +$ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 +``` + +## Testes + +Teste suas modificações conforme o estilo de código do projeto utilizando: + +```bash +$ ./script/cpplint.py +``` + +Teste funcionalidade utilizando: + +```bash +$ ./script/test.py +``` From 5f092a6c658c0d3f82ffcd9bac5975b124656b5a Mon Sep 17 00:00:00 2001 From: billyct Date: Sun, 6 Dec 2015 10:14:54 +0800 Subject: [PATCH 139/411] support an api with SetIgnoreMouseEvents, and worked fine with osx --- atom/browser/api/atom_api_window.cc | 5 +++++ atom/browser/api/atom_api_window.h | 1 + atom/browser/native_window.cc | 3 +++ atom/browser/native_window.h | 1 + atom/browser/native_window_mac.h | 1 + atom/browser/native_window_mac.mm | 4 ++++ 6 files changed, 15 insertions(+) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 84e5c53ebe..08b5bc4f7b 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -488,6 +488,10 @@ bool Window::IsDocumentEdited() { return window_->IsDocumentEdited(); } +void Window::SetIgnoreMouseEvents(bool ignore) { + return window_->SetIgnoreMouseEvents(ignore); +} + void Window::CapturePage(mate::Arguments* args) { gfx::Rect rect; base::Callback callback; @@ -662,6 +666,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("getRepresentedFilename", &Window::GetRepresentedFilename) .SetMethod("setDocumentEdited", &Window::SetDocumentEdited) .SetMethod("isDocumentEdited", &Window::IsDocumentEdited) + .SetMethod("setIgnoreMouseEvents", &Window::SetIgnoreMouseEvents) .SetMethod("focusOnWebView", &Window::FocusOnWebView) .SetMethod("blurWebView", &Window::BlurWebView) .SetMethod("isWebViewFocused", &Window::IsWebViewFocused) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 6d5ce22f43..3611c6e33f 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -126,6 +126,7 @@ class Window : public mate::TrackableObject, std::string GetRepresentedFilename(); void SetDocumentEdited(bool edited); bool IsDocumentEdited(); + void SetIgnoreMouseEvents(bool ignore); void CapturePage(mate::Arguments* args); void SetProgressBar(double progress); void SetOverlayIcon(const gfx::Image& overlay, diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index a3df240e4d..7875670126 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -245,6 +245,9 @@ bool NativeWindow::IsDocumentEdited() { return false; } +void NativeWindow::SetIgnoreMouseEvents(bool ignore) { +} + void NativeWindow::SetMenu(ui::MenuModel* menu) { } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 0c918d92df..3eb235b03c 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -139,6 +139,7 @@ class NativeWindow : public base::SupportsUserData, virtual std::string GetRepresentedFilename(); virtual void SetDocumentEdited(bool edited); virtual bool IsDocumentEdited(); + virtual void SetIgnoreMouseEvents(bool ignore); virtual void SetMenu(ui::MenuModel* menu); virtual bool HasModalDialog(); virtual gfx::NativeWindow GetNativeWindow() = 0; diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 08f9198e4f..38845140e6 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -62,6 +62,7 @@ class NativeWindowMac : public NativeWindow { std::string GetRepresentedFilename() override; void SetDocumentEdited(bool edited) override; bool IsDocumentEdited() override; + void SetIgnoreMouseEvents(bool ignore) override; bool HasModalDialog() override; gfx::NativeWindow GetNativeWindow() override; void SetProgressBar(double progress) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 42894c107d..049c3eefea 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -687,6 +687,10 @@ bool NativeWindowMac::IsDocumentEdited() { return [window_ isDocumentEdited]; } +void NativeWindowMac::SetIgnoreMouseEvents(bool ignore) { + [window_ setIgnoresMouseEvents:ignore]; +} + bool NativeWindowMac::HasModalDialog() { return [window_ attachedSheet] != nil; } From 766bbfcb056d53d809a97b67d0ed81d6c3533834 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 7 Dec 2015 15:14:31 +0800 Subject: [PATCH 140/411] Chrome 47.0.2526.73 --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index 2fb841acf3..4978302abb 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '17a4337f7948a45b5ea4b8f391df152ba8db5979' +LIBCHROMIUMCONTENT_COMMIT = 'd534691711ecdef1739878674a9ffd5f2d5ac4a2' PLATFORM = { 'cygwin': 'win32', From 73e7773d841cdb82a3460ab6b3ade1892861b396 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 7 Dec 2015 19:56:23 +0800 Subject: [PATCH 141/411] Update to API changes of Chrome 47 --- atom/app/atom_content_client.cc | 19 ++-- atom/app/atom_content_client.h | 2 +- atom/browser/api/atom_api_session.cc | 7 +- atom/browser/api/atom_api_web_contents.cc | 4 +- atom/browser/api/frame_subscriber.cc | 9 +- atom/browser/atom_browser_client.cc | 3 +- atom/browser/atom_browser_context.cc | 12 +- atom/browser/atom_browser_context.h | 4 +- atom/browser/common_web_contents_delegate.cc | 2 +- atom/browser/native_window.cc | 4 +- atom/browser/native_window_mac.mm | 43 ++++--- atom/browser/native_window_views.cc | 2 +- atom/browser/net/url_request_fetch_job.cc | 8 +- atom/browser/net/url_request_fetch_job.h | 1 + atom/browser/ui/accelerator_util.cc | 6 +- .../ui/cocoa/event_processing_window.h | 30 ----- .../ui/cocoa/event_processing_window.mm | 106 ------------------ atom/browser/ui/message_box_gtk.cc | 2 +- atom/browser/ui/message_box_win.cc | 2 +- atom/browser/ui/x/x_window_utils.cc | 2 +- atom/browser/web_contents_preferences.cc | 4 - atom/common/api/atom_api_native_image.cc | 3 +- atom/common/api/event_emitter_caller.cc | 2 +- atom/common/crash_reporter/crash_reporter.cc | 8 +- .../crash_reporter/crash_reporter_linux.cc | 2 +- .../crash_reporter/crash_reporter_linux.h | 4 +- .../crash_reporter/crash_reporter_mac.h | 4 +- .../crash_reporter/crash_reporter_mac.mm | 2 +- .../crash_reporter/crash_reporter_win.cc | 2 +- .../crash_reporter/crash_reporter_win.h | 4 +- .../native_mate_converters/blink_converter.cc | 10 +- atom/common/native_mate_converters/callback.h | 6 +- atom/common/node_bindings.cc | 2 +- atom/common/options_switches.cc | 2 - atom/common/options_switches.h | 2 - atom/renderer/atom_renderer_client.cc | 2 - .../printing/print_view_manager_base.cc | 2 +- .../chrome/browser/process_singleton_posix.cc | 2 +- .../browser/speech/tts_controller_impl.cc | 2 +- .../browser/speech/tts_controller_impl.h | 4 +- chromium_src/chrome/browser/speech/tts_mac.mm | 4 +- .../pepper/pepper_flash_renderer_host.cc | 3 +- .../printing/print_web_view_helper.cc | 1 - docs/api/browser-window.md | 3 - filenames.gypi | 2 - vendor/brightray | 2 +- 46 files changed, 113 insertions(+), 239 deletions(-) delete mode 100644 atom/browser/ui/cocoa/event_processing_window.h delete mode 100644 atom/browser/ui/cocoa/event_processing_window.mm diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 0931a1b55a..9f161ac569 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -31,8 +31,8 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, plugin.path = path; plugin.permissions = ppapi::PERMISSION_ALL_BITS; - std::vector flash_version_numbers; - base::SplitString(version, '.', &flash_version_numbers); + std::vector flash_version_numbers = base::SplitString( + version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); if (flash_version_numbers.size() < 1) flash_version_numbers.push_back("11"); // |SplitString()| puts in an empty string given an empty string. :( @@ -47,7 +47,7 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, // E.g., "Shockwave Flash 10.2 r154": plugin.description = plugin.name + " " + flash_version_numbers[0] + "." + flash_version_numbers[1] + " r" + flash_version_numbers[2]; - plugin.version = JoinString(flash_version_numbers, '.'); + plugin.version = base::JoinString(flash_version_numbers, "."); content::WebPluginMimeType swf_mime_type( content::kFlashPluginSwfMimeType, content::kFlashPluginSwfExtension, @@ -81,19 +81,18 @@ std::string AtomContentClient::GetUserAgent() const { } void AtomContentClient::AddAdditionalSchemes( - std::vector* standard_schemes, + std::vector* standard_schemes, std::vector* savable_schemes) { auto command_line = base::CommandLine::ForCurrentProcess(); auto custom_schemes = command_line->GetSwitchValueASCII( switches::kRegisterStandardSchemes); if (!custom_schemes.empty()) { - std::vector schemes; - base::SplitString(custom_schemes, ',', &schemes); - standard_schemes->insert(standard_schemes->end(), - schemes.begin(), - schemes.end()); + std::vector schemes = base::SplitString( + custom_schemes, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + for (const std::string& scheme : schemes) + standard_schemes->push_back({scheme.c_str(), url::SCHEME_WITHOUT_PORT}); } - standard_schemes->push_back("chrome-extension"); + standard_schemes->push_back({"chrome-extension", url::SCHEME_WITHOUT_PORT}); } void AtomContentClient::AddPepperPlugins( diff --git a/atom/app/atom_content_client.h b/atom/app/atom_content_client.h index a6b2f73e7f..2716b1eea4 100644 --- a/atom/app/atom_content_client.h +++ b/atom/app/atom_content_client.h @@ -22,7 +22,7 @@ class AtomContentClient : public brightray::ContentClient { std::string GetProduct() const override; std::string GetUserAgent() const override; void AddAdditionalSchemes( - std::vector* standard_schemes, + std::vector* standard_schemes, std::vector* savable_schemes) override; void AddPepperPlugins( std::vector* plugins) override; diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index f39073825c..9cec7378b8 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -51,7 +51,7 @@ struct ClearStorageDataOptions { uint32 GetStorageMask(const std::vector& storage_types) { uint32 storage_mask = 0; for (const auto& it : storage_types) { - auto type = base::StringToLowerASCII(it); + auto type = base::ToLowerASCII(it); if (type == "appcache") storage_mask |= StoragePartition::REMOVE_DATA_MASK_APPCACHE; else if (type == "cookies") @@ -75,7 +75,7 @@ uint32 GetStorageMask(const std::vector& storage_types) { uint32 GetQuotaMask(const std::vector& quota_types) { uint32 quota_mask = 0; for (const auto& it : quota_types) { - auto type = base::StringToLowerASCII(it); + auto type = base::ToLowerASCII(it); if (type == "temporary") quota_mask |= StoragePartition::QUOTA_MANAGED_STORAGE_MASK_TEMPORARY; else if (type == "persistent") @@ -233,7 +233,8 @@ void SetProxyInIO(net::URLRequestContextGetter* getter, const net::ProxyConfig& config, const base::Closure& callback) { auto proxy_service = getter->GetURLRequestContext()->proxy_service(); - proxy_service->ResetConfigService(new net::ProxyConfigServiceFixed(config)); + proxy_service->ResetConfigService(make_scoped_ptr( + new net::ProxyConfigServiceFixed(config))); // Refetches and applies the new pac script if provided. proxy_service->ForceReloadProxyConfig(); RunCallbackInUI(callback); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index b16ab8c899..a70b6cf4e0 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -148,7 +148,7 @@ struct Converter { std::string key; std::string value; while (headers->EnumerateHeaderLines(&iter, &key, &value)) { - key = base::StringToLowerASCII(key); + key = base::ToLowerASCII(key); if (response_headers.HasKey(key)) { base::ListValue* values = nullptr; if (response_headers.GetList(key, &values)) @@ -171,7 +171,7 @@ struct Converter { std::string save_type; if (!ConvertFromV8(isolate, val, &save_type)) return false; - save_type = base::StringToLowerASCII(save_type); + save_type = base::ToLowerASCII(save_type); if (save_type == "htmlonly") { *out = content::SAVE_PAGE_TYPE_AS_ONLY_HTML; } else if (save_type == "htmlcomplete") { diff --git a/atom/browser/api/frame_subscriber.cc b/atom/browser/api/frame_subscriber.cc index cf0eae14a9..5b7241486b 100644 --- a/atom/browser/api/frame_subscriber.cc +++ b/atom/browser/api/frame_subscriber.cc @@ -24,12 +24,11 @@ bool FrameSubscriber::ShouldCaptureFrame( base::TimeTicks present_time, scoped_refptr* storage, DeliverFrameCallback* callback) { - *storage = media::VideoFrame::CreateFrame(media::VideoFrame::YV12, size_, - gfx::Rect(size_), size_, - base::TimeDelta()); + *storage = media::VideoFrame::CreateFrame( + media::PIXEL_FORMAT_YV12, + size_, gfx::Rect(size_), size_, base::TimeDelta()); *callback = base::Bind(&FrameSubscriber::OnFrameDelivered, - base::Unretained(this), - *storage); + base::Unretained(this), *storage); return true; } diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 38fdc0e19f..b9b186d187 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -84,7 +84,7 @@ void AtomBrowserClient::SuppressRendererProcessRestartForOnce() { void AtomBrowserClient::SetCustomSchemes( const std::vector& schemes) { - g_custom_schemes = JoinString(schemes, ','); + g_custom_schemes = base::JoinString(schemes, ","); } AtomBrowserClient::AtomBrowserClient() : delegate_(nullptr) { @@ -116,7 +116,6 @@ void AtomBrowserClient::OverrideWebkitPrefs( prefs->javascript_can_open_windows_automatically = true; prefs->plugins_enabled = true; prefs->dom_paste_enabled = true; - prefs->java_enabled = false; prefs->allow_scripts_to_close_windows = true; prefs->javascript_can_access_clipboard = true; prefs->local_storage_enabled = true; diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index 08c7999627..b9589d569b 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -61,7 +61,7 @@ std::string RemoveWhitespace(const std::string& str) { AtomBrowserContext::AtomBrowserContext(const std::string& partition, bool in_memory) : brightray::BrowserContext(partition, in_memory), - cert_verifier_(new AtomCertVerifier), + cert_verifier_(nullptr), job_factory_(new AtomURLRequestJobFactory), allow_ntlm_everywhere_(false) { } @@ -86,7 +86,7 @@ std::string AtomBrowserContext::GetUserAgent() { return content::BuildUserAgentFromProduct(user_agent); } -net::URLRequestJobFactory* AtomBrowserContext::CreateURLRequestJobFactory( +scoped_ptr AtomBrowserContext::CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) { scoped_ptr job_factory(job_factory_); @@ -131,7 +131,7 @@ net::URLRequestJobFactory* AtomBrowserContext::CreateURLRequestJobFactory( top_job_factory.Pass(), make_scoped_ptr(*it))); interceptors->weak_clear(); - return top_job_factory.release(); + return top_job_factory.Pass(); } net::HttpCache::BackendFactory* @@ -160,8 +160,10 @@ content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() { return guest_manager_.get(); } -net::CertVerifier* AtomBrowserContext::CreateCertVerifier() { - return cert_verifier_; +scoped_ptr AtomBrowserContext::CreateCertVerifier() { + DCHECK(!cert_verifier_); + cert_verifier_ = new AtomCertVerifier; + return make_scoped_ptr(cert_verifier_); } net::SSLConfigService* AtomBrowserContext::CreateSSLConfigService() { diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index d3d7735c81..564c9955d9 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -23,12 +23,12 @@ class AtomBrowserContext : public brightray::BrowserContext { // brightray::URLRequestContextGetter::Delegate: std::string GetUserAgent() override; - net::URLRequestJobFactory* CreateURLRequestJobFactory( + scoped_ptr CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) override; net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory( const base::FilePath& base_path) override; - net::CertVerifier* CreateCertVerifier() override; + scoped_ptr CreateCertVerifier() override; net::SSLConfigService* CreateSSLConfigService() override; bool AllowNTLMCredentialsForDomain(const GURL& auth_origin) override; diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index 8b7a159dd7..72a664f8cd 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -380,7 +380,7 @@ gfx::ImageSkia CommonWebContentsDelegate::GetDevToolsWindowIcon() { void CommonWebContentsDelegate::GetDevToolsWindowWMClass( std::string* name, std::string* class_name) { *class_name = Browser::Get()->GetName(); - *name = base::StringToLowerASCII(*class_name); + *name = base::ToLowerASCII(*class_name); } #endif diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index a3df240e4d..666da2d6b7 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -291,10 +291,10 @@ void NativeWindow::CapturePage(const gfx::Rect& rect, 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( - rect.IsEmpty() ? gfx::Rect(view_size) : rect, + gfx::Rect(view_size), bitmap_size, base::Bind(&NativeWindow::OnCapturePageDone, weak_factory_.GetWeakPtr(), diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 42894c107d..d7ed15cf07 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -6,7 +6,6 @@ #include -#import "atom/browser/ui/cocoa/event_processing_window.h" #include "atom/common/draggable_region.h" #include "atom/common/options_switches.h" #include "base/mac/mac_util.h" @@ -19,6 +18,7 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "native_mate/dictionary.h" +#import "ui/base/cocoa/command_dispatcher.h" #include "ui/gfx/skia_util.h" namespace { @@ -209,10 +209,11 @@ bool ScopedDisableResize::disable_resize_ = false; @end -@interface AtomNSWindow : EventProcessingWindow { +@interface AtomNSWindow : NSWindow { @private atom::NativeWindowMac* shell_; bool enable_larger_than_screen_; + base::scoped_nsobject commandDispatcher_; } @property BOOL acceptsFirstMouse; @property BOOL disableAutoHideCursor; @@ -226,12 +227,15 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; + commandDispatcher_.reset([[CommandDispatcher alloc] initWithOwner:self]); } - (void)setEnableLargerThanScreen:(bool)enable { enable_larger_than_screen_ = enable; } +// NSWindow overrides. + - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen { // Resizing is disabled. if (ScopedDisableResize::IsResizeDisabled()) @@ -272,6 +276,25 @@ bool ScopedDisableResize::disable_resize_ = false; return !self.disableKeyOrMainWindow; } +// CommandDispatchingWindow implementation. + +- (void)setCommandHandler:(id)commandHandler { +} + +- (BOOL)redispatchKeyEvent:(NSEvent*)event { + return [commandDispatcher_ redispatchKeyEvent:event]; +} + +- (BOOL)defaultPerformKeyEquivalent:(NSEvent*)event { + return [super performKeyEquivalent:event]; +} + +- (void)commandDispatch:(id)sender { +} + +- (void)commandDispatchUsingKeyModifiers:(id)sender { +} + @end @interface ControlRegionView : NSView @@ -766,20 +789,14 @@ void NativeWindowMac::HandleKeyboardEvent( event.type == content::NativeWebKeyboardEvent::Char) return; - if (event.os_event.window == window_.get()) { - EventProcessingWindow* event_window = - static_cast(window_); - DCHECK([event_window isKindOfClass:[EventProcessingWindow class]]); - [event_window redispatchKeyEvent:event.os_event]; - } else { + BOOL handled = [[NSApp mainMenu] performKeyEquivalent:event.os_event]; + if (!handled && event.os_event.window != window_.get()) { // The event comes from detached devtools view, and it has already been - // handled by the devtools itself, we now send it to application menu to - // make menu acclerators work. - BOOL handled = [[NSApp mainMenu] performKeyEquivalent:event.os_event]; - // Handle the cmd+~ shortcut. if (!handled && (event.os_event.modifierFlags & NSCommandKeyMask) && - (event.os_event.keyCode == 50 /* ~ key */)) + (event.os_event.keyCode == 50 /* ~ key */)) { + // Handle the cmd+~ shortcut. Focus(true); + } } } diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index c12ae1986c..16faee58c5 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -180,7 +180,7 @@ NativeWindowViews::NativeWindowViews( // Set WM_WINDOW_ROLE. params.wm_role_name = "browser-window"; // Set WM_CLASS. - params.wm_class_name = base::StringToLowerASCII(name); + params.wm_class_name = base::ToLowerASCII(name); params.wm_class_class = name; #endif diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index 24a7222660..f04ecd4060 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -14,6 +14,7 @@ #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_response_writer.h" +#include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_builder.h" #include "net/url_request/url_request_status.h" @@ -23,7 +24,7 @@ namespace { // Convert string to RequestType. net::URLFetcher::RequestType GetRequestType(const std::string& raw) { - std::string method = base::StringToUpperASCII(raw); + std::string method = base::ToUpperASCII(raw); if (method.empty() || method == "GET") return net::URLFetcher::GET; else if (method == "POST") @@ -138,8 +139,9 @@ net::URLRequestContextGetter* URLRequestFetchJob::CreateRequestContext() { auto task_runner = base::ThreadTaskRunnerHandle::Get(); net::URLRequestContextBuilder builder; builder.set_proxy_service(net::ProxyService::CreateDirect()); - url_request_context_getter_ = - new net::TrivialURLRequestContextGetter(builder.Build(), task_runner); + request_context_ = builder.Build(); + url_request_context_getter_ = new net::TrivialURLRequestContextGetter( + request_context_.get(), task_runner); } return url_request_context_getter_.get(); } diff --git a/atom/browser/net/url_request_fetch_job.h b/atom/browser/net/url_request_fetch_job.h index 189cebf01b..399f78ae39 100644 --- a/atom/browser/net/url_request_fetch_job.h +++ b/atom/browser/net/url_request_fetch_job.h @@ -45,6 +45,7 @@ class URLRequestFetchJob : public JsAsker, // Create a independent request context. net::URLRequestContextGetter* CreateRequestContext(); + scoped_ptr request_context_; scoped_refptr url_request_context_getter_; scoped_ptr fetcher_; scoped_refptr pending_buffer_; diff --git a/atom/browser/ui/accelerator_util.cc b/atom/browser/ui/accelerator_util.cc index e25e14b796..a0b90e0c7e 100644 --- a/atom/browser/ui/accelerator_util.cc +++ b/atom/browser/ui/accelerator_util.cc @@ -24,10 +24,10 @@ bool StringToAccelerator(const std::string& description, LOG(ERROR) << "The accelerator string can only contain ASCII characters"; return false; } - std::string shortcut(base::StringToLowerASCII(description)); + std::string shortcut(base::ToLowerASCII(description)); - std::vector tokens; - base::SplitString(shortcut, '+', &tokens); + std::vector tokens = base::SplitString( + shortcut, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); // Now, parse it into an accelerator. int modifiers = ui::EF_NONE; diff --git a/atom/browser/ui/cocoa/event_processing_window.h b/atom/browser/ui/cocoa/event_processing_window.h deleted file mode 100644 index 88242711f8..0000000000 --- a/atom/browser/ui/cocoa/event_processing_window.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_UI_COCOA_EVENT_PROCESSING_WINDOW_H_ -#define ATOM_BROWSER_UI_COCOA_EVENT_PROCESSING_WINDOW_H_ - -#import - -// Override NSWindow to access unhandled keyboard events (for command -// processing); subclassing NSWindow is the only method to do -// this. -@interface EventProcessingWindow : NSWindow { - @private - BOOL redispatchingEvent_; - BOOL eventHandled_; -} - -// Sends a key event to |NSApp sendEvent:|, but also makes sure that it's not -// short-circuited to the RWHV. This is used to send keyboard events to the menu -// and the cmd-` handler if a keyboard event comes back unhandled from the -// renderer. The event must be of type |NSKeyDown|, |NSKeyUp|, or -// |NSFlagsChanged|. -// Returns |YES| if |event| has been handled. -- (BOOL)redispatchKeyEvent:(NSEvent*)event; - -- (BOOL)performKeyEquivalent:(NSEvent*)theEvent; -@end - -#endif // ATOM_BROWSER_UI_COCOA_EVENT_PROCESSING_WINDOW_H_ diff --git a/atom/browser/ui/cocoa/event_processing_window.mm b/atom/browser/ui/cocoa/event_processing_window.mm deleted file mode 100644 index d47cdf37b5..0000000000 --- a/atom/browser/ui/cocoa/event_processing_window.mm +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#import "atom/browser/ui/cocoa/event_processing_window.h" - -#include "base/logging.h" -#import "content/public/browser/render_widget_host_view_mac_base.h" - -@interface EventProcessingWindow () -// Duplicate the given key event, but changing the associated window. -- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event; -@end - -@implementation EventProcessingWindow - -- (BOOL)redispatchKeyEvent:(NSEvent*)event { - DCHECK(event); - NSEventType eventType = [event type]; - if (eventType != NSKeyDown && - eventType != NSKeyUp && - eventType != NSFlagsChanged) { - NOTREACHED(); - return YES; // Pretend it's been handled in an effort to limit damage. - } - - // Ordinarily, the event's window should be this window. However, when - // switching between normal and fullscreen mode, we switch out the window, and - // the event's window might be the previous window (or even an earlier one if - // the renderer is running slowly and several mode switches occur). In this - // rare case, we synthesize a new key event so that its associate window - // (number) is our own. - if ([event window] != self) - event = [self keyEventForWindow:self fromKeyEvent:event]; - - // Redispatch the event. - eventHandled_ = YES; - redispatchingEvent_ = YES; - [NSApp sendEvent:event]; - redispatchingEvent_ = NO; - - // If the event was not handled by [NSApp sendEvent:], the sendEvent: - // method below will be called, and because |redispatchingEvent_| is YES, - // |eventHandled_| will be set to NO. - return eventHandled_; -} - -- (void)sendEvent:(NSEvent*)event { - if (!redispatchingEvent_) - [super sendEvent:event]; - else - eventHandled_ = NO; -} - -- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event { - NSEventType eventType = [event type]; - - // Convert the event's location from the original window's coordinates into - // our own. - NSPoint eventLoc = [event locationInWindow]; - eventLoc = [self convertRectFromScreen: - [[event window] convertRectToScreen:NSMakeRect(eventLoc.x, eventLoc.y, 0, 0)]].origin; - - // Various things *only* apply to key down/up. - BOOL eventIsARepeat = NO; - NSString* eventCharacters = nil; - NSString* eventUnmodCharacters = nil; - if (eventType == NSKeyDown || eventType == NSKeyUp) { - eventIsARepeat = [event isARepeat]; - eventCharacters = [event characters]; - eventUnmodCharacters = [event charactersIgnoringModifiers]; - } - - // This synthesis may be slightly imperfect: we provide nil for the context, - // since I (viettrungluu) am sceptical that putting in the original context - // (if one is given) is valid. - return [NSEvent keyEventWithType:eventType - location:eventLoc - modifierFlags:[event modifierFlags] - timestamp:[event timestamp] - windowNumber:[window windowNumber] - context:nil - characters:eventCharacters - charactersIgnoringModifiers:eventUnmodCharacters - isARepeat:eventIsARepeat - keyCode:[event keyCode]]; -} - - -- (BOOL)performKeyEquivalent:(NSEvent*)event { - if (redispatchingEvent_) - return NO; - - // Give the web site a chance to handle the event. If it doesn't want to - // handle it, it will call us back with one of the |handle*| methods above. - NSResponder* r = [self firstResponder]; - if ([r conformsToProtocol:@protocol(RenderWidgetHostViewMacBase)]) - return [r performKeyEquivalent:event]; - - if ([super performKeyEquivalent:event]) - return YES; - - return NO; -} - -@end // EventProcessingWindow diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index 41682190e6..de8d994e5b 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -92,7 +92,7 @@ class GtkMessageBox { } const char* TranslateToStock(int id, const std::string& text) { - std::string lower = base::StringToLowerASCII(text); + std::string lower = base::ToLowerASCII(text); if (lower == "cancel") return GTK_STOCK_CANCEL; else if (lower == "no") diff --git a/atom/browser/ui/message_box_win.cc b/atom/browser/ui/message_box_win.cc index 697a7ad410..656be9f10b 100644 --- a/atom/browser/ui/message_box_win.cc +++ b/atom/browser/ui/message_box_win.cc @@ -34,7 +34,7 @@ struct CommonButtonID { int id; }; CommonButtonID GetCommonID(const base::string16& button) { - base::string16 lower = base::StringToLowerASCII(button); + base::string16 lower = base::ToLowerASCII(button); if (lower == L"ok") return { TDCBF_OK_BUTTON, IDOK }; else if (lower == L"yes") diff --git a/atom/browser/ui/x/x_window_utils.cc b/atom/browser/ui/x/x_window_utils.cc index f5c3f54ec1..db83753bb3 100644 --- a/atom/browser/ui/x/x_window_utils.cc +++ b/atom/browser/ui/x/x_window_utils.cc @@ -42,7 +42,7 @@ void SetWindowType(::Window xwindow, const std::string& type) { XDisplay* xdisplay = gfx::GetXDisplay(); std::string type_prefix = "_NET_WM_WINDOW_TYPE_"; ::Atom window_type = XInternAtom( - xdisplay, (type_prefix + base::StringToUpperASCII(type)).c_str(), False); + xdisplay, (type_prefix + base::ToUpperASCII(type)).c_str(), False); XChangeProperty(xdisplay, xwindow, XInternAtom(xdisplay, "_NET_WM_WINDOW_TYPE", False), XA_ATOM, diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 83145368c5..3d86df96dd 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -36,8 +36,6 @@ FeaturePair kWebRuntimeFeatures[] = { switches::kExperimentalCanvasFeatures }, { options::kOverlayScrollbars, switches::kOverlayScrollbars }, - { options::kOverlayFullscreenVideo, - switches::kOverlayFullscreenVideo }, { options::kSharedWorker, switches::kSharedWorker }, { options::kPageVisibility, @@ -148,8 +146,6 @@ void WebContentsPreferences::OverrideWebkitPrefs( prefs->javascript_enabled = b; if (self->web_preferences_.GetBoolean("images", &b)) prefs->images_enabled = b; - if (self->web_preferences_.GetBoolean("java", &b)) - prefs->java_enabled = b; if (self->web_preferences_.GetBoolean("textAreasAreResizable", &b)) prefs->text_areas_are_resizable = b; if (self->web_preferences_.GetBoolean("webgl", &b)) diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index e0f0940a74..a810069e71 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -63,7 +63,8 @@ float GetScaleFactorFromPath(const base::FilePath& path) { // We don't try to convert string to float here because it is very very // expensive. for (unsigned i = 0; i < arraysize(kScaleFactorPairs); ++i) { - if (base::EndsWith(filename, kScaleFactorPairs[i].name, true)) + if (base::EndsWith(filename, kScaleFactorPairs[i].name, + base::CompareCase::INSENSITIVE_ASCII)) return kScaleFactorPairs[i].scale; } diff --git a/atom/common/api/event_emitter_caller.cc b/atom/common/api/event_emitter_caller.cc index 94eb9ce9e7..4b44553d37 100644 --- a/atom/common/api/event_emitter_caller.cc +++ b/atom/common/api/event_emitter_caller.cc @@ -19,7 +19,7 @@ v8::Local CallEmitWithArgs(v8::Isolate* isolate, // Perform microtask checkpoint after running JavaScript. scoped_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script(isolate)); + nullptr : new blink::WebScopedRunV8Script); // Use node::MakeCallback to call the callback, and it will also run pending // tasks in Node.js. return node::MakeCallback( diff --git a/atom/common/crash_reporter/crash_reporter.cc b/atom/common/crash_reporter/crash_reporter.cc index b87ce54acd..f4f0ff9b7b 100644 --- a/atom/common/crash_reporter/crash_reporter.cc +++ b/atom/common/crash_reporter/crash_reporter.cc @@ -48,11 +48,11 @@ CrashReporter::GetUploadedReports(const std::string& path) { std::vector result; if (base::ReadFileToString(base::FilePath::FromUTF8Unsafe(path), &file_content)) { - std::vector reports; - base::SplitString(file_content, '\n', &reports); + std::vector reports = base::SplitString( + file_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); for (const std::string& report : reports) { - std::vector report_item; - base::SplitString(report, ',', &report_item); + std::vector report_item = base::SplitString( + report, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); int report_time = 0; if (report_item.size() >= 2 && base::StringToInt(report_item[0], &report_time)) { diff --git a/atom/common/crash_reporter/crash_reporter_linux.cc b/atom/common/crash_reporter/crash_reporter_linux.cc index 8a5608dad0..6fe69f4869 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.cc +++ b/atom/common/crash_reporter/crash_reporter_linux.cc @@ -130,7 +130,7 @@ bool CrashReporterLinux::CrashDone(const MinidumpDescriptor& minidump, // static CrashReporterLinux* CrashReporterLinux::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } // static diff --git a/atom/common/crash_reporter/crash_reporter_linux.h b/atom/common/crash_reporter/crash_reporter_linux.h index 2f7d639e90..165c288ab2 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.h +++ b/atom/common/crash_reporter/crash_reporter_linux.h @@ -12,7 +12,9 @@ #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" +namespace base { template struct DefaultSingletonTraits; +} namespace google_breakpad { class ExceptionHandler; @@ -34,7 +36,7 @@ class CrashReporterLinux : public CrashReporter { void SetUploadParameters() override; private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; CrashReporterLinux(); virtual ~CrashReporterLinux(); diff --git a/atom/common/crash_reporter/crash_reporter_mac.h b/atom/common/crash_reporter/crash_reporter_mac.h index cbdb3c65fe..f031543591 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.h +++ b/atom/common/crash_reporter/crash_reporter_mac.h @@ -14,7 +14,9 @@ #include "base/strings/string_piece.h" #include "vendor/crashpad/client/simple_string_dictionary.h" +namespace base { template struct DefaultSingletonTraits; +} namespace crash_reporter { @@ -31,7 +33,7 @@ class CrashReporterMac : public CrashReporter { void SetUploadParameters() override; private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; CrashReporterMac(); virtual ~CrashReporterMac(); diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index 00f37cc3fe..74ac70125b 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -126,7 +126,7 @@ CrashReporterMac::GetUploadedReports(const std::string& path) { // static CrashReporterMac* CrashReporterMac::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } // static diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index 240c229ca4..49a5ad8021 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -259,7 +259,7 @@ google_breakpad::CustomClientInfo* CrashReporterWin::GetCustomInfo( // static CrashReporterWin* CrashReporterWin::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } // static diff --git a/atom/common/crash_reporter/crash_reporter_win.h b/atom/common/crash_reporter/crash_reporter_win.h index 09c7ff4eaa..181c9eabd2 100644 --- a/atom/common/crash_reporter/crash_reporter_win.h +++ b/atom/common/crash_reporter/crash_reporter_win.h @@ -13,7 +13,9 @@ #include "base/memory/scoped_ptr.h" #include "vendor/breakpad/src/client/windows/handler/exception_handler.h" +namespace base { template struct DefaultSingletonTraits; +} namespace crash_reporter { @@ -33,7 +35,7 @@ class CrashReporterWin : public CrashReporter { int CrashForException(EXCEPTION_POINTERS* info); private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; CrashReporterWin(); virtual ~CrashReporterWin(); diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index 2c871276ba..d192018da0 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -45,7 +45,7 @@ template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Handle val, blink::WebInputEvent::Type* out) { - std::string type = base::StringToLowerASCII(V8ToString(val)); + std::string type = base::ToLowerASCII(V8ToString(val)); if (type == "mousedown") *out = blink::WebInputEvent::MouseDown; else if (type == "mouseup") @@ -82,7 +82,7 @@ template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Handle val, blink::WebMouseEvent::Button* out) { - std::string button = base::StringToLowerASCII(V8ToString(val)); + std::string button = base::ToLowerASCII(V8ToString(val)); if (button == "left") *out = blink::WebMouseEvent::Button::ButtonLeft; else if (button == "middle") @@ -97,7 +97,7 @@ template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Handle val, blink::WebInputEvent::Modifiers* out) { - std::string modifier = base::StringToLowerASCII(V8ToString(val)); + std::string modifier = base::ToLowerASCII(V8ToString(val)); if (modifier == "shift") *out = blink::WebInputEvent::ShiftKey; else if (modifier == "control" || modifier == "ctrl") @@ -166,7 +166,7 @@ bool Converter::FromV8( out->windowsKeyCode = atom::KeyboardCodeFromCharCode(code, &shifted); else if (dict.Get("keyCode", &identifier)) out->windowsKeyCode = atom::KeyboardCodeFromKeyIdentifier( - base::StringToLowerASCII(identifier)); + base::ToLowerASCII(identifier)); else return false; @@ -263,7 +263,7 @@ bool Converter::FromV8( std::string screen_position; if (dict.Get("screenPosition", &screen_position)) { - screen_position = base::StringToLowerASCII(screen_position); + screen_position = base::ToLowerASCII(screen_position); if (screen_position == "mobile") out->screenPosition = blink::WebDeviceEmulationParams::Mobile; else if (screen_position == "desktop") diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h index 3cba2b32a8..6ef8e74c73 100644 --- a/atom/common/native_mate_converters/callback.h +++ b/atom/common/native_mate_converters/callback.h @@ -51,7 +51,7 @@ struct V8FunctionInvoker(ArgTypes...)> { return v8::Null(isolate); scoped_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script(isolate)); + nullptr : new blink::WebScopedRunV8Script); v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); @@ -72,7 +72,7 @@ struct V8FunctionInvoker { return; scoped_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script(isolate)); + nullptr : new blink::WebScopedRunV8Script); v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); @@ -93,7 +93,7 @@ struct V8FunctionInvoker { return ret; scoped_ptr script_scope( Locker::IsBrowserProcess() ? - nullptr : new blink::WebScopedRunV8Script(isolate)); + nullptr : new blink::WebScopedRunV8Script); v8::Local holder = function.NewHandle(isolate); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index dbd0bd8d96..04a0c9139e 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -227,7 +227,7 @@ void NodeBindings::UvRunOnce() { // Perform microtask checkpoint after running JavaScript. scoped_ptr script_scope( - is_browser_ ? nullptr : new blink::WebScopedRunV8Script(env->isolate())); + is_browser_ ? nullptr : new blink::WebScopedRunV8Script); // Deal with uv events. int r = uv_run(uv_loop_, UV_RUN_NOWAIT); diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 1124cfba4b..a0cb8384a3 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -95,7 +95,6 @@ const char kDirectWrite[] = "directWrite"; const char kExperimentalFeatures[] = "experimentalFeatures"; const char kExperimentalCanvasFeatures[] = "experimentalCanvasFeatures"; const char kOverlayScrollbars[] = "overlayScrollbars"; -const char kOverlayFullscreenVideo[] = "overlayFullscreenVideo"; const char kSharedWorker[] = "sharedWorker"; } // namespace options @@ -139,7 +138,6 @@ const char kGuestInstanceID[] = "guest-instance-id"; const char kExperimentalFeatures[] = "experimental-features"; const char kExperimentalCanvasFeatures[] = "experimental-canvas-features"; const char kOverlayScrollbars[] = "overlay-scrollbars"; -const char kOverlayFullscreenVideo[] = "overlay-fullscreen-video"; const char kSharedWorker[] = "shared-worker"; const char kPageVisibility[] = "page-visiblity"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index cd52c97597..6960db83bc 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -50,7 +50,6 @@ extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; extern const char kOverlayScrollbars[]; -extern const char kOverlayFullscreenVideo[]; extern const char kSharedWorker[]; extern const char kPageVisibility[]; @@ -79,7 +78,6 @@ extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; extern const char kOverlayScrollbars[]; -extern const char kOverlayFullscreenVideo[]; extern const char kSharedWorker[]; extern const char kPageVisibility[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 362b0b8026..7c04c04249 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -226,8 +226,6 @@ void AtomRendererClient::EnableWebRuntimeFeatures() { blink::WebRuntimeFeatures::enableExperimentalCanvasFeatures(true); if (IsSwitchEnabled(command_line, switches::kOverlayScrollbars)) blink::WebRuntimeFeatures::enableOverlayScrollbars(true); - if (IsSwitchEnabled(command_line, switches::kOverlayFullscreenVideo)) - blink::WebRuntimeFeatures::enableOverlayFullscreenVideo(true); if (IsSwitchEnabled(command_line, switches::kSharedWorker)) blink::WebRuntimeFeatures::enableSharedWorker(true); } diff --git a/chromium_src/chrome/browser/printing/print_view_manager_base.cc b/chromium_src/chrome/browser/printing/print_view_manager_base.cc index 35c62fbc53..ede1d3b8ba 100644 --- a/chromium_src/chrome/browser/printing/print_view_manager_base.cc +++ b/chromium_src/chrome/browser/printing/print_view_manager_base.cc @@ -410,7 +410,7 @@ bool PrintViewManagerBase::RunInnerMessageLoop() { // be CPU bound, the page overly complex/large or the system just // memory-bound. static const int kPrinterSettingsTimeout = 60000; - base::OneShotTimer quit_timer; + base::OneShotTimer quit_timer; quit_timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), base::MessageLoop::current(), &base::MessageLoop::Quit); diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index b03ce431e4..98fb948730 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -503,7 +503,7 @@ class ProcessSingleton::LinuxWatcher // reads. size_t bytes_read_; - base::OneShotTimer timer_; + base::OneShotTimer timer_; DISALLOW_COPY_AND_ASSIGN(SocketReader); }; diff --git a/chromium_src/chrome/browser/speech/tts_controller_impl.cc b/chromium_src/chrome/browser/speech/tts_controller_impl.cc index 6b66b6a619..610ce16567 100644 --- a/chromium_src/chrome/browser/speech/tts_controller_impl.cc +++ b/chromium_src/chrome/browser/speech/tts_controller_impl.cc @@ -111,7 +111,7 @@ TtsController* TtsController::GetInstance() { // static TtsControllerImpl* TtsControllerImpl::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } TtsControllerImpl::TtsControllerImpl() diff --git a/chromium_src/chrome/browser/speech/tts_controller_impl.h b/chromium_src/chrome/browser/speech/tts_controller_impl.h index 651f836cdf..6c8aa5747d 100644 --- a/chromium_src/chrome/browser/speech/tts_controller_impl.h +++ b/chromium_src/chrome/browser/speech/tts_controller_impl.h @@ -77,7 +77,7 @@ class TtsControllerImpl : public TtsController { int GetMatchingVoice(const Utterance* utterance, std::vector& voices); - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; // The current utterance being spoken. Utterance* current_utterance_; @@ -101,4 +101,4 @@ class TtsControllerImpl : public TtsController { DISALLOW_COPY_AND_ASSIGN(TtsControllerImpl); }; -#endif // CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_ \ No newline at end of file +#endif // CHROME_BROWSER_SPEECH_TTS_CONTROLLER_IMPL_H_ diff --git a/chromium_src/chrome/browser/speech/tts_mac.mm b/chromium_src/chrome/browser/speech/tts_mac.mm index acfa5b58bf..aafbd46925 100644 --- a/chromium_src/chrome/browser/speech/tts_mac.mm +++ b/chromium_src/chrome/browser/speech/tts_mac.mm @@ -91,7 +91,7 @@ class TtsPlatformImplMac : public TtsPlatformImpl { int last_char_index_; bool paused_; - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplMac); }; @@ -291,7 +291,7 @@ TtsPlatformImplMac::~TtsPlatformImplMac() { // static TtsPlatformImplMac* TtsPlatformImplMac::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } @implementation ChromeTtsDelegate diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc index fe5e28ebbe..66edd3f938 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.cc @@ -29,7 +29,6 @@ #include "third_party/skia/include/core/SkMatrix.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkPoint.h" -#include "third_party/skia/include/core/SkTemplates.h" #include "third_party/skia/include/core/SkTypeface.h" #include "ui/gfx/geometry/rect.h" #include "url/gurl.h" @@ -315,7 +314,7 @@ int32_t PepperFlashRendererHost::OnNavigate( bool rejected = false; while (header_iter.GetNext()) { std::string lower_case_header_name = - base::StringToLowerASCII(header_iter.name()); + base::ToLowerASCII(header_iter.name()); if (!IsSimpleHeader(lower_case_header_name, header_iter.values())) { rejected = true; diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc index 20ac1fdc9b..3bfe719a0c 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc @@ -544,7 +544,6 @@ void PrepareFrameAndViewForPrint::CopySelection( // on the page). WebPreferences prefs = preferences; prefs.javascript_enabled = false; - prefs.java_enabled = false; blink::WebView* web_view = blink::WebView::create(this); owns_web_view_ = true; diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index a987e29246..db02b6aa14 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -123,7 +123,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `allowRunningInsecureContent` Boolean - Allow a https page to run JavaScript, CSS or plugins from http URLs. Default is `false`. * `images` Boolean - Enables image support. Default is `true`. - * `java` Boolean - Enables Java support. Default is `false`. * `textAreasAreResizable` Boolean - Make TextArea elements resizable. Default is `true`. * `webgl` Boolean - Enables WebGL support. Default is `true`. @@ -135,8 +134,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. canvas features. Default is `false`. * `overlayScrollbars` Boolean - Enables overlay scrollbars. Default is `false`. - * `overlayFullscreenVideo` Boolean - Enables overlay fullscreen video. Default - is `false` * `sharedWorker` Boolean - Enables Shared Worker support. Default is `false`. * `directWrite` Boolean - Enables DirectWrite font rendering system on Windows. Default is `true`. diff --git a/filenames.gypi b/filenames.gypi index 151a69ff1c..5e44ad7d71 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -200,8 +200,6 @@ 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', - 'atom/browser/ui/cocoa/event_processing_window.h', - 'atom/browser/ui/cocoa/event_processing_window.mm', 'atom/browser/ui/file_dialog.h', 'atom/browser/ui/file_dialog_gtk.cc', 'atom/browser/ui/file_dialog_mac.mm', diff --git a/vendor/brightray b/vendor/brightray index 57842edb81..fff0f0e2d3 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 57842edb817cc1ae38a02fd7f266dd6aa3afbb45 +Subproject commit fff0f0e2d39886a49fce4f78aa3b625b880b3607 From 95e7c796ec28e805a979004ba6912d1d87d8acab Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 7 Dec 2015 20:48:39 +0800 Subject: [PATCH 142/411] V8 now checks strictly when callin Neuter() --- script/lib/config.py | 2 +- vendor/node | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index 4978302abb..159839c7f5 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'd534691711ecdef1739878674a9ffd5f2d5ac4a2' +LIBCHROMIUMCONTENT_COMMIT = '451ea93cc3090f7000f8f0daa4cb84e90ad6c842' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/node b/vendor/node index 1445826ca7..97d9298d8a 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 1445826ca73cc79bc57d503dd11d4ffaf695625c +Subproject commit 97d9298d8a431f27e2aded918ae9f2a673c9cf6f From 647f151906dc46bc3be4b188294ccc389617b7e2 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 7 Dec 2015 21:25:19 +0800 Subject: [PATCH 143/411] Fix the failing sendSync --- atom/renderer/api/lib/remote.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 48cdd937fb..357b884069 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -119,7 +119,11 @@ metaToPlainObject = (meta) -> # Browser calls a callback in renderer. ipcRenderer.on 'ATOM_RENDERER_CALLBACK', (event, id, args) -> - callbacksRegistry.apply id, metaToValue(args) + # Delay the callback to next tick in case the browser is still in the middle + # of sending this message while the callback sends a sync message to browser, + # which can fail sometimes. + setImmediate -> + callbacksRegistry.apply id, metaToValue(args) # A callback in browser is released. ipcRenderer.on 'ATOM_RENDERER_RELEASE_CALLBACK', (event, id) -> From 27dd233820ccc7b8e1cd4a56982c9d31f3f4cd13 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 7 Dec 2015 21:28:58 +0800 Subject: [PATCH 144/411] spec: Make the "remote listeners" test more reliable --- spec/api-ipc-spec.coffee | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/api-ipc-spec.coffee b/spec/api-ipc-spec.coffee index a8d2a65cde..1aa715c9f7 100644 --- a/spec/api-ipc-spec.coffee +++ b/spec/api-ipc-spec.coffee @@ -89,14 +89,14 @@ describe 'ipc module', -> w.loadURL 'file://' + path.join(fixtures, 'api', 'send-sync-message.html') describe 'remote listeners', -> - it 'can be added and removed correctly', -> - count = 0 - w = new BrowserWindow(show: false) - listener = () -> - count += 1 - w.removeListener 'blur', listener - w.on 'blur', listener - w.emit 'blur' - w.emit 'blur' - assert.equal count, 1 + w = null + afterEach -> w.destroy() + + it 'can be added and removed correctly', -> + w = new BrowserWindow(show: false) + listener = -> + w.on 'test', listener + assert.equal w.listenerCount('test'), 1 + w.removeListener 'test', listener + assert.equal w.listenerCount('test'), 0 From d0be6c74116e92b667213c1740ff57014aceee66 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 7 Dec 2015 22:44:35 +0800 Subject: [PATCH 145/411] Fix cppling warning --- atom/browser/atom_browser_context.cc | 3 ++- vendor/brightray | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index b9589d569b..ec12382582 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -86,7 +86,8 @@ std::string AtomBrowserContext::GetUserAgent() { return content::BuildUserAgentFromProduct(user_agent); } -scoped_ptr AtomBrowserContext::CreateURLRequestJobFactory( +scoped_ptr +AtomBrowserContext::CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) { scoped_ptr job_factory(job_factory_); diff --git a/vendor/brightray b/vendor/brightray index fff0f0e2d3..878e63860b 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit fff0f0e2d39886a49fce4f78aa3b625b880b3607 +Subproject commit 878e63860b59d3443cd9f739d7533f2be1109773 From f1787877371200ea96d7507f6daca460fb53d934 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 09:12:48 -0800 Subject: [PATCH 146/411] Add failing spec for Menu.buildFromTemplate --- spec/api-menu-spec.coffee | 7 ++++++- spec/static/main.js | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/api-menu-spec.coffee b/spec/api-menu-spec.coffee index f23face228..c619be7530 100644 --- a/spec/api-menu-spec.coffee +++ b/spec/api-menu-spec.coffee @@ -1,6 +1,6 @@ assert = require 'assert' -{remote} = require 'electron' +{remote, ipcRenderer} = require 'electron' {Menu, MenuItem} = remote.require 'electron' describe 'menu module', -> @@ -9,6 +9,11 @@ describe 'menu module', -> menu = Menu.buildFromTemplate [label: 'text', extra: 'field'] assert.equal menu.items[0].extra, 'field' + it 'does not modify the specified template', -> + template = [label: 'text', submenu: [label: 'sub']] + builtTemplate = ipcRenderer.sendSync('menu-build-from-template', template) + assert.deepStrictEqual builtTemplate, template + describe 'Menu.buildFromTemplate should reorder based on item position specifiers', -> it 'should position before existing item', -> menu = Menu.buildFromTemplate [ diff --git a/spec/static/main.js b/spec/static/main.js index be3690cd9e..8618237977 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -3,6 +3,7 @@ const app = electron.app; const ipcMain = electron.ipcMain; const dialog = electron.dialog; const BrowserWindow = electron.BrowserWindow; +const Menu = electron.Menu; const path = require('path'); @@ -100,4 +101,10 @@ app.on('ready', function() { }); event.returnValue = "done"; }); + + // Verify Menu.buildFromTemplate does not modify the specified template + ipcMain.on('menu-build-from-template', function(event, template) { + Menu.buildFromTemplate(template); + event.returnValue = template; + }) }); From 26ac86c95c781fffed4b2714818016de8f986daf Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 09:28:48 -0800 Subject: [PATCH 147/411] Convert submenu when non-Menu is passed into MenuItem ctor --- atom/browser/api/lib/menu-item.coffee | 4 +++- atom/browser/api/lib/menu.coffee | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/lib/menu-item.coffee b/atom/browser/api/lib/menu-item.coffee index 57beb6ffda..737f9c000d 100644 --- a/atom/browser/api/lib/menu-item.coffee +++ b/atom/browser/api/lib/menu-item.coffee @@ -24,8 +24,10 @@ class MenuItem constructor: (options) -> {Menu} = require 'electron' - {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked, @submenu} = options + {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked} = options + if options.submenu? and options.submenu.constructor isnt Menu + @submenu = Menu.buildFromTemplate options.submenu @type = 'submenu' if not @type? and @submenu? throw new Error('Invalid submenu') if @type is 'submenu' and @submenu?.constructor isnt Menu diff --git a/atom/browser/api/lib/menu.coffee b/atom/browser/api/lib/menu.coffee index 26e2dc2335..eac6a607fd 100644 --- a/atom/browser/api/lib/menu.coffee +++ b/atom/browser/api/lib/menu.coffee @@ -169,7 +169,6 @@ Menu.buildFromTemplate = (template) -> for item in positionedTemplate throw new TypeError('Invalid template for MenuItem') unless typeof item is 'object' - item.submenu = Menu.buildFromTemplate item.submenu if item.submenu? menuItem = new MenuItem(item) menuItem[key] = value for key, value of item when not menuItem[key]? menu.append menuItem From d5c740957f746b6db3376da705fc7607fbd10a92 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 09:29:03 -0800 Subject: [PATCH 148/411] :art: --- atom/browser/api/lib/menu.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/lib/menu.coffee b/atom/browser/api/lib/menu.coffee index eac6a607fd..d81c345f43 100644 --- a/atom/browser/api/lib/menu.coffee +++ b/atom/browser/api/lib/menu.coffee @@ -170,7 +170,7 @@ Menu.buildFromTemplate = (template) -> throw new TypeError('Invalid template for MenuItem') unless typeof item is 'object' menuItem = new MenuItem(item) - menuItem[key] = value for key, value of item when not menuItem[key]? + menuItem[key] ?= value for key, value of item menu.append menuItem menu From a139a62ebf4c52ce3bc089bbd6014f693d4d11dc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 09:36:07 -0800 Subject: [PATCH 149/411] Mention conversion using Menu.buildFromTemplate --- docs/api/menu-item.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 37079233fc..af945e8aca 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -26,7 +26,9 @@ Create a new `MenuItem` with the following method: * `visible` Boolean * `checked` Boolean * `submenu` Menu - Should be specified for `submenu` type menu item, when - it's specified the `type: 'submenu'` can be omitted for the menu item + it's specified the `type: 'submenu'` can be omitted for the menu item. + If the value is not a `Menu` then it will be automatically converted to one + using `Menu.buildFromTemplate`. * `id` String - Unique within a single menu. If defined then it can be used as a reference to this item by the position attribute. * `position` String - This field allows fine-grained definition of the From e62092ebb26e487ee2ff62401db6e04e885180a1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 09:47:15 -0800 Subject: [PATCH 150/411] Move ipc handler to be near others --- spec/static/main.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/static/main.js b/spec/static/main.js index 8618237977..821c6ecc0c 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -43,6 +43,12 @@ ipcMain.on('echo', function(event, msg) { event.returnValue = msg; }); +// Verify Menu.buildFromTemplate does not modify the specified template +ipcMain.on('menu-build-from-template', function(event, template) { + Menu.buildFromTemplate(template); + event.returnValue = template; +}) + if (process.argv[2] == '--ci') { process.removeAllListeners('uncaughtException'); process.on('uncaughtException', function(error) { @@ -101,10 +107,4 @@ app.on('ready', function() { }); event.returnValue = "done"; }); - - // Verify Menu.buildFromTemplate does not modify the specified template - ipcMain.on('menu-build-from-template', function(event, template) { - Menu.buildFromTemplate(template); - event.returnValue = template; - }) }); From 3931ebb7ef4b38867440315b07b2796a05da411f Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 7 Dec 2015 10:02:06 -0800 Subject: [PATCH 151/411] Fix up Windows build errors --- atom/browser/browser_win.cc | 2 +- atom/browser/ui/file_dialog_win.cc | 6 ++++-- atom/common/crash_reporter/crash_reporter_win.cc | 4 ++-- atom/common/crash_reporter/win/crash_service.cc | 5 ++--- atom/common/crash_reporter/win/crash_service_main.cc | 2 +- chromium_src/chrome/browser/speech/tts_win.cc | 8 ++++---- .../renderer/printing/print_web_view_helper_pdf_win.cc | 1 - .../net/test/embedded_test_server/stream_listen_socket.cc | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc index ce36d56b62..fdf4bd8c3b 100644 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -127,7 +127,7 @@ void Browser::SetUserTasks(const std::vector& tasks) { PCWSTR Browser::GetAppUserModelID() { if (app_user_model_id_.empty()) { - SetAppUserModelID(ReplaceStringPlaceholders( + SetAppUserModelID(base::ReplaceStringPlaceholders( kAppUserModelIDFormat, base::UTF8ToUTF16(GetName()), nullptr)); } diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index d218beaa27..f39094f9df 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -51,7 +51,7 @@ void ConvertFilters(const Filters& filters, std::vector extensions(filter.second); for (size_t j = 0; j < extensions.size(); ++j) extensions[j].insert(0, "*."); - buffer->push_back(base::UTF8ToWide(JoinString(extensions, ";"))); + buffer->push_back(base::UTF8ToWide(base::JoinString(extensions, ";"))); spec.pszSpec = buffer->back().c_str(); filterspec->push_back(spec); @@ -273,7 +273,9 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, bool matched = false; for (size_t i = 0; i < filter.second.size(); ++i) { if (filter.second[i] == "*" || - base::EndsWith(file_name, filter.second[i], false)) { + base::EndsWith( + file_name, filter.second[i], + base::CompareCase::INSENSITIVE_ASCII)) { matched = true; break;; } diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index 49a5ad8021..939a02f090 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -153,9 +153,9 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, return; } - base::string16 pipe_name = ReplaceStringPlaceholders( + base::string16 pipe_name = base::ReplaceStringPlaceholders( kPipeNameFormat, base::UTF8ToUTF16(product_name), NULL); - base::string16 wait_name = ReplaceStringPlaceholders( + base::string16 wait_name = base::ReplaceStringPlaceholders( kWaitEventFormat, base::UTF8ToUTF16(product_name), NULL); // Wait until the crash service is started. diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index 9b6ba7e03a..67e22381ae 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -118,7 +118,7 @@ HWND g_top_window = NULL; bool CreateTopWindow(HINSTANCE instance, const base::string16& application_name, bool visible) { - base::string16 class_name = ReplaceStringPlaceholders( + base::string16 class_name = base::ReplaceStringPlaceholders( kClassNameFormat, application_name, NULL); WNDCLASSEXW wcx = {0}; @@ -309,7 +309,7 @@ bool CrashService::Initialize(const base::string16& application_name, // Create or open an event to signal the browser process that the crash // service is initialized. - base::string16 wait_name = ReplaceStringPlaceholders( + base::string16 wait_name = base::ReplaceStringPlaceholders( kWaitEventFormat, application_name, NULL); HANDLE wait_event = ::CreateEventW(NULL, TRUE, TRUE, wait_name.c_str()); ::SetEvent(wait_event); @@ -524,4 +524,3 @@ PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() { } } // namespace breakpad - diff --git a/atom/common/crash_reporter/win/crash_service_main.cc b/atom/common/crash_reporter/win/crash_service_main.cc index 7a5eeb1013..56d46970b6 100644 --- a/atom/common/crash_reporter/win/crash_service_main.cc +++ b/atom/common/crash_reporter/win/crash_service_main.cc @@ -68,7 +68,7 @@ int Main(const wchar_t* cmd) { VLOG(1) << "Session start. cmdline is [" << cmd << "]"; // Setting the crash reporter. - base::string16 pipe_name = ReplaceStringPlaceholders(kPipeNameFormat, + base::string16 pipe_name = base::ReplaceStringPlaceholders(kPipeNameFormat, application_name, NULL); cmd_line.AppendSwitch("no-window"); diff --git a/chromium_src/chrome/browser/speech/tts_win.cc b/chromium_src/chrome/browser/speech/tts_win.cc index c7b0a0ca72..89a8f413e5 100644 --- a/chromium_src/chrome/browser/speech/tts_win.cc +++ b/chromium_src/chrome/browser/speech/tts_win.cc @@ -57,7 +57,7 @@ class TtsPlatformImplWin : public TtsPlatformImpl { int char_position_; bool paused_; - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplWin); }; @@ -246,12 +246,12 @@ TtsPlatformImplWin::TtsPlatformImplWin() // static TtsPlatformImplWin* TtsPlatformImplWin::GetInstance() { - return Singleton >::get(); + return base::Singleton< TtsPlatformImplWin, + base::LeakySingletonTraits >::get(); } // static void TtsPlatformImplWin::SpeechEventCallback( WPARAM w_param, LPARAM l_param) { GetInstance()->OnSpeechEvent(); -} \ No newline at end of file +} diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc index dcd388fd11..5b34ff3090 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc @@ -135,7 +135,6 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, printed_page_params.page_size = page_size_in_dpi[i]; printed_page_params.content_area = content_area_in_dpi[i]; Send(new PrintHostMsg_DidPrintPage(routing_id(), printed_page_params)); - printed_page_params.metafile_data_handle = INVALID_HANDLE_VALUE; } return true; } diff --git a/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc index 1056983a8e..897b23bbd5 100644 --- a/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc @@ -228,7 +228,7 @@ void StreamListenSocket::CloseSocket() { void StreamListenSocket::WatchSocket(WaitState state) { #if defined(OS_WIN) WSAEventSelect(socket_, socket_event_, FD_ACCEPT | FD_CLOSE | FD_READ); - watcher_.StartWatching(socket_event_, this); + watcher_.StartWatchingOnce(socket_event_, this); #elif defined(OS_POSIX) // Implicitly calls StartWatchingFileDescriptor(). base::MessageLoopForIO::current()->WatchFileDescriptor( @@ -264,7 +264,7 @@ void StreamListenSocket::OnObjectSignaled(HANDLE object) { return; } // The object was reset by WSAEnumNetworkEvents. Watch for the next signal. - watcher_.StartWatching(object, this); + watcher_.StartWatchingOnce(object, this); if (ev.lNetworkEvents == 0) { // Occasionally the event is set even though there is no new data. From 83c69b56a4a72403c82620fe9e2d1552585dfa4c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 11:10:57 -0800 Subject: [PATCH 152/411] Use ipc eval in spec --- spec/api-menu-spec.coffee | 9 ++++++--- spec/static/main.js | 7 ------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/spec/api-menu-spec.coffee b/spec/api-menu-spec.coffee index c619be7530..2f1f646589 100644 --- a/spec/api-menu-spec.coffee +++ b/spec/api-menu-spec.coffee @@ -10,9 +10,12 @@ describe 'menu module', -> assert.equal menu.items[0].extra, 'field' it 'does not modify the specified template', -> - template = [label: 'text', submenu: [label: 'sub']] - builtTemplate = ipcRenderer.sendSync('menu-build-from-template', template) - assert.deepStrictEqual builtTemplate, template + template = ipcRenderer.sendSync 'eval', """ + var template = [{label: 'text', submenu: [{label: 'sub'}]}]; + require('electron').Menu.buildFromTemplate(template); + template; + """ + assert.deepStrictEqual template, [label: 'text', submenu: [label: 'sub']] describe 'Menu.buildFromTemplate should reorder based on item position specifiers', -> it 'should position before existing item', -> diff --git a/spec/static/main.js b/spec/static/main.js index 821c6ecc0c..be3690cd9e 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -3,7 +3,6 @@ const app = electron.app; const ipcMain = electron.ipcMain; const dialog = electron.dialog; const BrowserWindow = electron.BrowserWindow; -const Menu = electron.Menu; const path = require('path'); @@ -43,12 +42,6 @@ ipcMain.on('echo', function(event, msg) { event.returnValue = msg; }); -// Verify Menu.buildFromTemplate does not modify the specified template -ipcMain.on('menu-build-from-template', function(event, template) { - Menu.buildFromTemplate(template); - event.returnValue = template; -}) - if (process.argv[2] == '--ci') { process.removeAllListeners('uncaughtException'); process.on('uncaughtException', function(error) { From 96ef09742c69a2a972981cb4b85cc9b54e8e2bac Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 11:16:36 -0800 Subject: [PATCH 153/411] Directly assign submenu when constructor is Menu --- atom/browser/api/lib/menu-item.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/lib/menu-item.coffee b/atom/browser/api/lib/menu-item.coffee index 737f9c000d..86b5c190d6 100644 --- a/atom/browser/api/lib/menu-item.coffee +++ b/atom/browser/api/lib/menu-item.coffee @@ -26,8 +26,11 @@ class MenuItem {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked} = options - if options.submenu? and options.submenu.constructor isnt Menu - @submenu = Menu.buildFromTemplate options.submenu + if options.submenu? + if options.submenu.constructor is Menu + @submenu = options.submenu + else + @submenu = Menu.buildFromTemplate options.submenu @type = 'submenu' if not @type? and @submenu? throw new Error('Invalid submenu') if @type is 'submenu' and @submenu?.constructor isnt Menu From 68d937ed47b73cc9ea2217291e6d02fd0d5b66c7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 11:20:15 -0800 Subject: [PATCH 154/411] :art: --- atom/browser/api/lib/menu-item.coffee | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/lib/menu-item.coffee b/atom/browser/api/lib/menu-item.coffee index 86b5c190d6..242a48f54d 100644 --- a/atom/browser/api/lib/menu-item.coffee +++ b/atom/browser/api/lib/menu-item.coffee @@ -24,13 +24,10 @@ class MenuItem constructor: (options) -> {Menu} = require 'electron' - {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked} = options + {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked, @submenu} = options - if options.submenu? - if options.submenu.constructor is Menu - @submenu = options.submenu - else - @submenu = Menu.buildFromTemplate options.submenu + if @submenu? and @submenu.constructor isnt Menu + @submenu = Menu.buildFromTemplate @submenu @type = 'submenu' if not @type? and @submenu? throw new Error('Invalid submenu') if @type is 'submenu' and @submenu?.constructor isnt Menu From af289001898f80feddc5d8af62b6f3c0442dcaba Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 7 Dec 2015 13:27:05 -0800 Subject: [PATCH 155/411] Fix up Chrome47 changes --- atom/browser/ui/file_dialog_gtk.cc | 4 +++- atom/browser/ui/views/menu_bar.cc | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 5885ffe361..ed79449655 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -22,7 +22,9 @@ gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info, // Makes .* file extension matches all file types. if (*file_extension == ".*") return true; - return base::EndsWith(file_info->filename, *file_extension, false); + return base::EndsWith( + file_info->filename, + *file_extension, base::CompareCase::INSENSITIVE_ASCII); } // Deletes |data| when gtk_file_filter_add_custom() is done with it. diff --git a/atom/browser/ui/views/menu_bar.cc b/atom/browser/ui/views/menu_bar.cc index d3059a50a4..b7712929d0 100644 --- a/atom/browser/ui/views/menu_bar.cc +++ b/atom/browser/ui/views/menu_bar.cc @@ -17,7 +17,6 @@ #if defined(OS_WIN) #include "ui/gfx/color_utils.h" #elif defined(USE_X11) -#include "chrome/browser/ui/libgtk2ui/owned_widget_gtk2.h" #include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h" #endif @@ -33,15 +32,16 @@ const SkColor kDefaultColor = SkColorSetARGB(255, 233, 233, 233); #if defined(USE_X11) void GetMenuBarColor(SkColor* enabled, SkColor* disabled, SkColor* highlight, SkColor* hover, SkColor* background) { - libgtk2ui::OwnedWidgetGtk fake_menu_bar; - fake_menu_bar.Own(gtk_menu_bar_new()); + GtkWidget* menu_bar = gtk_menu_bar_new(); - GtkStyle* style = gtk_rc_get_style(fake_menu_bar.get()); + GtkStyle* style = gtk_rc_get_style(menu_bar); *enabled = libgtk2ui::GdkColorToSkColor(style->fg[GTK_STATE_NORMAL]); *disabled = libgtk2ui::GdkColorToSkColor(style->fg[GTK_STATE_INSENSITIVE]); *highlight = libgtk2ui::GdkColorToSkColor(style->fg[GTK_STATE_SELECTED]); *hover = libgtk2ui::GdkColorToSkColor(style->fg[GTK_STATE_PRELIGHT]); *background = libgtk2ui::GdkColorToSkColor(style->bg[GTK_STATE_NORMAL]); + + gtk_widget_destroy(menu_bar); } #endif From 8d5c153e9bbd5e324f2cb14fc87158044890d5d6 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 7 Dec 2015 14:23:01 -0800 Subject: [PATCH 156/411] Update to Chrome 47 version of tts_win --- chromium_src/chrome/browser/speech/tts_win.cc | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/chromium_src/chrome/browser/speech/tts_win.cc b/chromium_src/chrome/browser/speech/tts_win.cc index 89a8f413e5..ac25820588 100644 --- a/chromium_src/chrome/browser/speech/tts_win.cc +++ b/chromium_src/chrome/browser/speech/tts_win.cc @@ -15,26 +15,26 @@ class TtsPlatformImplWin : public TtsPlatformImpl { public: - virtual bool PlatformImplAvailable() { + bool PlatformImplAvailable() override { return true; } - virtual bool Speak( + bool Speak( int utterance_id, const std::string& utterance, const std::string& lang, const VoiceData& voice, - const UtteranceContinuousParameters& params); + const UtteranceContinuousParameters& params) override; - virtual bool StopSpeaking(); + bool StopSpeaking() override; - virtual void Pause(); + void Pause() override; - virtual void Resume(); + void Resume() override; - virtual bool IsSpeaking(); + bool IsSpeaking() override; - virtual void GetVoices(std::vector* out_voices) override; + void GetVoices(std::vector* out_voices) override; // Get the single instance of this class. static TtsPlatformImplWin* GetInstance(); @@ -43,7 +43,7 @@ class TtsPlatformImplWin : public TtsPlatformImpl { private: TtsPlatformImplWin(); - virtual ~TtsPlatformImplWin() {} + ~TtsPlatformImplWin() override {} void OnSpeechEvent(); @@ -220,6 +220,8 @@ void TtsPlatformImplWin::OnSpeechEvent() { utterance_id_, TTS_EVENT_SENTENCE, char_position_, std::string()); break; + default: + break; } } } @@ -246,8 +248,8 @@ TtsPlatformImplWin::TtsPlatformImplWin() // static TtsPlatformImplWin* TtsPlatformImplWin::GetInstance() { - return base::Singleton< TtsPlatformImplWin, - base::LeakySingletonTraits >::get(); + return base::Singleton>::get(); } // static From 4a8d58f9141610fa6986618f9d374f30bfc16d7b Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 7 Dec 2015 14:25:52 -0800 Subject: [PATCH 157/411] Update to Chrome47 version of GlobalMenuBarRegistrarX11 --- .../frame/global_menu_bar_registrar_x11.cc | 28 +++++++++---------- .../frame/global_menu_bar_registrar_x11.h | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc index 0d2a6dd738..3913325f21 100644 --- a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc +++ b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc @@ -21,7 +21,7 @@ const char kAppMenuRegistrarPath[] = "/com/canonical/AppMenu/Registrar"; // static GlobalMenuBarRegistrarX11* GlobalMenuBarRegistrarX11::GetInstance() { - return Singleton::get(); + return base::Singleton::get(); } void GlobalMenuBarRegistrarX11::OnWindowMapped(unsigned long xid) { @@ -39,7 +39,7 @@ void GlobalMenuBarRegistrarX11::OnWindowUnmapped(unsigned long xid) { } GlobalMenuBarRegistrarX11::GlobalMenuBarRegistrarX11() - : registrar_proxy_(NULL) { + : registrar_proxy_(nullptr) { // libdbusmenu uses the gio version of dbus; I tried using the code in dbus/, // but it looks like that's isn't sharing the bus name with the gio version, // even when |connection_type| is set to SHARED. @@ -49,11 +49,11 @@ GlobalMenuBarRegistrarX11::GlobalMenuBarRegistrarX11() G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), - NULL, + nullptr, kAppMenuRegistrarName, kAppMenuRegistrarPath, kAppMenuRegistrarName, - NULL, // TODO: Probalby want a real cancelable. + nullptr, // TODO: Probalby want a real cancelable. static_cast(OnProxyCreatedThunk), this); } @@ -70,7 +70,7 @@ GlobalMenuBarRegistrarX11::~GlobalMenuBarRegistrarX11() { void GlobalMenuBarRegistrarX11::RegisterXID(unsigned long xid) { DCHECK(registrar_proxy_); - std::string path = atom::GlobalMenuBarX11::GetPathForWindow(xid); + std::string path = GlobalMenuBarX11::GetPathForWindow(xid); ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087 // TODO(erg): The mozilla implementation goes to a lot of callback trouble @@ -84,14 +84,14 @@ void GlobalMenuBarRegistrarX11::RegisterXID(unsigned long xid) { "RegisterWindow", g_variant_new("(uo)", xid, path.c_str()), G_DBUS_CALL_FLAGS_NONE, -1, - NULL, - NULL, - NULL); + nullptr, + nullptr, + nullptr); } void GlobalMenuBarRegistrarX11::UnregisterXID(unsigned long xid) { DCHECK(registrar_proxy_); - std::string path = atom::GlobalMenuBarX11::GetPathForWindow(xid); + std::string path = GlobalMenuBarX11::GetPathForWindow(xid); ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087 // TODO(erg): The mozilla implementation goes to a lot of callback trouble @@ -105,14 +105,14 @@ void GlobalMenuBarRegistrarX11::UnregisterXID(unsigned long xid) { "UnregisterWindow", g_variant_new("(u)", xid), G_DBUS_CALL_FLAGS_NONE, -1, - NULL, - NULL, - NULL); + nullptr, + nullptr, + nullptr); } void GlobalMenuBarRegistrarX11::OnProxyCreated(GObject* source, GAsyncResult* result) { - GError* error = NULL; + GError* error = nullptr; GDBusProxy* proxy = g_dbus_proxy_new_for_bus_finish(result, &error); if (error) { g_error_free(error); @@ -128,7 +128,7 @@ void GlobalMenuBarRegistrarX11::OnProxyCreated(GObject* source, g_signal_connect(registrar_proxy_, "notify::g-name-owner", G_CALLBACK(OnNameOwnerChangedThunk), this); - OnNameOwnerChanged(NULL, NULL); + OnNameOwnerChanged(nullptr, nullptr); } void GlobalMenuBarRegistrarX11::OnNameOwnerChanged(GObject* /* ignored */, diff --git a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h index e35e87c0d2..694f776b24 100644 --- a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h +++ b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h @@ -28,7 +28,7 @@ class GlobalMenuBarRegistrarX11 { void OnWindowUnmapped(unsigned long xid); private: - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; GlobalMenuBarRegistrarX11(); ~GlobalMenuBarRegistrarX11(); From fe86239a9c2f120a9ef1eafee89890b6a98d0d80 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 7 Dec 2015 14:28:42 -0800 Subject: [PATCH 158/411] Update to Chrome47 version of tts_linux --- .../chrome/browser/speech/tts_linux.cc | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/chromium_src/chrome/browser/speech/tts_linux.cc b/chromium_src/chrome/browser/speech/tts_linux.cc index 43b28a5ead..ba15516ce0 100644 --- a/chromium_src/chrome/browser/speech/tts_linux.cc +++ b/chromium_src/chrome/browser/speech/tts_linux.cc @@ -13,6 +13,7 @@ #include "base/synchronization/lock.h" #include "chrome/browser/speech/tts_platform.h" #include "content/public/browser/browser_thread.h" +#include "content/public/common/content_switches.h" #include "library_loaders/libspeechd.h" @@ -32,18 +33,17 @@ struct SPDChromeVoice { class TtsPlatformImplLinux : public TtsPlatformImpl { public: - virtual bool PlatformImplAvailable() override; - virtual bool Speak( - int utterance_id, - const std::string& utterance, - const std::string& lang, - const VoiceData& voice, - const UtteranceContinuousParameters& params) override; - virtual bool StopSpeaking() override; - virtual void Pause() override; - virtual void Resume() override; - virtual bool IsSpeaking() override; - virtual void GetVoices(std::vector* out_voices) override; + bool PlatformImplAvailable() override; + bool Speak(int utterance_id, + const std::string& utterance, + const std::string& lang, + const VoiceData& voice, + const UtteranceContinuousParameters& params) override; + bool StopSpeaking() override; + void Pause() override; + void Resume() override; + bool IsSpeaking() override; + void GetVoices(std::vector* out_voices) override; void OnSpeechEvent(SPDNotificationType type); @@ -52,7 +52,7 @@ class TtsPlatformImplLinux : public TtsPlatformImpl { private: TtsPlatformImplLinux(); - virtual ~TtsPlatformImplLinux(); + ~TtsPlatformImplLinux() override; // Initiate the connection with the speech dispatcher. void Initialize(); @@ -83,7 +83,7 @@ class TtsPlatformImplLinux : public TtsPlatformImpl { // uniquely identify a voice across all available modules. scoped_ptr > all_native_voices_; - friend struct DefaultSingletonTraits; + friend struct base::DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux); }; @@ -94,6 +94,11 @@ SPDNotificationType TtsPlatformImplLinux::current_notification_ = TtsPlatformImplLinux::TtsPlatformImplLinux() : utterance_id_(0) { + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + if (!command_line.HasSwitch(switches::kEnableSpeechDispatcher)) + return; + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&TtsPlatformImplLinux::Initialize, @@ -111,7 +116,7 @@ void TtsPlatformImplLinux::Initialize() { // http://crbug.com/317360 ANNOTATE_SCOPED_MEMORY_LEAK; conn_ = libspeechd_loader_.spd_open( - "chrome", "extension_api", NULL, SPD_MODE_SINGLE); + "chrome", "extension_api", NULL, SPD_MODE_THREADED); } if (!conn_) return; @@ -146,7 +151,7 @@ void TtsPlatformImplLinux::Reset() { if (conn_) libspeechd_loader_.spd_close(conn_); conn_ = libspeechd_loader_.spd_open( - "chrome", "extension_api", NULL, SPD_MODE_SINGLE); + "chrome", "extension_api", NULL, SPD_MODE_THREADED); } bool TtsPlatformImplLinux::PlatformImplAvailable() { @@ -187,6 +192,10 @@ bool TtsPlatformImplLinux::Speak( libspeechd_loader_.spd_set_voice_rate(conn_, 100 * log10(rate) / log10(3)); libspeechd_loader_.spd_set_voice_pitch(conn_, 100 * log10(pitch) / log10(3)); + // Support languages other than the default + if (!lang.empty()) + libspeechd_loader_.spd_set_language(conn_, lang.c_str()); + utterance_ = utterance; utterance_id_ = utterance_id; @@ -337,8 +346,9 @@ void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id, // static TtsPlatformImplLinux* TtsPlatformImplLinux::GetInstance() { - return Singleton >::get(); + return base::Singleton< + TtsPlatformImplLinux, + base::LeakySingletonTraits>::get(); } // static From 9a0cecf943a0b2985ea6508539245eb911f0f1ab Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 7 Dec 2015 14:29:11 -0800 Subject: [PATCH 159/411] Rig GlobalMenuBarRegistrarX11 for Atom --- .../browser/ui/views/frame/global_menu_bar_registrar_x11.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc index 3913325f21..cead675a74 100644 --- a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc +++ b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc @@ -70,7 +70,7 @@ GlobalMenuBarRegistrarX11::~GlobalMenuBarRegistrarX11() { void GlobalMenuBarRegistrarX11::RegisterXID(unsigned long xid) { DCHECK(registrar_proxy_); - std::string path = GlobalMenuBarX11::GetPathForWindow(xid); + std::string path = atom::GlobalMenuBarX11::GetPathForWindow(xid); ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087 // TODO(erg): The mozilla implementation goes to a lot of callback trouble @@ -91,7 +91,7 @@ void GlobalMenuBarRegistrarX11::RegisterXID(unsigned long xid) { void GlobalMenuBarRegistrarX11::UnregisterXID(unsigned long xid) { DCHECK(registrar_proxy_); - std::string path = GlobalMenuBarX11::GetPathForWindow(xid); + std::string path = atom::GlobalMenuBarX11::GetPathForWindow(xid); ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087 // TODO(erg): The mozilla implementation goes to a lot of callback trouble From e78a02806e2a591c48e7f4ffaf678190a249012f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 10:07:47 +0800 Subject: [PATCH 160/411] Make it safe to use sendSync --- atom/renderer/api/lib/ipc-renderer.coffee | 8 ++++++++ atom/renderer/api/lib/remote.coffee | 6 +----- .../renderer/printing/print_web_view_helper_pdf_win.cc | 2 ++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/atom/renderer/api/lib/ipc-renderer.coffee b/atom/renderer/api/lib/ipc-renderer.coffee index 29004d212b..92be75aa20 100644 --- a/atom/renderer/api/lib/ipc-renderer.coffee +++ b/atom/renderer/api/lib/ipc-renderer.coffee @@ -1,9 +1,17 @@ +{EventEmitter} = require 'events' + binding = process.atomBinding 'ipc' v8Util = process.atomBinding 'v8_util' # Created by init.coffee. ipcRenderer = v8Util.getHiddenValue global, 'ipc' +# Delay the callback to next tick in case the browser is still in the middle +# of sending a message while the callback sends a sync message to browser, +# which can fail sometimes. +ipcRenderer.emit = (args...) -> + setTimeout (-> EventEmitter::emit.call ipcRenderer, args...), 0 + ipcRenderer.send = (args...) -> binding.send 'ipc-message', [args...] diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 357b884069..48cdd937fb 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -119,11 +119,7 @@ metaToPlainObject = (meta) -> # Browser calls a callback in renderer. ipcRenderer.on 'ATOM_RENDERER_CALLBACK', (event, id, args) -> - # Delay the callback to next tick in case the browser is still in the middle - # of sending this message while the callback sends a sync message to browser, - # which can fail sometimes. - setImmediate -> - callbacksRegistry.apply id, metaToValue(args) + callbacksRegistry.apply id, metaToValue(args) # A callback in browser is released. ipcRenderer.on 'ATOM_RENDERER_RELEASE_CALLBACK', (event, id) -> diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc index 5b34ff3090..0b21de4699 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc @@ -135,6 +135,8 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, printed_page_params.page_size = page_size_in_dpi[i]; printed_page_params.content_area = content_area_in_dpi[i]; Send(new PrintHostMsg_DidPrintPage(routing_id(), printed_page_params)); + // Send the rest of the pages with an invalid metafile handle. + printed_page_params.metafile_data_handle = base::SharedMemoryHandle(); } return true; } From c3f7f2447cbaaf60290ed21cb37c2a39dbf4618c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 10:26:32 +0800 Subject: [PATCH 161/411] Update the libspeechd_loader --- chromium_src/library_loaders/libspeechd.h | 1 + chromium_src/library_loaders/libspeechd_loader.cc | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/chromium_src/library_loaders/libspeechd.h b/chromium_src/library_loaders/libspeechd.h index 0d62f2c5da..f7b276287a 100644 --- a/chromium_src/library_loaders/libspeechd.h +++ b/chromium_src/library_loaders/libspeechd.h @@ -33,6 +33,7 @@ class LibSpeechdLoader { decltype(&::spd_set_synthesis_voice) spd_set_synthesis_voice; decltype(&::spd_list_modules) spd_list_modules; decltype(&::spd_set_output_module) spd_set_output_module; + decltype(&::spd_set_language) spd_set_language; private: diff --git a/chromium_src/library_loaders/libspeechd_loader.cc b/chromium_src/library_loaders/libspeechd_loader.cc index 6066610005..f09ea3ae86 100644 --- a/chromium_src/library_loaders/libspeechd_loader.cc +++ b/chromium_src/library_loaders/libspeechd_loader.cc @@ -201,6 +201,19 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { return false; } +#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) + spd_set_language = + reinterpret_castspd_set_language)>( + dlsym(library_, "spd_set_language")); +#endif +#if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) + spd_set_language = &::spd_set_language; +#endif + if (!spd_set_language) { + CleanUp(true); + return false; + } + loaded_ = true; return true; @@ -227,5 +240,6 @@ void LibSpeechdLoader::CleanUp(bool unload) { spd_set_synthesis_voice = NULL; spd_list_modules = NULL; spd_set_output_module = NULL; + spd_set_language = NULL; } From c63a8c944bef19a67a153b80ce98356645921f5f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 10:42:51 +0800 Subject: [PATCH 162/411] Fix release title --- script/upload.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/script/upload.py b/script/upload.py index c021d743a8..3245d9caaa 100755 --- a/script/upload.py +++ b/script/upload.py @@ -174,11 +174,10 @@ def create_or_get_release_draft(github, releases, tag, tag_exists): def create_release_draft(github, tag): + name = '{0} {1}'.format(PROJECT_NAME, tag) if os.environ.has_key('CI'): - name = '{0} pending draft'.format(PROJECT_NAME) body = '(placeholder)' else: - name = '{0} {1}'.format(PROJECT_NAME, tag) body = get_text_with_editor(name) if body == '': sys.stderr.write('Quit due to empty release note.\n') From 4af219089066ca515a18aedfb5603bf9248e73cd Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 11:14:30 +0800 Subject: [PATCH 163/411] Upgrade to Node v5.1.1 --- script/bootstrap.py | 2 +- vendor/node | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/bootstrap.py b/script/bootstrap.py index ef48c1f802..6eaf635bfd 100755 --- a/script/bootstrap.py +++ b/script/bootstrap.py @@ -196,7 +196,7 @@ def create_chrome_version_h(): def touch_config_gypi(): config_gypi = os.path.join(SOURCE_ROOT, 'vendor', 'node', 'config.gypi') with open(config_gypi, 'w+') as f: - content = '\n{}' + content = "\n{'variables':{}}" if f.read() != content: f.write(content) diff --git a/vendor/node b/vendor/node index 97d9298d8a..38d7918434 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 97d9298d8a431f27e2aded918ae9f2a673c9cf6f +Subproject commit 38d791843463b19c623c97c1c550a4e3c5a406d4 From 0f2f9b55430675e7fb9ff9dd915554075b728515 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 11:20:07 +0800 Subject: [PATCH 164/411] No need to use CommandDispatcher --- atom/browser/native_window_mac.mm | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index d7ed15cf07..ef33a119e1 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -18,7 +18,6 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "native_mate/dictionary.h" -#import "ui/base/cocoa/command_dispatcher.h" #include "ui/gfx/skia_util.h" namespace { @@ -209,11 +208,10 @@ bool ScopedDisableResize::disable_resize_ = false; @end -@interface AtomNSWindow : NSWindow { +@interface AtomNSWindow : NSWindow { @private atom::NativeWindowMac* shell_; bool enable_larger_than_screen_; - base::scoped_nsobject commandDispatcher_; } @property BOOL acceptsFirstMouse; @property BOOL disableAutoHideCursor; @@ -227,7 +225,6 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell { shell_ = shell; - commandDispatcher_.reset([[CommandDispatcher alloc] initWithOwner:self]); } - (void)setEnableLargerThanScreen:(bool)enable { @@ -276,25 +273,6 @@ bool ScopedDisableResize::disable_resize_ = false; return !self.disableKeyOrMainWindow; } -// CommandDispatchingWindow implementation. - -- (void)setCommandHandler:(id)commandHandler { -} - -- (BOOL)redispatchKeyEvent:(NSEvent*)event { - return [commandDispatcher_ redispatchKeyEvent:event]; -} - -- (BOOL)defaultPerformKeyEquivalent:(NSEvent*)event { - return [super performKeyEquivalent:event]; -} - -- (void)commandDispatch:(id)sender { -} - -- (void)commandDispatchUsingKeyModifiers:(id)sender { -} - @end @interface ControlRegionView : NSView From da208b5155037578a230affdb81733c72de845ae Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 11:41:47 +0800 Subject: [PATCH 165/411] We are now on 0.36.0 --- atom.gyp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom.gyp b/atom.gyp index 9f1ffe5825..01a2c99e08 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.35.4', + 'version%': '0.36.0', }, 'includes': [ 'filenames.gypi', From 407e88cbad1470ca312b7f7b992d3268e8eb7e41 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 12:06:32 +0800 Subject: [PATCH 166/411] Update brightray --- atom/common/chrome_version.h | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/common/chrome_version.h b/atom/common/chrome_version.h index 2500516837..c92fee1649 100644 --- a/atom/common/chrome_version.h +++ b/atom/common/chrome_version.h @@ -8,7 +8,7 @@ #ifndef ATOM_COMMON_CHROME_VERSION_H_ #define ATOM_COMMON_CHROME_VERSION_H_ -#define CHROME_VERSION_STRING "45.0.2454.85" +#define CHROME_VERSION_STRING "47.0.2526.73" #define CHROME_VERSION "v" CHROME_VERSION_STRING #endif // ATOM_COMMON_CHROME_VERSION_H_ diff --git a/vendor/brightray b/vendor/brightray index 878e63860b..9b4d052d2a 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 878e63860b59d3443cd9f739d7533f2be1109773 +Subproject commit 9b4d052d2af716c340034ed7815d9d758cd7804d From d458b24945c4353c22543b92bdddd01bbc122f21 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 12:44:55 +0800 Subject: [PATCH 167/411] Add desktopCapturer to electron --- atom/renderer/api/lib/exports/electron.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/renderer/api/lib/exports/electron.coffee b/atom/renderer/api/lib/exports/electron.coffee index 5d7f2a57ed..a224818e5f 100644 --- a/atom/renderer/api/lib/exports/electron.coffee +++ b/atom/renderer/api/lib/exports/electron.coffee @@ -3,6 +3,9 @@ module.exports = require '../../../../common/api/lib/exports/electron' Object.defineProperties module.exports, # Renderer side modules, please sort with alphabet order. + desktopCapturer: + enumerable: true + get: -> require '../desktop-capturer' ipcRenderer: enumerable: true get: -> require '../ipc-renderer' From 785bc2986b0752d40bd3b51108e6d1f1d0bc99ca Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 12:47:07 +0800 Subject: [PATCH 168/411] Update brightray and libchromium for desktopCapturer --- script/lib/config.py | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index 159839c7f5..3e7f23536c 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '451ea93cc3090f7000f8f0daa4cb84e90ad6c842' +LIBCHROMIUMCONTENT_COMMIT = '080aeb05f3ef869acc4234dbcc8a815e73047e2b' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index 9b4d052d2a..8c8c3dbade 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 9b4d052d2af716c340034ed7815d9d758cd7804d +Subproject commit 8c8c3dbade86d19de28bc750e7b3f79668a85613 From 51368952a201ab41863ec3ac1c77e63d1b518688 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 12:58:39 +0800 Subject: [PATCH 169/411] Remove deprecated API usages --- atom/browser/lib/desktop-capturer.coffee | 12 +++++------- atom/renderer/api/lib/desktop-capturer.coffee | 12 ++++-------- .../chrome/browser/media/desktop_media_list.h | 1 + .../browser/media/native_desktop_media_list.cc | 1 + 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 29d952595c..8ef3389f5f 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -1,15 +1,13 @@ -ipc = require 'ipc' +{ipcMain} = require 'electron' +{desktopCapturer} = process.atomBinding 'desktop_capturer' -# The browser module manages all desktop-capturer moduels in renderer process. -desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer - -isOptionsEqual = (opt1, opt2) -> +deepEqual = (opt1, opt2) -> return JSON.stringify(opt1) is JSON.stringify(opt2) # A queue for holding all requests from renderer process. requestsQueue = [] -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options, id) -> +ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options, id) -> request = { id: id, options: options, webContents: event.sender } requestsQueue.push request desktopCapturer.startHandling options if requestsQueue.length is 1 @@ -29,7 +27,7 @@ desktopCapturer.emit = (event_name, event, error_message, sources) -> # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue - if isOptionsEqual handledRequest.options, request.options + if deepEqual handledRequest.options, request.options request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", error_message, result else unhandledRequestsQueue.push request diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index 95e67ff999..fc4118f067 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,15 +1,11 @@ -ipc = require 'ipc' -NativeImage = require 'native-image' +{ipcRenderer, NativeImage} = require 'electron' nextId = 0 getNextId = -> ++nextId -getSources = (options, callback) -> +exports.getSources = (options, callback) -> id = getNextId() - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, id - ipc.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (error_message, sources) -> + ipcRenderer.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, id + ipcRenderer.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (error_message, sources) -> error = if error_message then Error error_message else null callback error, ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) - -module.exports = - getSources: getSources diff --git a/chromium_src/chrome/browser/media/desktop_media_list.h b/chromium_src/chrome/browser/media/desktop_media_list.h index fa0dbd8157..44850bd691 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list.h +++ b/chromium_src/chrome/browser/media/desktop_media_list.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ #include "base/basictypes.h" +#include "base/strings/string16.h" #include "base/time/time.h" #include "content/public/browser/desktop_media_id.h" #include "ui/gfx/image/image_skia.h" diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index 4d15b068b7..2464aeca49 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -10,6 +10,7 @@ #include "base/hash.h" #include "base/logging.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/media/desktop_media_list_observer.h" From b517b0c59843d6ab1c66789dcc17f01b7c6673cc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 13:09:36 +0800 Subject: [PATCH 170/411] docs: Improve docs of desktopCapturer --- docs/api/desktop-capturer.md | 53 +++++++++++++++--------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 2d805771ce..e0ec758428 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -1,11 +1,11 @@ -# desktop-capturer +# desktopCapturer -The `desktop-capturer` is a renderer module used to capture the content of -screen and individual app windows. +The `desktopCapturer` module can be used to get available sources that can be +used to be captured with `getUserMedia`. ```javascript // In the renderer process. -var desktopCapturer = require('desktop-capturer'); +var desktopCapturer = require('electron').desktopCapturer; desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) { if (error) throw error; @@ -44,35 +44,26 @@ The `desktopCapturer` module has the following methods: ### `desktopCapturer.getSources(options, callback)` -`options` Object, properties: -* `types` Array - An array of String that enums the types of desktop sources. - * `screen` String - Screen - * `window` String - Individual window -* `thumbnailSize` Object (optional) - The suggested size that thumbnail should - be scaled. - * `width` Integer - The width of thumbnail. By default, it is 150px. - * `height` Integer - The height of thumbnail. By default, it is 150px. +* `options` Object + * `types` Array - An array of String that lists the types of desktop sources + to be captured, available types are `screen` and `window`. + * `thumbnailSize` Object (optional) - The suggested size that thumbnail should + be scaled, it is `{width: 150, height: 150}` by default. +* `callback` Function -`callback` Function - `function(error, sources) {}` - -* `error` Error -* `sources` Array - An array of Source - -Gets all desktop sources. - -**Note:** There is no garuantee that the size of `source.thumbnail` is always -the same as the `thumnbailSize` in `options`. It also depends on the scale of -the screen or window. - -## Source - -`Source` is an object represents a captured screen or individual window. It has -following properties: +Starts a request to get all desktop sources, `callback` will be called with +`callback(error, sources)` when the request is completed. +The `sources` is an array of `Source` objects, each `Source` represents a +captured screen or individual window, and has following properties: * `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. The format looks like 'window:XX' or - 'screen:XX' where XX is a random generated number. -* `name` String - The descriped name of the capturing screen or window. If the - source is a screen, the name will be 'Entire Screen' or 'Screen '; if + `navigator.webkitGetUserMedia`. The format looks like `window:XX` or + `screen:XX` where `XX` is a random generated number. +* `name` String - The described name of the capturing screen or window. If the + source is a screen, the name will be `Entire Screen` or `Screen `; if it is a window, the name will be the window's title. * `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. + +**Note:** There is no guarantee that the size of `source.thumbnail` is always +the same as the `thumnbailSize` in `options`. It also depends on the scale of +the screen or window. From 468dd9e7c8c309788c69908c1dddb0c08a3161a4 Mon Sep 17 00:00:00 2001 From: Austin Burdine Date: Mon, 7 Dec 2015 23:13:45 -0600 Subject: [PATCH 171/411] :memo: fix grammatical error in desktop notification docs [ci skip] --- docs/tutorial/desktop-environment-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 6e570ee71f..868dc449ee 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -25,7 +25,7 @@ myNotification.onclick = function () { } ``` -While code and user experience across operating systems are similar, but there +While code and user experience across operating systems are similar, there are fine differences. ### Windows From f6c9000f5f33601e333b1c0e73f136e3b3de571f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 13:49:11 +0800 Subject: [PATCH 172/411] spec: Add a simple test case for desktopCapturer --- spec/api-desktop-capturer.coffee | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 spec/api-desktop-capturer.coffee diff --git a/spec/api-desktop-capturer.coffee b/spec/api-desktop-capturer.coffee new file mode 100644 index 0000000000..6f1842dd14 --- /dev/null +++ b/spec/api-desktop-capturer.coffee @@ -0,0 +1,9 @@ +assert = require 'assert' +{desktopCapturer} = require 'electron' + +describe 'desktopCapturer', -> + it 'should returns something', (done) -> + desktopCapturer.getSources {types: ['window', 'screen']}, (error, sources) -> + assert.equal error, null + assert.notEqual sources.length, 0 + done() From 836a8b179460a934c5bbb75baa0ef66c83def001 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 13:49:26 +0800 Subject: [PATCH 173/411] Simplify the desktopCapturer code --- atom/browser/api/atom_api_desktop_capturer.cc | 49 +++---------------- atom/browser/api/atom_api_desktop_capturer.h | 12 ++--- atom/browser/lib/desktop-capturer.coffee | 18 +++---- atom/renderer/api/lib/desktop-capturer.coffee | 19 +++++-- .../chrome/browser/media/desktop_media_list.h | 1 + .../media/native_desktop_media_list.cc | 4 ++ .../browser/media/native_desktop_media_list.h | 1 + 7 files changed, 38 insertions(+), 66 deletions(-) diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index 21e98fe47c..ceb69deca4 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -10,7 +10,6 @@ #include "base/strings/utf_string_conversions.h" #include "chrome/browser/media/desktop_media_list.h" #include "native_mate/dictionary.h" -#include "native_mate/handle.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" @@ -38,45 +37,15 @@ namespace atom { namespace api { -namespace { -const int kThumbnailWidth = 150; -const int kThumbnailHeight = 150; - -bool GetCapturerTypes(const mate::Dictionary& args, - bool* show_windows, - bool* show_screens) { - *show_windows = false; - *show_screens = false; - std::vector sources; - if (!args.Get("types", &sources)) - return false; - for (const auto& source_type : sources) { - if (source_type == "screen") - *show_screens = true; - else if (source_type == "window") - *show_windows = true; - } - return !show_windows && !show_screens ? false : true; -} - -} // namespace - DesktopCapturer::DesktopCapturer() { } DesktopCapturer::~DesktopCapturer() { } -void DesktopCapturer::StartHandling(const mate::Dictionary& args) { - bool show_screens = false; - bool show_windows = false; - if (!GetCapturerTypes(args, &show_windows, &show_screens)) { - Emit("handling-finished", - "Invalid options.", - std::vector()); - return; - } - +void DesktopCapturer::StartHandling(bool capture_window, + bool capture_screen, + const gfx::Size& thumbnail_size) { webrtc::DesktopCaptureOptions options = webrtc::DesktopCaptureOptions::CreateDefault(); @@ -91,15 +60,12 @@ void DesktopCapturer::StartHandling(const mate::Dictionary& args) { #endif scoped_ptr screen_capturer( - show_screens ? webrtc::ScreenCapturer::Create(options) : nullptr); + capture_screen ? webrtc::ScreenCapturer::Create(options) : nullptr); scoped_ptr window_capturer( - show_windows ? webrtc::WindowCapturer::Create(options) : nullptr); + capture_window ? webrtc::WindowCapturer::Create(options) : nullptr); media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); - gfx::Size thumbnail_size(kThumbnailWidth, kThumbnailHeight); - args.Get("thumbnailSize", &thumbnail_size); - media_list_->SetThumbnailSize(thumbnail_size); media_list_->StartUpdating(this); } @@ -120,11 +86,8 @@ void DesktopCapturer::OnSourceThumbnailChanged(int index) { } bool DesktopCapturer::OnRefreshFinished() { - std::vector sources; - for (int i = 0; i < media_list_->GetSourceCount(); ++i) - sources.push_back(media_list_->GetSource(i)); + Emit("finished", media_list_->GetSources()); media_list_.reset(); - Emit("handling-finished", "", sources); return false; } diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index 32687abf73..c22c8a4483 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -5,19 +5,11 @@ #ifndef ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ #define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_ -#include -#include - -#include "base/memory/scoped_ptr.h" #include "atom/browser/api/event_emitter.h" #include "chrome/browser/media/desktop_media_list_observer.h" #include "chrome/browser/media/native_desktop_media_list.h" #include "native_mate/handle.h" -namespace mate { -class Dictionary; -} - namespace atom { namespace api { @@ -27,7 +19,9 @@ class DesktopCapturer: public mate::EventEmitter, public: static mate::Handle Create(v8::Isolate* isolate); - void StartHandling(const mate::Dictionary& args); + void StartHandling(bool capture_window, + bool capture_screen, + const gfx::Size& thumbnail_size); protected: DesktopCapturer(); diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 8ef3389f5f..a7fb29ff76 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -7,31 +7,31 @@ deepEqual = (opt1, opt2) -> # A queue for holding all requests from renderer process. requestsQueue = [] -ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options, id) -> - request = { id: id, options: options, webContents: event.sender } +ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, captureWindow, captureScreen, thumbnailSize, id) -> + request = id: id, options: {captureWindow, captureScreen, thumbnailSize}, webContents: event.sender requestsQueue.push request - desktopCapturer.startHandling options if requestsQueue.length is 1 + desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize if requestsQueue.length is 1 # If the WebContents is destroyed before receiving result, just remove the # reference from requestsQueue to make the module not send the result to it. - event.sender.once 'destroyed', () -> + event.sender.once 'destroyed', -> request.webContents = null -desktopCapturer.emit = (event_name, event, error_message, sources) -> +desktopCapturer.emit = (event, name, sources) -> # Receiving sources result from main process, now send them back to renderer. handledRequest = requestsQueue.shift 0 - error = if error_message then Error error_message else null result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) - handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", error_message, result + handledRequest.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", result # Check the queue to see whether there is other same request. If has, handle # it for reducing redunplicated `desktopCaptuer.startHandling` calls. unhandledRequestsQueue = [] for request in requestsQueue if deepEqual handledRequest.options, request.options - request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", error_message, result + request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", errorMessage, result else unhandledRequestsQueue.push request requestsQueue = unhandledRequestsQueue # If the requestsQueue is not empty, start a new request handling. if requestsQueue.length > 0 - desktopCapturer.startHandling requestsQueue[0].options + {captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options + desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index fc4118f067..7c7c898249 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,11 +1,20 @@ -{ipcRenderer, NativeImage} = require 'electron' +{ipcRenderer, nativeImage} = require 'electron' nextId = 0 getNextId = -> ++nextId +# |options.type| can not be empty and has to include 'window' or 'screen'. +isValid = (options) -> + return options?.types? and Array.isArray options.types + exports.getSources = (options, callback) -> + return callback new Error('Invalid options') unless isValid options + + captureWindow = 'window' in options.types + captureScreen = 'screen' in options.types + options.thumbnailSize ?= width: 150, height: 150 + id = getNextId() - ipcRenderer.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, id - ipcRenderer.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (error_message, sources) -> - error = if error_message then Error error_message else null - callback error, ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) + ipcRenderer.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id + ipcRenderer.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (event, sources) -> + callback null, ({id: source.id, name: source.name, thumbnail: nativeImage.createFromDataURL source.thumbnail} for source in sources) diff --git a/chromium_src/chrome/browser/media/desktop_media_list.h b/chromium_src/chrome/browser/media/desktop_media_list.h index 44850bd691..7ef703e8b7 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list.h +++ b/chromium_src/chrome/browser/media/desktop_media_list.h @@ -56,6 +56,7 @@ class DesktopMediaList { virtual int GetSourceCount() const = 0; virtual const Source& GetSource(int index) const = 0; + virtual std::vector GetSources() const = 0; }; #endif // CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index 2464aeca49..4a7fb58962 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -282,6 +282,10 @@ const DesktopMediaList::Source& NativeDesktopMediaList::GetSource( return sources_[index]; } +std::vector NativeDesktopMediaList::GetSources() const { + return sources_; +} + void NativeDesktopMediaList::Refresh() { capture_task_runner_->PostTask( FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()), diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.h b/chromium_src/chrome/browser/media/native_desktop_media_list.h index 81bd321528..943d3dd325 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.h +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.h @@ -36,6 +36,7 @@ class NativeDesktopMediaList : public DesktopMediaList { void StartUpdating(DesktopMediaListObserver* observer) override; int GetSourceCount() const override; const Source& GetSource(int index) const override; + std::vector GetSources() const override; void SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id) override; private: From 9eb56272257d63b0934e0dbf8e1d3fa4c5ed5b33 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 14:21:23 +0800 Subject: [PATCH 174/411] Update clang to the same version with Chrome 47 --- script/update-clang.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/update-clang.sh b/script/update-clang.sh index 5f9b00da7d..801d603971 100755 --- a/script/update-clang.sh +++ b/script/update-clang.sh @@ -8,7 +8,7 @@ # Do NOT CHANGE this if you don't know what you're doing -- see # https://code.google.com/p/chromium/wiki/UpdatingClang # Reverting problematic clang rolls is safe, though. -CLANG_REVISION=245965 +CLANG_REVISION=247874 # This is incremented when pushing a new build of Clang at the same revision. CLANG_SUB_REVISION=1 From 06acc8b20854a4d4440c93a446c2315d2b2a450a Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 8 Dec 2015 15:38:10 +0900 Subject: [PATCH 175/411] Update as upstream --- docs-translations/ko-KR/api/browser-window.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 6ff934afa0..82f2d9cc32 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -122,7 +122,6 @@ win.show(); JavaScript와 CSS 또는 플러그인을 실행시킬 수 있도록 허용합니다. 기본값은 `false`입니다. * `images` Boolean - 이미지 지원을 활성화합니다. 기본값은 `true`입니다. - * `java` Boolean - Java 지원을 활성화합니다. 기본값은 `false`입니다. * `textAreasAreResizable` Boolean - HTML TextArea 요소의 크기를 재조정을 허용합니다. 기본값은 `true`입니다. * `webgl` Boolean - WebGL 지원을 활성화합니다. 기본값은 `true`입니다. @@ -134,8 +133,6 @@ win.show(); 활성화합니다. 기본값은 `false`입니다. * `overlayScrollbars` Boolean - 오버레이 스크롤바를 활성화합니다. 기본값은 `false`입니다. - * `overlayFullscreenVideo` Boolean - 오버레이 전체화면 비디오 기능을 활성화합니다. - 기본값은 `false`입니다. * `sharedWorker` Boolean - SharedWorker 기능을 활성화합니다. 기본값은 `false`입니다. * `directWrite` Boolean - Windows에서 폰트 랜더링을 위해 DirectWrite를 From ea2054c5161521e0f8d132082ef9df817b6de425 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 8 Dec 2015 15:39:01 +0900 Subject: [PATCH 176/411] Revert "Update as upstream" This reverts commit 06acc8b20854a4d4440c93a446c2315d2b2a450a. --- docs-translations/ko-KR/api/browser-window.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 82f2d9cc32..6ff934afa0 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -122,6 +122,7 @@ win.show(); JavaScript와 CSS 또는 플러그인을 실행시킬 수 있도록 허용합니다. 기본값은 `false`입니다. * `images` Boolean - 이미지 지원을 활성화합니다. 기본값은 `true`입니다. + * `java` Boolean - Java 지원을 활성화합니다. 기본값은 `false`입니다. * `textAreasAreResizable` Boolean - HTML TextArea 요소의 크기를 재조정을 허용합니다. 기본값은 `true`입니다. * `webgl` Boolean - WebGL 지원을 활성화합니다. 기본값은 `true`입니다. @@ -133,6 +134,8 @@ win.show(); 활성화합니다. 기본값은 `false`입니다. * `overlayScrollbars` Boolean - 오버레이 스크롤바를 활성화합니다. 기본값은 `false`입니다. + * `overlayFullscreenVideo` Boolean - 오버레이 전체화면 비디오 기능을 활성화합니다. + 기본값은 `false`입니다. * `sharedWorker` Boolean - SharedWorker 기능을 활성화합니다. 기본값은 `false`입니다. * `directWrite` Boolean - Windows에서 폰트 랜더링을 위해 DirectWrite를 From 54b62a2f31ad4bb2c1d7d9049afa9c54585b2dd8 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 8 Dec 2015 15:39:58 +0900 Subject: [PATCH 177/411] Update as upstream [ci skip] --- docs-translations/ko-KR/api/browser-window.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 6ff934afa0..82f2d9cc32 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -122,7 +122,6 @@ win.show(); JavaScript와 CSS 또는 플러그인을 실행시킬 수 있도록 허용합니다. 기본값은 `false`입니다. * `images` Boolean - 이미지 지원을 활성화합니다. 기본값은 `true`입니다. - * `java` Boolean - Java 지원을 활성화합니다. 기본값은 `false`입니다. * `textAreasAreResizable` Boolean - HTML TextArea 요소의 크기를 재조정을 허용합니다. 기본값은 `true`입니다. * `webgl` Boolean - WebGL 지원을 활성화합니다. 기본값은 `true`입니다. @@ -134,8 +133,6 @@ win.show(); 활성화합니다. 기본값은 `false`입니다. * `overlayScrollbars` Boolean - 오버레이 스크롤바를 활성화합니다. 기본값은 `false`입니다. - * `overlayFullscreenVideo` Boolean - 오버레이 전체화면 비디오 기능을 활성화합니다. - 기본값은 `false`입니다. * `sharedWorker` Boolean - SharedWorker 기능을 활성화합니다. 기본값은 `false`입니다. * `directWrite` Boolean - Windows에서 폰트 랜더링을 위해 DirectWrite를 From 98169032fd2ed336fdd9f685fe751eff38ad3b51 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 14:49:13 +0800 Subject: [PATCH 178/411] Update libchromiumcontent and brightray --- script/lib/config.py | 4 ++-- vendor/brightray | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index 3e7f23536c..acb4c2e970 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://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '080aeb05f3ef869acc4234dbcc8a815e73047e2b' + 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' +LIBCHROMIUMCONTENT_COMMIT = 'd4b964338cb1cf81a59cd8aeec432f9e6fded802' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index 8c8c3dbade..476b9fc550 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 8c8c3dbade86d19de28bc750e7b3f79668a85613 +Subproject commit 476b9fc55058a9528f6f016206f45564ee0113d9 From 640b4c3c666e27a866956ea09fd586a4343f2d6f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 16:56:20 +0800 Subject: [PATCH 179/411] Update brightray for various linking problems --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 476b9fc550..c5cac4882b 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 476b9fc55058a9528f6f016206f45564ee0113d9 +Subproject commit c5cac4882b3a5d991ad87fa410ccd754bafe5e53 From 9548a3801b0ebcda1fcee682261e571a60f7a1af Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 17:03:10 +0800 Subject: [PATCH 180/411] Update brightray: Link a few more X libraries --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index c5cac4882b..a2a2f0b3c3 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit c5cac4882b3a5d991ad87fa410ccd754bafe5e53 +Subproject commit a2a2f0b3c325c112bab86682d0e78d64d5988413 From 9daeafbace0acd7fbd02d5a9bb7feed4ffb33069 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 17:32:42 +0800 Subject: [PATCH 181/411] Install libxtst-dev on travis ci --- script/cibuild | 1 + 1 file changed, 1 insertion(+) diff --git a/script/cibuild b/script/cibuild index 08d59385c9..c0798dc7e2 100755 --- a/script/cibuild +++ b/script/cibuild @@ -17,6 +17,7 @@ LINUX_DEPS = [ 'libgtk2.0-dev', 'libnotify-dev', 'libnss3-dev', + 'libxtst-dev', 'gcc-multilib', 'g++-multilib', ] From 505193e239428d9dc790dd0a0de9d4faccff0b6e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 8 Dec 2015 18:15:01 +0800 Subject: [PATCH 182/411] Link with libyuv_neon.a on ARM --- script/lib/config.py | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index acb4c2e970..c1545e0d27 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'd4b964338cb1cf81a59cd8aeec432f9e6fded802' +LIBCHROMIUMCONTENT_COMMIT = 'cfbe8ec7e14af4cabd1474386f54e197db1f7ac1' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index a2a2f0b3c3..814923b77d 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit a2a2f0b3c325c112bab86682d0e78d64d5988413 +Subproject commit 814923b77d21261a9d1780bff0bd1beb55203843 From f77bb449522da886d1cc4ca53887d82d94908b4c Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 8 Dec 2015 23:00:08 +0530 Subject: [PATCH 183/411] fix chrome app and user path conflicts --- chromium_src/chrome/common/chrome_paths.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromium_src/chrome/common/chrome_paths.h b/chromium_src/chrome/common/chrome_paths.h index 581fdc06f7..3a76e3295f 100644 --- a/chromium_src/chrome/common/chrome_paths.h +++ b/chromium_src/chrome/common/chrome_paths.h @@ -17,7 +17,7 @@ class FilePath; namespace chrome { enum { - PATH_START = 1000, + PATH_START = 2000, DIR_APP = PATH_START, // Directory where dlls and data reside. DIR_LOGS, // Directory where logs should be written. From 855d49100f88afe823fea20fda72e33e71fa95ba Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 9 Dec 2015 00:51:46 +0530 Subject: [PATCH 184/411] protocol: api to register schemes that can handle service worker --- atom/app/atom_content_client.cc | 34 +++++++++++++++---- atom/app/atom_content_client.h | 3 ++ atom/browser/api/atom_api_protocol.cc | 7 ++++ atom/browser/api/atom_api_protocol.h | 3 ++ atom/browser/atom_browser_client.cc | 12 +++++++ atom/browser/atom_browser_client.h | 3 ++ atom/browser/net/asar/url_request_asar_job.cc | 18 ++++++++-- atom/browser/net/asar/url_request_asar_job.h | 2 ++ atom/common/options_switches.cc | 3 ++ atom/common/options_switches.h | 1 + atom/renderer/atom_renderer_client.cc | 4 +++ docs/api/protocol.md | 4 +++ 12 files changed, 86 insertions(+), 8 deletions(-) diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 9f161ac569..7a1241f2bc 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -17,6 +17,7 @@ #include "content/public/common/pepper_plugin_info.h" #include "content/public/common/user_agent.h" #include "ppapi/shared_impl/ppapi_permissions.h" +#include "url/url_constants.h" namespace atom { @@ -62,6 +63,17 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, return plugin; } +void ConvertStringWithSeparatorToVector(std::vector* vec, + const char* separator, + const char* cmd_switch) { + auto command_line = base::CommandLine::ForCurrentProcess(); + auto string_with_separator = command_line->GetSwitchValueASCII(cmd_switch); + if (!string_with_separator.empty()) + *vec = base::SplitString(string_with_separator, separator, + base::TRIM_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); +} + } // namespace AtomContentClient::AtomContentClient() { @@ -83,12 +95,10 @@ std::string AtomContentClient::GetUserAgent() const { void AtomContentClient::AddAdditionalSchemes( std::vector* standard_schemes, std::vector* savable_schemes) { - auto command_line = base::CommandLine::ForCurrentProcess(); - auto custom_schemes = command_line->GetSwitchValueASCII( - switches::kRegisterStandardSchemes); - if (!custom_schemes.empty()) { - std::vector schemes = base::SplitString( - custom_schemes, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + std::vector schemes; + ConvertStringWithSeparatorToVector(&schemes, ",", + switches::kRegisterStandardSchemes); + if (!schemes.empty()) { for (const std::string& scheme : schemes) standard_schemes->push_back({scheme.c_str(), url::SCHEME_WITHOUT_PORT}); } @@ -110,4 +120,16 @@ void AtomContentClient::AddPepperPlugins( CreatePepperFlashInfo(flash_path, flash_version)); } +void AtomContentClient::AddServiceWorkerSchemes( + std::set* service_worker_schemes) { + std::vector schemes; + ConvertStringWithSeparatorToVector(&schemes, ",", + switches::kRegisterServiceWorkerSchemes); + if (!schemes.empty()) { + for (const std::string& scheme : schemes) + service_worker_schemes->insert(scheme); + } + service_worker_schemes->insert(url::kFileScheme); +} + } // namespace atom diff --git a/atom/app/atom_content_client.h b/atom/app/atom_content_client.h index 2716b1eea4..76ac37642a 100644 --- a/atom/app/atom_content_client.h +++ b/atom/app/atom_content_client.h @@ -5,6 +5,7 @@ #ifndef ATOM_APP_ATOM_CONTENT_CLIENT_H_ #define ATOM_APP_ATOM_CONTENT_CLIENT_H_ +#include #include #include @@ -26,6 +27,8 @@ class AtomContentClient : public brightray::ContentClient { std::vector* savable_schemes) override; void AddPepperPlugins( std::vector* plugins) override; + void AddServiceWorkerSchemes( + std::set* service_worker_schemes) override; private: DISALLOW_COPY_AND_ASSIGN(AtomContentClient); diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index b1ad798137..09da9c71ca 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -32,6 +32,8 @@ mate::ObjectTemplateBuilder Protocol::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) .SetMethod("registerStandardSchemes", &Protocol::RegisterStandardSchemes) + .SetMethod("registerServiceWorkerSchemes", + &Protocol::RegisterServiceWorkerSchemes) .SetMethod("registerStringProtocol", &Protocol::RegisterProtocol) .SetMethod("registerBufferProtocol", @@ -58,6 +60,11 @@ void Protocol::RegisterStandardSchemes( atom::AtomBrowserClient::SetCustomSchemes(schemes); } +void Protocol::RegisterServiceWorkerSchemes( + const std::vector& schemes) { + atom::AtomBrowserClient::SetCustomServiceWorkerSchemes(schemes); +} + void Protocol::UnregisterProtocol( const std::string& scheme, mate::Arguments* args) { CompletionCallback callback; diff --git a/atom/browser/api/atom_api_protocol.h b/atom/browser/api/atom_api_protocol.h index 9f98eb7673..8aef406fbc 100644 --- a/atom/browser/api/atom_api_protocol.h +++ b/atom/browser/api/atom_api_protocol.h @@ -92,6 +92,9 @@ class Protocol : public mate::Wrappable { // Register schemes to standard scheme list. void RegisterStandardSchemes(const std::vector& schemes); + // Register schemes that can handle service worker. + void RegisterServiceWorkerSchemes(const std::vector& schemes); + // Register the protocol with certain request job. template void RegisterProtocol(const std::string& scheme, diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index b9b186d187..1303b91b04 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -54,6 +54,8 @@ bool g_suppress_renderer_process_restart = false; // Custom schemes to be registered to standard. std::string g_custom_schemes = ""; +// Custom schemes to be registered to handle service worker. +std::string g_custom_service_worker_schemes = ""; scoped_refptr ImportCertFromFile( const base::FilePath& path) { @@ -87,6 +89,11 @@ void AtomBrowserClient::SetCustomSchemes( g_custom_schemes = base::JoinString(schemes, ","); } +void AtomBrowserClient::SetCustomServiceWorkerSchemes( + const std::vector& schemes) { + g_custom_service_worker_schemes = base::JoinString(schemes, ","); +} + AtomBrowserClient::AtomBrowserClient() : delegate_(nullptr) { } @@ -172,6 +179,11 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( command_line->AppendSwitchASCII(switches::kRegisterStandardSchemes, g_custom_schemes); + // The registered service worker schemes. + if (!g_custom_service_worker_schemes.empty()) + command_line->AppendSwitchASCII(switches::kRegisterServiceWorkerSchemes, + g_custom_service_worker_schemes); + #if defined(OS_WIN) // Append --app-user-model-id. PWSTR current_app_id; diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index 75e1749459..3c54fab40b 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -38,6 +38,9 @@ class AtomBrowserClient : public brightray::BrowserClient, static void SuppressRendererProcessRestartForOnce(); // Custom schemes to be registered to standard. static void SetCustomSchemes(const std::vector& schemes); + // Custom schemes to be registered to handle service worker. + static void SetCustomServiceWorkerSchemes( + const std::vector& schemes); protected: // content::ContentBrowserClient: diff --git a/atom/browser/net/asar/url_request_asar_job.cc b/atom/browser/net/asar/url_request_asar_job.cc index 9b9a3c69d2..d926d11117 100644 --- a/atom/browser/net/asar/url_request_asar_job.cc +++ b/atom/browser/net/asar/url_request_asar_job.cc @@ -7,13 +7,14 @@ #include #include +#include "atom/common/asar/archive.h" +#include "atom/common/asar/asar_util.h" +#include "atom/common/atom_constants.h" #include "base/bind.h" #include "base/files/file_util.h" #include "base/strings/string_util.h" #include "base/synchronization/lock.h" #include "base/task_runner.h" -#include "atom/common/asar/archive.h" -#include "atom/common/asar/asar_util.h" #include "net/base/file_stream.h" #include "net/base/filename_util.h" #include "net/base/io_buffer.h" @@ -227,6 +228,19 @@ void URLRequestAsarJob::SetExtraRequestHeaders( } } +int URLRequestAsarJob::GetResponseCode() const { + // Request Job gets created only if path exists. + return 200; +} + +void URLRequestAsarJob::GetResponseInfo(net::HttpResponseInfo* info) { + std::string status("HTTP/1.1 200 OK"); + net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + + headers->AddHeader(atom::kCORSHeader); + info->headers = headers; +} + void URLRequestAsarJob::FetchMetaInfo(const base::FilePath& file_path, FileMetaInfo* meta_info) { base::File::Info file_info; diff --git a/atom/browser/net/asar/url_request_asar_job.h b/atom/browser/net/asar/url_request_asar_job.h index 15a723d79e..29d1afc521 100644 --- a/atom/browser/net/asar/url_request_asar_job.h +++ b/atom/browser/net/asar/url_request_asar_job.h @@ -61,6 +61,8 @@ class URLRequestAsarJob : public net::URLRequestJob { net::Filter* SetupFilter() const override; bool GetMimeType(std::string* mime_type) const override; void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override; + int GetResponseCode() const override; + void GetResponseInfo(net::HttpResponseInfo* info) override; private: // Meta information about the file. It's used as a member in the diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index a0cb8384a3..0303cf5469 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -119,6 +119,9 @@ const char kDisableHttpCache[] = "disable-http-cache"; // Register schemes to standard. const char kRegisterStandardSchemes[] = "register-standard-schemes"; +// Register schemes to handle service worker. +const char kRegisterServiceWorkerSchemes[] = "register-service-worker-schemes"; + // The minimum SSL/TLS version ("tls1", "tls1.1", or "tls1.2") that // TLS fallback will accept. const char kSSLVersionFallbackMin[] = "ssl-version-fallback-min"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 6960db83bc..36c2be1431 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -66,6 +66,7 @@ extern const char kPpapiFlashVersion[]; extern const char kClientCertificate[]; extern const char kDisableHttpCache[]; extern const char kRegisterStandardSchemes[]; +extern const char kRegisterServiceWorkerSchemes[]; extern const char kSSLVersionFallbackMin[]; extern const char kCipherSuiteBlacklist[]; extern const char kAppUserModelId[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 7c04c04249..e0d40189f2 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -27,6 +27,7 @@ #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebPluginParams.h" #include "third_party/WebKit/public/web/WebKit.h" +#include "third_party/WebKit/public/web/WebSecurityPolicy.h" #include "third_party/WebKit/public/web/WebRuntimeFeatures.h" #include "third_party/WebKit/public/web/WebView.h" @@ -129,6 +130,9 @@ void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new PepperHelper(render_frame); new AtomRenderFrameObserver(render_frame, this); + + // Allow file scheme to handle service worker by default. + blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file"); } void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) { diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 5f34165fa8..80597728e6 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -38,6 +38,10 @@ A standard `scheme` adheres to what RFC 3986 calls [generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3). This includes `file:` and `filesystem:`. +### `protocol.registerServiceWorkerSchemes(schemes)` + +* `schemes` Array - Custom schemes to be registered to handle service workers. + ### `protocol.registerFileProtocol(scheme, handler[, completion])` * `scheme` String From 48d07423f9b657bdd7634e94d7d6217486d09370 Mon Sep 17 00:00:00 2001 From: Andriy Tsok Date: Wed, 2 Dec 2015 23:02:04 +0200 Subject: [PATCH 185/411] Ukrainian translation (uk-UA) --- README.md | 1 + docs-translations/uk-UA/README.md | 83 +++++++++++++++++++++++ docs-translations/uk-UA/styleguide.md | 97 +++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 docs-translations/uk-UA/README.md create mode 100644 docs-translations/uk-UA/styleguide.md diff --git a/README.md b/README.md index bb814b2451..edc8ebb6ba 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ contains documents describing how to build and contribute to Electron. - [Spanish](https://github.com/atom/electron/tree/master/docs-translations/es) - [Simplified Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) - [Traditional Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) +- [Ukrainian](https://github.com/atom/electron/tree/master/docs-translations/uk-UA) - [Russian](https://github.com/atom/electron/tree/master/docs-translations/ru-RU) ## Quick Start diff --git a/docs-translations/uk-UA/README.md b/docs-translations/uk-UA/README.md new file mode 100644 index 0000000000..ef503a8844 --- /dev/null +++ b/docs-translations/uk-UA/README.md @@ -0,0 +1,83 @@ +Будь ласка, переконайтесь що ви використовуєте документацію, яка відповідає вашій версії Electron. +Номер версії повинен бути присутнім в URL адресі сторінки. Якщо це не так, Ви можливо, +використовуєте документацію із development гілки, +яка може містити зміни в API, які не сумісні з вашою версією Electron. +Якщо це так, тоді Ви можете переключитись на іншу версію документації +із списку [доступних версій](http://electron.atom.io/docs/) на atom.io, +або якщо ви використовуєте інтеррфейс GitHub, +тоді відкрийте список "Switch branches/tags" і виберіть потрібну вам +версію із списку тегів. + +## Довідник + +* [Підтримувані платформи](tutorial/supported-platforms.md) +* [Application Distribution](tutorial/application-distribution.md) +* [Mac App Store Submission Guide](tutorial/mac-app-store-submission-guide.md) +* [Упаковка додатку](tutorial/application-packaging.md) +* [Використання Native Node модулів](tutorial/using-native-node-modules.md) +* [Debugging Main Process](tutorial/debugging-main-process.md) +* [Використання Selenium and WebDriver](tutorial/using-selenium-and-webdriver.md) +* [DevTools Extension](tutorial/devtools-extension.md) +* [Використання Pepper Flash плагіну](tutorial/using-pepper-flash-plugin.md) + +## Підручники + +* [Швидкий старт](tutorial/quick-start.md) +* [Desktop Environment Integration](tutorial/desktop-environment-integration.md) +* [Online/Offline Event Detection](tutorial/online-offline-events.md) + +## API References + +* [Synopsis](api/synopsis.md) +* [Process Object](api/process.md) +* [Supported Chrome Command Line Switches](api/chrome-command-line-switches.md) +* [Environment Variables](api/environment-variables.md) + +### Користувальницькі елементи DOM + +* [`File` Object](api/file-object.md) +* [`` Tag](api/web-view-tag.md) +* [`window.open` Function](api/window-open.md) + +### Модулі для Main Process: + +* [app](api/app.md) +* [autoUpdater](api/auto-updater.md) +* [BrowserWindow](api/browser-window.md) +* [contentTracing](api/content-tracing.md) +* [dialog](api/dialog.md) +* [globalShortcut](api/global-shortcut.md) +* [ipcMain](api/ipc-main.md) +* [Menu](api/menu.md) +* [MenuItem](api/menu-item.md) +* [powerMonitor](api/power-monitor.md) +* [powerSaveBlocker](api/power-save-blocker.md) +* [protocol](api/protocol.md) +* [session](api/session.md) +* [webContents](api/web-contents.md) +* [Tray](api/tray.md) + +### Модулі для Renderer Process (Web Page): + +* [ipcRenderer](api/ipc-renderer.md) +* [remote](api/remote.md) +* [webFrame](api/web-frame.md) + +### Модулі для Both Processes: + +* [clipboard](api/clipboard.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.md) + +## Розробка + +* [Стиль кодування](development/coding-style.md) +* [Source Code Directory Structure](development/source-code-directory-structure.md) +* [Technical Differences to NW.js (formerly node-webkit)](development/atom-shell-vs-node-webkit.md) +* [Build System Overview](development/build-system-overview.md) +* [Build Instructions (OS X)](development/build-instructions-osx.md) +* [Build Instructions (Windows)](development/build-instructions-windows.md) +* [Build Instructions (Linux)](development/build-instructions-linux.md) +* [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) diff --git a/docs-translations/uk-UA/styleguide.md b/docs-translations/uk-UA/styleguide.md new file mode 100644 index 0000000000..0415967000 --- /dev/null +++ b/docs-translations/uk-UA/styleguide.md @@ -0,0 +1,97 @@ +# Electron Documentation Styleguide + +Find the appropriate section for your task: [reading Electron documentation](#reading-electron-documentation) +or [writing Electron documentation](#writing-electron-documentation). + +## Написання документації для Electron + +Існує кілька способів за допомогою яких ми ствоюємо документацію для Electron. + +- Maximum one `h1` title per page. +- Use `bash` instead of `cmd` in code blocks (because of syntax highlighter). +- Doc `h1` titles should match object name (i.e. `browser-window` → + `BrowserWindow`). + - Hyphen separated filenames, however, are fine. +- No headers following headers, add at least a one-sentence description. +- Methods headers are wrapped in `code` ticks. +- Event headers are wrapped in single 'quotation' marks. +- No nesting lists more than 2 levels (unfortunately because of markdown + renderer). +- Add section titles: Events, Class Methods and Instance Methods. +- Use 'will' over 'would' when describing outcomes. +- Events and methods are `h3` headers. +- Optional arguments written as `function (required[, optional])`. +- Optional arguments are denoted when called out in list. +- Line length is 80-column wrapped. +- Platform specific methods are noted in italics following method header. + - ```### `method(foo, bar)` _OS X_``` +- Prefer 'in the ___ process' over 'on' + +### Переклад документації + +Переклади документації знаходяться в дерикторії `docs-translations`. + +Щоб додати переклад (або частковий переклад) документації: + +- Create a subdirectory named by language abbreviation. +- Within that subdirectory, duplicate the `docs` directory, keeping the + names of directories and files same. +- Translate the files. +- Update the `README.md` within your language directory to link to the files + you have translated. +- Add a link to your translation directory on the main Electron [README](https://github.com/atom/electron#documentation-translations). + +## Читання документації Electron + +Кілька порад для того щоб легше зрозуміти синтаксис документації Electron. + +### Методи + +Приклад [методу](https://developer.mozilla.org/en-US/docs/Glossary/Method) +в документації: + +--- + +`methodName(required[, optional]))` + +* `require` String, **required** +* `optional` Integer + +--- + +The method name is followed by the arguments it takes. Optional arguments are +notated by brackets surrounding the optional argument as well as the comma +required if this optional argument follows another argument. + +Below the method is more detailed information on each of the arguments. The type +of argument is notated by either the common types: +[`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), +[`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), +[`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object), +[`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +or a custom type like Electron's [`webContent`](api/web-content.md). + +### Події + +Приклад [події](https://developer.mozilla.org/en-US/docs/Web/API/Event) +в документації: + +--- + +Подія: 'wake-up' + +Повердає: + +* `time` Текст + +--- + +The event is a string that is used after a `.on` listener method. If it returns +a value it and its type is noted below. If you were to listen and respond to +this event it might look something like this: + +```javascript +Alarm.on('wake-up', function(time) { + console.log(time) +}) +``` From 7bb3bf0f4884998331b22c68b99d927a93945289 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 9 Dec 2015 12:05:47 +0800 Subject: [PATCH 186/411] docs: win.setIgnoreMouseEvents --- docs/api/browser-window.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index db02b6aa14..2fbd1544c3 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -747,3 +747,9 @@ Sets whether the window should be visible on all workspaces. Returns whether the window is visible on all workspaces. **Note:** This API always returns false on Windows. + +### `win.setIgnoreMouseEvents(ignore)` _OS X_ + +* `ignore` Boolean + +Ignore all moused events that happened in the window. From f7b7b3407c6d5b9a3e889ad59572e46e3c2b1b3d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 9 Dec 2015 12:34:10 +0800 Subject: [PATCH 187/411] Update brightray for #3720 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 814923b77d..006fdfba00 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 814923b77d21261a9d1780bff0bd1beb55203843 +Subproject commit 006fdfba003f8350abf02446ff3868889a7b888c From 49434142742458623e88901534321662ddb0aac4 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Wed, 9 Dec 2015 17:16:54 +0900 Subject: [PATCH 188/411] Update as upstream [ci skip] --- docs-translations/ko-KR/README.md | 1 + docs-translations/ko-KR/api/browser-window.md | 6 ++ .../ko-KR/api/desktop-capturer.md | 68 +++++++++++++++++++ docs-translations/ko-KR/api/ipc-main.md | 2 +- docs-translations/ko-KR/api/menu-item.md | 3 +- .../desktop-environment-integration.md | 2 +- 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 docs-translations/ko-KR/api/desktop-capturer.md diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index e7aae9e6d6..38e7745796 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -62,6 +62,7 @@ GitHub 프로젝트내에서만 볼 수 있고 `master` 브랜치의 문서는 ### 랜더러 프로세스에서 사용할 수 있는 모듈 (웹 페이지): +* [desktopCapturer](api/desktop-capturer.md) * [ipcRenderer](api/ipc-renderer.md) * [remote](api/remote.md) * [webFrame](api/web-frame.md) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 82f2d9cc32..91a813f2da 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -737,3 +737,9 @@ Linux 플랫폼에선 Unity 데스크톱 환경만 지원합니다. 그리고 윈도우가 모든 워크스페이스에서 표시될지 여부를 반환합니다. **참고:** 이 API는 Windows에서 언제나 false를 반환합니다. + +### `win.setIgnoreMouseEvents(ignore)` _OS X_ + +* `ignore` Boolean + +윈도우에서 일어나는 모든 마우스 이벤트를 무시합니다. diff --git a/docs-translations/ko-KR/api/desktop-capturer.md b/docs-translations/ko-KR/api/desktop-capturer.md new file mode 100644 index 0000000000..f14b5433ea --- /dev/null +++ b/docs-translations/ko-KR/api/desktop-capturer.md @@ -0,0 +1,68 @@ +# desktopCapturer + +`desktopCapturer` 모듈은 `getUserMedia`에서 사용 가능한 소스를 가져올 때 사용할 수 +있습니다. + +```javascript +// 렌더러 프로세스 내부 +var desktopCapturer = require('electron').desktopCapturer; + +desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) { + if (error) throw error; + for (var i = 0; i < sources.length; ++i) { + if (sources[i].name == "Electron") { + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sources[i].id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } + } + }, gotStream, getUserMediaError); + return; + } + } +}); + +function gotStream(stream) { + document.querySelector('video').src = URL.createObjectURL(stream); +} + +function getUserMediaError(e) { + console.log('getUserMediaError'); +} +``` + +## Methods + +`desktopCapturer` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `desktopCapturer.getSources(options, callback)` + +* `options` Object + * `types` Array - 캡쳐될 데스크탑 소스의 타입에 대한 리스트를 배열로 지정합니다. + 사용 가능한 타입은 `screen`과 `window`입니다. + * `thumbnailSize` Object (optional) - 섬네일 크기를 조정할 때 최대한 맞춰질 크기, + 기본값은 `{width: 150, height: 150}`입니다. +* `callback` Function + +모든 데스크탑 소스를 요청합니다. 요청의 처리가 완료되면 `callback`은 +`callback(error, sources)` 형식으로 호출됩니다. + +`sources`는 `Source` 객체의 배열입니다. 각 `Source`는 캡쳐된 화면과 독립적인 +윈도우를 표현합니다. 그리고 다음과 같은 속성을 가지고 있습니다: +* `id` String - `navigator.webkitGetUserMedia` API에서 사용할 수 있는 캡쳐된 윈도우 + 또는 화면의 id입니다. 포맷은 `window:XX` 또는 `screen:XX`로 표현되며 `XX` 는 + 무작위로 생성된 숫자입니다. +* `name` String - 캡쳐된 화면과 윈도우에 대해 묘사된 이름입니다. 만약 소스가 + 화면이라면, `Entire Screen` 또는 `Screen `가 될 것이고 소스가 윈도우라면, + 해당 윈도우의 제목이 반환됩니다. +* `thumbnail` [NativeImage](NativeImage.md) - 섬네일 이미지. + +**참고:** `source.thumbnail`의 크기는 언제나 `options`의 `thumnbailSize`와 같다고 +보장할 수 없습니다. 섬네일의 크기는 화면과 윈도우의 크기에 의존하여 조정됩니다. diff --git a/docs-translations/ko-KR/api/ipc-main.md b/docs-translations/ko-KR/api/ipc-main.md index 6eb0d55275..9df7376200 100644 --- a/docs-translations/ko-KR/api/ipc-main.md +++ b/docs-translations/ko-KR/api/ipc-main.md @@ -66,4 +66,4 @@ ipcRenderer.send('asynchronous-message', 'ping'); 비동기로 메시지를 전달할 수 있습니다. 자세한 내용은 [webContents.send][webcontents-send]를 참고하세요. -[webcontents-send]: web-contents.md#webcontentssendchannel-args +[webcontents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- diff --git a/docs-translations/ko-KR/api/menu-item.md b/docs-translations/ko-KR/api/menu-item.md index 388df1c7d5..0e723fc657 100644 --- a/docs-translations/ko-KR/api/menu-item.md +++ b/docs-translations/ko-KR/api/menu-item.md @@ -17,7 +17,8 @@ * `role` String - 메뉴 아이템의 액션을 정의합니다. 이 속성을 지정하면 `click` 속성이 무시됩니다. * `type` String - `MenuItem`의 타입 `normal`, `separator`, `submenu`, - `checkbox` 또는 `radio` 사용가능 + `checkbox` 또는 `radio`를 사용할 수 있습니다. 만약 값이 `Menu`가 아니면 + `Menu.buildFromTemplate`를 통해 자동으로 변환됩니다. * `label` String * `sublabel` String * `accelerator` [Accelerator](accelerator.md) diff --git a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md index 1dc5f66137..42418e71a9 100644 --- a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md +++ b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md @@ -24,7 +24,7 @@ myNotification.onclick = function () { } ``` -위 코드를 통해 생성한 데스크톱 알림은 각 운영체제 모두 비슷한 사용자 경험을 제공합니다. +위 코드를 통해 생성한 데스크톱 알림은 각 운영체제 모두 비슷한 사용자 경험을 제공하지만, 하지만 몇 가지 다른 점들이 있습니다. ### Windows From d2e63dfc644e323bf23fbd6654f7493ed94d7991 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 9 Dec 2015 18:14:37 +0800 Subject: [PATCH 189/411] Use HTTPS for libchromiumcontent's URL Without using the subdomain we should be able to work around the domain license problem of python. --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index c1545e0d27..132f5b8d83 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -7,7 +7,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ - 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent' + 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' LIBCHROMIUMCONTENT_COMMIT = 'cfbe8ec7e14af4cabd1474386f54e197db1f7ac1' PLATFORM = { From 940289639eeac616c2bfd817b1fd95f2642676c3 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 4 Dec 2015 20:48:19 -0500 Subject: [PATCH 190/411] protocol: provide upload data when available --- .../native_mate_converters/net_converter.cc | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 4749a4fedf..6089d715e3 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -5,9 +5,14 @@ #include "atom/common/native_mate_converters/net_converter.h" #include +#include #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" +#include "net/base/upload_bytes_element_reader.h" +#include "net/base/upload_data_stream.h" +#include "net/base/upload_element_reader.h" +#include "net/base/upload_file_element_reader.h" #include "net/cert/x509_certificate.h" #include "net/url_request/url_request.h" @@ -20,6 +25,30 @@ v8::Local Converter::ToV8( dict.Set("method", val->method()); dict.Set("url", val->url().spec()); dict.Set("referrer", val->referrer()); + const net::UploadDataStream* upload_data = val->get_upload(); + if (upload_data) { + const ScopedVector* readers = + upload_data->GetElementReaders(); + std::vector upload_data_list; + upload_data_list.reserve(readers->size()); + for (const auto& reader : *readers) { + auto upload_data_dict = mate::Dictionary::CreateEmpty(isolate); + if (reader->AsBytesReader()) { + const net::UploadBytesElementReader* bytes_reader = + reader->AsBytesReader(); + auto bytes = + node::Buffer::Copy(isolate, bytes_reader->bytes(), + bytes_reader->length()).ToLocalChecked(); + upload_data_dict.Set("bytes", bytes); + } else if (reader->AsFileReader()) { + const net::UploadFileElementReader* file_reader = + reader->AsFileReader(); + upload_data_dict.Set("file", file_reader->path().AsUTF8Unsafe()); + } + upload_data_list.push_back(upload_data_dict); + } + dict.Set("uploadData", upload_data_list); + } return mate::ConvertToV8(isolate, dict); } From fbb5091f948279b9a0f19bf9328b5520fabccf92 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Sun, 6 Dec 2015 15:27:02 -0500 Subject: [PATCH 191/411] provide option to set content for POST request with url_fetcher --- atom/browser/net/url_request_fetch_job.cc | 10 ++++++++++ docs/api/protocol.md | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index f04ecd4060..8ffb6df012 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -90,12 +90,14 @@ void URLRequestFetchJob::StartAsync(scoped_ptr options) { std::string url, method, referrer; base::Value* session = nullptr; + base::DictionaryValue* upload_data = nullptr; base::DictionaryValue* dict = static_cast(options.get()); dict->GetString("url", &url); dict->GetString("method", &method); dict->GetString("referrer", &referrer); dict->Get("session", &session); + dict->GetDictionary("uploadData", &upload_data); // Check if URL is valid. GURL formated_url(url); @@ -127,6 +129,14 @@ void URLRequestFetchJob::StartAsync(scoped_ptr options) { else fetcher_->SetReferrer(referrer); + // Set the data needed for POSTs. + if (upload_data && request_type == net::URLFetcher::POST) { + std::string content_type, data; + upload_data->GetString("contentType", &content_type); + upload_data->GetString("data", &data); + fetcher_->SetUploadData(content_type, data); + } + // Use |request|'s headers. fetcher_->SetExtraRequestHeaders( request()->extra_request_headers().ToString()); diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 5f34165fa8..f76d006a3b 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -103,11 +103,16 @@ Registers a protocol of `scheme` that will send a `String` as a response. The Registers a protocol of `scheme` that will send an HTTP request as a response. The `callback` should be called with an object that has the `url`, `method`, -`referrer`, and `session` properties. +`referrer`, `uploadData` and `session` properties. By default the HTTP request will reuse the current session. If you want the request to have a different session you should set `session` to `null`. +POST request should provide an `uploadData` object. +* `uploadData` object + * `contentType` String - MIME type of the content. + * `data` String - Content to be sent. + ### `protocol.unregisterProtocol(scheme[, completion])` * `scheme` String From e80a95dc3768b537f4d3a9b8d977effd14197403 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 9 Dec 2015 20:10:34 +0530 Subject: [PATCH 192/411] add test --- spec/api-protocol-spec.coffee | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/spec/api-protocol-spec.coffee b/spec/api-protocol-spec.coffee index 034b976592..77eb90259b 100644 --- a/spec/api-protocol-spec.coffee +++ b/spec/api-protocol-spec.coffee @@ -1,6 +1,7 @@ assert = require 'assert' http = require 'http' path = require 'path' +qs = require 'querystring' {remote} = require 'electron' {protocol} = remote.require 'electron' @@ -8,6 +9,9 @@ path = require 'path' describe 'protocol module', -> protocolName = 'sp' text = 'valar morghulis' + postData = + name: 'post test' + type: 'string' afterEach (done) -> protocol.unregisterProtocol protocolName, -> @@ -405,6 +409,22 @@ describe 'protocol module', -> error: (xhr, errorType, error) -> done(error) + it 'can receive post data', (done) -> + handler = (request, callback) -> + uploadData = request.uploadData[0].bytes.toString() + callback({data: uploadData}) + protocol.interceptStringProtocol 'http', handler, (error) -> + return done(error) if error + $.ajax + url: "http://fake-host" + type: "POST" + data: postData + success: (data) -> + assert.deepEqual qs.parse(data), postData + done() + error: (xhr, errorType, error) -> + done(error) + describe 'protocol.interceptBufferProtocol', -> it 'can intercept http protocol', (done) -> handler = (request, callback) -> callback(new Buffer(text)) @@ -418,6 +438,55 @@ describe 'protocol module', -> error: (xhr, errorType, error) -> done(error) + it 'can receive post data', (done) -> + handler = (request, callback) -> + uploadData = request.uploadData[0].bytes + callback(uploadData) + protocol.interceptBufferProtocol 'http', handler, (error) -> + return done(error) if error + $.ajax + url: "http://fake-host" + type: "POST" + data: postData + success: (data) -> + assert.equal data, $.param postData + done() + error: (xhr, errorType, error) -> + done(error) + + describe 'protocol.interceptHttpProtocol', -> + it 'can send POST request', (done) -> + server = http.createServer (req, res) -> + body = '' + req.on 'data', (chunk) -> + body += chunk + req.on 'end', -> + res.end body + server.close() + server.listen 0, '127.0.0.1', -> + {port} = server.address() + url = "http://127.0.0.1:#{port}" + handler = (request, callback) -> + data = + url: url + method: 'POST' + uploadData: + contentType: 'application/x-www-form-urlencoded' + data: request.uploadData[0].bytes.toString() + session: null + callback(data) + protocol.interceptHttpProtocol 'http', handler, (error) -> + return done(error) if error + $.ajax + url: "http://fake-host" + type: "POST" + data: postData + success: (data) -> + assert.deepEqual qs.parse(data), postData + done() + error: (xhr, errorType, error) -> + done(error) + describe 'protocol.uninterceptProtocol', -> it 'returns error when scheme does not exist', (done) -> protocol.uninterceptProtocol 'not-exist', (error) -> From 13b5cab7380c4113e6674ae1fb986cb341594b4b Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 1 Dec 2015 10:22:22 +0530 Subject: [PATCH 193/411] session: add webrequest api --- atom/browser/api/atom_api_session.cc | 12 +- atom/browser/api/atom_api_session.h | 4 +- atom/browser/api/atom_api_web_request.cc | 116 +++++++++ atom/browser/api/atom_api_web_request.h | 51 ++++ atom/browser/atom_browser_context.cc | 6 + atom/browser/atom_browser_context.h | 5 + atom/browser/net/atom_network_delegate.cc | 220 ++++++++++++++++++ atom/browser/net/atom_network_delegate.h | 89 +++++++ .../native_mate_converters/net_converter.cc | 37 +++ .../native_mate_converters/net_converter.h | 8 + .../native_mate_converters/value_converter.cc | 7 + .../native_mate_converters/value_converter.h | 6 + filenames.gypi | 4 + 13 files changed, 563 insertions(+), 2 deletions(-) create mode 100644 atom/browser/api/atom_api_web_request.cc create mode 100644 atom/browser/api/atom_api_web_request.h create mode 100644 atom/browser/net/atom_network_delegate.cc create mode 100644 atom/browser/net/atom_network_delegate.h diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 9cec7378b8..8b73d61622 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -10,6 +10,7 @@ #include "atom/browser/api/atom_api_cookies.h" #include "atom/browser/api/atom_api_download_item.h" #include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/api/atom_api_web_request.h" #include "atom/browser/api/save_page_handler.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" @@ -368,6 +369,14 @@ v8::Local Session::Cookies(v8::Isolate* isolate) { return v8::Local::New(isolate, cookies_); } +v8::Local Session::WebRequest(v8::Isolate* isolate) { + if (web_request_.IsEmpty()) { + auto handle = atom::api::WebRequest::Create(isolate, browser_context()); + web_request_.Reset(isolate, handle.ToV8()); + } + return v8::Local::New(isolate, web_request_); +} + // static mate::Handle Session::CreateFrom( v8::Isolate* isolate, AtomBrowserContext* browser_context) { @@ -401,7 +410,8 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) - .SetProperty("cookies", &Session::Cookies); + .SetProperty("cookies", &Session::Cookies) + .SetProperty("webRequest", &Session::WebRequest); } void ClearWrapSession() { diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index e800a992d4..0034b14806 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -70,9 +70,11 @@ class Session: public mate::TrackableObject, void DisableNetworkEmulation(); void SetCertVerifyProc(v8::Local proc, mate::Arguments* args); v8::Local Cookies(v8::Isolate* isolate); + v8::Local WebRequest(v8::Isolate* isolate); - // Cached object for cookies API. + // Cached object. v8::Global cookies_; + v8::Global web_request_; scoped_refptr browser_context_; diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc new file mode 100644 index 0000000000..ad772e4c29 --- /dev/null +++ b/atom/browser/api/atom_api_web_request.cc @@ -0,0 +1,116 @@ +// 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/atom_api_web_request.h" + +#include "atom/browser/atom_browser_context.h" +#include "atom/browser/net/atom_network_delegate.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/net_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "content/public/browser/browser_thread.h" +#include "native_mate/dictionary.h" +#include "native_mate/object_template_builder.h" + +using content::BrowserThread; + +namespace atom { + +namespace api { + +WebRequest::WebRequest(AtomBrowserContext* browser_context) + : browser_context_(browser_context) { +} + +WebRequest::~WebRequest() { +} + +template +void WebRequest::SetListener(mate::Arguments* args) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + base::DictionaryValue* filter = new base::DictionaryValue(); + args->GetNext(filter); + AtomNetworkDelegate::Listener callback; + if (!args->GetNext(&callback)) { + args->ThrowError("Must pass null or a function"); + return; + } + + auto delegate = browser_context_->network_delegate(); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&AtomNetworkDelegate::SetListenerInIO, + base::Unretained(delegate), + type, filter, callback)); +} + +mate::ObjectTemplateBuilder WebRequest::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return mate::ObjectTemplateBuilder(isolate) + .SetMethod("onBeforeRequest", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnBeforeRequest>) + .SetMethod("onBeforeSendHeaders", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnBeforeSendHeaders>) + .SetMethod("onSendHeaders", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnSendHeaders>) + .SetMethod("onHeadersReceived", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnHeadersReceived>) + .SetMethod("onBeforeRedirect", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnBeforeRedirect>) + .SetMethod("onResponseStarted", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnResponseStarted>) + .SetMethod("onCompleted", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnCompleted>) + .SetMethod("onErrorOccurred", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnErrorOccurred>); +} + +// static +mate::Handle WebRequest::Create( + v8::Isolate* isolate, + AtomBrowserContext* browser_context) { + return mate::CreateHandle(isolate, new WebRequest(browser_context)); +} + +// static +void WebRequest::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("onBeforeRequest", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnBeforeRequest>) + .SetMethod("onBeforeSendHeaders", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnBeforeSendHeaders>) + .SetMethod("onSendHeaders", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnSendHeaders>) + .SetMethod("onHeadersReceived", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnHeadersReceived>) + .SetMethod("onBeforeRedirect", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnBeforeRedirect>) + .SetMethod("onResponseStarted", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnResponseStarted>) + .SetMethod("onCompleted", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnCompleted>) + .SetMethod("onErrorOccurred", + &WebRequest::SetListener< + AtomNetworkDelegate::kOnErrorOccurred>); +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/atom_api_web_request.h b/atom/browser/api/atom_api_web_request.h new file mode 100644 index 0000000000..04fb758a9a --- /dev/null +++ b/atom/browser/api/atom_api_web_request.h @@ -0,0 +1,51 @@ +// 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_ATOM_API_WEB_REQUEST_H_ +#define ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ + +#include + +#include "atom/browser/net/atom_network_delegate.h" +#include "base/callback.h" +#include "native_mate/arguments.h" +#include "native_mate/wrappable.h" + +namespace atom { + +class AtomBrowserContext; + +namespace api { + +class WebRequest : public mate::TrackableObject { + public: + static mate::Handle Create(v8::Isolate* isolate, + AtomBrowserContext* browser_context); + + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + explicit WebRequest(AtomBrowserContext* browser_context); + ~WebRequest(); + + template + void SetListener(mate::Arguments* args); + + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + + private: + scoped_refptr browser_context_; + + DISALLOW_COPY_AND_ASSIGN(WebRequest); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index ec12382582..12e0125752 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -8,6 +8,7 @@ #include "atom/browser/atom_download_manager_delegate.h" #include "atom/browser/browser.h" #include "atom/browser/net/atom_cert_verifier.h" +#include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/net/atom_ssl_config_service.h" #include "atom/browser/net/atom_url_request_job_factory.h" #include "atom/browser/net/asar/asar_protocol_handler.h" @@ -63,12 +64,17 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition, : brightray::BrowserContext(partition, in_memory), cert_verifier_(nullptr), job_factory_(new AtomURLRequestJobFactory), + network_delegate_(new AtomNetworkDelegate), allow_ntlm_everywhere_(false) { } AtomBrowserContext::~AtomBrowserContext() { } +net::NetworkDelegate* AtomBrowserContext::CreateNetworkDelegate() { + return network_delegate_; +} + std::string AtomBrowserContext::GetUserAgent() { Browser* browser = Browser::Get(); std::string name = RemoveWhitespace(browser->GetName()); diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 564c9955d9..9c94a60c30 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -13,6 +13,7 @@ namespace atom { class AtomDownloadManagerDelegate; class AtomCertVerifier; +class AtomNetworkDelegate; class AtomURLRequestJobFactory; class WebViewManager; @@ -22,6 +23,7 @@ class AtomBrowserContext : public brightray::BrowserContext { ~AtomBrowserContext() override; // brightray::URLRequestContextGetter::Delegate: + net::NetworkDelegate* CreateNetworkDelegate() override; std::string GetUserAgent() override; scoped_ptr CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, @@ -45,6 +47,8 @@ class AtomBrowserContext : public brightray::BrowserContext { AtomURLRequestJobFactory* job_factory() const { return job_factory_; } + AtomNetworkDelegate* network_delegate() const { return network_delegate_; } + private: scoped_ptr download_manager_delegate_; scoped_ptr guest_manager_; @@ -52,6 +56,7 @@ class AtomBrowserContext : public brightray::BrowserContext { // Managed by brightray::BrowserContext. AtomCertVerifier* cert_verifier_; AtomURLRequestJobFactory* job_factory_; + AtomNetworkDelegate* network_delegate_; bool allow_ntlm_everywhere_; diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc new file mode 100644 index 0000000000..3b3c8f9719 --- /dev/null +++ b/atom/browser/net/atom_network_delegate.cc @@ -0,0 +1,220 @@ +// 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/net/atom_network_delegate.h" + +#include + +#include "atom/common/native_mate_converters/net_converter.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/resource_request_info.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request.h" + +using content::BrowserThread; + +namespace atom { + +namespace { + +base::DictionaryValue* ExtractRequestInfo(net::URLRequest* request) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("id", request->identifier()); + dict->SetString("url", request->url().spec()); + dict->SetString("method", request->method()); + content::ResourceType resourceType = content::RESOURCE_TYPE_LAST_TYPE; + auto info = content::ResourceRequestInfo::ForRequest(request); + if (info) + resourceType = info->GetResourceType(); + dict->SetInteger("resourceType", resourceType); + dict->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); + + return dict; +} + +base::DictionaryValue* GetRequestHeadersDict( + const net::HttpRequestHeaders& headers) { + base::DictionaryValue* header_dict = new base::DictionaryValue(); + net::HttpRequestHeaders::Iterator it(headers); + while (it.GetNext()) + header_dict->SetString(it.name(), it.value()); + return header_dict; +} + +base::DictionaryValue* GetResponseHeadersDict( + const net::HttpResponseHeaders* headers) { + base::DictionaryValue* header_dict = new base::DictionaryValue(); + if (headers) { + void* iter = nullptr; + std::string key; + std::string value; + while (headers->EnumerateHeaderLines(&iter, &key, &value)) + header_dict->SetString(key, value); + } + return header_dict; +} + +void OnBeforeURLRequestResponse( + const net::CompletionCallback& callback, + GURL* new_url, + const AtomNetworkDelegate::BlockingResponse& result) { + if (!result.redirectURL.is_empty()) + *new_url = result.redirectURL; + callback.Run(result.cancel); +} + +void OnBeforeSendHeadersResponse( + const net::CompletionCallback& callback, + net::HttpRequestHeaders* headers, + const AtomNetworkDelegate::BlockingResponse& result) { + if (!result.requestHeaders.IsEmpty()) + *headers = result.requestHeaders; + callback.Run(result.cancel); +} + +void OnHeadersReceivedResponse( + const net::CompletionCallback& callback, + scoped_refptr* override_response_headers, + const AtomNetworkDelegate::BlockingResponse& result) { + if (result.responseHeaders.get()) + *override_response_headers = result.responseHeaders; + callback.Run(result.cancel); +} + +} // namespace + +// static +std::map +AtomNetworkDelegate::event_listener_map_; + +AtomNetworkDelegate::AtomNetworkDelegate() { +} + +AtomNetworkDelegate::~AtomNetworkDelegate() { +} + +void AtomNetworkDelegate::SetListenerInIO( + EventTypes type, + const base::DictionaryValue* filter, + const Listener& callback) { + ListenerInfo info; + info.callback = callback; + event_listener_map_[type] = info; +} + +int AtomNetworkDelegate::OnBeforeURLRequest( + net::URLRequest* request, + const net::CompletionCallback& callback, + GURL* new_url) { + brightray::NetworkDelegate::OnBeforeURLRequest(request, callback, new_url); + + auto listener_info = event_listener_map_[kOnBeforeRequest]; + if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + auto wrapped_callback = listener_info.callback; + auto details = ExtractRequestInfo(request); + + BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, + base::Bind(wrapped_callback, details), + base::Bind(&OnBeforeURLRequestResponse, + callback, new_url)); + + return net::ERR_IO_PENDING; + } + + return net::OK; +} + +int AtomNetworkDelegate::OnBeforeSendHeaders( + net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpRequestHeaders* headers) { + auto listener_info = event_listener_map_[kOnBeforeSendHeaders]; + if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + auto wrapped_callback = listener_info.callback; + auto details = ExtractRequestInfo(request); + details->Set("requestHeaders", GetRequestHeadersDict(*headers)); + + BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, + base::Bind(wrapped_callback, details), + base::Bind(&OnBeforeSendHeadersResponse, + callback, headers)); + + return net::ERR_IO_PENDING; + } + + return net::OK; +} + +void AtomNetworkDelegate::OnSendHeaders( + net::URLRequest* request, + const net::HttpRequestHeaders& headers) { + auto listener_info = event_listener_map_[kOnSendHeaders]; + if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + auto wrapped_callback = listener_info.callback; + auto details = ExtractRequestInfo(request); + details->Set("requestHeaders", GetRequestHeadersDict(headers)); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(wrapped_callback), + details)); + } +} + +int AtomNetworkDelegate::OnHeadersReceived( + net::URLRequest* request, + const net::CompletionCallback& callback, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr* override_response_headers, + GURL* allowed_unsafe_redirect_url) { + auto listener_info = event_listener_map_[kOnHeadersReceived]; + if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + auto wrapped_callback = listener_info.callback; + auto details = ExtractRequestInfo(request); + details->Set("responseHeaders", + GetResponseHeadersDict(original_response_headers)); + + BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, + base::Bind(wrapped_callback, details), + base::Bind(&OnHeadersReceivedResponse, + callback, + override_response_headers)); + + return net::ERR_IO_PENDING; + } + + return net::OK; +} + +void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, + const GURL& new_location) { + auto listener_info = event_listener_map_[kOnBeforeRedirect]; + if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + auto wrapped_callback = listener_info.callback; + auto details = ExtractRequestInfo(request); + details->SetString("redirectURL", new_location.spec()); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(wrapped_callback), + details)); + } +} + +void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { + auto listener_info = event_listener_map_[kOnResponseStarted]; + if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + auto wrapped_callback = listener_info.callback; + auto details = ExtractRequestInfo(request); + details->Set("responseHeaders", + GetResponseHeadersDict(request->response_headers())); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(wrapped_callback), + details)); + } +} + +void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { +} + +} // namespace atom diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h new file mode 100644 index 0000000000..e1cfdb2100 --- /dev/null +++ b/atom/browser/net/atom_network_delegate.h @@ -0,0 +1,89 @@ +// 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_NET_ATOM_NETWORK_DELEGATE_H_ +#define ATOM_BROWSER_NET_ATOM_NETWORK_DELEGATE_H_ + +#include +#include + +#include "brightray/browser/network_delegate.h" +#include "base/callback.h" +#include "base/values.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" + +namespace atom { + +class AtomNetworkDelegate : public brightray::NetworkDelegate { + public: + enum EventTypes { + kInvalidEvent = 0, + kOnBeforeRequest = 1 << 0, + kOnBeforeSendHeaders = 1 << 1, + kOnSendHeaders = 1 << 2, + kOnHeadersReceived = 1 << 3, + kOnBeforeRedirect = 1 << 4, + kOnResponseStarted = 1 << 5, + kOnErrorOccurred = 1 << 6, + kOnCompleted = 1 << 7, + }; + + struct BlockingResponse { + BlockingResponse() {} + ~BlockingResponse() {} + + bool cancel; + GURL redirectURL; + net::HttpRequestHeaders requestHeaders; + scoped_refptr responseHeaders; + }; + + using Listener = + base::Callback; + + AtomNetworkDelegate(); + ~AtomNetworkDelegate() override; + + void SetListenerInIO(EventTypes type, + const base::DictionaryValue* filter, + const Listener& callback); + + protected: + // net::NetworkDelegate: + int OnBeforeURLRequest(net::URLRequest* request, + const net::CompletionCallback& callback, + GURL* new_url) override; + int OnBeforeSendHeaders(net::URLRequest* request, + const net::CompletionCallback& callback, + net::HttpRequestHeaders* headers) override; + void OnSendHeaders(net::URLRequest* request, + const net::HttpRequestHeaders& headers) override; + int OnHeadersReceived( + net::URLRequest* request, + const net::CompletionCallback& callback, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr* override_response_headers, + GURL* allowed_unsafe_redirect_url) override; + void OnBeforeRedirect(net::URLRequest* request, + const GURL& new_location) override; + void OnResponseStarted(net::URLRequest* request) override; + void OnCompleted(net::URLRequest* request, bool started) override; + + private: + struct ListenerInfo { + ListenerInfo() {} + ~ListenerInfo() {} + + AtomNetworkDelegate::Listener callback; + }; + + static std::map event_listener_map_; + + DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_ATOM_NETWORK_DELEGATE_H_ diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 4749a4fedf..2086e84d58 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -7,8 +7,11 @@ #include #include "atom/common/node_includes.h" +#include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" #include "native_mate/dictionary.h" #include "net/cert/x509_certificate.h" +#include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" namespace mate { @@ -50,4 +53,38 @@ v8::Local Converter>::ToV8( return dict.GetHandle(); } +// static +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, + atom::AtomNetworkDelegate::BlockingResponse* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + if (!dict.Get("cancel", &(out->cancel))) + return false; + dict.Get("redirectURL", &(out->redirectURL)); + base::DictionaryValue request_headers; + if (dict.Get("requestHeaders", &request_headers)) { + for (base::DictionaryValue::Iterator it(request_headers); + !it.IsAtEnd(); + it.Advance()) { + std::string value; + CHECK(it.value().GetAsString(&value)); + out->requestHeaders.SetHeader(it.key(), value); + } + } + base::DictionaryValue response_headers; + if (dict.Get("responseHeaders", &response_headers)) { + out->responseHeaders = new net::HttpResponseHeaders(""); + for (base::DictionaryValue::Iterator it(response_headers); + !it.IsAtEnd(); + it.Advance()) { + std::string value; + CHECK(it.value().GetAsString(&value)); + out->responseHeaders->AddHeader(it.key() + " : " + value); + } + } + return true; +} + } // namespace mate diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index b11c55929b..f251da8c5c 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -5,6 +5,7 @@ #ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ +#include "atom/browser/net/atom_network_delegate.h" #include "base/memory/ref_counted.h" #include "native_mate/converter.h" @@ -34,6 +35,13 @@ struct Converter> { const scoped_refptr& val); }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + atom::AtomNetworkDelegate::BlockingResponse* out); +}; + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ diff --git a/atom/common/native_mate_converters/value_converter.cc b/atom/common/native_mate_converters/value_converter.cc index c9c1a861ba..431f11fbbd 100644 --- a/atom/common/native_mate_converters/value_converter.cc +++ b/atom/common/native_mate_converters/value_converter.cc @@ -30,6 +30,13 @@ v8::Local Converter::ToV8( return converter->ToV8Value(&val, isolate->GetCurrentContext()); } +v8::Local Converter::ToV8( + v8::Isolate* isolate, + const base::DictionaryValue* val) { + scoped_ptr converter(new atom::V8ValueConverter); + return converter->ToV8Value(val, isolate->GetCurrentContext()); +} + bool Converter::FromV8(v8::Isolate* isolate, v8::Local val, base::ListValue* out) { diff --git a/atom/common/native_mate_converters/value_converter.h b/atom/common/native_mate_converters/value_converter.h index 013dd99cc7..f660fd3f18 100644 --- a/atom/common/native_mate_converters/value_converter.h +++ b/atom/common/native_mate_converters/value_converter.h @@ -23,6 +23,12 @@ struct Converter { const base::DictionaryValue& val); }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const base::DictionaryValue* val); +}; + template<> struct Converter { static bool FromV8(v8::Isolate* isolate, diff --git a/filenames.gypi b/filenames.gypi index aefe493335..640607d401 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -110,6 +110,8 @@ 'atom/browser/api/atom_api_tray.h', 'atom/browser/api/atom_api_web_contents.cc', 'atom/browser/api/atom_api_web_contents.h', + 'atom/browser/api/atom_api_web_request.cc', + 'atom/browser/api/atom_api_web_request.h', 'atom/browser/api/atom_api_web_view_manager.cc', 'atom/browser/api/atom_api_window.cc', 'atom/browser/api/atom_api_window.h', @@ -178,6 +180,8 @@ 'atom/browser/net/asar/url_request_asar_job.h', 'atom/browser/net/atom_cert_verifier.cc', 'atom/browser/net/atom_cert_verifier.h', + 'atom/browser/net/atom_network_delegate.cc', + 'atom/browser/net/atom_network_delegate.h', 'atom/browser/net/atom_ssl_config_service.cc', 'atom/browser/net/atom_ssl_config_service.h', 'atom/browser/net/atom_url_request_job_factory.cc', From 29f32c5ec71fd4a5fa6903a524486ff91dba6153 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 4 Dec 2015 03:54:26 +0530 Subject: [PATCH 194/411] support filtering event with url regex --- atom/browser/api/atom_api_web_request.cc | 29 - atom/browser/api/atom_api_web_request.h | 9 +- atom/browser/net/atom_network_delegate.cc | 159 ++++- atom/browser/net/atom_network_delegate.h | 39 +- chromium_src/extensions/common/url_pattern.cc | 619 ++++++++++++++++++ chromium_src/extensions/common/url_pattern.h | 264 ++++++++ filenames.gypi | 2 + 7 files changed, 1061 insertions(+), 60 deletions(-) create mode 100644 chromium_src/extensions/common/url_pattern.cc create mode 100644 chromium_src/extensions/common/url_pattern.h diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc index ad772e4c29..c45cd900ab 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -45,35 +45,6 @@ void WebRequest::SetListener(mate::Arguments* args) { type, filter, callback)); } -mate::ObjectTemplateBuilder WebRequest::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return mate::ObjectTemplateBuilder(isolate) - .SetMethod("onBeforeRequest", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnBeforeRequest>) - .SetMethod("onBeforeSendHeaders", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnBeforeSendHeaders>) - .SetMethod("onSendHeaders", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnSendHeaders>) - .SetMethod("onHeadersReceived", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnHeadersReceived>) - .SetMethod("onBeforeRedirect", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnBeforeRedirect>) - .SetMethod("onResponseStarted", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnResponseStarted>) - .SetMethod("onCompleted", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnCompleted>) - .SetMethod("onErrorOccurred", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnErrorOccurred>); -} - // static mate::Handle WebRequest::Create( v8::Isolate* isolate, diff --git a/atom/browser/api/atom_api_web_request.h b/atom/browser/api/atom_api_web_request.h index 04fb758a9a..053bc1bec9 100644 --- a/atom/browser/api/atom_api_web_request.h +++ b/atom/browser/api/atom_api_web_request.h @@ -7,10 +7,11 @@ #include +#include "atom/browser/api/trackable_object.h" #include "atom/browser/net/atom_network_delegate.h" #include "base/callback.h" #include "native_mate/arguments.h" -#include "native_mate/wrappable.h" +#include "native_mate/handle.h" namespace atom { @@ -18,7 +19,7 @@ class AtomBrowserContext; namespace api { -class WebRequest : public mate::TrackableObject { +class WebRequest : public mate::TrackableObject { public: static mate::Handle Create(v8::Isolate* isolate, AtomBrowserContext* browser_context); @@ -34,10 +35,6 @@ class WebRequest : public mate::TrackableObject { template void SetListener(mate::Arguments* args); - // mate::Wrappable: - mate::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; - private: scoped_refptr browser_context_; diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 3b3c8f9719..c8d341c574 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -4,12 +4,9 @@ #include "atom/browser/net/atom_network_delegate.h" -#include - #include "atom/common/native_mate_converters/net_converter.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/resource_request_info.h" -#include "net/base/net_errors.h" #include "net/url_request/url_request.h" using content::BrowserThread; @@ -18,6 +15,40 @@ namespace atom { namespace { +std::string ResourceTypeToString(content::ResourceType type) { + switch (type) { + case content::RESOURCE_TYPE_MAIN_FRAME: + return "main_frame"; + case content::RESOURCE_TYPE_SUB_FRAME: + return "sub_frame"; + case content::RESOURCE_TYPE_STYLESHEET: + return "stylesheet"; + case content::RESOURCE_TYPE_SCRIPT: + return "script"; + case content::RESOURCE_TYPE_IMAGE: + return "image"; + case content::RESOURCE_TYPE_OBJECT: + return "object"; + case content::RESOURCE_TYPE_XHR: + return "xmlhttprequest"; + default: + return "other"; + } +} + +bool MatchesFilterCondition( + net::URLRequest* request, + const AtomNetworkDelegate::ListenerInfo& info) { + if (!info.url_patterns.empty()) { + auto url = request->url(); + for (auto& pattern : info.url_patterns) + if (pattern.MatchesURL(url)) + return true; + } + + return false; +} + base::DictionaryValue* ExtractRequestInfo(net::URLRequest* request) { base::DictionaryValue* dict = new base::DictionaryValue(); dict->SetInteger("id", request->identifier()); @@ -27,7 +58,7 @@ base::DictionaryValue* ExtractRequestInfo(net::URLRequest* request) { auto info = content::ResourceRequestInfo::ForRequest(request); if (info) resourceType = info->GetResourceType(); - dict->SetInteger("resourceType", resourceType); + dict->SetString("resourceType", ResourceTypeToString(resourceType)); dict->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); return dict; @@ -61,7 +92,7 @@ void OnBeforeURLRequestResponse( const AtomNetworkDelegate::BlockingResponse& result) { if (!result.redirectURL.is_empty()) *new_url = result.redirectURL; - callback.Run(result.cancel); + callback.Run(result.Cancel()); } void OnBeforeSendHeadersResponse( @@ -70,7 +101,7 @@ void OnBeforeSendHeadersResponse( const AtomNetworkDelegate::BlockingResponse& result) { if (!result.requestHeaders.IsEmpty()) *headers = result.requestHeaders; - callback.Run(result.cancel); + callback.Run(result.Cancel()); } void OnHeadersReceivedResponse( @@ -79,7 +110,7 @@ void OnHeadersReceivedResponse( const AtomNetworkDelegate::BlockingResponse& result) { if (result.responseHeaders.get()) *override_response_headers = result.responseHeaders; - callback.Run(result.cancel); + callback.Run(result.Cancel()); } } // namespace @@ -100,6 +131,19 @@ void AtomNetworkDelegate::SetListenerInIO( const Listener& callback) { ListenerInfo info; info.callback = callback; + + const base::ListValue* url_list = nullptr; + if (filter->GetList("urls", &url_list)) { + for (size_t i = 0; i < url_list->GetSize(); ++i) { + std::string url; + extensions::URLPattern pattern; + if (url_list->GetString(i, &url) && + pattern.Parse(url) == extensions::URLPattern::PARSE_SUCCESS) { + info.url_patterns.insert(pattern); + } + } + } + event_listener_map_[type] = info; } @@ -110,7 +154,11 @@ int AtomNetworkDelegate::OnBeforeURLRequest( brightray::NetworkDelegate::OnBeforeURLRequest(request, callback, new_url); auto listener_info = event_listener_map_[kOnBeforeRequest]; - if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + + if (!MatchesFilterCondition(request, listener_info)) + return net::OK; + + if (!listener_info.callback.is_null()) { auto wrapped_callback = listener_info.callback; auto details = ExtractRequestInfo(request); @@ -130,7 +178,11 @@ int AtomNetworkDelegate::OnBeforeSendHeaders( const net::CompletionCallback& callback, net::HttpRequestHeaders* headers) { auto listener_info = event_listener_map_[kOnBeforeSendHeaders]; - if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + + if (!MatchesFilterCondition(request, listener_info)) + return net::OK; + + if (!listener_info.callback.is_null()) { auto wrapped_callback = listener_info.callback; auto details = ExtractRequestInfo(request); details->Set("requestHeaders", GetRequestHeadersDict(*headers)); @@ -150,7 +202,11 @@ void AtomNetworkDelegate::OnSendHeaders( net::URLRequest* request, const net::HttpRequestHeaders& headers) { auto listener_info = event_listener_map_[kOnSendHeaders]; - if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + + if (!MatchesFilterCondition(request, listener_info)) + return; + + if (!listener_info.callback.is_null()) { auto wrapped_callback = listener_info.callback; auto details = ExtractRequestInfo(request); details->Set("requestHeaders", GetRequestHeadersDict(headers)); @@ -168,9 +224,17 @@ int AtomNetworkDelegate::OnHeadersReceived( scoped_refptr* override_response_headers, GURL* allowed_unsafe_redirect_url) { auto listener_info = event_listener_map_[kOnHeadersReceived]; - if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + + if (!MatchesFilterCondition(request, listener_info)) + return net::OK; + + if (!listener_info.callback.is_null()) { auto wrapped_callback = listener_info.callback; auto details = ExtractRequestInfo(request); + details->SetString("statusLine", + original_response_headers->GetStatusLine()); + details->SetInteger("statusCode", + original_response_headers->response_code()); details->Set("responseHeaders", GetResponseHeadersDict(original_response_headers)); @@ -189,10 +253,19 @@ int AtomNetworkDelegate::OnHeadersReceived( void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, const GURL& new_location) { auto listener_info = event_listener_map_[kOnBeforeRedirect]; - if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + + if (!MatchesFilterCondition(request, listener_info)) + return; + + if (!listener_info.callback.is_null()) { auto wrapped_callback = listener_info.callback; auto details = ExtractRequestInfo(request); details->SetString("redirectURL", new_location.spec()); + details->SetInteger("statusCode", request->GetResponseCode()); + auto ip = request->GetSocketAddress().host(); + if (!ip.empty()) + details->SetString("ip", ip); + details->SetBoolean("fromCache", request->was_cached()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(wrapped_callback), @@ -201,12 +274,23 @@ void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, } void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { + if (request->status().status() != net::URLRequestStatus::SUCCESS) + return; + auto listener_info = event_listener_map_[kOnResponseStarted]; - if (!event_listener_map_.empty() && !listener_info.callback.is_null()) { + + if (!MatchesFilterCondition(request, listener_info)) + return; + + if (!listener_info.callback.is_null()) { auto wrapped_callback = listener_info.callback; auto details = ExtractRequestInfo(request); details->Set("responseHeaders", GetResponseHeadersDict(request->response_headers())); + details->SetBoolean("fromCache", request->was_cached()); + details->SetInteger("statusCode", + request->response_headers() ? + request->response_headers()->response_code() : 200); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(wrapped_callback), @@ -215,6 +299,55 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { } void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { + if (request->status().status() == net::URLRequestStatus::FAILED || + request->status().status() == net::URLRequestStatus::CANCELED) { + OnErrorOccurred(request); + return; + } else { + bool is_redirect = request->response_headers() && + net::HttpResponseHeaders::IsRedirectResponseCode( + request->response_headers()->response_code()); + if (is_redirect) + return; + } + + auto listener_info = event_listener_map_[kOnCompleted]; + + if (!MatchesFilterCondition(request, listener_info)) + return; + + if (!listener_info.callback.is_null()) { + auto wrapped_callback = listener_info.callback; + auto details = ExtractRequestInfo(request); + details->Set("responseHeaders", + GetResponseHeadersDict(request->response_headers())); + details->SetBoolean("fromCache", request->was_cached()); + details->SetInteger("statusCode", + request->response_headers() ? + request->response_headers()->response_code() : 200); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(wrapped_callback), + details)); + } +} + +void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { + auto listener_info = event_listener_map_[kOnErrorOccurred]; + + if (!MatchesFilterCondition(request, listener_info)) + return; + + if (!listener_info.callback.is_null()) { + auto wrapped_callback = listener_info.callback; + auto details = ExtractRequestInfo(request); + details->SetBoolean("fromCache", request->was_cached()); + details->SetString("error", net::ErrorToString(request->status().error())); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(wrapped_callback), + details)); + } } } // namespace atom diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index e1cfdb2100..c48d252b2b 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -6,18 +6,29 @@ #define ATOM_BROWSER_NET_ATOM_NETWORK_DELEGATE_H_ #include +#include #include #include "brightray/browser/network_delegate.h" #include "base/callback.h" #include "base/values.h" +#include "extensions/common/url_pattern.h" +#include "net/base/net_errors.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" +namespace extensions { +class URLPattern; +} + namespace atom { class AtomNetworkDelegate : public brightray::NetworkDelegate { public: + struct BlockingResponse; + using Listener = + base::Callback; + enum EventTypes { kInvalidEvent = 0, kOnBeforeRequest = 1 << 0, @@ -26,23 +37,32 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { kOnHeadersReceived = 1 << 3, kOnBeforeRedirect = 1 << 4, kOnResponseStarted = 1 << 5, - kOnErrorOccurred = 1 << 6, - kOnCompleted = 1 << 7, + kOnCompleted = 1 << 6, + kOnErrorOccurred = 1 << 7, + }; + + struct ListenerInfo { + ListenerInfo() {} + ~ListenerInfo() {} + + std::set url_patterns; + AtomNetworkDelegate::Listener callback; }; struct BlockingResponse { BlockingResponse() {} ~BlockingResponse() {} + int Cancel() const { + return cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK; + } + bool cancel; GURL redirectURL; net::HttpRequestHeaders requestHeaders; scoped_refptr responseHeaders; }; - using Listener = - base::Callback; - AtomNetworkDelegate(); ~AtomNetworkDelegate() override; @@ -71,14 +91,9 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { void OnResponseStarted(net::URLRequest* request) override; void OnCompleted(net::URLRequest* request, bool started) override; + void OnErrorOccurred(net::URLRequest* request); + private: - struct ListenerInfo { - ListenerInfo() {} - ~ListenerInfo() {} - - AtomNetworkDelegate::Listener callback; - }; - static std::map event_listener_map_; DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); diff --git a/chromium_src/extensions/common/url_pattern.cc b/chromium_src/extensions/common/url_pattern.cc new file mode 100644 index 0000000000..a6e51aa675 --- /dev/null +++ b/chromium_src/extensions/common/url_pattern.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/url_pattern.h" + +#include + +#include "base/strings/pattern.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "content/public/common/url_constants.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" +#include "url/gurl.h" +#include "url/url_util.h" + +const char extensions::URLPattern::kAllUrlsPattern[] = ""; +const char kExtensionScheme[] = "chrome-extension"; + +namespace { + +// TODO(aa): What about more obscure schemes like data: and javascript: ? +// Note: keep this array in sync with kValidSchemeMasks. +const char* kValidSchemes[] = { + url::kHttpScheme, + url::kHttpsScheme, + url::kFileScheme, + url::kFtpScheme, + content::kChromeUIScheme, + kExtensionScheme, + url::kFileSystemScheme, +}; + +const int kValidSchemeMasks[] = { + extensions::URLPattern::SCHEME_HTTP, + extensions::URLPattern::SCHEME_HTTPS, + extensions::URLPattern::SCHEME_FILE, + extensions::URLPattern::SCHEME_FTP, + extensions::URLPattern::SCHEME_CHROMEUI, + extensions::URLPattern::SCHEME_EXTENSION, + extensions::URLPattern::SCHEME_FILESYSTEM, +}; + +static_assert(arraysize(kValidSchemes) == arraysize(kValidSchemeMasks), + "must keep these arrays in sync"); + +const char kParseSuccess[] = "Success."; +const char kParseErrorMissingSchemeSeparator[] = "Missing scheme separator."; +const char kParseErrorInvalidScheme[] = "Invalid scheme."; +const char kParseErrorWrongSchemeType[] = "Wrong scheme type."; +const char kParseErrorEmptyHost[] = "Host can not be empty."; +const char kParseErrorInvalidHostWildcard[] = "Invalid host wildcard."; +const char kParseErrorEmptyPath[] = "Empty path."; +const char kParseErrorInvalidPort[] = "Invalid port."; +const char kParseErrorInvalidHost[] = "Invalid host."; + +// Message explaining each URLPattern::ParseResult. +const char* const kParseResultMessages[] = { + kParseSuccess, + kParseErrorMissingSchemeSeparator, + kParseErrorInvalidScheme, + kParseErrorWrongSchemeType, + kParseErrorEmptyHost, + kParseErrorInvalidHostWildcard, + kParseErrorEmptyPath, + kParseErrorInvalidPort, + kParseErrorInvalidHost, +}; + +static_assert(extensions::URLPattern::NUM_PARSE_RESULTS == arraysize(kParseResultMessages), + "must add message for each parse result"); + +const char kPathSeparator[] = "/"; + +bool IsStandardScheme(const std::string& scheme) { + // "*" gets the same treatment as a standard scheme. + if (scheme == "*") + return true; + + return url::IsStandard(scheme.c_str(), + url::Component(0, static_cast(scheme.length()))); +} + +bool IsValidPortForScheme(const std::string& scheme, const std::string& port) { + if (port == "*") + return true; + + // Only accept non-wildcard ports if the scheme uses ports. + if (url::DefaultPortForScheme(scheme.c_str(), scheme.length()) == + url::PORT_UNSPECIFIED) { + return false; + } + + int parsed_port = url::PORT_UNSPECIFIED; + if (!base::StringToInt(port, &parsed_port)) + return false; + return (parsed_port >= 0) && (parsed_port < 65536); +} + +// Returns |path| with the trailing wildcard stripped if one existed. +// +// The functions that rely on this (OverlapsWith and Contains) are only +// called for the patterns inside URLPatternSet. In those cases, we know that +// the path will have only a single wildcard at the end. This makes figuring +// out overlap much easier. It seems like there is probably a computer-sciency +// way to solve the general case, but we don't need that yet. +std::string StripTrailingWildcard(const std::string& path) { + size_t wildcard_index = path.find('*'); + size_t path_last = path.size() - 1; + return wildcard_index == path_last ? path.substr(0, path_last) : path; +} + +} // namespace + +namespace extensions { +// static +bool URLPattern::IsValidSchemeForExtensions(const std::string& scheme) { + for (size_t i = 0; i < arraysize(kValidSchemes); ++i) { + if (scheme == kValidSchemes[i]) + return true; + } + return false; +} + +URLPattern::URLPattern() + : valid_schemes_(SCHEME_ALL), + match_all_urls_(false), + match_subdomains_(false), + port_("*") {} + +URLPattern::URLPattern(int valid_schemes) + : valid_schemes_(valid_schemes), + match_all_urls_(false), + match_subdomains_(false), + port_("*") {} + +URLPattern::URLPattern(int valid_schemes, const std::string& pattern) + // Strict error checking is used, because this constructor is only + // appropriate when we know |pattern| is valid. + : valid_schemes_(valid_schemes), + match_all_urls_(false), + match_subdomains_(false), + port_("*") { + ParseResult result = Parse(pattern); + if (PARSE_SUCCESS != result) + NOTREACHED() << "URLPattern invalid: " << pattern << " result " << result; +} + +URLPattern::~URLPattern() { +} + +bool URLPattern::operator<(const URLPattern& other) const { + return GetAsString() < other.GetAsString(); +} + +bool URLPattern::operator>(const URLPattern& other) const { + return GetAsString() > other.GetAsString(); +} + +bool URLPattern::operator==(const URLPattern& other) const { + return GetAsString() == other.GetAsString(); +} + +std::ostream& operator<<(std::ostream& out, const URLPattern& url_pattern) { + return out << '"' << url_pattern.GetAsString() << '"'; +} + +URLPattern::ParseResult URLPattern::Parse(const std::string& pattern) { + spec_.clear(); + SetMatchAllURLs(false); + SetMatchSubdomains(false); + SetPort("*"); + + // Special case pattern to match every valid URL. + if (pattern == kAllUrlsPattern) { + SetMatchAllURLs(true); + return PARSE_SUCCESS; + } + + // Parse out the scheme. + size_t scheme_end_pos = pattern.find(url::kStandardSchemeSeparator); + bool has_standard_scheme_separator = true; + + // Some urls also use ':' alone as the scheme separator. + if (scheme_end_pos == std::string::npos) { + scheme_end_pos = pattern.find(':'); + has_standard_scheme_separator = false; + } + + if (scheme_end_pos == std::string::npos) + return PARSE_ERROR_MISSING_SCHEME_SEPARATOR; + + if (!SetScheme(pattern.substr(0, scheme_end_pos))) + return PARSE_ERROR_INVALID_SCHEME; + + bool standard_scheme = IsStandardScheme(scheme_); + if (standard_scheme != has_standard_scheme_separator) + return PARSE_ERROR_WRONG_SCHEME_SEPARATOR; + + // Advance past the scheme separator. + scheme_end_pos += + (standard_scheme ? strlen(url::kStandardSchemeSeparator) : 1); + if (scheme_end_pos >= pattern.size()) + return PARSE_ERROR_EMPTY_HOST; + + // Parse out the host and path. + size_t host_start_pos = scheme_end_pos; + size_t path_start_pos = 0; + + if (!standard_scheme) { + path_start_pos = host_start_pos; + } else if (scheme_ == url::kFileScheme) { + size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos); + if (host_end_pos == std::string::npos) { + // Allow hostname omission. + // e.g. file://* is interpreted as file:///*, + // file://foo* is interpreted as file:///foo*. + path_start_pos = host_start_pos - 1; + } else { + // Ignore hostname if scheme is file://. + // e.g. file://localhost/foo is equal to file:///foo. + path_start_pos = host_end_pos; + } + } else { + size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos); + + // Host is required. + if (host_start_pos == host_end_pos) + return PARSE_ERROR_EMPTY_HOST; + + if (host_end_pos == std::string::npos) + return PARSE_ERROR_EMPTY_PATH; + + host_ = pattern.substr(host_start_pos, host_end_pos - host_start_pos); + + // The first component can optionally be '*' to match all subdomains. + std::vector host_components = base::SplitString( + host_, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + // Could be empty if the host only consists of whitespace characters. + if (host_components.empty() || + (host_components.size() == 1 && host_components[0].empty())) + return PARSE_ERROR_EMPTY_HOST; + + if (host_components[0] == "*") { + match_subdomains_ = true; + host_components.erase(host_components.begin(), + host_components.begin() + 1); + } + host_ = JoinString(host_components, "."); + + path_start_pos = host_end_pos; + } + + SetPath(pattern.substr(path_start_pos)); + + size_t port_pos = host_.find(':'); + if (port_pos != std::string::npos) { + if (!SetPort(host_.substr(port_pos + 1))) + return PARSE_ERROR_INVALID_PORT; + host_ = host_.substr(0, port_pos); + } + + // No other '*' can occur in the host, though. This isn't necessary, but is + // done as a convenience to developers who might otherwise be confused and + // think '*' works as a glob in the host. + if (host_.find('*') != std::string::npos) + return PARSE_ERROR_INVALID_HOST_WILDCARD; + + // Null characters are not allowed in hosts. + if (host_.find('\0') != std::string::npos) + return PARSE_ERROR_INVALID_HOST; + + return PARSE_SUCCESS; +} + +void URLPattern::SetValidSchemes(int valid_schemes) { + spec_.clear(); + valid_schemes_ = valid_schemes; +} + +void URLPattern::SetHost(const std::string& host) { + spec_.clear(); + host_ = host; +} + +void URLPattern::SetMatchAllURLs(bool val) { + spec_.clear(); + match_all_urls_ = val; + + if (val) { + match_subdomains_ = true; + scheme_ = "*"; + host_.clear(); + SetPath("/*"); + } +} + +void URLPattern::SetMatchSubdomains(bool val) { + spec_.clear(); + match_subdomains_ = val; +} + +bool URLPattern::SetScheme(const std::string& scheme) { + spec_.clear(); + scheme_ = scheme; + if (scheme_ == "*") { + valid_schemes_ &= (SCHEME_HTTP | SCHEME_HTTPS); + } else if (!IsValidScheme(scheme_)) { + return false; + } + return true; +} + +bool URLPattern::IsValidScheme(const std::string& scheme) const { + if (valid_schemes_ == SCHEME_ALL) + return true; + + for (size_t i = 0; i < arraysize(kValidSchemes); ++i) { + if (scheme == kValidSchemes[i] && (valid_schemes_ & kValidSchemeMasks[i])) + return true; + } + + return false; +} + +void URLPattern::SetPath(const std::string& path) { + spec_.clear(); + path_ = path; + path_escaped_ = path_; + base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "\\", "\\\\"); + base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "?", "\\?"); +} + +bool URLPattern::SetPort(const std::string& port) { + spec_.clear(); + if (IsValidPortForScheme(scheme_, port)) { + port_ = port; + return true; + } + return false; +} + +bool URLPattern::MatchesURL(const GURL& test) const { + const GURL* test_url = &test; + bool has_inner_url = test.inner_url() != NULL; + + if (has_inner_url) { + if (!test.SchemeIsFileSystem()) + return false; // The only nested URLs we handle are filesystem URLs. + test_url = test.inner_url(); + } + + if (!MatchesScheme(test_url->scheme())) + return false; + + if (match_all_urls_) + return true; + + std::string path_for_request = test.PathForRequest(); + if (has_inner_url) + path_for_request = test_url->path() + path_for_request; + + return MatchesSecurityOriginHelper(*test_url) && + MatchesPath(path_for_request); +} + +bool URLPattern::MatchesSecurityOrigin(const GURL& test) const { + const GURL* test_url = &test; + bool has_inner_url = test.inner_url() != NULL; + + if (has_inner_url) { + if (!test.SchemeIsFileSystem()) + return false; // The only nested URLs we handle are filesystem URLs. + test_url = test.inner_url(); + } + + if (!MatchesScheme(test_url->scheme())) + return false; + + if (match_all_urls_) + return true; + + return MatchesSecurityOriginHelper(*test_url); +} + +bool URLPattern::MatchesScheme(const std::string& test) const { + if (!IsValidScheme(test)) + return false; + + return scheme_ == "*" || test == scheme_; +} + +bool URLPattern::MatchesHost(const std::string& host) const { + std::string test(url::kHttpScheme); + test += url::kStandardSchemeSeparator; + test += host; + test += "/"; + return MatchesHost(GURL(test)); +} + +bool URLPattern::MatchesHost(const GURL& test) const { + // If the hosts are exactly equal, we have a match. + if (test.host() == host_) + return true; + + // If we're matching subdomains, and we have no host in the match pattern, + // that means that we're matching all hosts, which means we have a match no + // matter what the test host is. + if (match_subdomains_ && host_.empty()) + return true; + + // Otherwise, we can only match if our match pattern matches subdomains. + if (!match_subdomains_) + return false; + + // We don't do subdomain matching against IP addresses, so we can give up now + // if the test host is an IP address. + if (test.HostIsIPAddress()) + return false; + + // Check if the test host is a subdomain of our host. + if (test.host().length() <= (host_.length() + 1)) + return false; + + if (test.host().compare(test.host().length() - host_.length(), + host_.length(), host_) != 0) + return false; + + return test.host()[test.host().length() - host_.length() - 1] == '.'; +} + +bool URLPattern::ImpliesAllHosts() const { + // Check if it matches all urls or is a pattern like http://*/*. + if (match_all_urls_ || + (match_subdomains_ && host_.empty() && port_ == "*" && path_ == "/*")) { + return true; + } + + // If this doesn't even match subdomains, it can't possibly imply all hosts. + if (!match_subdomains_) + return false; + + // If |host_| is a recognized TLD, this will be 0. We don't include private + // TLDs, so that, e.g., *.appspot.com does not imply all hosts. + size_t registry_length = net::registry_controlled_domains::GetRegistryLength( + host_, + net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, + net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); + // If there was more than just a TLD in the host (e.g., *.foobar.com), it + // doesn't imply all hosts. + if (registry_length > 0) + return false; + + // At this point the host could either be just a TLD ("com") or some unknown + // TLD-like string ("notatld"). To disambiguate between them construct a + // fake URL, and check the registry. This returns 0 if the TLD is + // unrecognized, or the length of the recognized TLD. + registry_length = net::registry_controlled_domains::GetRegistryLength( + base::StringPrintf("foo.%s", host_.c_str()), + net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, + net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); + // If we recognized this TLD, then this is a pattern like *.com, and it + // should imply all hosts. Otherwise, this doesn't imply all hosts. + return registry_length > 0; +} + +bool URLPattern::MatchesSingleOrigin() const { + // Strictly speaking, the port is part of the origin, but in URLPattern it + // defaults to *. It's not very interesting anyway, so leave it out. + return !ImpliesAllHosts() && scheme_ != "*" && !match_subdomains_; +} + +bool URLPattern::MatchesPath(const std::string& test) const { + // Make the behaviour of OverlapsWith consistent with MatchesURL, which is + // need to match hosted apps on e.g. 'google.com' also run on 'google.com/'. + if (test + "/*" == path_escaped_) + return true; + + return base::MatchPattern(test, path_escaped_); +} + +const std::string& URLPattern::GetAsString() const { + if (!spec_.empty()) + return spec_; + + if (match_all_urls_) { + spec_ = kAllUrlsPattern; + return spec_; + } + + bool standard_scheme = IsStandardScheme(scheme_); + + std::string spec = scheme_ + + (standard_scheme ? url::kStandardSchemeSeparator : ":"); + + if (scheme_ != url::kFileScheme && standard_scheme) { + if (match_subdomains_) { + spec += "*"; + if (!host_.empty()) + spec += "."; + } + + if (!host_.empty()) + spec += host_; + + if (port_ != "*") { + spec += ":"; + spec += port_; + } + } + + if (!path_.empty()) + spec += path_; + + spec_ = spec; + return spec_; +} + +bool URLPattern::OverlapsWith(const URLPattern& other) const { + if (match_all_urls() || other.match_all_urls()) + return true; + return (MatchesAnyScheme(other.GetExplicitSchemes()) || + other.MatchesAnyScheme(GetExplicitSchemes())) + && (MatchesHost(other.host()) || other.MatchesHost(host())) + && (MatchesPortPattern(other.port()) || other.MatchesPortPattern(port())) + && (MatchesPath(StripTrailingWildcard(other.path())) || + other.MatchesPath(StripTrailingWildcard(path()))); +} + +bool URLPattern::Contains(const URLPattern& other) const { + if (match_all_urls()) + return true; + return MatchesAllSchemes(other.GetExplicitSchemes()) && + MatchesHost(other.host()) && + (!other.match_subdomains_ || match_subdomains_) && + MatchesPortPattern(other.port()) && + MatchesPath(StripTrailingWildcard(other.path())); +} + +bool URLPattern::MatchesAnyScheme( + const std::vector& schemes) const { + for (std::vector::const_iterator i = schemes.begin(); + i != schemes.end(); ++i) { + if (MatchesScheme(*i)) + return true; + } + + return false; +} + +bool URLPattern::MatchesAllSchemes( + const std::vector& schemes) const { + for (std::vector::const_iterator i = schemes.begin(); + i != schemes.end(); ++i) { + if (!MatchesScheme(*i)) + return false; + } + + return true; +} + +bool URLPattern::MatchesSecurityOriginHelper(const GURL& test) const { + // Ignore hostname if scheme is file://. + if (scheme_ != url::kFileScheme && !MatchesHost(test)) + return false; + + if (!MatchesPortPattern(base::IntToString(test.EffectiveIntPort()))) + return false; + + return true; +} + +bool URLPattern::MatchesPortPattern(const std::string& port) const { + return port_ == "*" || port_ == port; +} + +std::vector URLPattern::GetExplicitSchemes() const { + std::vector result; + + if (scheme_ != "*" && !match_all_urls_ && IsValidScheme(scheme_)) { + result.push_back(scheme_); + return result; + } + + for (size_t i = 0; i < arraysize(kValidSchemes); ++i) { + if (MatchesScheme(kValidSchemes[i])) { + result.push_back(kValidSchemes[i]); + } + } + + return result; +} + +std::vector URLPattern::ConvertToExplicitSchemes() const { + std::vector explicit_schemes = GetExplicitSchemes(); + std::vector result; + + for (std::vector::const_iterator i = explicit_schemes.begin(); + i != explicit_schemes.end(); ++i) { + URLPattern temp = *this; + temp.SetScheme(*i); + temp.SetMatchAllURLs(false); + result.push_back(temp); + } + + return result; +} + +// static +const char* URLPattern::GetParseResultString( + URLPattern::ParseResult parse_result) { + return kParseResultMessages[parse_result]; +} + +} // namespace extensions diff --git a/chromium_src/extensions/common/url_pattern.h b/chromium_src/extensions/common/url_pattern.h new file mode 100644 index 0000000000..fa9b6495e3 --- /dev/null +++ b/chromium_src/extensions/common/url_pattern.h @@ -0,0 +1,264 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef EXTENSIONS_COMMON_URL_PATTERN_H_ +#define EXTENSIONS_COMMON_URL_PATTERN_H_ + +#include +#include +#include +#include + +class GURL; + +namespace extensions { +// A pattern that can be used to match URLs. A URLPattern is a very restricted +// subset of URL syntax: +// +// := :// | '' +// := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome' | +// 'chrome-extension' | 'filesystem' +// := '*' | '*.' + +// := [':' ('*' | )] +// := '/' +// +// * Host is not used when the scheme is 'file'. +// * The path can have embedded '*' characters which act as glob wildcards. +// * '' is a special pattern that matches any URL that contains a +// valid scheme (as specified by valid_schemes_). +// * The '*' scheme pattern excludes file URLs. +// +// Examples of valid patterns: +// - http://*/* +// - http://*/foo* +// - https://*.google.com/foo*bar +// - file://monkey* +// - http://127.0.0.1/* +// +// Examples of invalid patterns: +// - http://* -- path not specified +// - http://*foo/bar -- * not allowed as substring of host component +// - http://foo.*.bar/baz -- * must be first component +// - http:/bar -- scheme separator not found +// - foo://* -- invalid scheme +// - chrome:// -- we don't support chrome internal URLs +class URLPattern { + public: + // A collection of scheme bitmasks for use with valid_schemes. + enum SchemeMasks { + SCHEME_NONE = 0, + SCHEME_HTTP = 1 << 0, + SCHEME_HTTPS = 1 << 1, + SCHEME_FILE = 1 << 2, + SCHEME_FTP = 1 << 3, + SCHEME_CHROMEUI = 1 << 4, + SCHEME_EXTENSION = 1 << 5, + SCHEME_FILESYSTEM = 1 << 6, + + // IMPORTANT! + // SCHEME_ALL will match every scheme, including chrome://, chrome- + // extension://, about:, etc. Because this has lots of security + // implications, third-party extensions should usually not be able to get + // access to URL patterns initialized this way. If there is a reason + // for violating this general rule, document why this it safe. + SCHEME_ALL = -1, + }; + + // Error codes returned from Parse(). + enum ParseResult { + PARSE_SUCCESS = 0, + PARSE_ERROR_MISSING_SCHEME_SEPARATOR, + PARSE_ERROR_INVALID_SCHEME, + PARSE_ERROR_WRONG_SCHEME_SEPARATOR, + PARSE_ERROR_EMPTY_HOST, + PARSE_ERROR_INVALID_HOST_WILDCARD, + PARSE_ERROR_EMPTY_PATH, + PARSE_ERROR_INVALID_PORT, + PARSE_ERROR_INVALID_HOST, + NUM_PARSE_RESULTS + }; + + // The string pattern. + static const char kAllUrlsPattern[]; + + // Returns true if the given |scheme| is considered valid for extensions. + static bool IsValidSchemeForExtensions(const std::string& scheme); + + explicit URLPattern(int valid_schemes); + + // Convenience to construct a URLPattern from a string. If the string is not + // known ahead of time, use Parse() instead, which returns success or failure. + URLPattern(int valid_schemes, const std::string& pattern); + + URLPattern(); + ~URLPattern(); + + bool operator<(const URLPattern& other) const; + bool operator>(const URLPattern& other) const; + bool operator==(const URLPattern& other) const; + + // Initializes this instance by parsing the provided string. Returns + // URLPattern::PARSE_SUCCESS on success, or an error code otherwise. On + // failure, this instance will have some intermediate values and is in an + // invalid state. + ParseResult Parse(const std::string& pattern_str); + + // Gets the bitmask of valid schemes. + int valid_schemes() const { return valid_schemes_; } + void SetValidSchemes(int valid_schemes); + + // Gets the host the pattern matches. This can be an empty string if the + // pattern matches all hosts (the input was ://*/). + const std::string& host() const { return host_; } + void SetHost(const std::string& host); + + // Gets whether to match subdomains of host(). + bool match_subdomains() const { return match_subdomains_; } + void SetMatchSubdomains(bool val); + + // Gets the path the pattern matches with the leading slash. This can have + // embedded asterisks which are interpreted using glob rules. + const std::string& path() const { return path_; } + void SetPath(const std::string& path); + + // Returns true if this pattern matches all urls. + bool match_all_urls() const { return match_all_urls_; } + void SetMatchAllURLs(bool val); + + // Sets the scheme for pattern matches. This can be a single '*' if the + // pattern matches all valid schemes (as defined by the valid_schemes_ + // property). Returns false on failure (if the scheme is not valid). + bool SetScheme(const std::string& scheme); + // Note: You should use MatchesScheme() instead of this getter unless you + // absolutely need the exact scheme. This is exposed for testing. + const std::string& scheme() const { return scheme_; } + + // Returns true if the specified scheme can be used in this URL pattern, and + // false otherwise. Uses valid_schemes_ to determine validity. + bool IsValidScheme(const std::string& scheme) const; + + // Returns true if this instance matches the specified URL. + bool MatchesURL(const GURL& test) const; + + // Returns true if this instance matches the specified security origin. + bool MatchesSecurityOrigin(const GURL& test) const; + + // Returns true if |test| matches our scheme. + // Note that if test is "filesystem", this may fail whereas MatchesURL + // may succeed. MatchesURL is smart enough to look at the inner_url instead + // of the outer "filesystem:" part. + bool MatchesScheme(const std::string& test) const; + + // Returns true if |test| matches our host. + bool MatchesHost(const std::string& test) const; + bool MatchesHost(const GURL& test) const; + + // Returns true if |test| matches our path. + bool MatchesPath(const std::string& test) const; + + // Returns true if the pattern is vague enough that it implies all hosts, + // such as *://*/*. + // This is an expensive method, and should be used sparingly! + // You should probably use URLPatternSet::ShouldWarnAllHosts(), which is + // cached. + bool ImpliesAllHosts() const; + + // Returns true if the pattern only matches a single origin. The pattern may + // include a path. + bool MatchesSingleOrigin() const; + + // Sets the port. Returns false if the port is invalid. + bool SetPort(const std::string& port); + const std::string& port() const { return port_; } + + // Returns a string representing this instance. + const std::string& GetAsString() const; + + // Determines whether there is a URL that would match this instance and + // another instance. This method is symmetrical: Calling + // other.OverlapsWith(this) would result in the same answer. + bool OverlapsWith(const URLPattern& other) const; + + // Returns true if this pattern matches all possible URLs that |other| can + // match. For example, http://*.google.com encompasses http://www.google.com. + bool Contains(const URLPattern& other) const; + + // Converts this URLPattern into an equivalent set of URLPatterns that don't + // use a wildcard in the scheme component. If this URLPattern doesn't use a + // wildcard scheme, then the returned set will contain one element that is + // equivalent to this instance. + std::vector ConvertToExplicitSchemes() const; + + static bool EffectiveHostCompare(const URLPattern& a, const URLPattern& b) { + if (a.match_all_urls_ && b.match_all_urls_) + return false; + return a.host_.compare(b.host_) < 0; + } + + // Used for origin comparisons in a std::set. + class EffectiveHostCompareFunctor { + public: + bool operator()(const URLPattern& a, const URLPattern& b) const { + return EffectiveHostCompare(a, b); + } + }; + + // Get an error string for a ParseResult. + static const char* GetParseResultString(URLPattern::ParseResult parse_result); + + private: + // Returns true if any of the |schemes| items matches our scheme. + bool MatchesAnyScheme(const std::vector& schemes) const; + + // Returns true if all of the |schemes| items matches our scheme. + bool MatchesAllSchemes(const std::vector& schemes) const; + + bool MatchesSecurityOriginHelper(const GURL& test) const; + + // Returns true if our port matches the |port| pattern (it may be "*"). + bool MatchesPortPattern(const std::string& port) const; + + // If the URLPattern contains a wildcard scheme, returns a list of + // equivalent literal schemes, otherwise returns the current scheme. + std::vector GetExplicitSchemes() const; + + // A bitmask containing the schemes which are considered valid for this + // pattern. Parse() uses this to decide whether a pattern contains a valid + // scheme. + int valid_schemes_; + + // True if this is a special-case "" pattern. + bool match_all_urls_; + + // The scheme for the pattern. + std::string scheme_; + + // The host without any leading "*" components. + std::string host_; + + // Whether we should match subdomains of the host. This is true if the first + // component of the pattern's host was "*". + bool match_subdomains_; + + // The port. + std::string port_; + + // The path to match. This is everything after the host of the URL, or + // everything after the scheme in the case of file:// URLs. + std::string path_; + + // The path with "?" and "\" characters escaped for use with the + // MatchPattern() function. + std::string path_escaped_; + + // A string representing this URLPattern. + mutable std::string spec_; +}; + +std::ostream& operator<<(std::ostream& out, const URLPattern& url_pattern); + +typedef std::vector URLPatternList; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_URL_PATTERN_H_ diff --git a/filenames.gypi b/filenames.gypi index 640607d401..f1209eeeb0 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -476,6 +476,8 @@ 'chromium_src/chrome/utility/utility_message_handler.h', 'chromium_src/extensions/browser/app_window/size_constraints.cc', 'chromium_src/extensions/browser/app_window/size_constraints.h', + 'chromium_src/extensions/common/url_pattern.cc', + 'chromium_src/extensions/common/url_pattern.h', 'chromium_src/library_loaders/libspeechd_loader.cc', 'chromium_src/library_loaders/libspeechd.h', 'chromium_src/net/test/embedded_test_server/stream_listen_socket.cc', From c5b5bbbeb27801255f5f2a4d16deb264b5c89d31 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 4 Dec 2015 05:33:56 +0530 Subject: [PATCH 195/411] add documentation --- atom/browser/net/atom_network_delegate.cc | 8 + docs/api/session.md | 175 ++++++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index c8d341c574..1ee886ba5d 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -266,6 +266,8 @@ void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, if (!ip.empty()) details->SetString("ip", ip); details->SetBoolean("fromCache", request->was_cached()); + details->Set("responseHeaders", + GetResponseHeadersDict(request->response_headers())); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(wrapped_callback), @@ -291,6 +293,9 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { details->SetInteger("statusCode", request->response_headers() ? request->response_headers()->response_code() : 200); + details->SetString("statusLine", + request->response_headers() ? + request->response_headers()->GetStatusLine() : std::string()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(wrapped_callback), @@ -325,6 +330,9 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { details->SetInteger("statusCode", request->response_headers() ? request->response_headers()->response_code() : 200); + details->SetString("statusLine", + request->response_headers() ? + request->response_headers()->GetStatusLine() : std::string()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(wrapped_callback), diff --git a/docs/api/session.md b/docs/api/session.md index a69c5939ed..d889ace4e7 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -286,3 +286,178 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c callback(false); }); ``` + +#### `ses.webRequest` + +The `webRequest` api allows to intercept and modify contents of a request at various +stages of its lifetime. + +```javascript +// Modify the user agent for all requests to the following urls. + +var filter = { + urls: ["https://*.github.com/*", "*://electron.github.io"] +} + +myWindow.webContents.session.webRequest.onBeforeSendHeaders(filter, function(details) { + details.requestHeaders['User-Agent'] = "MyAgent"; + return {cancel: false, requestHeaders: details.requestHeaders}; +}) +``` + +#### `ses.webRequest.onBeforeRequest([filter,] listener)` + +* `filter` Object + * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs + will be filtered out. +* `listener` Function + * `details` Object + * `id` String - Request id. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double +* `blockingResponse` Object + * `cancel` Boolean - Whether to continue or block the request. + * `redirectURL` String **optional** - The original request is prevented from being sent or + completed, and is instead redirected to the given URL. + +Fired when a request is about to occur. Should return a `blockingResponse`. + +#### `ses.webRequest.onBeforeSendHeaders([filter,] listener)` + +* `filter` Object + * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs + will be filtered out. +* `listener` Function + * `details` Object + * `id` String - Request id. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object +* `blockingResponse` Object + * `cancel` Boolean - Whether to continue or block the request. + * `requestHeaders` Object **optional** - When provided, request will be made with these + headers. + +Fired before sending an HTTP request, once the request headers are available. This may +occur after a TCP connection is made to the server, but before any http data is sent. +Should return a `blockingResponse`. + +#### `ses.webRequest.onSendHeaders([filter,] listener)` + +* `filter` Object + * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs + will be filtered out. +* `listener` Function + * `details` Object + * `id` String - Request id. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +Fired just before a request is going to be sent to the server, modifications of previous +`onBeforeSendHeaders` response are visible by the time this listener is fired. + +#### `ses.webRequest.onHeadersReceived([filter,] listener)` + +* `filter` Object + * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs + will be filtered out. +* `listener` Function + * `details` Object + * `id` String - Request id. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object +* `blockingResponse` Object + * `cancel` Boolean - Whether to continue or block the request. + * `responseHeaders` Object **optional** - When provided, the server is assumed to have + responded with these headers. + +Fired when HTTP response headers of a request have been received. Should return a +`blockingResponse`. + +#### `ses.webRequest.onResponseStarted([filter,] listener)` + +* `filter` Object + * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs + will be filtered out. +* `listener` Function + * `details` Object + * `id` String - Request id. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +Fired when first byte of the response body is received. For HTTP requests, this means that the +status line and response headers are available. + +#### `ses.webRequest.onBeforeRedirect([filter,] listener)` + +* `filter` Object + * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs + will be filtered out. +* `listener` Function + * `details` Object + * `id` String - Request id. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String - The server IP address that the request was actually sent to. + * `fromCache` Boolean + * `responseHeaders` Object + +Fired when a server initiated redirect is about to occur. + +#### `ses.webRequest.onCompleted([filter,] listener)` + +* `filter` Object + * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs + will be filtered out. +* `listener` Function + * `details` Object + * `id` String - Request id. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean - Indicates whether the response was fetched from disk cache. + * `statusCode` Integer + * `statusLine` String + +Fired when a request is completed. + +#### `ses.webRequest.onErrorOccurred([filter,] listener)` + +* `filter` Object + * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs + will be filtered out. +* `listener` Function + * `details` Object + * `id` String - Request id. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean - Indicates whether the response was fetched from disk cache. + * `error` String - The error description. + +Fired when an error occurs. From 461ee499888d2860c8c849f65d80339866b68dba Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 4 Dec 2015 07:24:01 +0530 Subject: [PATCH 196/411] fix response headers modification --- atom/browser/net/atom_network_delegate.cc | 38 ++++++++++++++++------- docs/api/session.md | 16 +++++----- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 1ee886ba5d..d4c83996b9 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -44,9 +44,10 @@ bool MatchesFilterCondition( for (auto& pattern : info.url_patterns) if (pattern.MatchesURL(url)) return true; + return false; } - return false; + return true; } base::DictionaryValue* ExtractRequestInfo(net::URLRequest* request) { @@ -106,10 +107,20 @@ void OnBeforeSendHeadersResponse( void OnHeadersReceivedResponse( const net::CompletionCallback& callback, + const net::HttpResponseHeaders* original_response_headers, scoped_refptr* override_response_headers, const AtomNetworkDelegate::BlockingResponse& result) { - if (result.responseHeaders.get()) - *override_response_headers = result.responseHeaders; + if (result.responseHeaders.get()) { + *override_response_headers = new net::HttpResponseHeaders( + original_response_headers->raw_headers()); + void* iter = nullptr; + std::string key; + std::string value; + while (result.responseHeaders->EnumerateHeaderLines(&iter, &key, &value)) { + (*override_response_headers)->RemoveHeader(key); + (*override_response_headers)->AddHeader(key + ": " + value); + } + } callback.Run(result.Cancel()); } @@ -242,6 +253,7 @@ int AtomNetworkDelegate::OnHeadersReceived( base::Bind(wrapped_callback, details), base::Bind(&OnHeadersReceivedResponse, callback, + original_response_headers, override_response_headers)); return net::ERR_IO_PENDING; @@ -290,12 +302,14 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { details->Set("responseHeaders", GetResponseHeadersDict(request->response_headers())); details->SetBoolean("fromCache", request->was_cached()); + + auto response_headers = request->response_headers(); details->SetInteger("statusCode", - request->response_headers() ? - request->response_headers()->response_code() : 200); + response_headers ? + response_headers->response_code() : 200); details->SetString("statusLine", - request->response_headers() ? - request->response_headers()->GetStatusLine() : std::string()); + response_headers ? + response_headers->GetStatusLine() : std::string()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(wrapped_callback), @@ -327,12 +341,14 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { details->Set("responseHeaders", GetResponseHeadersDict(request->response_headers())); details->SetBoolean("fromCache", request->was_cached()); + + auto response_headers = request->response_headers(); details->SetInteger("statusCode", - request->response_headers() ? - request->response_headers()->response_code() : 200); + response_headers ? + response_headers->response_code() : 200); details->SetString("statusLine", - request->response_headers() ? - request->response_headers()->GetStatusLine() : std::string()); + response_headers ? + response_headers->GetStatusLine() : std::string()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(wrapped_callback), diff --git a/docs/api/session.md b/docs/api/session.md index d889ace4e7..b16a9b1f0f 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -309,7 +309,7 @@ myWindow.webContents.session.webRequest.onBeforeSendHeaders(filter, function(det * `filter` Object * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. + will be filtered out. * `listener` Function * `details` Object * `id` String - Request id. @@ -320,7 +320,7 @@ myWindow.webContents.session.webRequest.onBeforeSendHeaders(filter, function(det * `blockingResponse` Object * `cancel` Boolean - Whether to continue or block the request. * `redirectURL` String **optional** - The original request is prevented from being sent or - completed, and is instead redirected to the given URL. + completed, and is instead redirected to the given URL. Fired when a request is about to occur. Should return a `blockingResponse`. @@ -328,7 +328,7 @@ Fired when a request is about to occur. Should return a `blockingResponse`. * `filter` Object * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. + will be filtered out. * `listener` Function * `details` Object * `id` String - Request id. @@ -340,7 +340,7 @@ Fired when a request is about to occur. Should return a `blockingResponse`. * `blockingResponse` Object * `cancel` Boolean - Whether to continue or block the request. * `requestHeaders` Object **optional** - When provided, request will be made with these - headers. + headers. Fired before sending an HTTP request, once the request headers are available. This may occur after a TCP connection is made to the server, but before any http data is sent. @@ -350,7 +350,7 @@ Should return a `blockingResponse`. * `filter` Object * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. + will be filtered out. * `listener` Function * `details` Object * `id` String - Request id. @@ -381,7 +381,7 @@ Fired just before a request is going to be sent to the server, modifications of * `blockingResponse` Object * `cancel` Boolean - Whether to continue or block the request. * `responseHeaders` Object **optional** - When provided, the server is assumed to have - responded with these headers. + responded with these headers. Fired when HTTP response headers of a request have been received. Should return a `blockingResponse`. @@ -430,7 +430,7 @@ Fired when a server initiated redirect is about to occur. * `filter` Object * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. + will be filtered out. * `listener` Function * `details` Object * `id` String - Request id. @@ -449,7 +449,7 @@ Fired when a request is completed. * `filter` Object * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. + will be filtered out. * `listener` Function * `details` Object * `id` String - Request id. From 63c0095efb2133484c6c87c0b37611384a99bce4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 16:45:49 -0800 Subject: [PATCH 197/411] Emit process exit event with app exit code --- atom/browser/api/atom_api_app.cc | 2 +- atom/browser/api/lib/app.coffee | 12 ++++++++++++ atom/browser/lib/init.coffee | 6 ------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 697d6eca6a..0c3623583c 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -344,7 +344,7 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( auto browser = base::Unretained(Browser::Get()); return mate::ObjectTemplateBuilder(isolate) .SetMethod("quit", base::Bind(&Browser::Quit, browser)) - .SetMethod("exit", base::Bind(&Browser::Exit, browser)) + .SetMethod("_exit", base::Bind(&Browser::Exit, browser)) .SetMethod("focus", base::Bind(&Browser::Focus, browser)) .SetMethod("getVersion", base::Bind(&Browser::GetVersion, browser)) .SetMethod("setVersion", base::Bind(&Browser::SetVersion, browser)) diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index d0ec41c4d2..fc0a78298c 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -34,6 +34,18 @@ app.setAppPath = (path) -> app.getAppPath = -> appPath +appExitCode = undefined +app.exit = (exitCode) -> + appExitCode = exitCode + app._exit(exitCode) + +# Map process.exit to app.exit, which quits gracefully. +process.exit = app.exit + +# Emit a process 'exit' event on app quit. +app.on 'quit', -> + process.emit 'exit', appExitCode + # Routes the events to webContents. for name in ['login', 'certificate-error', 'select-client-certificate'] do (name) -> diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index 85faf0f038..9487849e5e 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -51,13 +51,7 @@ process.on 'uncaughtException', (error) -> message = "Uncaught Exception:\n#{stack}" dialog.showErrorBox 'A JavaScript error occurred in the main process', message -# Emit 'exit' event on quit. {app} = require 'electron' -app.on 'quit', -> - process.emit 'exit' - -# Map process.exit to app.exit, which quits gracefully. -process.exit = app.exit # Load the RPC server. require './rpc-server' From aa82eddca8500a6d3fae9b7dbbcc6bfcd6895ddc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 7 Dec 2015 17:05:08 -0800 Subject: [PATCH 198/411] Add spec for exit code on event --- spec/api-app-spec.coffee | 19 +++++++++++++++++++ spec/fixtures/api/quit-app/main.js | 9 +++++++++ spec/fixtures/api/quit-app/package.json | 4 ++++ 3 files changed, 32 insertions(+) create mode 100644 spec/fixtures/api/quit-app/main.js create mode 100644 spec/fixtures/api/quit-app/package.json diff --git a/spec/api-app-spec.coffee b/spec/api-app-spec.coffee index 490727488d..adb64d363e 100644 --- a/spec/api-app-spec.coffee +++ b/spec/api-app-spec.coffee @@ -1,4 +1,6 @@ assert = require 'assert' +ChildProcess = require 'child_process' +path = require 'path' {remote} = require 'electron' {app, BrowserWindow} = remote.require 'electron' @@ -29,6 +31,23 @@ describe 'app module', -> it 'should not be empty', -> assert.notEqual app.getLocale(), '' + describe 'app.exit(exitCode)', -> + appProcess = null + afterEach -> + appProcess?.kill() + + it 'emits a process exit event with the code', (done) -> + appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') + electronPath = remote.getGlobal('process').execPath + appProcess = ChildProcess.spawn(electronPath, [appPath]) + + output = '' + appProcess.stdout.on 'data', (data) -> output += data + appProcess.on 'close', (code) -> + assert.notEqual output.indexOf('Exit event with code: 123'), -1 + assert.equal code, 123 + done() + describe 'BrowserWindow events', -> w = null afterEach -> diff --git a/spec/fixtures/api/quit-app/main.js b/spec/fixtures/api/quit-app/main.js new file mode 100644 index 0000000000..4bdeb93a5e --- /dev/null +++ b/spec/fixtures/api/quit-app/main.js @@ -0,0 +1,9 @@ +var app = require('electron').app + +app.on('ready', function () { + app.exit(123) +}) + +process.on('exit', function (code) { + console.log('Exit event with code: ' + code) +}) diff --git a/spec/fixtures/api/quit-app/package.json b/spec/fixtures/api/quit-app/package.json new file mode 100644 index 0000000000..ea5bb1643b --- /dev/null +++ b/spec/fixtures/api/quit-app/package.json @@ -0,0 +1,4 @@ +{ + "name": "quit-app", + "main": "main.js" +} From 92433be8888e3d5eaaaaf93599aab7bcd1e568d6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 9 Dec 2015 18:09:59 -0800 Subject: [PATCH 199/411] Include exit code with quit event --- atom/browser/api/atom_api_app.cc | 6 +++--- atom/browser/api/atom_api_app.h | 2 +- atom/browser/api/lib/app.coffee | 9 ++------- atom/browser/atom_browser_main_parts.cc | 4 ++++ atom/browser/atom_browser_main_parts.h | 3 +++ atom/browser/browser.cc | 3 ++- atom/browser/browser_observer.h | 2 +- spec/api-app-spec.coffee | 1 + spec/fixtures/api/quit-app/main.js | 2 +- spec/static/index.html | 1 + 10 files changed, 19 insertions(+), 14 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 0c3623583c..81ec3fefd5 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -180,8 +180,8 @@ void App::OnWindowAllClosed() { Emit("window-all-closed"); } -void App::OnQuit() { - Emit("quit"); +void App::OnQuit(const int code) { + Emit("quit", code); if (process_singleton_.get()) { process_singleton_->Cleanup(); @@ -344,7 +344,7 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( auto browser = base::Unretained(Browser::Get()); return mate::ObjectTemplateBuilder(isolate) .SetMethod("quit", base::Bind(&Browser::Quit, browser)) - .SetMethod("_exit", base::Bind(&Browser::Exit, browser)) + .SetMethod("exit", base::Bind(&Browser::Exit, browser)) .SetMethod("focus", base::Bind(&Browser::Focus, browser)) .SetMethod("getVersion", base::Bind(&Browser::GetVersion, browser)) .SetMethod("setVersion", base::Bind(&Browser::SetVersion, browser)) diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index a6f99d65e0..f04a19e67c 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -42,7 +42,7 @@ class App : public AtomBrowserClient::Delegate, void OnBeforeQuit(bool* prevent_default) override; void OnWillQuit(bool* prevent_default) override; void OnWindowAllClosed() override; - void OnQuit() override; + void OnQuit(int code) override; void OnOpenFile(bool* prevent_default, const std::string& file_path) override; void OnOpenURL(const std::string& url) override; void OnActivate(bool has_visible_windows) override; diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index fc0a78298c..9a7fc6e711 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -34,17 +34,12 @@ app.setAppPath = (path) -> app.getAppPath = -> appPath -appExitCode = undefined -app.exit = (exitCode) -> - appExitCode = exitCode - app._exit(exitCode) - # Map process.exit to app.exit, which quits gracefully. process.exit = app.exit # Emit a process 'exit' event on app quit. -app.on 'quit', -> - process.emit 'exit', appExitCode +app.on 'quit', (event, exitCode) -> + process.emit 'exit', exitCode # Routes the events to webContents. for name in ['login', 'certificate-error', 'select-client-certificate'] diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index fd72f5e4ae..eadd52ac44 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -61,6 +61,10 @@ bool AtomBrowserMainParts::SetExitCode(int code) { return true; } +int AtomBrowserMainParts::GetExitCode() { + return exit_code_ != nullptr ? *exit_code_ : 0; +} + base::Closure AtomBrowserMainParts::RegisterDestructionCallback( const base::Closure& callback) { auto iter = destructors_.insert(destructors_.end(), callback); diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index fbc59f7f81..c1c0c89c67 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -34,6 +34,9 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { // Sets the exit code, will fail if the the message loop is not ready. bool SetExitCode(int code); + // Gets the exit code + int GetExitCode(); + // Register a callback that should be destroyed before JavaScript environment // gets destroyed. // Returns a closure that can be used to remove |callback| from the list. diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index c77f359760..ee00efa071 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -72,7 +72,8 @@ void Browser::Shutdown() { is_shutdown_ = true; is_quiting_ = true; - FOR_EACH_OBSERVER(BrowserObserver, observers_, OnQuit()); + int exitCode = AtomBrowserMainParts::Get()->GetExitCode(); + FOR_EACH_OBSERVER(BrowserObserver, observers_, OnQuit(exitCode)); if (base::MessageLoop::current()) { base::MessageLoop::current()->PostTask( diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index f6d76bc13f..f3ae2e364d 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -24,7 +24,7 @@ class BrowserObserver { virtual void OnWindowAllClosed() {} // The browser is quitting. - virtual void OnQuit() {} + virtual void OnQuit(const int code) {} // The browser has opened a file by double clicking in Finder or dragging the // file to the Dock icon. (OS X only) diff --git a/spec/api-app-spec.coffee b/spec/api-app-spec.coffee index adb64d363e..7b7e5fa483 100644 --- a/spec/api-app-spec.coffee +++ b/spec/api-app-spec.coffee @@ -44,6 +44,7 @@ describe 'app module', -> output = '' appProcess.stdout.on 'data', (data) -> output += data appProcess.on 'close', (code) -> + console.log output assert.notEqual output.indexOf('Exit event with code: 123'), -1 assert.equal code, 123 done() diff --git a/spec/fixtures/api/quit-app/main.js b/spec/fixtures/api/quit-app/main.js index 4bdeb93a5e..85939acd73 100644 --- a/spec/fixtures/api/quit-app/main.js +++ b/spec/fixtures/api/quit-app/main.js @@ -5,5 +5,5 @@ app.on('ready', function () { }) process.on('exit', function (code) { - console.log('Exit event with code: ' + code) + console.log('Exit event with code: ' + JSON.stringify(code, null, 2)) }) diff --git a/spec/static/index.html b/spec/static/index.html index ea86f6ee30..2053ca4135 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -56,6 +56,7 @@ mocha.ui('bdd').reporter(isCi ? 'tap' : 'html'); var query = Mocha.utils.parseQuery(window.location.search || ''); + query.grep = 'app.exit' if (query.grep) mocha.grep(query.grep); if (query.invert) mocha.invert(); From fc724b51e8c206fe10a648bfd73ad0593e277601 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 9 Dec 2015 18:11:38 -0800 Subject: [PATCH 200/411] Move event forwarding back to init --- atom/browser/api/lib/app.coffee | 7 ------- atom/browser/lib/init.coffee | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index 9a7fc6e711..d0ec41c4d2 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -34,13 +34,6 @@ app.setAppPath = (path) -> app.getAppPath = -> appPath -# Map process.exit to app.exit, which quits gracefully. -process.exit = app.exit - -# Emit a process 'exit' event on app quit. -app.on 'quit', (event, exitCode) -> - process.emit 'exit', exitCode - # Routes the events to webContents. for name in ['login', 'certificate-error', 'select-client-certificate'] do (name) -> diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index 9487849e5e..41cd6fb22c 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -51,7 +51,13 @@ process.on 'uncaughtException', (error) -> message = "Uncaught Exception:\n#{stack}" dialog.showErrorBox 'A JavaScript error occurred in the main process', message +# Emit a process 'exit' event on app quit. {app} = require 'electron' +app.on 'quit', (event, exitCode) -> + process.emit 'exit', exitCode + +# Map process.exit to app.exit, which quits gracefully. +process.exit = app.exit # Load the RPC server. require './rpc-server' From c4389ad70f63b1aec9430b225bd084bf8b99833c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 9 Dec 2015 18:12:19 -0800 Subject: [PATCH 201/411] Remove grep value setting --- spec/static/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/static/index.html b/spec/static/index.html index 2053ca4135..ea86f6ee30 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -56,7 +56,6 @@ mocha.ui('bdd').reporter(isCi ? 'tap' : 'html'); var query = Mocha.utils.parseQuery(window.location.search || ''); - query.grep = 'app.exit' if (query.grep) mocha.grep(query.grep); if (query.invert) mocha.invert(); From 3e5caf7e544ca006a7a63660c10e9fbffbded505 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 9 Dec 2015 18:19:51 -0800 Subject: [PATCH 202/411] Get exit code from within App::OnQuit --- atom/browser/api/atom_api_app.cc | 5 +++-- atom/browser/api/atom_api_app.h | 2 +- atom/browser/browser.cc | 3 +-- atom/browser/browser_observer.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 81ec3fefd5..256ecff53e 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -180,8 +180,9 @@ void App::OnWindowAllClosed() { Emit("window-all-closed"); } -void App::OnQuit(const int code) { - Emit("quit", code); +void App::OnQuit() { + int exitCode = AtomBrowserMainParts::Get()->GetExitCode(); + Emit("quit", exitCode); if (process_singleton_.get()) { process_singleton_->Cleanup(); diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index f04a19e67c..a6f99d65e0 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -42,7 +42,7 @@ class App : public AtomBrowserClient::Delegate, void OnBeforeQuit(bool* prevent_default) override; void OnWillQuit(bool* prevent_default) override; void OnWindowAllClosed() override; - void OnQuit(int code) override; + void OnQuit() override; void OnOpenFile(bool* prevent_default, const std::string& file_path) override; void OnOpenURL(const std::string& url) override; void OnActivate(bool has_visible_windows) override; diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index ee00efa071..c77f359760 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -72,8 +72,7 @@ void Browser::Shutdown() { is_shutdown_ = true; is_quiting_ = true; - int exitCode = AtomBrowserMainParts::Get()->GetExitCode(); - FOR_EACH_OBSERVER(BrowserObserver, observers_, OnQuit(exitCode)); + FOR_EACH_OBSERVER(BrowserObserver, observers_, OnQuit()); if (base::MessageLoop::current()) { base::MessageLoop::current()->PostTask( diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index f3ae2e364d..f6d76bc13f 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -24,7 +24,7 @@ class BrowserObserver { virtual void OnWindowAllClosed() {} // The browser is quitting. - virtual void OnQuit(const int code) {} + virtual void OnQuit() {} // The browser has opened a file by double clicking in Finder or dragging the // file to the Dock icon. (OS X only) From ea1479a651bfbe4666e13f4e68e2b1177f3f512e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 9 Dec 2015 18:20:32 -0800 Subject: [PATCH 203/411] Revert comment tweak --- atom/browser/lib/init.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index 41cd6fb22c..c3d56d9c7c 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -51,7 +51,7 @@ process.on 'uncaughtException', (error) -> message = "Uncaught Exception:\n#{stack}" dialog.showErrorBox 'A JavaScript error occurred in the main process', message -# Emit a process 'exit' event on app quit. +# Emit 'exit' event on quit. {app} = require 'electron' app.on 'quit', (event, exitCode) -> process.emit 'exit', exitCode From e1654ee334962c9786756fab314f3b6649a34bb0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 9 Dec 2015 18:22:54 -0800 Subject: [PATCH 204/411] :memo: Document quit event includes exit code --- docs/api/app.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api/app.md b/docs/api/app.md index 3faf4a1f0a..f21f370397 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -64,6 +64,11 @@ the `will-quit` and `window-all-closed` events. ### Event: 'quit' +Returns: + +* `event` Event +* `exitCode` Integer + Emitted when the application is quitting. ### Event: 'open-file' _OS X_ From 516ff0644c9aca8e921c46fc70a928f0d1388966 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 9 Dec 2015 18:23:50 -0800 Subject: [PATCH 205/411] Just include exit code in spec output --- spec/api-app-spec.coffee | 1 - spec/fixtures/api/quit-app/main.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/api-app-spec.coffee b/spec/api-app-spec.coffee index 7b7e5fa483..adb64d363e 100644 --- a/spec/api-app-spec.coffee +++ b/spec/api-app-spec.coffee @@ -44,7 +44,6 @@ describe 'app module', -> output = '' appProcess.stdout.on 'data', (data) -> output += data appProcess.on 'close', (code) -> - console.log output assert.notEqual output.indexOf('Exit event with code: 123'), -1 assert.equal code, 123 done() diff --git a/spec/fixtures/api/quit-app/main.js b/spec/fixtures/api/quit-app/main.js index 85939acd73..4bdeb93a5e 100644 --- a/spec/fixtures/api/quit-app/main.js +++ b/spec/fixtures/api/quit-app/main.js @@ -5,5 +5,5 @@ app.on('ready', function () { }) process.on('exit', function (code) { - console.log('Exit event with code: ' + JSON.stringify(code, null, 2)) + console.log('Exit event with code: ' + code) }) From f5774e3fb24ca403de481e446c3bf68fa78b2c47 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 9 Dec 2015 18:39:59 -0800 Subject: [PATCH 206/411] Exit from a setImmediate callback --- spec/fixtures/api/quit-app/main.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/fixtures/api/quit-app/main.js b/spec/fixtures/api/quit-app/main.js index 4bdeb93a5e..e2f97affe6 100644 --- a/spec/fixtures/api/quit-app/main.js +++ b/spec/fixtures/api/quit-app/main.js @@ -1,7 +1,10 @@ var app = require('electron').app app.on('ready', function () { - app.exit(123) + // This setImmediate call gets the spec passing on Linux + setImmediate(function () { + app.exit(123) + }) }) process.on('exit', function (code) { From 388a18b265de0a6af702ade4c870787c6eac6f25 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 11:22:55 +0800 Subject: [PATCH 207/411] Don't emit `will-quit` event when calling app.exit --- atom/browser/browser.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index c77f359760..7a2c22ea9d 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -55,7 +55,7 @@ void Browser::Exit(int code) { // Must destroy windows before quitting, otherwise bad things can happen. atom::WindowList* window_list = atom::WindowList::GetInstance(); if (window_list->size() == 0) { - NotifyAndShutdown(); + Shutdown(); } else { // Unlike Quit(), we do not ask to close window, but destroy the window // without asking. From 2d940b7df7ca7e842eec94769f451afe88fab542 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 11:27:41 +0800 Subject: [PATCH 208/411] Don't add the "Enter Full Screen" menu item automatically --- atom/browser/mac/atom_application_delegate.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index a18d2fe40f..7662162ab6 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -21,6 +21,9 @@ } - (void)applicationWillFinishLaunching:(NSNotification*)notify { + // Don't add the "Enter Full Screen" menu item automatically. + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"]; + atom::Browser::Get()->WillFinishLaunching(); } From aa8efd90cca62624131f2f53e137dd5312bfaa1c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 12:09:46 +0800 Subject: [PATCH 209/411] Bring back AppVeyor --- appveyor.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..c2b3cca29c --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,29 @@ +# appveyor file +# http://www.appveyor.com/docs/appveyor-yml +version: "{build}" + +init: + - git config --global core.autocrlf input + +environment: + matrix: + - nodejs_version: '4' + +platform: + - x86 + - x64 + +install: + - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) + - cmd: SET PATH=C:\Program Files (x86)\MSBuild\12.0\bin\;%PATH% + - cmd: SET PATH=C:\python27;%PATH% + - cmd: python script/bootstrap.py --dev + - cmd: python script/build.py -c D + +test_script: + - node --version + - npm --version + - cmd: python script/cpplint.py + - cmd: python script/coffeelint.py + +build: off From 966bc8408ed013e01ee7b99d897ae609b8bbf5df Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Thu, 10 Dec 2015 13:21:08 +0900 Subject: [PATCH 210/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/app.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index 095e7b39c6..fea011a1cd 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -68,6 +68,11 @@ Returns: ### Event: 'quit' +Returns: + +* `event` Event +* `exitCode` Integer + 어플리케이션이 종료될 때 발생하는 이벤트입니다. ### Event: 'open-file' _OS X_ From 33db4a6ac1094492b5f9c262ce8dba87b3e62f7b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 12:21:52 +0800 Subject: [PATCH 211/411] Run cibuild --- appveyor.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c2b3cca29c..5e6a1b2b68 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,8 +17,7 @@ install: - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) - cmd: SET PATH=C:\Program Files (x86)\MSBuild\12.0\bin\;%PATH% - cmd: SET PATH=C:\python27;%PATH% - - cmd: python script/bootstrap.py --dev - - cmd: python script/build.py -c D + - cmd: python script/cibuild test_script: - node --version From cb806aa3627f5845a429321ca76ab5a35df05003 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 12:22:46 +0800 Subject: [PATCH 212/411] Use default node 0.12.7 Otherwise we would spend some time reinstalling node. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5e6a1b2b68..85071979a0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ init: environment: matrix: - - nodejs_version: '4' + - nodejs_version: '0.12.7' platform: - x86 From 8c31148f7d17340672d43d945a51f1391f10efcc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 12:35:22 +0800 Subject: [PATCH 213/411] Remove unneeded things --- appveyor.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 85071979a0..37a50bdf35 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,24 +5,11 @@ version: "{build}" init: - git config --global core.autocrlf input -environment: - matrix: - - nodejs_version: '0.12.7' - platform: - x86 - x64 install: - - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) - cmd: SET PATH=C:\Program Files (x86)\MSBuild\12.0\bin\;%PATH% - cmd: SET PATH=C:\python27;%PATH% - cmd: python script/cibuild - -test_script: - - node --version - - npm --version - - cmd: python script/cpplint.py - - cmd: python script/coffeelint.py - -build: off From f11f408420be35454b19a230b5e2cc88de889cbf Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 12:48:10 +0800 Subject: [PATCH 214/411] Do not automatically build everything --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 37a50bdf35..9f4cde8aa5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,3 +13,5 @@ install: - cmd: SET PATH=C:\Program Files (x86)\MSBuild\12.0\bin\;%PATH% - cmd: SET PATH=C:\python27;%PATH% - cmd: python script/cibuild + +build: off From 5cf369086cd98289f09360d72bacc4d81f1c5483 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 12:58:29 +0800 Subject: [PATCH 215/411] Also disable test phase --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 9f4cde8aa5..a3fd519255 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,4 +14,6 @@ install: - cmd: SET PATH=C:\python27;%PATH% - cmd: python script/cibuild +# disable build and test pahses build: off +test: off From bf5c3bc9ddc03929c4009fefc445e764ed7eac74 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 13:18:23 +0800 Subject: [PATCH 216/411] Avoid duplicate building for PR --- appveyor.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index a3fd519255..0fa0c0d9bd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,10 @@ install: - cmd: SET PATH=C:\python27;%PATH% - cmd: python script/cibuild +branches: + only: + - master + # disable build and test pahses build: off test: off From b42695a0b5e6dec92fe86b8a7f40ec0d0b7c8db7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 10 Dec 2015 14:37:09 +0800 Subject: [PATCH 217/411] win: Don't attach console for third party terms Close #3692. --- atom/app/atom_main.cc | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index 5b5df448df..1ffab8d922 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -7,10 +7,6 @@ #include #if defined(OS_WIN) -#include -#include -#include - #include #include #include @@ -56,12 +52,6 @@ bool IsRunAsNode() { } #if defined(OS_WIN) -bool IsCygwin() { - std::string os; - scoped_ptr env(base::Environment::Create()); - return env->GetVar("OS", &os) && os == "cygwin"; -} - // Win8.1 supports monitor-specific DPI scaling. bool SetProcessDpiAwarenessWrapper(PROCESS_DPI_AWARENESS value) { typedef HRESULT(WINAPI *SetProcessDpiAwarenessPtr)(PROCESS_DPI_AWARENESS); @@ -109,7 +99,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { wchar_t** wargv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); // Make output work in console if we are not in cygiwn. - if (!IsCygwin() && !IsEnvSet("ELECTRON_NO_ATTACH_CONSOLE")) { + if (!IsEnvSet("TERM") && !IsEnvSet("ELECTRON_NO_ATTACH_CONSOLE")) { AttachConsole(ATTACH_PARENT_PROCESS); FILE* dontcare; From 5f75bc36d91d2168382921686e7d8d7c3d39acb3 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Thu, 10 Dec 2015 23:19:50 +0900 Subject: [PATCH 218/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/protocol.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs-translations/ko-KR/api/protocol.md b/docs-translations/ko-KR/api/protocol.md index a85ac1428c..f6f2ac65c9 100644 --- a/docs-translations/ko-KR/api/protocol.md +++ b/docs-translations/ko-KR/api/protocol.md @@ -35,6 +35,10 @@ app.on('ready', function() { 표준 `scheme`의 형식은 RFC 3986 [일반 URI 구문](https://tools.ietf.org/html/rfc3986#section-3) 표준을 따릅니다. 이 형식은 `file:`과 `filesystem:`을 포함합니다. +### `protocol.registerServiceWorkerSchemes(schemes)` + +* `schemes` Array - 등록될 서비스 워커를 조작할 커스텀 스키마 + ### `protocol.registerFileProtocol(scheme, handler[, completion])` * `scheme` String @@ -97,12 +101,17 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `completion` Function (optional) `scheme`에 HTTP 요청을 응답으로 보내는 프로토콜을 등록합니다. 반드시 `url`, -`method`, `referrer`, `session` 속성을 포함하는 객체를 인자에 포함하여 `callback`을 -호출해야 합니다. +`method`, `referrer`, `uploadData` 그리고 `session` 속성을 포함하는 객체를 인자에 +포함하여 `callback`을 호출해야 합니다. 기본적으로 HTTP 요청은 현재 세션을 재사용합니다. 만약 서로 다른 세션에 요청을 보내고 싶으면 `session`을 `null`로 지정해야 합니다. +POST 요청은 반드시 `uploadData` 객체가 제공되어야 합니다. +* `uploadData` object + * `contentType` String - 컨텐츠의 MIME 타입. + * `data` String - 전송할 컨텐츠. + ### `protocol.unregisterProtocol(scheme[, completion])` * `scheme` String From a42fa5d5c2b3b71a3f589bdf254ac25fe4885d70 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 10:33:18 -0800 Subject: [PATCH 219/411] Parse spec arguments using yargs library --- spec/api-browser-window-spec.coffee | 2 +- spec/api-crash-reporter-spec.coffee | 2 +- spec/package.json | 5 +++-- spec/static/index.html | 3 +-- spec/static/main.js | 3 ++- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index 86beb1fdc6..38c8217041 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -8,7 +8,7 @@ os = require 'os' {remote, screen} = require 'electron' {ipcMain, BrowserWindow} = remote.require 'electron' -isCI = remote.process.argv[2] == '--ci' +isCI = remote.getGlobal('isCi') describe 'browser-window module', -> fixtures = path.resolve __dirname, 'fixtures' diff --git a/spec/api-crash-reporter-spec.coffee b/spec/api-crash-reporter-spec.coffee index 676dbf9d69..789ba6e01f 100644 --- a/spec/api-crash-reporter-spec.coffee +++ b/spec/api-crash-reporter-spec.coffee @@ -18,7 +18,7 @@ describe 'crash-reporter module', -> return if process.mas # The crash-reporter test is not reliable on CI machine. - isCI = remote.process.argv[2] == '--ci' + isCI = remote.getGlobal('isCi') return if isCI it 'should send minidump when renderer crashes', (done) -> diff --git a/spec/package.json b/spec/package.json index 38bd837967..79e7d954eb 100644 --- a/spec/package.json +++ b/spec/package.json @@ -5,13 +5,14 @@ "version": "0.1.0", "devDependencies": { "basic-auth": "^1.0.0", - "multiparty": "4.1.2", "graceful-fs": "3.0.5", "mocha": "2.1.0", + "multiparty": "4.1.2", "q": "0.9.7", "temp": "0.8.1", "walkdir": "0.0.7", - "ws": "0.7.2" + "ws": "0.7.2", + "yargs": "^3.31.0" }, "optionalDependencies": { "ffi": "2.0.0", diff --git a/spec/static/index.html b/spec/static/index.html index ea86f6ee30..f958e1b7ed 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -18,8 +18,7 @@ var remote = electron.remote; var ipcRenderer = electron.ipcRenderer; - var argv = remote.process.argv; - var isCi = argv[2] == '--ci'; + var isCi = remote.getGlobal('isCi') if (!isCi) { var win = remote.getCurrentWindow(); diff --git a/spec/static/main.js b/spec/static/main.js index be3690cd9e..2a637274a2 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -42,7 +42,8 @@ ipcMain.on('echo', function(event, msg) { event.returnValue = msg; }); -if (process.argv[2] == '--ci') { +global.isCi = !!argv.ci; +if (global.isCi) { process.removeAllListeners('uncaughtException'); process.on('uncaughtException', function(error) { console.error(error, error.stack); From 69c0a33c859a0c0424b593c7c203e1a48d53eadc Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 10:33:43 -0800 Subject: [PATCH 220/411] Pass mocha grep command line option through to spec app --- spec/static/main.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/static/main.js b/spec/static/main.js index 2a637274a2..e83e79c193 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -5,6 +5,12 @@ const dialog = electron.dialog; const BrowserWindow = electron.BrowserWindow; const path = require('path'); +const url = require('url'); + +var argv = require('yargs') + .boolean('ci') + .string('g').alias('g', 'grep') + .argv; var window = null; process.port = 0; // will be used by crash-reporter spec. @@ -68,7 +74,13 @@ app.on('ready', function() { javascript: true // Test whether web-preferences crashes. }, }); - window.loadURL('file://' + __dirname + '/index.html'); + window.loadURL(url.format({ + pathname: __dirname + '/index.html', + protocol: 'file', + query: { + grep: argv.grep + } + })); window.on('unresponsive', function() { var chosen = dialog.showMessageBox(window, { type: 'warning', From 065887e712334bf3ef1679f4602093314da32c2a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 10:35:51 -0800 Subject: [PATCH 221/411] Pass through mocha invert option from test CLI --- spec/static/main.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/static/main.js b/spec/static/main.js index e83e79c193..4de123830e 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -10,6 +10,7 @@ const url = require('url'); var argv = require('yargs') .boolean('ci') .string('g').alias('g', 'grep') + .boolean('i').alias('i', 'invert') .argv; var window = null; @@ -78,7 +79,8 @@ app.on('ready', function() { pathname: __dirname + '/index.html', protocol: 'file', query: { - grep: argv.grep + grep: argv.grep, + invert: argv.invert ? 'true': '' } })); window.on('unresponsive', function() { From 49b6e5bcacb974919dad4b8ab5a2609e3b915338 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 10:44:11 -0800 Subject: [PATCH 222/411] Add npm test script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a5d56e3a99..ea29419925 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "private": true, "scripts": { - "preinstall": "node -e 'process.exit(0)'" + "preinstall": "node -e 'process.exit(0)'", + "test": "python ./script/test.py" } } From 658accab94f3c9682ae381b3b56ab99e9d67660a Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 10 Dec 2015 00:46:03 +0530 Subject: [PATCH 223/411] fix pointer usage --- atom/browser/api/atom_api_web_request.cc | 8 +- atom/browser/api/atom_api_web_request.h | 3 +- atom/browser/net/atom_network_delegate.cc | 236 ++++++++++-------- atom/browser/net/atom_network_delegate.h | 23 +- .../native_mate_converters/net_converter.cc | 8 +- .../native_mate_converters/value_converter.cc | 7 - .../native_mate_converters/value_converter.h | 6 - chromium_src/extensions/common/url_pattern.cc | 2 +- docs/api/session.md | 8 +- 9 files changed, 150 insertions(+), 151 deletions(-) diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc index c45cd900ab..07c0576977 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -30,8 +30,8 @@ template void WebRequest::SetListener(mate::Arguments* args) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - base::DictionaryValue* filter = new base::DictionaryValue(); - args->GetNext(filter); + scoped_ptr filter(new base::DictionaryValue()); + args->GetNext(filter.get()); AtomNetworkDelegate::Listener callback; if (!args->GetNext(&callback)) { args->ThrowError("Must pass null or a function"); @@ -42,7 +42,7 @@ void WebRequest::SetListener(mate::Arguments* args) { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&AtomNetworkDelegate::SetListenerInIO, base::Unretained(delegate), - type, filter, callback)); + type, base::Passed(&filter), callback)); } // static @@ -54,7 +54,7 @@ mate::Handle WebRequest::Create( // static void WebRequest::BuildPrototype(v8::Isolate* isolate, - v8::Local prototype) { + v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("onBeforeRequest", &WebRequest::SetListener< diff --git a/atom/browser/api/atom_api_web_request.h b/atom/browser/api/atom_api_web_request.h index 053bc1bec9..597bb80f7c 100644 --- a/atom/browser/api/atom_api_web_request.h +++ b/atom/browser/api/atom_api_web_request.h @@ -9,7 +9,6 @@ #include "atom/browser/api/trackable_object.h" #include "atom/browser/net/atom_network_delegate.h" -#include "base/callback.h" #include "native_mate/arguments.h" #include "native_mate/handle.h" @@ -22,7 +21,7 @@ namespace api { class WebRequest : public mate::TrackableObject { public: static mate::Handle Create(v8::Isolate* isolate, - AtomBrowserContext* browser_context); + AtomBrowserContext* browser_context); // mate::TrackableObject: static void BuildPrototype(v8::Isolate* isolate, diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index d4c83996b9..c7a54ee664 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -18,9 +18,9 @@ namespace { std::string ResourceTypeToString(content::ResourceType type) { switch (type) { case content::RESOURCE_TYPE_MAIN_FRAME: - return "main_frame"; + return "mainFrame"; case content::RESOURCE_TYPE_SUB_FRAME: - return "sub_frame"; + return "subFrame"; case content::RESOURCE_TYPE_STYLESHEET: return "stylesheet"; case content::RESOURCE_TYPE_SCRIPT: @@ -30,12 +30,18 @@ std::string ResourceTypeToString(content::ResourceType type) { case content::RESOURCE_TYPE_OBJECT: return "object"; case content::RESOURCE_TYPE_XHR: - return "xmlhttprequest"; + return "xhr"; default: return "other"; } } +AtomNetworkDelegate::BlockingResponse RunListener( + const AtomNetworkDelegate::Listener& callback, + scoped_ptr details) { + return callback.Run(*(details.get())); +} + bool MatchesFilterCondition( net::URLRequest* request, const AtomNetworkDelegate::ListenerInfo& info) { @@ -50,8 +56,8 @@ bool MatchesFilterCondition( return true; } -base::DictionaryValue* ExtractRequestInfo(net::URLRequest* request) { - base::DictionaryValue* dict = new base::DictionaryValue(); +scoped_ptr ExtractRequestInfo(net::URLRequest* request) { + scoped_ptr dict(new base::DictionaryValue()); dict->SetInteger("id", request->identifier()); dict->SetString("url", request->url().spec()); dict->SetString("method", request->method()); @@ -62,21 +68,21 @@ base::DictionaryValue* ExtractRequestInfo(net::URLRequest* request) { dict->SetString("resourceType", ResourceTypeToString(resourceType)); dict->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); - return dict; + return dict.Pass(); } -base::DictionaryValue* GetRequestHeadersDict( +scoped_ptr GetRequestHeadersDict( const net::HttpRequestHeaders& headers) { - base::DictionaryValue* header_dict = new base::DictionaryValue(); + scoped_ptr header_dict(new base::DictionaryValue()); net::HttpRequestHeaders::Iterator it(headers); while (it.GetNext()) header_dict->SetString(it.name(), it.value()); - return header_dict; + return header_dict.Pass(); } -base::DictionaryValue* GetResponseHeadersDict( +scoped_ptr GetResponseHeadersDict( const net::HttpResponseHeaders* headers) { - base::DictionaryValue* header_dict = new base::DictionaryValue(); + scoped_ptr header_dict(new base::DictionaryValue()); if (headers) { void* iter = nullptr; std::string key; @@ -84,25 +90,25 @@ base::DictionaryValue* GetResponseHeadersDict( while (headers->EnumerateHeaderLines(&iter, &key, &value)) header_dict->SetString(key, value); } - return header_dict; + return header_dict.Pass(); } void OnBeforeURLRequestResponse( const net::CompletionCallback& callback, GURL* new_url, const AtomNetworkDelegate::BlockingResponse& result) { - if (!result.redirectURL.is_empty()) - *new_url = result.redirectURL; - callback.Run(result.Cancel()); + if (!result.redirect_url.is_empty()) + *new_url = result.redirect_url; + callback.Run(result.Code()); } void OnBeforeSendHeadersResponse( const net::CompletionCallback& callback, net::HttpRequestHeaders* headers, const AtomNetworkDelegate::BlockingResponse& result) { - if (!result.requestHeaders.IsEmpty()) - *headers = result.requestHeaders; - callback.Run(result.Cancel()); + if (!result.request_headers.IsEmpty()) + *headers = result.request_headers; + callback.Run(result.Code()); } void OnHeadersReceivedResponse( @@ -110,26 +116,22 @@ void OnHeadersReceivedResponse( const net::HttpResponseHeaders* original_response_headers, scoped_refptr* override_response_headers, const AtomNetworkDelegate::BlockingResponse& result) { - if (result.responseHeaders.get()) { + if (result.response_headers.get()) { *override_response_headers = new net::HttpResponseHeaders( original_response_headers->raw_headers()); void* iter = nullptr; std::string key; std::string value; - while (result.responseHeaders->EnumerateHeaderLines(&iter, &key, &value)) { + while (result.response_headers->EnumerateHeaderLines(&iter, &key, &value)) { (*override_response_headers)->RemoveHeader(key); (*override_response_headers)->AddHeader(key + ": " + value); } } - callback.Run(result.Cancel()); + callback.Run(result.Code()); } } // namespace -// static -std::map -AtomNetworkDelegate::event_listener_map_; - AtomNetworkDelegate::AtomNetworkDelegate() { } @@ -138,8 +140,13 @@ AtomNetworkDelegate::~AtomNetworkDelegate() { void AtomNetworkDelegate::SetListenerInIO( EventTypes type, - const base::DictionaryValue* filter, + scoped_ptr filter, const Listener& callback) { + if (callback.is_null()) { + event_listener_map_.erase(type); + return; + } + ListenerInfo info; info.callback = callback; @@ -162,69 +169,71 @@ int AtomNetworkDelegate::OnBeforeURLRequest( net::URLRequest* request, const net::CompletionCallback& callback, GURL* new_url) { - brightray::NetworkDelegate::OnBeforeURLRequest(request, callback, new_url); + auto listener_info = event_listener_map_.find(kOnBeforeRequest); + if (listener_info != event_listener_map_.end()) { + if (!MatchesFilterCondition(request, listener_info->second)) + return net::OK; - auto listener_info = event_listener_map_[kOnBeforeRequest]; - - if (!MatchesFilterCondition(request, listener_info)) - return net::OK; - - if (!listener_info.callback.is_null()) { - auto wrapped_callback = listener_info.callback; + auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(wrapped_callback, details), + base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), base::Bind(&OnBeforeURLRequestResponse, callback, new_url)); return net::ERR_IO_PENDING; } - return net::OK; + return brightray::NetworkDelegate::OnBeforeURLRequest(request, + callback, + new_url); } int AtomNetworkDelegate::OnBeforeSendHeaders( net::URLRequest* request, const net::CompletionCallback& callback, net::HttpRequestHeaders* headers) { - auto listener_info = event_listener_map_[kOnBeforeSendHeaders]; + auto listener_info = event_listener_map_.find(kOnBeforeSendHeaders); + if (listener_info != event_listener_map_.end()) { + if (!MatchesFilterCondition(request, listener_info->second)) + return net::OK; - if (!MatchesFilterCondition(request, listener_info)) - return net::OK; - - if (!listener_info.callback.is_null()) { - auto wrapped_callback = listener_info.callback; + auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); - details->Set("requestHeaders", GetRequestHeadersDict(*headers)); + details->Set("requestHeaders", GetRequestHeadersDict(*headers).release()); BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(wrapped_callback, details), + base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), base::Bind(&OnBeforeSendHeadersResponse, callback, headers)); return net::ERR_IO_PENDING; } - return net::OK; + return brightray::NetworkDelegate::OnBeforeSendHeaders(request, + callback, + headers); } void AtomNetworkDelegate::OnSendHeaders( net::URLRequest* request, const net::HttpRequestHeaders& headers) { - auto listener_info = event_listener_map_[kOnSendHeaders]; + auto listener_info = event_listener_map_.find(kOnSendHeaders); + if (listener_info != event_listener_map_.end()) { + if (!MatchesFilterCondition(request, listener_info->second)) + return; - if (!MatchesFilterCondition(request, listener_info)) - return; - - if (!listener_info.callback.is_null()) { - auto wrapped_callback = listener_info.callback; + auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); - details->Set("requestHeaders", GetRequestHeadersDict(headers)); + details->Set("requestHeaders", GetRequestHeadersDict(headers).release()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(wrapped_callback), - details)); + base::Bind(base::IgnoreResult(&RunListener), + wrapped_callback, + base::Passed(&details))); + } else { + brightray::NetworkDelegate::OnSendHeaders(request, headers); } } @@ -234,23 +243,22 @@ int AtomNetworkDelegate::OnHeadersReceived( const net::HttpResponseHeaders* original_response_headers, scoped_refptr* override_response_headers, GURL* allowed_unsafe_redirect_url) { - auto listener_info = event_listener_map_[kOnHeadersReceived]; + auto listener_info = event_listener_map_.find(kOnHeadersReceived); + if (listener_info != event_listener_map_.end()) { + if (!MatchesFilterCondition(request, listener_info->second)) + return net::OK; - if (!MatchesFilterCondition(request, listener_info)) - return net::OK; - - if (!listener_info.callback.is_null()) { - auto wrapped_callback = listener_info.callback; + auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); details->SetString("statusLine", original_response_headers->GetStatusLine()); details->SetInteger("statusCode", original_response_headers->response_code()); details->Set("responseHeaders", - GetResponseHeadersDict(original_response_headers)); + GetResponseHeadersDict(original_response_headers).release()); BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(wrapped_callback, details), + base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), base::Bind(&OnHeadersReceivedResponse, callback, original_response_headers, @@ -259,18 +267,19 @@ int AtomNetworkDelegate::OnHeadersReceived( return net::ERR_IO_PENDING; } - return net::OK; + return brightray::NetworkDelegate::OnHeadersReceived( + request, callback, original_response_headers, override_response_headers, + allowed_unsafe_redirect_url); } void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, const GURL& new_location) { - auto listener_info = event_listener_map_[kOnBeforeRedirect]; + auto listener_info = event_listener_map_.find(kOnBeforeRedirect); + if (listener_info != event_listener_map_.end()) { + if (!MatchesFilterCondition(request, listener_info->second)) + return; - if (!MatchesFilterCondition(request, listener_info)) - return; - - if (!listener_info.callback.is_null()) { - auto wrapped_callback = listener_info.callback; + auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); details->SetString("redirectURL", new_location.spec()); details->SetInteger("statusCode", request->GetResponseCode()); @@ -279,28 +288,30 @@ void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, details->SetString("ip", ip); details->SetBoolean("fromCache", request->was_cached()); details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers())); + GetResponseHeadersDict(request->response_headers()).release()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(wrapped_callback), - details)); + base::Bind(base::IgnoreResult(&RunListener), + wrapped_callback, + base::Passed(&details))); + } else { + brightray::NetworkDelegate::OnBeforeRedirect(request, new_location); } } void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { - if (request->status().status() != net::URLRequestStatus::SUCCESS) - return; + auto listener_info = event_listener_map_.find(kOnResponseStarted); + if (listener_info != event_listener_map_.end()) { + if (request->status().status() != net::URLRequestStatus::SUCCESS) + return; - auto listener_info = event_listener_map_[kOnResponseStarted]; + if (!MatchesFilterCondition(request, listener_info->second)) + return; - if (!MatchesFilterCondition(request, listener_info)) - return; - - if (!listener_info.callback.is_null()) { - auto wrapped_callback = listener_info.callback; + auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers())); + GetResponseHeadersDict(request->response_headers()).release()); details->SetBoolean("fromCache", request->was_cached()); auto response_headers = request->response_headers(); @@ -312,34 +323,36 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { response_headers->GetStatusLine() : std::string()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(wrapped_callback), - details)); + base::Bind(base::IgnoreResult(&RunListener), + wrapped_callback, + base::Passed(&details))); + } else { + brightray::NetworkDelegate::OnResponseStarted(request); } } void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { - if (request->status().status() == net::URLRequestStatus::FAILED || - request->status().status() == net::URLRequestStatus::CANCELED) { - OnErrorOccurred(request); - return; - } else { - bool is_redirect = request->response_headers() && - net::HttpResponseHeaders::IsRedirectResponseCode( - request->response_headers()->response_code()); - if (is_redirect) + auto listener_info = event_listener_map_.find(kOnCompleted); + if (listener_info != event_listener_map_.end()) { + if (request->status().status() == net::URLRequestStatus::FAILED || + request->status().status() == net::URLRequestStatus::CANCELED) { + OnErrorOccurred(request); return; - } + } else { + bool is_redirect = request->response_headers() && + net::HttpResponseHeaders::IsRedirectResponseCode( + request->response_headers()->response_code()); + if (is_redirect) + return; + } - auto listener_info = event_listener_map_[kOnCompleted]; + if (!MatchesFilterCondition(request, listener_info->second)) + return; - if (!MatchesFilterCondition(request, listener_info)) - return; - - if (!listener_info.callback.is_null()) { - auto wrapped_callback = listener_info.callback; + auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers())); + GetResponseHeadersDict(request->response_headers()).release()); details->SetBoolean("fromCache", request->was_cached()); auto response_headers = request->response_headers(); @@ -351,26 +364,29 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { response_headers->GetStatusLine() : std::string()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(wrapped_callback), - details)); + base::Bind(base::IgnoreResult(&RunListener), + wrapped_callback, + base::Passed(&details))); + } else { + brightray::NetworkDelegate::OnCompleted(request, started); } } void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { - auto listener_info = event_listener_map_[kOnErrorOccurred]; + auto listener_info = event_listener_map_.find(kOnErrorOccurred); + if (listener_info != event_listener_map_.end()) { + if (!MatchesFilterCondition(request, listener_info->second)) + return; - if (!MatchesFilterCondition(request, listener_info)) - return; - - if (!listener_info.callback.is_null()) { - auto wrapped_callback = listener_info.callback; + auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); details->SetBoolean("fromCache", request->was_cached()); details->SetString("error", net::ErrorToString(request->status().error())); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(wrapped_callback), - details)); + base::Bind(base::IgnoreResult(&RunListener), + wrapped_callback, + base::Passed(&details))); } } diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index c48d252b2b..9f78cc8d92 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -27,7 +27,7 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { public: struct BlockingResponse; using Listener = - base::Callback; + base::Callback; enum EventTypes { kInvalidEvent = 0, @@ -42,35 +42,34 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { }; struct ListenerInfo { - ListenerInfo() {} - ~ListenerInfo() {} - std::set url_patterns; AtomNetworkDelegate::Listener callback; }; struct BlockingResponse { - BlockingResponse() {} + BlockingResponse() : cancel(false) {} ~BlockingResponse() {} - int Cancel() const { + int Code() const { return cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK; } bool cancel; - GURL redirectURL; - net::HttpRequestHeaders requestHeaders; - scoped_refptr responseHeaders; + GURL redirect_url; + net::HttpRequestHeaders request_headers; + scoped_refptr response_headers; }; AtomNetworkDelegate(); ~AtomNetworkDelegate() override; void SetListenerInIO(EventTypes type, - const base::DictionaryValue* filter, + scoped_ptr filter, const Listener& callback); protected: + void OnErrorOccurred(net::URLRequest* request); + // net::NetworkDelegate: int OnBeforeURLRequest(net::URLRequest* request, const net::CompletionCallback& callback, @@ -91,10 +90,8 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { void OnResponseStarted(net::URLRequest* request) override; void OnCompleted(net::URLRequest* request, bool started) override; - void OnErrorOccurred(net::URLRequest* request); - private: - static std::map event_listener_map_; + std::map event_listener_map_; DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); }; diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 2086e84d58..876b22536a 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -62,7 +62,7 @@ bool Converter::FromV8( return false; if (!dict.Get("cancel", &(out->cancel))) return false; - dict.Get("redirectURL", &(out->redirectURL)); + dict.Get("redirectURL", &(out->redirect_url)); base::DictionaryValue request_headers; if (dict.Get("requestHeaders", &request_headers)) { for (base::DictionaryValue::Iterator it(request_headers); @@ -70,18 +70,18 @@ bool Converter::FromV8( it.Advance()) { std::string value; CHECK(it.value().GetAsString(&value)); - out->requestHeaders.SetHeader(it.key(), value); + out->request_headers.SetHeader(it.key(), value); } } base::DictionaryValue response_headers; if (dict.Get("responseHeaders", &response_headers)) { - out->responseHeaders = new net::HttpResponseHeaders(""); + out->response_headers = new net::HttpResponseHeaders(""); for (base::DictionaryValue::Iterator it(response_headers); !it.IsAtEnd(); it.Advance()) { std::string value; CHECK(it.value().GetAsString(&value)); - out->responseHeaders->AddHeader(it.key() + " : " + value); + out->response_headers->AddHeader(it.key() + " : " + value); } } return true; diff --git a/atom/common/native_mate_converters/value_converter.cc b/atom/common/native_mate_converters/value_converter.cc index 431f11fbbd..c9c1a861ba 100644 --- a/atom/common/native_mate_converters/value_converter.cc +++ b/atom/common/native_mate_converters/value_converter.cc @@ -30,13 +30,6 @@ v8::Local Converter::ToV8( return converter->ToV8Value(&val, isolate->GetCurrentContext()); } -v8::Local Converter::ToV8( - v8::Isolate* isolate, - const base::DictionaryValue* val) { - scoped_ptr converter(new atom::V8ValueConverter); - return converter->ToV8Value(val, isolate->GetCurrentContext()); -} - bool Converter::FromV8(v8::Isolate* isolate, v8::Local val, base::ListValue* out) { diff --git a/atom/common/native_mate_converters/value_converter.h b/atom/common/native_mate_converters/value_converter.h index f660fd3f18..013dd99cc7 100644 --- a/atom/common/native_mate_converters/value_converter.h +++ b/atom/common/native_mate_converters/value_converter.h @@ -23,12 +23,6 @@ struct Converter { const base::DictionaryValue& val); }; -template<> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const base::DictionaryValue* val); -}; - template<> struct Converter { static bool FromV8(v8::Isolate* isolate, diff --git a/chromium_src/extensions/common/url_pattern.cc b/chromium_src/extensions/common/url_pattern.cc index a6e51aa675..4303689fe8 100644 --- a/chromium_src/extensions/common/url_pattern.cc +++ b/chromium_src/extensions/common/url_pattern.cc @@ -250,7 +250,7 @@ URLPattern::ParseResult URLPattern::Parse(const std::string& pattern) { host_components.erase(host_components.begin(), host_components.begin() + 1); } - host_ = JoinString(host_components, "."); + host_ = base::JoinString(host_components, "."); path_start_pos = host_end_pos; } diff --git a/docs/api/session.md b/docs/api/session.md index b16a9b1f0f..9429bc3c2d 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -367,7 +367,7 @@ Fired just before a request is going to be sent to the server, modifications of * `filter` Object * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. + will be filtered out. * `listener` Function * `details` Object * `id` String - Request id. @@ -390,7 +390,7 @@ Fired when HTTP response headers of a request have been received. Should return * `filter` Object * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. + will be filtered out. * `listener` Function * `details` Object * `id` String - Request id. @@ -410,7 +410,7 @@ status line and response headers are available. * `filter` Object * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. + will be filtered out. * `listener` Function * `details` Object * `id` String - Request id. @@ -420,7 +420,7 @@ status line and response headers are available. * `timestamp` Double * `redirectURL` String * `statusCode` Integer - * `ip` String - The server IP address that the request was actually sent to. + * `ip` String **optional** - The server IP address that the request was actually sent to. * `fromCache` Boolean * `responseHeaders` Object From 053c77d6f421051a2fbb3e8749a088dc5167b5c0 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 11 Dec 2015 00:27:15 +0530 Subject: [PATCH 224/411] default session should be persistent --- atom/browser/api/lib/session.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/lib/session.coffee b/atom/browser/api/lib/session.coffee index 6abfe7925e..7fc3c2df6f 100644 --- a/atom/browser/api/lib/session.coffee +++ b/atom/browser/api/lib/session.coffee @@ -14,7 +14,7 @@ exports.fromPartition = (partition='') -> # Returns the default session. Object.defineProperty exports, 'defaultSession', enumerable: true - get: -> exports.fromPartition '' + get: -> exports.fromPartition 'persist:' wrapSession = (session) -> # session is an EventEmitter. From afd736d9f91a3032dd46d47b20da76e0c419ae84 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 15:51:15 -0800 Subject: [PATCH 225/411] Guard against null guest or embedder Uncaught exceptions would occur when these were null and the target origin was '*' --- atom/browser/lib/guest-window-manager.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 53bbb735b0..763162a5de 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -76,12 +76,12 @@ ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, met ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin) -> guestContents = BrowserWindow.fromId(guestId)?.webContents if guestContents?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - guestContents.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, targetOrigin + guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, targetOrigin ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> embedder = v8Util.getHiddenValue event.sender, 'embedder' if embedder?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - embedder.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin + embedder?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?.webContents?[method] args... From 2cb752e3deb95bcdb84eac449b5943428f8501b4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 16:49:23 -0800 Subject: [PATCH 226/411] Add failing spec for event origin --- spec/chromium-spec.coffee | 13 +++++++++++++ spec/fixtures/pages/window-open-postMessage.html | 9 +++++++++ 2 files changed, 22 insertions(+) create mode 100644 spec/fixtures/pages/window-open-postMessage.html diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 211de34684..2b768f670b 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -115,6 +115,19 @@ describe 'chromium feature', -> window.addEventListener 'message', listener b = window.open url, '', 'show=no' + describe 'window.postMessage', -> + it 'sets the origin correctly', (done) -> + listener = (event) -> + window.removeEventListener 'message', listener + b.close() + assert.equal event.data, 'file://testing' + assert.equal event.origin, 'file://' + done() + window.addEventListener 'message', listener + b = window.open "file://#{fixtures}/pages/window-open-postMessage.html", '', 'show=no' + BrowserWindow.fromId(b.guestId).webContents.once 'did-finish-load', -> + b.postMessage('testing', '*') + describe 'window.opener.postMessage', -> it 'sets source and origin correctly', (done) -> listener = (event) -> diff --git a/spec/fixtures/pages/window-open-postMessage.html b/spec/fixtures/pages/window-open-postMessage.html new file mode 100644 index 0000000000..e547fa2a60 --- /dev/null +++ b/spec/fixtures/pages/window-open-postMessage.html @@ -0,0 +1,9 @@ + + + + + From 9bc7c62588bc831c1779c0ed61290f2a3e8c7707 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 16:52:39 -0800 Subject: [PATCH 227/411] Use source origin in window.postMessage event --- atom/browser/lib/guest-window-manager.coffee | 4 ++-- atom/renderer/lib/override.coffee | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 763162a5de..9ed75c225f 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -73,10 +73,10 @@ ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, guestId) -> ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?[method] args... -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> guestContents = BrowserWindow.fromId(guestId)?.webContents if guestContents?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, targetOrigin + guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> embedder = v8Util.getHiddenValue event.sender, 'embedder' diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index cb4fb8fbac..9da84fb9a4 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -23,7 +23,7 @@ class BrowserWindowProxy ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', @guestId, 'blur' postMessage: (message, targetOrigin='*') -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', @guestId, message, targetOrigin + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', @guestId, message, targetOrigin, location.origin eval: (args...) -> ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', @guestId, 'executeJavaScript', args... From dd01466f3c17c4644367309170b14248831551a0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 17:02:35 -0800 Subject: [PATCH 228/411] sequnce -> sequence --- spec/chromium-spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 211de34684..ecb5ff1ad9 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -210,7 +210,7 @@ describe 'chromium feature', -> setImmediate -> called = false Promise.resolve().then -> - done(if called then undefined else new Error('wrong sequnce')) + done(if called then undefined else new Error('wrong sequence')) document.createElement 'x-element' called = true @@ -224,6 +224,6 @@ describe 'chromium feature', -> remote.getGlobal('setImmediate') -> called = false Promise.resolve().then -> - done(if called then undefined else new Error('wrong sequnce')) + done(if called then undefined else new Error('wrong sequence')) document.createElement 'y-element' called = true From 7dfca3c29311472770f3c6e3431d8482adb7d4e6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 17:04:36 -0800 Subject: [PATCH 229/411] Expect event source and opened window to be equal --- spec/chromium-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index ecb5ff1ad9..82eef9e38a 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -120,7 +120,7 @@ describe 'chromium feature', -> listener = (event) -> window.removeEventListener 'message', listener b.close() - assert.equal event.source.guestId, b.guestId + assert.equal event.source, b assert.equal event.origin, 'file://' done() window.addEventListener 'message', listener From 49ca7509c7cae2b3c0983cca3f4659e711ce7b86 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 17:20:10 -0800 Subject: [PATCH 230/411] Reuse BrowserWindowProxy instances --- atom/renderer/lib/override.coffee | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index cb4fb8fbac..828c48c061 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -8,9 +8,18 @@ resolveURL = (url) -> # Window object returned by "window.open". class BrowserWindowProxy + @proxies: {} + + @getOrCreate: (guestId) -> + @proxies[guestId] ?= new BrowserWindowProxy(guestId) + + @removeWindow: (guestId) -> + delete @proxies[guestId] + constructor: (@guestId) -> @closed = false ipcRenderer.once "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{@guestId}", => + BrowserWindowProxy.removeWindow(@guestId) @closed = true close: -> @@ -60,7 +69,7 @@ window.open = (url, frameName='', features='') -> guestId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options if guestId - new BrowserWindowProxy(guestId) + BrowserWindowProxy.getOrCreate(guestId) else null @@ -96,7 +105,7 @@ ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, guestId, message, event.initEvent 'message', false, false event.data = message event.origin = sourceOrigin - event.source = new BrowserWindowProxy(guestId) + event.source = BrowserWindowProxy.getOrCreate(guestId) window.dispatchEvent event # Forward history operations to browser. From 8401ece5371ba2ff0a740cb3eb0872aca98a9be0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 10 Dec 2015 17:22:05 -0800 Subject: [PATCH 231/411] removeWindow -> remove --- atom/renderer/lib/override.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 828c48c061..c8b0b2a468 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -13,13 +13,13 @@ class BrowserWindowProxy @getOrCreate: (guestId) -> @proxies[guestId] ?= new BrowserWindowProxy(guestId) - @removeWindow: (guestId) -> + @remove: (guestId) -> delete @proxies[guestId] constructor: (@guestId) -> @closed = false ipcRenderer.once "ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_#{@guestId}", => - BrowserWindowProxy.removeWindow(@guestId) + BrowserWindowProxy.remove(@guestId) @closed = true close: -> From fdef3d56b82c159dfd3cb383b4dc6477f5e79c60 Mon Sep 17 00:00:00 2001 From: Laekh Date: Thu, 10 Dec 2015 21:48:52 +0100 Subject: [PATCH 232/411] Update browser-window.md Made it more clear on how to define BrowserWindow (outside of the main process, specifically) --- docs/api/browser-window.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 2fbd1544c3..44c4da5c65 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -4,8 +4,12 @@ The `BrowserWindow` class gives you the ability to create a browser window. For example: ```javascript +// In the main process. const BrowserWindow = require('electron').BrowserWindow; +// Or in the renderer process. +const BrowserWindow = require('electron').remote.BrowserWindow; + var win = new BrowserWindow({ width: 800, height: 600, show: false }); win.on('closed', function() { win = null; From 9495392a1acf464b98b26a7a13df633a2bb25acf Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 11:49:01 +0800 Subject: [PATCH 233/411] Bump v0.36.0 --- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 179f26db03..4c72c04cfa 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.35.4 + 0.36.0 CFBundleShortVersionString - 0.35.4 + 0.36.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 05fb3f336f..9568629c42 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,35,4,0 - PRODUCTVERSION 0,35,4,0 + FILEVERSION 0,36,0,0 + PRODUCTVERSION 0,36,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.35.4" + VALUE "FileVersion", "0.36.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.35.4" + VALUE "ProductVersion", "0.36.0" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index a4930d0edb..46293a0eb8 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 35 -#define ATOM_PATCH_VERSION 4 +#define ATOM_MINOR_VERSION 36 +#define ATOM_PATCH_VERSION 0 #define ATOM_VERSION_IS_RELEASE 1 From efac12ed89258e9b18041313965df53efc05a82b Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 11 Dec 2015 14:26:01 +0900 Subject: [PATCH 234/411] :memo: Update as upstream [ci skip] --- docs/api/browser-window.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 2fbd1544c3..00ea09bf96 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -4,8 +4,12 @@ The `BrowserWindow` class gives you the ability to create a browser window. For example: ```javascript +// 메인 프로세스에서 const BrowserWindow = require('electron').BrowserWindow; +// 또는 랜더러 프로세스에서 +const BrowserWindow = require('electron').remote.BrowserWindow; + var win = new BrowserWindow({ width: 800, height: 600, show: false }); win.on('closed', function() { win = null; From b7fce578d56236f64a3af3183159f6c07a6c7110 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 19:02:56 +0800 Subject: [PATCH 235/411] docs: Improve the webRequets docs --- docs/api/session.md | 266 ++++++++++++++++++++++++-------------------- 1 file changed, 143 insertions(+), 123 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index 9429bc3c2d..87c598e7bf 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -12,7 +12,7 @@ const BrowserWindow = require('electron').BrowserWindow; var win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL("http://github.com"); -var ses = win.webContents.session +var ses = win.webContents.session; ``` ## Methods @@ -289,175 +289,195 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c #### `ses.webRequest` -The `webRequest` api allows to intercept and modify contents of a request at various -stages of its lifetime. +The `webRequest` API set allows to intercept and modify contents of a request at +various stages of its lifetime. + +Each API accepts an optional `filter` and a `listener`, the `listener` will be +called with `listener(details)` when the API's event has happened, the `details` +is an object that describes the request. Passing `null` as `listener` will +unsubscribe from the event. + +The `filter` is an object that has an `urls` property, which is an Array of URL +patterns that will be used to filter out the requests that do not match the URL +patterns. If the `filter` is omitted then all requests will be matched. + +For certain events the `listener` is required to return an object that describes +how to handle the request, users can specify the `cancel` property to `true` to +cancel the request. ```javascript // Modify the user agent for all requests to the following urls. - var filter = { urls: ["https://*.github.com/*", "*://electron.github.io"] -} +}; -myWindow.webContents.session.webRequest.onBeforeSendHeaders(filter, function(details) { +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details) { details.requestHeaders['User-Agent'] = "MyAgent"; return {cancel: false, requestHeaders: details.requestHeaders}; -}) +}); ``` -#### `ses.webRequest.onBeforeRequest([filter,] listener)` +#### `ses.webRequest.onBeforeRequest([filter, ]listener)` * `filter` Object - * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. * `listener` Function - * `details` Object - * `id` String - Request id. - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double -* `blockingResponse` Object - * `cancel` Boolean - Whether to continue or block the request. - * `redirectURL` String **optional** - The original request is prevented from being sent or - completed, and is instead redirected to the given URL. -Fired when a request is about to occur. Should return a `blockingResponse`. +The `listener` will be called with `listener(details)` when a request is about +to occur. -#### `ses.webRequest.onBeforeSendHeaders([filter,] listener)` +* `details` Object + * `id` String - Request ID. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + +The `listener` has to return an `response` object: + +* `response` Object + * `cancel` Boolean + * `redirectURL` String __optional__ - The original request is prevented from + being sent or completed, and is instead redirected to the given URL. + +#### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)` * `filter` Object - * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. * `listener` Function - * `details` Object - * `id` String - Request id. - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `requestHeaders` Object -* `blockingResponse` Object - * `cancel` Boolean - Whether to continue or block the request. - * `requestHeaders` Object **optional** - When provided, request will be made with these - headers. -Fired before sending an HTTP request, once the request headers are available. This may -occur after a TCP connection is made to the server, but before any http data is sent. -Should return a `blockingResponse`. +The `listener` will be called with `listener(details)` before sending an HTTP +request, once the request headers are available. This may occur after a TCP +connection is made to the server, but before any http data is sent. -#### `ses.webRequest.onSendHeaders([filter,] listener)` +* `details` Object + * `id` String - Request ID. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +The `listener` has to return an `response` object: + +* `response` Object + * `cancel` Boolean + * `requestHeaders` Object __optional__ - When provided, request will be made + with these headers. + +#### `ses.webRequest.onSendHeaders([filter, ]listener)` * `filter` Object - * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. * `listener` Function - * `details` Object - * `id` String - Request id. - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `requestHeaders` Object -Fired just before a request is going to be sent to the server, modifications of previous -`onBeforeSendHeaders` response are visible by the time this listener is fired. +The `listener` will be called with `listener(details)` just before a request is +going to be sent to the server, modifications of previous `onBeforeSendHeaders` +response are visible by the time this listener is fired. + +* `details` Object + * `id` String - Request ID. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object #### `ses.webRequest.onHeadersReceived([filter,] listener)` * `filter` Object - * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. * `listener` Function - * `details` Object - * `id` String - Request id. - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `statusLine` String - * `statusCode` Integer - * `responseHeaders` Object -* `blockingResponse` Object - * `cancel` Boolean - Whether to continue or block the request. - * `responseHeaders` Object **optional** - When provided, the server is assumed to have - responded with these headers. -Fired when HTTP response headers of a request have been received. Should return a -`blockingResponse`. +The `listener` will be called with `listener(details)` when HTTP response +headers of a request have been received. -#### `ses.webRequest.onResponseStarted([filter,] listener)` +* `details` Object + * `id` String - Request ID. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object + +The `listener` has to return an `response` object: + +* `response` Object + * `cancel` Boolean + * `responseHeaders` Object __optional__ - When provided, the server is assumed + to have responded with these headers. + +#### `ses.webRequest.onResponseStarted([filter, ]listener)` * `filter` Object - * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. * `listener` Function - * `details` Object - * `id` String - Request id. - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `responseHeaders` Object - * `fromCache` Boolean - * `statusCode` Integer - * `statusLine` String -Fired when first byte of the response body is received. For HTTP requests, this means that the -status line and response headers are available. +The `listener` will be called with `listener(details)` when first byte of the +response body is received. For HTTP requests, this means that the status line +and response headers are available. -#### `ses.webRequest.onBeforeRedirect([filter,] listener)` +* `details` Object + * `id` String - Request ID. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean - Indicates whether the response was fetched from disk + cache. + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onBeforeRedirect([filter, ]listener)` * `filter` Object - * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. * `listener` Function - * `details` Object - * `id` String - Request id. - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `redirectURL` String - * `statusCode` Integer - * `ip` String **optional** - The server IP address that the request was actually sent to. - * `fromCache` Boolean - * `responseHeaders` Object -Fired when a server initiated redirect is about to occur. +The `listener` will be called with `listener(details)` when a server initiated +redirect is about to occur. -#### `ses.webRequest.onCompleted([filter,] listener)` +* `details` Object + * `id` String - Request ID. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String __optional__ - The server IP address that the request was + actually sent to. + * `fromCache` Boolean + * `responseHeaders` Object + +#### `ses.webRequest.onCompleted([filter, ]listener)` * `filter` Object - * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. * `listener` Function - * `details` Object - * `id` String - Request id. - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `responseHeaders` Object - * `fromCache` Boolean - Indicates whether the response was fetched from disk cache. - * `statusCode` Integer - * `statusLine` String -Fired when a request is completed. +The `listener` will be called with `listener(details)` when a request is +completed. -#### `ses.webRequest.onErrorOccurred([filter,] listener)` +* `details` Object + * `id` String - Request ID. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onErrorOccurred([filter, ]listener)` * `filter` Object - * `urls` Array - A list of URLs or URL patterns. Request that cannot match any of the URLs - will be filtered out. * `listener` Function - * `details` Object - * `id` String - Request id. - * `url` String - * `method` String - * `resourceType` String - * `timestamp` Double - * `fromCache` Boolean - Indicates whether the response was fetched from disk cache. - * `error` String - The error description. -Fired when an error occurs. +The `listener` will be called with `listener(details)` when an error occurs. + +* `details` Object + * `id` String - Request ID. + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean + * `error` String - The error description. From 62f4b25cf92a3e1c275e4cfed0ce6bfa27780b07 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 19:11:09 +0800 Subject: [PATCH 236/411] Allow passing null to webRequest --- atom/browser/api/atom_api_web_request.cc | 11 ++++++----- atom/browser/api/atom_api_web_request.h | 2 -- atom/browser/net/atom_network_delegate.h | 1 - 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc index 07c0576977..a1b4df70c1 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -28,13 +28,14 @@ WebRequest::~WebRequest() { template void WebRequest::SetListener(mate::Arguments* args) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - scoped_ptr filter(new base::DictionaryValue()); + scoped_ptr filter(new base::DictionaryValue); args->GetNext(filter.get()); + + v8::Local value; AtomNetworkDelegate::Listener callback; - if (!args->GetNext(&callback)) { - args->ThrowError("Must pass null or a function"); + if (!args->GetNext(&callback) && + !(args->GetNext(&value) && value->IsNull())) { + args->ThrowError("Must pass null or a Function"); return; } diff --git a/atom/browser/api/atom_api_web_request.h b/atom/browser/api/atom_api_web_request.h index 597bb80f7c..f156b58161 100644 --- a/atom/browser/api/atom_api_web_request.h +++ b/atom/browser/api/atom_api_web_request.h @@ -5,8 +5,6 @@ #ifndef ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ #define ATOM_BROWSER_API_ATOM_API_WEB_REQUEST_H_ -#include - #include "atom/browser/api/trackable_object.h" #include "atom/browser/net/atom_network_delegate.h" #include "native_mate/arguments.h" diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 9f78cc8d92..adc6935f20 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -7,7 +7,6 @@ #include #include -#include #include "brightray/browser/network_delegate.h" #include "base/callback.h" From 467e3b25b2836758223e526f8af06d684b9853d6 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 20:11:22 +0800 Subject: [PATCH 237/411] EvenTypes => EventType --- atom/browser/api/atom_api_web_request.cc | 2 +- atom/browser/api/atom_api_web_request.h | 2 +- atom/browser/net/atom_network_delegate.cc | 2 +- atom/browser/net/atom_network_delegate.h | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc index a1b4df70c1..ab583d355f 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -26,7 +26,7 @@ WebRequest::WebRequest(AtomBrowserContext* browser_context) WebRequest::~WebRequest() { } -template +template void WebRequest::SetListener(mate::Arguments* args) { scoped_ptr filter(new base::DictionaryValue); args->GetNext(filter.get()); diff --git a/atom/browser/api/atom_api_web_request.h b/atom/browser/api/atom_api_web_request.h index f156b58161..0292a00104 100644 --- a/atom/browser/api/atom_api_web_request.h +++ b/atom/browser/api/atom_api_web_request.h @@ -29,7 +29,7 @@ class WebRequest : public mate::TrackableObject { explicit WebRequest(AtomBrowserContext* browser_context); ~WebRequest(); - template + template void SetListener(mate::Arguments* args); private: diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index c7a54ee664..9d7c8e8099 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -139,7 +139,7 @@ AtomNetworkDelegate::~AtomNetworkDelegate() { } void AtomNetworkDelegate::SetListenerInIO( - EventTypes type, + EventType type, scoped_ptr filter, const Listener& callback) { if (callback.is_null()) { diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index adc6935f20..d6b2569787 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -28,7 +28,7 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { using Listener = base::Callback; - enum EventTypes { + enum EventType { kInvalidEvent = 0, kOnBeforeRequest = 1 << 0, kOnBeforeSendHeaders = 1 << 1, @@ -62,7 +62,7 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { AtomNetworkDelegate(); ~AtomNetworkDelegate() override; - void SetListenerInIO(EventTypes type, + void SetListenerInIO(EventType type, scoped_ptr filter, const Listener& callback); @@ -90,7 +90,7 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { void OnCompleted(net::URLRequest* request, bool started) override; private: - std::map event_listener_map_; + std::map event_listener_map_; DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); }; From e295eb0de714ad29ec4a3962f82c9c4b45c477c5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 20:11:59 +0800 Subject: [PATCH 238/411] Fix memory leak --- atom/browser/net/atom_network_delegate.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 9d7c8e8099..d9ed812a1b 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -201,7 +201,7 @@ int AtomNetworkDelegate::OnBeforeSendHeaders( auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); - details->Set("requestHeaders", GetRequestHeadersDict(*headers).release()); + details->Set("requestHeaders", GetRequestHeadersDict(*headers).get()); BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), @@ -226,7 +226,7 @@ void AtomNetworkDelegate::OnSendHeaders( auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); - details->Set("requestHeaders", GetRequestHeadersDict(headers).release()); + details->Set("requestHeaders", GetRequestHeadersDict(headers).get()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&RunListener), @@ -255,7 +255,7 @@ int AtomNetworkDelegate::OnHeadersReceived( details->SetInteger("statusCode", original_response_headers->response_code()); details->Set("responseHeaders", - GetResponseHeadersDict(original_response_headers).release()); + GetResponseHeadersDict(original_response_headers).get()); BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), @@ -288,7 +288,7 @@ void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, details->SetString("ip", ip); details->SetBoolean("fromCache", request->was_cached()); details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers()).release()); + GetResponseHeadersDict(request->response_headers()).get()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&RunListener), @@ -311,7 +311,7 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers()).release()); + GetResponseHeadersDict(request->response_headers()).get()); details->SetBoolean("fromCache", request->was_cached()); auto response_headers = request->response_headers(); @@ -352,7 +352,7 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { auto wrapped_callback = listener_info->second.callback; auto details = ExtractRequestInfo(request); details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers()).release()); + GetResponseHeadersDict(request->response_headers()).get()); details->SetBoolean("fromCache", request->was_cached()); auto response_headers = request->response_headers(); From 9d406b695ff2258ac4e76e232e653fb3947eaa77 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 20:40:39 +0800 Subject: [PATCH 239/411] Simpily the code that fills |details| --- atom/browser/net/atom_network_delegate.cc | 181 ++++++++++++---------- 1 file changed, 96 insertions(+), 85 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index d9ed812a1b..abab930b4b 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -5,6 +5,7 @@ #include "atom/browser/net/atom_network_delegate.h" #include "atom/common/native_mate_converters/net_converter.h" +#include "base/strings/string_util.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/resource_request_info.h" #include "net/url_request/url_request.h" @@ -15,7 +16,7 @@ namespace atom { namespace { -std::string ResourceTypeToString(content::ResourceType type) { +const char* ResourceTypeToString(content::ResourceType type) { switch (type) { case content::RESOURCE_TYPE_MAIN_FRAME: return "mainFrame"; @@ -56,41 +57,70 @@ bool MatchesFilterCondition( return true; } -scoped_ptr ExtractRequestInfo(net::URLRequest* request) { - scoped_ptr dict(new base::DictionaryValue()); - dict->SetInteger("id", request->identifier()); - dict->SetString("url", request->url().spec()); - dict->SetString("method", request->method()); - content::ResourceType resourceType = content::RESOURCE_TYPE_LAST_TYPE; +void FillDetailsObject(base::DictionaryValue* details, + net::URLRequest* request) { + details->SetInteger("id", request->identifier()); + details->SetString("url", request->url().spec()); + details->SetString("method", request->method()); + details->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); auto info = content::ResourceRequestInfo::ForRequest(request); - if (info) - resourceType = info->GetResourceType(); - dict->SetString("resourceType", ResourceTypeToString(resourceType)); - dict->SetDouble("timestamp", base::Time::Now().ToDoubleT() * 1000); - - return dict.Pass(); + details->SetString("resourceType", + info ? ResourceTypeToString(info->GetResourceType()) + : "other"); } -scoped_ptr GetRequestHeadersDict( - const net::HttpRequestHeaders& headers) { - scoped_ptr header_dict(new base::DictionaryValue()); +void FillDetailsObject(base::DictionaryValue* details, + const net::HttpRequestHeaders& headers) { + scoped_ptr dict(new base::DictionaryValue); net::HttpRequestHeaders::Iterator it(headers); while (it.GetNext()) - header_dict->SetString(it.name(), it.value()); - return header_dict.Pass(); + dict->SetString(it.name(), it.value()); + details->Set("requestHeaders", dict.Pass()); } -scoped_ptr GetResponseHeadersDict( - const net::HttpResponseHeaders* headers) { - scoped_ptr header_dict(new base::DictionaryValue()); - if (headers) { - void* iter = nullptr; - std::string key; - std::string value; - while (headers->EnumerateHeaderLines(&iter, &key, &value)) - header_dict->SetString(key, value); +void FillDetailsObject(base::DictionaryValue* details, + net::HttpResponseHeaders* headers) { + if (!headers) + return; + + scoped_ptr dict(new base::DictionaryValue); + void* iter = nullptr; + std::string key; + std::string value; + while (headers->EnumerateHeaderLines(&iter, &key, &value)) { + key = base::ToLowerASCII(key); + if (dict->HasKey(key)) { + base::ListValue* values = nullptr; + if (dict->GetList(key, &values)) + values->AppendString(value); + } else { + scoped_ptr values(new base::ListValue); + values->AppendString(value); + dict->Set(key, values.Pass()); + } } - return header_dict.Pass(); + details->Set("responseHeaders", dict.Pass()); + details->SetString("statusLine", headers->GetStatusLine()); + details->SetInteger("statusCode", headers->response_code()); +} + +void FillDetailsObject(base::DictionaryValue* details, const GURL& location) { + details->SetString("redirectURL", location.spec()); +} + +void FillDetailsObject(base::DictionaryValue* details, + const net::HostPortPair& host_port) { + if (host_port.host().empty()) + details->SetString("ip", host_port.host()); +} + +void FillDetailsObject(base::DictionaryValue* details, bool from_cache) { + details->SetBoolean("fromCache", from_cache); +} + +void FillDetailsObject(base::DictionaryValue* details, + const net::URLRequestStatus& status) { + details->SetString("error", net::ErrorToString(status.error())); } void OnBeforeURLRequestResponse( @@ -174,9 +204,10 @@ int AtomNetworkDelegate::OnBeforeURLRequest( if (!MatchesFilterCondition(request, listener_info->second)) return net::OK; - auto wrapped_callback = listener_info->second.callback; - auto details = ExtractRequestInfo(request); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + auto wrapped_callback = listener_info->second.callback; BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), base::Bind(&OnBeforeURLRequestResponse, @@ -199,10 +230,11 @@ int AtomNetworkDelegate::OnBeforeSendHeaders( if (!MatchesFilterCondition(request, listener_info->second)) return net::OK; - auto wrapped_callback = listener_info->second.callback; - auto details = ExtractRequestInfo(request); - details->Set("requestHeaders", GetRequestHeadersDict(*headers).get()); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), *headers); + auto wrapped_callback = listener_info->second.callback; BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), base::Bind(&OnBeforeSendHeadersResponse, @@ -224,10 +256,11 @@ void AtomNetworkDelegate::OnSendHeaders( if (!MatchesFilterCondition(request, listener_info->second)) return; - auto wrapped_callback = listener_info->second.callback; - auto details = ExtractRequestInfo(request); - details->Set("requestHeaders", GetRequestHeadersDict(headers).get()); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), headers); + auto wrapped_callback = listener_info->second.callback; BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&RunListener), wrapped_callback, @@ -248,15 +281,11 @@ int AtomNetworkDelegate::OnHeadersReceived( if (!MatchesFilterCondition(request, listener_info->second)) return net::OK; - auto wrapped_callback = listener_info->second.callback; - auto details = ExtractRequestInfo(request); - details->SetString("statusLine", - original_response_headers->GetStatusLine()); - details->SetInteger("statusCode", - original_response_headers->response_code()); - details->Set("responseHeaders", - GetResponseHeadersDict(original_response_headers).get()); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), original_response_headers); + auto wrapped_callback = listener_info->second.callback; BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), base::Bind(&OnHeadersReceivedResponse, @@ -273,23 +302,20 @@ int AtomNetworkDelegate::OnHeadersReceived( } void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, - const GURL& new_location) { + const GURL& new_location) { auto listener_info = event_listener_map_.find(kOnBeforeRedirect); if (listener_info != event_listener_map_.end()) { if (!MatchesFilterCondition(request, listener_info->second)) return; - auto wrapped_callback = listener_info->second.callback; - auto details = ExtractRequestInfo(request); - details->SetString("redirectURL", new_location.spec()); - details->SetInteger("statusCode", request->GetResponseCode()); - auto ip = request->GetSocketAddress().host(); - if (!ip.empty()) - details->SetString("ip", ip); - details->SetBoolean("fromCache", request->was_cached()); - details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers()).get()); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), new_location); + FillDetailsObject(details.get(), request->response_headers()); + FillDetailsObject(details.get(), request->GetSocketAddress()); + FillDetailsObject(details.get(), request->was_cached()); + auto wrapped_callback = listener_info->second.callback; BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&RunListener), wrapped_callback, @@ -308,20 +334,12 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { if (!MatchesFilterCondition(request, listener_info->second)) return; + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), request->response_headers()); + FillDetailsObject(details.get(), request->was_cached()); + auto wrapped_callback = listener_info->second.callback; - auto details = ExtractRequestInfo(request); - details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers()).get()); - details->SetBoolean("fromCache", request->was_cached()); - - auto response_headers = request->response_headers(); - details->SetInteger("statusCode", - response_headers ? - response_headers->response_code() : 200); - details->SetString("statusLine", - response_headers ? - response_headers->GetStatusLine() : std::string()); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&RunListener), wrapped_callback, @@ -349,20 +367,12 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { if (!MatchesFilterCondition(request, listener_info->second)) return; + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), request->response_headers()); + FillDetailsObject(details.get(), request->was_cached()); + auto wrapped_callback = listener_info->second.callback; - auto details = ExtractRequestInfo(request); - details->Set("responseHeaders", - GetResponseHeadersDict(request->response_headers()).get()); - details->SetBoolean("fromCache", request->was_cached()); - - auto response_headers = request->response_headers(); - details->SetInteger("statusCode", - response_headers ? - response_headers->response_code() : 200); - details->SetString("statusLine", - response_headers ? - response_headers->GetStatusLine() : std::string()); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&RunListener), wrapped_callback, @@ -378,11 +388,12 @@ void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { if (!MatchesFilterCondition(request, listener_info->second)) return; - auto wrapped_callback = listener_info->second.callback; - auto details = ExtractRequestInfo(request); - details->SetBoolean("fromCache", request->was_cached()); - details->SetString("error", net::ErrorToString(request->status().error())); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), request->was_cached()); + FillDetailsObject(details.get(), request->status()); + auto wrapped_callback = listener_info->second.callback; BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(base::IgnoreResult(&RunListener), wrapped_callback, From 79a627014c28564c75f80b0255895706f152d7b4 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 21:02:44 +0800 Subject: [PATCH 240/411] Unify how listeners are handled --- atom/browser/net/atom_network_delegate.cc | 308 +++++++++++----------- atom/browser/net/atom_network_delegate.h | 4 +- 2 files changed, 152 insertions(+), 160 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index abab930b4b..311446804c 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -5,6 +5,7 @@ #include "atom/browser/net/atom_network_delegate.h" #include "atom/common/native_mate_converters/net_converter.h" +#include "base/stl_util.h" #include "base/strings/string_util.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/resource_request_info.h" @@ -43,18 +44,16 @@ AtomNetworkDelegate::BlockingResponse RunListener( return callback.Run(*(details.get())); } -bool MatchesFilterCondition( - net::URLRequest* request, - const AtomNetworkDelegate::ListenerInfo& info) { - if (!info.url_patterns.empty()) { - auto url = request->url(); - for (auto& pattern : info.url_patterns) - if (pattern.MatchesURL(url)) - return true; - return false; - } +bool MatchesFilterCondition(net::URLRequest* request, + const URLPatterns& patterns) { + if (patterns.empty()) + return true; - return true; + for (const auto& pattern : patterns) { + if (pattern.MatchesURL(request->url())) + return true; + } + return false; } void FillDetailsObject(base::DictionaryValue* details, @@ -199,75 +198,67 @@ int AtomNetworkDelegate::OnBeforeURLRequest( net::URLRequest* request, const net::CompletionCallback& callback, GURL* new_url) { - auto listener_info = event_listener_map_.find(kOnBeforeRequest); - if (listener_info != event_listener_map_.end()) { - if (!MatchesFilterCondition(request, listener_info->second)) - return net::OK; + if (!ContainsKey(event_listener_map_, kOnBeforeRequest)) + return brightray::NetworkDelegate::OnBeforeURLRequest( + request, callback, new_url); - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); + const ListenerInfo& info = event_listener_map_[kOnBeforeRequest]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return net::OK; - auto wrapped_callback = listener_info->second.callback; - BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), - base::Bind(&OnBeforeURLRequestResponse, - callback, new_url)); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); - return net::ERR_IO_PENDING; - } - - return brightray::NetworkDelegate::OnBeforeURLRequest(request, - callback, - new_url); + BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, + base::Bind(&RunListener, info.callback, base::Passed(&details)), + base::Bind(&OnBeforeURLRequestResponse, + callback, new_url)); + return net::ERR_IO_PENDING; } int AtomNetworkDelegate::OnBeforeSendHeaders( net::URLRequest* request, const net::CompletionCallback& callback, net::HttpRequestHeaders* headers) { - auto listener_info = event_listener_map_.find(kOnBeforeSendHeaders); - if (listener_info != event_listener_map_.end()) { - if (!MatchesFilterCondition(request, listener_info->second)) - return net::OK; + if (!ContainsKey(event_listener_map_, kOnBeforeSendHeaders)) + return brightray::NetworkDelegate::OnBeforeSendHeaders( + request, callback, headers); - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), *headers); + const ListenerInfo& info = event_listener_map_[kOnBeforeSendHeaders]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return net::OK; - auto wrapped_callback = listener_info->second.callback; - BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), - base::Bind(&OnBeforeSendHeadersResponse, - callback, headers)); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), *headers); - return net::ERR_IO_PENDING; - } - - return brightray::NetworkDelegate::OnBeforeSendHeaders(request, - callback, - headers); + BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, + base::Bind(&RunListener, info.callback, base::Passed(&details)), + base::Bind(&OnBeforeSendHeadersResponse, + callback, headers)); + return net::ERR_IO_PENDING; } void AtomNetworkDelegate::OnSendHeaders( net::URLRequest* request, const net::HttpRequestHeaders& headers) { - auto listener_info = event_listener_map_.find(kOnSendHeaders); - if (listener_info != event_listener_map_.end()) { - if (!MatchesFilterCondition(request, listener_info->second)) - return; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), headers); - - auto wrapped_callback = listener_info->second.callback; - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - wrapped_callback, - base::Passed(&details))); - } else { + if (!ContainsKey(event_listener_map_, kOnSendHeaders)) { brightray::NetworkDelegate::OnSendHeaders(request, headers); + return; } + + const ListenerInfo& info = event_listener_map_[kOnSendHeaders]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return; + + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), headers); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(&RunListener), + info.callback, + base::Passed(&details))); } int AtomNetworkDelegate::OnHeadersReceived( @@ -276,129 +267,128 @@ int AtomNetworkDelegate::OnHeadersReceived( const net::HttpResponseHeaders* original_response_headers, scoped_refptr* override_response_headers, GURL* allowed_unsafe_redirect_url) { - auto listener_info = event_listener_map_.find(kOnHeadersReceived); - if (listener_info != event_listener_map_.end()) { - if (!MatchesFilterCondition(request, listener_info->second)) - return net::OK; + if (!ContainsKey(event_listener_map_, kOnHeadersReceived)) + return brightray::NetworkDelegate::OnHeadersReceived( + request, callback, original_response_headers, override_response_headers, + allowed_unsafe_redirect_url); - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), original_response_headers); + const ListenerInfo& info = event_listener_map_[kOnHeadersReceived]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return net::OK; - auto wrapped_callback = listener_info->second.callback; - BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(&RunListener, wrapped_callback, base::Passed(&details)), - base::Bind(&OnHeadersReceivedResponse, - callback, - original_response_headers, - override_response_headers)); + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), original_response_headers); - return net::ERR_IO_PENDING; - } - - return brightray::NetworkDelegate::OnHeadersReceived( - request, callback, original_response_headers, override_response_headers, - allowed_unsafe_redirect_url); + BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, + base::Bind(&RunListener, info.callback, base::Passed(&details)), + base::Bind(&OnHeadersReceivedResponse, + callback, + original_response_headers, + override_response_headers)); + return net::ERR_IO_PENDING; } void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, const GURL& new_location) { - auto listener_info = event_listener_map_.find(kOnBeforeRedirect); - if (listener_info != event_listener_map_.end()) { - if (!MatchesFilterCondition(request, listener_info->second)) - return; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), new_location); - FillDetailsObject(details.get(), request->response_headers()); - FillDetailsObject(details.get(), request->GetSocketAddress()); - FillDetailsObject(details.get(), request->was_cached()); - - auto wrapped_callback = listener_info->second.callback; - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - wrapped_callback, - base::Passed(&details))); - } else { + if (!ContainsKey(event_listener_map_, kOnBeforeRedirect)) { brightray::NetworkDelegate::OnBeforeRedirect(request, new_location); + return; } + + const ListenerInfo& info = event_listener_map_[kOnBeforeRedirect]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return; + + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), new_location); + FillDetailsObject(details.get(), request->response_headers()); + FillDetailsObject(details.get(), request->GetSocketAddress()); + FillDetailsObject(details.get(), request->was_cached()); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(&RunListener), + info.callback, + base::Passed(&details))); } void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { - auto listener_info = event_listener_map_.find(kOnResponseStarted); - if (listener_info != event_listener_map_.end()) { - if (request->status().status() != net::URLRequestStatus::SUCCESS) - return; - - if (!MatchesFilterCondition(request, listener_info->second)) - return; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), request->response_headers()); - FillDetailsObject(details.get(), request->was_cached()); - - auto wrapped_callback = listener_info->second.callback; - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - wrapped_callback, - base::Passed(&details))); - } else { + if (!ContainsKey(event_listener_map_, kOnResponseStarted)) { brightray::NetworkDelegate::OnResponseStarted(request); + return; } + + if (request->status().status() != net::URLRequestStatus::SUCCESS) + return; + + const ListenerInfo& info = event_listener_map_[kOnResponseStarted]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return; + + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), request->response_headers()); + FillDetailsObject(details.get(), request->was_cached()); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(&RunListener), + info.callback, + base::Passed(&details))); } void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { - auto listener_info = event_listener_map_.find(kOnCompleted); - if (listener_info != event_listener_map_.end()) { - if (request->status().status() == net::URLRequestStatus::FAILED || - request->status().status() == net::URLRequestStatus::CANCELED) { + if (request->status().status() == net::URLRequestStatus::FAILED || + request->status().status() == net::URLRequestStatus::CANCELED) { + // Error event. + if (ContainsKey(event_listener_map_, kOnErrorOccurred)) OnErrorOccurred(request); - return; - } else { - bool is_redirect = request->response_headers() && - net::HttpResponseHeaders::IsRedirectResponseCode( - request->response_headers()->response_code()); - if (is_redirect) - return; - } - - if (!MatchesFilterCondition(request, listener_info->second)) - return; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), request->response_headers()); - FillDetailsObject(details.get(), request->was_cached()); - - auto wrapped_callback = listener_info->second.callback; - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - wrapped_callback, - base::Passed(&details))); - } else { + else + brightray::NetworkDelegate::OnCompleted(request, started); + return; + } else if (request->response_headers() && + net::HttpResponseHeaders::IsRedirectResponseCode( + request->response_headers()->response_code())) { + // Redirect event. brightray::NetworkDelegate::OnCompleted(request, started); + return; } + + if (!ContainsKey(event_listener_map_, kOnCompleted)) { + brightray::NetworkDelegate::OnCompleted(request, started); + return; + } + + const ListenerInfo& info = event_listener_map_[kOnCompleted]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return; + + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), request->response_headers()); + FillDetailsObject(details.get(), request->was_cached()); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(&RunListener), + info.callback, + base::Passed(&details))); } void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { - auto listener_info = event_listener_map_.find(kOnErrorOccurred); - if (listener_info != event_listener_map_.end()) { - if (!MatchesFilterCondition(request, listener_info->second)) - return; - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), request->was_cached()); - FillDetailsObject(details.get(), request->status()); + const ListenerInfo& info = event_listener_map_[kOnErrorOccurred]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return; - auto wrapped_callback = listener_info->second.callback; - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - wrapped_callback, - base::Passed(&details))); - } + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request); + FillDetailsObject(details.get(), request->was_cached()); + FillDetailsObject(details.get(), request->status()); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(&RunListener), + info.callback, + base::Passed(&details))); } } // namespace atom diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index d6b2569787..075fcc77ee 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -22,6 +22,8 @@ class URLPattern; namespace atom { +using URLPatterns = std::set; + class AtomNetworkDelegate : public brightray::NetworkDelegate { public: struct BlockingResponse; @@ -41,7 +43,7 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { }; struct ListenerInfo { - std::set url_patterns; + URLPatterns url_patterns; AtomNetworkDelegate::Listener callback; }; From 67886cf513c1caccf340cd07f6e9797cce5c99d8 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 21:14:13 +0800 Subject: [PATCH 241/411] Parse filters in API code --- atom/browser/api/atom_api_web_request.cc | 24 ++++++++++++++++++++--- atom/browser/net/atom_network_delegate.cc | 24 ++++------------------- atom/browser/net/atom_network_delegate.h | 2 +- vendor/native_mate | 2 +- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc index ab583d355f..5b9d2fbe64 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -15,6 +15,21 @@ using content::BrowserThread; +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + extensions::URLPattern* out) { + std::string pattern; + if (!ConvertFromV8(isolate, val, &pattern)) + return false; + return out->Parse(pattern) == extensions::URLPattern::PARSE_SUCCESS; + } +}; + +} // namespace mate + namespace atom { namespace api { @@ -28,9 +43,12 @@ WebRequest::~WebRequest() { template void WebRequest::SetListener(mate::Arguments* args) { - scoped_ptr filter(new base::DictionaryValue); - args->GetNext(filter.get()); + // { urls }. + URLPatterns patterns; + mate::Dictionary dict; + args->GetNext(&dict) && dict.Get("urls", &patterns); + // Function or null. v8::Local value; AtomNetworkDelegate::Listener callback; if (!args->GetNext(&callback) && @@ -43,7 +61,7 @@ void WebRequest::SetListener(mate::Arguments* args) { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&AtomNetworkDelegate::SetListenerInIO, base::Unretained(delegate), - type, base::Passed(&filter), callback)); + type, patterns, callback)); } // static diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 311446804c..9ec2fdafa0 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -167,31 +167,15 @@ AtomNetworkDelegate::AtomNetworkDelegate() { AtomNetworkDelegate::~AtomNetworkDelegate() { } -void AtomNetworkDelegate::SetListenerInIO( - EventType type, - scoped_ptr filter, - const Listener& callback) { +void AtomNetworkDelegate::SetListenerInIO(EventType type, + const URLPatterns& patterns, + const Listener& callback) { if (callback.is_null()) { event_listener_map_.erase(type); return; } - ListenerInfo info; - info.callback = callback; - - const base::ListValue* url_list = nullptr; - if (filter->GetList("urls", &url_list)) { - for (size_t i = 0; i < url_list->GetSize(); ++i) { - std::string url; - extensions::URLPattern pattern; - if (url_list->GetString(i, &url) && - pattern.Parse(url) == extensions::URLPattern::PARSE_SUCCESS) { - info.url_patterns.insert(pattern); - } - } - } - - event_listener_map_[type] = info; + event_listener_map_[type] = { patterns, callback }; } int AtomNetworkDelegate::OnBeforeURLRequest( diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 075fcc77ee..869c0ec09d 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -65,7 +65,7 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { ~AtomNetworkDelegate() override; void SetListenerInIO(EventType type, - scoped_ptr filter, + const URLPatterns& patterns, const Listener& callback); protected: diff --git a/vendor/native_mate b/vendor/native_mate index 5e70868fd0..a3dcf8ced6 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit 5e70868fd0c005dc2c43bea15ca6e93da0b68741 +Subproject commit a3dcf8ced663e974ac94ad5e50a1d25a43995a9d From 15cc8164b8edfe13682b823848ea0a7256499205 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 21:21:02 +0800 Subject: [PATCH 242/411] Use lower case for getters --- atom/browser/net/atom_network_delegate.cc | 6 +++--- atom/browser/net/atom_network_delegate.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 9ec2fdafa0..d536b0f8a9 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -128,7 +128,7 @@ void OnBeforeURLRequestResponse( const AtomNetworkDelegate::BlockingResponse& result) { if (!result.redirect_url.is_empty()) *new_url = result.redirect_url; - callback.Run(result.Code()); + callback.Run(result.code()); } void OnBeforeSendHeadersResponse( @@ -137,7 +137,7 @@ void OnBeforeSendHeadersResponse( const AtomNetworkDelegate::BlockingResponse& result) { if (!result.request_headers.IsEmpty()) *headers = result.request_headers; - callback.Run(result.Code()); + callback.Run(result.code()); } void OnHeadersReceivedResponse( @@ -156,7 +156,7 @@ void OnHeadersReceivedResponse( (*override_response_headers)->AddHeader(key + ": " + value); } } - callback.Run(result.Code()); + callback.Run(result.code()); } } // namespace diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 869c0ec09d..72a69b1656 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -51,7 +51,7 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { BlockingResponse() : cancel(false) {} ~BlockingResponse() {} - int Code() const { + int code() const { return cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK; } From d3c8363450ab271e3e7708a62e28e2ce144837b3 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 21:22:23 +0800 Subject: [PATCH 243/411] EventType can not be OR'ed --- atom/browser/net/atom_network_delegate.h | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 72a69b1656..671ef82018 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -31,15 +31,14 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { base::Callback; enum EventType { - kInvalidEvent = 0, - kOnBeforeRequest = 1 << 0, - kOnBeforeSendHeaders = 1 << 1, - kOnSendHeaders = 1 << 2, - kOnHeadersReceived = 1 << 3, - kOnBeforeRedirect = 1 << 4, - kOnResponseStarted = 1 << 5, - kOnCompleted = 1 << 6, - kOnErrorOccurred = 1 << 7, + kOnBeforeRequest, + kOnBeforeSendHeaders, + kOnSendHeaders, + kOnHeadersReceived, + kOnBeforeRedirect, + kOnResponseStarted, + kOnCompleted, + kOnErrorOccurred, }; struct ListenerInfo { @@ -69,8 +68,6 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { const Listener& callback); protected: - void OnErrorOccurred(net::URLRequest* request); - // net::NetworkDelegate: int OnBeforeURLRequest(net::URLRequest* request, const net::CompletionCallback& callback, @@ -91,6 +88,8 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { void OnResponseStarted(net::URLRequest* request) override; void OnCompleted(net::URLRequest* request, bool started) override; + void OnErrorOccurred(net::URLRequest* request); + private: std::map event_listener_map_; From fed94aada0870f8fd042cf8e53337f37dac698b9 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 11 Dec 2015 23:54:32 +0800 Subject: [PATCH 244/411] Make the webRequest listener asynchronous --- atom/browser/api/atom_api_web_contents.cc | 1 + atom/browser/api/atom_api_web_request.cc | 45 ++-- atom/browser/api/atom_api_web_request.h | 9 +- atom/browser/net/atom_network_delegate.cc | 231 +++++++++++------- atom/browser/net/atom_network_delegate.h | 49 ++-- .../native_mate_converters/net_converter.cc | 34 --- .../native_mate_converters/net_converter.h | 8 - 7 files changed, 197 insertions(+), 180 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index a70b6cf4e0..cb89db911f 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -5,6 +5,7 @@ #include "atom/browser/api/atom_api_web_contents.h" #include +#include #include "atom/browser/api/atom_api_session.h" #include "atom/browser/api/atom_api_window.h" diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc index 5b9d2fbe64..7b39c59423 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -41,8 +41,20 @@ WebRequest::WebRequest(AtomBrowserContext* browser_context) WebRequest::~WebRequest() { } -template -void WebRequest::SetListener(mate::Arguments* args) { +template +void WebRequest::SetSimpleListener(mate::Arguments* args) { + SetListener( + &AtomNetworkDelegate::SetSimpleListenerInIO, type, args); +} + +template +void WebRequest::SetResponseListener(mate::Arguments* args) { + SetListener( + &AtomNetworkDelegate::SetResponseListenerInIO, type, args); +} + +template +void WebRequest::SetListener(Method method, Event type, mate::Arguments* args) { // { urls }. URLPatterns patterns; mate::Dictionary dict; @@ -50,8 +62,8 @@ void WebRequest::SetListener(mate::Arguments* args) { // Function or null. v8::Local value; - AtomNetworkDelegate::Listener callback; - if (!args->GetNext(&callback) && + Listener listener; + if (!args->GetNext(&listener) && !(args->GetNext(&value) && value->IsNull())) { args->ThrowError("Must pass null or a Function"); return; @@ -59,9 +71,8 @@ void WebRequest::SetListener(mate::Arguments* args) { auto delegate = browser_context_->network_delegate(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&AtomNetworkDelegate::SetListenerInIO, - base::Unretained(delegate), - type, patterns, callback)); + base::Bind(method, base::Unretained(delegate), type, + patterns, listener)); } // static @@ -76,28 +87,28 @@ void WebRequest::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("onBeforeRequest", - &WebRequest::SetListener< + &WebRequest::SetResponseListener< AtomNetworkDelegate::kOnBeforeRequest>) .SetMethod("onBeforeSendHeaders", - &WebRequest::SetListener< + &WebRequest::SetResponseListener< AtomNetworkDelegate::kOnBeforeSendHeaders>) - .SetMethod("onSendHeaders", - &WebRequest::SetListener< - AtomNetworkDelegate::kOnSendHeaders>) .SetMethod("onHeadersReceived", - &WebRequest::SetListener< + &WebRequest::SetResponseListener< AtomNetworkDelegate::kOnHeadersReceived>) + .SetMethod("onSendHeaders", + &WebRequest::SetSimpleListener< + AtomNetworkDelegate::kOnSendHeaders>) .SetMethod("onBeforeRedirect", - &WebRequest::SetListener< + &WebRequest::SetSimpleListener< AtomNetworkDelegate::kOnBeforeRedirect>) .SetMethod("onResponseStarted", - &WebRequest::SetListener< + &WebRequest::SetSimpleListener< AtomNetworkDelegate::kOnResponseStarted>) .SetMethod("onCompleted", - &WebRequest::SetListener< + &WebRequest::SetSimpleListener< AtomNetworkDelegate::kOnCompleted>) .SetMethod("onErrorOccurred", - &WebRequest::SetListener< + &WebRequest::SetSimpleListener< AtomNetworkDelegate::kOnErrorOccurred>); } diff --git a/atom/browser/api/atom_api_web_request.h b/atom/browser/api/atom_api_web_request.h index 0292a00104..9a6e17a046 100644 --- a/atom/browser/api/atom_api_web_request.h +++ b/atom/browser/api/atom_api_web_request.h @@ -29,8 +29,13 @@ class WebRequest : public mate::TrackableObject { explicit WebRequest(AtomBrowserContext* browser_context); ~WebRequest(); - template - void SetListener(mate::Arguments* args); + // C++ can not distinguish overloaded member function. + template + void SetSimpleListener(mate::Arguments* args); + template + void SetResponseListener(mate::Arguments* args); + template + void SetListener(Method method, Event type, mate::Arguments* args); private: scoped_refptr browser_context_; diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index d536b0f8a9..4b58a8cc6e 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -4,6 +4,8 @@ #include "atom/browser/net/atom_network_delegate.h" +#include + #include "atom/common/native_mate_converters/net_converter.h" #include "base/stl_util.h" #include "base/strings/string_util.h" @@ -38,12 +40,19 @@ const char* ResourceTypeToString(content::ResourceType type) { } } -AtomNetworkDelegate::BlockingResponse RunListener( - const AtomNetworkDelegate::Listener& callback, - scoped_ptr details) { - return callback.Run(*(details.get())); +void RunSimpleListener(const AtomNetworkDelegate::SimpleListener& listener, + scoped_ptr details) { + return listener.Run(*(details.get())); } +void RunResponseListener( + const AtomNetworkDelegate::ResponseListener& listener, + scoped_ptr details, + const AtomNetworkDelegate::ResponseCallback& callback) { + return listener.Run(*(details.get()), callback); +} + +// Test whether the URL of |request| matches |patterns|. bool MatchesFilterCondition(net::URLRequest* request, const URLPatterns& patterns) { if (patterns.empty()) @@ -56,6 +65,7 @@ bool MatchesFilterCondition(net::URLRequest* request, return false; } +// Overloaded by multiple types to fill the |details| object. void FillDetailsObject(base::DictionaryValue* details, net::URLRequest* request) { details->SetInteger("id", request->identifier()); @@ -122,41 +132,65 @@ void FillDetailsObject(base::DictionaryValue* details, details->SetString("error", net::ErrorToString(status.error())); } -void OnBeforeURLRequestResponse( - const net::CompletionCallback& callback, - GURL* new_url, - const AtomNetworkDelegate::BlockingResponse& result) { - if (!result.redirect_url.is_empty()) - *new_url = result.redirect_url; - callback.Run(result.code()); +// Fill the native types with the result from the response object. +void ReadFromResponseObject(const base::DictionaryValue& response, + GURL* new_location) { + std::string url; + if (response.GetString("redirectURL", &url)) + *new_location = GURL(url); } -void OnBeforeSendHeadersResponse( - const net::CompletionCallback& callback, - net::HttpRequestHeaders* headers, - const AtomNetworkDelegate::BlockingResponse& result) { - if (!result.request_headers.IsEmpty()) - *headers = result.request_headers; - callback.Run(result.code()); -} - -void OnHeadersReceivedResponse( - const net::CompletionCallback& callback, - const net::HttpResponseHeaders* original_response_headers, - scoped_refptr* override_response_headers, - const AtomNetworkDelegate::BlockingResponse& result) { - if (result.response_headers.get()) { - *override_response_headers = new net::HttpResponseHeaders( - original_response_headers->raw_headers()); - void* iter = nullptr; - std::string key; - std::string value; - while (result.response_headers->EnumerateHeaderLines(&iter, &key, &value)) { - (*override_response_headers)->RemoveHeader(key); - (*override_response_headers)->AddHeader(key + ": " + value); +void ReadFromResponseObject(const base::DictionaryValue& response, + net::HttpRequestHeaders* headers) { + const base::DictionaryValue* dict; + if (response.GetDictionary("requestHeaders", &dict)) { + for (base::DictionaryValue::Iterator it(*dict); + !it.IsAtEnd(); + it.Advance()) { + std::string value; + if (it.value().GetAsString(&value)) + headers->SetHeader(it.key(), value); } } - callback.Run(result.code()); +} + +void ReadFromResponseObject(const base::DictionaryValue& response, + scoped_refptr* headers) { + const base::DictionaryValue* dict; + if (response.GetDictionary("responseHeaders", &dict)) { + *headers = new net::HttpResponseHeaders(""); + for (base::DictionaryValue::Iterator it(*dict); + !it.IsAtEnd(); + it.Advance()) { + std::string value; + if (it.value().GetAsString(&value)) { + (*headers)->RemoveHeader(it.key()); + (*headers)->AddHeader(it.key() + " : " + value); + } + } + } +} + +// Deal with the results of Listener. +template +void OnListenerResultInIO(const net::CompletionCallback& callback, + T out, + scoped_ptr response) { + ReadFromResponseObject(*response.get(), out); + + bool cancel = false; + response->GetBoolean("cancel", &cancel); + callback.Run(cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK); +} + +template +void OnListenerResultInUI(const net::CompletionCallback& callback, + T out, + const base::DictionaryValue& response) { + scoped_ptr copy = response.CreateDeepCopy(); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(OnListenerResultInIO, callback, out, base::Passed(©))); } } // namespace @@ -167,36 +201,47 @@ AtomNetworkDelegate::AtomNetworkDelegate() { AtomNetworkDelegate::~AtomNetworkDelegate() { } -void AtomNetworkDelegate::SetListenerInIO(EventType type, - const URLPatterns& patterns, - const Listener& callback) { - if (callback.is_null()) { - event_listener_map_.erase(type); - return; - } +void AtomNetworkDelegate::SetSimpleListenerInIO( + SimpleEvent type, + const URLPatterns& patterns, + const SimpleListener& callback) { + if (callback.is_null()) + simple_listeners_.erase(type); + else + simple_listeners_[type] = { patterns, callback }; +} - event_listener_map_[type] = { patterns, callback }; +void AtomNetworkDelegate::SetResponseListenerInIO( + ResponseEvent type, + const URLPatterns& patterns, + const ResponseListener& callback) { + if (callback.is_null()) + response_listeners_.erase(type); + else + response_listeners_[type] = { patterns, callback }; } int AtomNetworkDelegate::OnBeforeURLRequest( net::URLRequest* request, const net::CompletionCallback& callback, GURL* new_url) { - if (!ContainsKey(event_listener_map_, kOnBeforeRequest)) + if (!ContainsKey(response_listeners_, kOnBeforeRequest)) return brightray::NetworkDelegate::OnBeforeURLRequest( request, callback, new_url); - const ListenerInfo& info = event_listener_map_[kOnBeforeRequest]; + const auto& info = response_listeners_[kOnBeforeRequest]; if (!MatchesFilterCondition(request, info.url_patterns)) return net::OK; scoped_ptr details(new base::DictionaryValue); FillDetailsObject(details.get(), request); - BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(&RunListener, info.callback, base::Passed(&details)), - base::Bind(&OnBeforeURLRequestResponse, - callback, new_url)); + ResponseCallback response = + base::Bind(OnListenerResultInUI, callback, new_url); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunResponseListener, info.listener, base::Passed(&details), + response)); return net::ERR_IO_PENDING; } @@ -204,11 +249,11 @@ int AtomNetworkDelegate::OnBeforeSendHeaders( net::URLRequest* request, const net::CompletionCallback& callback, net::HttpRequestHeaders* headers) { - if (!ContainsKey(event_listener_map_, kOnBeforeSendHeaders)) + if (!ContainsKey(response_listeners_, kOnBeforeSendHeaders)) return brightray::NetworkDelegate::OnBeforeSendHeaders( request, callback, headers); - const ListenerInfo& info = event_listener_map_[kOnBeforeSendHeaders]; + const auto& info = response_listeners_[kOnBeforeSendHeaders]; if (!MatchesFilterCondition(request, info.url_patterns)) return net::OK; @@ -216,22 +261,25 @@ int AtomNetworkDelegate::OnBeforeSendHeaders( FillDetailsObject(details.get(), request); FillDetailsObject(details.get(), *headers); - BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(&RunListener, info.callback, base::Passed(&details)), - base::Bind(&OnBeforeSendHeadersResponse, - callback, headers)); + ResponseCallback response = + base::Bind(OnListenerResultInUI, + callback, headers); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunResponseListener, info.listener, base::Passed(&details), + response)); return net::ERR_IO_PENDING; } void AtomNetworkDelegate::OnSendHeaders( net::URLRequest* request, const net::HttpRequestHeaders& headers) { - if (!ContainsKey(event_listener_map_, kOnSendHeaders)) { + if (!ContainsKey(simple_listeners_, kOnSendHeaders)) { brightray::NetworkDelegate::OnSendHeaders(request, headers); return; } - const ListenerInfo& info = event_listener_map_[kOnSendHeaders]; + const auto& info = simple_listeners_[kOnSendHeaders]; if (!MatchesFilterCondition(request, info.url_patterns)) return; @@ -239,10 +287,9 @@ void AtomNetworkDelegate::OnSendHeaders( FillDetailsObject(details.get(), request); FillDetailsObject(details.get(), headers); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - info.callback, - base::Passed(&details))); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&RunSimpleListener, info.listener, base::Passed(&details))); } int AtomNetworkDelegate::OnHeadersReceived( @@ -251,12 +298,12 @@ int AtomNetworkDelegate::OnHeadersReceived( const net::HttpResponseHeaders* original_response_headers, scoped_refptr* override_response_headers, GURL* allowed_unsafe_redirect_url) { - if (!ContainsKey(event_listener_map_, kOnHeadersReceived)) + if (!ContainsKey(response_listeners_, kOnHeadersReceived)) return brightray::NetworkDelegate::OnHeadersReceived( request, callback, original_response_headers, override_response_headers, allowed_unsafe_redirect_url); - const ListenerInfo& info = event_listener_map_[kOnHeadersReceived]; + const auto& info = response_listeners_[kOnHeadersReceived]; if (!MatchesFilterCondition(request, info.url_patterns)) return net::OK; @@ -264,23 +311,24 @@ int AtomNetworkDelegate::OnHeadersReceived( FillDetailsObject(details.get(), request); FillDetailsObject(details.get(), original_response_headers); - BrowserThread::PostTaskAndReplyWithResult(BrowserThread::UI, FROM_HERE, - base::Bind(&RunListener, info.callback, base::Passed(&details)), - base::Bind(&OnHeadersReceivedResponse, - callback, - original_response_headers, - override_response_headers)); + ResponseCallback response = + base::Bind(OnListenerResultInUI*>, + callback, override_response_headers); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunResponseListener, info.listener, base::Passed(&details), + response)); return net::ERR_IO_PENDING; } void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, const GURL& new_location) { - if (!ContainsKey(event_listener_map_, kOnBeforeRedirect)) { + if (!ContainsKey(simple_listeners_, kOnBeforeRedirect)) { brightray::NetworkDelegate::OnBeforeRedirect(request, new_location); return; } - const ListenerInfo& info = event_listener_map_[kOnBeforeRedirect]; + const auto& info = simple_listeners_[kOnBeforeRedirect]; if (!MatchesFilterCondition(request, info.url_patterns)) return; @@ -291,14 +339,13 @@ void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, FillDetailsObject(details.get(), request->GetSocketAddress()); FillDetailsObject(details.get(), request->was_cached()); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - info.callback, - base::Passed(&details))); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&RunSimpleListener, info.listener, base::Passed(&details))); } void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { - if (!ContainsKey(event_listener_map_, kOnResponseStarted)) { + if (!ContainsKey(simple_listeners_, kOnResponseStarted)) { brightray::NetworkDelegate::OnResponseStarted(request); return; } @@ -306,7 +353,7 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { if (request->status().status() != net::URLRequestStatus::SUCCESS) return; - const ListenerInfo& info = event_listener_map_[kOnResponseStarted]; + const auto& info = simple_listeners_[kOnResponseStarted]; if (!MatchesFilterCondition(request, info.url_patterns)) return; @@ -315,17 +362,16 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { FillDetailsObject(details.get(), request->response_headers()); FillDetailsObject(details.get(), request->was_cached()); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - info.callback, - base::Passed(&details))); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunSimpleListener, info.listener, base::Passed(&details))); } void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { if (request->status().status() == net::URLRequestStatus::FAILED || request->status().status() == net::URLRequestStatus::CANCELED) { // Error event. - if (ContainsKey(event_listener_map_, kOnErrorOccurred)) + if (ContainsKey(simple_listeners_, kOnErrorOccurred)) OnErrorOccurred(request); else brightray::NetworkDelegate::OnCompleted(request, started); @@ -338,12 +384,12 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { return; } - if (!ContainsKey(event_listener_map_, kOnCompleted)) { + if (!ContainsKey(simple_listeners_, kOnCompleted)) { brightray::NetworkDelegate::OnCompleted(request, started); return; } - const ListenerInfo& info = event_listener_map_[kOnCompleted]; + const auto& info = simple_listeners_[kOnCompleted]; if (!MatchesFilterCondition(request, info.url_patterns)) return; @@ -352,15 +398,13 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { FillDetailsObject(details.get(), request->response_headers()); FillDetailsObject(details.get(), request->was_cached()); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - info.callback, - base::Passed(&details))); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunSimpleListener, info.listener, base::Passed(&details))); } void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { - - const ListenerInfo& info = event_listener_map_[kOnErrorOccurred]; + const auto& info = simple_listeners_[kOnErrorOccurred]; if (!MatchesFilterCondition(request, info.url_patterns)) return; @@ -369,10 +413,9 @@ void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { FillDetailsObject(details.get(), request->was_cached()); FillDetailsObject(details.get(), request->status()); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(&RunListener), - info.callback, - base::Passed(&details))); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunSimpleListener, info.listener, base::Passed(&details))); } } // namespace atom diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 671ef82018..927dc58aa9 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -26,46 +26,44 @@ using URLPatterns = std::set; class AtomNetworkDelegate : public brightray::NetworkDelegate { public: - struct BlockingResponse; - using Listener = - base::Callback; + using ResponseCallback = base::Callback; + using SimpleListener = base::Callback; + using ResponseListener = base::Callback; - enum EventType { - kOnBeforeRequest, - kOnBeforeSendHeaders, + enum SimpleEvent { kOnSendHeaders, - kOnHeadersReceived, kOnBeforeRedirect, kOnResponseStarted, kOnCompleted, kOnErrorOccurred, }; - struct ListenerInfo { - URLPatterns url_patterns; - AtomNetworkDelegate::Listener callback; + enum ResponseEvent { + kOnBeforeRequest, + kOnBeforeSendHeaders, + kOnHeadersReceived, }; - struct BlockingResponse { - BlockingResponse() : cancel(false) {} - ~BlockingResponse() {} + struct SimpleListenerInfo { + URLPatterns url_patterns; + SimpleListener listener; + }; - int code() const { - return cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK; - } - - bool cancel; - GURL redirect_url; - net::HttpRequestHeaders request_headers; - scoped_refptr response_headers; + struct ResponseListenerInfo { + URLPatterns url_patterns; + ResponseListener listener; }; AtomNetworkDelegate(); ~AtomNetworkDelegate() override; - void SetListenerInIO(EventType type, - const URLPatterns& patterns, - const Listener& callback); + void SetSimpleListenerInIO(SimpleEvent type, + const URLPatterns& patterns, + const SimpleListener& callback); + void SetResponseListenerInIO(ResponseEvent type, + const URLPatterns& patterns, + const ResponseListener& callback); protected: // net::NetworkDelegate: @@ -91,7 +89,8 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { void OnErrorOccurred(net::URLRequest* request); private: - std::map event_listener_map_; + std::map simple_listeners_; + std::map response_listeners_;; DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); }; diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index ff0d3be881..7a1b48d931 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -82,38 +82,4 @@ v8::Local Converter>::ToV8( return dict.GetHandle(); } -// static -bool Converter::FromV8( - v8::Isolate* isolate, v8::Local val, - atom::AtomNetworkDelegate::BlockingResponse* out) { - mate::Dictionary dict; - if (!ConvertFromV8(isolate, val, &dict)) - return false; - if (!dict.Get("cancel", &(out->cancel))) - return false; - dict.Get("redirectURL", &(out->redirect_url)); - base::DictionaryValue request_headers; - if (dict.Get("requestHeaders", &request_headers)) { - for (base::DictionaryValue::Iterator it(request_headers); - !it.IsAtEnd(); - it.Advance()) { - std::string value; - CHECK(it.value().GetAsString(&value)); - out->request_headers.SetHeader(it.key(), value); - } - } - base::DictionaryValue response_headers; - if (dict.Get("responseHeaders", &response_headers)) { - out->response_headers = new net::HttpResponseHeaders(""); - for (base::DictionaryValue::Iterator it(response_headers); - !it.IsAtEnd(); - it.Advance()) { - std::string value; - CHECK(it.value().GetAsString(&value)); - out->response_headers->AddHeader(it.key() + " : " + value); - } - } - return true; -} - } // namespace mate diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index f251da8c5c..b11c55929b 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -5,7 +5,6 @@ #ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ -#include "atom/browser/net/atom_network_delegate.h" #include "base/memory/ref_counted.h" #include "native_mate/converter.h" @@ -35,13 +34,6 @@ struct Converter> { const scoped_refptr& val); }; -template<> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - atom::AtomNetworkDelegate::BlockingResponse* out); -}; - } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ From d3e723557e6158ce1d6216a19f2945ac924cd896 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 11:31:19 +0800 Subject: [PATCH 245/411] spec: Add test cases for webRequest --- atom/browser/net/atom_network_delegate.cc | 13 +- docs/api/session.md | 20 +- spec/api-web-request-spec.coffee | 232 ++++++++++++++++++++++ 3 files changed, 250 insertions(+), 15 deletions(-) create mode 100644 spec/api-web-request-spec.coffee diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 4b58a8cc6e..1625655004 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -88,7 +88,7 @@ void FillDetailsObject(base::DictionaryValue* details, } void FillDetailsObject(base::DictionaryValue* details, - net::HttpResponseHeaders* headers) { + const net::HttpResponseHeaders* headers) { if (!headers) return; @@ -97,7 +97,6 @@ void FillDetailsObject(base::DictionaryValue* details, std::string key; std::string value; while (headers->EnumerateHeaderLines(&iter, &key, &value)) { - key = base::ToLowerASCII(key); if (dict->HasKey(key)) { base::ListValue* values = nullptr; if (dict->GetList(key, &values)) @@ -162,10 +161,14 @@ void ReadFromResponseObject(const base::DictionaryValue& response, for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { - std::string value; - if (it.value().GetAsString(&value)) { + const base::ListValue* list; + if (it.value().GetAsList(&list)) { (*headers)->RemoveHeader(it.key()); - (*headers)->AddHeader(it.key() + " : " + value); + for (size_t i = 0; i < list->GetSize(); ++i) { + std::string value; + if (list->GetString(i, &value)) + (*headers)->AddHeader(it.key() + " : " + value); + } } } } diff --git a/docs/api/session.md b/docs/api/session.md index 87c598e7bf..d1db7e39e5 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -326,7 +326,7 @@ The `listener` will be called with `listener(details)` when a request is about to occur. * `details` Object - * `id` String - Request ID. + * `id` Integer * `url` String * `method` String * `resourceType` String @@ -335,7 +335,7 @@ to occur. The `listener` has to return an `response` object: * `response` Object - * `cancel` Boolean + * `cancel` Boolean __optional__ * `redirectURL` String __optional__ - The original request is prevented from being sent or completed, and is instead redirected to the given URL. @@ -349,7 +349,7 @@ request, once the request headers are available. This may occur after a TCP connection is made to the server, but before any http data is sent. * `details` Object - * `id` String - Request ID. + * `id` Integer * `url` String * `method` String * `resourceType` String @@ -359,7 +359,7 @@ connection is made to the server, but before any http data is sent. The `listener` has to return an `response` object: * `response` Object - * `cancel` Boolean + * `cancel` Boolean __optional__ * `requestHeaders` Object __optional__ - When provided, request will be made with these headers. @@ -373,7 +373,7 @@ going to be sent to the server, modifications of previous `onBeforeSendHeaders` response are visible by the time this listener is fired. * `details` Object - * `id` String - Request ID. + * `id` Integer * `url` String * `method` String * `resourceType` String @@ -389,7 +389,7 @@ The `listener` will be called with `listener(details)` when HTTP response headers of a request have been received. * `details` Object - * `id` String - Request ID. + * `id` String * `url` String * `method` String * `resourceType` String @@ -415,7 +415,7 @@ response body is received. For HTTP requests, this means that the status line and response headers are available. * `details` Object - * `id` String - Request ID. + * `id` Integer * `url` String * `method` String * `resourceType` String @@ -435,7 +435,7 @@ The `listener` will be called with `listener(details)` when a server initiated redirect is about to occur. * `details` Object - * `id` String - Request ID. + * `id` String * `url` String * `method` String * `resourceType` String @@ -456,7 +456,7 @@ The `listener` will be called with `listener(details)` when a request is completed. * `details` Object - * `id` String - Request ID. + * `id` Integer * `url` String * `method` String * `resourceType` String @@ -474,7 +474,7 @@ completed. The `listener` will be called with `listener(details)` when an error occurs. * `details` Object - * `id` String - Request ID. + * `id` Integer * `url` String * `method` String * `resourceType` String diff --git a/spec/api-web-request-spec.coffee b/spec/api-web-request-spec.coffee new file mode 100644 index 0000000000..a10ff3d127 --- /dev/null +++ b/spec/api-web-request-spec.coffee @@ -0,0 +1,232 @@ +assert = require 'assert' +http = require 'http' + +{remote} = require 'electron' +{session} = remote + +describe 'webRequest module', -> + ses = session.defaultSession + server = http.createServer (req, res) -> + res.setHeader('Custom', ['Header']) + content = req.url + if req.headers.accept is '*/*;test/header' + content += 'header/received' + res.end content + defaultURL = null + + before (done) -> + server.listen 0, '127.0.0.1', -> + {port} = server.address() + defaultURL = "http://127.0.0.1:#{port}/" + done() + after -> + server.close() + + describe 'webRequest.onBeforeRequest', -> + afterEach -> + ses.webRequest.onBeforeRequest null + + it 'can cancel the request', (done) -> + ses.webRequest.onBeforeRequest (details, callback) -> + callback(cancel: true) + $.ajax + url: defaultURL + success: (data) -> done('unexpected success') + error: (xhr, errorType, error) -> done() + + it 'can filter URLs', (done) -> + filter = urls: ["#{defaultURL}filter/*"] + ses.webRequest.onBeforeRequest filter, (details, callback) -> + callback(cancel: true) + $.ajax + url: "#{defaultURL}nofilter/test" + success: (data) -> + assert.equal data, '/nofilter/test' + $.ajax + url: "#{defaultURL}filter/test" + success: (data) -> done('unexpected success') + error: (xhr, errorType, error) -> done() + error: (xhr, errorType, error) -> done(errorType) + + it 'receives details object', (done) -> + ses.webRequest.onBeforeRequest (details, callback) -> + assert.equal typeof details.id, 'number' + assert.equal typeof details.timestamp, 'number' + assert.equal details.url, defaultURL + assert.equal details.method, 'GET' + assert.equal details.resourceType, 'xhr' + callback({}) + $.ajax + url: defaultURL + success: (data) -> + assert.equal data, '/' + done() + error: (xhr, errorType, error) -> done(errorType) + + it 'can redirect the request', (done) -> + ses.webRequest.onBeforeRequest (details, callback) -> + if details.url is defaultURL + callback(redirectURL: "#{defaultURL}redirect") + else + callback({}) + $.ajax + url: defaultURL + success: (data) -> + assert.equal data, '/redirect' + done() + error: (xhr, errorType, error) -> done(errorType) + + describe 'webRequest.onBeforeSendHeaders', -> + afterEach -> + ses.webRequest.onBeforeSendHeaders null + + it 'receives details object', (done) -> + ses.webRequest.onBeforeSendHeaders (details, callback) -> + assert.equal typeof details.requestHeaders, 'object' + callback({}) + $.ajax + url: defaultURL + success: (data) -> + assert.equal data, '/' + done() + error: (xhr, errorType, error) -> done(errorType) + + it 'can change the request headers', (done) -> + ses.webRequest.onBeforeSendHeaders (details, callback) -> + {requestHeaders} = details + requestHeaders.Accept = '*/*;test/header' + callback({requestHeaders}) + $.ajax + url: defaultURL + success: (data, textStatus, request) -> + assert.equal data, '/header/received' + done() + error: (xhr, errorType, error) -> done(errorType) + + describe 'webRequest.onSendHeaders', -> + afterEach -> + ses.webRequest.onSendHeaders null + + it 'receives details object', (done) -> + ses.webRequest.onSendHeaders (details, callback) -> + assert.equal typeof details.requestHeaders, 'object' + $.ajax + url: defaultURL + success: (data) -> + assert.equal data, '/' + done() + error: (xhr, errorType, error) -> done(errorType) + + describe 'webRequest.onHeadersReceived', -> + afterEach -> + ses.webRequest.onHeadersReceived null + + it 'receives details object', (done) -> + ses.webRequest.onHeadersReceived (details, callback) -> + assert.equal details.statusLine, 'HTTP/1.1 200 OK' + assert.equal details.statusCode, 200 + assert.equal details.responseHeaders['Custom'], 'Header' + callback({}) + $.ajax + url: defaultURL + success: (data) -> + assert.equal data, '/' + done() + error: (xhr, errorType, error) -> done(errorType) + + it 'can change the response header', (done) -> + ses.webRequest.onHeadersReceived (details, callback) -> + {responseHeaders} = details + responseHeaders['Custom'] = ['Changed'] + callback({responseHeaders}) + $.ajax + url: defaultURL + success: (data, status, xhr) -> + assert.equal xhr.getResponseHeader('Custom'), 'Changed' + assert.equal data, '/' + done() + error: (xhr, errorType, error) -> done(errorType) + + it 'does not change header by default', (done) -> + ses.webRequest.onHeadersReceived (details, callback) -> + callback({}) + $.ajax + url: defaultURL + success: (data, status, xhr) -> + assert.equal xhr.getResponseHeader('Custom'), 'Header' + assert.equal data, '/' + done() + error: (xhr, errorType, error) -> done(errorType) + + describe 'webRequest.onResponseStarted', -> + afterEach -> + ses.webRequest.onResponseStarted null + + it 'receives details object', (done) -> + ses.webRequest.onResponseStarted (details) -> + assert.equal typeof details.fromCache, 'boolean' + assert.equal details.statusLine, 'HTTP/1.1 200 OK' + assert.equal details.statusCode, 200 + assert.equal details.responseHeaders['Custom'], 'Header' + $.ajax + url: defaultURL + success: (data, status, xhr) -> + assert.equal xhr.getResponseHeader('Custom'), 'Header' + assert.equal data, '/' + done() + error: (xhr, errorType, error) -> done(errorType) + + describe 'webRequest.onBeforeRedirect', -> + afterEach -> + ses.webRequest.onBeforeRedirect null + ses.webRequest.onBeforeRequest null + + it 'receives details object', (done) -> + redirectURL = "#{defaultURL}redirect" + ses.webRequest.onBeforeRequest (details, callback) -> + if details.url is defaultURL + callback({redirectURL}) + else + callback({}) + ses.webRequest.onBeforeRedirect (details) -> + assert.equal typeof details.fromCache, 'boolean' + assert.equal details.statusLine, 'HTTP/1.1 307 Internal Redirect' + assert.equal details.statusCode, 307 + assert.equal details.redirectURL, redirectURL + $.ajax + url: defaultURL + success: (data, status, xhr) -> + assert.equal data, '/redirect' + done() + error: (xhr, errorType, error) -> done(errorType) + + describe 'webRequest.onCompleted', -> + afterEach -> + ses.webRequest.onCompleted null + + it 'receives details object', (done) -> + ses.webRequest.onCompleted (details) -> + assert.equal typeof details.fromCache, 'boolean' + assert.equal details.statusLine, 'HTTP/1.1 200 OK' + assert.equal details.statusCode, 200 + $.ajax + url: defaultURL + success: (data, status, xhr) -> + assert.equal data, '/' + done() + error: (xhr, errorType, error) -> done(errorType) + + describe 'webRequest.onErrorOccurred', -> + afterEach -> + ses.webRequest.onErrorOccurred null + ses.webRequest.onBeforeRequest null + + it 'receives details object', (done) -> + ses.webRequest.onBeforeRequest (details, callback) -> + callback(cancel: true) + ses.webRequest.onErrorOccurred (details) -> + assert.equal details.error, 'net::ERR_BLOCKED_BY_CLIENT' + done() + $.ajax + url: defaultURL + success: (data) -> done('unexpected success') From f976e1eda3ce2a403c3accb2e157a6f610955723 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 13:20:53 +0800 Subject: [PATCH 246/411] Add generic version for FillDetailsObject --- atom/browser/net/atom_network_delegate.cc | 67 ++++++++++++----------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 1625655004..a791e0aeb5 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -66,8 +66,7 @@ bool MatchesFilterCondition(net::URLRequest* request, } // Overloaded by multiple types to fill the |details| object. -void FillDetailsObject(base::DictionaryValue* details, - net::URLRequest* request) { +void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) { details->SetInteger("id", request->identifier()); details->SetString("url", request->url().spec()); details->SetString("method", request->method()); @@ -78,8 +77,8 @@ void FillDetailsObject(base::DictionaryValue* details, : "other"); } -void FillDetailsObject(base::DictionaryValue* details, - const net::HttpRequestHeaders& headers) { +void ToDictionary(base::DictionaryValue* details, + const net::HttpRequestHeaders& headers) { scoped_ptr dict(new base::DictionaryValue); net::HttpRequestHeaders::Iterator it(headers); while (it.GetNext()) @@ -87,8 +86,8 @@ void FillDetailsObject(base::DictionaryValue* details, details->Set("requestHeaders", dict.Pass()); } -void FillDetailsObject(base::DictionaryValue* details, - const net::HttpResponseHeaders* headers) { +void ToDictionary(base::DictionaryValue* details, + const net::HttpResponseHeaders* headers) { if (!headers) return; @@ -112,25 +111,37 @@ void FillDetailsObject(base::DictionaryValue* details, details->SetInteger("statusCode", headers->response_code()); } -void FillDetailsObject(base::DictionaryValue* details, const GURL& location) { +void ToDictionary(base::DictionaryValue* details, const GURL& location) { details->SetString("redirectURL", location.spec()); } -void FillDetailsObject(base::DictionaryValue* details, - const net::HostPortPair& host_port) { +void ToDictionary(base::DictionaryValue* details, + const net::HostPortPair& host_port) { if (host_port.host().empty()) details->SetString("ip", host_port.host()); } -void FillDetailsObject(base::DictionaryValue* details, bool from_cache) { +void ToDictionary(base::DictionaryValue* details, bool from_cache) { details->SetBoolean("fromCache", from_cache); } -void FillDetailsObject(base::DictionaryValue* details, - const net::URLRequestStatus& status) { +void ToDictionary(base::DictionaryValue* details, + const net::URLRequestStatus& status) { details->SetString("error", net::ErrorToString(status.error())); } +// Helper function to fill |details| with arbitrary |args|. +template +void FillDetailsObject(base::DictionaryValue* details, Arg arg) { + ToDictionary(details, arg); +} + +template +void FillDetailsObject(base::DictionaryValue* details, Arg arg, Args... args) { + ToDictionary(details, arg); + FillDetailsObject(details, args...); +} + // Fill the native types with the result from the response object. void ReadFromResponseObject(const base::DictionaryValue& response, GURL* new_location) { @@ -261,8 +272,7 @@ int AtomNetworkDelegate::OnBeforeSendHeaders( return net::OK; scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), *headers); + FillDetailsObject(details.get(), request, *headers); ResponseCallback response = base::Bind(OnListenerResultInUI, @@ -287,8 +297,7 @@ void AtomNetworkDelegate::OnSendHeaders( return; scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), headers); + FillDetailsObject(details.get(), request, headers); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -311,8 +320,7 @@ int AtomNetworkDelegate::OnHeadersReceived( return net::OK; scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), original_response_headers); + FillDetailsObject(details.get(), request, original_response_headers); ResponseCallback response = base::Bind(OnListenerResultInUI*>, @@ -336,11 +344,9 @@ void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, return; scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), new_location); - FillDetailsObject(details.get(), request->response_headers()); - FillDetailsObject(details.get(), request->GetSocketAddress()); - FillDetailsObject(details.get(), request->was_cached()); + FillDetailsObject(details.get(), request, new_location, + request->response_headers(), request->GetSocketAddress(), + request->was_cached()); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -361,9 +367,8 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { return; scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), request->response_headers()); - FillDetailsObject(details.get(), request->was_cached()); + FillDetailsObject(details.get(), request, request->response_headers(), + request->was_cached()); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -397,9 +402,8 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { return; scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), request->response_headers()); - FillDetailsObject(details.get(), request->was_cached()); + FillDetailsObject(details.get(), request, request->response_headers(), + request->was_cached()); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -412,9 +416,8 @@ void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { return; scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - FillDetailsObject(details.get(), request->was_cached()); - FillDetailsObject(details.get(), request->status()); + FillDetailsObject(details.get(), request, request->was_cached(), + request->status()); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, From 9438d42322b89b714b88634b9891f96f676c681e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 13:49:58 +0800 Subject: [PATCH 247/411] Use generic function to handle events --- atom/browser/api/atom_api_web_request.cc | 2 + atom/browser/net/atom_network_delegate.cc | 137 +++++++--------------- atom/browser/net/atom_network_delegate.h | 11 ++ 3 files changed, 58 insertions(+), 92 deletions(-) diff --git a/atom/browser/api/atom_api_web_request.cc b/atom/browser/api/atom_api_web_request.cc index 7b39c59423..a987369ed8 100644 --- a/atom/browser/api/atom_api_web_request.cc +++ b/atom/browser/api/atom_api_web_request.cc @@ -4,6 +4,8 @@ #include "atom/browser/api/atom_api_web_request.h" +#include + #include "atom/browser/atom_browser_context.h" #include "atom/browser/net/atom_network_delegate.h" #include "atom/common/native_mate_converters/callback.h" diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index a791e0aeb5..6bc25027db 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -243,20 +243,7 @@ int AtomNetworkDelegate::OnBeforeURLRequest( return brightray::NetworkDelegate::OnBeforeURLRequest( request, callback, new_url); - const auto& info = response_listeners_[kOnBeforeRequest]; - if (!MatchesFilterCondition(request, info.url_patterns)) - return net::OK; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request); - - ResponseCallback response = - base::Bind(OnListenerResultInUI, callback, new_url); - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(RunResponseListener, info.listener, base::Passed(&details), - response)); - return net::ERR_IO_PENDING; + return HandleResponseEvent(kOnBeforeRequest, request, callback, new_url); } int AtomNetworkDelegate::OnBeforeSendHeaders( @@ -267,21 +254,8 @@ int AtomNetworkDelegate::OnBeforeSendHeaders( return brightray::NetworkDelegate::OnBeforeSendHeaders( request, callback, headers); - const auto& info = response_listeners_[kOnBeforeSendHeaders]; - if (!MatchesFilterCondition(request, info.url_patterns)) - return net::OK; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request, *headers); - - ResponseCallback response = - base::Bind(OnListenerResultInUI, - callback, headers); - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(RunResponseListener, info.listener, base::Passed(&details), - response)); - return net::ERR_IO_PENDING; + return HandleResponseEvent( + kOnBeforeSendHeaders, request, callback, headers, *headers); } void AtomNetworkDelegate::OnSendHeaders( @@ -292,44 +266,21 @@ void AtomNetworkDelegate::OnSendHeaders( return; } - const auto& info = simple_listeners_[kOnSendHeaders]; - if (!MatchesFilterCondition(request, info.url_patterns)) - return; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request, headers); - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&RunSimpleListener, info.listener, base::Passed(&details))); + HandleSimpleEvent(kOnSendHeaders, request, headers); } int AtomNetworkDelegate::OnHeadersReceived( net::URLRequest* request, const net::CompletionCallback& callback, - const net::HttpResponseHeaders* original_response_headers, - scoped_refptr* override_response_headers, - GURL* allowed_unsafe_redirect_url) { + const net::HttpResponseHeaders* original, + scoped_refptr* override, + GURL* allowed) { if (!ContainsKey(response_listeners_, kOnHeadersReceived)) return brightray::NetworkDelegate::OnHeadersReceived( - request, callback, original_response_headers, override_response_headers, - allowed_unsafe_redirect_url); + request, callback, original, override, allowed); - const auto& info = response_listeners_[kOnHeadersReceived]; - if (!MatchesFilterCondition(request, info.url_patterns)) - return net::OK; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request, original_response_headers); - - ResponseCallback response = - base::Bind(OnListenerResultInUI*>, - callback, override_response_headers); - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(RunResponseListener, info.listener, base::Passed(&details), - response)); - return net::ERR_IO_PENDING; + return HandleResponseEvent( + kOnHeadersReceived, request, callback, override, original); } void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, @@ -339,18 +290,9 @@ void AtomNetworkDelegate::OnBeforeRedirect(net::URLRequest* request, return; } - const auto& info = simple_listeners_[kOnBeforeRedirect]; - if (!MatchesFilterCondition(request, info.url_patterns)) - return; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request, new_location, + HandleSimpleEvent(kOnBeforeRedirect, request, new_location, request->response_headers(), request->GetSocketAddress(), request->was_cached()); - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&RunSimpleListener, info.listener, base::Passed(&details))); } void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { @@ -362,17 +304,8 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { if (request->status().status() != net::URLRequestStatus::SUCCESS) return; - const auto& info = simple_listeners_[kOnResponseStarted]; - if (!MatchesFilterCondition(request, info.url_patterns)) - return; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request, request->response_headers(), + HandleSimpleEvent(kOnResponseStarted, request, request->response_headers(), request->was_cached()); - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(RunSimpleListener, info.listener, base::Passed(&details))); } void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { @@ -397,27 +330,47 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { return; } - const auto& info = simple_listeners_[kOnCompleted]; - if (!MatchesFilterCondition(request, info.url_patterns)) - return; - - scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request, request->response_headers(), + HandleSimpleEvent(kOnCompleted, request, request->response_headers(), request->was_cached()); - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(RunSimpleListener, info.listener, base::Passed(&details))); } void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { - const auto& info = simple_listeners_[kOnErrorOccurred]; + HandleSimpleEvent(kOnErrorOccurred, request, request->was_cached(), + request->status()); +} + +template +int AtomNetworkDelegate::HandleResponseEvent( + ResponseEvent type, + net::URLRequest* request, + const net::CompletionCallback& callback, + Out out, + Args... args) { + const auto& info = response_listeners_[type]; + if (!MatchesFilterCondition(request, info.url_patterns)) + return net::OK; + + scoped_ptr details(new base::DictionaryValue); + FillDetailsObject(details.get(), request, args...); + + ResponseCallback response = + base::Bind(OnListenerResultInUI, callback, out); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(RunResponseListener, info.listener, base::Passed(&details), + response)); + return net::ERR_IO_PENDING; +} + +template +void AtomNetworkDelegate::HandleSimpleEvent( + SimpleEvent type, net::URLRequest* request, Args... args) { + const auto& info = simple_listeners_[type]; if (!MatchesFilterCondition(request, info.url_patterns)) return; scoped_ptr details(new base::DictionaryValue); - FillDetailsObject(details.get(), request, request->was_cached(), - request->status()); + FillDetailsObject(details.get(), request, args...); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 927dc58aa9..8950a1b511 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -89,6 +89,17 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { void OnErrorOccurred(net::URLRequest* request); private: + template + void HandleSimpleEvent(SimpleEvent type, + net::URLRequest* request, + Args... args); + template + int HandleResponseEvent(ResponseEvent type, + net::URLRequest* request, + const net::CompletionCallback& callback, + Out out, + Args... args); + std::map simple_listeners_; std::map response_listeners_;; From 3c48198c3c2d63544c74f5254c93740cf0b4fc3d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 15:32:49 +0800 Subject: [PATCH 248/411] spec: Suppress navigator.webkitGetUserMedia test It is stressing the whole test case. --- spec/chromium-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 8122fb4a76..2cc2237385 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -45,7 +45,7 @@ describe 'chromium feature', -> done() w.loadURL url - describe 'navigator.webkitGetUserMedia', -> + xdescribe 'navigator.webkitGetUserMedia', -> it 'calls its callbacks', (done) -> @timeout 5000 navigator.webkitGetUserMedia audio: true, video: false, From 4d1e2230445b8ca8200db50bc2aad11b0ab8ed41 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 15:33:51 +0800 Subject: [PATCH 249/411] Cleanup the cookies code --- atom/browser/api/atom_api_cookies.cc | 374 ++++++++++----------------- atom/browser/api/atom_api_cookies.h | 45 +--- spec/api-session-spec.coffee | 5 +- 3 files changed, 146 insertions(+), 278 deletions(-) diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index 3b1bd499be..6323e5110c 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -7,7 +7,6 @@ #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" -#include "base/bind.h" #include "base/time/time.h" #include "base/values.h" #include "content/public/browser/browser_context.h" @@ -20,139 +19,21 @@ #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" -using atom::api::Cookies; using content::BrowserThread; -namespace { - -bool GetCookieListFromStore( - net::CookieStore* cookie_store, - const std::string& url, - const net::CookieMonster::GetCookieListCallback& callback) { - DCHECK(cookie_store); - GURL gurl(url); - net::CookieMonster* monster = cookie_store->GetCookieMonster(); - // Empty url will match all url cookies. - if (url.empty()) { - monster->GetAllCookiesAsync(callback); - return true; - } - - if (!gurl.is_valid()) - return false; - - monster->GetAllCookiesForURLAsync(gurl, callback); - return true; -} - -void RunGetCookiesCallbackOnUIThread(v8::Isolate* isolate, - const std::string& error_message, - const net::CookieList& cookie_list, - const Cookies::CookiesCallback& callback) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); - - if (!error_message.empty()) { - v8::Local error = mate::ConvertToV8(isolate, error_message); - callback.Run(error, v8::Null(isolate)); - return; - } - callback.Run(v8::Null(isolate), mate::ConvertToV8(isolate, cookie_list)); -} - -void RunRemoveCookiesCallbackOnUIThread( - v8::Isolate* isolate, - const std::string& error_message, - const Cookies::CookiesCallback& callback) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); - - if (!error_message.empty()) { - v8::Local error = mate::ConvertToV8(isolate, error_message); - callback.Run(error, v8::Null(isolate)); - return; - } - - callback.Run(v8::Null(isolate), v8::Null(isolate)); -} - -void RunSetCookiesCallbackOnUIThread(v8::Isolate* isolate, - const std::string& error_message, - bool set_success, - const Cookies::CookiesCallback& callback) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); - - if (!error_message.empty()) { - v8::Local error = mate::ConvertToV8(isolate, error_message); - callback.Run(error, v8::Null(isolate)); - return; - } - if (!set_success) { - v8::Local error = mate::ConvertToV8( - isolate, "Failed to set cookies"); - callback.Run(error, v8::Null(isolate)); - } - - callback.Run(v8::Null(isolate), v8::Null(isolate)); -} - -bool MatchesDomain(const base::DictionaryValue* filter, - const std::string& cookie_domain) { - std::string filter_domain; - if (!filter->GetString("domain", &filter_domain)) - return true; - - // Add a leading '.' character to the filter domain if it doesn't exist. - if (net::cookie_util::DomainIsHostOnly(filter_domain)) - filter_domain.insert(0, "."); - - std::string sub_domain(cookie_domain); - // Strip any leading '.' character from the input cookie domain. - if (!net::cookie_util::DomainIsHostOnly(sub_domain)) - sub_domain = sub_domain.substr(1); - - // Now check whether the domain argument is a subdomain of the filter domain. - for (sub_domain.insert(0, "."); - sub_domain.length() >= filter_domain.length();) { - if (sub_domain == filter_domain) { - return true; - } - const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot. - sub_domain.erase(0, next_dot); - } - return false; -} - -bool MatchesCookie(const base::DictionaryValue* filter, - const net::CanonicalCookie& cookie) { - std::string name, domain, path; - bool is_secure, session; - if (filter->GetString("name", &name) && name != cookie.Name()) - return false; - if (filter->GetString("path", &path) && path != cookie.Path()) - return false; - if (!MatchesDomain(filter, cookie.Domain())) - return false; - if (filter->GetBoolean("secure", &is_secure) && - is_secure != cookie.IsSecure()) - return false; - if (filter->GetBoolean("session", &session) && - session != cookie.IsPersistent()) - return false; - return true; -} - -} // namespace - namespace mate { +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + atom::api::Cookies::Error val) { + if (val == atom::api::Cookies::SUCCESS) + return v8::Null(isolate); + else + return v8::Exception::Error(StringToV8(isolate, "failed")); + } +}; + template<> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, @@ -161,11 +42,11 @@ struct Converter { dict.Set("name", val.Name()); dict.Set("value", val.Value()); dict.Set("domain", val.Domain()); - dict.Set("host_only", net::cookie_util::DomainIsHostOnly(val.Domain())); + dict.Set("hostOnly", net::cookie_util::DomainIsHostOnly(val.Domain())); dict.Set("path", val.Path()); dict.Set("secure", val.IsSecure()); - dict.Set("http_only", val.IsHttpOnly()); - dict.Set("session", val.IsPersistent()); + dict.Set("httpOnly", val.IsHttpOnly()); + dict.Set("session", !val.IsPersistent()); if (!val.IsPersistent()) dict.Set("expirationDate", val.ExpiryDate().ToDoubleT()); return dict.GetHandle(); @@ -178,121 +59,117 @@ namespace atom { namespace api { -Cookies::Cookies(content::BrowserContext* browser_context) - : request_context_getter_(browser_context->GetRequestContext()) { -} +namespace { -Cookies::~Cookies() { -} +// Returns whether |domain| matches |filter|. +bool MatchesDomain(std::string filter, const std::string& domain) { + // Add a leading '.' character to the filter domain if it doesn't exist. + if (net::cookie_util::DomainIsHostOnly(filter)) + filter.insert(0, "."); -void Cookies::Get(const base::DictionaryValue& options, - const CookiesCallback& callback) { - scoped_ptr filter( - options.DeepCopyWithoutEmptyChildren()); + std::string sub_domain(domain); + // Strip any leading '.' character from the input cookie domain. + if (!net::cookie_util::DomainIsHostOnly(sub_domain)) + sub_domain = sub_domain.substr(1); - content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&Cookies::GetCookiesOnIOThread, base::Unretained(this), - Passed(&filter), callback)); -} - -void Cookies::GetCookiesOnIOThread(scoped_ptr filter, - const CookiesCallback& callback) { - std::string url; - filter->GetString("url", &url); - if (!GetCookieListFromStore(GetCookieStore(), url, - base::Bind(&Cookies::OnGetCookies, base::Unretained(this), - Passed(&filter), callback))) { - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&RunGetCookiesCallbackOnUIThread, isolate(), - "URL is not valid", net::CookieList(), callback)); + // Now check whether the domain argument is a subdomain of the filter domain. + for (sub_domain.insert(0, "."); sub_domain.length() >= filter.length();) { + if (sub_domain == filter) + return true; + const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot. + sub_domain.erase(0, next_dot); } + return false; } -void Cookies::OnGetCookies(scoped_ptr filter, - const CookiesCallback& callback, - const net::CookieList& cookie_list) { +// Returns whether |cookie| matches |filter|. +bool MatchesCookie(const base::DictionaryValue* filter, + const net::CanonicalCookie& cookie) { + std::string str; + bool b; + if (filter->GetString("name", &str) && str != cookie.Name()) + return false; + if (filter->GetString("path", &str) && str != cookie.Path()) + return false; + if (filter->GetString("domain", &str) && !MatchesDomain(str, cookie.Domain())) + return false; + if (filter->GetBoolean("secure", &b) && b != cookie.IsSecure()) + return false; + if (filter->GetBoolean("session", &b) && b != !cookie.IsPersistent()) + return false; + return true; +} + +// Helper to returns the CookieStore. +inline net::CookieStore* GetCookieStore( + scoped_refptr getter) { + return getter->GetURLRequestContext()->cookie_store(); +} + +// Run |callback| on UI thread. +void RunCallbackInUI(const base::Closure& callback) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback); +} + +// Remove cookies from |list| not matching |filter|, and pass it to |callback|. +void FilterCookies(scoped_ptr filter, + const Cookies::GetCallback& callback, + const net::CookieList& list) { net::CookieList result; - for (const auto& cookie : cookie_list) { + for (const auto& cookie : list) { if (MatchesCookie(filter.get(), cookie)) result.push_back(cookie); } - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( - &RunGetCookiesCallbackOnUIThread, isolate(), "", result, callback)); + RunCallbackInUI(base::Bind(callback, Cookies::SUCCESS, result)); } -void Cookies::Remove(const mate::Dictionary& details, - const CookiesCallback& callback) { - GURL url; - std::string name; - std::string error_message; - if (!details.Get("url", &url) || !details.Get("name", &name)) { - error_message = "Details(url, name) of removing cookie are required."; - } - if (error_message.empty() && !url.is_valid()) { - error_message = "URL is not valid."; - } - if (!error_message.empty()) { - RunRemoveCookiesCallbackOnUIThread(isolate(), error_message, callback); - return; - } - content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&Cookies::RemoveCookiesOnIOThread, base::Unretained(this), - url, name, callback)); -} - -void Cookies::RemoveCookiesOnIOThread(const GURL& url, const std::string& name, - const CookiesCallback& callback) { - GetCookieStore()->DeleteCookieAsync(url, name, - base::Bind(&Cookies::OnRemoveCookies, base::Unretained(this), callback)); -} - -void Cookies::OnRemoveCookies(const CookiesCallback& callback) { - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&RunRemoveCookiesCallbackOnUIThread, isolate(), "", callback)); -} - -void Cookies::Set(const base::DictionaryValue& options, - const CookiesCallback& callback) { +// Receives cookies matching |filter| in IO thread. +void GetCookiesOnIO(scoped_refptr getter, + scoped_ptr filter, + const Cookies::GetCallback& callback) { std::string url; - std::string error_message; - if (!options.GetString("url", &url)) { - error_message = "The url field is required."; - } + filter->GetString("url", &url); - GURL gurl(url); - if (error_message.empty() && !gurl.is_valid()) { - error_message = "URL is not valid."; - } + auto filtered_callback = + base::Bind(FilterCookies, base::Passed(&filter), callback); - if (!error_message.empty()) { - RunSetCookiesCallbackOnUIThread(isolate(), error_message, false, callback); - return; - } - - scoped_ptr details( - options.DeepCopyWithoutEmptyChildren()); - - content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&Cookies::SetCookiesOnIOThread, base::Unretained(this), - Passed(&details), gurl, callback)); + net::CookieMonster* monster = GetCookieStore(getter)->GetCookieMonster(); + // Empty url will match all url cookies. + if (url.empty()) + monster->GetAllCookiesAsync(filtered_callback); + else + monster->GetAllCookiesForURLAsync(GURL(url), filtered_callback); } -void Cookies::SetCookiesOnIOThread(scoped_ptr details, - const GURL& url, - const CookiesCallback& callback) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); +// Removes cookie with |url| and |name| in IO thread. +void RemoveCookieOnIOThread(scoped_refptr getter, + const GURL& url, const std::string& name, + const base::Closure& callback) { + GetCookieStore(getter)->DeleteCookieAsync( + url, name, base::Bind(RunCallbackInUI, callback)); +} - std::string name, value, domain, path; +// Callback of SetCookie. +void OnSetCookie(const Cookies::SetCallback& callback, bool success) { + RunCallbackInUI( + base::Bind(callback, success ? Cookies::SUCCESS : Cookies::FAILED)); +} + +// Sets cookie with |details| in IO thread. +void SetCookieOnIO(scoped_refptr getter, + scoped_ptr details, + const Cookies::SetCallback& callback) { + std::string url, name, value, domain, path; bool secure = false; bool http_only = false; double expiration_date; - + details->GetString("url", &url); details->GetString("name", &name); details->GetString("value", &value); details->GetString("domain", &domain); details->GetString("path", &path); details->GetBoolean("secure", &secure); - details->GetBoolean("http_only", &http_only); + details->GetBoolean("httpOnly", &http_only); base::Time expiration_time; if (details->GetDouble("expirationDate", &expiration_date)) { @@ -301,29 +178,44 @@ void Cookies::SetCookiesOnIOThread(scoped_ptr details, base::Time::FromDoubleT(expiration_date); } - GetCookieStore()->GetCookieMonster()->SetCookieWithDetailsAsync( - url, - name, - value, - domain, - path, - expiration_time, - secure, - http_only, - false, - net::COOKIE_PRIORITY_DEFAULT, - base::Bind(&Cookies::OnSetCookies, base::Unretained(this), callback)); + GetCookieStore(getter)->GetCookieMonster()->SetCookieWithDetailsAsync( + GURL(url), name, value, domain, path, expiration_time, secure, http_only, + false, net::COOKIE_PRIORITY_DEFAULT, base::Bind(OnSetCookie, callback)); } -void Cookies::OnSetCookies(const CookiesCallback& callback, - bool set_success) { - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&RunSetCookiesCallbackOnUIThread, isolate(), "", set_success, - callback)); +} // namespace + +Cookies::Cookies(content::BrowserContext* browser_context) + : request_context_getter_(browser_context->GetRequestContext()) { } -net::CookieStore* Cookies::GetCookieStore() { - return request_context_getter_->GetURLRequestContext()->cookie_store(); +Cookies::~Cookies() { +} + +void Cookies::Get(const base::DictionaryValue& filter, + const GetCallback& callback) { + scoped_ptr copied(filter.CreateDeepCopy()); + auto getter = make_scoped_refptr(request_context_getter_); + content::BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(GetCookiesOnIO, getter, Passed(&copied), callback)); +} + +void Cookies::Remove(const GURL& url, const std::string& name, + const base::Closure& callback) { + auto getter = make_scoped_refptr(request_context_getter_); + content::BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(RemoveCookieOnIOThread, getter, url, name, callback)); +} + +void Cookies::Set(const base::DictionaryValue& details, + const SetCallback& callback) { + scoped_ptr copied(details.CreateDeepCopy()); + auto getter = make_scoped_refptr(request_context_getter_); + content::BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(SetCookieOnIO, getter, Passed(&copied), callback)); } // static diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h index 5afa1bd23c..302fd1b251 100644 --- a/atom/browser/api/atom_api_cookies.h +++ b/atom/browser/api/atom_api_cookies.h @@ -20,12 +20,7 @@ namespace content { class BrowserContext; } -namespace mate { -class Dictionary; -} - namespace net { -class CookieStore; class URLRequestContextGetter; } @@ -35,9 +30,13 @@ namespace api { class Cookies : public mate::TrackableObject { public: - // node.js style callback function(error, result) - typedef base::Callback, v8::Local)> - CookiesCallback; + enum Error { + SUCCESS, + FAILED, + }; + + using GetCallback = base::Callback; + using SetCallback = base::Callback; static mate::Handle Create(v8::Isolate* isolate, content::BrowserContext* browser_context); @@ -50,34 +49,12 @@ class Cookies : public mate::TrackableObject { explicit Cookies(content::BrowserContext* browser_context); ~Cookies(); - void Get(const base::DictionaryValue& options, - const CookiesCallback& callback); - void Remove(const mate::Dictionary& details, - const CookiesCallback& callback); - void Set(const base::DictionaryValue& details, - const CookiesCallback& callback); - - void GetCookiesOnIOThread(scoped_ptr filter, - const CookiesCallback& callback); - void OnGetCookies(scoped_ptr filter, - const CookiesCallback& callback, - const net::CookieList& cookie_list); - - void RemoveCookiesOnIOThread(const GURL& url, - const std::string& name, - const CookiesCallback& callback); - void OnRemoveCookies(const CookiesCallback& callback); - - void SetCookiesOnIOThread(scoped_ptr details, - const GURL& url, - const CookiesCallback& callback); - void OnSetCookies(const CookiesCallback& callback, - bool set_success); + void Get(const base::DictionaryValue& filter, const GetCallback& callback); + void Remove(const GURL& url, const std::string& name, + const base::Closure& callback); + void Set(const base::DictionaryValue& details, const SetCallback& callback); private: - // Must be called on IO thread. - net::CookieStore* GetCookieStore(); - net::URLRequestContextGetter* request_context_getter_; DISALLOW_COPY_AND_ASSIGN(Cookies); diff --git a/spec/api-session-spec.coffee b/spec/api-session-spec.coffee index 98e47e357a..03c62c1f1d 100644 --- a/spec/api-session-spec.coffee +++ b/spec/api-session-spec.coffee @@ -49,12 +49,11 @@ describe 'session module', -> it 'should remove cookies', (done) -> session.defaultSession.cookies.set {url: url, name: '2', value: '2'}, (error) -> return done(error) if error - session.defaultSession.cookies.remove {url: url, name: '2'}, (error) -> - return done(error) if error + session.defaultSession.cookies.remove url, '2', -> session.defaultSession.cookies.get {url: url}, (error, list) -> return done(error) if error for cookie in list when cookie.name is '2' - return done('Cookie not deleted') + return done('Cookie not deleted') done() describe 'session.clearStorageData(options)', -> From ab14a4466d0e01f0cb047d1d4ffb8bad0a122ae7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 15:41:04 +0800 Subject: [PATCH 250/411] docs: Improve the cookies docs --- docs/api/session.md | 127 +++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 67 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index d1db7e39e5..dcd674465c 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -63,7 +63,7 @@ Emitted when Electron is about to download `item` in `webContents`. Calling `event.preventDefault()` will cancel the download. ```javascript -session.on('will-download', function(event, item, webContents) { +session.defaultSession.on('will-download', function(event, item, webContents) { event.preventDefault(); require('request')(item.getURL(), function(data) { require('fs').writeFileSync('/somewhere', data); @@ -80,91 +80,84 @@ The following methods are available on instances of `Session`: The `cookies` gives you ability to query and modify cookies. For example: ```javascript -const BrowserWindow = require('electron').BrowserWindow; +// Query all cookies. +session.defaultSession.cookies.get({}, function(error, cookies) { + console.log(cookies); +}); -var win = new BrowserWindow({ width: 800, height: 600 }); +// Query all cookies associated with a specific url. +session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) { + console.log(cookies); +}); -win.loadURL('https://github.com'); - -win.webContents.on('did-finish-load', function() { - // Query all cookies. - win.webContents.session.cookies.get({}, function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); - - // Query all cookies associated with a specific url. - win.webContents.session.cookies.get({ url : "http://www.github.com" }, - function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); - - // Set a cookie with the given cookie data; - // may overwrite equivalent cookies if they exist. - win.webContents.session.cookies.set( - { url : "http://www.github.com", name : "dummy_name", value : "dummy"}, - function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); +// Set a cookie with the given cookie data; +// may overwrite equivalent cookies if they exist. +var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" }; +session.defaultSession.cookies.set(cookie, function(error) { + if (error) + console.error(error); }); ``` -#### `ses.cookies.get(details, callback)` +#### `ses.cookies.get(filter, callback)` -`details` Object, properties: +* `filter` Object + * `url` String __optional__ - Retrieves cookies which are associated with + `url`. Empty implies retrieving cookies of all urls. + * `name` String __optional__ - Filters cookies by name. + * `domain` String __optional__ - Retrieves cookies whose domains match or are + subdomains of `domains` + * `path` String __optional__ - Retrieves cookies whose path matches `path`. + * `secure` Boolean __optional__ - Filters cookies by their Secure property. + * `session` Boolean __optional__ - Filters out session or persistent cookies. +* `callback` Function -* `url` String - Retrieves cookies which are associated with `url`. - Empty implies retrieving cookies of all urls. -* `name` String - Filters cookies by name -* `domain` String - Retrieves cookies whose domains match or are subdomains of - `domains` -* `path` String - Retrieves cookies whose path matches `path` -* `secure` Boolean - Filters cookies by their Secure property -* `session` Boolean - Filters out session or persistent cookies. -* `callback` Function - function(error, cookies) -* `error` Error -* `cookies` Array - array of `cookie` objects, properties: +Sends a request to get all cookies matching `details`, `callback` will be called +with `callback(error, cookies)` on complete. + +`cookies` is an Array of `cookie` objects. + +* `cookie` Object * `name` String - The name of the cookie. * `value` String - The value of the cookie. * `domain` String - The domain of the cookie. - * `host_only` String - Whether the cookie is a host-only cookie. + * `hostOnly` String - Whether the cookie is a host-only cookie. * `path` String - The path of the cookie. - * `secure` Boolean - Whether the cookie is marked as Secure (typically HTTPS). - * `http_only` Boolean - Whether the cookie is marked as HttpOnly. + * `secure` Boolean - Whether the cookie is marked as secure. + * `httpOnly` Boolean - Whether the cookie is marked as HTTP only. * `session` Boolean - Whether the cookie is a session cookie or a persistent cookie with an expiration date. - * `expirationDate` Double - (Option) The expiration date of the cookie as + * `expirationDate` Double __optional__ - The expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies. #### `ses.cookies.set(details, callback)` -`details` Object, properties: - -* `url` String - Retrieves cookies which are associated with `url` -* `name` String - The name of the cookie. Empty by default if omitted. -* `value` String - The value of the cookie. Empty by default if omitted. -* `domain` String - The domain of the cookie. Empty by default if omitted. -* `path` String - The path of the cookie. Empty by default if omitted. -* `secure` Boolean - Whether the cookie should be marked as Secure. Defaults to - false. -* `session` Boolean - Whether the cookie should be marked as HttpOnly. Defaults - to false. -* `expirationDate` Double - The expiration date of the cookie as the number of - seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie. - -* `callback` Function - function(error) - * `error` Error - -#### `ses.cookies.remove(details, callback)` - * `details` Object - * `url` String - The URL associated with the cookie - * `name` String - The name of cookie to remove -* `callback` Function - function(error) - * `error` Error + * `url` String - Retrieves cookies which are associated with `url` + * `name` String - The name of the cookie. Empty by default if omitted. + * `value` String - The value of the cookie. Empty by default if omitted. + * `domain` String - The domain of the cookie. Empty by default if omitted. + * `path` String - The path of the cookie. Empty by default if omitted. + * `secure` Boolean - Whether the cookie should be marked as Secure. Defaults to + false. + * `session` Boolean - Whether the cookie should be marked as HttpOnly. Defaults + to false. + * `expirationDate` Double - The expiration date of the cookie as the number of + seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie. +* `callback` Function + +Sets the cookie with `details`, `callback` will be called with `callback(error)` +on complete. + +#### `ses.cookies.remove(url, name, callback)` + +* `url` String - The URL associated with the cookie. +* `name` String - The name of cookie to remove. +* `callback` Function + +Removes the cookies matching `url` and `name`, `callback` will called with +`callback()` on complete. #### `ses.clearCache(callback)` From 56c1f04a51b2271a972fb00c88a79803c96afd50 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 15:45:02 +0800 Subject: [PATCH 251/411] docs: Update webRequest API --- docs/api/session.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index dcd674465c..21464bd481 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -294,9 +294,8 @@ The `filter` is an object that has an `urls` property, which is an Array of URL patterns that will be used to filter out the requests that do not match the URL patterns. If the `filter` is omitted then all requests will be matched. -For certain events the `listener` is required to return an object that describes -how to handle the request, users can specify the `cancel` property to `true` to -cancel the request. +For certain events the `listener` is passed with a `callback`, which should be +called with an `response` object when `listener` has done its work. ```javascript // Modify the user agent for all requests to the following urls. @@ -304,9 +303,9 @@ var filter = { urls: ["https://*.github.com/*", "*://electron.github.io"] }; -session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details) { +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) { details.requestHeaders['User-Agent'] = "MyAgent"; - return {cancel: false, requestHeaders: details.requestHeaders}; + callback({cancel: false, requestHeaders: details.requestHeaders}); }); ``` @@ -315,8 +314,8 @@ session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details) * `filter` Object * `listener` Function -The `listener` will be called with `listener(details)` when a request is about -to occur. +The `listener` will be called with `listener(details, callback)` when a request +is about to occur. * `details` Object * `id` Integer @@ -325,7 +324,7 @@ to occur. * `resourceType` String * `timestamp` Double -The `listener` has to return an `response` object: +The `callback` has to be called with an `response` object: * `response` Object * `cancel` Boolean __optional__ @@ -337,9 +336,9 @@ The `listener` has to return an `response` object: * `filter` Object * `listener` Function -The `listener` will be called with `listener(details)` before sending an HTTP -request, once the request headers are available. This may occur after a TCP -connection is made to the server, but before any http data is sent. +The `listener` will be called with `listener(details, callback)` before sending +an HTTP request, once the request headers are available. This may occur after a +TCP connection is made to the server, but before any http data is sent. * `details` Object * `id` Integer @@ -349,7 +348,7 @@ connection is made to the server, but before any http data is sent. * `timestamp` Double * `requestHeaders` Object -The `listener` has to return an `response` object: +The `callback` has to be called with an `response` object: * `response` Object * `cancel` Boolean __optional__ @@ -378,8 +377,8 @@ response are visible by the time this listener is fired. * `filter` Object * `listener` Function -The `listener` will be called with `listener(details)` when HTTP response -headers of a request have been received. +The `listener` will be called with `listener(details, callback)` when HTTP +response headers of a request have been received. * `details` Object * `id` String @@ -391,7 +390,7 @@ headers of a request have been received. * `statusCode` Integer * `responseHeaders` Object -The `listener` has to return an `response` object: +The `callback` has to be called with an `response` object: * `response` Object * `cancel` Boolean From 10cad5d9ec75a3034bd0684722aabd98aad21abf Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 15:47:15 +0800 Subject: [PATCH 252/411] Passing '' to fromPartiion should return default partition --- atom/browser/api/lib/session.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/lib/session.coffee b/atom/browser/api/lib/session.coffee index 7fc3c2df6f..5c65aa29cf 100644 --- a/atom/browser/api/lib/session.coffee +++ b/atom/browser/api/lib/session.coffee @@ -6,6 +6,7 @@ PERSIST_PERFIX = 'persist:' # Returns the Session from |partition| string. exports.fromPartition = (partition='') -> + return exports.defaultSession if partition is '' if partition.startsWith PERSIST_PERFIX bindings.fromPartition partition.substr(PERSIST_PERFIX.length), false else @@ -14,7 +15,7 @@ exports.fromPartition = (partition='') -> # Returns the default session. Object.defineProperty exports, 'defaultSession', enumerable: true - get: -> exports.fromPartition 'persist:' + get: -> bindings.fromPartition '', false wrapSession = (session) -> # session is an EventEmitter. From c6085f5178b22277c3d252815d5ac975fbcc5e01 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sat, 12 Dec 2015 21:41:17 +0900 Subject: [PATCH 253/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/session.md | 308 ++++++++++++++++++++----- 1 file changed, 244 insertions(+), 64 deletions(-) diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index 3fda5747f6..5728b54386 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -11,7 +11,7 @@ var BrowserWindow = require('browser-window'); var win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL("http://github.com"); -var ses = win.webContents.session +var ses = win.webContents.session; ``` ## Methods @@ -62,7 +62,7 @@ Electron의 `webContents`에서 `item`을 다운로드할 때 발생하는 이 `event.preventDefault()` 메서드를 호출하면 다운로드를 취소합니다. ```javascript -session.on('will-download', function(event, item, webContents) { +session.defaultSession.on('will-download', function(event, item, webContents) { event.preventDefault(); require('request')(item.getURL(), function(data) { require('fs').writeFileSync('/somewhere', data); @@ -80,60 +80,51 @@ session.on('will-download', function(event, item, webContents) { 있습니다: ```javascript -var BrowserWindow = require('browser-window'); +// 모든 쿠키를 요청합니다. +session.defaultSession.cookies.get({}, function(error, cookies) { + console.log(cookies); +}); -var win = new BrowserWindow({ width: 800, height: 600 }); +// url에 관련된 쿠키를 모두 가져옵니다. +session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) { + console.log(cookies); +}); -win.loadURL('https://github.com'); - -win.webContents.on('did-finish-load', function() { - // 모든 쿠키를 가져옵니다. - win.webContents.session.cookies.get({}, function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); - - // Url에 관련된 쿠키를 모두 가져옵니다. - win.webContents.session.cookies.get({ url : "http://www.github.com" }, - function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); - - // 지정한 쿠키 데이터를 설정합니다. - // 동일한 쿠키가 있으면 해당 쿠키를 덮어씁니다. - win.webContents.session.cookies.set( - { url : "http://www.github.com", name : "dummy_name", value : "dummy"}, - function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); +// 지정한 쿠키 데이터를 설정합니다. +// 동일한 쿠키가 있으면 해당 쿠키를 덮어씁니다. +var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" }; +session.defaultSession.cookies.set(cookie, function(error) { + if (error) + console.error(error); }); ``` -#### `ses.cookies.get(details, callback)` +#### `ses.cookies.get(filter, callback)` -`details` Object +* `filter` Object + * `url` String __optional__ - `url`에 해당하는 쿠키를 취득합니다. 이 속성을 + 생략하면 모든 url에서 찾습니다. + * `name` String __optional__ - 쿠키의 이름입니다. + * `domain` String __optional__ - 도메인 또는 서브 도메인에 일치하는 쿠키를 + 취득합니다. + * `path` String __optional__ - `path`에 일치하는 쿠키를 취득합니다. + * `secure` Boolean __optional__ - 보안 속성에 따라 쿠키를 필터링합니다. + * `session` Boolean __optional__ - 세션 또는 지속성 쿠키를 필터링합니다. +* `callback` Function -* `url` String - `url`에 관련된 쿠키를 가져옵니다. 이 속성을 비워두면 모든 url의 - 쿠키를 가져옵니다. -* `name` String - 이름을 기준으로 쿠키를 필터링합니다. -* `domain` String - `domain`과 일치하는 도메인과 서브 도메인에 대한 쿠키를 가져옵니다. -* `path` String - `path`와 일치하는 경로에 대한 쿠키를 가져옵니다. -* `secure` Boolean - 보안 속성을 기준으로 쿠키를 필터링합니다. -* `session` Boolean - 세션 또는 영구 쿠키를 필터링합니다. +`details` 객체에서 묘사한 모든 쿠키를 요청합니다. 모든 작업이 끝나면 `callback`이 +`callback(error, cookies)` 형태로 호출됩니다. -* `callback` Function - function(error, cookies) -* `error` Error -* `cookies` Array - `cookie` 객체의 배열, 속성은 다음과 같습니다: +`cookies`는 `cookie` 객체의 배열입니다. + +* `cookie` Object * `name` String - 쿠키의 이름. * `value` String - 쿠키의 값. * `domain` String - 쿠키의 도메인. - * `host_only` String - 쿠키가 호스트 전용인가에 대한 여부. + * `hostOnly` String - 쿠키가 호스트 전용인가에 대한 여부. * `path` String - 쿠키의 경로. - * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부. (일반적으로 - HTTPS) - * `http_only` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부. + * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부. + * `httpOnly` Boolean - 쿠키가 HTTP로만 표시되는지에 대한 여부. * `session` Boolean - 쿠키가 세션 쿠키 또는 만료일이 있는 영구 쿠키인지에 대한 여부. * `expirationDate` Double - (Option) UNIX 시간으로 표시되는 쿠키의 만료일에 @@ -141,30 +132,31 @@ win.webContents.on('did-finish-load', function() { #### `ses.cookies.set(details, callback)` -`details` Object +* `details` Object + * `url` String - `url`에 관련된 쿠키를 가져옵니다. + * `name` String - 쿠키의 이름입니다. 기본적으로 비워두면 생략됩니다. + * `value` String - 쿠키의 값입니다. 기본적으로 비워두면 생략됩니다. + * `domain` String - 쿠키의 도메인입니다. 기본적으로 비워두면 생략됩니다. + * `path` String - 쿠키의 경로입니다. 기본적으로 비워두면 생략됩니다. + * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부입니다. 기본값은 + false입니다. + * `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 + false입니다. + * `expirationDate` Double __optional__ - UNIX 시간으로 표시되는 쿠키의 만료일에 + 대한 초 단위 시간입니다. 세션 쿠키에 제공되지 않습니다. +* `callback` Function -* `url` String - `url`에 관련된 쿠키를 가져옵니다. -* `name` String - 쿠키의 이름입니다. 기본적으로 비워두면 생략됩니다. -* `value` String - 쿠키의 값입니다. 기본적으로 비워두면 생략됩니다. -* `domain` String - 쿠키의 도메인입니다. 기본적으로 비워두면 생략됩니다. -* `path` String - 쿠키의 경로입니다. 기본적으로 비워두면 생략됩니다. -* `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부입니다. 기본값은 - false입니다. -* `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 - false입니다. -* `expirationDate` Double - UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 - 시간입니다. 생략하면 쿠키는 세션 쿠키가 됩니다. +`details` 객체에 따라 쿠키를 설정합니다. 작업이 완료되면 `callback`이 +`callback(error)` 형태로 호출됩니다. -* `callback` Function - function(error) - * `error` Error +#### `ses.cookies.remove(url, name, callback)` -#### `ses.cookies.remove(details, callback)` +* `url` String - 쿠키와 관련된 URL입니다. +* `name` String - 지울 쿠키의 이름입니다. +* `callback` Function -* `details` Object, proprties: - * `url` String - 쿠키와 관련된 URL입니다. - * `name` String - 지울 쿠키의 이름입니다. -* `callback` Function - function(error) - * `error` Error +`url`과 `name`에 일치하는 쿠키를 삭제합니다. 작업이 완료되면 `callback`이 +`callback()` 형식으로 호출됩니다. #### `ses.clearCache(callback)` @@ -287,3 +279,191 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c callback(false); }); ``` + +#### `ses.webRequest` + +`webRequest` API는 생명주기의 다양한 단계에 맞춰 요청 컨텐츠를 가로채거나 변경할 수 +있도록 합니다. + +각 API는 `filter`와 `listener`를 선택적으로 받을 수 있습니다. `listener`는 API의 +이벤트가 발생했을 때 `listener(details)` 형태로 호출되며 `defails`는 요청을 묘사하는 +객체입니다. `listener`에 `null`을 전달하면 이벤트 수신을 중지합니다. + +`filter`는 `urls` 속성을 가진 객체입니다. 이 속성은 URL 규칙의 배열이며 URL 규칙에 +일치하지 않는 요청을 모두 거르는데 사용됩니다. 만약 `filter`가 생략되면 모든 요청을 +여과 없이 통과시킵니다. + +어떤 `listener`의 이벤트들은 `callback`을 같이 전달하는데, 이벤트 처리시 +`listener`의 작업을 완료한 후 `response` 객체를 포함하여 호출해야 합니다. + +```javascript +// 다음 url에 대한 User Agent를 조작합니다. +var filter = { + urls: ["https://*.github.com/*", "*://electron.github.io"] +}; + +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) { + details.requestHeaders['User-Agent'] = "MyAgent"; + callback({cancel: false, requestHeaders: details.requestHeaders}); +}); +``` + +#### `ses.webRequest.onBeforeRequest([filter, ]listener)` + +* `filter` Object +* `listener` Function + +요청이 발생하면 `listener`가 `listener(details, callback)` 형태로 호출됩니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + +`callback`은 `response` 객체와 함께 호출되어야 합니다: + +* `response` Object + * `cancel` Boolean __optional__ + * `redirectURL` String __optional__ - 원래 요청은 전송과 완료가 방지되지만 이 + 속성을 지정하면 해당 URL로 리다이렉트됩니다. + +#### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener`가 +`listener(details, callback)` 형태로 호출됩니다. 이 이벤트는 서버와의 TCP 연결이 +완료된 후에 발생할 수도 있지만 http 데이터가 전송되기 전에 발생합니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +`callback`은 `response` 객체와 함께 호출되어야 합니다: + +* `response` Object + * `cancel` Boolean __optional__ + * `requestHeaders` Object __optional__ - 이 속성이 제공되면, 요청은 이 헤더로 + 만들어 집니다. + +#### `ses.webRequest.onSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +서버에 요청이 전송되기 바로 전에 `listener`가 `listener(details)` 형태로 호출됩니다. +이전 `onBeforeSendHeaders`의 response와 다른점은 리스너가 호출되는 시간으로 볼 수 +있습니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +#### `ses.webRequest.onHeadersReceived([filter,] listener)` + +* `filter` Object +* `listener` Function + +요청의 HTTP 응답 헤더를 받았을 때 `listener`가 `listener(details, callback)` 형태로 +호출됩니다. + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object + +`callback`은 `response` 객체와 함께 호출되어야 합니다: + +* `response` Object + * `cancel` Boolean + * `responseHeaders` Object __optional__ - 이 속성이 제공되면 서버는 이 헤더와 + 함께 응답합니다. + +#### `ses.webRequest.onResponseStarted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +요청 본문의 첫 번째 바이트를 받았을 때 `listener`가 `listener(details)` 형태로 +호출됩니다. 이는 HTTP 요청에서 상태 줄과 요청 헤더가 사용 가능한 상태를 의미합니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean - 응답을 디스크 캐시에서 가져올지에 대한 여부. + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onBeforeRedirect([filter, ]listener)` + +* `filter` Object +* `listener` Function + +서버에서 시작된 리다이렉트가 발생했을 때 `listener`가 `listener(details)` 형태로 +호출됩니다. + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String __optional__ - 요청이 실질적으로 전송될 서버 아이피 주소. + * `fromCache` Boolean + * `responseHeaders` Object + +#### `ses.webRequest.onCompleted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +요청이 완료되면 `listener`가 `listener(details)` 형태로 호출됩니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onErrorOccurred([filter, ]listener)` + +* `filter` Object +* `listener` Function + +에러가 발생하면 `listener`가 `listener(details)` 형태로 호출됩니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean + * `error` String - 에러 설명. From 0df9eeb2ddde8d9634490480a1d1f968e7a45e29 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 12 Dec 2015 22:16:28 +0800 Subject: [PATCH 254/411] Backport https://codereview.chromium.org/1406133003 --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index 132f5b8d83..1237a4be4a 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'cfbe8ec7e14af4cabd1474386f54e197db1f7ac1' +LIBCHROMIUMCONTENT_COMMIT = '66bd8d1c705b7258f76c82436e4b16e82afbbd33' PLATFORM = { 'cygwin': 'win32', From 8ae99ebd1f3b1b3b7678bcb3735ca9c8f114f245 Mon Sep 17 00:00:00 2001 From: "Brian R. Bondy" Date: Sat, 12 Dec 2015 22:25:45 -0500 Subject: [PATCH 255/411] Indicate that Update 5 is required for VS2013 --- docs/development/build-instructions-windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 733966f1da..2342131a78 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -5,7 +5,7 @@ Follow the guidelines below for building Electron on Windows. ## Prerequisites * Windows 7 / Server 2008 R2 or higher -* Visual Studio 2013 - [download VS 2013 Community Edition for +* Visual Studio 2013 with Update 5 - [download VS 2013 Community Edition for free](https://www.visualstudio.com/downloads/download-visual-studio-vs). * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) From db73647d07bacd728fd0160e738ef2ff44199e24 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sun, 13 Dec 2015 16:29:18 +0900 Subject: [PATCH 256/411] Revert ":memo: Update as upstream" This reverts commit efac12ed89258e9b18041313965df53efc05a82b. --- docs/api/browser-window.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 00ea09bf96..2fbd1544c3 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -4,12 +4,8 @@ The `BrowserWindow` class gives you the ability to create a browser window. For example: ```javascript -// 메인 프로세스에서 const BrowserWindow = require('electron').BrowserWindow; -// 또는 랜더러 프로세스에서 -const BrowserWindow = require('electron').remote.BrowserWindow; - var win = new BrowserWindow({ width: 800, height: 600, show: false }); win.on('closed', function() { win = null; From d0fe2ac904933559081a5d80bfd8d1e120e0cf06 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sun, 13 Dec 2015 16:33:27 +0900 Subject: [PATCH 257/411] :memo: Fix confused translation [ci skip] --- docs-translations/ko-KR/api/browser-window.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 91a813f2da..a278a50752 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -5,8 +5,12 @@ 다음 예제는 윈도우 창을 생성합니다: ```javascript +// 메인 프로세스에서 const BrowserWindow = require('electron').BrowserWindow; +// 또는 랜더러 프로세스에서 +const BrowserWindow = require('electron').remote.BrowserWindow; + var win = new BrowserWindow({ width: 800, height: 600, show: false }); win.on('closed', function() { win = null; From 3c5e5053e3138bd3b661bac23ba8cf337addba36 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 14 Dec 2015 00:52:05 +0530 Subject: [PATCH 258/411] browser: dont lose coordinates in capturepage src rect --- atom/browser/native_window.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 8e686924ac..3013923ecd 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -282,22 +282,22 @@ void NativeWindow::CapturePage(const gfx::Rect& rect, } // Capture full page if user doesn't specify a |rect|. - const gfx::Size view_size = rect.IsEmpty() ? view->GetViewBounds().size() : - rect.size(); + const gfx::Rect view_rect = rect.IsEmpty() ? view->GetViewBounds() : + rect; // By default, the requested bitmap size is the view size in screen // coordinates. However, if there's more pixel detail available on the // current system, increase the requested bitmap size to capture it all. - gfx::Size bitmap_size = view_size; + gfx::Size bitmap_size = view_rect.size(); const gfx::NativeView native_view = view->GetNativeView(); gfx::Screen* const screen = gfx::Screen::GetScreenFor(native_view); const float scale = screen->GetDisplayNearestWindow(native_view).device_scale_factor(); if (scale > 1.0f) - bitmap_size = gfx::ScaleToCeiledSize(view_size, scale); + bitmap_size = gfx::ScaleToCeiledSize(view_rect.size(), scale); host->CopyFromBackingStore( - gfx::Rect(view_size), + view_rect, bitmap_size, base::Bind(&NativeWindow::OnCapturePageDone, weak_factory_.GetWeakPtr(), From f48c28f22bdfb28da615e22a911373d990ede81d Mon Sep 17 00:00:00 2001 From: Arinde Eniola Date: Mon, 14 Dec 2015 01:02:30 +0100 Subject: [PATCH 259/411] Update auto-updater.md --- docs/api/auto-updater.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index fa4f92cb93..0a5ca2d2d3 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -31,7 +31,7 @@ The server-side setup is also different from OS X. You can read the documents of ### Linux -There is not built-in support for auto-updater on Linux, so it is recommended to +There is no built-in support for auto-updater on Linux, so it is recommended to use the distribution's package manager to update your app. ## Events From 2fa7088fd123a189ca8aae59532eae3d5f3541a0 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 14 Dec 2015 18:11:01 +0800 Subject: [PATCH 260/411] Upgrade brightray for atom/brightray#184 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 006fdfba00..58ff3de971 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 006fdfba003f8350abf02446ff3868889a7b888c +Subproject commit 58ff3de971bf5b8cbaed77bf6a2a0fd6199783e8 From 2d2ad0d33bdfc7d89fc2e68deb363fd846492328 Mon Sep 17 00:00:00 2001 From: "Sean Francis N. Ballais" Date: Thu, 10 Dec 2015 19:46:06 +0800 Subject: [PATCH 261/411] Addeda versionin package.json. --- package.json | 1 + script/bump-version.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/package.json b/package.json index ea29419925..af0e315dda 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "electron", + "version": "0.36.0", "devDependencies": { "asar": "^0.8.0", "coffee-script": "^1.9.2", diff --git a/script/bump-version.py b/script/bump-version.py index 3ee0b23df3..e2c22e8cd9 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -31,6 +31,7 @@ def main(): update_win_rc(version, versions) update_version_h(versions) update_info_plist(version) + update_package_json(version) tag_version(version) @@ -113,6 +114,21 @@ def update_info_plist(version): f.write(''.join(lines)) +def update_package_json(version): + package_json = 'package.json' + with open(package_json, 'r') as f: + lines = f.readlines() + + for i in range(0, len(lines)): + line = lines[i]; + if 'version' in line: + lines[i] = ' "version": "{0}",\n'.format(version) + break + + with open(package_json, 'w') as f: + f.write(''.join(lines)) + + def tag_version(version): execute(['git', 'commit', '-a', '-m', 'Bump v{0}'.format(version)]) From bd1b6fdc74fa0c3ea8501be2ee9eed0c85204951 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sat, 12 Dec 2015 21:41:17 +0900 Subject: [PATCH 262/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/browser-window.md | 4 + docs-translations/ko-KR/api/session.md | 308 ++++++++++++++---- 2 files changed, 248 insertions(+), 64 deletions(-) diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 91a813f2da..a278a50752 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -5,8 +5,12 @@ 다음 예제는 윈도우 창을 생성합니다: ```javascript +// 메인 프로세스에서 const BrowserWindow = require('electron').BrowserWindow; +// 또는 랜더러 프로세스에서 +const BrowserWindow = require('electron').remote.BrowserWindow; + var win = new BrowserWindow({ width: 800, height: 600, show: false }); win.on('closed', function() { win = null; diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index 3fda5747f6..5728b54386 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -11,7 +11,7 @@ var BrowserWindow = require('browser-window'); var win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL("http://github.com"); -var ses = win.webContents.session +var ses = win.webContents.session; ``` ## Methods @@ -62,7 +62,7 @@ Electron의 `webContents`에서 `item`을 다운로드할 때 발생하는 이 `event.preventDefault()` 메서드를 호출하면 다운로드를 취소합니다. ```javascript -session.on('will-download', function(event, item, webContents) { +session.defaultSession.on('will-download', function(event, item, webContents) { event.preventDefault(); require('request')(item.getURL(), function(data) { require('fs').writeFileSync('/somewhere', data); @@ -80,60 +80,51 @@ session.on('will-download', function(event, item, webContents) { 있습니다: ```javascript -var BrowserWindow = require('browser-window'); +// 모든 쿠키를 요청합니다. +session.defaultSession.cookies.get({}, function(error, cookies) { + console.log(cookies); +}); -var win = new BrowserWindow({ width: 800, height: 600 }); +// url에 관련된 쿠키를 모두 가져옵니다. +session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) { + console.log(cookies); +}); -win.loadURL('https://github.com'); - -win.webContents.on('did-finish-load', function() { - // 모든 쿠키를 가져옵니다. - win.webContents.session.cookies.get({}, function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); - - // Url에 관련된 쿠키를 모두 가져옵니다. - win.webContents.session.cookies.get({ url : "http://www.github.com" }, - function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); - - // 지정한 쿠키 데이터를 설정합니다. - // 동일한 쿠키가 있으면 해당 쿠키를 덮어씁니다. - win.webContents.session.cookies.set( - { url : "http://www.github.com", name : "dummy_name", value : "dummy"}, - function(error, cookies) { - if (error) throw error; - console.log(cookies); - }); +// 지정한 쿠키 데이터를 설정합니다. +// 동일한 쿠키가 있으면 해당 쿠키를 덮어씁니다. +var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" }; +session.defaultSession.cookies.set(cookie, function(error) { + if (error) + console.error(error); }); ``` -#### `ses.cookies.get(details, callback)` +#### `ses.cookies.get(filter, callback)` -`details` Object +* `filter` Object + * `url` String __optional__ - `url`에 해당하는 쿠키를 취득합니다. 이 속성을 + 생략하면 모든 url에서 찾습니다. + * `name` String __optional__ - 쿠키의 이름입니다. + * `domain` String __optional__ - 도메인 또는 서브 도메인에 일치하는 쿠키를 + 취득합니다. + * `path` String __optional__ - `path`에 일치하는 쿠키를 취득합니다. + * `secure` Boolean __optional__ - 보안 속성에 따라 쿠키를 필터링합니다. + * `session` Boolean __optional__ - 세션 또는 지속성 쿠키를 필터링합니다. +* `callback` Function -* `url` String - `url`에 관련된 쿠키를 가져옵니다. 이 속성을 비워두면 모든 url의 - 쿠키를 가져옵니다. -* `name` String - 이름을 기준으로 쿠키를 필터링합니다. -* `domain` String - `domain`과 일치하는 도메인과 서브 도메인에 대한 쿠키를 가져옵니다. -* `path` String - `path`와 일치하는 경로에 대한 쿠키를 가져옵니다. -* `secure` Boolean - 보안 속성을 기준으로 쿠키를 필터링합니다. -* `session` Boolean - 세션 또는 영구 쿠키를 필터링합니다. +`details` 객체에서 묘사한 모든 쿠키를 요청합니다. 모든 작업이 끝나면 `callback`이 +`callback(error, cookies)` 형태로 호출됩니다. -* `callback` Function - function(error, cookies) -* `error` Error -* `cookies` Array - `cookie` 객체의 배열, 속성은 다음과 같습니다: +`cookies`는 `cookie` 객체의 배열입니다. + +* `cookie` Object * `name` String - 쿠키의 이름. * `value` String - 쿠키의 값. * `domain` String - 쿠키의 도메인. - * `host_only` String - 쿠키가 호스트 전용인가에 대한 여부. + * `hostOnly` String - 쿠키가 호스트 전용인가에 대한 여부. * `path` String - 쿠키의 경로. - * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부. (일반적으로 - HTTPS) - * `http_only` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부. + * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부. + * `httpOnly` Boolean - 쿠키가 HTTP로만 표시되는지에 대한 여부. * `session` Boolean - 쿠키가 세션 쿠키 또는 만료일이 있는 영구 쿠키인지에 대한 여부. * `expirationDate` Double - (Option) UNIX 시간으로 표시되는 쿠키의 만료일에 @@ -141,30 +132,31 @@ win.webContents.on('did-finish-load', function() { #### `ses.cookies.set(details, callback)` -`details` Object +* `details` Object + * `url` String - `url`에 관련된 쿠키를 가져옵니다. + * `name` String - 쿠키의 이름입니다. 기본적으로 비워두면 생략됩니다. + * `value` String - 쿠키의 값입니다. 기본적으로 비워두면 생략됩니다. + * `domain` String - 쿠키의 도메인입니다. 기본적으로 비워두면 생략됩니다. + * `path` String - 쿠키의 경로입니다. 기본적으로 비워두면 생략됩니다. + * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부입니다. 기본값은 + false입니다. + * `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 + false입니다. + * `expirationDate` Double __optional__ - UNIX 시간으로 표시되는 쿠키의 만료일에 + 대한 초 단위 시간입니다. 세션 쿠키에 제공되지 않습니다. +* `callback` Function -* `url` String - `url`에 관련된 쿠키를 가져옵니다. -* `name` String - 쿠키의 이름입니다. 기본적으로 비워두면 생략됩니다. -* `value` String - 쿠키의 값입니다. 기본적으로 비워두면 생략됩니다. -* `domain` String - 쿠키의 도메인입니다. 기본적으로 비워두면 생략됩니다. -* `path` String - 쿠키의 경로입니다. 기본적으로 비워두면 생략됩니다. -* `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부입니다. 기본값은 - false입니다. -* `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 - false입니다. -* `expirationDate` Double - UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 - 시간입니다. 생략하면 쿠키는 세션 쿠키가 됩니다. +`details` 객체에 따라 쿠키를 설정합니다. 작업이 완료되면 `callback`이 +`callback(error)` 형태로 호출됩니다. -* `callback` Function - function(error) - * `error` Error +#### `ses.cookies.remove(url, name, callback)` -#### `ses.cookies.remove(details, callback)` +* `url` String - 쿠키와 관련된 URL입니다. +* `name` String - 지울 쿠키의 이름입니다. +* `callback` Function -* `details` Object, proprties: - * `url` String - 쿠키와 관련된 URL입니다. - * `name` String - 지울 쿠키의 이름입니다. -* `callback` Function - function(error) - * `error` Error +`url`과 `name`에 일치하는 쿠키를 삭제합니다. 작업이 완료되면 `callback`이 +`callback()` 형식으로 호출됩니다. #### `ses.clearCache(callback)` @@ -287,3 +279,191 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c callback(false); }); ``` + +#### `ses.webRequest` + +`webRequest` API는 생명주기의 다양한 단계에 맞춰 요청 컨텐츠를 가로채거나 변경할 수 +있도록 합니다. + +각 API는 `filter`와 `listener`를 선택적으로 받을 수 있습니다. `listener`는 API의 +이벤트가 발생했을 때 `listener(details)` 형태로 호출되며 `defails`는 요청을 묘사하는 +객체입니다. `listener`에 `null`을 전달하면 이벤트 수신을 중지합니다. + +`filter`는 `urls` 속성을 가진 객체입니다. 이 속성은 URL 규칙의 배열이며 URL 규칙에 +일치하지 않는 요청을 모두 거르는데 사용됩니다. 만약 `filter`가 생략되면 모든 요청을 +여과 없이 통과시킵니다. + +어떤 `listener`의 이벤트들은 `callback`을 같이 전달하는데, 이벤트 처리시 +`listener`의 작업을 완료한 후 `response` 객체를 포함하여 호출해야 합니다. + +```javascript +// 다음 url에 대한 User Agent를 조작합니다. +var filter = { + urls: ["https://*.github.com/*", "*://electron.github.io"] +}; + +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) { + details.requestHeaders['User-Agent'] = "MyAgent"; + callback({cancel: false, requestHeaders: details.requestHeaders}); +}); +``` + +#### `ses.webRequest.onBeforeRequest([filter, ]listener)` + +* `filter` Object +* `listener` Function + +요청이 발생하면 `listener`가 `listener(details, callback)` 형태로 호출됩니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + +`callback`은 `response` 객체와 함께 호출되어야 합니다: + +* `response` Object + * `cancel` Boolean __optional__ + * `redirectURL` String __optional__ - 원래 요청은 전송과 완료가 방지되지만 이 + 속성을 지정하면 해당 URL로 리다이렉트됩니다. + +#### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener`가 +`listener(details, callback)` 형태로 호출됩니다. 이 이벤트는 서버와의 TCP 연결이 +완료된 후에 발생할 수도 있지만 http 데이터가 전송되기 전에 발생합니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +`callback`은 `response` 객체와 함께 호출되어야 합니다: + +* `response` Object + * `cancel` Boolean __optional__ + * `requestHeaders` Object __optional__ - 이 속성이 제공되면, 요청은 이 헤더로 + 만들어 집니다. + +#### `ses.webRequest.onSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +서버에 요청이 전송되기 바로 전에 `listener`가 `listener(details)` 형태로 호출됩니다. +이전 `onBeforeSendHeaders`의 response와 다른점은 리스너가 호출되는 시간으로 볼 수 +있습니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +#### `ses.webRequest.onHeadersReceived([filter,] listener)` + +* `filter` Object +* `listener` Function + +요청의 HTTP 응답 헤더를 받았을 때 `listener`가 `listener(details, callback)` 형태로 +호출됩니다. + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object + +`callback`은 `response` 객체와 함께 호출되어야 합니다: + +* `response` Object + * `cancel` Boolean + * `responseHeaders` Object __optional__ - 이 속성이 제공되면 서버는 이 헤더와 + 함께 응답합니다. + +#### `ses.webRequest.onResponseStarted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +요청 본문의 첫 번째 바이트를 받았을 때 `listener`가 `listener(details)` 형태로 +호출됩니다. 이는 HTTP 요청에서 상태 줄과 요청 헤더가 사용 가능한 상태를 의미합니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean - 응답을 디스크 캐시에서 가져올지에 대한 여부. + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onBeforeRedirect([filter, ]listener)` + +* `filter` Object +* `listener` Function + +서버에서 시작된 리다이렉트가 발생했을 때 `listener`가 `listener(details)` 형태로 +호출됩니다. + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String __optional__ - 요청이 실질적으로 전송될 서버 아이피 주소. + * `fromCache` Boolean + * `responseHeaders` Object + +#### `ses.webRequest.onCompleted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +요청이 완료되면 `listener`가 `listener(details)` 형태로 호출됩니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onErrorOccurred([filter, ]listener)` + +* `filter` Object +* `listener` Function + +에러가 발생하면 `listener`가 `listener(details)` 형태로 호출됩니다. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean + * `error` String - 에러 설명. From 4fa6d4aeadd23e322ba6171176d6f2e0970af901 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 15 Dec 2015 05:40:13 +0900 Subject: [PATCH 263/411] :memo: Update as upstream [ci skip] --- .../ko-KR/development/build-instructions-windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/ko-KR/development/build-instructions-windows.md b/docs-translations/ko-KR/development/build-instructions-windows.md index 13bbf47a3a..a5b0de78bd 100644 --- a/docs-translations/ko-KR/development/build-instructions-windows.md +++ b/docs-translations/ko-KR/development/build-instructions-windows.md @@ -5,7 +5,7 @@ ## 빌드전 요구 사항 * Windows 7 / Server 2008 R2 또는 최신 버전 -* Visual Studio 2013 - [VS 2013 커뮤니티 에디션 무료 다운로드](http://www.visualstudio.com/products/visual-studio-community-vs) +* Visual Studio 2013 Update 5 - [VS 2013 커뮤니티 에디션 무료 다운로드](http://www.visualstudio.com/products/visual-studio-community-vs) * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) * [Git](http://git-scm.com) From 574eec3e748e153b15adceb262871b17d4f4ff25 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 14 Dec 2015 14:55:48 -0800 Subject: [PATCH 264/411] Make window.opener a BrowserWindowProxy --- atom/renderer/lib/override.coffee | 7 ++++--- spec/chromium-spec.coffee | 5 ++++- spec/fixtures/pages/window-open-postMessage.html | 7 ++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 333acc8273..3b741e004d 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -94,9 +94,10 @@ window.prompt = -> # Implement window.postMessage if current window is a guest window. guestId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID' if guestId? - window.opener = - postMessage: (message, targetOrigin='*') -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', guestId, message, targetOrigin, location.origin + window.opener = BrowserWindowProxy.getOrCreate(guestId) + Object.setPrototypeOf(window.opener, null) + window.opener.postMessage = (message, targetOrigin='*') -> + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', guestId, message, targetOrigin, location.origin ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, guestId, message, sourceOrigin) -> # Manually dispatch event instead of using postMessage because we also need to diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 2cc2237385..e6ae8a732f 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -120,7 +120,10 @@ describe 'chromium feature', -> listener = (event) -> window.removeEventListener 'message', listener b.close() - assert.equal event.data, 'file://testing' + message = JSON.parse(event.data) + assert.equal message.data, 'testing' + assert.equal message.origin, 'file://' + assert.equal message.sourceEqualsOpener, true assert.equal event.origin, 'file://' done() window.addEventListener 'message', listener diff --git a/spec/fixtures/pages/window-open-postMessage.html b/spec/fixtures/pages/window-open-postMessage.html index e547fa2a60..550b61b275 100644 --- a/spec/fixtures/pages/window-open-postMessage.html +++ b/spec/fixtures/pages/window-open-postMessage.html @@ -2,7 +2,12 @@ From 0ef0ce73451c34fd8b6153132b6890b7ef47d6b4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 14 Dec 2015 14:57:38 -0800 Subject: [PATCH 265/411] Add comment about window.opener tweaks --- atom/renderer/lib/override.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 3b741e004d..213dc497d0 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -91,10 +91,10 @@ window.confirm = (message, title='') -> window.prompt = -> throw new Error('prompt() is and will not be supported.') -# Implement window.postMessage if current window is a guest window. guestId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID' if guestId? window.opener = BrowserWindowProxy.getOrCreate(guestId) + # Remove BrowserWindowProxy API and give it a custom postMessage method Object.setPrototypeOf(window.opener, null) window.opener.postMessage = (message, targetOrigin='*') -> ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', guestId, message, targetOrigin, location.origin From 184b11be4caeeea454ee937a13eabb6cdc1df79b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 14 Dec 2015 16:47:33 -0800 Subject: [PATCH 266/411] Use id from source window when building proxy for event source --- atom/browser/lib/guest-window-manager.coffee | 22 ++++++++++++-------- atom/renderer/lib/override.coffee | 14 ++++++------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 9ed75c225f..97c285df07 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -74,23 +74,27 @@ ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, met BrowserWindow.fromId(guestId)?[method] args... ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> + sourceId = BrowserWindow.fromWebContents(event.sender)?.id + return unless sourceId? + guestContents = BrowserWindow.fromId(guestId)?.webContents if guestContents?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin + guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin + +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, message, targetOrigin, sourceOrigin) -> + sourceId = BrowserWindow.fromWebContents(event.sender)?.id + return unless sourceId? -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> embedder = v8Util.getHiddenValue event.sender, 'embedder' if embedder?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - embedder?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin + embedder?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?.webContents?[method] args... -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID', (event) -> +ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_OPENER_ID', (event) -> embedder = v8Util.getHiddenValue event.sender, 'embedder' + openerId = null if embedder? - guest = BrowserWindow.fromWebContents event.sender - if guest? - event.returnValue = guest.id - return - event.returnValue = null + openerId = BrowserWindow.fromWebContents(embedder)?.id + event.returnValue = openerId diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 213dc497d0..8b33c6e223 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -91,22 +91,20 @@ window.confirm = (message, title='') -> window.prompt = -> throw new Error('prompt() is and will not be supported.') -guestId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID' -if guestId? - window.opener = BrowserWindowProxy.getOrCreate(guestId) - # Remove BrowserWindowProxy API and give it a custom postMessage method - Object.setPrototypeOf(window.opener, null) +openerId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_OPENER_ID' +if openerId? + window.opener = BrowserWindowProxy.getOrCreate(openerId) window.opener.postMessage = (message, targetOrigin='*') -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', guestId, message, targetOrigin, location.origin + ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', message, targetOrigin, location.origin -ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, guestId, message, sourceOrigin) -> +ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, sourceId, message, sourceOrigin) -> # Manually dispatch event instead of using postMessage because we also need to # set event.source. event = document.createEvent 'Event' event.initEvent 'message', false, false event.data = message event.origin = sourceOrigin - event.source = BrowserWindowProxy.getOrCreate(guestId) + event.source = BrowserWindowProxy.getOrCreate(sourceId) window.dispatchEvent event # Forward history operations to browser. From c5936a024dbeaafb42430d616c4f2421d9eebc98 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 14 Dec 2015 16:54:26 -0800 Subject: [PATCH 267/411] Remove unneeded custom postMessage on window.opener --- atom/renderer/lib/override.coffee | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 8b33c6e223..ba15c7b3f6 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -92,10 +92,7 @@ window.prompt = -> throw new Error('prompt() is and will not be supported.') openerId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_OPENER_ID' -if openerId? - window.opener = BrowserWindowProxy.getOrCreate(openerId) - window.opener.postMessage = (message, targetOrigin='*') -> - ipcRenderer.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', message, targetOrigin, location.origin +window.opener = BrowserWindowProxy.getOrCreate(openerId) if openerId? ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, sourceId, message, sourceOrigin) -> # Manually dispatch event instead of using postMessage because we also need to From d133553c6a68386046f16859af154e8db5fa4efa Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 14 Dec 2015 16:58:32 -0800 Subject: [PATCH 268/411] Remove unused ipc event handler --- atom/browser/lib/guest-window-manager.coffee | 8 -------- 1 file changed, 8 deletions(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 97c285df07..1e9ee13c71 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -81,14 +81,6 @@ ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId if guestContents?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' guestContents?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, message, targetOrigin, sourceOrigin) -> - sourceId = BrowserWindow.fromWebContents(event.sender)?.id - return unless sourceId? - - embedder = v8Util.getHiddenValue event.sender, 'embedder' - if embedder?.getURL().indexOf(targetOrigin) is 0 or targetOrigin is '*' - embedder?.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin - ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?.webContents?[method] args... From b8de1bd9de21b413408fdf6ba911fe5ed886a097 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 14 Dec 2015 17:02:36 -0800 Subject: [PATCH 269/411] Assert that source id matches opener --- spec/chromium-spec.coffee | 2 ++ spec/fixtures/pages/window-open-postMessage.html | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index e6ae8a732f..f0e56a47ff 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -117,6 +117,7 @@ describe 'chromium feature', -> describe 'window.postMessage', -> it 'sets the origin correctly', (done) -> + sourceId = remote.getCurrentWindow().id listener = (event) -> window.removeEventListener 'message', listener b.close() @@ -124,6 +125,7 @@ describe 'chromium feature', -> assert.equal message.data, 'testing' assert.equal message.origin, 'file://' assert.equal message.sourceEqualsOpener, true + assert.equal message.sourceId, sourceId assert.equal event.origin, 'file://' done() window.addEventListener 'message', listener diff --git a/spec/fixtures/pages/window-open-postMessage.html b/spec/fixtures/pages/window-open-postMessage.html index 550b61b275..401b7d645f 100644 --- a/spec/fixtures/pages/window-open-postMessage.html +++ b/spec/fixtures/pages/window-open-postMessage.html @@ -5,7 +5,8 @@ var reply = JSON.stringify({ origin: e.origin, data: e.data, - sourceEqualsOpener: e.source === window.opener + sourceEqualsOpener: e.source === window.opener, + sourceId: e.source.guestId }) window.opener.postMessage(reply, '*'); }); From d1989b3624ccb508640920c2123c7cd19a1d16c2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 14 Dec 2015 17:02:58 -0800 Subject: [PATCH 270/411] Mention source in spec description --- spec/chromium-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index f0e56a47ff..c456a6919b 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -116,7 +116,7 @@ describe 'chromium feature', -> b = window.open url, '', 'show=no' describe 'window.postMessage', -> - it 'sets the origin correctly', (done) -> + it 'sets the source and origin correctly', (done) -> sourceId = remote.getCurrentWindow().id listener = (event) -> window.removeEventListener 'message', listener From aa9c7662a30da7374a03d80ec12a1375529400e1 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 14 Dec 2015 17:09:31 -0800 Subject: [PATCH 271/411] Inline JSON message response --- spec/fixtures/pages/window-open-postMessage.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/fixtures/pages/window-open-postMessage.html b/spec/fixtures/pages/window-open-postMessage.html index 401b7d645f..0e7e4d8cce 100644 --- a/spec/fixtures/pages/window-open-postMessage.html +++ b/spec/fixtures/pages/window-open-postMessage.html @@ -2,13 +2,12 @@ From 86bf7341b76503cb0cf59c44403fa93fa31de61c Mon Sep 17 00:00:00 2001 From: BJR Matos Date: Mon, 14 Dec 2015 23:28:25 -0500 Subject: [PATCH 272/411] Add A5 Page Size to printToPDF --- atom/browser/api/lib/web-contents.coffee | 5 +++++ docs/api/web-contents.md | 1 + 2 files changed, 6 insertions(+) diff --git a/atom/browser/api/lib/web-contents.coffee b/atom/browser/api/lib/web-contents.coffee index dacbc919d6..2eda00f068 100644 --- a/atom/browser/api/lib/web-contents.coffee +++ b/atom/browser/api/lib/web-contents.coffee @@ -7,6 +7,11 @@ nextId = 0 getNextId = -> ++nextId PDFPageSize = + A5: + custom_display_name: "A5" + height_microns: 210000 + name: "ISO_A5" + width_microns: 148000 A4: custom_display_name: "A4" height_microns: 297000 diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index c35bbc9ae3..90ecda9168 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -454,6 +454,7 @@ size. * 1 - none * 2 - minimum * `pageSize` String - Specify page size of the generated PDF. + * `A5` * `A4` * `A3` * `Legal` From 6347f0e321f6fa709ea383b586f9b3f04fe2b348 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 15 Dec 2015 16:25:44 +0800 Subject: [PATCH 273/411] Upgrade node, fix #3786 --- vendor/node | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/node b/vendor/node index 38d7918434..2e11f0fcb1 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 38d791843463b19c623c97c1c550a4e3c5a406d4 +Subproject commit 2e11f0fcb14dd035f9e073cf8b7778d60cf4f668 From d0962b1a93f8ddf4eff36292d37abbe403474a3f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 15 Dec 2015 17:17:24 +0800 Subject: [PATCH 274/411] Fix crash when passing empty path to addRecentDocument --- atom/browser/browser_mac.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 6589057c2c..6a3481cf06 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -19,7 +19,12 @@ void Browser::Focus() { } void Browser::AddRecentDocument(const base::FilePath& path) { - NSURL* u = [NSURL fileURLWithPath:base::mac::FilePathToNSString(path)]; + NSString* path_string = base::mac::FilePathToNSString(path); + if (!path_string) + return; + NSURL* u = [NSURL fileURLWithPath:path_string]; + if (!u) + return; [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:u]; } From 2470ff1b142e763ea2016837de4d467a7ef8a3a2 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 15 Dec 2015 18:45:13 +0900 Subject: [PATCH 275/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/web-contents.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index 4667ba7339..71502c3dcc 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -451,6 +451,7 @@ print기능을 사용하지 않는 경우 전체 바이너리 크기를 줄이 * 1 - none * 2 - minimum * `pageSize` String - 생성되는 PDF의 페이지 크기를 지정합니다. + * `A5` * `A4` * `A3` * `Legal` From e729104d0087fcb27dda7a6a5a8632b8a9a8d9b0 Mon Sep 17 00:00:00 2001 From: Hashed Hyphen Date: Wed, 16 Dec 2015 00:52:14 +0900 Subject: [PATCH 276/411] :bug: Support strict mode on Tutorial [ci skip] --- docs/tutorial/quick-start.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 4c61413436..bc1a0f69e3 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -78,6 +78,8 @@ The `main.js` should create windows and handle system events, a typical example being: ```javascript +'use strict'; + const electron = require('electron'); const app = electron.app; // Module to control application life. const BrowserWindow = electron.BrowserWindow; // Module to create native browser window. From c98db82668b41ec4d62d27b8074042385bd9e452 Mon Sep 17 00:00:00 2001 From: "=^._.^=" Date: Tue, 15 Dec 2015 14:07:55 -0800 Subject: [PATCH 277/411] mention getUserMedia options in desktop-capturer docs --- docs/api/desktop-capturer.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index e0ec758428..7863622e37 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -38,6 +38,15 @@ function getUserMediaError(e) { } ``` +When creating a constraints object for the `navigator.webkitGetUserMedia` call, +if you are using a source from `desktopCapturer` your `chromeMediaSource` must +be set to `"desktop"` and your `audio` must be set to `false`. + +If you wish to +capture the audio and video from the entire desktop you can set +`chromeMediaSource` to `"screen"` and `audio` to `true`. When using this method +you cannot specify a `chromeMediaSourceId`. + ## Methods The `desktopCapturer` module has the following methods: From 58106e53c8af2432eb8486eaec38c92de8b40b18 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:09:01 -0800 Subject: [PATCH 278/411] Mark companyName and submitURL as required --- docs/api/crash-reporter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 6c66a855f7..1b09282635 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -25,8 +25,8 @@ The `crash-reporter` module has the following methods: `options` Object, properties: * `productName` String, default: Electron. -* `companyName` String, default: GitHub, Inc. -* `submitURL` String, default: http://54.249.141.255:1127/post. +* `companyName` String (**required**) +* `submitURL` String, (**required**) * URL that crash reports will be sent to as POST. * `autoSubmit` Boolean, default: `true`. * Send the crash report without user interaction. From dcc99dd5cb33d26d90545b0e05baf9ebfaf7279b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:10:04 -0800 Subject: [PATCH 279/411] Remove duplicate start calls --- atom/common/api/lib/crash-reporter.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/atom/common/api/lib/crash-reporter.coffee b/atom/common/api/lib/crash-reporter.coffee index bd98ae2a42..fb0f1eba89 100644 --- a/atom/common/api/lib/crash-reporter.coffee +++ b/atom/common/api/lib/crash-reporter.coffee @@ -40,9 +40,7 @@ class CrashReporter env = ATOM_SHELL_INTERNAL_CRASH_SERVICE: 1 spawn process.execPath, args, {env, detached: true} - start() - else - start() + start() getLastCrashReport: -> reports = this.getUploadedReports() From 524649797fb4f2b078526a40973251a1cdace82f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:22:31 -0800 Subject: [PATCH 280/411] Make companyName and submitURL required options --- atom/common/api/lib/crash-reporter.coffee | 11 ++++++++--- atom/common/api/lib/deprecate.coffee | 6 +++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/atom/common/api/lib/crash-reporter.coffee b/atom/common/api/lib/crash-reporter.coffee index fb0f1eba89..e1bfa157cb 100644 --- a/atom/common/api/lib/crash-reporter.coffee +++ b/atom/common/api/lib/crash-reporter.coffee @@ -19,8 +19,6 @@ class CrashReporter {app} = if process.type is 'browser' then electron else electron.remote @productName ?= app.getName() - companyName ?= 'GitHub, Inc' - submitURL ?= 'http://54.249.141.255:1127/post' autoSubmit ?= true ignoreSystemCrashHandler ?= false extra ?= {} @@ -29,6 +27,14 @@ class CrashReporter extra._companyName ?= companyName extra._version ?= app.getVersion() + unless companyName? + deprecate.log('companyName is now a required option to CrashReporter::start') + return + + unless submitURL? + deprecate.log('submitURL is now a required option to CrashReporter::start') + return + start = => binding.start @productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra if process.platform is 'win32' @@ -59,6 +65,5 @@ class CrashReporter path.join tmpdir, "#{@productName} Crashes", 'uploads.log' binding._getUploadedReports log - crashRepoter = new CrashReporter module.exports = crashRepoter diff --git a/atom/common/api/lib/deprecate.coffee b/atom/common/api/lib/deprecate.coffee index 1daf5e4714..a7de926262 100644 --- a/atom/common/api/lib/deprecate.coffee +++ b/atom/common/api/lib/deprecate.coffee @@ -54,7 +54,10 @@ deprecate.event = (emitter, oldName, newName, fn) -> # Print deprecate warning. deprecate.warn = (oldName, newName) -> - message = "#{oldName} is deprecated. Use #{newName} instead." + deprecate.log("#{oldName} is deprecated. Use #{newName} instead.") + +# Print deprecation message +deprecate.log = (message) -> if process.throwDeprecation throw new Error(message) else if process.traceDeprecation @@ -62,4 +65,5 @@ deprecate.warn = (oldName, newName) -> else console.warn "(electron) #{message}" + module.exports = deprecate From 4718c726f6c7c7b26d18255c6bada816083ed3d9 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:22:50 -0800 Subject: [PATCH 281/411] Remove starting crash reporter from quick start --- docs-translations/es/tutorial/quick-start.md | 3 --- docs-translations/es/tutorial/using-pepper-flash-plugin.md | 3 --- docs-translations/jp/tutorial/quick-start.md | 3 --- docs-translations/ko-KR/tutorial/quick-start.md | 3 --- docs-translations/pt-BR/tutorial/quick-start.md | 3 --- docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md | 3 --- docs-translations/zh-CN/tutorial/quick-start.md | 3 --- docs-translations/zh-TW/tutorial/quick-start.md | 3 --- docs/tutorial/quick-start.md | 3 --- 9 files changed, 27 deletions(-) diff --git a/docs-translations/es/tutorial/quick-start.md b/docs-translations/es/tutorial/quick-start.md index ee1127eb02..b038f7cb08 100644 --- a/docs-translations/es/tutorial/quick-start.md +++ b/docs-translations/es/tutorial/quick-start.md @@ -70,9 +70,6 @@ El `main.js` debería crear las ventanas y gestionar los eventos del sistema, un var app = require('app'); // Módulo para controlar el ciclo de vida de la aplicación. var BrowserWindow = require('browser-window'); // Módulo para crear uan ventana de navegador. -// Reportar crashes a nuestro servidor. -require('crash-reporter').start(); - // Mantener una referencia global al objeto window, si no lo haces, esta ventana // se cerrará automáticamente cuando el objeto JavaScript sea recolectado (garbage collected): var mainWindow = null; diff --git a/docs-translations/es/tutorial/using-pepper-flash-plugin.md b/docs-translations/es/tutorial/using-pepper-flash-plugin.md index 4e45524fb6..5c41ff8782 100644 --- a/docs-translations/es/tutorial/using-pepper-flash-plugin.md +++ b/docs-translations/es/tutorial/using-pepper-flash-plugin.md @@ -15,9 +15,6 @@ También puedes agregar la opción `plugins` de `browser-window`. Por ejemplo, var app = require('app'); var BrowserWindow = require('browser-window'); -// Report crashes to our server. -require('crash-reporter').start(); - // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the javascript object is GCed. var mainWindow = null; diff --git a/docs-translations/jp/tutorial/quick-start.md b/docs-translations/jp/tutorial/quick-start.md index 9a929ff84d..4bd816cb6a 100644 --- a/docs-translations/jp/tutorial/quick-start.md +++ b/docs-translations/jp/tutorial/quick-start.md @@ -53,9 +53,6 @@ your-app/ var app = require('app'); // Module to control application life. var BrowserWindow = require('browser-window'); // Module to create native browser window. -// Report crashes to our server. -require('crash-reporter').start(); - // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the javascript object is GCed. var mainWindow = null; diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index cfdc19f00e..582cc99905 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -78,9 +78,6 @@ const electron = require('electron'); const app = electron.app; // 어플리케이션 기반을 조작 하는 모듈. const BrowserWindow = electron.BrowserWindow; // 네이티브 브라우저 창을 만드는 모듈. -// Electron 개발자에게 crash-report를 보냄. -electron.crashReporter.start(); - // 윈도우 객체를 전역에 유지합니다. 만약 이렇게 하지 않으면 // 자바스크립트 GC가 일어날 때 창이 멋대로 닫혀버립니다. var mainWindow = null; diff --git a/docs-translations/pt-BR/tutorial/quick-start.md b/docs-translations/pt-BR/tutorial/quick-start.md index eecf67878f..1b1d694237 100644 --- a/docs-translations/pt-BR/tutorial/quick-start.md +++ b/docs-translations/pt-BR/tutorial/quick-start.md @@ -81,9 +81,6 @@ exemplo: var app = require('app'); // Módulo para controlar o ciclo de vida do app. var BrowserWindow = require('browser-window'); // Módulo para criar uma janela nativa do browser. -// Relate falhas para nossos servidores. -require('crash-reporter').start(); - // Mantenha uma referência global para o objeto window, se você não o fizer, // a janela será fechada automaticamente quando o objeto JavaScript for // coletado pelo garbage collector. diff --git a/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md b/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md index e5222ba077..7ffba7cc33 100644 --- a/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md +++ b/docs-translations/pt-BR/tutorial/using-pepper-flash-plugin.md @@ -23,9 +23,6 @@ Por exemplo: 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; diff --git a/docs-translations/zh-CN/tutorial/quick-start.md b/docs-translations/zh-CN/tutorial/quick-start.md index 906db8f445..1bb8473b36 100644 --- a/docs-translations/zh-CN/tutorial/quick-start.md +++ b/docs-translations/zh-CN/tutorial/quick-start.md @@ -45,9 +45,6 @@ your-app/ var app = require('app'); // 控制应用生命周期的模块。 var BrowserWindow = require('browser-window'); // 创建原生浏览器窗口的模块 -// 给我们的服务器发送异常报告。 -require('crash-reporter').start(); - // 保持一个对于 window 对象的全局引用,不然,当 JavaScript 被 GC, // window 会被自动地关闭 var mainWindow = null; diff --git a/docs-translations/zh-TW/tutorial/quick-start.md b/docs-translations/zh-TW/tutorial/quick-start.md index 18c62c5e75..8c5c701f17 100644 --- a/docs-translations/zh-TW/tutorial/quick-start.md +++ b/docs-translations/zh-TW/tutorial/quick-start.md @@ -62,9 +62,6 @@ __注意__:如果 `main` 沒有在 `package.json` 裏, Electron會嘗試載 var app = require('app'); // 控制應用程式生命週期的模組。 var BrowserWindow = require('browser-window'); // 創造原生瀏覽器窗口的模組 -// 對我們的伺服器傳送異常報告。 -require('crash-reporter').start(); - // 保持一個對於 window 物件的全域的引用,不然,當 JavaScript 被GC, // window 會被自動地關閉 var mainWindow = null; diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 4c61413436..affef83d98 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -82,9 +82,6 @@ const electron = require('electron'); const app = electron.app; // Module to control application life. const BrowserWindow = electron.BrowserWindow; // Module to create native browser window. -// Report crashes to our server. -electron.crashReporter.start(); - // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. var mainWindow = null; From 80e963122069bd543ef2c132a1c47145cf8915d0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:27:01 -0800 Subject: [PATCH 282/411] Use better signature match in deprecation message --- atom/common/api/lib/crash-reporter.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/common/api/lib/crash-reporter.coffee b/atom/common/api/lib/crash-reporter.coffee index e1bfa157cb..544791b7d1 100644 --- a/atom/common/api/lib/crash-reporter.coffee +++ b/atom/common/api/lib/crash-reporter.coffee @@ -28,11 +28,11 @@ class CrashReporter extra._version ?= app.getVersion() unless companyName? - deprecate.log('companyName is now a required option to CrashReporter::start') + deprecate.log('companyName is now a required option to crashReporter.start') return unless submitURL? - deprecate.log('submitURL is now a required option to CrashReporter::start') + deprecate.log('submitURL is now a required option to crashReporter.start') return start = => binding.start @productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra From aedfd3bf0ebbf5de5bc03c83f54c9b8f98b28515 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:44:38 -0800 Subject: [PATCH 283/411] Add specs for companyName/submitURL being required --- atom/common/api/lib/deprecate.coffee | 3 +-- spec/api-crash-reporter-spec.coffee | 9 ++++++++- spec/static/main.js | 3 +++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/atom/common/api/lib/deprecate.coffee b/atom/common/api/lib/deprecate.coffee index a7de926262..2481790cf8 100644 --- a/atom/common/api/lib/deprecate.coffee +++ b/atom/common/api/lib/deprecate.coffee @@ -52,7 +52,7 @@ deprecate.event = (emitter, oldName, newName, fn) -> else @emit oldName, args... -# Print deprecate warning. +# Print deprecation warning. deprecate.warn = (oldName, newName) -> deprecate.log("#{oldName} is deprecated. Use #{newName} instead.") @@ -65,5 +65,4 @@ deprecate.log = (message) -> else console.warn "(electron) #{message}" - module.exports = deprecate diff --git a/spec/api-crash-reporter-spec.coffee b/spec/api-crash-reporter-spec.coffee index 789ba6e01f..71b7d1dbc9 100644 --- a/spec/api-crash-reporter-spec.coffee +++ b/spec/api-crash-reporter-spec.coffee @@ -55,5 +55,12 @@ describe 'crash-reporter module', -> pathname: path.join fixtures, 'api', 'crash.html' search: "?port=#{port}" if process.platform is 'darwin' - crashReporter.start {'submitURL': 'http://127.0.0.1:' + port} + crashReporter.start + companyName: 'Umbrella Corporation' + submitURL: "http://127.0.0.1:#{port}" w.loadURL url + + describe ".start(options)", -> + it 'requires that the companyName and submitURL option fields be specified', -> + assert.throws(-> crashReporter.start({companyName: 'Missing submitURL'})) + assert.throws(-> crashReporter.start({submitURL: 'Missing companyName'})) diff --git a/spec/static/main.js b/spec/static/main.js index 4de123830e..35ae544dcd 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -4,6 +4,9 @@ const ipcMain = electron.ipcMain; const dialog = electron.dialog; const BrowserWindow = electron.BrowserWindow; +// Disable use of deprecated functions. +process.throwDeprecation = true; + const path = require('path'); const url = require('url'); From d44a9d1fcc5e5128efc4bce03274674568f4154b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:46:53 -0800 Subject: [PATCH 284/411] :art: Remove parens --- atom/common/api/lib/deprecate.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/common/api/lib/deprecate.coffee b/atom/common/api/lib/deprecate.coffee index 2481790cf8..acfc0b1d05 100644 --- a/atom/common/api/lib/deprecate.coffee +++ b/atom/common/api/lib/deprecate.coffee @@ -54,7 +54,7 @@ deprecate.event = (emitter, oldName, newName, fn) -> # Print deprecation warning. deprecate.warn = (oldName, newName) -> - deprecate.log("#{oldName} is deprecated. Use #{newName} instead.") + deprecate.log "#{oldName} is deprecated. Use #{newName} instead." # Print deprecation message deprecate.log = (message) -> From 67c0de36a3a5c3340a65b4309dd171e9604d365a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:47:19 -0800 Subject: [PATCH 285/411] :memo: Add missing period --- atom/common/api/lib/deprecate.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/common/api/lib/deprecate.coffee b/atom/common/api/lib/deprecate.coffee index acfc0b1d05..71c2ee5cab 100644 --- a/atom/common/api/lib/deprecate.coffee +++ b/atom/common/api/lib/deprecate.coffee @@ -56,7 +56,7 @@ deprecate.event = (emitter, oldName, newName, fn) -> deprecate.warn = (oldName, newName) -> deprecate.log "#{oldName} is deprecated. Use #{newName} instead." -# Print deprecation message +# Print deprecation message. deprecate.log = (message) -> if process.throwDeprecation throw new Error(message) From 5fb5526b0676b25e5bd9b6a542411b3f581aac50 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 16:49:19 -0800 Subject: [PATCH 286/411] Improve spec description --- spec/api-crash-reporter-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api-crash-reporter-spec.coffee b/spec/api-crash-reporter-spec.coffee index 71b7d1dbc9..334956d3c0 100644 --- a/spec/api-crash-reporter-spec.coffee +++ b/spec/api-crash-reporter-spec.coffee @@ -61,6 +61,6 @@ describe 'crash-reporter module', -> w.loadURL url describe ".start(options)", -> - it 'requires that the companyName and submitURL option fields be specified', -> + it 'requires that the companyName and submitURL options be specified', -> assert.throws(-> crashReporter.start({companyName: 'Missing submitURL'})) assert.throws(-> crashReporter.start({submitURL: 'Missing companyName'})) From d863fd5c6ccd1ced0fefcbee54b9ae6eb36a9a78 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 15 Dec 2015 17:02:33 -0800 Subject: [PATCH 287/411] Set process.throwDeprecation as early as possible --- spec/static/main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/static/main.js b/spec/static/main.js index 35ae544dcd..b295ba1855 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -1,12 +1,12 @@ +// Disable use of deprecated functions. +process.throwDeprecation = true; + const electron = require('electron'); const app = electron.app; const ipcMain = electron.ipcMain; const dialog = electron.dialog; const BrowserWindow = electron.BrowserWindow; -// Disable use of deprecated functions. -process.throwDeprecation = true; - const path = require('path'); const url = require('url'); From a29abf1e34ff4b0c2a71158031c28d857b75c7ee Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 16 Dec 2015 18:03:18 +0800 Subject: [PATCH 288/411] Update libchromiumcontent Remove usages of private xpc_ APIs, fix #3823. --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index 1237a4be4a..dd848bc816 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '66bd8d1c705b7258f76c82436e4b16e82afbbd33' +LIBCHROMIUMCONTENT_COMMIT = 'e334f07f86371385c51a9cda528ea531ea81bffa' PLATFORM = { 'cygwin': 'win32', From c6634b1ea5554f72d577a0bb89e24ddeabf38622 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 16 Dec 2015 22:38:04 +0800 Subject: [PATCH 289/411] Don't pump message loop when sending sync msg In old days sending sync message to browser process requires pumping message loop in the renderer process, but now in Chrome 47 it is not true anymore. And even when we do it, the Send method may fail sometimes, so this change seems to be required for the Chrome 47 upgrade. --- atom/renderer/api/atom_api_renderer_ipc.cc | 2 -- atom/renderer/api/lib/ipc-renderer.coffee | 6 ------ 2 files changed, 8 deletions(-) diff --git a/atom/renderer/api/atom_api_renderer_ipc.cc b/atom/renderer/api/atom_api_renderer_ipc.cc index 061293e80d..a82562f936 100644 --- a/atom/renderer/api/atom_api_renderer_ipc.cc +++ b/atom/renderer/api/atom_api_renderer_ipc.cc @@ -54,8 +54,6 @@ base::string16 SendSync(mate::Arguments* args, IPC::SyncMessage* message = new AtomViewHostMsg_Message_Sync( render_view->GetRoutingID(), channel, arguments, &json); - // Enable the UI thread in browser to receive messages. - message->EnableMessagePumping(); bool success = render_view->Send(message); if (!success) diff --git a/atom/renderer/api/lib/ipc-renderer.coffee b/atom/renderer/api/lib/ipc-renderer.coffee index 92be75aa20..0dd629e54f 100644 --- a/atom/renderer/api/lib/ipc-renderer.coffee +++ b/atom/renderer/api/lib/ipc-renderer.coffee @@ -6,12 +6,6 @@ v8Util = process.atomBinding 'v8_util' # Created by init.coffee. ipcRenderer = v8Util.getHiddenValue global, 'ipc' -# Delay the callback to next tick in case the browser is still in the middle -# of sending a message while the callback sends a sync message to browser, -# which can fail sometimes. -ipcRenderer.emit = (args...) -> - setTimeout (-> EventEmitter::emit.call ipcRenderer, args...), 0 - ipcRenderer.send = (args...) -> binding.send 'ipc-message', [args...] From 71303d4804b623b5c57d4feeffb9b808fc05aaa6 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 16 Dec 2015 22:57:03 +0800 Subject: [PATCH 290/411] Fix context menu not working in devtools --- atom/renderer/lib/inspector.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/renderer/lib/inspector.coffee b/atom/renderer/lib/inspector.coffee index d5ddfd72e4..364ccc39d4 100644 --- a/atom/renderer/lib/inspector.coffee +++ b/atom/renderer/lib/inspector.coffee @@ -27,7 +27,9 @@ convertToMenuTemplate = (items) -> label: item.label enabled: item.enabled if item.id? - transformed.click = -> DevToolsAPI.contextMenuItemSelected item.id + transformed.click = -> + DevToolsAPI.contextMenuItemSelected item.id + DevToolsAPI.contextMenuCleared() template.push transformed template @@ -37,9 +39,7 @@ createMenu = (x, y, items, document) -> menu = Menu.buildFromTemplate convertToMenuTemplate(items) # The menu is expected to show asynchronously. - setImmediate -> - menu.popup remote.getCurrentWindow() - DevToolsAPI.contextMenuCleared() + setTimeout -> menu.popup remote.getCurrentWindow() showFileChooserDialog = (callback) -> {remote} = require 'electron' From fe9c09ef64ea3a874307855924f5cfbde4770318 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 17 Dec 2015 13:07:41 +0800 Subject: [PATCH 291/411] Upgrade node to fix crash in Buffer --- vendor/node | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/node b/vendor/node index 2e11f0fcb1..aff2d71b16 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 2e11f0fcb14dd035f9e073cf8b7778d60cf4f668 +Subproject commit aff2d71b16a0691a30f0be159a253bab80d01b2c From afedce0b18eaab06194b6fd8a318a1f4472cc7bd Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 17 Dec 2015 18:40:52 +0800 Subject: [PATCH 292/411] docs: |options| is not optional Close #3845. --- docs/api/dialog.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 884fb7c073..80a5289e93 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -20,10 +20,10 @@ parameter. The `dialog` module has the following methods: -### `dialog.showOpenDialog([browserWindow][, options][, callback])` +### `dialog.showOpenDialog([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (optional) -* `options` Object (optional) +* `options` Object * `title` String * `defaultPath` String * `filters` Array @@ -61,10 +61,10 @@ and a directory selector, so if you set `properties` to `['openFile', 'openDirectory']` on these platforms, a directory selector will be shown. -### `dialog.showSaveDialog([browserWindow][, options][, callback])` +### `dialog.showSaveDialog([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (optional) -* `options` Object (optional) +* `options` Object * `title` String * `defaultPath` String * `filters` Array @@ -79,10 +79,10 @@ The `filters` specifies an array of file types that can be displayed, see If a `callback` is passed, the API call will be asynchronous and the result will be passed via `callback(filename)` -### `dialog.showMessageBox([browserWindow][, options][, callback])` +### `dialog.showMessageBox([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (optional) -* `options` Object (optional) +* `options` Object * `type` String - Can be `"none"`, `"info"`, `"error"`, `"question"` or `"warning"`. On Windows, "question" displays the same icon as "info", unless you set an icon using the "icon" option. From c7bfd5f09df91d2a66ef1e5df9429921e3400d05 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 17 Dec 2015 20:11:21 +0800 Subject: [PATCH 293/411] Upgrade libchromiumcontent: enable sending sync message to UI thread --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index dd848bc816..5fbd15c172 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'e334f07f86371385c51a9cda528ea531ea81bffa' +LIBCHROMIUMCONTENT_COMMIT = '8b80cf3e621b19a9706cf73c7cb8bb4724f5cea1' PLATFORM = { 'cygwin': 'win32', From 0282d424bfa935d267b025659073b6aa5c1343ae Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 17 Dec 2015 21:27:14 +0800 Subject: [PATCH 294/411] Pass opener ID in command line --- atom/browser/lib/guest-window-manager.coffee | 14 ++++---------- atom/browser/web_contents_preferences.cc | 6 ++++++ atom/common/options_switches.cc | 4 ++++ atom/common/options_switches.h | 2 ++ atom/renderer/lib/init.coffee | 3 +++ atom/renderer/lib/override.coffee | 4 ++-- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 1e9ee13c71..af73db5c78 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -30,12 +30,13 @@ createGuest = (embedder, url, frameName, options) -> guest.loadURL url return guest.id + # Remember the embedder window's id. + options.webPreferences ?= {} + options.webPreferences.openerId = BrowserWindow.fromWebContents(embedder)?.id + guest = new BrowserWindow(options) guest.loadURL url - # Remember the embedder, will be used by window.opener methods. - v8Util.setHiddenValue guest.webContents, 'embedder', embedder - # When |embedder| is destroyed we should also destroy attached guest, and if # guest is closed by user then we should prevent |embedder| from double # closing guest. @@ -83,10 +84,3 @@ ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> BrowserWindow.fromId(guestId)?.webContents?[method] args... - -ipcMain.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_OPENER_ID', (event) -> - embedder = v8Util.getHiddenValue event.sender, 'embedder' - openerId = null - if embedder? - openerId = BrowserWindow.fromWebContents(embedder)?.id - event.returnValue = openerId diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 3d86df96dd..382c28951d 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -132,6 +132,12 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( if (web_preferences.GetInteger(options::kGuestInstanceID, &guest_instance_id)) command_line->AppendSwitchASCII(switches::kGuestInstanceID, base::IntToString(guest_instance_id)); + + // Pass the opener's window id. + int opener_id; + if (web_preferences.GetInteger(options::kOpenerID, &opener_id)) + command_line->AppendSwitchASCII(switches::kOpenerID, + base::IntToString(opener_id)); } // static diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 0303cf5469..fe279bbf04 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -91,6 +91,9 @@ const char kPageVisibility[] = "pageVisibility"; // Enable DirectWrite on Windows. const char kDirectWrite[] = "directWrite"; +// Opener window's ID. +const char kOpenerID[] = "openerId"; + // Web runtime features. const char kExperimentalFeatures[] = "experimentalFeatures"; const char kExperimentalCanvasFeatures[] = "experimentalCanvasFeatures"; @@ -143,6 +146,7 @@ const char kExperimentalCanvasFeatures[] = "experimental-canvas-features"; const char kOverlayScrollbars[] = "overlay-scrollbars"; const char kSharedWorker[] = "shared-worker"; const char kPageVisibility[] = "page-visiblity"; +const char kOpenerID[] = "opener-id"; } // namespace switches diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 36c2be1431..d85e789c7b 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -52,6 +52,7 @@ extern const char kExperimentalCanvasFeatures[]; extern const char kOverlayScrollbars[]; extern const char kSharedWorker[]; extern const char kPageVisibility[]; +extern const char kOpenerID[]; } // namespace options @@ -81,6 +82,7 @@ extern const char kExperimentalCanvasFeatures[]; extern const char kOverlayScrollbars[]; extern const char kSharedWorker[]; extern const char kPageVisibility[]; +extern const char kOpenerID[]; } // namespace switches diff --git a/atom/renderer/lib/init.coffee b/atom/renderer/lib/init.coffee index d9f104f8d4..b9028f2dd6 100644 --- a/atom/renderer/lib/init.coffee +++ b/atom/renderer/lib/init.coffee @@ -30,6 +30,9 @@ for arg in process.argv if arg.indexOf('--guest-instance-id=') == 0 # This is a guest web view. process.guestInstanceId = parseInt arg.substr(arg.indexOf('=') + 1) + else if arg.indexOf('--opener-id=') == 0 + # This is a guest BrowserWindow. + process.openerId = parseInt arg.substr(arg.indexOf('=') + 1) else if arg.indexOf('--node-integration=') == 0 nodeIntegration = arg.substr arg.indexOf('=') + 1 else if arg.indexOf('--preload=') == 0 diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index ba15c7b3f6..7279b232a9 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -91,8 +91,8 @@ window.confirm = (message, title='') -> window.prompt = -> throw new Error('prompt() is and will not be supported.') -openerId = ipcRenderer.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_OPENER_ID' -window.opener = BrowserWindowProxy.getOrCreate(openerId) if openerId? +if process.openerId? + window.opener = BrowserWindowProxy.getOrCreate process.openerId ipcRenderer.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (event, sourceId, message, sourceOrigin) -> # Manually dispatch event instead of using postMessage because we also need to From 353f08e47786dce5801720b419d8ddb8c3249f1d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 17 Dec 2015 22:03:16 +0800 Subject: [PATCH 295/411] Remove sync call in remote module --- atom/browser/api/lib/exports/electron.coffee | 8 ++-- atom/browser/lib/rpc-server.coffee | 3 -- atom/common/api/lib/exports/electron.coffee | 42 ++++++++++--------- atom/renderer/api/lib/exports/electron.coffee | 8 ++-- atom/renderer/api/lib/remote.coffee | 4 +- 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/atom/browser/api/lib/exports/electron.coffee b/atom/browser/api/lib/exports/electron.coffee index 3f7d9b1a13..9c61a55070 100644 --- a/atom/browser/api/lib/exports/electron.coffee +++ b/atom/browser/api/lib/exports/electron.coffee @@ -1,7 +1,9 @@ -# Import common modules. -module.exports = require '../../../../common/api/lib/exports/electron' +common = require '../../../../common/api/lib/exports/electron' -Object.defineProperties module.exports, +# Import common modules. +common.defineProperties exports + +Object.defineProperties exports, # Browser side modules, please sort with alphabet order. app: enumerable: true diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index e388a0fd57..e0256081e6 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -219,9 +219,6 @@ ipcMain.on 'ATOM_BROWSER_GUEST_WEB_CONTENTS', (event, guestInstanceId) -> catch e event.returnValue = exceptionToMeta e -ipcMain.on 'ATOM_BROWSER_LIST_MODULES', (event) -> - event.returnValue = (name for name of electron) - ipcMain.on 'ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', (event, guestInstanceId, method, args...) -> try guestViewManager = require './guest-view-manager' diff --git a/atom/common/api/lib/exports/electron.coffee b/atom/common/api/lib/exports/electron.coffee index e981443065..7598f9ee32 100644 --- a/atom/common/api/lib/exports/electron.coffee +++ b/atom/common/api/lib/exports/electron.coffee @@ -5,23 +5,25 @@ exports.hideInternalModules = -> # Remove the "common/api/lib" and "browser-or-renderer/api/lib". globalPaths.splice 0, 2 -Object.defineProperties exports, - # Common modules, please sort with alphabet order. - clipboard: - # Must be enumerable, otherwise it woulde be invisible to remote module. - enumerable: true - get: -> require '../clipboard' - crashReporter: - enumerable: true - get: -> require '../crash-reporter' - nativeImage: - enumerable: true - get: -> require '../native-image' - shell: - enumerable: true - get: -> require '../shell' - # The internal modules, invisible unless you know their names. - CallbacksRegistry: - get: -> require '../callbacks-registry' - deprecate: - get: -> require '../deprecate' +# Attaches properties to |exports|. +exports.defineProperties = (exports) -> + Object.defineProperties exports, + # Common modules, please sort with alphabet order. + clipboard: + # Must be enumerable, otherwise it woulde be invisible to remote module. + enumerable: true + get: -> require '../clipboard' + crashReporter: + enumerable: true + get: -> require '../crash-reporter' + nativeImage: + enumerable: true + get: -> require '../native-image' + shell: + enumerable: true + get: -> require '../shell' + # The internal modules, invisible unless you know their names. + CallbacksRegistry: + get: -> require '../callbacks-registry' + deprecate: + get: -> require '../deprecate' diff --git a/atom/renderer/api/lib/exports/electron.coffee b/atom/renderer/api/lib/exports/electron.coffee index a224818e5f..d0b3af3c55 100644 --- a/atom/renderer/api/lib/exports/electron.coffee +++ b/atom/renderer/api/lib/exports/electron.coffee @@ -1,7 +1,9 @@ -# Import common modules. -module.exports = require '../../../../common/api/lib/exports/electron' +common = require '../../../../common/api/lib/exports/electron' -Object.defineProperties module.exports, +# Import common modules. +common.defineProperties exports + +Object.defineProperties exports, # Renderer side modules, please sort with alphabet order. desktopCapturer: enumerable: true diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 48cdd937fb..2828586849 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -126,9 +126,9 @@ ipcRenderer.on 'ATOM_RENDERER_RELEASE_CALLBACK', (event, id) -> callbacksRegistry.remove id # List all built-in modules in browser process. -browserModules = ipcRenderer.sendSync 'ATOM_BROWSER_LIST_MODULES' +browserModules = require '../../../browser/api/lib/exports/electron' # And add a helper receiver for each one. -for name in browserModules +for name of browserModules do (name) -> Object.defineProperty exports, name, get: -> exports.getBuiltin name From 9eb797296ca59e358f6bb2e2bb240d2c21572336 Mon Sep 17 00:00:00 2001 From: rehez Date: Thu, 17 Dec 2015 16:20:33 +0100 Subject: [PATCH 296/411] escape url string --- atom/common/platform_util_mac.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/common/platform_util_mac.mm b/atom/common/platform_util_mac.mm index 2f9e2b7642..dcf622feb5 100644 --- a/atom/common/platform_util_mac.mm +++ b/atom/common/platform_util_mac.mm @@ -121,7 +121,8 @@ void OpenItem(const base::FilePath& full_path) { bool OpenExternal(const GURL& url) { DCHECK([NSThread isMainThread]); NSString* url_string = base::SysUTF8ToNSString(url.spec()); - NSURL* ns_url = [NSURL URLWithString:url_string]; + NSString* url_escaped_string = [url_string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSURL* ns_url = [NSURL URLWithString:url_escaped_string]; if (!ns_url) { return false; } From ac92917353683dce886deea239ef8e3f702094e7 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 18 Dec 2015 03:10:08 +0900 Subject: [PATCH 297/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/crash-reporter.md | 4 ++-- docs-translations/ko-KR/api/desktop-capturer.md | 9 +++++++++ docs-translations/ko-KR/api/dialog.md | 14 +++++++------- docs-translations/ko-KR/tutorial/quick-start.md | 2 ++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/docs-translations/ko-KR/api/crash-reporter.md b/docs-translations/ko-KR/api/crash-reporter.md index a4d42f0133..0b7daba14d 100644 --- a/docs-translations/ko-KR/api/crash-reporter.md +++ b/docs-translations/ko-KR/api/crash-reporter.md @@ -25,8 +25,8 @@ crashReporter.start({ * `options` Object, properties: * `productName` String, 기본값: Electron -* `companyName` String, 기본값: GitHub, Inc -* `submitURL` String, 기본값: http://54.249.141.255:1127/post +* `companyName` String (**필수항목**) +* `submitURL` String, (**필수항목**) * 크래시 리포트는 POST 방식으로 이 URL로 전송됩니다. * `autoSubmit` Boolean, 기본값: true * true로 지정할 경우 유저의 승인 없이 자동으로 오류를 보고합니다. diff --git a/docs-translations/ko-KR/api/desktop-capturer.md b/docs-translations/ko-KR/api/desktop-capturer.md index f14b5433ea..5a531bf44a 100644 --- a/docs-translations/ko-KR/api/desktop-capturer.md +++ b/docs-translations/ko-KR/api/desktop-capturer.md @@ -38,6 +38,15 @@ function getUserMediaError(e) { } ``` +`navigator.webkitGetUserMedia` 호출에 대해 제약된 객체를 생성할 때 +`desktopCapturer`에서 소스를 사용한다면 `chromeMediaSource`은 반드시 +`"desktop"`으로 지정되어야 하며, `audio` 도 반드시 `false`로 지정되어야 합니다. + +만약 전체 데스크탑에서 오디오와 비디오 모두 캡쳐를 하고 싶을 땐 `chromeMediaSource`를 +`"screen"` 그리고 `audio`를 `true`로 지정할 수 있습니다. 이 방법을 사용하면 +`chromeMediaSourceId`를 지정할 수 없습니다. + + ## Methods `desktopCapturer` 모듈은 다음과 같은 메서드를 가지고 있습니다: diff --git a/docs-translations/ko-KR/api/dialog.md b/docs-translations/ko-KR/api/dialog.md index bb92c1f43e..3fff8683cd 100644 --- a/docs-translations/ko-KR/api/dialog.md +++ b/docs-translations/ko-KR/api/dialog.md @@ -19,10 +19,10 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' `dialog` 모듈은 다음과 같은 메서드를 가지고 있습니다: -### `dialog.showOpenDialog([browserWindow][, options][, callback])` +### `dialog.showOpenDialog([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (optional) -* `options` Object (optional) +* `options` Object * `title` String * `defaultPath` String * `filters` Array @@ -63,10 +63,10 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' 없습니다. 이러한 이유로 `properties`를 `['openFile', 'openDirectory']`로 설정하면 디렉터리 선택 대화 상자가 표시됩니다. -### `dialog.showSaveDialog([browserWindow][, options][, callback])` +### `dialog.showSaveDialog([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (optional) -* `options` Object (optional) +* `options` Object * `title` String * `defaultPath` String * `filters` Array @@ -80,9 +80,9 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' `callback`이 전달되면 메서드가 비동기로 작동되며 결과는 `callback(filename)`을 통해 전달됩니다. -### `dialog.showMessageBox([browserWindow][, options][, callback])` +### `dialog.showMessageBox([browserWindow, ]options[, callback])` -* `browserWindow` BrowserWindow +* `browserWindow` BrowserWindow (optional) * `options` Object * `type` String - `"none"`, `"info"`, `"error"`, `"question"`, `"warning"` 중 하나를 사용할 수 있습니다. Windows에선 따로 `icon`을 설정하지 않은 이상 @@ -101,7 +101,7 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', ' 버튼을 찾으려고 시도하고 대화 상자 내에서 해당 버튼을 커맨드 링크처럼 만듭니다. 이 기능으로 앱을 좀 더 Modern Windows 앱처럼 만들 수 있습니다. 이 기능을 원하지 않으면 `noLink`를 true로 지정하면 됩니다. -* `callback` Function +* `callback` Function (optional) 대화 상자를 표시합니다. `browserWindow`를 지정하면 대화 상자가 완전히 닫힐 때까지 지정한 창을 사용할 수 없습니다. 완료 시 유저가 선택한 버튼의 인덱스를 반환합니다. diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index cfdc19f00e..d24261d72a 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -74,6 +74,8 @@ __알림__: 만약 `main` 필드가 `package.json`에 설정되어 있지 않으 다음과 같이 작성할 수 있습니다: ```javascript +'use strict'; + const electron = require('electron'); const app = electron.app; // 어플리케이션 기반을 조작 하는 모듈. const BrowserWindow = electron.BrowserWindow; // 네이티브 브라우저 창을 만드는 모듈. From a39834740c1b2e0af66dda2243fcf25470af7424 Mon Sep 17 00:00:00 2001 From: Jeff Rehbein Date: Thu, 17 Dec 2015 14:00:04 -0600 Subject: [PATCH 298/411] DockShow workaround Implemented workaround in DockShow for TransformProcessType bugginess based on discussion at http://stackoverflow.com/questions/7596643/ --- atom/browser/browser_mac.mm | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 6a3481cf06..48050d6201 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -70,8 +70,21 @@ void Browser::DockHide() { } void Browser::DockShow() { + BOOL active = [[NSRunningApplication currentApplication] isActive]; ProcessSerialNumber psn = { 0, kCurrentProcess }; - TransformProcessType(&psn, kProcessTransformToForegroundApplication); + if (active) { + // Workaround buggy behavior of TransformProcessType + for (NSRunningApplication * app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { + [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + break; + } + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + }); + } else { + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + } } void Browser::DockSetMenu(ui::MenuModel* model) { From 836c13b33089f53b149df36eba066f3fb52be711 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 17 Dec 2015 23:27:05 +0530 Subject: [PATCH 299/411] browser: check for rvh existence --- atom/browser/atom_browser_client.cc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 1303b91b04..627d616cd6 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -197,9 +197,16 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( if (ContainsKey(pending_processes_, process_id)) process_id = pending_processes_[process_id]; + + // Certain render process will be created with no associated render view, + // for example: ServiceWorker. + auto rvh = content::RenderViewHost::FromID(process_id, kDefaultRoutingID); + if (!rvh) + return; + // Get the WebContents of the render process. - content::WebContents* web_contents = content::WebContents::FromRenderViewHost( - content::RenderViewHost::FromID(process_id, kDefaultRoutingID)); + content::WebContents* web_contents = + content::WebContents::FromRenderViewHost(rvh); if (!web_contents) return; From 177cfd9958b6feb85e6ec4e60b059c59c89bbe69 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 18 Dec 2015 09:19:13 +0800 Subject: [PATCH 300/411] Revert "Upgrade node to fix crash in Buffer" This reverts commit fe9c09ef64ea3a874307855924f5cfbde4770318. --- vendor/node | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/node b/vendor/node index aff2d71b16..2e11f0fcb1 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit aff2d71b16a0691a30f0be159a253bab80d01b2c +Subproject commit 2e11f0fcb14dd035f9e073cf8b7778d60cf4f668 From d5168f09d6e0df0604ce2056ad5a1b0c1b4a33cc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 18 Dec 2015 11:07:32 +0800 Subject: [PATCH 301/411] docs: Use "(optional)" instead of "__optional__" --- docs/api/session.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/api/session.md b/docs/api/session.md index 21464bd481..09fa61e211 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -102,14 +102,14 @@ session.defaultSession.cookies.set(cookie, function(error) { #### `ses.cookies.get(filter, callback)` * `filter` Object - * `url` String __optional__ - Retrieves cookies which are associated with + * `url` String (optional) - Retrieves cookies which are associated with `url`. Empty implies retrieving cookies of all urls. - * `name` String __optional__ - Filters cookies by name. - * `domain` String __optional__ - Retrieves cookies whose domains match or are + * `name` String (optional) - Filters cookies by name. + * `domain` String (optional) - Retrieves cookies whose domains match or are subdomains of `domains` - * `path` String __optional__ - Retrieves cookies whose path matches `path`. - * `secure` Boolean __optional__ - Filters cookies by their Secure property. - * `session` Boolean __optional__ - Filters out session or persistent cookies. + * `path` String (optional) - Retrieves cookies whose path matches `path`. + * `secure` Boolean (optional) - Filters cookies by their Secure property. + * `session` Boolean (optional) - Filters out session or persistent cookies. * `callback` Function Sends a request to get all cookies matching `details`, `callback` will be called @@ -127,7 +127,7 @@ with `callback(error, cookies)` on complete. * `httpOnly` Boolean - Whether the cookie is marked as HTTP only. * `session` Boolean - Whether the cookie is a session cookie or a persistent cookie with an expiration date. - * `expirationDate` Double __optional__ - The expiration date of the cookie as + * `expirationDate` Double (optional) - The expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies. @@ -327,8 +327,8 @@ is about to occur. The `callback` has to be called with an `response` object: * `response` Object - * `cancel` Boolean __optional__ - * `redirectURL` String __optional__ - The original request is prevented from + * `cancel` Boolean (optional) + * `redirectURL` String (optional) - The original request is prevented from being sent or completed, and is instead redirected to the given URL. #### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)` @@ -351,8 +351,8 @@ TCP connection is made to the server, but before any http data is sent. The `callback` has to be called with an `response` object: * `response` Object - * `cancel` Boolean __optional__ - * `requestHeaders` Object __optional__ - When provided, request will be made + * `cancel` Boolean (optional) + * `requestHeaders` Object (optional) - When provided, request will be made with these headers. #### `ses.webRequest.onSendHeaders([filter, ]listener)` @@ -394,7 +394,7 @@ The `callback` has to be called with an `response` object: * `response` Object * `cancel` Boolean - * `responseHeaders` Object __optional__ - When provided, the server is assumed + * `responseHeaders` Object (optional) - When provided, the server is assumed to have responded with these headers. #### `ses.webRequest.onResponseStarted([filter, ]listener)` @@ -434,7 +434,7 @@ redirect is about to occur. * `timestamp` Double * `redirectURL` String * `statusCode` Integer - * `ip` String __optional__ - The server IP address that the request was + * `ip` String (optional) - The server IP address that the request was actually sent to. * `fromCache` Boolean * `responseHeaders` Object From 4ad79b2a8cccff2b44fc4ac1690d6bd4c18ed722 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 18 Dec 2015 11:12:07 +0800 Subject: [PATCH 302/411] docs: Standarize the "(**required**)" --- docs/api/native-image.md | 2 +- docs/styleguide.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/native-image.md b/docs/api/native-image.md index c08788965a..515e89fba1 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -125,7 +125,7 @@ Returns a [Buffer][buffer] that contains the image's `PNG` encoded data. ### `image.toJpeg(quality)` -* `quality` Integer between 0 - 100 (**required**) +* `quality` Integer (**required**) - Between 0 - 100. Returns a [Buffer][buffer] that contains the image's `JPEG` encoded data. diff --git a/docs/styleguide.md b/docs/styleguide.md index b471c19fba..6b745b988b 100644 --- a/docs/styleguide.md +++ b/docs/styleguide.md @@ -55,7 +55,7 @@ documentation: `methodName(required[, optional]))` -* `require` String, **required** +* `require` String (**required**) * `optional` Integer --- From cefce45ffa358ca78309f1c6d34bcca2b41bf297 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 18 Dec 2015 12:00:10 +0800 Subject: [PATCH 303/411] Update node: Use phantom callback in CallbackInfo --- vendor/node | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/node b/vendor/node index 2e11f0fcb1..3b044608ee 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 2e11f0fcb14dd035f9e073cf8b7778d60cf4f668 +Subproject commit 3b044608ee51ca001dabe944cade6e64f46b0724 From bff28613115ce2a951412fffc2dfb1c073f1cabf Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 18 Dec 2015 13:16:30 +0800 Subject: [PATCH 304/411] Bump v0.36.1 --- atom.gyp | 2 +- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atom.gyp b/atom.gyp index edfb8009c6..78f71e9de1 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.36.0', + 'version%': '0.36.1', }, 'includes': [ 'filenames.gypi', diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 4c72c04cfa..bf8c345466 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.36.0 + 0.36.1 CFBundleShortVersionString - 0.36.0 + 0.36.1 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 9568629c42..99821482e8 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,36,0,0 - PRODUCTVERSION 0,36,0,0 + FILEVERSION 0,36,1,0 + PRODUCTVERSION 0,36,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.36.0" + VALUE "FileVersion", "0.36.1" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.36.0" + VALUE "ProductVersion", "0.36.1" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 46293a0eb8..e9e283c146 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 0 #define ATOM_MINOR_VERSION 36 -#define ATOM_PATCH_VERSION 0 +#define ATOM_PATCH_VERSION 1 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/package.json b/package.json index af0e315dda..048ab84353 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "0.36.0", + "version": "0.36.1", "devDependencies": { "asar": "^0.8.0", "coffee-script": "^1.9.2", From 7e0775f9d795b86925b6d912b3bc1a33ae4209f0 Mon Sep 17 00:00:00 2001 From: WenryXu Date: Sat, 19 Dec 2015 14:27:47 +0800 Subject: [PATCH 305/411] docs: Fix markdown code language --- .../desktop-environment-integration.md | 38 +++++++++---------- .../zh-CN/tutorial/online-offline-events.md | 16 ++++---- .../zh-CN/tutorial/quick-start.md | 32 ++++++++-------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs-translations/zh-CN/tutorial/desktop-environment-integration.md b/docs-translations/zh-CN/tutorial/desktop-environment-integration.md index f8fbdaaaf5..6ded331627 100644 --- a/docs-translations/zh-CN/tutorial/desktop-environment-integration.md +++ b/docs-translations/zh-CN/tutorial/desktop-environment-integration.md @@ -14,14 +14,14 @@ Windows 和 OS X 提供获取最近文档列表的便捷方式,那就是打开 为了增加一个文件到最近文件列表,你可以使用 [app.addRecentDocument][3] API: -```` +```javascript var app = require('app'); app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); -```` +``` 或者你也可以使用 [app.clearRecentDocuments][4] API 来清空最近文件列表。 -```` +```javascript app.clearRecentDocuments(); -```` +``` ## Windows 需注意 为了这个特性在 Windows 上表现正常,你的应用需要被注册成为一种文件类型的句柄,否则,在你注册之前,文件不会出现在跳转列表。你可以在 [Application Registration][5] 里找到任何关于注册事宜的说明。 @@ -34,7 +34,7 @@ OS X 可以让开发者定制自己的菜单,通常会包含一些常用特性 [Dock menu of Terminal.app][6] 使用 `app.dock.setMenu` API 来设置你的菜单,这仅在 OS X 上可行: -```` +```javascript var app = require('app'); var Menu = require('menu'); var dockMenu = Menu.buildFromTemplate([ @@ -46,7 +46,7 @@ var dockMenu = Menu.buildFromTemplate([ { label: 'New Command...'} ]); app.dock.setMenu(dockMenu); -```` +``` ## 用户任务(Windows) 在 Windows,你可以特别定义跳转列表的 `Tasks` 目录的行为,引用 MSDN 的文档: @@ -60,7 +60,7 @@ app.dock.setMenu(dockMenu); 不同于 OS X 的鱼眼菜单,Windows 上的用户任务表现得更像一个快捷方式,比如当用户点击一个任务,一个程序将会被传入特定的参数并且运行。 你可以使用 [app.setUserTasks][8] API 来设置你的应用中的用户任务: -```` +```javascript var app = require('app'); app.setUserTasks([ { @@ -72,11 +72,11 @@ app.setUserTasks([ description: 'Create a new window' } ]); -```` +``` 调用 `app.setUserTasks` 并传入空数组就可以清除你的任务列表: -```` +```javascript app.setUserTasks([]); -```` +``` 当你的应用关闭时,用户任务会仍然会出现,在你的应用被卸载前,任务指定的图标和程序的路径必须是存在的。 ### 缩略图工具栏 @@ -90,7 +90,7 @@ app.setUserTasks([]); ### Windows Media Player 的缩略图工具栏 ![Thumbnail toolbar of Windows Media Player][9] 你可以使用 [BrowserWindow.setThumbarButtons][10] 来设置你的应用的缩略图工具栏。 -```` +```javascript var BrowserWindow = require('browser-window'); var path = require('path'); var win = new BrowserWindow({ @@ -110,11 +110,11 @@ win.setThumbarButtons([ click: function() { console.log("button2 clicked."); } } ]); -```` +``` 调用 `BrowserWindow.setThumbarButtons` 并传入空数组即可清空缩略图工具栏: -```` +```javascript win.setThumbarButtons([]); -```` +``` ## Unity launcher 快捷方式(Linux) 在 Unity,你可以通过改变 `.desktop` 文件来增加自定义运行器的快捷方式,详情看 [Adding shortcuts to a launcher][11]。 @@ -132,20 +132,20 @@ Unity DE 也具有同样的特性,在运行器上显示进度条。 ![Progress bar in Unity launcher][14] 给一个窗口设置进度条,你可以调用 [BrowserWindow.setProgressBar][15] API: -```` +```javascript var window = new BrowserWindow({...}); window.setProgressBar(0.5); -```` +``` 在 OS X,一个窗口可以设置它展示的文件,文件的图标可以出现在标题栏,当用户 Command-Click 或者 Control-Click 标题栏,文件路径弹窗将会出现。 ### 展示文件弹窗菜单: ![Represented file popup menu][16] 你可以调用 [BrowserWindow.setRepresentedFilename][17] 和 [BrowserWindow.setDocumentEdited][18] APIs: -```` +```javascript var window = new BrowserWindow({...}); window.setRepresentedFilename('/etc/passwd'); window.setDocumentEdited(true); -```` +``` [1]:https://camo.githubusercontent.com/3310597e01f138b1d687e07aa618c50908a88dec/687474703a2f2f692e6d73646e2e6d6963726f736f66742e636f6d2f64796e696d672f49433432303533382e706e67 [2]: https://cloud.githubusercontent.com/assets/639601/5069610/2aa80758-6e97-11e4-8cfb-c1a414a10774.png @@ -164,4 +164,4 @@ window.setDocumentEdited(true); [15]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md [16]: https://cloud.githubusercontent.com/assets/639601/5082061/670a949a-6f14-11e4-987a-9aaa04b23c1d.png [17]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md - [18]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md \ No newline at end of file + [18]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md diff --git a/docs-translations/zh-CN/tutorial/online-offline-events.md b/docs-translations/zh-CN/tutorial/online-offline-events.md index 764b81aa5d..7389afa4c9 100644 --- a/docs-translations/zh-CN/tutorial/online-offline-events.md +++ b/docs-translations/zh-CN/tutorial/online-offline-events.md @@ -2,7 +2,7 @@ 使用标准 HTML5 APIs 可以实现在线和离线事件的探测,就像以下例子: *main.js* -```` +```javascript var app = require('app'); var BrowserWindow = require('browser-window'); var onlineStatusWindow; @@ -11,10 +11,10 @@ app.on('ready', function() { onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); onlineStatusWindow.loadURL('file://' + __dirname + '/online-status.html'); }); -```` +``` *online-status.html* -```` +```html @@ -30,12 +30,12 @@ app.on('ready', function() { -```` +``` 也会有人想要在主进程也有回应这些事件的实例。然后主进程没有 `navigator` 对象因此不能直接探测在线还是离线。使用 Electron 的进程间通讯工具,事件就可以在主进程被使,就像下面的例子: *main.js* -```` +```javascript var app = require('app'); var ipc = require('ipc'); var BrowserWindow = require('browser-window'); @@ -49,10 +49,10 @@ app.on('ready', function() { ipc.on('online-status-changed', function(event, status) { console.log(status); }); -```` +``` *online-status.html* -```` +```html @@ -69,4 +69,4 @@ ipc.on('online-status-changed', function(event, status) { -```` +``` diff --git a/docs-translations/zh-CN/tutorial/quick-start.md b/docs-translations/zh-CN/tutorial/quick-start.md index 1bb8473b36..d54dddbfc9 100644 --- a/docs-translations/zh-CN/tutorial/quick-start.md +++ b/docs-translations/zh-CN/tutorial/quick-start.md @@ -31,17 +31,17 @@ your-app/ └── index.html ```` `package.json `的格式和 Node 的完全一致,并且那个被 `main` 字段声明的脚本文件是你的应用的启动脚本,它运行在主进程上。你应用里的 `package.json` 看起来应该像: -```` +```json { "name" : "your-app", "version" : "0.1.0", "main" : "main.js" } -```` +``` **注意**:如果 `main` 字段没有在 `package.json` 声明,Electron会优先加载 `index.js`。 `main.js` 应该用于创建窗口和处理系统时间,一个典型的例子如下: -```` +```javascript var app = require('app'); // 控制应用生命周期的模块。 var BrowserWindow = require('browser-window'); // 创建原生浏览器窗口的模块 @@ -78,9 +78,9 @@ app.on('ready', function() { mainWindow = null; }); }); -```` +``` 最后,你想展示的 `index.html` : -```` +```html @@ -92,35 +92,35 @@ app.on('ready', function() { and Electron . -```` +``` # 运行你的应用 一旦你创建了最初的 `main.js`, `index.html` 和 `package.json` 这几个文件,你可能会想尝试在本地运行并测试,看看是不是和期望的那样正常运行。 ## electron-prebuild 如果你已经用 `npm` 全局安装了 `electron-prebuilt`,你只需要按照如下方式直接运行你的应用: -```` +```bash electron . -```` +``` 如果你是局部安装,那运行: -```` +```bash ./node_modules/.bin/electron . -```` +``` ## 手工下载 Electron 二进制文件 如果你手工下载了 Electron 的二进制文件,你也可以直接使用其中的二进制文件直接运行你的应用。 ### Windows -```` +```bash $ .\electron\electron.exe your-app\ -```` +``` ### Linux -```` +```bash $ ./electron/electron your-app/ -```` +``` ### OS X -```` +```bash $ ./Electron.app/Contents/MacOS/Electron your-app/ -```` +``` `Electron.app` 里面是 Electron 发布包,你可以在[这里][3]下载到。 # 以发行版本运行 From 7b03ac6d610f9f012a05fe771d514a6e31d83e7d Mon Sep 17 00:00:00 2001 From: Robo Date: Sun, 20 Dec 2015 01:23:47 +0530 Subject: [PATCH 306/411] navigaton: handle history operations from renderer --- atom/browser/api/atom_api_web_contents.cc | 5 +++++ atom/browser/api/atom_api_web_contents.h | 1 + 2 files changed, 6 insertions(+) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index cb89db911f..0539684c51 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -421,6 +421,11 @@ bool WebContents::HandleContextMenu(const content::ContextMenuParams& params) { return true; } +bool WebContents::OnGoToEntryOffset(int offset) { + GoToOffset(offset); + return false; +} + void WebContents::BeforeUnloadFired(const base::TimeTicks& proceed_time) { // Do nothing, we override this method just to avoid compilation error since // there are two virtual functions named BeforeUnloadFired. diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index fb8892f105..7fd09b9b86 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -186,6 +186,7 @@ class WebContents : public mate::TrackableObject, void RendererUnresponsive(content::WebContents* source) override; void RendererResponsive(content::WebContents* source) override; bool HandleContextMenu(const content::ContextMenuParams& params) override; + bool OnGoToEntryOffset(int offset) override; // content::WebContentsObserver: void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; From fed0c439700fc54f0152f3039ac6058faf2ae8c1 Mon Sep 17 00:00:00 2001 From: "Brian R. Bondy" Date: Sat, 19 Dec 2015 22:16:22 -0500 Subject: [PATCH 307/411] Add media play events to webview --- atom/browser/api/atom_api_web_contents.cc | 8 ++++++++ atom/browser/api/atom_api_web_contents.h | 2 ++ atom/browser/lib/guest-view-manager.coffee | 4 +++- .../lib/web-view/guest-view-internal.coffee | 2 ++ docs/api/web-contents.md | 8 ++++++++ spec/fixtures/assets/LICENSE | 3 +++ spec/fixtures/assets/tone.wav | Bin 0 -> 302674 bytes spec/fixtures/pages/audio.html | 1 + spec/webview-spec.coffee | 11 +++++++++++ 9 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/assets/LICENSE create mode 100644 spec/fixtures/assets/tone.wav create mode 100644 spec/fixtures/pages/audio.html diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index cb89db911f..2fae7f10cd 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -452,6 +452,14 @@ void WebContents::PluginCrashed(const base::FilePath& plugin_path, Emit("plugin-crashed", info.name, info.version); } +void WebContents::MediaStartedPlaying() { + Emit("media-started-playing"); +} + +void WebContents::MediaPaused() { + Emit("media-paused"); +} + void WebContents::DocumentLoadedInFrame( content::RenderFrameHost* render_frame_host) { if (!render_frame_host->GetParent()) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index fb8892f105..74d4705a15 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -224,6 +224,8 @@ class WebContents : public mate::TrackableObject, const std::vector& urls) override; void PluginCrashed(const base::FilePath& plugin_path, base::ProcessId plugin_pid) override; + void MediaStartedPlaying() override; + void MediaPaused() override; // brightray::InspectableWebContentsViewDelegate: void DevToolsFocused() override; diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index d4bf55158f..335d6516fc 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -22,7 +22,9 @@ supportedWebViewEvents = [ 'page-title-updated' 'page-favicon-updated' 'enter-html-full-screen' - 'leave-html-full-screen' + 'leave-html-full-screen', + 'media-started-playing', + 'media-paused', ] nextInstanceId = 0 diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index b28fec23ed..04fa707da7 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -20,6 +20,8 @@ WEB_VIEW_EVENTS = 'crashed': [] 'gpu-crashed': [] 'plugin-crashed': ['name', 'version'] + 'media-started-playing': [] + 'media-paused': [] 'destroyed': [] 'page-title-updated': ['title', 'explicitSet'] 'page-favicon-updated': ['favicons'] diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 90ecda9168..abb038ac1a 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -221,6 +221,14 @@ Emitted when `webContents` wants to do basic auth. The usage is the same with [the `login` event of `app`](app.md#event-login). +### Event: 'media-started-playing' + +Emitted when media starts playing. + +### Event: 'media-paused' + +Emitted when media is paused or done playing. + ## Instance Methods The `webContents` object has the following instance methods: diff --git a/spec/fixtures/assets/LICENSE b/spec/fixtures/assets/LICENSE new file mode 100644 index 0000000000..ec56b38f44 --- /dev/null +++ b/spec/fixtures/assets/LICENSE @@ -0,0 +1,3 @@ +tone.wav +http://soundbible.com/1815-A-Tone.html +License: Public Domain diff --git a/spec/fixtures/assets/tone.wav b/spec/fixtures/assets/tone.wav new file mode 100644 index 0000000000000000000000000000000000000000..7fbc54cbe196e91342d24f26db9d1bea5b98c130 GIT binary patch literal 302674 zcmXWD3sh72o-P>jeiQN}B!mz^4hLmelr-bPVrbOn(pWT$(q_3(+NPi#TFYTk(hjX< zb1AtjE=toJXotqK8I;Rq^e`SvF2lugT$Emhri#l&5fOwC5<yhGmeZO&PHZ2`q( zC)xY|{l3?(DEYHL`|}4>Qstli<*C2<`kGomB9UT9WYWneB$D9wWD=DmAl3f$wZAU> zo=Q3!jYiHyBauIeh9bGqU_=)E6p=<(Bm0q$5pQHF@*(nHBg>J=$WrA0iToFAICjA38 zZB!fm(`a(EHBuANg{Q-R9sbj>8vhs#t%nlB;n4Tt3*m71Q~3MHw~+!I>%T^;@jw0* z`z>sV(W%I>$VAu`Y6#s4vVuSP|H|+8d3|NxF|XeJ-#lxs8qZ(4{>t;EE5<8vAM#as z7WUJ8;XsA|i!e2`5uJ!MlJCSAs7cf%dN%zl<_68NWUqu$&q+p}7dOvFa58i(J|L##c{?gOqjC%gNJInXpr{7NwMg#wM zBq6LIi=!#DeV7Fe?|5%zlr%@7$-=VXnzv@N6Mc^l;r<2 zY$5$axSgaA{~O5}`4H1i`aR_#Ifs6V7SB4&EaLtp=WgtA{xxAtoJ>+6ij%z)C&lX| zU&T|U{jy^cs&q&6o#^Md8v;fwkGIY_&+1`5ru`Z1B3Vgpj#ftH;r-C<;GYKnk3d}D z?*jkPzlwb~7#GM7hXZ2~PDn$NhrfwQkNkV`Lc~bUjS_nSc`TA0GZ=mm{fm%3{C5FP z===Sgz#H%P`+U#Wz89`-?}BsNd&l{E-+yC3&V$K9!Q7?r5f>}qp z&$&wZj{g~5C-^P1UwDQ6A4Gr8`Ol&aPE>fG(;3&r{-@YwW-RZ&(*K-&jQSO0BIW^A z995EgLhI4L3zS5(`||KV`Wiyp-Xo#c-X47Qg}(L;hV%A`e;bQ*_!lBJ|DDJ${=x8{ z`O8A*_U{E=d(Z99x|cnjPN8dO?|-wOv|6pxyZ^&nvy;F3AGiO)^gkOorhnA;n0~L< z@7^$MSmKPWdq+&2&IR*-@vPX$ex2h;$n17T7V$cA0-2N}p<~or;Rn?8NEP;O%3!zv z$Jb6O3=+@36k7BZ2CKcl_|5L`_aiRJ{&m-)?<~GfxxU|TbD#3}dU9|~p@7>r98C3_ zf;RtfFeeZS2m&4cOa7-m$$pkc;VpLdyAt=_*%!=n*4sPNII>COvQA={(k|=jH;Oi2 ztzXyb*3W9^HzL~F&2nAKR;fO9`|&o%)L>dL=UVz~L7U9+z;V>I<}$l`J$}z?Z;SVe zuiD$?^Lt8t<(?sLE#8&u_}6)-+D$wk)p^!Md==waZiTtiZ9iF%3l*y1qZ{= zLocGVNMFoCghg>iJd`^T8TEYl8Rd4cl}z@Jk`&&O$h`A*@Z#Q$eX*t7W7?^Arfxsl zGinbfz^%m>nZR4J4C(W5|u61jyL*Ah6#{N~iAaK*6 z3Z^+{g2hf#kczJl@UzU(6zH};_GjA3*kA5tA7nROx|T`YV?kqa!Odpv#E%?<^!n0fo1 zv~kaA%9N`jrrt>+J#geks~qC!D@Sv*(^*IAcTL31x=&LSULCd6cb%TMKg-bWFEdm9 z)hx2Vg>`>l!IJEkGc$Z@Mzwbyk9Ct;j>pUL6p(v86EW4^zL-T{0r|1Nn{qhRNpnWB zm`ZXUr;|3!UuNbCQ@L*>WwA%)VxdA=A^u*KDLtdElGP+r$a2*0q~EJr zB|}Q9s4XE^SRa2ua8-JnKOwep+ePO%>p}*5Kr+<>4#crpal^4^B;~!9;NuN{rxY<}*qyBa;%LXHtq7?tQ!7(j$Tm6uM^SY-bhA84`i8A zp}ammA}>m)O&C|G6J9H?%fC*nk9Q}^WD?aQi9}T)mZ(mO+=&B1iK-I&fbblC{$ACG zBVkETB~jxaCw;;Zk1K3R#A`dJ>PR9dX;oU4J5i!~t!z_g|#J;~$&lyqi^f5L z?^X&~6klv}^bC&^>SDjdS+DR?X=|=laxt!wD#vtK=9mdqIRt?;=ly-5>xxh9Zt-S$ zma+0=d0MIXfoX)=EC+=%&=VA5gFO>S;LvA-ybgovsg0(Z?cd*r)LQS$acg zo^~dXMqBh%QMsP0lwxNAxpj{pqp=P|)6KLX`?N{rkwu7r3+yAm6 zFb*!?Gd8?0++n}R+WUUVG`Kvy`!6eFX64!oOWMY$wNbZX(;AF>9Fxo;vJ^YNv0Zna zwzuK+k9%G@4Bje-+t+Q^?ko2m`|GSZfjj1VfhE(ufNG~HATSC7r?yY|$=m1lFKuV| za*X4inw^NtXR3FOnY-;LtP{2g9PyhynmNmE6`}V0^85z-{gaLFmO3|PurJ~Ju@CkQ{_0t+=Tk(h-kjac(2whK zj10Zcbi*LB2#llFnVkvS^saJm0PoU-t-{uAud~)Wb1iCjy1CbLe)qL^$@J9sgXzY8 z{;qbv#H`(4x4hgB+Rp9YbS&b0VE7DPh4+#_%X2$edD(z^m&GJhAa5Vu*EU8 zY~3-pZ1HVFThHvJdb?9%FuPA}w|FOwS9}eoMc-v}m#^K*@V(hncqvYK1WnUtQ`tCaM( zZps+;yV!3Dx+ydK)07S_i}IS)N?xO{#CRwcQbo*qR2&_PMmbCl6 z&=p@vMBycoxSn8)+U=qAyPnX_y3~y8t}N!7tCus;O9~=i|E)kIKsx*X2U_b@@A~J$^!59Ip{o$((U4>E*aS z@!hy}p;(w6rxBIKUK8Krv7}ngx3Vkj`FKC`lbp+}O(bK&p&KfG`fFuH zdZQvceI#M@P*K8qT2aDSsyQJ)MWGx{?n<;Jr6x&Klj>^a5zPqxRw&n}Jdo*Au1e)8 z1(M<93UNo$sK}rsiKgVu!c1V;kHWFIve=TiRPMB3h;>0Q!tllR(|cl%(vQSW(M^0a zV}}2lHN!u}<;0r!4Y6Z!`GN{@N!&B(fbe(m*6!F)a-zhi85Pk|n}rLh3vsz=cd+SQlKSOZRed}ou}U_Q@P+hSS%G9u#25Qwe-x6rm*cohx!?iyHoq^%#?6i9 zaR$RJtmmOw#`DlodVcs7^>XAHg+zKoo`_MByD2L%#WW?kpZ)gIeuLZ88`g=?r z{sGI+ej`?y&s>v%DFU#=-}ow&DF}+I#P=YdhZdYg68? zX@7nj(W;h;Hh;6+wn_TP)n$Gf*Y#^#wix;@{g=jzs#EM2y(U)yJ^->9%vt=Czvei}2sSt~OYtX3q0}Hy!SYEs4ruHtLn`E-BxyM(Neb3_^e8A8E4~CND!C_=Isv$TfCgm2yj#Ax)XWe zs3Tpqw~`<3-J~k_j?xSE+87V_8ktw^%ghIkUZ&7B%b0avrw@UryxeC|CIb~QJ>YMc z0wWqydSoJ|1S}5RhH4?z)2>qb>HSy(GT7rx6;`=ip@fquJ;nVbpX3>oM`BMUO$);6 zO5sehRWy@aAPK9xr5#D%%DNKM;>#8F@j`iV{5$C@*^1aBJt=x5sTA_X&bUh8#|6Q< zu#nFepW?PkuCR~E#+kp1FJh<@gmj*wnAWS%QOmK9Bv5Hb<(;%+vZM4BafF^LG&347 z9sk08&AP(w!cpaM+8A2kNC$U>k;7B4CV3K04S$t)BsLu=G9gZntB(ikQDlo#lZ+Bm zvQ9=zRVP%YRVy_>3(}!`>fE#*aFLSK2F*a)Jz$6y7*d+ZPgf}C4wWm`(|C%p)N;ii z@Z;;m$C%$26DQSvMT4d&L7(!wcyZb>S#w&Zv^lLnGM0KxEKjkDR@Lob1sc&v!n#l< z8xXdODuuq-g*Y3xB<>1(P|ypO)W#@_En*AZp8G{Xo{EVM+BVed2}GI#FfXkHTi`xoH~F z81Q2{#VF~~=)e*qasd$UrRs+2>%<)O=gOMoEQKjqoiGC)HL325*Q$ue_5kfOr3I1# zNl6@EoWuK3Sj{Slo2T`}8p)HqcASY~Fu#(BnQ=J+jvT#7ts`Ypl;i@khcXfKgw_{R z%LvBwG9QtvS??%Ata>1an$gJgGb402yO?IM>t>GuKQ6~D#nR&L#!Z7A4Pr95B_J?XF7U-p@)}~F zvS;{e#w715rmcbKBAXLhW@`7R=nC&KYQ%Ld=81D5qH}OUIw%75P6Ot<5qB-Ljb2Zc zW5|2jUg|UMJ;e-o#m54|JhNwc9ymU8J#l*MYgkpdxU!Gq3NQ72X_V}14O-yFDgUDW zvHzUG=KpzH5HRiJ1jfu~0xPzA;5e&+Mo(ez{=O+#7g+3KPfY{Z9d+f-5lD=(p}d!>aGGC>OT5)v=54{ z!|xSaswKr%=$&6TyyVx(mxN~czh9z*B0gGuTXdB$UY9Fvv zIr?ljfu(Ql#moz+_+;-+xTurx1MLXxjwQfTR*n` z>{IqC>(iYN7uOoz_pKH#&3z2LBYpgCsS?Ps@WJ`v&WDNBJ0D)Hi&nV0^_4tB_G%&4 z=jq*7>+2TvW;W*hpzYMQ61q{J?K1Q+i*>-lvR^j8a-824x*ALo7su4*#(M=`SLD8I zo^sV$?M~w!#rec3bM$&x_8a>Jd#d0!w)5dR>q4Z?`XZ{qzHV8Fys+eln=Pw>N{id) zG>_nagPrVJtUut@ez#MzlViNJeczC0$k4m=ztx>Hs5d*d?He^aX&ckPw-eTz>&89Z zdZ+Wr#;jYtIpkf|xqUAE{rx86OR%CmFrq8IhkMoDH1LoScf|GD`Ms?JI zsE|HNiqKohW(JkE%q(XPvB=ymXfgLW+2UWgY4Ixlvf_EHR&_b9Fj*s7(yUmHMLJH7Z@dB-nTL> z=cv4t4W)?rxzfzYNGxJ>C8`;v_*$(5;w3Z!U#eM|(o%LEw6H%xdUTilgsl&q?CvRf(YgaP1(6-aR{ zy)ZdDeK6&34h^J<)2^l8O8IZU8cu$e@k^5I*XLB;jEuxDGOCpqfAu+jdq^ox>w*GC zPAW{kr#_X`qbW@+N$pkqn5Is!9-5E8mada!ryrA@JoHRDmU>lMruhO2Ql@lSVU!fd zKa%u`FG^@}E8Fh;9J>3 z+$VXvXdL)q0DgR(SesCqXi&aZ5{ldma6z9^AK#qzOgeX{K=RM&+2UY&yJ!V_1-@@T zbWu{7CIey~mG@{G6@|%*(69vR?^QEkQu34`<*k%uAY+z-;8Hnib;8$){c?4JJ^l+R zH9lAPPO1Xq%wStZQ-4%c`(jpkB+^SxWfVsrZDX7y&C@1gZcz0eM8{btMXB5$g(iM87;7cCTrPNJ<-xu#ti<2t3Wxa%aW^<6;7b<- z$EcjxI?@^5QYeq}*uTs)c!hMcI}>dDB+2f~$Ar}s=!b6KcA$c`h%4eEkfa65^`fuRqTOFI+5D5n$w11sDtOLN6U;N*4r;MC8E*%F zFx3Q`Ej8ddw}YR#W-!r}g&zCm;j$17=;4fD9fIDMjq8i>T3D1NOpj**?a9vk%a(lir>!rI8Tt(UQ+-4$F+BT}hY9_hq2$9kL(ls>!*@$Aea+HSJ^OvB zesDQMPx_dlH?EcH&uSO-KS53U(r5$6soAN6KK2m&{M%S(gD zenn4l9^Wc`7Z{9boX}LhYo9p(Ciq$k|R#odiyg&VE zc&T8maH(R|v{d);{PK&H=9S8og0;$(`i=FKaoy-gtwFgqv-9>-iCL&UVg0SH&vwpG zuvfT4ykir#QR^J|#Q?Oq%jSqHf0uYampvSl!dnI=k-yvM83Pi`S*M`ebvi$D-EZT=1GNPL(0Zgd*_DTd0m_3;pz-thF${o@8$ks za2uyNZ-2#BigQunErOOhcRmkIx$?t}p26^_P; za(5yI_?9`m0dS5wQm(Kfrd@QAY!#iR)QMzNu~0{q3yNv?c*V4*Y$|PpQAK@4CFZ_9 zoR0)^7z_`FYl64Jss3~*WtCB`haYp@Wdwt#(jGV>bSRokD4MKJ=Tr8YYmwdSDP>>r z9cMlEk1%F}b{Y+y5s9QE2jOM@0Y4TF=F13OLz=}z*~olARj|0s*Q}SEF09V?Is5|* zDddkUe87-}I92kT=(*;Nx;d|%i-1V7uhDhf>jo4X7t34FrLG0Sc-kk^2L(5`2rK4m{~7FIU9f% z!WWk&zL7KmL-JE<6LQlG%D*|(kz_q|L^FEmZt8m4+@Z^+(mE&iK1=Rk9{1j4kjQ?6lkiuVD_ZTr>2n zg~&3~32lEJ^G0^eRSFB5td2@(SOw%PrV$+E7?lAGVX$WzLo5TUk$DPQV-255yC8sT zEwqqEMdIj+cskrFSqi<8Jjb3Nz9_j9SrOx!6&uNQq9@=XS zPl}w-KXZW~m0%;4!Z0saxCpLN#26G@rIf`EM4RA;BeLCD8LfAg zg$O<1Gt75I?lyP1=ZUk$d(+Y7duz|ze`Y_oFN5k&sQ-3nIrQkyT)m!Wb}$8KBzQy+G&@Edy-3!FPg6K>Lp%I3>X7#tTumeSt^Ha|HdZXBSsu>cJrg(#&NH)9rNu&M)6E3@=Fx_uh5so8B?>g-b14OYe_wK|{Dw|%C2#ut_z z7~l)$tM+WT)!lZPW8QhyJ`07f#am~6>bqk;x8DOlVb$mcei*zr42>R%q2G1RP~}M3 z4%+U4$&{J$al|2TrWA03E`7KC_|{EFqwbYsb`yBCnQ8xOlMf8J3_Y%Jr)YCguijK| z_G_Pg5^CE&Vs2lSt(%sNYm@ILR-4|ne^f0sL*E+vu<#-ML*HuphX?CpAIzICRwUpd zs-4VF(APGaE#p4S(t!AgfR$?Bt@@+3H-S#`?PG|nE+RXtb7w(LELI-|Lx#uvr z$n0j&;n@>15e^J<7fN`vgwRS#O2gt%~`YG+H?cxp9&aw4dyU#vptZzR-Azd$a zc5bw}zuHuIhoFe2Y%lsaaA-u944)ES2oM(MX8*x-^K$=z<4mB+Q3I!)6MEuo2-Uiu z!%=@8viYBfZo&Jnh*=61)8t_VYdYM)y&Nfn8+aK!%Mtw$kL*~cuW1N_8h{st9C7>luq&$Hb*`6p)|UtEidwv-C_m zm-)IFG!(t(Cmrm2!0(Hw=xY)@mb8G1T~{q zah=|xJW7A9?59^N)r@ilm)S2@u&QLG>nV^x>2rr}!GrEFh<6?S-=k%C6C^*Aw7v1J(9%xyWLUqD~xLonAj9_(*ic)y| zgqvTcc_q7?%94@}T@&-uNuq1%&bW&7%W-pu#^QcV1AeB?fgwJU6hggwoK%#M2UIsG z$@pwm)maJ-xJM7PxFZ^M!m2s~Ph?82fK%TYPk2|<_(8!tX$QAJ(u-sVm6jf-B+u|k zq*U%0m@_S+W|Y8N7bC~iPU-_M9wo`3YT0RXq*_KFv@8}yf%8=X9jljF&8mggI8RgY z-+@(6#5m!bpA_lC+2V%Kgm@-6A-)~FCO#apLeJ8Okfss_WA4W3sM7*7BVXVK`<>)1 z#rm+_jk^FHOAq8IiMtGC>p8SR0USC5ixzhl?`10)tR(W7ixr+N*{@|ZdX7>1T^3TY zb1}+#SA-PkIe|_yG zS>3+#yJY{W@n`=_Lrx$=e_4|Z zi4+nCP6(kpIH41_lS(2hG4imHtO|Bh?(aXNEF;Ncho8=}kCN)FcOoTbQ?SQG_77vN zD@59-W=FYq*d(&#@0OV^n{C@KEH3?=b!h7w=vaL=!ehwNkuZM~`Jb7cp)IYU6e`N1 zzIa1o2(IQCL|A2e-apk(zRS@6{I+H5|9U&FYkHTZJNzE{*N3k*`&LIbb?aI1vlUxp ztbT`09otUxFuag-^TWMS>uEdT6LdOT_eR{=wrcMH)Rw_rH}K63&!*IO-{AKw>Wkc6 z`ZVXF{>|Qf!wIXvNT^sfJ6pS_wxQ1GCv1LQrro?LgN{|@Skrp!Q(EX^+EHsnyS1y{ zoHVN8jjw6@wP&@gPn}>G57vLPeDzb|QgAf~_+fjSyK?Ux?Ze^s^5v2bJ@5IeKfFJN z-}q|tZ$ET_O*I({)_f-2`hwZKNrD^GZY|ttx0aaK!4MYU=@0I9I?GIJuEL#JcaxFp z`O;{1-!m4uhj-3`o1{6LEpP40z1F=nBnh%S?N&llS`9Q?n}9Y$ZG6 zo>2zjW)AbyXk>00O~HCV71EzkGAYCrdy;e~LW8G%JJi z@XaIgd0IKVkk6I%v=-$&&99iEn-gXkM}Z+8=vdjnkP=`>A^!|?EMnaa!$%MEj?!0o zLU`&^^fSCHW)825ox^K_ub>yy@cYEiW7FbG;>H#2A|j!5J9$2y&~V05U#i4u!^sQq zLG&r3hk7&@f3>QDNJ7HyOnoDfr?8~=)L%%u60b@l@~hHJDN9-@Y?aL5XkJ3cQm~wH zwcsDufgjiDO?VBbxZ}(l95-76M**{F@G3q+ecvOu#Yt=|u^%P_xtt z0`)vp!(+gZRtZ>?X!KCC(0b@@+~^@1@S`N|ZmL))PpK2tBwv&~PI`dsmKvW$i?UJi zQZ)je7?IB;_XCCc<)-B8@I(6L->drN%L=66;-Sllb+QYBZn$16;&SFX*zg5GFlLgs z9(A+tAf-o(bkfozrzzs-wU|1ns!DQY)B^|Y3GGEx&6qhE;7Ma&krLJ5Y+=`2p_);+|mTDNlXkg{O8d4 zE(0}k;iKgON78{JMDoDKy&Lx$7;=>Ei@iv$;g3a5aaF+~sI~p{Mh~H5y*S_?6sLAS z&AIHEhiWkeFWu~Zh`DdV#_$n2n-$wrXweLC62GU|Sp+pY4Q@#*{$|~hw=dsK_1`l- z_TPa2!O+|M$G1BCSatFJx&8YFw=aZr&DL(IFB^~a0BnZq(eCH%3j(Ww+wd&&!|9O) z=+5mVS|o_ckwpmwo>6Y>zoQW8%6X>}7*Yoep#eiw(8Y)~dc<7=FJyRU&e~v_-o3RO zGLD&F8c0^dmc>fo2Uj;?GeiI9>NLQR67%t`nH>W#qzf1#F%)l{1BRUYBZgeje|-1T z*3WO3bw9r~Z{B-Xw0Zb_zjo}yb#32jKa#UWn~gvbt$_gyNdbm@x5Ed9w8Bfzw0GNC zcA@LyUY3V%d+lwuECNIFpkomj!tlK`^m;IFyPoQ+948^rz5cu)HF{PE^>zh6G=A88_WMJDiSB-6(rTU`HDWJ&dPxBj^j}O*wEoXlE zZmAC(WPK&)-QRxr_)fmu@V@N*oewhy%2w5S&-=E`t>vfs^_4$h2=p&=xo+KZ-%w{Y zL638qUszUbV{j~P!3QZb)w&CJ2<#v*WW+sbTyxFrJaOLICDbV6-o?E>Td}j<+UR*< zxv`&XQ3Zk9aHkf4AewK6(ty&TE5^@RFtfA{q&>sD{NxQ&w>K;n)OVIEL1`m7;-_- zkwg-O)vd@P>3|^-U`Ve*s~W<@n}K=PYHpf7=pn*OX(j+_XoMzd2+T@zV!{{PeN7_CqCc&B$fZQY(cGnmN%u^&^Q^ zMU5|olS1ISIUyB{WL5nMdE_a$B%h!?5|5?^?iVE}pk@6o{yLtmGk#j&K_WR(vJ4EV zr$L1ypW)S!uCT@MLGDC!)P+bZc_2DUvXGjikI=5DqB^014&Yz;P(Y1jKa#wD<}9O} zNt`)0ZawYDLF({Y3|#-{oM<{+0UlBz9u8VXw}U4|Wg)ob;gYz4=mmk2+z?B}na^d` z@QHlHBrwDmTL#4N#a0Rljq)xKBv<%6wpp0Nt3*yN&O%e5(^>28hHg=?H{|VuF41RmLr0Jxu|*(2HJr>5x7l3} zrOUYI_MNb%`fu&p{2}9+fYxv(fV8H62pEE?fBy;;?Ud~+z8XxDC1wL2EyG7~F8UgQ zAve$|Q3XvwIn=C@$N*B5*N{~)lI57w3WE=*+I^_RUJr$EFT24*_)y@T@Ir3wcKLct zYWHDNHMmlR03T_`Iz492(3hDc2Ex--Y^7|^ zZ=Bk0T}#=10WIq{%ZvK&mJD0>-YtWJ%x)GgMYK!r&uVj5K0CJfR5|wQeSsv~MZKv(89H;F?K-+3>1>U6>@P%k*sC1_L`}csGr?!_p_YZU|nd7D5 z8{6aU1+!}BunFD~z9TqO_0~fir_3?3NpXI)ndWTUd;mS`v^`5#Vf!tzW#hWOQU-S)cNEg<2={JdM?c%<%c$c59uLgcaTi9cv5T`46UY55etJJH4J&@Q}itHQ-98 zlV+AVPFS(B?|oylJL{}|V2F0V(sDakX_*ecIN%>7k_B4Ugl!>maZet8vv)7>2As8Z zuhu<*W>=kc6dCL3T_T6}rLkjsQGWwD`_gc1T>uVZ+F4mmH}|cMT9wFTokk|B z7+TiIrr*=0XZTj}E~cAHF~O#EB<^t%U#v=gCY{0BJe*9dv<6J;i;1O)d8#bMVxk=? zzDJs+D3I9W`Qk6&g|v%Kif#!;MU!v_BpjC1&wM2-Vju+!&d~`D(n&J_KhVLT4=J;l zy^0~$lw5+`?c*5mih=@$gy9mlBt=Ok`84q!V76pev#3u z_~&$7N=MqM( znhJ4_I#YTB$z1|H#-W0l6D}pyCKMu%MP$mBG;R25#?L%OYEn5Kou?4WYZD42XOY}~ zB|8Hq)WXt;km41brsVJ{VlvnylA2M8b7cV2$4N{deNaI&Dabux9=U$tqD{n%Gv85; zBN47(`I(K(`^d}{@}JOd37%0Z54s;OqJ83Ycppp9vJQv%*fpZ($oQrsl}IA_Vq3{a zV(ZaHD#vGgj&u7!m&_L|LAjb141mK92#G&WihN+QO}siZZ+;X)U5g|9koD*<+=kF= zR)_y6o#AUGXCYG`ajAm+uBX0!paan}Lni|calu{&_y>U>=k|@*WoQUIaT1+0v-`Mr z9$1hGhqWDjh^^hpz;`>Lz$N%0E@)X9`VN1F{^h;|9OUtKDbxsqcL5j@1cnp?t9m`B z{P*`)14J$h+zS5Z9BBc`mCKQ12OR%)5Obmb39ZHZin`{?q{ti=(u6G?$)8Z5#C!$X z*%bN}nfCnM7nZHvA52bj4$@BN4B0lr7F2Vr%YNOPz2o@)C-9Oky~uLI@B?zcKX2Q% zhv2NA)#ey8KiRfVd`#KiTFx^ZexCs!WLc+Ln%y)lt!Zy9f2Mtb4#q=tG3GbgHjg8j z)v*oT3w}th<@_$3Wb2!~tM;q*&sl(gNA5A){>l{4U|oyYw1s&CVz|$fz}F(^y|QaxH7FbaT1K2Os^gsTx@_g|}uW z3#~w+ztZWvgIw>NwH2=v^EUjAYVW$`l>ddr6eNKa)>)h3qiaaYJrc>dCysX8q0=}* zSk)!y{SiF&rXw32lw8YYv(Ll<6ZsPDkW<@PIx_ND9owD*9jn2#vN~p-SmoPV*RI+< zpPo2{TC|XK3g}o2-(k}g-+-kQI_hh$&^3;}W1CyDf7bOF`N7q|1IKOP$4pS?7!DHI zEHg0Vimxfq1PsvwLndNEflk^CFvJ(?;NFQGi9Jawf$B{7AS5`7&B6#h9n)%AY!=e* zv&grs(c2isG{RHAh*iIxG#Ji}-V5A`JoR0UnB8LZ3r~`|?GwNdE;$ISSO+86VNoQcNCF=}&^O1m7Xhs$0M9jh4qkYd_3r2S>o zPFfM;D3X~3hKvJ4y4dfeHg0VKGEj-=p(PP{UZP*vmT)_HNI9vNs7@jM`gJ0)D&0_> zm6xJpHDgMCl%A!Sr&R+#7>R4Z5Hka= zJgZl+$o?ch#eD#WHz>Z%&lQ$oBJDwjiO66biS32|F#-&!#jML^UgEZ}Qn~jzsoWa= zFHkwc_&?8qAx`j+3AF8WSlbC_AebP>Nrxo)yiM+no1MNNm;zLuG@g)Zs^6UURtnkuD4}A+vNsFIO znU5#5u5(D=niJ;ZkIB;F(VBy&eo=CUH!31LU!DldjWAXcLl1*J9?-DO!OuM>L9b)LdXl zJ86V*k=zR%%g-!g_?ZlL7E;)1oW-5A%TQ-EqKTLa@p?2UE{P;cS7L}BO**bCIlRW2U_U(-7&qiiqeWb&DK~*3 z$~}Gfgq85-UwgNpzuelrxHr8kvXqz)Bh&ly_6cbH6~K@JcMe_6^d<`HeSg zL<78SQ?Ye?>(cfOLm8%Nr?~{Zxo^-2x#?h`N5irkyshZfd;=9{-EuE*+1!NO7|~-{ zJm?d4Y=7puw0+h7GZ?@mdMq4M4f^J%wg=G9?mNJcWnjo}fgxy~Bb%jj{Iu1Lp527) zDX?!*&oOoBUm6Ts<+^1ZW#g;OATZ=#R=Ph;FMqSzgEmOR`@v-&bgYsO-z{lA)+}MN zU#j2u4KO4Z+1{1aNuzGvXKDneA;T~F!F0l^LH{z-zHXU!5^W2bS>g4WhVU5;d54il z9)_mHG4;FT@InZ`#9~$M5q}c?27w{C^8gaob=K)H--bJR*w&-Qy%$lBeF2?`{4fP8 zyABv~49$&9yUy_qnv9heC)BJSI3laYQvJ_pcy_=`7Z|U8Qtc?$mQ3HQE*xOU!?n}) zDr9@7w6*S`t#OZS`?dExnyYi@79IotErN>m8WW5b7;+|158m+#*zo{+nzIIZzfged zX#$1_fFS~4NKODb$v`p96kNu7PUecEGkku`v;gh;I4ba?A8AUII0fSeiB&?ODP&-erE(TP(~V2E-L9cX(wQwJ|Z?S9P~ z@(!_1`TgJ^vv5Su;(EVHy-0pUeg+JAaDX9%ewB`X;hhMT_6LM{MU_$0fFVRqGXmGT z7k%@EB$jgmMBy z=0xwLw*-ljCZ0wlK?}VF9IBD|7@w?!<7X0mN!&#M2w2D`+9QM?QphLLz*VwrsDQ6z zpTI(j5^g}p8b+fdJr#{jJnOaeR$vH`=_QG>fgvPd$YtnQJsJ&ktZU+zsxPD&&=Z#x zBS0X7vKE=^BKRR~=!Ni*>Fq@(OQF06mF^|ezf}9I5O^X^PoGhUN$eC?9v)|&3FxS9-yH5tv4negr22_F zUevnLjdFYJjc_`Sdy#JXqYom1d+GMo!*MTiE6`J}buZ$lQ+}5HOrYJW3epZRL_63a`ED47&<^t>FS?AJs2?rG*GSg5eP;J#|03@3k%KFp6FP}DS1WlM zI`OTroz@Uqqpt=cbnQN}BL^750*2I)3ZRtKSugoT=wGCpb&hGUfh{!T?wAXADlI9% zkS@&iME{|B>mvFV7x##DnDCHOpc@I$Q=ddjU4^M$wetrI5g08W$=hRao_)w+vELIM z_2J&%#;?bvSJvjH_$jXroGO>ouk8=5T{INsx1#=T`|S| zDYhuipSG$TSM-^7f}`Z2e|XMd+HnCx7;q8Bb;anZUtFWWS2wPue`xE~2mSgPPC^H@3q9Jo2vJ9fP&$kU&WR6P9b=Mtp zmS=kRIJ)UWUJiK3Ftn`Uom%LG^T=u)b2!a!_V|Bnvw!A#VNoD8@DiSSAuuFA{Eh8S zq;)SB`~y8G2j*%dY&)Iz0>#LK--Mq=U`V!Y1;I9$2`8v;`2{wq3RUrLlDl z39(h5i4P3e^WD>pA9AL;}{}taQ|Czug+~wwHe|F40++AlTSkAl^KF4W}hIxH4zF5pE zaZhMta9m5EcH|3=vxfP{Sshqu7~lbIXltb5UO+~8nAtIhad#cC^it9k z8f>2{1>hi;l1|}18owmHR(2$r6V9oG@*(B7a0OV>DzwEbu%%;y3}bCS$7z-LncdPN z1{Ge&S@{#%NWwh2h1cPOw4oQ`XO1hn*dqz4Tzh;I&m$e?Ujz0mz*jv29`YDniY_QW ze%ySEvLEV5N-Fw^4BU`lO>{Z>{fY)fdBlqL%0k8xx*$xZ4W z%{1o^@B#-D_ z6B*=tJis7kdbgBFNfje4ML6E6Nk`#eK$}Z_ z0K~cq3`vwsq|YIfwJ!XdL%BjRbgcZ8Twx7bJC~BKp%s%URVaym4toCyE$}_=oZBOB*L zJMNTFN2-ik;1aLI+zPWOdaRI6q%cNFQ*!cTVJy~<*ft7TiDbN%+VGTjc3I&Fd z-~D;}vH!l|B|3&(Xot0U@556TU>)U{%l8G51%f)xii^3&FetxKP6y8)lQW4i>u!!d=>aVy+uIlz#6#%G^8w)ybY7e4&7 zb$&TZSN48Nd-#1l?iuo6J^jP6bkrrJH|jUkxDQqJ);R-tJ7hd;y0wce5`5-S ztMH(;-G??rw(V!!%^|zYks+xH9k`1D*Y_T1c%M-kcr!8V%Xs@6Rr){@vdP4^8|u!W=aA z5I@1*=~nx}nR6&$i^DZjyYOU2_E^Q_A@?oaLO-V75BO$~&i5tULabyClew3)I6@p1 zb4AW)vqtxE)SI7aGU|6eJes?;*5VMl0ko`za~lab`x+pQ^te))5E^|N@^ap~z?EY@ zk@U{~_a~>*(clzoE1GuEewaO0_3*>@kA}?C6+^FQb`5uo5N$kAD+Prw-MjAaOtC z1fC0B4Dync;~Eg!we7obTXe1IL`*riTDgP#Bc|P#KoF+J0Z1B)JhcaKrRJKG@waA1 z#E}e<4a9HUHj>aHoX1;B0<5MgH9RPF*0u(9N)%+Js2NRj)G2LBRD>>eTcy4#ayaz zS#*uLot*Z?*qBcrLO3}R_g&(VxSL6x+(EcfC;#?IRdRbw2iXuU8Z!6UDnyiK-qcs4 zl(>1zqHk=^;`Uq>c`P==)cL6-FaEOi92yc%lJ9r|*YEhA$O&IYMpEIiIH=knT=k$K z-=iT9BINnE4zpv79e+~L0sTIv50n!3YnSFcZ|q8bB$Aq3-qNlMH(tYY@FdhuW<0~> zj7(r3RR!CmF!Cw6^Vdv3|HTFWXkttF(Zok#-Jktxyql0_RO83`BJQ{`H8w6RJ?6Xc zvS=;0C{<+Ewhgm*cWcbOu)gG1CY8DI4!WNj`yx7?sqvfbn(e`8NCKDpLB;|$Pg~d7 zyEO*wh0F5T=L}5dHrC?zz~||h#i_c>PpDbTJRz@eDSpiq+pX9WaM*t&z!(s#iGuz$ zrppbu7Lu*zLhpYGw_!zCw;}`$2?=N4Og^27X)Q7I)>{7-Jcc3XHki`bMP(`r^tGx= zc3`ReZ0rwFGII|5SSOdr`gd~&8RTu^bi^>*&@o*#UTH;GO~7-~xWi=W4+ULWF9}%t zkg056k<^so!vMOF;t>LthYWh={B550QA?zneZDL^cah8FLUz0N@lWYJn&lGm!LJ;g#(-74$0N|~Iu@>Pyqq8Y5W-|6Pczrd z>G|sqHRGS$us}fkgx4R z+4y$&#>DZ#RqOab*1KhRQ~m#U*>ZBAi5&RN(X&G)G$i|NHX4!w%g{}_X)gr zT#5VG1#RFws#}_!8(VoacZ@yO@CVmS>xbvlkGY3Lto*<;xz%oTb9r&5k_5S9ze3J# z4OiBf4UZZca*D54?*9YU!{kHyoSF%bvvH!xDLKF~-~4nCuh~ZpVYg$QdDru{=WX(6 z(dg{ZEX2yji2?f+1qGH z#EOCoScpRh8*A_SO(;3mg^6|M7T8&xY?K_Z4{t2zF^628&vt#uHRa@V95fm=zJ?3r zC4YE-*%I^qG&4x|(9Ws2p_9{J4!L|9VjuZwzVK}xIhh{Um@Rhk0&gCQxg2hDIX)(& zqziEqx+~F;RCMDaTQl*azgn$=?b*if$zo6XiqI-AemH@K#QQYFq|(7U;rDq^g`!e# z$C;P_RU#J0PBRzRI_(!E&su}a*m2~bA-NDo-tecmn{D*Tb+|*TA%1mBU%^-uT0#XI z)uVnP4sIoLH_;HQDih8LJRnUjY?D=wKCpddMsv-ZubJRaSezmOHeM6yFwu}%g~$Kd z<{R$nMd$}9u+6LNDo&F;e4xxDg&eE7?9-4t!M6j|q=Jv~1}NmK4LF8TJlSsWetsgR zaeFD3kcb!qyg~yTqj;WFH_(ujs18$MWDyF2^EGrk4p)eTK0lV083ek9S`}I7(-1P! z>X~h%QSf5rL?w}1Z`ZY%Q_-t4A%EHW#g-7ChR9x{GbA;57?pSo@uZYp&>@&7r~dsu zzsiL{o;-`(rVTs=pTay;gyou>$c&exY9~-RUEFZ&@dQXy;oq zf5c>CjdoM5{WtI38uB2TCYdkrtkyyG}d z>tBuWpHze$+TIf09Q7A2DXPd(v!n+HqS_!eiVg-xJ^o|>f0;~+hwus9;KwJ|lCo$O zGs|H3&$tk@ThE02YE0LCL3XoL$FGdW<9}IoRr#G?3u(D(=8qP?r)#H`PuG&skPL3> z3A~p$)gFKGg4f^zDZvfWgCEPPg8SwFs}WZ^8q!S$q$2Di=j6xGSBDwjOq9Oap+tdX7 zm79HPaJQmND|d&7-Vnc!eA%&;x}_Y*iiNm!597xovEY%k1Dhc(7&cfZHg*YhOB?P1 z{;4kB1$VoZtHNeHjSJ^XG$e5*mE2ze{@xQSaFLdUnWnux0A~%3$SzI6YQCm-*{NRjc>FiqA%6b6P>L(d1&20Qy-|B?D8Y_OnDF?68s+hIqaxPG(Wj-w&n zLu|$@W409Q9s3SQzROd*^TR4`;8^2cm?6HcR z2WJM!AXdzgMkgT>HoM!MhK4+YWAX^+z@X!yH`&?0c6s3>`oZuci)Kn)v6N> zxeSf-^n4Y+Qd;LPQ{DVnTr;P}uT1BTU6^Vb)lIbwADKEkv^-@QRL^`lbZPcmZt6Ej zf12Mt#^qzY!R452bKjpX=dELAvDTL>tjCWPyLgY^JMv$UeEKop{P8gt+rE;IzL3UQ zPG9L=Zbd`x`{odQzy43zj=DDLwBPwD^&bCLk`7ZijV_=cLJsTA^OV=%;u`FNFZ>AU|;tXI5(L0PpVgJVkTO_1h96 zYk7(u4Nu-G9NJ<2=Lq+hR~YGYFT3 zV5>>DMZ5JD8X~E(BU_%b*|fPRwn6y1D;YIW< z(&)13k@xg2T(!=~1EekPz$KPBWI685*5^CECzqa$e(WK;7`ttm?A{r2mIeIiC8aMK zl83vunl0YzQ2FY=;CWc7Z^whx8 z^fYSkVrJS83*dZETR>ZYRaJzyyn<`U!Hv_(W%T0$zYm{6?vP#9U0fN_$_BNS921!y zoP3hmDO4|Ssa3tkGZhEH=c9()C1ZGfgOtbmgy9O5xclq-Lk?~jf{$+=(09Y+dZ`@K zS(!sNe81y#TOd6ee$z`PJtX`gSE7fcFJquFJ7D&v>srF2w7;Su{oMU4^+!oKiqrVQ znnH0Anq2$J;_?mXCNGvcxLe|8!hw6rH-l88A3u>XG(oj^I#)pAXvPeX3{XDyyp6uOqz`XGIup4T%W;m`MZ zQ}<4D!^V%*PUb;w=NfcF=8qeEO7Un_PV%+kqxu0Kly2$=wnzsiafqQI{|RNoYLTpZ z+4$^`Y<+~km}j~E$Klbi!IvY7A?;}Ou!lGP2JRtxYYLf?8xudgf5kDE7QB( zPhf;y@?8fmes~7QMI5hB&=9hNtIJbK%fg&`_`Y`G3}mVw-oIvI+R0s6cq!>N@fs`8 zJmFUfiBZe`s>pfIb{O~8%Xx#n#UWI!RNmbyCp+J#+jdNrjhBsQz1tY;uyk0WM_!Ix z8r(H(9{7@kx@GX6eQ`s|zS*H2ebFPSZ<)1e$oDrOU^G- zgN~Ea9Z;+`ctYRyTtPqf^6BSZ^~+Q}9^c*kT3u!avCng(zeYTX*=K3d5t%;*p?FvGu37DO7^+G(36U<175-7I<_^31`-W7W%g=c(ALVVe1J>&d z$pd+hl{eZuu}Ry9xzHYqZRT=N52IY3UR6*RYfTkgmG{DcHv|`LJCU2F*Lk~V%VNDIkrpKI%=UR|xh<%fEEVdB+ zFz)>A6K67nbTs5INsI6nzuA5+K56^IPm_4cezRRBkt`^ORS?v_H%azm>eDw{`^o0t z-;ofh<_4mU-!NYxiFhk62cJ_h&uF<}i-th1hu!Xl?sD9yh5{*H`#mP8Q}AQzxfqCk zY>?!%h7% zK5^b#lvVswJuudK)a|qdT!U|NACA>=5>IE*knXTqn2OIfe>IZa#)q}nkhn30eq3Y* zsnm1d(;fA@r_=f?^(ysmXh;nAuBTgigMJJ>p#LiD7%scv;AD1e9$X#bF#pTe-k`zo zfk1JgT-ajb?%(29kM|hP+&W}=Z|jH0tMEAZQ+NaKOA@|*NpaY?h1iy&Nf62mVuian z;*MGx@Z@+JxvIzeb_piD`Jy2eKCcw?D{|ns_oI!?f0_CbjT-=J9G8^G6zPSMuxLbG|u5$NfsfSne=~uS|QGK~AF_A&yy6BtP*z?oqQf zZpB=)ho9+X$n>jCB>SY(qm5t5F7%_=)9Eetw0`KK8Ks2Q05($G)N+|VGf>gf@XH>} zb-g=2+hMsrb9F>L(>;_pV;Q_YqZm3qJ3E9^dW00?Te~kSGiJ+Xn`XoZ*5+<JZfCByPz>C3$*yWe%+=t(5Dj*RMxSvq&Ia2e6Os`0{W2*(kV}Cmuv_3-@i= zQ|Q@{{KTo~{rH#mb3Zu7w*4PmN&bw6@U*g0YnTh`=c+mtve(c`&hUWV zz}KVXS$qPG6?$(eA8}{3vJbkzP2tDThar%eNcY(z5@_Fu-6jb^UEDX@FD3jV=JcNq zeDeM0m7mBCYY%>`o@C`GwV#*9JowY%_OCu`+WxV`5>A(oJBpTpTZ7_QlV7222Gmw-=8co|W_3=)#) zWd`{!+>$UC_G3IdyErl>v5&&qV?wx64Tcxvh07=1K8l8mhGgOHm9I$@KZzezI@5=e zxv_vbD;|bpI&Oiw=sf%ngW-9mG$WqdEoOs5FSA|>IgeUZTL6tU>euLndEJA4H1UC7 z!X69fqbh{=k~gpyM*<4z2`N-RMhCvwa@n`Xx@!~$)*v*vZn*A3V@TUnhd$Z3u;n@( zBC^9ezNuu7Rf-0b>JIzI^Y2P#^dWBYD+cj~3GK6z<{AUqVSv(Esn3J5R@7vH1r%STVzwk6jgz0Bt6Vd{o zu@5hoY|pzE;}m3%{Tr97)c1IUi$_1=SNCb#SDH!r%4UE(t>>yTpWY`Y9-P_*4ILd$ z*iD(F`{XTIN7m0l>Y^0Q=tVyo`J|KNQ}Xu=c42xv!w+)s9ws zwreNP+`VuW)p)SZp&i#}bWqSA;!h*nhL+0nS$^Eo1|m7gTd0_P1^4mvo|}ollXZUL z?A&?grrrs!guuam9z=dv)YMU(x8$n+`Z9yHUk1^yU<)68FT8$pe$->5KMm6xD;K zmGE_?@b$_3QN{;n9($~1_E;yU{mC2_IiIk{vdli3JIXf8j)rtV_!oljZI5J5wM>K^ zTq1TeP1Y|yWTJfNUG>Cx2Rw!kGd}07FmZC&hnBE0tA!+QXVxOQ4)@VBdTF+07-t(d zkY|I(XXAzxb4R(S7mY#@8CTdo{ctGC$|H4`DHOn7z19ds??_-Tkm!^`S&@C{P8$5f4uR{AT8WN zlATrRY!^EP81Lul3OV+nja+&O?06@BtZd)=WHJg;#2k{0QaoVG?e*J*dK^VX__NMb zGK*Vl&@Y!8X>ni`Ekrr!+dZCdM`)H%gl&&#+G;YzaxvDx-YEDa7-w%W9@yLPVT+>- zu`@ic<*8M}Td*_I%iA;zB-0sYic(#zDN5TxHa#ISm(D3Cw~$;kBulelz8%OVI*8OX zcaV=Wh>Er$nO`#LEP2EZ&Baz++Ce(ml3(NvSv+e>y%3o;coxTM48b$Za&l9E?ZnLT z2r}1$2oLo6V7`_Eq&v(z${;S{SV}q(qvD2c+&LDrXJ>m%VR9*~-t?Hk#KrB$6MDDb zife>(+spGdJ!Zy7S}))hl8T1JpdX!dZC#*uY8n2Pc}F>Uy<)N!tNiHy9PQxNApg#v zqjPz7XP9bD;%a^kD{v|#kp_`lTg1aFt|McBA-GCy2&R*WC#Jl0>)=`nF_OeKIf$EE z&xYl1+ZDdZZDT(@AU5t(V{33S7sD8yD7Y zt(C6Ckj!$dCUBJ$uD4_gt09tj(Ge{~#mmefxy&Hqib-PQCEk3;sy|*zVN7@-?46yf zTy?=2ygzN@-CORm8}5fb{m6#Hl+A9vhpgI$JC2m|AbiFqxD-{c#Eq=QU2K{?{*dMa z8X?rBuk6#Nuas#Qmz$vtN&8!es@PKv`+Wk!1W>7k0gnf_sFo?@G~C(f`7`t>RZh0R zqHW=RUX72pnj6Sb(%`csF}|d4DGffN8sgtAE&;=KwryOfZMaKk-kq>H-|pjD5X;+o z!dvff(dg0P%#mHgEyLdqd-Vk*5;P(Nt#*o8LA*Zvw87kw!>$)q()Z#@L%i}by(fy$rjf8S{98|~;0W{(rS zFGv2n_wZ;zpUEQk^zngQRI!W3s(oBhZcL=odXhNvil3=gdO@WBsuK^dczKWTxkW3- zFYMixr<^{viIBj;W{*0g#jAyRFWGr%7Dv+Dh9v&nQU?G~_(Kv=}ykuV)fmr{^2!uyMjpFuD&d8_^3Ry5V#^X8sU1Xgiv+h}ZYf za*D5=v2H11#kS;N4uNd4dX6c(TOn<5U-@Vv<>W8+vCD4^I?v1@TmDLY0sm9#Pv}Z@ zWw=#G0_wC)Jj>%?+FigQ*uGMz&hd1p3SGihG^wf`8LHX2JXHuYNc4OHdn}wzZm0UT zXHS5*2xGi^0`B8ZhaE(6u!GB}i8?Q@dQAYAlK_XljVFn;If*~ElDl+28qythHNtAj zpdG`Bqbh%!8n4x^#JcDvxLDa_(v3S8w>y&~Vyv(hli6dr`5p~XMSkJyFWew+7>s@l(2sbEtc5tza3yaqCmG(uC8CIw`lGPZ zK37&NJE~rsPQ_@2c%`#RL8$mtsn^kvYwR(PY&igR_$gbY3MQ!UO%{?ZLi!2`@1)r( zk#>?_6TV}AwTn*(-|r4P=bJ&u{l|9ydj@G?2DuuMMeA0Z`Fm3h8X}vlF5lL%IVy?G zTq$Xg0=#Apl7$X*^#C1*y)?ea4ZerQtE;#lp5UUEt5X@Sg({ez(J*<_=-z~(4zH6O zxmTq=vep5mvxEIs2U#P2Kazo!iRFYYPsPRw#^PcB*P#!TzZwVq6!?1Y8qX?T!(6;? zIKD~NXR{UkD9~R*KjIXo67*e7E#fnhf>qv1A`Do|sy0A<51=A6gua)i3cSxoDXi=iPgmXVC@TzjyNd zyfD?VAWsl^qjb%1)gYnfrBk-aIYqN6#8S7{PtTvwD7FB$CDDyC-1TKz4erRgrJ4W~ z!+&P6Lb2iQUo-N&@P`GNvCsseDsIj`zjyj7m+H)U>B4}0eqrjKZ9n;t%E_5`H+ZYhA@{v|tY|a?|J6?;(M%uSVNsI7VLKY) z8BKVb@OB1T?g_5ax~YGSLk6=ov2n_WCv~3A##gIHVA}Uj?OZdXA!hVMxM9mvHs0%F z7yI!xNw<@E`rM3s1$ch#*YR1Ukr%g+9mJ>1&1Q_R*)cij7`6|hAKyBTu%A0JB^@OP zXGHaO;3u|0XUT$@m1p^z_2}@%*#G;Z;q6p^t>smpXY{{&Z;rZuZyG((YqI>aufx*W zFKvcxW0@l}?{<$x(Tsa&;>`OK{8tY!=@{&Vu2%MpF>pq~meE^$MddhhQ#!Oz;vyQ}jZmTE`l$n4zNp+|F<1}peH;`<@|y%tA@<@Ef_ zJ1w3xxu0Y(_XtHM0d`xVtC{}d8~lH5U!ipj*Oz#E$5wXxdWFyPUQGs9rG4yV zZD`pdEy<5*LheLE&e0^Wi}~L~{$eCR_@*zk>AJ=sVRih*1R~!?kaUo=uQ6bs_N6i> zFnp7X2`N{($mK6)!*^Gy`RY|i9UW+hm2VUHYuRHpsN2wxG8z;PtQv8pOIJyiIu2c^ z_RB*>@LO6W+)nvUdAFZ8N(dwtEwsqcY%` zR3+P@oykettCGLjo&hK8^q+2QS1_aO-OEIS}hdD(0eubB% zV+Q#GUdYF+-l^#I*gBrb(iC_*N~W_Mnp1MkS0nPE6<>|Gg`QRF!{LoRH{?UAZ%|K# zbkGneErHTAoTbr`8hN_KLHo&ln1bJNgQp$D_-EP>D?d3Kc7(1%Ve$T&@F?u7&mM&- zK0{@=gWQL$*!gJ%Gl-;hjxvLI!@IVQOT>B+5R;LDp;333{pePN8{-sem{?CC`|J-n4wvN8COm(7 zqaMNHs-}lfE`uKb$1obtz+(QZp-XeuI1nfqkRqs?9W-4QMxGDK=66vZ)9h8;;TB^X zw~z#NsgB()eFYDcXPH7=G>+e2rz?Ud^nP!X^CFw6$E(#mH_D|KKA9?WnIu_2&^uT^Cr-wo= zERWHU{xxyh=eo82+)LHPw1!4O7&^7|LR+>(m&%epaKPi_4KSJi``yhp_d~BZeG*(H zG+4Zx_rQX^>AVH$t}@wb#)FqPNzKmyE%u!nx~V;y66&MK^X0zZ?l+i#5o-;qYj!Mega%)(r^dDON5V zxTxH867HDHA@h>HvSzCb$TyMNw)AF-1jJaPP_SeIP6@;2YiyYspoVs z9b^)4G0D&`LY9T@61)I%_pgQqFIRWb59w=?%@_9-xW0GnN2c2Oiub_`E@2vy>&=%# zVvi7F%?G7}<{rhE0@9(vi|L(O~9DRjvrIqp0VC=iYqbb%b^7W;p6E8ptL+Ncr z=%3SNpl|YyeE`P0a7}J|gkO1tuXmYWv-{lAud^k5HhY$KDoDEQu{^k#3tio?+@t4< zJ$*1os-WCtEwXcD2P)b@M+#}5v6U$1fP4rB&{{lRM;C7H651`ZU56%wBdK-vjgrh9 zZgD7vzI7ZQJnH!S;J1$Mp+SdzBy+yuE#zDH#g&sy5Nt9Qy5}x0m=-z~M%@0cC|aj_ z`SovK$#yAWoQ$C*C(w`^c*I|@W!=Z6Zk6;QbQCnC^+Wo~Q&{b;Hd9EZ;tvoIV!p* zxh(p#InAh{BmG#c#n>OGpqntx=fP5Z1|=OY);a#VKkh52 zmla`3_E<^mv2K&d&nMGa10kivEWTc6WIY-pS@optDez~CarPFYA1PdJBq>sZ=1Uu{ z2U7iM_I?%cXr9nK+s`$79TM3Ax|n+*Wz}h~tQW&MDA$}J>F^M@>eDqRRD&wte$?X+ za19O71esKi^*8j23{v1TU(Ec1L-$p^<%n9^{XKBKXdzj!la1Ivv zJJnvcv5yqXYiXP9D{9(im)8opSRZD?6g_{8$>Q;9H1kL@Y?E5LLrUD@`)y{ARgbT- zgj+~9o=Oilji2Ue8g#U;#Lba$gFXny_NWWPEMP69+?&r>Bn=;7`evk*^Uc| z@6LxN=W~b{A$~@;hihdG+hly|o;qzaq@iY-7jB~|DV}WpbY?1J7q8RSaEJRz*%G}H zu8xI4M;_@o+3Giux;TsvHW@#Y+@w!)Q((7dKg#pCg6Hrhi0}tz?sJEbT*mU$$Lq8K z4T(TQ2I#jf86%Bgxoo*Q;$f3@bF_Qtrq9vq9lmVo!_}MXyM_GcyRYp+{uKvRFHPU= zwte`{l&fFc*FLP-b!%td?_7KM{+d_vF*=&RkH9ayg^vC*1%r@O_!XFtYDkMuW**}+ zZpWWgis!f;A3y=g!>`eg{XYHJ<>;TPm@`8F>z{gpi|gdH1E|;qApYyT|@xV1$N|`geI2VGZvae@p}IeG-jw zHOg2}!tScXGiN|UF8XvNg?U8yl?~{=$0?bR*E1mw1w53a?6F?XODFo5a}99cv*<*= zP3u_QQVC?W6wis3GUkr+Xh$Pns3bhKP!gF`>qwE_=2yF%-wh>h$E5IE@)n$LZ#t7l z?eihSSLdG%X3o=74v{LMF=cWC212GqQUVh3ykE39{a zpN^EGA-(v^HT>7aEjYjwGQgC)kDm9W z=!7}DPkW4ua4L@8c(M=D0#{F3>oT{HZUs-rwQ}EmyxG$h(BR@$f%i%n?;(7SI;8h@ zPe31k&*;t#__)V1l4|NvpIk3gOGDu-zF^6PWO4 z%c@Y+1e|8}QPCxo!nPtny04FjE9hw#QpP-y^uV0o0PCpUEAgsnqlH1JWPA&syN zqoR#$@`~t|t)S2S3hh&pnLh-5s!QX7MW7D7th!<_srEuSHyfO4vmu2hK5?;jhVZ6N zdlnh^(EM%TMy@l)_1H_sy*uvGmkw_*;p4{@|Eux7kF6LNMhiTdLYhgUTf&8R6-zUE z4VRcUSngTSR#IqTFN4M3OO7y}-BoI=+^_0=%^|qbNM2e^Wzj|aJA*LI;@F}Iud?4a z>&ccVn&}{XGbw0!4RV6FIFM_jW{Sl1UVMee@%atXgWCZWM;g={(U4=oMgAAIyi`uW z;C&tXonJUJ2$V#Hv>V)_Cu)DlU1pGHo2BfqZZLx+1$~ExoL2Ut14{CRt-%c_tn??= zYj(k(rw#V0L6q;G3=Wn(x9TpI*i7Bu}+Zo6Mgk*~LCmbb9H;g(H{0 zcxAm~;pv*z*@=d%ap5@jfeSCwg1q(hvTe34t&$39L_?H#VB~U=N?(J4f9k-hG}gt< zy;w>kD{*e7#;seuGJLUK(qWyPddY8TKRN9}@oCU2pW_9+9;n*)|^K0}N>Ub6}qapX<`3jNo%CuxN|&@y|3)bdic1uwI(|*LV>L4iXBsL-2SXuR12rc)zyqTx&%~ zV&2Di&%Cds*CYwPq;$#F(v8)^9?J!@_LnKyOMT2jD#?A&a6WgyclZC+5rgyhW1H_R z6U&!;q}}`=9VO;z2c9hHaPN9IW~;T}Sf*F~|NW6-?S$&}&%U;?^xoXD8^7-!>-{~K zCit2$Ar2@0QU0!F7<%K|0qei;0}tZ`crw#SBUif33OjhiEpyakc)|wzZOf~JILObZy$%-*xnJVPs%9F|vNwcPMjLJ>9Ns>%Lm!!&=eb}^8LnT z-U4{%*hg1#4jy2k0q45MY=y2II4%iqJLDG@l-$hMb86|; z>J2R9NA{7$Fc58qRhTDNR z@kUuTyYN=Qk64M-%q(sMB)HlFw9dAG76*PAKJu-7ek1@&|G)iMB|frSCp!nZgUZf& z7crVon1!P<#UAS^9NNPZ3IhLr}?4&LPpdFqTn0FR;pJf1YcQ|>2FK*AGfqAiPVqS2$4Bu8Q7cZqw< zO-XI$&yusykgjdn$th8$&v|=?kEQsmj9j6OT%q`}B4P&EV{Mr6ZZU%-L=M9^Il~T1 z7{xlcw>o^ho!mgqe5#>e{ScFj%q>x*_2d>JPwxj2KjTL;ncxUU9MFFkehMnbNWgKU z3Z9Kgl^E*rpD=X5_%DG?uu6V2UZ2Pn;#}w><^Xbwp*d0EMtV++*FH@{FVc*z_%!;` zaW(M~F4l4!Oq5S8#sYR-H@1i1=M_r)gNO~Y^gA;lN z9of0orFp)drBT2)d7v}~G^nd^4qCZ|WT=MGkf*Q{Y5(e)8l+EqSf8C@kr77;n}8eY4As`&p63G*D`y^n`D#oH5zi9 zp4GUySdwC8?6C@%Zl12078DA9mzG|T9(9RZAIO`lHf!+^Ez~x?Z4SiC$)C~`N;bGk~o;Fd!ZZlZVg?3|Ser(=P%^FpdUZ=rJ1#Lf1m)4@E_O0Vogp2@$UAy=l4 zlR;FYA>xTzV=F|TqFu;SlC(&%#*($LT5tkEGx>Jd|82#P6%Fx!yKA@*(xQ_~NX=LZ zy!CcS<-bftjX%PPTf5LWq4%7yMXV?%Au>%qgb#LPE!95dy+a4^Fd6+AZtXwdNz>u! zm3~F@^mQ~u`mtKcsXV50>cC_L^G66bkZ;+!RdWN8ulK0q%5*pBKgHaAD3bm70M4L# z1*Gc#HEytvz)9~LJwH(~bZY$mK(UW5yrVDco%?s1oPHk}i|CC-Q?lL#(VUewm|}f7 z(rj%T+efngU*ozdNi#N1*ykf`RjyJSJ1F=8J0_daksMDdw~=A8^bh&jyoHDC`E)fJ z@(2xyb_zLL0)%c^d&T?3s&7Ki{N1#c!)l|&8MTUCY{qXcsigttqB`y}5i6Zg*q+lp zc$a3(qddXIO`pdNvH|C88woQ~tXzbJC}P0*SxV9&N}Ce6hiu^kWf$4Nj~S2ixH_^H zV>Pv8&}nngw7}=Bc4TRy=kql;omrYfSC*y=jS)YV;X`h~g|!}E8%P{`Eb;3OlKUW0 z!VTmqJFHCQGj>=m+91v`gB+wA_akv}PrxqrSWPgz+enKYqLCwpT!VBrByUZm<@*vl zmJ0Ut)%b-nBc?tT9Vyz{O8Rar#>D+P&$NeKylmGm84K~}_HYZ?L*H!)Pk>S85a}$0 zMN1wehpyW?{Jg?7;X2MOy@!9^$}RolE#&Ez>hR8xvrxW|(~NqK*19VOJsQG~j4k*D z5^VD6hYb5yLzYI3i^s*yM4p4)Vczh;@DlUOh{o+gr~jV5jf6;VBJ`2ZbJ37Cb0Ion z{k(o#5nUnwlUN)Tp0FAvxk3k7mv11w%PnLS4f&Z3T{c|FLf^J-imQ(fX1Wd^R_CWS z_E_JLto#O-tSvg7{GQ}OiXpswY>@vk)I;L?K}0%<>S8Xy#(-aqwJQ3`xi*VyDM9^X z=th7vDgDUTEqrrVXiK;&w2F+zpQFRk2mH*RxzJ4Qw>_$$)m&)3s+JJ-$!o${^Zuv#Bfhu)M?8;@)1Rm234X!n$&!q^ z0rI$;F5JJ7_t-rai}y8`+jGO)*`dO>XZchNHN34JcD^kdtsxbX1@T>Pbxbx}$EVAn zZzWA|2bfqzL)urkQqUU@?`v}B+T+PUC21=5&Y+I^Z>S8KLz(wVC2+^#~eaG%}Y0waQ zycU{9FE4yMoVO4+#JzklgKzVEe=UE%gzl|>jd$=)(18P^7EWXBLdHVgLYq4S)iR+W z(mEhb%z1EfYDqC=bG0`@O^`k+aSY0=T*_CG3MWgqwg$=b{`xbzd%k0%WG6A0p*n?^ zo;$LyLur_l;V=x^-(UzXLQoQJU4lAa(}JgWf^~@jS{pNtrp6ts$ymtJ{Df`{I?6R4 z`wF}GdtINWgqXwp|U0$M-7Ol4>7Vh;HVukJ3tH$DoMC|EZ%6WL*8@Wdv}H>c|U0lA!Qvm_HO?w%n%zAeufsqDf;rR zefpBUR`S+}7)h_c*_sU}YB2nJZUJn6@K}xM;4gABm%UXvvq!!Lr;vt(V&IG~Qz*Sw z!UN_Vls%Sc2q`|^--YoT6Y^EqV||)&kvqpKiGeK5GBe0iwBr_xv3|bYYv_T>)I~%2 zji5^;n$MD9kz{;kL^-#C_Lw(YZ;=SJK()I1C+H)eJ0m|!79FWE=Y76m{@*{9Z+rCF z%(jE^LbClcc?p}(0UJ!p1(z6e$T8m5rTOV0&VebOz+d3y)x}9CZqoLcPlZ=3wEH@; zh|=;Q1ofM2h8~3_;75D2HI;0>BpJ#zkHV_dim(jTU8A&VKnO8bLA0%BtA8UXjjvle z!;ky=f=kTDjkf4CqvI1-D4cpaL7t!;=g^UJp^1FVJD`7{Ex&zo+!zs~2xAjR^Lk5o zzHbg`BA?zt=KN<^jU~(=vCJUl%pkJA5=wt88X^sbRV44fXKwrp|NLM$1eLJ6#&F{q z+US$GIT`VeU4?*n3HQ{=^-|rG7mhAXvs1J@nb%kGcoYX#`*m?M+X#?`0Lg_E=r1!z z-1KP(bX`AnXt#pAB)uSap;I5{4sx6s!hYlSevrdqjiUMQ85*5RbnmNZ!YyA$jeBzj9VF24$p} zlwX7E+=d6{5l*M|pXP6|)0*;12SgPq;4GZHvc)>Td}`?)Ibg|mJw`(cR>@SaM3W?$ zWd>2ql`p2vI^l%ZE?j5+xIQD@MmKSU)-J@&?RJMaZu3-;29h&e?w>#iAN0#+kJZDQ zoY5Uc^BeC??xdhx_bDyyRvM=(LEXTvtI%~2lHWzttc;XngG-)eLKw07++JtiTge?* zdG6GrB^e8A_9pfqlA!bY-s#8L35kYWK|^j$4clYhOG=_~LNl=sW{4hoq1>HI##+#f z3MlVi@_9659PsCOJe8%KeH@PrnLblpP@s`6Mkb zGL@V{S>%bH2eVVU)K5<5a(QW>3njtT^VYhMJc>39Uq(AFFIWa^7ak3DEM$&w@qp7S z-CK>c1@C{K;CeFS?;3P;a819w5Q`6Qz^5S|+Rx-;LO;Z9p1{@K$$oRgo#UHBZ2b2j zMK2$t!Eoo=lvho&vSqV{O;(4>uB_K&s3biYPtr^n+2ZhQ4UQ!#7fWM^ka{;z8Q#a( z7xb^Sv72hc6$sPLy%FGH=J;v8ocW_mgL6!CnC^6cm!4@^A6T~3rBSZ(7U^vZIEfcC z98d38%poCY$Sz2Vvp#ya?69I~Fw8_h9+1j946{P=V)D<*TU`zeZyOAeQe7o^zAFYH z=PvtN0h^=JjHNO3wc;nehUc^s9f={oQ2B|_abI%NP2QRT;Uff6$8$q11lWYg6Wr6w zv@(Zm@FXaQX);45oz$B)IBGEnItZO4t!T(ph!a1Sm3w*$T!>9*?bvD=#R0-YUFMI&olsc!3oY~TCStJuB@l!+NJ!>y zJBEJjM*+n@Ck-dZ*wFohxiKpGW50_~F%RPE74O?By#p$BOh~m~YOrggfL4xnU8grf z_t1MI=*U`9(DU^&?X%6*z)auGtX$KjsiEz%fqc0%I5+vD|H><9$bCb%!fa3|zCc6P z@$&wrPh$o-&J1GN)cjjR4h3bh$2yE3E02G-khg(7%orO1ya8xTrWp;_HBBz_hg{O6 zHMwc4F<@6XZE0lS4Z(-mxSodWR*kQ)Tk+5vr&xqV+C_S=&~<&I&#B(fIL zY}jhitIO;&F8l7Q)x4j&xYGCI7#0no8DrrN@2xtbtH>#G?+M!~ zp5D4~#hlP|8z&$#aSJ&C+g>yT8lrd1b_N}}$V}3{QZQM^tvz;8Hf-Y5{Q;8K1@>qs z(U5~=J<{N^C}6L2&&6;@BwblNR5p{(a-G|A2T80c`i!p@}3qXJg*? zatm>jVvMk{1+wjSr`z(;kS=DCa&}rH?gKpK9g~;&dUi8YXa#eGmnua-Z( zg#4P1h=$ZIe2I3P9rAY_9Zqm1(^**cw!!r`D_t};sJ~qHFW~|EiSBmZG8Xa|FQXwD zkamO`?ZIho;`^5u_CarJrVlsg-x|`)9P)yt9RUc(!TTe96q+02!h< zP_nfNTrGP@3FJb?tzjN%3uxDoFUL_U8-l|$n%~`oq(#P~l&nXNr^d%H>2u01t6Xyx z{jkqN4p}G;#1g~tr~@@*wJTK__ecpUAwW`l`Fybe=@t`H3|nv2zlGVRdbbLO%`#1xJABCwOx`k-SUOZ0E4Q=d$ zqO`}21yC`T@o@>|Fgx-kw~(T(7fF^+pdpWZ8j=vXlvL=`k)@=cBmehLG%P0OnqS9{ znlIqgHQ-n3-5y1rTZ0}fZWq^*(d>Y_F1_PtNWpZHqMYIm(#aj<3>wnP3?fvmf8cmJ z#6Q0pmBAM2yKraZIj$N)c8bttM&ty37ruw>434+3H}s5&j#yRVOtl!JAkQA4Go*h@ z96gV5T;aLBZ?iD#T|z_BVDY|&>3$9!Neun#jtK+gdc&DdCk(%Rk{FsE^Ca{|^l@Wx zR9sj+dBj+H2(!4Uci{?aj*%M`q^on+py6{bxJEYvj&Kayc_r! zSq}1n*O(3DroOzT#jk%$x8leah2ji5tW-2a+Hw|y(zwt++kBz@IES1^L++s=soch; z!|)va=#S8l2WUv1sU8if$H_;Iid0dDDL3F{giLcCs;67t__ynXHt+Qlr~K=Uvx?yl z%s0!&H}e-?)A=BC2;J{&nTnkB&@S9s%XW3bu%L;JHU{3iVc2PS-z@ehS4UtIW#g;t z!}WW5UdW8)i(kS(mn3r-WF!~*5jI=I3}RvislpLjPa|p)^mysLCTj`#)ZbO)Uq!mt z$Sq{IPeWd4MN4{kyDM}#aZGCc$wkvhv2!hh9XLI)izap(8SFU3lO?WUD>rq2-uC5V zML%j6cKXs7?8?ZEsCg#KUg$Z#EJ^p#uxNiZS-=$HX0j+7-#u1rHCf8vogNuMH)`-P zNyBU#GfC{bqS5+y&bKMnRsL5A>F^|k-CexLTNggQzxUFS{%Up1H;0IRtbI^UzFJjI zMyyEhZRI4}zR5n^MKWomk&-`&D@#ZxXZWj__X@J}-T02b_36h+F4&3C)vwPS^*y=O z%puF{y@ZplBvF5AywzSZMnZ2CvOOAdWdL4ef7EzEU$gbUdz;aZ0qc&wsPWb}r^f$d zz&0-bOy6kZME$!6TdD1YZ6|ccyELd@#r@o|aL<K! zZ||bJFm^GFT*wdadtB~`Y*!bz5Ub^*hI~8RvCxfv>>AE?ogS%jWmuf_O&Q()8qal0 za&s>fva{Up4?7!N4XB7T5e)GDP`8x9U0w7;ny5rq{Fy;MYKZKzqL_>h@jEo07WMt! zBWn}uiS%MxHic1DL(>9X;@lurYKOjxbb~PJ_HY-;r8RI5mvp&PI$F_o{;I=eJY&J<>l;=mF2?Z8zy?-46GwD6FWfD5eN5bsP6*$^w4 z4-=kUA-li)_YNylIkA3PdF9_4BHW|9XbAaPjR`MU7Sm8K9jy<^E8jJC@-#FXM$wQL z(JtP9f93fW=kt}`VIHZZ754`Dgp{aip524tnRsf`j4#y@iT?M`L!66XpqeboIoq~(lal< z*r%kR<=ef6D&9!*X(7_E$D;Q;OwAr^JOBMXw8$Rf87Xv<#CXw=4%5{?SxrSrs>qv3 z36XYYlK)98jI_`k^?b)3^EL7#2WZzw;u0cl8d)SIOMFh{f=^1!sU#_fxkjJ)R9dOZ zxTVV;>yVE!SwQ0A1cWdf9OK(jS~f%CB`_N@Oa?k_@vX9z*%jWPt_aIh#rZTO1N|uE zNtmyR3)9fVm&(`u+)zO_<7}9f?%b3p1$mEO;kc(6J3pm2g}GxnPW0ox;o+xO498-x z7{+3r8>EpqieHc5s9%lw+q%PKCnetA;3%0d66jKQGHaCJP98PiA-fa9k5MM7RIWSi zF?puC=w$BigJjb`?y;`1$2uQ$3y<5ikYxWm+(LfSCvL?1G-T&my6(c-B6Rf|G^;0Z z$&l=8ly*-*PT+vgTUbKUGh4kM?g*Srx@oCGK-^Va48F@oER8&2_~v;u#Ksg; zhla>Kq!$f|*B^%6E|X;;+db}a;Tr+kt>qALK59r$q>v3p0zAwhE#cYf*P(f;Ga-ll z&j(#q?g^+S;l$2tJx*cxaB1_!Qut=p;uGc&`ezpsnMIzXA1C2>KP4}sTgzYE?`?)% zA-?}bwpsUZZnm%R1j1L@$c?j(6j_evTgM20>V?jl$CgQMAVs7%xR|0TXXkR=yVzsl z2!eC9A3qj7Tnn&ZTp21Se>!)&Qv!3{GyFH) z*v9ucJ?!Q)aC%){*vn0QCv@3Pb|3QDiKfUc_KJ^4`*Qvu&*emVqGT^53H6xwXV_%D z;yRFSn|Ws&m()k!=*+v!k*s%;*_$2O82c9e_-Um6U9ClO6$94u6Ls_*vOnTI{xr{E zQf;;YZqeyzg=6&q+95lv9ljYPesu?XEK($%g2^7&D_mk1?IAd>(%@5xw^*|BQ}2b= zsF>U7%Yalfe~5lW&p#p2=bEW@Ji!emy7vpY$YCh)r6@qNb=clOXY5ZSrM72-HzxjL z;9qFSzVUye8%jPq`u2^dzA0nBrJ1ITy;iO5Hr&UPd#886_u4TjB;W+s zj>!@_c||+eQQF%0zSC7OY2}*Kha2G~8shbB*3R+OC?G9XJM3sk1`{CuR(JklJ=czA zm{euHB%cx&t!PL(8d68Pubdf0x@TG(8}5Tpfm`hjuJi1t{_|ajPebg?A%nx#g=*%H zX!N6K)X5C8+pV|ChET{jBm8@9{EjK%S9`bH=$xcuvDPEMWDaF%(wIlSo&1J*j`!$Y zB-yLO&%t_s$UV70U$nI`D8p2yRpHF+;ZtI!U2$7dkg&1p*<+P&yBM6cja(VKtR8$> z9VT|_TU%*}sDu-|NMrUs?UT@Q&2yg*R^C8O+(JY{6gZCpn0QRG=Z=MA>IqO%M1CCaNMUK2t?LfxP=P?q}S)d(5T0v)4s;Z3~VP z&g}~}LBf0=gO~N#w+%}1ZM$O7kQnai(jRXin;uVVqs%NXa1-Qk*&a2!LbLgCc(%f! zWslXu9&0X)G#wqxTth$Z#O^f|eDYVr;`S?s7ts@j+fhIIZd6&@&W2?%0oAk5D#p)z3WlQi ztf9FuN#S{8k{XW9r6H>)veI`;_iQaDSBP7UsSWlod#qIUSo`Q5;H`yQ$gyC{W~F|D zuFd|nd+0~HZW#?p+-PJIb%;aT75R6DWYTD`%da)~ ztl}|V-gaFYc|vG}XvhogT|VN=+U1w7i&j3?CzDIe!10`|{uK?8pRuB?`5L&M?62s7 z<__|`3GZQq6D1awqeYA+kVXZugC9NBN9#&3@1SToU#>f!(NedPO1(6f5bm%q_C z)ox3gI5O2sqP&!Bo)Qu{u3&am_f}p_8tB@BRmrCkJt^St9iX-Kz@)J5v(XT(^K1L; zoNTWoDJZlqafi9kGX+=nZnUi${Sf9?CRCAVS~^aA>g2`bz z8xv_R6bG2}@MgOnqal^t&a#4a>*Am-^CkJ>|E(dt+(M+;a>QNC<)WS>=?E>wyO}GhTsC?cV|^M@yFg}h z;WYOTeADc(8eFv&qq}Xa#(ipBz8XoWWN}r?TBM(RQLgE^{I7nI{U)An$r$eACPpI} zGl-S+Nj4fH?dltNvvNom+W4C8t*p_3dvg8AS{w;lJ88ZKh$AU@5zhyC@WXfTXQ9xS zw60{Qm97)=NtL<=cHK??1a?p=^)gI}SF2W)ZAqoZ2gp^fhm3kIIx-SKQUx!To*AS# z@V2Kp@W%20bkOF&Gjt>_xqh{@u?*e2%+y*3s1O`*U6Ie+Jr}Z?M10 z0YBvQgC~WW1pa&x3^_yZltuB*pT_SvMVr$(I!9mfWM2<%149n%8xDCMiPrW{la_~n zf~!Cm^fx&6^zc0V>o-H=cH^aHGt!K|Jd(D#{iNmHp*y|B@V)Ig4zX-h{^(WgkI2S) z9+*qMqHrPPrI_%#<3+?X28U8Zx_7emUf>e<^k;Ae9U(n54dvsd*qK3U!I8)8?CoZ0 zAQ}ZjrUJ_>Uj^TU3%REFLI&VvF2JX^Di|W&f4#PAu{$`@&D%l#y)34e^xbJ&D?Z7G zD6n3WS05e`MKVYb7~&;m>j2mwskV}4|0`H=F3L-%dL{VLLPD|h07^=6F7Au!J=xsq zgJ32a?9F7mJPGRD?cT-noIO@1+nH%*kVNe>xb$b}d!H$LEH4=HJTQ+etp}kA7U`-Q z4E+aQ@jBki>wVG8ca4@Oa3UxE*H0ml|4SEgm-*x2pOVbiBkktX`;yGz5zXeH@Ys;5 zy{?cH_FuVsm*7SASx3qGeG!pJ7ER0^NotX8T#14o7CUWQ%qG1yoRv&WH>@@=|7Pej z%LuySDKgRG1H?l=9yI2+4Eucw40#@Cr*Cs)%Nkg*{d(6h7$VN{ezks=mK+r6PNy@B zn(d(b>bo>_E*l@H)M$CF@E|)gNCGoRBeRJ3Lrf@#H~r29#nEM( zh~k;=dDLtBtP}n90nc)SxW^uAKtBe{x5#uKO^zwq zjoxA3@wEalB-S_N!bR_z!S&a?o$nGihJ8E5U8Ll&R<4cHPW{j;9)M%)z1^rcuPTk| zG3=rxQ8nk|kn0evYu*xy-6i2bY-q8jQD@zUH<8$D%B-=t5lsR zUM9eh3FeO-Etc@>`P}>r8u74~eD!#^fP+>&^JoFQT`)h<~sT{Z$%H@L?rUBpDxg zG8i%jh8$ux$x|}EB-{7E%rEnQeRr9@CX(RCr)CqzR>6?dGe3;{^Zi}0ql6O)e$@W& z(=)^WB5{&N#->R%xmvgAeK*sW_$`i<64b-u+AF~={w>=gv!jPyTf^$B52W6LD`rPI ziiDb#Ep}P+sM^-iW~DhDU`Xf6cQD%q{_cfMKSE+!mHR5$xN?`+k1Jg;M3hjX@|C0! z3m77|3dz+PA?@bYhiX>~7~(^7rTM)aT~-sk`ZQRQ$_|UCvoeE_7LDVk#8E|xu>7}e zO4fVtrxf`62{5FT_qxI?QVoYr-#qh2oic+|^Y+N97hTpY7$PZIue{ek&u^aFDDWNJ zGWqFzZ|Hj}B>{C540*ll4O*05?XS%L54pchfgi$4rU&pHNAK-@^)z0MCbI3ywfnv0 z+RC*iZLWKoX*pG=0Y|1kk$mgg>6vPCrw3%O)sgNtN=rdk0BOYm_HAQ8tS^`D+XnI$ z+jU_~Kpp6Jr2|_ykc%`uOWQN*N3>amd}LYp*sFDk=xn0&b+lU?P+Da>XyTBGA{D1c zP`Jz?!y(rr&s$`lHH9X~y-yXIO(sXm-ssR?TQrG`CsByrgqK(feFFNZjr**R=nR=P zbb=#I{qt=)+g&vp)%9&tl3{vU_$tzbn}~z#dC(&%MkZG;J`0lv&z! zWtLq1GqO*>31c2zS&|RA6l&PL6l!6Xs|%M5_miRf!D(~Uc}qzYGeT5s2wm{z=aEU~ zNBivN$_P6%2#Fwj$xqq)6BtrP>u(-=u==p@2()0#V8y<+3Rp4(rlisw z_Pwnb_1F+e0{6)WPOvnh$BJc^8$r`6j7CXNhTko=Pg8+2;K#*nE9bQ`gEYd3XZT&+ zbw+bwSGDei;)AU;6f5b;SB#}_dil)NiFgeSnq}jr-+tpF9ktP_N4pxCLC%98=ao6+ zz^-U=78g}ljAMS=K@PQKG!BPobq_*UbU9Yx&SmzHeO4~qy6AZuP$dlMV$ov>MhS{F z_;>sDqf!rpXKdXJ`*QE|ZD05r-}R&!9$D?9!&G`qNh)2vxIF=e47h8zMm_VJFV-SA zuhMb|2LLB>8a3xA>}3kRVA*^perj0lr)8)crh5FB>yQIZno2j>Z*>mghDY(gC)Js#u!(nisxXmV19Bny4!uMo_p$ z&K2`~ZJ5GqH>sbgc^m$|f1-H$#zg$I0sKguOrN$)jpFUDr1kayT_M(4mvf3nL76S2 z&-bq5G`aRupT`}`-f?i`uH&J%#PR&|VSa!4L!IZ9@@y_=H}tcDA>*XCx8hE(g$=1t zdf#K=%x}Y;OQK;JxbYPHkdr_zRROqS2VYJoom#Yrzb8kR4qMOLZyg!qr#`s;bz#;% zdS_;SL}rnHD)Yx#c3AKEz36{iXC2HcE5A!d{PKr~O5ftRqY(xsf+R-|DTb-8EqowP zz>yNi5qe$P!4wTA5miFX$_cc{^QhX^Q6-4C=P7CL8O!acsiWYv<<395CLK>SQz{rT zyC%D=V_=Bvkp@T`N#&`Y^KpP#!iPJ?!lq38>9jL}A*pl|CQg=r`t5BmK9Lw#K0JEV zJ6bWBL7uZ&`+>BTSf|l5NSc9R1tSyRU6R&Y1v%abF}jc)yyA)A4A{?J5atC zuX3-MVxK0hQ|lz0lzj=@l1%De7zgR4&ZafQil=J7()%getZHVEc3mYi2)k4ESdwkk zgw{4o`{QP!_W5U+z3r=j!V`BSz3Z#6aJF-Ev>SWBwL0< zydMnt2PwKi;WOshh>j5PPhW_9hL3s@4XTP6WYS`>qc=iRbO@hXM~I5_aPf5vqN%zT z#?BjktAS@K*;OPTz1~$#_KC&LGskWYYQS3v&r4s(6LL-tlkP41uVZl0vj!7RkfZj?%u>%uCqHi3XN}UE;6hG-(&Kqfh+;x~u&p4nG7p9x{KNB1hnu z;!95?=lvUc?)O;_g$=^R(4mWqR5AeQ+*5oZi>gy#$i2W=pEa-#{GeBf8RRO?z^m-B zPBTU8cddXSPJK2#U|j*$K=gp5*vmXE`mBdW$>2>^(|rl&k;mR@66`1gL&}&z1c{Nia|cYpab{UPvyzH~Srq+=G-sDHkHp{tl>L@;MV&^w^~&q`c$S$W z?9240ZohVRRpS5fF^A;32b7b7Q_Htnjo*+Y7BWy$$@FbPU2MZoR>Kn|ZYAGk07DEb z88nBe(3^&$`s!fjC{WbIy^0%N^jaYpLK8XiwzMeiu*omgCO)<+-WP z;zz$c_t1M{Za-LJ{e0Ka!h9n6Ar|iWiMZzlLnO6JMS^>RlKd!BNE%J%=NA(ezg}vj zg>jPom9P$z3Wh|W0}#v`vckcTsCOh2f+5p*414KHPyJovDxIo!rB5GOy$TPq%!F4& z=dFvhwG1*H3s;I2k4Y)L%z`3DMSIo2JaR;tNn|eR;-ACU8pt|9Wd^RjBDL$~CQ1B0 zGwtUuHw~KR?P^j-2K4mU8gjsp8qHK7GX^=O_ee)Q&8@DEr1hizsqC?)wGZ%g^r5%i zqVw?j=Qo<;Yi~5@IJ6DiYL2XGaVyEKC(~bi>$D)yuo)k4#akROzEOvdSTF<@dRz8c zKL0ejN@CIN_H8$5E4JT&AJWw0Q26vTc3WlSWE~=pu~3uG>-UiI7*F=f3tIKA2jT1hl7(cnzOfM!}D$z3gZ3ulHL?XT@b8i5>Igjo?rNLxj_) z54*Z6$9i>F9NdT`uuj{i0;~7jBHP3q#GVEGfD-~gQu)2_m39d9I#zFh2|jv~4S5G! zjz0ESI~XEaQ>nVMc+(#Tdjpcp_vkHrX$r&Lpkg1yKiKzKv*{B*^`}mDSy{FUFr;Ju zG(N+fBMr9AXtln=H}Zru-u>i$$=&=fbor&DXR0D2BnkvcVW)+YK3p2CM6p!|j!0^u zbOEOGHkTQM6qkL8xGXlqM#CGK1lEJm%`!_#I(Z$bPCalTBwn)1LhXwuM4Syil03Wt zXr#%|I$=_w1GvfjG#J|X2Zp4FFNMxVJYdE<364Co{5>)O%!c#XCv#wh`7dBdF&Hwi z*A9N*uiQIhZrj@dw@ykSd$1P~^TYIjP8+L$v#q@A`h-r2) z(9ku>%vcnhU{M?U=wT)u6E4J|UZz!0t+3$YQI7>XZu?F%NgTjget>jD{jPG-u`>N% z7>sPPTF6v*z01hGBLy5eN~@NZx%!RfI=zJMUELtgAYPDYQny5pCGX`a`mAW&i-RiJ zcb!#y?b4asq~!*nyN`!Zyy!BE6moK`KDL3Scn{l2wh=#kr^cz5w&rUJ7gDiZ_T}zI zhtJAS+wrvu=8dFnAGpy&51n+;rMO4nK^|{)d5X5O6%4t&d3s$q5y{k(O}K2t<7va~ zS?yU(bq%?G!#Vyl7}C95{z=?ugG)nbv7&GYc;VZRk^cJx*ZW1djma+>SB`p!6zzsp zi@)GT6}P%JoWNBmU{c67=~=y@KC-G(RnsjZ?G(08SLq8GaL0jDj*oFG4u$2-TZ&^A z5%%5#mL#GEEB};$6Gcua%zg*AXr0qQ+TkYZ+6VYC&Xcy=w~+n*;C$OV`prpDB&m?* z5Is0@{JjSpnVuY-&YbcpJcyNy!Y#arI?@qMBqG|_^)!$i=p|Q*UW&QrpXcXJdF8;d z(3yT`J#sF5ZJcdPiE_7>|NqY)MVB@1$OA*3f+1y#cEzpIxEQ)PxhPpwi#RaqNmt)w z7K!-iVy`6*acQeEgFKIBhMn=#%?s7PqgH;q<)9@U44Im39<|P{|8MXO9H|&3!vr4v zCOn7(&b|HjKY#cy9Fg~C?>c^jIQyN~;Mw)LImvY%GiN|l2ltnlBhR;76J^Q_0a75I4dYtL{ zb@BJ1s)M!a@;q2_y5BEExwn{hF5rq63`qe)EcDw}|DFhzlz+;^Z`k%O-YdZ)9f`?_uZ3?-p~sxaBKBt~=!Kz-xQ% zZp^Md-n#HvQbe!$wbER8ar-E4jdaqlW(~<;NFh7jEbVo)S+95HD>De}hAvgvW3l;Q zw}c{5Te2a&g*9ME4T;g6v@qNFt}NI3R$~;79hbhpew}2*&jb2pZ+t)`do0?w*kz>$ zG=m{CWKztKZNyYhb`}f*xP)3B9EoG6Wq?bM(?rwL9>Gs!775-dX#G#~JSZW%tB(|? zrO;oEox4w%EOv6Lxbyxc0$(-$w-*tO_!6F32EY%C{h7tHyOI98^K3=VTPnEMwvtaG zp7AO6{Z`(~>s{TN`WRflp!LF)8 z+fm~5YQ+(N+KKr?@=b=okScv`@T|d3*1IIJ3^V;qT4;)ek=P7tiEvm2LoS0Mb}&S@ zUrTGPc263onSZa1{+<-xL}n11P44S@kid(>JSdzEJpR1gy`^^CkW3@#;nD|^j^4LU z!I2B(DvE3GI*O-bJe42qGr&Fwi&4NvNjxs6z(GBDX$-(C&ju!6H>u1Yv^de2)Dc4R z0N(U4%PID6g0fvLTIV4D#B?jS{gcnh9#4j=oeu%cZnH^S=*$IABaZ>gs zfg^Y<_ZEZ{J$?JXlzz_r30iDcaS+?u!weEl);V}Q=A!#B>pgXKP>?iQ+ z1hDHS?|6U=8QB!wQ2z>xF$IHR4eVh4=#mNi zmg?VkX2!;?vx$5@6JVp{qCA96FG9^c>*7KTSpyb2C*>X>jp&tYzoCs2J$D1T?g2V1N73h1l5{eQ%2&@* ze0B{)K&3Z|Q^-g&R|;raikq<=3?Z){3~2yEGC2)XS)Z!l*hi;RU3YNJKVV4-yO%7rIO$P6bEUdoq)Bc_!e|eas*!%pj6J-{tv{@6_h)3!hc0 zSKb`;kDF+SwtHw$uM4<|rg78H%|`hc`9f|wd-O_EVZOqpCz4;+jSDdh45_7Slnft@ z_464`_8P5%o%8BK?6gqN!=)GE8+n6sz8nO}*Tn}&+B@&ZR7nDdd)GBOZ8l6&k=06ByzONuX3Df&Dr@YhxMr+eyoXz?wt2smKCDq~$ji93f+voz?&-5=0w&I(px98aU#^AMIP4GZ40q%sq^S?wCgw(N53<|Gc?hx$Q z!4TPJ3AbJsJ`Bb@vmDqbGfaKhg~%J$zemRHzK*tF=W|Tt^Yg%i6z=|J_YG?v$dzQ4 zNz_%X>AY7i8NP>ZIwl04}avT!QF5Yo-T5J{PS zM81M#_q;}1_-I!zsqvkl&3*ohZof*yBL8(BQ;hV5M1xQ#RfmHHRWD7~{4SC4>k2tT zXKtdl9{*qt`m26xss9bQ^=|MZkEF2z(0AU{&fa%g>kW9L8G&4EW^)0NxEJ<&%xXryh)%EM0_(@}2lG&{G?%ew?z=;T# zF6`I@Oo@D@HkU^|=V?3sg_hvM3Wf~9hs3yy(@o4GS?shD!H=v@|HZ%0^85EtOW)=H z_X|xc!_EQpTfUk^vge4C3Y1 zqhIe}L%Rru#Az;XrZNGf=sSF)0VlzbZofxlUB70l3_n2bC^H2!NITE)LiAXRcmfOH zLIQc#li{Orub+QN@_z$mmef}jIRbYrye{jo) zwpX%#2lQ-$ITPfb9L4WmtsB{zX1+`v-%28dtocS3eM%Z6(62P9-u44340v1x6vXQj5b;_lC5Qyld!V|Az@@Lxk4gM zF69|Xwgqjbs?h7;2)l6_>GoOGXtjj%xP^D*A$RsGUHoV1hTO33|C8i+ zNqa55?6lWDj+P8~EvY5b?68{P3BKoko@hG_hGc^wb@(cRz>xu3Y}?428rXZ36n7u# zB~F?EyXce6h8vkBfl8bg(zcI4(S?li=Ph^+PqPDiz3))iF>*1FMfO`?!`A5W%5gUFkT9m^h*!jlZyn#t1pf2M z3$r~_Q?tiu)|#6A)^VBM^gjAhp3>l0&+7^^&RXnc2>Eiw|H@>g?xcK>=Q4Lb10opYHu#BA?Y4B`Dx+`G2+ay(BSdgO7>a? zHwQLwp0l)&iTcYryW*;RpNa>)_mgp&{9Q5?|BJUR@0&65!j&ElKYttyIlk5dj--GZZQw{eK9D+)eK6GU#@@d z_Iu@h1BT$Q#<6kRhn|yp{+g0iDl>T{>F&j(toOi!^lDReH`Jo(WphK%D0$MFhhT{H zbDZV{4b5JULBWwOa3p-q!|#!|O*`}R&nvoD-ctX&WNQ6L$3r04!7WVxc&kl6vX#s6 z(f1&o59DfZ#puXdpzwjjrY|1N1N`eZ)QQ?&ex>NE@uxK46&uix@PuxJ_1%X$H_>*N z+x5K3XP5nnE~IVma7YvwVzD<`P&irQm_e?El_`6y88i7DV8}MlN4ej^%(BlS8!%9G ztb;tSu7&MKt98ogP;lgw@y%{ESaO~D;|}=ILgP?f0GTQJP44g}HXG=@;1SAEel}?@ zDIUX8G8c^jXX%?N3YLDwv%wWiK4~~o5=a<1sd%<$>4cr)4t_3*+(V^(a9U{~{E-BA zN#R|_r#%%(t161BS@1(TwTk~6LnLo11`HV@$6ne%auvOA8=1UOO3!Tz4!#!=D3)n2 z{|h(&y6|S6i@N9e!($DRkFZ|#G4)tB&zJYYkX#}VCv%zZBY?hD}0b!^R=!H`RQJub1= zYK9*n{}&`{Vb6G){nw*?gW!npBFR=gNEeRsxDCCtG~5^N78UVkm@dQ=wjCrn03Cc> zm+;yI26t+nC>XLx!?~z$W&3neB~wH=ZL{8hIE|MqH81{jFYfekIQ3CHhFxs5(!r57 znwz8aFK`~x^uc~s93+Jr(U7zl#>mDJPvM~IdEf)M^g-1L{3DOSkk}BicPs`l#G|1l zQ`^l~y*|tWlT?b6U%Es}LARsu_3hx&G|EAb#zT{u2}ZrZcT*L(RrY0>em8n+w|V|C zZxo@wI>_uXkDu@*o!G5-LR$H${1NQ9%-I4zN;XFMX%+QLoBK754KL`%6_;=d8GPt* z$)p%^dGK(=k?i_2-t`>Xt8(DdfBrbXX@EZOT!}`jO9L;OtRt&UY?ozsl@Eq!b?F`( zTY`Rdyk}7buZmLaG47O-4GSH0spvT+Gsy#|UbtdIXX^Oq_!t3>)GIh*SQ%s%5gbv0 zBjRinE~I(k42ii8CIK3?=;x4Ua?AVB#dMMrQ{L6imrk8v77=d!&mVedC>(hAngp#o zxDW5llsF#2rCVpEexgp_k} zs)$=YknSnb$axw6Tr{Ea%^-8}J0_M4rNj5p%-2iB zU`XcFZD-bG1sDPbjh~py`}emF)xU3j`1;=@$N&5KY{Ueeiul*t!9wRd@q|27^jLwk zwx{7}lqRcgHe?PIYMzz%@EkgF_(j2{=5CcsA2O3wtbX?O@0t>HY;yZXck|RMI1IhxB9h2MsewHBHG!R%4lZR3xs|gCPdEkSv@>I~bA;hKTa&xRT|V z&JGoCIH*<3O-3>kx;!Nt;WVhVtfl$xte;ff`<$a*_Ts7~tz?9!dI1XRq~Qty6P#JyCn? zb1&N8eBCmgh2!Kb{z$jO{ARBHEQnR4oD_X6{d2VnhR{~QKC5|q8qHTZp2I|KhF>CF zI_g86mOYl-^LAQgB}rWsdIx4(p77^_s`s?w;27biO}2>rC)7t_mKc(Gg$K!jOMei0 z9!@&}E+h<}hG59Bl0|!k{yxcLpCS`VO%{n7-{5{@eb_0qyW$<03S`&9Zj1g1bX(u- z_HuuB>TkfJcB6@1Vvi-s7GaiNZ5qnY3i1`#$$l>i9%YZ^21Cx#b+I0N$vlZF>pnN@ z)IBON#1!@drS0`dhxJ!Dk-JKZZIf*~YMM4e!4K`8{b0xiID)@{AuV_*M!}DC1w#_S zkMB8JFl1*I8KB3hYH!uvD5en->|xy{(?k@sUKCWaK?(}54Nk!;nQ6aHTk8v2lJEe5 zA@!( z_&xGC(fqS8_6c}EI>3)uFnk6KX;ySuqIIZa4v}4#?7R-J`}*6yJopq-n4Ug%=~xd6 zzi*WsWWvwA9lv{D3{yGt22+_!RZx`JX2-@ved)CO;-eFhmkb2FW$K27ctI zN9iKy3h341zQ%{@^Y026(02vYq2u!g$X2Tjl@^WRhD?8Q37Bb{m{{I`AqLG0IKZpS z-^b8t?Khh6gd9STmEmVMkCE@2qi$5@kT)7r7#Y)e(U;KiMu#5tr;W?s&J40mmV-3& z(k4P@qDK?Q_VuQUTu1WWr87h@7J7+qlXSi_rWb6*}$2rujz;oKbhq2Rg?Oj z^-0vdhLs!Oi0~rcF^eoRi`)c9CUM7$L%slSi0rWr!UBvt$eIU3R%i0Skn>Qb$&9xblqe z#XWxG*@5{lDauQdXP(N#!LGoSp1q)M8 z4@n2Pcnh7VC+hwiLxiXAfjQ50OUu-8UQtj>+6pD*-OH^;zFRBV$R1cH8E72`)n|j| z@WwP~P^w@^ny)M1>6haM6Z+m{wcP5A0Ur3lCRmVZZ5(}Yhx}0Q;0v*XA>=41bI1ke zkgH$_-^c0~oLTl-r`HQLuh;QfeJl_{iwIf#}Yo#b^%tLzLIaxzrZRL=uFq|e6C4kc+; zN9`5nl|5;6wURhtOMnL{vvf0qM1vt6a3M)x$h>J-!H~n?$V21KEFwECHQ87DjW;8x7n!i=NGb|` z9#|V3X&MRcGhf0Pq6;zXcJXmd!t)P?w$RCBjTBC#aQ8C2`8k>h590$_M(cWt^LI3^ zuh~ZF(PL$UAzlAJ43SwR6`e0h8}RF*(<%i?{;?-0T$D}UFtw2A4kILrXOgr`Z|%RL zGh^2R7xIQ(+Swq&>s6uvP*8V*OJ{_H0K$+L)jk=hxqITV2J2>MUU0T9%}}kDi)rK_fPi8 zEP4mwQDn>ZmxzooanJkEs^z13F5JBjLR&>PyJ^th)kT4z3uet!4NUCrg`sh{5WI}1@ z`)L{svC)Qk1|{%)^&|N8>w(=kPwM@~gRc1nlBsoxt}417nM2|5Ui~It^=On7 z2{1^K!5D~&n|?CcmBi;t>>?Se^bP8qjb!@JF<; zU%{7tf&}@b?cdP!W_=ojPZxiBA-di~x}~zfkVJM^o#4j}HJx{U#h$V+@gB4-?$*sH z_bsv=>9STlW#UheIj4z637YU=MGPy+;D{t~n3fG-NE{dv$A@!) z`MVV4!gI)m=J=ZQq+euz)dzm)oRw>_&QXuJ8gx8+`@oP2?(<3PjMmXNji8g2joLbB zW?%T3O!XYpyAkxv3Wk`#5J^{;)^Z&TNfCOiCpg=m({K3C_t8tvcV&x~sSb8oeG74K zf0<8)6X_lw$7^`l5ixP=gW)Yr8$(yUcdYrf9QqJoyr_? z@AD9CGCixph-KsJ8^9$*>M|)-;cPCwp2@{7Pv3$tLv8ROKDwte$uQZ;J2_3-*dyi- zT#$6V^peh{VPcc%$3j|X14{4|H@UblU+GMqqlz<~BsI8}N}P?AYmyZ_O4g$^roTpC zuJ&d#9k;9fCTczx=jw)I3qA(V($kQ)7cx6CG9K#Hh%N}c% z9mEx*5AG6wA{+8NFeDia>BKeOYde9@@N97Yo+Mn@I{FNMqHoU?lEeN;xOCBDrGX*q z!8;f-PEzAJFyvQu?J|qVvueLlW)ZyYfe+Yg9ikt$KFm#?)M++mErw*P@aRr#gPTg5&rg;~T$ zH@q~&uk49}DXBu$G_V)D^>Wek`s~}xAgAbzzq5e2INgjLWtFMF)(WFm)yYZG{Fq!Y3be_;+7Qtx+xi9!5?FJQ!vbK`;G(G+vzk$y`$ zJrwmtJV{wb&_i+;1x<39N+$g2s{}`6+ogpy$wI+Z1Mg?hSOagUr^q%D?_s2A%&#ao z0$)fD7}5_PLYAAwqiC~0QvB%E+#19WvV!k@1+B37r($%p=kSyAM(N^w`en?oS;3IW zFVZtT1%|lSlh`8Dn5FRNt;+7|Kll-@%pZ3*x;%$B(z)?DX`jla3D?JTK-&kqEH4Uu zBPvTDZsH@j!SYdK_M)Roq-V}UgVk>&$kVJ!#$p*cy^HwMlhI_w(JS>vlj8Ph22i}P z|5g{ngVcF$s_Hz$zI2a$+eu1SF0UrfDw%ew*_cg#T7h$s^w{4(5m9L6p!DqqKLkVa z;MW`J+Z#sD+sEdnmfQbjwn&E^-OGXovKx{)qzep*$8%wvK06;jeR-~Hx`bBTS4C~pSKlvGvlp5swJQ<+`Zp$`ovEmKp&!@0_`f9`OLke(R4xi%QE64+E1V~X@v!5; z(uG;UyUE3YcjuKkq!$dSc?&NNCvsyVa_;Ae5=Y!yG7=|m!RMU9b5G8r!ll0=4eVPI zUBnmi1AO{HoF;-H6<~-CmZ(5+&WM91#^nM(LKoXP@&qsC>@X6du2n6Cuid2gUb@9d z`(>V#e9i0dA;-Xw*UA)HMekEQ+B|Xmje2U(QPsnzH-bLR3$pbSm9vlh5=nR*a#`_- zpkAQ;s`nGBYLZnXBU1ybPqse@_LPsnM7~MR$2ND(N<3Y=@_xNI2t|FB%^s_g?Nuc+ zNF{l}lGP;&4^eyQ*U2@7OLwp5sh)pE-vSr%WFucU4~E#cCBZ5l3=w^Ae88Arm0rBI z!lHXLotiA|rk`Y1l`DKYy=Y*_8_hBpG6jZ^?*?u-)Q>oVC!*E5hvs*`S7w!RFhr}9 zSCV|jA2%(`BIsB+2iaj2DSWzxEckX^EFIX=cbi9!$|ifWH@x}^isE>l3Ur!t-xIbv zo~PY$1w%Gr!M`CnG0{d6l8xR#GEcHdb?@Ar7Sd1ew{#}mq(L=-O_mNmeS|&M%RlB2 zN&FQIx%I~^qGlG648?o6PA-5WZhYuhcV&dVARFBqP>-LmK1`mw4Q#?K+}|akHI=7H zK6<7hI%FHb5L5zWI*kNdN%A^OLvInAtl^Lf^C7nTg$jmn=jZP3rLXq@`c^BP^55w- znTjO8ik!jga3JE}7M-h}QwfH6;n9l~49NmRNQ-5cl?#TXu+Mr+A7H6{h<5l)d(55& zyN!lGwp4pFNLrW4Ku0UrSHa{^p-(3N3{8N|#(cRu9!-F>xmM8%KA&xkQIqelZZ?yX z5E7>ev(#wHEH+#;($b|Rzc-&|v34e-A)Q&_((~D#;fwOm(t0#CB$xKnUohrZ@dt*S z+i>~zdv$2DF0#*h%uVhfJFS=h14G8yUkQH54r|$K^xR?wp(TM$_yp5|V91U_KOU83 z3NA2F^0%Yzj3JA$m-a)F#A!h++A9APi#Po#8CfR0g%Nl^9O|CcGpHrcfFTAjBpD1j z1cpq0sbh~N&PJzue4`5es1FRuM3pn-%B8;$H5V)J>^`Y60d_F5ktNUSp({P22iJBv9^`Wwj| z<1U}(9AYmN1J@w@wG9V-Co@2y;#Lvo{B1DAg_BUGkEe@`i_z?{q^0U63gDZ(YTl9+ z{y+QuQ!peHK7@VK@68K|lXTR+4V*80dwH$~zhUC!VRU87A4=K98K)nj=Y54I#Ec&6 zE?te0B)hcEHKNH{yxCHRrBIOIAHew^p&T?T$M!e~#zfyg%M!BQpo zaR6QRBRa!)cEYD8f*%_EAuIIH3PX{rsBOjdr{U%h2Ztfanq?qRA3hJ^Lu7h;MIzUq zNp+mUCo(k~L%O2|xBoOfFB)7bsj&LhOhcB{=%rHH0eq`BKBA<>QQC_uUb?PI6+S&1 z-dxaLW z%|MU8?3pe8R`yuug5Xht(@~#RvdiKb5=JtS^;eQR#)CY&Uy>yFm}K@#G}Y=tlC824 z5m&m4tv-Ew^zex$YX?LA=N#gq>s7e*^Weub%Q5(MH(7&McRdK5H7td)+Y771Pw3Py zg+|fUREnBYG6^LUq#9lC5I53HI%8Axm&`a{%z838o`WIM`@J5V1fMSHT(Js<ytKyJO&p6&=V;4^Q(krQ_2St+bZZ-A>K9pnf%M6+P;kV2ETZCZomb2SW<~2ZmI$RXXZF zpr7!s(x-qQZJ-IsZKU{?f*~t>KZ(X~8eLWv8efmbO<(%+z&LtB9O@^$f_T$wgYmhV z<211$HJXl)HyRh}ZE*taFkks7xT|&J;D_Wm9H1w`32!(=8c&U;P$OMx=V{})xV^|O zOQkB=2>Wu%n+C65!9Htb>%jWiEpho)!iOAZkLC3|fkT%a)?s#7J2OZXd#n_aF67BR zK^N)-Pj?^MEL?)9E*qFi;7{Q%E_{x0y`o>_@+Lh|csvS7mM8fR46&?k`ZeGojB-2F zRp@w)o*Z?$ryl%xzzlMpHmsTLbQEGFifjmCMY&IdRlzC z9bia@GX)%JfKOO~XCMa|?pnOwX(T{Or{irVq+#a`(iO)PO_uDjR7>Jl*MT9mU`XU# z9zK?@m5(+1ec7V(oy*xUmFBDlLwdoGtV#SElO)_vZaGqaC#z)Yu;Z`n;zse^-vI|t zfr49j(|?)2tnlei=`E48uw*bK4h)lzqX`U&QRa^X@B`f!*x_4!x>!X@mEeas1cgVR zUF)WEQgjit=d#DL(m(6QA98116j(jXhPm7uoM^k_**l?!_*m>QEvKXFHG&~+DAZDL z(@c=2k_nEixFk30p^_F8gL_7lYca4!`5^7iB=j{HQV)joD}C)aF+PdruMS7yDBoY< zN*0$)A$RDoxkskSGH%xsIA0%cJz7sgVL16^>T`zQL$5=9cO#Lsl6;=xnYjFXq>iNM zpMFUJLng?qKI)&tQ`~^Yq!4Wsd?Fg#Mzm5oa;+Y3iJGg1O<1Ak7V2QN*P!6Ybp=1- zl>L^P8Rd$CA@qoDlxy#9^nzGC2~ZNFMb`yx@pH%-;Z(q-r_pJcK!)#4Rg5x&NUo!J zLUQ#(+!r(b?*;nE%oDbuOWE86_g}!3J{RbF9tgdY;=4 zc>X=J{KS1)G)BWAwZYOh(g7dxe`5%)XYk`Z_|b@;ZPIdeS2XyM7n*Ij8QMiptdUd2 z-e*Vmo*HpE*OGeQSqk_gCk+!MgphI;#Be%qrs0n_F@l0v(OCpqr@`dK?y^FZAXD1 zy@^xkv1SdDd%TX{h9-L@Es?}}Qc7-sA<4AVCgTvip%zwLFr-eILwcD*@^v&VfgcV2 zxuA!1rs1s9my(ks-37A85?xj=-lr_>9WdkqxeSu=zD!2%HW`aANtdq;KBKv0uF=ep za}o=Fq$w_-X(hk6mK^xYrfQ`d;aFfQJ25T(*K%$=!l$3%XXOSR27)1js(A%N63Fc> zP2gD$&Yxp|>=$(a2>h z-ovxy2wScp*M4s`y@UC#dHfwoBmmzg(Q)#NWpz-+bHdNJ8i5BSM?K(9Mu*i62Vw$4 zRLUN!&Qk$~%x@ZbW$_?L4pNV@F(ZQ;&OVhk37JI5e@ zdCwkegc;-|Gl=w*zap`AirvsL8Z_?AxX3V+97AaXsU?kfe(w4#nI-t~#+CbfiR1l3 z3Heex{&L616ZDHqHpszwA9^dN+m8OKdBF?!5zZbx8U^9bFXoW0*iFL8Aowu}e(dbA@^Fvj!G+{4o%*0!dQJ0rHZzlPy2I(4 z(m5?t+}3{|nUDGXAg6V%`uE7WBU4Z4r7CfxPk+lUuEcQ+9DK#+aT#y=`h4r$ZCVgj zAk!hVU^!^Age8)G;%PpfYUU7`KN^(zV+TKs;0H*~4$HLMvR1EXvF@z*gCWv0mPiM! z2K;Du>6r^1c+U;g+>3Y%%V1O9qfisS2dV`emy*o2LLyhYOSXcN zu5uX%jqo5z;D_w7P!6mJ*cK3qzRpS zAwLxn{O=%s@$>jK&<%niHJaAVLe1TcGt3!r%o_&g4uj^oa^jTz*8jv1^F|h)_9pE( zIC5fh8eOm!4AJTybCU3<>wME%7e@u(BD{S+so7aDAr?F#M(&MzG`(u}SmQx>OGprr z_VQI^+(d&&j#mM*MrBxF0nuA!=a|5!8J|U>sOe60z-cKzcAz> zyR7}(cgg<7YeQN8DT+duZykPdR+YO!A`rNM{*Zs!=_??brPNM&Cn+B;~RXJ;$e&q&7-SO6?!&Ux>j5v*8^kW_|XP4d1m^z znqp`}vylRNhD7Op+68ZbAveH~`wE756$}wRqzQ*cDi|`PtI}tZ&^h7n14$Z~N~BFh z6k9TbXi3v*Vg?Z(t@K54-yuVbJWVh}vf*n;&g|6K%^pn!DK^cdp*7Pe2HU0+{AdL~ zO6Wz{PpXluVV94E#gfF)gZPpB%fQw+rV|H0* z(PxRa_wpvWP~b-iGsrSLh;ZpspQ~^L=eqBZPBaV0DOyX>>pMXbaf=9d!R{OF=R@z1 zYinc3#Z0dxICl6lSD$|AS$&}Lt!DV;y1V@b++%(__#rzi;XxYNV-@)7;6l<-hZ)(R z{RcxP*k(xck%j&vX(S@=m5tVpD_#--6G`V1pSol)O81ljP4CYiTfq=Op?l##yv!e?xFL!;Mvu$chMq|@O{J?Yo{T$uLoewo;(V-ut&tqu zC1Y`(JywZBlwg7(jf<6vm!$v=+53f_sicJ?Q@_k}iQ%-) zi7q+no%G{AanL+P(=8d0vs;dDVbfponOvmpYS>wXCw>IC$wfFA_!K6zEHFe`W{3FL z{=&2tSHsJ(}UsB{|{o)}3|P#-3*ed7!%G-SjKj z5N~0k_AF0vVIpv*PQd%tS@KV2@kzX(->?#Q zM>zTCUG%?;FGRTXDlo)nZ({zKw#oJ?h8biBKZd}Ml|879c`C2$InS0!hXRcBdDLo> zGwi|R@(o$<(w#mQhIX60;hUl1e4fV*3Fw}N;nEA(W5wc^(S?-!fg$qI%imiW7$WoB z7&y`oj?^$uW!Xrv1*!7)bTI97gk++pZs5G-_&9Y`@W<3Eu%yC8FPv%G?UN2 z`Vai50Y8$h57=PcL_r+QSL_)%GzpepjRhg3DVnpH>jw0J%poVhkaHyN-r`CBJWv?$ z`>1j6gCYGm1f{K`5M8feNI7$e77VFof;|d`^pLTZ0-6lyS76(#6)wFJ{E!*skurmH zf=V?SHlnaCHJV89mT5$AI7Efvqsw45HpFvUEkF!rOSW#IrufW>H9zCk&GqIbnz7~lG|&dXVHX< ze34gt!1hd)gTl`MKtWv$T)=!enD7Ii&)2Xh+v8n%-)j+DE`R9~^oWPkbXvsyb$n z@UEA7-DzTt{lOSBJd+gaiUoGNHXz-NHWgEYBK4-XTXqhFhmle zdwBm&UQNE!4v;s0;WG`9XnCJ(h>8$}HCn9O${tJYHQ_6aP(R`6|8kR`5*k3HNvZ=j zyOCzvJXJnygqB2;EHI=7m7?er>uL1NQyIt}VLwa`;`)|B6S(D2M{YXQ=QbQ_azfQt z6daMg7B@xqSo^_{Q*i3Hz>sA)k#o!<@O~(XvzUG)DpX8l6jMOB3*(7X*S2gh9sG1%tdI8el$srZUH-+4xX9xjiCLZKLY$HqZeK9gRDk& zJhJBz{HTI+j?yRKp%|toqyX+*$J=J`gG@o(j&e^P#Z&l#ekAv9o8T6INBooP=4L(;&Iv_G`HJ2;X8hKT0(t-_fI4}TK=L*@_3a9mP4NixXd znj{UgfEna5Gl*meTwxx1y-PZ$YQYe^KeREYvN_7qo+1_ZI?M%%>;Hiv4s|#R-O&Ja zt2mm3S0CUzCzpMekKX16Fytt^tak1rF?>JuqQ%P6UfpHWNF4qt z0z<@O_H`$8`S-jAEAV$UFR#~`@zK;^{` z;$;TG+w2>K3#lV@B^?BFy8jP`_?SZ)NL7&@Rys#qD=r(2dJfX&$Cgt`cGRHh5v7r2 zzw#*cm&mQFnB zk5LlNpau#1a_n=oO6@)5x9m+;Z!tXt!fJ@qL+6tOEG=zO9_0B9J7_u{isNq6RfgiWP4{Mj+Bv(H5`(}R;B_BX*Qo_yYwT^ z$GbskaK}HfyBWq05r*UaJowQFFYt`{V+TKsN8zMEY@r1DT>L{S+L?uN-sG85n!P5!8xIJaolqpU_!F=KmjEo)-F` zCHFVe|3ctVX5$s!Up1ORnU{qNdBb?&_DT4VMsj<@;6uc#uJhrw z-mU~gygb1r^CVu`CY|6(E^~-zvi9Rozd$moG>5d%RyPWE#C!DX(nmA_ev~RZto_~< zxbzj*Ip&Z^Fy!TC!)g&;K|4wEedu*XbJfAD;Q}whzJ$}l8@}cueV#N51wUkuMeYzx zOq=^53b13a_bUI6>T|bfgbX)3P~90 zhsoF^cj^$$OJ8%pKfid`QQ$m`;`Oj&Wd2*n!TGz~*|*sJNXL5M{1(5vMQfn=@+4#T zrIK$IfkIfECen13&*nvRyFMJ%ZES0diW^%T*|))!+sq)ssSm@cyVw$j<1*edms9xv@b}FN z|24fnpRQ<;(HEmfPG|n;VvlwF{auG7d#%nqar_xBWM`N4!<>C#9zNtOe8?~Cvc#Jr zIVX~D0=I&0Yy#e2_HITP@M6zX939b$7RyUUrq?5hSCUvIoh@0P(n(-9;sR;|HB!({ zOt8O_^mWlpP2(lE;o~@iCM)M-9SP0xY{UkXS!9@5B$mGo=`s||klocU;K&Ha4iD1D z5kwO%uUJU-i1a~8 zH?4FGl3txirYn&svK`*D0^(&mbi=%>HG5PJ_ zhP;M2(j{5G)Sn!AeWG@h{ea{>UI<*_>CwYYw2iGs9jO;BZ13;z#J|Tb>lVALlAz77 z6Q&#NvHGpG9J~3xwZfcioiTUAtDD#(i9Yo_F2ghwS~8iO#U~=UizRTk4;6o5BU$Hk zj=-l&ZwA?|XoAQG(-J3epQJzKBXV^}mtzaSX5Fb0x#JkPKVNvGwf! zJ6XNQ(E3W|`)oun+`8nx$KWT-N5T6KSoD=Wy|$xv&+bfGbxYyWOW{JqL7s`yRv3zE zCWIL9ogTuTAr+Ez-C*m*6(xxlg-f9xwGXLPyooutZ@Pt%y z7m*Hw*L+W1r{{Spu!*TAi@p!;I7arZuq}s`eyVahs(LxHJ*!rFpqIgoTSm!hn^3eJ z?p;xOJ=t5~Bs)5Jr3QaVa**%dCT?GEn5hlqy+x?kw=(=LZ{AcbdnZvtCVaWdHXwUV z$CI7JQ947a$X-lw&*SjB%o*QEai2qF`w&iDa+3YlgmWqlbCK$4)Dk_cp+LO=nMqs%bl%Y}J;vJ|>?&HfR;NQ|z1~lwqw(I`E6N zTX@Hg;X!_ZD>nkozVM1&mbCmyLV6_Ah)g7+XR08l@HQDCjcA%A zBXNwUz2jpn-ii+V-J!~U%Cb7i?ODYJKbjkKG@F=cx>BOayH_cFz*)>5lDu-`WAq=~ zN1noc+{Q=POj?#a*>MYk9b;feHy@*TSiW1f;$tDj9S!p2;&F0Wx~2=1?04t85oQq7 zvdkdjiU^|8@1Xnx~v^+ z8)7~xjNCiYi0 z(4~fs57A>uI$8$)@|(P;r;DO0C}j>ALbsc$q&l7@)iDqxmSo^Sl7Y{X@HoPjP2L5_oy7YvY9HCzMqFdZc{*H$_q-Pv!~U7Pl%N!FL=?1@ za3YU*vP(+IGHrWLf|f#mG#v{2id|N1@F9{imS`)?Q&c{7IFWT`f)SLq!c#oPKQ8`_ zk>FECX(356%iiPw`Qj~X$+CIR;drBkxtAmVEDTH~Y4a}(Ns4y&DmyE;GIvCp)W*9( z4~>U|N`lNmCrryAia98W+k62VGil&h;;FdLIucy7yV=~!4#t8*{;R(lP*kD}g{2_(z*hFqyc+YQn|$N^+Cq{aD{ z3Kx zo*6|QR=~_M42CA44Sq&;K(xYvTnk%*1vwPfNHf?^AbkZ4RuR6CNM?~`V+T9NX0v!T z!wo;;Dm@o80V5`j%vz8j5qEhfyDRa8NSXrP2Yn4U509n_byOA8YcA|ZE_`|vx?Uez ztOoK;GRayTQZVE~U@sUVo3S^VTM8GlqsbComh7~a*_h!v0$+NyS3s@(%I>O^`D23I z-xU3UT~%bJWq>MzlUxFV$ZlcZv0c0vThc@e#SJH__nMXunDOr1xH}Pm)kVTw_J!J7^ zuHIsoWoDOE!r9s&A%yodzlqip3>jflX6ODW*?9B>bB^Iok>0uMpU=|!(7Neff3k6h zgn2!!yV>Y)M&VQA(dLMI(YNYZK^Ocnlij&I#f6XExzEbo_8?w^AJB_N&Q0Tg&LXLO z2TKk+a_|{`$Fn-`|7GiKK&oET^?x!keh$SI55{?qm9ua?uNFrfMgdGV-k*E>zmx%O2SR!I4#GTu1+G^$2R?FA_=X$=n z_nw(UJiX^z`h33keP7pgQ;(F*mohbPoT*@X{&!pyb`(s1<@=PMM>!SLA!+z3j^NGv zo@e+t=F!RUBOS$;=HmOQeBA7Z7sb|pi6fgh)118|1-|92I?ZJG0e{~A`3C!dZ+x8j zbc0m)`=lz$O&CY-s)5eZ3iFSAw8wX;m1gkK+t_k$bEmR7w|P4MQz!{tweRs}m~56Z z^jSZUSAXZj7@76|cWUX6Xs3H&VuKgYwwztYVbolW|XHuobN zcYk2ovS+^XleRwkeAI5@m+S_6%#%Oq$W#pX(eo-Pcm87u6gaiiOC^!azNtau^gG66 zf>g7GCQ=B#{beu8I0qS4sy~jhYpRuuMG`U5hq|_>JD8|blUqy8UwioM z>p|tw13$*#hw9d&@T2uh_QpvMo1?$pfFVwFSyy~hR&e94CbPq^jctKSXRv4y$sBzU zhNM$}Xm>{!ROv#ORYdJz+ZM9XMn5Z=UA5y7=!$REN}TT_I1ev=NrE} zPo6Gx_-jG<+vy<5$rnIXu)s}YF=T>%Ryz#IfFIo)ZM*eQ2Y7b9!&#`b-UD=JlIUg~ zA_XzZnE^wJVMskXs?lAwJnshXtS}4_FCy^UMNsYN9OdUC)>ArYG5oz+=H{XFteWjn z&S*y|{CMHW3MzE01^GFKAWHYHXm001Zb28$_eR6EKZO?WD#EwYPY=lb$0KNy_U#EG z%}QQF`9x0QH&p)M2uDBuAAUW}zP~*9a*JNqz5ife6xGJdKURkJvsbCb;bu3 zIJ1JHoR5OmVBi`wNr55lsC(@&q#lNZ!;lIX@*eNz0T`0@hyP{2mFvpZvxKbXd>WZ1s*5fe;v?e8q6Q>4$dt z;7?aBe;WU@`dHiPXH5hhX6xAAz;e6P75h=;UZYBVv+XdwtKqUf`UL|Ky|6034ru_R1Kf2&YI;_xEt2V!2cC7117r)k?L$(Ug=W6ly zGnJ2?QlRIwW05eptWa>sdVyLzKB_ z&C@TT$+}KtgFrwD-E%I&_JBxYzM|X|J{_ z6#U5EtJEKC@I#N4wd`JaWcpc6zmP`}m zCF$P9Q(lMaOOyMX@Znot`&({2)MY%g>ohP0xC~3;xCQ57j3zYqnStEEIe(uSx{`UF zsClj3=>sTjN9isdB#{xfAX`SdNCob4cl=c3{!dua2S;+I8_4&1_OTX?@5%R_bJIT# zeE5Gqj?T9JILizq<>&v0FH$|MOQw%CG1Es4qIt*!?n3*lXSRa9?XTu8&VA3+zL(7; zvCQ*xQLpLkzlw_X7&V7B&N7+84>C_5%gODZ$?0W-U@{6T8ynGIbEEfvCZV4+w8u=M zhM9BUf+2Zdrbr^V#-t?7YzHF^Hw-zjP9o5Tc0bRMSZ(7zx1n)YC#wx*meg6wUw6Wh<36e% z^o*5E^`d`$g<2$Tvlh{$C6kGL11wqL$4b9=`0)sSRN+S~HhV$B@VSSvb$7{Vg;&sf zDW5J8{nrWjaT&y-vk2!j_9@$8D6L z``A=;pXYt2sX;t2Bq{hKcb`>a8pchrYD*=XFq7Ip+&JZgsb|`Or z0e+Cp!Q5aPj+|s>@DPS1pu1{@A<T{w4a}b-WSU<>Y0i-U=s9!i`#L5%HszBc>>qF#^6QPwL!%uuwNPg(jH&^Hk-G?9Vs6UR-zhWydQ;G}R+Ww@z{3UFZI;5EH9@$SJ z?Jy+8nZ0|0-qj;2rBWE071Zu5g&d9WV=ZVRs0xN0f*~H1$?xIq0=3&d`f>kShiG!I z8M<<@EQa8Zfg!`NnaTSZ>IYqS|Z#jLKECZn&LP)@=iKf$Mp7(o)eHXDxYd_W(|KfupHjvFyS1W(f~tlzz}6FN>$Is zMP?yN&6cVb4;>8Yrk_>M$zY#|9j>&a^OfhY6g5l-6+++FhwQ@*#pl(GPR7qDfA`Yu zx(Yvb{~~@gawe!lQdS^QTQ>J48j^p36;dNWp|z3nS~ zFeKsYb8oGuoR=TyQ9`RW4&8$x$M6ljr4EsX=PC?A??umK;KS8l!qET~o78n?Fl>31 z21q#?f2DWQ{&Tv?e;*>taQ5eS^Y+?pbUDJ`&>Ugj z%%juh2HA4{+C0PGr^d+SHTN?8U4b2XUe{5=Q!h-p%uN{Qaxcuo5bc$JMjbLw9b(PU zZ{sZdVNOmB`B#dV+q{M$>b>QX3Y<&+VA$uHpSlc3w7tE2I*zPi%|`a%YYCXU^xiXj z^1XdF=6%7>C)u!A%M3(wka9G=shc&3x^eXoVcXQVks(od_SCYs{yakJNU zosZtVMY#dz4L@}2>x>zOjPv{XyidwrUUKinr6GE#261z@vyqS#f@v61>$!$w#M)B4 zXcSy`*)X!=%3~@om&q`*^|z=B#CgkYc>t$O0J;YqHlpBkJi#nPHOT2-E*o!QHJV=b z0KloexQ%-7gT$caZT=d8pU}%^;257nJxfVzJ=7q$a+sqX*|=^Pvd?GHqdH{XM^!`B z3R~J%$9>c2+&-a8HSg1AdRVGLy5L758CkjH9_IQtpqFye=iO)Yy;}_F^{uDhYca%$ zTG(kA(qR~4^{}#x3k9ttxhTp<(f(X^YKY;<$8+zDPpQKa;Cb@vOneGZvfy4dlId+!E6?xPMV zqXy|@J~6otZ@=&)=G3G~0`Jjo=9kgbnIdMZgd*F8Hw>%8v>$-U*8#e`6jJ zVEXep{F_O8yR*>oD9Gh-QHi8NlRkTuGY5v`z>spL({jkw7`{E@<{qa$iv0$s0a~V; z`10hhQ-1F)X6mXn9WFMhmz$t%lI z30#M%aPHwpd5f}_-Bf;4WKC=DO`BgDbx1dLNECHQ2X%<%=`k?mDD(6ZlTzh@A){ue z;XOE_>Oqdb9{eIladl0TP;|*N`E?P7=#HPH$B@dTq=rnA=k&3HVMi8qNGV6!EcLR~ z4Qq!Xv!nrMpvSCV9l;gfOzjcv^{m#wkLTX{)ei4LI8v}O`L%R8=W8JhN%%TVwq692 z`x53MdOkgdW=ESzl+AY)ceNbhO=w8j;EVDW>UH3Q2ojEu;taV`nx!9@v*zj9Fl3)$NCjOdC8uP=kbT_2^Dv|VhOBI+ zI_1L--M7i~ea5Ln8kh=ax?XZm_j6x&;hw*X4(l=u`JV5Oz>qW;qV&Uh-lkbLjL5;L zZRY>Q37)z3x#OoLci~UzaHR5+<|EA{2PaG)!PgQnHv&T*!jM`R@(@4B2>eKxeuOV% zf`pa=G|IC$L;emKDCvvdIx<^oQo%BSq?eH=TkxxWg ztJ^SS0ahsGQI35%R1WZ?-1187dICf4!;o>~3(=hAFuuZDOwyGp?=^l9KeW7Ar0x{( zSy)UhdxpE>z#fAhb2vrgX=-l4kh=d`@FeINUL_I7` z)sx}JI4WOhZsjrbp$<7>zCI%T&a5@i33EcCXFiXXpFyQ(yj0Cau5iZTNxsqjt}?Z}JbCRzUN_A4-iMA03YFrI<*xwg2_ZJx4M*7@pNdS*!*p$n_f@Kzep`8jh$I+0wt(Qim+=3}=?E`lSE{mBJ8x zKWSeJN`N6FJT;3*i*&_u>=$|4vU#G`lXU}6*qZe(ahL9=VmOUY;v88~r;JCWm-)K5 zF>X#P>`e_Z%u z1>VANp8iKk4l8B~b7)s3y(}r8_c3c&pa!`^4N`Avka+kJ9F)bkUcg{M; zb|{@76;;I|Rm3#PDzEXQtB(~+|H`)Q7(cSxoS$nR(oZdNlbO2i_H)!BCFEosW%eDj z^E+yZo75qig@pM>Q-c)3i$d6uN&;83W88O=1eJ1oKDF-*yVkqN(Yl5Y@d6%K?HpNW ztJOX7e-H0C1wWMH_!j58o7crB_V%uAyGeGK@}KAHq>LxB_iI<^^&aPIBb%HP?ZSP} zWV?dSfD}<=oHA42V#qM{hPqOJ}X^)hq^QvaNyrgM3G6F}MzjnirZrITcLozr+D>dGz6+dr042kBo zXveDN_H*>bPg95Vf7X=uE%!|m8R1RFWu0bpKXU)cMXodw`P!N|-*TiS?KcVR@*HZ8 zJ!-bmzM2cC*OLTA&Y)jXHsO!>Lel1{ z5GNw(x+od25{BsL&Xqf^fq6(KYO(o`*I-By_jWQ2iGv|NXn7adQ5ZndNjMBqD*O;N zND2xihAu%vy1coHQ5T*IDX#*$w3=By&{j45d(lAr{ zw?_AHWc~zmE6qNXI-kgoi8$q3*jc=dK9=69Z|P&bWtLt|)uTR^e5;oFFO(XjkQzj( zJ3~~%QVlA{AOL;e59=i`WQ=^A>tAj&6Y=rM!K+^5z2UREJnvJy8t1Fe_G}X3?&FLa z_f2L#kq<-E!%__*r${CYxzE?eJLcdEbiNL(kNbwNW%?dmou^j6&)3Nfrt}t>G!yAC zPWa@_S$aCN^ez-&OWUkImUy(ZZH2zERA1_2?WYEjH-3G4obPbpyw5GNgO8d$CJpRV z%qNpu8cebqQFul_pLG(2R5Kg*w6%4Dr`L8kk^wvP>^zFPryFflG0yZuD2-;B zqE|WFodu?MQ{zZMhg;yRWiI{{{iu8hV@&Fm?{|w-;2vrbWk?;RkJW1H#L<|>1pf|w zEDzl(I}?v6r}|jI)F7on#q7gO@|O;hVpPe)aQCe@UfJtrV2! zzhg)+3=v1HTI950h*ZK=FhrZ#e`83hVMzU|3x>F0NQZZIIocb%-28R&>j?9fKsp&m zzudzipkDkn7$WVbX0PAFkR$ZlFQQT%Ky91!VK3XC_Ab_&^A?_Hx2hXW(0;rKVQlJ~ zVRJ_>6Y}$PsE*95+f(tuLp5>`FU4O?4e|qZ$ewxWdgU0ehd9b`Ds zyK&=$t2XSoOAT_@aHN`k);e_imadHcULrM1EOp3=Yo2NPZ?lj95*L$5uBxYxC7se= zng6Gt16U?YnRJ`~-%mJJ`TSZHAAYyy^0rC?VYdMlxdYhm8?AE0g(3C9k6nJ-zx^ zH%Z7kO%1X)(CIrDh%1Wh;G=A(n`S#it=a2fgCU7#CbAZ!{P&U_qr0Z*XX$xg>3q^a zRXN|WVaf+}kM6xPwt-tUh@Bc_h#I88nL-s3Pv_r{+cK1y_(^X5R_-Y;u9F_9bZbWi z3}J7p(PMQo3+cpzr#@C1ciNpzJ+B=fVH7?P%|cR9Wmya<;H9amsfTUqb9H}a3DRaMdr_Pyq23rX^~e?GBdR}~_(neBWhdX!WJ;Z2lb>(!47h99NO zb=F|X5M-%0Ti~NeI39vL>N0uAkU0xO24KhluVEngCB*uU8ss#ykJIe4cT;<47nncJ zl*BMHc2N@#hR!-yOqWWtDQSf}@tc&9V5SrrB?)9Q2f2cpPLy?`JHD~!1q|uq$v|4U zR``C4I^-O6$Z11>>0jsRTMQ9D(m8K9*I)?y`tTM?jaALeTRTIJ(Sv%ty%vUqvL|Yb zA?jC6;&$(Y85TbpsCl&qdc{``_&~CJ+1G^v%%AR=8-Ker!qxP~9LzyVc&R1yu}X&Z#p$?RiiBeWVzh-g67kd4?>NmxppNgQnCB@cWTqK%@xZ#It5vvYqT(;^EG34Lt z5HaLACwMu7)4uZDJF#5ob$R=~_Q9x2o|8;N>@ z^`|+=0;ddr;Xab9=E(>d$Ne`*#_nt8>HW;3?O&SabaaqOdVxK*T8p|{L!`#3J zpN~JV6)}5$5Qc<(zK@GW6QoQtvk@ocwi@=qkv=$*^RA*^Plrp-7 zJb@NmGx~_0;3N8SA^+1=YLIpGyu;{urR476e?0=d#!;S8v_11EDWKYw-ot0-t)&5a zSqI>U>X0pll>HM!8tFdSVMqmCsSxUr^Dv~>Hy=fo?)UuP)gf_Y_TJ$6>d8hrp1?L3 zBJF#Fubp02is@zDhapl7KQpS~tmR&`T9JNLxKJ`ST__jvgxuax4H8CwYzcaF;p^3A zt2Xq!k>t!b_#UH=_3sQ(()SGgh9!JpCyX|0)_E9)v@*d6g(2DiQVK&-od4K8%gLaJ zC4Yu$5V;ENxc%ButrpPjSN*US)J)~@aA%Y=aA$$>2YPoLf+3^EeKJNr%ezB+#s9`E zy^~qG?z01U5$^oLTfoX3_iD61J9o z5Uju5&c9NXFiJ-(DY%ZA&R=*fs(3Unchfc74MW`QZc+b=ZSc-q_|f%Efpf-W&!~%* z!JNti+wTNtLyuR+`K}b2`%GG#CYi>LBD(;FG{TS$7}Cr%sg{o5b-e$lc1#5A4XnZ4 zmw>`s2_+G1A3SCByy|6%A9b8Cwk7w&kee{X>SJjZQcWN07+#LIWF4I5C1|EDT~-)J zsVRfhxRLa(#tlD6f@TI62|p@vFUGP9THD#Dc#SW~`c-bcCLnVp%AYbx@~2^lHcut; zx^3G|^BH_0V4qJS**&qicUMfGH+Weory&~;Jr`lfd*j%UbEAZbr4D;tO@B*zEln_L zN!SkkTCmdmwQ65~*Qk@f;tA3&pRCG)5P@KBVcjIAZw>8<~f!;xJJ!ODSCH zWfjxQ>ZX@9x|(WcA-^%i1w(4Q&mmO#*PO30R3h4Vx|ht>n+qlQ5u`Y^6j0|;7M0D* z(RU8FUo{zjN+H=g`t@E;lkq6Xf0zadl)9eA&6+m1?_=8B0^J@>o^|JDFd>;`mTpbl zZ?Fj}lbph2h?9;dB#|5M#KH}psFh5vS$gFrKWuEdu;I|2;jPjR`Q%UTg%#HnDUeI< z%WPQ8boIJ2QT8@4l~{ox`KW|vNYJgrwU~$>vLB8pd+;W68zlqZgeND+lWHL;_%(Vh z>3+w_YPs>T!)!B2n@j!a%!mK{seU$mwOV$dx87RTyiS&V#(%C_m3*Y0m&$ZlrpV#vz{ zE7|`h3}F)p8;Z+Gf}i}_0z(q1N36Msl76Mn`mLY!4iDjN^jFy^qt(BXbAO+i&Rig8 zz`dZ?luM1NY?EWuoIUihG)pf>+pW*9iRC;J+@#0wntA#FpPkCY`GNBaZ)ym2NHz?) zZ9F2XL&T6-W+IwwyjoxJJEu5wVdhO2t!g(S_Fg7FQn1u4aKj+Bu+81j(0x)QH~aXqZTk#^_p z-S%DC)FZFphvpz>>0=eckQ5jq4XY;L2{7am40&oRa2~a_({mR?ln9&zKL$_^EAQ_j zJuKybD7)#vADUr^AAXWT7}7y6%!Q&i9!+l}M4AX%ghrQk4pEB~8-`f!Z>0er#|L?Z z+p#UQoosq`De(-y+hh~oW*?O_zT${xBOm$sM>5g1v;2n11=)}5BP?v+o>)8;e+oU1 zo8msngz|RxqKVBnDTrxIx&ufbk)F2#c1S@S&d*1gMieuR*qVo=K!XXWkOdu7X}q9o z)qyTR^^^;Jvg!~KmgY3B5xLc+vaNC+XU=p5y;~bumUZ7GQHQ9OQlcaKX{mVRioZaz z$`rk;Eq>TwNHPr3B>x#&2lW@r*hIX5%kyvK@CT6OSYcFi@`Ol_<-{fWjIQ7LJ%uo& z9EO~PArbfs9Xr((gb{ykXA4Ic}>^HJ=oX@jf-x$ucEs{OZN&}9?pR`00S3fGS2V^x~ zWi#S2Y8&k&Im|}HQzW?^COu`CIm|J(?OffSNmA((+`4IaaHV+E#PPXzA6uuhnM8KN zkTg`1Qo7th@lwH$6);75-y~)w;Up4uuP(A%r2K0Y6Yq)TR4RoU?%LQj?N2~E5RFlml*Z44JjwBLog1W~R4`nu~PT7riODTh4 z^igK0DtggI-5}>M8|TEuxgX{#(7=w)?m^32#;%QhA2Zn5^!HhHz3x&q)Zq)!Q}Y8D zqUZQy^m#M`QT=fWeuyD+i%Oau2Vev8)kwZl4s8=xT0J*{g zY!f+y#?>=Bh0pGS$^Izb%mcCW>mmAEe`VetMdITl_W1r6{`KIQck>-Ty<)4$kH#Na zgFn*slig&sM41XCgBoNM9-jH|ZvFtI)N8vcTU z<1}6oRXFmjA48v|TEq=IZZbnZ!3^XCJ**20mt7C2LB6F1d2Bp|r|DM?!;qusd6liG z9fk4mBZ^!TJHB+K@FelsIlyOW06i==+|tidM$UEgSuvh681jw|?QuG_+SD26n?%(u zSM9rve7`st5{ctab9H&hm$vm9O;#8#cIEb_Gt*$N!WuhzP;?EgT*0Xr2Q$PD_E2+5 zVaO^Bse&QJuXR6sUoPhAYs}SWoz*bJ%k)BNf#voE`dOCiJPPGe6_evxk`-rhUCS@0 z8l;LEB*oMqecbpXJ12qycdi9px24d>5<~3p!I)5EfAkoVLe6);4( z?smfvxk7@uce7x~BN!58rYk3RX3)zDM^PjdR|vP^ac;#cA?&>fO`#4UeaSq(D_bfA zANOr)k$q+_xoVPX&i~NA(w@K*@I$JrKeK~Yo{B3}ANO(Py(1gs$nGm_^pJbJ4Ykyt znNqw#IV=TKF20al7?KD>^x9&-nKie0X2==}=tdY44;^0MVyU6xkgG-S!3+9XS!Nz$ zF~kEy0+^f}p|;aDj|RF;n&;HPz(kzyqLe@U*l)B~#r)dN@3(K(A1S=X6#k8=+ zy09LPPLlDyA0UT+fQsZS*(Opys7h%^nY~6063p&G?Y9r5mnFZBa>$f$)3p5s?x1Fz zBqS9TYtw8X{B|O+Lp1J?>B*hUX#c2uTSu(hoEZt0frnfX(L{~LggaA&Wwb8uf{>Hsv!pl z;3N*fXwz*9Uhbe)cpW)kIv`eZ0U6#d=r!b@z zhR9dw2SaMmWIgxVnTMp%%gTZw(Y$P_zVhg1%&|S>DoL^1*k`BYjeKrz`JvC7JdI%z zpOkW_)O805R@Qk1PsA3b3hv<7=|2#Et%_@ptt zUw~G(=dUrxc>qJCIaURC7;cCm@_~$*pEqo3u^J$=YLH0ksk`tnmOfV4XYFgzOnoSN`Z&CCaX#}GZ~yyqMsFXtlq-cEdm3!7f` zCx#*FKFJ~Ip`wt!_cjc0X>&iKGT;qh_(u|B^pZ*(TZ(s#KvavzMgB-v&e4OdWESI%J(ExoQ~V zF#CA5*FDpGZIe06+_MgbkRMOx-4Mwa-T2k-Fm=_WqSMqOF6t58l53>$YKu~pGn`}1 zK~l^dBpN420yq9881fX~L4+*|epE5H)$BF{DvdH zM0JRViMtfQs_o9h0jZ`c$<;4{N=x6=@A?Z}vKDhPnB3c`c!rptSJA%`Ka{AlhUYU1 zeiTp_4mm^UFa8bB`vLMV-;{KbLcJ%gX7BXq_%hIR=li2BXXxFj8 zDCc)%@eZcw+Gn*F zid=YH7okCndd?e-Nm?%W`;bb(#bXQ;bpSCr4tKz74@p9In&E|C(U)FJlq8IGtP zxo%QO?rb{A!*P<2uJ*x@Xr|tUOuai$PCe(Qe@?Z~;XUc;;CZozewJqIHDp{_8m-4L zq|tF&1q20E6GuzBydqhHT0W}8G>ZFaqC(sQ#-AHEJERC1Rq>+pLx zl0E;5N@RhaRvAmCew z%jTrrFl0YZ)?qNj=3a3{ep+D~qNJ>3DuPNj7pe{_B7uDgj%Y@+jhvPXWDh>T$Nkc< z#AE6b?HJF6BX#bSpYM_z+3PB0KW_ZYlX(}li0h{VEK-joP>bX+7s)}RwMa^%#gN~8B6G|{REsDT<}_Ley*0b( zs_mv~J53GJM7M0+Y*AK@qw;w#d3x}rdzU&)KCfmW5$sEkFm*^cb;uc0izuJd3rE-j z218!KkR#L~Pnhx@H)`Ahx>4OQL=%o9C=!q35wU6!<&Uo5BhTE-eb+qw6`8&7akYD> z(+|Rs0vsk2rgvp=q|wwM4b&j=u*=IXPx}lnIh|Y+?RS%AxDE$noyj=uHw@9f#7Lgv zv^(+2W|;2xPWIIQm>o`KY>p~3yPaCXq`1mvYM#A&1t*@9>BsRs z|6nh?wlbb4{YxoUSMXM>m=uybaHN7%E=|3|IivKjw2jNo6r;-7Yi{&XTr@6}&^0h5 z8ix2$Px;Z4i>4c=_eC8o>9V#Ma={Ag%RFTN}SxkpZ6D&=#G|^HusXmqneAE`s z{we#ex7*iHtoV+>k6SyEIhB4TWzKw6KqgV^nbhEM#@`*FWW8IpZ3FgF1c{puhWLMy_d|Nd7Qn+VaV-`Y2)@) zM?hbV2iBE_deSq%Zo`VTP@I68T=;p5ac@{Ry~U7JrXuAqM7bobFk}pk?_th8!x7Ec zgW*Tx@;MkXj3%puSx5x4kXFMG58W}3(f9hH$x6jrSV)DF3YVgJsguk?a&Vq3dbEM+ zC7!lxpYy(?v#&duBtXl9uml@CLdG$N;BEH6>p47CZW1)sz=vLfZ-w(?r; zz!KFZ9_H-DFvO1j_pfB3M3Ei&h<=uKizqwtzh*kgYB_)sc>u5d9TIxwO26|#Ec~7t zq=7z`JT1zbzW_tzOFxGn#KA|d#PqHjOdWCyekj{S{jIGUM4MSx`9Bjo?;dampScdvU7{kw+vGm&ds89VQ&FP+Z9!^^97i0gZ1mN?#F0nY}ha~k!I%W zO6pS*R6H)aEL?P@OhA-m)rgb4l$*Yk8l;pOq%la|wK2nx6d2M@PfHqKr2=>3IaDQ~ z9IYgjUr8v4rRl9V_pTi2s%KKUcMGXQIvkUBmm`OltbW!*W+JuddmqA(MHr$wL_1Ez zkWv_u!bT4}^Mh`5uxHW6zUL@6q!X={T!IUzbQhSCFR*!$?3;zE=vIS~9ORJWBdWDw<)wm8~g$PDQ!y7g6qC>dYAi+_+WDbl{j z0jc-r5MIdw^S;RCB8seW?9?FPsDIO7aT$8I8`Kk5n0V(i&#|#@xQNrWd8EDiGqaCD zYLDbiete{&P*rgoy>$q_olcXsKT8sDF&gV|Ly>UufaUyD_T>=WM@?Sh`P*{$C)m^I zW8L9@c9j3&S^lTjNGMPR$}(5>h7@f(xl_~bSZa|Xssv@8Tr&yJ!}ON!8Q-{^A@YSp z;OS_!HNlYY(DuG0Y3l^1#Xp8RWST5CZ62|QEMZ6RsLAEthUHYJX(o`fGGoE$tNeP0hks4BW-n$~O1lJK<| zcVQ0Mjw2+>UoxBXl+Bdx&ij)5Nt0m95qASugEsGJd(zhV9l*X+*LX;5c-ghn_c9#0|FLn#hh2x+^Q$wjP?23k z4UjgM^x**dfV8|>zI1f~#SqmW;)iDIhnL3b435+1 zlIJBEJ=R?_3y~&ElkO(6vgA{L3`5?+khgd@-m*#k@h{V)DPQtvYo%r*`xoRFdFbha zAv1;{*U@Il{a!*pYmQW|YSLBYZd^CJ3M+X{b+{P&Kc6Gx{T^w++GD2am$f5Ao{%i~ z5d}XqPgll)<{@H;)E=7H$r-DvIc+_Vsa`D%$$=r7scQnBLN_X7bp>sf5`NmqJZXa% zm5?Kuo>s4Kq@NR|)^UC(n=0iZl&OF=-6S>6Qfr78t8hc?m@sp6=^0m`QY1Bqd?anu zAU6E%v1DVV)5lVVN;;h^F~mkPgvAhf)OVwdls0P}j#LL;@jbkw!Pkpl{V5x>AJTKs zt~u2q7DKMWkaIAk1QnH16192TntTuNl${h@L!z#x#xAN4sgBffil?E736x_sQQNOeZ_RUg7dl7QZ@^?@hMltsTYu54Sr{Z|)Ac21DN9 z952H8mrezdZt{ASd2)-1h4fJlCKkiYLhg}3B43CyPArB@+rEP%4^VYU+0}yQLz>+> zQ_YO+7&LW<*m0QSpaya9dk(r~s&WpSjU?6{f_qdt$Kc3gs*smtWc9P5!15Np449{n zRkqQO-7t*ZI7MVxYbQdapC6vvgs;zhUq%kt3A>Snfn5RO>>^vSQO=z16FW3{sJ^b`zv%RP0I8E!85OzH0HU-Egr?R7=a zYmZ>tg_rxu%X2P!NX*eDaQ2eGk!R-KQmWuvbiljmqp;5%m&a{x*RuJ$Jf)ALe08E+ z`ksBX+DKbAKMzrQAw1@e!@}d^tM<+)t34kfevJI!# zRc~_amD5s5Hsd%Mzj2)NY*>7Ui=_-V{hs+zHccrRYk_ad#=30uytjERnp}TR_vk(q zfZldZCse_0F~rhmi6Qb7S`5+6J_AG2sSuVpElesdEGTn-oo{=Yc+0zSj3nK?i%-A2 zr53sRORbrWoM1k3jqI$~Y#gzX!I+O3R<$xb~ zWHi@OgY<3ou^M4W3jDD04aATLo$3JcVwQ zvU_#orD7?&AAgvgM~tPL{G3Erw_&qFwIQfekREm)^n(45_3J zVb3mJ_1_qBdV4*c@+3A04xpq`Eusv@_uShR+>TbKCe_@I>NurRE#z?1*Y0#Vrfu=` zv9jo6SqvH55k(DBg+6NtuZW!*q?j6{9>;k%4CyAhMEO{{Z|x@IWD;M=B%Lcix>(T= zq|o8VOK*lyIm|>txs~;=639DIf35GI7*h1jM3A&g{^(#Y{82rvQPbCwyL<{|RW43J zsjLQ>l#jE6-MN`te;ax%;H>x?TLlmN;T>D8jvH<49a2cNt-S({oTVO-6L1uE6z^JL z8&@wKs5m-H%6j)fGn>tG{@+MC)~*ow?TdN8W(^bN42dW0BnsuhA-o|;!P?UG=vRhh z!VoK8KgG;EX5hvkbw?%a_!B+426Sg4)p?UtQVKt`1>_O6%o=^g6q71n5v;6$?p@*3 zBHhrW7>>+34?q~vDvj##03R)7lAT4nA z0oaN@>!?jz0;b45xyi)h>UK>*=J>O;xrI-gpB24mcECiY{ zb%)q77pT+5u^H8u1BN(o#cNOD9ICHs(j51~k*lOE-VAUDw2*I7v`wGA7w{JP2OKf; z^d;u$>Az!00}OHcj(F}Rliw1_gazUnF~&$n}Z5{{&L2i9u5XV&E6aGA+>0qUto zFvJBzqNy9KT#{5~>#58|^!(U?PRj*LvPfy1AZN7ze$1k+$uNDaK-lpcL+YtT?DVu! zPz}f9Jdvt*gg(}F7~+5-+OE6BkRtcfFL%+6uDFz^kq1Ze*!+ET^JojWv_Mv3hq_MM zhop2}r6it52ej{F3*DX9+~qc&(lw)y=60Tag6BWp=le`N-I1nZc!!?J2VeU4JjZL6 zKF?FMrZ9SbALliQA;~aAK9Nf1=}HpTJiYSMH5gLoz6(R{G7TAoAxd3LM;%qiOI&h` zV^54fVjEde%8Qcb^#Lq-z~*&v`P*#Om4f))d^))-*NmUV$`}6kK9)5TImP?pFbs(_{uZ%t-E8ns zqD&b4=r^6Fy-Y#QE$*dO7$*6$ggo7dFUQzpxz238=@-qbE}`|kgy-<2XUQE%cGgpJ ze6REW6hSRAW~v`4hn1odN#!=k=|aucPd1htz3^UMrgq&vMUSxaXT02m_ohZ5e8 zLyc!_(uwnZ=5xi@ho6PuLXy`FO8eSdMBrDV)k!Z0r2hmF#G-<%^Odl(q zK9=;t=@2B=?CRNK$o_zQJcf}b>%^Lg^!lD7Gt04K9EKz_?`)$EiG?A`$kSdR?X&)F zd-1MyHVMuJR*+7jt*HYhdAAl#)FKqf;V$oEdafG5k4hoc5o#}VoZQ*rcxrPwj@p7@ zNEUU7)z6AR5l z6}RERPMsk;-TQeuP6fvC-n9eEZTpE4TL$9`&T)2^g|wX6nbOLzF@? zZfcPzdRi7c@?pmc>}Y@;PIPBCAl24Zl2`P>tgR#yxKUEbO=))~;nVT3-Eov}_0Z%H z9BGFxQK%oJH!h?DnL|dF6bTWwVy0c1dPOJa?z zUo(9dJRliRK}z1yN*CpWA(EOj5f2CS%~r$)yJykEHYOQC95}qtlpfjtK^#m zbJWSICCxGK%LtmR+TSrGkY4-rm%&d@>9xy$(8~SP3QNQjiz%1kiTWqHA*Dc)1~?AK z^}Kl+e*h8WO9+D_MR4RUw2+JYGP)@zZt`azrNUL`sfc{STzq-b#D2>xdN1|IFy1@e zxwqKQw8fCaFyx-`7D}P|uM8Q4Av2#YyJFq*<|TH)kuYBWH7W#cHr!8Lq-nioH4lE} zNGA-@oJ1M7;UB`NMe5kbYl9>CpN6PKhJMZ@H*y{ai&BNpGr{?u?$!4neb{alM=Dn( zRZkbx+R9gymP|WAK9*eLdr2OVYvUXY zsfHobP#JaYWc&IL3Cw!;n(b&x%0fd!0QMPpL&>P*y1~c7zjZQWWiQ zL|N}uChuKJUR51^&}ovFE`}5uhUibq`CC@ya)+sda)o{Hhd5!#pXg=TzR_mEDC47O zLCYjfYzw(kN+pq!Dwat`K0XZ5KpUB~VZ4le?625sei3Iy0M&?ev3GEMSMaB2;mBG3 zro4JeXdgA3fa9n^wAJw$xBq!_v%fGkh^nFkOlWjhTMQ{TGm#;BSyeDZKK>MRY*F0z z>On~-Ep1g4J5bcmS_>Mds!`n`c8tRgX_lQK@$f^cToLEx59GT^{6gbbCzu}_$=;sKhid6n(F}X8#{$~rE+C27vEl^Wz@(81swZ#zi zv+Aftmgs3+qbsEiw{ihC;R4h&U6n|Q@u({+OS?mMa~|L~_vddaEhP-CmHJe2nb_dP zjNd&@wK+Bv!%_`vBgq^7Oi7N~O45%T-9Mm-DTo____K9t$+(-e`Ll)mg+%|(^*7tj zpzGD1=!xY@!;ollf68GQ)9@sQgq zhIGS_3>eaG^4^uz8_FbP(Q}EUtO&dzbIjBAoU1zI8uwEd_mg_<`KI6gl>6x^EYY!; zGEQAG&a8r)3Ric6iRIJ_Oo zOw^V{W%Mf3z1PePG)q?>OX@I7Wo$9zEefM=IVaF$ZPg(&R3ec~)*E0*oU4x1lO=BU zM5==pjw&2=n$#(K086$wqQ2G%YLRW!A}wft!|;&Qk&6-wNA8jpAIa$@H}W!@3FAdAyzR)w%WUMP$?|>rC6T#R9X-bxyz2Qd zB$D}+MP2PV>_G`FpHC0_bH~W%^)g$D_~O73lE=48o;-Yo*UfWCmigQfL-MFYRKseP zc9dt3qx3@aa4ub^mu0CxF4BE{#ZGm7*6#Z!hWyrjQa`Jb>LHoANU!fZ$Z_1~2H&1+ z-1m9KZqI=Ad7q>;Ck%;$AfL*zrXt>I#LC(|1(kw(d@5)$I z9kRq!B$ru;7~&5@E>MTOB=6)mhOEF4Ylp~Fv@;KR>TE`pqn$vF)FA~hAB1CaA&k*ac7P_!Ct$w+&*oawCCBPVMq-Oacy$s5wj7? zEAo_DB!-EGMWI0VR}>6%0o@AV(?$u^1wKmgpkV6jQaxk*JOB z?@$|k-2r}!e=U1c9Y82Ij2fxiIA2BHrq-jvTNfizt zxk*dSJiVWkT_v%LA$4Z1E{*R=Cgaw0LwWFS=+H!>l`=@?@Bp47+p33OdGVJ=P=`do z5HA|t9{N%xCbdzDF0n$%zc!QJs07Dz{95jJB>`8%kZKrG9hi%Pw*>{3CLmK(AN~RT z{!{*4s6s0Ju8~2V=>Lv=20!3~O2Q8{%PeGKIUk0Qsi{;K#*vm-jvVNaEDPkXd?JnyZ(>5KY#j@dTEeT0~j9HE=|pk$4h*89IAXk79Pqw8%l&$!Bo zo&I&-k#s-eNU~vxG(b95eIh-Ob#IkWH7L2`92|K}H6cx@+~&H&m7rMUKF^JNAC5TD zPxa#C&=fotZ(!KxUe~sTaZ)(O`Ijragh({J((`KDt9A5q`9yU0iXl>}ZZV{bUX~b= zjKX)^m5yV*4k|5CkL2PQ@2A4qZ#;H4V2HdDTO3heOEZ#Aqt|L-&qgBtkuGvk(&5M; zTY2+2EAV3+2V?{4vCHVoE}Ok1^VCz3R6XhL2G=#-{+kQE@K1gZYi4fMQ0l{3M?JYy z-0tcwi6QddpEF*`bojC4J_kdpNlLy4LzF-=2SXf?!$BINrr)YX&QXiJMWc0^^MGyZ z+ZL{~m+JbLF0>D^?g(ne2>zeEe3ZseAB>@b9%Jviml{O*@ke1#p5aglYAk(w$W}Au zcy{1hE#=w6dj7b+$&fr4k_SWbV90fPS*?a4+TwhXS;ztj)JpH!N5bxXeCk#mB8GG` z!O%X#fc1QC|46@(jRxPc4X5vujq^S}KDRga!I1MX42rj(kvtsJ(lL_7DH_GvcwSWo%-JxvO*ma$xJh!I;0KlWh~pX>tINt ze+>*Ng&`?0M2ekaW+KvOb+c2Xo0~@tk0H8JQFNtBgQjgAj+mX3_DefM?GJaJgeySUJo=a?Iqz#T31U z3MZ9XL<~`9tB*fb27I4Mhp%9^|2J}2Zli{}&pq1!L-Io#sYNEtY+V{HWrEZ(pN=)z zQbWw9XVE~}p@5b9l}+x~D|{7cZ18>0e%d>b>DS2P{`x#KB zc-JHTB-rc%Q_oO6MClr;oTr#k9Sdv>I=wxf|LqPsn5ml#(H3;e9r(ssK;dXq{Rk_Pmsya(2Umn&e1`dJ5=ixj|+ zM=&JVxXKHuMGBdVhyl`Rxo~%6Eni|E;U!PX$|V@0q?Cs+L~cc^{!k54%$bEBDeNvR zg&~Ff+Z;MsF;pT-+#6nup_dhi?q~W-KMYZyq{!4Ffq0<<$@kOVqrlB0t~}*jhb?;k z?cs)$^Fhu!b#zWM)sWjxIe+R@y{0xu=WbWONwbSeCK&n5GV<{V<{O`fxS(w%%2#yY z2azY_*5*-H#=-gD81mX(`6GpFXMQv1!`gVLt%5HNM>N6D zh9Pn_O8XmtGOPkWViJif9{O1xYLRf9E8491BQD4vNHdW(;CpJR%T!U7R6bq2#(w-M zFY!7yk-c&bJ}8_1w;D=S!OCW5NgpBox#l`|;fRe%x6*T$*gw_6#QOyNc)2hKLk=^w zI1D)s!w?6v3)Lc;i>&{06rFD#-t|1VkcS5QDF06p{69r}vB8T$7@{-7N$2;L+(*B( zpwqqyZ<>B-B~MdRkm)beY>HVVV{`JWJgb3xK1`#@nnu^#N)6je9ddn>A=l|;i6KwT z9%XI6`GHwT2=nyY_zT65XD~!bCB0M>Tep9*Ny17a_oS2U2O%5tKG_?`AxPQAlZ{vF zJ}_jT&zZI3KBMf`DPFyAIHG#wAbqW3UW$ApcVI{wvyc#G>F@A`+&A;|-xyMa!%z%S zUhls%WQAUq_E04oeT+0Sa+KBb)Tw-mLRNTlRRmK~x>@UlkSI>TS54za1d>ddQoyM;^jlW{lY)AQr z@#z**DhS$@bdN# z;LnsUH+!=dQHK3}Dx3Sv&F`ZWJ`Weg4{iH6Vkni$Ye|J4g}m=I@Z$y5g;G^YNeR}h zJ_>#mKq)1cX-kO|V9JfrOhz@IYQO@ge4h^HRN9~Z9scwa0fk1_8^$&$&D6z^F6QcQ z_^e1I`=lSG?g=vC-EgFdbltr;3zf_6;G6QYZs8HR1w(EzF)2Zxd&=l^6WLUy8bjL; z#SA4kZlyPFC2+}ozQjp{ABq0jGW8wjLBKVCe@>Xcl!aktfBG{Yvtw|va(SGP=7*Y;HnyE#asYNEKMf8kaT@yi2i@ur`pgxz*( zk*$7~I$D~oOXr(o`cc8lBisQao(WFM$_NZugdvMCM74*sSPAAw^|6LvNECmb#orY} z5=dH%p%PJdd~Q)WC)3&u&(7!Gjp#reUvdX^;V}w z8>23p#6fLfIzcjfKX-W|-Ky8z?M2iIv5>>&wz+5UcFdrynn7bF2er~<W8VvdI!xEnMI(G$GzjDr3kOKMRhf30fh;^t!j9F?Mt06;zIwSSRMGo1~A5j^)<0bOHKvO}dAfg$%=xVH8ut zOuXk{h^F3$nOcY;rwl_>iIkXHq?%epS!P%1N4eQeb%N7Rjgk1-yVM0gRCB~`9x>%L z{K$nL`>9!W(*s-Q|JGrqASccLxl%U$sAFB6Mz##3P{R(vk%Q>LEniV9HApLcEG11_ zeJrWaJDG(Xp#v*j?{OcOVTe_S$jwo2=IW7VQ+fdVL@qM<=%jnK05y)lkZc%od*i_R zlZ}csA0N-^KA(E_>ts-mWKfS3Gau>3FOr02Oa76IFhrVOANcW%(?A`Pj4wpGUTdZ< zh74{pB%fYZ@}|#Fd#J<^X)N2QL*xMyL*Bp;?Z2KPyH~rY-1M`qvX4r84V49d4=2GK zN>Dw)mY}%mp;M)5Bf?g0ziu1Z`P3G(GX{oSf+34Ly*qMX$RsC~zx9H@C9PItP%9i! z2FgjC9<{JTxr`(HdXdgpC_SuDYLHMWjs$9u-x#8)i0TljwdA<4oQO%>rD=cY;C|Iz z8_e^2R!}d`@X0Vl92rE@ByE%Y>zY%K(<_qxNnI!({N34PwkS6@4S(JMO4y2EkI}_? z&@+u1$6hC!YUR_*HYt-z6d9swC}1DCrs0k7BN%m54J3=EI#Ax1ye_IiLg{1uD?{SV z_NiHJ_aXXDL*&G?lNVD3$K*BF+oZGzpu^P5}gw^HclVgE@ut(bNId1Z$d}qI3>(@M9fs`tI#DFeDm=C?&BR zRYd~)&}NSjW+7t8rA>we?sTKNy1M;3Q}hU1HMNNPS%;yCm5$g%)|DH6NC7;B#!54C zZO&EFmJ%D&x2HF%UuciGTjqD;^<)kp{(7eNL(o%A$J<8!nIX_B|rTyul z{JlkbUn9)rwRcJkiGd-3Fl3s0=Bc@F_Afl;8P}>s>R?E^I|dDDjM-VCtXQ2vY7))d zHP5|kdMMiJI)`#qTWcSa8LtU}rX2gpYHA@FF_C1eCGPnyZu~lT5#JI&7rCc64;G5> z*cCAwDB?~nA{VO&ex!3^%_)H;4p?GkE#5UVb!mI2U`W{KyS%g+2og(0QpC%D4M!5` zFkRzyPQemQD{hj4sLYR-h9fs%h~^{jj8;oKV&xyvzKt;IkuH4gMVwe-Y;3saXYkt3 zd@94YsD4}=wUqj1u~0DabFBNsLM%B~nvKU&Pl$gO_j+KS)m2hwX{#q^c_sCC9K3&) zVk!}SNHui=en>f`G~IQ!(^kU~G2|2svGzpmg(HvguJ7jDWWJ$HnrqBD!r;hj_#u{b zZSo@#e)QAp((L3k_2oJ0Ou5qYzSL5Ass???=fFd@3)Gq#wqWHX42dDBO{t&KV<^2% z2|R&pO^d<7bjkFwUeU*rChG~^*W)H(*Xm_87?rLZ9UdwPZMc1p^6J2P92|M((+NXf zt(R?tz>p&_q-^8P+UGAuk+xaK!3> zJqog>DHB1puww*I`v~6lMSLZTJA+}!i%o{4{!1NVDSqYJ%OFWa9+d+yBm{=|@Z2ti z?G+d@&OBNik*-NcKObk#pjm_b6?)$P2NcjOLhgwGIC976V(Za0S-RMII8qEplpu15 zgnRp@SFg%U!IgxsY{gC{6KQnQ0_bDiK_zzf*BT_08YBU|nKchl9U^C;osN?TqG`n} z)9xW&w>;*0sp@H|JEjSX9Phz+2sNqKq(zG1Xx>*pW*~mN-*UlgQ+**H2UQs&g*s)8 zOzkJ|F-k$K?3n`cVnkf^38Sd}CiwV^{#optayj0@4|hN+44EV+OP$Od7@`_P40*^b zq?Qa{F+|S6KpceAw$m`=Ew#uwY7zCbq|iEyKI;@Sk;Ax09Q3p1&{o}Jwoz@;c9j?} zUes}l;6>f$>Eg#X+bjoR82m`221%p_N%Y^(Q58~0HQi>ifRw>tV>jC%eyDuDiXx5w zR+{%Ld{fWiM=D2NLvdshj%br;Gz^i#zQ%L~p1_dojriqPFysKI0)|-qEGr>#V%cT< zhR@-LGTXKLW0G|EJ{Zyr8=6hh?-06O>31_Y+L^AC0Y_%hV^x`XdMKK#5l=J>(X*%+ z5-CMp5T(O6@HQxO(As(^ zjwnY;Gxif`wYJgI>L=ez`!?ihe+@UfI7N8e({Vp)qe(it0BtByudImbqsU#yw`-s4 z+&35M$SIM7Pc=mmZ@b05G2ULQtEBFdcrs{eD)I0X@6Tc0x5K=jhe_E#yfnqR06*L$ zCSQdg7DH5loHG5a68c&4iu5oSA0z)nGx9O0Gq%+Kxrdh^j*KlW@fyRZO>*gRwZIQO z3%Fs(W9D4<&`jJST~U*eTC*2Z{Y>k5Vq*EBr+^;lEWOYSW@+{4F+7|k*ipe5Si8j5 z0y&u$nT3?Y5I-vCnoWj0GV_oICK|n_6EzA$l3>Uka(v%2IX|#|-zSX@)(?=QbN$u& z!g|_z2n@;INP;7tRUdj<$Kl6)esuCL8~B&w{3|)$NevK={NhQ zv_C`)QEFX245>sP(*{Fks6)~XLoRV+B)||qhd5|zia?I;XswM9^ojP0o1*<=1h9Py(TDMau40?k%D z{j4l%ky2`rMyB$OaHJH5WO0HyE++IHMkk!ZETo)Sh~^=hidc1s9Ei~*`xes6QL4xb zp3${cNxdIwm`aV8LL8B{$JDX6Q9WH;@KH-ouZx#sTj~qNBx8(P@ZH zD^*k+QV*|@7x0L0+P|x06=gmu19K5h$MA6$$4jUns{=dOTbK$%CdqqO)?y9}NkExr z)gW(Ri0Y8v7!qR`A_ebT6j*ziq-%rWuNNe-8wqTKS`w%AccL0=?c7~1}TFf3-IIOde&w?D;|ATJhS!Sv=UZB%k&hViEN@%!a4Cg$0IOl`cp#T%}rR)1Z{A z$#i!jx$QMAh@|tB%+y1bgU#K-&D-)h?DMtH{hSL6*T_51{rs47ZZVf<^4!m-m;~SA zHk7JJEGgl>)pNU^^Y_A$t8nDy!XPhk5N*~BJtZ4eO*-GpEu!?3M5YuiaAXRO>^B^1 zpcWBFRF7y@bKlI^FVNGHJAMi;VhcV*WkrVJYkv(py70Ky!H+t8CUs~6>d<_3QRAos z_uBm$2KMt_JXi?(tjt~UZw`%B32$!?Z?83x(6n1eJdsLD{kd5Fjuf|D@FtA+^%`dz z{I~!=+(v@gp%Lny2biZe;axtl6^mEM~)y zeK6!cjEI9Hsz;=4(y{chS!7IQ(Id*D2FaoZDJ3_u5q<0g8>6DQ&#O#-sThtNLK|y` zBdQeIsZgr8=?hR%NsTM&GzJ|o%33R1QI)}2<4c#{URo^GAYzDg%jL!&qPNyg*U8S* z!p`JdJA0}($e;72&ea`Y>WE7~LjS23|q zfg{?w+lZ4P}|egpH=>Xo|P(@|0Tl^%>>7d?p-ND zs`Op3qXurI8g~4~4>3fUz7|7#=nRM0IA_+fmb+odK~6gyiQ+Ua2QSC- zO*H8+`R&>z6}6HILzIP;>YXLaDv#IhZ40@dr#^36nBoqeA}2~ofKzCb)YEC+~k(}e=a+te&e5GlYOcP zBA6x}MR}|A%A-t`A~>2_NG)a6S4(h2&W#q%HrR2Kv&9gloXDjvhsd0nt*auD=TII& z?XUfo^MHRL--kFd1w;B_h-M_)n73bG4x$=FTDx=9AcsxDyC&(OD7q8y2IZKUS}8ry zDANNCCBtBm8bmt2htwcSaC=A%GD06~5r%}~ge_+a&Lq0t)L$8*4Yu;BXBu_xK^RiZ z8D%=21P#ut&HLPeAp=Z50$@m*sY24$e^`IN_G-NXjwG#>nVwcSGj?gUDhzcR=zxvm zB}pbDOQ|U{WTn_(NbDvS#^fqp#A=Gcb=M|RG!KjQF? zR@MJ$dSwORg=r`%fEuv3DMiLUq2qU_?wdOSYJsz*?ygR41&&}`0;p&)~|IRt@$Lo3hd0sH& zFbrveAvFaIae}d!4IIG`H#ghK1jV*m#A|bha;gm#@Qs5v16R=w&OFO!i}@UCgK77*iJ|9_dJU| z4^8sio-^YYoE^TPBmR9lQhtXa?q>UnncHBSPh8 zx)bnYHF0iwFZry!f1QFM?-6^ZwxLBXOrw{ah>B{@hFb{Ko(_+sJ3B zMG83*ERiQMhxjErC!l2X#j@^K_i}fUEvITs>|*J5Tjv0qWxPcUqX`E=!0Kih*Rue&^7fTRqyv<*ChEYITCur`baZ5Eq7};$KtN>D`}?vG8{RP z=G%2qTti>-8v4rY3D}OVF>_`H>m&;5 zjPslM7a>oqR9Lb27aSQ!lc=>05LbxBi6MPdI{K2z7~;MfhDeR;Fr*@pqz8^{Acn|x z)e{m&^0@D1F?p@U)JN>&b^qZl7~(9+5HmkLXpl}cNF#m(o8d{iCFqac@ZZYf4Ms4wAS(w@sW?A%?dV= z{7gsJ`!M7S81f?w`2**z*)U`oXSZd%{+th)J@Y{F%lZS)#Xn%kFEGR{K7YWF*{R>s z`L97d@7mb|Z$x z4s(iQOr6w`!!noEpK@8jkZtIYms}mK6dy&0{3i@?R>HYEyQFkqH>YK7M3&r0brSPj z)~Nj$dGmz&^jx-lIA5`i=?Qyd@8$ZM4yL}QgLF^X8@rGz{YyULB2yUBMyIQ}#IIb@ z&8E3C%ef1u9@QVR!`aC(<|v(g$rw@tKXiTH3q$Oll2m(O$O}lN($TL(-xB>#^hI`t zP&P&%uYFiGd?lUnhjUYl_7><0Qw%?J2HuEMz8=SVRO&tBe5So^a@h{-!)U;!icY#9 zu9pp$PtWQqubK3Jx*rVZW~R@_OnW^{~XeRmP56Ies*k-euqBT{MWh zHzkPkvdfs8{Ute+-baJD#m!3Mi!^`xDGYHB!W`=AkKx2)c=4Fr)s0*ZOMa+9cEJy; zlSQ~joOrbthU}##a_p~(jE8|Q$xE7O3%Q#|v#`=i8TK0`OKQfd0h;#KiC!S2>dZ$;G40&AS2C0K-!s7=zbiYE2e2NyC!zS7mkxSuK@SD{8J3|W%?JOxM8 zBA-K-&&g?hjTX6z7FjYqCv^q8o&7!>`8K&X{)qpt`*mkSj@c;-kY|Wa5wkhx{Q*P% zV3*-+YMah{eS-hQmuY{4-bQ6yEqY-{1N(!DVMr4jiGC&1F&8be5{|4Sr!_ZiD!MeQ ziE3?)wg)_3|KNGmki`i@j7?9%ecK2}Ho}n_I8u|FS}aD36mtz6W&(Z~RibBBF(>PA^gRtj_P`LY<`0>ZFrT%W z{Eu2>5#0DF^~MG}qQ}Xai68TX{E*I3&geN`VQ=g<_~F$(-`BJrl34$iEEk!t*qc7; z0yT{bI8B;Gvm#+u$vMKPp9eq6VTe=wkLX#xmgXnBl8@vy>f2VnukksrB~Gb!?D2J) zT)&c17_tP0EP)}BE%SRk13#oy4g4sk4rG^Z%*f~}c8@-_y=1Jm<=n-jM^l=nX@bA% zxh8(E8}Xm9gRZ!ul?v7&GLS>?!>xly;D_5j4p2W|K>b|pp(-^zS!}1T_ax7b5;VvW z`m$t5Jq&TGaGLGs&aCfD43Q#k{Wv~rLz%Aaw~}YcrCH9MJ2!X|c5H_oYsgy});~gj z$dAQ@6NLv-CEaZ7GJJdfz}@;soaMgZy!8HC3|Rp~=D?80$=TFS`opRIVI4iskDI&_ z=l->bkGYTjkRhMFIz~<87&Vb&j4>F}iw?0@XY*@@3~_d~u5LfiV#3;b zAxC6M@Fe=S#$kvjZF@=2bHa9amDS=esSa4d_%bO69X*ENhwildIJMWKJU(FO>j(Ip zyK(6$Gult-ZM}sCIm`9bt|PCAo~|R~Z+^>K$<8sorN*ga|0}5)Gh)f0AKO~VnZ=!f8l^cZDsy@J0*=kFyrV)cC@YCXZE2=XniX zA5AdiCaQVvU%T)*$umUN?WCXzdEybN7Ws?`#iF9$aZ35H_)|7oP4Zg%hCS`G$pDy1UVsJ(9g;C* zHhF_(QR?xLVYuUD%6LK1(g~D^*uJK%cN}`&Y6bhlv`hvHj(j zxB^YH|I|IG?y_^Q_I0{-Dj>+#M3UOKIMPR*-F~gA*>%jax0T0aNq>16Q;JS1ZpJI6 zh3MdIeN)D`mmyuS#M|Klh8%<;Tey-tfqg2M&w2(I_9u>n=CI#)5i=HU^$31ge>eB_ zYnsuMKfk5k*tyWczNWo&GUlG^lo-a1I`$@e~@$H|)bf4~pFs$F4QxT5Rx^&jM` z^lXtG<;;OUO0$zX)Za<-lc$-z*Sq(0npI!P=etNa#o?UD(cHY@*D&N1`&b_0O!AP~ zb2DZ#?muCOy#p6=U-1##IGFT@mGYRmT$!B3hEW{R1MTn1-z`#N%gt09aE7m@E;5~*+t0#}X54p6@V~kOKXgz$kZR|*V8~gbIpwg_Am`B_-&0F> zgYzfpEc7AIXP?{OlE*p$L(Y-Iy-HunZ=Ax)c@LLueNIvT#TI3?N^I%V#YDnH z)~lQe#f&3vv)YliBHG6lvn9jSRK21srNXN7MJuxdJ?Xol5$hts5j9DK zJzHz2yX&{HfGec)Z1x4|tzi}XJRH&M?>rp&SDK{Tz$C_s)f9%;^X}}tH?gN zwXllceiaI$iY$~9Ax;;{5v%HEwEVMBi`1vzm|3tvq6+>uGD8x+C>wY@-A&mCL)u}; zW=;*2uk%=*C(Lg~ABO!G7l`*>4x>Sa$zv7cm{82bYccK#HB7&5gdt5Zvg3^`3aB9`;)-;=s^?Y8;|j$9^V^%+XTYWi+CQi>MwtxSuYLW^8QtvG+Un>qGp z_@V57^(kJzKg_s+=kKSu$WQWG`;pr+Ce_cwkvv1(g}V$L@(B$21cun7|0Oy^ztp=h zWE_U9;VQA6E6GXrmp-5-vbeOEnuuD&E!$_{$eA=ru!U$N_B#q3Fw8N4CSTaJCz1syx`r$}Fo+>mLNp(7UWc`R=(Ghk*S&b9n!81Ag3LhSC{)9lnO)h;tfq;gSql&+gzygf$p_O7zcir zzDKv}ZlNmgRzzvEKIxB)AN};J=?c@8o5<`(xAnu`E`EPqI0!fa+J<*PTX`oMaFpz0 zby;QkJ~j^Cf**_77o}V~>iP32{<~-4$EMPGaO5eo=eKa;JkwR4r>wr zUc`y+GW<9JKV-;!;@tE-w8%xY$VKWR7jqoB2uCi$k$Z6D9>iEUxnpuYw}a5+8XS3q zLMbQfRZbP&owtw3ZCN9+-hPk%Ezh2Z$z$lZIL5T{Qu;id8R%qopobZPX=<>}6F6&_ zX%TZ;>JeFT5^fxL9iFhe;YuScSw}>s#*Qj0#^Z2goV?Zors%e(iowq0EZCg(g-=6| zAvDM(JO*_nH#@U0<#N0duA?gBHgT`7W@%bEud_Ucnu+!Ilc<>ksh-`Os`J~aPpsj# z9gb{=BM0EfNh&2*V2Qn84`9j6)XDW5h5n~*P!xsW$zp8PrE=k@2V zCyo=kTR9P;7O_Xmx%F_-%4-pSW1T3MKJr>B7-taOH`2}j9APfqnGk14J8(9+Lr-rd z3Zx;I$Lc4KHJtVUSTEZ{-Rw8wJ+=?Hf#51p3Nw}vyQdFN+$QFuKb(ZUOy`Hs+Fi`3 z-zd75%VlK@akqLo8pO%LqfDYZ0rFzXX^_=u5i99dLsTMHdFvEf#Jb2;{`DbR#4g}- z(;qRB_!ItGbCP4@s+39hD%A1r@mWjX9iqx= zh1Kb0_Y&EuGs;z^7DeJLb6<`ljWA?@T6-B?z?+#vbfV-=nrk0`B_qtXm*KayId|QY zBX@{O7}7?B-$^kWMe+=>+sW&zXZ2FpppTTjO!C6M*evGf_+clm{E#{7mpy5=;!L8$ zdN|^aLAQJygB*LgZG$9pb3L-ZCYO6V!ko9zTR7NI$Nd)Xn7sZ;X8w`510&* zdOP?kJJJ-#dX$LMl0T4=g=Q%$zwx=yXXF{kt3ChxHHVWZc%$ zmE(t5tG3j|*3WON4T4k>9k`e+eYXw`VlAq&d@CC85ZT8e_^}EFc?5oxl(vw^(icm9 zlqG(g$7k{GOgDfGz>x0QSBS;%;|N|SPjOY$8B5=+j3LuZhD_6uHBB{L6>=$2WFHh+ zm9~J)n{_04DBi*Y>nt2O0!MV@wU5_6tf+&82DzKEShLAv{X&iJ$KuCmkl2gxJKLmx zX1dB*+hEB0$;V`iWynu3ec>VNB=!tqBZ$C|0 z8mq?@u*AB%9=C3Fokv#e2-W$;#1$waNfuD+{ulf>PkawUoMN={zKk5yY~l~}gqkFB zTh>VANONw6B=(jGNIQA6I!-WU2k&62-gywOP&(*azC>_`*C83pM5%UL@i={UH=m^T8Gdg8~B^G^-D0seTVyC zi0b7M|8tt}!rGQ|>3uMyoil=uv)Rjj7~&aXWy)g>lgF}euLg!}Oxw@azHEXahtn2< z(&PeqX5u@X+-~E-vpUy(ewnQ6B2GOSL#(NLVzMS;K5GRhs5xknpKvnS1xK6!ks)?r zSphMNRSHKkhFneRWIh_i?qB^-7U7PvtLXl#8%4W{R?L_)<103h{G61?FK|Tn_w#VX z`NB6D@+CUtdwMEkKKKwG6Yg#3#MfvYSBgT0G*D&jhaqZ_8nj3a9I1gL!(>0Y%IA^8 z@(NVX-nxD4jj+>mh*PP47m*#WfFq;nCQX7PTd9<^rhct%I=4pB3`rw1?d`CnJ59Nd zy*t9SZYkHj&9J0}=!GYh=}av{%+th15jQ4!HMcj*IXh>=9I-QYCG_ymU-_Hwwd-jkA7M2{Z~88xj*7NEExkkw zvAH1pyL0&ddjnoz{X`Sr^%LS4+GZh2ME9)n)DQL&R@fVMFV**TNxVRfW)BS60z)40 z{YC%o#$5f}x=^M+GJcr#tAihP+$!O3CBMfp+ynHUxkGQ9-E+NU9eW{v2Q%j_Y(C%2 ziNTtOnnUd|!p09{0ETqKkanIK?J%SJk<8<7kn6D3MiAWFEJtrMI{pfgifLmy|Zckxk^ZoFy!QAy??dvJSEUcP#rs%*UF? z`T=ciuGYHWGxAtVU`Tjc%Md#rA9G5(k+Q}sVTc*zxqn$dxBijokBlED;Ku$}Q`FR_ zV8|2+9p+)Y|qa{b~Q5(63ALBhZG6hE#PVSl7F}Zx|AoREfM;^hEa^AZf!Yqd) z>JcYPth29&BNyobp36Lmo?Bhq+#O=C=Q?HvTIv0~PXA6d9MQj5k8!VpYLV+k>Wq3&)E*YW>Z{A?ceZ0p*axUyU2mL%JW1%%TZR!Ytj-@_2A z@1G=&EY5Kx^o2E&tY<6S6OW|+tx4uczF=G8FNFPMR$o*;n@}K6`O5Aa+zAOQPyng` zT@Blbn8@(AmxVN*$NQ~vN9aCc)jJ>Yf4lj4-F(~4&)%M+fg7koi&_q z^R)~3QjWesop$=`x}WPiV1BZSTGt2+ajvj|Ojb?u$gv+Q%VXICzLAYJO)$jmHD_SR zP4ZYb(IHQ8l70e1=BBBo)#wj92hF;Ega-LGosgzdUslGD738yYQG8rf4nww($2vOY zH2Qv~K%Py%2SYxGAz$Yh68WsFyf-?wR=@fVFO>PO?&FKHsAv&hzdse-fFdhq{0>LH z;MDdz>shL4MWcVTZRmvL+s+b!&Svj9rIagk>T=Y;s{JR%lmH8#q*R5$SQJJ z_2jU0zBtaQG`pHi=Qb4WB(ss{h^|Vtsm?xuDp|+uNPC(gS;tI!wKTBUixncPCLVj3Vu%9dA z$9fp@5<*-gB*jX?t&C6TYx3q>zQ;aUE7$Hd=|TnQ?d9BFZ*nizyX7S_d@qxa%ToMX z!plX*EQBEoVTc{L3t@7n~!Iz4imne|wj+c;>= zV}#DUV8{8icgjw^cGPJj&x&=k9}s%K>&p9-ScL|WA-bDff+0?*UxFdWVTfH>^=Oe* zXpwm-n^n&~!l&@#Ec|HZw)izeN?^zX=Fy!8S(9q%;nQOt>wQ$UwUAvf#9GL081j%> z$b0CJJ!FHg6)?nCo63JBue^*Mk-xf+_P7i?PQVP~Gh+3tg_HA%XE}y^$GuvlG;w4K zj!eOkg_AGPAQxcBo~hGNLyD9`ljXcug<`f#Jz^)%oAr-+i7KX=JsZcVLwvyA zzq4G`H<4YM$<1D=1JK0TlQqNw!fMICi1YBndbsl`vk7NaBCqBA;}L2k#@Q4fa_>yb zFF0W>V-n>&u^rVj6Ly?UeE=V@XYd0^Xos!epW0y`jMxV+RB>;H?=wcy$2i4V&(HT} z_HGd&o5K+3^AYO@5|p>uzIo%ezZTo4nyqK9)uz7Fk~qN8G|AH zFyt`(6f5bgxCcXaph27^+5$g*gCFm~4>zElVuOwD(Yht*-gb^jrDt@S+c9Xxt_XH~ zM0}f0NWqXfIUSNQ>w9#F3<-}S{kAL6 zAf4~HVdy~0ddrYr7*YvCoEqqYAzd(}3x>3%F3?+4VLidlOZ&6!(5ZqW+0`UYqh>WW zzz};q?fSe3-FxljatFrf zM{Ebx#*{6KipxuKWcrfWrRC(I-CtYahhdLcISjd$+)Cy`8|Melzz@I28ora*$rVm8 zd44qH_|XA7mg0Xo!0)t*a875mZ1cN2>3+DxBj=;nOt0>Pq(04Abtm0%9&Q+RS&XOs zh3nEbsRwM7+5|h!vOnZ0v5H509~wl49ETytVaV~^gke4TtW|JiUam9Cjf6+iMyXq{ z!@W}8+zfIOerzwf0z(eK5c_$prK>{h$1;yKhiCO77-B8tyIeipzFxahf+1#$b;tfT zDSuCIdsFR0?eQi*7EbPm8=t*eKe?I+hAf=SYY{mz1xFT6UWFfDzg$1L_T>&ZavF|Y zgCmciha}koN4DfRQcmUMQR?E_##G5%I)U{`(Y0|Sr%@->``M-5_GeYvr(XHz^|3oJ zq&Mxv&9sQ8XlK=K!4GE+&b|)4nA+iq8UD!fM@N!fNyfTd)i~=qvgT{f#U0_!Tz`OM6Bfvk#fc-<$E|Vv7x4}JP;*2$5BW65k9qWy&0|B9S+ftIRy~_)>#5@GqlTk5 zq7&oh-r~9+t|AiHsf>uF+#BI&sjFpdM~_LIYh>vmmtqf`8_uwYOL8y_hi3CHe zl7xb@gG+z-b?LWQi@$px3~}yf1cvBt(tzr$VJ`h9kG#Ek(T`;wYa@9q8FB`O+=L-> zslMshwhe}CgCToiNOTHXsk35N#IyRNqA8x$Q>g5P>9iz6=8(&pgATb-bZzom81fO0 zy!T)2L4zEfnh!(HO~07hJ-s%^kW&!En|;0?bNh~)PAKz}H)9dikZI!jubXKzGvj)Gw{k^^tH>ozqGmMQxLut(b*|?)Vpc0tBYmh5ClRY4 zi0&wj%(aKx)@H7I9ptp`a19*eEL{gf+)UDj643=dwovZ2coPrcM>Rq$g)1#EMK3Tl%P?Us%!-S3mV1PqUv_CdN^uNsIDxnq zcGx54#;TR@V=fIpr^M8hOCAmAH$HZQ%}~L44F#Zk7kZl|BDDuare<4my`BbIC%ng?1vi} zKY}5_k=roCP>by5{$rwadOaN30Y?tPk!x_|5gb`QwTCF5+ET!ga`cFG_8oBK9y27m zq_~Hq7>>Bfs)K2Dw@6gMk=F6LKS#$Z|J(^jrrF!=UR+Pp*4NF@WdleQ0vRN=JJNcO&)E8qZOHFzMMs5hhye&)C& zPX#RLAq!T`_4`t~n$M#mVRc(IVy(M5Wwzu6PMf+4{X-4dJONYp#b zS%rhjQ{LwUqx|@QEwxS&+KX%j#IL)L9M*AEl+)suQU$^;C><;9NZ6dRfo23F{QFX( z>~-j%YxsX;OYlX;M8y037&qV+GQ?RC`?KuvwZa~|a&<+KFcbXCI)008O7DJN^?ZO^ z8NFC`V|iL|Z`l|gi*?k}oh#J;Mo*lL6Gzb?FX#ndi~i81?JE3mGHFTbFuzDv)h>^y z)cLF}f*revpWw%hA~Uw~V+Z#KiPMum6+N2V1w$6Wkjt;Oph1q3$GVUh@-bRu30kBy zr$wx-FX6VEn?5M3Up*t6wVF)U{!~?8L1)&_aO6W?ZJyd@vr+wfrn2s$Mb`YU3~}d= z`yI9?9U?>AfxR?!c{(j1M@rzvVqz28qq)=yYzqw0c`r6e%!42ExSFiuDxy!)KCT|e zIfY(Il~t>&azsrsiYBo}vK3d1&QxXJiWcdFA}SI|5*%qyu8qMF^Dr%Nq!*5Og|t@e zx%*eHoqNeBIU6BE{(}x%CpBIWdciw6E=$xUF;}4{inAoTjb!|=u8{Gg2JI1Nd*@HQ zJ~yE_)I83lSj&&AwHc!t__2|hJ!dYQjXcAQ4oueP74mgC2={;YD;1+eIAx#dd?~edPvr>odQm{MxU4cdDY;%4PRe)X%M; z%MS_C!mSL7bffL6_OaM*>M+k7~?Z1P^g3N z97ucA``9Yg2}3$zNGA+wb?5#O2%AN>m6H;VSSo}5l@;dF8~H)YsddI@&yW7=>P zVO8CVh}%IPz>kwCku^|c91U_F4f180E1X18uYe)D(p*T)7RHX~=sD!%kEUFMSXNakO}^5 zBy(@}WgxkB>HavHSmMTubySBvVe9=-0ZV$Q#8=bD<+XetnqVF*xdlg@SB%Wo5jYYo zdGHoX*6?>-9{+^~`JQmneHp!0v#FQQrrx20$}-sT=d5j+4W3 zIzt*-yV3197_ph(TuTbwMIttHZ|FE;l%=ri)(E~tH(W5tw{^G*hN#6hqzsiCaf2Ze z#Eh0*S}GIYtCE}Gz3cc*Ha?%=x7iLsmf~z;_jViJnl*Ua=ue=x$rKE6<}~`Ttc9G( zF~rR^I<@UhlSy`#+tr~OwnC>;zDz9QS-lH({6yS&Rh$J#Qr z8iw44A>W}xWJoDmM23{ok+pkB&Q zf1!hA8SZr7;{tydEwZNMLu%?jqC@mLS%wa=rk>@p3K?=B=@1!GmK<6i(BmsdN>bx{yZirhrAE`9%BI8sI~ zW;55sUN~Zn*s91zuHwJae`G~MCqcWN^!(TZM}8xm!_(7NP4XV0KDhulqA&JMe(Z%I z=7rqY9_)zO3eWd7i5gBeMxU%I$c&LaV$Q8N2kA^?87vuLUt>3$xbzXx2Q0k6=E4yD zMOMO)l`v!*3^7|K4Ro`TA~GUau^x8pNV+3qhmMK%ht1_9p71?dmpKF5LLJGqt{g!m zY$y_Ceggd?XXAHtAR z@ZCEcFb+M93L$|VbKd*ry z!+&;;cl}v0-tcEnPK#8N)6%QYOpGVw$heq+>4pbU{j~y|@kEwnJTd3>l#nz2d(10% zqBdLQS-Fa9-2@aF4P1A8xvBXV26CmGrXIc{dD04#SX@ zcq?8^I|AC0H|O#xdwR1x))VqrE0epiWcg zTQr}}-u+A&UPk5nO4Bv#1`L@4L%y0Zjuu(N_BZ!WJ)l1;=G2`Ju`esDhp0oWha4bV z;l^wy$=o*dfF56?1a>Tj9h>OhF?-$&KkV+bvU)3>NNc&Gc>3%iA`5ONygtW~eZ0>q z>m^R7cJ5Tu^K0LhJB!?*I!Z6_+j^uCMKVB@q%57K&B;g+Gg{V*bz#)KV;h@t@4*o} zY@O0@cH0^53yCF8k4IQ5`47U{yM3|xY?)EAE^Uu+)D;TZ;bdMHq>vhIaLaSP^_Vuk zdRzJ~>^a`d+=_FNBg}nwGymP5JiwaL9(j2(0#BBrPxPo7BN~$9uyy&lFk~fkSV`xw z8pC;%V9COyJ7kBMuK4%p4YQ_i220X~Lh`#*nfZN5SUGDAJ!&AE$zeH%qd&mF>{0l! zlj)sH@M9GnbZ$JiK7J*at6IRLFE{QIc6uA}tY~GER4aL`t!b;&5$daS!?J(51)7UIh&RJ ziBnM#&+OGO#LY9>0z`~R#(i(JoXk)65vy4e{|$XSNPo!(Y_jo^v< zS@mZ6dqjtk)#0da9O8;-u4OABJ*v31bJZIltN@qMiCV^_N*P^ZBYadlu@0ULa+TEY z!wgOjm3TXXRU^llE~)3bJ}+^k9<5-_`%O&}ENP~5YZDyV1V@_TNHcx9N6;h2Sy-Zv z*Hhk}NA5~5mJiOZ!g&tzfGWbQzNe;YuYt>~7Ot#He8ECPK%Nd<0kg zKN0?aBd=N1E9Ud;(rqNFps#A9Gm%4rcxxp($p-fE=LEMZY9Ni!qn)YI0lMu*o_Eo` zy%EKE6Nb3I(mgXf@HKga4%tFaZ}elGDad2(O`YU+0`7(%=irB@7*(>T9-qC7;KoP9 zx5Q5=ca`VIt{gw|8e|W7tfSH`}7-BwaE&Aj0+>~K>pnR6LQ|)FtB=%OVn4#N= z9V_>7Ui=7#xTWVY+ulBfAwR>A$r&e0zJMV==KQjj!4T&|?Cl+AJ|t@Dt(-W`S~S8C zCjmw{q3Vtlq0^ol?A(Of%+=>es;rvT@QUKe)9m*l!Yb+p!j2rhE;5#w-?~oN`RNqF zRyfl721jbiX?2p*8l+>Zin>T643Q!uoTisTkl3@^#TC4UE3%tKwsEq*2uIelS;_56 z8ArC{G>H@B{{ctzwLJ|tb|ii{S5kutkRRc_b@+A8DyuDo;!yKAV__vG&iPT7vBq!T zv2!dVY;Wvl_FK=Ec39G$V@c)%rax5&p^v@l$!>s+C<^<$>>A#N#@I{9i^7mC{+qX7 z`PjpR-d9mYk0}u6&+U1(-=G7A*u(2@${Myjn8rs43Q((;D>QKMKDB;oQ5N( zVaRE;$Tc|f2raUFs+712L%xO|Yl#!;KIl-zwa(iB5!K%+axZRFbR*UvT=AniQ+8&MtE5-U=xeay${F8h*qai?ieAm;9;BO=bKDcJ#uGh*~CYB6Q6)HouOHEu%@Ns8vF<1XIE> zzzGk3YduN5*>f-fKl=DBs62umt-Rd{LCgsTl?R}R-B@l4X`o|b7=|2%A#>RgauJ5; zuc(K!y8)alypZ~_tc4Wju`Z@=aw~9VQ9Z>3Lk^~Aa1qb!kBD74-J$l79T`80QXM4I zAcYLMfDQ?cnA5UHE8I>_~XZn znv&mP$cOBo`UZyR+pE`!bL!3*x}$V_<|s80uNZw?F{)B_+$!sXG?hHUX?1|;h9B*T zAI)5Oj-Wq0k)GvB;)&DD_boEtPq|uTAxGqi9MQwI9xbvDh8*X#Y8R-J%^T1nnI5U2 zD&q8rp1*@kkl3x2RoF-9OkGN?*lBAsNZ}0crbD}H-M+pfIf(>E^Z|(|re~`d?cp4W z9*uAEBRkPY&Pv@8nW12Z)fdT8nQrEZdWjCgeew1kJIB(^Ok_8+-|euZF|j0g(hW~C z53mmMU3w5VP>b&;Y9NT3!dq}8W>I8`Av;2Q*j+rAF5{@w=wcG}{P4qfeqUu4Osj)w z&Vks+YaiBTa#&@!C=cK~(~Y;T8pPT1s+57Wzv=<(IGOTQorxK8V*$)qkhY-L@@%N( z-`z%7K`o>LhE(vV%Lq9VIV*KX7QWBTQ%7J(GtY`mJS$3IND0phoqMmq5M3ADbnB)< zH%;l%d!FgSPnZsI)6F+@f!GaVMNdV&h*QxCPDjt!IVD5fo0Boby`JSv>8k8~r5#Ks zH(z;|VZ#X+AwTxNdIUor@#_401V^s%>dnV(-aZFAz9V*{K|Y2bOQyG=MYbd@5**nA zN6OJ7_G;OowUDWii&WP2{5_1jZ!y#AZa%!jM%<-rLhXKjW@6p*rinoq(wfsE)6OZ1H?)AaWeH@*}wZis-K@t{4iT)4P+?QVXVjW!H+6_s&=mL*8Z2K>wjpD zUKkOvm3t>{B09L&O(w!^f<|y9(J?#tc+yL7MfwXBwp@^vdL9<9 z$YAv)Wj6qsm!^qP9eTT|a*c30SjtJE1r5@R1{q`5R0A5sy=24qV{J@5y=!0hV_8c# zkL6_R6nQK=I)Wi)T=(*5c%zfa(C))%G*=L)yd?=#2i--9JvlhDpD7y69svW=#4T+UQ4gW zbxg9i!x6VwWs@Vl=#fgUl3iTEb!0r8I-xoaxpt z-kCgSDy)5&yNa5J?1+4onj=#km8rfEQx?|r%@esT-m|`QkY2AV;YVef|89gO5y2C+ z$q1oGRY&stvKDVQfR%778sV#}B8kkFlZ>HF{4>)Yb{U&(%kxA3MC*n| z`tL?UxgZ~c6Y}Eic$*)Ka}0UF_K+(uDzF`P|Rv-?MlIGmP8BIoM%*2S0WbAH$H+H#o8!J@P2m^Sfj6Xqpt+%cRH?oELS? z8pa{1f$0FZux}L?c>cVtga{tl>Y^BDK^-D#k0(Br!7*nSHY^W^Fuqn`tqN z6Fl)m{&x6pJ?SdwytJ9A5%>{Vt92+3YrfGrWbN1J&P6aqebUaE+Kks=x>EL~yp3C# zVouwvQV&|hE~!k91V{3@t*Dbke(NckM5p;%u;dnX_bi_B_EX+B4|Z5nu|{vLKKKz{ zEcS%CTQFud%q>aAvbXqg2R7V+6?c-;x?2PLQs^Yp!P{}~+lI~nx*-}FPrR={uQ*{| zfgY>iH)g+@wIM6!Ms4b2tKv6mRmmq!Irmeg<^Fr@O&Mmp{6mKfoYU5W;2L4)+Z zSjaBq(>(g_x!gl7Z;^ zVjd*p$9{TxR};^sgCXYBH=)cyU38Mk?5 zt%f1{d8OTd<;m?!UVV2t#VsgVG2=kV4>PWyMHaJ1y%{ZX3oWt=&))FM`hMn4yiVMA zZB1lr(jsOzu8@i>jtJgKZa2P_FF~X%3fjlk+CDD^XzVNLQi(wp+X(A zdYieoPfS(h{EFA?_7r1WyA65bwOs$Nyl0EGlRI4RGY@+^z3dnclBh_|Bux^^BaW@-?pc$^J9 z!b!j$s|WDoWO~$VdGvh-s7Q_L@Z&n0qufH+gO2D)TWvRS@1q_khwD755!v7K`ooV8 z4f22uhF4(7$=n8!HEDxLJqp;)j$82K2%R1(WH&XRM1yQE`HIueW1hjgcqaRdcK7W$ zcCBR$u^#dpJz4IC-olh_Ic`Co&f*p4EAKLVI6(wImctPFVdqLd=JUS7cm^|!+le2` zVaW0vL$)M_s71_aMQx;RU$VkW&mJFvJ+(Bf8;>y+u;QKBIM9DJRm&+ZwWN=uWp+qeoN- zR=R^D)lB^j<#Joc3+f~pOJsy0PqLWDq+-0^`9bUR&VU?;A=CV+mzQ5#Nav;Lb5HQKo%U%YhtHHIDUWi?i`aKtnf8P%PaeI+XpoJ}ggBf2gt&ypi%!0p1hLWbi3Q35wSv2NmuV^>Zy z8S&;f__33#N}eCh$>%#b5=;q>xQobqmvxiiNJVazM2Cz{IHG&_ARMV8^RkW#aC_<` zYK0+vX}@kS9PtY671K(#F7Z|xo)mDzO??;P$VKYx7wOm1OUiCqyP@V1p61Oa<@u4- z%I%8H`kKsHjlJfEHER39tTO~ZoY{+T7Sj3ed<0XvlSUciYJ#=sC(+*MEt4i<*{PM zV=<5BgY;-ukh2aDbm;Fol|SzP))RlA>g#;za(`xDRLMLd%)Cx-lyW5|c8 z9%5gXn|Ch2kUh!iVHc+}C#~X@X1BcEj*%%==^LKb3S&O+%i%9a^C I@8^ca~&%E^F_Fp?8Tr!rtr` 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 literal 0 HcmV?d00001 diff --git a/spec/fixtures/pages/audio.html b/spec/fixtures/pages/audio.html new file mode 100644 index 0000000000..0fda8e7075 --- /dev/null +++ b/spec/fixtures/pages/audio.html @@ -0,0 +1 @@ + diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index c60af8d74d..28b25a2266 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -379,3 +379,14 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/onmouseup.html" webview.setAttribute 'nodeintegration', 'on' document.body.appendChild webview + + describe 'media-started-playing media-paused events', -> + it 'emits when audio starts and stops playing', (done) -> + audioPlayed = false + webview.addEventListener 'media-started-playing', -> + audioPlayed = true + webview.addEventListener 'media-paused', -> + assert audioPlayed + done() + webview.src = "file://#{fixtures}/pages/audio.html" + document.body.appendChild webview From da9c202d86fc4ce9d9567bf19d2a2345f4ec139f Mon Sep 17 00:00:00 2001 From: Adam Lynch Date: Sun, 20 Dec 2015 18:34:05 +0000 Subject: [PATCH 308/411] Desktop Environment Integration: typo --- docs/tutorial/desktop-environment-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 868dc449ee..c03ba2f4d6 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -39,7 +39,7 @@ however, that it does not need to be pinned to the Start screen. To use an image in your notification, pass a local image file (preferably `png`) in the `icon` property of your notification's options. The notification will -still display if you submit and incorrect or `http/https`-based URL, but the +still display if you submit an incorrect or `http/https`-based URL, but the image will not be displayed. ```javascript From 520b5373628b1a6cddee70e11b2b7641fd1cea53 Mon Sep 17 00:00:00 2001 From: Adam Lynch Date: Sun, 20 Dec 2015 18:34:57 +0000 Subject: [PATCH 309/411] Desktop Environment Integration: rearranged sentence --- docs/tutorial/desktop-environment-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 868dc449ee..bf3e4947af 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -49,7 +49,7 @@ new Notification('Title', { }); ``` -Keep furthermore in mind that the maximum length for the body is 250 characters, +Furthermore, keep in mind that the maximum length for the body is 250 characters, with the Windows team recommending that notifications should be kept to 200 characters. From 68733eb8df60f1942628920eb1428f3c033a55c6 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 21 Dec 2015 01:32:08 +0530 Subject: [PATCH 310/411] spec: serviceWorker registration with file scheme --- spec/chromium-spec.coffee | 22 ++++++++++++++++++- spec/fixtures/pages/service-worker/index.html | 18 +++++++++++++++ .../pages/service-worker/service-worker.js | 9 ++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/pages/service-worker/index.html create mode 100644 spec/fixtures/pages/service-worker/service-worker.js diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index c456a6919b..5559c36eba 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -5,7 +5,7 @@ path = require 'path' ws = require 'ws' {remote} = require 'electron' -{BrowserWindow} = remote.require 'electron' +{BrowserWindow, session} = remote.require 'electron' describe 'chromium feature', -> fixtures = path.resolve __dirname, 'fixtures' @@ -56,6 +56,26 @@ describe 'chromium feature', -> it 'should not be empty', -> assert.notEqual navigator.language, '' + describe 'navigator.serviceWorker', -> + url = "file://#{fixtures}/pages/service-worker/index.html" + w = null + + afterEach -> + w?.destroy() + + it 'should register for file scheme', (done) -> + w = new BrowserWindow(show:false) + w.webContents.on 'ipc-message', (event, args) -> + if args[0] == 'reload' + w.webContents.reload() + else if args[0] == 'error' + done('unexpected error : ' + args[1]) + else if args[0] == 'response' + assert.equal args[1], 'Hello from serviceWorker!' + session.defaultSession.clearStorageData {storages: ['serviceworkers']}, -> + done() + w.loadURL url + describe 'window.open', -> @timeout 10000 diff --git a/spec/fixtures/pages/service-worker/index.html b/spec/fixtures/pages/service-worker/index.html new file mode 100644 index 0000000000..3495827a23 --- /dev/null +++ b/spec/fixtures/pages/service-worker/index.html @@ -0,0 +1,18 @@ + diff --git a/spec/fixtures/pages/service-worker/service-worker.js b/spec/fixtures/pages/service-worker/service-worker.js new file mode 100644 index 0000000000..854b3558a9 --- /dev/null +++ b/spec/fixtures/pages/service-worker/service-worker.js @@ -0,0 +1,9 @@ +self.addEventListener('fetch', function(event) { + var requestUrl = new URL(event.request.url); + + if (requestUrl.pathname === '/echo' && + event.request.headers.has('X-Mock-Response')) { + var mockResponse = new Response('Hello from serviceWorker!'); + event.respondWith(mockResponse); + } +}); From 3b3c5a0edb4bc6be1ee975ad6aebdb9fac6bac6d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 21 Dec 2015 10:09:20 +0800 Subject: [PATCH 311/411] Update brightray for #3831 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 58ff3de971..5bfdec0985 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 58ff3de971bf5b8cbaed77bf6a2a0fd6199783e8 +Subproject commit 5bfdec0985c2412c6312749154a8adb4b1f79b8a From 99a661e2d256f6c56b9f7d95219d4ae000f0bb87 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 21 Dec 2015 10:52:49 +0800 Subject: [PATCH 312/411] Code cleanup of browser_mac.mm --- atom/browser/browser_mac.mm | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 48050d6201..4944e7d7d8 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -43,7 +43,8 @@ std::string Browser::GetExecutableFileProductName() const { } int Browser::DockBounce(BounceType type) { - return [[AtomApplication sharedApplication] requestUserAttention:(NSRequestUserAttentionType)type]; + return [[AtomApplication sharedApplication] + requestUserAttention:(NSRequestUserAttentionType)type]; } void Browser::DockCancelBounce(int rid) { @@ -73,14 +74,22 @@ void Browser::DockShow() { BOOL active = [[NSRunningApplication currentApplication] isActive]; ProcessSerialNumber psn = { 0, kCurrentProcess }; if (active) { - // Workaround buggy behavior of TransformProcessType - for (NSRunningApplication * app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { + // Workaround buggy behavior of TransformProcessType. + // http://stackoverflow.com/questions/7596643/ + NSArray* runningApps = [NSRunningApplication + runningApplicationsWithBundleIdentifier:@"com.apple.dock"]; + for (NSRunningApplication* app in runningApps) { [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; break; } - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + dispatch_time_t one_ms = dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC); + dispatch_after(one_ms, dispatch_get_main_queue(), ^{ TransformProcessType(&psn, kProcessTransformToForegroundApplication); - [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + dispatch_time_t one_ms = dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC); + dispatch_after(one_ms, dispatch_get_main_queue(), ^{ + [[NSRunningApplication currentApplication] + activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + }); }); } else { TransformProcessType(&psn, kProcessTransformToForegroundApplication); From 6c26aa8d050c2d2f840d0d47b8d0184f6a3cdc7f Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sat, 19 Dec 2015 02:02:30 +0900 Subject: [PATCH 313/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/native-image.md | 2 +- docs-translations/ko-KR/api/session.md | 26 ++++++++++----------- docs-translations/ko-KR/styleguide.md | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs-translations/ko-KR/api/native-image.md b/docs-translations/ko-KR/api/native-image.md index 917dbb3a71..80be2beeaf 100644 --- a/docs-translations/ko-KR/api/native-image.md +++ b/docs-translations/ko-KR/api/native-image.md @@ -123,7 +123,7 @@ var image = nativeImage.createFromPath('/Users/somebody/images/icon.png'); ### `image.toJpeg(quality)` -* `quality` Integer 0 - 100 사이의 값 (**required**) +* `quality` Integer (**required**) 0 - 100 사이의 값 `JPEG` 이미지를 인코딩한 데이터를 [Buffer][buffer]로 반환합니다. diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index 5728b54386..c857e11157 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -102,14 +102,14 @@ session.defaultSession.cookies.set(cookie, function(error) { #### `ses.cookies.get(filter, callback)` * `filter` Object - * `url` String __optional__ - `url`에 해당하는 쿠키를 취득합니다. 이 속성을 + * `url` String (optional) - `url`에 해당하는 쿠키를 취득합니다. 이 속성을 생략하면 모든 url에서 찾습니다. - * `name` String __optional__ - 쿠키의 이름입니다. - * `domain` String __optional__ - 도메인 또는 서브 도메인에 일치하는 쿠키를 + * `name` String (optional) - 쿠키의 이름입니다. + * `domain` String (optional) - 도메인 또는 서브 도메인에 일치하는 쿠키를 취득합니다. - * `path` String __optional__ - `path`에 일치하는 쿠키를 취득합니다. - * `secure` Boolean __optional__ - 보안 속성에 따라 쿠키를 필터링합니다. - * `session` Boolean __optional__ - 세션 또는 지속성 쿠키를 필터링합니다. + * `path` String (optional) - `path`에 일치하는 쿠키를 취득합니다. + * `secure` Boolean (optional) - 보안 속성에 따라 쿠키를 필터링합니다. + * `session` Boolean (optional) - 세션 또는 지속성 쿠키를 필터링합니다. * `callback` Function `details` 객체에서 묘사한 모든 쿠키를 요청합니다. 모든 작업이 끝나면 `callback`이 @@ -142,7 +142,7 @@ session.defaultSession.cookies.set(cookie, function(error) { false입니다. * `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 false입니다. - * `expirationDate` Double __optional__ - UNIX 시간으로 표시되는 쿠키의 만료일에 + * `expirationDate` Double (optional) - UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 시간입니다. 세션 쿠키에 제공되지 않습니다. * `callback` Function @@ -325,8 +325,8 @@ session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, `callback`은 `response` 객체와 함께 호출되어야 합니다: * `response` Object - * `cancel` Boolean __optional__ - * `redirectURL` String __optional__ - 원래 요청은 전송과 완료가 방지되지만 이 + * `cancel` Boolean (optional) + * `redirectURL` String (optional) - 원래 요청은 전송과 완료가 방지되지만 이 속성을 지정하면 해당 URL로 리다이렉트됩니다. #### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)` @@ -349,8 +349,8 @@ HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener `callback`은 `response` 객체와 함께 호출되어야 합니다: * `response` Object - * `cancel` Boolean __optional__ - * `requestHeaders` Object __optional__ - 이 속성이 제공되면, 요청은 이 헤더로 + * `cancel` Boolean (optional) + * `requestHeaders` Object (optional) - 이 속성이 제공되면, 요청은 이 헤더로 만들어 집니다. #### `ses.webRequest.onSendHeaders([filter, ]listener)` @@ -392,7 +392,7 @@ HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener * `response` Object * `cancel` Boolean - * `responseHeaders` Object __optional__ - 이 속성이 제공되면 서버는 이 헤더와 + * `responseHeaders` Object (optional) - 이 속성이 제공되면 서버는 이 헤더와 함께 응답합니다. #### `ses.webRequest.onResponseStarted([filter, ]listener)` @@ -430,7 +430,7 @@ HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener * `timestamp` Double * `redirectURL` String * `statusCode` Integer - * `ip` String __optional__ - 요청이 실질적으로 전송될 서버 아이피 주소. + * `ip` String (optional) - 요청이 실질적으로 전송될 서버 아이피 주소. * `fromCache` Boolean * `responseHeaders` Object diff --git a/docs-translations/ko-KR/styleguide.md b/docs-translations/ko-KR/styleguide.md index dd02629c16..8327cd3c1d 100644 --- a/docs-translations/ko-KR/styleguide.md +++ b/docs-translations/ko-KR/styleguide.md @@ -57,7 +57,7 @@ Electron 문서 구조를 이해하는 데 참고할 수 있는 유용한 도움 `methodName(required[, optional]))` -* `require` String, **필수** +* `require` String (**required**) * `optional` Integer --- From 39e615ed87da2f5a8eee87ca9b5b027f740ebf10 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 17 Dec 2015 22:57:56 +0530 Subject: [PATCH 314/411] webContents: adding findInPage api --- atom/browser/api/atom_api_web_contents.cc | 51 +++++++++++++++++++ atom/browser/api/atom_api_web_contents.h | 8 +++ .../native_mate_converters/blink_converter.cc | 16 ++++++ .../native_mate_converters/blink_converter.h | 7 +++ .../content_converter.cc | 20 ++++++++ .../content_converter.h | 7 +++ docs/api/web-contents.md | 49 +++++++++++++++++- 7 files changed, 157 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 82227d7032..8c24ea1a82 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -49,6 +49,7 @@ #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" #include "content/public/common/context_menu_params.h" +#include "native_mate/converter.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/http/http_response_headers.h" @@ -426,6 +427,29 @@ bool WebContents::OnGoToEntryOffset(int offset) { return false; } +void WebContents::FindReply(content::WebContents* web_contents, + int request_id, + int number_of_matches, + const gfx::Rect& selection_rect, + int active_match_ordinal, + bool final_update) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + + mate::Dictionary result = mate::Dictionary::CreateEmpty(isolate()); + if (number_of_matches == -1) { + result.Set("requestId", request_id); + result.Set("selectionArea", selection_rect); + result.Set("finalUpdate", final_update); + Emit("find-in-page-response", result); + } else if (final_update) { + result.Set("requestId", request_id); + result.Set("matches", number_of_matches); + result.Set("finalUpdate", final_update); + Emit("find-in-page-response", result); + } +} + void WebContents::BeforeUnloadFired(const base::TimeTicks& proceed_time) { // Do nothing, we override this method just to avoid compilation error since // there are two virtual functions named BeforeUnloadFired. @@ -902,6 +926,31 @@ void WebContents::ReplaceMisspelling(const base::string16& word) { web_contents()->ReplaceMisspelling(word); } +void WebContents::FindInPage(mate::Arguments* args) { + int request_id; + base::string16 search_text; + blink::WebFindOptions options; + if (!args->GetNext(&request_id)) { + args->ThrowError("Must provide a request id"); + return; + } + + if (!args->GetNext(&search_text)) { + args->ThrowError("Must provide a non-empty search content"); + return; + } + + args->GetNext(&options); + + web_contents()->Find(request_id, search_text, options); + web_contents()->GetMainFrame() + ->ActivateFindInPageResultForAccessibility(request_id); +} + +void WebContents::StopFindInPage(content::StopFindAction action) { + web_contents()->StopFinding(action); +} + void WebContents::Focus() { web_contents()->Focus(); } @@ -1048,6 +1097,8 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("unselect", &WebContents::Unselect) .SetMethod("replace", &WebContents::Replace) .SetMethod("replaceMisspelling", &WebContents::ReplaceMisspelling) + .SetMethod("findInPage", &WebContents::FindInPage) + .SetMethod("stopFindInPage", &WebContents::StopFindInPage) .SetMethod("focus", &WebContents::Focus) .SetMethod("tabTraverse", &WebContents::TabTraverse) .SetMethod("_send", &WebContents::SendIPCMessage) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 0173b9de0c..d78455608d 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -110,6 +110,8 @@ class WebContents : public mate::TrackableObject, void Unselect(); void Replace(const base::string16& word); void ReplaceMisspelling(const base::string16& word); + void FindInPage(mate::Arguments* args); + void StopFindInPage(content::StopFindAction action); // Focus. void Focus(); @@ -187,6 +189,12 @@ class WebContents : public mate::TrackableObject, void RendererResponsive(content::WebContents* source) override; bool HandleContextMenu(const content::ContextMenuParams& params) override; bool OnGoToEntryOffset(int offset) override; + void FindReply(content::WebContents* web_contents, + int request_id, + int number_of_matches, + const gfx::Rect& selection_rect, + int active_match_ordinal, + bool final_update) override; // content::WebContentsObserver: void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index d192018da0..71a2ac9778 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -282,4 +282,20 @@ bool Converter::FromV8( return true; } +bool Converter::FromV8( + v8::Isolate* isolate, + v8::Local val, + blink::WebFindOptions* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + + dict.Get("forward", &out->forward); + dict.Get("matchCase", &out->matchCase); + dict.Get("findNext", &out->findNext); + dict.Get("wordStart", &out->wordStart); + dict.Get("medialCapitalAsWordStart", &out->medialCapitalAsWordStart); + return true; +} + } // namespace mate diff --git a/atom/common/native_mate_converters/blink_converter.h b/atom/common/native_mate_converters/blink_converter.h index 17bb108d34..9f58659a3a 100644 --- a/atom/common/native_mate_converters/blink_converter.h +++ b/atom/common/native_mate_converters/blink_converter.h @@ -6,6 +6,7 @@ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ #include "native_mate/converter.h" +#include "third_party/WebKit/public/web/WebFindOptions.h" namespace blink { class WebInputEvent; @@ -80,6 +81,12 @@ struct Converter { blink::WebDeviceEmulationParams* out); }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebFindOptions* out); +}; + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index 15a57dea5f..41d19e3969 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -4,6 +4,7 @@ #include "atom/common/native_mate_converters/content_converter.h" +#include #include #include "atom/common/native_mate_converters/callback.h" @@ -97,4 +98,23 @@ v8::Local Converter::ToV8( return mate::ConvertToV8(isolate, dict); } +// static +bool Converter::FromV8( + v8::Isolate* isolate, + v8::Local val, + content::StopFindAction* out) { + std::string action; + if (!ConvertFromV8(isolate, val, &action)) + return false; + + if (action == "clearSelection") + *out = content::STOP_FIND_ACTION_CLEAR_SELECTION; + else if (action == "keepSelection") + *out = content::STOP_FIND_ACTION_KEEP_SELECTION; + else + *out = content::STOP_FIND_ACTION_ACTIVATE_SELECTION; + + return true; +} + } // namespace mate diff --git a/atom/common/native_mate_converters/content_converter.h b/atom/common/native_mate_converters/content_converter.h index 7edee24fa1..a5708e022b 100644 --- a/atom/common/native_mate_converters/content_converter.h +++ b/atom/common/native_mate_converters/content_converter.h @@ -8,6 +8,7 @@ #include #include "content/public/common/menu_item.h" +#include "content/public/common/stop_find_action.h" #include "native_mate/converter.h" namespace content { @@ -32,6 +33,12 @@ struct Converter { const ContextMenuParamsWithWebContents& val); }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + content::StopFindAction* out); +}; + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_CONTENT_CONVERTER_H_ diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index abb038ac1a..4d7907c0f8 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -229,6 +229,20 @@ Emitted when media starts playing. Emitted when media is paused or done playing. +### Event: 'find-in-page-response' + +Returns: + +* `event` Event +* `result` Object + * `requestId` Integer + * `matches` Integer __Optional__ - Number of Matches. + * `selectionArea` Object __Optional__ - Coordinates of first match region. + * `finalUpdate` Boolean - Indicates if more responses are to follow. + +Emitted when a result is available for +[`webContents.findInPage`](web-contents.md#webcontentsfindinpage) request. + ## Instance Methods The `webContents` object has the following instance methods: @@ -420,6 +434,39 @@ Executes the editing command `replace` in web page. Executes the editing command `replaceMisspelling` in web page. +### `webContents.findInPage(id, text[, options])` + +* `id` Integer +* `text` String - Content to be searched, must not be empty. +* `options` Object __Optional__ + * `forward` Boolean - Whether to search forward or backward. + * `findNext` Boolean - Whether the operation is first request or a follow up. + * `matchCase` Boolean - Whether search should be case-sensitive. + * `wordStart` Boolean - Whether to look only at the start of words. + * ` medialCapitalAsWordStart` Boolean - When combined with `wordStart`, + accepts a match in the middle of a word if the match begins with an + uppercase letter followed by a lowercase or non-letter. + Accepts several other intra-word matches + +Finds all matches for the `text` in the web page. + +### `webContents.stopFindInPage(action)` + +* `action` String - Should be called with either `clearSelection` or `keepSelection`. + By default it keeps the last selection. + +Stops any `findInPage` request for the `webContents` +with the provided `action`. + +```javascript +webContents.on('find-in-page-response', function(event, result) { + if (result.finalUpdate) + webContents.stopFindInPage("clearSelection"); +}); + +webContents.findInPage(1, "api"); +```. + ### `webContents.hasServiceWorker(callback)` * `callback` Function @@ -462,7 +509,7 @@ size. * 1 - none * 2 - minimum * `pageSize` String - Specify page size of the generated PDF. - * `A5` + * `A5` * `A4` * `A3` * `Legal` From d162180196a87f0029aa0420d1e15f4157c62d3c Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 18 Dec 2015 04:40:42 +0530 Subject: [PATCH 315/411] add api to webview --- atom/browser/api/atom_api_web_contents.cc | 23 ++++---- atom/browser/api/atom_api_web_contents.h | 9 +++- atom/browser/lib/guest-view-manager.coffee | 7 +-- .../native_mate_converters/blink_converter.cc | 1 + .../native_mate_converters/blink_converter.h | 2 +- .../content_converter.cc | 4 +- .../lib/web-view/guest-view-internal.coffee | 1 + atom/renderer/lib/web-view/web-view.coffee | 2 + docs/api/web-contents.md | 44 +++++++++------- docs/api/web-view-tag.md | 52 +++++++++++++++++++ spec/fixtures/pages/content.html | 16 ++++++ spec/webview-spec.coffee | 16 ++++++ 12 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 spec/fixtures/pages/content.html diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 8c24ea1a82..995774376e 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -225,7 +225,8 @@ WebContents::WebContents(content::WebContents* web_contents) } WebContents::WebContents(v8::Isolate* isolate, - const mate::Dictionary& options) { + const mate::Dictionary& options) + : request_id_(0) { // Whether it is a guest WebContents. bool is_guest = false; options.Get("isGuest", &is_guest); @@ -441,12 +442,12 @@ void WebContents::FindReply(content::WebContents* web_contents, result.Set("requestId", request_id); result.Set("selectionArea", selection_rect); result.Set("finalUpdate", final_update); - Emit("find-in-page-response", result); + Emit("found-in-page", result); } else if (final_update) { result.Set("requestId", request_id); result.Set("matches", number_of_matches); result.Set("finalUpdate", final_update); - Emit("find-in-page-response", result); + Emit("found-in-page", result); } } @@ -926,25 +927,19 @@ void WebContents::ReplaceMisspelling(const base::string16& word) { web_contents()->ReplaceMisspelling(word); } -void WebContents::FindInPage(mate::Arguments* args) { - int request_id; +uint32 WebContents::FindInPage(mate::Arguments* args) { + uint32 request_id = GetNextRequestId(); base::string16 search_text; blink::WebFindOptions options; - if (!args->GetNext(&request_id)) { - args->ThrowError("Must provide a request id"); - return; - } - - if (!args->GetNext(&search_text)) { + if (!args->GetNext(&search_text) || search_text.empty()) { args->ThrowError("Must provide a non-empty search content"); - return; + return 0; } args->GetNext(&options); web_contents()->Find(request_id, search_text, options); - web_contents()->GetMainFrame() - ->ActivateFindInPageResultForAccessibility(request_id); + return request_id; } void WebContents::StopFindInPage(content::StopFindAction action) { diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index d78455608d..0c7f129bd2 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -110,7 +110,7 @@ class WebContents : public mate::TrackableObject, void Unselect(); void Replace(const base::string16& word); void ReplaceMisspelling(const base::string16& word); - void FindInPage(mate::Arguments* args); + uint32 FindInPage(mate::Arguments* args); void StopFindInPage(content::StopFindAction action); // Focus. @@ -250,6 +250,10 @@ class WebContents : public mate::TrackableObject, AtomBrowserContext* GetBrowserContext() const; + uint32 GetNextRequestId() { + return ++request_id_; + } + // Called when received a message from renderer. void OnRendererMessage(const base::string16& channel, const base::ListValue& args); @@ -271,6 +275,9 @@ class WebContents : public mate::TrackableObject, // The type of current WebContents. Type type_; + // Request id used for findInPage request. + uint32 request_id_; + DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index 335d6516fc..17308f4ce1 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -22,9 +22,10 @@ supportedWebViewEvents = [ 'page-title-updated' 'page-favicon-updated' 'enter-html-full-screen' - 'leave-html-full-screen', - 'media-started-playing', - 'media-paused', + 'leave-html-full-screen' + 'media-started-playing' + 'media-paused' + 'found-in-page' ] nextInstanceId = 0 diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index 71a2ac9778..095490ab88 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -13,6 +13,7 @@ #include "content/public/browser/native_web_keyboard_event.h" #include "native_mate/dictionary.h" #include "third_party/WebKit/public/web/WebDeviceEmulationParams.h" +#include "third_party/WebKit/public/web/WebFindOptions.h" #include "third_party/WebKit/public/web/WebInputEvent.h" namespace { diff --git a/atom/common/native_mate_converters/blink_converter.h b/atom/common/native_mate_converters/blink_converter.h index 9f58659a3a..5e715c6317 100644 --- a/atom/common/native_mate_converters/blink_converter.h +++ b/atom/common/native_mate_converters/blink_converter.h @@ -6,7 +6,6 @@ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ #include "native_mate/converter.h" -#include "third_party/WebKit/public/web/WebFindOptions.h" namespace blink { class WebInputEvent; @@ -14,6 +13,7 @@ class WebMouseEvent; class WebMouseWheelEvent; class WebKeyboardEvent; struct WebDeviceEmulationParams; +struct WebFindOptions; struct WebFloatPoint; struct WebPoint; struct WebSize; diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index 41d19e3969..d79094f79d 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -111,8 +111,10 @@ bool Converter::FromV8( *out = content::STOP_FIND_ACTION_CLEAR_SELECTION; else if (action == "keepSelection") *out = content::STOP_FIND_ACTION_KEEP_SELECTION; - else + else if (action == "activateSelection") *out = content::STOP_FIND_ACTION_ACTIVATE_SELECTION; + else + return false; return true; } diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 04fa707da7..9178b3273d 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -27,6 +27,7 @@ WEB_VIEW_EVENTS = 'page-favicon-updated': ['favicons'] 'enter-html-full-screen': [] 'leave-html-full-screen': [] + 'found-in-page': ['result'] DEPRECATED_EVENTS = 'page-title-updated': 'page-title-set' diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee index da36886f6f..2d81bd6aa6 100644 --- a/atom/renderer/lib/web-view/web-view.coffee +++ b/atom/renderer/lib/web-view/web-view.coffee @@ -287,6 +287,8 @@ registerWebViewElement = -> 'unselect' 'replace' 'replaceMisspelling' + 'findInPage' + 'stopFindInPage' 'getId' 'downloadURL' 'inspectServiceWorker' diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 4d7907c0f8..3d903a8414 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -229,16 +229,16 @@ Emitted when media starts playing. Emitted when media is paused or done playing. -### Event: 'find-in-page-response' +### Event: 'found-in-page' Returns: * `event` Event * `result` Object * `requestId` Integer - * `matches` Integer __Optional__ - Number of Matches. - * `selectionArea` Object __Optional__ - Coordinates of first match region. * `finalUpdate` Boolean - Indicates if more responses are to follow. + * `matches` Integer (Optional) - Number of Matches. + * `selectionArea` Object (Optional) - Coordinates of first match region. Emitted when a result is available for [`webContents.findInPage`](web-contents.md#webcontentsfindinpage) request. @@ -434,38 +434,44 @@ Executes the editing command `replace` in web page. Executes the editing command `replaceMisspelling` in web page. -### `webContents.findInPage(id, text[, options])` +### `webContents.findInPage(text[, options])` -* `id` Integer * `text` String - Content to be searched, must not be empty. -* `options` Object __Optional__ - * `forward` Boolean - Whether to search forward or backward. - * `findNext` Boolean - Whether the operation is first request or a follow up. - * `matchCase` Boolean - Whether search should be case-sensitive. +* `options` Object (Optional) + * `forward` Boolean - Whether to search forward or backward, defaults to `true`. + * `findNext` Boolean - Whether the operation is first request or a follow up, + defaults to `false`. + * `matchCase` Boolean - Whether search should be case-sensitive, + defaults to `false`. * `wordStart` Boolean - Whether to look only at the start of words. - * ` medialCapitalAsWordStart` Boolean - When combined with `wordStart`, + defaults to `false`. + * `medialCapitalAsWordStart` Boolean - When combined with `wordStart`, accepts a match in the middle of a word if the match begins with an uppercase letter followed by a lowercase or non-letter. - Accepts several other intra-word matches + Accepts several other intra-word matches, defaults to `false`. -Finds all matches for the `text` in the web page. +Starts a request to find all matches for the `text` in the web page and returns an `Integer` +representing the request id used for the request. The result of the request can be +obtained by subscribing to [`found-in-page`](web-contents.md#event-found-in-page) event. ### `webContents.stopFindInPage(action)` -* `action` String - Should be called with either `clearSelection` or `keepSelection`. - By default it keeps the last selection. +* `action` String - Specifies the action to take place when ending + [`webContents.findInPage `](web-contents.md#webcontentfindinpage) request. + * `clearSelection` - Translate the selection into a normal selection. + * `keepSelection` - Clear the selection. + * `activateSelection` - Focus and click the selection node. -Stops any `findInPage` request for the `webContents` -with the provided `action`. +Stops any `findInPage` request for the `webContents` with the provided `action`. ```javascript -webContents.on('find-in-page-response', function(event, result) { +webContents.on('found-in-page', function(event, result) { if (result.finalUpdate) webContents.stopFindInPage("clearSelection"); }); -webContents.findInPage(1, "api"); -```. +const requestId = webContents.findInPage("api"); +``` ### `webContents.hasServiceWorker(callback)` diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index e317ef8eab..a65bc60636 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -348,6 +348,36 @@ Executes editing command `replace` in page. Executes editing command `replaceMisspelling` in page. +### `.findInPage(text[, options])` + +* `text` String - Content to be searched, must not be empty. +* `options` Object (Optional) + * `forward` Boolean - Whether to search forward or backward, defaults to `true`. + * `findNext` Boolean - Whether the operation is first request or a follow up, + defaults to `false`. + * `matchCase` Boolean - Whether search should be case-sensitive, + defaults to `false`. + * `wordStart` Boolean - Whether to look only at the start of words. + defaults to `false`. + * `medialCapitalAsWordStart` Boolean - When combined with `wordStart`, + accepts a match in the middle of a word if the match begins with an + uppercase letter followed by a lowercase or non-letter. + Accepts several other intra-word matches, defaults to `false`. + +Starts a request to find all matches for the `text` in the web page and returns an `Integer` +representing the request id used for the request. The result of the request can be +obtained by subscribing to [`found-in-page`](web-view-tag.md#event-found-in-page) event. + +### `.stopFindInPage(action)` + +* `action` String - Specifies the action to take place when ending + [`.findInPage `](web-view-tag.md#webviewtagfindinpage) request. + * `clearSelection` - Translate the selection into a normal selection. + * `keepSelection` - Clear the selection. + * `activateSelection` - Focus and click the selection node. + +Stops any `findInPage` request for the `webview` with the provided `action`. + ### `.print([options])` Prints `webview`'s web page. Same with `webContents.print([options])`. @@ -499,6 +529,28 @@ webview.addEventListener('console-message', function(e) { }); ``` +### Event: 'found-in-page' + +Returns: + +* `result` Object + * `requestId` Integer + * `finalUpdate` Boolean - Indicates if more responses are to follow. + * `matches` Integer (Optional) - Number of Matches. + * `selectionArea` Object (Optional) - Coordinates of first match region. + +Fired when a result is available for +[`webview.findInPage`](web-view-tag.md#webviewtagfindinpage) request. + +```javascript +webview.addEventListener('found-in-page', function(e) { + if (e.result.finalUpdate) + webview.stopFindInPage("keepSelection"); +}); + +const rquestId = webview.findInPage("test"); +``` + ### Event: 'new-window' Returns: diff --git a/spec/fixtures/pages/content.html b/spec/fixtures/pages/content.html new file mode 100644 index 0000000000..e37ba34c1d --- /dev/null +++ b/spec/fixtures/pages/content.html @@ -0,0 +1,16 @@ +

+ Virtual member functions are key to the object-oriented paradigm, + such as making it easy for old code to call new code. + A virtual function allows derived classes to replace the implementation + provided by the base class. The compiler makes sure the replacement is + always called whenever the object in question is actually of the derived class, + even if the object is accessed by a base pointer rather than a derived pointer. + This allows algorithms in the base class to be replaced in the derived class, + even if users dont know about the derived class. + + class A { + public: + virtual void foo() {} + } + +

diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index 28b25a2266..c0578d0505 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -390,3 +390,19 @@ describe ' tag', -> done() webview.src = "file://#{fixtures}/pages/audio.html" document.body.appendChild webview + + describe 'found-in-page event', -> + it 'emits when a request is made', (done) -> + requestId = null + listener = (e) -> + assert.equal e.result.requestId, requestId + if e.result.finalUpdate + assert.equal e.result.matches, 3 + webview.stopFindInPage "clearSelection" + done() + listener2 = (e) -> + requestId = webview.findInPage "virtual" + webview.addEventListener 'found-in-page', listener + webview.addEventListener 'did-finish-load', listener2 + webview.src = "file://#{fixtures}/pages/content.html" + document.body.appendChild webview From ff51e4033a720316a5762258cba7ea2a4e4c5d0a Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 19 Dec 2015 04:23:30 +0530 Subject: [PATCH 316/411] browser: fix value of document.hidden --- atom/renderer/lib/override.coffee | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 7279b232a9..15acbdd48b 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -118,6 +118,12 @@ Object.defineProperty window.history, 'length', get: -> getHistoryOperation 'length' -# Make document.hidden return the correct value. +# Make document.hidden and document.visibilityState return the correct value. Object.defineProperty document, 'hidden', - get: -> !remote.getCurrentWindow().isVisible() + get: -> + currentWindow = remote.getCurrentWindow() + !currentWindow.isFocused() || !currentWindow.isVisible() + +Object.defineProperty document, 'visibilityState', + get: -> + if document.hidden then "hidden" else "visible" From cc7040d75f0ed7552622b93877043effdc0cf284 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 21 Dec 2015 17:50:40 +0530 Subject: [PATCH 317/411] add test --- atom/renderer/lib/override.coffee | 2 +- spec/chromium-spec.coffee | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 15acbdd48b..5280f1927e 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -122,7 +122,7 @@ Object.defineProperty window.history, 'length', Object.defineProperty document, 'hidden', get: -> currentWindow = remote.getCurrentWindow() - !currentWindow.isFocused() || !currentWindow.isVisible() + currentWindow.isMinimized() || !currentWindow.isVisible() Object.defineProperty document, 'visibilityState', get: -> diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index 5559c36eba..d19933393f 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -45,6 +45,14 @@ describe 'chromium feature', -> done() w.loadURL url + it 'is set correctly when window is inactive', (done) -> + w = new BrowserWindow(show:false) + w.webContents.on 'ipc-message', (event, args) -> + assert.deepEqual args, ['hidden', false] + done() + w.showInactive() + w.loadURL url + xdescribe 'navigator.webkitGetUserMedia', -> it 'calls its callbacks', (done) -> @timeout 5000 From 61004f0e46cbeb4be7cf71dae36a13a1c463f39a Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 21 Dec 2015 18:24:55 +0530 Subject: [PATCH 318/411] fix cpplint warning --- atom/browser/api/atom_api_web_contents.cc | 1 - atom/common/native_mate_converters/blink_converter.h | 2 +- docs/api/web-contents.md | 2 +- docs/api/web-view-tag.md | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 995774376e..037663db47 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -49,7 +49,6 @@ #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" #include "content/public/common/context_menu_params.h" -#include "native_mate/converter.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/http/http_response_headers.h" diff --git a/atom/common/native_mate_converters/blink_converter.h b/atom/common/native_mate_converters/blink_converter.h index 5e715c6317..6a36019292 100644 --- a/atom/common/native_mate_converters/blink_converter.h +++ b/atom/common/native_mate_converters/blink_converter.h @@ -17,7 +17,7 @@ struct WebFindOptions; struct WebFloatPoint; struct WebPoint; struct WebSize; -} +} // namespace blink namespace content { struct NativeWebKeyboardEvent; diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 3d903a8414..f856b9229c 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -457,7 +457,7 @@ obtained by subscribing to [`found-in-page`](web-contents.md#event-found-in-page ### `webContents.stopFindInPage(action)` * `action` String - Specifies the action to take place when ending - [`webContents.findInPage `](web-contents.md#webcontentfindinpage) request. + [`webContents.findInPage`](web-contents.md#webcontentfindinpage) request. * `clearSelection` - Translate the selection into a normal selection. * `keepSelection` - Clear the selection. * `activateSelection` - Focus and click the selection node. diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index a65bc60636..ea99358290 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -371,7 +371,7 @@ obtained by subscribing to [`found-in-page`](web-view-tag.md#event-found-in-page ### `.stopFindInPage(action)` * `action` String - Specifies the action to take place when ending - [`.findInPage `](web-view-tag.md#webviewtagfindinpage) request. + [`.findInPage`](web-view-tag.md#webviewtagfindinpage) request. * `clearSelection` - Translate the selection into a normal selection. * `keepSelection` - Clear the selection. * `activateSelection` - Focus and click the selection node. From 2f732a7c0f65db58c3c266c7d277cf71e93544e4 Mon Sep 17 00:00:00 2001 From: Jason Zhekov Date: Tue, 22 Dec 2015 09:04:36 +0200 Subject: [PATCH 319/411] Formatting app.md This looks a bit odd on the site: http://electron.atom.io/docs/v0.36.0/api/app/#app-setusertasks-tasks-windows --- docs/api/app.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api/app.md b/docs/api/app.md index f21f370397..abe73ab20a 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -339,7 +339,8 @@ Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. `tasks` is an array of `Task` objects in the following format: -`Task` Object +`Task` Object: + * `program` String - Path of the program to execute, usually you should specify `process.execPath` which opens the current program. * `arguments` String - The command line arguments when `program` is From 9aa37580e36bad7df63e12ccf5bf32da5fb8f6d3 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 22 Dec 2015 03:31:50 +0900 Subject: [PATCH 320/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/web-contents.md | 8 ++++++++ .../ko-KR/tutorial/desktop-environment-integration.md | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index 71502c3dcc..dc4ec67940 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -219,6 +219,14 @@ Returns: [`app`의 `login`이벤트](app.md#event-login)와 사용 방법은 같습니다. +### Event: 'media-started-playing' + +미디어가 재생되기 시작할 때 발생하는 이벤트입니다. + +### Event: 'media-paused' + +미디어가 중지되거나 재생이 완료되었을 때 발생하는 이벤트입니다. + ## Instance Methods `webContents`객체는 다음과 같은 인스턴스 메서드들을 가지고 있습니다. diff --git a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md index 42418e71a9..d2e50beb6c 100644 --- a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md +++ b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md @@ -47,7 +47,7 @@ new Notification('Title', { }); ``` -또한 `body`의 최대 길이는 250자 입니다. Windows 개발팀에선 알림 문자열을 200자 이하로 +또한 본문의 최대 길이는 250자 입니다. Windows 개발팀에선 알림 문자열을 200자 이하로 유지하는 것을 권장합니다. ### Linux @@ -67,8 +67,8 @@ OS X에서의 데스크톱 알림은 아주 직관적입니다. 하지만 데스 ## 최근 사용한 문서 (Windows & OS X) -알다 싶이 Windows와 OS X는 JumpList 또는 dock 메뉴를 통해 최근 문서 리스트에 쉽게 -접근할 수 있습니다. +Windows와 OS X는 JumpList 또는 dock 메뉴를 통해 최근 문서 리스트에 쉽게 접근할 수 +있습니다. __JumpList:__ From c8c438108587e4a0cc2d368db770a2e9dd6476dc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 22 Dec 2015 22:08:33 +0800 Subject: [PATCH 321/411] Cancel callback when URLRequest is destroyed --- atom/browser/net/atom_network_delegate.cc | 27 ++++++++++++++++++----- atom/browser/net/atom_network_delegate.h | 10 +++++---- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 6bc25027db..f12911f286 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -190,6 +190,10 @@ template void OnListenerResultInIO(const net::CompletionCallback& callback, T out, scoped_ptr response) { + // The request has been destroyed. + if (callback.is_null()) + return; + ReadFromResponseObject(*response.get(), out); bool cancel = false; @@ -312,10 +316,7 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { if (request->status().status() == net::URLRequestStatus::FAILED || request->status().status() == net::URLRequestStatus::CANCELED) { // Error event. - if (ContainsKey(simple_listeners_, kOnErrorOccurred)) - OnErrorOccurred(request); - else - brightray::NetworkDelegate::OnCompleted(request, started); + OnErrorOccurred(request, started); return; } else if (request->response_headers() && net::HttpResponseHeaders::IsRedirectResponseCode( @@ -334,7 +335,17 @@ void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { request->was_cached()); } -void AtomNetworkDelegate::OnErrorOccurred(net::URLRequest* request) { +void AtomNetworkDelegate::OnURLRequestDestroyed(net::URLRequest* request) { + callbacks_.erase(request->identifier()); +} + +void AtomNetworkDelegate::OnErrorOccurred( + net::URLRequest* request, bool started) { + if (!ContainsKey(simple_listeners_, kOnErrorOccurred)) { + brightray::NetworkDelegate::OnCompleted(request, started); + return; + } + HandleSimpleEvent(kOnErrorOccurred, request, request->was_cached(), request->status()); } @@ -353,8 +364,12 @@ int AtomNetworkDelegate::HandleResponseEvent( scoped_ptr details(new base::DictionaryValue); FillDetailsObject(details.get(), request, args...); + // The |request| could be destroyed before the |callback| is called. + callbacks_[request->identifier()].Reset(callback); + const auto& cancelable = callbacks_[request->identifier()].callback(); + ResponseCallback response = - base::Bind(OnListenerResultInUI, callback, out); + base::Bind(OnListenerResultInUI, cancelable, out); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(RunResponseListener, info.listener, base::Passed(&details), diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 8950a1b511..579f33f6d7 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -9,7 +9,7 @@ #include #include "brightray/browser/network_delegate.h" -#include "base/callback.h" +#include "base/cancelable_callback.h" #include "base/values.h" #include "extensions/common/url_pattern.h" #include "net/base/net_errors.h" @@ -85,10 +85,11 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { const GURL& new_location) override; void OnResponseStarted(net::URLRequest* request) override; void OnCompleted(net::URLRequest* request, bool started) override; - - void OnErrorOccurred(net::URLRequest* request); + void OnURLRequestDestroyed(net::URLRequest* request) override; private: + void OnErrorOccurred(net::URLRequest* request, bool started); + template void HandleSimpleEvent(SimpleEvent type, net::URLRequest* request, @@ -101,7 +102,8 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { Args... args); std::map simple_listeners_; - std::map response_listeners_;; + std::map response_listeners_; + std::map> callbacks_; DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); }; From aa2f7aaf3a19a61f141d9788dfcde9e0b3164897 Mon Sep 17 00:00:00 2001 From: jaanus Date: Mon, 21 Dec 2015 20:55:23 +0200 Subject: [PATCH 322/411] Fixes #2810: correct look of hidden-inset windows in full screen. `hidden` and `hidden-inset` windows differ only by the hidden-inset window having a toolbar. Yet, the toolbar yields an incorrect look in fullscreen mode. So, we hide and recreate the toolbar for such windows when going to/from fullscreen. There are some visible artifacts during the fullscreen animations, as the toolbar gets created and destroyed. When entering fullscreen, you see a toolbar that then disappears. When going back to normal window, you see the traffic light buttons jump around a little bit. Yet, this is definitely better than the current broken fullscreen look. --- atom/browser/native_window_mac.h | 6 ++++++ atom/browser/native_window_mac.mm | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 38845140e6..3b81786e79 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -78,6 +78,10 @@ class NativeWindowMac : public NativeWindow { UpdateDraggableRegionViews(draggable_regions_); } + bool ShouldHideNativeToolbarInFullscreen() const { + return should_hide_native_toolbar_in_fullscreen_; + } + protected: // NativeWindow: void HandleKeyboardEvent( @@ -118,6 +122,8 @@ class NativeWindowMac : public NativeWindow { // The presentation options before entering kiosk mode. NSApplicationPresentationOptions kiosk_options_; + bool should_hide_native_toolbar_in_fullscreen_; + DISALLOW_COPY_AND_ASSIGN(NativeWindowMac); }; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 1ee69fc1fc..d8d1f66197 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -177,10 +177,25 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)windowDidEnterFullScreen:(NSNotification*)notification { + if (shell_->ShouldHideNativeToolbarInFullscreen()) { + NSWindow* window = shell_->GetNativeWindow(); + [window setToolbar:nil]; + } + shell_->NotifyWindowEnterFullScreen(); } - (void)windowDidExitFullScreen:(NSNotification*)notification { + + // Restore the native toolbar for styling if needed + if (shell_->ShouldHideNativeToolbarInFullscreen()) { + NSWindow* window = shell_->GetNativeWindow(); + base::scoped_nsobject toolbar( + [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); + [toolbar setShowsBaselineSeparator:NO]; + [window setToolbar:toolbar]; + } + if (!shell_->has_frame()) { NSWindow* window = shell_->GetNativeWindow(); [[window standardWindowButton:NSWindowFullScreenButton] setHidden:YES]; @@ -378,6 +393,15 @@ NativeWindowMac::NativeWindowMac( styleMask |= NSUnifiedTitleAndToolbarWindowMask; } + // We capture this because we need to access the option later when entering/exiting fullscreen + // and since the options dict is only passed to the constructor but not stored, + // let’s store this option this way. + if (titleBarStyle == "hidden-inset") { + should_hide_native_toolbar_in_fullscreen_ = true; + } else { + should_hide_native_toolbar_in_fullscreen_ = false; + } + window_.reset([[AtomNSWindow alloc] initWithContentRect:cocoa_bounds styleMask:styleMask From 11b20155357740a5f7e0e912e066865bae624448 Mon Sep 17 00:00:00 2001 From: "Brian R. Bondy" Date: Tue, 22 Dec 2015 17:16:00 -0500 Subject: [PATCH 323/411] Add did-change-theme-color event to webview --- atom/browser/api/atom_api_web_contents.cc | 10 ++++++++++ atom/browser/api/atom_api_web_contents.h | 1 + atom/browser/lib/guest-view-manager.coffee | 1 + .../lib/web-view/guest-view-internal.coffee | 1 + docs/api/web-contents.md | 8 -------- docs/api/web-view-tag.md | 14 ++++++++++++++ 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 037663db47..05c2683b26 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -489,6 +489,16 @@ void WebContents::MediaPaused() { Emit("media-paused"); } +void WebContents::DidChangeThemeColor(SkColor theme_color) { + char themeColor[8] = { 0 }; + snprintf(themeColor, sizeof(themeColor), + "#%02X%02X%02X", + SkColorGetR(theme_color), + SkColorGetG(theme_color), + SkColorGetB(theme_color)); + Emit("did-change-theme-color", themeColor); +} + void WebContents::DocumentLoadedInFrame( content::RenderFrameHost* render_frame_host) { if (!render_frame_host->GetParent()) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 0c7f129bd2..52a244eb89 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -235,6 +235,7 @@ class WebContents : public mate::TrackableObject, base::ProcessId plugin_pid) override; void MediaStartedPlaying() override; void MediaPaused() override; + void DidChangeThemeColor(SkColor theme_color) override; // brightray::InspectableWebContentsViewDelegate: void DevToolsFocused() override; diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index 17308f4ce1..b0ba82eef3 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -26,6 +26,7 @@ supportedWebViewEvents = [ 'media-started-playing' 'media-paused' 'found-in-page' + 'did-change-theme-color' ] nextInstanceId = 0 diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 9178b3273d..55e0c49a1a 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -22,6 +22,7 @@ WEB_VIEW_EVENTS = 'plugin-crashed': ['name', 'version'] 'media-started-playing': [] 'media-paused': [] + 'did-change-theme-color': ['themeColor'] 'destroyed': [] 'page-title-updated': ['title', 'explicitSet'] 'page-favicon-updated': ['favicons'] diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index f856b9229c..7e41b02ae6 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -221,14 +221,6 @@ Emitted when `webContents` wants to do basic auth. The usage is the same with [the `login` event of `app`](app.md#event-login). -### Event: 'media-started-playing' - -Emitted when media starts playing. - -### Event: 'media-paused' - -Emitted when media is paused or done playing. - ### Event: 'found-in-page' Returns: diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index ea99358290..0eda488246 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -634,3 +634,17 @@ Fired when a plugin process is crashed. ### Event: 'destroyed' Fired when the WebContents is destroyed. + +### Event: 'media-started-playing' + +Emitted when media starts playing. + +### Event: 'media-paused' + +Emitted when media is paused or done playing. + +### Event: 'did-change-theme-color' + +Emitted when a page's theme color changes. This is usually due to encountering a meta tag: + + From 3e1edfc9d0e7098458987861c650142b1142b631 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 22 Dec 2015 23:46:25 +0800 Subject: [PATCH 324/411] Cancel callback in OnComplete event --- atom/browser/net/atom_network_delegate.cc | 59 ++++++++++++----------- atom/browser/net/atom_network_delegate.h | 12 ++++- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index f12911f286..228cb5828e 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -185,32 +185,6 @@ void ReadFromResponseObject(const base::DictionaryValue& response, } } -// Deal with the results of Listener. -template -void OnListenerResultInIO(const net::CompletionCallback& callback, - T out, - scoped_ptr response) { - // The request has been destroyed. - if (callback.is_null()) - return; - - ReadFromResponseObject(*response.get(), out); - - bool cancel = false; - response->GetBoolean("cancel", &cancel); - callback.Run(cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK); -} - -template -void OnListenerResultInUI(const net::CompletionCallback& callback, - T out, - const base::DictionaryValue& response) { - scoped_ptr copy = response.CreateDeepCopy(); - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(OnListenerResultInIO, callback, out, base::Passed(©))); -} - } // namespace AtomNetworkDelegate::AtomNetworkDelegate() { @@ -313,6 +287,9 @@ void AtomNetworkDelegate::OnResponseStarted(net::URLRequest* request) { } void AtomNetworkDelegate::OnCompleted(net::URLRequest* request, bool started) { + // OnCompleted may happen before other events. + callbacks_.erase(request->identifier()); + if (request->status().status() == net::URLRequestStatus::FAILED || request->status().status() == net::URLRequestStatus::CANCELED) { // Error event. @@ -365,11 +342,11 @@ int AtomNetworkDelegate::HandleResponseEvent( FillDetailsObject(details.get(), request, args...); // The |request| could be destroyed before the |callback| is called. - callbacks_[request->identifier()].Reset(callback); - const auto& cancelable = callbacks_[request->identifier()].callback(); + callbacks_[request->identifier()] = callback; ResponseCallback response = - base::Bind(OnListenerResultInUI, cancelable, out); + base::Bind(&AtomNetworkDelegate::OnListenerResultInUI, + base::Unretained(this), request->identifier(), out); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(RunResponseListener, info.listener, base::Passed(&details), @@ -392,4 +369,28 @@ void AtomNetworkDelegate::HandleSimpleEvent( base::Bind(RunSimpleListener, info.listener, base::Passed(&details))); } +template +void AtomNetworkDelegate::OnListenerResultInIO( + uint64_t id, T out, scoped_ptr response) { + // The request has been destroyed. + if (!ContainsKey(callbacks_, id)) + return; + + ReadFromResponseObject(*response.get(), out); + + bool cancel = false; + response->GetBoolean("cancel", &cancel); + callbacks_[id].Run(cancel ? net::ERR_BLOCKED_BY_CLIENT : net::OK); +} + +template +void AtomNetworkDelegate::OnListenerResultInUI( + uint64_t id, T out, const base::DictionaryValue& response) { + scoped_ptr copy = response.CreateDeepCopy(); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&AtomNetworkDelegate::OnListenerResultInIO, + base::Unretained(this), id, out, base::Passed(©))); +} + } // namespace atom diff --git a/atom/browser/net/atom_network_delegate.h b/atom/browser/net/atom_network_delegate.h index 579f33f6d7..4f55f7c098 100644 --- a/atom/browser/net/atom_network_delegate.h +++ b/atom/browser/net/atom_network_delegate.h @@ -9,7 +9,7 @@ #include #include "brightray/browser/network_delegate.h" -#include "base/cancelable_callback.h" +#include "base/callback.h" #include "base/values.h" #include "extensions/common/url_pattern.h" #include "net/base/net_errors.h" @@ -101,9 +101,17 @@ class AtomNetworkDelegate : public brightray::NetworkDelegate { Out out, Args... args); + // Deal with the results of Listener. + template + void OnListenerResultInIO( + uint64_t id, T out, scoped_ptr response); + template + void OnListenerResultInUI( + uint64_t id, T out, const base::DictionaryValue& response); + std::map simple_listeners_; std::map response_listeners_; - std::map> callbacks_; + std::map callbacks_; DISALLOW_COPY_AND_ASSIGN(AtomNetworkDelegate); }; From 29b00ae0d6558c42f31cb3f1588f012ffb7e91c2 Mon Sep 17 00:00:00 2001 From: "Brian R. Bondy" Date: Tue, 22 Dec 2015 17:16:31 -0500 Subject: [PATCH 325/411] Add tests for did-change-theme-color event --- atom/browser/api/atom_api_web_contents.cc | 12 +++++------- docs/api/web-contents.md | 16 ++++++++++++++++ docs/api/web-view-tag.md | 4 +++- spec/fixtures/pages/theme-color.html | 7 +++++++ spec/webview-spec.coffee | 7 +++++++ 5 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 spec/fixtures/pages/theme-color.html diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 05c2683b26..461a66c4b2 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -490,13 +490,11 @@ void WebContents::MediaPaused() { } void WebContents::DidChangeThemeColor(SkColor theme_color) { - char themeColor[8] = { 0 }; - snprintf(themeColor, sizeof(themeColor), - "#%02X%02X%02X", - SkColorGetR(theme_color), - SkColorGetG(theme_color), - SkColorGetB(theme_color)); - Emit("did-change-theme-color", themeColor); + std::string hex_theme_color = base::StringPrintf("#%02X%02X%02X", + SkColorGetR(theme_color), + SkColorGetG(theme_color), + SkColorGetB(theme_color)); + Emit("did-change-theme-color", hex_theme_color); } void WebContents::DocumentLoadedInFrame( diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 7e41b02ae6..2057b515c0 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -235,6 +235,22 @@ Returns: Emitted when a result is available for [`webContents.findInPage`](web-contents.md#webcontentsfindinpage) request. +### Event: 'media-started-playing' + +Emitted when media starts playing. + +### Event: 'media-paused' + +Emitted when media is paused or done playing. + +### Event: 'did-change-theme-color' + +Emitted when a page's theme color changes. This is usually due to encountering a meta tag: + +```html + +``` + ## Instance Methods The `webContents` object has the following instance methods: diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 0eda488246..0c79083c11 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -647,4 +647,6 @@ Emitted when media is paused or done playing. Emitted when a page's theme color changes. This is usually due to encountering a meta tag: - +```html + +``` diff --git a/spec/fixtures/pages/theme-color.html b/spec/fixtures/pages/theme-color.html new file mode 100644 index 0000000000..57c49f3373 --- /dev/null +++ b/spec/fixtures/pages/theme-color.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index c0578d0505..d6c1a2de7b 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -406,3 +406,10 @@ describe ' tag', -> webview.addEventListener 'did-finish-load', listener2 webview.src = "file://#{fixtures}/pages/content.html" document.body.appendChild webview + + describe 'did-change-theme-color event', -> + it 'emits when theme color changes', (done) -> + webview.addEventListener 'did-change-theme-color', (e) -> + done() + webview.src = "file://#{fixtures}/pages/theme-color.html" + document.body.appendChild webview From e90435e2364c1c580bdccba26973f3425704d209 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 23 Dec 2015 12:38:11 +0800 Subject: [PATCH 326/411] Remove visual artifacts of hidden-inset window --- atom/browser/native_window_mac.h | 2 +- atom/browser/native_window_mac.mm | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 3b81786e79..23f210d597 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -78,7 +78,7 @@ class NativeWindowMac : public NativeWindow { UpdateDraggableRegionViews(draggable_regions_); } - bool ShouldHideNativeToolbarInFullscreen() const { + bool should_hide_native_toolbar_in_fullscreen() const { return should_hide_native_toolbar_in_fullscreen_; } diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index d8d1f66197..45123d2487 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -176,26 +176,30 @@ bool ScopedDisableResize::disable_resize_ = false; return YES; } -- (void)windowDidEnterFullScreen:(NSNotification*)notification { - if (shell_->ShouldHideNativeToolbarInFullscreen()) { +- (void)windowWillEnterFullScreen:(NSNotification*)notification { + // Hide the native toolbar before entering fullscreen, so there is no visual + // artifacts. + if (shell_->should_hide_native_toolbar_in_fullscreen()) { NSWindow* window = shell_->GetNativeWindow(); [window setToolbar:nil]; } - - shell_->NotifyWindowEnterFullScreen(); } -- (void)windowDidExitFullScreen:(NSNotification*)notification { +- (void)windowDidEnterFullScreen:(NSNotification*)notification { + shell_->NotifyWindowEnterFullScreen(); - // Restore the native toolbar for styling if needed - if (shell_->ShouldHideNativeToolbarInFullscreen()) { + // Restore the native toolbar immediately after entering fullscreen, if we do + // this before leaving fullscreen, traffic light buttons will be jumping. + if (shell_->should_hide_native_toolbar_in_fullscreen()) { NSWindow* window = shell_->GetNativeWindow(); base::scoped_nsobject toolbar( [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); [toolbar setShowsBaselineSeparator:NO]; [window setToolbar:toolbar]; } +} +- (void)windowDidExitFullScreen:(NSNotification*)notification { if (!shell_->has_frame()) { NSWindow* window = shell_->GetNativeWindow(); [[window standardWindowButton:NSWindowFullScreenButton] setHidden:YES]; @@ -349,7 +353,8 @@ NativeWindowMac::NativeWindowMac( const mate::Dictionary& options) : NativeWindow(web_contents, options), is_kiosk_(false), - attention_request_id_(0) { + attention_request_id_(0), + should_hide_native_toolbar_in_fullscreen_(false) { int width = 800, height = 600; options.Get(options::kWidth, &width); options.Get(options::kHeight, &height); @@ -392,14 +397,11 @@ NativeWindowMac::NativeWindowMac( styleMask |= NSFullSizeContentViewWindowMask; styleMask |= NSUnifiedTitleAndToolbarWindowMask; } - - // We capture this because we need to access the option later when entering/exiting fullscreen - // and since the options dict is only passed to the constructor but not stored, - // let’s store this option this way. + // We capture this because we need to access the option later when + // entering/exiting fullscreen and since the options dict is only passed to + // the constructor but not stored, let’s store this option this way. if (titleBarStyle == "hidden-inset") { should_hide_native_toolbar_in_fullscreen_ = true; - } else { - should_hide_native_toolbar_in_fullscreen_ = false; } window_.reset([[AtomNSWindow alloc] From 3cdd5dd418dbb362fc3bf3cebda19386b53913cb Mon Sep 17 00:00:00 2001 From: fscherwi Date: Wed, 23 Dec 2015 19:08:51 +0100 Subject: [PATCH 327/411] :arrow_up: asar --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 048ab84353..6f799fba44 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "electron", "version": "0.36.1", "devDependencies": { - "asar": "^0.8.0", + "asar": "^0.9.0", "coffee-script": "^1.9.2", "coffeelint": "^1.9.4", "request": "*" From 046839cb565d414155e2fb73f261554d87b0e7a4 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 11:16:46 +0800 Subject: [PATCH 328/411] Update brightray for #2294 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 5bfdec0985..c38b45f455 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 5bfdec0985c2412c6312749154a8adb4b1f79b8a +Subproject commit c38b45f45528d562dea04638bc5cd9a491a8446c From 8b88c99685d9983b11e4eee5c4014e1f48948bfb Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 11:17:32 +0800 Subject: [PATCH 329/411] No longer need to ship with libnotify.so --- script/create-dist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/script/create-dist.py b/script/create-dist.py index 29f81ebd85..a619e04d3e 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -72,7 +72,6 @@ TARGET_DIRECTORIES = { SYSTEM_LIBRARIES = [ 'libgcrypt.so', - 'libnotify.so', ] From d3d8ab7c66a3d0b36e7d4cc0e3d8bc808d34ffd4 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 11:43:14 +0800 Subject: [PATCH 330/411] linux: Fix pressing Alt not toggling window menu bar --- atom/browser/native_window_views.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 16faee58c5..1abb2ef8d3 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -60,12 +60,7 @@ const int kMenuBarHeight = 25; #endif bool IsAltKey(const content::NativeWebKeyboardEvent& event) { -#if defined(USE_X11) - // 164 and 165 represent VK_LALT and VK_RALT. - return event.windowsKeyCode == 164 || event.windowsKeyCode == 165; -#else return event.windowsKeyCode == ui::VKEY_MENU; -#endif } bool IsAltModifier(const content::NativeWebKeyboardEvent& event) { From 7bd9f2e5d07eb7a38912ddeb6f64379d6032161e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 12:43:07 +0800 Subject: [PATCH 331/411] Fix converting string to NSURL --- atom/common/platform_util_mac.mm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/atom/common/platform_util_mac.mm b/atom/common/platform_util_mac.mm index dcf622feb5..7184593ae1 100644 --- a/atom/common/platform_util_mac.mm +++ b/atom/common/platform_util_mac.mm @@ -12,6 +12,7 @@ #include "base/mac/mac_logging.h" #include "base/mac/scoped_aedesc.h" #include "base/strings/sys_string_conversions.h" +#include "net/base/mac/url_conversions.h" #include "url/gurl.h" namespace platform_util { @@ -120,9 +121,7 @@ void OpenItem(const base::FilePath& full_path) { bool OpenExternal(const GURL& url) { DCHECK([NSThread isMainThread]); - NSString* url_string = base::SysUTF8ToNSString(url.spec()); - NSString* url_escaped_string = [url_string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - NSURL* ns_url = [NSURL URLWithString:url_escaped_string]; + NSURL* ns_url = net::NSURLWithGURL(url); if (!ns_url) { return false; } From 2d0e1c313a49ef334e8062f7a90e3868ed20918e Mon Sep 17 00:00:00 2001 From: Tyler Gibson Date: Thu, 24 Dec 2015 03:54:50 +0000 Subject: [PATCH 332/411] Adding check for Windows version and alternate flags for Windows Vista/7 --- atom/common/platform_util_win.cc | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/atom/common/platform_util_win.cc b/atom/common/platform_util_win.cc index cca392952e..735e974d1f 100644 --- a/atom/common/platform_util_win.cc +++ b/atom/common/platform_util_win.cc @@ -340,13 +340,26 @@ bool MoveItemToTrash(const base::FilePath& path) { // Elevation prompt enabled for UAC protected files. This overrides the // SILENT, NO_UI and NOERRORUI flags. - if (FAILED(pfo->SetOperationFlags(FOF_NO_UI | - FOF_ALLOWUNDO | - FOF_NOERRORUI | - FOF_SILENT | - FOFX_SHOWELEVATIONPROMPT | - FOFX_RECYCLEONDELETE))) - return false; + + if (base::win::GetVersion() >= base::win::VERSION_WIN8) { + // Windows 8 introduces the flag RECYCLEONDELETE and deprecates the + // ALLOWUNDO in favor of ADDUNDORECORD. + if (FAILED(pfo->SetOperationFlags(FOF_NO_UI | + FOFX_ADDUNDORECORD | + FOF_NOERRORUI | + FOF_SILENT | + FOFX_SHOWELEVATIONPROMPT | + FOFX_RECYCLEONDELETE))) + return false; + } else { + // For Windows 7 and Vista, RecycleOnDelete is the default behavior. + if (FAILED(pfo->SetOperationFlags(FOF_NO_UI | + FOF_ALLOWUNDO | + FOF_NOERRORUI | + FOF_SILENT | + FOFX_SHOWELEVATIONPROMPT))) + return false; + } // Create an IShellItem from the supplied source path. base::win::ScopedComPtr delete_item; From e96f89133c6b2e1b21e8465aeee580b353391985 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 16:59:13 +0800 Subject: [PATCH 333/411] Reset whole headers when requestHeaders is set --- atom/browser/net/atom_network_delegate.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 228cb5828e..4f9bc835ed 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -154,6 +154,7 @@ void ReadFromResponseObject(const base::DictionaryValue& response, net::HttpRequestHeaders* headers) { const base::DictionaryValue* dict; if (response.GetDictionary("requestHeaders", &dict)) { + headers->Clear(); for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { From 2b5c91bbb5be928e60f88ec1d1b4603b6b1b971e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 17:02:30 +0800 Subject: [PATCH 334/411] spec: onBeforeSendHeaders should reset the whole headers --- spec/api-web-request-spec.coffee | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/api-web-request-spec.coffee b/spec/api-web-request-spec.coffee index a10ff3d127..5c78ef1d30 100644 --- a/spec/api-web-request-spec.coffee +++ b/spec/api-web-request-spec.coffee @@ -103,12 +103,23 @@ describe 'webRequest module', -> done() error: (xhr, errorType, error) -> done(errorType) + it 'resets the whole headers', (done) -> + requestHeaders = Test: 'header' + ses.webRequest.onBeforeSendHeaders (details, callback) -> + callback({requestHeaders}) + ses.webRequest.onSendHeaders (details) -> + assert.deepEqual details.requestHeaders, requestHeaders + done() + $.ajax + url: defaultURL + error: (xhr, errorType, error) -> done(errorType) + describe 'webRequest.onSendHeaders', -> afterEach -> ses.webRequest.onSendHeaders null it 'receives details object', (done) -> - ses.webRequest.onSendHeaders (details, callback) -> + ses.webRequest.onSendHeaders (details) -> assert.equal typeof details.requestHeaders, 'object' $.ajax url: defaultURL From c36ae86fabae571dbd886b43d445a8736d99c711 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 17:08:32 +0800 Subject: [PATCH 335/411] spec: Increase timeout for window.open --- spec/chromium-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index d19933393f..cc28f2f7ad 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -85,7 +85,7 @@ describe 'chromium feature', -> w.loadURL url describe 'window.open', -> - @timeout 10000 + @timeout 20000 it 'returns a BrowserWindowProxy object', -> b = window.open 'about:blank', '', 'show=no' From 7f02e0a716e55cafb082046578a33183ffc068c7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 19:05:28 +0800 Subject: [PATCH 336/411] spec: Surppess did-change-theme-color test It is too flaky. --- spec/webview-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index d6c1a2de7b..bcc56b79e2 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -407,7 +407,7 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/content.html" document.body.appendChild webview - describe 'did-change-theme-color event', -> + xdescribe 'did-change-theme-color event', -> it 'emits when theme color changes', (done) -> webview.addEventListener 'did-change-theme-color', (e) -> done() From 67e19cac85d210044131fb25dd4b5e50b73b95fa Mon Sep 17 00:00:00 2001 From: Juan Paulo Gutierrez Date: Thu, 24 Dec 2015 20:22:22 +0900 Subject: [PATCH 337/411] Removed unnecessary backtick. --- docs/api/chrome-command-line-switches.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index edeba0c9ad..798d756820 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -56,7 +56,7 @@ list of hosts. This flag has an effect only if used in tandem with For example: ```javascript -app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678')` +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') ``` Will use the proxy server for all hosts except for local addresses (`localhost`, From 673c6e691731e51c23cf05600071b4c797ad2db9 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 24 Dec 2015 20:08:16 +0800 Subject: [PATCH 338/411] Upgrade brightray for #3866 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index c38b45f455..caf15a59c4 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit c38b45f45528d562dea04638bc5cd9a491a8446c +Subproject commit caf15a59c4c398d9c338f194758630adf3078486 From ab9d1bf97ed2015829bcc5381c149d2826d04015 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 25 Dec 2015 02:19:56 +0530 Subject: [PATCH 339/411] renderer: dont fork the process when there is server redirect --- atom/renderer/atom_renderer_client.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index e0d40189f2..486aa2e234 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -194,7 +194,7 @@ bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, // the OpenURLFromTab is triggered, which means form posting would not work, // we should solve this by patching Chromium in future. *send_referrer = true; - return http_method == "GET"; + return http_method == "GET" && !is_server_redirect; } content::BrowserPluginDelegate* AtomRendererClient::CreateBrowserPluginDelegate( From 26f9f83cfd3c29bb99e1f55e7d1a7e85bf40f71a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 25 Dec 2015 11:57:35 +0800 Subject: [PATCH 340/411] Upgrade brightray for atom/brightray#188 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index caf15a59c4..be00b1d551 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit caf15a59c4c398d9c338f194758630adf3078486 +Subproject commit be00b1d551a977612bfe74979bdbfdec5e8c748f From 2f99a1ac8e51a8e3f9f46840b5ad89048cfaf165 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 25 Dec 2015 06:07:01 +0900 Subject: [PATCH 341/411] :memo: Update as upstream [ci skip] --- README-ko.md | 5 +- docs-translations/ko-KR/api/app.md | 8 +-- .../ko-KR/api/chrome-command-line-switches.md | 2 +- docs-translations/ko-KR/api/web-contents.md | 63 +++++++++++++++++ docs-translations/ko-KR/api/web-view-tag.md | 70 +++++++++++++++++++ 5 files changed, 142 insertions(+), 6 deletions(-) diff --git a/README-ko.md b/README-ko.md index 2bd24dfe58..a2db99929c 100644 --- a/README-ko.md +++ b/README-ko.md @@ -49,12 +49,14 @@ API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝 ## 참조 문서 (번역) -- [브라질 포르투칼어](https://github.com/atom/electron/tree/master/docs-translations/pt-BR) +- [브라질 포르투갈어](https://github.com/atom/electron/tree/master/docs-translations/pt-BR) - [한국어](https://github.com/atom/electron/tree/master/docs-translations/ko-KR) - [일본어](https://github.com/atom/electron/tree/master/docs-translations/jp) - [스페인어](https://github.com/atom/electron/tree/master/docs-translations/es) - [중국어 간체](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) - [중국어 번체](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) +- [우크라이나어](https://github.com/atom/electron/tree/master/docs-translations/uk-UA) +- [러시아어](https://github.com/atom/electron/tree/master/docs-translations/ru-RU) ## 시작하기 @@ -68,6 +70,7 @@ API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝 - Atom 포럼의 [`electron`](http://discuss.atom.io/c/electron) 카테고리 - Freenode 채팅의 `#atom-shell` 채널 - Slack의 [`Atom`](http://atom-slack.herokuapp.com/) 채널 +- [`electron-br`](https://electron-br.slack.com) *(브라질 포르투갈어)* [awesome-electron](https://github.com/sindresorhus/awesome-electron) 프로젝트에 커뮤니티가 운영중인 유용한 예제 어플리케이션과 도구, 리소스가 있으니 한번 참고해 보시기 diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index fea011a1cd..503864d042 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -135,7 +135,7 @@ Returns: [browserWindow](browser-window.md)에 대한 포커스가 발생했을 때 발생하는 이벤트 입니다. -**역주:** _포커스_는 창을 클릭해서 활성화 시켰을 때를 말합니다. +**역주:** _포커스_ 는 창을 클릭해서 활성화 시켰을 때를 말합니다. ### Event: 'browser-window-created' @@ -351,7 +351,7 @@ Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `t `tasks`는 다음과 같은 구조를 가지는 `Task` 객체의 배열입니다: -`Task` Object +`Task` Object: * `program` String - 실행할 프로그램의 경로. 보통 현재 작동중인 어플리케이션의 경로인 `process.execPath`를 지정합니다. * `arguments` String - `program`이 실행될 때 사용될 명령줄 인자. @@ -378,7 +378,7 @@ Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `t * `callback` Function -현재 어플리케이션을 **Single Instance Application**으로 만들어줍니다. +현재 어플리케이션을 **Single Instance Application** 으로 만들어줍니다. 이 메서드는 어플리케이션이 여러 번 실행됐을 때 다중 인스턴스가 생성되는 대신 한 개의 주 인스턴스만 유지되도록 만들 수 있습니다. 이때 중복 생성된 인스턴스는 주 인스턴스에 신호를 보내고 종료됩니다. @@ -397,7 +397,7 @@ Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `t 중복 생성된 인스턴스는 즉시 종료시켜야 합니다. OS X에선 사용자가 Finder에서 어플리케이션의 두 번째 인스턴스를 열려고 했을 때 자동으로 -**Single Instance**화 하고 `open-file`과 `open-url` 이벤트를 발생시킵니다. 그러나 +**Single Instance** 화 하고 `open-file`과 `open-url` 이벤트를 발생시킵니다. 그러나 사용자가 어플리케이션을 CLI 터미널에서 실행하면 운영체제 시스템의 싱글 인스턴스 메커니즘이 무시되며 그대로 중복 실행됩니다. 따라서 OS X에서도 이 메서드를 통해 확실히 중복 실행을 방지하는 것이 좋습니다. diff --git a/docs-translations/ko-KR/api/chrome-command-line-switches.md b/docs-translations/ko-KR/api/chrome-command-line-switches.md index 443a8f2187..f13f37920a 100644 --- a/docs-translations/ko-KR/api/chrome-command-line-switches.md +++ b/docs-translations/ko-KR/api/chrome-command-line-switches.md @@ -57,7 +57,7 @@ Electron이 세미콜론으로 구분된 호스트 리스트에서 지정한 프 예시: ```javascript -app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678')` +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678'); ``` 위 예시는 로컬 주소(`localhost`, `127.0.0.1`, 등)와 `google.com`의 서브도메인, diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index dc4ec67940..e6dec1bfc9 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -219,6 +219,20 @@ Returns: [`app`의 `login`이벤트](app.md#event-login)와 사용 방법은 같습니다. +### Event: 'found-in-page' + +Returns: + +* `event` Event +* `result` Object + * `requestId` Integer + * `finalUpdate` Boolean - 더 많은 응답이 따르는 경우를 표시합니다. + * `matches` Integer (Optional) - 일치하는 개수. + * `selectionArea` Object (Optional) - 첫 일치 부위의 좌표. + +[`webContents.findInPage`](web-contents.md#webcontentsfindinpage) 요청의 결과를 +사용할 수 있을 때 발생하는 이벤트입니다. + ### Event: 'media-started-playing' 미디어가 재생되기 시작할 때 발생하는 이벤트입니다. @@ -227,6 +241,15 @@ Returns: 미디어가 중지되거나 재생이 완료되었을 때 발생하는 이벤트입니다. +### Event: 'did-change-theme-color' + +페이지의 테마 색이 변경될 때 발생하는 이벤트입니다. 이 이벤트는 보통 meta 태그에 +의해서 발생합니다: + +```html + +``` + ## Instance Methods `webContents`객체는 다음과 같은 인스턴스 메서드들을 가지고 있습니다. @@ -417,6 +440,46 @@ CSS 코드를 현재 웹 페이지에 삽입합니다. 웹 페이지에서 `replaceMisspelling` 편집 커맨드를 실행합니다. +### `webContents.findInPage(text[, options])` + +* `text` String - 찾을 컨텐츠, 반드시 공백이 아니여야 합니다. +* `options` Object (Optional) + * `forward` Boolean - 앞에서부터 검색할지 뒤에서부터 검색할지 여부입니다. 기본값은 + `true`입니다. + * `findNext` Boolean - 작업을 계속 처리할지 첫 요청만 처리할지 여부입니다. 기본값은 + `false`입니다. + * `matchCase` Boolean - 검색이 대소문자를 구분할지 여부입니다. 기본값은 + `false`입니다. + * `wordStart` Boolean - 단어의 시작 부분만 볼 지 여부입니다. 기본값은 + `false`입니다. + * `medialCapitalAsWordStart` Boolean - `wordStart`와 합쳐질 때, 소문자 또는 + 비문자가 따라붙은 대문자로 일치가 시작하는 경우 단어 중간의 일치를 허용합니다. + 여러가지 다른 단어 내의 일치를 허용합니다. 기본값은 `false`입니다. + +웹 페이지에서 `text`에 일치하는 모든 대상을 찾는 요청을 시작하고 요청에 사용된 요청을 +표현하는 `정수(integer)`를 반환합니다. 요청의 결과는 +[`found-in-page`](web-contents.md#event-found-in-page) 이벤트를 통해 취득할 수 +있습니다. + +### `webContents.stopFindInPage(action)` + +* `action` String - [`webContents.findInPage`](web-contents.md#webcontentfindinpage) + 요청이 종료되었을 때 일어날 수 있는 작업을 지정합니다. + * `clearSelection` - 선택을 일반 선택으로 변경합니다. + * `keepSelection` - 선택을 취소합니다. + * `activateSelection` - 포커스한 후 선택된 노드를 클릭합니다. + +제공된 `action`에 대한 `webContents`의 모든 `findInPage` 요청을 중지합니다. + +```javascript +webContents.on('found-in-page', function(event, result) { + if (result.finalUpdate) + webContents.stopFindInPage("clearSelection"); +}); + +const requestId = webContents.findInPage("api"); +``` + ### `webContents.hasServiceWorker(callback)` * `callback` Function diff --git a/docs-translations/ko-KR/api/web-view-tag.md b/docs-translations/ko-KR/api/web-view-tag.md index 935976b6bd..8a4fd439bb 100644 --- a/docs-translations/ko-KR/api/web-view-tag.md +++ b/docs-translations/ko-KR/api/web-view-tag.md @@ -336,6 +336,37 @@ Service worker에 대한 개발자 도구를 엽니다. 페이지에서 `replaceMisspelling` 커맨드를 실행합니다. +### `webContents.findInPage(text[, options])` + +* `text` String - 찾을 컨텐츠, 반드시 공백이 아니여야 합니다. +* `options` Object (Optional) + * `forward` Boolean - 앞에서부터 검색할지 뒤에서부터 검색할지 여부입니다. 기본값은 + `true`입니다. + * `findNext` Boolean - 작업을 계속 처리할지 첫 요청만 처리할지 여부입니다. 기본값은 + `false`입니다. + * `matchCase` Boolean - 검색이 대소문자를 구분할지 여부입니다. 기본값은 + `false`입니다. + * `wordStart` Boolean - 단어의 시작 부분만 볼 지 여부입니다. 기본값은 + `false`입니다. + * `medialCapitalAsWordStart` Boolean - `wordStart`와 합쳐질 때, 소문자 또는 + 비문자가 따라붙은 대문자로 일치가 시작하는 경우 단어 중간의 일치를 허용합니다. + 여러가지 다른 단어 내의 일치를 허용합니다. 기본값은 `false`입니다. + +웹 페이지에서 `text`에 일치하는 모든 대상을 찾는 요청을 시작하고 요청에 사용된 요청을 +표현하는 `정수(integer)`를 반환합니다. 요청의 결과는 +[`found-in-page`](web-view-tag.md#event-found-in-page) 이벤트를 통해 취득할 수 +있습니다. + +### `webContents.stopFindInPage(action)` + +* `action` String - [`.findInPage`](web-view-tag.md#webviewtagfindinpage) + 요청이 종료되었을 때 일어날 수 있는 작업을 지정합니다. + * `clearSelection` - 선택을 일반 선택으로 변경합니다. + * `keepSelection` - 선택을 취소합니다. + * `activateSelection` - 포커스한 후 선택된 노드를 클릭합니다. + +제공된 `action`에 대한 `webContents`의 모든 `findInPage` 요청을 중지합니다. + ### `.print([options])` Webview 페이지를 인쇄합니다. `webContents.print([options])` 메서드와 같습니다. @@ -488,6 +519,28 @@ webview.addEventListener('console-message', function(e) { }); ``` +### Event: 'found-in-page' + +Returns: + +* `result` Object + * `requestId` Integer + * `finalUpdate` Boolean - 더 많은 응답이 따르는 경우를 표시합니다. + * `matches` Integer (Optional) - 일치하는 개수. + * `selectionArea` Object (Optional) - 첫 일치 부위의 좌표. + +[`webContents.findInPage`](web-contents.md#webcontentsfindinpage) 요청의 결과를 +사용할 수 있을 때 발생하는 이벤트입니다. + +```javascript +webview.addEventListener('found-in-page', function(e) { + if (e.result.finalUpdate) + webview.stopFindInPage("keepSelection"); +}); + +const rquestId = webview.findInPage("test"); +``` + ### Event: 'new-window' Returns: @@ -570,3 +623,20 @@ Returns: ### Event: 'destroyed' WebContents가 파괴될 때 발생하는 이벤트입니다. + +### Event: 'media-started-playing' + +미디어가 재생되기 시작할 때 발생하는 이벤트입니다. + +### Event: 'media-paused' + +미디어가 중지되거나 재생이 완료되었을 때 발생하는 이벤트입니다. + +### Event: 'did-change-theme-color' + +페이지의 테마 색이 변경될 때 발생하는 이벤트입니다. 이 이벤트는 보통 meta 태그에 +의해서 발생합니다: + +```html + +``` From 9ffe502fb1f3aa801ca7800ef69a50ff0ee72fbc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 25 Dec 2015 13:45:07 +0800 Subject: [PATCH 342/411] Bump v0.36.2 --- atom.gyp | 2 +- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atom.gyp b/atom.gyp index 78f71e9de1..c27471514b 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.36.1', + 'version%': '0.36.2', }, 'includes': [ 'filenames.gypi', diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index bf8c345466..2f321fa8fc 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.36.1 + 0.36.2 CFBundleShortVersionString - 0.36.1 + 0.36.2 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 99821482e8..225d910bc3 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,36,1,0 - PRODUCTVERSION 0,36,1,0 + FILEVERSION 0,36,2,0 + PRODUCTVERSION 0,36,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.36.1" + VALUE "FileVersion", "0.36.2" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.36.1" + VALUE "ProductVersion", "0.36.2" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index e9e283c146..9ad405faeb 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 0 #define ATOM_MINOR_VERSION 36 -#define ATOM_PATCH_VERSION 1 +#define ATOM_PATCH_VERSION 2 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/package.json b/package.json index 6f799fba44..f7202f28d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "0.36.1", + "version": "0.36.2", "devDependencies": { "asar": "^0.9.0", "coffee-script": "^1.9.2", From ab53e04a80f615c865e0db542346f264e6b86962 Mon Sep 17 00:00:00 2001 From: Frank Laub Date: Thu, 24 Dec 2015 23:04:44 -0800 Subject: [PATCH 343/411] s/Phrase/Phase --- docs/development/build-system-overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/build-system-overview.md b/docs/development/build-system-overview.md index 856c53273f..04067ce8c7 100644 --- a/docs/development/build-system-overview.md +++ b/docs/development/build-system-overview.md @@ -51,7 +51,7 @@ $ ./script/bootstrap.py --dev $ ./script/build.py -c D ``` -## Two-Phrase Project Generation +## Two-Phase Project Generation Electron links with different sets of libraries in `Release` and `Debug` builds. `gyp`, however, doesn't support configuring different link settings for From 6e43be99d7c39b7d3597a0a0a398fae3c6fe453f Mon Sep 17 00:00:00 2001 From: "Howard.Zuo" Date: Mon, 28 Dec 2015 13:10:15 +0800 Subject: [PATCH 344/411] add zh-CN translation for devtools --- .../zh-CN/tutorial/devtools-extension.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs-translations/zh-CN/tutorial/devtools-extension.md diff --git a/docs-translations/zh-CN/tutorial/devtools-extension.md b/docs-translations/zh-CN/tutorial/devtools-extension.md new file mode 100644 index 0000000000..9a2d9fc883 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/devtools-extension.md @@ -0,0 +1,45 @@ +# DevTools扩展 + +为了使调试更容易,Electron原生支持[Chrome DevTools Extension][devtools-extension]。 + +对于大多数DevTools的扩展,你可以直接下载源码,然后通过`BrowserWindow.addDevToolsExtension`API加载它们。Electron会记住已经加载了哪些扩展,所以你不需要每次创建一个新window时都调用`BrowserWindow.addDevToolsExtension`API。 + +** 注:React DevTools目前不能直接工作,详情留意[https://github.com/atom/electron/issues/915](https://github.com/atom/electron/issues/915) ** + +例如,要用[React DevTools Extension](https://github.com/facebook/react-devtools),你得先下载他的源码: + +```bash +$ cd /some-directory +$ git clone --recursive https://github.com/facebook/react-devtools.git +``` + +参考[`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md)来编译这个扩展源码。 + +然后你就可以在任意页面的DevTools里加载React DevTools了,通过控制台输入如下命令加载扩展: + +```javascript +const BrowserWindow = require('electron').remote.BrowserWindow; +BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); +``` + +要卸载扩展,可以调用`BrowserWindow.removeDevToolsExtension`API(扩展名作为参数传入),该扩展在下次打开DevTools时就不会加载了: + +```javascript +BrowserWindow.removeDevToolsExtension('React Developer Tools'); +``` + +## DevTools扩展的格式 + +理论上,Electron可以加载所有为chrome浏览器编写的DevTools扩展,但它们必须存放在文件夹里。那些以`crx`形式发布的扩展是不能被加载的,除非你把它们解压到一个文件夹里。 + +## 后台运行(background pages) + +Electron目前并不支持chrome扩展里的后台运行(background pages)功能,所以那些依赖此特性的DevTools扩展在Electron里可能无法正常工作。 + +## `chrome.*` APIs + +有些chrome扩展使用了`chrome.*`APIs,而且这些扩展在Electron中需要额外实现一些代码才能使用,所以并不是所有的这类扩展都已经在Electron中实现完毕了。 + +考虑到并非所有的`chrome.*`APIs都实现完毕,如果DevTools正在使用除了`chrome.devtools.*`之外的其它APIs,这个扩展很可能无法正常工作。你可以通过报告这个扩展的异常信息,这样做方便我们对该扩展的支持。 + +[devtools-extension]: https://developer.chrome.com/extensions/devtools From b5fd491c2dd60a3d5f8c527891d7fdaa72f06cdc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 28 Dec 2015 22:31:14 +0800 Subject: [PATCH 345/411] Fix circular reference caused by RemoteMemberFunction --- atom/renderer/api/lib/remote.coffee | 38 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 2828586849..88e598902c 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -72,20 +72,10 @@ metaToValue = (meta) -> # Polulate delegate members. for member in meta.members - do (member) -> - if member.type is 'function' - ret[member.name] = - class RemoteMemberFunction - constructor: -> - if @constructor is RemoteMemberFunction - # Constructor call. - obj = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', meta.id, member.name, wrapArgs(arguments) - return metaToValue obj - else - # Call member function. - ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CALL', meta.id, member.name, wrapArgs(arguments) - return metaToValue ret - else + if member.type is 'function' + ret[member.name] = createRemoteMemberFunction meta.id, member.name + else + do (member) -> Object.defineProperty ret, member.name, enumerable: true, configurable: false, @@ -93,11 +83,10 @@ metaToValue = (meta) -> # Set member data. ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_SET', meta.id, member.name, value value - get: -> # Get member data. - ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_GET', meta.id, member.name - metaToValue ret + val = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_GET', meta.id, member.name + metaToValue val # Track delegate object's life time, and tell the browser to clean up # when the object is GCed. @@ -117,6 +106,21 @@ metaToPlainObject = (meta) -> obj[name] = value for {name, value} in meta.members obj +# Create a RemoteMemberFunction instance. +# This function's content should not be inlined into metaToValue, otherwise V8 +# may consider it circular reference. +createRemoteMemberFunction = (metaId, name) -> + class RemoteMemberFunction + constructor: -> + if @constructor is RemoteMemberFunction + # Constructor call. + ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, name, wrapArgs(arguments) + return metaToValue ret + else + # Call member function. + ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CALL', metaId, name, wrapArgs(arguments) + return metaToValue ret + # Browser calls a callback in renderer. ipcRenderer.on 'ATOM_RENDERER_CALLBACK', (event, id, args) -> callbacksRegistry.apply id, metaToValue(args) From 6785870ddef7e364fb166239acf1b0a721afe5e9 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 28 Dec 2015 22:32:07 +0800 Subject: [PATCH 346/411] Variables are not shadowed in inline class --- atom/renderer/api/lib/remote.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 88e598902c..716d3be5fd 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -65,8 +65,8 @@ metaToValue = (meta) -> return metaToValue obj else # Function call. - ret = ipcRenderer.sendSync 'ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments) - return metaToValue ret + obj = ipcRenderer.sendSync 'ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments) + return metaToValue obj else ret = v8Util.createObjectWithName meta.name From 3d2163230b4b43b420d72b4073fd2ab8b35389d5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 28 Dec 2015 22:51:01 +0800 Subject: [PATCH 347/411] Optimize the case when creating plain object --- atom/common/api/atom_api_v8_util.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index bba3399a8d..dfa65ab8ec 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -10,14 +10,16 @@ namespace { v8::Local CreateObjectWithName(v8::Isolate* isolate, - v8::Local name) { + const std::string& name) { + if (name == "Object") + return v8::Object::New(isolate); v8::Local t = v8::FunctionTemplate::New(isolate); - t->SetClassName(name); + t->SetClassName(mate::StringToV8(isolate, name)); return t->GetFunction()->NewInstance(); } v8::Local GetHiddenValue(v8::Local object, - v8::Local key) { + v8::Local key) { return object->GetHiddenValue(key); } From ffc2870ccb6767378f0eb188a642d23bf00c2939 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 29 Dec 2015 10:17:35 +0800 Subject: [PATCH 348/411] Fix circular reference caused by Object.defineProperty --- atom/renderer/api/lib/remote.coffee | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 716d3be5fd..3ed44278bd 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -75,18 +75,7 @@ metaToValue = (meta) -> if member.type is 'function' ret[member.name] = createRemoteMemberFunction meta.id, member.name else - do (member) -> - Object.defineProperty ret, member.name, - enumerable: true, - configurable: false, - set: (value) -> - # Set member data. - ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_SET', meta.id, member.name, value - value - get: -> - # Get member data. - val = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_GET', meta.id, member.name - metaToValue val + Object.defineProperty ret, member.name, createRemoteMemberProperty(meta.id, member.name) # Track delegate object's life time, and tell the browser to clean up # when the object is GCed. @@ -121,6 +110,20 @@ createRemoteMemberFunction = (metaId, name) -> ret = ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_CALL', metaId, name, wrapArgs(arguments) return metaToValue ret +# Create configuration for defineProperty. +# This function's content should not be inlined into metaToValue, otherwise V8 +# may consider it circular reference. +createRemoteMemberProperty = (metaId, name) -> + enumerable: true, + configurable: false, + set: (value) -> + # Set member data. + ipcRenderer.sendSync 'ATOM_BROWSER_MEMBER_SET', metaId, name, value + value + get: -> + # Get member data. + metaToValue ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, name) + # Browser calls a callback in renderer. ipcRenderer.on 'ATOM_RENDERER_CALLBACK', (event, id, args) -> callbacksRegistry.apply id, metaToValue(args) From d8d963b780c3f1dd3e5f01f189014650f61be0c2 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 29 Dec 2015 10:29:48 +0800 Subject: [PATCH 349/411] Cache function templates created by CreateObjectWithName --- atom/common/api/atom_api_v8_util.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index dfa65ab8ec..7b9490df6a 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -2,19 +2,35 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +#include +#include + #include "atom/common/api/object_life_monitor.h" #include "atom/common/node_includes.h" +#include "base/stl_util.h" #include "native_mate/dictionary.h" #include "v8/include/v8-profiler.h" namespace { +using FunctionTemplateHandle = + v8::CopyablePersistentTraits::CopyablePersistent; + +// The handles are leaked on purpose. +std::map function_templates_; + v8::Local CreateObjectWithName(v8::Isolate* isolate, const std::string& name) { if (name == "Object") return v8::Object::New(isolate); + + if (ContainsKey(function_templates_, name)) + return v8::Local::New( + isolate, function_templates_[name])->GetFunction()->NewInstance(); + v8::Local t = v8::FunctionTemplate::New(isolate); t->SetClassName(mate::StringToV8(isolate, name)); + function_templates_[name] = FunctionTemplateHandle(isolate, t); return t->GetFunction()->NewInstance(); } From 2294a5ce693d01453bdb2424ac5d0238a24f235f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 29 Dec 2015 10:40:10 +0800 Subject: [PATCH 350/411] Leak FunctionTemplateHandle They are cached through the app's lifetime, and freeing them at the right time is complicate, so just leak them. --- atom/common/api/atom_api_v8_util.cc | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index 7b9490df6a..c86335adb1 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -13,10 +13,21 @@ namespace { -using FunctionTemplateHandle = - v8::CopyablePersistentTraits::CopyablePersistent; +// A Persistent that can be copied and will not free itself. +template +struct LeakedPersistentTraits { + typedef v8::Persistent > LeakedPersistent; + static const bool kResetInDestructor = false; + template + static V8_INLINE void Copy(const v8::Persistent& source, + LeakedPersistent* dest) { + // do nothing, just allow copy + } +}; // The handles are leaked on purpose. +using FunctionTemplateHandle = + LeakedPersistentTraits::LeakedPersistent; std::map function_templates_; v8::Local CreateObjectWithName(v8::Isolate* isolate, From 9d878ad6b272e96cf9f0bbea25c0ed555c20d6e9 Mon Sep 17 00:00:00 2001 From: Cyrille Lebeaupin Date: Wed, 16 Dec 2015 11:06:38 +0100 Subject: [PATCH 351/411] Add widevine third party Add 2 new command options to use widevine: - widevine-cdm-path: Path to widevine plugin - widevine-cdm-version: Version of the widevine plugin --- atom.gyp | 20 ++ atom/app/atom_content_client.cc | 114 +++++++++++- atom/browser/atom_browser_client.cc | 3 + atom/common/common_message_generator.h | 1 + atom/common/options_switches.cc | 6 + atom/common/options_switches.h | 3 + atom/renderer/atom_renderer_client.cc | 7 + atom/renderer/atom_renderer_client.h | 3 +- .../pepper/widevine_cdm_message_filter.cc | 70 +++++++ .../pepper/widevine_cdm_message_filter.h | 49 +++++ chromium_src/chrome/common/chrome_paths.cc | 5 +- .../chrome/common/widevine_cdm_constants.cc | 16 ++ .../chrome/common/widevine_cdm_constants.h | 19 ++ .../chrome/common/widevine_cdm_messages.h | 31 ++++ .../renderer/media/chrome_key_systems.cc | 173 ++++++++++++++++++ .../renderer/media/chrome_key_systems.h | 14 ++ filenames.gypi | 7 + 17 files changed, 529 insertions(+), 12 deletions(-) create mode 100644 chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc create mode 100644 chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h create mode 100644 chromium_src/chrome/common/widevine_cdm_constants.cc create mode 100644 chromium_src/chrome/common/widevine_cdm_constants.h create mode 100644 chromium_src/chrome/common/widevine_cdm_messages.h create mode 100644 chromium_src/chrome/renderer/media/chrome_key_systems.cc create mode 100644 chromium_src/chrome/renderer/media/chrome_key_systems.h diff --git a/atom.gyp b/atom.gyp index c27471514b..56883ccf04 100644 --- a/atom.gyp +++ b/atom.gyp @@ -235,6 +235,8 @@ # Defined in Chromium but not exposed in its gyp file. 'V8_USE_EXTERNAL_STARTUP_DATA', 'ENABLE_PLUGINS', + 'ENABLE_PEPPER_CDMS', + 'USE_PROPRIETARY_CODECS', ], 'sources': [ '<@(lib_sources)', @@ -260,11 +262,18 @@ '<(libchromiumcontent_src_dir)/third_party/libyuv/include', # The 'third_party/webrtc/modules/desktop_capture/desktop_frame.h' is using 'webrtc/base/scoped_ptr.h'. '<(libchromiumcontent_src_dir)/third_party/', + '<(libchromiumcontent_src_dir)/components/cdm', + '<(libchromiumcontent_src_dir)/third_party/widevine', ], 'direct_dependent_settings': { 'include_dirs': [ '.', ], + 'ldflags': [ + '-Wl,--whole-archive', + '<@(libchromiumcontent_libraries)', + '-Wl,--no-whole-archive', + ], }, 'export_dependent_settings': [ 'vendor/brightray/brightray.gyp:brightray', @@ -434,6 +443,7 @@ '<(libchromiumcontent_dir)/icudtl.dat', '<(libchromiumcontent_dir)/natives_blob.bin', '<(libchromiumcontent_dir)/snapshot_blob.bin', + '<(libchromiumcontent_dir)/widevinecdmadapter.plugin', ], 'xcode_settings': { 'ATOM_BUNDLE_ID': 'com.<(company_abbr).<(project_name).framework', @@ -490,6 +500,16 @@ '${BUILT_PRODUCTS_DIR}/<(product_name) Framework.framework/Versions/A/<(product_name) Framework', ], }, + { + 'postbuild_name': 'Fix path of libwidevinecdm', + 'action': [ + 'install_name_tool', + '-change', + '@loader_path/libwidevinecdm.dylib', + '@rpath/libwidevinecdm.dylib', + '${BUILT_PRODUCTS_DIR}/<(product_name) Framework.framework/Versions/A/<(product_name) Framework', + ], + }, { 'postbuild_name': 'Add symlinks for framework subdirectories', 'action': [ diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 7a1241f2bc..18bc36f944 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -11,14 +11,24 @@ #include "atom/common/chrome_version.h" #include "atom/common/options_switches.h" #include "base/command_line.h" +#include "base/files/file_util.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "content/public/common/content_constants.h" #include "content/public/common/pepper_plugin_info.h" #include "content/public/common/user_agent.h" #include "ppapi/shared_impl/ppapi_permissions.h" #include "url/url_constants.h" +#include "third_party/widevine/cdm/stub/widevine_cdm_version.h" + +// The following must be after widevine_cdm_version.h. +#if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) +// && !defined(WIDEVINE_CDM_IS_COMPONENT) +#include "chrome/common/widevine_cdm_constants.h" +#endif + namespace atom { namespace { @@ -63,6 +73,52 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, return plugin; } + +#if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) +//&& !defined(WIDEVINE_CDM_IS_COMPONENT) + +content::PepperPluginInfo CreateWidevineCdmInfo(const base::FilePath& path, + const std::string& version) { + content::PepperPluginInfo widevine_cdm; + widevine_cdm.is_out_of_process = true; + widevine_cdm.path = path; + widevine_cdm.name = kWidevineCdmDisplayName; + widevine_cdm.description = kWidevineCdmDescription + + std::string(" (version: ") + + version + ")"; + widevine_cdm.version = version; + content::WebPluginMimeType widevine_cdm_mime_type( + kWidevineCdmPluginMimeType, + kWidevineCdmPluginExtension, + kWidevineCdmPluginMimeTypeDescription); + + // Add the supported codecs as if they came from the component manifest. + std::vector codecs; + codecs.push_back(kCdmSupportedCodecVorbis); + codecs.push_back(kCdmSupportedCodecVp8); + codecs.push_back(kCdmSupportedCodecVp9); +#if defined(USE_PROPRIETARY_CODECS) + codecs.push_back(kCdmSupportedCodecAac); + codecs.push_back(kCdmSupportedCodecAvc1); +#endif // defined(USE_PROPRIETARY_CODECS) + std::string codec_string = base::JoinString( + codecs, std::string(1, kCdmSupportedCodecsValueDelimiter)); + widevine_cdm_mime_type.additional_param_names.push_back( + base::ASCIIToUTF16(kCdmSupportedCodecsParamName)); + widevine_cdm_mime_type.additional_param_values.push_back( + base::ASCIIToUTF16(codec_string)); + + widevine_cdm.mime_types.push_back(widevine_cdm_mime_type); + widevine_cdm.permissions = kWidevineCdmPluginPermissions; + + return widevine_cdm; +} + +#endif +// defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) && +// !defined(WIDEVINE_CDM_IS_COMPONENT) + + void ConvertStringWithSeparatorToVector(std::vector* vec, const char* separator, const char* cmd_switch) { @@ -76,6 +132,49 @@ void ConvertStringWithSeparatorToVector(std::vector* vec, } // namespace +void AddPepperFlashFromCommandLine( + std::vector* plugins) { + auto command_line = base::CommandLine::ForCurrentProcess(); + auto flash_path = command_line->GetSwitchValueNative( + switches::kPpapiFlashPath); + if (flash_path.empty()) + return; + + auto flash_version = command_line->GetSwitchValueASCII( + switches::kPpapiFlashVersion); + + plugins->push_back( + CreatePepperFlashInfo(base::FilePath(flash_path), flash_version)); +} + +#if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) +//&& !defined(WIDEVINE_CDM_IS_COMPONENT) +void AddWidevineCdmFromCommandLine( + std::vector* plugins) { + auto command_line = base::CommandLine::ForCurrentProcess(); + auto widevine_cdm_path = command_line->GetSwitchValueNative( + switches::kWidevineCdmPath); + if (widevine_cdm_path.empty()) + return; + + if (!base::PathExists(base::FilePath(widevine_cdm_path))) + return; + + auto widevine_cdm_version = command_line->GetSwitchValueASCII( + switches::kWidevineCdmVersion); + if (widevine_cdm_version.empty()) + return; + + plugins->push_back( + CreateWidevineCdmInfo(base::FilePath(widevine_cdm_path), + widevine_cdm_version)); +} +#endif +// defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) && +// !defined(WIDEVINE_CDM_IS_COMPONENT) + + + AtomContentClient::AtomContentClient() { } @@ -107,17 +206,12 @@ void AtomContentClient::AddAdditionalSchemes( void AtomContentClient::AddPepperPlugins( std::vector* plugins) { - auto command_line = base::CommandLine::ForCurrentProcess(); - auto flash_path = command_line->GetSwitchValuePath( - switches::kPpapiFlashPath); - if (flash_path.empty()) - return; + AddPepperFlashFromCommandLine(plugins); - auto flash_version = command_line->GetSwitchValueASCII( - switches::kPpapiFlashVersion); - - plugins->push_back( - CreatePepperFlashInfo(flash_path, flash_version)); + #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) + //&& !defined(WIDEVINE_CDM_IS_COMPONENT) + AddWidevineCdmFromCommandLine(plugins); + #endif } void AtomContentClient::AddServiceWorkerSchemes( diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 627d616cd6..87653a6b94 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -25,6 +25,7 @@ #include "base/strings/string_number_conversions.h" #include "chrome/browser/printing/printing_message_filter.h" #include "chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h" +#include "chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h" #include "chrome/browser/speech/tts_message_filter.h" #include "content/public/browser/browser_ppapi_host.h" #include "content/public/browser/client_certificate_delegate.h" @@ -105,6 +106,8 @@ void AtomBrowserClient::RenderProcessWillLaunch( int process_id = host->GetID(); host->AddFilter(new printing::PrintingMessageFilter(process_id)); host->AddFilter(new TtsMessageFilter(process_id, host->GetBrowserContext())); + host->AddFilter( + new WidevineCdmMessageFilter(process_id, host->GetBrowserContext())); } content::SpeechRecognitionManagerDelegate* diff --git a/atom/common/common_message_generator.h b/atom/common/common_message_generator.h index 24f0f63d9d..832de1abf7 100644 --- a/atom/common/common_message_generator.h +++ b/atom/common/common_message_generator.h @@ -7,3 +7,4 @@ #include "atom/common/api/api_messages.h" #include "chrome/common/print_messages.h" #include "chrome/common/tts_messages.h" +#include "chrome/common/widevine_cdm_messages.h" diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index fe279bbf04..6a3490f41d 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -148,6 +148,12 @@ const char kSharedWorker[] = "shared-worker"; const char kPageVisibility[] = "page-visiblity"; const char kOpenerID[] = "opener-id"; +// Widevine options +// Path to Widevine CDM binaries. +const char kWidevineCdmPath[] = "widevine-cdm-path"; +// Widevine CDM version. +const char kWidevineCdmVersion[] = "widevine-cdm-version"; + } // namespace switches } // namespace atom diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index d85e789c7b..161244450a 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -84,6 +84,9 @@ extern const char kSharedWorker[]; extern const char kPageVisibility[]; extern const char kOpenerID[]; +extern const char kWidevineCdmPath[]; +extern const char kWidevineCdmVersion[]; + } // namespace switches } // namespace atom diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 486aa2e234..ab2ac39815 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -5,6 +5,7 @@ #include "atom/renderer/atom_renderer_client.h" #include +#include #include "atom/common/api/api_messages.h" #include "atom/common/api/atom_bindings.h" @@ -15,6 +16,7 @@ #include "atom/renderer/guest_view_container.h" #include "atom/renderer/node_array_buffer_bridge.h" #include "base/command_line.h" +#include "chrome/renderer/media/chrome_key_systems.h" #include "chrome/renderer/pepper/pepper_helper.h" #include "chrome/renderer/printing/print_web_view_helper.h" #include "chrome/renderer/tts_dispatcher.h" @@ -234,4 +236,9 @@ void AtomRendererClient::EnableWebRuntimeFeatures() { blink::WebRuntimeFeatures::enableSharedWorker(true); } +void AtomRendererClient::AddKeySystems( + std::vector* key_systems) { + AddChromeKeySystems(key_systems); +} + } // namespace atom diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index 206ed9f9b9..e6da536666 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -6,6 +6,7 @@ #define ATOM_RENDERER_ATOM_RENDERER_CLIENT_H_ #include +#include #include "content/public/renderer/content_renderer_client.h" #include "content/public/renderer/render_process_observer.h" @@ -58,7 +59,7 @@ class AtomRendererClient : public content::ContentRendererClient, bool ShouldOverridePageVisibilityState( const content::RenderFrame* render_frame, blink::WebPageVisibilityState* override_state) override; - + void AddKeySystems(std::vector* key_systems) override; void EnableWebRuntimeFeatures(); scoped_ptr node_bindings_; diff --git a/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc new file mode 100644 index 0000000000..869bf85cd3 --- /dev/null +++ b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc @@ -0,0 +1,70 @@ +// 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 "chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h" + +#include "base/bind.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/common/webplugininfo.h" +#include "content/public/browser/plugin_service.h" +using content::PluginService; +using content::WebPluginInfo; +using content::BrowserThread; + +WidevineCdmMessageFilter::WidevineCdmMessageFilter( + int render_process_id, + content::BrowserContext* browser_context) + : BrowserMessageFilter(ChromeMsgStart), + render_process_id_(render_process_id), + browser_context_(browser_context) { +} + +bool WidevineCdmMessageFilter::OnMessageReceived(const IPC::Message& message) { + IPC_BEGIN_MESSAGE_MAP(WidevineCdmMessageFilter, message) +#if defined(ENABLE_PEPPER_CDMS) + IPC_MESSAGE_HANDLER( + ChromeViewHostMsg_IsInternalPluginAvailableForMimeType, + OnIsInternalPluginAvailableForMimeType) +#endif + IPC_MESSAGE_UNHANDLED(return false) + IPC_END_MESSAGE_MAP() + return true; +} + +#if defined(ENABLE_PEPPER_CDMS) +void WidevineCdmMessageFilter::OnIsInternalPluginAvailableForMimeType( + const std::string& mime_type, + bool* is_available, + std::vector* additional_param_names, + std::vector* additional_param_values) { + + std::vector plugins; + PluginService::GetInstance()->GetInternalPlugins(&plugins); + + for (size_t i = 0; i < plugins.size(); ++i) { + const WebPluginInfo& plugin = plugins[i]; + const std::vector& mime_types = + plugin.mime_types; + for (size_t j = 0; j < mime_types.size(); ++j) { + + if (mime_types[j].mime_type == mime_type) { + *is_available = true; + *additional_param_names = mime_types[j].additional_param_names; + *additional_param_values = mime_types[j].additional_param_values; + return; + } + } + } + + *is_available = false; +} +#endif // defined(ENABLE_PEPPER_CDMS) + +void WidevineCdmMessageFilter::OnDestruct() const { + BrowserThread::DeleteOnUIThread::Destruct(this); +} + +WidevineCdmMessageFilter::~WidevineCdmMessageFilter() { +} \ No newline at end of file diff --git a/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h new file mode 100644 index 0000000000..b8f3d562ac --- /dev/null +++ b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h @@ -0,0 +1,49 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_RENDERER_HOST_PEPPER_WIDEVINE_CDM_MESSAGE_FILTER_H_ +#define CHROME_BROWSER_RENDERER_HOST_PEPPER_WIDEVINE_CDM_MESSAGE_FILTER_H_ + +#include "chrome/common/widevine_cdm_messages.h" +#include "content/public/browser/browser_message_filter.h" + +namespace content { +class BrowserContext; +} + +class WidevineCdmMessageFilter : public content::BrowserMessageFilter { + public: + explicit WidevineCdmMessageFilter(int render_process_id, + content::BrowserContext* browser_context); + bool OnMessageReceived(const IPC::Message& message) override; + void OnDestruct() const override; + + private: + friend class content::BrowserThread; + friend class base::DeleteHelper; + + virtual ~WidevineCdmMessageFilter(); + + #if defined(ENABLE_PEPPER_CDMS) + // Returns whether any internal plugin supporting |mime_type| is registered + // and enabled. Does not determine whether the plugin can actually be + // instantiated (e.g. whether it has all its dependencies). + // When the returned *|is_available| is true, |additional_param_names| and + // |additional_param_values| contain the name-value pairs, if any, specified + // for the *first* non-disabled plugin found that is registered for + // |mime_type|. + void OnIsInternalPluginAvailableForMimeType( + const std::string& mime_type, + bool* is_available, + std::vector* additional_param_names, + std::vector* additional_param_values); +#endif + + int render_process_id_; + content::BrowserContext* browser_context_; + + DISALLOW_COPY_AND_ASSIGN(WidevineCdmMessageFilter); +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_PEPPER_WIDEVINE_CDM_MESSAGE_FILTER_H_ diff --git a/chromium_src/chrome/common/chrome_paths.cc b/chromium_src/chrome/common/chrome_paths.cc index d8a32446fe..3cc7b02ffa 100644 --- a/chromium_src/chrome/common/chrome_paths.cc +++ b/chromium_src/chrome/common/chrome_paths.cc @@ -15,6 +15,7 @@ #include "base/version.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths_internal.h" +#include "chrome/common/widevine_cdm_constants.h" #if defined(OS_ANDROID) #include "base/android/path_utils.h" @@ -31,6 +32,8 @@ #include "base/win/registry.h" #endif +#include "third_party/widevine/cdm/stub/widevine_cdm_version.h" + namespace { // The Pepper Flash plugins are in a directory with this name. @@ -362,7 +365,7 @@ bool PathProvider(int key, base::FilePath* result) { case chrome::DIR_COMPONENT_WIDEVINE_CDM: if (!PathService::Get(chrome::DIR_USER_DATA, &cur)) return false; - cur = cur.Append(FILE_PATH_LITERAL("WidevineCDM")); + cur = cur.Append(kWidevineCdmBaseDirectory); break; #endif // defined(WIDEVINE_CDM_IS_COMPONENT) // TODO(xhwang): FILE_WIDEVINE_CDM_ADAPTER has different meanings. diff --git a/chromium_src/chrome/common/widevine_cdm_constants.cc b/chromium_src/chrome/common/widevine_cdm_constants.cc new file mode 100644 index 0000000000..60f487e2ae --- /dev/null +++ b/chromium_src/chrome/common/widevine_cdm_constants.cc @@ -0,0 +1,16 @@ +// 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 "chrome/common/widevine_cdm_constants.h" + +#include "build/build_config.h" +#include "ppapi/shared_impl/ppapi_permissions.h" + +const base::FilePath::CharType kWidevineCdmBaseDirectory[] = + FILE_PATH_LITERAL("WidevineCDM"); + +const char kWidevineCdmPluginExtension[] = ""; + +const int32 kWidevineCdmPluginPermissions = ppapi::PERMISSION_DEV | + ppapi::PERMISSION_PRIVATE; diff --git a/chromium_src/chrome/common/widevine_cdm_constants.h b/chromium_src/chrome/common/widevine_cdm_constants.h new file mode 100644 index 0000000000..b626079a11 --- /dev/null +++ b/chromium_src/chrome/common/widevine_cdm_constants.h @@ -0,0 +1,19 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ +#define CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ + +#include "base/basictypes.h" +#include "base/files/file_path.h" + +// The Widevine CDM adapter and Widevine CDM are in this directory. +extern const base::FilePath::CharType kWidevineCdmBaseDirectory[]; + +extern const char kWidevineCdmPluginExtension[]; + +// Permission bits for Widevine CDM plugin. +extern const int32 kWidevineCdmPluginPermissions; + +#endif // CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ diff --git a/chromium_src/chrome/common/widevine_cdm_messages.h b/chromium_src/chrome/common/widevine_cdm_messages.h new file mode 100644 index 0000000000..17c908776b --- /dev/null +++ b/chromium_src/chrome/common/widevine_cdm_messages.h @@ -0,0 +1,31 @@ +// 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. + +// Multiply-included message file, hence no include guard. + +#include + +#include "ipc/ipc_message_macros.h" +// #include "ipc/ipc_param_traits.h" + +#define IPC_MESSAGE_START ChromeMsgStart + +// Renderer -> Browser messages. + +#if defined(ENABLE_PEPPER_CDMS) +// Returns whether any internal plugin supporting |mime_type| is registered and +// enabled. Does not determine whether the plugin can actually be instantiated +// (e.g. whether it has all its dependencies). +// When the returned *|is_available| is true, |additional_param_names| and +// |additional_param_values| contain the name-value pairs, if any, specified +// for the *first* non-disabled plugin found that is registered for |mime_type|. +IPC_SYNC_MESSAGE_CONTROL1_3( + ChromeViewHostMsg_IsInternalPluginAvailableForMimeType, + std::string /* mime_type */, + bool /* is_available */, + std::vector /* additional_param_names */, + std::vector /* additional_param_values */) +#endif + +// Browser -> Renderer messages. diff --git a/chromium_src/chrome/renderer/media/chrome_key_systems.cc b/chromium_src/chrome/renderer/media/chrome_key_systems.cc new file mode 100644 index 0000000000..d3c1f691a4 --- /dev/null +++ b/chromium_src/chrome/renderer/media/chrome_key_systems.cc @@ -0,0 +1,173 @@ +// Copyright 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 "chrome/renderer/media/chrome_key_systems.h" + +#include +#include + +#include "base/logging.h" +#include "base/strings/string16.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/widevine_cdm_messages.h" +#include "components/cdm/renderer/widevine_key_systems.h" +#include "content/public/renderer/render_thread.h" +#include "media/base/eme_constants.h" + +#if defined(OS_ANDROID) +#include "components/cdm/renderer/android_key_systems.h" +#endif + +// #include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR. +#include "third_party/widevine/cdm/stub/widevine_cdm_version.h" + +// The following must be after widevine_cdm_version.h. + +#if defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) +#include +#include "base/version.h" +#endif + +using media::KeySystemInfo; +using media::SupportedCodecs; + +#if defined(ENABLE_PEPPER_CDMS) +static bool IsPepperCdmAvailable( + const std::string& pepper_type, + std::vector* additional_param_names, + std::vector* additional_param_values) { + + bool is_available = false; + content::RenderThread::Get()->Send( + new ChromeViewHostMsg_IsInternalPluginAvailableForMimeType( + pepper_type, + &is_available, + additional_param_names, + additional_param_values)); + + return is_available; +} + +#if defined(WIDEVINE_CDM_AVAILABLE) +// This function finds "codecs" and parses the value into the vector |codecs|. +// Converts the codec strings to UTF-8 since we only expect ASCII strings and +// this simplifies the rest of the code in this file. +void GetSupportedCodecsForPepperCdm( + const std::vector& additional_param_names, + const std::vector& additional_param_values, + std::vector* codecs) { + DCHECK(codecs->empty()); + DCHECK_EQ(additional_param_names.size(), additional_param_values.size()); + for (size_t i = 0; i < additional_param_names.size(); ++i) { + if (additional_param_names[i] == + base::ASCIIToUTF16(kCdmSupportedCodecsParamName)) { + const base::string16& codecs_string16 = additional_param_values[i]; + std::string codecs_string; + if (!base::UTF16ToUTF8(codecs_string16.c_str(), + codecs_string16.length(), + &codecs_string)) { + DLOG(WARNING) << "Non-UTF-8 codecs string."; + // Continue using the best effort conversion. + } + *codecs = base::SplitString( + codecs_string, std::string(1, kCdmSupportedCodecsValueDelimiter), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + break; + } + } +} + +static void AddPepperBasedWidevine( + std::vector* concrete_key_systems) { + +#if defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) + + Version glibc_version(gnu_get_libc_version()); + DCHECK(glibc_version.IsValid()); + if (glibc_version.IsOlderThan(WIDEVINE_CDM_MIN_GLIBC_VERSION)) + return; +#endif // defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) + + std::vector additional_param_names; + std::vector additional_param_values; + if (!IsPepperCdmAvailable(kWidevineCdmPluginMimeType, + &additional_param_names, + &additional_param_values)) { + // DVLOG(1) << "Widevine CDM is not currently available."; + + return; + } + + std::vector codecs; + GetSupportedCodecsForPepperCdm(additional_param_names, + additional_param_values, + &codecs); + + SupportedCodecs supported_codecs = media::EME_CODEC_NONE; + + // Audio codecs are always supported. + // TODO(sandersd): Distinguish these from those that are directly supported, + // as those may offer a higher level of protection. + supported_codecs |= media::EME_CODEC_WEBM_OPUS; + supported_codecs |= media::EME_CODEC_WEBM_VORBIS; + +#if defined(USE_PROPRIETARY_CODECS) + supported_codecs |= media::EME_CODEC_MP4_AAC; + +#endif // defined(USE_PROPRIETARY_CODECS) + + for (size_t i = 0; i < codecs.size(); ++i) { + if (codecs[i] == kCdmSupportedCodecVp8) + supported_codecs |= media::EME_CODEC_WEBM_VP8; + + if (codecs[i] == kCdmSupportedCodecVp9) + supported_codecs |= media::EME_CODEC_WEBM_VP9; + +#if defined(USE_PROPRIETARY_CODECS) + if (codecs[i] == kCdmSupportedCodecAvc1) + supported_codecs |= media::EME_CODEC_MP4_AVC1; + +#endif // defined(USE_PROPRIETARY_CODECS) + } + + cdm::AddWidevineWithCodecs( + cdm::WIDEVINE, supported_codecs, +#if defined(OS_CHROMEOS) + media::EmeRobustness::HW_SECURE_ALL, // Maximum audio robustness. + media::EmeRobustness::HW_SECURE_ALL, // Maximim video robustness. + media::EmeSessionTypeSupport:: + SUPPORTED_WITH_IDENTIFIER, // Persistent-license. + media::EmeSessionTypeSupport:: + NOT_SUPPORTED, // Persistent-release-message. + media::EmeFeatureSupport::REQUESTABLE, // Persistent state. + media::EmeFeatureSupport::REQUESTABLE, // Distinctive identifier. +#else // (Desktop) + media::EmeRobustness::SW_SECURE_CRYPTO, // Maximum audio robustness. + media::EmeRobustness::SW_SECURE_DECODE, // Maximum video robustness. + media::EmeSessionTypeSupport::NOT_SUPPORTED, // persistent-license. + media::EmeSessionTypeSupport:: + NOT_SUPPORTED, // persistent-release-message. + media::EmeFeatureSupport::REQUESTABLE, // Persistent state. + media::EmeFeatureSupport::NOT_SUPPORTED, // Distinctive identifier. +#endif // defined(OS_CHROMEOS) + concrete_key_systems); +} +#endif // defined(WIDEVINE_CDM_AVAILABLE) +#endif // defined(ENABLE_PEPPER_CDMS) + +void AddChromeKeySystems(std::vector* key_systems_info) { + +#if defined(ENABLE_PEPPER_CDMS) +#if defined(WIDEVINE_CDM_AVAILABLE) + + AddPepperBasedWidevine(key_systems_info); + +#endif // defined(WIDEVINE_CDM_AVAILABLE) +#endif // defined(ENABLE_PEPPER_CDMS) + +#if defined(OS_ANDROID) + cdm::AddAndroidWidevine(key_systems_info); +#endif // defined(OS_ANDROID) +} diff --git a/chromium_src/chrome/renderer/media/chrome_key_systems.h b/chromium_src/chrome/renderer/media/chrome_key_systems.h new file mode 100644 index 0000000000..dfec84f3b3 --- /dev/null +++ b/chromium_src/chrome/renderer/media/chrome_key_systems.h @@ -0,0 +1,14 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_H_ +#define CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_H_ + +#include + +#include "media/base/key_system_info.h" + +void AddChromeKeySystems(std::vector* key_systems_info); + +#endif // CHROME_RENDERER_MEDIA_CHROME_KEY_SYSTEMS_H_ diff --git a/filenames.gypi b/filenames.gypi index f1209eeeb0..d7eb840926 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -418,6 +418,8 @@ 'chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.cc', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.h', + 'chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc', + 'chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.h', 'chromium_src/chrome/browser/speech/tts_controller.h', 'chromium_src/chrome/browser/speech/tts_controller_impl.cc', 'chromium_src/chrome/browser/speech/tts_controller_impl.h', @@ -450,6 +452,11 @@ 'chromium_src/chrome/common/tts_messages.h', 'chromium_src/chrome/common/tts_utterance_request.cc', 'chromium_src/chrome/common/tts_utterance_request.h', + 'chromium_src/chrome/common/widevine_cdm_messages.h', + 'chromium_src/chrome/common/widevine_cdm_constants.cc', + 'chromium_src/chrome/common/widevine_cdm_constants.h', + 'chromium_src/chrome/renderer/media/chrome_key_systems.cc', + 'chromium_src/chrome/renderer/media/chrome_key_systems.h', 'chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc', 'chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h', 'chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.cc', From 19ab68abfbf72e762e15167f84efe04df6d72f8f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 29 Dec 2015 14:17:15 +0800 Subject: [PATCH 352/411] Update libchromiumcontent to include widevine libraries --- script/lib/config.py | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index 5fbd15c172..fb953e1bf1 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '8b80cf3e621b19a9706cf73c7cb8bb4724f5cea1' +LIBCHROMIUMCONTENT_COMMIT = '9191c371e4a921c192beee09a66b8b12e88fbd54' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index be00b1d551..fe335c233b 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit be00b1d551a977612bfe74979bdbfdec5e8c748f +Subproject commit fe335c233ba47732dd2aa25140682ff025deeb77 From 38adaf5b3c2af2f6578ee64e8ab457d2f024fbe5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 29 Dec 2015 14:50:29 +0800 Subject: [PATCH 353/411] Remove hacky build settings --- atom.gyp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/atom.gyp b/atom.gyp index 56883ccf04..1fb1fb7e73 100644 --- a/atom.gyp +++ b/atom.gyp @@ -269,11 +269,6 @@ 'include_dirs': [ '.', ], - 'ldflags': [ - '-Wl,--whole-archive', - '<@(libchromiumcontent_libraries)', - '-Wl,--no-whole-archive', - ], }, 'export_dependent_settings': [ 'vendor/brightray/brightray.gyp:brightray', @@ -500,16 +495,6 @@ '${BUILT_PRODUCTS_DIR}/<(product_name) Framework.framework/Versions/A/<(product_name) Framework', ], }, - { - 'postbuild_name': 'Fix path of libwidevinecdm', - 'action': [ - 'install_name_tool', - '-change', - '@loader_path/libwidevinecdm.dylib', - '@rpath/libwidevinecdm.dylib', - '${BUILT_PRODUCTS_DIR}/<(product_name) Framework.framework/Versions/A/<(product_name) Framework', - ], - }, { 'postbuild_name': 'Add symlinks for framework subdirectories', 'action': [ From 8ca1bea58b7397fdc7f96f33e409758396df685b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 29 Dec 2015 15:45:34 +0800 Subject: [PATCH 354/411] Do not link with unnecessary libraries --- script/lib/config.py | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index fb953e1bf1..cb50d3ab57 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '9191c371e4a921c192beee09a66b8b12e88fbd54' +LIBCHROMIUMCONTENT_COMMIT = '0926c1773ffd358f480529434c3f98acfc39fc36' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index fe335c233b..a9121868e2 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit fe335c233ba47732dd2aa25140682ff025deeb77 +Subproject commit a9121868e20e26ee42ac324b2f8adb5ea57263dd From 2bbf86c524d8d5b4dbf09a48246eaac8767cdafc Mon Sep 17 00:00:00 2001 From: Nishanth Shanmugham Date: Tue, 29 Dec 2015 03:25:21 -0600 Subject: [PATCH 355/411] tray: Support file-drop from OS X Dock A long-standing Apple bug does not call `prepareForDragOperation:sender` for file drag-and-drop operations from the Dock. So we manually call our custom `handleDrop:sender` for all drag-and-drop cases (that is, from the Dock and from Finder). References to the bug in question: - http://stackoverflow.com/q/9534543/3309046 - http://openradar.appspot.com/radar?id=1745403 However, we still need to return YES from `prepareForDragOperation:sender`, otherwise the "drag failed" animation occurs. For the same reason, we also return YES from `performDragOperation:sender`. --- atom/browser/ui/tray_icon_cocoa.mm | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 997ac6fd31..62da2015e5 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -281,14 +281,14 @@ const CGFloat kVerticalTitleMargin = 2; - (void)draggingEnded:(id )sender { trayIcon_->NotifyDragEnded(); + + if (NSPointInRect([sender draggingLocation], self.frame)) { + trayIcon_->NotifyDrop(); + [self handleDrop:sender]; + } } -- (BOOL)prepareForDragOperation:(id )sender { - trayIcon_->NotifyDrop(); - return YES; -} - -- (BOOL)performDragOperation:(id )sender { +- (BOOL)handleDrop:(id )sender { NSPasteboard* pboard = [sender draggingPasteboard]; if ([[pboard types] containsObject:NSFilenamesPboardType]) { @@ -302,6 +302,14 @@ const CGFloat kVerticalTitleMargin = 2; return NO; } +- (BOOL)prepareForDragOperation:(id )sender { + return YES; +} + +- (BOOL)performDragOperation:(id )sender { + return YES; +} + - (BOOL)shouldHighlight { if (isHighlightEnable_ && forceHighlight_) return true; From 2e78aba09038fadc785e65a23aceae3dae492776 Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 29 Dec 2015 15:56:19 +0530 Subject: [PATCH 356/411] webFrame: support fetch api for schemes that are privileged. --- atom/renderer/api/atom_api_web_frame.cc | 2 ++ docs/api/web-frame.md | 4 ++-- spec/api-web-frame-spec.coffee | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 spec/api-web-frame-spec.coffee diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index e9b2b03055..83d67a8b66 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -112,6 +112,8 @@ void WebFrame::RegisterURLSchemeAsPrivileged(const std::string& scheme) { privileged_scheme); blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers( privileged_scheme); + blink::WebSecurityPolicy::registerURLSchemeAsSupportingFetchAPI( + privileged_scheme); } mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 38c5e30db4..114afd041c 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -87,7 +87,7 @@ Content Security Policy. * `scheme` String -Registers the `scheme` as secure, bypasses content security policy for resources and -allows registering ServiceWorker. +Registers the `scheme` as secure, bypasses content security policy for resources, +allows registering ServiceWorker and supports fetch API. [spellchecker]: https://github.com/atom/node-spellchecker diff --git a/spec/api-web-frame-spec.coffee b/spec/api-web-frame-spec.coffee new file mode 100644 index 0000000000..cece329084 --- /dev/null +++ b/spec/api-web-frame-spec.coffee @@ -0,0 +1,18 @@ +assert = require 'assert' +path = require 'path' + +{webFrame} = require 'electron' + +describe 'webFrame module', -> + fixtures = path.resolve __dirname, 'fixtures' + + describe 'webFrame.registerURLSchemeAsPrivileged', -> + it 'supports fetch api', (done) -> + webFrame.registerURLSchemeAsPrivileged 'file' + url = "file://#{fixtures}/assets/logo.png" + + fetch(url).then((response) -> + assert response.ok + done() + ).catch (err) -> + done('unexpected error : ' + err) From c76db0ba1d51a2100a0d920f099dfc4d9226cd4b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 29 Dec 2015 16:41:44 +0800 Subject: [PATCH 357/411] Fix coding styles --- atom/app/atom_content_client.cc | 32 ++++--------------- atom/browser/atom_browser_client.cc | 2 +- atom/renderer/atom_renderer_client.cc | 2 +- atom/renderer/atom_renderer_client.h | 1 + .../pepper/widevine_cdm_message_filter.cc | 4 +-- chromium_src/chrome/common/chrome_paths.cc | 3 +- 6 files changed, 13 insertions(+), 31 deletions(-) diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 18bc36f944..997f66447c 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -19,13 +19,10 @@ #include "content/public/common/pepper_plugin_info.h" #include "content/public/common/user_agent.h" #include "ppapi/shared_impl/ppapi_permissions.h" +#include "third_party/widevine/cdm/stub/widevine_cdm_version.h" #include "url/url_constants.h" -#include "third_party/widevine/cdm/stub/widevine_cdm_version.h" - -// The following must be after widevine_cdm_version.h. #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) -// && !defined(WIDEVINE_CDM_IS_COMPONENT) #include "chrome/common/widevine_cdm_constants.h" #endif @@ -73,10 +70,7 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, return plugin; } - #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) -//&& !defined(WIDEVINE_CDM_IS_COMPONENT) - content::PepperPluginInfo CreateWidevineCdmInfo(const base::FilePath& path, const std::string& version) { content::PepperPluginInfo widevine_cdm; @@ -113,11 +107,7 @@ content::PepperPluginInfo CreateWidevineCdmInfo(const base::FilePath& path, return widevine_cdm; } - #endif -// defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) && -// !defined(WIDEVINE_CDM_IS_COMPONENT) - void ConvertStringWithSeparatorToVector(std::vector* vec, const char* separator, @@ -148,12 +138,11 @@ void AddPepperFlashFromCommandLine( } #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) -//&& !defined(WIDEVINE_CDM_IS_COMPONENT) void AddWidevineCdmFromCommandLine( std::vector* plugins) { auto command_line = base::CommandLine::ForCurrentProcess(); auto widevine_cdm_path = command_line->GetSwitchValueNative( - switches::kWidevineCdmPath); + switches::kWidevineCdmPath); if (widevine_cdm_path.empty()) return; @@ -161,19 +150,14 @@ void AddWidevineCdmFromCommandLine( return; auto widevine_cdm_version = command_line->GetSwitchValueASCII( - switches::kWidevineCdmVersion); + switches::kWidevineCdmVersion); if (widevine_cdm_version.empty()) return; - plugins->push_back( - CreateWidevineCdmInfo(base::FilePath(widevine_cdm_path), - widevine_cdm_version)); + plugins->push_back(CreateWidevineCdmInfo(base::FilePath(widevine_cdm_path), + widevine_cdm_version)); } #endif -// defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) && -// !defined(WIDEVINE_CDM_IS_COMPONENT) - - AtomContentClient::AtomContentClient() { } @@ -207,11 +191,9 @@ void AtomContentClient::AddAdditionalSchemes( void AtomContentClient::AddPepperPlugins( std::vector* plugins) { AddPepperFlashFromCommandLine(plugins); - - #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) - //&& !defined(WIDEVINE_CDM_IS_COMPONENT) +#if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) AddWidevineCdmFromCommandLine(plugins); - #endif +#endif } void AtomContentClient::AddServiceWorkerSchemes( diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 87653a6b94..5ad8e69ffc 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -107,7 +107,7 @@ void AtomBrowserClient::RenderProcessWillLaunch( host->AddFilter(new printing::PrintingMessageFilter(process_id)); host->AddFilter(new TtsMessageFilter(process_id, host->GetBrowserContext())); host->AddFilter( - new WidevineCdmMessageFilter(process_id, host->GetBrowserContext())); + new WidevineCdmMessageFilter(process_id, host->GetBrowserContext())); } content::SpeechRecognitionManagerDelegate* diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index ab2ac39815..6abe839b83 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -237,7 +237,7 @@ void AtomRendererClient::EnableWebRuntimeFeatures() { } void AtomRendererClient::AddKeySystems( - std::vector* key_systems) { + std::vector* key_systems) { AddChromeKeySystems(key_systems); } diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index e6da536666..ee082e964c 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -60,6 +60,7 @@ class AtomRendererClient : public content::ContentRendererClient, const content::RenderFrame* render_frame, blink::WebPageVisibilityState* override_state) override; void AddKeySystems(std::vector* key_systems) override; + void EnableWebRuntimeFeatures(); scoped_ptr node_bindings_; diff --git a/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc index 869bf85cd3..c926b9e94a 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc +++ b/chromium_src/chrome/browser/renderer_host/pepper/widevine_cdm_message_filter.cc @@ -9,6 +9,7 @@ #include "content/public/browser/render_process_host.h" #include "content/public/common/webplugininfo.h" #include "content/public/browser/plugin_service.h" + using content::PluginService; using content::WebPluginInfo; using content::BrowserThread; @@ -39,7 +40,6 @@ void WidevineCdmMessageFilter::OnIsInternalPluginAvailableForMimeType( bool* is_available, std::vector* additional_param_names, std::vector* additional_param_values) { - std::vector plugins; PluginService::GetInstance()->GetInternalPlugins(&plugins); @@ -67,4 +67,4 @@ void WidevineCdmMessageFilter::OnDestruct() const { } WidevineCdmMessageFilter::~WidevineCdmMessageFilter() { -} \ No newline at end of file +} diff --git a/chromium_src/chrome/common/chrome_paths.cc b/chromium_src/chrome/common/chrome_paths.cc index 3cc7b02ffa..293ea2638a 100644 --- a/chromium_src/chrome/common/chrome_paths.cc +++ b/chromium_src/chrome/common/chrome_paths.cc @@ -16,6 +16,7 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths_internal.h" #include "chrome/common/widevine_cdm_constants.h" +#include "third_party/widevine/cdm/stub/widevine_cdm_version.h" #if defined(OS_ANDROID) #include "base/android/path_utils.h" @@ -32,8 +33,6 @@ #include "base/win/registry.h" #endif -#include "third_party/widevine/cdm/stub/widevine_cdm_version.h" - namespace { // The Pepper Flash plugins are in a directory with this name. From c47aebaeb4dcc694205d142463c06941e51642fc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 29 Dec 2015 21:38:01 +0800 Subject: [PATCH 358/411] Do not ship widevine plugin --- atom.gyp | 1 - script/lib/config.py | 2 +- vendor/brightray | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/atom.gyp b/atom.gyp index 1fb1fb7e73..3f76dd4613 100644 --- a/atom.gyp +++ b/atom.gyp @@ -438,7 +438,6 @@ '<(libchromiumcontent_dir)/icudtl.dat', '<(libchromiumcontent_dir)/natives_blob.bin', '<(libchromiumcontent_dir)/snapshot_blob.bin', - '<(libchromiumcontent_dir)/widevinecdmadapter.plugin', ], 'xcode_settings': { 'ATOM_BUNDLE_ID': 'com.<(company_abbr).<(project_name).framework', diff --git a/script/lib/config.py b/script/lib/config.py index cb50d3ab57..5a40efbff0 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '0926c1773ffd358f480529434c3f98acfc39fc36' +LIBCHROMIUMCONTENT_COMMIT = 'ae1b1395d809df1c5bf3ff74257dcfecf69a91bd' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index a9121868e2..7c7c094570 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit a9121868e20e26ee42ac324b2f8adb5ea57263dd +Subproject commit 7c7c094570263c91d13e0eec754726730a43e642 From 4fd08cccb4318a91d76966ee3306c5050d870648 Mon Sep 17 00:00:00 2001 From: Adam Lynch Date: Sun, 27 Dec 2015 03:21:10 +0000 Subject: [PATCH 359/411] Fixing link to web contents from ipcMain --- docs/api/ipc-main.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/api/ipc-main.md b/docs/api/ipc-main.md index 7c37a26c03..bbaa3c0043 100644 --- a/docs/api/ipc-main.md +++ b/docs/api/ipc-main.md @@ -7,7 +7,7 @@ a renderer will be emitted to this module. ## Sending Messages It is also possible to send messages from the main process to the renderer -process, see [webContents.send][webcontents-send] for more information. +process, see [webContents.send](web-contents.md#webcontentssendchannel-arg1-arg2-) for more information. * When sending a message, the event name is the `channel`. * To reply a synchronous message, you need to set `event.returnValue`. @@ -66,6 +66,4 @@ Set this to the value to be returned in a synchronous message. Returns the `webContents` that sent the message, you can call `event.sender.send` to reply to the asynchronous message, see -[webContents.send][webcontents-send] for more information. - -[webcontents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- +[webContents.send](web-contents.md#webcontentssendchannel-arg1-arg2-) for more information. From 72374b6e31b41ac257e11da14e441e9a0958f981 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 30 Dec 2015 01:41:31 +0530 Subject: [PATCH 360/411] remote: support arguments of type Date --- atom/browser/lib/rpc-server.coffee | 1 + atom/renderer/api/lib/remote.coffee | 2 ++ spec/api-ipc-spec.coffee | 10 +++++++++- spec/fixtures/module/print_name.js | 4 ++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index e0256081e6..873349f3e9 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -67,6 +67,7 @@ unwrapArgs = (sender, args) -> when 'remote-object' then objectsRegistry.get meta.id when 'array' then unwrapArgs sender, meta.value when 'buffer' then new Buffer(meta.value) + when 'date' then new Date(meta.value) when 'promise' then Promise.resolve(then: metaToValue(meta.then)) when 'object' ret = v8Util.createObjectWithName meta.name diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 3ed44278bd..b73fbf50b7 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -18,6 +18,8 @@ wrapArgs = (args, visited=[]) -> type: 'array', value: wrapArgs(value, visited) else if Buffer.isBuffer value type: 'buffer', value: Array::slice.call(value, 0) + else if value instanceof Date + type: 'date', value: value.getTime() else if value?.constructor.name is 'Promise' type: 'promise', then: valueToMeta(value.then.bind(value)) else if value? and typeof value is 'object' and v8Util.getHiddenValue value, 'atomId' diff --git a/spec/api-ipc-spec.coffee b/spec/api-ipc-spec.coffee index 1aa715c9f7..67ef717d50 100644 --- a/spec/api-ipc-spec.coffee +++ b/spec/api-ipc-spec.coffee @@ -53,11 +53,19 @@ describe 'ipc module', -> assert.equal obj.test, 'test' describe 'remote value in browser', -> + print = path.join(fixtures, 'module', 'print_name.js') + it 'keeps its constructor name for objects', -> buf = new Buffer('test') - print_name = remote.require path.join(fixtures, 'module', 'print_name.js') + print_name = remote.require print assert.equal print_name.print(buf), 'Buffer' + it 'supports instanceof Date', -> + now = new Date() + print_name = remote.require print + assert.equal print_name.print(now), 'Date' + assert.deepEqual print_name.echo(now), now + describe 'remote promise', -> it 'can be used as promise in each side', (done) -> promise = remote.require path.join(fixtures, 'module', 'promise.js') diff --git a/spec/fixtures/module/print_name.js b/spec/fixtures/module/print_name.js index 09ec33341f..01d13f4ba8 100644 --- a/spec/fixtures/module/print_name.js +++ b/spec/fixtures/module/print_name.js @@ -1,3 +1,7 @@ exports.print = function(obj) { return obj.constructor.name; } + +exports.echo = function(obj) { + return obj; +} From 20d03b10d9004787f4b5ab2b78654e1a29372a96 Mon Sep 17 00:00:00 2001 From: Nishanth Shanmugham Date: Tue, 29 Dec 2015 18:35:54 -0600 Subject: [PATCH 361/411] osx: Implement clear recent documents Fixes #3932 --- atom/browser/browser_mac.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 4944e7d7d8..8b1dd31f35 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -29,6 +29,7 @@ void Browser::AddRecentDocument(const base::FilePath& path) { } void Browser::ClearRecentDocuments() { + [[NSDocumentController sharedDocumentController] clearRecentDocuments:nil]; } void Browser::SetAppUserModelID(const base::string16& name) { From c5238bb8f0b3bb16573a11f0d6cbba9177696fc0 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 30 Dec 2015 11:40:52 +0800 Subject: [PATCH 362/411] Update brightray and libchromiumcontent with widevine support --- .../renderer/media/chrome_key_systems.cc | 18 +----------------- script/lib/config.py | 2 +- vendor/brightray | 2 +- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/chromium_src/chrome/renderer/media/chrome_key_systems.cc b/chromium_src/chrome/renderer/media/chrome_key_systems.cc index d3c1f691a4..5d97169005 100644 --- a/chromium_src/chrome/renderer/media/chrome_key_systems.cc +++ b/chromium_src/chrome/renderer/media/chrome_key_systems.cc @@ -38,7 +38,6 @@ static bool IsPepperCdmAvailable( const std::string& pepper_type, std::vector* additional_param_names, std::vector* additional_param_values) { - bool is_available = false; content::RenderThread::Get()->Send( new ChromeViewHostMsg_IsInternalPluginAvailableForMimeType( @@ -81,9 +80,7 @@ void GetSupportedCodecsForPepperCdm( static void AddPepperBasedWidevine( std::vector* concrete_key_systems) { - #if defined(WIDEVINE_CDM_MIN_GLIBC_VERSION) - Version glibc_version(gnu_get_libc_version()); DCHECK(glibc_version.IsValid()); if (glibc_version.IsOlderThan(WIDEVINE_CDM_MIN_GLIBC_VERSION)) @@ -95,8 +92,7 @@ static void AddPepperBasedWidevine( if (!IsPepperCdmAvailable(kWidevineCdmPluginMimeType, &additional_param_names, &additional_param_values)) { - // DVLOG(1) << "Widevine CDM is not currently available."; - + DVLOG(1) << "Widevine CDM is not currently available."; return; } @@ -112,23 +108,18 @@ static void AddPepperBasedWidevine( // as those may offer a higher level of protection. supported_codecs |= media::EME_CODEC_WEBM_OPUS; supported_codecs |= media::EME_CODEC_WEBM_VORBIS; - #if defined(USE_PROPRIETARY_CODECS) supported_codecs |= media::EME_CODEC_MP4_AAC; - #endif // defined(USE_PROPRIETARY_CODECS) for (size_t i = 0; i < codecs.size(); ++i) { if (codecs[i] == kCdmSupportedCodecVp8) supported_codecs |= media::EME_CODEC_WEBM_VP8; - if (codecs[i] == kCdmSupportedCodecVp9) supported_codecs |= media::EME_CODEC_WEBM_VP9; - #if defined(USE_PROPRIETARY_CODECS) if (codecs[i] == kCdmSupportedCodecAvc1) supported_codecs |= media::EME_CODEC_MP4_AVC1; - #endif // defined(USE_PROPRIETARY_CODECS) } @@ -158,16 +149,9 @@ static void AddPepperBasedWidevine( #endif // defined(ENABLE_PEPPER_CDMS) void AddChromeKeySystems(std::vector* key_systems_info) { - #if defined(ENABLE_PEPPER_CDMS) #if defined(WIDEVINE_CDM_AVAILABLE) - AddPepperBasedWidevine(key_systems_info); - #endif // defined(WIDEVINE_CDM_AVAILABLE) #endif // defined(ENABLE_PEPPER_CDMS) - -#if defined(OS_ANDROID) - cdm::AddAndroidWidevine(key_systems_info); -#endif // defined(OS_ANDROID) } diff --git a/script/lib/config.py b/script/lib/config.py index 5a40efbff0..fb82c63adb 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'ae1b1395d809df1c5bf3ff74257dcfecf69a91bd' +LIBCHROMIUMCONTENT_COMMIT = '389d11b3bba3bdd536075c4743dec9ff4e0965ff' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index 7c7c094570..3961caaa0a 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 7c7c094570263c91d13e0eec754726730a43e642 +Subproject commit 3961caaa0a38aeb06e1afa4ef12fe9e7c6c24e5d From 1f7d72bcc0edbfc7044a7b798694eb2d477df3dc Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 29 Dec 2015 22:56:27 +0900 Subject: [PATCH 363/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/web-frame.md | 5 +++-- docs-translations/ko-KR/development/build-system-overview.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs-translations/ko-KR/api/web-frame.md b/docs-translations/ko-KR/api/web-frame.md index 2e4a469b54..8181c0f3bf 100644 --- a/docs-translations/ko-KR/api/web-frame.md +++ b/docs-translations/ko-KR/api/web-frame.md @@ -70,7 +70,7 @@ webFrame.setSpellCheckProvider("en-US", true, { * `scheme` String -지정한 `scheme`을 보안 스킴으로 등록합니다. +`scheme`을 보안 스킴으로 등록합니다. 보안 스킴은 혼합된 컨텐츠 경고를 발생시키지 않습니다. 예를 들어 `https` 와 `data`는 네트워크 공격자로부터 손상될 가능성이 없기 때문에 보안 스킴이라고 할 수 있습니다. @@ -85,6 +85,7 @@ webFrame.setSpellCheckProvider("en-US", true, { * `scheme` String -보안 `scheme`를 지정합니다. 리소스와 ServiceWorker 설정에 대해 보안 정책을 우회합니다. +`scheme`를 보안된 스킴으로 등록합니다. 리소스에 대해 보안 정책을 우회하며, +ServiceWorker의 등록과 fetch API를 사용할 수 있도록 지원합니다. [spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs-translations/ko-KR/development/build-system-overview.md b/docs-translations/ko-KR/development/build-system-overview.md index 71002ff584..79ead32718 100644 --- a/docs-translations/ko-KR/development/build-system-overview.md +++ b/docs-translations/ko-KR/development/build-system-overview.md @@ -48,7 +48,7 @@ $ ./script/bootstrap.py --dev $ ./script/build.py -c D ``` -## 프로젝트 생성 (two-phrase) +## 두 절차에 따른 프로젝트 생성 Electron은 `Release`와 `Debug` 빌드가 서로 다른 라이브러리 링크 방식을 사용합니다. 하지만 `gyp`는 따로 빌드 설정을 분리하여 라이브러리 링크 방식을 정의하는 방법을 From 1585a7a0adf2ac4058d79e8a1b036c5eae6f850c Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 30 Dec 2015 10:46:22 +0530 Subject: [PATCH 364/411] browser: provide localized string from resourcebundle --- atom.gyp | 32 +++----------------------------- atom/app/atom_content_client.cc | 5 +++++ atom/app/atom_content_client.h | 1 + 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/atom.gyp b/atom.gyp index 3f76dd4613..83605eedbc 100644 --- a/atom.gyp +++ b/atom.gyp @@ -121,10 +121,6 @@ ], }], ], - }, { # OS=="mac" - 'dependencies': [ - 'make_locale_paks', - ], }], # OS!="mac" ['OS=="win"', { 'include_dirs': [ @@ -155,6 +151,7 @@ 'destination': '<(PRODUCT_DIR)', 'files': [ '<@(copied_libraries)', + '<(libchromiumcontent_dir)/locales', '<(libchromiumcontent_dir)/libEGL.dll', '<(libchromiumcontent_dir)/libGLESv2.dll', '<(libchromiumcontent_dir)/icudtl.dat', @@ -203,6 +200,7 @@ 'destination': '<(PRODUCT_DIR)', 'files': [ '<@(copied_libraries)', + '<(libchromiumcontent_dir)/locales', '<(libchromiumcontent_dir)/icudtl.dat', '<(libchromiumcontent_dir)/content_shell.pak', '<(libchromiumcontent_dir)/natives_blob.bin', @@ -434,6 +432,7 @@ 'mac_bundle': 1, 'mac_bundle_resources': [ 'atom/common/resources/mac/MainMenu.xib', + '<(libchromiumcontent_dir)/locales', '<(libchromiumcontent_dir)/content_shell.pak', '<(libchromiumcontent_dir)/icudtl.dat', '<(libchromiumcontent_dir)/natives_blob.bin', @@ -546,31 +545,6 @@ }, }, # target helper ], - }, { # OS=="mac" - 'targets': [ - { - 'target_name': 'make_locale_paks', - 'type': 'none', - 'actions': [ - { - 'action_name': 'Make Empty Paks', - 'inputs': [ - 'tools/make_locale_paks.py', - ], - 'outputs': [ - '<(PRODUCT_DIR)/locales' - ], - 'action': [ - 'python', - 'tools/make_locale_paks.py', - '<(PRODUCT_DIR)', - '<@(locales)', - ], - 'msvs_cygwin_shell': 0, - }, - ], - }, - ], }], # OS!="mac" ], } diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 997f66447c..25e9d0f193 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -20,6 +20,7 @@ #include "content/public/common/user_agent.h" #include "ppapi/shared_impl/ppapi_permissions.h" #include "third_party/widevine/cdm/stub/widevine_cdm_version.h" +#include "ui/base/l10n/l10n_util.h" #include "url/url_constants.h" #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) @@ -175,6 +176,10 @@ std::string AtomContentClient::GetUserAgent() const { ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING); } +base::string16 AtomContentClient::GetLocalizedString(int message_id) const { + return l10n_util::GetStringUTF16(message_id); +} + void AtomContentClient::AddAdditionalSchemes( std::vector* standard_schemes, std::vector* savable_schemes) { diff --git a/atom/app/atom_content_client.h b/atom/app/atom_content_client.h index 76ac37642a..33dbe19d99 100644 --- a/atom/app/atom_content_client.h +++ b/atom/app/atom_content_client.h @@ -22,6 +22,7 @@ class AtomContentClient : public brightray::ContentClient { // content::ContentClient: std::string GetProduct() const override; std::string GetUserAgent() const override; + base::string16 GetLocalizedString(int message_id) const override; void AddAdditionalSchemes( std::vector* standard_schemes, std::vector* savable_schemes) override; From fe87570876647e13418d435bede87996b185c790 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 30 Dec 2015 16:10:00 +0800 Subject: [PATCH 365/411] docs: Using Widevine CDM Plugin --- docs/README.md | 1 + docs/tutorial/using-widevine-cdm-plugin.md | 78 ++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 docs/tutorial/using-widevine-cdm-plugin.md diff --git a/docs/README.md b/docs/README.md index 3a47eff0fa..2503612d4e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,7 @@ select the tag that matches your version. * [Using Selenium and WebDriver](tutorial/using-selenium-and-webdriver.md) * [DevTools Extension](tutorial/devtools-extension.md) * [Using Pepper Flash Plugin](tutorial/using-pepper-flash-plugin.md) +* [Using Widevine CDM Plugin](tutorial/using-widevine-cdm-plugin.md) ## Tutorials diff --git a/docs/tutorial/using-widevine-cdm-plugin.md b/docs/tutorial/using-widevine-cdm-plugin.md new file mode 100644 index 0000000000..340dad343c --- /dev/null +++ b/docs/tutorial/using-widevine-cdm-plugin.md @@ -0,0 +1,78 @@ +# Using Widevine CDM Plugin + +In Electron you can use the Widevine CDM plugin shipped with Chrome browser. + +## Getting the plugin + +Electron doesn't ship with the Widevine CDM plugin for license reasons, to get +it, you need to install the official Chrome browser first, which should match +the architecture and Chrome version of the Electron build you use. + +### Windows & OS X + +Open `chrome://components/` in Chrome browser, find `WidevineCdm` and make +sure it is up to date, then you can find all the plugin binaries from the +`APP_DATA/Google/Chrome/WidevineCDM/VERSION/_platform_specific/PLATFORM_ARCH/` +directory. + +`APP_DATA` is system's location for storing app data, on Windows it is +`%LOCALAPPDATA%`, on OS X it is `~/Library/Application Support`. `VERSION` is +Widevine CDM plugin's version string, like `1.4.8.866`. `PLATFORM` is `mac` or +`win`. `ARCH` is `x86` or `x64`. + +On Windows the required binaries are `widevinecdm.dll` and +`widevinecdmadapter.dll`, on OS X they are `libwidevinecdm.dylib` and +`widevinecdmadapter.plugin`. You can copy them to anywhere you like, but they +have to be put together. + +### Linux + +On Linux the plugin binaries are shipped together with Chrome browser, you can +find them under `/opt/google/chrome`, the filenames are `libwidevinecdm.so` and +`libwidevinecdmadapter.so`. + +## Using the plugin + +After getting the plugin files, you should pass the `widevinecdmadapter`'s path +to Electron with `--widevine-cdm-path` command line switch, and the plugin's +version with `--widevine-cdm-version` switch. + +__Note:__ Though only the `widevinecdmadapter` binary is passed to Electron, the +`widevinecdm` binary has to be put aside it. + +The command line switches have to be passed before the `ready` event of `app` +module gets emitted, and the page that uses this plugin must have plugin +enabled. + +Example code: + +```javascript +// You have to pass the filename of `widevinecdmadapter` here, it is +// * `widevinecdmadapter.plugin` on OS X, +// * `libwidevinecdmadapter.so` on Linux, +// * `widevinecdmadapter.dll` on Windows. +app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin'); +// The version of plugin can be got from `chrome://plugins` page in Chrome. +app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866'); + +var mainWindow = null; +app.on('ready', function() { + mainWindow = new BrowserWindow({ + webPreferences: { + // The `plugins` have to be enabled. + plugins: true + } + }) +}); +``` + +## Verifying the plugin + +To verify whether the plugin works, you can use following ways: + +* Open devtools and check whether `navigator.plugins` includes the Widevine +CDM plugin. +* Open https://shaka-player-demo.appspot.com/ and load a manifest that uses +`Widevine`. +* Open http://www.dash-player.com/demo/drm-test-area/, check whether the page +says `bitdash uses Widevine in your browser`, then play the video. From b3832629a2736d4502d2c452c8ef3c83b2e5bcb1 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 30 Dec 2015 16:12:40 +0800 Subject: [PATCH 366/411] Update libchromiumcontent: component_updater is not needed --- script/lib/config.py | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index fb82c63adb..964e0bdef2 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '389d11b3bba3bdd536075c4743dec9ff4e0965ff' +LIBCHROMIUMCONTENT_COMMIT = '4e2ca6e3ca27c2cfcdda324b35124a10ac2d58e5' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index 3961caaa0a..f50aa462aa 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 3961caaa0a38aeb06e1afa4ef12fe9e7c6c24e5d +Subproject commit f50aa462aa9391d6995cf10a74b1b0ba41d04e81 From ba26a4b4e69089d7447d56e53fb6014a3b1182d8 Mon Sep 17 00:00:00 2001 From: leethomas Date: Wed, 30 Dec 2015 12:38:02 -0800 Subject: [PATCH 367/411] :apple: fix #3864 where saving a file causes a crash if file extension array is empty --- atom/browser/ui/file_dialog_mac.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 1cbe46e3b5..676c632a2a 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -34,7 +34,12 @@ void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { [file_type_set addObject:base::mac::CFToNSCast(ext_cf.get())]; } } - [dialog setAllowedFileTypes:[file_type_set allObjects]]; + + NSArray* file_types = nil; + if ([file_type_set count]) + file_types = [file_type_set allObjects]; + + [dialog setAllowedFileTypes: file_types]; } void SetupDialog(NSSavePanel* dialog, From eac2f6fec3272dbc5fcd6724c59fb77b14fb29c9 Mon Sep 17 00:00:00 2001 From: leethomas Date: Wed, 30 Dec 2015 19:36:02 -0800 Subject: [PATCH 368/411] :apple: only add sub menus to Window when they actually have menu items. fixes #3873 --- atom/browser/ui/cocoa/atom_menu_controller.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index e3aa78aa24..9c8c99da9a 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -148,10 +148,11 @@ Role kRolesMap[] = { // Set submenu's role. base::string16 role = model->GetRoleAt(index); - if (role == base::ASCIIToUTF16("window")) + if (role == base::ASCIIToUTF16("window") && [submenu numberOfItems]) [NSApp setWindowsMenu:submenu]; else if (role == base::ASCIIToUTF16("help")) [NSApp setHelpMenu:submenu]; + if (role == base::ASCIIToUTF16("services")) [NSApp setServicesMenu:submenu]; } else { From 8aced2c31eb796df291ef336a099e82b870c1396 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 31 Dec 2015 18:58:16 +0800 Subject: [PATCH 369/411] Add comment on why checking empty set --- atom/browser/ui/file_dialog_mac.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 676c632a2a..49662c69b3 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -35,11 +35,12 @@ void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { } } + // Passing empty array to setAllowedFileTypes will cause exception. NSArray* file_types = nil; if ([file_type_set count]) file_types = [file_type_set allObjects]; - [dialog setAllowedFileTypes: file_types]; + [dialog setAllowedFileTypes:file_types]; } void SetupDialog(NSSavePanel* dialog, From 96d68b92857eddfb2118cd9c4da0cebe72c1a52b Mon Sep 17 00:00:00 2001 From: leethomas Date: Thu, 31 Dec 2015 21:11:21 -0800 Subject: [PATCH 370/411] :bug: add isDevToolsFocused to WebContents to fix #3928, add devtools-[focused|open|close] events to WebView to fix #3783. --- atom/browser/api/atom_api_web_contents.cc | 8 ++++++++ atom/browser/api/atom_api_web_contents.h | 1 + atom/browser/api/lib/browser-window.coffee | 1 + atom/browser/lib/guest-view-manager.coffee | 3 +++ atom/renderer/lib/web-view/guest-view-internal.coffee | 3 +++ 5 files changed, 16 insertions(+) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 461a66c4b2..08f159c347 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -780,6 +780,13 @@ bool WebContents::IsDevToolsOpened() { return managed_web_contents()->IsDevToolsViewShowing(); } +bool WebContents::IsDevToolsFocused() { + if (type_ == REMOTE) + return false; + + return managed_web_contents()->GetView()->IsDevToolsViewFocused(); +} + void WebContents::EnableDeviceEmulation( const blink::WebDeviceEmulationParams& params) { if (type_ == REMOTE) @@ -1080,6 +1087,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("openDevTools", &WebContents::OpenDevTools) .SetMethod("closeDevTools", &WebContents::CloseDevTools) .SetMethod("isDevToolsOpened", &WebContents::IsDevToolsOpened) + .SetMethod("isDevToolsFocused", &WebContents::IsDevToolsFocused) .SetMethod("enableDeviceEmulation", &WebContents::EnableDeviceEmulation) .SetMethod("disableDeviceEmulation", diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 52a244eb89..bd7149e38a 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -79,6 +79,7 @@ class WebContents : public mate::TrackableObject, void OpenDevTools(mate::Arguments* args); void CloseDevTools(); bool IsDevToolsOpened(); + bool IsDevToolsFocused(); void ToggleDevTools(); void EnableDeviceEmulation(const blink::WebDeviceEmulationParams& params); void DisableDeviceEmulation(); diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee index 07b4191d7c..92a230ba45 100644 --- a/atom/browser/api/lib/browser-window.coffee +++ b/atom/browser/api/lib/browser-window.coffee @@ -83,6 +83,7 @@ BrowserWindow::send = -> @webContents.send.apply @webContents, arguments BrowserWindow::openDevTools = -> @webContents.openDevTools.apply @webContents, arguments BrowserWindow::closeDevTools = -> @webContents.closeDevTools() BrowserWindow::isDevToolsOpened = -> @webContents.isDevToolsOpened() +BrowserWindow::isDevToolsFocused = -> @webContents.isDevToolsFocused() BrowserWindow::toggleDevTools = -> @webContents.toggleDevTools() BrowserWindow::inspectElement = -> @webContents.inspectElement.apply @webContents, arguments BrowserWindow::inspectServiceWorker = -> @webContents.inspectServiceWorker() diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index b0ba82eef3..83ad706769 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -13,6 +13,9 @@ supportedWebViewEvents = [ 'did-get-redirect-request' 'dom-ready' 'console-message' + 'devtools-opened' + 'devtools-closed' + 'devtools-focused' 'new-window' 'close' 'crashed' diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 55e0c49a1a..7603797119 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -15,6 +15,9 @@ WEB_VIEW_EVENTS = 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'] 'dom-ready': [] 'console-message': ['level', 'message', 'line', 'sourceId'] + 'devtools-opened': [] + 'devtools-closed': [] + 'devtools-focused': [] 'new-window': ['url', 'frameName', 'disposition', 'options'] 'close': [] 'crashed': [] From 8857eb9cbaff237a03227491e0f6e97d6d2b1694 Mon Sep 17 00:00:00 2001 From: leethomas Date: Fri, 1 Jan 2016 01:41:49 -0800 Subject: [PATCH 371/411] :white_check_mark: add tests for webview devtools-* events --- spec/fixtures/pages/base-page.html | 4 +++ spec/webview-spec.coffee | 46 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 spec/fixtures/pages/base-page.html diff --git a/spec/fixtures/pages/base-page.html b/spec/fixtures/pages/base-page.html new file mode 100644 index 0000000000..7879e1ce9f --- /dev/null +++ b/spec/fixtures/pages/base-page.html @@ -0,0 +1,4 @@ + + + + diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index bcc56b79e2..e9dfe6940d 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -279,6 +279,52 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/close.html" document.body.appendChild webview + describe 'devtools-opened event', -> + it 'should fire when webview.openDevTools() is called', (done) -> + listener = -> + webview.removeEventListener 'devtools-opened', listener + webview.closeDevTools() + done() + + webview.addEventListener 'devtools-opened', listener + webview.addEventListener 'dom-ready', -> + webview.openDevTools() + + webview.src = "file://#{fixtures}/pages/base-page.html" + document.body.appendChild webview + + describe 'devtools-closed event', -> + it 'should fire when webview.closeDevTools() is called', (done) -> + listener2 = -> + webview.removeEventListener 'devtools-closed', listener2 + done() + + listener = -> + webview.removeEventListener 'devtools-opened', listener + webview.closeDevTools() + + webview.addEventListener 'devtools-opened', listener + webview.addEventListener 'devtools-closed', listener2 + webview.addEventListener 'dom-ready', -> + webview.openDevTools() + + webview.src = "file://#{fixtures}/pages/base-page.html" + document.body.appendChild webview + + describe 'devtools-focused event', -> + it 'should fire when webview.openDevTools() is called', (done) -> + listener = -> + webview.removeEventListener 'devtools-focused', listener + webview.closeDevTools() + done() + + webview.addEventListener 'devtools-focused', listener + webview.addEventListener 'dom-ready', -> + webview.openDevTools() + + webview.src = "file://#{fixtures}/pages/base-page.html" + document.body.appendChild webview + describe '.reload()', -> it 'should emit beforeunload handler', (done) -> listener = (e) -> From 54c8c3e3fcf7b632bc8e93ad881b47c41be6d7e5 Mon Sep 17 00:00:00 2001 From: leethomas Date: Fri, 1 Jan 2016 01:45:03 -0800 Subject: [PATCH 372/411] :memo: update webview docs to include devtools-* events --- atom/browser/api/atom_api_web_contents.cc | 2 +- docs/api/web-view-tag.md | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 08f159c347..610fa2bb8d 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -783,7 +783,7 @@ bool WebContents::IsDevToolsOpened() { bool WebContents::IsDevToolsFocused() { if (type_ == REMOTE) return false; - + return managed_web_contents()->GetView()->IsDevToolsViewFocused(); } diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 0c79083c11..79e759c8cb 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -650,3 +650,15 @@ Emitted when a page's theme color changes. This is usually due to encountering a ```html ``` + +### Event: 'devtools-opened' + +Emitted when DevTools is opened. + +### Event: 'devtools-closed' + +Emitted when DevTools is closed. + +### Event: 'devtools-focused' + +Emitted when DevTools is focused / opened. From 9f6319dd71fd2d51a63c19b724c37adc11416214 Mon Sep 17 00:00:00 2001 From: leethomas Date: Fri, 1 Jan 2016 13:33:26 -0800 Subject: [PATCH 373/411] add will-navigate, did-navigate-to-different-page events to webview --- atom/browser/lib/guest-view-manager.coffee | 2 ++ atom/renderer/lib/web-view/guest-view-internal.coffee | 2 ++ 2 files changed, 4 insertions(+) diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index b0ba82eef3..43aba8ca2d 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -14,6 +14,8 @@ supportedWebViewEvents = [ 'dom-ready' 'console-message' 'new-window' + 'will-navigate' + 'did-navigate-to-different-page' 'close' 'crashed' 'gpu-crashed' diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 55e0c49a1a..7d339d3557 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -16,6 +16,8 @@ WEB_VIEW_EVENTS = 'dom-ready': [] 'console-message': ['level', 'message', 'line', 'sourceId'] 'new-window': ['url', 'frameName', 'disposition', 'options'] + 'will-navigate': ['url'] + 'did-navigate-to-different-page': [] 'close': [] 'crashed': [] 'gpu-crashed': [] From 911e60b5072ba6711fa32d6366a9e9c0c6bd3260 Mon Sep 17 00:00:00 2001 From: leethomas Date: Fri, 1 Jan 2016 14:43:02 -0800 Subject: [PATCH 374/411] add did-navigate-in-page event to webview & webcontents (triggers on hash/ref changes, anchor links...), pass url into events --- atom/browser/api/atom_api_web_contents.cc | 4 +++- atom/browser/lib/guest-view-manager.coffee | 1 + atom/renderer/lib/web-view/guest-view-internal.coffee | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 461a66c4b2..d5577c0b9e 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -566,7 +566,9 @@ void WebContents::DidNavigateMainFrame( const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) { if (details.is_navigation_to_different_page()) - Emit("did-navigate-to-different-page"); + Emit("did-navigate-to-different-page", params.url); + else if (details.is_in_page) + Emit("did-navigate-in-page", params.url); } void WebContents::TitleWasSet(content::NavigationEntry* entry, diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index 43aba8ca2d..f070324a10 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -16,6 +16,7 @@ supportedWebViewEvents = [ 'new-window' 'will-navigate' 'did-navigate-to-different-page' + 'did-navigate-in-page' 'close' 'crashed' 'gpu-crashed' diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 7d339d3557..4e28dd3474 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -17,7 +17,8 @@ WEB_VIEW_EVENTS = 'console-message': ['level', 'message', 'line', 'sourceId'] 'new-window': ['url', 'frameName', 'disposition', 'options'] 'will-navigate': ['url'] - 'did-navigate-to-different-page': [] + 'did-navigate-to-different-page': ['url'] + 'did-navigate-in-page': ['url'] 'close': [] 'crashed': [] 'gpu-crashed': [] From 9e2b76361f0044a129c10c5a91017581742338e2 Mon Sep 17 00:00:00 2001 From: leethomas Date: Fri, 1 Jan 2016 16:05:27 -0800 Subject: [PATCH 375/411] :memo: add 'did-navigate-in-page', 'did-navigate-to-different-page', 'will-navigate' to docs --- docs/api/web-contents.md | 14 ++++++++++++++ docs/api/web-view-tag.md | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 2057b515c0..5b53cd14f0 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -135,8 +135,22 @@ Emitted when a user or the page wants to start navigation. It can happen when th This event will not emit when the navigation is started programmatically with APIs like `webContents.loadURL` and `webContents.back`. +It is also not emitted during in-page navigation, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` for this purpose. + Calling `event.preventDefault()` will prevent the navigation. +### Event: 'did-navigate-to-different-page' + +Emitted when the new page that was navigated to is different from the previous +page. + +### Event: 'did-navigate-in-page' + +Emitted when the page url changes but does not cause navigation outside of the page. +Examples of this occurring are when anchor links are clicked or when the +DOM `hashchange` event is triggered. + ### Event: 'crashed' Emitted when the renderer process has crashed. diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 0c79083c11..8575964aab 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -572,6 +572,28 @@ webview.addEventListener('new-window', function(e) { }); ``` +### Event: 'will-navigate' + +Returns: +* `url` String + +Emitted when a user or the page wants to start navigation. It can happen when the +`window.location` object is changed or a user clicks a link in the page. It not +emitted during in-page navigation such as clicking anchor links or updating the +`window.location.hash`. Use `did-navigate-in-page` for this purpose. + + +### Event: 'did-navigate-to-different-page' + +Emitted when the new page that was navigated to is different from the previous +page. + +### Event: 'did-navigate-in-page' + +Emitted when the page url changes but does not cause navigation outside of the page. +Examples of this occurring are when anchor links are clicked or when the +DOM `hashchange` event is triggered. + ### Event: 'close' Fired when the guest page attempts to close itself. From 26397d9155c07c197ef5dad2ac0a78cc64e787d1 Mon Sep 17 00:00:00 2001 From: leethomas Date: Fri, 1 Jan 2016 18:13:07 -0800 Subject: [PATCH 376/411] :white_check_mark: add tests for will-navigate, did-navigate-to-different-page, did-navigate-in-page events in webview --- .../pages/webview-did-navigate-in-page.html | 12 ++++++++ .../fixtures/pages/webview-will-navigate.html | 11 +++++++ spec/webview-spec.coffee | 30 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 spec/fixtures/pages/webview-did-navigate-in-page.html create mode 100644 spec/fixtures/pages/webview-will-navigate.html diff --git a/spec/fixtures/pages/webview-did-navigate-in-page.html b/spec/fixtures/pages/webview-did-navigate-in-page.html new file mode 100644 index 0000000000..2686b414f9 --- /dev/null +++ b/spec/fixtures/pages/webview-did-navigate-in-page.html @@ -0,0 +1,12 @@ + + +
Click me. + This is content. + + + diff --git a/spec/fixtures/pages/webview-will-navigate.html b/spec/fixtures/pages/webview-will-navigate.html new file mode 100644 index 0000000000..52cb4966b3 --- /dev/null +++ b/spec/fixtures/pages/webview-will-navigate.html @@ -0,0 +1,11 @@ + + + Test + + + diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index bcc56b79e2..2e48b24c84 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -271,6 +271,36 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/a.html" document.body.appendChild webview + describe 'will-navigate event', -> + it 'emits when a url that leads to oustide of the page is clicked', (done) -> + webview.addEventListener 'will-navigate', (e) -> + assert.equal e.url, "http://host/" + done() + + webview.src = "file://#{fixtures}/pages/webview-will-navigate.html" + document.body.appendChild webview + + describe 'did-navigate-to-different-page event', -> + page_url = "file://#{fixtures}/pages/webview-will-navigate.html" + + it 'emits when a url that leads to outside of the page is clicked', (done) -> + webview.addEventListener 'did-navigate-to-different-page', (e) -> + assert.equal e.url, page_url + done() + + webview.src = page_url + document.body.appendChild webview + + describe 'did-navigate-in-page event', -> + it 'emits when an anchor link is clicked', (done) -> + page_url = "file://#{fixtures}/pages/webview-did-navigate-in-page.html" + webview.addEventListener 'did-navigate-in-page', (e) -> + assert.equal e.url, "#{page_url}#test_content" + done() + + webview.src = page_url + document.body.appendChild webview + describe 'close event', -> it 'should fire when interior page calls window.close', (done) -> webview.addEventListener 'close', -> From ce733e5927a6c0273571130f51a068b865af03b9 Mon Sep 17 00:00:00 2001 From: leethomas Date: Fri, 1 Jan 2016 18:51:12 -0800 Subject: [PATCH 377/411] :white_check_mark: add tests to insure window.location.hash changes & window.history.replaceState cause 'did-navigate-in-page' to fire --- .../webview-did-navigate-in-page-with-hash.html | 9 +++++++++ ...bview-did-navigate-in-page-with-history.html | 9 +++++++++ spec/webview-spec.coffee | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 spec/fixtures/pages/webview-did-navigate-in-page-with-hash.html create mode 100644 spec/fixtures/pages/webview-did-navigate-in-page-with-history.html diff --git a/spec/fixtures/pages/webview-did-navigate-in-page-with-hash.html b/spec/fixtures/pages/webview-did-navigate-in-page-with-hash.html new file mode 100644 index 0000000000..894095d47f --- /dev/null +++ b/spec/fixtures/pages/webview-did-navigate-in-page-with-hash.html @@ -0,0 +1,9 @@ + + + + + diff --git a/spec/fixtures/pages/webview-did-navigate-in-page-with-history.html b/spec/fixtures/pages/webview-did-navigate-in-page-with-history.html new file mode 100644 index 0000000000..b32b7ebcd8 --- /dev/null +++ b/spec/fixtures/pages/webview-did-navigate-in-page-with-history.html @@ -0,0 +1,9 @@ + + + + + diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index 2e48b24c84..e03fa534bf 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -301,6 +301,23 @@ describe ' tag', -> webview.src = page_url document.body.appendChild webview + it 'emits when window.history.replaceState is called', (done) -> + webview.addEventListener 'did-navigate-in-page', (e) -> + assert.equal e.url, "http://host/" + done() + + webview.src = "file://#{fixtures}/pages/webview-did-navigate-in-page-with-history.html" + document.body.appendChild webview + + it 'emits when window.location.hash is changed', (done) -> + page_url = "file://#{fixtures}/pages/webview-did-navigate-in-page-with-hash.html" + webview.addEventListener 'did-navigate-in-page', (e) -> + assert.equal e.url, "#{page_url}#test" + done() + + webview.src = page_url + document.body.appendChild webview + describe 'close event', -> it 'should fire when interior page calls window.close', (done) -> webview.addEventListener 'close', -> From ef58c6d36b855e90bdeb1e4990646b4f9bd27732 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 1 Jan 2016 15:57:51 +0900 Subject: [PATCH 378/411] :memo: Update as upstream * Update as upstream * Add `using-widevine-cdm-plugin.md` --- docs-translations/ko-KR/README.md | 1 + docs-translations/ko-KR/api/ipc-main.md | 7 +- .../tutorial/using-widevine-cdm-plugin.md | 78 +++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index 38e7745796..473f521136 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -22,6 +22,7 @@ GitHub 프로젝트내에서만 볼 수 있고 `master` 브랜치의 문서는 * [Selenium 과 WebDriver 사용하기](tutorial/using-selenium-and-webdriver.md) * [개발자 도구 확장 기능](tutorial/devtools-extension.md) * [Pepper 플래시 플러그인 사용하기](tutorial/using-pepper-flash-plugin.md) +* [Widevine CDM 플러그인 사용하기](tutorial/using-widevine-cdm-plugin.md) ## 튜토리얼 diff --git a/docs-translations/ko-KR/api/ipc-main.md b/docs-translations/ko-KR/api/ipc-main.md index 9df7376200..4714fd7aff 100644 --- a/docs-translations/ko-KR/api/ipc-main.md +++ b/docs-translations/ko-KR/api/ipc-main.md @@ -7,7 +7,7 @@ ## 메시지 전송 물론 메시지를 받는 것 말고도 메인 프로세스에서 랜더러 프로세스로 보내는 것도 가능합니다. -자세한 내용은 [webContents.send](web-contents.md#webcontentssendchannel-args)를 +자세한 내용은 [webContents.send](web-contents.md#webcontentssendchannel-arg1-arg2-)를 참고하세요. * 메시지를 전송할 때 이벤트 이름은 `channel`이 됩니다. @@ -64,6 +64,5 @@ ipcRenderer.send('asynchronous-message', 'ping'); 메시지를 보낸 `webContents` 객체를 반환합니다. `event.sender.send` 메서드를 통해 비동기로 메시지를 전달할 수 있습니다. 자세한 내용은 -[webContents.send][webcontents-send]를 참고하세요. - -[webcontents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- +[webContents.send](web-contents.md#webcontentssendchannel-arg1-arg2-)를 +참고하세요. diff --git a/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md b/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md new file mode 100644 index 0000000000..d729c0efc0 --- /dev/null +++ b/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md @@ -0,0 +1,78 @@ +# Widevine CDM 플러그인 사용하기 + +Electron에선 Chrome 브라우저에서 취득해온 Widevine CDM 플러그인을 사용할 수 있습니다. + +## 플러그인 취득 + +Electron은 라이센스상의 문제로 Widevine CDM 플러그인을 직접 제공하지 않습니다. +따라서 플러그인을 얻으려면 먼저 사용할 Electron 빌드의 아키텍쳐와 버전에 맞춰 공식 +Chrome 브라우저를 설치해야 합니다. + +### Windows & OS X + +Chrome 브라우저에서 `chrome://components/`를 열고 `WidevineCdm`을 찾은 후 확실히 +최신버전인지 확인합니다. 여기까지 하면 모든 플러그인 바이너리를 +`APP_DATA/Google/Chrome/WidevineCDM/VERSION/_platform_specific/PLATFORM_ARCH/` +디렉터리에서 찾을 수 있습니다. + +`APP_DATA`는 어플리케이션 데이터를 저장하고 있는 시스템 경로입니다. Windows에선 +`%LOCALAPPDATA%`로 접근할 수 있고 OS X에선 `~/Library/Application Support`로 +접근할 수 있습니다. `VERSION`은 `1.4.8.866` 같은 Widevine CDM 플러그인의 버전 +문자열입니다. `PLATFORM`은 플랫폼을 뜻하며 `mac` 또는 `win`이 될 수 있으며 `ARCH`는 +아키텍쳐를 뜻하고 `x86` 또는 `x64`가 될 수 있습니다. + +Windows에선 `widevinecdm.dll` 와 `widevinecdmadapter.dll` 같은 바이너리를 +요구하며 OS X에선 `libwidevinecdm.dylib`와 `widevinecdmadapter.plugin` 바이너리를 +요구합니다. 원하는 곳에 이들을 복사해 놓을 수 있습니다. 하지만 반드시 바이너리는 같은 +위치에 두어야 합니다. + +### Linux + +Linux에선 플러그인 바이너리들이 Chrome 브라우저와 함께 제공됩니다. +`/opt/google/chrome` 경로에서 찾을 수 있으며, 파일 이름은 `libwidevinecdm.so`와 +`libwidevinecdmadapter.so`입니다. + +## 플러그인 사용 + +플러그인 파일을 가져온 후, Electron의 `--widevine-cdm-path` 커맨드 라인 스위치에 +`widevinecdmadapter`의 위치를 전달하고 플러그인의 버전을 `--widevine-cdm-version` +스위치에 전달해야 합니다. + +__참고:__ `widevinecdmadapter` 바이너리가 Electron으로 전달되어도, `widevinecdm` +바이너리는 옆에 같이 두어야 합니다. + +커맨드 라인 스위치들은 `app` 모듈의 `ready` 이벤트가 발생하기 전에 전달되어야 합니다. +그리고 이 플러그인을 사용하는 페이지는 플러그인(속성)이 활성화되어있어야 합니다. + +예시 코드: + +```javascript +// `widevinecdmadapter`의 파일 이름을 이곳에 전달해야 합니다. 파일 이름은 +// * OS X에선 `widevinecdmadapter.plugin`로 지정합니다, +// * Linux에선 `libwidevinecdmadapter.so`로 지정합니다, +// * Windows에선 `widevinecdmadapter.dll`로 지정합니다. +app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin'); +// 플러그인의 버전은 크롬의 `chrome://plugins` 페이지에서 취득할 수 있습니다. +app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866'); + +var mainWindow = null; +app.on('ready', function() { + mainWindow = new BrowserWindow({ + webPreferences: { + // `plugins`은 활성화되어야 합니다. + plugins: true + } + }) +}); +``` + +## 플러그인 작동 확인 + +플러그인 정상적으로 작동하는지 확인하려면 다음과 같은 방법을 사용할 수 있습니다: + +* 개발자 도구를 연 후 `navigator.plugins`를 확인하여 Widevine CDM 플러그인이 + 포함되었는지 알 수 있습니다. +* https://shaka-player-demo.appspot.com/를 열어, `Widevine`을 사용하는 설정을 + 로드합니다. +* http://www.dash-player.com/demo/drm-test-area/를 열어, 페이지에서 + `bitdash uses Widevine in your browser`라고 적혀있는지 확인하고 비디오를 재생합니다. From 14db4a194679369acef651a3fdb45bd66db12111 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 4 Jan 2016 10:46:30 +0800 Subject: [PATCH 379/411] Add isDevToolsFocused to webview --- atom/browser/api/atom_api_window.cc | 5 ----- atom/browser/api/atom_api_window.h | 1 - atom/browser/native_window.cc | 4 ---- atom/browser/native_window.h | 1 - atom/renderer/lib/web-view/web-view.coffee | 1 + docs/api/web-contents.md | 10 +++++++--- docs/api/web-view-tag.md | 4 ++++ 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 08b5bc4f7b..222f2cae03 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -468,10 +468,6 @@ bool Window::IsWebViewFocused() { return window_->IsWebViewFocused(); } -bool Window::IsDevToolsFocused() { - return window_->IsDevToolsFocused(); -} - void Window::SetRepresentedFilename(const std::string& filename) { window_->SetRepresentedFilename(filename); } @@ -670,7 +666,6 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("focusOnWebView", &Window::FocusOnWebView) .SetMethod("blurWebView", &Window::BlurWebView) .SetMethod("isWebViewFocused", &Window::IsWebViewFocused) - .SetMethod("isDevToolsFocused", &Window::IsDevToolsFocused) .SetMethod("capturePage", &Window::CapturePage) .SetMethod("setProgressBar", &Window::SetProgressBar) .SetMethod("setOverlayIcon", &Window::SetOverlayIcon) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 3611c6e33f..9297b2fe75 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -121,7 +121,6 @@ class Window : public mate::TrackableObject, void FocusOnWebView(); void BlurWebView(); bool IsWebViewFocused(); - bool IsDevToolsFocused(); void SetRepresentedFilename(const std::string& filename); std::string GetRepresentedFilename(); void SetDocumentEdited(bool edited); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 3013923ecd..d946704b9f 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -268,10 +268,6 @@ bool NativeWindow::IsWebViewFocused() { return host_view && host_view->HasFocus(); } -bool NativeWindow::IsDevToolsFocused() { - return inspectable_web_contents_->GetView()->IsDevToolsViewFocused(); -} - void NativeWindow::CapturePage(const gfx::Rect& rect, const CapturePageCallback& callback) { const auto view = web_contents()->GetRenderWidgetHostView(); diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 3eb235b03c..c60c0dd5f1 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -157,7 +157,6 @@ class NativeWindow : public base::SupportsUserData, virtual void FocusOnWebView(); virtual void BlurWebView(); virtual bool IsWebViewFocused(); - virtual bool IsDevToolsFocused(); // Captures the page with |rect|, |callback| would be called when capturing is // done. diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee index 2d81bd6aa6..fc01725746 100644 --- a/atom/renderer/lib/web-view/web-view.coffee +++ b/atom/renderer/lib/web-view/web-view.coffee @@ -273,6 +273,7 @@ registerWebViewElement = -> 'openDevTools' 'closeDevTools' 'isDevToolsOpened' + 'isDevToolsFocused' 'inspectElement' 'setAudioMuted' 'isAudioMuted' diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 2057b515c0..7fd1910393 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -596,15 +596,19 @@ Removes the specified path from DevTools workspace. * `options` Object (optional). Properties: * `detach` Boolean - opens DevTools in a new window -Opens the developer tools. +Opens the devtools. ### `webContents.closeDevTools()` -Closes the developer tools. +Closes the devtools. ### `webContents.isDevToolsOpened()` -Returns whether the developer tools are opened. +Returns whether the devtools is opened. + +### `webContents.isDevToolsFocused()` + +Returns whether the devtools view is focused . ### `webContents.toggleDevTools()` diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 79e759c8cb..a4c1be0e20 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -279,6 +279,10 @@ Closes the DevTools window of guest page. Returns a boolean whether guest page has a DevTools window attached. +### `.isDevToolsFocused()` + +Returns a boolean whether DevTools window of guest page is focused. + ### `.inspectElement(x, y)` * `x` Integer From cf09e7cb51c8574a3e5350ce2a258e249f2f8334 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 4 Jan 2016 10:58:01 +0800 Subject: [PATCH 380/411] Update libchromiumcontent for #3958 --- script/lib/config.py | 2 +- vendor/brightray | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/lib/config.py b/script/lib/config.py index 964e0bdef2..932562fdcb 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '4e2ca6e3ca27c2cfcdda324b35124a10ac2d58e5' +LIBCHROMIUMCONTENT_COMMIT = '0fbded6cf3d9244389db05f0c022e474a06ad32a' PLATFORM = { 'cygwin': 'win32', diff --git a/vendor/brightray b/vendor/brightray index f50aa462aa..d572361a4e 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit f50aa462aa9391d6995cf10a74b1b0ba41d04e81 +Subproject commit d572361a4eeeb3a2fe6d3f2de457fbecb5775c0a From 4844e68ba16a6c67f87ba654e489c29ed196e0dc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 4 Jan 2016 12:09:11 +0800 Subject: [PATCH 381/411] Rename did-navigate-to-different-page to did-navigate --- atom/browser/api/atom_api_web_contents.cc | 2 +- atom/browser/lib/guest-view-manager.coffee | 4 +- .../lib/web-view/guest-view-internal.coffee | 2 +- docs/api/web-contents.md | 36 ++++++++++++----- docs/api/web-view-tag.md | 40 ++++++++++++++----- spec/webview-spec.coffee | 22 +++++----- 6 files changed, 71 insertions(+), 35 deletions(-) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index bbf9d690d5..3f39519a3f 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -566,7 +566,7 @@ void WebContents::DidNavigateMainFrame( const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) { if (details.is_navigation_to_different_page()) - Emit("did-navigate-to-different-page", params.url); + Emit("did-navigate", params.url); else if (details.is_in_page) Emit("did-navigate-in-page", params.url); } diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index 8009e920eb..c99bf5757d 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -18,7 +18,7 @@ supportedWebViewEvents = [ 'devtools-focused' 'new-window' 'will-navigate' - 'did-navigate-to-different-page' + 'did-navigate' 'did-navigate-in-page' 'close' 'crashed' @@ -57,7 +57,7 @@ createGuest = (embedder, params) -> guestInstances[id] = {guest, embedder} # Destroy guest when the embedder is gone or navigated. - destroyEvents = ['destroyed', 'crashed', 'did-navigate-to-different-page'] + destroyEvents = ['destroyed', 'crashed', 'did-navigate'] destroy = -> destroyGuest embedder, id if guestInstances[id]? for event in destroyEvents diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 9c06ac1fe9..30fe3d2a6e 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -20,7 +20,7 @@ WEB_VIEW_EVENTS = 'devtools-focused': [] 'new-window': ['url', 'frameName', 'disposition', 'options'] 'will-navigate': ['url'] - 'did-navigate-to-different-page': ['url'] + 'did-navigate': ['url'] 'did-navigate-in-page': ['url'] 'close': [] 'crashed': [] diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 66e8483ac9..b399e143aa 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -129,27 +129,43 @@ Returns: * `event` Event * `url` String -Emitted when a user or the page wants to start navigation. It can happen when the -`window.location` object is changed or a user clicks a link in the page. +Emitted when a user or the page wants to start navigation. It can happen when +the `window.location` object is changed or a user clicks a link in the page. This event will not emit when the navigation is started programmatically with APIs like `webContents.loadURL` and `webContents.back`. -It is also not emitted during in-page navigation, such as clicking anchor links -or updating the `window.location.hash`. Use `did-navigate-in-page` for this purpose. +It is also not emitted for in-page navigations, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` event for +this purpose. Calling `event.preventDefault()` will prevent the navigation. -### Event: 'did-navigate-to-different-page' +### Event: 'did-navigate' -Emitted when the new page that was navigated to is different from the previous -page. +Returns: + +* `event` Event +* `url` String + +Emitted when a navigation is done. + +This event is not emitted for in-page navigations, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` event for +this purpose. ### Event: 'did-navigate-in-page' -Emitted when the page url changes but does not cause navigation outside of the page. -Examples of this occurring are when anchor links are clicked or when the -DOM `hashchange` event is triggered. +Returns: + +* `event` Event +* `url` String + +Emitted when an in-page navigation happened. + +When in-page navigation happens, the page URL changes but does not cause +navigation outside of the page. Examples of this occurring are when anchor links +are clicked or when the DOM `hashchange` event is triggered. ### Event: 'crashed' diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 26b08f10b5..8b8f5ffd49 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -579,24 +579,44 @@ webview.addEventListener('new-window', function(e) { ### Event: 'will-navigate' Returns: + * `url` String -Emitted when a user or the page wants to start navigation. It can happen when the -`window.location` object is changed or a user clicks a link in the page. It not -emitted during in-page navigation such as clicking anchor links or updating the -`window.location.hash`. Use `did-navigate-in-page` for this purpose. +Emitted when a user or the page wants to start navigation. It can happen when +the `window.location` object is changed or a user clicks a link in the page. +This event will not emit when the navigation is started programmatically with +APIs like `.loadURL` and `.back`. -### Event: 'did-navigate-to-different-page' +It is also not emitted during in-page navigation, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` event for +this purpose. -Emitted when the new page that was navigated to is different from the previous -page. +Calling `event.preventDefault()` does __NOT__ have any effect. + +### Event: 'did-navigate' + +Returns: + +* `url` String + +Emitted when a navigation is done. + +This event is not emitted for in-page navigations, such as clicking anchor links +or updating the `window.location.hash`. Use `did-navigate-in-page` event for +this purpose. ### Event: 'did-navigate-in-page' -Emitted when the page url changes but does not cause navigation outside of the page. -Examples of this occurring are when anchor links are clicked or when the -DOM `hashchange` event is triggered. +Returns: + +* `url` String + +Emitted when an in-page navigation happened. + +When in-page navigation happens, the page URL changes but does not cause +navigation outside of the page. Examples of this occurring are when anchor links +are clicked or when the DOM `hashchange` event is triggered. ### Event: 'close' diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index 9801b69a5a..1dcf739ec0 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -280,25 +280,25 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/webview-will-navigate.html" document.body.appendChild webview - describe 'did-navigate-to-different-page event', -> - page_url = "file://#{fixtures}/pages/webview-will-navigate.html" + describe 'did-navigate event', -> + pageUrl = "file://#{fixtures}/pages/webview-will-navigate.html" it 'emits when a url that leads to outside of the page is clicked', (done) -> - webview.addEventListener 'did-navigate-to-different-page', (e) -> - assert.equal e.url, page_url + webview.addEventListener 'did-navigate', (e) -> + assert.equal e.url, pageUrl done() - webview.src = page_url + webview.src = pageUrl document.body.appendChild webview describe 'did-navigate-in-page event', -> it 'emits when an anchor link is clicked', (done) -> - page_url = "file://#{fixtures}/pages/webview-did-navigate-in-page.html" + pageUrl = "file://#{fixtures}/pages/webview-did-navigate-in-page.html" webview.addEventListener 'did-navigate-in-page', (e) -> - assert.equal e.url, "#{page_url}#test_content" + assert.equal e.url, "#{pageUrl}#test_content" done() - webview.src = page_url + webview.src = pageUrl document.body.appendChild webview it 'emits when window.history.replaceState is called', (done) -> @@ -310,12 +310,12 @@ describe ' tag', -> document.body.appendChild webview it 'emits when window.location.hash is changed', (done) -> - page_url = "file://#{fixtures}/pages/webview-did-navigate-in-page-with-hash.html" + pageUrl = "file://#{fixtures}/pages/webview-did-navigate-in-page-with-hash.html" webview.addEventListener 'did-navigate-in-page', (e) -> - assert.equal e.url, "#{page_url}#test" + assert.equal e.url, "#{pageUrl}#test" done() - webview.src = page_url + webview.src = pageUrl document.body.appendChild webview describe 'close event', -> From 7a1717156e32a49b1da04fab5c0488c24b4c791b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 4 Jan 2016 19:37:25 +0800 Subject: [PATCH 382/411] Revert "browser: dont lose coordinates in capturepage src rect" This reverts commit 3c5e5053e3138bd3b661bac23ba8cf337addba36. --- atom/browser/native_window.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index d946704b9f..c9837017e4 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -278,22 +278,22 @@ void NativeWindow::CapturePage(const gfx::Rect& rect, } // Capture full page if user doesn't specify a |rect|. - const gfx::Rect view_rect = rect.IsEmpty() ? view->GetViewBounds() : - rect; + const gfx::Size view_size = rect.IsEmpty() ? view->GetViewBounds().size() : + rect.size(); // By default, the requested bitmap size is the view size in screen // coordinates. However, if there's more pixel detail available on the // current system, increase the requested bitmap size to capture it all. - gfx::Size bitmap_size = view_rect.size(); + gfx::Size bitmap_size = view_size; const gfx::NativeView native_view = view->GetNativeView(); gfx::Screen* const screen = gfx::Screen::GetScreenFor(native_view); const float scale = screen->GetDisplayNearestWindow(native_view).device_scale_factor(); if (scale > 1.0f) - bitmap_size = gfx::ScaleToCeiledSize(view_rect.size(), scale); + bitmap_size = gfx::ScaleToCeiledSize(view_size, scale); host->CopyFromBackingStore( - view_rect, + gfx::Rect(view_size), bitmap_size, base::Bind(&NativeWindow::OnCapturePageDone, weak_factory_.GetWeakPtr(), From 0df03a23a3213903c984a2a2089fd2635702d19d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 4 Jan 2016 20:06:36 +0800 Subject: [PATCH 383/411] Pass origin in capturePage --- atom/browser/native_window.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index c9837017e4..bed42b1019 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -32,10 +32,10 @@ #include "ipc/ipc_message_macros.h" #include "native_mate/dictionary.h" #include "ui/gfx/codec/png_codec.h" -#include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/screen.h" #include "ui/gl/gpu_switching_manager.h" @@ -293,7 +293,7 @@ void NativeWindow::CapturePage(const gfx::Rect& rect, bitmap_size = gfx::ScaleToCeiledSize(view_size, scale); host->CopyFromBackingStore( - gfx::Rect(view_size), + gfx::Rect(rect.origin(), view_size), bitmap_size, base::Bind(&NativeWindow::OnCapturePageDone, weak_factory_.GetWeakPtr(), From 48451032e33e57cb6b01bfbfae026addbb48fd89 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 5 Jan 2016 10:22:42 +0800 Subject: [PATCH 384/411] Update brightray to fix menu not loading resources --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index d572361a4e..f9c272ec86 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit d572361a4eeeb3a2fe6d3f2de457fbecb5775c0a +Subproject commit f9c272ec86ee83915729cf2ecdfdd5aa418b77eb From 43bfce26a72b62128f2a4288e5f479c1f1dc8d58 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 5 Jan 2016 11:57:58 +0800 Subject: [PATCH 385/411] Do not behave like bookmarkbar menu --- atom/browser/ui/views/menu_bar.cc | 18 +++--- atom/browser/ui/views/menu_bar.h | 4 +- atom/browser/ui/views/menu_delegate.cc | 83 +++++++++++++------------- atom/browser/ui/views/menu_delegate.h | 20 ++----- 4 files changed, 60 insertions(+), 65 deletions(-) diff --git a/atom/browser/ui/views/menu_bar.cc b/atom/browser/ui/views/menu_bar.cc index b7712929d0..54b6957fa5 100644 --- a/atom/browser/ui/views/menu_bar.cc +++ b/atom/browser/ui/views/menu_bar.cc @@ -134,6 +134,16 @@ bool MenuBar::GetMenuButtonFromScreenPoint(const gfx::Point& point, return false; } +void MenuBar::RunMenu(views::MenuButton* button) { + int id = button->tag(); + ui::MenuModel::ItemType type = menu_model_->GetTypeAt(id); + if (type != ui::MenuModel::TYPE_SUBMENU) + return; + + MenuDelegate menu_delegate(this); + menu_delegate.RunMenu(menu_model_->GetSubmenuModelAt(id), button); +} + const char* MenuBar::GetClassName() const { return kViewClassName; } @@ -150,13 +160,7 @@ void MenuBar::OnMenuButtonClicked(views::View* source, return; views::MenuButton* button = static_cast(source); - int id = button->tag(); - ui::MenuModel::ItemType type = menu_model_->GetTypeAt(id); - if (type != ui::MenuModel::TYPE_SUBMENU) - return; - - menu_delegate_.reset(new MenuDelegate(this)); - menu_delegate_->RunMenu(menu_model_->GetSubmenuModelAt(id), button); + RunMenu(button); } } // namespace atom diff --git a/atom/browser/ui/views/menu_bar.h b/atom/browser/ui/views/menu_bar.h index ac82711f8b..09d257c097 100644 --- a/atom/browser/ui/views/menu_bar.h +++ b/atom/browser/ui/views/menu_bar.h @@ -49,6 +49,9 @@ class MenuBar : public views::View, ui::MenuModel** menu_model, views::MenuButton** button); + // Shows the menu with |button|. + void RunMenu(views::MenuButton* button); + protected: // views::View: const char* GetClassName() const override; @@ -71,7 +74,6 @@ class MenuBar : public views::View, #endif ui::MenuModel* menu_model_; - scoped_ptr menu_delegate_; DISALLOW_COPY_AND_ASSIGN(MenuBar); }; diff --git a/atom/browser/ui/views/menu_delegate.cc b/atom/browser/ui/views/menu_delegate.cc index 84c35d9cd1..1c28cc10df 100644 --- a/atom/browser/ui/views/menu_delegate.cc +++ b/atom/browser/ui/views/menu_delegate.cc @@ -5,7 +5,7 @@ #include "atom/browser/ui/views/menu_delegate.h" #include "atom/browser/ui/views/menu_bar.h" -#include "base/stl_util.h" +#include "content/public/browser/browser_thread.h" #include "ui/views/controls/button/menu_button.h" #include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_model_adapter.h" @@ -16,13 +16,10 @@ namespace atom { MenuDelegate::MenuDelegate(MenuBar* menu_bar) : menu_bar_(menu_bar), - id_(-1), - items_(menu_bar_->GetItemCount()), - delegates_(menu_bar_->GetItemCount()) { + id_(-1) { } MenuDelegate::~MenuDelegate() { - STLDeleteElements(&delegates_); } void MenuDelegate::RunMenu(ui::MenuModel* model, views::MenuButton* button) { @@ -33,12 +30,15 @@ void MenuDelegate::RunMenu(ui::MenuModel* model, views::MenuButton* button) { button->height() - 1); id_ = button->tag(); - views::MenuItemView* item = BuildMenu(model); + adapter_.reset(new views::MenuModelAdapter(model)); - views::MenuRunner menu_runner( + views::MenuItemView* item = new views::MenuItemView(this); + static_cast(adapter_.get())->BuildMenu(item); + + menu_runner_.reset(new views::MenuRunner( item, - views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); - ignore_result(menu_runner.RunMenuAt( + views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS)); + ignore_result(menu_runner_->RunMenuAt( button->GetWidget()->GetTopLevelWidget(), button, bounds, @@ -46,68 +46,53 @@ void MenuDelegate::RunMenu(ui::MenuModel* model, views::MenuButton* button) { ui::MENU_SOURCE_MOUSE)); } -views::MenuItemView* MenuDelegate::BuildMenu(ui::MenuModel* model) { - DCHECK_GE(id_, 0); - - if (!items_[id_]) { - views::MenuModelAdapter* delegate = new views::MenuModelAdapter(model); - delegates_[id_] = delegate; - - views::MenuItemView* item = new views::MenuItemView(this); - delegate->BuildMenu(item); - items_[id_] = item; - } - - return items_[id_]; -} - void MenuDelegate::ExecuteCommand(int id) { - delegate()->ExecuteCommand(id); + adapter_->ExecuteCommand(id); } void MenuDelegate::ExecuteCommand(int id, int mouse_event_flags) { - delegate()->ExecuteCommand(id, mouse_event_flags); + adapter_->ExecuteCommand(id, mouse_event_flags); } bool MenuDelegate::IsTriggerableEvent(views::MenuItemView* source, const ui::Event& e) { - return delegate()->IsTriggerableEvent(source, e); + return adapter_->IsTriggerableEvent(source, e); } bool MenuDelegate::GetAccelerator(int id, ui::Accelerator* accelerator) const { - return delegate()->GetAccelerator(id, accelerator); + return adapter_->GetAccelerator(id, accelerator); } base::string16 MenuDelegate::GetLabel(int id) const { - return delegate()->GetLabel(id); + return adapter_->GetLabel(id); } const gfx::FontList* MenuDelegate::GetLabelFontList(int id) const { - return delegate()->GetLabelFontList(id); + return adapter_->GetLabelFontList(id); } bool MenuDelegate::IsCommandEnabled(int id) const { - return delegate()->IsCommandEnabled(id); + return adapter_->IsCommandEnabled(id); } bool MenuDelegate::IsCommandVisible(int id) const { - return delegate()->IsCommandVisible(id); + return adapter_->IsCommandVisible(id); } bool MenuDelegate::IsItemChecked(int id) const { - return delegate()->IsItemChecked(id); + return adapter_->IsItemChecked(id); } void MenuDelegate::SelectionChanged(views::MenuItemView* menu) { - delegate()->SelectionChanged(menu); + adapter_->SelectionChanged(menu); } void MenuDelegate::WillShowMenu(views::MenuItemView* menu) { - delegate()->WillShowMenu(menu); + adapter_->WillShowMenu(menu); } void MenuDelegate::WillHideMenu(views::MenuItemView* menu) { - delegate()->WillHideMenu(menu); + adapter_->WillHideMenu(menu); } views::MenuItemView* MenuDelegate::GetSiblingMenu( @@ -115,16 +100,28 @@ views::MenuItemView* MenuDelegate::GetSiblingMenu( const gfx::Point& screen_point, views::MenuAnchorPosition* anchor, bool* has_mnemonics, - views::MenuButton** button) { + views::MenuButton**) { + views::MenuButton* button; ui::MenuModel* model; - if (!menu_bar_->GetMenuButtonFromScreenPoint(screen_point, &model, button)) - return NULL; + if (menu_bar_->GetMenuButtonFromScreenPoint(screen_point, &model, &button) && + button->tag() != id_) { + // Switch to sibling menu on next tick, otherwise crash may happen. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&MenuDelegate::SwitchToSiblingMenu, + base::Unretained(this), button)); + } - *anchor = views::MENU_ANCHOR_TOPLEFT; - *has_mnemonics = true; + return nullptr; +} - id_ = (*button)->tag(); - return BuildMenu(model); +void MenuDelegate::SwitchToSiblingMenu(views::MenuButton* button) { + menu_runner_->Cancel(); + // After canceling the menu, we need to wait until next tick so we are out of + // nested message loop. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&MenuBar::RunMenu, base::Unretained(menu_bar_), button)); } } // namespace atom diff --git a/atom/browser/ui/views/menu_delegate.h b/atom/browser/ui/views/menu_delegate.h index a837e5d53e..f83e5896c6 100644 --- a/atom/browser/ui/views/menu_delegate.h +++ b/atom/browser/ui/views/menu_delegate.h @@ -5,12 +5,11 @@ #ifndef ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_ #define ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_ -#include - +#include "base/memory/scoped_ptr.h" #include "ui/views/controls/menu/menu_delegate.h" namespace views { -class MenuModelAdapter; +class MenuRunner; } namespace ui { @@ -51,20 +50,13 @@ class MenuDelegate : public views::MenuDelegate { views::MenuButton** button) override; private: - // Gets the cached menu item view from the model. - views::MenuItemView* BuildMenu(ui::MenuModel* model); - - // Returns delegate for current item. - views::MenuDelegate* delegate() const { return delegates_[id_]; } + // Close this menu and run the menu of |button|. + void SwitchToSiblingMenu(views::MenuButton* button); MenuBar* menu_bar_; - - // Current item's id. int id_; - // Cached menu items, managed by MenuRunner. - std::vector items_; - // Cached menu delegates for each menu item, managed by us. - std::vector delegates_; + scoped_ptr adapter_; + scoped_ptr menu_runner_; DISALLOW_COPY_AND_ASSIGN(MenuDelegate); }; From 698700716b44531e57035bcb24d83653b1c11dd8 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 5 Jan 2016 12:05:27 +0800 Subject: [PATCH 386/411] Show menu by clicking the menu button --- atom/browser/ui/views/menu_bar.cc | 18 +++++++----------- atom/browser/ui/views/menu_bar.h | 3 --- atom/browser/ui/views/menu_delegate.cc | 3 ++- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/atom/browser/ui/views/menu_bar.cc b/atom/browser/ui/views/menu_bar.cc index 54b6957fa5..ba0a542c1e 100644 --- a/atom/browser/ui/views/menu_bar.cc +++ b/atom/browser/ui/views/menu_bar.cc @@ -134,16 +134,6 @@ bool MenuBar::GetMenuButtonFromScreenPoint(const gfx::Point& point, return false; } -void MenuBar::RunMenu(views::MenuButton* button) { - int id = button->tag(); - ui::MenuModel::ItemType type = menu_model_->GetTypeAt(id); - if (type != ui::MenuModel::TYPE_SUBMENU) - return; - - MenuDelegate menu_delegate(this); - menu_delegate.RunMenu(menu_model_->GetSubmenuModelAt(id), button); -} - const char* MenuBar::GetClassName() const { return kViewClassName; } @@ -160,7 +150,13 @@ void MenuBar::OnMenuButtonClicked(views::View* source, return; views::MenuButton* button = static_cast(source); - RunMenu(button); + int id = button->tag(); + ui::MenuModel::ItemType type = menu_model_->GetTypeAt(id); + if (type != ui::MenuModel::TYPE_SUBMENU) + return; + + MenuDelegate menu_delegate(this); + menu_delegate.RunMenu(menu_model_->GetSubmenuModelAt(id), button); } } // namespace atom diff --git a/atom/browser/ui/views/menu_bar.h b/atom/browser/ui/views/menu_bar.h index 09d257c097..9d77cfdf2a 100644 --- a/atom/browser/ui/views/menu_bar.h +++ b/atom/browser/ui/views/menu_bar.h @@ -49,9 +49,6 @@ class MenuBar : public views::View, ui::MenuModel** menu_model, views::MenuButton** button); - // Shows the menu with |button|. - void RunMenu(views::MenuButton* button); - protected: // views::View: const char* GetClassName() const override; diff --git a/atom/browser/ui/views/menu_delegate.cc b/atom/browser/ui/views/menu_delegate.cc index 1c28cc10df..8ef8bca72d 100644 --- a/atom/browser/ui/views/menu_delegate.cc +++ b/atom/browser/ui/views/menu_delegate.cc @@ -121,7 +121,8 @@ void MenuDelegate::SwitchToSiblingMenu(views::MenuButton* button) { // nested message loop. content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&MenuBar::RunMenu, base::Unretained(menu_bar_), button)); + base::Bind(base::IgnoreResult(&views::MenuButton::Activate), + base::Unretained(button))); } } // namespace atom From 2b6ac966c0143a6b5535126f0337df0aefaa9fad Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 5 Jan 2016 13:45:34 +0800 Subject: [PATCH 387/411] Do not write our own filter code The Win32 API has done everything for us, there is no need to do this oursevles. --- atom/browser/ui/file_dialog_win.cc | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index f39094f9df..6577e4c084 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -262,30 +262,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, if (FAILED(hr)) return false; - std::string file_name = base::WideToUTF8(std::wstring(buffer)); - - // Append extension according to selected filter. - if (!filters.empty()) { - UINT filter_index = 1; - save_dialog.GetPtr()->GetFileTypeIndex(&filter_index); - const Filter& filter = filters[filter_index - 1]; - - bool matched = false; - for (size_t i = 0; i < filter.second.size(); ++i) { - if (filter.second[i] == "*" || - base::EndsWith( - file_name, filter.second[i], - base::CompareCase::INSENSITIVE_ASCII)) { - matched = true; - break;; - } - } - - if (!matched && !filter.second.empty()) - file_name += ("." + filter.second[0]); - } - - *path = base::FilePath(base::UTF8ToUTF16(file_name)); + *path = base::FilePath(buffer); return true; } From 554bb69101d15d0108e4b1d113c49bc4bd79f6f8 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 5 Jan 2016 19:52:57 +0800 Subject: [PATCH 388/411] Add FAQ --- docs/README.md | 7 ++++ docs/faq/electron-faq.md | 83 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 docs/faq/electron-faq.md diff --git a/docs/README.md b/docs/README.md index 2503612d4e..8708434a9e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,13 @@ you can switch to a different version of the documentation at the you're using the GitHub interface, open the "Switch branches/tags" dropdown and select the tag that matches your version. +## FAQ + +There are questions that are asked quite often, check this out before creating +an issue: + +* [Electron FAQ](faq/api-faq.md) + ## Guides * [Supported Platforms](tutorial/supported-platforms.md) diff --git a/docs/faq/electron-faq.md b/docs/faq/electron-faq.md new file mode 100644 index 0000000000..e8f5b191d7 --- /dev/null +++ b/docs/faq/electron-faq.md @@ -0,0 +1,83 @@ +# Electron FAQ + +## When will Electron upgrade to latest Chrome? + +The Chrome version of Electron is usually bumped within one or two weeks after +a new stable Chrome version gets released. + +Also we only use stable channel of Chrome, if an important fix is in beta or dev +channel, we will back-port it. + +## When will Electron upgrade to latest Node.js? + +When a new version of Node.js gets released, we usually wait for about a month +before upgrading the one in Electron. So we can avoid getting affected by bugs +introduced in new Node.js versions, which happens very often. + +New features of Node.js are usually brought by V8 upgrades, since Electron is +using the V8 shipped by Chrome browser, the shiny new JavaScript feature of a +new Node.js version is usually already in Electron. + +## My app's window/tray disappeared after a few minutes. + +This happens when the variable which is used to store the window/tray gets +garbage collected. + +It is recommended to have a reading of following articles you encountered this +problem: + +* [Memory Management][memory-management] +* [Variable Scope][variable-scope] + +If you want a quick fix, you can make the variables global by changing your +code from this: + +```javascript +app.on('ready', function() { + var tray = new Tray('/path/to/icon.png'); +}) +``` + +to this: + +```javascript +var tray = null; +app.on('ready', function() { + tray = new Tray('/path/to/icon.png'); +}) +``` + +## I can not use jQuery/RequireJS/Meteor/AngularJS in Electron. + +Due to the Node.js integration of Electron, there are some extra symbols +inserted into DOM, like `module`, `exports`, `require`. This causes troubles for +some libraries since they want to insert the symbols with same names. + +To solve this, you can turn off node integration in Electron: + +```javascript +// In the main process. +var mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: false + } +}); +``` + +But if you want to keep the abilities of using Node.js and Electron APIs, you +have to rename the symbols in the page before including other libraries: + +```html + + + + +``` + +[memory-management]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management +[variable-scope]: https://msdn.microsoft.com/library/bzt2dkta(v=vs.94).aspx From 59eda67ba4b935eb5fd205d1130af313d76e3471 Mon Sep 17 00:00:00 2001 From: leethomas Date: Tue, 5 Jan 2016 09:47:19 -0800 Subject: [PATCH 389/411] :apple::bug: make displaying alternate tray image depend on mouse down event instead of highlight state --- atom/browser/ui/tray_icon_cocoa.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 62da2015e5..d2a2fe8346 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -92,7 +92,7 @@ const CGFloat kVerticalTitleMargin = 2; // Make use of NSImageView to draw the image, which can correctly draw // template image under dark menu bar. - if (highlight && alternateImage_ && + if (inMouseEventSequence_ && alternateImage_ && [image_view_ image] != alternateImage_.get()) { [image_view_ setImage:alternateImage_]; } else if ([image_view_ image] != image_.get()) { From bf327cf5f078b4faabf2ac97737444bfa749e316 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Wed, 6 Jan 2016 06:12:30 +0900 Subject: [PATCH 390/411] :memo: Update as upstream [ci skip] --- docs-translations/ko-KR/api/web-contents.md | 32 +++++++++++- docs-translations/ko-KR/api/web-view-tag.md | 55 +++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index e6dec1bfc9..59b7d0c153 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -130,11 +130,39 @@ Returns: 사용자 또는 페이지가 새로운 페이지로 이동할 때 발생하는 이벤트입니다. `window.location` 객체가 변경되거나 사용자가 페이지의 링크를 클릭했을 때 발생합니다. -이 이벤트는 `webContents.loadURL`과 `webContents.back` 같은 API를 이용하여 +이 이벤트는 `webContents.loadURL`과 `webContents.back` 같은 API를 이용한 프로그램적으로 시작된 탐색에 대해서는 발생하지 않습니다. +이 이벤트는 앵커 링크를 클릭하거나 `window.location.hash`의 값을 변경하는 등의 페이지 +내 탐색시엔 발생하지 않습니다. 대신 `did-navigate-in-page` 이벤트를 사용해야 합니다. + `event.preventDefault()`를 호출하면 탐색을 방지할 수 있습니다. +### Event: 'did-navigate' + +Returns: + +* `event` Event +* `url` String + +탐색이 완료되면 발생하는 이벤트입니다. + +이 이벤트는 앵커 링크를 클릭하거나 `window.location.hash`의 값을 변경하는 등의 페이지 +내 탐색시엔 발생하지 않습니다. 대신 `did-navigate-in-page` 이벤트를 사용해야 합니다. + +### Event: 'did-navigate-in-page' + +Returns: + +* `event` Event +* `url` String + +페이지 내의 탐색이 완료되면 발생하는 이벤트입니다. + +페이지 내의 탐색이 발생하면 페이지 URL이 변경되지만 페이지 밖으로의 탐색은 일어나지 +않습니다. 예를 들어 앵커 링크를 클릭했을 때, 또는 DOM `hashchange` 이벤트가 발생했을 +때로 볼 수 있습니다. + ### Event: 'crashed' 렌더러 프로세스가 예기치 못하게 종료되었을 때 발생되는 이벤트입니다. @@ -612,7 +640,7 @@ mainWindow.webContents.on('devtools-opened', function() { ### `webContents.isDevToolsFocused()` -개발자 도구에 포커스가 가있는지 여부를 반화합니다. +개발자 도구에 포커스 되어있는지 여부를 반환합니다. ### `webContents.inspectElement(x, y)` diff --git a/docs-translations/ko-KR/api/web-view-tag.md b/docs-translations/ko-KR/api/web-view-tag.md index 8a4fd439bb..591721752f 100644 --- a/docs-translations/ko-KR/api/web-view-tag.md +++ b/docs-translations/ko-KR/api/web-view-tag.md @@ -277,6 +277,10 @@ webview.addEventListener("dom-ready", function() { 페이지에 대한 개발자 도구가 열려있는지 확인합니다. 불린 값을 반환합니다. +### `.isDevToolsFocused()` + +페이지의 개발자 도구에 포커스 되어있는지 여부를 반화합니다. + ### `.inspectElement(x, y)` * `x` Integer @@ -561,6 +565,46 @@ webview.addEventListener('new-window', function(e) { }); ``` +### Event: 'will-navigate' + +Returns: + +* `url` String + +사용자 또는 페이지가 새로운 페이지로 이동할 때 발생하는 이벤트입니다. +`window.location` 객체가 변경되거나 사용자가 페이지의 링크를 클릭했을 때 발생합니다. + +이 이벤트는 `.loadURL`과 `.back` 같은 API를 이용한 +프로그램적으로 시작된 탐색에 대해서는 발생하지 않습니다. + +이 이벤트는 앵커 링크를 클릭하거나 `window.location.hash`의 값을 변경하는 등의 페이지 +내 탐색시엔 발생하지 않습니다. 대신 `did-navigate-in-page` 이벤트를 사용해야 합니다. + +`event.preventDefault()`를 호출하는 것은 __아무__ 효과도 내지 않습니다. + +### Event: 'did-navigate' + +Returns: + +* `url` String + +탐색이 완료되면 발생하는 이벤트입니다. + +이 이벤트는 앵커 링크를 클릭하거나 `window.location.hash`의 값을 변경하는 등의 페이지 +내 탐색시엔 발생하지 않습니다. 대신 `did-navigate-in-page` 이벤트를 사용해야 합니다. + +### Event: 'did-navigate-in-page' + +Returns: + +* `url` String + +페이지 내의 탐색이 완료되면 발생하는 이벤트입니다. + +페이지 내의 탐색이 발생하면 페이지 URL이 변경되지만 페이지 밖으로의 탐색은 일어나지 +않습니다. 예를 들어 앵커 링크를 클릭했을 때, 또는 DOM `hashchange` 이벤트가 발생했을 +때로 볼 수 있습니다. + ### Event: 'close' 페이지가 자체적으로 닫힐 때 발생하는 이벤트입니다. @@ -640,3 +684,14 @@ WebContents가 파괴될 때 발생하는 이벤트입니다. ```html ``` +### Event: 'devtools-opened' + +개발자 도구가 열렸을 때 발생하는 이벤트입니다. + +### Event: 'devtools-closed' + +개발자 도구가 닫혔을 때 발생하는 이벤트입니다. + +### Event: 'devtools-focused' + +개발자 도구가 포커스되거나 열렸을 때 발생하는 이벤트입니다. From c4071a7f665b4007e0b19fa9f815e8f6b1676d45 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 6 Jan 2016 12:04:16 +0800 Subject: [PATCH 391/411] Throw ENOTDIR when calling mkdir inside asar archive --- atom/common/lib/asar.coffee | 27 +++++++++++++++++++++++++++ spec/asar-spec.coffee | 19 +++++++++++++++++++ spec/package.json | 1 + 3 files changed, 47 insertions(+) diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index 2373385f75..5f690e9a8f 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -63,6 +63,15 @@ notFoundError = (asarPath, filePath, callback) -> throw error process.nextTick -> callback error +# Create a ENOTDIR error. +notDirError = (callback) -> + error = new Error('ENOTDIR, not a directory') + error.code = 'ENOTDIR' + error.errno = -20 + unless typeof callback is 'function' + throw error + process.nextTick -> callback error + # Create invalid archive error. invalidArchiveError = (asarPath, callback) -> error = new Error("Invalid package #{asarPath}") @@ -351,6 +360,24 @@ exports.wrapFsWithAsar = (fs) -> if stats.isDirectory then return 1 else return 0 + # Calling mkdir for directory inside asar archive should throw ENOTDIR + # error, but on Windows it throws ENOENT. + # This is to work around the recursive looping bug of mkdirp since it is + # widely used. + if process.platform is 'win32' + mkdir = fs.mkdir + fs.mkdir = (p, mode, callback) -> + callback = mode if typeof mode is 'function' + [isAsar, asarPath, filePath] = splitPath p + return notDirError callback if isAsar and filePath.length + mkdir p, mode, callback + + mkdirSync = fs.mkdirSync + fs.mkdirSync = (p, mode) -> + [isAsar, asarPath, filePath] = splitPath p + notDirError() if isAsar and filePath.length + mkdirSync p, mode + overrideAPI fs, 'open' overrideAPI child_process, 'execFile' overrideAPISync process, 'dlopen', 1 diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index 479443292b..7642283cea 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -374,6 +374,18 @@ describe 'asar package', -> assert.equal err.code, 'ENOENT' done() + describe 'fs.mkdir', -> + it 'throws error when calling inside asar archive', (done) -> + p = path.join fixtures, 'asar', 'a.asar', 'not-exist' + fs.mkdir p, (err) -> + assert.equal err.code, 'ENOTDIR' + done() + + describe 'fs.mkdirSync', -> + it 'throws error when calling inside asar archive', -> + p = path.join fixtures, 'asar', 'a.asar', 'not-exist' + assert.throws (-> fs.mkdirSync p), new RegExp('ENOTDIR') + describe 'child_process.fork', -> child_process = require 'child_process' @@ -547,6 +559,13 @@ describe 'asar package', -> it 'does not touch global fs object', -> assert.notEqual fs.readdir, gfs.readdir + describe 'mkdirp module', -> + mkdirp = require 'mkdirp' + + it 'throws error when calling inside asar archive', -> + p = path.join fixtures, 'asar', 'a.asar', 'not-exist' + assert.throws (-> mkdirp.sync p), new RegExp('ENOTDIR') + describe 'native-image', -> it 'reads image from asar archive', -> p = path.join fixtures, 'asar', 'logo.asar', 'logo.png' diff --git a/spec/package.json b/spec/package.json index 79e7d954eb..6d49f0da8b 100644 --- a/spec/package.json +++ b/spec/package.json @@ -7,6 +7,7 @@ "basic-auth": "^1.0.0", "graceful-fs": "3.0.5", "mocha": "2.1.0", + "mkdirp": "0.5.1", "multiparty": "4.1.2", "q": "0.9.7", "temp": "0.8.1", From 9a55021609ae275fb0713875e9ab6b1e706912c6 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 6 Jan 2016 12:27:12 +0800 Subject: [PATCH 392/411] spec: Fix failing specs on Windows --- spec/webview-spec.coffee | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index 1dcf739ec0..4754ea4348 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -1,6 +1,7 @@ assert = require 'assert' path = require 'path' http = require 'http' +url = require 'url' describe ' tag', -> @timeout 10000 @@ -261,12 +262,12 @@ describe ' tag', -> it 'emits when favicon urls are received', (done) -> webview.addEventListener 'page-favicon-updated', (e) -> assert.equal e.favicons.length, 2 - url = + pageUrl = if process.platform is 'win32' 'file:///C:/favicon.png' else 'file:///favicon.png' - assert.equal e.favicons[0], url + assert.equal e.favicons[0], pageUrl done() webview.src = "file://#{fixtures}/pages/a.html" document.body.appendChild webview @@ -281,7 +282,9 @@ describe ' tag', -> document.body.appendChild webview describe 'did-navigate event', -> - pageUrl = "file://#{fixtures}/pages/webview-will-navigate.html" + p = path.join fixtures, 'pages', 'webview-will-navigate.html' + p = p.replace /\\/g, '/' + pageUrl = url.format protocol: 'file', slashes: true, pathname: p it 'emits when a url that leads to outside of the page is clicked', (done) -> webview.addEventListener 'did-navigate', (e) -> @@ -293,7 +296,10 @@ describe ' tag', -> describe 'did-navigate-in-page event', -> it 'emits when an anchor link is clicked', (done) -> - pageUrl = "file://#{fixtures}/pages/webview-did-navigate-in-page.html" + p = path.join fixtures, 'pages', 'webview-did-navigate-in-page.html' + p = p.replace /\\/g, '/' + pageUrl = url.format protocol: 'file', slashes: true, pathname: p + webview.addEventListener 'did-navigate-in-page', (e) -> assert.equal e.url, "#{pageUrl}#test_content" done() @@ -310,7 +316,10 @@ describe ' tag', -> document.body.appendChild webview it 'emits when window.location.hash is changed', (done) -> - pageUrl = "file://#{fixtures}/pages/webview-did-navigate-in-page-with-hash.html" + p = path.join fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html' + p = p.replace /\\/g, '/' + pageUrl = url.format protocol: 'file', slashes: true, pathname: p + webview.addEventListener 'did-navigate-in-page', (e) -> assert.equal e.url, "#{pageUrl}#test" done() From 325966463717ce281daee6f2c1a271fd25c9a207 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Wed, 6 Jan 2016 07:13:25 +0000 Subject: [PATCH 393/411] Add info on how to debug from 0.30 onwards --- docs/tutorial/debugging-main-process.md | 36 +++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index aa95ae312b..977621e1a0 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -23,13 +23,30 @@ __Note:__ Electron doesn't currently work very well with node-inspector, and the main process will crash if you inspect the `process` object under node-inspector's console. -### 1. Start the [node-inspector][node-inspector] server +### 1. Make sure you have [node-gyp required tools][node-gyp-required-tools] installed + +### 2. Install [node-inspector][node-inspector] ```bash -$ node-inspector +$ npm i node-inspector ``` -### 2. Enable debug mode for Electron +### 3. Install a patched version of `node-pre-gyp` + +```bash +$ npm i git+https://git@github.com/enlight/node-pre-gyp.git#detect-electron-runtime-in-find +``` + +### 4. Recompile the `node-inspector` `v8` modules for electron (change the target to your electron version number) + +```bash +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +``` + +See also [How to install native modules](how-to-install-native-modules). + +### 5. Enable debug mode for Electron You can either start Electron with a debug flag like: @@ -43,8 +60,17 @@ or, to pause your script on the first line: $ electron --debug-brk=5858 your/app ``` -### 3. Load the debugger UI +### 5. Start the [node-inspector][node-inspector] server using electron -Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome browser. +```bash +$ ATOM_SHELL_INTERNAL_RUN_AS_NODE=1 path/to/electron.exe node_modules/node-inspector/bin/inspector.js +``` + +### 6. Load the debugger UI + +Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome browser. You may have to click pause if starting with debug-brk to see the entry line. [node-inspector]: https://github.com/node-inspector/node-inspector +[node-gyp-required-tools]: https://github.com/nodejs/node-gyp#installation +[how-to-install-native-modules]: using-native-node-modules.md#how-to-install-native-modules + From 89fccb7eb2a99dbe74ea4a5dae1e0911ca96f1f6 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 6 Jan 2016 21:07:07 +0800 Subject: [PATCH 394/411] Fix crash when request failed --- atom/browser/net/url_request_fetch_job.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index 8ffb6df012..6f41829017 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -205,7 +205,7 @@ bool URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, } bool URLRequestFetchJob::GetMimeType(std::string* mime_type) const { - if (!response_info_) + if (!response_info_ || !response_info_->headers) return false; return response_info_->headers->GetMimeType(mime_type); @@ -217,7 +217,7 @@ void URLRequestFetchJob::GetResponseInfo(net::HttpResponseInfo* info) { } int URLRequestFetchJob::GetResponseCode() const { - if (!response_info_) + if (!response_info_ || !response_info_->headers) return -1; return response_info_->headers->response_code(); From aaf5f3331a68f394dfdfcaf15efec4044ece93ac Mon Sep 17 00:00:00 2001 From: Levin Rickert Date: Wed, 6 Jan 2016 14:41:51 +0100 Subject: [PATCH 395/411] Update debugging-main-process.md --- docs/tutorial/debugging-main-process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index 977621e1a0..703f7dc4d7 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -63,7 +63,7 @@ $ electron --debug-brk=5858 your/app ### 5. Start the [node-inspector][node-inspector] server using electron ```bash -$ ATOM_SHELL_INTERNAL_RUN_AS_NODE=1 path/to/electron.exe node_modules/node-inspector/bin/inspector.js +$ ELECTRON_RUN_AS_NODE=1 path/to/electron.exe node_modules/node-inspector/bin/inspector.js ``` ### 6. Load the debugger UI From 062253bdfc9a4124a3e3dcb0b1828ce777aa7d8e Mon Sep 17 00:00:00 2001 From: Levin Rickert Date: Wed, 6 Jan 2016 14:43:13 +0100 Subject: [PATCH 396/411] Update debugging-main-process.md --- docs/tutorial/debugging-main-process.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index 703f7dc4d7..73710211e3 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -28,13 +28,13 @@ with node-inspector, and the main process will crash if you inspect the ### 2. Install [node-inspector][node-inspector] ```bash -$ npm i node-inspector +$ npm install node-inspector ``` ### 3. Install a patched version of `node-pre-gyp` ```bash -$ npm i git+https://git@github.com/enlight/node-pre-gyp.git#detect-electron-runtime-in-find +$ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electron-runtime-in-find ``` ### 4. Recompile the `node-inspector` `v8` modules for electron (change the target to your electron version number) @@ -63,7 +63,7 @@ $ electron --debug-brk=5858 your/app ### 5. Start the [node-inspector][node-inspector] server using electron ```bash -$ ELECTRON_RUN_AS_NODE=1 path/to/electron.exe node_modules/node-inspector/bin/inspector.js +$ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin/inspector.js ``` ### 6. Load the debugger UI From 3ca5b0ce23b302b9be80b49d39d6f5fd733933e3 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 6 Jan 2016 23:02:33 +0800 Subject: [PATCH 397/411] win: Don't change transparent window's style Close #1952. --- atom/browser/native_window_views.cc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 1abb2ef8d3..35e12ad3c9 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -424,12 +424,14 @@ void NativeWindowViews::SetResizable(bool resizable) { // WS_MAXIMIZEBOX => Maximize button // WS_MINIMIZEBOX => Minimize button // WS_THICKFRAME => Resize handle - DWORD style = ::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE); - if (resizable) - style |= WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME; - else - style = (style & ~(WS_MAXIMIZEBOX | WS_THICKFRAME)) | WS_MINIMIZEBOX; - ::SetWindowLong(GetAcceleratedWidget(), GWL_STYLE, style); + if (!transparent()) { + DWORD style = ::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE); + if (resizable) + style |= WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME; + else + style = (style & ~(WS_MAXIMIZEBOX | WS_THICKFRAME)) | WS_MINIMIZEBOX; + ::SetWindowLong(GetAcceleratedWidget(), GWL_STYLE, style); + } #elif defined(USE_X11) if (resizable != resizable_) { // On Linux there is no "resizable" property of a window, we have to set From 705001a50e3790c3c3e232dcb5be2ebf187b9417 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 6 Jan 2016 10:37:12 -0800 Subject: [PATCH 398/411] Remove custom WM_GETOBJECT As of Chromium 47 this seems to be handled automatically, nvda still reports elements and the typing lag issue described in #4001 is fixed --- atom/browser/native_window_views_win.cc | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 02ebd2ef32..513b33bcb8 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -84,20 +84,6 @@ bool NativeWindowViews::PreHandleMSG( NotifyWindowMessage(message, w_param, l_param); switch (message) { - // Screen readers send WM_GETOBJECT in order to get the accessibility - // object, so take this opportunity to push Chromium into accessible - // mode if it isn't already, always say we didn't handle the message - // because we still want Chromium to handle returning the actual - // accessibility object. - case WM_GETOBJECT: { - const DWORD obj_id = static_cast(l_param); - if (obj_id == OBJID_CLIENT) { - const auto axState = content::BrowserAccessibilityState::GetInstance(); - if (axState && !axState->IsAccessibleBrowser()) - axState->OnScreenReaderDetected(); - } - return false; - } case WM_COMMAND: // Handle thumbar button click message. if (HIWORD(w_param) == THBN_CLICKED) From c6e03f83905b6784619bc5db8404fe75df1dc4e2 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 12:17:19 +0800 Subject: [PATCH 399/411] Remove overlayScrollbars and sharedWorker options They are already enabled by default. --- atom/browser/web_contents_preferences.cc | 4 ---- atom/common/options_switches.cc | 4 ---- atom/common/options_switches.h | 4 ---- atom/renderer/atom_renderer_client.cc | 4 ---- docs/api/browser-window.md | 3 --- 5 files changed, 19 deletions(-) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 382c28951d..0c2055126a 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -34,10 +34,6 @@ FeaturePair kWebRuntimeFeatures[] = { switches::kExperimentalFeatures }, { options::kExperimentalCanvasFeatures, switches::kExperimentalCanvasFeatures }, - { options::kOverlayScrollbars, - switches::kOverlayScrollbars }, - { options::kSharedWorker, - switches::kSharedWorker }, { options::kPageVisibility, switches::kPageVisibility }, }; diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 6a3490f41d..b87f10a83a 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -97,8 +97,6 @@ const char kOpenerID[] = "openerId"; // Web runtime features. const char kExperimentalFeatures[] = "experimentalFeatures"; const char kExperimentalCanvasFeatures[] = "experimentalCanvasFeatures"; -const char kOverlayScrollbars[] = "overlayScrollbars"; -const char kSharedWorker[] = "sharedWorker"; } // namespace options @@ -143,8 +141,6 @@ const char kNodeIntegration[] = "node-integration"; const char kGuestInstanceID[] = "guest-instance-id"; const char kExperimentalFeatures[] = "experimental-features"; const char kExperimentalCanvasFeatures[] = "experimental-canvas-features"; -const char kOverlayScrollbars[] = "overlay-scrollbars"; -const char kSharedWorker[] = "shared-worker"; const char kPageVisibility[] = "page-visiblity"; const char kOpenerID[] = "opener-id"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 161244450a..7052acb048 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -49,8 +49,6 @@ extern const char kNodeIntegration[]; extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; -extern const char kOverlayScrollbars[]; -extern const char kSharedWorker[]; extern const char kPageVisibility[]; extern const char kOpenerID[]; @@ -79,8 +77,6 @@ extern const char kNodeIntegration[]; extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; -extern const char kOverlayScrollbars[]; -extern const char kSharedWorker[]; extern const char kPageVisibility[]; extern const char kOpenerID[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 6abe839b83..bae49bf9f1 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -230,10 +230,6 @@ void AtomRendererClient::EnableWebRuntimeFeatures() { blink::WebRuntimeFeatures::enableExperimentalFeatures(true); if (IsSwitchEnabled(command_line, switches::kExperimentalCanvasFeatures)) blink::WebRuntimeFeatures::enableExperimentalCanvasFeatures(true); - if (IsSwitchEnabled(command_line, switches::kOverlayScrollbars)) - blink::WebRuntimeFeatures::enableOverlayScrollbars(true); - if (IsSwitchEnabled(command_line, switches::kSharedWorker)) - blink::WebRuntimeFeatures::enableSharedWorker(true); } void AtomRendererClient::AddKeySystems( diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 44c4da5c65..b6a46316ae 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -136,9 +136,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. Default is `false`. * `experimentalCanvasFeatures` Boolean - Enables Chromium's experimental canvas features. Default is `false`. - * `overlayScrollbars` Boolean - Enables overlay scrollbars. Default is - `false`. - * `sharedWorker` Boolean - Enables Shared Worker support. Default is `false`. * `directWrite` Boolean - Enables DirectWrite font rendering system on Windows. Default is `true`. * `pageVisibility` Boolean - Page would be forced to be always in visible From 16d23bbda55059cbbcbb14f3ca995e4407c1cd8e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 12:28:20 +0800 Subject: [PATCH 400/411] Remove pageVisibility option The original purpose of this option is not working anymore, also adds docs on the current way to disable process backgrounding. --- atom/browser/web_contents_preferences.cc | 2 -- atom/common/options_switches.cc | 4 ---- atom/common/options_switches.h | 2 -- atom/renderer/atom_renderer_client.cc | 13 ------------- atom/renderer/atom_renderer_client.h | 3 --- docs/api/browser-window.md | 4 ---- docs/api/chrome-command-line-switches.md | 20 +++++++++++++++----- 7 files changed, 15 insertions(+), 33 deletions(-) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 0c2055126a..92b89de614 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -34,8 +34,6 @@ FeaturePair kWebRuntimeFeatures[] = { switches::kExperimentalFeatures }, { options::kExperimentalCanvasFeatures, switches::kExperimentalCanvasFeatures }, - { options::kPageVisibility, - switches::kPageVisibility }, }; } // namespace diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index b87f10a83a..776113cb10 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -85,9 +85,6 @@ const char kNodeIntegration[] = "nodeIntegration"; // Instancd ID of guest WebContents. const char kGuestInstanceID[] = "guestInstanceId"; -// Set page visiblity to always visible. -const char kPageVisibility[] = "pageVisibility"; - // Enable DirectWrite on Windows. const char kDirectWrite[] = "directWrite"; @@ -141,7 +138,6 @@ const char kNodeIntegration[] = "node-integration"; const char kGuestInstanceID[] = "guest-instance-id"; const char kExperimentalFeatures[] = "experimental-features"; const char kExperimentalCanvasFeatures[] = "experimental-canvas-features"; -const char kPageVisibility[] = "page-visiblity"; const char kOpenerID[] = "opener-id"; // Widevine options diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 7052acb048..f22d35df18 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -49,7 +49,6 @@ extern const char kNodeIntegration[]; extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; -extern const char kPageVisibility[]; extern const char kOpenerID[]; } // namespace options @@ -77,7 +76,6 @@ extern const char kNodeIntegration[]; extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; -extern const char kPageVisibility[]; extern const char kOpenerID[]; extern const char kWidevineCdmPath[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index bae49bf9f1..31336f3bab 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -210,19 +210,6 @@ content::BrowserPluginDelegate* AtomRendererClient::CreateBrowserPluginDelegate( } } -bool AtomRendererClient::ShouldOverridePageVisibilityState( - const content::RenderFrame* render_frame, - blink::WebPageVisibilityState* override_state) { - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - - if (IsSwitchEnabled(command_line, switches::kPageVisibility)) { - *override_state = blink::WebPageVisibilityStateVisible; - return true; - } - - return false; -} - void AtomRendererClient::EnableWebRuntimeFeatures() { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index ee082e964c..d99e83f79a 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -56,9 +56,6 @@ class AtomRendererClient : public content::ContentRendererClient, content::RenderFrame* render_frame, const std::string& mime_type, const GURL& original_url) override; - bool ShouldOverridePageVisibilityState( - const content::RenderFrame* render_frame, - blink::WebPageVisibilityState* override_state) override; void AddKeySystems(std::vector* key_systems) override; void EnableWebRuntimeFeatures(); diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index b6a46316ae..6dc5257667 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -138,10 +138,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. canvas features. Default is `false`. * `directWrite` Boolean - Enables DirectWrite font rendering system on Windows. Default is `true`. - * `pageVisibility` Boolean - Page would be forced to be always in visible - or hidden state once set, instead of reflecting current window's - visibility. Users can set it to `true` to prevent throttling of DOM - timers. Default is `false`. ## Events diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index 798d756820..65f096eac6 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -94,10 +94,6 @@ connection, and the endpoint host in a `SOCKS` proxy connection). Like `--host-rules` but these `rules` only apply to the host resolver. -[app]: app.md -[append-switch]: app.md#appcommandlineappendswitchswitch-value -[ready]: app.md#event-ready - ## --ignore-certificate-errors Ignores certificate related errors. @@ -121,7 +117,16 @@ fallback will accept. ## --cipher-suite-blacklist=`cipher_suites` -Specify comma-separated list of SSL cipher suites to disable. +Specifies comma-separated list of SSL cipher suites to disable. + +## --disable-renderer-backgrounding + +Prevents Chromium from lowering the priority of invisible pages' renderer +processes. + +This flag is global to all renderer processes, if you only want to disable +throttling in one window, you can take the hack of +[playing silent audio][play-silent-audio]. ## --enable-logging @@ -149,3 +154,8 @@ whole pathname and not just the module. E.g. `*/foo/bar/*=2` would change the logging level for all code in the source files under a `foo/bar` directory. This switch only works when `--enable-logging` is also passed. + +[app]: app.md +[append-switch]: app.md#appcommandlineappendswitchswitch-value +[ready]: app.md#event-ready +[play-silent-audio]: https://github.com/atom/atom/pull/9485/files From bd20b3f32a4e35e47a0c75f94b28612fc272462a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 12:49:00 +0800 Subject: [PATCH 401/411] Rely on content switches for implementing experimental features --- atom/browser/web_contents_preferences.cc | 30 ++++++------------------ atom/common/options_switches.cc | 2 -- atom/common/options_switches.h | 2 -- atom/renderer/atom_renderer_client.cc | 16 ------------- atom/renderer/atom_renderer_client.h | 2 -- 5 files changed, 7 insertions(+), 45 deletions(-) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 92b89de614..03003a563a 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -10,6 +10,7 @@ #include "atom/common/options_switches.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" +#include "content/public/common/content_switches.h" #include "content/public/common/web_preferences.h" #include "native_mate/dictionary.h" #include "net/base/filename_util.h" @@ -22,22 +23,6 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPreferences); namespace atom { -namespace { - -// Array of available web runtime features. -struct FeaturePair { - const char* name; - const char* cmd; -}; -FeaturePair kWebRuntimeFeatures[] = { - { options::kExperimentalFeatures, - switches::kExperimentalFeatures }, - { options::kExperimentalCanvasFeatures, - switches::kExperimentalCanvasFeatures }, -}; - -} // namespace - WebContentsPreferences::WebContentsPreferences( content::WebContents* web_contents, const mate::Dictionary& web_preferences) { @@ -79,13 +64,12 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( if (web_preferences.GetBoolean("plugins", &b) && b) command_line->AppendSwitch(switches::kEnablePlugins); - // This set of options are not availabe in WebPreferences, so we have to pass - // them via command line and enable them in renderer procss. - for (size_t i = 0; i < arraysize(kWebRuntimeFeatures); ++i) { - const auto& feature = kWebRuntimeFeatures[i]; - if (web_preferences.GetBoolean(feature.name, &b)) - command_line->AppendSwitchASCII(feature.cmd, b ? "true" : "false"); - } + // Experimental flags. + if (web_preferences.GetBoolean(options::kExperimentalFeatures, &b) && b) + command_line->AppendSwitch( + ::switches::kEnableExperimentalWebPlatformFeatures); + if (web_preferences.GetBoolean(options::kExperimentalCanvasFeatures, &b) && b) + command_line->AppendSwitch(::switches::kEnableExperimentalCanvasFeatures); // Check if we have node integration specified. bool node_integration = true; diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 776113cb10..a62af41e16 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -136,8 +136,6 @@ const char kPreloadScript[] = "preload"; const char kPreloadURL[] = "preload-url"; const char kNodeIntegration[] = "node-integration"; const char kGuestInstanceID[] = "guest-instance-id"; -const char kExperimentalFeatures[] = "experimental-features"; -const char kExperimentalCanvasFeatures[] = "experimental-canvas-features"; const char kOpenerID[] = "opener-id"; // Widevine options diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index f22d35df18..ef860b6aea 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -74,8 +74,6 @@ extern const char kPreloadScript[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; extern const char kGuestInstanceID[]; -extern const char kExperimentalFeatures[]; -extern const char kExperimentalCanvasFeatures[]; extern const char kOpenerID[]; extern const char kWidevineCdmPath[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 31336f3bab..d9c364c373 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -41,11 +41,6 @@ namespace atom { namespace { -bool IsSwitchEnabled(base::CommandLine* command_line, - const char* switch_string) { - return command_line->GetSwitchValueASCII(switch_string) == "true"; -} - // Helper class to forward the messages to the client. class AtomRenderFrameObserver : public content::RenderFrameObserver { public: @@ -95,8 +90,6 @@ AtomRendererClient::~AtomRendererClient() { } void AtomRendererClient::WebKitInitialized() { - EnableWebRuntimeFeatures(); - blink::WebCustomElement::addEmbedderCustomElementName("webview"); blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); @@ -210,15 +203,6 @@ content::BrowserPluginDelegate* AtomRendererClient::CreateBrowserPluginDelegate( } } -void AtomRendererClient::EnableWebRuntimeFeatures() { - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - - if (IsSwitchEnabled(command_line, switches::kExperimentalFeatures)) - blink::WebRuntimeFeatures::enableExperimentalFeatures(true); - if (IsSwitchEnabled(command_line, switches::kExperimentalCanvasFeatures)) - blink::WebRuntimeFeatures::enableExperimentalCanvasFeatures(true); -} - void AtomRendererClient::AddKeySystems( std::vector* key_systems) { AddChromeKeySystems(key_systems); diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index d99e83f79a..beeeb9d530 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -58,8 +58,6 @@ class AtomRendererClient : public content::ContentRendererClient, const GURL& original_url) override; void AddKeySystems(std::vector* key_systems) override; - void EnableWebRuntimeFeatures(); - scoped_ptr node_bindings_; scoped_ptr atom_bindings_; From 3f2b26ddb77685785e54fca77387009b44373645 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 14:10:18 +0800 Subject: [PATCH 402/411] Add blinkFeatures option --- atom/browser/web_contents_preferences.cc | 6 ++++++ atom/common/options_switches.cc | 9 ++++++--- atom/common/options_switches.h | 1 + docs/api/browser-window.md | 6 ++++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 03003a563a..c75b0daaff 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -116,6 +116,12 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( if (web_preferences.GetInteger(options::kOpenerID, &opener_id)) command_line->AppendSwitchASCII(switches::kOpenerID, base::IntToString(opener_id)); + + // Enable blink features. + std::string blink_features; + if (web_preferences.GetString(options::kBlinkFeatures, &blink_features)) + command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures, + blink_features); } // static diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index a62af41e16..6c4e8477cc 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -88,13 +88,16 @@ const char kGuestInstanceID[] = "guestInstanceId"; // Enable DirectWrite on Windows. const char kDirectWrite[] = "directWrite"; -// Opener window's ID. -const char kOpenerID[] = "openerId"; - // Web runtime features. const char kExperimentalFeatures[] = "experimentalFeatures"; const char kExperimentalCanvasFeatures[] = "experimentalCanvasFeatures"; +// Opener window's ID. +const char kOpenerID[] = "openerId"; + +// Enable blink features. +const char kBlinkFeatures[] = "blinkFeatures"; + } // namespace options namespace switches { diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index ef860b6aea..f038521673 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -50,6 +50,7 @@ extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; extern const char kOpenerID[]; +extern const char kBlinkFeatures[]; } // namespace options diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 6dc5257667..8511ca7a56 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -138,6 +138,10 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. canvas features. Default is `false`. * `directWrite` Boolean - Enables DirectWrite font rendering system on Windows. Default is `true`. + * `blinkFeatures` String - A list of feature strings separated by `,`, like + `CSSVariables,KeyboardEventKey`. The full list of supported feature strings + can be found in the [setFeatureEnabledFromString][blink-feature-string] + function. ## Events @@ -750,3 +754,5 @@ Returns whether the window is visible on all workspaces. * `ignore` Boolean Ignore all moused events that happened in the window. + +[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 From c0e728ab6ae82468c36ae527c7381079b8d038ae Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 14:23:21 +0800 Subject: [PATCH 403/411] docs: Orgnize the options of BrowserWindow --- docs/api/browser-window.md | 216 +++++++++++++++++++------------------ 1 file changed, 113 insertions(+), 103 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 8511ca7a56..8fb64b5612 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -31,117 +31,127 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. ### `new BrowserWindow([options])` -`options` Object (optional), properties: +* `options` Object + * `width` Integer - Window's width in pixels. Default is `800`. + * `height` Integer - Window's height in pixels. Default is `600`. + * `x` Integer - Window's left offset from screen. Default is to center the + window. + * `y` Integer - Window's top offset from screen. Default is to center the + window. + * `useContentSize` Boolean - The `width` and `height` would be used as web + page's size, which means the actual window's size will include window + frame's size and be slightly larger. Default is `false`. + * `center` Boolean - Show window in the center of the screen. + * `minWidth` Integer - Window's minimum width. Default is `0`. + * `minHeight` Integer - Window's minimum height. Default is `0`. + * `maxWidth` Integer - Window's maximum width. Default is no limit. + * `maxHeight` Integer - Window's maximum height. Default is no limit. + * `resizable` Boolean - Whether window is resizable. Default is `true`. + * `alwaysOnTop` Boolean - Whether the window should always stay on top of + other windows. Default is `false`. + * `fullscreen` Boolean - Whether the window should show in fullscreen. When + set to `false` the fullscreen button will be hidden or disabled on OS X. + Default is `false`. + * `skipTaskbar` Boolean - Whether to show the window in taskbar. Default is + `false`. + * `kiosk` Boolean - The kiosk mode. Default is `false`. + * `title` String - Default window title. Default is `"Electron"`. + * `icon` [NativeImage](native-image.md) - The window icon, when omitted on + Windows the executable's icon would be used as window icon. + * `show` Boolean - Whether window should be shown when created. Default is + `true`. + * `frame` Boolean - Specify `false` to create a + [Frameless Window](frameless-window.md). Default is `true`. + * `acceptFirstMouse` Boolean - Whether the web view accepts a single + mouse-down event that simultaneously activates the window. Default is `false`. + * `disableAutoHideCursor` Boolean - Whether to hide cursor when typing. Default + is `false`. + * `autoHideMenuBar` Boolean - Auto hide the menu bar unless the `Alt` + key is pressed. Default is `false`. + * `enableLargerThanScreen` Boolean - Enable the window to be resized larger + than screen. Default is `false`. + * `backgroundColor` String - Window's background color as Hexadecimal value, + like `#66CD00` or `#FFF`. This is only implemented on Linux and Windows. + Default is `#000` (black). + * `darkTheme` Boolean - Forces using dark theme for the window, only works on + some GTK+3 desktop environments. Default is `false`. + * `transparent` Boolean - Makes the window [transparent](frameless-window.md). + Default is `false`. + * `type` String - The type of window, default is normal window. See more about + this bellow. + * `titleBarStyle` String - The style of window title bar. See more about this + bellow. + * `webPreferences` Object - Settings of web page's features. See more about + this bellow. -* `width` Integer - Window's width in pixels. Default is `800`. -* `height` Integer - Window's height in pixels. Default is `600`. -* `x` Integer - Window's left offset from screen. Default is to center the - window. -* `y` Integer - Window's top offset from screen. Default is to center the - window. -* `useContentSize` Boolean - The `width` and `height` would be used as web - page's size, which means the actual window's size will include window - frame's size and be slightly larger. Default is `false`. -* `center` Boolean - Show window in the center of the screen. -* `minWidth` Integer - Window's minimum width. Default is `0`. -* `minHeight` Integer - Window's minimum height. Default is `0`. -* `maxWidth` Integer - Window's maximum width. Default is no limit. -* `maxHeight` Integer - Window's maximum height. Default is no limit. -* `resizable` Boolean - Whether window is resizable. Default is `true`. -* `alwaysOnTop` Boolean - Whether the window should always stay on top of - other windows. Default is `false`. -* `fullscreen` Boolean - Whether the window should show in fullscreen. When - set to `false` the fullscreen button will be hidden or disabled on OS X. - Default is `false`. -* `skipTaskbar` Boolean - Whether to show the window in taskbar. Default is - `false`. -* `kiosk` Boolean - The kiosk mode. Default is `false`. -* `title` String - Default window title. Default is `"Electron"`. -* `icon` [NativeImage](native-image.md) - The window icon, when omitted on - Windows the executable's icon would be used as window icon. -* `show` Boolean - Whether window should be shown when created. Default is - `true`. -* `frame` Boolean - Specify `false` to create a - [Frameless Window](frameless-window.md). Default is `true`. -* `acceptFirstMouse` Boolean - Whether the web view accepts a single - mouse-down event that simultaneously activates the window. Default is `false`. -* `disableAutoHideCursor` Boolean - Whether to hide cursor when typing. Default - is `false`. -* `autoHideMenuBar` Boolean - Auto hide the menu bar unless the `Alt` - key is pressed. Default is `false`. -* `enableLargerThanScreen` Boolean - Enable the window to be resized larger - than screen. Default is `false`. -* `backgroundColor` String - Window's background color as Hexadecimal value, - like `#66CD00` or `#FFF`. This is only implemented on Linux and Windows. - Default is `#000` (black). -* `darkTheme` Boolean - Forces using dark theme for the window, only works on - some GTK+3 desktop environments. Default is `false`. -* `transparent` Boolean - Makes the window [transparent](frameless-window.md). - Default is `false`. -* `type` String - Specifies the type of the window, which applies - additional platform-specific properties. By default it's undefined and you'll - get a regular app window. Supported values: - * On Linux, possible types are `desktop`, `dock`, `toolbar`, `splash`, - `notification`. - * On OS X, possible types are `desktop`, `textured`. The `textured` type adds - metal gradient appearance (`NSTexturedBackgroundWindowMask`). The `desktop` - type places the window at the desktop background window level +The possible values and behaviors of `type` option are platform dependent, +supported values are: + +* On Linux, possible types are `desktop`, `dock`, `toolbar`, `splash`, + `notification`. +* On OS X, possible types are `desktop`, `textured`. + * The `textured` type adds metal gradient appearance + (`NSTexturedBackgroundWindowMask`). + * The `desktop` type places the window at the desktop background window level (`kCGDesktopWindowLevel - 1`). Note that desktop window will not receive focus, keyboard or mouse events, but you can use `globalShortcut` to receive input sparingly. -* `titleBarStyle` String, OS X - specifies the style of window title bar. - This option is supported on OS X 10.10 Yosemite and newer. There are three - possible values: - * `default` or not specified, results in the standard gray opaque Mac title + +The `titleBarStyle` option is only supported on OS X 10.10 Yosemite and newer. +Possible values are: + +* `default` or not specified, results in the standard gray opaque Mac title bar. - * `hidden` results in a hidden title bar and a full size content window, yet +* `hidden` results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls ("traffic lights") in the top left. - * `hidden-inset` results in a hidden title bar with an alternative look +* `hidden-inset` results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. -* `webPreferences` Object - Settings of web page's features, properties: - * `nodeIntegration` Boolean - Whether node integration is enabled. Default - is `true`. - * `preload` String - Specifies a script that will be loaded before other - scripts run in the page. This script will always have access to node APIs - no matter whether node integration is turned on or off. The value should - be the absolute file path to the script. - When node integration is turned off, the preload script can reintroduce - Node global symbols back to the global scope. See example - [here](process.md#event-loaded). - * `partition` String - Sets the session used by the page. If `partition` - starts with `persist:`, the page will use a persistent session available to - all pages in the app with the same `partition`. if there is no `persist:` - prefix, the page will use an in-memory session. By assigning the same - `partition`, multiple pages can share the same session. If the `partition` - is unset then default session of the app will be used. - * `zoomFactor` Number - The default zoom factor of the page, `3.0` represents - `300%`. Default is `1.0`. - * `javascript` Boolean - Enables JavaScript support. Default is `true`. - * `webSecurity` Boolean - When setting `false`, it will disable the - same-origin policy (Usually using testing websites by people), and set - `allowDisplayingInsecureContent` and `allowRunningInsecureContent` to - `true` if these two options are not set by user. Default is `true`. - * `allowDisplayingInsecureContent` Boolean - Allow an https page to display - content like images from http URLs. Default is `false`. - * `allowRunningInsecureContent` Boolean - Allow a https page to run - JavaScript, CSS or plugins from http URLs. Default is `false`. - * `images` Boolean - Enables image support. Default is `true`. - * `textAreasAreResizable` Boolean - Make TextArea elements resizable. Default - is `true`. - * `webgl` Boolean - Enables WebGL support. Default is `true`. - * `webaudio` Boolean - Enables WebAudio support. Default is `true`. - * `plugins` Boolean - Whether plugins should be enabled. Default is `false`. - * `experimentalFeatures` Boolean - Enables Chromium's experimental features. - Default is `false`. - * `experimentalCanvasFeatures` Boolean - Enables Chromium's experimental - canvas features. Default is `false`. - * `directWrite` Boolean - Enables DirectWrite font rendering system on - Windows. Default is `true`. - * `blinkFeatures` String - A list of feature strings separated by `,`, like - `CSSVariables,KeyboardEventKey`. The full list of supported feature strings - can be found in the [setFeatureEnabledFromString][blink-feature-string] - function. + +The `webPreferences` option is an object that can have following properties: + +* `nodeIntegration` Boolean - Whether node integration is enabled. Default + is `true`. +* `preload` String - Specifies a script that will be loaded before other + scripts run in the page. This script will always have access to node APIs + no matter whether node integration is turned on or off. The value should + be the absolute file path to the script. + When node integration is turned off, the preload script can reintroduce + Node global symbols back to the global scope. See example + [here](process.md#event-loaded). +* `partition` String - Sets the session used by the page. If `partition` + starts with `persist:`, the page will use a persistent session available to + all pages in the app with the same `partition`. if there is no `persist:` + prefix, the page will use an in-memory session. By assigning the same + `partition`, multiple pages can share the same session. If the `partition` + is unset then default session of the app will be used. +* `zoomFactor` Number - The default zoom factor of the page, `3.0` represents + `300%`. Default is `1.0`. +* `javascript` Boolean - Enables JavaScript support. Default is `true`. +* `webSecurity` Boolean - When setting `false`, it will disable the + same-origin policy (Usually using testing websites by people), and set + `allowDisplayingInsecureContent` and `allowRunningInsecureContent` to + `true` if these two options are not set by user. Default is `true`. +* `allowDisplayingInsecureContent` Boolean - Allow an https page to display + content like images from http URLs. Default is `false`. +* `allowRunningInsecureContent` Boolean - Allow a https page to run + JavaScript, CSS or plugins from http URLs. Default is `false`. +* `images` Boolean - Enables image support. Default is `true`. +* `textAreasAreResizable` Boolean - Make TextArea elements resizable. Default + is `true`. +* `webgl` Boolean - Enables WebGL support. Default is `true`. +* `webaudio` Boolean - Enables WebAudio support. Default is `true`. +* `plugins` Boolean - Whether plugins should be enabled. Default is `false`. +* `experimentalFeatures` Boolean - Enables Chromium's experimental features. + Default is `false`. +* `experimentalCanvasFeatures` Boolean - Enables Chromium's experimental + canvas features. Default is `false`. +* `directWrite` Boolean - Enables DirectWrite font rendering system on + Windows. Default is `true`. +* `blinkFeatures` String - A list of feature strings separated by `,`, like + `CSSVariables,KeyboardEventKey`. The full list of supported feature strings + can be found in the [setFeatureEnabledFromString][blink-feature-string] + function. ## Events From 10f663d0178c5a3fa51a5641d00be578b49365af Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 15:40:06 +0800 Subject: [PATCH 404/411] Fix hiding fullscreen button on EL Capitan --- atom/browser/native_window_mac.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 45123d2487..3735772fe1 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -469,12 +469,17 @@ NativeWindowMac::NativeWindowMac( [window_ setDisableAutoHideCursor:disableAutoHideCursor]; // Disable fullscreen button when 'fullscreen' is specified to false. - bool fullscreen; + bool fullscreen = false; if (!(options.Get(options::kFullscreen, &fullscreen) && !fullscreen)) { NSUInteger collectionBehavior = [window_ collectionBehavior]; collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; [window_ setCollectionBehavior:collectionBehavior]; + } else if (base::mac::IsOSElCapitanOrLater()) { + // On EL Capitan this flag is required to hide fullscreen button. + NSUInteger collectionBehavior = [window_ collectionBehavior]; + collectionBehavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; + [window_ setCollectionBehavior:collectionBehavior]; } NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); From 3a32dc5da75471aa9036b05f17fe0148f9b4c6b2 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 16:35:05 +0800 Subject: [PATCH 405/411] Fix "name" of Error object not serialized Close #3364. --- atom/browser/lib/rpc-server.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index 873349f3e9..7b05fa3d14 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -43,6 +43,8 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) -> meta.then = valueToMeta sender, value.then.bind(value) else if meta.type is 'error' meta.members = plainObjectToMeta value + # Error.name is not part of own properties. + meta.members.push {name: 'name', value: value.name} else if meta.type is 'date' meta.value = value.getTime() else From e323c6dad906e441ae087c059e9fe875994217ec Mon Sep 17 00:00:00 2001 From: foxy Date: Thu, 7 Jan 2016 22:26:21 +1100 Subject: [PATCH 406/411] update faq link --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 8708434a9e..9d2b36eb6c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,7 @@ select the tag that matches your version. There are questions that are asked quite often, check this out before creating an issue: -* [Electron FAQ](faq/api-faq.md) +* [Electron FAQ](faq/electron-faq.md) ## Guides From 04d626f0b074704784869c107f0fe8319566bb0a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 20:21:11 +0800 Subject: [PATCH 407/411] docs: Document the display object Fix #3571. --- docs/api/screen.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/api/screen.md b/docs/api/screen.md index eafb56a363..61210c9e11 100644 --- a/docs/api/screen.md +++ b/docs/api/screen.md @@ -54,6 +54,23 @@ app.on('ready', function() { }); ``` +## The `Display` object + +The `Display` object represents a physical display connected to the system. A +fake `Display` may exist on a headless system, or a `Display` may correspond to +a remote, virtual display. + +* `display` object + * `id` Integer - Unique identifier associated with the display. + * `rotation` Integer - Can be 0, 1, 2, 3, each represents screen rotation in + clock-wise degrees of 0, 90, 180, 270. + * `scaleFactor` Number - Output device's pixel scale factor. + * `touchSupport` String - Can be `available`, `unavailable`, `unknown`. + * `bounds` Object + * `size` Object + * `workArea` Object + * `workAreaSize` Object + ## Events The `screen` module emits the following events: From 3d908f1483e7034061197b614ca674ba7f000217 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 20:32:57 +0800 Subject: [PATCH 408/411] docs: Mention third party packing tools Fix #1723. --- docs/tutorial/application-distribution.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/tutorial/application-distribution.md b/docs/tutorial/application-distribution.md index d65bc08ca8..e8b944e7f6 100644 --- a/docs/tutorial/application-distribution.md +++ b/docs/tutorial/application-distribution.md @@ -118,3 +118,11 @@ a Grunt task has been created that will handle this automatically: This task will automatically handle editing the `.gyp` file, building from source, then rebuilding your app's native Node modules to match the new executable name. + +## Packaging Tools + +Apart from packaging your app manually, you can also choose to use third party +packaging tools to do the work for you: + +* [electron-packager](https://github.com/maxogden/electron-packager) +* [electron-builder](https://github.com/loopline-systems/electron-builder) From 9e254821bb36d2da0be1dc3a44285ac35dca2115 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 7 Jan 2016 21:26:08 +0800 Subject: [PATCH 409/411] docs: Mention the limitation of globalShortcut Close #3816. --- docs/api/global-shortcut.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index ab6123bd87..1ec0503bd9 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -1,6 +1,6 @@ -# global-shortcut +# globalShortcut -The `global-shortcut` module can register/unregister a global keyboard shortcut +The `globalShortcut` module can register/unregister a global keyboard shortcut with the operating system so that you can customize the operations for various shortcuts. @@ -38,7 +38,7 @@ app.on('will-quit', function() { ## Methods -The `global-shortcut` module has the following methods: +The `globalShortcut` module has the following methods: ### `globalShortcut.register(accelerator, callback)` @@ -46,17 +46,21 @@ The `global-shortcut` module has the following methods: * `callback` Function Registers a global shortcut of `accelerator`. The `callback` is called when -the registered shortcut is pressed by the user. Returns `true` if the shortcut -`accelerator` was registered, `false` otherwise. For example, the specified -`accelerator` has already been registered by another caller or other native -applications. +the registered shortcut is pressed by the user. + +When the accelerator is already taken by other applications, this call will +silently fail. This behavior is intended by operating systems, since they don't +want applications to fight for global shortcuts. ### `globalShortcut.isRegistered(accelerator)` * `accelerator` [Accelerator](accelerator.md) -Returns `true` or `false` depending on whether the shortcut `accelerator` is -registered. +Returns whether this application has registered `accelerator`. + +When the accelerator is already taken by other applications, this call will +still return `false`. This behavior is intended by operating systems, since they +don't want applications to fight for global shortcuts. ### `globalShortcut.unregister(accelerator)` From ec4c5e58fff874773e5c940d0c6f80f8ddc3d6f8 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 8 Jan 2016 12:06:06 +0800 Subject: [PATCH 410/411] Initialize resource bundle on browser process separately --- atom/app/atom_main_delegate.cc | 13 +------------ atom/app/atom_main_delegate.h | 2 -- atom/browser/atom_browser_main_parts_mac.mm | 12 +++--------- vendor/brightray | 2 +- 4 files changed, 5 insertions(+), 24 deletions(-) diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 8028313106..698da4f4de 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -18,6 +18,7 @@ #include "base/logging.h" #include "chrome/common/chrome_paths.h" #include "content/public/common/content_switches.h" +#include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" namespace atom { @@ -137,16 +138,4 @@ scoped_ptr AtomMainDelegate::CreateContentClient() { return scoped_ptr(new AtomContentClient).Pass(); } -void AtomMainDelegate::AddDataPackFromPath( - ui::ResourceBundle* bundle, const base::FilePath& pak_dir) { -#if defined(OS_WIN) - bundle->AddDataPackFromPath( - pak_dir.Append(FILE_PATH_LITERAL("ui_resources_200_percent.pak")), - ui::SCALE_FACTOR_200P); - bundle->AddDataPackFromPath( - pak_dir.Append(FILE_PATH_LITERAL("content_resources_200_percent.pak")), - ui::SCALE_FACTOR_200P); -#endif -} - } // namespace atom diff --git a/atom/app/atom_main_delegate.h b/atom/app/atom_main_delegate.h index 7bcde8125c..5f4369302f 100644 --- a/atom/app/atom_main_delegate.h +++ b/atom/app/atom_main_delegate.h @@ -25,8 +25,6 @@ class AtomMainDelegate : public brightray::MainDelegate { // brightray::MainDelegate: scoped_ptr CreateContentClient() override; - void AddDataPackFromPath( - ui::ResourceBundle* bundle, const base::FilePath& pak_dir) override; #if defined(OS_MACOSX) void OverrideChildProcessPath() override; void OverrideFrameworkBundlePath() override; diff --git a/atom/browser/atom_browser_main_parts_mac.mm b/atom/browser/atom_browser_main_parts_mac.mm index 42e3100f49..d6e83fd968 100644 --- a/atom/browser/atom_browser_main_parts_mac.mm +++ b/atom/browser/atom_browser_main_parts_mac.mm @@ -13,20 +13,14 @@ namespace atom { void AtomBrowserMainParts::PreMainMessageLoopStart() { - // Initialize locale setting. - l10n_util::OverrideLocaleWithCocoaLocale(); - // Force the NSApplication subclass to be used. - NSApplication* application = [AtomApplication sharedApplication]; + [AtomApplication sharedApplication]; + // Set our own application delegate. AtomApplicationDelegate* delegate = [[AtomApplicationDelegate alloc] init]; [NSApp setDelegate:(id)delegate]; - NSBundle* frameworkBundle = base::mac::FrameworkBundle(); - NSNib* mainNib = [[NSNib alloc] initWithNibNamed:@"MainMenu" - bundle:frameworkBundle]; - [mainNib instantiateWithOwner:application topLevelObjects:nil]; - [mainNib release]; + brightray::BrowserMainParts::PreMainMessageLoopStart(); // Prevent Cocoa from turning command-line arguments into // |-application:openFiles:|, since we already handle them directly. diff --git a/vendor/brightray b/vendor/brightray index f9c272ec86..8550f2a032 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit f9c272ec86ee83915729cf2ecdfdd5aa418b77eb +Subproject commit 8550f2a032b332d86bd8a7ec235685e22d028906 From 5514e8927666ce499248726c66a2132ff04f87a3 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 8 Jan 2016 12:38:00 +0800 Subject: [PATCH 411/411] Copy locales in Cocoa format --- atom.gyp | 11 +++++++- tools/mac/copy-locales.py | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100755 tools/mac/copy-locales.py diff --git a/atom.gyp b/atom.gyp index 83605eedbc..eb4302d74c 100644 --- a/atom.gyp +++ b/atom.gyp @@ -432,7 +432,6 @@ 'mac_bundle': 1, 'mac_bundle_resources': [ 'atom/common/resources/mac/MainMenu.xib', - '<(libchromiumcontent_dir)/locales', '<(libchromiumcontent_dir)/content_shell.pak', '<(libchromiumcontent_dir)/icudtl.dat', '<(libchromiumcontent_dir)/natives_blob.bin', @@ -501,6 +500,16 @@ 'Libraries', ], }, + { + 'postbuild_name': 'Copy locales', + 'action': [ + 'tools/mac/copy-locales.py', + '-d', + '<(libchromiumcontent_dir)/locales', + '${BUILT_PRODUCTS_DIR}/<(product_name) Framework.framework/Resources', + '<@(locales)', + ], + }, ], 'conditions': [ ['mas_build==0', { diff --git a/tools/mac/copy-locales.py b/tools/mac/copy-locales.py new file mode 100755 index 0000000000..30d4788006 --- /dev/null +++ b/tools/mac/copy-locales.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# Copyright (c) 2013 GitHub, Inc. +# Use of this source code is governed by the MIT license that can be +# found in the LICENSE file. + +import errno +import optparse +import os +import shutil +import sys + +def main(argv): + parser = optparse.OptionParser() + usage = 'usage: %s [options ...] src dest locale_list' + parser.set_usage(usage.replace('%s', '%prog')) + parser.add_option('-d', dest='dash_to_underscore', action="store_true", + default=False, + help='map "en-US" to "en" and "-" to "_" in locales') + + (options, arglist) = parser.parse_args(argv) + + if len(arglist) < 4: + print 'ERROR: need src, dest and list of locales' + return 1 + + src = arglist[1] + dest = arglist[2] + locales = arglist[3:] + + for locale in locales: + # For Cocoa to find the locale at runtime, it needs to use '_' instead + # of '-' (http://crbug.com/20441). Also, 'en-US' should be represented + # simply as 'en' (http://crbug.com/19165, http://crbug.com/25578). + dirname = locale + if options.dash_to_underscore: + if locale == 'en-US': + dirname = 'en' + else: + dirname = locale.replace('-', '_') + + dirname = os.path.join(dest, dirname + '.lproj') + safe_mkdir(dirname) + shutil.copy2(os.path.join(src, locale + '.pak'), + os.path.join(dirname, 'locale.pak')) + + +def safe_mkdir(path): + try: + os.makedirs(path) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + +if __name__ == '__main__': + sys.exit(main(sys.argv))