refactor: rename the atom directory to shell

This commit is contained in:
Samuel Attard 2019-06-19 13:43:10 -07:00 committed by Samuel Attard
parent 4575a4aae3
commit d7f07e8a80
631 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,104 @@
// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/accelerator_util.h"
#include <stdio.h>
#include <string>
#include <vector>
#include "atom/common/keyboard_util.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
namespace accelerator_util {
bool StringToAccelerator(const std::string& shortcut,
ui::Accelerator* accelerator) {
if (!base::IsStringASCII(shortcut)) {
LOG(ERROR) << "The accelerator string can only contain ASCII characters";
return false;
}
std::vector<std::string> tokens = base::SplitString(
shortcut, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// Now, parse it into an accelerator.
int modifiers = ui::EF_NONE;
ui::KeyboardCode key = ui::VKEY_UNKNOWN;
for (const auto& token : tokens) {
bool shifted = false;
ui::KeyboardCode code = atom::KeyboardCodeFromStr(token, &shifted);
if (shifted)
modifiers |= ui::EF_SHIFT_DOWN;
switch (code) {
// The token can be a modifier.
case ui::VKEY_SHIFT:
modifiers |= ui::EF_SHIFT_DOWN;
break;
case ui::VKEY_CONTROL:
modifiers |= ui::EF_CONTROL_DOWN;
break;
case ui::VKEY_MENU:
modifiers |= ui::EF_ALT_DOWN;
break;
case ui::VKEY_COMMAND:
modifiers |= ui::EF_COMMAND_DOWN;
break;
case ui::VKEY_ALTGR:
modifiers |= ui::EF_ALTGR_DOWN;
break;
// Or it is a normal key.
default:
key = code;
}
}
if (key == ui::VKEY_UNKNOWN) {
LOG(WARNING) << shortcut << " doesn't contain a valid key";
return false;
}
*accelerator = ui::Accelerator(key, modifiers);
return true;
}
void GenerateAcceleratorTable(AcceleratorTable* table,
atom::AtomMenuModel* model) {
int count = model->GetItemCount();
for (int i = 0; i < count; ++i) {
atom::AtomMenuModel::ItemType type = model->GetTypeAt(i);
if (type == atom::AtomMenuModel::TYPE_SUBMENU) {
auto* submodel = model->GetSubmenuModelAt(i);
GenerateAcceleratorTable(table, submodel);
} else {
ui::Accelerator accelerator;
if (model->ShouldRegisterAcceleratorAt(i)) {
if (model->GetAcceleratorAtWithParams(i, true, &accelerator)) {
MenuItem item = {i, model};
(*table)[accelerator] = item;
}
}
}
}
}
bool TriggerAcceleratorTableCommand(AcceleratorTable* table,
const ui::Accelerator& accelerator) {
if (base::ContainsKey(*table, accelerator)) {
const accelerator_util::MenuItem& item = (*table)[accelerator];
if (item.model->IsEnabledAt(item.position)) {
const auto event_flags =
accelerator.MaskOutKeyEventFlags(accelerator.modifiers());
item.model->ActivatedAt(item.position, event_flags);
return true;
}
}
return false;
}
} // namespace accelerator_util

View file

@ -0,0 +1,36 @@
// 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_ACCELERATOR_UTIL_H_
#define ATOM_BROWSER_UI_ACCELERATOR_UTIL_H_
#include <map>
#include <string>
#include "atom/browser/ui/atom_menu_model.h"
#include "ui/base/accelerators/accelerator.h"
namespace accelerator_util {
typedef struct {
int position;
atom::AtomMenuModel* model;
} MenuItem;
typedef std::map<ui::Accelerator, MenuItem> AcceleratorTable;
// Parse a string as an accelerator.
bool StringToAccelerator(const std::string& description,
ui::Accelerator* accelerator);
// Generate a table that contains memu model's accelerators and command ids.
void GenerateAcceleratorTable(AcceleratorTable* table,
atom::AtomMenuModel* model);
// Trigger command from the accelerators table.
bool TriggerAcceleratorTableCommand(AcceleratorTable* table,
const ui::Accelerator& accelerator);
} // namespace accelerator_util
#endif // ATOM_BROWSER_UI_ACCELERATOR_UTIL_H_

View file

@ -0,0 +1,80 @@
// 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/ui/atom_menu_model.h"
#include "base/stl_util.h"
namespace atom {
bool AtomMenuModel::Delegate::GetAcceleratorForCommandId(
int command_id,
ui::Accelerator* accelerator) const {
return GetAcceleratorForCommandIdWithParams(command_id, false, accelerator);
}
AtomMenuModel::AtomMenuModel(Delegate* delegate)
: ui::SimpleMenuModel(delegate), delegate_(delegate) {}
AtomMenuModel::~AtomMenuModel() {}
void AtomMenuModel::SetRole(int index, const base::string16& role) {
int command_id = GetCommandIdAt(index);
roles_[command_id] = role;
}
base::string16 AtomMenuModel::GetRoleAt(int index) {
int command_id = GetCommandIdAt(index);
if (base::ContainsKey(roles_, command_id))
return roles_[command_id];
else
return base::string16();
}
bool AtomMenuModel::GetAcceleratorAtWithParams(
int index,
bool use_default_accelerator,
ui::Accelerator* accelerator) const {
if (delegate_) {
return delegate_->GetAcceleratorForCommandIdWithParams(
GetCommandIdAt(index), use_default_accelerator, accelerator);
}
return false;
}
bool AtomMenuModel::ShouldRegisterAcceleratorAt(int index) const {
if (delegate_) {
return delegate_->ShouldRegisterAcceleratorForCommandId(
GetCommandIdAt(index));
}
return true;
}
bool AtomMenuModel::WorksWhenHiddenAt(int index) const {
if (delegate_) {
return delegate_->ShouldCommandIdWorkWhenHidden(GetCommandIdAt(index));
}
return true;
}
void AtomMenuModel::MenuWillClose() {
ui::SimpleMenuModel::MenuWillClose();
for (Observer& observer : observers_) {
observer.OnMenuWillClose();
}
}
void AtomMenuModel::MenuWillShow() {
ui::SimpleMenuModel::MenuWillShow();
for (Observer& observer : observers_) {
observer.OnMenuWillShow();
}
}
AtomMenuModel* AtomMenuModel::GetSubmenuModelAt(int index) {
return static_cast<AtomMenuModel*>(
ui::SimpleMenuModel::GetSubmenuModelAt(index));
}
} // namespace atom

View file

@ -0,0 +1,82 @@
// 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_UI_ATOM_MENU_MODEL_H_
#define ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_
#include <map>
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "ui/base/models/simple_menu_model.h"
namespace atom {
class AtomMenuModel : public ui::SimpleMenuModel {
public:
class Delegate : public ui::SimpleMenuModel::Delegate {
public:
~Delegate() override {}
virtual bool GetAcceleratorForCommandIdWithParams(
int command_id,
bool use_default_accelerator,
ui::Accelerator* accelerator) const = 0;
virtual bool ShouldRegisterAcceleratorForCommandId(
int command_id) const = 0;
virtual bool ShouldCommandIdWorkWhenHidden(int command_id) const = 0;
private:
// ui::SimpleMenuModel::Delegate:
bool GetAcceleratorForCommandId(
int command_id,
ui::Accelerator* accelerator) const override;
};
class Observer : public base::CheckedObserver {
public:
~Observer() override {}
// Notifies the menu will open.
virtual void OnMenuWillShow() {}
// Notifies the menu has been closed.
virtual void OnMenuWillClose() {}
};
explicit AtomMenuModel(Delegate* delegate);
~AtomMenuModel() override;
void AddObserver(Observer* obs) { observers_.AddObserver(obs); }
void RemoveObserver(Observer* obs) { observers_.RemoveObserver(obs); }
void SetRole(int index, const base::string16& role);
base::string16 GetRoleAt(int index);
bool GetAcceleratorAtWithParams(int index,
bool use_default_accelerator,
ui::Accelerator* accelerator) const;
bool ShouldRegisterAcceleratorAt(int index) const;
bool WorksWhenHiddenAt(int index) const;
// ui::SimpleMenuModel:
void MenuWillClose() override;
void MenuWillShow() override;
using SimpleMenuModel::GetSubmenuModelAt;
AtomMenuModel* GetSubmenuModelAt(int index);
private:
Delegate* delegate_; // weak ref.
std::map<int, base::string16> roles_; // command id -> role
base::ObserverList<Observer> observers_;
DISALLOW_COPY_AND_ASSIGN(AtomMenuModel);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_

View file

@ -0,0 +1,219 @@
// 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 <algorithm>
#include <utility>
#include <vector>
#include "atom/browser/native_window_views.h"
#include "atom/browser/ui/autofill_popup.h"
#include "atom/common/api/api.mojom.h"
#include "base/i18n/rtl.h"
#include "chrome/browser/ui/autofill/popup_view_common.h"
#include "electron/buildflags/buildflags.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/text_utils.h"
#if BUILDFLAG(ENABLE_OSR)
#include "atom/browser/osr/osr_render_widget_host_view.h"
#include "atom/browser/osr/osr_view_proxy.h"
#endif
namespace atom {
class PopupViewCommon : public autofill::PopupViewCommon {
public:
explicit PopupViewCommon(const gfx::Rect& window_bounds)
: window_bounds_(window_bounds) {}
gfx::Rect GetWindowBounds(gfx::NativeView container_view) override {
return window_bounds_;
}
private:
gfx::Rect window_bounds_;
};
AutofillPopup::AutofillPopup() {
bold_font_list_ = gfx::FontList().DeriveWithWeight(gfx::Font::Weight::BOLD);
smaller_font_list_ =
gfx::FontList().DeriveWithSizeDelta(kSmallerFontSizeDelta);
}
AutofillPopup::~AutofillPopup() {
Hide();
}
void AutofillPopup::CreateView(content::RenderFrameHost* frame_host,
content::RenderFrameHost* embedder_frame_host,
bool offscreen,
views::View* parent,
const gfx::RectF& r) {
Hide();
frame_host_ = frame_host;
element_bounds_ = gfx::ToEnclosedRect(r);
gfx::Vector2d height_offset(0, element_bounds_.height());
gfx::Point menu_position(element_bounds_.origin() + height_offset);
views::View::ConvertPointToScreen(parent, &menu_position);
popup_bounds_ = gfx::Rect(menu_position, element_bounds_.size());
parent_ = parent;
parent_->AddObserver(this);
view_ = new AutofillPopupView(this, parent->GetWidget());
#if BUILDFLAG(ENABLE_OSR)
if (offscreen) {
auto* rwhv = frame_host->GetView();
if (embedder_frame_host != nullptr) {
rwhv = embedder_frame_host->GetView();
}
auto* osr_rwhv = static_cast<OffScreenRenderWidgetHostView*>(rwhv);
view_->view_proxy_.reset(new OffscreenViewProxy(view_));
osr_rwhv->AddViewProxy(view_->view_proxy_.get());
}
#endif
// Do this after OSR setup, we check for view_proxy_ when showing
view_->Show();
}
void AutofillPopup::Hide() {
if (parent_) {
parent_->RemoveObserver(this);
parent_ = nullptr;
}
if (view_) {
view_->Hide();
view_ = nullptr;
}
}
void AutofillPopup::SetItems(const std::vector<base::string16>& values,
const std::vector<base::string16>& labels) {
DCHECK(view_);
values_ = values;
labels_ = labels;
UpdatePopupBounds();
view_->OnSuggestionsChanged();
if (view_) // could be hidden after the change
view_->DoUpdateBoundsAndRedrawPopup();
}
void AutofillPopup::AcceptSuggestion(int index) {
mojom::ElectronAutofillAgentAssociatedPtr autofill_agent;
frame_host_->GetRemoteAssociatedInterfaces()->GetInterface(
mojo::MakeRequest(&autofill_agent));
autofill_agent->AcceptDataListSuggestion(GetValueAt(index));
}
void AutofillPopup::UpdatePopupBounds() {
DCHECK(parent_);
gfx::Point origin(element_bounds_.origin());
views::View::ConvertPointToScreen(parent_, &origin);
gfx::Rect bounds(origin, element_bounds_.size());
gfx::Rect window_bounds = parent_->GetBoundsInScreen();
PopupViewCommon popup_view_common(window_bounds);
popup_bounds_ = popup_view_common.CalculatePopupBounds(
GetDesiredPopupWidth(), GetDesiredPopupHeight(), bounds,
gfx::NativeView(), base::i18n::IsRTL());
}
gfx::Rect AutofillPopup::popup_bounds_in_view() {
gfx::Point origin(popup_bounds_.origin());
views::View::ConvertPointFromScreen(parent_, &origin);
return gfx::Rect(origin, popup_bounds_.size());
}
void AutofillPopup::OnViewBoundsChanged(views::View* view) {
UpdatePopupBounds();
view_->DoUpdateBoundsAndRedrawPopup();
}
void AutofillPopup::OnViewIsDeleting(views::View* view) {
Hide();
}
int AutofillPopup::GetDesiredPopupHeight() {
return 2 * kPopupBorderThickness + values_.size() * kRowHeight;
}
int AutofillPopup::GetDesiredPopupWidth() {
int popup_width = element_bounds_.width();
for (size_t i = 0; i < values_.size(); ++i) {
int row_size =
kEndPadding + 2 * kPopupBorderThickness +
gfx::GetStringWidth(GetValueAt(i), GetValueFontListForRow(i)) +
gfx::GetStringWidth(GetLabelAt(i), GetLabelFontListForRow(i));
if (GetLabelAt(i).length() > 0)
row_size += kNamePadding + kEndPadding;
popup_width = std::max(popup_width, row_size);
}
return popup_width;
}
gfx::Rect AutofillPopup::GetRowBounds(int index) {
int top = kPopupBorderThickness + index * kRowHeight;
return gfx::Rect(kPopupBorderThickness, top,
popup_bounds_.width() - 2 * kPopupBorderThickness,
kRowHeight);
}
const gfx::FontList& AutofillPopup::GetValueFontListForRow(int index) const {
return bold_font_list_;
}
const gfx::FontList& AutofillPopup::GetLabelFontListForRow(int index) const {
return smaller_font_list_;
}
ui::NativeTheme::ColorId AutofillPopup::GetBackgroundColorIDForRow(
int index) const {
return (view_ && index == view_->GetSelectedLine())
? ui::NativeTheme::kColorId_ResultsTableHoveredBackground
: ui::NativeTheme::kColorId_ResultsTableNormalBackground;
}
int AutofillPopup::GetLineCount() {
return values_.size();
}
base::string16 AutofillPopup::GetValueAt(int i) {
return values_.at(i);
}
base::string16 AutofillPopup::GetLabelAt(int i) {
return labels_.at(i);
}
int AutofillPopup::LineFromY(int y) const {
int current_height = kPopupBorderThickness;
for (size_t i = 0; i < values_.size(); ++i) {
current_height += kRowHeight;
if (y <= current_height)
return i;
}
return values_.size() - 1;
}
} // namespace atom

View file

@ -0,0 +1,91 @@
// 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 ATOM_BROWSER_UI_AUTOFILL_POPUP_H_
#define ATOM_BROWSER_UI_AUTOFILL_POPUP_H_
#include <vector>
#include "atom/browser/ui/views/autofill_popup_view.h"
#include "content/public/browser/render_frame_host.h"
#include "ui/gfx/font_list.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace atom {
class AutofillPopupView;
class AutofillPopup : public views::ViewObserver {
public:
AutofillPopup();
~AutofillPopup() override;
void CreateView(content::RenderFrameHost* render_frame,
content::RenderFrameHost* embedder_frame,
bool offscreen,
views::View* parent,
const gfx::RectF& bounds);
void Hide();
void SetItems(const std::vector<base::string16>& values,
const std::vector<base::string16>& labels);
void UpdatePopupBounds();
gfx::Rect popup_bounds_in_view();
private:
friend class AutofillPopupView;
// views::ViewObserver:
void OnViewBoundsChanged(views::View* view) override;
void OnViewIsDeleting(views::View* view) override;
void AcceptSuggestion(int index);
int GetDesiredPopupHeight();
int GetDesiredPopupWidth();
gfx::Rect GetRowBounds(int i);
const gfx::FontList& GetValueFontListForRow(int index) const;
const gfx::FontList& GetLabelFontListForRow(int index) const;
ui::NativeTheme::ColorId GetBackgroundColorIDForRow(int index) const;
int GetLineCount();
base::string16 GetValueAt(int i);
base::string16 GetLabelAt(int i);
int LineFromY(int y) const;
int selected_index_;
// Popup location
gfx::Rect popup_bounds_;
// Bounds of the autofilled element
gfx::Rect element_bounds_;
// Datalist suggestions
std::vector<base::string16> values_;
std::vector<base::string16> labels_;
// Font lists for the suggestions
gfx::FontList smaller_font_list_;
gfx::FontList bold_font_list_;
// For sending the accepted suggestion to the render frame that
// asked to open the popup
content::RenderFrameHost* frame_host_ = nullptr;
// The popup view. The lifetime is managed by the owning Widget
AutofillPopupView* view_ = nullptr;
// The parent view that the popup view shows on. Weak ref.
views::View* parent_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(AutofillPopup);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_AUTOFILL_POPUP_H_

View file

@ -0,0 +1,27 @@
// Copyright (c) 2017 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_CERTIFICATE_TRUST_H_
#define ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_
#include <string>
#include "atom/common/promise_util.h"
#include "base/memory/ref_counted.h"
#include "net/cert/x509_certificate.h"
namespace atom {
class NativeWindow;
} // namespace atom
namespace certificate_trust {
v8::Local<v8::Promise> ShowCertificateTrust(
atom::NativeWindow* parent_window,
const scoped_refptr<net::X509Certificate>& cert,
const std::string& message);
} // namespace certificate_trust
#endif // ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_

View file

@ -0,0 +1,124 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/certificate_trust.h"
#include <memory>
#include <string>
#include <utility>
#import <Cocoa/Cocoa.h>
#import <SecurityInterface/SFCertificateTrustPanel.h>
#include "atom/browser/native_window.h"
#include "base/strings/sys_string_conversions.h"
#include "net/cert/cert_database.h"
#include "net/cert/x509_util_ios_and_mac.h"
#include "net/cert/x509_util_mac.h"
@interface TrustDelegate : NSObject {
@private
std::unique_ptr<atom::util::Promise> promise_;
SFCertificateTrustPanel* panel_;
scoped_refptr<net::X509Certificate> cert_;
SecTrustRef trust_;
CFArrayRef cert_chain_;
SecPolicyRef sec_policy_;
}
- (id)initWithPromise:(atom::util::Promise)promise
panel:(SFCertificateTrustPanel*)panel
cert:(const scoped_refptr<net::X509Certificate>&)cert
trust:(SecTrustRef)trust
certChain:(CFArrayRef)certChain
secPolicy:(SecPolicyRef)secPolicy;
- (void)panelDidEnd:(NSWindow*)sheet
returnCode:(int)returnCode
contextInfo:(void*)contextInfo;
@end
@implementation TrustDelegate
- (void)dealloc {
[panel_ release];
CFRelease(trust_);
CFRelease(cert_chain_);
CFRelease(sec_policy_);
[super dealloc];
}
- (id)initWithPromise:(atom::util::Promise)promise
panel:(SFCertificateTrustPanel*)panel
cert:(const scoped_refptr<net::X509Certificate>&)cert
trust:(SecTrustRef)trust
certChain:(CFArrayRef)certChain
secPolicy:(SecPolicyRef)secPolicy {
if ((self = [super init])) {
promise_.reset(new atom::util::Promise(std::move(promise)));
panel_ = panel;
cert_ = cert;
trust_ = trust;
cert_chain_ = certChain;
sec_policy_ = secPolicy;
}
return self;
}
- (void)panelDidEnd:(NSWindow*)sheet
returnCode:(int)returnCode
contextInfo:(void*)contextInfo {
auto* cert_db = net::CertDatabase::GetInstance();
// This forces Chromium to reload the certificate since it might be trusted
// now.
cert_db->NotifyObserversCertDBChanged();
promise_->Resolve();
[self autorelease];
}
@end
namespace certificate_trust {
v8::Local<v8::Promise> ShowCertificateTrust(
atom::NativeWindow* parent_window,
const scoped_refptr<net::X509Certificate>& cert,
const std::string& message) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
atom::util::Promise promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
auto* sec_policy = SecPolicyCreateBasicX509();
auto cert_chain =
net::x509_util::CreateSecCertificateArrayForX509Certificate(cert.get());
SecTrustRef trust = nullptr;
SecTrustCreateWithCertificates(cert_chain, sec_policy, &trust);
NSWindow* window = parent_window
? parent_window->GetNativeWindow().GetNativeNSWindow()
: nil;
auto msg = base::SysUTF8ToNSString(message);
auto panel = [[SFCertificateTrustPanel alloc] init];
auto delegate = [[TrustDelegate alloc] initWithPromise:std::move(promise)
panel:panel
cert:cert
trust:trust
certChain:cert_chain
secPolicy:sec_policy];
[panel beginSheetForWindow:window
modalDelegate:delegate
didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:)
contextInfo:nil
trust:trust
message:msg];
return handle;
}
} // namespace certificate_trust

View file

@ -0,0 +1,87 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/certificate_trust.h"
#include <windows.h> // windows.h must be included first
#include <wincrypt.h>
#include "net/cert/cert_database.h"
#include "net/cert/x509_util_win.h"
namespace certificate_trust {
// Add the provided certificate to the Trusted Root Certificate Authorities
// store for the current user.
//
// This requires prompting the user to confirm they trust the certificate.
BOOL AddToTrustedRootStore(const PCCERT_CONTEXT cert_context,
const scoped_refptr<net::X509Certificate>& cert) {
auto* root_cert_store = CertOpenStore(
CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"Root");
if (root_cert_store == NULL) {
return false;
}
auto result = CertAddCertificateContextToStore(
root_cert_store, cert_context, CERT_STORE_ADD_REPLACE_EXISTING, NULL);
if (result) {
// force Chromium to reload it's database for this certificate
auto* cert_db = net::CertDatabase::GetInstance();
cert_db->NotifyObserversCertDBChanged();
}
CertCloseStore(root_cert_store, CERT_CLOSE_STORE_FORCE_FLAG);
return result;
}
CERT_CHAIN_PARA GetCertificateChainParameters() {
CERT_ENHKEY_USAGE enhkey_usage;
enhkey_usage.cUsageIdentifier = 0;
enhkey_usage.rgpszUsageIdentifier = NULL;
CERT_USAGE_MATCH cert_usage;
// ensure the rules are applied to the entire chain
cert_usage.dwType = USAGE_MATCH_TYPE_AND;
cert_usage.Usage = enhkey_usage;
CERT_CHAIN_PARA params = {sizeof(CERT_CHAIN_PARA)};
params.RequestedUsage = cert_usage;
return params;
}
v8::Local<v8::Promise> ShowCertificateTrust(
atom::NativeWindow* parent_window,
const scoped_refptr<net::X509Certificate>& cert,
const std::string& message) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
atom::util::Promise promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
PCCERT_CHAIN_CONTEXT chain_context;
auto cert_context = net::x509_util::CreateCertContextWithChain(cert.get());
auto params = GetCertificateChainParameters();
if (CertGetCertificateChain(NULL, cert_context.get(), NULL, NULL, &params,
NULL, NULL, &chain_context)) {
auto error_status = chain_context->TrustStatus.dwErrorStatus;
if (error_status == CERT_TRUST_IS_SELF_SIGNED ||
error_status == CERT_TRUST_IS_UNTRUSTED_ROOT) {
// these are the only scenarios we're interested in supporting
AddToTrustedRootStore(cert_context.get(), cert);
}
CertFreeCertificateChain(chain_context);
}
promise.Resolve();
return handle;
}
} // namespace certificate_trust

View file

@ -0,0 +1,18 @@
// Created by Mathias Leppich on 03/02/14.
// Copyright (c) 2014 Bit Bar. All rights reserved.
// Copyright (c) 2017 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_NSCOLOR_HEX_H_
#define ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_
#import <Cocoa/Cocoa.h>
@interface NSColor (Hex)
- (NSString*)hexadecimalValue;
- (NSString*)RGBAValue;
+ (NSColor*)colorWithHexColorString:(NSString*)hex;
@end
#endif // ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_

View file

@ -0,0 +1,77 @@
// Created by Mathias Leppich on 03/02/14.
// Copyright (c) 2014 Bit Bar. All rights reserved.
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/NSColor+Hex.h"
@implementation NSColor (Hex)
- (NSString*)RGBAValue {
double redFloatValue, greenFloatValue, blueFloatValue, alphaFloatValue;
NSColor* convertedColor =
[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
if (convertedColor) {
[convertedColor getRed:&redFloatValue
green:&greenFloatValue
blue:&blueFloatValue
alpha:&alphaFloatValue];
int redIntValue = redFloatValue * 255.99999f;
int greenIntValue = greenFloatValue * 255.99999f;
int blueIntValue = blueFloatValue * 255.99999f;
int alphaIntValue = alphaFloatValue * 255.99999f;
return
[NSString stringWithFormat:@"%02x%02x%02x%02x", redIntValue,
greenIntValue, blueIntValue, alphaIntValue];
}
return nil;
}
- (NSString*)hexadecimalValue {
double redFloatValue, greenFloatValue, blueFloatValue;
NSColor* convertedColor =
[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
if (convertedColor) {
[convertedColor getRed:&redFloatValue
green:&greenFloatValue
blue:&blueFloatValue
alpha:NULL];
int redIntValue = redFloatValue * 255.99999f;
int greenIntValue = greenFloatValue * 255.99999f;
int blueIntValue = blueFloatValue * 255.99999f;
return [NSString stringWithFormat:@"#%02x%02x%02x", redIntValue,
greenIntValue, blueIntValue];
}
return nil;
}
+ (NSColor*)colorWithHexColorString:(NSString*)inColorString {
unsigned colorCode = 0;
if (inColorString) {
NSScanner* scanner = [NSScanner scannerWithString:inColorString];
(void)[scanner scanHexInt:&colorCode]; // ignore error
}
unsigned char redByte = (unsigned char)(colorCode >> 16);
unsigned char greenByte = (unsigned char)(colorCode >> 8);
unsigned char blueByte = (unsigned char)(colorCode); // masks off high bits
return [NSColor colorWithCalibratedRed:(CGFloat)redByte / 0xff
green:(CGFloat)greenByte / 0xff
blue:(CGFloat)blueByte / 0xff
alpha:1.0];
}
@end

View file

@ -0,0 +1,17 @@
// Created by Kent Karlsson on 3/11/16.
// Copyright (c) 2016 Bit Bar. All rights reserved.
// Copyright (c) 2017 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_NSSTRING_ANSI_H_
#define ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_
#import <Foundation/Foundation.h>
@interface NSString (ANSI)
- (BOOL)containsANSICodes;
- (NSMutableAttributedString*)attributedStringParsingANSICodes;
@end
#endif // ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_

View file

@ -0,0 +1,166 @@
// Created by Kent Karlsson on 3/11/16.
// Copyright (c) 2016 Bit Bar. All rights reserved.
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/NSColor+Hex.h"
#include "atom/browser/ui/cocoa/NSString+ANSI.h"
#include "base/mac/scoped_nsobject.h"
@implementation NSMutableDictionary (ANSI)
- (NSMutableDictionary*)modifyAttributesForANSICodes:(NSString*)codes {
BOOL bold = NO;
NSFont* font = self[NSFontAttributeName];
NSArray* codeArray = [codes componentsSeparatedByString:@";"];
for (NSString* codeString in codeArray) {
int code = codeString.intValue;
switch (code) {
case 0:
[self removeAllObjects];
// remove italic and bold from font here
if (font)
self[NSFontAttributeName] = font;
break;
case 1:
case 22:
bold = (code == 1);
break;
// case 3: italic
// case 23: italic off
// case 4: underlined
// case 24: underlined off
case 30:
self[NSForegroundColorAttributeName] =
[NSColor colorWithHexColorString:bold ? @"7f7f7f" : @"000000"];
break;
case 31:
self[NSForegroundColorAttributeName] =
[NSColor colorWithHexColorString:bold ? @"cd0000" : @"ff0000"];
break;
case 32:
self[NSForegroundColorAttributeName] =
[NSColor colorWithHexColorString:bold ? @"00cd00" : @"00ff00"];
break;
case 33:
self[NSForegroundColorAttributeName] =
[NSColor colorWithHexColorString:bold ? @"cdcd00" : @"ffff00"];
break;
case 34:
self[NSForegroundColorAttributeName] =
[NSColor colorWithHexColorString:bold ? @"0000ee" : @"5c5cff"];
break;
case 35:
self[NSForegroundColorAttributeName] =
[NSColor colorWithHexColorString:bold ? @"cd00cd" : @"ff00ff"];
break;
case 36:
self[NSForegroundColorAttributeName] =
[NSColor colorWithHexColorString:bold ? @"00cdcd" : @"00ffff"];
break;
case 37:
self[NSForegroundColorAttributeName] =
[NSColor colorWithHexColorString:bold ? @"e5e5e5" : @"ffffff"];
break;
case 39:
[self removeObjectForKey:NSForegroundColorAttributeName];
break;
case 40:
self[NSBackgroundColorAttributeName] =
[NSColor colorWithHexColorString:@"7f7f7f"];
break;
case 41:
self[NSBackgroundColorAttributeName] =
[NSColor colorWithHexColorString:@"cd0000"];
break;
case 42:
self[NSBackgroundColorAttributeName] =
[NSColor colorWithHexColorString:@"00cd00"];
break;
case 43:
self[NSBackgroundColorAttributeName] =
[NSColor colorWithHexColorString:@"cdcd00"];
break;
case 44:
self[NSBackgroundColorAttributeName] =
[NSColor colorWithHexColorString:@"0000ee"];
break;
case 45:
self[NSBackgroundColorAttributeName] =
[NSColor colorWithHexColorString:@"cd00cd"];
break;
case 46:
self[NSBackgroundColorAttributeName] =
[NSColor colorWithHexColorString:@"00cdcd"];
break;
case 47:
self[NSBackgroundColorAttributeName] =
[NSColor colorWithHexColorString:@"e5e5e5"];
break;
case 49:
[self removeObjectForKey:NSBackgroundColorAttributeName];
break;
default:
break;
}
}
return self;
}
@end
@implementation NSString (ANSI)
- (BOOL)containsANSICodes {
return [self rangeOfString:@"\033["].location != NSNotFound;
}
- (NSMutableAttributedString*)attributedStringParsingANSICodes {
NSMutableAttributedString* result = [[NSMutableAttributedString alloc] init];
base::scoped_nsobject<NSMutableDictionary> attributes(
[[NSMutableDictionary alloc] init]);
NSArray* parts = [self componentsSeparatedByString:@"\033["];
[result appendAttributedString:[[[NSAttributedString alloc]
initWithString:parts.firstObject
attributes:nil] autorelease]];
for (NSString* part in
[parts subarrayWithRange:NSMakeRange(1, parts.count - 1)]) {
if (part.length == 0)
continue;
NSArray* sequence = [part componentsSeparatedByString:@"m"];
NSString* text = sequence.lastObject;
if (sequence.count < 2) {
[result
appendAttributedString:[[[NSAttributedString alloc]
initWithString:text
attributes:attributes] autorelease]];
} else if (sequence.count >= 2) {
text = [[sequence subarrayWithRange:NSMakeRange(1, sequence.count - 1)]
componentsJoinedByString:@"m"];
[attributes modifyAttributesForANSICodes:sequence[0]];
[result
appendAttributedString:[[[NSAttributedString alloc]
initWithString:text
attributes:attributes] autorelease]];
}
}
return result;
}
@end

View file

@ -0,0 +1,43 @@
// Copyright (c) 2017 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_ATOM_BUNDLE_MOVER_H_
#define ATOM_BROWSER_UI_COCOA_ATOM_BUNDLE_MOVER_H_
#include <string>
#include "native_mate/persistent_dictionary.h"
namespace atom {
namespace ui {
namespace cocoa {
class AtomBundleMover {
public:
static bool Move(mate::Arguments* args);
static bool IsCurrentAppInApplicationsFolder();
private:
static bool IsInApplicationsFolder(NSString* bundlePath);
static NSString* ContainingDiskImageDevice(NSString* bundlePath);
static void Relaunch(NSString* destinationPath);
static NSString* ShellQuotedString(NSString* string);
static bool CopyBundle(NSString* srcPath, NSString* dstPath);
static bool AuthorizedInstall(NSString* srcPath,
NSString* dstPath,
bool* canceled);
static bool IsApplicationAtPathRunning(NSString* bundlePath);
static bool DeleteOrTrash(NSString* path);
static bool Trash(NSString* path);
};
} // namespace cocoa
} // namespace ui
} // namespace atom
#endif // ATOM_BROWSER_UI_COCOA_ATOM_BUNDLE_MOVER_H_

View file

@ -0,0 +1,425 @@
// Copyright (c) 2017 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/atom_bundle_mover.h"
#include <string>
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <Security/Security.h>
#import <dlfcn.h>
#import <sys/mount.h>
#import <sys/param.h>
#import "atom/browser/browser.h"
namespace atom {
namespace ui {
namespace cocoa {
bool AtomBundleMover::Move(mate::Arguments* args) {
// Path of the current bundle
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
// Skip if the application is already in the Applications folder
if (IsInApplicationsFolder(bundlePath))
return true;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSString* diskImageDevice = ContainingDiskImageDevice(bundlePath);
NSString* applicationsDirectory = [[NSSearchPathForDirectoriesInDomains(
NSApplicationDirectory, NSLocalDomainMask, true) lastObject]
stringByResolvingSymlinksInPath];
NSString* bundleName = [bundlePath lastPathComponent];
NSString* destinationPath =
[applicationsDirectory stringByAppendingPathComponent:bundleName];
// Check if we can write to the applications directory
// and then make sure that if the app already exists we can overwrite it
bool needAuthorization =
![fileManager isWritableFileAtPath:applicationsDirectory] |
([fileManager fileExistsAtPath:destinationPath] &&
![fileManager isWritableFileAtPath:destinationPath]);
// Activate app -- work-around for focus issues related to "scary file from
// internet" OS dialog.
if (![NSApp isActive]) {
[NSApp activateIgnoringOtherApps:true];
}
// Move to applications folder
if (needAuthorization) {
bool authorizationCanceled;
if (!AuthorizedInstall(bundlePath, destinationPath,
&authorizationCanceled)) {
if (authorizationCanceled) {
// User rejected the authorization request
args->ThrowError("User rejected the authorization request");
return false;
} else {
args->ThrowError(
"Failed to copy to applications directory even with authorization");
return false;
}
}
} else {
// If a copy already exists in the Applications folder, put it in the Trash
if ([fileManager fileExistsAtPath:destinationPath]) {
// But first, make sure that it's not running
if (IsApplicationAtPathRunning(destinationPath)) {
// Give the running app focus and terminate myself
[[NSTask
launchedTaskWithLaunchPath:@"/usr/bin/open"
arguments:[NSArray
arrayWithObject:destinationPath]]
waitUntilExit];
atom::Browser::Get()->Quit();
return true;
} else {
if (!Trash([applicationsDirectory
stringByAppendingPathComponent:bundleName])) {
args->ThrowError("Failed to delete existing application");
return false;
}
}
}
if (!CopyBundle(bundlePath, destinationPath)) {
args->ThrowError(
"Failed to copy current bundle to the applications folder");
return false;
}
}
// Trash the original app. It's okay if this fails.
// NOTE: This final delete does not work if the source bundle is in a network
// mounted volume.
// Calling rm or file manager's delete method doesn't work either. It's
// unlikely to happen but it'd be great if someone could fix this.
if (diskImageDevice == nil && !DeleteOrTrash(bundlePath)) {
// Could not delete original but we just don't care
}
// Relaunch.
Relaunch(destinationPath);
// Launched from within a disk image? -- unmount (if no files are open after 5
// seconds, otherwise leave it mounted).
if (diskImageDevice) {
NSString* script = [NSString
stringWithFormat:@"(/bin/sleep 5 && /usr/bin/hdiutil detach %@) &",
ShellQuotedString(diskImageDevice)];
[NSTask launchedTaskWithLaunchPath:@"/bin/sh"
arguments:[NSArray arrayWithObjects:@"-c", script,
nil]];
}
atom::Browser::Get()->Quit();
return true;
}
bool AtomBundleMover::IsCurrentAppInApplicationsFolder() {
return IsInApplicationsFolder([[NSBundle mainBundle] bundlePath]);
}
bool AtomBundleMover::IsInApplicationsFolder(NSString* bundlePath) {
// Check all the normal Application directories
NSArray* applicationDirs = NSSearchPathForDirectoriesInDomains(
NSApplicationDirectory, NSAllDomainsMask, true);
for (NSString* appDir in applicationDirs) {
if ([bundlePath hasPrefix:appDir])
return true;
}
// Also, handle the case that the user has some other Application directory
// (perhaps on a separate data partition).
if ([[bundlePath pathComponents] containsObject:@"Applications"])
return true;
return false;
}
NSString* AtomBundleMover::ContainingDiskImageDevice(NSString* bundlePath) {
NSString* containingPath = [bundlePath stringByDeletingLastPathComponent];
struct statfs fs;
if (statfs([containingPath fileSystemRepresentation], &fs) ||
(fs.f_flags & MNT_ROOTFS))
return nil;
NSString* device = [[NSFileManager defaultManager]
stringWithFileSystemRepresentation:fs.f_mntfromname
length:strlen(fs.f_mntfromname)];
NSTask* hdiutil = [[[NSTask alloc] init] autorelease];
[hdiutil setLaunchPath:@"/usr/bin/hdiutil"];
[hdiutil setArguments:[NSArray arrayWithObjects:@"info", @"-plist", nil]];
[hdiutil setStandardOutput:[NSPipe pipe]];
[hdiutil launch];
[hdiutil waitUntilExit];
NSData* data =
[[[hdiutil standardOutput] fileHandleForReading] readDataToEndOfFile];
NSDictionary* info =
[NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListImmutable
format:NULL
error:NULL];
if (![info isKindOfClass:[NSDictionary class]])
return nil;
NSArray* images = (NSArray*)[info objectForKey:@"images"];
if (![images isKindOfClass:[NSArray class]])
return nil;
for (NSDictionary* image in images) {
if (![image isKindOfClass:[NSDictionary class]])
return nil;
id systemEntities = [image objectForKey:@"system-entities"];
if (![systemEntities isKindOfClass:[NSArray class]])
return nil;
for (NSDictionary* systemEntity in systemEntities) {
if (![systemEntity isKindOfClass:[NSDictionary class]])
return nil;
NSString* devEntry = [systemEntity objectForKey:@"dev-entry"];
if (![devEntry isKindOfClass:[NSString class]])
return nil;
if ([devEntry isEqualToString:device])
return device;
}
}
return nil;
}
bool AtomBundleMover::AuthorizedInstall(NSString* srcPath,
NSString* dstPath,
bool* canceled) {
if (canceled)
*canceled = false;
// Make sure that the destination path is an app bundle. We're essentially
// running 'sudo rm -rf' so we really don't want to screw this up.
if (![[dstPath pathExtension] isEqualToString:@"app"])
return false;
// Do some more checks
if ([[dstPath stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceCharacterSet]]
length] == 0)
return false;
if ([[srcPath stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceCharacterSet]]
length] == 0)
return false;
int pid, status;
AuthorizationRef myAuthorizationRef;
// Get the authorization
OSStatus err =
AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
kAuthorizationFlagDefaults, &myAuthorizationRef);
if (err != errAuthorizationSuccess)
return false;
AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights myRights = {1, &myItems};
AuthorizationFlags myFlags = (AuthorizationFlags)(
kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights |
kAuthorizationFlagPreAuthorize);
err = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags,
NULL);
if (err != errAuthorizationSuccess) {
if (err == errAuthorizationCanceled && canceled)
*canceled = true;
goto fail;
}
static OSStatus (*security_AuthorizationExecuteWithPrivileges)(
AuthorizationRef authorization, const char* pathToTool,
AuthorizationFlags options, char* const* arguments,
FILE** communicationsPipe) = NULL;
if (!security_AuthorizationExecuteWithPrivileges) {
// On 10.7, AuthorizationExecuteWithPrivileges is deprecated. We want to
// still use it since there's no good alternative (without requiring code
// signing). We'll look up the function through dyld and fail if it is no
// longer accessible. If Apple removes the function entirely this will fail
// gracefully. If they keep the function and throw some sort of exception,
// this won't fail gracefully, but that's a risk we'll have to take for now.
security_AuthorizationExecuteWithPrivileges = (OSStatus(*)(
AuthorizationRef, const char*, AuthorizationFlags, char* const*,
FILE**))dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges");
}
if (!security_AuthorizationExecuteWithPrivileges)
goto fail;
// Delete the destination
{
char rf[] = "-rf";
char* args[] = {rf, (char*)[dstPath fileSystemRepresentation], NULL};
err = security_AuthorizationExecuteWithPrivileges(
myAuthorizationRef, "/bin/rm", kAuthorizationFlagDefaults, args, NULL);
if (err != errAuthorizationSuccess)
goto fail;
// Wait until it's done
pid = wait(&status);
if (pid == -1 || !WIFEXITED(status))
goto fail; // We don't care about exit status as the destination most
// likely does not exist
}
// Copy
{
char pR[] = "-pR";
char* args[] = {pR, (char*)[srcPath fileSystemRepresentation],
(char*)[dstPath fileSystemRepresentation], NULL};
err = security_AuthorizationExecuteWithPrivileges(
myAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, args, NULL);
if (err != errAuthorizationSuccess)
goto fail;
// Wait until it's done
pid = wait(&status);
if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status))
goto fail;
}
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
return true;
fail:
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
return false;
}
bool AtomBundleMover::CopyBundle(NSString* srcPath, NSString* dstPath) {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSError* error = nil;
if ([fileManager copyItemAtPath:srcPath toPath:dstPath error:&error]) {
return true;
} else {
return false;
}
}
NSString* AtomBundleMover::ShellQuotedString(NSString* string) {
return [NSString
stringWithFormat:@"'%@'",
[string stringByReplacingOccurrencesOfString:@"'"
withString:@"'\\''"]];
}
void AtomBundleMover::Relaunch(NSString* destinationPath) {
// The shell script waits until the original app process terminates.
// This is done so that the relaunched app opens as the front-most app.
int pid = [[NSProcessInfo processInfo] processIdentifier];
// Command run just before running open /final/path
NSString* preOpenCmd = @"";
NSString* quotedDestinationPath = ShellQuotedString(destinationPath);
// Before we launch the new app, clear xattr:com.apple.quarantine to avoid
// duplicate "scary file from the internet" dialog.
preOpenCmd = [NSString
stringWithFormat:@"/usr/bin/xattr -d -r com.apple.quarantine %@",
quotedDestinationPath];
NSString* script =
[NSString stringWithFormat:
@"(while /bin/kill -0 %d >&/dev/null; do /bin/sleep 0.1; "
@"done; %@; /usr/bin/open %@) &",
pid, preOpenCmd, quotedDestinationPath];
[NSTask
launchedTaskWithLaunchPath:@"/bin/sh"
arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
}
bool AtomBundleMover::IsApplicationAtPathRunning(NSString* bundlePath) {
bundlePath = [bundlePath stringByStandardizingPath];
for (NSRunningApplication* runningApplication in
[[NSWorkspace sharedWorkspace] runningApplications]) {
NSString* runningAppBundlePath =
[[[runningApplication bundleURL] path] stringByStandardizingPath];
if ([runningAppBundlePath isEqualToString:bundlePath]) {
return true;
}
}
return false;
}
bool AtomBundleMover::Trash(NSString* path) {
bool result = false;
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_8) {
result = [[NSFileManager defaultManager]
trashItemAtURL:[NSURL fileURLWithPath:path]
resultingItemURL:NULL
error:NULL];
}
if (!result) {
result = [[NSWorkspace sharedWorkspace]
performFileOperation:NSWorkspaceRecycleOperation
source:[path stringByDeletingLastPathComponent]
destination:@""
files:[NSArray arrayWithObject:[path lastPathComponent]]
tag:NULL];
}
// As a last resort try trashing with AppleScript.
// This allows us to trash the app in macOS Sierra even when the app is
// running inside an app translocation image.
if (!result) {
auto* code = R"str(
set theFile to POSIX file "%@"
tell application "Finder"
move theFile to trash
end tell
)str";
NSAppleScript* appleScript = [[[NSAppleScript alloc]
initWithSource:[NSString stringWithFormat:@(code), path]] autorelease];
NSDictionary* errorDict = nil;
NSAppleEventDescriptor* scriptResult =
[appleScript executeAndReturnError:&errorDict];
result = (scriptResult != nil);
}
return result;
}
bool AtomBundleMover::DeleteOrTrash(NSString* path) {
NSError* error;
if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
return true;
} else {
return Trash(path);
}
}
} // namespace cocoa
} // namespace ui
} // namespace atom

View file

@ -0,0 +1,47 @@
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_
#define ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_
#import <AppKit/AppKit.h>
#include "base/mac/scoped_nsobject.h"
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
#include "ui/base/cocoa/base_view.h"
namespace atom {
class InspectableWebContentsViewMac;
}
using atom::InspectableWebContentsViewMac;
@interface AtomInspectableWebContentsView : BaseView <NSWindowDelegate> {
@private
atom::InspectableWebContentsViewMac* inspectableWebContentsView_;
base::scoped_nsobject<NSView> fake_view_;
base::scoped_nsobject<NSWindow> devtools_window_;
BOOL devtools_visible_;
BOOL devtools_docked_;
BOOL devtools_is_first_responder_;
DevToolsContentsResizingStrategy strategy_;
}
- (instancetype)initWithInspectableWebContentsViewMac:
(InspectableWebContentsViewMac*)view;
- (void)removeObservers;
- (void)notifyDevToolsFocused;
- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate;
- (BOOL)isDevToolsVisible;
- (BOOL)isDevToolsFocused;
- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate;
- (void)setContentsResizingStrategy:
(const DevToolsContentsResizingStrategy&)strategy;
- (void)setTitle:(NSString*)title;
@end
#endif // ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_

View file

@ -0,0 +1,269 @@
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#include "atom/browser/ui/cocoa/atom_inspectable_web_contents_view.h"
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
#include "atom/browser/ui/inspectable_web_contents_impl.h"
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
#include "atom/browser/ui/inspectable_web_contents_view_mac.h"
#include "content/public/browser/render_widget_host_view.h"
#include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h"
@implementation AtomInspectableWebContentsView
- (instancetype)initWithInspectableWebContentsViewMac:
(InspectableWebContentsViewMac*)view {
self = [super init];
if (!self)
return nil;
inspectableWebContentsView_ = view;
devtools_visible_ = NO;
devtools_docked_ = NO;
devtools_is_first_responder_ = NO;
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(viewDidBecomeFirstResponder:)
name:kViewDidBecomeFirstResponder
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(parentWindowBecameMain:)
name:NSWindowDidBecomeMainNotification
object:nil];
if (inspectableWebContentsView_->inspectable_web_contents()->IsGuest()) {
fake_view_.reset([[NSView alloc] init]);
[fake_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[self addSubview:fake_view_];
} else {
auto* contents = inspectableWebContentsView_->inspectable_web_contents()
->GetWebContents();
auto* contentsView = contents->GetNativeView().GetNativeNSView();
[contentsView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[self addSubview:contentsView];
}
// See https://code.google.com/p/chromium/issues/detail?id=348490.
[self setWantsLayer:YES];
return self;
}
- (void)removeObservers {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
[self adjustSubviews];
}
- (IBAction)showDevTools:(id)sender {
inspectableWebContentsView_->inspectable_web_contents()->ShowDevTools(true);
}
- (void)notifyDevToolsFocused {
if (inspectableWebContentsView_->GetDelegate())
inspectableWebContentsView_->GetDelegate()->DevToolsFocused();
}
- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate {
if (visible == devtools_visible_)
return;
auto* inspectable_web_contents =
inspectableWebContentsView_->inspectable_web_contents();
auto* devToolsWebContents =
inspectable_web_contents->GetDevToolsWebContents();
auto* devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
devtools_visible_ = visible;
if (devtools_docked_) {
if (visible) {
// Place the devToolsView under contentsView, notice that we didn't set
// sizes for them until the setContentsResizingStrategy message.
[self addSubview:devToolsView positioned:NSWindowBelow relativeTo:nil];
[self adjustSubviews];
// Focus on web view.
devToolsWebContents->RestoreFocus();
} else {
gfx::ScopedCocoaDisableScreenUpdates disabler;
[devToolsView removeFromSuperview];
[self adjustSubviews];
}
} else {
if (visible) {
if (activate) {
[devtools_window_ makeKeyAndOrderFront:nil];
} else {
[devtools_window_ orderBack:nil];
}
} else {
[devtools_window_ setDelegate:nil];
[devtools_window_ close];
devtools_window_.reset();
}
}
}
- (BOOL)isDevToolsVisible {
return devtools_visible_;
}
- (BOOL)isDevToolsFocused {
if (devtools_docked_) {
return [[self window] isKeyWindow] && devtools_is_first_responder_;
} else {
return [devtools_window_ isKeyWindow];
}
}
- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate {
// Revert to no-devtools state.
[self setDevToolsVisible:NO activate:NO];
// Switch to new state.
devtools_docked_ = docked;
if (!docked) {
auto* inspectable_web_contents =
inspectableWebContentsView_->inspectable_web_contents();
auto* devToolsWebContents =
inspectable_web_contents->GetDevToolsWebContents();
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
auto styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSMiniaturizableWindowMask | NSWindowStyleMaskResizable |
NSTexturedBackgroundWindowMask |
NSWindowStyleMaskUnifiedTitleAndToolbar;
devtools_window_.reset([[EventDispatchingWindow alloc]
initWithContentRect:NSMakeRect(0, 0, 800, 600)
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:YES]);
[devtools_window_ setDelegate:self];
[devtools_window_ setFrameAutosaveName:@"electron.devtools"];
[devtools_window_ setTitle:@"Developer Tools"];
[devtools_window_ setReleasedWhenClosed:NO];
[devtools_window_ setAutorecalculatesContentBorderThickness:NO
forEdge:NSMaxYEdge];
[devtools_window_ setContentBorderThickness:24 forEdge:NSMaxYEdge];
NSView* contentView = [devtools_window_ contentView];
devToolsView.frame = contentView.bounds;
devToolsView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[contentView addSubview:devToolsView];
}
[self setDevToolsVisible:YES activate:activate];
}
- (void)setContentsResizingStrategy:
(const DevToolsContentsResizingStrategy&)strategy {
strategy_.CopyFrom(strategy);
[self adjustSubviews];
}
- (void)adjustSubviews {
if (![[self subviews] count])
return;
if (![self isDevToolsVisible] || devtools_window_) {
DCHECK_EQ(1u, [[self subviews] count]);
NSView* contents = [[self subviews] objectAtIndex:0];
[contents setFrame:[self bounds]];
return;
}
NSView* devToolsView = [[self subviews] objectAtIndex:0];
NSView* contentsView = [[self subviews] objectAtIndex:1];
DCHECK_EQ(2u, [[self subviews] count]);
gfx::Rect new_devtools_bounds;
gfx::Rect new_contents_bounds;
ApplyDevToolsContentsResizingStrategy(
strategy_, gfx::Size(NSSizeToCGSize([self bounds].size)),
&new_devtools_bounds, &new_contents_bounds);
[devToolsView setFrame:[self flipRectToNSRect:new_devtools_bounds]];
[contentsView setFrame:[self flipRectToNSRect:new_contents_bounds]];
}
- (void)setTitle:(NSString*)title {
[devtools_window_ setTitle:title];
}
- (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
auto* inspectable_web_contents =
inspectableWebContentsView_->inspectable_web_contents();
if (!inspectable_web_contents || inspectable_web_contents->IsGuest())
return;
auto* webContents = inspectable_web_contents->GetWebContents();
auto* webContentsView = webContents->GetNativeView().GetNativeNSView();
NSView* view = [notification object];
if ([[webContentsView subviews] containsObject:view]) {
devtools_is_first_responder_ = NO;
return;
}
auto* devToolsWebContents =
inspectable_web_contents->GetDevToolsWebContents();
if (!devToolsWebContents)
return;
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
if ([[devToolsView subviews] containsObject:view]) {
devtools_is_first_responder_ = YES;
[self notifyDevToolsFocused];
}
}
- (void)parentWindowBecameMain:(NSNotification*)notification {
NSWindow* parentWindow = [notification object];
if ([self window] == parentWindow && devtools_docked_ &&
devtools_is_first_responder_)
[self notifyDevToolsFocused];
}
#pragma mark - NSWindowDelegate
- (void)windowWillClose:(NSNotification*)notification {
inspectableWebContentsView_->inspectable_web_contents()->CloseDevTools();
}
- (void)windowDidBecomeMain:(NSNotification*)notification {
content::WebContents* web_contents =
inspectableWebContentsView_->inspectable_web_contents()
->GetDevToolsWebContents();
if (!web_contents)
return;
web_contents->RestoreFocus();
content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetActive(true);
[self notifyDevToolsFocused];
}
- (void)windowDidResignMain:(NSNotification*)notification {
content::WebContents* web_contents =
inspectableWebContentsView_->inspectable_web_contents()
->GetDevToolsWebContents();
if (!web_contents)
return;
web_contents->StoreFocus();
content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetActive(false);
}
@end

View file

@ -0,0 +1,62 @@
// Copyright (c) 2013 GitHub, Inc.
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
#define ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
#import <Cocoa/Cocoa.h>
#include "base/callback.h"
#include "base/mac/scoped_nsobject.h"
#include "base/strings/string16.h"
namespace atom {
class AtomMenuModel;
}
// A controller for the cross-platform menu model. The menu that's created
// has the tag and represented object set for each menu item. The object is a
// NSValue holding a pointer to the model for that level of the menu (to
// allow for hierarchical menus). The tag is the index into that model for
// that particular item. It is important that the model outlives this object
// as it only maintains weak references.
@interface AtomMenuController : NSObject <NSMenuDelegate> {
@protected
atom::AtomMenuModel* model_; // weak
base::scoped_nsobject<NSMenu> menu_;
BOOL isMenuOpen_;
BOOL useDefaultAccelerator_;
base::Callback<void()> closeCallback;
}
@property(nonatomic, assign) atom::AtomMenuModel* model;
// Builds a NSMenu from the pre-built model (must not be nil). Changes made
// to the contents of the model after calling this will not be noticed.
- (id)initWithModel:(atom::AtomMenuModel*)model useDefaultAccelerator:(BOOL)use;
- (void)setCloseCallback:(const base::Callback<void()>&)callback;
// Populate current NSMenu with |model|.
- (void)populateWithModel:(atom::AtomMenuModel*)model;
// Programmatically close the constructed menu.
- (void)cancel;
// Access to the constructed menu if the complex initializer was used. If the
// default initializer was used, then this will create the menu on first call.
- (NSMenu*)menu;
// Whether the menu is currently open.
- (BOOL)isMenuOpen;
// NSMenuDelegate methods this class implements. Subclasses should call super
// if extending the behavior.
- (void)menuWillOpen:(NSMenu*)menu;
- (void)menuDidClose:(NSMenu*)menu;
@end
#endif // ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_

View file

@ -0,0 +1,384 @@
// Copyright (c) 2013 GitHub, Inc.
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#import "atom/browser/ui/cocoa/atom_menu_controller.h"
#include "atom/browser/mac/atom_application.h"
#include "atom/browser/ui/atom_menu_model.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/events/cocoa/cocoa_event_utils.h"
#include "ui/gfx/image/image.h"
#include "ui/strings/grit/ui_strings.h"
using content::BrowserThread;
namespace {
struct Role {
SEL selector;
const char* role;
};
Role kRolesMap[] = {
{@selector(orderFrontStandardAboutPanel:), "about"},
{@selector(hide:), "hide"},
{@selector(hideOtherApplications:), "hideothers"},
{@selector(unhideAllApplications:), "unhide"},
{@selector(arrangeInFront:), "front"},
{@selector(undo:), "undo"},
{@selector(redo:), "redo"},
{@selector(cut:), "cut"},
{@selector(copy:), "copy"},
{@selector(paste:), "paste"},
{@selector(delete:), "delete"},
{@selector(pasteAndMatchStyle:), "pasteandmatchstyle"},
{@selector(selectAll:), "selectall"},
{@selector(startSpeaking:), "startspeaking"},
{@selector(stopSpeaking:), "stopspeaking"},
{@selector(performMiniaturize:), "minimize"},
{@selector(performClose:), "close"},
{@selector(performZoom:), "zoom"},
{@selector(terminate:), "quit"},
// ↓ is intentionally not `toggleFullScreen`. The macOS full screen menu
// item behaves weird. If we use `toggleFullScreen`, then the menu item will
// use the default label, and not take the one provided.
{@selector(toggleFullScreenMode:), "togglefullscreen"},
{@selector(toggleTabBar:), "toggletabbar"},
{@selector(selectNextTab:), "selectnexttab"},
{@selector(selectPreviousTab:), "selectprevioustab"},
{@selector(mergeAllWindows:), "mergeallwindows"},
{@selector(moveTabToNewWindow:), "movetabtonewwindow"},
{@selector(clearRecentDocuments:), "clearrecentdocuments"},
};
// Called when adding a submenu to the menu and checks if the submenu, via its
// |model|, has visible child items.
bool MenuHasVisibleItems(const atom::AtomMenuModel* model) {
int count = model->GetItemCount();
for (int index = 0; index < count; index++) {
if (model->IsVisibleAt(index))
return true;
}
return false;
}
// Called when an empty submenu is created. This inserts a menu item labeled
// "(empty)" into the submenu. Matches Windows behavior.
NSMenu* MakeEmptySubmenu() {
base::scoped_nsobject<NSMenu> submenu([[NSMenu alloc] initWithTitle:@""]);
NSString* empty_menu_title =
l10n_util::GetNSString(IDS_APP_MENU_EMPTY_SUBMENU);
[submenu addItemWithTitle:empty_menu_title action:NULL keyEquivalent:@""];
[[submenu itemAtIndex:0] setEnabled:NO];
return submenu.autorelease();
}
} // namespace
// Menu item is located for ease of removing it from the parent owner
static base::scoped_nsobject<NSMenuItem> recentDocumentsMenuItem_;
// Submenu retained to be swapped back to |recentDocumentsMenuItem_|
static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
@implementation AtomMenuController
@synthesize model = model_;
- (id)initWithModel:(atom::AtomMenuModel*)model
useDefaultAccelerator:(BOOL)use {
if ((self = [super init])) {
model_ = model;
isMenuOpen_ = NO;
useDefaultAccelerator_ = use;
[self menu];
}
return self;
}
- (void)dealloc {
[menu_ setDelegate:nil];
// Close the menu if it is still open. This could happen if a tab gets closed
// while its context menu is still open.
[self cancel];
model_ = nil;
[super dealloc];
}
- (void)setCloseCallback:(const base::Callback<void()>&)callback {
closeCallback = callback;
}
- (void)populateWithModel:(atom::AtomMenuModel*)model {
if (!menu_)
return;
if (!recentDocumentsMenuItem_) {
// Locate & retain the recent documents menu item
recentDocumentsMenuItem_.reset(
[[[[[NSApp mainMenu] itemWithTitle:@"Electron"] submenu]
itemWithTitle:@"Open Recent"] retain]);
}
model_ = model;
[menu_ removeAllItems];
const int count = model->GetItemCount();
for (int index = 0; index < count; index++) {
if (model->GetTypeAt(index) == atom::AtomMenuModel::TYPE_SEPARATOR)
[self addSeparatorToMenu:menu_ atIndex:index];
else
[self addItemToMenu:menu_ atIndex:index fromModel:model];
}
}
- (void)cancel {
if (isMenuOpen_) {
[menu_ cancelTracking];
isMenuOpen_ = NO;
model_->MenuWillClose();
if (!closeCallback.is_null()) {
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
}
}
}
// Creates a NSMenu from the given model. If the model has submenus, this can
// be invoked recursively.
- (NSMenu*)menuFromModel:(atom::AtomMenuModel*)model {
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
const int count = model->GetItemCount();
for (int index = 0; index < count; index++) {
if (model->GetTypeAt(index) == atom::AtomMenuModel::TYPE_SEPARATOR)
[self addSeparatorToMenu:menu atIndex:index];
else
[self addItemToMenu:menu atIndex:index fromModel:model];
}
return menu;
}
// Adds a separator item at the given index. As the separator doesn't need
// anything from the model, this method doesn't need the model index as the
// other method below does.
- (void)addSeparatorToMenu:(NSMenu*)menu atIndex:(int)index {
NSMenuItem* separator = [NSMenuItem separatorItem];
[menu insertItem:separator atIndex:index];
}
// Empties the source menu items to the destination.
- (void)moveMenuItems:(NSMenu*)source to:(NSMenu*)destination {
const NSInteger count = [source numberOfItems];
for (NSInteger index = 0; index < count; index++) {
NSMenuItem* removedItem = [[[source itemAtIndex:0] retain] autorelease];
[source removeItemAtIndex:0];
[destination addItem:removedItem];
}
}
// Replaces the item's submenu instance with the singleton recent documents
// menu. Previously replaced menu items will be recovered.
- (void)replaceSubmenuShowingRecentDocuments:(NSMenuItem*)item {
NSMenu* recentDocumentsMenu =
[[[recentDocumentsMenuItem_ submenu] retain] autorelease];
// Remove menu items in recent documents back to swap menu
[self moveMenuItems:recentDocumentsMenu to:recentDocumentsMenuSwap_];
// Swap back the submenu
[recentDocumentsMenuItem_ setSubmenu:recentDocumentsMenuSwap_];
// Retain the item's submenu for a future recovery
recentDocumentsMenuSwap_.reset([[item submenu] retain]);
// Repopulate with items from the submenu to be replaced
[self moveMenuItems:recentDocumentsMenuSwap_ to:recentDocumentsMenu];
// Update the submenu's title
[recentDocumentsMenu setTitle:[recentDocumentsMenuSwap_ title]];
// Replace submenu
[item setSubmenu:recentDocumentsMenu];
// Remember the new menu item that carries the recent documents menu
recentDocumentsMenuItem_.reset([item retain]);
}
// Adds an item or a hierarchical menu to the item at the |index|,
// associated with the entry in the model identified by |modelIndex|.
- (void)addItemToMenu:(NSMenu*)menu
atIndex:(NSInteger)index
fromModel:(atom::AtomMenuModel*)model {
base::string16 label16 = model->GetLabelAt(index);
NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
initWithTitle:label
action:@selector(itemSelected:)
keyEquivalent:@""]);
// If the menu item has an icon, set it.
gfx::Image icon;
if (model->GetIconAt(index, &icon) && !icon.IsEmpty())
[item setImage:icon.ToNSImage()];
base::string16 role = model->GetRoleAt(index);
atom::AtomMenuModel::ItemType type = model->GetTypeAt(index);
if (role == base::ASCIIToUTF16("services")) {
base::string16 title = base::ASCIIToUTF16("Services");
NSString* label = l10n_util::FixUpWindowsStyleLabel(title);
[item setTarget:nil];
[item setAction:nil];
NSMenu* submenu = [[NSMenu alloc] initWithTitle:label];
[item setSubmenu:submenu];
[NSApp setServicesMenu:submenu];
} else if (type == atom::AtomMenuModel::TYPE_SUBMENU &&
model->IsVisibleAt(index)) {
// We need to specifically check that the submenu top-level item has been
// enabled as it's not validated by validateUserInterfaceItem
if (!model->IsEnabledAt(index))
[item setEnabled:NO];
// Recursively build a submenu from the sub-model at this index.
[item setTarget:nil];
[item setAction:nil];
atom::AtomMenuModel* submenuModel =
static_cast<atom::AtomMenuModel*>(model->GetSubmenuModelAt(index));
NSMenu* submenu = MenuHasVisibleItems(submenuModel)
? [self menuFromModel:submenuModel]
: MakeEmptySubmenu();
[submenu setTitle:[item title]];
[item setSubmenu:submenu];
// Set submenu's role.
if ((role == base::ASCIIToUTF16("window") ||
role == base::ASCIIToUTF16("windowmenu")) &&
[submenu numberOfItems])
[NSApp setWindowsMenu:submenu];
else if (role == base::ASCIIToUTF16("help"))
[NSApp setHelpMenu:submenu];
else if (role == base::ASCIIToUTF16("recentdocuments"))
[self replaceSubmenuShowingRecentDocuments:item];
} else {
// The MenuModel works on indexes so we can't just set the command id as the
// tag like we do in other menus. Also set the represented object to be
// the model so hierarchical menus check the correct index in the correct
// model. Setting the target to |self| allows this class to participate
// in validation of the menu items.
[item setTag:index];
NSValue* modelObject = [NSValue valueWithPointer:model];
[item setRepresentedObject:modelObject]; // Retains |modelObject|.
ui::Accelerator accelerator;
if (model->GetAcceleratorAtWithParams(index, useDefaultAccelerator_,
&accelerator)) {
NSString* key_equivalent;
NSUInteger modifier_mask;
GetKeyEquivalentAndModifierMaskFromAccelerator(
accelerator, &key_equivalent, &modifier_mask);
[item setKeyEquivalent:key_equivalent];
[item setKeyEquivalentModifierMask:modifier_mask];
}
if (@available(macOS 10.13, *)) {
[(id)item
setAllowsKeyEquivalentWhenHidden:(model->WorksWhenHiddenAt(index))];
}
// Set menu item's role.
[item setTarget:self];
if (!role.empty()) {
for (const Role& pair : kRolesMap) {
if (role == base::ASCIIToUTF16(pair.role)) {
[item setTarget:nil];
[item setAction:pair.selector];
break;
}
}
}
}
[menu insertItem:item atIndex:index];
}
// Called before the menu is to be displayed to update the state (enabled,
// radio, etc) of each item in the menu.
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
SEL action = [item action];
if (action != @selector(itemSelected:))
return NO;
NSInteger modelIndex = [item tag];
atom::AtomMenuModel* model = static_cast<atom::AtomMenuModel*>(
[[(id)item representedObject] pointerValue]);
DCHECK(model);
if (model) {
BOOL checked = model->IsItemCheckedAt(modelIndex);
DCHECK([(id)item isKindOfClass:[NSMenuItem class]]);
[(id)item setState:(checked ? NSOnState : NSOffState)];
[(id)item setHidden:(!model->IsVisibleAt(modelIndex))];
return model->IsEnabledAt(modelIndex);
}
return NO;
}
// Called when the user chooses a particular menu item. |sender| is the menu
// item chosen.
- (void)itemSelected:(id)sender {
NSInteger modelIndex = [sender tag];
atom::AtomMenuModel* model = static_cast<atom::AtomMenuModel*>(
[[sender representedObject] pointerValue]);
DCHECK(model);
if (model) {
NSEvent* event = [NSApp currentEvent];
model->ActivatedAt(modelIndex,
ui::EventFlagsFromModifiers([event modifierFlags]));
}
}
- (NSMenu*)menu {
if (menu_)
return menu_.get();
menu_.reset([[NSMenu alloc] initWithTitle:@""]);
[menu_ setDelegate:self];
if (model_)
[self populateWithModel:model_];
return menu_.get();
}
- (BOOL)isMenuOpen {
return isMenuOpen_;
}
- (void)menuWillOpen:(NSMenu*)menu {
isMenuOpen_ = YES;
model_->MenuWillShow();
}
- (void)menuDidClose:(NSMenu*)menu {
if (isMenuOpen_) {
isMenuOpen_ = NO;
model_->MenuWillClose();
// Post async task so that itemSelected runs before the close callback
// deletes the controller from the map which deallocates it
if (!closeCallback.is_null()) {
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
}
}
}
@end

View file

@ -0,0 +1,35 @@
// Copyright (c) 2018 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_ATOM_NATIVE_WIDGET_MAC_H_
#define ATOM_BROWSER_UI_COCOA_ATOM_NATIVE_WIDGET_MAC_H_
#include "ui/views/widget/native_widget_mac.h"
namespace atom {
class NativeWindowMac;
class AtomNativeWidgetMac : public views::NativeWidgetMac {
public:
AtomNativeWidgetMac(NativeWindowMac* shell,
NSUInteger style_mask,
views::internal::NativeWidgetDelegate* delegate);
~AtomNativeWidgetMac() override;
protected:
// NativeWidgetMac:
NativeWidgetMacNSWindow* CreateNSWindow(
const remote_cocoa::mojom::CreateWindowParams* params) override;
private:
NativeWindowMac* shell_;
NSUInteger style_mask_;
DISALLOW_COPY_AND_ASSIGN(AtomNativeWidgetMac);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NATIVE_WIDGET_MAC_H_

View file

@ -0,0 +1,27 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/atom_native_widget_mac.h"
#include "atom/browser/ui/cocoa/atom_ns_window.h"
namespace atom {
AtomNativeWidgetMac::AtomNativeWidgetMac(
NativeWindowMac* shell,
NSUInteger style_mask,
views::internal::NativeWidgetDelegate* delegate)
: views::NativeWidgetMac(delegate),
shell_(shell),
style_mask_(style_mask) {}
AtomNativeWidgetMac::~AtomNativeWidgetMac() {}
NativeWidgetMacNSWindow* AtomNativeWidgetMac::CreateNSWindow(
const remote_cocoa::mojom::CreateWindowParams* params) {
return [[[AtomNSWindow alloc] initWithShell:shell_
styleMask:style_mask_] autorelease];
}
} // namespace atom

View file

@ -0,0 +1,47 @@
// Copyright (c) 2018 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_ATOM_NS_WINDOW_H_
#define ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_H_
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
#include "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
#include "ui/views/widget/native_widget_mac.h"
namespace atom {
class NativeWindowMac;
// Prevents window from resizing during the scope.
class ScopedDisableResize {
public:
ScopedDisableResize() { disable_resize_ = true; }
~ScopedDisableResize() { disable_resize_ = false; }
static bool IsResizeDisabled() { return disable_resize_; }
private:
static bool disable_resize_;
};
} // namespace atom
@interface AtomNSWindow : NativeWidgetMacNSWindow {
@private
atom::NativeWindowMac* shell_;
}
@property BOOL acceptsFirstMouse;
@property BOOL enableLargerThanScreen;
@property BOOL disableAutoHideCursor;
@property BOOL disableKeyOrMainWindow;
@property(nonatomic, retain) NSView* vibrantView;
- (id)initWithShell:(atom::NativeWindowMac*)shell
styleMask:(NSUInteger)styleMask;
- (atom::NativeWindowMac*)shell;
- (id)accessibilityFocusedUIElement;
- (NSRect)originalContentRectForFrameRect:(NSRect)frameRect;
- (void)toggleFullScreenMode:(id)sender;
@end
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_H_

View file

@ -0,0 +1,191 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/atom_ns_window.h"
#include "atom/browser/native_window_mac.h"
#include "atom/browser/ui/cocoa/atom_preview_item.h"
#include "atom/browser/ui/cocoa/atom_touch_bar.h"
#include "atom/browser/ui/cocoa/root_view_mac.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/base/cocoa/window_size_constants.h"
namespace atom {
bool ScopedDisableResize::disable_resize_ = false;
} // namespace atom
@implementation AtomNSWindow
@synthesize acceptsFirstMouse;
@synthesize enableLargerThanScreen;
@synthesize disableAutoHideCursor;
@synthesize disableKeyOrMainWindow;
@synthesize vibrantView;
- (id)initWithShell:(atom::NativeWindowMac*)shell
styleMask:(NSUInteger)styleMask {
if ((self = [super initWithContentRect:ui::kWindowSizeDeterminedLater
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:YES])) {
shell_ = shell;
}
return self;
}
- (atom::NativeWindowMac*)shell {
return shell_;
}
- (id)accessibilityFocusedUIElement {
views::Widget* widget = shell_->widget();
id superFocus = [super accessibilityFocusedUIElement];
if (!widget || shell_->IsFocused())
return superFocus;
return nil;
}
- (NSRect)originalContentRectForFrameRect:(NSRect)frameRect {
return [super contentRectForFrameRect:frameRect];
}
- (NSTouchBar*)makeTouchBar API_AVAILABLE(macosx(10.12.2)) {
if (shell_->touch_bar())
return [shell_->touch_bar() makeTouchBar];
else
return nil;
}
// NSWindow overrides.
- (void)swipeWithEvent:(NSEvent*)event {
if (event.deltaY == 1.0) {
shell_->NotifyWindowSwipe("up");
} else if (event.deltaX == -1.0) {
shell_->NotifyWindowSwipe("right");
} else if (event.deltaY == -1.0) {
shell_->NotifyWindowSwipe("down");
} else if (event.deltaX == 1.0) {
shell_->NotifyWindowSwipe("left");
}
}
- (NSRect)contentRectForFrameRect:(NSRect)frameRect {
if (shell_->has_frame())
return [super contentRectForFrameRect:frameRect];
else
return frameRect;
}
- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen {
// Resizing is disabled.
if (atom::ScopedDisableResize::IsResizeDisabled())
return [self frame];
// Enable the window to be larger than screen.
if ([self enableLargerThanScreen])
return frameRect;
else
return [super constrainFrameRect:frameRect toScreen:screen];
}
- (void)setFrame:(NSRect)windowFrame display:(BOOL)displayViews {
// constrainFrameRect is not called on hidden windows so disable adjusting
// the frame directly when resize is disabled
if (!atom::ScopedDisableResize::IsResizeDisabled())
[super setFrame:windowFrame display:displayViews];
}
- (id)accessibilityAttributeValue:(NSString*)attribute {
if ([attribute isEqual:NSAccessibilityTitleAttribute])
return base::SysUTF8ToNSString(shell_->GetTitle());
if ([attribute isEqual:NSAccessibilityEnabledAttribute])
return [NSNumber numberWithBool:YES];
if (![attribute isEqualToString:@"AXChildren"])
return [super accessibilityAttributeValue:attribute];
// Filter out objects that aren't the title bar buttons. This has the effect
// of removing the window title, which VoiceOver already sees.
// * when VoiceOver is disabled, this causes Cmd+C to be used for TTS but
// still leaves the buttons available in the accessibility tree.
// * when VoiceOver is enabled, the full accessibility tree is used.
// Without removing the title and with VO disabled, the TTS would always read
// the window title instead of using Cmd+C to get the selected text.
NSPredicate* predicate = [NSPredicate
predicateWithFormat:@"(self isKindOfClass: %@) OR (self.className == %@)",
[NSButtonCell class], @"RenderWidgetHostViewCocoa"];
NSArray* children = [super accessibilityAttributeValue:attribute];
return [children filteredArrayUsingPredicate:predicate];
}
- (BOOL)canBecomeMainWindow {
return !self.disableKeyOrMainWindow;
}
- (BOOL)canBecomeKeyWindow {
return !self.disableKeyOrMainWindow;
}
- (NSView*)frameView {
return [[self contentView] superview];
}
// Quicklook methods
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel*)panel {
return YES;
}
- (void)beginPreviewPanelControl:(QLPreviewPanel*)panel {
panel.delegate = [self delegate];
panel.dataSource = static_cast<id<QLPreviewPanelDataSource>>([self delegate]);
}
- (void)endPreviewPanelControl:(QLPreviewPanel*)panel {
panel.delegate = nil;
panel.dataSource = nil;
}
// Custom window button methods
- (BOOL)windowShouldClose:(id)sender {
return YES;
}
- (void)performClose:(id)sender {
if (shell_->title_bar_style() ==
atom::NativeWindowMac::TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER) {
[[self delegate] windowShouldClose:self];
} else if (shell_->IsSimpleFullScreen()) {
if ([[self delegate] respondsToSelector:@selector(windowShouldClose:)]) {
if (![[self delegate] windowShouldClose:self])
return;
} else if ([self respondsToSelector:@selector(windowShouldClose:)]) {
if (![self windowShouldClose:self])
return;
}
[self close];
} else {
[super performClose:sender];
}
}
- (void)toggleFullScreenMode:(id)sender {
if (shell_->simple_fullscreen())
shell_->SetSimpleFullScreen(!shell_->IsSimpleFullScreen());
else
[super toggleFullScreen:sender];
}
- (void)performMiniaturize:(id)sender {
if (shell_->title_bar_style() ==
atom::NativeWindowMac::TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER)
[self miniaturize:self];
else
[super performMiniaturize:sender];
}
@end

View file

@ -0,0 +1,27 @@
// Copyright (c) 2018 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_ATOM_NS_WINDOW_DELEGATE_H_
#define ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_DELEGATE_H_
#include <Quartz/Quartz.h>
#include "components/remote_cocoa/app_shim/views_nswindow_delegate.h"
namespace atom {
class NativeWindowMac;
}
@interface AtomNSWindowDelegate
: ViewsNSWindowDelegate <NSTouchBarDelegate, QLPreviewPanelDataSource> {
@private
atom::NativeWindowMac* shell_;
bool is_zooming_;
int level_;
bool is_resizable_;
}
- (id)initWithShell:(atom::NativeWindowMac*)shell;
@end
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_DELEGATE_H_

View file

@ -0,0 +1,306 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/atom_ns_window_delegate.h"
#include <algorithm>
#include "atom/browser/browser.h"
#include "atom/browser/native_window_mac.h"
#include "atom/browser/ui/cocoa/atom_preview_item.h"
#include "atom/browser/ui/cocoa/atom_touch_bar.h"
#include "base/mac/mac_util.h"
#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
#include "ui/views/widget/native_widget_mac.h"
using TitleBarStyle = atom::NativeWindowMac::TitleBarStyle;
@implementation AtomNSWindowDelegate
- (id)initWithShell:(atom::NativeWindowMac*)shell {
// The views library assumes the window delegate must be an instance of
// ViewsNSWindowDelegate, since we don't have a way to override the creation
// of NSWindowDelegate, we have to dynamically replace the window delegate
// on the fly.
// TODO(zcbenz): Add interface in NativeWidgetMac to allow overriding creating
// window delegate.
auto* bridge_host = views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
shell->GetNativeWindow());
auto* bridged_view = bridge_host->bridge_impl();
if ((self = [super initWithBridgedNativeWidget:bridged_view])) {
shell_ = shell;
is_zooming_ = false;
level_ = [shell_->GetNativeWindow().GetNativeNSWindow() level];
}
return self;
}
#pragma mark - NSWindowDelegate
- (void)windowDidChangeOcclusionState:(NSNotification*)notification {
// notification.object is the window that changed its state.
// It's safe to use self.window instead if you don't assign one delegate to
// many windows
NSWindow* window = notification.object;
// check occlusion binary flag
if (window.occlusionState & NSWindowOcclusionStateVisible) {
// The app is visible
shell_->NotifyWindowShow();
} else {
// The app is not visible
shell_->NotifyWindowHide();
}
}
// Called when the user clicks the zoom button or selects it from the Window
// menu to determine the "standard size" of the window.
- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
defaultFrame:(NSRect)frame {
if (!shell_->zoom_to_page_width())
return frame;
// If the shift key is down, maximize.
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask)
return frame;
// Get preferred width from observers. Usually the page width.
int preferred_width = 0;
shell_->NotifyWindowRequestPreferredWith(&preferred_width);
// Never shrink from the current size on zoom.
NSRect window_frame = [window frame];
CGFloat zoomed_width =
std::max(static_cast<CGFloat>(preferred_width), NSWidth(window_frame));
// |frame| determines our maximum extents. We need to set the origin of the
// frame -- and only move it left if necessary.
if (window_frame.origin.x + zoomed_width > NSMaxX(frame))
frame.origin.x = NSMaxX(frame) - zoomed_width;
else
frame.origin.x = window_frame.origin.x;
// Set the width. Don't touch y or height.
frame.size.width = zoomed_width;
return frame;
}
- (void)windowDidBecomeMain:(NSNotification*)notification {
shell_->NotifyWindowFocus();
}
- (void)windowDidResignMain:(NSNotification*)notification {
shell_->NotifyWindowBlur();
}
- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
double aspectRatio = shell_->GetAspectRatio();
if (aspectRatio > 0.0) {
gfx::Size windowSize = shell_->GetSize();
gfx::Size contentSize = shell_->GetContentSize();
gfx::Size extraSize = shell_->GetAspectRatioExtraSize();
double extraWidthPlusFrame =
windowSize.width() - contentSize.width() + extraSize.width();
double extraHeightPlusFrame =
windowSize.height() - contentSize.height() + extraSize.height();
newSize.width =
roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio +
extraWidthPlusFrame);
newSize.height =
roundf((newSize.width - extraWidthPlusFrame) / aspectRatio +
extraHeightPlusFrame);
}
{
bool prevent_default = false;
gfx::Rect new_bounds(gfx::Point(sender.frame.origin), gfx::Size(newSize));
shell_->NotifyWindowWillResize(new_bounds, &prevent_default);
if (prevent_default) {
return sender.frame.size;
}
}
return newSize;
}
- (void)windowDidResize:(NSNotification*)notification {
[super windowDidResize:notification];
shell_->NotifyWindowResize();
}
- (void)windowDidMove:(NSNotification*)notification {
[super windowDidMove:notification];
// TODO(zcbenz): Remove the alias after figuring out a proper
// way to dispatch move.
shell_->NotifyWindowMove();
shell_->NotifyWindowMoved();
}
- (void)windowWillMiniaturize:(NSNotification*)notification {
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
// store the current status window level to be restored in
// windowDidDeminiaturize
level_ = [window level];
[window setLevel:NSNormalWindowLevel];
}
- (void)windowDidMiniaturize:(NSNotification*)notification {
[super windowDidMiniaturize:notification];
shell_->NotifyWindowMinimize();
}
- (void)windowDidDeminiaturize:(NSNotification*)notification {
[super windowDidDeminiaturize:notification];
[shell_->GetNativeWindow().GetNativeNSWindow() setLevel:level_];
shell_->NotifyWindowRestore();
}
- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame {
is_zooming_ = true;
return YES;
}
- (void)windowDidEndLiveResize:(NSNotification*)notification {
if (is_zooming_) {
if (shell_->IsMaximized())
shell_->NotifyWindowMaximize();
else
shell_->NotifyWindowUnmaximize();
is_zooming_ = false;
}
}
- (void)windowWillEnterFullScreen:(NSNotification*)notification {
// Setting resizable to true before entering fullscreen
is_resizable_ = shell_->IsResizable();
shell_->SetResizable(true);
// Hide the native toolbar before entering fullscreen, so there is no visual
// artifacts.
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
[window setToolbar:nil];
}
}
- (void)windowDidEnterFullScreen:(NSNotification*)notification {
shell_->NotifyWindowEnterFullScreen();
// For frameless window we don't show set title for normal mode since the
// titlebar is expected to be empty, but after entering fullscreen mode we
// have to set one, because title bar is visible here.
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
if ((shell_->transparent() || !shell_->has_frame()) &&
// FIXME(zcbenz): Showing titlebar for hiddenInset window is weird under
// fullscreen mode.
// Show title if fullscreen_window_title flag is set
(shell_->title_bar_style() != TitleBarStyle::HIDDEN_INSET ||
shell_->fullscreen_window_title())) {
[window setTitleVisibility:NSWindowTitleVisible];
}
// Restore the native toolbar immediately after entering fullscreen, if we
// do this before leaving fullscreen, traffic light buttons will be jumping.
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
base::scoped_nsobject<NSToolbar> toolbar(
[[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]);
[toolbar setShowsBaselineSeparator:NO];
[window setToolbar:toolbar];
// Set window style to hide the toolbar, otherwise the toolbar will show
// in fullscreen mode.
[window setTitlebarAppearsTransparent:NO];
shell_->SetStyleMask(true, NSWindowStyleMaskFullSizeContentView);
}
}
- (void)windowWillExitFullScreen:(NSNotification*)notification {
// Restore the titlebar visibility.
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
if ((shell_->transparent() || !shell_->has_frame()) &&
(shell_->title_bar_style() != TitleBarStyle::HIDDEN_INSET ||
shell_->fullscreen_window_title())) {
[window setTitleVisibility:NSWindowTitleHidden];
}
// Turn off the style for toolbar.
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
shell_->SetStyleMask(false, NSWindowStyleMaskFullSizeContentView);
[window setTitlebarAppearsTransparent:YES];
}
}
- (void)windowDidExitFullScreen:(NSNotification*)notification {
shell_->SetResizable(is_resizable_);
shell_->NotifyWindowLeaveFullScreen();
}
- (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.
auto* bridge_host = views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
shell_->GetNativeWindow());
auto* bridged_view = bridge_host->bridge_impl();
bridged_view->OnWindowWillClose();
}
- (BOOL)windowShouldClose:(id)window {
shell_->NotifyWindowCloseButtonClicked();
return NO;
}
- (NSRect)window:(NSWindow*)window
willPositionSheet:(NSWindow*)sheet
usingRect:(NSRect)rect {
NSView* view = window.contentView;
rect.origin.x = shell_->GetSheetOffsetX();
rect.origin.y = view.frame.size.height - shell_->GetSheetOffsetY();
return rect;
}
- (void)windowWillBeginSheet:(NSNotification*)notification {
shell_->NotifyWindowSheetBegin();
}
- (void)windowDidEndSheet:(NSNotification*)notification {
shell_->NotifyWindowSheetEnd();
}
- (IBAction)newWindowForTab:(id)sender {
shell_->NotifyNewWindowForTab();
atom::Browser::Get()->NewWindowForTab();
}
#pragma mark - NSTouchBarDelegate
- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
API_AVAILABLE(macosx(10.12.2)) {
if (touchBar && shell_->touch_bar())
return [shell_->touch_bar() makeItemForIdentifier:identifier];
else
return nil;
}
#pragma mark - QLPreviewPanelDataSource
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel*)panel {
return 1;
}
- (id<QLPreviewItem>)previewPanel:(QLPreviewPanel*)panel
previewItemAtIndex:(NSInteger)index {
return shell_->preview_item();
}
@end

View file

@ -0,0 +1,17 @@
// Copyright (c) 2018 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_ATOM_PREVIEW_ITEM_H_
#define ATOM_BROWSER_UI_COCOA_ATOM_PREVIEW_ITEM_H_
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
@interface AtomPreviewItem : NSObject <QLPreviewItem>
@property(nonatomic, retain) NSURL* previewItemURL;
@property(nonatomic, retain) NSString* previewItemTitle;
- (id)initWithURL:(NSURL*)url title:(NSString*)title;
@end
#endif // ATOM_BROWSER_UI_COCOA_ATOM_PREVIEW_ITEM_H_

View file

@ -0,0 +1,21 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/atom_preview_item.h"
@implementation AtomPreviewItem
@synthesize previewItemURL;
@synthesize previewItemTitle;
- (id)initWithURL:(NSURL*)url title:(NSString*)title {
self = [super init];
if (self) {
self.previewItemURL = url;
self.previewItemTitle = title;
}
return self;
}
@end

View file

@ -0,0 +1,101 @@
// Copyright (c) 2017 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_ATOM_TOUCH_BAR_H_
#define ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_
#import <Cocoa/Cocoa.h>
#include <map>
#include <string>
#include <vector>
#include "atom/browser/native_window.h"
#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h"
#include "base/mac/scoped_nsobject.h"
#include "native_mate/constructor.h"
#include "native_mate/persistent_dictionary.h"
@interface AtomTouchBar : NSObject <NSScrubberDelegate,
NSScrubberDataSource,
NSScrubberFlowLayoutDelegate> {
@protected
std::vector<mate::PersistentDictionary> ordered_settings_;
std::map<std::string, mate::PersistentDictionary> settings_;
id<NSTouchBarDelegate> delegate_;
atom::NativeWindow* window_;
}
- (id)initWithDelegate:(id<NSTouchBarDelegate>)delegate
window:(atom::NativeWindow*)window
settings:(const std::vector<mate::PersistentDictionary>&)settings;
- (NSTouchBar*)makeTouchBar API_AVAILABLE(macosx(10.12.2));
- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items
API_AVAILABLE(macosx(10.12.2));
- (NSMutableArray*)identifiersFromSettings:
(const std::vector<mate::PersistentDictionary>&)settings;
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
id:(const std::string&)item_id
API_AVAILABLE(macosx(10.12.2));
- (void)addNonDefaultTouchBarItems:
(const std::vector<mate::PersistentDictionary>&)items;
- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item
forTouchBar:(NSTouchBar*)touchBar
API_AVAILABLE(macosx(10.12.2));
- (NSString*)idFromIdentifier:(NSString*)identifier
withPrefix:(NSString*)prefix;
- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id
type:(const std::string&)typere;
- (bool)hasItemWithID:(const std::string&)item_id;
- (NSColor*)colorFromHexColorString:(const std::string&)colorString;
// Selector actions
- (void)buttonAction:(id)sender;
- (void)colorPickerAction:(id)sender API_AVAILABLE(macosx(10.12.2));
- (void)sliderAction:(id)sender API_AVAILABLE(macosx(10.12.2));
// Helpers to create touch bar items
- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
API_AVAILABLE(macosx(10.12.2));
- (NSTouchBarItem*)makeButtonForID:(NSString*)id
withIdentifier:(NSString*)identifier
API_AVAILABLE(macosx(10.12.2));
- (NSTouchBarItem*)makeLabelForID:(NSString*)id
withIdentifier:(NSString*)identifier
API_AVAILABLE(macosx(10.12.2));
- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id
withIdentifier:(NSString*)identifier
API_AVAILABLE(macosx(10.12.2));
- (NSTouchBarItem*)makeSliderForID:(NSString*)id
withIdentifier:(NSString*)identifier
API_AVAILABLE(macosx(10.12.2));
- (NSTouchBarItem*)makePopoverForID:(NSString*)id
withIdentifier:(NSString*)identifier
API_AVAILABLE(macosx(10.12.2));
- (NSTouchBarItem*)makeGroupForID:(NSString*)id
withIdentifier:(NSString*)identifier
API_AVAILABLE(macosx(10.12.2));
// Helpers to update touch bar items
- (void)updateButton:(NSCustomTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2));
- (void)updateLabel:(NSCustomTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2));
- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2));
- (void)updateSlider:(NSSliderTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2));
- (void)updatePopover:(NSPopoverTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2));
@end
#endif // ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_

View file

@ -0,0 +1,820 @@
// Copyright (c) 2017 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/atom_touch_bar.h"
#include <string>
#include <vector>
#include "atom/common/color_util.h"
#include "atom/common/native_mate_converters/image_converter.h"
#include "base/strings/sys_string_conversions.h"
#include "skia/ext/skia_utils_mac.h"
#include "ui/gfx/image/image.h"
@implementation AtomTouchBar
static NSTouchBarItemIdentifier ButtonIdentifier =
@"com.electron.touchbar.button.";
static NSTouchBarItemIdentifier ColorPickerIdentifier =
@"com.electron.touchbar.colorpicker.";
static NSTouchBarItemIdentifier GroupIdentifier =
@"com.electron.touchbar.group.";
static NSTouchBarItemIdentifier LabelIdentifier =
@"com.electron.touchbar.label.";
static NSTouchBarItemIdentifier PopoverIdentifier =
@"com.electron.touchbar.popover.";
static NSTouchBarItemIdentifier SliderIdentifier =
@"com.electron.touchbar.slider.";
static NSTouchBarItemIdentifier SegmentedControlIdentifier =
@"com.electron.touchbar.segmentedcontrol.";
static NSTouchBarItemIdentifier ScrubberIdentifier =
@"com.electron.touchbar.scrubber.";
static NSString* const TextScrubberItemIdentifier = @"scrubber.text.item";
static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item";
- (id)initWithDelegate:(id<NSTouchBarDelegate>)delegate
window:(atom::NativeWindow*)window
settings:
(const std::vector<mate::PersistentDictionary>&)settings {
if ((self = [super init])) {
delegate_ = delegate;
window_ = window;
ordered_settings_ = settings;
}
return self;
}
- (NSTouchBar*)makeTouchBar {
NSMutableArray* identifiers =
[self identifiersFromSettings:ordered_settings_];
return [self touchBarFromItemIdentifiers:identifiers];
}
- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items {
base::scoped_nsobject<NSTouchBar> bar(
[[NSClassFromString(@"NSTouchBar") alloc] init]);
[bar setDelegate:delegate_];
[bar setDefaultItemIdentifiers:items];
return bar.autorelease();
}
- (NSMutableArray*)identifiersFromSettings:
(const std::vector<mate::PersistentDictionary>&)dicts {
NSMutableArray* identifiers = [NSMutableArray array];
if (@available(macOS 10.12.2, *)) {
for (const auto& item : dicts) {
std::string type;
std::string item_id;
if (item.Get("type", &type) && item.Get("id", &item_id)) {
NSTouchBarItemIdentifier identifier = nil;
if (type == "spacer") {
std::string size;
item.Get("size", &size);
if (size == "large") {
identifier = NSTouchBarItemIdentifierFixedSpaceLarge;
} else if (size == "flexible") {
identifier = NSTouchBarItemIdentifierFlexibleSpace;
} else {
identifier = NSTouchBarItemIdentifierFixedSpaceSmall;
}
} else {
identifier = [self identifierFromID:item_id type:type];
}
if (identifier) {
settings_[item_id] = item;
[identifiers addObject:identifier];
}
}
}
[identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy];
}
return identifiers;
}
- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
NSString* item_id = nil;
if ([identifier hasPrefix:ButtonIdentifier]) {
item_id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier];
return [self makeButtonForID:item_id withIdentifier:identifier];
} else if ([identifier hasPrefix:LabelIdentifier]) {
item_id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier];
return [self makeLabelForID:item_id withIdentifier:identifier];
} else if ([identifier hasPrefix:ColorPickerIdentifier]) {
item_id = [self idFromIdentifier:identifier
withPrefix:ColorPickerIdentifier];
return [self makeColorPickerForID:item_id withIdentifier:identifier];
} else if ([identifier hasPrefix:SliderIdentifier]) {
item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier];
return [self makeSliderForID:item_id withIdentifier:identifier];
} else if ([identifier hasPrefix:PopoverIdentifier]) {
item_id = [self idFromIdentifier:identifier withPrefix:PopoverIdentifier];
return [self makePopoverForID:item_id withIdentifier:identifier];
} else if ([identifier hasPrefix:GroupIdentifier]) {
item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier];
return [self makeGroupForID:item_id withIdentifier:identifier];
} else if ([identifier hasPrefix:SegmentedControlIdentifier]) {
item_id = [self idFromIdentifier:identifier
withPrefix:SegmentedControlIdentifier];
return [self makeSegmentedControlForID:item_id withIdentifier:identifier];
} else if ([identifier hasPrefix:ScrubberIdentifier]) {
item_id = [self idFromIdentifier:identifier withPrefix:ScrubberIdentifier];
return [self makeScrubberForID:item_id withIdentifier:identifier];
}
return nil;
}
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
id:(NSTouchBarItemIdentifier)identifier
withType:(const std::string&)item_type
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2)) {
NSTouchBarItem* item = [touchBar itemForIdentifier:identifier];
if (!item)
return;
if (item_type == "button") {
[self updateButton:(NSCustomTouchBarItem*)item withSettings:settings];
} else if (item_type == "label") {
[self updateLabel:(NSCustomTouchBarItem*)item withSettings:settings];
} else if (item_type == "colorpicker") {
[self updateColorPicker:(NSColorPickerTouchBarItem*)item
withSettings:settings];
} else if (item_type == "slider") {
[self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings];
} else if (item_type == "popover") {
[self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings];
} else if (item_type == "segmented_control") {
[self updateSegmentedControl:(NSCustomTouchBarItem*)item
withSettings:settings];
} else if (item_type == "scrubber") {
[self updateScrubber:(NSCustomTouchBarItem*)item withSettings:settings];
} else if (item_type == "group") {
[self updateGroup:(NSGroupTouchBarItem*)item withSettings:settings];
}
}
- (void)addNonDefaultTouchBarItems:
(const std::vector<mate::PersistentDictionary>&)items {
[self identifiersFromSettings:items];
}
- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item
forTouchBar:(NSTouchBar*)touchBar {
if (![touchBar
respondsToSelector:@selector(escapeKeyReplacementItemIdentifier)])
return;
std::string type;
std::string item_id;
NSTouchBarItemIdentifier identifier = nil;
if (item.Get("type", &type) && item.Get("id", &item_id)) {
identifier = [self identifierFromID:item_id type:type];
}
if (identifier) {
[self addNonDefaultTouchBarItems:{item}];
touchBar.escapeKeyReplacementItemIdentifier = identifier;
} else {
touchBar.escapeKeyReplacementItemIdentifier = nil;
}
}
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
id:(const std::string&)item_id {
if (![self hasItemWithID:item_id])
return;
mate::PersistentDictionary settings = settings_[item_id];
std::string item_type;
settings.Get("type", &item_type);
auto identifier = [self identifierFromID:item_id type:item_type];
if (!identifier)
return;
std::vector<mate::Dictionary> parents;
settings.Get("_parents", &parents);
for (auto& parent : parents) {
std::string parent_type;
std::string parent_id;
if (!parent.Get("type", &parent_type) || !parent.Get("id", &parent_id))
continue;
auto parentIdentifier = [self identifierFromID:parent_id type:parent_type];
if (!parentIdentifier)
continue;
if (parent_type == "popover") {
NSPopoverTouchBarItem* popoverItem =
[touchBar itemForIdentifier:parentIdentifier];
[self refreshTouchBarItem:popoverItem.popoverTouchBar
id:identifier
withType:item_type
withSettings:settings];
} else if (parent_type == "group") {
NSGroupTouchBarItem* groupItem =
[touchBar itemForIdentifier:parentIdentifier];
[self refreshTouchBarItem:groupItem.groupTouchBar
id:identifier
withType:item_type
withSettings:settings];
}
}
[self refreshTouchBarItem:touchBar
id:identifier
withType:item_type
withSettings:settings];
}
- (void)buttonAction:(id)sender {
NSString* item_id =
[NSString stringWithFormat:@"%ld", ((NSButton*)sender).tag];
window_->NotifyTouchBarItemInteraction([item_id UTF8String],
base::DictionaryValue());
}
- (void)colorPickerAction:(id)sender {
NSString* identifier = ((NSColorPickerTouchBarItem*)sender).identifier;
NSString* item_id = [self idFromIdentifier:identifier
withPrefix:ColorPickerIdentifier];
NSColor* color = ((NSColorPickerTouchBarItem*)sender).color;
std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color));
base::DictionaryValue details;
details.SetString("color", hex_color);
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
}
- (void)sliderAction:(id)sender {
NSString* identifier = ((NSSliderTouchBarItem*)sender).identifier;
NSString* item_id = [self idFromIdentifier:identifier
withPrefix:SliderIdentifier];
base::DictionaryValue details;
details.SetInteger("value",
[((NSSliderTouchBarItem*)sender).slider intValue]);
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
}
- (NSString*)idFromIdentifier:(NSString*)identifier
withPrefix:(NSString*)prefix {
return [identifier substringFromIndex:[prefix length]];
}
- (void)segmentedControlAction:(id)sender {
NSString* item_id =
[NSString stringWithFormat:@"%ld", ((NSSegmentedControl*)sender).tag];
base::DictionaryValue details;
details.SetInteger("selectedIndex",
((NSSegmentedControl*)sender).selectedSegment);
details.SetBoolean(
"isSelected",
[((NSSegmentedControl*)sender)
isSelectedForSegment:((NSSegmentedControl*)sender).selectedSegment]);
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
}
- (void)scrubber:(NSScrubber*)scrubber
didSelectItemAtIndex:(NSInteger)selectedIndex
API_AVAILABLE(macosx(10.12.2)) {
base::DictionaryValue details;
details.SetInteger("selectedIndex", selectedIndex);
details.SetString("type", "select");
window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String],
details);
}
- (void)scrubber:(NSScrubber*)scrubber
didHighlightItemAtIndex:(NSInteger)highlightedIndex
API_AVAILABLE(macosx(10.12.2)) {
base::DictionaryValue details;
details.SetInteger("highlightedIndex", highlightedIndex);
details.SetString("type", "highlight");
window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String],
details);
}
- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id
type:(const std::string&)type {
NSTouchBarItemIdentifier base_identifier = nil;
if (type == "button")
base_identifier = ButtonIdentifier;
else if (type == "label")
base_identifier = LabelIdentifier;
else if (type == "colorpicker")
base_identifier = ColorPickerIdentifier;
else if (type == "slider")
base_identifier = SliderIdentifier;
else if (type == "popover")
base_identifier = PopoverIdentifier;
else if (type == "group")
base_identifier = GroupIdentifier;
else if (type == "segmented_control")
base_identifier = SegmentedControlIdentifier;
else if (type == "scrubber")
base_identifier = ScrubberIdentifier;
if (base_identifier)
return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()];
else
return nil;
}
- (bool)hasItemWithID:(const std::string&)item_id {
return settings_.find(item_id) != settings_.end();
}
- (NSColor*)colorFromHexColorString:(const std::string&)colorString {
SkColor color = atom::ParseHexColor(colorString);
return skia::SkColorToDeviceNSColor(color);
}
- (NSTouchBarItem*)makeButtonForID:(NSString*)id
withIdentifier:(NSString*)identifier {
std::string s_id([id UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
NSButton* button = [NSButton buttonWithTitle:@""
target:self
action:@selector(buttonAction:)];
button.tag = [id floatValue];
[item setView:button];
[self updateButton:item withSettings:settings];
return item.autorelease();
}
- (void)updateButton:(NSCustomTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings {
NSButton* button = (NSButton*)item.view;
std::string backgroundColor;
if (settings.Get("backgroundColor", &backgroundColor)) {
button.bezelColor = [self colorFromHexColorString:backgroundColor];
}
std::string label;
settings.Get("label", &label);
button.title = base::SysUTF8ToNSString(label);
gfx::Image image;
if (settings.Get("icon", &image)) {
button.image = image.AsNSImage();
std::string iconPosition;
settings.Get("iconPosition", &iconPosition);
if (iconPosition == "left") {
button.imagePosition = NSImageLeft;
} else if (iconPosition == "right") {
button.imagePosition = NSImageRight;
} else {
button.imagePosition = NSImageOverlaps;
}
}
}
- (NSTouchBarItem*)makeLabelForID:(NSString*)id
withIdentifier:(NSString*)identifier {
std::string s_id([id UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
[item setView:[NSTextField labelWithString:@""]];
[self updateLabel:item withSettings:settings];
return item.autorelease();
}
- (void)updateLabel:(NSCustomTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings {
NSTextField* text_field = (NSTextField*)item.view;
std::string label;
settings.Get("label", &label);
text_field.stringValue = base::SysUTF8ToNSString(label);
std::string textColor;
if (settings.Get("textColor", &textColor) && !textColor.empty()) {
text_field.textColor = [self colorFromHexColorString:textColor];
} else {
text_field.textColor = nil;
}
}
- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id
withIdentifier:(NSString*)identifier {
std::string s_id([id UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
base::scoped_nsobject<NSColorPickerTouchBarItem> item([[NSClassFromString(
@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]);
[item setTarget:self];
[item setAction:@selector(colorPickerAction:)];
[self updateColorPicker:item withSettings:settings];
return item.autorelease();
}
- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings {
std::vector<std::string> colors;
if (settings.Get("availableColors", &colors) && !colors.empty()) {
NSColorList* color_list =
[[[NSColorList alloc] initWithName:@""] autorelease];
for (size_t i = 0; i < colors.size(); ++i) {
[color_list insertColor:[self colorFromHexColorString:colors[i]]
key:base::SysUTF8ToNSString(colors[i])
atIndex:i];
}
item.colorList = color_list;
}
std::string selectedColor;
if (settings.Get("selectedColor", &selectedColor)) {
item.color = [self colorFromHexColorString:selectedColor];
}
}
- (NSTouchBarItem*)makeSliderForID:(NSString*)id
withIdentifier:(NSString*)identifier {
std::string s_id([id UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
base::scoped_nsobject<NSSliderTouchBarItem> item([[NSClassFromString(
@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]);
[item setTarget:self];
[item setAction:@selector(sliderAction:)];
[self updateSlider:item withSettings:settings];
return item.autorelease();
}
- (void)updateSlider:(NSSliderTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings {
std::string label;
settings.Get("label", &label);
item.label = base::SysUTF8ToNSString(label);
int maxValue = 100;
int minValue = 0;
int value = 50;
settings.Get("minValue", &minValue);
settings.Get("maxValue", &maxValue);
settings.Get("value", &value);
item.slider.minValue = minValue;
item.slider.maxValue = maxValue;
item.slider.doubleValue = value;
}
- (NSTouchBarItem*)makePopoverForID:(NSString*)id
withIdentifier:(NSString*)identifier {
std::string s_id([id UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
base::scoped_nsobject<NSPopoverTouchBarItem> item([[NSClassFromString(
@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]);
[self updatePopover:item withSettings:settings];
return item.autorelease();
}
- (void)updatePopover:(NSPopoverTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings {
std::string label;
settings.Get("label", &label);
item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label);
gfx::Image image;
if (settings.Get("icon", &image)) {
item.collapsedRepresentationImage = image.AsNSImage();
}
bool showCloseButton = true;
settings.Get("showCloseButton", &showCloseButton);
item.showsCloseButton = showCloseButton;
mate::PersistentDictionary child;
std::vector<mate::PersistentDictionary> items;
if (settings.Get("child", &child) && child.Get("ordereredItems", &items)) {
item.popoverTouchBar =
[self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]];
}
}
- (NSTouchBarItem*)makeGroupForID:(NSString*)id
withIdentifier:(NSString*)identifier {
std::string s_id([id UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
mate::PersistentDictionary child;
if (!settings.Get("child", &child))
return nil;
std::vector<mate::PersistentDictionary> items;
if (!child.Get("ordereredItems", &items))
return nil;
NSMutableArray* generatedItems = [NSMutableArray array];
NSMutableArray* identifiers = [self identifiersFromSettings:items];
for (NSUInteger i = 0; i < [identifiers count]; ++i) {
if ([identifiers objectAtIndex:i] !=
NSTouchBarItemIdentifierOtherItemsProxy) {
NSTouchBarItem* generatedItem =
[self makeItemForIdentifier:[identifiers objectAtIndex:i]];
if (generatedItem) {
[generatedItems addObject:generatedItem];
}
}
}
return [NSClassFromString(@"NSGroupTouchBarItem")
groupItemWithIdentifier:identifier
items:generatedItems];
}
- (void)updateGroup:(NSGroupTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2)) {
mate::PersistentDictionary child;
if (!settings.Get("child", &child))
return;
std::vector<mate::PersistentDictionary> items;
if (!child.Get("ordereredItems", &items))
return;
item.groupTouchBar =
[self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]];
}
- (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id
withIdentifier:(NSString*)identifier
API_AVAILABLE(macosx(10.12.2)) {
std::string s_id([id UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
NSSegmentedControl* control = [NSSegmentedControl
segmentedControlWithLabels:[NSMutableArray array]
trackingMode:NSSegmentSwitchTrackingSelectOne
target:self
action:@selector(segmentedControlAction:)];
control.tag = [id floatValue];
[item setView:control];
[self updateSegmentedControl:item withSettings:settings];
return item.autorelease();
}
- (void)updateSegmentedControl:(NSCustomTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2)) {
NSSegmentedControl* control = item.view;
std::string segmentStyle;
settings.Get("segmentStyle", &segmentStyle);
if (segmentStyle == "rounded")
control.segmentStyle = NSSegmentStyleRounded;
else if (segmentStyle == "textured-rounded")
control.segmentStyle = NSSegmentStyleTexturedRounded;
else if (segmentStyle == "round-rect")
control.segmentStyle = NSSegmentStyleRoundRect;
else if (segmentStyle == "textured-square")
control.segmentStyle = NSSegmentStyleTexturedSquare;
else if (segmentStyle == "capsule")
control.segmentStyle = NSSegmentStyleCapsule;
else if (segmentStyle == "small-square")
control.segmentStyle = NSSegmentStyleSmallSquare;
else if (segmentStyle == "separated")
control.segmentStyle = NSSegmentStyleSeparated;
else
control.segmentStyle = NSSegmentStyleAutomatic;
std::string segmentMode;
settings.Get("mode", &segmentMode);
if (segmentMode == "multiple")
control.trackingMode = NSSegmentSwitchTrackingSelectAny;
else if (segmentMode == "buttons")
control.trackingMode = NSSegmentSwitchTrackingMomentary;
else
control.trackingMode = NSSegmentSwitchTrackingSelectOne;
std::vector<mate::Dictionary> segments;
settings.Get("segments", &segments);
control.segmentCount = segments.size();
for (size_t i = 0; i < segments.size(); ++i) {
std::string label;
gfx::Image image;
bool enabled = true;
segments[i].Get("enabled", &enabled);
if (segments[i].Get("label", &label)) {
[control setLabel:base::SysUTF8ToNSString(label) forSegment:i];
} else {
[control setLabel:@"" forSegment:i];
}
if (segments[i].Get("icon", &image)) {
[control setImage:image.AsNSImage() forSegment:i];
[control setImageScaling:NSImageScaleProportionallyUpOrDown forSegment:i];
} else {
[control setImage:nil forSegment:i];
}
[control setEnabled:enabled forSegment:i];
}
int selectedIndex = 0;
settings.Get("selectedIndex", &selectedIndex);
if (selectedIndex >= 0 && selectedIndex < control.segmentCount)
control.selectedSegment = selectedIndex;
}
- (NSTouchBarItem*)makeScrubberForID:(NSString*)id
withIdentifier:(NSString*)identifier
API_AVAILABLE(macosx(10.12.2)) {
std::string s_id([id UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
NSScrubber* scrubber = [[[NSClassFromString(@"NSScrubber") alloc]
initWithFrame:NSZeroRect] autorelease];
[scrubber registerClass:NSClassFromString(@"NSScrubberTextItemView")
forItemIdentifier:TextScrubberItemIdentifier];
[scrubber registerClass:NSClassFromString(@"NSScrubberImageItemView")
forItemIdentifier:ImageScrubberItemIdentifier];
scrubber.delegate = self;
scrubber.dataSource = self;
scrubber.identifier = id;
[item setView:scrubber];
[self updateScrubber:item withSettings:settings];
return item.autorelease();
}
- (void)updateScrubber:(NSCustomTouchBarItem*)item
withSettings:(const mate::PersistentDictionary&)settings
API_AVAILABLE(macosx(10.12.2)) {
NSScrubber* scrubber = item.view;
bool showsArrowButtons = false;
settings.Get("showArrowButtons", &showsArrowButtons);
scrubber.showsArrowButtons = showsArrowButtons;
std::string selectedStyle;
std::string overlayStyle;
settings.Get("selectedStyle", &selectedStyle);
settings.Get("overlayStyle", &overlayStyle);
if (selectedStyle == "outline") {
scrubber.selectionBackgroundStyle =
[NSClassFromString(@"NSScrubberSelectionStyle") outlineOverlayStyle];
} else if (selectedStyle == "background") {
scrubber.selectionBackgroundStyle =
[NSClassFromString(@"NSScrubberSelectionStyle") roundedBackgroundStyle];
} else {
scrubber.selectionBackgroundStyle = nil;
}
if (overlayStyle == "outline") {
scrubber.selectionOverlayStyle =
[NSClassFromString(@"NSScrubberSelectionStyle") outlineOverlayStyle];
} else if (overlayStyle == "background") {
scrubber.selectionOverlayStyle =
[NSClassFromString(@"NSScrubberSelectionStyle") roundedBackgroundStyle];
} else {
scrubber.selectionOverlayStyle = nil;
}
std::string mode;
settings.Get("mode", &mode);
if (mode == "fixed") {
scrubber.mode = NSScrubberModeFixed;
} else {
scrubber.mode = NSScrubberModeFree;
}
bool continuous = true;
settings.Get("continuous", &continuous);
scrubber.continuous = continuous;
[scrubber reloadData];
}
- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber
API_AVAILABLE(macosx(10.12.2)) {
std::string s_id([[scrubber identifier] UTF8String]);
if (![self hasItemWithID:s_id])
return 0;
mate::PersistentDictionary settings = settings_[s_id];
std::vector<mate::PersistentDictionary> items;
settings.Get("items", &items);
return items.size();
}
- (NSScrubberItemView*)scrubber:(NSScrubber*)scrubber
viewForItemAtIndex:(NSInteger)index
API_AVAILABLE(macosx(10.12.2)) {
std::string s_id([[scrubber identifier] UTF8String]);
if (![self hasItemWithID:s_id])
return nil;
mate::PersistentDictionary settings = settings_[s_id];
std::vector<mate::PersistentDictionary> items;
if (!settings.Get("items", &items))
return nil;
if (index >= static_cast<NSInteger>(items.size()))
return nil;
mate::PersistentDictionary item = items[index];
NSScrubberItemView* itemView;
std::string title;
if (item.Get("label", &title)) {
NSScrubberTextItemView* view =
[scrubber makeItemWithIdentifier:TextScrubberItemIdentifier owner:self];
view.title = base::SysUTF8ToNSString(title);
itemView = view;
} else {
NSScrubberImageItemView* view =
[scrubber makeItemWithIdentifier:ImageScrubberItemIdentifier
owner:self];
gfx::Image image;
if (item.Get("icon", &image)) {
view.image = image.AsNSImage();
}
itemView = view;
}
return itemView;
}
- (NSSize)scrubber:(NSScrubber*)scrubber
layout:(NSScrubberFlowLayout*)layout
sizeForItemAtIndex:(NSInteger)itemIndex API_AVAILABLE(macosx(10.12.2)) {
NSInteger width = 50;
NSInteger height = 30;
NSInteger margin = 15;
NSSize defaultSize = NSMakeSize(width, height);
std::string s_id([[scrubber identifier] UTF8String]);
if (![self hasItemWithID:s_id])
return defaultSize;
mate::PersistentDictionary settings = settings_[s_id];
std::vector<mate::PersistentDictionary> items;
if (!settings.Get("items", &items))
return defaultSize;
if (itemIndex >= static_cast<NSInteger>(items.size()))
return defaultSize;
mate::PersistentDictionary item = items[itemIndex];
std::string title;
if (item.Get("label", &title)) {
NSSize size = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX);
NSRect textRect = [base::SysUTF8ToNSString(title)
boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin |
NSStringDrawingUsesFontLeading
attributes:@{
NSFontAttributeName : [NSFont systemFontOfSize:0]
}];
width = textRect.size.width + margin;
} else {
gfx::Image image;
if (item.Get("icon", &image)) {
width = image.AsNSImage().size.width;
}
}
return NSMakeSize(width, height);
}
@end

View file

@ -0,0 +1,21 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/delayed_native_view_host.h"
namespace atom {
DelayedNativeViewHost::DelayedNativeViewHost(gfx::NativeView native_view)
: native_view_(native_view) {}
DelayedNativeViewHost::~DelayedNativeViewHost() {}
void DelayedNativeViewHost::ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) {
NativeViewHost::ViewHierarchyChanged(details);
if (details.is_add && GetWidget())
Attach(native_view_);
}
} // namespace atom

View file

@ -0,0 +1,31 @@
// Copyright (c) 2018 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_DELAYED_NATIVE_VIEW_HOST_H_
#define ATOM_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
#include "ui/views/controls/native/native_view_host.h"
namespace atom {
// Automatically attach the native view after the NativeViewHost is attached to
// a widget. (Attaching it directly would cause crash.)
class DelayedNativeViewHost : public views::NativeViewHost {
public:
explicit DelayedNativeViewHost(gfx::NativeView native_view);
~DelayedNativeViewHost() override;
// views::View:
void ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) override;
private:
gfx::NativeView native_view_;
DISALLOW_COPY_AND_ASSIGN(DelayedNativeViewHost);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_

View file

@ -0,0 +1,19 @@
// Copyright (c) 2016 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_DISPATCHING_WINDOW_H_
#define ATOM_BROWSER_UI_COCOA_EVENT_DISPATCHING_WINDOW_H_
#import "ui/base/cocoa/underlay_opengl_hosting_window.h"
@interface EventDispatchingWindow : UnderlayOpenGLHostingWindow {
@private
BOOL redispatchingEvent_;
}
- (void)redispatchKeyEvent:(NSEvent*)event;
@end
#endif // ATOM_BROWSER_UI_COCOA_EVENT_DISPATCHING_WINDOW_H_

View file

@ -0,0 +1,34 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
@implementation EventDispatchingWindow
- (void)sendEvent:(NSEvent*)event {
if (!redispatchingEvent_)
[super sendEvent:event];
}
- (BOOL)performKeyEquivalent:(NSEvent*)event {
if (redispatchingEvent_)
return NO;
else
return [super performKeyEquivalent:event];
}
- (void)redispatchKeyEvent:(NSEvent*)event {
NSEventType eventType = [event type];
if (eventType != NSKeyDown && eventType != NSKeyUp &&
eventType != NSFlagsChanged) {
return;
}
// Redispatch the event.
redispatchingEvent_ = YES;
[NSApp sendEvent:event];
redispatchingEvent_ = NO;
}
@end

View file

@ -0,0 +1,33 @@
// Copyright (c) 2018 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_ROOT_VIEW_MAC_H_
#define ATOM_BROWSER_UI_COCOA_ROOT_VIEW_MAC_H_
#include "ui/views/view.h"
namespace atom {
class NativeWindow;
class RootViewMac : public views::View {
public:
explicit RootViewMac(NativeWindow* window);
~RootViewMac() override;
// views::View:
void Layout() override;
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
private:
// Parent window, weak ref.
NativeWindow* window_;
DISALLOW_COPY_AND_ASSIGN(RootViewMac);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_COCOA_ROOT_VIEW_MAC_H_

View file

@ -0,0 +1,32 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/root_view_mac.h"
#include "atom/browser/native_window.h"
namespace atom {
RootViewMac::RootViewMac(NativeWindow* window) : window_(window) {
set_owned_by_client();
}
RootViewMac::~RootViewMac() {}
void RootViewMac::Layout() {
if (!window_->content_view()) // Not ready yet.
return;
window_->content_view()->SetBoundsRect(gfx::Rect(gfx::Point(), size()));
}
gfx::Size RootViewMac::GetMinimumSize() const {
return window_->GetMinimumSize();
}
gfx::Size RootViewMac::GetMaximumSize() const {
return window_->GetMaximumSize();
}
} // namespace atom

View file

@ -0,0 +1,267 @@
// Copyright 2016 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 ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_
#define ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_
// Once Chrome no longer supports OSX 10.12.0, this file can be deleted.
#import <Foundation/Foundation.h>
#if !defined(MAC_OS_X_VERSION_10_12_1)
#pragma clang assume_nonnull begin
@class NSTouchBar, NSTouchBarItem;
@class NSScrubber, NSScrubberItemView, NSScrubberArrangedView,
NSScrubberTextItemView, NSScrubberImageItemView, NSScrubberSelectionStyle;
@protocol NSTouchBarDelegate
, NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate,
NSScrubberFlowLayout;
typedef float NSTouchBarItemPriority;
static const NSTouchBarItemPriority NSTouchBarItemPriorityHigh = 1000;
static const NSTouchBarItemPriority NSTouchBarItemPriorityNormal = 0;
static const NSTouchBarItemPriority NSTouchBarItemPriorityLow = -1000;
enum NSScrubberMode { NSScrubberModeFixed = 0, NSScrubberModeFree };
typedef NSString* NSTouchBarItemIdentifier;
typedef NSString* NSTouchBarCustomizationIdentifier;
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceSmall =
@"NSTouchBarItemIdentifierFixedSpaceSmall";
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceLarge =
@"NSTouchBarItemIdentifierFixedSpaceLarge";
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFlexibleSpace =
@"NSTouchBarItemIdentifierFlexibleSpace";
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy =
@"NSTouchBarItemIdentifierOtherItemsProxy";
@interface NSTouchBar : NSObject <NSCoding>
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder*)aDecoder
NS_DESIGNATED_INITIALIZER;
@property(copy, nullable)
NSTouchBarCustomizationIdentifier customizationIdentifier;
@property(copy) NSArray* customizationAllowedItemIdentifiers;
@property(copy) NSArray* customizationRequiredItemIdentifiers;
@property(copy) NSArray* defaultItemIdentifiers;
@property(copy, readonly) NSArray* itemIdentifiers;
@property(copy, nullable) NSTouchBarItemIdentifier principalItemIdentifier;
@property(copy, nullable)
NSTouchBarItemIdentifier escapeKeyReplacementItemIdentifier;
@property(copy) NSSet* templateItems;
@property(nullable, weak) id<NSTouchBarDelegate> delegate;
- (nullable __kindof NSTouchBarItem*)itemForIdentifier:
(NSTouchBarItemIdentifier)identifier;
@property(readonly, getter=isVisible) BOOL visible;
@end
@interface NSTouchBarItem : NSObject <NSCoding>
- (instancetype)initWithIdentifier:(NSTouchBarItemIdentifier)identifier
NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder*)coder
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@property(readonly, copy) NSTouchBarItemIdentifier identifier;
@property NSTouchBarItemPriority visibilityPriority;
@property(readonly, nullable) NSView* view;
@property(readonly, nullable) NSViewController* viewController;
@property(readwrite, copy) NSString* customizationLabel;
@property(readonly, getter=isVisible) BOOL visible;
@end
@interface NSGroupTouchBarItem : NSTouchBarItem
+ (NSGroupTouchBarItem*)groupItemWithIdentifier:
(NSTouchBarItemIdentifier)identifier
items:(NSArray*)items;
@property(strong) NSTouchBar* groupTouchBar;
@property(readwrite, copy, null_resettable) NSString* customizationLabel;
@end
@interface NSCustomTouchBarItem : NSTouchBarItem
@property(readwrite, strong) __kindof NSView* view;
@property(readwrite, strong, nullable)
__kindof NSViewController* viewController;
@property(readwrite, copy, null_resettable) NSString* customizationLabel;
@end
@interface NSColorPickerTouchBarItem : NSTouchBarItem
@property SEL action;
@property(weak) id target;
@property(copy) NSColor* color;
@property(strong) NSColorList* colorList;
@end
@interface NSPopoverTouchBarItem : NSTouchBarItem
@property BOOL showsCloseButton;
@property(strong) NSImage* collapsedRepresentationImage;
@property(strong) NSString* collapsedRepresentationLabel;
@property(strong) NSTouchBar* popoverTouchBar;
@end
@interface NSSliderTouchBarItem : NSTouchBarItem
@property SEL action;
@property(weak) id target;
@property(copy) NSString* label;
@property(strong) NSSlider* slider;
@end
@interface NSScrubber : NSView
@property(weak) id<NSScrubberDelegate> delegate;
@property(weak) id<NSScrubberDataSource> dataSource;
@property NSScrubberMode mode;
@property BOOL showsArrowButtons;
@property(getter=isContinuous) BOOL continuous;
@property(strong, nullable) NSScrubberSelectionStyle* selectionBackgroundStyle;
@property(strong, nullable) NSScrubberSelectionStyle* selectionOverlayStyle;
- (void)registerClass:(Class)itemViewClass
forItemIdentifier:(NSString*)itemIdentifier;
- (__kindof NSScrubberItemView*)makeItemWithIdentifier:(NSString*)itemIdentifier
owner:(id)owner;
- (void)reloadData;
@end
@interface NSScrubberFlowLayout : NSObject
@end
@interface NSScrubberSelectionStyle : NSObject <NSCoding>
@property(class, strong, readonly)
NSScrubberSelectionStyle* outlineOverlayStyle;
@property(class, strong, readonly)
NSScrubberSelectionStyle* roundedBackgroundStyle;
@end
@interface NSScrubberArrangedView : NSView
@end
@interface NSScrubberItemView : NSScrubberArrangedView
@end
@interface NSScrubberTextItemView : NSScrubberItemView
@property(copy) NSString* title;
@end
@interface NSScrubberImageItemView : NSScrubberItemView
@property(copy) NSImage* image;
@end
@interface NSWindow (TouchBarSDK)
@property(strong, readwrite, nullable) NSTouchBar* touchBar;
@end
@interface NSButton (TouchBarSDK)
@property(copy) NSColor* bezelColor;
+ (instancetype)buttonWithTitle:(NSString*)title
target:(id)target
action:(SEL)action;
@end
@interface NSTextField (TouchBarSDK)
+ (instancetype)labelWithString:(NSString*)stringValue;
@end
@interface NSSegmentedControl (TouchBarSDK)
+ (instancetype)segmentedControlWithLabels:(NSArray*)labels
trackingMode:(NSSegmentSwitchTracking)trackingMode
target:(id)target
action:(SEL)action;
@end
@protocol NSTouchBarDelegate <NSObject>
@optional
- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier;
@end
@protocol NSScrubberDelegate <NSObject>
- (void)scrubber:(NSScrubber*)scrubber
didHighlightItemAtIndex:(NSInteger)highlightedIndex;
- (void)scrubber:(NSScrubber*)scrubber
didSelectItemAtIndex:(NSInteger)selectedIndex;
@end
@protocol NSScrubberDataSource <NSObject>
- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber;
- (__kindof NSScrubberItemView*)scrubber:(NSScrubber*)scrubber
viewForItemAtIndex:(NSInteger)index;
@end
@protocol NSScrubberFlowLayoutDelegate <NSObject>
- (NSSize)scrubber:(NSScrubber*)scrubber
layout:(NSScrubberFlowLayout*)layout
sizeForItemAtIndex:(NSInteger)itemIndex;
@end
#pragma clang assume_nonnull end
#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12_1
// When compiling against the 10.12.1 SDK or later, just provide forward
// declarations to suppress the partial availability warnings.
@class NSCustomTouchBarItem;
@class NSGroupTouchBarItem;
@class NSTouchBar;
@protocol NSTouchBarDelegate;
@class NSTouchBarItem;
@interface NSWindow (TouchBarSDK)
@property(strong, readonly) NSTouchBar* touchBar API_AVAILABLE(macosx(10.12.2));
@end
#endif // MAC_OS_X_VERSION_10_12_1
#endif // ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_

View file

@ -0,0 +1,30 @@
// Copyright (c) 2018 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_VIEWS_DELEGATE_MAC_H_
#define ATOM_BROWSER_UI_COCOA_VIEWS_DELEGATE_MAC_H_
#include "ui/views/views_delegate.h"
namespace atom {
class ViewsDelegateMac : public views::ViewsDelegate {
public:
ViewsDelegateMac();
~ViewsDelegateMac() override;
// ViewsDelegate:
void OnBeforeWidgetInit(
views::Widget::InitParams* params,
views::internal::NativeWidgetDelegate* delegate) override;
ui::ContextFactory* GetContextFactory() override;
ui::ContextFactoryPrivate* GetContextFactoryPrivate() override;
private:
DISALLOW_COPY_AND_ASSIGN(ViewsDelegateMac);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_COCOA_VIEWS_DELEGATE_MAC_H_

View file

@ -0,0 +1,43 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/cocoa/views_delegate_mac.h"
#include "content/public/browser/context_factory.h"
#include "ui/views/widget/native_widget_mac.h"
namespace atom {
ViewsDelegateMac::ViewsDelegateMac() {}
ViewsDelegateMac::~ViewsDelegateMac() {}
void ViewsDelegateMac::OnBeforeWidgetInit(
views::Widget::InitParams* params,
views::internal::NativeWidgetDelegate* delegate) {
// If we already have a native_widget, we don't have to try to come
// up with one.
if (params->native_widget)
return;
if (!native_widget_factory().is_null()) {
params->native_widget = native_widget_factory().Run(*params, delegate);
if (params->native_widget)
return;
}
// Setting null here causes Widget to create the default NativeWidget
// implementation.
params->native_widget = nullptr;
}
ui::ContextFactory* ViewsDelegateMac::GetContextFactory() {
return content::GetContextFactory();
}
ui::ContextFactoryPrivate* ViewsDelegateMac::GetContextFactoryPrivate() {
return content::GetContextFactoryPrivate();
}
} // namespace atom

View file

@ -0,0 +1,125 @@
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#include "atom/browser/ui/devtools_manager_delegate.h"
#include <memory>
#include <utility>
#include <vector>
#include "atom/browser/atom_paths.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/devtools_frontend_host.h"
#include "content/public/browser/devtools_socket_factory.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/user_agent.h"
#include "electron/grit/electron_resources.h"
#include "net/base/net_errors.h"
#include "net/socket/stream_socket.h"
#include "net/socket/tcp_server_socket.h"
#include "ui/base/resource/resource_bundle.h"
namespace atom {
namespace {
class TCPServerSocketFactory : public content::DevToolsSocketFactory {
public:
TCPServerSocketFactory(const std::string& address, int port)
: address_(address), port_(port) {}
private:
// content::ServerSocketFactory.
std::unique_ptr<net::ServerSocket> CreateForHttpServer() override {
std::unique_ptr<net::ServerSocket> socket(
new net::TCPServerSocket(nullptr, net::NetLogSource()));
if (socket->ListenWithAddressAndPort(address_, port_, 10) != net::OK)
return std::unique_ptr<net::ServerSocket>();
return socket;
}
std::unique_ptr<net::ServerSocket> CreateForTethering(
std::string* name) override {
return std::unique_ptr<net::ServerSocket>();
}
std::string address_;
uint16_t port_;
DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory);
};
std::unique_ptr<content::DevToolsSocketFactory> CreateSocketFactory() {
auto& command_line = *base::CommandLine::ForCurrentProcess();
// See if the user specified a port on the command line (useful for
// automation). If not, use an ephemeral port by specifying 0.
int port = 0;
if (command_line.HasSwitch(switches::kRemoteDebuggingPort)) {
int temp_port;
std::string port_str =
command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort);
if (base::StringToInt(port_str, &temp_port) && temp_port >= 0 &&
temp_port < 65535) {
port = temp_port;
} else {
DLOG(WARNING) << "Invalid http debugger port number " << temp_port;
}
}
return std::unique_ptr<content::DevToolsSocketFactory>(
new TCPServerSocketFactory("127.0.0.1", port));
}
} // namespace
// DevToolsManagerDelegate ---------------------------------------------------
// static
void DevToolsManagerDelegate::StartHttpHandler() {
base::FilePath user_dir;
base::PathService::Get(DIR_USER_DATA, &user_dir);
content::DevToolsAgentHost::StartRemoteDebuggingServer(
CreateSocketFactory(), user_dir, base::FilePath());
}
DevToolsManagerDelegate::DevToolsManagerDelegate() {}
DevToolsManagerDelegate::~DevToolsManagerDelegate() {}
void DevToolsManagerDelegate::Inspect(content::DevToolsAgentHost* agent_host) {}
void DevToolsManagerDelegate::HandleCommand(
content::DevToolsAgentHost* agent_host,
content::DevToolsAgentHostClient* client,
const std::string& method,
const std::string& message,
NotHandledCallback callback) {
std::move(callback).Run(message);
}
scoped_refptr<content::DevToolsAgentHost>
DevToolsManagerDelegate::CreateNewTarget(const GURL& url) {
return nullptr;
}
std::string DevToolsManagerDelegate::GetDiscoveryPageHTML() {
return ui::ResourceBundle::GetSharedInstance()
.GetRawDataResource(IDR_CONTENT_SHELL_DEVTOOLS_DISCOVERY_PAGE)
.as_string();
}
bool DevToolsManagerDelegate::HasBundledFrontendResources() {
return true;
}
} // namespace atom

View file

@ -0,0 +1,42 @@
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_DEVTOOLS_MANAGER_DELEGATE_H_
#define ATOM_BROWSER_UI_DEVTOOLS_MANAGER_DELEGATE_H_
#include <memory>
#include <string>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "content/public/browser/devtools_manager_delegate.h"
namespace atom {
class DevToolsManagerDelegate : public content::DevToolsManagerDelegate {
public:
static void StartHttpHandler();
DevToolsManagerDelegate();
~DevToolsManagerDelegate() override;
// DevToolsManagerDelegate implementation.
void Inspect(content::DevToolsAgentHost* agent_host) override;
void HandleCommand(content::DevToolsAgentHost* agent_host,
content::DevToolsAgentHostClient* client,
const std::string& method,
const std::string& message,
NotHandledCallback callback) override;
scoped_refptr<content::DevToolsAgentHost> CreateNewTarget(
const GURL& url) override;
std::string GetDiscoveryPageHTML() override;
bool HasBundledFrontendResources() override;
private:
DISALLOW_COPY_AND_ASSIGN(DevToolsManagerDelegate);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_DEVTOOLS_MANAGER_DELEGATE_H_

View file

@ -0,0 +1,120 @@
// 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-CHROMIUM file.
#include "atom/browser/ui/devtools_ui.h"
#include <memory>
#include <string>
#include "base/memory/ref_counted_memory.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "content/public/browser/devtools_frontend_host.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
namespace atom {
namespace {
const char kChromeUIDevToolsHost[] = "devtools";
const char kChromeUIDevToolsBundledPath[] = "bundled";
std::string PathWithoutParams(const std::string& path) {
return GURL(std::string("devtools://devtools/") + path).path().substr(1);
}
std::string GetMimeTypeForPath(const std::string& path) {
std::string filename = PathWithoutParams(path);
if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) {
return "text/html";
} else if (base::EndsWith(filename, ".css",
base::CompareCase::INSENSITIVE_ASCII)) {
return "text/css";
} else if (base::EndsWith(filename, ".js",
base::CompareCase::INSENSITIVE_ASCII)) {
return "application/javascript";
} else if (base::EndsWith(filename, ".png",
base::CompareCase::INSENSITIVE_ASCII)) {
return "image/png";
} else if (base::EndsWith(filename, ".gif",
base::CompareCase::INSENSITIVE_ASCII)) {
return "image/gif";
} else if (base::EndsWith(filename, ".svg",
base::CompareCase::INSENSITIVE_ASCII)) {
return "image/svg+xml";
} else if (base::EndsWith(filename, ".manifest",
base::CompareCase::INSENSITIVE_ASCII)) {
return "text/cache-manifest";
}
return "text/html";
}
class BundledDataSource : public content::URLDataSource {
public:
BundledDataSource() {}
~BundledDataSource() override {}
// content::URLDataSource implementation.
std::string GetSource() const override { return kChromeUIDevToolsHost; }
void StartDataRequest(
const std::string& path,
const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
const GotDataCallback& callback) override {
// Serve request from local bundle.
std::string bundled_path_prefix(kChromeUIDevToolsBundledPath);
bundled_path_prefix += "/";
if (base::StartsWith(path, bundled_path_prefix,
base::CompareCase::INSENSITIVE_ASCII)) {
StartBundledDataRequest(path.substr(bundled_path_prefix.length()),
callback);
return;
}
// We do not handle remote and custom requests.
callback.Run(nullptr);
}
std::string GetMimeType(const std::string& path) const override {
return GetMimeTypeForPath(path);
}
bool ShouldAddContentSecurityPolicy() const override { return false; }
bool ShouldDenyXFrameOptions() const override { return false; }
bool ShouldServeMimeTypeAsContentTypeHeader() const override { return true; }
void StartBundledDataRequest(const std::string& path,
const GotDataCallback& callback) {
std::string filename = PathWithoutParams(path);
base::StringPiece resource =
content::DevToolsFrontendHost::GetFrontendResource(filename);
DLOG_IF(WARNING, resource.empty())
<< "Unable to find dev tool resource: " << filename
<< ". If you compiled with debug_devtools=1, try running with "
"--debug-devtools.";
scoped_refptr<base::RefCountedStaticMemory> bytes(
new base::RefCountedStaticMemory(resource.data(), resource.length()));
callback.Run(bytes.get());
}
private:
DISALLOW_COPY_AND_ASSIGN(BundledDataSource);
};
} // namespace
DevToolsUI::DevToolsUI(content::BrowserContext* browser_context,
content::WebUI* web_ui)
: WebUIController(web_ui) {
web_ui->SetBindings(0);
content::URLDataSource::Add(browser_context,
std::make_unique<BundledDataSource>());
}
} // namespace atom

View file

@ -0,0 +1,27 @@
// Copyright (c) 2011 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-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_DEVTOOLS_UI_H_
#define ATOM_BROWSER_UI_DEVTOOLS_UI_H_
#include "base/compiler_specific.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_ui_controller.h"
namespace atom {
class BrowserContext;
class DevToolsUI : public content::WebUIController {
public:
explicit DevToolsUI(content::BrowserContext* browser_context,
content::WebUI* web_ui);
private:
DISALLOW_COPY_AND_ASSIGN(DevToolsUI);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_DEVTOOLS_UI_H_

View file

@ -0,0 +1,24 @@
// Copyright (c) 2016 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_DRAG_UTIL_H_
#define ATOM_BROWSER_UI_DRAG_UTIL_H_
#include <vector>
#include "ui/gfx/image/image.h"
namespace base {
class FilePath;
}
namespace atom {
void DragFileItems(const std::vector<base::FilePath>& files,
const gfx::Image& icon,
gfx::NativeView view);
} // namespace atom
#endif // ATOM_BROWSER_UI_DRAG_UTIL_H_

View file

@ -0,0 +1,61 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#include <vector>
#include "atom/browser/ui/drag_util.h"
#include "base/files/file_path.h"
#include "base/strings/sys_string_conversions.h"
namespace atom {
namespace {
// Write information about the file being dragged to the pasteboard.
void AddFilesToPasteboard(NSPasteboard* pasteboard,
const std::vector<base::FilePath>& files) {
NSMutableArray* fileList = [NSMutableArray array];
for (const base::FilePath& file : files)
[fileList addObject:base::SysUTF8ToNSString(file.value())];
[pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType]
owner:nil];
[pasteboard setPropertyList:fileList forType:NSFilenamesPboardType];
}
} // namespace
void DragFileItems(const std::vector<base::FilePath>& files,
const gfx::Image& icon,
gfx::NativeView view) {
NSPasteboard* pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
AddFilesToPasteboard(pasteboard, files);
// Synthesize a drag event, since we don't have access to the actual event
// that initiated a drag (possibly consumed by the Web UI, for example).
NSWindow* window = [view.GetNativeNSView() window];
NSPoint position = [window mouseLocationOutsideOfEventStream];
NSTimeInterval eventTime = [[NSApp currentEvent] timestamp];
NSEvent* dragEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
location:position
modifierFlags:NSLeftMouseDraggedMask
timestamp:eventTime
windowNumber:[window windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
// Run the drag operation.
[window dragImage:icon.ToNSImage()
at:position
offset:NSZeroSize
event:dragEvent
pasteboard:pasteboard
source:view.GetNativeNSView()
slideBack:YES];
}
} // namespace atom

View file

@ -0,0 +1,49 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/drag_util.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/window.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/file_info.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/button_drag_utils.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
namespace atom {
void DragFileItems(const std::vector<base::FilePath>& files,
const gfx::Image& icon,
gfx::NativeView view) {
// Set up our OLE machinery
ui::OSExchangeData data;
button_drag_utils::SetDragImage(
GURL(), files[0].LossyDisplayName(), icon.AsImageSkia(), nullptr,
*views::Widget::GetTopLevelWidgetForNativeView(view), &data);
std::vector<ui::FileInfo> file_infos;
for (const base::FilePath& file : files) {
file_infos.push_back(ui::FileInfo(file, base::FilePath()));
}
data.SetFilenames(file_infos);
aura::Window* root_window = view->GetRootWindow();
if (!root_window || !aura::client::GetDragDropClient(root_window))
return;
gfx::Point location = display::Screen::GetScreen()->GetCursorScreenPoint();
// TODO(varunjain): Properly determine and send DRAG_EVENT_SOURCE below.
aura::client::GetDragDropClient(root_window)
->StartDragAndDrop(
data, root_window, view, location,
ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK,
ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
}
} // namespace atom

View file

@ -0,0 +1,69 @@
// 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_FILE_DIALOG_H_
#define ATOM_BROWSER_UI_FILE_DIALOG_H_
#include <string>
#include <utility>
#include <vector>
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "atom/common/promise_util.h"
#include "base/files/file_path.h"
#include "native_mate/dictionary.h"
namespace atom {
class NativeWindow;
}
namespace file_dialog {
// <description, extensions>
typedef std::pair<std::string, std::vector<std::string>> Filter;
typedef std::vector<Filter> Filters;
enum FileDialogProperty {
FILE_DIALOG_OPEN_FILE = 1 << 0,
FILE_DIALOG_OPEN_DIRECTORY = 1 << 1,
FILE_DIALOG_MULTI_SELECTIONS = 1 << 2,
FILE_DIALOG_CREATE_DIRECTORY = 1 << 3,
FILE_DIALOG_SHOW_HIDDEN_FILES = 1 << 4,
FILE_DIALOG_PROMPT_TO_CREATE = 1 << 5,
FILE_DIALOG_NO_RESOLVE_ALIASES = 1 << 6,
FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY = 1 << 7,
};
struct DialogSettings {
atom::NativeWindow* parent_window = nullptr;
std::string title;
std::string message;
std::string button_label;
std::string name_field_label;
base::FilePath default_path;
Filters filters;
int properties = 0;
bool shows_tag_field = true;
bool force_detached = false;
bool security_scoped_bookmarks = false;
DialogSettings();
DialogSettings(const DialogSettings&);
~DialogSettings();
};
bool ShowOpenDialogSync(const DialogSettings& settings,
std::vector<base::FilePath>* paths);
void ShowOpenDialog(const DialogSettings& settings,
atom::util::Promise promise);
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path);
void ShowSaveDialog(const DialogSettings& settings,
atom::util::Promise promise);
} // namespace file_dialog
#endif // ATOM_BROWSER_UI_FILE_DIALOG_H_

View file

@ -0,0 +1,311 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/file_dialog.h"
#include <glib/gi18n.h> // _() macro
#include "atom/browser/native_window_views.h"
#include "atom/browser/unresponsive_suppressor.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/strings/string_util.h"
#include "chrome/browser/ui/libgtkui/gtk_util.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
namespace file_dialog {
DialogSettings::DialogSettings() = default;
DialogSettings::DialogSettings(const DialogSettings&) = default;
DialogSettings::~DialogSettings() = default;
namespace {
static const int kPreviewWidth = 256;
static const int kPreviewHeight = 512;
// Makes sure that .jpg also shows .JPG.
gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
std::string* file_extension) {
// Makes .* file extension matches all file types.
if (*file_extension == ".*")
return true;
return base::EndsWith(file_info->filename, *file_extension,
base::CompareCase::INSENSITIVE_ASCII);
}
// Deletes |data| when gtk_file_filter_add_custom() is done with it.
void OnFileFilterDataDestroyed(std::string* file_extension) {
delete file_extension;
}
class FileChooserDialog {
public:
FileChooserDialog(GtkFileChooserAction action, const DialogSettings& settings)
: parent_(static_cast<atom::NativeWindowViews*>(settings.parent_window)),
filters_(settings.filters) {
const char* confirm_text = _("_OK");
if (!settings.button_label.empty())
confirm_text = settings.button_label.c_str();
else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
confirm_text = _("_Save");
else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
confirm_text = _("_Open");
dialog_ = gtk_file_chooser_dialog_new(
settings.title.c_str(), NULL, action, _("_Cancel"), GTK_RESPONSE_CANCEL,
confirm_text, GTK_RESPONSE_ACCEPT, NULL);
if (parent_) {
parent_->SetEnabled(false);
libgtkui::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
}
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog_),
TRUE);
if (action != GTK_FILE_CHOOSER_ACTION_OPEN)
gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog_), TRUE);
if (!settings.default_path.empty()) {
if (base::DirectoryExists(settings.default_path)) {
gtk_file_chooser_set_current_folder(
GTK_FILE_CHOOSER(dialog_), settings.default_path.value().c_str());
} else {
if (settings.default_path.IsAbsolute()) {
gtk_file_chooser_set_current_folder(
GTK_FILE_CHOOSER(dialog_),
settings.default_path.DirName().value().c_str());
}
gtk_file_chooser_set_current_name(
GTK_FILE_CHOOSER(dialog_),
settings.default_path.BaseName().value().c_str());
}
}
if (!settings.filters.empty())
AddFilters(settings.filters);
preview_ = gtk_image_new();
g_signal_connect(dialog_, "update-preview",
G_CALLBACK(OnUpdatePreviewThunk), this);
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog_), preview_);
}
~FileChooserDialog() {
gtk_widget_destroy(dialog_);
if (parent_)
parent_->SetEnabled(true);
}
void SetupProperties(int properties) {
const auto hasProp = [properties](FileDialogProperty prop) {
return gboolean((properties & prop) != 0);
};
auto* file_chooser = GTK_FILE_CHOOSER(dialog());
gtk_file_chooser_set_select_multiple(file_chooser,
hasProp(FILE_DIALOG_MULTI_SELECTIONS));
gtk_file_chooser_set_show_hidden(file_chooser,
hasProp(FILE_DIALOG_SHOW_HIDDEN_FILES));
}
void RunAsynchronous() {
g_signal_connect(dialog_, "delete-event",
G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(dialog_, "response", G_CALLBACK(OnFileDialogResponseThunk),
this);
gtk_widget_show_all(dialog_);
// We need to call gtk_window_present after making the widgets visible to
// make sure window gets correctly raised and gets focus.
int time = ui::X11EventSource::GetInstance()->GetTimestamp();
gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
}
void RunSaveAsynchronous(atom::util::Promise promise) {
save_promise_.reset(new atom::util::Promise(std::move(promise)));
RunAsynchronous();
}
void RunOpenAsynchronous(atom::util::Promise promise) {
open_promise_.reset(new atom::util::Promise(std::move(promise)));
RunAsynchronous();
}
base::FilePath GetFileName() const {
gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog_));
const base::FilePath path(filename);
g_free(filename);
return path;
}
std::vector<base::FilePath> GetFileNames() const {
std::vector<base::FilePath> paths;
auto* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog_));
for (auto* iter = filenames; iter != NULL; iter = iter->next) {
auto* filename = static_cast<char*>(iter->data);
paths.emplace_back(filename);
g_free(filename);
}
g_slist_free(filenames);
return paths;
}
CHROMEG_CALLBACK_1(FileChooserDialog,
void,
OnFileDialogResponse,
GtkWidget*,
int);
GtkWidget* dialog() const { return dialog_; }
private:
void AddFilters(const Filters& filters);
atom::NativeWindowViews* parent_;
atom::UnresponsiveSuppressor unresponsive_suppressor_;
GtkWidget* dialog_;
GtkWidget* preview_;
Filters filters_;
std::unique_ptr<atom::util::Promise> save_promise_;
std::unique_ptr<atom::util::Promise> open_promise_;
// Callback for when we update the preview for the selection.
CHROMEG_CALLBACK_0(FileChooserDialog, void, OnUpdatePreview, GtkWidget*);
DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
};
void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
gtk_widget_hide(dialog_);
if (save_promise_) {
mate::Dictionary dict =
mate::Dictionary::CreateEmpty(save_promise_->isolate());
if (response == GTK_RESPONSE_ACCEPT) {
dict.Set("canceled", false);
dict.Set("filePath", GetFileName());
} else {
dict.Set("canceled", true);
dict.Set("filePath", base::FilePath());
}
save_promise_->Resolve(dict.GetHandle());
} else if (open_promise_) {
mate::Dictionary dict =
mate::Dictionary::CreateEmpty(open_promise_->isolate());
if (response == GTK_RESPONSE_ACCEPT) {
dict.Set("canceled", false);
dict.Set("filePaths", GetFileNames());
} else {
dict.Set("canceled", true);
dict.Set("filePaths", std::vector<base::FilePath>());
}
open_promise_->Resolve(dict.GetHandle());
}
delete this;
}
void FileChooserDialog::AddFilters(const Filters& filters) {
for (size_t i = 0; i < filters.size(); ++i) {
const Filter& filter = filters[i];
GtkFileFilter* gtk_filter = gtk_file_filter_new();
for (size_t j = 0; j < filter.second.size(); ++j) {
auto file_extension =
std::make_unique<std::string>("." + filter.second[j]);
gtk_file_filter_add_custom(
gtk_filter, GTK_FILE_FILTER_FILENAME,
reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
file_extension.release(),
reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
}
gtk_file_filter_set_name(gtk_filter, filter.first.c_str());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_), gtk_filter);
}
}
void FileChooserDialog::OnUpdatePreview(GtkWidget* chooser) {
gchar* filename =
gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(chooser));
if (!filename) {
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
FALSE);
return;
}
// Don't attempt to open anything which isn't a regular file. If a named pipe,
// this may hang. See https://crbug.com/534754.
struct stat stat_buf;
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
g_free(filename);
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
FALSE);
return;
}
// This will preserve the image's aspect ratio.
GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
kPreviewHeight, nullptr);
g_free(filename);
if (pixbuf) {
gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
g_object_unref(pixbuf);
}
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
pixbuf ? TRUE : FALSE);
}
} // namespace
bool ShowOpenDialogSync(const DialogSettings& settings,
std::vector<base::FilePath>* paths) {
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
FileChooserDialog open_dialog(action, settings);
open_dialog.SetupProperties(settings.properties);
gtk_widget_show_all(open_dialog.dialog());
int response = gtk_dialog_run(GTK_DIALOG(open_dialog.dialog()));
if (response == GTK_RESPONSE_ACCEPT) {
*paths = open_dialog.GetFileNames();
return true;
}
return false;
}
void ShowOpenDialog(const DialogSettings& settings,
atom::util::Promise promise) {
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
FileChooserDialog* open_dialog = new FileChooserDialog(action, settings);
open_dialog->SetupProperties(settings.properties);
open_dialog->RunOpenAsynchronous(std::move(promise));
}
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
gtk_widget_show_all(save_dialog.dialog());
int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog()));
if (response == GTK_RESPONSE_ACCEPT) {
*path = save_dialog.GetFileName();
return true;
}
return false;
}
void ShowSaveDialog(const DialogSettings& settings,
atom::util::Promise promise) {
FileChooserDialog* save_dialog =
new FileChooserDialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
save_dialog->RunSaveAsynchronous(std::move(promise));
}
} // namespace file_dialog

View file

@ -0,0 +1,424 @@
// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/file_dialog.h"
#include <string>
#include <utility>
#include <vector>
#import <Cocoa/Cocoa.h>
#import <CoreServices/CoreServices.h>
#include "atom/browser/native_window.h"
#include "base/files/file_util.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/strings/sys_string_conversions.h"
@interface PopUpButtonHandler : NSObject
@property(nonatomic, assign) NSSavePanel* savePanel;
@property(nonatomic, strong) NSArray* fileTypesList;
- (instancetype)initWithPanel:(NSSavePanel*)panel
andTypesList:(NSArray*)typesList;
- (void)selectFormat:(id)sender;
@end
@implementation PopUpButtonHandler
@synthesize savePanel;
@synthesize fileTypesList;
- (instancetype)initWithPanel:(NSSavePanel*)panel
andTypesList:(NSArray*)typesList {
self = [super init];
if (self) {
[self setSavePanel:panel];
[self setFileTypesList:typesList];
}
return self;
}
- (void)selectFormat:(id)sender {
NSPopUpButton* button = (NSPopUpButton*)sender;
NSInteger selectedItemIndex = [button indexOfSelectedItem];
NSArray* list = [self fileTypesList];
NSArray* fileTypes = [list objectAtIndex:selectedItemIndex];
// If we meet a '*' file extension, we allow all the file types and no
// need to set the specified file types.
if ([fileTypes count] == 0 || [fileTypes containsObject:@"*"])
[[self savePanel] setAllowedFileTypes:nil];
else
[[self savePanel] setAllowedFileTypes:fileTypes];
}
@end
// Manages the PopUpButtonHandler.
@interface AtomAccessoryView : NSView
@end
@implementation AtomAccessoryView
- (void)dealloc {
auto* popupButton =
static_cast<NSPopUpButton*>([[self subviews] objectAtIndex:1]);
[[popupButton target] release];
[super dealloc];
}
@end
namespace file_dialog {
DialogSettings::DialogSettings() = default;
DialogSettings::DialogSettings(const DialogSettings&) = default;
DialogSettings::~DialogSettings() = default;
namespace {
void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) {
NSMutableArray* file_types_list = [NSMutableArray array];
NSMutableArray* filter_names = [NSMutableArray array];
// Create array to keep file types and their name.
for (const Filter& filter : filters) {
NSMutableSet* file_type_set = [NSMutableSet set];
[filter_names addObject:@(filter.first.c_str())];
for (const std::string& ext : filter.second) {
[file_type_set addObject:@(ext.c_str())];
}
[file_types_list addObject:[file_type_set allObjects]];
}
// Passing empty array to setAllowedFileTypes will cause exception.
NSArray* file_types = nil;
NSUInteger count = [file_types_list count];
if (count > 0) {
file_types = [[file_types_list objectAtIndex:0] allObjects];
// If we meet a '*' file extension, we allow all the file types and no
// need to set the specified file types.
if ([file_types count] == 0 || [file_types containsObject:@"*"])
file_types = nil;
}
[dialog setAllowedFileTypes:file_types];
if (count <= 1)
return; // don't add file format picker
// Add file format picker.
AtomAccessoryView* accessoryView =
[[AtomAccessoryView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 200, 32.0)];
NSTextField* label =
[[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 60, 22)];
[label setEditable:NO];
[label setStringValue:@"Format:"];
[label setBordered:NO];
[label setBezeled:NO];
[label setDrawsBackground:NO];
NSPopUpButton* popupButton =
[[NSPopUpButton alloc] initWithFrame:NSMakeRect(50.0, 2, 140, 22.0)
pullsDown:NO];
PopUpButtonHandler* popUpButtonHandler =
[[PopUpButtonHandler alloc] initWithPanel:dialog
andTypesList:file_types_list];
[popupButton addItemsWithTitles:filter_names];
[popupButton setTarget:popUpButtonHandler];
[popupButton setAction:@selector(selectFormat:)];
[accessoryView addSubview:[label autorelease]];
[accessoryView addSubview:[popupButton autorelease]];
[dialog setAccessoryView:[accessoryView autorelease]];
}
void SetupDialog(NSSavePanel* dialog, const DialogSettings& settings) {
if (!settings.title.empty())
[dialog setTitle:base::SysUTF8ToNSString(settings.title)];
if (!settings.button_label.empty())
[dialog setPrompt:base::SysUTF8ToNSString(settings.button_label)];
if (!settings.message.empty())
[dialog setMessage:base::SysUTF8ToNSString(settings.message)];
if (!settings.name_field_label.empty())
[dialog
setNameFieldLabel:base::SysUTF8ToNSString(settings.name_field_label)];
[dialog setShowsTagField:settings.shows_tag_field];
NSString* default_dir = nil;
NSString* default_filename = nil;
if (!settings.default_path.empty()) {
base::ThreadRestrictions::ScopedAllowIO allow_io;
if (base::DirectoryExists(settings.default_path)) {
default_dir = base::SysUTF8ToNSString(settings.default_path.value());
} else {
if (settings.default_path.IsAbsolute()) {
default_dir =
base::SysUTF8ToNSString(settings.default_path.DirName().value());
}
default_filename =
base::SysUTF8ToNSString(settings.default_path.BaseName().value());
}
}
if (settings.filters.empty()) {
[dialog setAllowsOtherFileTypes:YES];
} else {
// Set setAllowedFileTypes before setNameFieldStringValue as it might
// override the extension set using setNameFieldStringValue
SetAllowedFileTypes(dialog, settings.filters);
}
// Make sure the extension is always visible. Without this, the extension in
// the default filename will not be used in the saved file.
[dialog setExtensionHidden:NO];
if (default_dir)
[dialog setDirectoryURL:[NSURL fileURLWithPath:default_dir]];
if (default_filename)
[dialog setNameFieldStringValue:default_filename];
}
void SetupDialogForProperties(NSOpenPanel* dialog, int properties) {
[dialog setCanChooseFiles:(properties & FILE_DIALOG_OPEN_FILE)];
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
[dialog setCanChooseDirectories:YES];
if (properties & FILE_DIALOG_CREATE_DIRECTORY)
[dialog setCanCreateDirectories:YES];
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
[dialog setAllowsMultipleSelection:YES];
if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
[dialog setShowsHiddenFiles:YES];
if (properties & FILE_DIALOG_NO_RESOLVE_ALIASES)
[dialog setResolvesAliases:NO];
if (properties & FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
[dialog setTreatsFilePackagesAsDirectories:YES];
}
// Run modal dialog with parent window and return user's choice.
int RunModalDialog(NSSavePanel* dialog, const DialogSettings& settings) {
__block int chosen = NSFileHandlingPanelCancelButton;
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
settings.force_detached) {
chosen = [dialog runModal];
} else {
NSWindow* window =
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
[dialog beginSheetModalForWindow:window
completionHandler:^(NSInteger c) {
chosen = c;
[NSApp stopModal];
}];
[NSApp runModalForWindow:window];
}
return chosen;
}
// Create bookmark data and serialise it into a base64 string.
std::string GetBookmarkDataFromNSURL(NSURL* url) {
// Create the file if it doesn't exist (necessary for NSSavePanel options).
NSFileManager* defaultManager = [NSFileManager defaultManager];
if (![defaultManager fileExistsAtPath:[url path]]) {
[defaultManager createFileAtPath:[url path] contents:nil attributes:nil];
}
NSError* error = nil;
NSData* bookmarkData =
[url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&error];
if (error != nil) {
// Send back an empty string if there was an error.
return "";
} else {
// Encode NSData in base64 then convert to NSString.
NSString* base64data = [[NSString alloc]
initWithData:[bookmarkData base64EncodedDataWithOptions:0]
encoding:NSUTF8StringEncoding];
return base::SysNSStringToUTF8(base64data);
}
}
void ReadDialogPathsWithBookmarks(NSOpenPanel* dialog,
std::vector<base::FilePath>* paths,
std::vector<std::string>* bookmarks) {
NSArray* urls = [dialog URLs];
for (NSURL* url in urls)
if ([url isFileURL]) {
paths->push_back(base::FilePath(base::SysNSStringToUTF8([url path])));
bookmarks->push_back(GetBookmarkDataFromNSURL(url));
}
}
void ReadDialogPaths(NSOpenPanel* dialog, std::vector<base::FilePath>* paths) {
std::vector<std::string> ignored_bookmarks;
ReadDialogPathsWithBookmarks(dialog, paths, &ignored_bookmarks);
}
} // namespace
bool ShowOpenDialogSync(const DialogSettings& settings,
std::vector<base::FilePath>* paths) {
DCHECK(paths);
NSOpenPanel* dialog = [NSOpenPanel openPanel];
SetupDialog(dialog, settings);
SetupDialogForProperties(dialog, settings.properties);
int chosen = RunModalDialog(dialog, settings);
if (chosen == NSFileHandlingPanelCancelButton)
return false;
ReadDialogPaths(dialog, paths);
return true;
}
void OpenDialogCompletion(int chosen,
NSOpenPanel* dialog,
bool security_scoped_bookmarks,
atom::util::Promise promise) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
if (chosen == NSFileHandlingPanelCancelButton) {
dict.Set("canceled", true);
dict.Set("filePaths", std::vector<base::FilePath>());
#if defined(MAS_BUILD)
dict.Set("bookmarks", std::vector<std::string>());
#endif
promise.Resolve(dict.GetHandle());
} else {
std::vector<base::FilePath> paths;
dict.Set("canceled", false);
#if defined(MAS_BUILD)
std::vector<std::string> bookmarks;
if (security_scoped_bookmarks)
ReadDialogPathsWithBookmarks(dialog, &paths, &bookmarks);
else
ReadDialogPaths(dialog, &paths);
dict.Set("filePaths", paths);
dict.Set("bookmarks", bookmarks);
#else
ReadDialogPaths(dialog, &paths);
dict.Set("filePaths", paths);
#endif
promise.Resolve(dict.GetHandle());
}
}
void ShowOpenDialog(const DialogSettings& settings,
atom::util::Promise promise) {
NSOpenPanel* dialog = [NSOpenPanel openPanel];
SetupDialog(dialog, settings);
SetupDialogForProperties(dialog, settings.properties);
// Capture the value of the security_scoped_bookmarks settings flag
// and pass it to the completion handler.
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
__block atom::util::Promise p = std::move(promise);
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
settings.force_detached) {
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks,
std::move(p));
}];
} else {
NSWindow* window =
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
[dialog
beginSheetModalForWindow:window
completionHandler:^(NSInteger chosen) {
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks,
std::move(p));
}];
}
}
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
DCHECK(path);
NSSavePanel* dialog = [NSSavePanel savePanel];
SetupDialog(dialog, settings);
int chosen = RunModalDialog(dialog, settings);
if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL])
return false;
*path = base::FilePath(base::SysNSStringToUTF8([[dialog URL] path]));
return true;
}
void SaveDialogCompletion(int chosen,
NSSavePanel* dialog,
bool security_scoped_bookmarks,
atom::util::Promise promise) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
if (chosen == NSFileHandlingPanelCancelButton) {
dict.Set("canceled", true);
dict.Set("filePath", base::FilePath());
#if defined(MAS_BUILD)
dict.Set("bookmark", "");
#endif
} else {
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
dict.Set("filePath", base::FilePath(path));
dict.Set("canceled", false);
#if defined(MAS_BUILD)
std::string bookmark;
if (security_scoped_bookmarks) {
bookmark = GetBookmarkDataFromNSURL([dialog URL]);
dict.Set("bookmark", bookmark);
}
#endif
}
promise.Resolve(dict.GetHandle());
}
void ShowSaveDialog(const DialogSettings& settings,
atom::util::Promise promise) {
NSSavePanel* dialog = [NSSavePanel savePanel];
SetupDialog(dialog, settings);
[dialog setCanSelectHiddenExtension:YES];
// Capture the value of the security_scoped_bookmarks settings flag
// and pass it to the completion handler.
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
__block atom::util::Promise p = std::move(promise);
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
settings.force_detached) {
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks,
std::move(p));
}];
} else {
NSWindow* window =
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
[dialog
beginSheetModalForWindow:window
completionHandler:^(NSInteger chosen) {
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks,
std::move(p));
}];
}
}
} // namespace file_dialog

View file

@ -0,0 +1,334 @@
// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/file_dialog.h"
#include <windows.h> // windows.h must be included first
#include <atlbase.h> // atlbase.h for CComPtr
#include <shlobj.h>
#include <shobjidl.h>
#include "atom/browser/native_window_views.h"
#include "atom/browser/unresponsive_suppressor.h"
#include "base/files/file_util.h"
#include "base/i18n/case_conversion.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/registry.h"
namespace file_dialog {
DialogSettings::DialogSettings() = default;
DialogSettings::DialogSettings(const DialogSettings&) = default;
DialogSettings::~DialogSettings() = default;
namespace {
// Distinguish directories from regular files.
bool IsDirectory(const base::FilePath& path) {
base::File::Info file_info;
return base::GetFileInfo(path, &file_info) ? file_info.is_directory
: path.EndsWithSeparator();
}
void ConvertFilters(const Filters& filters,
std::vector<std::wstring>* buffer,
std::vector<COMDLG_FILTERSPEC>* filterspec) {
if (filters.empty()) {
COMDLG_FILTERSPEC spec = {L"All Files (*.*)", L"*.*"};
filterspec->push_back(spec);
return;
}
buffer->reserve(filters.size() * 2);
for (size_t i = 0; i < filters.size(); ++i) {
const Filter& filter = filters[i];
COMDLG_FILTERSPEC spec;
buffer->push_back(base::UTF8ToWide(filter.first));
spec.pszName = buffer->back().c_str();
std::vector<std::string> extensions(filter.second);
for (size_t j = 0; j < extensions.size(); ++j)
extensions[j].insert(0, "*.");
buffer->push_back(base::UTF8ToWide(base::JoinString(extensions, ";")));
spec.pszSpec = buffer->back().c_str();
filterspec->push_back(spec);
}
}
struct RunState {
base::Thread* dialog_thread;
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner;
};
bool CreateDialogThread(RunState* run_state) {
auto thread =
std::make_unique<base::Thread>(ELECTRON_PRODUCT_NAME "FileDialogThread");
thread->init_com_with_mta(false);
if (!thread->Start())
return false;
run_state->dialog_thread = thread.release();
run_state->ui_task_runner = base::ThreadTaskRunnerHandle::Get();
return true;
}
void OnDialogOpened(atom::util::Promise promise,
bool canceled,
std::vector<base::FilePath> paths) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
dict.Set("canceled", canceled);
dict.Set("filePaths", paths);
promise.Resolve(dict.GetHandle());
}
void RunOpenDialogInNewThread(const RunState& run_state,
const DialogSettings& settings,
atom::util::Promise promise) {
std::vector<base::FilePath> paths;
bool result = ShowOpenDialogSync(settings, &paths);
run_state.ui_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&OnDialogOpened, std::move(promise), !result, paths));
run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread);
}
void OnSaveDialogDone(atom::util::Promise promise,
bool canceled,
const base::FilePath path) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
dict.Set("canceled", canceled);
dict.Set("filePath", path);
promise.Resolve(dict.GetHandle());
}
void RunSaveDialogInNewThread(const RunState& run_state,
const DialogSettings& settings,
atom::util::Promise promise) {
base::FilePath path;
bool result = ShowSaveDialogSync(settings, &path);
run_state.ui_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&OnSaveDialogDone, std::move(promise), !result, path));
run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread);
}
} // namespace
static HRESULT GetFileNameFromShellItem(IShellItem* pShellItem,
SIGDN type,
LPWSTR lpstr,
size_t cchLength) {
assert(pShellItem != NULL);
LPWSTR lpstrName = NULL;
HRESULT hRet = pShellItem->GetDisplayName(type, &lpstrName);
if (SUCCEEDED(hRet)) {
if (wcslen(lpstrName) < cchLength) {
wcscpy_s(lpstr, cchLength, lpstrName);
} else {
NOTREACHED();
hRet = DISP_E_BUFFERTOOSMALL;
}
::CoTaskMemFree(lpstrName);
}
return hRet;
}
static void SetDefaultFolder(IFileDialog* dialog,
const base::FilePath file_path) {
std::wstring directory =
IsDirectory(file_path) ? file_path.value() : file_path.DirName().value();
ATL::CComPtr<IShellItem> folder_item;
HRESULT hr = SHCreateItemFromParsingName(directory.c_str(), NULL,
IID_PPV_ARGS(&folder_item));
if (SUCCEEDED(hr))
dialog->SetFolder(folder_item);
}
static HRESULT ShowFileDialog(IFileDialog* dialog,
const DialogSettings& settings) {
atom::UnresponsiveSuppressor suppressor;
HWND parent_window =
settings.parent_window
? static_cast<atom::NativeWindowViews*>(settings.parent_window)
->GetAcceleratedWidget()
: NULL;
return dialog->Show(parent_window);
}
static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
std::wstring file_part;
if (!IsDirectory(settings.default_path))
file_part = settings.default_path.BaseName().value();
dialog->SetFileName(file_part.c_str());
if (!settings.title.empty())
dialog->SetTitle(base::UTF8ToUTF16(settings.title).c_str());
if (!settings.button_label.empty())
dialog->SetOkButtonLabel(base::UTF8ToUTF16(settings.button_label).c_str());
std::vector<std::wstring> buffer;
std::vector<COMDLG_FILTERSPEC> filterspec;
ConvertFilters(settings.filters, &buffer, &filterspec);
if (!filterspec.empty()) {
dialog->SetFileTypes(filterspec.size(), filterspec.data());
}
// By default, *.* will be added to the file name if file type is "*.*". In
// Electron, we disable it to make a better experience.
//
// From MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/
// bb775970(v=vs.85).aspx
//
// If SetDefaultExtension is not called, the dialog will not update
// automatically when user choose a new file type in the file dialog.
//
// We set file extension to the first none-wildcard extension to make
// sure the dialog will update file extension automatically.
for (size_t i = 0; i < filterspec.size(); ++i) {
if (std::wstring(filterspec[i].pszSpec) != L"*.*") {
// SetFileTypeIndex is regarded as one-based index.
dialog->SetFileTypeIndex(i + 1);
dialog->SetDefaultExtension(filterspec[i].pszSpec);
break;
}
}
if (settings.default_path.IsAbsolute()) {
SetDefaultFolder(dialog, settings.default_path);
}
}
bool ShowOpenDialogSync(const DialogSettings& settings,
std::vector<base::FilePath>* paths) {
ATL::CComPtr<IFileOpenDialog> file_open_dialog;
HRESULT hr = file_open_dialog.CoCreateInstance(CLSID_FileOpenDialog);
if (FAILED(hr))
return false;
DWORD options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST;
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
options |= FOS_PICKFOLDERS;
if (settings.properties & FILE_DIALOG_MULTI_SELECTIONS)
options |= FOS_ALLOWMULTISELECT;
if (settings.properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
options |= FOS_FORCESHOWHIDDEN;
if (settings.properties & FILE_DIALOG_PROMPT_TO_CREATE)
options |= FOS_CREATEPROMPT;
file_open_dialog->SetOptions(options);
ApplySettings(file_open_dialog, settings);
hr = ShowFileDialog(file_open_dialog, settings);
if (FAILED(hr))
return false;
ATL::CComPtr<IShellItemArray> items;
hr = file_open_dialog->GetResults(&items);
if (FAILED(hr))
return false;
ATL::CComPtr<IShellItem> item;
DWORD count = 0;
hr = items->GetCount(&count);
if (FAILED(hr))
return false;
paths->reserve(count);
for (DWORD i = 0; i < count; ++i) {
hr = items->GetItemAt(i, &item);
if (FAILED(hr))
return false;
wchar_t file_name[MAX_PATH];
hr = GetFileNameFromShellItem(item, SIGDN_FILESYSPATH, file_name,
base::size(file_name));
if (FAILED(hr))
return false;
paths->push_back(base::FilePath(file_name));
}
return true;
}
void ShowOpenDialog(const DialogSettings& settings,
atom::util::Promise promise) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
RunState run_state;
if (!CreateDialogThread(&run_state)) {
dict.Set("canceled", true);
dict.Set("filePaths", std::vector<base::FilePath>());
promise.Resolve(dict.GetHandle());
} else {
run_state.dialog_thread->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&RunOpenDialogInNewThread, run_state,
settings, std::move(promise)));
}
}
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
ATL::CComPtr<IFileSaveDialog> file_save_dialog;
HRESULT hr = file_save_dialog.CoCreateInstance(CLSID_FileSaveDialog);
if (FAILED(hr))
return false;
file_save_dialog->SetOptions(FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST |
FOS_OVERWRITEPROMPT);
ApplySettings(file_save_dialog, settings);
hr = ShowFileDialog(file_save_dialog, settings);
if (FAILED(hr))
return false;
CComPtr<IShellItem> pItem;
hr = file_save_dialog->GetResult(&pItem);
if (FAILED(hr))
return false;
PWSTR result_path = nullptr;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &result_path);
if (!SUCCEEDED(hr))
return false;
*path = base::FilePath(result_path);
CoTaskMemFree(result_path);
return true;
}
void ShowSaveDialog(const DialogSettings& settings,
atom::util::Promise promise) {
RunState run_state;
if (!CreateDialogThread(&run_state)) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
dict.Set("canceled", true);
dict.Set("filePath", base::FilePath());
promise.Resolve(dict.GetHandle());
} else {
run_state.dialog_thread->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&RunSaveDialogInNewThread, run_state,
settings, std::move(promise)));
}
}
} // namespace file_dialog

View file

@ -0,0 +1,19 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#include "atom/browser/ui/inspectable_web_contents.h"
#include "atom/browser/ui/inspectable_web_contents_impl.h"
namespace atom {
InspectableWebContents* InspectableWebContents::Create(
content::WebContents* web_contents,
PrefService* pref_service,
bool is_guest) {
return new InspectableWebContentsImpl(web_contents, pref_service, is_guest);
}
} // namespace atom

View file

@ -0,0 +1,66 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_H_
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_H_
#include <string>
#include "content/public/browser/web_contents.h"
namespace base {
class Value;
}
namespace content {
class DevToolsAgentHost;
}
class PrefService;
namespace atom {
class InspectableWebContentsDelegate;
class InspectableWebContentsView;
// TODO(zcbenz): Remove this abstract wrapper and rename
// InspectableWebContentsImpl to InspectableWebContents instead.
class InspectableWebContents {
public:
// The returned InspectableWebContents takes ownership of the passed-in
// WebContents.
static InspectableWebContents* Create(content::WebContents* web_contents,
PrefService* pref_service,
bool is_guest);
virtual ~InspectableWebContents() {}
virtual InspectableWebContentsView* GetView() const = 0;
virtual content::WebContents* GetWebContents() const = 0;
virtual content::WebContents* GetDevToolsWebContents() const = 0;
// The delegate manages its own life.
virtual void SetDelegate(InspectableWebContentsDelegate* delegate) = 0;
virtual InspectableWebContentsDelegate* GetDelegate() const = 0;
virtual bool IsGuest() const = 0;
virtual void ReleaseWebContents() = 0;
virtual void SetDevToolsWebContents(content::WebContents* devtools) = 0;
virtual void SetDockState(const std::string& state) = 0;
virtual void ShowDevTools(bool activate) = 0;
virtual void CloseDevTools() = 0;
virtual bool IsDevToolsViewShowing() = 0;
virtual void AttachTo(scoped_refptr<content::DevToolsAgentHost>) = 0;
virtual void Detach() = 0;
virtual void CallClientFunction(const std::string& function_name,
const base::Value* arg1 = nullptr,
const base::Value* arg2 = nullptr,
const base::Value* arg3 = nullptr) = 0;
virtual void InspectElement(int x, int y) = 0;
};
} // namespace atom
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_H_

View file

@ -0,0 +1,42 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_DELEGATE_H_
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_DELEGATE_H_
#include <string>
#include "base/files/file_path.h"
namespace atom {
class InspectableWebContentsDelegate {
public:
virtual ~InspectableWebContentsDelegate() {}
// Requested by WebContents of devtools.
virtual void DevToolsReloadPage() {}
virtual void DevToolsSaveToFile(const std::string& url,
const std::string& content,
bool save_as) {}
virtual void DevToolsAppendToFile(const std::string& url,
const std::string& content) {}
virtual void DevToolsRequestFileSystems() {}
virtual void DevToolsAddFileSystem(const std::string& type,
const base::FilePath& file_system_path) {}
virtual void DevToolsRemoveFileSystem(
const base::FilePath& file_system_path) {}
virtual void DevToolsIndexPath(int request_id,
const std::string& file_system_path,
const std::string& excluded_folders) {}
virtual void DevToolsStopIndexing(int request_id) {}
virtual void DevToolsSearchInPath(int request_id,
const std::string& file_system_path,
const std::string& query) {}
};
} // namespace atom
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_DELEGATE_H_

View file

@ -0,0 +1,917 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#include "atom/browser/ui/inspectable_web_contents_impl.h"
#include <memory>
#include <utility>
#include "atom/browser/ui/inspectable_web_contents_delegate.h"
#include "atom/browser/ui/inspectable_web_contents_view.h"
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
#include "atom/common/platform_util.h"
#include "base/base64.h"
#include "base/guid.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/pattern.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/values.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/user_agent.h"
#include "ipc/ipc_channel.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
#include "third_party/blink/public/common/logging/logging_utils.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
namespace atom {
namespace {
const double kPresetZoomFactors[] = {0.25, 0.333, 0.5, 0.666, 0.75, 0.9,
1.0, 1.1, 1.25, 1.5, 1.75, 2.0,
2.5, 3.0, 4.0, 5.0};
const char kChromeUIDevToolsURL[] =
"devtools://devtools/bundled/devtools_app.html?"
"remoteBase=%s&"
"can_dock=%s&"
"toolbarColor=rgba(223,223,223,1)&"
"textColor=rgba(0,0,0,1)&"
"experiments=true";
const char kChromeUIDevToolsRemoteFrontendBase[] =
"https://devtools-frontend.appspot.com/";
const char kChromeUIDevToolsRemoteFrontendPath[] = "serve_file";
const char kDevToolsBoundsPref[] = "electron.devtools.bounds";
const char kDevToolsZoomPref[] = "electron.devtools.zoom";
const char kDevToolsPreferences[] = "electron.devtools.preferences";
const char kFrontendHostId[] = "id";
const char kFrontendHostMethod[] = "method";
const char kFrontendHostParams[] = "params";
const char kTitleFormat[] = "Developer Tools - %s";
const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4;
// Stores all instances of InspectableWebContentsImpl.
InspectableWebContentsImpl::List g_web_contents_instances_;
base::Value RectToDictionary(const gfx::Rect& bounds) {
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetKey("x", base::Value(bounds.x()));
dict.SetKey("y", base::Value(bounds.y()));
dict.SetKey("width", base::Value(bounds.width()));
dict.SetKey("height", base::Value(bounds.height()));
return dict;
}
gfx::Rect DictionaryToRect(const base::Value* dict) {
const base::Value* found = dict->FindKey("x");
int x = found ? found->GetInt() : 0;
found = dict->FindKey("y");
int y = found ? found->GetInt() : 0;
found = dict->FindKey("width");
int width = found ? found->GetInt() : 800;
found = dict->FindKey("height");
int height = found ? found->GetInt() : 600;
return gfx::Rect(x, y, width, height);
}
bool IsPointInRect(const gfx::Point& point, const gfx::Rect& rect) {
return point.x() > rect.x() && point.x() < (rect.width() + rect.x()) &&
point.y() > rect.y() && point.y() < (rect.height() + rect.y());
}
bool IsPointInScreen(const gfx::Point& point) {
for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) {
if (IsPointInRect(point, display.bounds()))
return true;
}
return false;
}
void SetZoomLevelForWebContents(content::WebContents* web_contents,
double level) {
content::HostZoomMap::SetZoomLevel(web_contents, level);
}
double GetNextZoomLevel(double level, bool out) {
double factor = content::ZoomLevelToZoomFactor(level);
size_t size = base::size(kPresetZoomFactors);
for (size_t i = 0; i < size; ++i) {
if (!content::ZoomValuesEqual(kPresetZoomFactors[i], factor))
continue;
if (out && i > 0)
return content::ZoomFactorToZoomLevel(kPresetZoomFactors[i - 1]);
if (!out && i != size - 1)
return content::ZoomFactorToZoomLevel(kPresetZoomFactors[i + 1]);
}
return level;
}
GURL GetRemoteBaseURL() {
return GURL(base::StringPrintf("%s%s/%s/",
kChromeUIDevToolsRemoteFrontendBase,
kChromeUIDevToolsRemoteFrontendPath,
content::GetWebKitRevision().c_str()));
}
GURL GetDevToolsURL(bool can_dock) {
auto url_string = base::StringPrintf(kChromeUIDevToolsURL,
GetRemoteBaseURL().spec().c_str(),
can_dock ? "true" : "");
return GURL(url_string);
}
} // namespace
class InspectableWebContentsImpl::NetworkResourceLoader
: public network::SimpleURLLoaderStreamConsumer {
public:
NetworkResourceLoader(int stream_id,
InspectableWebContentsImpl* bindings,
std::unique_ptr<network::SimpleURLLoader> loader,
network::mojom::URLLoaderFactory* url_loader_factory,
const DispatchCallback& callback)
: stream_id_(stream_id),
bindings_(bindings),
loader_(std::move(loader)),
callback_(callback) {
loader_->SetOnResponseStartedCallback(base::BindOnce(
&NetworkResourceLoader::OnResponseStarted, base::Unretained(this)));
loader_->DownloadAsStream(url_loader_factory, this);
}
NetworkResourceLoader(const NetworkResourceLoader&) = delete;
NetworkResourceLoader& operator=(const NetworkResourceLoader&) = delete;
private:
void OnResponseStarted(const GURL& final_url,
const network::ResourceResponseHead& response_head) {
response_headers_ = response_head.headers;
}
void OnDataReceived(base::StringPiece chunk,
base::OnceClosure resume) override {
base::Value chunkValue;
bool encoded = !base::IsStringUTF8(chunk);
if (encoded) {
std::string encoded_string;
base::Base64Encode(chunk, &encoded_string);
chunkValue = base::Value(std::move(encoded_string));
} else {
chunkValue = base::Value(chunk);
}
base::Value id(stream_id_);
base::Value encodedValue(encoded);
bindings_->CallClientFunction("DevToolsAPI.streamWrite", &id, &chunkValue,
&encodedValue);
std::move(resume).Run();
}
void OnComplete(bool success) override {
base::DictionaryValue response;
response.SetInteger("statusCode", response_headers_
? response_headers_->response_code()
: 200);
auto headers = std::make_unique<base::DictionaryValue>();
size_t iterator = 0;
std::string name;
std::string value;
while (response_headers_ &&
response_headers_->EnumerateHeaderLines(&iterator, &name, &value))
headers->SetString(name, value);
response.Set("headers", std::move(headers));
callback_.Run(&response);
bindings_->loaders_.erase(bindings_->loaders_.find(this));
}
void OnRetry(base::OnceClosure start_retry) override {}
const int stream_id_;
InspectableWebContentsImpl* const bindings_;
std::unique_ptr<network::SimpleURLLoader> loader_;
DispatchCallback callback_;
scoped_refptr<net::HttpResponseHeaders> response_headers_;
};
// Implemented separately on each platform.
InspectableWebContentsView* CreateInspectableContentsView(
InspectableWebContentsImpl* inspectable_web_contents_impl);
// static
const InspectableWebContentsImpl::List& InspectableWebContentsImpl::GetAll() {
return g_web_contents_instances_;
}
// static
void InspectableWebContentsImpl::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(kDevToolsBoundsPref,
RectToDictionary(gfx::Rect(0, 0, 800, 600)));
registry->RegisterDoublePref(kDevToolsZoomPref, 0.);
registry->RegisterDictionaryPref(kDevToolsPreferences);
}
InspectableWebContentsImpl::InspectableWebContentsImpl(
content::WebContents* web_contents,
PrefService* pref_service,
bool is_guest)
: frontend_loaded_(false),
can_dock_(true),
delegate_(nullptr),
pref_service_(pref_service),
web_contents_(web_contents),
is_guest_(is_guest),
view_(CreateInspectableContentsView(this)),
weak_factory_(this) {
const base::Value* bounds_dict = pref_service_->Get(kDevToolsBoundsPref);
if (bounds_dict->is_dict()) {
devtools_bounds_ = DictionaryToRect(bounds_dict);
// Sometimes the devtools window is out of screen or has too small size.
if (devtools_bounds_.height() < 100 || devtools_bounds_.width() < 100) {
devtools_bounds_.set_height(600);
devtools_bounds_.set_width(800);
}
if (!IsPointInScreen(devtools_bounds_.origin())) {
gfx::Rect display;
if (!is_guest && web_contents->GetNativeView()) {
display = display::Screen::GetScreen()
->GetDisplayNearestView(web_contents->GetNativeView())
.bounds();
} else {
display = display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
}
devtools_bounds_.set_x(display.x() +
(display.width() - devtools_bounds_.width()) / 2);
devtools_bounds_.set_y(
display.y() + (display.height() - devtools_bounds_.height()) / 2);
}
}
g_web_contents_instances_.push_back(this);
}
InspectableWebContentsImpl::~InspectableWebContentsImpl() {
g_web_contents_instances_.remove(this);
// Unsubscribe from devtools and Clean up resources.
if (GetDevToolsWebContents()) {
if (managed_devtools_web_contents_)
managed_devtools_web_contents_->SetDelegate(nullptr);
// Calling this also unsubscribes the observer, so WebContentsDestroyed
// won't be called again.
WebContentsDestroyed();
}
// Let destructor destroy managed_devtools_web_contents_.
}
InspectableWebContentsView* InspectableWebContentsImpl::GetView() const {
return view_.get();
}
content::WebContents* InspectableWebContentsImpl::GetWebContents() const {
return web_contents_.get();
}
content::WebContents* InspectableWebContentsImpl::GetDevToolsWebContents()
const {
if (external_devtools_web_contents_)
return external_devtools_web_contents_;
else
return managed_devtools_web_contents_.get();
}
void InspectableWebContentsImpl::InspectElement(int x, int y) {
if (agent_host_)
agent_host_->InspectElement(web_contents_->GetMainFrame(), x, y);
}
void InspectableWebContentsImpl::SetDelegate(
InspectableWebContentsDelegate* delegate) {
delegate_ = delegate;
}
InspectableWebContentsDelegate* InspectableWebContentsImpl::GetDelegate()
const {
return delegate_;
}
bool InspectableWebContentsImpl::IsGuest() const {
return is_guest_;
}
void InspectableWebContentsImpl::ReleaseWebContents() {
web_contents_.release();
}
void InspectableWebContentsImpl::SetDockState(const std::string& state) {
if (state == "detach") {
can_dock_ = false;
} else {
can_dock_ = true;
dock_state_ = state;
}
}
void InspectableWebContentsImpl::SetDevToolsWebContents(
content::WebContents* devtools) {
if (!managed_devtools_web_contents_)
external_devtools_web_contents_ = devtools;
}
void InspectableWebContentsImpl::ShowDevTools(bool activate) {
if (embedder_message_dispatcher_) {
if (managed_devtools_web_contents_)
view_->ShowDevTools(activate);
return;
}
activate_ = activate;
// Show devtools only after it has done loading, this is to make sure the
// SetIsDocked is called *BEFORE* ShowDevTools.
embedder_message_dispatcher_ =
DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this);
if (!external_devtools_web_contents_) { // no external devtools
managed_devtools_web_contents_ = content::WebContents::Create(
content::WebContents::CreateParams(web_contents_->GetBrowserContext()));
managed_devtools_web_contents_->SetDelegate(this);
}
Observe(GetDevToolsWebContents());
AttachTo(content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get()));
GetDevToolsWebContents()->GetController().LoadURL(
GetDevToolsURL(can_dock_), content::Referrer(),
ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
}
void InspectableWebContentsImpl::CloseDevTools() {
if (GetDevToolsWebContents()) {
frontend_loaded_ = false;
if (managed_devtools_web_contents_) {
view_->CloseDevTools();
managed_devtools_web_contents_.reset();
}
embedder_message_dispatcher_.reset();
if (!IsGuest())
web_contents_->Focus();
}
}
bool InspectableWebContentsImpl::IsDevToolsViewShowing() {
return managed_devtools_web_contents_ && view_->IsDevToolsViewShowing();
}
void InspectableWebContentsImpl::AttachTo(
scoped_refptr<content::DevToolsAgentHost> host) {
Detach();
agent_host_ = std::move(host);
// We could use ForceAttachClient here if problem arises with
// devtools multiple session support.
agent_host_->AttachClient(this);
}
void InspectableWebContentsImpl::Detach() {
if (agent_host_)
agent_host_->DetachClient(this);
agent_host_ = nullptr;
}
void InspectableWebContentsImpl::Reattach(const DispatchCallback& callback) {
if (agent_host_) {
agent_host_->DetachClient(this);
agent_host_->AttachClient(this);
}
callback.Run(nullptr);
}
void InspectableWebContentsImpl::CallClientFunction(
const std::string& function_name,
const base::Value* arg1,
const base::Value* arg2,
const base::Value* arg3) {
if (!GetDevToolsWebContents())
return;
std::string javascript = function_name + "(";
if (arg1) {
std::string json;
base::JSONWriter::Write(*arg1, &json);
javascript.append(json);
if (arg2) {
base::JSONWriter::Write(*arg2, &json);
javascript.append(", ").append(json);
if (arg3) {
base::JSONWriter::Write(*arg3, &json);
javascript.append(", ").append(json);
}
}
}
javascript.append(");");
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(
base::UTF8ToUTF16(javascript), base::NullCallback());
}
gfx::Rect InspectableWebContentsImpl::GetDevToolsBounds() const {
return devtools_bounds_;
}
void InspectableWebContentsImpl::SaveDevToolsBounds(const gfx::Rect& bounds) {
pref_service_->Set(kDevToolsBoundsPref, RectToDictionary(bounds));
devtools_bounds_ = bounds;
}
double InspectableWebContentsImpl::GetDevToolsZoomLevel() const {
return pref_service_->GetDouble(kDevToolsZoomPref);
}
void InspectableWebContentsImpl::UpdateDevToolsZoomLevel(double level) {
pref_service_->SetDouble(kDevToolsZoomPref, level);
}
void InspectableWebContentsImpl::ActivateWindow() {
// Set the zoom level.
SetZoomLevelForWebContents(GetDevToolsWebContents(), GetDevToolsZoomLevel());
}
void InspectableWebContentsImpl::CloseWindow() {
GetDevToolsWebContents()->DispatchBeforeUnload(false /* auto_cancel */);
}
void InspectableWebContentsImpl::LoadCompleted() {
frontend_loaded_ = true;
if (managed_devtools_web_contents_)
view_->ShowDevTools(activate_);
// If the devtools can dock, "SetIsDocked" will be called by devtools itself.
if (!can_dock_) {
SetIsDocked(DispatchCallback(), false);
} else {
if (dock_state_.empty()) {
const base::DictionaryValue* prefs =
pref_service_->GetDictionary(kDevToolsPreferences);
std::string current_dock_state;
prefs->GetString("currentDockState", &current_dock_state);
base::RemoveChars(current_dock_state, "\"", &dock_state_);
}
base::string16 javascript = base::UTF8ToUTF16(
"Components.dockController.setDockSide(\"" + dock_state_ + "\");");
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(
javascript, base::NullCallback());
}
if (view_->GetDelegate())
view_->GetDelegate()->DevToolsOpened();
}
void InspectableWebContentsImpl::SetInspectedPageBounds(const gfx::Rect& rect) {
DevToolsContentsResizingStrategy strategy(rect);
if (contents_resizing_strategy_.Equals(strategy))
return;
contents_resizing_strategy_.CopyFrom(strategy);
if (managed_devtools_web_contents_)
view_->SetContentsResizingStrategy(contents_resizing_strategy_);
}
void InspectableWebContentsImpl::InspectElementCompleted() {}
void InspectableWebContentsImpl::InspectedURLChanged(const std::string& url) {
if (managed_devtools_web_contents_)
view_->SetTitle(
base::UTF8ToUTF16(base::StringPrintf(kTitleFormat, url.c_str())));
}
void InspectableWebContentsImpl::LoadNetworkResource(
const DispatchCallback& callback,
const std::string& url,
const std::string& headers,
int stream_id) {
GURL gurl(url);
if (!gurl.is_valid()) {
base::DictionaryValue response;
response.SetInteger("statusCode", 404);
callback.Run(&response);
return;
}
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = gurl;
resource_request->headers.AddHeadersFromString(headers);
auto* partition = content::BrowserContext::GetDefaultStoragePartition(
GetDevToolsWebContents()->GetBrowserContext());
auto factory = partition->GetURLLoaderFactoryForBrowserProcess();
auto simple_url_loader = network::SimpleURLLoader::Create(
std::move(resource_request), NO_TRAFFIC_ANNOTATION_YET);
auto resource_loader = std::make_unique<NetworkResourceLoader>(
stream_id, this, std::move(simple_url_loader), factory.get(), callback);
loaders_.insert(std::move(resource_loader));
}
void InspectableWebContentsImpl::SetIsDocked(const DispatchCallback& callback,
bool docked) {
if (managed_devtools_web_contents_)
view_->SetIsDocked(docked, activate_);
if (!callback.is_null())
callback.Run(nullptr);
}
void InspectableWebContentsImpl::OpenInNewTab(const std::string& url) {}
void InspectableWebContentsImpl::ShowItemInFolder(
const std::string& file_system_path) {
if (file_system_path.empty())
return;
base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path);
platform_util::OpenItem(path);
}
void InspectableWebContentsImpl::SaveToFile(const std::string& url,
const std::string& content,
bool save_as) {
if (delegate_)
delegate_->DevToolsSaveToFile(url, content, save_as);
}
void InspectableWebContentsImpl::AppendToFile(const std::string& url,
const std::string& content) {
if (delegate_)
delegate_->DevToolsAppendToFile(url, content);
}
void InspectableWebContentsImpl::RequestFileSystems() {
if (delegate_)
delegate_->DevToolsRequestFileSystems();
}
void InspectableWebContentsImpl::AddFileSystem(const std::string& type) {
if (delegate_)
delegate_->DevToolsAddFileSystem(type, base::FilePath());
}
void InspectableWebContentsImpl::RemoveFileSystem(
const std::string& file_system_path) {
if (delegate_)
delegate_->DevToolsRemoveFileSystem(
base::FilePath::FromUTF8Unsafe(file_system_path));
}
void InspectableWebContentsImpl::UpgradeDraggedFileSystemPermissions(
const std::string& file_system_url) {}
void InspectableWebContentsImpl::IndexPath(
int request_id,
const std::string& file_system_path,
const std::string& excluded_folders) {
if (delegate_)
delegate_->DevToolsIndexPath(request_id, file_system_path,
excluded_folders);
}
void InspectableWebContentsImpl::StopIndexing(int request_id) {
if (delegate_)
delegate_->DevToolsStopIndexing(request_id);
}
void InspectableWebContentsImpl::SearchInPath(
int request_id,
const std::string& file_system_path,
const std::string& query) {
if (delegate_)
delegate_->DevToolsSearchInPath(request_id, file_system_path, query);
}
void InspectableWebContentsImpl::SetWhitelistedShortcuts(
const std::string& message) {}
void InspectableWebContentsImpl::SetEyeDropperActive(bool active) {}
void InspectableWebContentsImpl::ShowCertificateViewer(
const std::string& cert_chain) {}
void InspectableWebContentsImpl::ZoomIn() {
double new_level = GetNextZoomLevel(GetDevToolsZoomLevel(), false);
SetZoomLevelForWebContents(GetDevToolsWebContents(), new_level);
UpdateDevToolsZoomLevel(new_level);
}
void InspectableWebContentsImpl::ZoomOut() {
double new_level = GetNextZoomLevel(GetDevToolsZoomLevel(), true);
SetZoomLevelForWebContents(GetDevToolsWebContents(), new_level);
UpdateDevToolsZoomLevel(new_level);
}
void InspectableWebContentsImpl::ResetZoom() {
SetZoomLevelForWebContents(GetDevToolsWebContents(), 0.);
UpdateDevToolsZoomLevel(0.);
}
void InspectableWebContentsImpl::SetDevicesDiscoveryConfig(
bool discover_usb_devices,
bool port_forwarding_enabled,
const std::string& port_forwarding_config,
bool network_discovery_enabled,
const std::string& network_discovery_config) {}
void InspectableWebContentsImpl::SetDevicesUpdatesEnabled(bool enabled) {}
void InspectableWebContentsImpl::PerformActionOnRemotePage(
const std::string& page_id,
const std::string& action) {}
void InspectableWebContentsImpl::OpenRemotePage(const std::string& browser_id,
const std::string& url) {}
void InspectableWebContentsImpl::OpenNodeFrontend() {}
void InspectableWebContentsImpl::DispatchProtocolMessageFromDevToolsFrontend(
const std::string& message) {
// If the devtools wants to reload the page, hijack the message and handle it
// to the delegate.
if (base::MatchPattern(message,
"{\"id\":*,"
"\"method\":\"Page.reload\","
"\"params\":*}")) {
if (delegate_)
delegate_->DevToolsReloadPage();
return;
}
if (agent_host_)
agent_host_->DispatchProtocolMessage(this, message);
}
void InspectableWebContentsImpl::SendJsonRequest(
const DispatchCallback& callback,
const std::string& browser_id,
const std::string& url) {
callback.Run(nullptr);
}
void InspectableWebContentsImpl::GetPreferences(
const DispatchCallback& callback) {
const base::DictionaryValue* prefs =
pref_service_->GetDictionary(kDevToolsPreferences);
callback.Run(prefs);
}
void InspectableWebContentsImpl::SetPreference(const std::string& name,
const std::string& value) {
DictionaryPrefUpdate update(pref_service_, kDevToolsPreferences);
update.Get()->SetKey(name, base::Value(value));
}
void InspectableWebContentsImpl::RemovePreference(const std::string& name) {
DictionaryPrefUpdate update(pref_service_, kDevToolsPreferences);
update.Get()->RemoveWithoutPathExpansion(name, nullptr);
}
void InspectableWebContentsImpl::ClearPreferences() {
DictionaryPrefUpdate update(pref_service_, kDevToolsPreferences);
update.Get()->Clear();
}
void InspectableWebContentsImpl::ConnectionReady() {}
void InspectableWebContentsImpl::RegisterExtensionsAPI(
const std::string& origin,
const std::string& script) {
extensions_api_[origin + "/"] = script;
}
void InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend(
const std::string& message) {
// TODO(alexeykuzmin): Should we expect it to exist?
if (!embedder_message_dispatcher_) {
return;
}
std::string method;
base::ListValue empty_params;
base::ListValue* params = &empty_params;
base::DictionaryValue* dict = nullptr;
std::unique_ptr<base::Value> parsed_message(
base::JSONReader::ReadDeprecated(message));
if (!parsed_message || !parsed_message->GetAsDictionary(&dict) ||
!dict->GetString(kFrontendHostMethod, &method) ||
(dict->HasKey(kFrontendHostParams) &&
!dict->GetList(kFrontendHostParams, &params))) {
LOG(ERROR) << "Invalid message was sent to embedder: " << message;
return;
}
int id = 0;
dict->GetInteger(kFrontendHostId, &id);
embedder_message_dispatcher_->Dispatch(
base::BindRepeating(&InspectableWebContentsImpl::SendMessageAck,
weak_factory_.GetWeakPtr(), id),
method, params);
}
void InspectableWebContentsImpl::DispatchProtocolMessage(
content::DevToolsAgentHost* agent_host,
const std::string& message) {
if (!frontend_loaded_)
return;
if (message.length() < kMaxMessageChunkSize) {
base::string16 javascript =
base::UTF8ToUTF16("DevToolsAPI.dispatchMessage(" + message + ");");
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(
javascript, base::NullCallback());
return;
}
base::Value total_size(static_cast<int>(message.length()));
for (size_t pos = 0; pos < message.length(); pos += kMaxMessageChunkSize) {
base::Value message_value(message.substr(pos, kMaxMessageChunkSize));
CallClientFunction("DevToolsAPI.dispatchMessageChunk", &message_value,
pos ? nullptr : &total_size, nullptr);
}
}
void InspectableWebContentsImpl::AgentHostClosed(
content::DevToolsAgentHost* agent_host) {}
void InspectableWebContentsImpl::RenderFrameHostChanged(
content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) {
if (new_host->GetParent())
return;
frontend_host_ = content::DevToolsFrontendHost::Create(
new_host,
base::BindRepeating(
&InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend,
weak_factory_.GetWeakPtr()));
}
void InspectableWebContentsImpl::WebContentsDestroyed() {
frontend_loaded_ = false;
external_devtools_web_contents_ = nullptr;
Observe(nullptr);
Detach();
embedder_message_dispatcher_.reset();
if (view_ && view_->GetDelegate())
view_->GetDelegate()->DevToolsClosed();
}
bool InspectableWebContentsImpl::DidAddMessageToConsole(
content::WebContents* source,
blink::mojom::ConsoleMessageLevel level,
const base::string16& message,
int32_t line_no,
const base::string16& source_id) {
logging::LogMessage("CONSOLE", line_no,
blink::ConsoleMessageLevelToLogSeverity(level))
.stream()
<< "\"" << message << "\", source: " << source_id << " (" << line_no
<< ")";
return true;
}
bool InspectableWebContentsImpl::ShouldCreateWebContents(
content::WebContents* web_contents,
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
int32_t route_id,
int32_t main_frame_route_id,
int32_t main_frame_widget_route_id,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) {
return false;
}
bool InspectableWebContentsImpl::HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) {
auto* delegate = web_contents_->GetDelegate();
return !delegate || delegate->HandleKeyboardEvent(source, event);
}
void InspectableWebContentsImpl::CloseContents(content::WebContents* source) {
// This is where the devtools closes itself (by clicking the x button).
CloseDevTools();
}
content::ColorChooser* InspectableWebContentsImpl::OpenColorChooser(
content::WebContents* source,
SkColor color,
const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
auto* delegate = web_contents_->GetDelegate();
if (delegate)
return delegate->OpenColorChooser(source, color, suggestions);
return nullptr;
}
void InspectableWebContentsImpl::RunFileChooser(
content::RenderFrameHost* render_frame_host,
std::unique_ptr<content::FileSelectListener> listener,
const blink::mojom::FileChooserParams& params) {
auto* delegate = web_contents_->GetDelegate();
if (delegate)
delegate->RunFileChooser(render_frame_host, std::move(listener), params);
}
void InspectableWebContentsImpl::EnumerateDirectory(
content::WebContents* source,
std::unique_ptr<content::FileSelectListener> listener,
const base::FilePath& path) {
auto* delegate = web_contents_->GetDelegate();
if (delegate)
delegate->EnumerateDirectory(source, std::move(listener), path);
}
void InspectableWebContentsImpl::OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) {
#if defined(TOOLKIT_VIEWS)
if (view_->GetDelegate())
view_->GetDelegate()->DevToolsFocused();
#endif
}
void InspectableWebContentsImpl::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) {
if (navigation_handle->IsInMainFrame()) {
if (navigation_handle->GetRenderFrameHost() ==
GetDevToolsWebContents()->GetMainFrame() &&
frontend_host_) {
return;
}
frontend_host_ = content::DevToolsFrontendHost::Create(
web_contents()->GetMainFrame(),
base::BindRepeating(
&InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend,
base::Unretained(this)));
return;
}
}
void InspectableWebContentsImpl::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (navigation_handle->IsInMainFrame() ||
!navigation_handle->GetURL().SchemeIs("chrome-extension") ||
!navigation_handle->HasCommitted())
return;
content::RenderFrameHost* frame = navigation_handle->GetRenderFrameHost();
auto origin = navigation_handle->GetURL().GetOrigin().spec();
auto it = extensions_api_.find(origin);
if (it == extensions_api_.end())
return;
// Injected Script from devtools frontend doesn't expose chrome,
// most likely bug in chromium.
base::ReplaceFirstSubstringAfterOffset(&it->second, 0, "var chrome",
"var chrome = window.chrome ");
auto script = base::StringPrintf("%s(\"%s\")", it->second.c_str(),
base::GenerateGUID().c_str());
// Invoking content::DevToolsFrontendHost::SetupExtensionsAPI(frame, script);
// should be enough, but it seems to be a noop currently.
frame->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script),
base::NullCallback());
}
void InspectableWebContentsImpl::SendMessageAck(int request_id,
const base::Value* arg) {
base::Value id_value(request_id);
CallClientFunction("DevToolsAPI.embedderMessageAck", &id_value, arg, nullptr);
}
} // namespace atom

View file

@ -0,0 +1,250 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_IMPL_H_
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_IMPL_H_
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "atom/browser/ui/inspectable_web_contents.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
#include "chrome/browser/devtools/devtools_embedder_message_dispatcher.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/devtools_frontend_host.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/gfx/geometry/rect.h"
class PrefService;
class PrefRegistrySimple;
namespace atom {
class InspectableWebContentsDelegate;
class InspectableWebContentsView;
class InspectableWebContentsImpl
: public InspectableWebContents,
public content::DevToolsAgentHostClient,
public content::WebContentsObserver,
public content::WebContentsDelegate,
public DevToolsEmbedderMessageDispatcher::Delegate {
public:
using List = std::list<InspectableWebContentsImpl*>;
static const List& GetAll();
static void RegisterPrefs(PrefRegistrySimple* pref_registry);
InspectableWebContentsImpl(content::WebContents* web_contents,
PrefService* pref_service,
bool is_guest);
~InspectableWebContentsImpl() override;
InspectableWebContentsView* GetView() const override;
content::WebContents* GetWebContents() const override;
content::WebContents* GetDevToolsWebContents() const override;
void SetDelegate(InspectableWebContentsDelegate* delegate) override;
InspectableWebContentsDelegate* GetDelegate() const override;
bool IsGuest() const override;
void ReleaseWebContents() override;
void SetDevToolsWebContents(content::WebContents* devtools) override;
void SetDockState(const std::string& state) override;
void ShowDevTools(bool activate) override;
void CloseDevTools() override;
bool IsDevToolsViewShowing() override;
void AttachTo(scoped_refptr<content::DevToolsAgentHost>) override;
void Detach() override;
void CallClientFunction(const std::string& function_name,
const base::Value* arg1,
const base::Value* arg2,
const base::Value* arg3) override;
void InspectElement(int x, int y) override;
// Return the last position and size of devtools window.
gfx::Rect GetDevToolsBounds() const;
void SaveDevToolsBounds(const gfx::Rect& bounds);
// Return the last set zoom level of devtools window.
double GetDevToolsZoomLevel() const;
void UpdateDevToolsZoomLevel(double level);
private:
// DevToolsEmbedderMessageDispacher::Delegate
void ActivateWindow() override;
void CloseWindow() override;
void LoadCompleted() override;
void SetInspectedPageBounds(const gfx::Rect& rect) override;
void InspectElementCompleted() override;
void InspectedURLChanged(const std::string& url) override;
void LoadNetworkResource(const DispatchCallback& callback,
const std::string& url,
const std::string& headers,
int stream_id) override;
void SetIsDocked(const DispatchCallback& callback, bool is_docked) override;
void OpenInNewTab(const std::string& url) override;
void ShowItemInFolder(const std::string& file_system_path) override;
void SaveToFile(const std::string& url,
const std::string& content,
bool save_as) override;
void AppendToFile(const std::string& url,
const std::string& content) override;
void RequestFileSystems() override;
void AddFileSystem(const std::string& type) override;
void RemoveFileSystem(const std::string& file_system_path) override;
void UpgradeDraggedFileSystemPermissions(
const std::string& file_system_url) override;
void IndexPath(int index_request_id,
const std::string& file_system_path,
const std::string& excluded_folders) override;
void StopIndexing(int index_request_id) override;
void SearchInPath(int search_request_id,
const std::string& file_system_path,
const std::string& query) override;
void SetWhitelistedShortcuts(const std::string& message) override;
void SetEyeDropperActive(bool active) override;
void ShowCertificateViewer(const std::string& cert_chain) override;
void ZoomIn() override;
void ZoomOut() override;
void ResetZoom() override;
void SetDevicesDiscoveryConfig(
bool discover_usb_devices,
bool port_forwarding_enabled,
const std::string& port_forwarding_config,
bool network_discovery_enabled,
const std::string& network_discovery_config) override;
void SetDevicesUpdatesEnabled(bool enabled) override;
void PerformActionOnRemotePage(const std::string& page_id,
const std::string& action) override;
void OpenRemotePage(const std::string& browser_id,
const std::string& url) override;
void OpenNodeFrontend() override;
void DispatchProtocolMessageFromDevToolsFrontend(
const std::string& message) override;
void SendJsonRequest(const DispatchCallback& callback,
const std::string& browser_id,
const std::string& url) override;
void GetPreferences(const DispatchCallback& callback) override;
void SetPreference(const std::string& name,
const std::string& value) override;
void RemovePreference(const std::string& name) override;
void ClearPreferences() override;
void ConnectionReady() override;
void RegisterExtensionsAPI(const std::string& origin,
const std::string& script) override;
void Reattach(const DispatchCallback& callback) override;
void RecordEnumeratedHistogram(const std::string& name,
int sample,
int boundary_value) override {}
void ReadyForTest() override {}
void SetOpenNewWindowForPopups(bool value) override {}
void RecordPerformanceHistogram(const std::string& name,
double duration) override {}
void RecordUserMetricsAction(const std::string& name) override {}
// content::DevToolsFrontendHostDelegate:
void HandleMessageFromDevToolsFrontend(const std::string& message);
// content::DevToolsAgentHostClient:
void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host,
const std::string& message) override;
void AgentHostClosed(content::DevToolsAgentHost* agent_host) override;
// content::WebContentsObserver:
void RenderFrameHostChanged(content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) override;
void WebContentsDestroyed() override;
void OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) override;
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
// content::WebContentsDelegate:
bool DidAddMessageToConsole(content::WebContents* source,
blink::mojom::ConsoleMessageLevel level,
const base::string16& message,
int32_t line_no,
const base::string16& source_id) override;
bool ShouldCreateWebContents(
content::WebContents* web_contents,
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
int32_t route_id,
int32_t main_frame_route_id,
int32_t main_frame_widget_route_id,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) override;
bool HandleKeyboardEvent(content::WebContents*,
const content::NativeWebKeyboardEvent&) override;
void CloseContents(content::WebContents* source) override;
content::ColorChooser* OpenColorChooser(
content::WebContents* source,
SkColor color,
const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
override;
void RunFileChooser(content::RenderFrameHost* render_frame_host,
std::unique_ptr<content::FileSelectListener> listener,
const blink::mojom::FileChooserParams& params) override;
void EnumerateDirectory(content::WebContents* source,
std::unique_ptr<content::FileSelectListener> listener,
const base::FilePath& path) override;
void SendMessageAck(int request_id, const base::Value* arg1);
bool frontend_loaded_;
scoped_refptr<content::DevToolsAgentHost> agent_host_;
std::unique_ptr<content::DevToolsFrontendHost> frontend_host_;
std::unique_ptr<DevToolsEmbedderMessageDispatcher>
embedder_message_dispatcher_;
DevToolsContentsResizingStrategy contents_resizing_strategy_;
gfx::Rect devtools_bounds_;
bool can_dock_;
std::string dock_state_;
bool activate_ = true;
InspectableWebContentsDelegate* delegate_; // weak references.
PrefService* pref_service_; // weak reference.
std::unique_ptr<content::WebContents> web_contents_;
// The default devtools created by this class when we don't have an external
// one assigned by SetDevToolsWebContents.
std::unique_ptr<content::WebContents> managed_devtools_web_contents_;
// The external devtools assigned by SetDevToolsWebContents.
content::WebContents* external_devtools_web_contents_ = nullptr;
bool is_guest_;
std::unique_ptr<InspectableWebContentsView> view_;
class NetworkResourceLoader;
std::set<std::unique_ptr<NetworkResourceLoader>, base::UniquePtrComparator>
loaders_;
using ExtensionsAPIs = std::map<std::string, std::string>;
ExtensionsAPIs extensions_api_;
base::WeakPtrFactory<InspectableWebContentsImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(InspectableWebContentsImpl);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_IMPL_H_

View file

@ -0,0 +1,62 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_H_
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_H_
#include "base/strings/string16.h"
#include "ui/gfx/native_widget_types.h"
class DevToolsContentsResizingStrategy;
#if defined(TOOLKIT_VIEWS)
namespace views {
class View;
}
#endif
namespace atom {
class InspectableWebContentsViewDelegate;
class InspectableWebContentsView {
public:
InspectableWebContentsView() : delegate_(nullptr) {}
virtual ~InspectableWebContentsView() {}
// The delegate manages its own life.
void SetDelegate(InspectableWebContentsViewDelegate* delegate) {
delegate_ = delegate;
}
InspectableWebContentsViewDelegate* GetDelegate() const { return delegate_; }
#if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX)
// Returns the container control, which has devtools view attached.
virtual views::View* GetView() = 0;
// Returns the web view control, which can be used by the
// GetInitiallyFocusedView() to set initial focus to web view.
virtual views::View* GetWebView() = 0;
#else
virtual gfx::NativeView GetNativeView() const = 0;
#endif
virtual void ShowDevTools(bool activate) = 0;
// Hide the DevTools view.
virtual void CloseDevTools() = 0;
virtual bool IsDevToolsViewShowing() = 0;
virtual bool IsDevToolsViewFocused() = 0;
virtual void SetIsDocked(bool docked, bool activate) = 0;
virtual void SetContentsResizingStrategy(
const DevToolsContentsResizingStrategy& strategy) = 0;
virtual void SetTitle(const base::string16& title) = 0;
private:
InspectableWebContentsViewDelegate* delegate_; // weak references.
};
} // namespace atom
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_H_

View file

@ -0,0 +1,14 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
namespace atom {
gfx::ImageSkia InspectableWebContentsViewDelegate::GetDevToolsWindowIcon() {
return gfx::ImageSkia();
}
} // namespace atom

View file

@ -0,0 +1,35 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_DELEGATE_H_
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_DELEGATE_H_
#include <string>
#include "ui/gfx/image/image_skia.h"
namespace atom {
class InspectableWebContentsViewDelegate {
public:
virtual ~InspectableWebContentsViewDelegate() {}
virtual void DevToolsFocused() {}
virtual void DevToolsOpened() {}
virtual void DevToolsClosed() {}
// Returns the icon of devtools window.
virtual gfx::ImageSkia GetDevToolsWindowIcon();
#if defined(USE_X11)
// Called when creating devtools window.
virtual void GetDevToolsWindowWMClass(std::string* name,
std::string* class_name) {}
#endif
};
} // namespace atom
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_DELEGATE_H_

View file

@ -0,0 +1,50 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_
#include "atom/browser/ui/inspectable_web_contents_view.h"
#include "base/mac/scoped_nsobject.h"
@class AtomInspectableWebContentsView;
namespace atom {
class InspectableWebContentsImpl;
class InspectableWebContentsViewMac : public InspectableWebContentsView {
public:
explicit InspectableWebContentsViewMac(
InspectableWebContentsImpl* inspectable_web_contents_impl);
~InspectableWebContentsViewMac() override;
gfx::NativeView GetNativeView() const override;
void ShowDevTools(bool activate) override;
void CloseDevTools() override;
bool IsDevToolsViewShowing() override;
bool IsDevToolsViewFocused() override;
void SetIsDocked(bool docked, bool activate) override;
void SetContentsResizingStrategy(
const DevToolsContentsResizingStrategy& strategy) override;
void SetTitle(const base::string16& title) override;
InspectableWebContentsImpl* inspectable_web_contents() {
return inspectable_web_contents_;
}
private:
// Owns us.
InspectableWebContentsImpl* inspectable_web_contents_;
base::scoped_nsobject<AtomInspectableWebContentsView> view_;
DISALLOW_COPY_AND_ASSIGN(InspectableWebContentsViewMac);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_

View file

@ -0,0 +1,66 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#include "atom/browser/ui/inspectable_web_contents_view_mac.h"
#import <AppKit/AppKit.h>
#import "atom/browser/ui/cocoa/atom_inspectable_web_contents_view.h"
#include "atom/browser/ui/inspectable_web_contents.h"
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
#include "base/strings/sys_string_conversions.h"
namespace atom {
InspectableWebContentsView* CreateInspectableContentsView(
InspectableWebContentsImpl* inspectable_web_contents) {
return new InspectableWebContentsViewMac(inspectable_web_contents);
}
InspectableWebContentsViewMac::InspectableWebContentsViewMac(
InspectableWebContentsImpl* inspectable_web_contents)
: inspectable_web_contents_(inspectable_web_contents),
view_([[AtomInspectableWebContentsView alloc]
initWithInspectableWebContentsViewMac:this]) {}
InspectableWebContentsViewMac::~InspectableWebContentsViewMac() {
[view_ removeObservers];
CloseDevTools();
}
gfx::NativeView InspectableWebContentsViewMac::GetNativeView() const {
return view_.get();
}
void InspectableWebContentsViewMac::ShowDevTools(bool activate) {
[view_ setDevToolsVisible:YES activate:activate];
}
void InspectableWebContentsViewMac::CloseDevTools() {
[view_ setDevToolsVisible:NO activate:NO];
}
bool InspectableWebContentsViewMac::IsDevToolsViewShowing() {
return [view_ isDevToolsVisible];
}
bool InspectableWebContentsViewMac::IsDevToolsViewFocused() {
return [view_ isDevToolsFocused];
}
void InspectableWebContentsViewMac::SetIsDocked(bool docked, bool activate) {
[view_ setIsDocked:docked activate:activate];
}
void InspectableWebContentsViewMac::SetContentsResizingStrategy(
const DevToolsContentsResizingStrategy& strategy) {
[view_ setContentsResizingStrategy:strategy];
}
void InspectableWebContentsViewMac::SetTitle(const base::string16& title) {
[view_ setTitle:base::SysUTF16ToNSString(title)];
}
} // namespace atom

View file

@ -0,0 +1,65 @@
// 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_MESSAGE_BOX_H_
#define ATOM_BROWSER_UI_MESSAGE_BOX_H_
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/strings/string16.h"
#include "ui/gfx/image/image_skia.h"
namespace atom {
class NativeWindow;
enum class MessageBoxType {
kNone = 0,
kInformation,
kWarning,
kError,
kQuestion,
};
enum MessageBoxOptions {
MESSAGE_BOX_NONE = 0,
MESSAGE_BOX_NO_LINK = 1 << 0,
};
struct MessageBoxSettings {
atom::NativeWindow* parent_window = nullptr;
MessageBoxType type = atom::MessageBoxType::kNone;
std::vector<std::string> buttons;
int default_id;
int cancel_id;
int options = atom::MessageBoxOptions::MESSAGE_BOX_NONE;
std::string title;
std::string message;
std::string detail;
std::string checkbox_label;
bool checkbox_checked = false;
gfx::ImageSkia icon;
MessageBoxSettings();
MessageBoxSettings(const MessageBoxSettings&);
~MessageBoxSettings();
};
int ShowMessageBoxSync(const MessageBoxSettings& settings);
typedef base::OnceCallback<void(int code, bool checkbox_checked)>
MessageBoxCallback;
void ShowMessageBox(const MessageBoxSettings& settings,
MessageBoxCallback callback);
// Like ShowMessageBox with simplest settings, but safe to call at very early
// stage of application.
void ShowErrorBox(const base::string16& title, const base::string16& content);
} // namespace atom
#endif // ATOM_BROWSER_UI_MESSAGE_BOX_H_

View file

@ -0,0 +1,227 @@
// 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/ui/message_box.h"
#include <glib/gi18n.h>
#include "atom/browser/browser.h"
#include "atom/browser/native_window_observer.h"
#include "atom/browser/native_window_views.h"
#include "atom/browser/unresponsive_suppressor.h"
#include "base/callback.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/libgtkui/gtk_util.h"
#include "chrome/browser/ui/libgtkui/skia_utils_gtk.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
#define ANSI_FOREGROUND_RED "\x1b[31m"
#define ANSI_FOREGROUND_BLACK "\x1b[30m"
#define ANSI_TEXT_BOLD "\x1b[1m"
#define ANSI_BACKGROUND_GRAY "\x1b[47m"
#define ANSI_RESET "\x1b[0m"
namespace atom {
MessageBoxSettings::MessageBoxSettings() = default;
MessageBoxSettings::MessageBoxSettings(const MessageBoxSettings&) = default;
MessageBoxSettings::~MessageBoxSettings() = default;
namespace {
class GtkMessageBox : public NativeWindowObserver {
public:
explicit GtkMessageBox(const MessageBoxSettings& settings)
: cancel_id_(settings.cancel_id),
parent_(static_cast<NativeWindow*>(settings.parent_window)) {
// Create dialog.
dialog_ =
gtk_message_dialog_new(nullptr, // parent
static_cast<GtkDialogFlags>(0), // no flags
GetMessageType(settings.type), // type
GTK_BUTTONS_NONE, // no buttons
"%s", settings.message.c_str());
if (!settings.detail.empty())
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog_),
"%s", settings.detail.c_str());
if (!settings.title.empty())
gtk_window_set_title(GTK_WINDOW(dialog_), settings.title.c_str());
if (!settings.icon.isNull()) {
// No easy way to obtain this programmatically, but GTK+'s docs
// define GTK_ICON_SIZE_DIALOG to be 48 pixels
static constexpr int pixel_width = 48;
static constexpr int pixel_height = 48;
GdkPixbuf* pixbuf =
libgtkui::GdkPixbufFromSkBitmap(*settings.icon.bitmap());
GdkPixbuf* scaled_pixbuf = gdk_pixbuf_scale_simple(
pixbuf, pixel_width, pixel_height, GDK_INTERP_BILINEAR);
GtkWidget* w = gtk_image_new_from_pixbuf(scaled_pixbuf);
gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog_), w);
gtk_widget_show(w);
g_clear_pointer(&scaled_pixbuf, g_object_unref);
g_clear_pointer(&pixbuf, g_object_unref);
}
if (!settings.checkbox_label.empty()) {
GtkWidget* message_area =
gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog_));
GtkWidget* check_button =
gtk_check_button_new_with_label(settings.checkbox_label.c_str());
g_signal_connect(check_button, "toggled",
G_CALLBACK(OnCheckboxToggledThunk), this);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button),
settings.checkbox_checked);
gtk_container_add(GTK_CONTAINER(message_area), check_button);
gtk_widget_show(check_button);
}
// Add buttons.
GtkDialog* dialog = GTK_DIALOG(dialog_);
for (size_t i = 0; i < settings.buttons.size(); ++i) {
gtk_dialog_add_button(dialog, TranslateToStock(i, settings.buttons[i]),
i);
}
gtk_dialog_set_default_response(dialog, settings.default_id);
// Parent window.
if (parent_) {
parent_->AddObserver(this);
static_cast<NativeWindowViews*>(parent_)->SetEnabled(false);
libgtkui::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
}
}
~GtkMessageBox() override {
gtk_widget_destroy(dialog_);
if (parent_) {
parent_->RemoveObserver(this);
static_cast<NativeWindowViews*>(parent_)->SetEnabled(true);
}
}
GtkMessageType GetMessageType(MessageBoxType type) {
switch (type) {
case MessageBoxType::kInformation:
return GTK_MESSAGE_INFO;
case MessageBoxType::kWarning:
return GTK_MESSAGE_WARNING;
case MessageBoxType::kQuestion:
return GTK_MESSAGE_QUESTION;
case MessageBoxType::kError:
return GTK_MESSAGE_ERROR;
default:
return GTK_MESSAGE_OTHER;
}
}
const char* TranslateToStock(int id, const std::string& text) {
const std::string lower = base::ToLowerASCII(text);
if (lower == "cancel")
return _("_Cancel");
if (lower == "no")
return _("_No");
if (lower == "ok")
return _("_OK");
if (lower == "yes")
return _("_Yes");
return text.c_str();
}
void Show() {
gtk_widget_show(dialog_);
// We need to call gtk_window_present after making the widgets visible to
// make sure window gets correctly raised and gets focus.
int time = ui::X11EventSource::GetInstance()->GetTimestamp();
gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
}
int RunSynchronous() {
Show();
int response = gtk_dialog_run(GTK_DIALOG(dialog_));
return (response < 0) ? cancel_id_ : response;
}
void RunAsynchronous(MessageBoxCallback callback) {
callback_ = std::move(callback);
g_signal_connect(dialog_, "delete-event",
G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseDialogThunk),
this);
Show();
}
void OnWindowClosed() override {
parent_->RemoveObserver(this);
parent_ = nullptr;
}
CHROMEG_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, GtkWidget*, int);
CHROMEG_CALLBACK_0(GtkMessageBox, void, OnCheckboxToggled, GtkWidget*);
private:
atom::UnresponsiveSuppressor unresponsive_suppressor_;
// The id to return when the dialog is closed without pressing buttons.
int cancel_id_ = 0;
bool checkbox_checked_ = false;
NativeWindow* parent_;
GtkWidget* dialog_;
MessageBoxCallback callback_;
DISALLOW_COPY_AND_ASSIGN(GtkMessageBox);
};
void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) {
gtk_widget_hide(dialog_);
if (response < 0)
std::move(callback_).Run(cancel_id_, checkbox_checked_);
else
std::move(callback_).Run(response, checkbox_checked_);
delete this;
}
void GtkMessageBox::OnCheckboxToggled(GtkWidget* widget) {
checkbox_checked_ = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
}
} // namespace
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
return GtkMessageBox(settings).RunSynchronous();
}
void ShowMessageBox(const MessageBoxSettings& settings,
MessageBoxCallback callback) {
(new GtkMessageBox(settings))->RunAsynchronous(std::move(callback));
}
void ShowErrorBox(const base::string16& title, const base::string16& content) {
if (Browser::Get()->is_ready()) {
atom::MessageBoxSettings settings;
settings.type = atom::MessageBoxType::kError;
settings.buttons = {"OK"};
settings.title = "Error";
settings.message = base::UTF16ToUTF8(title);
settings.detail = base::UTF16ToUTF8(content);
GtkMessageBox(settings).RunSynchronous();
} else {
fprintf(stderr,
ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY ANSI_FOREGROUND_RED
"%s\n" ANSI_FOREGROUND_BLACK "%s" ANSI_RESET "\n",
base::UTF16ToUTF8(title).c_str(),
base::UTF16ToUTF8(content).c_str());
}
}
} // namespace atom

View file

@ -0,0 +1,152 @@
// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/message_box.h"
#include <string>
#include <utility>
#include <vector>
#import <Cocoa/Cocoa.h>
#include "atom/browser/native_window.h"
#include "base/callback.h"
#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "skia/ext/skia_utils_mac.h"
#include "ui/gfx/image/image_skia.h"
namespace atom {
MessageBoxSettings::MessageBoxSettings() = default;
MessageBoxSettings::MessageBoxSettings(const MessageBoxSettings&) = default;
MessageBoxSettings::~MessageBoxSettings() = default;
namespace {
NSAlert* CreateNSAlert(const MessageBoxSettings& settings) {
// Ignore the title; it's the window title on other platforms and ignorable.
NSAlert* alert = [[NSAlert alloc] init];
[alert setMessageText:base::SysUTF8ToNSString(settings.message)];
[alert setInformativeText:base::SysUTF8ToNSString(settings.detail)];
switch (settings.type) {
case MessageBoxType::kInformation:
alert.alertStyle = NSInformationalAlertStyle;
break;
case MessageBoxType::kWarning:
case MessageBoxType::kError:
// NSWarningAlertStyle shows the app icon while NSCriticalAlertStyle
// shows a warning icon with an app icon badge. Since there is no
// error variant, lets just use NSCriticalAlertStyle.
alert.alertStyle = NSCriticalAlertStyle;
break;
default:
break;
}
for (size_t i = 0; i < settings.buttons.size(); ++i) {
NSString* title = base::SysUTF8ToNSString(settings.buttons[i]);
// An empty title causes crash on macOS.
if (settings.buttons[i].empty())
title = @"(empty)";
NSButton* button = [alert addButtonWithTitle:title];
[button setTag:i];
}
NSArray* ns_buttons = [alert buttons];
int button_count = static_cast<int>([ns_buttons count]);
if (settings.default_id >= 0 && settings.default_id < button_count) {
// Focus the button at default_id if the user opted to do so.
// The first button added gets set as the default selected.
// So remove that default, and make the requested button the default.
[[ns_buttons objectAtIndex:0] setKeyEquivalent:@""];
[[ns_buttons objectAtIndex:settings.default_id] setKeyEquivalent:@"\r"];
}
// Bind cancel id button to escape key if there is more than one button
if (button_count > 1 && settings.cancel_id >= 0 &&
settings.cancel_id < button_count) {
[[ns_buttons objectAtIndex:settings.cancel_id] setKeyEquivalent:@"\e"];
}
if (!settings.checkbox_label.empty()) {
alert.showsSuppressionButton = YES;
alert.suppressionButton.title =
base::SysUTF8ToNSString(settings.checkbox_label);
alert.suppressionButton.state =
settings.checkbox_checked ? NSOnState : NSOffState;
}
if (!settings.icon.isNull()) {
NSImage* image = skia::SkBitmapToNSImageWithColorSpace(
*settings.icon.bitmap(), base::mac::GetGenericRGBColorSpace());
[alert setIcon:image];
}
return alert;
}
} // namespace
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
NSAlert* alert = CreateNSAlert(settings);
// Use runModal for synchronous alert without parent, since we don't have a
// window to wait for.
if (!settings.parent_window)
return [[alert autorelease] runModal];
__block int ret_code = -1;
NSWindow* window =
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
[alert beginSheetModalForWindow:window
completionHandler:^(NSModalResponse response) {
ret_code = response;
[NSApp stopModal];
}];
[NSApp runModalForWindow:window];
return ret_code;
}
void ShowMessageBox(const MessageBoxSettings& settings,
MessageBoxCallback callback) {
NSAlert* alert = CreateNSAlert(settings);
// Use runModal for synchronous alert without parent, since we don't have a
// window to wait for.
if (!settings.parent_window) {
int ret = [[alert autorelease] runModal];
std::move(callback).Run(ret, alert.suppressionButton.state == NSOnState);
} else {
NSWindow* window =
settings.parent_window
? settings.parent_window->GetNativeWindow().GetNativeNSWindow()
: nil;
// Duplicate the callback object here since c is a reference and gcd would
// only store the pointer, by duplication we can force gcd to store a copy.
__block MessageBoxCallback callback_ = std::move(callback);
[alert beginSheetModalForWindow:window
completionHandler:^(NSModalResponse response) {
std::move(callback_).Run(
response, alert.suppressionButton.state == NSOnState);
}];
}
}
void ShowErrorBox(const base::string16& title, const base::string16& content) {
NSAlert* alert = [[NSAlert alloc] init];
[alert setMessageText:base::SysUTF16ToNSString(title)];
[alert setInformativeText:base::SysUTF16ToNSString(content)];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert runModal];
[alert release];
}
} // namespace atom

View file

@ -0,0 +1,249 @@
// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/message_box.h"
#include <windows.h> // windows.h must be included first
#include <commctrl.h>
#include <map>
#include <vector>
#include "atom/browser/browser.h"
#include "atom/browser/native_window_views.h"
#include "atom/browser/unresponsive_suppressor.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/thread.h"
#include "base/win/scoped_gdi_object.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ui/gfx/icon_util.h"
#include "ui/gfx/image/image_skia.h"
namespace atom {
MessageBoxSettings::MessageBoxSettings() = default;
MessageBoxSettings::MessageBoxSettings(const MessageBoxSettings&) = default;
MessageBoxSettings::~MessageBoxSettings() = default;
namespace {
// Small command ID values are already taken by Windows, we have to start from
// a large number to avoid conflicts with Windows.
const int kIDStart = 100;
// Get the common ID from button's name.
struct CommonButtonID {
int button;
int id;
};
CommonButtonID GetCommonID(const base::string16& button) {
base::string16 lower = base::ToLowerASCII(button);
if (lower == L"ok")
return {TDCBF_OK_BUTTON, IDOK};
else if (lower == L"yes")
return {TDCBF_YES_BUTTON, IDYES};
else if (lower == L"no")
return {TDCBF_NO_BUTTON, IDNO};
else if (lower == L"cancel")
return {TDCBF_CANCEL_BUTTON, IDCANCEL};
else if (lower == L"retry")
return {TDCBF_RETRY_BUTTON, IDRETRY};
else if (lower == L"close")
return {TDCBF_CLOSE_BUTTON, IDCLOSE};
return {-1, -1};
}
// Determine whether the buttons are common buttons, if so map common ID
// to button ID.
void MapToCommonID(const std::vector<base::string16>& buttons,
std::map<int, int>* id_map,
TASKDIALOG_COMMON_BUTTON_FLAGS* button_flags,
std::vector<TASKDIALOG_BUTTON>* dialog_buttons) {
for (size_t i = 0; i < buttons.size(); ++i) {
auto common = GetCommonID(buttons[i]);
if (common.button != -1) {
// It is a common button.
(*id_map)[common.id] = i;
(*button_flags) |= common.button;
} else {
// It is a custom button.
dialog_buttons->push_back(
{static_cast<int>(i + kIDStart), buttons[i].c_str()});
}
}
}
int ShowTaskDialogUTF16(NativeWindow* parent,
MessageBoxType type,
const std::vector<base::string16>& buttons,
int default_id,
int cancel_id,
int options,
const base::string16& title,
const base::string16& message,
const base::string16& detail,
const base::string16& checkbox_label,
bool* checkbox_checked,
const gfx::ImageSkia& icon) {
TASKDIALOG_FLAGS flags =
TDF_SIZE_TO_CONTENT | // Show all content.
TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog.
TASKDIALOGCONFIG config = {0};
config.cbSize = sizeof(config);
config.hInstance = GetModuleHandle(NULL);
config.dwFlags = flags;
if (parent) {
config.hwndParent =
static_cast<atom::NativeWindowViews*>(parent)->GetAcceleratedWidget();
}
if (default_id > 0)
config.nDefaultButton = kIDStart + default_id;
// TaskDialogIndirect doesn't allow empty name, if we set empty title it
// will show "electron.exe" in title.
base::string16 app_name = base::UTF8ToUTF16(Browser::Get()->GetName());
if (title.empty())
config.pszWindowTitle = app_name.c_str();
else
config.pszWindowTitle = title.c_str();
base::win::ScopedHICON hicon;
if (!icon.isNull()) {
hicon = IconUtil::CreateHICONFromSkBitmap(*icon.bitmap());
config.dwFlags |= TDF_USE_HICON_MAIN;
config.hMainIcon = hicon.get();
} else {
// Show icon according to dialog's type.
switch (type) {
case MessageBoxType::kInformation:
case MessageBoxType::kQuestion:
config.pszMainIcon = TD_INFORMATION_ICON;
break;
case MessageBoxType::kWarning:
config.pszMainIcon = TD_WARNING_ICON;
break;
case MessageBoxType::kError:
config.pszMainIcon = TD_ERROR_ICON;
break;
case MessageBoxType::kNone:
break;
}
}
// If "detail" is empty then don't make message hilighted.
if (detail.empty()) {
config.pszContent = message.c_str();
} else {
config.pszMainInstruction = message.c_str();
config.pszContent = detail.c_str();
}
if (!checkbox_label.empty()) {
config.pszVerificationText = checkbox_label.c_str();
if (checkbox_checked && *checkbox_checked) {
config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED;
}
}
// Iterate through the buttons, put common buttons in dwCommonButtons
// and custom buttons in pButtons.
std::map<int, int> id_map;
std::vector<TASKDIALOG_BUTTON> dialog_buttons;
if (options & MESSAGE_BOX_NO_LINK) {
for (size_t i = 0; i < buttons.size(); ++i)
dialog_buttons.push_back(
{static_cast<int>(i + kIDStart), buttons[i].c_str()});
} else {
MapToCommonID(buttons, &id_map, &config.dwCommonButtons, &dialog_buttons);
}
if (dialog_buttons.size() > 0) {
config.pButtons = &dialog_buttons.front();
config.cButtons = dialog_buttons.size();
if (!(options & MESSAGE_BOX_NO_LINK))
config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links.
}
int id = 0;
BOOL verificationFlagChecked = FALSE;
TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked);
if (checkbox_checked) {
*checkbox_checked = verificationFlagChecked;
}
if (id_map.find(id) != id_map.end()) // common button.
return id_map[id];
else if (id >= kIDStart) // custom button.
return id - kIDStart;
else
return cancel_id;
}
int ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
std::vector<base::string16> utf16_buttons;
for (const auto& button : settings.buttons)
utf16_buttons.push_back(base::UTF8ToUTF16(button));
const base::string16 title_16 = base::UTF8ToUTF16(settings.title);
const base::string16 message_16 = base::UTF8ToUTF16(settings.message);
const base::string16 detail_16 = base::UTF8ToUTF16(settings.detail);
const base::string16 checkbox_label_16 =
base::UTF8ToUTF16(settings.checkbox_label);
bool cb_checked = settings.checkbox_checked;
return ShowTaskDialogUTF16(
settings.parent_window, settings.type, utf16_buttons, settings.default_id,
settings.cancel_id, settings.options, title_16, message_16, detail_16,
checkbox_label_16, &cb_checked, settings.icon);
}
void RunMessageBoxInNewThread(base::Thread* thread,
const MessageBoxSettings& settings,
MessageBoxCallback callback) {
int result = ShowTaskDialogUTF8(settings);
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(std::move(callback), result, settings.checkbox_checked));
content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
thread);
}
} // namespace
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
atom::UnresponsiveSuppressor suppressor;
return ShowTaskDialogUTF8(settings);
}
void ShowMessageBox(const MessageBoxSettings& settings,
MessageBoxCallback callback) {
auto thread =
std::make_unique<base::Thread>(ELECTRON_PRODUCT_NAME "MessageBoxThread");
thread->init_com_with_mta(false);
if (!thread->Start()) {
std::move(callback).Run(settings.cancel_id, settings.checkbox_checked);
return;
}
base::Thread* unretained = thread.release();
unretained->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&RunMessageBoxInNewThread, base::Unretained(unretained),
settings, std::move(callback)));
}
void ShowErrorBox(const base::string16& title, const base::string16& content) {
atom::UnresponsiveSuppressor suppressor;
ShowTaskDialogUTF16(nullptr, MessageBoxType::kError, {}, -1, 0, 0, L"Error",
title, content, L"", nullptr, gfx::ImageSkia());
}
} // namespace atom

View file

@ -0,0 +1,105 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/tray_icon.h"
namespace atom {
TrayIcon::TrayIcon() {}
TrayIcon::~TrayIcon() {}
void TrayIcon::SetPressedImage(ImageType image) {}
void TrayIcon::SetHighlightMode(TrayIcon::HighlightMode mode) {}
void TrayIcon::DisplayBalloon(ImageType icon,
const base::string16& title,
const base::string16& contents) {}
void TrayIcon::PopUpContextMenu(const gfx::Point& pos,
AtomMenuModel* menu_model) {}
gfx::Rect TrayIcon::GetBounds() {
return gfx::Rect();
}
void TrayIcon::NotifyClicked(const gfx::Rect& bounds,
const gfx::Point& location,
int modifiers) {
for (TrayIconObserver& observer : observers_)
observer.OnClicked(bounds, location, modifiers);
}
void TrayIcon::NotifyDoubleClicked(const gfx::Rect& bounds, int modifiers) {
for (TrayIconObserver& observer : observers_)
observer.OnDoubleClicked(bounds, modifiers);
}
void TrayIcon::NotifyBalloonShow() {
for (TrayIconObserver& observer : observers_)
observer.OnBalloonShow();
}
void TrayIcon::NotifyBalloonClicked() {
for (TrayIconObserver& observer : observers_)
observer.OnBalloonClicked();
}
void TrayIcon::NotifyBalloonClosed() {
for (TrayIconObserver& observer : observers_)
observer.OnBalloonClosed();
}
void TrayIcon::NotifyRightClicked(const gfx::Rect& bounds, int modifiers) {
for (TrayIconObserver& observer : observers_)
observer.OnRightClicked(bounds, modifiers);
}
void TrayIcon::NotifyDrop() {
for (TrayIconObserver& observer : observers_)
observer.OnDrop();
}
void TrayIcon::NotifyDropFiles(const std::vector<std::string>& files) {
for (TrayIconObserver& observer : observers_)
observer.OnDropFiles(files);
}
void TrayIcon::NotifyDropText(const std::string& text) {
for (TrayIconObserver& observer : observers_)
observer.OnDropText(text);
}
void TrayIcon::NotifyMouseEntered(const gfx::Point& location, int modifiers) {
for (TrayIconObserver& observer : observers_)
observer.OnMouseEntered(location, modifiers);
}
void TrayIcon::NotifyMouseExited(const gfx::Point& location, int modifiers) {
for (TrayIconObserver& observer : observers_)
observer.OnMouseExited(location, modifiers);
}
void TrayIcon::NotifyMouseMoved(const gfx::Point& location, int modifiers) {
for (TrayIconObserver& observer : observers_)
observer.OnMouseMoved(location, modifiers);
}
void TrayIcon::NotifyDragEntered() {
for (TrayIconObserver& observer : observers_)
observer.OnDragEntered();
}
void TrayIcon::NotifyDragExited() {
for (TrayIconObserver& observer : observers_)
observer.OnDragExited();
}
void TrayIcon::NotifyDragEnded() {
for (TrayIconObserver& observer : observers_)
observer.OnDragEnded();
}
} // namespace atom

View file

@ -0,0 +1,112 @@
// Copyright (c) 2014 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_TRAY_ICON_H_
#define ATOM_BROWSER_UI_TRAY_ICON_H_
#include <string>
#include <vector>
#include "atom/browser/ui/atom_menu_model.h"
#include "atom/browser/ui/tray_icon_observer.h"
#include "base/observer_list.h"
#include "ui/gfx/geometry/rect.h"
namespace atom {
class TrayIcon {
public:
static TrayIcon* Create();
#if defined(OS_WIN)
using ImageType = HICON;
#else
using ImageType = const gfx::Image&;
#endif
virtual ~TrayIcon();
// Sets the image associated with this status icon.
virtual void SetImage(ImageType image) = 0;
// Sets the image associated with this status icon when pressed.
virtual void SetPressedImage(ImageType image);
// Sets the hover text for this status icon. This is also used as the label
// for the menu item which is created as a replacement for the status icon
// click action on platforms that do not support custom click actions for the
// status icon (e.g. Ubuntu Unity).
virtual void SetToolTip(const std::string& tool_tip) = 0;
// Sets the status icon highlight mode. This only works on macOS.
enum class HighlightMode {
ALWAYS, // Always highlight the tray icon
NEVER, // Never highlight the tray icon
SELECTION // Highlight the tray icon when clicked or the menu is opened
};
virtual void SetHighlightMode(HighlightMode mode);
#if defined(OS_MACOSX)
// Set/Get flag determining whether to ignore double click events.
virtual void SetIgnoreDoubleClickEvents(bool ignore) = 0;
virtual bool GetIgnoreDoubleClickEvents() = 0;
// Set/Get title displayed next to status icon in the status bar.
virtual void SetTitle(const std::string& title) = 0;
virtual std::string GetTitle() = 0;
#endif
// Displays a notification balloon with the specified contents.
// Depending on the platform it might not appear by the icon tray.
virtual void DisplayBalloon(ImageType icon,
const base::string16& title,
const base::string16& contents);
// Popups the menu.
virtual void PopUpContextMenu(const gfx::Point& pos,
AtomMenuModel* menu_model);
// Set the context menu for this icon.
virtual void SetContextMenu(AtomMenuModel* menu_model) = 0;
// Returns the bounds of tray icon.
virtual gfx::Rect GetBounds();
void AddObserver(TrayIconObserver* obs) { observers_.AddObserver(obs); }
void RemoveObserver(TrayIconObserver* obs) { observers_.RemoveObserver(obs); }
void NotifyClicked(const gfx::Rect& = gfx::Rect(),
const gfx::Point& location = gfx::Point(),
int modifiers = 0);
void NotifyDoubleClicked(const gfx::Rect& = gfx::Rect(), int modifiers = 0);
void NotifyBalloonShow();
void NotifyBalloonClicked();
void NotifyBalloonClosed();
void NotifyRightClicked(const gfx::Rect& bounds = gfx::Rect(),
int modifiers = 0);
void NotifyDrop();
void NotifyDropFiles(const std::vector<std::string>& files);
void NotifyDropText(const std::string& text);
void NotifyDragEntered();
void NotifyDragExited();
void NotifyDragEnded();
void NotifyMouseEntered(const gfx::Point& location = gfx::Point(),
int modifiers = 0);
void NotifyMouseExited(const gfx::Point& location = gfx::Point(),
int modifiers = 0);
void NotifyMouseMoved(const gfx::Point& location = gfx::Point(),
int modifiers = 0);
protected:
TrayIcon();
private:
base::ObserverList<TrayIconObserver> observers_;
DISALLOW_COPY_AND_ASSIGN(TrayIcon);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_TRAY_ICON_H_

View file

@ -0,0 +1,57 @@
// Copyright (c) 2014 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_TRAY_ICON_COCOA_H_
#define ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_
#import <Cocoa/Cocoa.h>
#include <string>
#include "atom/browser/ui/tray_icon.h"
#include "base/mac/scoped_nsobject.h"
@class AtomMenuController;
@class StatusItemView;
namespace atom {
class TrayIconCocoa : public TrayIcon, public AtomMenuModel::Observer {
public:
TrayIconCocoa();
~TrayIconCocoa() override;
void SetImage(const gfx::Image& image) override;
void SetPressedImage(const gfx::Image& image) override;
void SetToolTip(const std::string& tool_tip) override;
void SetTitle(const std::string& title) override;
std::string GetTitle() override;
void SetHighlightMode(TrayIcon::HighlightMode mode) override;
void SetIgnoreDoubleClickEvents(bool ignore) override;
bool GetIgnoreDoubleClickEvents() override;
void PopUpContextMenu(const gfx::Point& pos,
AtomMenuModel* menu_model) override;
void SetContextMenu(AtomMenuModel* menu_model) override;
gfx::Rect GetBounds() override;
protected:
// AtomMenuModel::Observer:
void OnMenuWillClose() override;
private:
// Atom custom view for NSStatusItem.
base::scoped_nsobject<StatusItemView> status_item_view_;
// Status menu shown when right-clicking the system icon.
base::scoped_nsobject<AtomMenuController> menu_;
// Used for unregistering observer.
AtomMenuModel* menu_model_ = nullptr; // weak ref.
DISALLOW_COPY_AND_ASSIGN(TrayIconCocoa);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_

View file

@ -0,0 +1,530 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/tray_icon_cocoa.h"
#include <string>
#include <vector>
#include "atom/browser/mac/atom_application.h"
#include "atom/browser/ui/cocoa/NSString+ANSI.h"
#include "atom/browser/ui/cocoa/atom_menu_controller.h"
#include "base/mac/sdk_forward_declarations.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/display/screen.h"
#include "ui/events/cocoa/cocoa_event_utils.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/mac/coordinate_conversion.h"
namespace {
// By default, macOS sets 4px to tray image as left and right padding margin.
const CGFloat kHorizontalMargin = 4;
// macOS tends to make the title 2px lower.
const CGFloat kVerticalTitleMargin = 2;
} // namespace
@interface StatusItemView : NSView {
atom::TrayIconCocoa* trayIcon_; // weak
AtomMenuController* menuController_; // weak
atom::TrayIcon::HighlightMode highlight_mode_;
BOOL ignoreDoubleClickEvents_;
BOOL forceHighlight_;
BOOL inMouseEventSequence_;
BOOL ANSI_;
base::scoped_nsobject<NSImage> image_;
base::scoped_nsobject<NSImage> alternateImage_;
base::scoped_nsobject<NSString> title_;
base::scoped_nsobject<NSMutableAttributedString> attributedTitle_;
base::scoped_nsobject<NSStatusItem> statusItem_;
base::scoped_nsobject<NSTrackingArea> trackingArea_;
}
@end // @interface StatusItemView
@implementation StatusItemView
- (void)dealloc {
trayIcon_ = nil;
menuController_ = nil;
[super dealloc];
}
- (id)initWithIcon:(atom::TrayIconCocoa*)icon {
trayIcon_ = icon;
menuController_ = nil;
highlight_mode_ = atom::TrayIcon::HighlightMode::SELECTION;
ignoreDoubleClickEvents_ = NO;
forceHighlight_ = NO;
inMouseEventSequence_ = NO;
if ((self = [super initWithFrame:CGRectZero])) {
[self registerForDraggedTypes:@[
NSFilenamesPboardType,
NSStringPboardType,
]];
// Create the status item.
NSStatusItem* item = [[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength];
statusItem_.reset([item retain]);
[statusItem_ setView:self];
// Finalize setup by sizing our views
[self updateDimensions];
// Add NSTrackingArea for listening to mouseEnter, mouseExit, and mouseMove
// events
trackingArea_.reset([[NSTrackingArea alloc]
initWithRect:[self bounds]
options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
NSTrackingActiveAlways
owner:self
userInfo:nil]);
[self addTrackingArea:trackingArea_];
}
return self;
}
- (void)updateDimensions {
NSStatusBar* bar = [NSStatusBar systemStatusBar];
[self setFrame:NSMakeRect(0, 0, [self fullWidth], [bar thickness])];
[self setNeedsDisplay:YES];
}
- (void)removeItem {
// Turn off tracking events to prevent crash
if (trackingArea_) {
[self removeTrackingArea:trackingArea_];
trackingArea_.reset();
}
[[NSStatusBar systemStatusBar] removeStatusItem:statusItem_];
[statusItem_ setView:nil];
statusItem_.reset();
}
- (void)drawRect:(NSRect)dirtyRect {
// Draw the tray icon and title that align with NSStatusItem, layout:
// ----------------
// | icon | title |
/// ----------------
CGFloat thickness = [[statusItem_ statusBar] thickness];
// Draw the system bar background.
[statusItem_ drawStatusBarBackgroundInRect:self.bounds
withHighlight:[self shouldHighlight]];
// Determine which image to use.
NSImage* image = image_.get();
if (inMouseEventSequence_ && alternateImage_) {
image = alternateImage_.get();
}
// Apply the higlight color if the image is a template image. When this moves
// to using the new [NSStatusItem button] API, this should work automagically.
if ([image isTemplate] == YES) {
NSImage* imageWithColor = [[image copy] autorelease];
[imageWithColor lockFocus];
[[self colorWithHighlight:[self isHighlighted]] set];
CGRect imageBounds = CGRectMake(0, 0, image.size.width, image.size.height);
NSRectFillUsingOperation(imageBounds, NSCompositeSourceAtop);
[imageWithColor unlockFocus];
image = imageWithColor;
}
// Draw the image
[image
drawInRect:CGRectMake(roundf(([self iconWidth] - image.size.width) / 2),
roundf((thickness - image.size.height) / 2),
image.size.width, image.size.height)];
if (title_) {
// Draw title.
NSRect titleDrawRect = NSMakeRect([self iconWidth], -kVerticalTitleMargin,
[self titleWidth], thickness);
[attributedTitle_ drawInRect:titleDrawRect];
}
}
- (BOOL)isDarkMode {
if (@available(macOS 10.14, *)) {
return [[NSApplication sharedApplication].effectiveAppearance.name
isEqualToString:NSAppearanceNameDarkAqua];
}
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSString* mode = [defaults stringForKey:@"AppleInterfaceStyle"];
return mode && [mode isEqualToString:@"Dark"];
}
- (BOOL)isHighlighted {
BOOL highlight = [self shouldHighlight];
return highlight | [self isDarkMode];
}
// The width of the full status item.
- (CGFloat)fullWidth {
if (title_)
return [self iconWidth] + [self titleWidth] + kHorizontalMargin;
else
return [self iconWidth];
}
// The width of the icon.
- (CGFloat)iconWidth {
if (!image_ && title_)
return kHorizontalMargin;
CGFloat thickness = [[NSStatusBar systemStatusBar] thickness];
CGFloat imageHeight = [image_ size].height;
CGFloat imageWidth = [image_ size].width;
CGFloat iconWidth = imageWidth;
if (imageWidth < thickness) {
// Image's width must be larger than menu bar's height.
iconWidth = thickness;
} else {
CGFloat verticalMargin = thickness - imageHeight;
// Image must have same horizontal vertical margin.
if (verticalMargin > 0 && imageWidth != imageHeight)
iconWidth = imageWidth + verticalMargin;
CGFloat horizontalMargin = thickness - imageWidth;
// Image must have at least kHorizontalMargin horizontal margin on each
// side.
if (horizontalMargin < 2 * kHorizontalMargin)
iconWidth = imageWidth + 2 * kHorizontalMargin;
}
return iconWidth;
}
// The width of the title.
- (CGFloat)titleWidth {
if (!title_)
return 0;
return [attributedTitle_ size].width;
}
- (NSColor*)colorWithHighlight:(BOOL)highlight {
return highlight ? [NSColor whiteColor]
: [NSColor colorWithRed:0.265625
green:0.25390625
blue:0.234375
alpha:1.0];
}
- (void)setImage:(NSImage*)image {
image_.reset([image copy]);
[self updateDimensions];
}
- (void)setAlternateImage:(NSImage*)image {
alternateImage_.reset([image copy]);
}
- (void)setHighlight:(atom::TrayIcon::HighlightMode)mode {
highlight_mode_ = mode;
[self setNeedsDisplay:YES];
}
- (void)setIgnoreDoubleClickEvents:(BOOL)ignore {
ignoreDoubleClickEvents_ = ignore;
}
- (BOOL)getIgnoreDoubleClickEvents {
return ignoreDoubleClickEvents_;
}
- (void)setTitle:(NSString*)title {
if (title.length > 0) {
title_.reset([title copy]);
ANSI_ = [title containsANSICodes];
} else {
title_.reset();
ANSI_ = NO;
}
[self updateAttributedTitle];
[self updateDimensions];
}
- (NSString*)title {
return title_;
}
- (void)updateAttributedTitle {
NSDictionary* attributes =
@{NSFontAttributeName : [NSFont menuBarFontOfSize:0]};
if (ANSI_) {
NSCharacterSet* whites = [NSCharacterSet whitespaceCharacterSet];
NSString* title = [title_ stringByTrimmingCharactersInSet:whites];
attributedTitle_.reset([title attributedStringParsingANSICodes]);
[attributedTitle_ addAttributes:attributes
range:NSMakeRange(0, [attributedTitle_ length])];
return;
}
// check title_ being nil
NSString* title = @"";
if (title_)
title = title_;
attributedTitle_.reset([[NSMutableAttributedString alloc]
initWithString:title
attributes:attributes]);
// NSFontAttributeName:[NSFont menuBarFontOfSize:0],
// NSForegroundColorAttributeName:[self colorWithHighlight: highlight]
[attributedTitle_ addAttributes:attributes
range:NSMakeRange(0, [attributedTitle_ length])];
[attributedTitle_ addAttribute:NSForegroundColorAttributeName
value:[self colorWithHighlight:[self isHighlighted]]
range:NSMakeRange(0, [attributedTitle_ length])];
}
- (void)setMenuController:(AtomMenuController*)menu {
menuController_ = menu;
}
- (void)mouseDown:(NSEvent*)event {
inMouseEventSequence_ = YES;
[self setNeedsDisplay:YES];
}
- (void)mouseUp:(NSEvent*)event {
if (!inMouseEventSequence_) {
// If the menu is showing, when user clicked the tray icon, the `mouseDown`
// event will be dissmissed, we need to close the menu at this time.
[self setNeedsDisplay:YES];
return;
}
inMouseEventSequence_ = NO;
// Show menu when there is a context menu.
// NB(hokein): Make tray's behavior more like official one's.
// When the tray icon gets clicked quickly multiple times, the
// event.clickCount doesn't always return 1. Instead, it returns a value that
// counts the clicked times.
// So we don't check the clickCount here, just pop up the menu for each click
// event.
if (menuController_)
[statusItem_ popUpStatusItemMenu:[menuController_ menu]];
// Don't emit click events when menu is showing.
if (menuController_)
return;
// If we are ignoring double click events, we should ignore the `clickCount`
// value and immediately emit a click event.
BOOL shouldBeHandledAsASingleClick =
(event.clickCount == 1) || ignoreDoubleClickEvents_;
if (shouldBeHandledAsASingleClick)
trayIcon_->NotifyClicked(
gfx::ScreenRectFromNSRect(event.window.frame),
gfx::ScreenPointFromNSPoint([event locationInWindow]),
ui::EventFlagsFromModifiers([event modifierFlags]));
// Double click event.
BOOL shouldBeHandledAsADoubleClick =
(event.clickCount == 2) && !ignoreDoubleClickEvents_;
if (shouldBeHandledAsADoubleClick)
trayIcon_->NotifyDoubleClicked(
gfx::ScreenRectFromNSRect(event.window.frame),
ui::EventFlagsFromModifiers([event modifierFlags]));
[self setNeedsDisplay:YES];
}
- (void)popUpContextMenu:(atom::AtomMenuModel*)menu_model {
// Show a custom menu.
if (menu_model) {
base::scoped_nsobject<AtomMenuController> menuController(
[[AtomMenuController alloc] initWithModel:menu_model
useDefaultAccelerator:NO]);
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 tray icon to show highlight if it is enabled.
[self setNeedsDisplay:YES];
[statusItem_ popUpStatusItemMenu:[menuController_ menu]];
// The popUpStatusItemMenu returns only after the showing menu is closed.
// When it returns, we need to redraw the tray icon to not show highlight.
[self setNeedsDisplay:YES];
}
}
- (void)rightMouseUp:(NSEvent*)event {
trayIcon_->NotifyRightClicked(
gfx::ScreenRectFromNSRect(event.window.frame),
ui::EventFlagsFromModifiers([event modifierFlags]));
}
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
trayIcon_->NotifyDragEntered();
return NSDragOperationCopy;
}
- (void)mouseExited:(NSEvent*)event {
trayIcon_->NotifyMouseExited(
gfx::ScreenPointFromNSPoint([event locationInWindow]),
ui::EventFlagsFromModifiers([event modifierFlags]));
}
- (void)mouseEntered:(NSEvent*)event {
trayIcon_->NotifyMouseEntered(
gfx::ScreenPointFromNSPoint([event locationInWindow]),
ui::EventFlagsFromModifiers([event modifierFlags]));
}
- (void)mouseMoved:(NSEvent*)event {
trayIcon_->NotifyMouseMoved(
gfx::ScreenPointFromNSPoint([event locationInWindow]),
ui::EventFlagsFromModifiers([event modifierFlags]));
}
- (void)draggingExited:(id<NSDraggingInfo>)sender {
trayIcon_->NotifyDragExited();
}
- (void)draggingEnded:(id<NSDraggingInfo>)sender {
trayIcon_->NotifyDragEnded();
if (NSPointInRect([sender draggingLocation], self.frame)) {
trayIcon_->NotifyDrop();
}
}
- (BOOL)handleDrop:(id<NSDraggingInfo>)sender {
NSPasteboard* pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
std::vector<std::string> dropFiles;
NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
for (NSString* file in files)
dropFiles.push_back(base::SysNSStringToUTF8(file));
trayIcon_->NotifyDropFiles(dropFiles);
return YES;
} else if ([[pboard types] containsObject:NSStringPboardType]) {
NSString* dropText = [pboard stringForType:NSStringPboardType];
trayIcon_->NotifyDropText(base::SysNSStringToUTF8(dropText));
return YES;
}
return NO;
}
- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender {
return YES;
}
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
[self handleDrop:sender];
return YES;
}
- (void)setNeedsDisplay:(BOOL)display {
[self updateAttributedTitle];
[super setNeedsDisplay:display];
}
- (BOOL)shouldHighlight {
using HighlightMode = atom::TrayIcon::HighlightMode;
switch (highlight_mode_) {
case HighlightMode::ALWAYS:
return true;
case HighlightMode::NEVER:
return false;
case HighlightMode::SELECTION:
BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen];
return forceHighlight_ || inMouseEventSequence_ || isMenuOpen;
}
}
@end
namespace atom {
TrayIconCocoa::TrayIconCocoa() {
status_item_view_.reset([[StatusItemView alloc] initWithIcon:this]);
}
TrayIconCocoa::~TrayIconCocoa() {
[status_item_view_ removeItem];
if (menu_model_)
menu_model_->RemoveObserver(this);
}
void TrayIconCocoa::SetImage(const gfx::Image& image) {
[status_item_view_ setImage:image.IsEmpty() ? nil : image.AsNSImage()];
}
void TrayIconCocoa::SetPressedImage(const gfx::Image& image) {
[status_item_view_ setAlternateImage:image.AsNSImage()];
}
void TrayIconCocoa::SetToolTip(const std::string& tool_tip) {
[status_item_view_ setToolTip:base::SysUTF8ToNSString(tool_tip)];
}
void TrayIconCocoa::SetTitle(const std::string& title) {
[status_item_view_ setTitle:base::SysUTF8ToNSString(title)];
}
std::string TrayIconCocoa::GetTitle() {
return base::SysNSStringToUTF8([status_item_view_ title]);
}
void TrayIconCocoa::SetHighlightMode(TrayIcon::HighlightMode mode) {
[status_item_view_ setHighlight:mode];
}
void TrayIconCocoa::SetIgnoreDoubleClickEvents(bool ignore) {
[status_item_view_ setIgnoreDoubleClickEvents:ignore];
}
bool TrayIconCocoa::GetIgnoreDoubleClickEvents() {
return [status_item_view_ getIgnoreDoubleClickEvents];
}
void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos,
AtomMenuModel* menu_model) {
[status_item_view_ popUpContextMenu:menu_model];
}
void TrayIconCocoa::SetContextMenu(AtomMenuModel* menu_model) {
// Substribe to MenuClosed event.
if (menu_model_)
menu_model_->RemoveObserver(this);
menu_model_ = menu_model;
if (menu_model) {
menu_model->AddObserver(this);
// Create native menu.
menu_.reset([[AtomMenuController alloc] initWithModel:menu_model
useDefaultAccelerator:NO]);
} else {
menu_.reset();
}
[status_item_view_ setMenuController:menu_.get()];
}
gfx::Rect TrayIconCocoa::GetBounds() {
auto bounds = gfx::ScreenRectFromNSRect([status_item_view_ window].frame);
return bounds;
}
void TrayIconCocoa::OnMenuWillClose() {
[status_item_view_ setNeedsDisplay:YES];
}
// static
TrayIcon* TrayIcon::Create() {
return new TrayIconCocoa;
}
} // namespace atom

View file

@ -0,0 +1,53 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/tray_icon_gtk.h"
#include "atom/browser/browser.h"
#include "atom/common/application_info.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/gfx/image/image.h"
#include "ui/views/linux_ui/linux_ui.h"
namespace atom {
TrayIconGtk::TrayIconGtk() {}
TrayIconGtk::~TrayIconGtk() {}
void TrayIconGtk::SetImage(const gfx::Image& image) {
if (icon_) {
icon_->SetImage(image.AsImageSkia());
return;
}
const auto toolTip = base::UTF8ToUTF16(GetApplicationName());
icon_ = views::LinuxUI::instance()->CreateLinuxStatusIcon(
image.AsImageSkia(), toolTip, Browser::Get()->GetName().c_str());
icon_->set_delegate(this);
}
void TrayIconGtk::SetToolTip(const std::string& tool_tip) {
icon_->SetToolTip(base::UTF8ToUTF16(tool_tip));
}
void TrayIconGtk::SetContextMenu(AtomMenuModel* menu_model) {
icon_->UpdatePlatformContextMenu(menu_model);
}
void TrayIconGtk::OnClick() {
NotifyClicked();
}
bool TrayIconGtk::HasClickAction() {
return false;
}
// static
TrayIcon* TrayIcon::Create() {
return new TrayIconGtk;
}
} // namespace atom

View file

@ -0,0 +1,42 @@
// Copyright (c) 2014 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_TRAY_ICON_GTK_H_
#define ATOM_BROWSER_UI_TRAY_ICON_GTK_H_
#include <memory>
#include <string>
#include "atom/browser/ui/tray_icon.h"
#include "ui/views/linux_ui/status_icon_linux.h"
namespace views {
class StatusIconLinux;
}
namespace atom {
class TrayIconGtk : public TrayIcon, public views::StatusIconLinux::Delegate {
public:
TrayIconGtk();
~TrayIconGtk() override;
// TrayIcon:
void SetImage(const gfx::Image& image) override;
void SetToolTip(const std::string& tool_tip) override;
void SetContextMenu(AtomMenuModel* menu_model) override;
private:
// views::StatusIconLinux::Delegate:
void OnClick() override;
bool HasClickAction() override;
std::unique_ptr<views::StatusIconLinux> icon_;
DISALLOW_COPY_AND_ASSIGN(TrayIconGtk);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_TRAY_ICON_GTK_H_

View file

@ -0,0 +1,46 @@
// Copyright (c) 2014 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_TRAY_ICON_OBSERVER_H_
#define ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_
#include <string>
#include <vector>
#include "base/observer_list_types.h"
namespace gfx {
class Rect;
class Point;
} // namespace gfx
namespace atom {
class TrayIconObserver : public base::CheckedObserver {
public:
virtual void OnClicked(const gfx::Rect& bounds,
const gfx::Point& location,
int modifiers) {}
virtual void OnDoubleClicked(const gfx::Rect& bounds, int modifiers) {}
virtual void OnBalloonShow() {}
virtual void OnBalloonClicked() {}
virtual void OnBalloonClosed() {}
virtual void OnRightClicked(const gfx::Rect& bounds, int modifiers) {}
virtual void OnDrop() {}
virtual void OnDropFiles(const std::vector<std::string>& files) {}
virtual void OnDropText(const std::string& text) {}
virtual void OnDragEntered() {}
virtual void OnDragExited() {}
virtual void OnDragEnded() {}
virtual void OnMouseEntered(const gfx::Point& location, int modifiers) {}
virtual void OnMouseExited(const gfx::Point& location, int modifiers) {}
virtual void OnMouseMoved(const gfx::Point& location, int modifiers) {}
protected:
~TrayIconObserver() override {}
};
} // namespace atom
#endif // ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_

View file

@ -0,0 +1,16 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/win/notify_icon.h"
#include "atom/browser/ui/win/notify_icon_host.h"
namespace atom {
// static
TrayIcon* TrayIcon::Create() {
static NotifyIconHost host;
return host.CreateNotifyIcon();
}
} // namespace atom

View file

@ -0,0 +1,115 @@
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#include "atom/browser/ui/views/atom_views_delegate.h"
#include <memory>
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/native_widget_aura.h"
#if defined(OS_LINUX)
#include "base/environment.h"
#include "base/nix/xdg_util.h"
#include "ui/views/linux_ui/linux_ui.h"
#endif
namespace {
#if defined(OS_LINUX)
bool IsDesktopEnvironmentUnity() {
std::unique_ptr<base::Environment> env(base::Environment::Create());
base::nix::DesktopEnvironment desktop_env =
base::nix::GetDesktopEnvironment(env.get());
return desktop_env == base::nix::DESKTOP_ENVIRONMENT_UNITY;
}
#endif
} // namespace
namespace atom {
ViewsDelegate::ViewsDelegate() {}
ViewsDelegate::~ViewsDelegate() {}
void ViewsDelegate::SaveWindowPlacement(const views::Widget* window,
const std::string& window_name,
const gfx::Rect& bounds,
ui::WindowShowState show_state) {}
bool ViewsDelegate::GetSavedWindowPlacement(
const views::Widget* widget,
const std::string& window_name,
gfx::Rect* bounds,
ui::WindowShowState* show_state) const {
return false;
}
void ViewsDelegate::NotifyMenuItemFocused(const base::string16& menu_name,
const base::string16& menu_item_name,
int item_index,
int item_count,
bool has_submenu) {}
#if defined(OS_WIN)
HICON ViewsDelegate::GetDefaultWindowIcon() const {
// Use current exe's icon as default window icon.
return LoadIcon(GetModuleHandle(NULL),
MAKEINTRESOURCE(1 /* IDR_MAINFRAME */));
}
HICON ViewsDelegate::GetSmallWindowIcon() const {
return GetDefaultWindowIcon();
}
bool ViewsDelegate::IsWindowInMetro(gfx::NativeWindow window) const {
return false;
}
#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
gfx::ImageSkia* ViewsDelegate::GetDefaultWindowIcon() const {
return NULL;
}
#endif
views::NonClientFrameView* ViewsDelegate::CreateDefaultNonClientFrameView(
views::Widget* widget) {
return NULL;
}
void ViewsDelegate::AddRef() {}
void ViewsDelegate::ReleaseRef() {}
void ViewsDelegate::OnBeforeWidgetInit(
views::Widget::InitParams* params,
views::internal::NativeWidgetDelegate* delegate) {
// If we already have a native_widget, we don't have to try to come
// up with one.
if (params->native_widget)
return;
if (params->parent && params->type != views::Widget::InitParams::TYPE_MENU &&
params->type != views::Widget::InitParams::TYPE_TOOLTIP) {
params->native_widget = new views::NativeWidgetAura(delegate);
} else {
params->native_widget = new views::DesktopNativeWidgetAura(delegate);
}
}
bool ViewsDelegate::WindowManagerProvidesTitleBar(bool maximized) {
#if defined(OS_LINUX)
// On Ubuntu Unity, the system always provides a title bar for maximized
// windows.
if (!maximized)
return false;
static bool is_desktop_environment_unity = IsDesktopEnvironmentUnity();
return is_desktop_environment_unity;
#else
return false;
#endif
}
} // namespace atom

View file

@ -0,0 +1,58 @@
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_VIEWS_ATOM_VIEWS_DELEGATE_H_
#define ATOM_BROWSER_UI_VIEWS_ATOM_VIEWS_DELEGATE_H_
#include <string>
#include "base/compiler_specific.h"
#include "ui/views/views_delegate.h"
namespace atom {
class ViewsDelegate : public views::ViewsDelegate {
public:
ViewsDelegate();
~ViewsDelegate() override;
protected:
// views::ViewsDelegate:
void SaveWindowPlacement(const views::Widget* window,
const std::string& window_name,
const gfx::Rect& bounds,
ui::WindowShowState show_state) override;
bool GetSavedWindowPlacement(const views::Widget* widget,
const std::string& window_name,
gfx::Rect* bounds,
ui::WindowShowState* show_state) const override;
void NotifyMenuItemFocused(const base::string16& menu_name,
const base::string16& menu_item_name,
int item_index,
int item_count,
bool has_submenu) override;
#if defined(OS_WIN)
HICON GetDefaultWindowIcon() const override;
HICON GetSmallWindowIcon() const override;
bool IsWindowInMetro(gfx::NativeWindow window) const override;
#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
gfx::ImageSkia* GetDefaultWindowIcon() const override;
#endif
views::NonClientFrameView* CreateDefaultNonClientFrameView(
views::Widget* widget) override;
void AddRef() override;
void ReleaseRef() override;
void OnBeforeWidgetInit(
views::Widget::InitParams* params,
views::internal::NativeWidgetDelegate* delegate) override;
bool WindowManagerProvidesTitleBar(bool maximized) override;
private:
DISALLOW_COPY_AND_ASSIGN(ViewsDelegate);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_ATOM_VIEWS_DELEGATE_H_

View file

@ -0,0 +1,492 @@
// 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 "atom/browser/ui/views/autofill_popup_view.h"
#include <memory>
#include "base/bind.h"
#include "base/i18n/rtl.h"
#include "cc/paint/skia_paint_canvas.h"
#include "content/public/browser/render_view_host.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/text_utils.h"
#include "ui/views/border.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/widget/widget.h"
namespace atom {
void AutofillPopupChildView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kMenuItem;
node_data->SetName(suggestion_);
}
AutofillPopupView::AutofillPopupView(AutofillPopup* popup,
views::Widget* parent_widget)
: popup_(popup), parent_widget_(parent_widget), weak_ptr_factory_(this) {
CreateChildViews();
SetFocusBehavior(FocusBehavior::ALWAYS);
set_drag_controller(this);
}
AutofillPopupView::~AutofillPopupView() {
if (popup_) {
auto* host = popup_->frame_host_->GetRenderViewHost()->GetWidget();
host->RemoveKeyPressEventCallback(keypress_callback_);
popup_->view_ = nullptr;
popup_ = nullptr;
}
RemoveObserver();
#if BUILDFLAG(ENABLE_OSR)
if (view_proxy_.get()) {
view_proxy_->ResetView();
}
#endif
if (GetWidget()) {
GetWidget()->Close();
}
}
void AutofillPopupView::Show() {
bool visible = parent_widget_->IsVisible();
#if BUILDFLAG(ENABLE_OSR)
visible = visible || view_proxy_;
#endif
if (!popup_ || !visible || parent_widget_->IsClosed())
return;
const bool initialize_widget = !GetWidget();
if (initialize_widget) {
parent_widget_->AddObserver(this);
// The widget is destroyed by the corresponding NativeWidget, so we use
// a weak pointer to hold the reference and don't have to worry about
// deletion.
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.delegate = this;
params.parent = parent_widget_->GetNativeView();
widget->Init(params);
// No animation for popup appearance (too distracting).
widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_HIDE);
show_time_ = base::Time::Now();
}
SetBorder(views::CreateSolidBorder(
kPopupBorderThickness,
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_UnfocusedBorderColor)));
DoUpdateBoundsAndRedrawPopup();
GetWidget()->Show();
if (initialize_widget)
views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this);
keypress_callback_ = base::BindRepeating(
&AutofillPopupView::HandleKeyPressEvent, base::Unretained(this));
auto* host = popup_->frame_host_->GetRenderViewHost()->GetWidget();
host->AddKeyPressEventCallback(keypress_callback_);
NotifyAccessibilityEvent(ax::mojom::Event::kMenuStart, true);
}
void AutofillPopupView::Hide() {
if (popup_) {
auto* host = popup_->frame_host_->GetRenderViewHost()->GetWidget();
host->RemoveKeyPressEventCallback(keypress_callback_);
popup_ = nullptr;
}
RemoveObserver();
NotifyAccessibilityEvent(ax::mojom::Event::kMenuEnd, true);
if (GetWidget()) {
GetWidget()->Close();
}
}
void AutofillPopupView::OnSuggestionsChanged() {
if (!popup_)
return;
CreateChildViews();
if (popup_->GetLineCount() == 0) {
popup_->Hide();
return;
}
DoUpdateBoundsAndRedrawPopup();
}
void AutofillPopupView::WriteDragDataForView(views::View*,
const gfx::Point&,
ui::OSExchangeData*) {}
int AutofillPopupView::GetDragOperationsForView(views::View*,
const gfx::Point&) {
return ui::DragDropTypes::DRAG_NONE;
}
bool AutofillPopupView::CanStartDragForView(views::View*,
const gfx::Point&,
const gfx::Point&) {
return false;
}
void AutofillPopupView::OnSelectedRowChanged(
base::Optional<int> previous_row_selection,
base::Optional<int> current_row_selection) {
SchedulePaint();
if (current_row_selection) {
int selected = current_row_selection.value_or(-1);
if (selected == -1 || static_cast<size_t>(selected) >= children().size())
return;
children().at(selected)->NotifyAccessibilityEvent(
ax::mojom::Event::kSelection, true);
}
}
void AutofillPopupView::DrawAutofillEntry(gfx::Canvas* canvas,
int index,
const gfx::Rect& entry_rect) {
if (!popup_)
return;
canvas->FillRect(entry_rect, GetNativeTheme()->GetSystemColor(
popup_->GetBackgroundColorIDForRow(index)));
const bool is_rtl = base::i18n::IsRTL();
const int text_align =
is_rtl ? gfx::Canvas::TEXT_ALIGN_RIGHT : gfx::Canvas::TEXT_ALIGN_LEFT;
gfx::Rect value_rect = entry_rect;
value_rect.Inset(kEndPadding, 0);
int x_align_left = value_rect.x();
const int value_width = gfx::GetStringWidth(
popup_->GetValueAt(index), popup_->GetValueFontListForRow(index));
int value_x_align_left = x_align_left;
value_x_align_left =
is_rtl ? value_rect.right() - value_width : value_rect.x();
canvas->DrawStringRectWithFlags(
popup_->GetValueAt(index), popup_->GetValueFontListForRow(index),
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ResultsTableNormalText),
gfx::Rect(value_x_align_left, value_rect.y(), value_width,
value_rect.height()),
text_align);
// Draw the label text, if one exists.
if (!popup_->GetLabelAt(index).empty()) {
const int label_width = gfx::GetStringWidth(
popup_->GetLabelAt(index), popup_->GetLabelFontListForRow(index));
int label_x_align_left = x_align_left;
label_x_align_left =
is_rtl ? value_rect.x() : value_rect.right() - label_width;
canvas->DrawStringRectWithFlags(
popup_->GetLabelAt(index), popup_->GetLabelFontListForRow(index),
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ResultsTableDimmedText),
gfx::Rect(label_x_align_left, entry_rect.y(), label_width,
entry_rect.height()),
text_align);
}
}
void AutofillPopupView::CreateChildViews() {
if (!popup_)
return;
RemoveAllChildViews(true);
for (int i = 0; i < popup_->GetLineCount(); ++i) {
auto* child_view = new AutofillPopupChildView(popup_->GetValueAt(i));
child_view->set_drag_controller(this);
AddChildView(child_view);
}
}
void AutofillPopupView::DoUpdateBoundsAndRedrawPopup() {
if (!popup_)
return;
GetWidget()->SetBounds(popup_->popup_bounds_);
#if BUILDFLAG(ENABLE_OSR)
if (view_proxy_.get()) {
view_proxy_->SetBounds(popup_->popup_bounds_in_view());
}
#endif
SchedulePaint();
}
void AutofillPopupView::OnPaint(gfx::Canvas* canvas) {
if (!popup_ ||
static_cast<size_t>(popup_->GetLineCount()) != children().size())
return;
gfx::Canvas* draw_canvas = canvas;
SkBitmap bitmap;
#if BUILDFLAG(ENABLE_OSR)
std::unique_ptr<cc::SkiaPaintCanvas> paint_canvas;
if (view_proxy_.get()) {
bitmap.allocN32Pixels(popup_->popup_bounds_in_view().width(),
popup_->popup_bounds_in_view().height(), true);
paint_canvas.reset(new cc::SkiaPaintCanvas(bitmap));
draw_canvas = new gfx::Canvas(paint_canvas.get(), 1.0);
}
#endif
draw_canvas->DrawColor(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ResultsTableNormalBackground));
OnPaintBorder(draw_canvas);
for (int i = 0; i < popup_->GetLineCount(); ++i) {
gfx::Rect line_rect = popup_->GetRowBounds(i);
DrawAutofillEntry(draw_canvas, i, line_rect);
}
#if BUILDFLAG(ENABLE_OSR)
if (view_proxy_.get()) {
view_proxy_->SetBounds(popup_->popup_bounds_in_view());
view_proxy_->SetBitmap(bitmap);
}
#endif
}
void AutofillPopupView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kMenu;
node_data->SetName("Autofill Menu");
}
void AutofillPopupView::OnMouseCaptureLost() {
ClearSelection();
}
bool AutofillPopupView::OnMouseDragged(const ui::MouseEvent& event) {
if (HitTestPoint(event.location())) {
SetSelection(event.location());
// We must return true in order to get future OnMouseDragged and
// OnMouseReleased events.
return true;
}
// If we move off of the popup, we lose the selection.
ClearSelection();
return false;
}
void AutofillPopupView::OnMouseExited(const ui::MouseEvent& event) {
// Pressing return causes the cursor to hide, which will generate an
// OnMouseExited event. Pressing return should activate the current selection
// via AcceleratorPressed, so we need to let that run first.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AutofillPopupView::ClearSelection,
weak_ptr_factory_.GetWeakPtr()));
}
void AutofillPopupView::OnMouseMoved(const ui::MouseEvent& event) {
// A synthesized mouse move will be sent when the popup is first shown.
// Don't preview a suggestion if the mouse happens to be hovering there.
#if defined(OS_WIN)
if (base::Time::Now() - show_time_ <= base::TimeDelta::FromMilliseconds(50))
return;
#else
if (event.flags() & ui::EF_IS_SYNTHESIZED)
return;
#endif
if (HitTestPoint(event.location()))
SetSelection(event.location());
else
ClearSelection();
}
bool AutofillPopupView::OnMousePressed(const ui::MouseEvent& event) {
return event.GetClickCount() == 1;
}
void AutofillPopupView::OnMouseReleased(const ui::MouseEvent& event) {
// We only care about the left click.
if (event.IsOnlyLeftMouseButton() && HitTestPoint(event.location()))
AcceptSelection(event.location());
}
void AutofillPopupView::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN:
case ui::ET_GESTURE_SCROLL_BEGIN:
case ui::ET_GESTURE_SCROLL_UPDATE:
if (HitTestPoint(event->location()))
SetSelection(event->location());
else
ClearSelection();
break;
case ui::ET_GESTURE_TAP:
case ui::ET_GESTURE_SCROLL_END:
if (HitTestPoint(event->location()))
AcceptSelection(event->location());
else
ClearSelection();
break;
case ui::ET_GESTURE_TAP_CANCEL:
case ui::ET_SCROLL_FLING_START:
ClearSelection();
break;
default:
return;
}
event->SetHandled();
}
bool AutofillPopupView::AcceleratorPressed(const ui::Accelerator& accelerator) {
if (accelerator.modifiers() != ui::EF_NONE)
return false;
if (accelerator.key_code() == ui::VKEY_ESCAPE) {
if (popup_)
popup_->Hide();
return true;
}
if (accelerator.key_code() == ui::VKEY_RETURN)
return AcceptSelectedLine();
return false;
}
bool AutofillPopupView::HandleKeyPressEvent(
const content::NativeWebKeyboardEvent& event) {
if (!popup_)
return false;
switch (event.windows_key_code) {
case ui::VKEY_UP:
SelectPreviousLine();
return true;
case ui::VKEY_DOWN:
SelectNextLine();
return true;
case ui::VKEY_PRIOR: // Page up.
SetSelectedLine(0);
return true;
case ui::VKEY_NEXT: // Page down.
SetSelectedLine(popup_->GetLineCount() - 1);
return true;
case ui::VKEY_ESCAPE:
popup_->Hide();
return true;
case ui::VKEY_TAB:
// A tab press should cause the selected line to be accepted, but still
// return false so the tab key press propagates and changes the cursor
// location.
AcceptSelectedLine();
return false;
case ui::VKEY_RETURN:
return AcceptSelectedLine();
default:
return false;
}
}
void AutofillPopupView::OnNativeFocusChanged(gfx::NativeView focused_now) {
if (GetWidget() && GetWidget()->GetNativeView() != focused_now && popup_)
popup_->Hide();
}
void AutofillPopupView::OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& new_bounds) {
if (widget != parent_widget_)
return;
if (popup_)
popup_->Hide();
}
void AutofillPopupView::AcceptSuggestion(int index) {
if (!popup_)
return;
popup_->AcceptSuggestion(index);
popup_->Hide();
}
bool AutofillPopupView::AcceptSelectedLine() {
if (!selected_line_ || selected_line_.value() >= popup_->GetLineCount())
return false;
AcceptSuggestion(selected_line_.value());
return true;
}
void AutofillPopupView::AcceptSelection(const gfx::Point& point) {
if (!popup_)
return;
SetSelectedLine(popup_->LineFromY(point.y()));
AcceptSelectedLine();
}
void AutofillPopupView::SetSelectedLine(base::Optional<int> selected_line) {
if (!popup_)
return;
if (selected_line_ == selected_line)
return;
if (selected_line && selected_line.value() >= popup_->GetLineCount())
return;
auto previous_selected_line(selected_line_);
selected_line_ = selected_line;
OnSelectedRowChanged(previous_selected_line, selected_line_);
}
void AutofillPopupView::SetSelection(const gfx::Point& point) {
if (!popup_)
return;
SetSelectedLine(popup_->LineFromY(point.y()));
}
void AutofillPopupView::SelectNextLine() {
if (!popup_)
return;
int new_selected_line = selected_line_ ? *selected_line_ + 1 : 0;
if (new_selected_line >= popup_->GetLineCount())
new_selected_line = 0;
SetSelectedLine(new_selected_line);
}
void AutofillPopupView::SelectPreviousLine() {
if (!popup_)
return;
int new_selected_line = selected_line_.value_or(0) - 1;
if (new_selected_line < 0)
new_selected_line = popup_->GetLineCount() - 1;
SetSelectedLine(new_selected_line);
}
void AutofillPopupView::ClearSelection() {
SetSelectedLine(base::nullopt);
}
void AutofillPopupView::RemoveObserver() {
parent_widget_->RemoveObserver(this);
views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this);
}
} // namespace atom

View file

@ -0,0 +1,155 @@
// 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 ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_
#define ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_
#include <memory>
#include "atom/browser/ui/autofill_popup.h"
#include "base/optional.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "content/public/browser/render_widget_host.h"
#include "electron/buildflags/buildflags.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/views/drag_controller.h"
#include "ui/views/focus/widget_focus_manager.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_observer.h"
#if BUILDFLAG(ENABLE_OSR)
#include "atom/browser/osr/osr_view_proxy.h"
#endif
namespace atom {
const int kPopupBorderThickness = 1;
const int kSmallerFontSizeDelta = -1;
const int kEndPadding = 8;
const int kNamePadding = 15;
const int kRowHeight = 24;
class AutofillPopup;
// Child view only for triggering accessibility events. Rendering is handled
// by |AutofillPopupViewViews|.
class AutofillPopupChildView : public views::View {
public:
explicit AutofillPopupChildView(const base::string16& suggestion)
: suggestion_(suggestion) {
SetFocusBehavior(FocusBehavior::ALWAYS);
}
private:
~AutofillPopupChildView() override {}
// views::Views implementation
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
base::string16 suggestion_;
DISALLOW_COPY_AND_ASSIGN(AutofillPopupChildView);
};
class AutofillPopupView : public views::WidgetDelegateView,
public views::WidgetFocusChangeListener,
public views::WidgetObserver,
public views::DragController {
public:
explicit AutofillPopupView(AutofillPopup* popup,
views::Widget* parent_widget);
~AutofillPopupView() override;
void Show();
void Hide();
void OnSuggestionsChanged();
int GetSelectedLine() { return selected_line_.value_or(-1); }
void WriteDragDataForView(views::View*,
const gfx::Point&,
ui::OSExchangeData*) override;
int GetDragOperationsForView(views::View*, const gfx::Point&) override;
bool CanStartDragForView(views::View*,
const gfx::Point&,
const gfx::Point&) override;
private:
friend class AutofillPopup;
void OnSelectedRowChanged(base::Optional<int> previous_row_selection,
base::Optional<int> current_row_selection);
// Draw the given autofill entry in |entry_rect|.
void DrawAutofillEntry(gfx::Canvas* canvas,
int index,
const gfx::Rect& entry_rect);
// Creates child views based on the suggestions given by |controller_|. These
// child views are used for accessibility events only. We need child views to
// populate the correct |AXNodeData| when user selects a suggestion.
void CreateChildViews();
void DoUpdateBoundsAndRedrawPopup();
// views::Views implementation.
void OnPaint(gfx::Canvas* canvas) override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
void OnMouseCaptureLost() override;
bool OnMouseDragged(const ui::MouseEvent& event) override;
void OnMouseExited(const ui::MouseEvent& event) override;
void OnMouseMoved(const ui::MouseEvent& event) override;
bool OnMousePressed(const ui::MouseEvent& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
bool HandleKeyPressEvent(const content::NativeWebKeyboardEvent& event);
// views::WidgetFocusChangeListener implementation.
void OnNativeFocusChanged(gfx::NativeView focused_now) override;
// views::WidgetObserver implementation.
void OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& new_bounds) override;
void AcceptSuggestion(int index);
bool AcceptSelectedLine();
void AcceptSelection(const gfx::Point& point);
void SetSelectedLine(base::Optional<int> selected_line);
void SetSelection(const gfx::Point& point);
void SelectNextLine();
void SelectPreviousLine();
void ClearSelection();
// Stop observing the widget.
void RemoveObserver();
// Controller for this popup. Weak reference.
AutofillPopup* popup_;
// The widget of the window that triggered this popup. Weak reference.
views::Widget* parent_widget_;
// The time when the popup was shown.
base::Time show_time_;
// The index of the currently selected line
base::Optional<int> selected_line_;
#if BUILDFLAG(ENABLE_OSR)
std::unique_ptr<OffscreenViewProxy> view_proxy_;
#endif
// The registered keypress callback, responsible for switching lines on
// key presses
content::RenderWidgetHost::KeyPressEventCallback keypress_callback_;
base::WeakPtrFactory<AutofillPopupView> weak_ptr_factory_;
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_

View file

@ -0,0 +1,114 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/views/frameless_view.h"
#include "atom/browser/native_window_views.h"
#include "ui/aura/window.h"
#include "ui/base/hit_test.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace atom {
namespace {
const int kResizeInsideBoundsSize = 5;
const int kResizeAreaCornerSize = 16;
} // namespace
// static
const char FramelessView::kViewClassName[] = "FramelessView";
FramelessView::FramelessView() {}
FramelessView::~FramelessView() {}
void FramelessView::Init(NativeWindowViews* window, views::Widget* frame) {
window_ = window;
frame_ = frame;
}
int FramelessView::ResizingBorderHitTest(const gfx::Point& point) {
// Check the frame first, as we allow a small area overlapping the contents
// to be used for resize handles.
bool can_ever_resize = frame_->widget_delegate()
? frame_->widget_delegate()->CanResize()
: false;
// Don't allow overlapping resize handles when the window is maximized or
// fullscreen, as it can't be resized in those states.
int resize_border = frame_->IsMaximized() || frame_->IsFullscreen()
? 0
: kResizeInsideBoundsSize;
return GetHTComponentForFrame(point, resize_border, resize_border,
kResizeAreaCornerSize, kResizeAreaCornerSize,
can_ever_resize);
}
gfx::Rect FramelessView::GetBoundsForClientView() const {
return bounds();
}
gfx::Rect FramelessView::GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const {
gfx::Rect window_bounds = client_bounds;
// Enforce minimum size (1, 1) in case that client_bounds is passed with
// empty size. This could occur when the frameless window is being
// initialized.
if (window_bounds.IsEmpty()) {
window_bounds.set_width(1);
window_bounds.set_height(1);
}
return window_bounds;
}
int FramelessView::NonClientHitTest(const gfx::Point& cursor) {
if (frame_->IsFullscreen())
return HTCLIENT;
// Check for possible draggable region in the client area for the frameless
// window.
SkRegion* draggable_region = window_->draggable_region();
if (draggable_region && draggable_region->contains(cursor.x(), cursor.y()))
return HTCAPTION;
// Support resizing frameless window by dragging the border.
int frame_component = ResizingBorderHitTest(cursor);
if (frame_component != HTNOWHERE)
return frame_component;
return HTCLIENT;
}
void FramelessView::GetWindowMask(const gfx::Size& size, SkPath* window_mask) {}
void FramelessView::ResetWindowControls() {}
void FramelessView::UpdateWindowIcon() {}
void FramelessView::UpdateWindowTitle() {}
void FramelessView::SizeConstraintsChanged() {}
gfx::Size FramelessView::CalculatePreferredSize() const {
return frame_->non_client_view()
->GetWindowBoundsForClientBounds(
gfx::Rect(frame_->client_view()->GetPreferredSize()))
.size();
}
gfx::Size FramelessView::GetMinimumSize() const {
return window_->GetContentMinimumSize();
}
gfx::Size FramelessView::GetMaximumSize() const {
return window_->GetContentMaximumSize();
}
const char* FramelessView::GetClassName() const {
return kViewClassName;
}
} // namespace atom

View file

@ -0,0 +1,57 @@
// Copyright (c) 2014 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_VIEWS_FRAMELESS_VIEW_H_
#define ATOM_BROWSER_UI_VIEWS_FRAMELESS_VIEW_H_
#include "ui/views/window/non_client_view.h"
namespace views {
class Widget;
}
namespace atom {
class NativeWindowViews;
class FramelessView : public views::NonClientFrameView {
public:
static const char kViewClassName[];
FramelessView();
~FramelessView() override;
virtual void Init(NativeWindowViews* window, views::Widget* frame);
// Returns whether the |point| is on frameless window's resizing border.
int ResizingBorderHitTest(const gfx::Point& point);
protected:
// views::NonClientFrameView:
gfx::Rect GetBoundsForClientView() const override;
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override;
int NonClientHitTest(const gfx::Point& point) override;
void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override;
void ResetWindowControls() override;
void UpdateWindowIcon() override;
void UpdateWindowTitle() override;
void SizeConstraintsChanged() override;
// Overridden from View:
gfx::Size CalculatePreferredSize() const override;
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
const char* GetClassName() const override;
// Not owned.
NativeWindowViews* window_ = nullptr;
views::Widget* frame_ = nullptr;
private:
DISALLOW_COPY_AND_ASSIGN(FramelessView);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_FRAMELESS_VIEW_H_

View file

@ -0,0 +1,336 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/views/global_menu_bar_x11.h"
#include <dlfcn.h>
#include <glib-object.h>
#include "atom/browser/native_window_views.h"
#include "atom/browser/ui/atom_menu_model.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/gfx/x/x11.h"
// libdbusmenu-glib types
typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_new_func)();
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_new_with_id_func)(int id);
typedef int (*dbusmenu_menuitem_get_id_func)(DbusmenuMenuitem* item);
typedef GList* (*dbusmenu_menuitem_get_children_func)(DbusmenuMenuitem* item);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_child_append_func)(
DbusmenuMenuitem* parent,
DbusmenuMenuitem* child);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_func)(
DbusmenuMenuitem* item,
const char* property,
const char* value);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_variant_func)(
DbusmenuMenuitem* item,
const char* property,
GVariant* value);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_bool_func)(
DbusmenuMenuitem* item,
const char* property,
bool value);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_int_func)(
DbusmenuMenuitem* item,
const char* property,
int value);
typedef struct _DbusmenuServer DbusmenuServer;
typedef DbusmenuServer* (*dbusmenu_server_new_func)(const char* object);
typedef void (*dbusmenu_server_set_root_func)(DbusmenuServer* self,
DbusmenuMenuitem* root);
namespace atom {
namespace {
// Retrieved functions from libdbusmenu-glib.
// DbusmenuMenuItem methods:
dbusmenu_menuitem_new_func menuitem_new = NULL;
dbusmenu_menuitem_new_with_id_func menuitem_new_with_id = NULL;
dbusmenu_menuitem_get_id_func menuitem_get_id = NULL;
dbusmenu_menuitem_get_children_func menuitem_get_children = NULL;
dbusmenu_menuitem_get_children_func menuitem_take_children = NULL;
dbusmenu_menuitem_child_append_func menuitem_child_append = NULL;
dbusmenu_menuitem_property_set_func menuitem_property_set = NULL;
dbusmenu_menuitem_property_set_variant_func menuitem_property_set_variant =
NULL;
dbusmenu_menuitem_property_set_bool_func menuitem_property_set_bool = NULL;
dbusmenu_menuitem_property_set_int_func menuitem_property_set_int = NULL;
// DbusmenuServer methods:
dbusmenu_server_new_func server_new = NULL;
dbusmenu_server_set_root_func server_set_root = NULL;
// Properties that we set on menu items:
const char kPropertyEnabled[] = "enabled";
const char kPropertyLabel[] = "label";
const char kPropertyShortcut[] = "shortcut";
const char kPropertyType[] = "type";
const char kPropertyToggleType[] = "toggle-type";
const char kPropertyToggleState[] = "toggle-state";
const char kPropertyVisible[] = "visible";
const char kPropertyChildrenDisplay[] = "children-display";
const char kToggleCheck[] = "checkmark";
const char kToggleRadio[] = "radio";
const char kTypeSeparator[] = "separator";
const char kDisplaySubmenu[] = "submenu";
void EnsureMethodsLoaded() {
static bool attempted_load = false;
if (attempted_load)
return;
attempted_load = true;
void* dbusmenu_lib = dlopen("libdbusmenu-glib.so", RTLD_LAZY);
if (!dbusmenu_lib)
dbusmenu_lib = dlopen("libdbusmenu-glib.so.4", RTLD_LAZY);
if (!dbusmenu_lib)
return;
// DbusmenuMenuItem methods.
menuitem_new = reinterpret_cast<dbusmenu_menuitem_new_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_new"));
menuitem_new_with_id = reinterpret_cast<dbusmenu_menuitem_new_with_id_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_new_with_id"));
menuitem_get_id = reinterpret_cast<dbusmenu_menuitem_get_id_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_get_id"));
menuitem_get_children = reinterpret_cast<dbusmenu_menuitem_get_children_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_get_children"));
menuitem_take_children =
reinterpret_cast<dbusmenu_menuitem_get_children_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_take_children"));
menuitem_child_append = reinterpret_cast<dbusmenu_menuitem_child_append_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_append"));
menuitem_property_set = reinterpret_cast<dbusmenu_menuitem_property_set_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set"));
menuitem_property_set_variant =
reinterpret_cast<dbusmenu_menuitem_property_set_variant_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_variant"));
menuitem_property_set_bool =
reinterpret_cast<dbusmenu_menuitem_property_set_bool_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_bool"));
menuitem_property_set_int =
reinterpret_cast<dbusmenu_menuitem_property_set_int_func>(
dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_int"));
// DbusmenuServer methods.
server_new = reinterpret_cast<dbusmenu_server_new_func>(
dlsym(dbusmenu_lib, "dbusmenu_server_new"));
server_set_root = reinterpret_cast<dbusmenu_server_set_root_func>(
dlsym(dbusmenu_lib, "dbusmenu_server_set_root"));
}
AtomMenuModel* ModelForMenuItem(DbusmenuMenuitem* item) {
return reinterpret_cast<AtomMenuModel*>(
g_object_get_data(G_OBJECT(item), "model"));
}
bool GetMenuItemID(DbusmenuMenuitem* item, int* id) {
gpointer id_ptr = g_object_get_data(G_OBJECT(item), "menu-id");
if (id_ptr != NULL) {
*id = GPOINTER_TO_INT(id_ptr) - 1;
return true;
}
return false;
}
void SetMenuItemID(DbusmenuMenuitem* item, int id) {
DCHECK_GE(id, 0);
// Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
g_object_set_data(G_OBJECT(item), "menu-id", GINT_TO_POINTER(id + 1));
}
std::string GetMenuModelStatus(AtomMenuModel* model) {
std::string ret;
for (int i = 0; i < model->GetItemCount(); ++i) {
int status = model->GetTypeAt(i) | (model->IsVisibleAt(i) << 3) |
(model->IsEnabledAt(i) << 4) |
(model->IsItemCheckedAt(i) << 5);
ret += base::StringPrintf(
"%s-%X\n", base::UTF16ToUTF8(model->GetLabelAt(i)).c_str(), status);
}
return ret;
}
} // namespace
GlobalMenuBarX11::GlobalMenuBarX11(NativeWindowViews* window)
: window_(window),
xid_(window_->GetNativeWindow()->GetHost()->GetAcceleratedWidget()) {
EnsureMethodsLoaded();
if (server_new)
InitServer(xid_);
GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid_);
}
GlobalMenuBarX11::~GlobalMenuBarX11() {
if (IsServerStarted())
g_object_unref(server_);
GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid_);
}
// static
std::string GlobalMenuBarX11::GetPathForWindow(gfx::AcceleratedWidget xid) {
return base::StringPrintf("/com/canonical/menu/%lX", xid);
}
void GlobalMenuBarX11::SetMenu(AtomMenuModel* menu_model) {
if (!IsServerStarted())
return;
DbusmenuMenuitem* root_item = menuitem_new();
menuitem_property_set(root_item, kPropertyLabel, "Root");
menuitem_property_set_bool(root_item, kPropertyVisible, true);
if (menu_model != nullptr) {
BuildMenuFromModel(menu_model, root_item);
}
server_set_root(server_, root_item);
g_object_unref(root_item);
}
bool GlobalMenuBarX11::IsServerStarted() const {
return server_;
}
void GlobalMenuBarX11::InitServer(gfx::AcceleratedWidget xid) {
std::string path = GetPathForWindow(xid);
server_ = server_new(path.c_str());
}
void GlobalMenuBarX11::OnWindowMapped() {
GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid_);
}
void GlobalMenuBarX11::OnWindowUnmapped() {
GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid_);
}
void GlobalMenuBarX11::BuildMenuFromModel(AtomMenuModel* model,
DbusmenuMenuitem* parent) {
for (int i = 0; i < model->GetItemCount(); ++i) {
DbusmenuMenuitem* item = menuitem_new();
menuitem_property_set_bool(item, kPropertyVisible, model->IsVisibleAt(i));
AtomMenuModel::ItemType type = model->GetTypeAt(i);
if (type == AtomMenuModel::TYPE_SEPARATOR) {
menuitem_property_set(item, kPropertyType, kTypeSeparator);
} else {
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
base::UTF16ToUTF8(model->GetLabelAt(i)));
menuitem_property_set(item, kPropertyLabel, label.c_str());
menuitem_property_set_bool(item, kPropertyEnabled, model->IsEnabledAt(i));
g_object_set_data(G_OBJECT(item), "model", model);
SetMenuItemID(item, i);
if (type == AtomMenuModel::TYPE_SUBMENU) {
menuitem_property_set(item, kPropertyChildrenDisplay, kDisplaySubmenu);
g_signal_connect(item, "about-to-show", G_CALLBACK(OnSubMenuShowThunk),
this);
} else {
ui::Accelerator accelerator;
if (model->GetAcceleratorAtWithParams(i, true, &accelerator))
RegisterAccelerator(item, accelerator);
g_signal_connect(item, "item-activated",
G_CALLBACK(OnItemActivatedThunk), this);
if (type == AtomMenuModel::TYPE_CHECK ||
type == AtomMenuModel::TYPE_RADIO) {
menuitem_property_set(
item, kPropertyToggleType,
type == AtomMenuModel::TYPE_CHECK ? kToggleCheck : kToggleRadio);
menuitem_property_set_int(item, kPropertyToggleState,
model->IsItemCheckedAt(i));
}
}
}
menuitem_child_append(parent, item);
g_object_unref(item);
}
}
void GlobalMenuBarX11::RegisterAccelerator(DbusmenuMenuitem* item,
const ui::Accelerator& accelerator) {
// A translation of libdbusmenu-gtk's menuitem_property_set_shortcut()
// translated from GDK types to ui::Accelerator types.
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
if (accelerator.IsCtrlDown())
g_variant_builder_add(&builder, "s", "Control");
if (accelerator.IsAltDown())
g_variant_builder_add(&builder, "s", "Alt");
if (accelerator.IsShiftDown())
g_variant_builder_add(&builder, "s", "Shift");
char* name =
XKeysymToString(XKeysymForWindowsKeyCode(accelerator.key_code(), false));
if (!name) {
NOTIMPLEMENTED();
return;
}
g_variant_builder_add(&builder, "s", name);
GVariant* inside_array = g_variant_builder_end(&builder);
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
g_variant_builder_add_value(&builder, inside_array);
GVariant* outside_array = g_variant_builder_end(&builder);
menuitem_property_set_variant(item, kPropertyShortcut, outside_array);
}
void GlobalMenuBarX11::OnItemActivated(DbusmenuMenuitem* item,
unsigned int timestamp) {
int id;
AtomMenuModel* model = ModelForMenuItem(item);
if (model && GetMenuItemID(item, &id))
model->ActivatedAt(id, 0);
}
void GlobalMenuBarX11::OnSubMenuShow(DbusmenuMenuitem* item) {
int id;
AtomMenuModel* model = ModelForMenuItem(item);
if (!model || !GetMenuItemID(item, &id))
return;
// Do not update menu if the submenu has not been changed.
std::string status = GetMenuModelStatus(model);
char* old = static_cast<char*>(g_object_get_data(G_OBJECT(item), "status"));
if (old && status == old)
return;
// Save the new status.
g_object_set_data_full(G_OBJECT(item), "status", g_strdup(status.c_str()),
g_free);
// Clear children.
GList* children = menuitem_take_children(item);
g_list_foreach(children, reinterpret_cast<GFunc>(g_object_unref), NULL);
g_list_free(children);
// Build children.
BuildMenuFromModel(model->GetSubmenuModelAt(id), item);
}
} // namespace atom

View file

@ -0,0 +1,81 @@
// Copyright (c) 2014 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_VIEWS_GLOBAL_MENU_BAR_X11_H_
#define ATOM_BROWSER_UI_VIEWS_GLOBAL_MENU_BAR_X11_H_
#include <string>
#include "atom/browser/ui/atom_menu_model.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/gfx/native_widget_types.h"
typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
typedef struct _DbusmenuServer DbusmenuServer;
namespace ui {
class Accelerator;
}
namespace atom {
class NativeWindowViews;
// Controls the Mac style menu bar on Unity.
//
// Unity has an Apple-like menu bar at the top of the screen that changes
// depending on the active window. In the GTK port, we had a hidden GtkMenuBar
// object in each GtkWindow which existed only to be scrapped by the
// libdbusmenu-gtk code. Since we don't have GtkWindows anymore, we need to
// interface directly with the lower level libdbusmenu-glib, which we
// opportunistically dlopen() since not everyone is running Ubuntu.
//
// This class is like the chrome's corresponding one, but it generates the menu
// from menu models instead, and it is also per-window specific.
class GlobalMenuBarX11 {
public:
explicit GlobalMenuBarX11(NativeWindowViews* window);
virtual ~GlobalMenuBarX11();
// Creates the object path for DbusmenuServer which is attached to |xid|.
static std::string GetPathForWindow(gfx::AcceleratedWidget xid);
void SetMenu(AtomMenuModel* menu_model);
bool IsServerStarted() const;
// Called by NativeWindow when it show/hides.
void OnWindowMapped();
void OnWindowUnmapped();
private:
// Creates a DbusmenuServer.
void InitServer(gfx::AcceleratedWidget xid);
// Create a menu from menu model.
void BuildMenuFromModel(AtomMenuModel* model, DbusmenuMenuitem* parent);
// Sets the accelerator for |item|.
void RegisterAccelerator(DbusmenuMenuitem* item,
const ui::Accelerator& accelerator);
CHROMEG_CALLBACK_1(GlobalMenuBarX11,
void,
OnItemActivated,
DbusmenuMenuitem*,
unsigned int);
CHROMEG_CALLBACK_0(GlobalMenuBarX11, void, OnSubMenuShow, DbusmenuMenuitem*);
NativeWindowViews* window_;
gfx::AcceleratedWidget xid_;
DbusmenuServer* server_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(GlobalMenuBarX11);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_GLOBAL_MENU_BAR_X11_H_

View file

@ -0,0 +1,235 @@
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#include "atom/browser/ui/views/inspectable_web_contents_view_views.h"
#include "atom/browser/ui/inspectable_web_contents_delegate.h"
#include "atom/browser/ui/inspectable_web_contents_impl.h"
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/client_view.h"
namespace atom {
namespace {
class DevToolsWindowDelegate : public views::ClientView,
public views::WidgetDelegate {
public:
DevToolsWindowDelegate(InspectableWebContentsViewViews* shell,
views::View* view,
views::Widget* widget)
: views::ClientView(widget, view),
shell_(shell),
view_(view),
widget_(widget) {
// A WidgetDelegate should be deleted on DeleteDelegate.
set_owned_by_client();
if (shell->GetDelegate())
icon_ = shell->GetDelegate()->GetDevToolsWindowIcon();
}
~DevToolsWindowDelegate() override {}
// views::WidgetDelegate:
void DeleteDelegate() override { delete this; }
views::View* GetInitiallyFocusedView() override { return view_; }
bool CanResize() const override { return true; }
bool CanMaximize() const override { return true; }
bool CanMinimize() const override { return true; }
base::string16 GetWindowTitle() const override { return shell_->GetTitle(); }
gfx::ImageSkia GetWindowAppIcon() override { return GetWindowIcon(); }
gfx::ImageSkia GetWindowIcon() override { return icon_; }
views::Widget* GetWidget() override { return widget_; }
const views::Widget* GetWidget() const override { return widget_; }
views::View* GetContentsView() override { return view_; }
views::ClientView* CreateClientView(views::Widget* widget) override {
return this;
}
// views::ClientView:
bool CanClose() override {
shell_->inspectable_web_contents()->CloseDevTools();
return false;
}
private:
InspectableWebContentsViewViews* shell_;
views::View* view_;
views::Widget* widget_;
gfx::ImageSkia icon_;
DISALLOW_COPY_AND_ASSIGN(DevToolsWindowDelegate);
};
} // namespace
InspectableWebContentsView* CreateInspectableContentsView(
InspectableWebContentsImpl* inspectable_web_contents) {
return new InspectableWebContentsViewViews(inspectable_web_contents);
}
InspectableWebContentsViewViews::InspectableWebContentsViewViews(
InspectableWebContentsImpl* inspectable_web_contents)
: inspectable_web_contents_(inspectable_web_contents),
devtools_window_web_view_(nullptr),
contents_web_view_(nullptr),
devtools_web_view_(new views::WebView(nullptr)),
devtools_visible_(false),
devtools_window_delegate_(nullptr),
title_(base::ASCIIToUTF16("Developer Tools")) {
set_owned_by_client();
if (!inspectable_web_contents_->IsGuest() &&
inspectable_web_contents_->GetWebContents()->GetNativeView()) {
views::WebView* contents_web_view = new views::WebView(nullptr);
contents_web_view->SetWebContents(
inspectable_web_contents_->GetWebContents());
contents_web_view_ = contents_web_view;
} else {
contents_web_view_ =
new views::Label(base::ASCIIToUTF16("No content under offscreen mode"));
}
devtools_web_view_->SetVisible(false);
AddChildView(devtools_web_view_);
AddChildView(contents_web_view_);
}
InspectableWebContentsViewViews::~InspectableWebContentsViewViews() {
if (devtools_window_)
inspectable_web_contents()->SaveDevToolsBounds(
devtools_window_->GetWindowBoundsInScreen());
}
views::View* InspectableWebContentsViewViews::GetView() {
return this;
}
views::View* InspectableWebContentsViewViews::GetWebView() {
return contents_web_view_;
}
void InspectableWebContentsViewViews::ShowDevTools(bool activate) {
if (devtools_visible_)
return;
devtools_visible_ = true;
if (devtools_window_) {
devtools_window_web_view_->SetWebContents(
inspectable_web_contents_->GetDevToolsWebContents());
devtools_window_->SetBounds(
inspectable_web_contents()->GetDevToolsBounds());
if (activate) {
devtools_window_->Show();
} else {
devtools_window_->ShowInactive();
}
} else {
devtools_web_view_->SetVisible(true);
devtools_web_view_->SetWebContents(
inspectable_web_contents_->GetDevToolsWebContents());
devtools_web_view_->RequestFocus();
Layout();
}
}
void InspectableWebContentsViewViews::CloseDevTools() {
if (!devtools_visible_)
return;
devtools_visible_ = false;
if (devtools_window_) {
inspectable_web_contents()->SaveDevToolsBounds(
devtools_window_->GetWindowBoundsInScreen());
devtools_window_.reset();
devtools_window_web_view_ = nullptr;
devtools_window_delegate_ = nullptr;
} else {
devtools_web_view_->SetVisible(false);
devtools_web_view_->SetWebContents(NULL);
Layout();
}
}
bool InspectableWebContentsViewViews::IsDevToolsViewShowing() {
return devtools_visible_;
}
bool InspectableWebContentsViewViews::IsDevToolsViewFocused() {
if (devtools_window_web_view_)
return devtools_window_web_view_->HasFocus();
else if (devtools_web_view_)
return devtools_web_view_->HasFocus();
else
return false;
}
void InspectableWebContentsViewViews::SetIsDocked(bool docked, bool activate) {
CloseDevTools();
if (!docked) {
devtools_window_.reset(new views::Widget);
devtools_window_web_view_ = new views::WebView(NULL);
devtools_window_delegate_ = new DevToolsWindowDelegate(
this, devtools_window_web_view_, devtools_window_.get());
views::Widget::InitParams params;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.delegate = devtools_window_delegate_;
params.bounds = inspectable_web_contents()->GetDevToolsBounds();
#if defined(USE_X11)
params.wm_role_name = "devtools";
if (GetDelegate())
GetDelegate()->GetDevToolsWindowWMClass(&params.wm_class_name,
&params.wm_class_class);
#endif
devtools_window_->Init(params);
devtools_window_->UpdateWindowIcon();
}
ShowDevTools(activate);
}
void InspectableWebContentsViewViews::SetContentsResizingStrategy(
const DevToolsContentsResizingStrategy& strategy) {
strategy_.CopyFrom(strategy);
Layout();
}
void InspectableWebContentsViewViews::SetTitle(const base::string16& title) {
if (devtools_window_) {
title_ = title;
devtools_window_->UpdateWindowTitle();
}
}
void InspectableWebContentsViewViews::Layout() {
if (!devtools_web_view_->GetVisible()) {
contents_web_view_->SetBoundsRect(GetContentsBounds());
return;
}
gfx::Size container_size(width(), height());
gfx::Rect new_devtools_bounds;
gfx::Rect new_contents_bounds;
ApplyDevToolsContentsResizingStrategy(
strategy_, container_size, &new_devtools_bounds, &new_contents_bounds);
// DevTools cares about the specific position, so we have to compensate RTL
// layout here.
new_devtools_bounds.set_x(GetMirroredXForRect(new_devtools_bounds));
new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds));
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
contents_web_view_->SetBoundsRect(new_contents_bounds);
}
} // namespace atom

View file

@ -0,0 +1,72 @@
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
#define ATOM_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
#include <memory>
#include "atom/browser/ui/inspectable_web_contents_view.h"
#include "base/compiler_specific.h"
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
#include "ui/views/view.h"
namespace views {
class WebView;
class Widget;
class WidgetDelegate;
} // namespace views
namespace atom {
class InspectableWebContentsImpl;
class InspectableWebContentsViewViews : public InspectableWebContentsView,
public views::View {
public:
explicit InspectableWebContentsViewViews(
InspectableWebContentsImpl* inspectable_web_contents_impl);
~InspectableWebContentsViewViews() override;
// InspectableWebContentsView:
views::View* GetView() override;
views::View* GetWebView() override;
void ShowDevTools(bool activate) override;
void CloseDevTools() override;
bool IsDevToolsViewShowing() override;
bool IsDevToolsViewFocused() override;
void SetIsDocked(bool docked, bool activate) override;
void SetContentsResizingStrategy(
const DevToolsContentsResizingStrategy& strategy) override;
void SetTitle(const base::string16& title) override;
InspectableWebContentsImpl* inspectable_web_contents() {
return inspectable_web_contents_;
}
const base::string16& GetTitle() const { return title_; }
private:
// views::View:
void Layout() override;
// Owns us.
InspectableWebContentsImpl* inspectable_web_contents_;
std::unique_ptr<views::Widget> devtools_window_;
views::WebView* devtools_window_web_view_;
views::View* contents_web_view_;
views::WebView* devtools_web_view_;
DevToolsContentsResizingStrategy strategy_;
bool devtools_visible_;
views::WidgetDelegate* devtools_window_delegate_;
base::string16 title_;
DISALLOW_COPY_AND_ASSIGN(InspectableWebContentsViewViews);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_

View file

@ -0,0 +1,349 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/views/menu_bar.h"
#include <memory>
#include <set>
#include <sstream>
#include "atom/browser/ui/views/submenu_button.h"
#include "atom/common/keyboard_util.h"
#include "ui/aura/window.h"
#include "ui/base/models/menu_model.h"
#include "ui/views/background.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
#if defined(USE_X11)
#include "chrome/browser/ui/libgtkui/gtk_util.h"
#endif
#if defined(OS_WIN)
#include "ui/gfx/color_utils.h"
#endif
namespace atom {
namespace {
// Default color of the menu bar.
const SkColor kDefaultColor = SkColorSetARGB(255, 233, 233, 233);
} // namespace
const char MenuBar::kViewClassName[] = "ElectronMenuBar";
MenuBarColorUpdater::MenuBarColorUpdater(MenuBar* menu_bar)
: menu_bar_(menu_bar) {}
MenuBarColorUpdater::~MenuBarColorUpdater() {}
void MenuBarColorUpdater::OnDidChangeFocus(views::View* focused_before,
views::View* focused_now) {
if (menu_bar_) {
// if we've changed window focus, update menu bar colors
const auto had_focus = menu_bar_->has_focus_;
menu_bar_->has_focus_ = focused_now != nullptr;
if (menu_bar_->has_focus_ != had_focus)
menu_bar_->UpdateViewColors();
}
}
MenuBar::MenuBar(RootView* window)
: background_color_(kDefaultColor),
window_(window),
color_updater_(new MenuBarColorUpdater(this)) {
RefreshColorCache();
UpdateViewColors();
SetFocusBehavior(FocusBehavior::ALWAYS);
SetLayoutManager(
std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
window_->GetFocusManager()->AddFocusChangeListener(color_updater_.get());
}
MenuBar::~MenuBar() {
window_->GetFocusManager()->RemoveFocusChangeListener(color_updater_.get());
}
void MenuBar::SetMenu(AtomMenuModel* model) {
menu_model_ = model;
RebuildChildren();
}
void MenuBar::SetAcceleratorVisibility(bool visible) {
for (auto* child : GetChildrenInZOrder())
static_cast<SubmenuButton*>(child)->SetAcceleratorVisibility(visible);
}
MenuBar::View* MenuBar::FindAccelChild(base::char16 key) {
for (auto* child : GetChildrenInZOrder()) {
if (static_cast<SubmenuButton*>(child)->accelerator() == key)
return child;
}
return nullptr;
}
bool MenuBar::HasAccelerator(base::char16 key) {
return FindAccelChild(key) != nullptr;
}
void MenuBar::ActivateAccelerator(base::char16 key) {
auto* child = FindAccelChild(key);
if (child)
static_cast<SubmenuButton*>(child)->Activate(nullptr);
}
int MenuBar::GetItemCount() const {
return menu_model_ ? menu_model_->GetItemCount() : 0;
}
bool MenuBar::GetMenuButtonFromScreenPoint(const gfx::Point& screenPoint,
AtomMenuModel** menu_model,
views::MenuButton** button) {
if (!GetBoundsInScreen().Contains(screenPoint))
return false;
auto children = GetChildrenInZOrder();
for (int i = 0, n = children.size(); i < n; ++i) {
if (children[i]->GetBoundsInScreen().Contains(screenPoint) &&
(menu_model_->GetTypeAt(i) == AtomMenuModel::TYPE_SUBMENU)) {
*menu_model = menu_model_->GetSubmenuModelAt(i);
*button = static_cast<views::MenuButton*>(children[i]);
return true;
}
}
return false;
}
void MenuBar::OnBeforeExecuteCommand() {
if (GetPaneFocusTraversable() != nullptr) {
RemovePaneFocus();
}
window_->RestoreFocus();
}
void MenuBar::OnMenuClosed() {
SetAcceleratorVisibility(true);
}
bool MenuBar::AcceleratorPressed(const ui::Accelerator& accelerator) {
views::View* focused_view = GetFocusManager()->GetFocusedView();
if (!ContainsForFocusSearch(this, focused_view))
return false;
switch (accelerator.key_code()) {
case ui::VKEY_MENU:
case ui::VKEY_ESCAPE: {
RemovePaneFocus();
window_->RestoreFocus();
return true;
}
case ui::VKEY_LEFT:
GetFocusManager()->AdvanceFocus(true);
return true;
case ui::VKEY_RIGHT:
GetFocusManager()->AdvanceFocus(false);
return true;
case ui::VKEY_HOME:
GetFocusManager()->SetFocusedViewWithReason(
GetFirstFocusableChild(), views::FocusManager::kReasonFocusTraversal);
return true;
case ui::VKEY_END:
GetFocusManager()->SetFocusedViewWithReason(
GetLastFocusableChild(), views::FocusManager::kReasonFocusTraversal);
return true;
default: {
auto children = GetChildrenInZOrder();
for (int i = 0, n = children.size(); i < n; ++i) {
auto* button = static_cast<SubmenuButton*>(children[i]);
bool shifted = false;
auto keycode =
atom::KeyboardCodeFromCharCode(button->accelerator(), &shifted);
if (keycode == accelerator.key_code()) {
const gfx::Point p(0, 0);
auto event = accelerator.ToKeyEvent();
OnMenuButtonClicked(button, p, &event);
return true;
}
}
return false;
}
}
}
bool MenuBar::SetPaneFocus(views::View* initial_focus) {
// TODO(zcbenz): Submit patch to upstream Chromium to fix the crash.
//
// Without this check, Electron would crash when running tests.
//
// Check failed: rules_->CanFocusWindow(window, nullptr).
// logging::LogMessage::~LogMessage
// wm::FocusController::SetFocusedWindow
// wm::FocusController::ResetFocusWithinActiveWindow
// views::View::OnFocus
// views::Button::OnFocus
// views::LabelButton::OnFocus
// views::View::Focus
// views::FocusManager::SetFocusedViewWithReason
// views::AccessiblePaneView::SetPaneFocus
// atom::MenuBar::SetPaneFocus
if (initial_focus && initial_focus->GetWidget()) {
aura::Window* window = initial_focus->GetWidget()->GetNativeWindow();
if (!window || !window->GetRootWindow())
return false;
}
bool result = views::AccessiblePaneView::SetPaneFocus(initial_focus);
if (result) {
auto children = GetChildrenInZOrder();
std::set<ui::KeyboardCode> reg;
for (int i = 0, n = children.size(); i < n; ++i) {
auto* button = static_cast<SubmenuButton*>(children[i]);
bool shifted = false;
auto keycode =
atom::KeyboardCodeFromCharCode(button->accelerator(), &shifted);
// We want the menu items to activate if the user presses the accelerator
// key, even without alt, since we are now focused on the menu bar
if (keycode != ui::VKEY_UNKNOWN && reg.find(keycode) != reg.end()) {
reg.insert(keycode);
focus_manager()->RegisterAccelerator(
ui::Accelerator(keycode, ui::EF_NONE),
ui::AcceleratorManager::kNormalPriority, this);
}
}
// We want to remove focus / hide menu bar when alt is pressed again
focus_manager()->RegisterAccelerator(
ui::Accelerator(ui::VKEY_MENU, ui::EF_ALT_DOWN),
ui::AcceleratorManager::kNormalPriority, this);
}
return result;
}
void MenuBar::RemovePaneFocus() {
views::AccessiblePaneView::RemovePaneFocus();
SetAcceleratorVisibility(false);
auto children = GetChildrenInZOrder();
std::set<ui::KeyboardCode> unreg;
for (int i = 0, n = children.size(); i < n; ++i) {
auto* button = static_cast<SubmenuButton*>(children[i]);
bool shifted = false;
auto keycode =
atom::KeyboardCodeFromCharCode(button->accelerator(), &shifted);
if (keycode != ui::VKEY_UNKNOWN && unreg.find(keycode) != unreg.end()) {
unreg.insert(keycode);
focus_manager()->UnregisterAccelerator(
ui::Accelerator(keycode, ui::EF_NONE), this);
}
}
focus_manager()->UnregisterAccelerator(
ui::Accelerator(ui::VKEY_MENU, ui::EF_ALT_DOWN), this);
}
const char* MenuBar::GetClassName() const {
return kViewClassName;
}
void MenuBar::OnMenuButtonClicked(views::Button* source,
const gfx::Point& point,
const ui::Event* event) {
// Hide the accelerator when a submenu is activated.
SetAcceleratorVisibility(false);
if (!menu_model_)
return;
if (!window_->HasFocus())
window_->RequestFocus();
int id = source->tag();
AtomMenuModel::ItemType type = menu_model_->GetTypeAt(id);
if (type != AtomMenuModel::TYPE_SUBMENU) {
menu_model_->ActivatedAt(id, 0);
return;
}
GetFocusManager()->SetFocusedViewWithReason(
source, views::FocusManager::kReasonFocusTraversal);
// Deleted in MenuDelegate::OnMenuClosed
MenuDelegate* menu_delegate = new MenuDelegate(this);
menu_delegate->RunMenu(menu_model_->GetSubmenuModelAt(id), source,
event != nullptr && event->IsKeyEvent()
? ui::MENU_SOURCE_KEYBOARD
: ui::MENU_SOURCE_MOUSE);
menu_delegate->AddObserver(this);
}
void MenuBar::RefreshColorCache() {
const ui::NativeTheme* theme = GetNativeTheme();
if (theme) {
#if defined(USE_X11)
background_color_ = libgtkui::GetBgColor("GtkMenuBar#menubar");
enabled_color_ = libgtkui::GetFgColor(
"GtkMenuBar#menubar GtkMenuItem#menuitem GtkLabel");
disabled_color_ = libgtkui::GetFgColor(
"GtkMenuBar#menubar GtkMenuItem#menuitem:disabled GtkLabel");
#else
background_color_ =
theme->GetSystemColor(ui::NativeTheme::kColorId_MenuBackgroundColor);
#endif
}
#if defined(OS_WIN)
background_color_ = color_utils::GetSysSkColor(COLOR_MENUBAR);
#endif
}
void MenuBar::OnThemeChanged() {
RefreshColorCache();
UpdateViewColors();
}
void MenuBar::RebuildChildren() {
RemoveAllChildViews(true);
for (int i = 0, n = GetItemCount(); i < n; ++i) {
auto* button =
new SubmenuButton(menu_model_->GetLabelAt(i), this, background_color_);
button->set_tag(i);
AddChildView(button);
}
UpdateViewColors();
}
void MenuBar::UpdateViewColors() {
// set menubar background color
SetBackground(views::CreateSolidBackground(background_color_));
// set child colors
if (menu_model_ == nullptr)
return;
#if defined(USE_X11)
const auto& textColor = has_focus_ ? enabled_color_ : disabled_color_;
for (auto* child : GetChildrenInZOrder()) {
auto* button = static_cast<SubmenuButton*>(child);
button->SetTextColor(views::Button::STATE_NORMAL, textColor);
button->SetTextColor(views::Button::STATE_DISABLED, disabled_color_);
button->SetTextColor(views::Button::STATE_PRESSED, enabled_color_);
button->SetTextColor(views::Button::STATE_HOVERED, textColor);
button->SetUnderlineColor(textColor);
}
#elif defined(OS_WIN)
for (auto* child : GetChildrenInZOrder()) {
auto* button = static_cast<SubmenuButton*>(child);
button->SetUnderlineColor(color_utils::GetSysSkColor(COLOR_MENUTEXT));
}
#endif
}
} // namespace atom

View file

@ -0,0 +1,114 @@
// Copyright (c) 2014 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_VIEWS_MENU_BAR_H_
#define ATOM_BROWSER_UI_VIEWS_MENU_BAR_H_
#include <memory>
#include "atom/browser/ui/atom_menu_model.h"
#include "atom/browser/ui/views/menu_delegate.h"
#include "atom/browser/ui/views/root_view.h"
#include "ui/views/accessible_pane_view.h"
#include "ui/views/controls/button/menu_button_listener.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/view.h"
namespace views {
class Button;
class MenuButton;
} // namespace views
namespace atom {
class MenuBarColorUpdater : public views::FocusChangeListener {
public:
explicit MenuBarColorUpdater(MenuBar* menu_bar);
~MenuBarColorUpdater() override;
void OnDidChangeFocus(views::View* focused_before,
views::View* focused_now) override;
void OnWillChangeFocus(views::View* focused_before,
views::View* focused_now) override {}
private:
MenuBar* menu_bar_;
};
class MenuBar : public views::AccessiblePaneView,
public views::MenuButtonListener,
public atom::MenuDelegate::Observer {
public:
static const char kViewClassName[];
explicit MenuBar(RootView* window);
~MenuBar() override;
// Replaces current menu with a new one.
void SetMenu(AtomMenuModel* menu_model);
// Shows underline under accelerators.
void SetAcceleratorVisibility(bool visible);
// Returns true if the submenu has accelerator |key|
bool HasAccelerator(base::char16 key);
// Shows the submenu whose accelerator is |key|.
void ActivateAccelerator(base::char16 key);
// Returns there are how many items in the root menu.
int GetItemCount() const;
// Get the menu under specified screen point.
bool GetMenuButtonFromScreenPoint(const gfx::Point& point,
AtomMenuModel** menu_model,
views::MenuButton** button);
// atom::MenuDelegate::Observer:
void OnBeforeExecuteCommand() override;
void OnMenuClosed() override;
// views::AccessiblePaneView:
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
bool SetPaneFocus(views::View* initial_focus) override;
void RemovePaneFocus() override;
protected:
// views::View:
const char* GetClassName() const override;
// views::MenuButtonListener:
void OnMenuButtonClicked(views::Button* source,
const gfx::Point& point,
const ui::Event* event) override;
void OnThemeChanged() override;
private:
friend class MenuBarColorUpdater;
void RebuildChildren();
void UpdateViewColors();
void RefreshColorCache();
SkColor background_color_;
#if defined(USE_X11)
SkColor enabled_color_;
SkColor disabled_color_;
#endif
RootView* window_ = nullptr;
AtomMenuModel* menu_model_ = nullptr;
View* FindAccelChild(base::char16 key);
bool has_focus_ = true;
std::unique_ptr<MenuBarColorUpdater> color_updater_;
DISALLOW_COPY_AND_ASSIGN(MenuBar);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_MENU_BAR_H_

View file

@ -0,0 +1,143 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/views/menu_delegate.h"
#include "atom/browser/ui/views/menu_bar.h"
#include "atom/browser/ui/views/menu_model_adapter.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.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_runner.h"
#include "ui/views/widget/widget.h"
namespace atom {
MenuDelegate::MenuDelegate(MenuBar* menu_bar)
: menu_bar_(menu_bar), id_(-1), hold_first_switch_(false) {}
MenuDelegate::~MenuDelegate() {}
void MenuDelegate::RunMenu(AtomMenuModel* model,
views::Button* button,
ui::MenuSourceType source_type) {
gfx::Point screen_loc;
views::View::ConvertPointToScreen(button, &screen_loc);
// Subtract 1 from the height to make the popup flush with the button border.
gfx::Rect bounds(screen_loc.x(), screen_loc.y(), button->width(),
button->height() - 1);
if (source_type == ui::MENU_SOURCE_KEYBOARD) {
hold_first_switch_ = true;
}
id_ = button->tag();
adapter_.reset(new MenuModelAdapter(model));
views::MenuItemView* item = new views::MenuItemView(this);
static_cast<MenuModelAdapter*>(adapter_.get())->BuildMenu(item);
menu_runner_.reset(new views::MenuRunner(
item,
views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS));
menu_runner_->RunMenuAt(
button->GetWidget()->GetTopLevelWidget(),
static_cast<views::MenuButton*>(button)->button_controller(), bounds,
views::MenuAnchorPosition::kTopRight, source_type);
}
void MenuDelegate::ExecuteCommand(int id) {
for (Observer& obs : observers_)
obs.OnBeforeExecuteCommand();
adapter_->ExecuteCommand(id);
}
void MenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
for (Observer& obs : observers_)
obs.OnBeforeExecuteCommand();
adapter_->ExecuteCommand(id, mouse_event_flags);
}
bool MenuDelegate::IsTriggerableEvent(views::MenuItemView* source,
const ui::Event& e) {
return adapter_->IsTriggerableEvent(source, e);
}
bool MenuDelegate::GetAccelerator(int id, ui::Accelerator* accelerator) const {
return adapter_->GetAccelerator(id, accelerator);
}
base::string16 MenuDelegate::GetLabel(int id) const {
return adapter_->GetLabel(id);
}
void MenuDelegate::GetLabelStyle(int id, LabelStyle* style) const {
return adapter_->GetLabelStyle(id, style);
}
bool MenuDelegate::IsCommandEnabled(int id) const {
return adapter_->IsCommandEnabled(id);
}
bool MenuDelegate::IsCommandVisible(int id) const {
return adapter_->IsCommandVisible(id);
}
bool MenuDelegate::IsItemChecked(int id) const {
return adapter_->IsItemChecked(id);
}
void MenuDelegate::WillShowMenu(views::MenuItemView* menu) {
adapter_->WillShowMenu(menu);
}
void MenuDelegate::WillHideMenu(views::MenuItemView* menu) {
adapter_->WillHideMenu(menu);
}
void MenuDelegate::OnMenuClosed(views::MenuItemView* menu) {
for (Observer& obs : observers_)
obs.OnMenuClosed();
// Only switch to new menu when current menu is closed.
if (button_to_open_)
button_to_open_->Activate(nullptr);
delete this;
}
views::MenuItemView* MenuDelegate::GetSiblingMenu(
views::MenuItemView* menu,
const gfx::Point& screen_point,
views::MenuAnchorPosition* anchor,
bool* has_mnemonics,
views::MenuButton**) {
if (hold_first_switch_) {
hold_first_switch_ = false;
return nullptr;
}
// TODO(zcbenz): We should follow Chromium's logics on implementing the
// sibling menu switches, this code is almost a hack.
views::MenuButton* button;
AtomMenuModel* model;
if (menu_bar_->GetMenuButtonFromScreenPoint(screen_point, &model, &button) &&
button->tag() != id_) {
bool switch_in_progress = !!button_to_open_;
// Always update target to open.
button_to_open_ = button;
// Switching menu asyncnously to avoid crash.
if (!switch_in_progress) {
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&views::MenuRunner::Cancel,
base::Unretained(menu_runner_.get())));
}
}
return nullptr;
}
} // namespace atom

View file

@ -0,0 +1,80 @@
// Copyright (c) 2014 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_VIEWS_MENU_DELEGATE_H_
#define ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_
#include <memory>
#include "atom/browser/ui/atom_menu_model.h"
#include "base/observer_list.h"
#include "ui/views/controls/menu/menu_delegate.h"
namespace views {
class MenuRunner;
class Button;
} // namespace views
namespace atom {
class MenuBar;
class MenuDelegate : public views::MenuDelegate {
public:
explicit MenuDelegate(MenuBar* menu_bar);
~MenuDelegate() override;
void RunMenu(AtomMenuModel* model,
views::Button* button,
ui::MenuSourceType source_type);
class Observer {
public:
virtual void OnBeforeExecuteCommand() = 0;
virtual void OnMenuClosed() = 0;
};
void AddObserver(Observer* obs) { observers_.AddObserver(obs); }
void RemoveObserver(const Observer* obs) { observers_.RemoveObserver(obs); }
protected:
// views::MenuDelegate:
void ExecuteCommand(int id) override;
void ExecuteCommand(int id, int mouse_event_flags) override;
bool IsTriggerableEvent(views::MenuItemView* source,
const ui::Event& e) override;
bool GetAccelerator(int id, ui::Accelerator* accelerator) const override;
base::string16 GetLabel(int id) const override;
void GetLabelStyle(int id, LabelStyle* style) const override;
bool IsCommandEnabled(int id) const override;
bool IsCommandVisible(int id) const override;
bool IsItemChecked(int id) const override;
void WillShowMenu(views::MenuItemView* menu) override;
void WillHideMenu(views::MenuItemView* menu) override;
void OnMenuClosed(views::MenuItemView* menu) override;
views::MenuItemView* GetSiblingMenu(views::MenuItemView* menu,
const gfx::Point& screen_point,
views::MenuAnchorPosition* anchor,
bool* has_mnemonics,
views::MenuButton** button) override;
private:
MenuBar* menu_bar_;
int id_;
std::unique_ptr<views::MenuDelegate> adapter_;
std::unique_ptr<views::MenuRunner> menu_runner_;
// The menu button to switch to.
views::MenuButton* button_to_open_ = nullptr;
bool hold_first_switch_;
base::ObserverList<Observer>::Unchecked observers_;
DISALLOW_COPY_AND_ASSIGN(MenuDelegate);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_

View file

@ -0,0 +1,25 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/views/menu_model_adapter.h"
namespace atom {
MenuModelAdapter::MenuModelAdapter(AtomMenuModel* menu_model)
: views::MenuModelAdapter(menu_model), menu_model_(menu_model) {}
MenuModelAdapter::~MenuModelAdapter() {}
bool MenuModelAdapter::GetAccelerator(int id,
ui::Accelerator* accelerator) const {
ui::MenuModel* model = menu_model_;
int index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
return static_cast<AtomMenuModel*>(model)->GetAcceleratorAtWithParams(
index, true, accelerator);
}
return false;
}
} // namespace atom

View file

@ -0,0 +1,29 @@
// Copyright (c) 2016 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_VIEWS_MENU_MODEL_ADAPTER_H_
#define ATOM_BROWSER_UI_VIEWS_MENU_MODEL_ADAPTER_H_
#include "atom/browser/ui/atom_menu_model.h"
#include "ui/views/controls/menu/menu_model_adapter.h"
namespace atom {
class MenuModelAdapter : public views::MenuModelAdapter {
public:
explicit MenuModelAdapter(AtomMenuModel* menu_model);
~MenuModelAdapter() override;
protected:
bool GetAccelerator(int id, ui::Accelerator* accelerator) const override;
private:
AtomMenuModel* menu_model_;
DISALLOW_COPY_AND_ASSIGN(MenuModelAdapter);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_MENU_MODEL_ADAPTER_H_

View file

@ -0,0 +1,28 @@
// 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/ui/views/native_frame_view.h"
#include "atom/browser/native_window.h"
namespace atom {
const char NativeFrameView::kViewClassName[] = "AtomNativeFrameView";
NativeFrameView::NativeFrameView(NativeWindow* window, views::Widget* widget)
: views::NativeFrameView(widget), window_(window) {}
gfx::Size NativeFrameView::GetMinimumSize() const {
return window_->GetMinimumSize();
}
gfx::Size NativeFrameView::GetMaximumSize() const {
return window_->GetMaximumSize();
}
const char* NativeFrameView::GetClassName() const {
return kViewClassName;
}
} // namespace atom

View file

@ -0,0 +1,35 @@
// 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_UI_VIEWS_NATIVE_FRAME_VIEW_H_
#define ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_
#include "ui/views/window/native_frame_view.h"
namespace atom {
class NativeWindow;
// Like the views::NativeFrameView, but returns the min/max size from the
// NativeWindowViews.
class NativeFrameView : public views::NativeFrameView {
public:
static const char kViewClassName[];
NativeFrameView(NativeWindow* window, views::Widget* widget);
protected:
// views::View:
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
const char* GetClassName() const override;
private:
NativeWindow* window_; // weak ref.
DISALLOW_COPY_AND_ASSIGN(NativeFrameView);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_

View file

@ -0,0 +1,233 @@
// Copyright (c) 2018 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/views/root_view.h"
#include "atom/browser/native_window.h"
#include "atom/browser/ui/views/menu_bar.h"
#include "content/public/browser/native_web_keyboard_event.h"
namespace atom {
namespace {
// The menu bar height in pixels.
#if defined(OS_WIN)
const int kMenuBarHeight = 20;
#else
const int kMenuBarHeight = 25;
#endif
bool IsAltKey(const content::NativeWebKeyboardEvent& event) {
return event.windows_key_code == ui::VKEY_MENU;
}
bool IsAltModifier(const content::NativeWebKeyboardEvent& event) {
typedef content::NativeWebKeyboardEvent::Modifiers Modifiers;
int modifiers = event.GetModifiers();
modifiers &= ~Modifiers::kNumLockOn;
modifiers &= ~Modifiers::kCapsLockOn;
return (modifiers == Modifiers::kAltKey) ||
(modifiers == (Modifiers::kAltKey | Modifiers::kIsLeft)) ||
(modifiers == (Modifiers::kAltKey | Modifiers::kIsRight));
}
} // namespace
RootView::RootView(NativeWindow* window)
: window_(window),
last_focused_view_tracker_(std::make_unique<views::ViewTracker>()) {
set_owned_by_client();
}
RootView::~RootView() {}
void RootView::SetMenu(AtomMenuModel* menu_model) {
if (menu_model == nullptr) {
// Remove accelerators
UnregisterAcceleratorsWithFocusManager();
// and menu bar.
SetMenuBarVisibility(false);
menu_bar_.reset();
return;
}
RegisterAcceleratorsWithFocusManager(menu_model);
// Do not show menu bar in frameless window.
if (!window_->has_frame())
return;
if (!menu_bar_) {
menu_bar_.reset(new MenuBar(this));
menu_bar_->set_owned_by_client();
if (!menu_bar_autohide_)
SetMenuBarVisibility(true);
}
menu_bar_->SetMenu(menu_model);
Layout();
}
bool RootView::HasMenu() const {
return !!menu_bar_;
}
int RootView::GetMenuBarHeight() const {
return kMenuBarHeight;
}
void RootView::SetAutoHideMenuBar(bool auto_hide) {
menu_bar_autohide_ = auto_hide;
}
bool RootView::IsMenuBarAutoHide() const {
return menu_bar_autohide_;
}
void RootView::SetMenuBarVisibility(bool visible) {
if (!window_->content_view() || !menu_bar_ || menu_bar_visible_ == visible)
return;
menu_bar_visible_ = visible;
if (visible) {
DCHECK_EQ(children().size(), 1ul);
AddChildView(menu_bar_.get());
} else {
DCHECK_EQ(children().size(), 2ul);
RemoveChildView(menu_bar_.get());
}
Layout();
}
bool RootView::IsMenuBarVisible() const {
return menu_bar_visible_;
}
void RootView::HandleKeyEvent(const content::NativeWebKeyboardEvent& event) {
if (!menu_bar_)
return;
// Show accelerator when "Alt" is pressed.
if (menu_bar_visible_ && IsAltKey(event))
menu_bar_->SetAcceleratorVisibility(event.GetType() ==
blink::WebInputEvent::kRawKeyDown);
// Show the submenu when "Alt+Key" is pressed.
if (event.GetType() == blink::WebInputEvent::kRawKeyDown &&
!IsAltKey(event) && IsAltModifier(event)) {
if (menu_bar_->HasAccelerator(event.windows_key_code)) {
if (!menu_bar_visible_) {
SetMenuBarVisibility(true);
View* focused_view = GetFocusManager()->GetFocusedView();
last_focused_view_tracker_->SetView(focused_view);
menu_bar_->RequestFocus();
}
menu_bar_->ActivateAccelerator(event.windows_key_code);
}
return;
}
// Toggle the menu bar only when a single Alt is released.
if (event.GetType() == blink::WebInputEvent::kRawKeyDown && IsAltKey(event)) {
// When a single Alt is pressed:
menu_bar_alt_pressed_ = true;
} else if (event.GetType() == blink::WebInputEvent::kKeyUp &&
IsAltKey(event) && menu_bar_alt_pressed_) {
// When a single Alt is released right after a Alt is pressed:
menu_bar_alt_pressed_ = false;
if (menu_bar_autohide_)
SetMenuBarVisibility(!menu_bar_visible_);
View* focused_view = GetFocusManager()->GetFocusedView();
last_focused_view_tracker_->SetView(focused_view);
if (menu_bar_visible_) {
menu_bar_->RequestFocus();
// Show accelerators when menu bar is focused
menu_bar_->SetAcceleratorVisibility(true);
}
} else {
// When any other keys except single Alt have been pressed/released:
menu_bar_alt_pressed_ = false;
}
}
void RootView::RestoreFocus() {
View* last_focused_view = last_focused_view_tracker_->view();
if (last_focused_view) {
GetFocusManager()->SetFocusedViewWithReason(
last_focused_view, views::FocusManager::kReasonFocusRestore);
}
if (menu_bar_autohide_)
SetMenuBarVisibility(false);
}
void RootView::ResetAltState() {
menu_bar_alt_pressed_ = false;
}
void RootView::Layout() {
if (!window_->content_view()) // Not ready yet.
return;
const auto menu_bar_bounds =
menu_bar_visible_
? gfx::Rect(insets_.left(), insets_.top(),
size().width() - insets_.width(), kMenuBarHeight)
: gfx::Rect();
if (menu_bar_)
menu_bar_->SetBoundsRect(menu_bar_bounds);
window_->content_view()->SetBoundsRect(
gfx::Rect(insets_.left(),
menu_bar_visible_ ? menu_bar_bounds.bottom() : insets_.top(),
size().width() - insets_.width(),
size().height() - menu_bar_bounds.height() - insets_.height()));
}
gfx::Size RootView::GetMinimumSize() const {
return window_->GetMinimumSize();
}
gfx::Size RootView::GetMaximumSize() const {
return window_->GetMaximumSize();
}
bool RootView::AcceleratorPressed(const ui::Accelerator& accelerator) {
return accelerator_util::TriggerAcceleratorTableCommand(&accelerator_table_,
accelerator);
}
void RootView::RegisterAcceleratorsWithFocusManager(AtomMenuModel* menu_model) {
if (!menu_model)
return;
// Clear previous accelerators.
UnregisterAcceleratorsWithFocusManager();
views::FocusManager* focus_manager = GetFocusManager();
// Register accelerators with focus manager.
accelerator_util::GenerateAcceleratorTable(&accelerator_table_, menu_model);
for (const auto& iter : accelerator_table_) {
focus_manager->RegisterAccelerator(
iter.first, ui::AcceleratorManager::kNormalPriority, this);
}
}
void RootView::UnregisterAcceleratorsWithFocusManager() {
views::FocusManager* focus_manager = GetFocusManager();
accelerator_table_.clear();
focus_manager->UnregisterAccelerators(this);
}
void RootView::SetInsets(const gfx::Insets& insets) {
if (insets != insets_) {
insets_ = insets;
Layout();
}
}
} // namespace atom

View file

@ -0,0 +1,74 @@
// Copyright (c) 2018 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_VIEWS_ROOT_VIEW_H_
#define ATOM_BROWSER_UI_VIEWS_ROOT_VIEW_H_
#include <memory>
#include "atom/browser/ui/accelerator_util.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/view.h"
#include "ui/views/view_tracker.h"
namespace content {
struct NativeWebKeyboardEvent;
}
namespace atom {
class AtomMenuModel;
class MenuBar;
class NativeWindow;
class RootView : public views::View {
public:
explicit RootView(NativeWindow* window);
~RootView() override;
void SetMenu(AtomMenuModel* menu_model);
bool HasMenu() const;
int GetMenuBarHeight() const;
void SetAutoHideMenuBar(bool auto_hide);
bool IsMenuBarAutoHide() const;
void SetMenuBarVisibility(bool visible);
bool IsMenuBarVisible() const;
void HandleKeyEvent(const content::NativeWebKeyboardEvent& event);
void ResetAltState();
void RestoreFocus();
// Register/Unregister accelerators supported by the menu model.
void RegisterAcceleratorsWithFocusManager(AtomMenuModel* menu_model);
void UnregisterAcceleratorsWithFocusManager();
void SetInsets(const gfx::Insets& insets);
gfx::Insets insets() const { return insets_; }
// views::View:
void Layout() override;
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
private:
// Parent window, weak ref.
NativeWindow* window_;
// Menu bar.
std::unique_ptr<MenuBar> menu_bar_;
bool menu_bar_autohide_ = false;
bool menu_bar_visible_ = false;
bool menu_bar_alt_pressed_ = false;
gfx::Insets insets_;
// Map from accelerator to menu item's command id.
accelerator_util::AcceleratorTable accelerator_table_;
std::unique_ptr<views::ViewTracker> last_focused_view_tracker_;
DISALLOW_COPY_AND_ASSIGN(RootView);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_ROOT_VIEW_H_

View file

@ -0,0 +1,116 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/views/submenu_button.h"
#include <memory>
#include <utility>
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/text_utils.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop_host_view.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/controls/button/label_button_border.h"
namespace atom {
SubmenuButton::SubmenuButton(const base::string16& title,
views::MenuButtonListener* menu_button_listener,
const SkColor& background_color)
: views::MenuButton(gfx::RemoveAcceleratorChar(title, '&', NULL, NULL),
menu_button_listener),
background_color_(background_color) {
#if defined(OS_LINUX)
// Dont' use native style border.
SetBorder(CreateDefaultBorder());
#endif
if (GetUnderlinePosition(title, &accelerator_, &underline_start_,
&underline_end_))
gfx::Canvas::SizeStringInt(GetText(), gfx::FontList(), &text_width_,
&text_height_, 0, 0);
SetInkDropMode(InkDropMode::ON);
set_ink_drop_base_color(
color_utils::BlendTowardMaxContrast(background_color_, 0x81));
}
SubmenuButton::~SubmenuButton() {}
std::unique_ptr<views::InkDropRipple> SubmenuButton::CreateInkDropRipple()
const {
std::unique_ptr<views::InkDropRipple> ripple(
new views::FloodFillInkDropRipple(
size(), GetInkDropCenterBasedOnLastEvent(), GetInkDropBaseColor(),
ink_drop_visible_opacity()));
return ripple;
}
std::unique_ptr<views::InkDrop> SubmenuButton::CreateInkDrop() {
std::unique_ptr<views::InkDropImpl> ink_drop =
views::Button::CreateDefaultInkDropImpl();
ink_drop->SetShowHighlightOnHover(false);
ink_drop->SetShowHighlightOnFocus(true);
return std::move(ink_drop);
}
void SubmenuButton::SetAcceleratorVisibility(bool visible) {
if (visible == show_underline_)
return;
show_underline_ = visible;
SchedulePaint();
}
void SubmenuButton::SetUnderlineColor(SkColor color) {
underline_color_ = color;
}
void SubmenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->SetName(GetAccessibleName());
node_data->role = ax::mojom::Role::kPopUpButton;
}
void SubmenuButton::PaintButtonContents(gfx::Canvas* canvas) {
views::MenuButton::PaintButtonContents(canvas);
if (show_underline_ && (underline_start_ != underline_end_)) {
float padding = (width() - text_width_) / 2;
float underline_height = (height() + text_height_) / 2 - 2;
canvas->DrawSharpLine(
gfx::PointF(underline_start_ + padding, underline_height),
gfx::PointF(underline_end_ + padding, underline_height),
underline_color_);
}
}
bool SubmenuButton::GetUnderlinePosition(const base::string16& text,
base::char16* accelerator,
int* start,
int* end) const {
int pos, span;
base::string16 trimmed = gfx::RemoveAcceleratorChar(text, '&', &pos, &span);
if (pos > -1 && span != 0) {
*accelerator = base::ToUpperASCII(trimmed[pos]);
GetCharacterPosition(trimmed, pos, start);
GetCharacterPosition(trimmed, pos + span, end);
return true;
}
return false;
}
void SubmenuButton::GetCharacterPosition(const base::string16& text,
int index,
int* pos) const {
int height = 0;
gfx::Canvas::SizeStringInt(text.substr(0, index), gfx::FontList(), pos,
&height, 0, 0);
}
} // namespace atom

View file

@ -0,0 +1,63 @@
// Copyright (c) 2014 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_VIEWS_SUBMENU_BUTTON_H_
#define ATOM_BROWSER_UI_VIEWS_SUBMENU_BUTTON_H_
#include <memory>
#include "ui/accessibility/ax_node_data.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/controls/button/menu_button.h"
namespace atom {
// Special button that used by menu bar to show submenus.
class SubmenuButton : public views::MenuButton {
public:
SubmenuButton(const base::string16& title,
views::MenuButtonListener* menu_button_listener,
const SkColor& background_color);
~SubmenuButton() override;
void SetAcceleratorVisibility(bool visible);
void SetUnderlineColor(SkColor color);
base::char16 accelerator() const { return accelerator_; }
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
// views::MenuButton:
void PaintButtonContents(gfx::Canvas* canvas) override;
// views::InkDropHostView:
std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
std::unique_ptr<views::InkDrop> CreateInkDrop() override;
private:
bool GetUnderlinePosition(const base::string16& text,
base::char16* accelerator,
int* start,
int* end) const;
void GetCharacterPosition(const base::string16& text,
int index,
int* pos) const;
base::char16 accelerator_ = 0;
bool show_underline_ = false;
int underline_start_ = 0;
int underline_end_ = 0;
int text_width_ = 0;
int text_height_ = 0;
SkColor underline_color_ = SK_ColorBLACK;
SkColor background_color_;
DISALLOW_COPY_AND_ASSIGN(SubmenuButton);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_SUBMENU_BUTTON_H_

View file

@ -0,0 +1,37 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/views/win_frame_view.h"
#include "atom/browser/native_window_views.h"
#include "ui/views/widget/widget.h"
#include "ui/views/win/hwnd_util.h"
namespace atom {
const char WinFrameView::kViewClassName[] = "WinFrameView";
WinFrameView::WinFrameView() {}
WinFrameView::~WinFrameView() {}
gfx::Rect WinFrameView::GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const {
return views::GetWindowBoundsForClientBounds(
static_cast<views::View*>(const_cast<WinFrameView*>(this)),
client_bounds);
}
int WinFrameView::NonClientHitTest(const gfx::Point& point) {
if (window_->has_frame())
return frame_->client_view()->NonClientHitTest(point);
else
return FramelessView::NonClientHitTest(point);
}
const char* WinFrameView::GetClassName() const {
return kViewClassName;
}
} // namespace atom

View file

@ -0,0 +1,32 @@
// Copyright (c) 2014 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_VIEWS_WIN_FRAME_VIEW_H_
#define ATOM_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_
#include "atom/browser/ui/views/frameless_view.h"
namespace atom {
class WinFrameView : public FramelessView {
public:
static const char kViewClassName[];
WinFrameView();
~WinFrameView() override;
// views::NonClientFrameView:
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override;
int NonClientHitTest(const gfx::Point& point) override;
// views::View:
const char* GetClassName() const override;
private:
DISALLOW_COPY_AND_ASSIGN(WinFrameView);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_

View file

@ -0,0 +1,231 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/webui/pdf_viewer_handler.h"
#include <memory>
#include <utility>
#include "atom/common/atom_constants.h"
#include "base/bind.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "content/public/browser/stream_handle.h"
#include "content/public/browser/stream_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/common/page_zoom.h"
#include "content/public/common/url_constants.h"
#include "net/http/http_response_headers.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
namespace atom {
namespace {
void CreateResponseHeadersDictionary(const net::HttpResponseHeaders* headers,
base::DictionaryValue* result) {
if (!headers)
return;
size_t iter = 0;
std::string header_name;
std::string header_value;
while (headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) {
base::Value* existing_value = nullptr;
if (result->Get(header_name, &existing_value)) {
std::string src = existing_value->GetString();
result->SetString(header_name, src + ", " + header_value);
} else {
result->SetString(header_name, header_value);
}
}
}
void PopulateStreamInfo(base::DictionaryValue* stream_info,
content::StreamInfo* stream,
const std::string& original_url) {
auto headers_dict = std::make_unique<base::DictionaryValue>();
auto stream_url = stream->handle->GetURL().spec();
CreateResponseHeadersDictionary(stream->response_headers.get(),
headers_dict.get());
stream_info->SetString("streamURL", stream_url);
stream_info->SetString("originalURL", original_url);
stream_info->Set("responseHeaders", std::move(headers_dict));
}
} // namespace
PdfViewerHandler::PdfViewerHandler(const std::string& src)
: original_url_(src) {}
PdfViewerHandler::~PdfViewerHandler() {
RemoveObserver();
}
void PdfViewerHandler::SetPdfResourceStream(content::StreamInfo* stream) {
stream_ = stream;
if (!!initialize_callback_id_.get()) {
auto list = std::make_unique<base::ListValue>();
list->Set(0, std::move(initialize_callback_id_));
Initialize(list.get());
}
}
void PdfViewerHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"initialize", base::BindRepeating(&PdfViewerHandler::Initialize,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getDefaultZoom", base::BindRepeating(&PdfViewerHandler::GetInitialZoom,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getInitialZoom", base::BindRepeating(&PdfViewerHandler::GetInitialZoom,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setZoom",
base::BindRepeating(&PdfViewerHandler::SetZoom, base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getStrings", base::BindRepeating(&PdfViewerHandler::GetStrings,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"reload",
base::BindRepeating(&PdfViewerHandler::Reload, base::Unretained(this)));
}
void PdfViewerHandler::OnJavascriptAllowed() {
AddObserver();
}
void PdfViewerHandler::OnJavascriptDisallowed() {
RemoveObserver();
}
void PdfViewerHandler::Initialize(const base::ListValue* args) {
CHECK_EQ(1U, args->GetSize());
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
if (stream_) {
CHECK(!initialize_callback_id_.get());
AllowJavascript();
auto stream_info = std::make_unique<base::DictionaryValue>();
PopulateStreamInfo(stream_info.get(), stream_, original_url_);
ResolveJavascriptCallback(*callback_id, *stream_info);
} else {
initialize_callback_id_ =
base::Value::ToUniquePtrValue(callback_id.Clone());
}
auto zoom_controller =
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
zoom_controller->SetZoomMode(WebContentsZoomController::ZoomMode::MANUAL);
zoom_controller->SetZoomLevel(0);
}
void PdfViewerHandler::GetDefaultZoom(const base::ListValue* args) {
if (!IsJavascriptAllowed())
return;
CHECK_EQ(1U, args->GetSize());
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
auto zoom_controller =
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
double zoom_level = zoom_controller->GetDefaultZoomLevel();
ResolveJavascriptCallback(
*callback_id, base::Value(content::ZoomLevelToZoomFactor(zoom_level)));
}
void PdfViewerHandler::GetInitialZoom(const base::ListValue* args) {
if (!IsJavascriptAllowed())
return;
CHECK_EQ(1U, args->GetSize());
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
auto zoom_controller =
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
double zoom_level = zoom_controller->GetZoomLevel();
ResolveJavascriptCallback(
*callback_id, base::Value(content::ZoomLevelToZoomFactor(zoom_level)));
}
void PdfViewerHandler::SetZoom(const base::ListValue* args) {
if (!IsJavascriptAllowed())
return;
CHECK_EQ(2U, args->GetSize());
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
double zoom_level = 0.0;
CHECK(args->GetDouble(1, &zoom_level));
auto zoom_controller =
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
zoom_controller->SetZoomLevel(zoom_level);
ResolveJavascriptCallback(*callback_id, base::Value(zoom_level));
}
void PdfViewerHandler::GetStrings(const base::ListValue* args) {
if (!IsJavascriptAllowed())
return;
CHECK_EQ(1U, args->GetSize());
const base::Value* callback_id;
CHECK(args->Get(0, &callback_id));
auto result = std::make_unique<base::DictionaryValue>();
// TODO(deepak1556): Generate strings from components/pdf_strings.grdp.
#define SET_STRING(id, resource) result->SetString(id, resource)
SET_STRING("passwordPrompt",
"This document is password protected. Please enter a password.");
SET_STRING("passwordSubmit", "Submit");
SET_STRING("passwordInvalid", "Incorrect password");
SET_STRING("pageLoading", "Loading...");
SET_STRING("pageLoadFailed", "Failed to load PDF document");
SET_STRING("pageReload", "Reload");
SET_STRING("bookmarks", "Bookmarks");
SET_STRING("labelPageNumber", "Page number");
SET_STRING("tooltipRotateCW", "Rotate clockwise");
SET_STRING("tooltipDownload", "Download");
SET_STRING("tooltipFitToPage", "Fit to page");
SET_STRING("tooltipFitToWidth", "Fit to width");
SET_STRING("tooltipZoomIn", "Zoom in");
SET_STRING("tooltipZoomOut", "Zoom out");
#undef SET_STRING
webui::SetLoadTimeDataDefaults(g_browser_process->GetApplicationLocale(),
result.get());
ResolveJavascriptCallback(*callback_id, *result);
}
void PdfViewerHandler::Reload(const base::ListValue* args) {
CHECK_EQ(0U, args->GetSize());
web_ui()->GetWebContents()->ReloadFocusedFrame(false);
}
void PdfViewerHandler::OnZoomLevelChanged(content::WebContents* web_contents,
double level,
bool is_temporary) {
if (web_ui()->GetWebContents() == web_contents) {
CallJavascriptFunction("cr.webUIListenerCallback",
base::Value("onZoomLevelChanged"),
base::Value(content::ZoomLevelToZoomFactor(level)));
}
}
void PdfViewerHandler::AddObserver() {
auto zoom_controller =
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
zoom_controller->AddObserver(this);
}
void PdfViewerHandler::RemoveObserver() {
auto zoom_controller =
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
zoom_controller->RemoveObserver(this);
}
} // namespace atom

View file

@ -0,0 +1,61 @@
// Copyright (c) 2017 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_WEBUI_PDF_VIEWER_HANDLER_H_
#define ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_HANDLER_H_
#include <memory>
#include <string>
#include "atom/browser/web_contents_zoom_controller.h"
#include "base/macros.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/web_ui_message_handler.h"
namespace base {
class ListValue;
}
namespace content {
struct StreamInfo;
}
namespace atom {
class PdfViewerHandler : public content::WebUIMessageHandler,
public WebContentsZoomController::Observer {
public:
explicit PdfViewerHandler(const std::string& original_url);
~PdfViewerHandler() override;
void SetPdfResourceStream(content::StreamInfo* stream);
protected:
// WebUIMessageHandler implementation.
void RegisterMessages() override;
void OnJavascriptAllowed() override;
void OnJavascriptDisallowed() override;
private:
void Initialize(const base::ListValue* args);
void GetDefaultZoom(const base::ListValue* args);
void GetInitialZoom(const base::ListValue* args);
void SetZoom(const base::ListValue* args);
void GetStrings(const base::ListValue* args);
void Reload(const base::ListValue* args);
void OnZoomLevelChanged(content::WebContents* web_contents,
double level,
bool is_temporary);
void AddObserver();
void RemoveObserver();
std::unique_ptr<base::Value> initialize_callback_id_;
content::StreamInfo* stream_ = nullptr;
std::string original_url_;
DISALLOW_COPY_AND_ASSIGN(PdfViewerHandler);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_HANDLER_H_

View file

@ -0,0 +1,258 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/webui/pdf_viewer_ui.h"
#include <map>
#include <memory>
#include <utility>
#include "atom/browser/atom_browser_context.h"
#include "atom/browser/loader/layered_resource_handler.h"
#include "atom/browser/ui/webui/pdf_viewer_handler.h"
#include "atom/common/atom_constants.h"
#include "base/sequenced_task_runner_helpers.h"
#include "base/task/post_task.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/browser/loader/stream_resource_handler.h"
#include "content/browser/resource_context_impl.h"
#include "content/browser/streams/stream.h"
#include "content/browser/streams/stream_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/stream_handle.h"
#include "content/public/browser/stream_info.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_contents.h"
#include "grit/pdf_viewer_resources_map.h"
#include "net/base/load_flags.h"
#include "net/base/mime_util.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "services/network/public/cpp/resource_response.h"
#include "ui/base/resource/resource_bundle.h"
using content::BrowserThread;
namespace atom {
namespace {
// Extracts the path value from the URL without the leading '/',
// which follows the mapping of names in pdf_viewer_resources_map.
std::string PathWithoutParams(const std::string& path) {
return GURL(kPdfViewerUIOrigin + path).path().substr(1);
}
class BundledDataSource : public content::URLDataSource {
public:
BundledDataSource() {
for (size_t i = 0; i < kPdfViewerResourcesSize; ++i) {
std::string resource_path = kPdfViewerResources[i].name;
DCHECK(path_to_resource_id_.find(resource_path) ==
path_to_resource_id_.end());
path_to_resource_id_[resource_path] = kPdfViewerResources[i].value;
}
}
// content::URLDataSource implementation.
std::string GetSource() const override { return kPdfViewerUIHost; }
void StartDataRequest(
const std::string& path,
const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
const GotDataCallback& callback) override {
std::string filename = PathWithoutParams(path);
auto entry = path_to_resource_id_.find(filename);
if (entry != path_to_resource_id_.end()) {
int resource_id = entry->second;
const ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
callback.Run(rb.LoadDataResourceBytes(resource_id));
} else {
LOG(ERROR) << "Unable to find: " << path;
callback.Run(new base::RefCountedString());
}
}
std::string GetMimeType(const std::string& path) const override {
base::FilePath::StringType ext =
base::FilePath::FromUTF8Unsafe(PathWithoutParams(path)).Extension();
std::string mime_type;
if (!ext.empty() &&
net::GetWellKnownMimeTypeFromExtension(ext.substr(1), &mime_type))
return mime_type;
return "text/html";
}
bool ShouldAddContentSecurityPolicy() const override { return false; }
bool ShouldDenyXFrameOptions() const override { return false; }
bool ShouldServeMimeTypeAsContentTypeHeader() const override { return true; }
private:
~BundledDataSource() override {}
// A map from a resource path to the resource ID.
std::map<std::string, int> path_to_resource_id_;
DISALLOW_COPY_AND_ASSIGN(BundledDataSource);
};
// Helper to convert from OnceCallback to Callback.
template <typename T>
void CallMigrationCallback(T callback,
std::unique_ptr<content::StreamInfo> stream_info) {
std::move(callback).Run(std::move(stream_info));
}
} // namespace
class PdfViewerUI::ResourceRequester
: public base::RefCountedThreadSafe<ResourceRequester,
BrowserThread::DeleteOnIOThread>,
public atom::LayeredResourceHandler::Delegate {
public:
explicit ResourceRequester(StreamResponseCallback cb)
: stream_response_cb_(std::move(cb)) {}
void StartRequest(const GURL& url,
const GURL& origin,
int render_process_id,
int render_view_id,
int render_frame_id,
content::ResourceContext* resource_context) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const net::URLRequestContext* request_context =
resource_context->GetRequestContext();
std::unique_ptr<net::URLRequest> request(
request_context->CreateRequest(url, net::DEFAULT_PRIORITY, nullptr));
request->set_method("GET");
content::ResourceDispatcherHostImpl::Get()->InitializeURLRequest(
request.get(), content::Referrer(url, blink::kWebReferrerPolicyDefault),
false, // download.
render_process_id, render_view_id, render_frame_id,
content::PREVIEWS_OFF, resource_context);
content::ResourceRequestInfoImpl* info =
content::ResourceRequestInfoImpl::ForRequest(request.get());
content::StreamContext* stream_context =
content::GetStreamContextForResourceContext(resource_context);
std::unique_ptr<content::ResourceHandler> handler =
std::make_unique<content::StreamResourceHandler>(
request.get(), stream_context->registry(), origin, false);
info->set_is_stream(true);
stream_info_.reset(new content::StreamInfo);
stream_info_->handle =
static_cast<content::StreamResourceHandler*>(handler.get())
->stream()
->CreateHandle();
stream_info_->original_url = request->url();
// Helper to fill stream response details.
handler.reset(new atom::LayeredResourceHandler(request.get(),
std::move(handler), this));
content::ResourceDispatcherHostImpl::Get()->BeginURLRequest(
std::move(request), std::move(handler),
false, // download
false, // content_initiated (download specific)
false, // do_not_prompt_for_login (download specific)
resource_context);
}
protected:
// atom::LayeredResourceHandler::Delegate:
void OnResponseStarted(network::ResourceResponse* response) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto resource_response_head = response->head;
auto headers = resource_response_head.headers;
auto mime_type = resource_response_head.mime_type;
if (headers.get())
stream_info_->response_headers =
new net::HttpResponseHeaders(headers->raw_headers());
stream_info_->mime_type = mime_type;
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&CallMigrationCallback<StreamResponseCallback>,
base::Passed(&stream_response_cb_),
base::Passed(&stream_info_)));
}
private:
friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
friend class base::DeleteHelper<ResourceRequester>;
~ResourceRequester() override {}
StreamResponseCallback stream_response_cb_;
std::unique_ptr<content::StreamInfo> stream_info_;
DISALLOW_COPY_AND_ASSIGN(ResourceRequester);
};
PdfViewerUI::PdfViewerUI(content::BrowserContext* browser_context,
content::WebUI* web_ui,
const std::string& src)
: content::WebUIController(web_ui),
content::WebContentsObserver(web_ui->GetWebContents()),
src_(src) {
pdf_handler_ = new PdfViewerHandler(src);
web_ui->AddMessageHandler(
std::unique_ptr<content::WebUIMessageHandler>(pdf_handler_));
content::URLDataSource::Add(browser_context, new BundledDataSource);
}
PdfViewerUI::~PdfViewerUI() {}
bool PdfViewerUI::OnMessageReceived(
const IPC::Message& message,
content::RenderFrameHost* render_frame_host) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PdfViewerUI, message)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void PdfViewerUI::OnPdfStreamCreated(
std::unique_ptr<content::StreamInfo> stream) {
stream_ = std::move(stream);
if (pdf_handler_)
pdf_handler_->SetPdfResourceStream(stream_.get());
resource_requester_ = nullptr;
}
void PdfViewerUI::RenderFrameCreated(content::RenderFrameHost* rfh) {
int render_process_id = rfh->GetProcess()->GetID();
int render_frame_id = rfh->GetRoutingID();
int render_view_id = rfh->GetRenderViewHost()->GetRoutingID();
auto resource_context =
web_contents()->GetBrowserContext()->GetResourceContext();
auto callback =
base::BindOnce(&PdfViewerUI::OnPdfStreamCreated, base::Unretained(this));
resource_requester_ = new ResourceRequester(std::move(callback));
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&ResourceRequester::StartRequest, resource_requester_,
GURL(src_), GURL(kPdfViewerUIOrigin), render_process_id,
render_view_id, render_frame_id, resource_context));
}
void PdfViewerUI::OnSaveURLAs(const GURL& url,
const content::Referrer& referrer) {
web_contents()->SaveFrame(url, referrer);
}
} // namespace atom

View file

@ -0,0 +1,61 @@
// Copyright (c) 2017 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_WEBUI_PDF_VIEWER_UI_H_
#define ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_UI_H_
#include <memory>
#include <string>
#include "base/macros.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui_controller.h"
#include "ipc/ipc_message.h"
namespace content {
class BrowserContext;
struct StreamInfo;
} // namespace content
namespace atom {
class PdfViewerHandler;
class PdfViewerUI : public content::WebUIController,
public content::WebContentsObserver {
public:
PdfViewerUI(content::BrowserContext* browser_context,
content::WebUI* web_ui,
const std::string& src);
~PdfViewerUI() override;
// content::WebContentsObserver:
bool OnMessageReceived(const IPC::Message& message,
content::RenderFrameHost* render_frame_host) override;
void RenderFrameCreated(content::RenderFrameHost* rfh) override;
private:
using StreamResponseCallback =
base::OnceCallback<void(std::unique_ptr<content::StreamInfo>)>;
class ResourceRequester;
void OnPdfStreamCreated(std::unique_ptr<content::StreamInfo> stream_info);
void OnSaveURLAs(const GURL& url, const content::Referrer& referrer);
// Source URL from where the PDF originates.
std::string src_;
PdfViewerHandler* pdf_handler_;
scoped_refptr<ResourceRequester> resource_requester_;
// Pdf Resource stream.
std::unique_ptr<content::StreamInfo> stream_;
DISALLOW_COPY_AND_ASSIGN(PdfViewerUI);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_UI_H_

View file

@ -0,0 +1,59 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/win/atom_desktop_native_widget_aura.h"
#include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h"
#include "ui/views/corewm/tooltip_controller.h"
#include "ui/wm/public/tooltip_client.h"
namespace atom {
AtomDesktopNativeWidgetAura::AtomDesktopNativeWidgetAura(
NativeWindowViews* native_window_view)
: views::DesktopNativeWidgetAura(native_window_view->widget()),
native_window_view_(native_window_view) {
GetNativeWindow()->SetName("AtomDesktopNativeWidgetAura");
// This is to enable the override of OnWindowActivated
wm::SetActivationChangeObserver(GetNativeWindow(), this);
}
void AtomDesktopNativeWidgetAura::InitNativeWidget(
const views::Widget::InitParams& params) {
views::Widget::InitParams modified_params = params;
desktop_window_tree_host_ = new AtomDesktopWindowTreeHostWin(
native_window_view_,
static_cast<views::DesktopNativeWidgetAura*>(params.native_widget));
modified_params.desktop_window_tree_host = desktop_window_tree_host_;
views::DesktopNativeWidgetAura::InitNativeWidget(modified_params);
}
void AtomDesktopNativeWidgetAura::Activate() {
// Activate can cause the focused window to be blurred so only
// call when the window being activated is visible. This prevents
// hidden windows from blurring the focused window when created.
if (IsVisible())
views::DesktopNativeWidgetAura::Activate();
}
void AtomDesktopNativeWidgetAura::OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
views::DesktopNativeWidgetAura::OnWindowActivated(reason, gained_active,
lost_active);
if (lost_active != nullptr) {
auto* tooltip_controller = static_cast<views::corewm::TooltipController*>(
wm::GetTooltipClient(lost_active->GetRootWindow()));
// This will cause the tooltip to be hidden when a window is deactivated,
// as it should be.
// TODO(brenca): Remove this fix when the chromium issue is fixed.
// crbug.com/724538
if (tooltip_controller != nullptr)
tooltip_controller->OnCancelMode(nullptr);
}
}
} // namespace atom

Some files were not shown because too many files have changed in this diff Show more