Merge pull request #5711 from electron/extension-code-cleanup
Implement partial chrome.* API for devtools extension
This commit is contained in:
commit
9f0fc96025
32 changed files with 1151 additions and 216 deletions
88
atom/browser/api/atom_api_render_process_preferences.cc
Normal file
88
atom/browser/api/atom_api_render_process_preferences.cc
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// 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/api/atom_api_render_process_preferences.h"
|
||||||
|
|
||||||
|
#include "atom/browser/atom_browser_client.h"
|
||||||
|
#include "atom/browser/native_window.h"
|
||||||
|
#include "atom/browser/window_list.h"
|
||||||
|
#include "atom/common/native_mate_converters/value_converter.h"
|
||||||
|
#include "atom/common/node_includes.h"
|
||||||
|
#include "content/public/browser/render_process_host.h"
|
||||||
|
#include "native_mate/dictionary.h"
|
||||||
|
#include "native_mate/object_template_builder.h"
|
||||||
|
|
||||||
|
namespace atom {
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool IsBrowserWindow(content::RenderProcessHost* process) {
|
||||||
|
content::WebContents* web_contents =
|
||||||
|
static_cast<AtomBrowserClient*>(AtomBrowserClient::Get())->
|
||||||
|
GetWebContentsFromProcessID(process->GetID());
|
||||||
|
if (!web_contents)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NativeWindow* window = NativeWindow::FromWebContents(web_contents);
|
||||||
|
if (!window)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
RenderProcessPreferences::RenderProcessPreferences(
|
||||||
|
v8::Isolate* isolate,
|
||||||
|
const atom::RenderProcessPreferences::Predicate& predicate)
|
||||||
|
: preferences_(predicate) {
|
||||||
|
Init(isolate);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderProcessPreferences::~RenderProcessPreferences() {
|
||||||
|
}
|
||||||
|
|
||||||
|
int RenderProcessPreferences::AddEntry(const base::DictionaryValue& entry) {
|
||||||
|
return preferences_.AddEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderProcessPreferences::RemoveEntry(int id) {
|
||||||
|
preferences_.RemoveEntry(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void RenderProcessPreferences::BuildPrototype(
|
||||||
|
v8::Isolate* isolate, v8::Local<v8::ObjectTemplate> prototype) {
|
||||||
|
mate::ObjectTemplateBuilder(isolate, prototype)
|
||||||
|
.SetMethod("addEntry", &RenderProcessPreferences::AddEntry)
|
||||||
|
.SetMethod("removeEntry", &RenderProcessPreferences::RemoveEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
mate::Handle<RenderProcessPreferences>
|
||||||
|
RenderProcessPreferences::ForAllBrowserWindow(v8::Isolate* isolate) {
|
||||||
|
return mate::CreateHandle(
|
||||||
|
isolate,
|
||||||
|
new RenderProcessPreferences(isolate, base::Bind(&IsBrowserWindow)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
|
||||||
|
} // namespace atom
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
|
||||||
|
v8::Local<v8::Context> context, void* priv) {
|
||||||
|
mate::Dictionary dict(context->GetIsolate(), exports);
|
||||||
|
dict.SetMethod("forAllBrowserWindow",
|
||||||
|
&atom::api::RenderProcessPreferences::ForAllBrowserWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_render_process_preferences,
|
||||||
|
Initialize)
|
44
atom/browser/api/atom_api_render_process_preferences.h
Normal file
44
atom/browser/api/atom_api_render_process_preferences.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// 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_API_ATOM_API_RENDER_PROCESS_PREFERENCES_H_
|
||||||
|
#define ATOM_BROWSER_API_ATOM_API_RENDER_PROCESS_PREFERENCES_H_
|
||||||
|
|
||||||
|
#include "atom/browser/render_process_preferences.h"
|
||||||
|
#include "native_mate/handle.h"
|
||||||
|
#include "native_mate/wrappable.h"
|
||||||
|
|
||||||
|
namespace atom {
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
class RenderProcessPreferences
|
||||||
|
: public mate::Wrappable<RenderProcessPreferences> {
|
||||||
|
public:
|
||||||
|
static mate::Handle<RenderProcessPreferences>
|
||||||
|
ForAllBrowserWindow(v8::Isolate* isolate);
|
||||||
|
|
||||||
|
static void BuildPrototype(v8::Isolate* isolate,
|
||||||
|
v8::Local<v8::ObjectTemplate> prototype);
|
||||||
|
|
||||||
|
int AddEntry(const base::DictionaryValue& entry);
|
||||||
|
void RemoveEntry(int id);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
RenderProcessPreferences(
|
||||||
|
v8::Isolate* isolate,
|
||||||
|
const atom::RenderProcessPreferences::Predicate& predicate);
|
||||||
|
~RenderProcessPreferences() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
atom::RenderProcessPreferences preferences_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(RenderProcessPreferences);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace api
|
||||||
|
|
||||||
|
} // namespace atom
|
||||||
|
|
||||||
|
#endif // ATOM_BROWSER_API_ATOM_API_RENDER_PROCESS_PREFERENCES_H_
|
|
@ -655,6 +655,11 @@ void WebContents::DevToolsOpened() {
|
||||||
isolate(), managed_web_contents()->GetDevToolsWebContents());
|
isolate(), managed_web_contents()->GetDevToolsWebContents());
|
||||||
devtools_web_contents_.Reset(isolate(), handle.ToV8());
|
devtools_web_contents_.Reset(isolate(), handle.ToV8());
|
||||||
|
|
||||||
|
// Set inspected tabID.
|
||||||
|
base::FundamentalValue tab_id(ID());
|
||||||
|
managed_web_contents()->CallClientFunction(
|
||||||
|
"DevToolsAPI.setInspectedTabId", &tab_id, nullptr, nullptr);
|
||||||
|
|
||||||
// Inherit owner window in devtools.
|
// Inherit owner window in devtools.
|
||||||
if (owner_window())
|
if (owner_window())
|
||||||
handle->SetOwnerWindow(managed_web_contents()->GetDevToolsWebContents(),
|
handle->SetOwnerWindow(managed_web_contents()->GetDevToolsWebContents(),
|
||||||
|
@ -1083,9 +1088,10 @@ void WebContents::TabTraverse(bool reverse) {
|
||||||
web_contents()->FocusThroughTabTraversal(reverse);
|
web_contents()->FocusThroughTabTraversal(reverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebContents::SendIPCMessage(const base::string16& channel,
|
bool WebContents::SendIPCMessage(bool all_frames,
|
||||||
|
const base::string16& channel,
|
||||||
const base::ListValue& args) {
|
const base::ListValue& args) {
|
||||||
return Send(new AtomViewMsg_Message(routing_id(), channel, args));
|
return Send(new AtomViewMsg_Message(routing_id(), all_frames, channel, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContents::SendInputEvent(v8::Isolate* isolate,
|
void WebContents::SendInputEvent(v8::Isolate* isolate,
|
||||||
|
@ -1333,6 +1339,8 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
|
||||||
mate::Dictionary dict(isolate, exports);
|
mate::Dictionary dict(isolate, exports);
|
||||||
dict.SetMethod("create", &atom::api::WebContents::Create);
|
dict.SetMethod("create", &atom::api::WebContents::Create);
|
||||||
dict.SetMethod("_setWrapWebContents", &atom::api::SetWrapWebContents);
|
dict.SetMethod("_setWrapWebContents", &atom::api::SetWrapWebContents);
|
||||||
|
dict.SetMethod("fromId",
|
||||||
|
&mate::TrackableObject<atom::api::WebContents>::FromWeakMapID);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -122,7 +122,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||||
void TabTraverse(bool reverse);
|
void TabTraverse(bool reverse);
|
||||||
|
|
||||||
// Send messages to browser.
|
// Send messages to browser.
|
||||||
bool SendIPCMessage(const base::string16& channel,
|
bool SendIPCMessage(bool all_frames,
|
||||||
|
const base::string16& channel,
|
||||||
const base::ListValue& args);
|
const base::ListValue& args);
|
||||||
|
|
||||||
// Send WebInputEvent to the page.
|
// Send WebInputEvent to the page.
|
||||||
|
|
|
@ -73,6 +73,17 @@ AtomBrowserClient::AtomBrowserClient() : delegate_(nullptr) {
|
||||||
AtomBrowserClient::~AtomBrowserClient() {
|
AtomBrowserClient::~AtomBrowserClient() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content::WebContents* AtomBrowserClient::GetWebContentsFromProcessID(
|
||||||
|
int process_id) {
|
||||||
|
// If the process is a pending process, we should use the old one.
|
||||||
|
if (ContainsKey(pending_processes_, process_id))
|
||||||
|
process_id = pending_processes_[process_id];
|
||||||
|
|
||||||
|
// Certain render process will be created with no associated render view,
|
||||||
|
// for example: ServiceWorker.
|
||||||
|
return WebContentsPreferences::GetWebContentsFromProcessID(process_id);
|
||||||
|
}
|
||||||
|
|
||||||
void AtomBrowserClient::RenderProcessWillLaunch(
|
void AtomBrowserClient::RenderProcessWillLaunch(
|
||||||
content::RenderProcessHost* host) {
|
content::RenderProcessHost* host) {
|
||||||
int process_id = host->GetID();
|
int process_id = host->GetID();
|
||||||
|
@ -172,14 +183,7 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// If the process is a pending process, we should use the old one.
|
content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
|
||||||
if (ContainsKey(pending_processes_, process_id))
|
|
||||||
process_id = pending_processes_[process_id];
|
|
||||||
|
|
||||||
// Certain render process will be created with no associated render view,
|
|
||||||
// for example: ServiceWorker.
|
|
||||||
content::WebContents* web_contents =
|
|
||||||
WebContentsPreferences::GetWebContentsFromProcessID(process_id);
|
|
||||||
if (!web_contents)
|
if (!web_contents)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,9 @@ class AtomBrowserClient : public brightray::BrowserClient,
|
||||||
using Delegate = content::ContentBrowserClient;
|
using Delegate = content::ContentBrowserClient;
|
||||||
void set_delegate(Delegate* delegate) { delegate_ = delegate; }
|
void set_delegate(Delegate* delegate) { delegate_ = delegate; }
|
||||||
|
|
||||||
|
// Returns the WebContents for pending render processes.
|
||||||
|
content::WebContents* GetWebContentsFromProcessID(int process_id);
|
||||||
|
|
||||||
// Don't force renderer process to restart for once.
|
// Don't force renderer process to restart for once.
|
||||||
static void SuppressRendererProcessRestartForOnce();
|
static void SuppressRendererProcessRestartForOnce();
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,24 @@
|
||||||
|
|
||||||
#include "atom/common/atom_constants.h"
|
#include "atom/common/atom_constants.h"
|
||||||
#include "base/strings/string_number_conversions.h"
|
#include "base/strings/string_number_conversions.h"
|
||||||
|
#include "base/strings/utf_string_conversions.h"
|
||||||
|
#include "net/base/mime_util.h"
|
||||||
#include "net/base/net_errors.h"
|
#include "net/base/net_errors.h"
|
||||||
|
|
||||||
namespace atom {
|
namespace atom {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string GetExtFromURL(const GURL& url) {
|
||||||
|
std::string spec = url.spec();
|
||||||
|
size_t index = spec.find_last_of('.');
|
||||||
|
if (index == std::string::npos || index == spec.size())
|
||||||
|
return std::string();
|
||||||
|
return spec.substr(index + 1, spec.size() - index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
URLRequestBufferJob::URLRequestBufferJob(
|
URLRequestBufferJob::URLRequestBufferJob(
|
||||||
net::URLRequest* request, net::NetworkDelegate* network_delegate)
|
net::URLRequest* request, net::NetworkDelegate* network_delegate)
|
||||||
: JsAsker<net::URLRequestSimpleJob>(request, network_delegate),
|
: JsAsker<net::URLRequestSimpleJob>(request, network_delegate),
|
||||||
|
@ -30,6 +44,15 @@ void URLRequestBufferJob::StartAsync(std::unique_ptr<base::Value> options) {
|
||||||
options->GetAsBinary(&binary);
|
options->GetAsBinary(&binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mime_type_.empty()) {
|
||||||
|
std::string ext = GetExtFromURL(request()->url());
|
||||||
|
#if defined(OS_WIN)
|
||||||
|
net::GetWellKnownMimeTypeFromExtension(base::UTF8ToUTF16(ext), &mime_type_);
|
||||||
|
#else
|
||||||
|
net::GetWellKnownMimeTypeFromExtension(ext, &mime_type_);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
NotifyStartError(net::URLRequestStatus(
|
NotifyStartError(net::URLRequestStatus(
|
||||||
net::URLRequestStatus::FAILED, net::ERR_NOT_IMPLEMENTED));
|
net::URLRequestStatus::FAILED, net::ERR_NOT_IMPLEMENTED));
|
||||||
|
|
63
atom/browser/render_process_preferences.cc
Normal file
63
atom/browser/render_process_preferences.cc
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// 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/render_process_preferences.h"
|
||||||
|
|
||||||
|
#include "atom/common/api/api_messages.h"
|
||||||
|
#include "content/public/browser/notification_service.h"
|
||||||
|
#include "content/public/browser/notification_types.h"
|
||||||
|
#include "content/public/browser/render_process_host.h"
|
||||||
|
|
||||||
|
namespace atom {
|
||||||
|
|
||||||
|
RenderProcessPreferences::RenderProcessPreferences(const Predicate& predicate)
|
||||||
|
: predicate_(predicate),
|
||||||
|
next_id_(0),
|
||||||
|
cache_needs_update_(true) {
|
||||||
|
registrar_.Add(this,
|
||||||
|
content::NOTIFICATION_RENDERER_PROCESS_CREATED,
|
||||||
|
content::NotificationService::AllBrowserContextsAndSources());
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderProcessPreferences::~RenderProcessPreferences() {
|
||||||
|
}
|
||||||
|
|
||||||
|
int RenderProcessPreferences::AddEntry(const base::DictionaryValue& entry) {
|
||||||
|
int id = ++next_id_;
|
||||||
|
entries_[id] = entry.CreateDeepCopy();
|
||||||
|
cache_needs_update_ = true;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderProcessPreferences::RemoveEntry(int id) {
|
||||||
|
cache_needs_update_ = true;
|
||||||
|
entries_.erase(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderProcessPreferences::Observe(
|
||||||
|
int type,
|
||||||
|
const content::NotificationSource& source,
|
||||||
|
const content::NotificationDetails& details) {
|
||||||
|
DCHECK_EQ(type, content::NOTIFICATION_RENDERER_PROCESS_CREATED);
|
||||||
|
content::RenderProcessHost* process =
|
||||||
|
content::Source<content::RenderProcessHost>(source).ptr();
|
||||||
|
|
||||||
|
if (!predicate_.Run(process))
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdateCache();
|
||||||
|
process->Send(new AtomMsg_UpdatePreferences(cached_entries_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderProcessPreferences::UpdateCache() {
|
||||||
|
if (!cache_needs_update_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cached_entries_.Clear();
|
||||||
|
for (const auto& iter : entries_)
|
||||||
|
cached_entries_.Append(iter.second->CreateDeepCopy());
|
||||||
|
cache_needs_update_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace atom
|
61
atom/browser/render_process_preferences.h
Normal file
61
atom/browser/render_process_preferences.h
Normal 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.
|
||||||
|
|
||||||
|
#ifndef ATOM_BROWSER_RENDER_PROCESS_PREFERENCES_H_
|
||||||
|
#define ATOM_BROWSER_RENDER_PROCESS_PREFERENCES_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "base/callback.h"
|
||||||
|
#include "base/values.h"
|
||||||
|
#include "content/public/browser/notification_observer.h"
|
||||||
|
#include "content/public/browser/notification_registrar.h"
|
||||||
|
|
||||||
|
namespace content {
|
||||||
|
class RenderProcessHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace atom {
|
||||||
|
|
||||||
|
// Sets user preferences for render processes.
|
||||||
|
class RenderProcessPreferences : public content::NotificationObserver {
|
||||||
|
public:
|
||||||
|
using Predicate = base::Callback<bool(content::RenderProcessHost*)>;
|
||||||
|
|
||||||
|
// The |predicate| is used to determine whether to set preferences for a
|
||||||
|
// render process.
|
||||||
|
explicit RenderProcessPreferences(const Predicate& predicate);
|
||||||
|
virtual ~RenderProcessPreferences();
|
||||||
|
|
||||||
|
int AddEntry(const base::DictionaryValue& entry);
|
||||||
|
void RemoveEntry(int id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// content::NotificationObserver:
|
||||||
|
void Observe(int type,
|
||||||
|
const content::NotificationSource& source,
|
||||||
|
const content::NotificationDetails& details) override;
|
||||||
|
|
||||||
|
void UpdateCache();
|
||||||
|
|
||||||
|
// Manages our notification registrations.
|
||||||
|
content::NotificationRegistrar registrar_;
|
||||||
|
|
||||||
|
Predicate predicate_;
|
||||||
|
|
||||||
|
int next_id_;
|
||||||
|
std::unordered_map<int, std::unique_ptr<base::DictionaryValue>> entries_;
|
||||||
|
|
||||||
|
// We need to convert the |entries_| to ListValue for multiple times, this
|
||||||
|
// caches is only updated when we are sending messages.
|
||||||
|
bool cache_needs_update_;
|
||||||
|
base::ListValue cached_entries_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(RenderProcessPreferences);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace atom
|
||||||
|
|
||||||
|
#endif // ATOM_BROWSER_RENDER_PROCESS_PREFERENCES_H_
|
|
@ -150,6 +150,16 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
|
||||||
command_line->AppendSwitch(switches::kScrollBounce);
|
command_line->AppendSwitch(switches::kScrollBounce);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Custom command line switches.
|
||||||
|
const base::ListValue* args;
|
||||||
|
if (web_preferences.GetList("commandLineSwitches", &args)) {
|
||||||
|
for (size_t i = 0; i < args->GetSize(); ++i) {
|
||||||
|
std::string arg;
|
||||||
|
if (args->GetString(i, &arg) && !arg.empty())
|
||||||
|
command_line->AppendSwitch(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enable blink features.
|
// Enable blink features.
|
||||||
std::string blink_features;
|
std::string blink_features;
|
||||||
if (web_preferences.GetString(options::kBlinkFeatures, &blink_features))
|
if (web_preferences.GetString(options::kBlinkFeatures, &blink_features))
|
||||||
|
|
|
@ -29,6 +29,7 @@ class WebContentsPreferences
|
||||||
: public content::WebContentsUserData<WebContentsPreferences> {
|
: public content::WebContentsUserData<WebContentsPreferences> {
|
||||||
public:
|
public:
|
||||||
// Get WebContents according to process ID.
|
// Get WebContents according to process ID.
|
||||||
|
// FIXME(zcbenz): This method does not belong here.
|
||||||
static content::WebContents* GetWebContentsFromProcessID(int process_id);
|
static content::WebContents* GetWebContentsFromProcessID(int process_id);
|
||||||
|
|
||||||
// Append command paramters according to |web_contents|'s preferences.
|
// Append command paramters according to |web_contents|'s preferences.
|
||||||
|
|
|
@ -30,10 +30,14 @@ IPC_SYNC_MESSAGE_ROUTED2_1(AtomViewHostMsg_Message_Sync,
|
||||||
base::ListValue /* arguments */,
|
base::ListValue /* arguments */,
|
||||||
base::string16 /* result (in JSON) */)
|
base::string16 /* result (in JSON) */)
|
||||||
|
|
||||||
IPC_MESSAGE_ROUTED2(AtomViewMsg_Message,
|
IPC_MESSAGE_ROUTED3(AtomViewMsg_Message,
|
||||||
|
bool /* send_to_all */,
|
||||||
base::string16 /* channel */,
|
base::string16 /* channel */,
|
||||||
base::ListValue /* arguments */)
|
base::ListValue /* arguments */)
|
||||||
|
|
||||||
// Sent by the renderer when the draggable regions are updated.
|
// Sent by the renderer when the draggable regions are updated.
|
||||||
IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions,
|
IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions,
|
||||||
std::vector<atom::DraggableRegion> /* regions */)
|
std::vector<atom::DraggableRegion> /* regions */)
|
||||||
|
|
||||||
|
// Update renderer process preferences.
|
||||||
|
IPC_MESSAGE_CONTROL1(AtomMsg_UpdatePreferences, base::ListValue)
|
||||||
|
|
|
@ -35,7 +35,7 @@ void RemoteCallbackFreer::RunDestructor() {
|
||||||
base::ASCIIToUTF16("ELECTRON_RENDERER_RELEASE_CALLBACK");
|
base::ASCIIToUTF16("ELECTRON_RENDERER_RELEASE_CALLBACK");
|
||||||
base::ListValue args;
|
base::ListValue args;
|
||||||
args.AppendInteger(object_id_);
|
args.AppendInteger(object_id_);
|
||||||
Send(new AtomViewMsg_Message(routing_id(), channel, args));
|
Send(new AtomViewMsg_Message(routing_id(), false, channel, args));
|
||||||
|
|
||||||
Observe(nullptr);
|
Observe(nullptr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ REFERENCE_MODULE(atom_browser_power_monitor);
|
||||||
REFERENCE_MODULE(atom_browser_power_save_blocker);
|
REFERENCE_MODULE(atom_browser_power_save_blocker);
|
||||||
REFERENCE_MODULE(atom_browser_protocol);
|
REFERENCE_MODULE(atom_browser_protocol);
|
||||||
REFERENCE_MODULE(atom_browser_global_shortcut);
|
REFERENCE_MODULE(atom_browser_global_shortcut);
|
||||||
|
REFERENCE_MODULE(atom_browser_render_process_preferences);
|
||||||
REFERENCE_MODULE(atom_browser_session);
|
REFERENCE_MODULE(atom_browser_session);
|
||||||
REFERENCE_MODULE(atom_browser_system_preferences);
|
REFERENCE_MODULE(atom_browser_system_preferences);
|
||||||
REFERENCE_MODULE(atom_browser_tray);
|
REFERENCE_MODULE(atom_browser_tray);
|
||||||
|
|
|
@ -59,6 +59,34 @@ std::vector<v8::Local<v8::Value>> ListValueToVector(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmitIPCEvent(blink::WebFrame* frame,
|
||||||
|
const base::string16& channel,
|
||||||
|
const base::ListValue& args) {
|
||||||
|
if (!frame || frame->isWebRemoteFrame())
|
||||||
|
return;
|
||||||
|
|
||||||
|
v8::Isolate* isolate = blink::mainThreadIsolate();
|
||||||
|
v8::HandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
v8::Local<v8::Context> context = frame->mainWorldScriptContext();
|
||||||
|
v8::Context::Scope context_scope(context);
|
||||||
|
|
||||||
|
// Only emit IPC event for context with node integration.
|
||||||
|
node::Environment* env = node::Environment::GetCurrent(context);
|
||||||
|
if (!env)
|
||||||
|
return;
|
||||||
|
|
||||||
|
v8::Local<v8::Object> ipc;
|
||||||
|
if (GetIPCObject(isolate, context, &ipc)) {
|
||||||
|
auto args_vector = ListValueToVector(isolate, args);
|
||||||
|
// Insert the Event object, event.sender is ipc.
|
||||||
|
mate::Dictionary event = mate::Dictionary::CreateEmpty(isolate);
|
||||||
|
event.Set("sender", ipc);
|
||||||
|
args_vector.insert(args_vector.begin(), event.GetHandle());
|
||||||
|
mate::EmitEvent(isolate, ipc, channel, args_vector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
base::StringPiece NetResourceProvider(int key) {
|
base::StringPiece NetResourceProvider(int key) {
|
||||||
if (key == IDR_DIR_HEADER_HTML) {
|
if (key == IDR_DIR_HEADER_HTML) {
|
||||||
base::StringPiece html_data =
|
base::StringPiece html_data =
|
||||||
|
@ -123,7 +151,8 @@ bool AtomRenderViewObserver::OnMessageReceived(const IPC::Message& message) {
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel,
|
void AtomRenderViewObserver::OnBrowserMessage(bool send_to_all,
|
||||||
|
const base::string16& channel,
|
||||||
const base::ListValue& args) {
|
const base::ListValue& args) {
|
||||||
if (!document_created_)
|
if (!document_created_)
|
||||||
return;
|
return;
|
||||||
|
@ -135,20 +164,13 @@ void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel,
|
||||||
if (!frame || frame->isWebRemoteFrame())
|
if (!frame || frame->isWebRemoteFrame())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
v8::Isolate* isolate = blink::mainThreadIsolate();
|
EmitIPCEvent(frame, channel, args);
|
||||||
v8::HandleScope handle_scope(isolate);
|
|
||||||
|
|
||||||
v8::Local<v8::Context> context = frame->mainWorldScriptContext();
|
// Also send the message to all sub-frames.
|
||||||
v8::Context::Scope context_scope(context);
|
if (send_to_all) {
|
||||||
|
for (blink::WebFrame* child = frame->firstChild(); child;
|
||||||
v8::Local<v8::Object> ipc;
|
child = child->nextSibling())
|
||||||
if (GetIPCObject(isolate, context, &ipc)) {
|
EmitIPCEvent(child, channel, args);
|
||||||
auto args_vector = ListValueToVector(isolate, args);
|
|
||||||
// Insert the Event object, event.sender is ipc.
|
|
||||||
mate::Dictionary event = mate::Dictionary::CreateEmpty(isolate);
|
|
||||||
event.Set("sender", ipc);
|
|
||||||
args_vector.insert(args_vector.begin(), event.GetHandle());
|
|
||||||
mate::EmitEvent(isolate, ipc, channel, args_vector);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,8 @@ class AtomRenderViewObserver : public content::RenderViewObserver {
|
||||||
void DraggableRegionsChanged(blink::WebFrame* frame) override;
|
void DraggableRegionsChanged(blink::WebFrame* frame) override;
|
||||||
bool OnMessageReceived(const IPC::Message& message) override;
|
bool OnMessageReceived(const IPC::Message& message) override;
|
||||||
|
|
||||||
void OnBrowserMessage(const base::string16& channel,
|
void OnBrowserMessage(bool send_to_all,
|
||||||
|
const base::string16& channel,
|
||||||
const base::ListValue& args);
|
const base::ListValue& args);
|
||||||
|
|
||||||
// Weak reference to renderer client.
|
// Weak reference to renderer client.
|
||||||
|
|
|
@ -11,12 +11,14 @@
|
||||||
#include "atom/common/api/atom_bindings.h"
|
#include "atom/common/api/atom_bindings.h"
|
||||||
#include "atom/common/api/event_emitter_caller.h"
|
#include "atom/common/api/event_emitter_caller.h"
|
||||||
#include "atom/common/color_util.h"
|
#include "atom/common/color_util.h"
|
||||||
|
#include "atom/common/native_mate_converters/value_converter.h"
|
||||||
#include "atom/common/node_bindings.h"
|
#include "atom/common/node_bindings.h"
|
||||||
#include "atom/common/node_includes.h"
|
#include "atom/common/node_includes.h"
|
||||||
#include "atom/common/options_switches.h"
|
#include "atom/common/options_switches.h"
|
||||||
#include "atom/renderer/atom_render_view_observer.h"
|
#include "atom/renderer/atom_render_view_observer.h"
|
||||||
#include "atom/renderer/guest_view_container.h"
|
#include "atom/renderer/guest_view_container.h"
|
||||||
#include "atom/renderer/node_array_buffer_bridge.h"
|
#include "atom/renderer/node_array_buffer_bridge.h"
|
||||||
|
#include "atom/renderer/preferences_manager.h"
|
||||||
#include "base/command_line.h"
|
#include "base/command_line.h"
|
||||||
#include "base/strings/utf_string_conversions.h"
|
#include "base/strings/utf_string_conversions.h"
|
||||||
#include "chrome/renderer/media/chrome_key_systems.h"
|
#include "chrome/renderer/media/chrome_key_systems.h"
|
||||||
|
@ -29,8 +31,10 @@
|
||||||
#include "content/public/renderer/render_thread.h"
|
#include "content/public/renderer/render_thread.h"
|
||||||
#include "content/public/renderer/render_view.h"
|
#include "content/public/renderer/render_view.h"
|
||||||
#include "ipc/ipc_message_macros.h"
|
#include "ipc/ipc_message_macros.h"
|
||||||
|
#include "native_mate/dictionary.h"
|
||||||
#include "net/base/net_errors.h"
|
#include "net/base/net_errors.h"
|
||||||
#include "third_party/WebKit/public/web/WebCustomElement.h"
|
#include "third_party/WebKit/public/web/WebCustomElement.h"
|
||||||
|
#include "third_party/WebKit/public/web/WebDocument.h"
|
||||||
#include "third_party/WebKit/public/web/WebFrameWidget.h"
|
#include "third_party/WebKit/public/web/WebFrameWidget.h"
|
||||||
#include "third_party/WebKit/public/web/WebLocalFrame.h"
|
#include "third_party/WebKit/public/web/WebLocalFrame.h"
|
||||||
#include "third_party/WebKit/public/web/WebPluginParams.h"
|
#include "third_party/WebKit/public/web/WebPluginParams.h"
|
||||||
|
@ -59,6 +63,7 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
|
||||||
AtomRenderFrameObserver(content::RenderFrame* frame,
|
AtomRenderFrameObserver(content::RenderFrame* frame,
|
||||||
AtomRendererClient* renderer_client)
|
AtomRendererClient* renderer_client)
|
||||||
: content::RenderFrameObserver(frame),
|
: content::RenderFrameObserver(frame),
|
||||||
|
render_frame_(frame),
|
||||||
world_id_(-1),
|
world_id_(-1),
|
||||||
renderer_client_(renderer_client) {}
|
renderer_client_(renderer_client) {}
|
||||||
|
|
||||||
|
@ -69,22 +74,45 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
|
||||||
if (world_id_ != -1 && world_id_ != world_id)
|
if (world_id_ != -1 && world_id_ != world_id)
|
||||||
return;
|
return;
|
||||||
world_id_ = world_id;
|
world_id_ = world_id;
|
||||||
renderer_client_->DidCreateScriptContext(context);
|
renderer_client_->DidCreateScriptContext(context, render_frame_);
|
||||||
}
|
}
|
||||||
void WillReleaseScriptContext(v8::Local<v8::Context> context,
|
void WillReleaseScriptContext(v8::Local<v8::Context> context,
|
||||||
int world_id) override {
|
int world_id) override {
|
||||||
if (world_id_ != world_id)
|
if (world_id_ != world_id)
|
||||||
return;
|
return;
|
||||||
renderer_client_->WillReleaseScriptContext(context);
|
renderer_client_->WillReleaseScriptContext(context, render_frame_);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
content::RenderFrame* render_frame_;
|
||||||
int world_id_;
|
int world_id_;
|
||||||
AtomRendererClient* renderer_client_;
|
AtomRendererClient* renderer_client_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver);
|
DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
v8::Local<v8::Value> GetRenderProcessPreferences(
|
||||||
|
const PreferencesManager* preferences_manager, v8::Isolate* isolate) {
|
||||||
|
if (preferences_manager->preferences())
|
||||||
|
return mate::ConvertToV8(isolate, *preferences_manager->preferences());
|
||||||
|
else
|
||||||
|
return v8::Null(isolate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddRenderBindings(v8::Isolate* isolate,
|
||||||
|
v8::Local<v8::Object> process,
|
||||||
|
const PreferencesManager* preferences_manager) {
|
||||||
|
mate::Dictionary dict(isolate, process);
|
||||||
|
dict.SetMethod(
|
||||||
|
"getRenderProcessPreferences",
|
||||||
|
base::Bind(GetRenderProcessPreferences, preferences_manager));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDevToolsExtension(content::RenderFrame* render_frame) {
|
||||||
|
return static_cast<GURL>(render_frame->GetWebFrame()->document().url())
|
||||||
|
.SchemeIs("chrome-extension");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
AtomRendererClient::AtomRendererClient()
|
AtomRendererClient::AtomRendererClient()
|
||||||
|
@ -101,6 +129,8 @@ void AtomRendererClient::RenderThreadStarted() {
|
||||||
|
|
||||||
OverrideNodeArrayBuffer();
|
OverrideNodeArrayBuffer();
|
||||||
|
|
||||||
|
preferences_manager_.reset(new PreferencesManager);
|
||||||
|
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
// Set ApplicationUserModelID in renderer process.
|
// Set ApplicationUserModelID in renderer process.
|
||||||
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
||||||
|
@ -128,15 +158,11 @@ void AtomRendererClient::RenderThreadStarted() {
|
||||||
void AtomRendererClient::RenderFrameCreated(
|
void AtomRendererClient::RenderFrameCreated(
|
||||||
content::RenderFrame* render_frame) {
|
content::RenderFrame* render_frame) {
|
||||||
new PepperHelper(render_frame);
|
new PepperHelper(render_frame);
|
||||||
|
new AtomRenderFrameObserver(render_frame, this);
|
||||||
|
|
||||||
// Allow file scheme to handle service worker by default.
|
// Allow file scheme to handle service worker by default.
|
||||||
|
// FIXME(zcbenz): Can this be moved elsewhere?
|
||||||
blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file");
|
blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file");
|
||||||
|
|
||||||
// Only insert node integration for the main frame.
|
|
||||||
if (!render_frame->IsMainFrame())
|
|
||||||
return;
|
|
||||||
|
|
||||||
new AtomRenderFrameObserver(render_frame, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) {
|
void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) {
|
||||||
|
@ -164,6 +190,23 @@ void AtomRendererClient::RunScriptsAtDocumentStart(
|
||||||
// Make sure every page will get a script context created.
|
// Make sure every page will get a script context created.
|
||||||
render_frame->GetWebFrame()->executeScript(
|
render_frame->GetWebFrame()->executeScript(
|
||||||
blink::WebScriptSource("void 0"));
|
blink::WebScriptSource("void 0"));
|
||||||
|
|
||||||
|
// Inform the document start pharse.
|
||||||
|
node::Environment* env = node_bindings_->uv_env();
|
||||||
|
if (env) {
|
||||||
|
v8::HandleScope handle_scope(env->isolate());
|
||||||
|
mate::EmitEvent(env->isolate(), env->process_object(), "document-start");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AtomRendererClient::RunScriptsAtDocumentEnd(
|
||||||
|
content::RenderFrame* render_frame) {
|
||||||
|
// Inform the document end pharse.
|
||||||
|
node::Environment* env = node_bindings_->uv_env();
|
||||||
|
if (env) {
|
||||||
|
v8::HandleScope handle_scope(env->isolate());
|
||||||
|
mate::EmitEvent(env->isolate(), env->process_object(), "document-end");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blink::WebSpeechSynthesizer* AtomRendererClient::OverrideSpeechSynthesizer(
|
blink::WebSpeechSynthesizer* AtomRendererClient::OverrideSpeechSynthesizer(
|
||||||
|
@ -186,7 +229,12 @@ bool AtomRendererClient::OverrideCreatePlugin(
|
||||||
}
|
}
|
||||||
|
|
||||||
void AtomRendererClient::DidCreateScriptContext(
|
void AtomRendererClient::DidCreateScriptContext(
|
||||||
v8::Handle<v8::Context> context) {
|
v8::Handle<v8::Context> context, content::RenderFrame* render_frame) {
|
||||||
|
// Only allow node integration for the main frame, unless it is a devtools
|
||||||
|
// extension page.
|
||||||
|
if (!render_frame->IsMainFrame() && !IsDevToolsExtension(render_frame))
|
||||||
|
return;
|
||||||
|
|
||||||
// Whether the node binding has been initialized.
|
// Whether the node binding has been initialized.
|
||||||
bool first_time = node_bindings_->uv_env() == nullptr;
|
bool first_time = node_bindings_->uv_env() == nullptr;
|
||||||
|
|
||||||
|
@ -201,6 +249,8 @@ void AtomRendererClient::DidCreateScriptContext(
|
||||||
|
|
||||||
// Add atom-shell extended APIs.
|
// Add atom-shell extended APIs.
|
||||||
atom_bindings_->BindTo(env->isolate(), env->process_object());
|
atom_bindings_->BindTo(env->isolate(), env->process_object());
|
||||||
|
AddRenderBindings(env->isolate(), env->process_object(),
|
||||||
|
preferences_manager_.get());
|
||||||
|
|
||||||
// Load everything.
|
// Load everything.
|
||||||
node_bindings_->LoadEnvironment(env);
|
node_bindings_->LoadEnvironment(env);
|
||||||
|
@ -215,8 +265,9 @@ void AtomRendererClient::DidCreateScriptContext(
|
||||||
}
|
}
|
||||||
|
|
||||||
void AtomRendererClient::WillReleaseScriptContext(
|
void AtomRendererClient::WillReleaseScriptContext(
|
||||||
v8::Handle<v8::Context> context) {
|
v8::Handle<v8::Context> context, content::RenderFrame* render_frame) {
|
||||||
node::Environment* env = node::Environment::GetCurrent(context);
|
node::Environment* env = node::Environment::GetCurrent(context);
|
||||||
|
if (env)
|
||||||
mate::EmitEvent(env->isolate(), env->process_object(), "exit");
|
mate::EmitEvent(env->isolate(), env->process_object(), "exit");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
namespace atom {
|
namespace atom {
|
||||||
|
|
||||||
class AtomBindings;
|
class AtomBindings;
|
||||||
|
class PreferencesManager;
|
||||||
class NodeBindings;
|
class NodeBindings;
|
||||||
|
|
||||||
class AtomRendererClient : public content::ContentRendererClient {
|
class AtomRendererClient : public content::ContentRendererClient {
|
||||||
|
@ -20,8 +21,10 @@ class AtomRendererClient : public content::ContentRendererClient {
|
||||||
AtomRendererClient();
|
AtomRendererClient();
|
||||||
virtual ~AtomRendererClient();
|
virtual ~AtomRendererClient();
|
||||||
|
|
||||||
void DidCreateScriptContext(v8::Handle<v8::Context> context);
|
void DidCreateScriptContext(
|
||||||
void WillReleaseScriptContext(v8::Handle<v8::Context> context);
|
v8::Handle<v8::Context> context, content::RenderFrame* render_frame);
|
||||||
|
void WillReleaseScriptContext(
|
||||||
|
v8::Handle<v8::Context> context, content::RenderFrame* render_frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum NodeIntegration {
|
enum NodeIntegration {
|
||||||
|
@ -36,6 +39,7 @@ class AtomRendererClient : public content::ContentRendererClient {
|
||||||
void RenderFrameCreated(content::RenderFrame*) override;
|
void RenderFrameCreated(content::RenderFrame*) override;
|
||||||
void RenderViewCreated(content::RenderView*) override;
|
void RenderViewCreated(content::RenderView*) override;
|
||||||
void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
|
void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
|
||||||
|
void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override;
|
||||||
blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer(
|
blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer(
|
||||||
blink::WebSpeechSynthesizerClient* client) override;
|
blink::WebSpeechSynthesizerClient* client) override;
|
||||||
bool OverrideCreatePlugin(content::RenderFrame* render_frame,
|
bool OverrideCreatePlugin(content::RenderFrame* render_frame,
|
||||||
|
@ -61,6 +65,7 @@ class AtomRendererClient : public content::ContentRendererClient {
|
||||||
|
|
||||||
std::unique_ptr<NodeBindings> node_bindings_;
|
std::unique_ptr<NodeBindings> node_bindings_;
|
||||||
std::unique_ptr<AtomBindings> atom_bindings_;
|
std::unique_ptr<AtomBindings> atom_bindings_;
|
||||||
|
std::unique_ptr<PreferencesManager> preferences_manager_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(AtomRendererClient);
|
DISALLOW_COPY_AND_ASSIGN(AtomRendererClient);
|
||||||
};
|
};
|
||||||
|
|
34
atom/renderer/preferences_manager.cc
Normal file
34
atom/renderer/preferences_manager.cc
Normal 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/renderer/preferences_manager.h"
|
||||||
|
|
||||||
|
#include "atom/common/api/api_messages.h"
|
||||||
|
#include "content/public/renderer/render_thread.h"
|
||||||
|
|
||||||
|
namespace atom {
|
||||||
|
|
||||||
|
PreferencesManager::PreferencesManager() {
|
||||||
|
content::RenderThread::Get()->AddObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
PreferencesManager::~PreferencesManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreferencesManager::OnControlMessageReceived(
|
||||||
|
const IPC::Message& message) {
|
||||||
|
bool handled = true;
|
||||||
|
IPC_BEGIN_MESSAGE_MAP(PreferencesManager, message)
|
||||||
|
IPC_MESSAGE_HANDLER(AtomMsg_UpdatePreferences, OnUpdatePreferences)
|
||||||
|
IPC_MESSAGE_UNHANDLED(handled = false)
|
||||||
|
IPC_END_MESSAGE_MAP()
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreferencesManager::OnUpdatePreferences(
|
||||||
|
const base::ListValue& preferences) {
|
||||||
|
preferences_ = preferences.CreateDeepCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace atom
|
35
atom/renderer/preferences_manager.h
Normal file
35
atom/renderer/preferences_manager.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// 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_RENDERER_PREFERENCES_MANAGER_H_
|
||||||
|
#define ATOM_RENDERER_PREFERENCES_MANAGER_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "base/values.h"
|
||||||
|
#include "content/public/renderer/render_process_observer.h"
|
||||||
|
|
||||||
|
namespace atom {
|
||||||
|
|
||||||
|
class PreferencesManager : public content::RenderProcessObserver {
|
||||||
|
public:
|
||||||
|
PreferencesManager();
|
||||||
|
~PreferencesManager() override;
|
||||||
|
|
||||||
|
const base::ListValue* preferences() const { return preferences_.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// content::RenderThreadObserver:
|
||||||
|
bool OnControlMessageReceived(const IPC::Message& message) override;
|
||||||
|
|
||||||
|
void OnUpdatePreferences(const base::ListValue& preferences);
|
||||||
|
|
||||||
|
std::unique_ptr<base::ListValue> preferences_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(PreferencesManager);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace atom
|
||||||
|
|
||||||
|
#endif // ATOM_RENDERER_PREFERENCES_MANAGER_H_
|
|
@ -1,62 +1,52 @@
|
||||||
# DevTools Extension
|
# DevTools Extension
|
||||||
|
|
||||||
To make debugging easier, Electron has basic support for the
|
Electron supports the [Chrome DevTools Extension][devtools-extension], which can
|
||||||
[Chrome DevTools Extension][devtools-extension].
|
be used to extend the ability of devtools for debugging popular web frameworks.
|
||||||
|
|
||||||
For most DevTools extensions you can simply download the source code and use
|
## How to load a DevTools Extension
|
||||||
the `BrowserWindow.addDevToolsExtension` API to load them. The loaded extensions
|
|
||||||
will be remembered so you don't need to call the API every time when creating
|
|
||||||
a window.
|
|
||||||
|
|
||||||
** NOTE: React DevTools does not work, follow the issue here https://github.com/electron/electron/issues/915 **
|
To load an extension in Electron, you need to download it in Chrome browser,
|
||||||
|
locate its filesystem path, and then load it by calling the
|
||||||
|
`BrowserWindow.addDevToolsExtension(extension)` API.
|
||||||
|
|
||||||
For example, to use the [React DevTools Extension](https://github.com/facebook/react-devtools)
|
Using the [React Developer Tools][react-devtools] as example:
|
||||||
, first you need to download its source code:
|
|
||||||
|
|
||||||
```bash
|
1. Install it in Chrome browser.
|
||||||
$ cd /some-directory
|
1. Navigate to `chrome://extensions`, and find its extension ID, which is a hash
|
||||||
$ git clone --recursive https://github.com/facebook/react-devtools.git
|
string like `fmkadmapgofadopljbjfkapdkoienihi`.
|
||||||
```
|
1. Find out filesystem location used by Chrome for storing extensions:
|
||||||
|
* on Windows it is `%LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions`;
|
||||||
|
* on Linux it is `~/.config/google-chrome/Default/Extensions/`;
|
||||||
|
* on OS X it is `~/Library/Application Support/Google/Chrome/Default/Extensions`.
|
||||||
|
1. Pass the location of the extension to `BrowserWindow.addDevToolsExtension`
|
||||||
|
API, for the React Developer Tools, it is something like:
|
||||||
|
`~/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/0.14.10_0`
|
||||||
|
|
||||||
Follow the instructions in [`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md) to build the extension.
|
The name of the extension is returned by `BrowserWindow.addDevToolsExtension`,
|
||||||
|
and you can pass the name of the extension to the `BrowserWindow.removeDevToolsExtension`
|
||||||
|
API to unload it.
|
||||||
|
|
||||||
Then you can load the extension in Electron by opening DevTools in any window,
|
## Supported DevTools Extensions
|
||||||
and running the following code in the DevTools console:
|
|
||||||
|
|
||||||
```javascript
|
Electron only supports a limited set of `chrome.*` APIs, so some extensions
|
||||||
const BrowserWindow = require('electron').remote.BrowserWindow;
|
using unsupported `chrome.*` APIs for chrome extension features may not work.
|
||||||
BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome');
|
Following Devtools Extensions are tested and guaranteed to work in Electron:
|
||||||
```
|
|
||||||
|
|
||||||
To unload the extension, you can call the `BrowserWindow.removeDevToolsExtension`
|
* [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
||||||
API with its name and it will not load the next time you open the DevTools:
|
* [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
|
||||||
|
* [Backbone Debugger](https://chrome.google.com/webstore/detail/backbone-debugger/bhljhndlimiafopmmhjlgfpnnchjjbhd)
|
||||||
|
* [jQuery Debugger](https://chrome.google.com/webstore/detail/jquery-debugger/dbhhnnnpaeobfddmlalhnehgclcmjimi)
|
||||||
|
* [AngularJS Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk)
|
||||||
|
* [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||||
|
|
||||||
```javascript
|
### What should I do if a DevTools Extension is not working?
|
||||||
BrowserWindow.removeDevToolsExtension('React Developer Tools');
|
|
||||||
```
|
|
||||||
|
|
||||||
## Format of DevTools Extension
|
Fist please make sure the extension is still being maintained, some extensions
|
||||||
|
can not even work for recent versions of Chrome browser, and we are not able to
|
||||||
|
do anything for them.
|
||||||
|
|
||||||
Ideally all DevTools extensions written for the Chrome browser can be loaded by
|
Then file a bug at Electron's issues list, and describe which part of the
|
||||||
Electron, but they have to be in a plain directory. For those packaged with
|
extension is not working as expected.
|
||||||
`crx` extensions, there is no way for Electron to load them unless you find a
|
|
||||||
way to extract them into a directory.
|
|
||||||
|
|
||||||
## Background Pages
|
|
||||||
|
|
||||||
Currently Electron doesn't support the background pages feature in Chrome
|
|
||||||
extensions, so some DevTools extensions that rely on this feature may
|
|
||||||
not work in Electron.
|
|
||||||
|
|
||||||
## `chrome.*` APIs
|
|
||||||
|
|
||||||
Some Chrome extensions may use `chrome.*` APIs for features and while there has
|
|
||||||
been some effort to implement those APIs in Electron, not all have been
|
|
||||||
implemented.
|
|
||||||
|
|
||||||
Given that not all `chrome.*` APIs are implemented if the DevTools extension is
|
|
||||||
using APIs other than `chrome.devtools.*`, the extension is very likely not to
|
|
||||||
work. You can report failing extensions in the issue tracker so that we can add
|
|
||||||
support for those APIs.
|
|
||||||
|
|
||||||
[devtools-extension]: https://developer.chrome.com/extensions/devtools
|
[devtools-extension]: https://developer.chrome.com/extensions/devtools
|
||||||
|
[react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
'lib/common/init.js',
|
'lib/common/init.js',
|
||||||
'lib/common/reset-search-paths.js',
|
'lib/common/reset-search-paths.js',
|
||||||
'lib/renderer/chrome-api.js',
|
'lib/renderer/chrome-api.js',
|
||||||
|
'lib/renderer/content-scripts-injector.js',
|
||||||
'lib/renderer/init.js',
|
'lib/renderer/init.js',
|
||||||
'lib/renderer/inspector.js',
|
'lib/renderer/inspector.js',
|
||||||
'lib/renderer/override.js',
|
'lib/renderer/override.js',
|
||||||
|
@ -109,6 +110,8 @@
|
||||||
'atom/browser/api/atom_api_power_monitor.h',
|
'atom/browser/api/atom_api_power_monitor.h',
|
||||||
'atom/browser/api/atom_api_power_save_blocker.cc',
|
'atom/browser/api/atom_api_power_save_blocker.cc',
|
||||||
'atom/browser/api/atom_api_power_save_blocker.h',
|
'atom/browser/api/atom_api_power_save_blocker.h',
|
||||||
|
'atom/browser/api/atom_api_render_process_preferences.cc',
|
||||||
|
'atom/browser/api/atom_api_render_process_preferences.h',
|
||||||
'atom/browser/api/atom_api_protocol.cc',
|
'atom/browser/api/atom_api_protocol.cc',
|
||||||
'atom/browser/api/atom_api_protocol.h',
|
'atom/browser/api/atom_api_protocol.h',
|
||||||
'atom/browser/api/atom_api_screen.cc',
|
'atom/browser/api/atom_api_screen.cc',
|
||||||
|
@ -220,6 +223,8 @@
|
||||||
'atom/browser/net/url_request_fetch_job.h',
|
'atom/browser/net/url_request_fetch_job.h',
|
||||||
'atom/browser/node_debugger.cc',
|
'atom/browser/node_debugger.cc',
|
||||||
'atom/browser/node_debugger.h',
|
'atom/browser/node_debugger.h',
|
||||||
|
'atom/browser/render_process_preferences.cc',
|
||||||
|
'atom/browser/render_process_preferences.h',
|
||||||
'atom/browser/ui/accelerator_util.cc',
|
'atom/browser/ui/accelerator_util.cc',
|
||||||
'atom/browser/ui/accelerator_util.h',
|
'atom/browser/ui/accelerator_util.h',
|
||||||
'atom/browser/ui/accelerator_util_mac.mm',
|
'atom/browser/ui/accelerator_util_mac.mm',
|
||||||
|
@ -396,6 +401,8 @@
|
||||||
'atom/renderer/guest_view_container.h',
|
'atom/renderer/guest_view_container.h',
|
||||||
'atom/renderer/node_array_buffer_bridge.cc',
|
'atom/renderer/node_array_buffer_bridge.cc',
|
||||||
'atom/renderer/node_array_buffer_bridge.h',
|
'atom/renderer/node_array_buffer_bridge.h',
|
||||||
|
'atom/renderer/preferences_manager.cc',
|
||||||
|
'atom/renderer/preferences_manager.h',
|
||||||
'atom/utility/atom_content_utility_client.cc',
|
'atom/utility/atom_content_utility_client.cc',
|
||||||
'atom/utility/atom_content_utility_client.h',
|
'atom/utility/atom_content_utility_client.h',
|
||||||
'chromium_src/chrome/browser/browser_process.cc',
|
'chromium_src/chrome/browser/browser_process.cc',
|
||||||
|
|
|
@ -71,12 +71,15 @@ let wrapWebContents = function (webContents) {
|
||||||
webContents.setMaxListeners(0)
|
webContents.setMaxListeners(0)
|
||||||
|
|
||||||
// WebContents::send(channel, args..)
|
// WebContents::send(channel, args..)
|
||||||
webContents.send = function (channel, ...args) {
|
// WebContents::sendToAll(channel, args..)
|
||||||
|
const sendWrapper = (allFrames, channel, ...args) => {
|
||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
throw new Error('Missing required channel argument')
|
throw new Error('Missing required channel argument')
|
||||||
}
|
}
|
||||||
return this._send(channel, args)
|
return webContents._send(allFrames, channel, args)
|
||||||
}
|
}
|
||||||
|
webContents.send = sendWrapper.bind(null, false)
|
||||||
|
webContents.sendToAll = sendWrapper.bind(null, true)
|
||||||
|
|
||||||
// The navigation controller.
|
// The navigation controller.
|
||||||
controller = new NavigationController(webContents)
|
controller = new NavigationController(webContents)
|
||||||
|
@ -218,9 +221,12 @@ binding._setWrapWebContents(wrapWebContents)
|
||||||
debuggerBinding._setWrapDebugger(wrapDebugger)
|
debuggerBinding._setWrapDebugger(wrapDebugger)
|
||||||
sessionBinding._setWrapSession(wrapSession)
|
sessionBinding._setWrapSession(wrapSession)
|
||||||
|
|
||||||
module.exports.create = function (options) {
|
module.exports = {
|
||||||
if (options == null) {
|
create (options = {}) {
|
||||||
options = {}
|
|
||||||
}
|
|
||||||
return binding.create(options)
|
return binding.create(options)
|
||||||
|
},
|
||||||
|
|
||||||
|
fromId (id) {
|
||||||
|
return binding.fromId(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +1,224 @@
|
||||||
const {app, protocol, BrowserWindow} = require('electron')
|
const {app, ipcMain, protocol, webContents, BrowserWindow} = require('electron')
|
||||||
|
const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllBrowserWindow()
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const url = require('url')
|
const url = require('url')
|
||||||
|
|
||||||
// Mapping between hostname and file path.
|
// TODO(zcbenz): Remove this when we have Object.values().
|
||||||
var hostPathMap = {}
|
const objectValues = function (object) {
|
||||||
var hostPathMapNextKey = 0
|
return Object.keys(object).map(function (key) { return object[key] })
|
||||||
|
|
||||||
var getHostForPath = function (path) {
|
|
||||||
var key
|
|
||||||
key = 'extension-' + (++hostPathMapNextKey)
|
|
||||||
hostPathMap[key] = path
|
|
||||||
return key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var getPathForHost = function (host) {
|
// Mapping between extensionId(hostname) and manifest.
|
||||||
return hostPathMap[host]
|
const manifestMap = {} // extensionId => manifest
|
||||||
|
const manifestNameMap = {} // name => manifest
|
||||||
|
|
||||||
|
const generateExtensionIdFromName = function (name) {
|
||||||
|
return name.replace(/[\W_]+/g, '-').toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache extensionInfo.
|
// Create or get manifest object from |srcDirectory|.
|
||||||
var extensionInfoMap = {}
|
const getManifestFromPath = function (srcDirectory) {
|
||||||
|
const manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json')))
|
||||||
var getExtensionInfoFromPath = function (srcDirectory) {
|
if (!manifestNameMap[manifest.name]) {
|
||||||
var manifest, page
|
const extensionId = generateExtensionIdFromName(manifest.name)
|
||||||
manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json')))
|
console.log(extensionId)
|
||||||
if (extensionInfoMap[manifest.name] == null) {
|
manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
|
||||||
|
Object.assign(manifest, {
|
||||||
|
srcDirectory: srcDirectory,
|
||||||
|
extensionId: extensionId,
|
||||||
// We can not use 'file://' directly because all resources in the extension
|
// We can not use 'file://' directly because all resources in the extension
|
||||||
// will be treated as relative to the root in Chrome.
|
// will be treated as relative to the root in Chrome.
|
||||||
page = url.format({
|
startPage: url.format({
|
||||||
protocol: 'chrome-extension',
|
protocol: 'chrome-extension',
|
||||||
slashes: true,
|
slashes: true,
|
||||||
hostname: getHostForPath(srcDirectory),
|
hostname: extensionId,
|
||||||
pathname: manifest.devtools_page
|
pathname: manifest.devtools_page
|
||||||
})
|
})
|
||||||
extensionInfoMap[manifest.name] = {
|
})
|
||||||
startPage: page,
|
return manifest
|
||||||
name: manifest.name,
|
|
||||||
srcDirectory: srcDirectory,
|
|
||||||
exposeExperimentalAPIs: true
|
|
||||||
}
|
|
||||||
return extensionInfoMap[manifest.name]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The loaded extensions cache and its persistent path.
|
// Manage the background pages.
|
||||||
var loadedExtensions = null
|
const backgroundPages = {}
|
||||||
var loadedExtensionsPath = null
|
|
||||||
|
const startBackgroundPages = function (manifest) {
|
||||||
|
if (backgroundPages[manifest.extensionId] || !manifest.background) return
|
||||||
|
|
||||||
|
const scripts = manifest.background.scripts.map((name) => {
|
||||||
|
return `<script src="${name}"></script>`
|
||||||
|
}).join('')
|
||||||
|
const html = new Buffer(`<html><body>${scripts}</body></html>`)
|
||||||
|
|
||||||
|
const contents = webContents.create({
|
||||||
|
commandLineSwitches: ['--background-page']
|
||||||
|
})
|
||||||
|
backgroundPages[manifest.extensionId] = { html: html, webContents: contents }
|
||||||
|
contents.loadURL(url.format({
|
||||||
|
protocol: 'chrome-extension',
|
||||||
|
slashes: true,
|
||||||
|
hostname: manifest.extensionId,
|
||||||
|
pathname: '_generated_background_page.html'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeBackgroundPages = function (manifest) {
|
||||||
|
if (!backgroundPages[manifest.extensionId]) return
|
||||||
|
|
||||||
|
backgroundPages[manifest.extensionId].webContents.destroy()
|
||||||
|
delete backgroundPages[manifest.extensionId]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch tabs events.
|
||||||
|
const hookWindowForTabEvents = function (win) {
|
||||||
|
const tabId = win.webContents.id
|
||||||
|
for (const page of objectValues(backgroundPages)) {
|
||||||
|
page.webContents.sendToAll('CHROME_TABS_ONCREATED', tabId)
|
||||||
|
}
|
||||||
|
|
||||||
|
win.once('closed', () => {
|
||||||
|
for (const page of objectValues(backgroundPages)) {
|
||||||
|
page.webContents.sendToAll('CHROME_TABS_ONREMOVED', tabId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the chrome.* API messages.
|
||||||
|
let nextId = 0
|
||||||
|
|
||||||
|
ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
|
||||||
|
const page = backgroundPages[extensionId]
|
||||||
|
if (!page) {
|
||||||
|
console.error(`Connect to unkown extension ${extensionId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const portId = ++nextId
|
||||||
|
event.returnValue = {tabId: page.webContents.id, portId: portId}
|
||||||
|
|
||||||
|
event.sender.once('render-view-deleted', () => {
|
||||||
|
if (page.webContents.isDestroyed()) return
|
||||||
|
page.webContents.sendToAll(`CHROME_PORT_DISCONNECT_${portId}`)
|
||||||
|
})
|
||||||
|
page.webContents.sendToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message) {
|
||||||
|
const page = backgroundPages[extensionId]
|
||||||
|
if (!page) {
|
||||||
|
console.error(`Connect to unkown extension ${extensionId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page.webContents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBackgroundPage, message) {
|
||||||
|
const contents = webContents.fromId(tabId)
|
||||||
|
if (!contents) {
|
||||||
|
console.error(`Sending message to unkown tab ${tabId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderTabId = isBackgroundPage ? null : event.sender.id
|
||||||
|
|
||||||
|
contents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, tabId, extensionId, details) {
|
||||||
|
const contents = webContents.fromId(tabId)
|
||||||
|
if (!contents) {
|
||||||
|
console.error(`Sending message to unkown tab ${tabId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let code, url
|
||||||
|
if (details.file) {
|
||||||
|
const manifest = manifestMap[extensionId]
|
||||||
|
code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)))
|
||||||
|
url = `chrome-extension://${extensionId}${details.file}`
|
||||||
|
} else {
|
||||||
|
code = details.code
|
||||||
|
url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.send('CHROME_TABS_EXECUTESCRIPT', event.sender.id, requestId, extensionId, url, code)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Transfer the content scripts to renderer.
|
||||||
|
const contentScripts = {}
|
||||||
|
|
||||||
|
const injectContentScripts = function (manifest) {
|
||||||
|
if (contentScripts[manifest.name] || !manifest.content_scripts) return
|
||||||
|
|
||||||
|
const readArrayOfFiles = function (relativePath) {
|
||||||
|
return {
|
||||||
|
url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
|
||||||
|
code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentScriptToEntry = function (script) {
|
||||||
|
return {
|
||||||
|
matches: script.matches,
|
||||||
|
js: script.js.map(readArrayOfFiles),
|
||||||
|
runAt: script.run_at || 'document_idle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entry = {
|
||||||
|
extensionId: manifest.extensionId,
|
||||||
|
contentScripts: manifest.content_scripts.map(contentScriptToEntry)
|
||||||
|
}
|
||||||
|
contentScripts[manifest.name] = renderProcessPreferences.addEntry(entry)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to read content scripts', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeContentScripts = function (manifest) {
|
||||||
|
if (!contentScripts[manifest.name]) return
|
||||||
|
|
||||||
|
renderProcessPreferences.removeEntry(contentScripts[manifest.name])
|
||||||
|
delete contentScripts[manifest.name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer the |manifest| to a format that can be recognized by the
|
||||||
|
// |DevToolsAPI.addExtensions|.
|
||||||
|
const manifestToExtensionInfo = function (manifest) {
|
||||||
|
return {
|
||||||
|
startPage: manifest.startPage,
|
||||||
|
srcDirectory: manifest.srcDirectory,
|
||||||
|
name: manifest.name,
|
||||||
|
exposeExperimentalAPIs: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the extensions for the window.
|
||||||
|
const loadExtension = function (manifest) {
|
||||||
|
startBackgroundPages(manifest)
|
||||||
|
injectContentScripts(manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadDevToolsExtensions = function (win, manifests) {
|
||||||
|
if (!win.devToolsWebContents) return
|
||||||
|
|
||||||
|
manifests.forEach(loadExtension)
|
||||||
|
|
||||||
|
const extensionInfoArray = manifests.map(manifestToExtensionInfo)
|
||||||
|
win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The persistent path of "DevTools Extensions" preference file.
|
||||||
|
let loadedExtensionsPath = null
|
||||||
|
|
||||||
app.on('will-quit', function () {
|
app.on('will-quit', function () {
|
||||||
try {
|
try {
|
||||||
loadedExtensions = Object.keys(extensionInfoMap).map(function (key) {
|
const loadedExtensions = objectValues(manifestMap).map(function (manifest) {
|
||||||
return extensionInfoMap[key].srcDirectory
|
return manifest.srcDirectory
|
||||||
})
|
})
|
||||||
if (loadedExtensions.length > 0) {
|
if (loadedExtensions.length > 0) {
|
||||||
try {
|
try {
|
||||||
|
@ -69,74 +237,78 @@ app.on('will-quit', function () {
|
||||||
|
|
||||||
// We can not use protocol or BrowserWindow until app is ready.
|
// We can not use protocol or BrowserWindow until app is ready.
|
||||||
app.once('ready', function () {
|
app.once('ready', function () {
|
||||||
var chromeExtensionHandler, i, init, len, srcDirectory
|
// The chrome-extension: can map a extension URL request to real file path.
|
||||||
|
const chromeExtensionHandler = function (request, callback) {
|
||||||
|
const parsed = url.parse(request.url)
|
||||||
|
if (!parsed.hostname || !parsed.path) return callback()
|
||||||
|
|
||||||
|
const manifest = manifestMap[parsed.hostname]
|
||||||
|
if (!manifest) return callback()
|
||||||
|
|
||||||
|
if (parsed.path === '/_generated_background_page.html' &&
|
||||||
|
backgroundPages[parsed.hostname]) {
|
||||||
|
return callback({
|
||||||
|
mimeType: 'text/html',
|
||||||
|
data: backgroundPages[parsed.hostname].html
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
|
||||||
|
if (err) {
|
||||||
|
return callback(-6) // FILE_NOT_FOUND
|
||||||
|
} else {
|
||||||
|
return callback(content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
|
||||||
|
if (error) {
|
||||||
|
console.error(`Unable to register chrome-extension protocol: ${error}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Load persisted extensions.
|
// Load persisted extensions.
|
||||||
loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
|
loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
|
||||||
try {
|
try {
|
||||||
loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath))
|
const loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath))
|
||||||
if (!Array.isArray(loadedExtensions)) {
|
if (Array.isArray(loadedExtensions)) {
|
||||||
loadedExtensions = []
|
for (const srcDirectory of loadedExtensions) {
|
||||||
|
// Start background pages and set content scripts.
|
||||||
|
const manifest = getManifestFromPath(srcDirectory)
|
||||||
|
loadExtension(manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preheat the extensionInfo cache.
|
|
||||||
for (i = 0, len = loadedExtensions.length; i < len; i++) {
|
|
||||||
srcDirectory = loadedExtensions[i]
|
|
||||||
getExtensionInfoFromPath(srcDirectory)
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore error
|
// Ignore error
|
||||||
}
|
}
|
||||||
|
|
||||||
// The chrome-extension: can map a extension URL request to real file path.
|
// The public API to add/remove extensions.
|
||||||
chromeExtensionHandler = function (request, callback) {
|
|
||||||
var directory, parsed
|
|
||||||
parsed = url.parse(request.url)
|
|
||||||
if (!(parsed.hostname && (parsed.path != null))) {
|
|
||||||
return callback()
|
|
||||||
}
|
|
||||||
if (!/extension-\d+/.test(parsed.hostname)) {
|
|
||||||
return callback()
|
|
||||||
}
|
|
||||||
directory = getPathForHost(parsed.hostname)
|
|
||||||
if (directory == null) {
|
|
||||||
return callback()
|
|
||||||
}
|
|
||||||
return callback(path.join(directory, parsed.path))
|
|
||||||
}
|
|
||||||
protocol.registerFileProtocol('chrome-extension', chromeExtensionHandler, function (error) {
|
|
||||||
if (error) {
|
|
||||||
return console.error('Unable to register chrome-extension protocol')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
BrowserWindow.prototype._loadDevToolsExtensions = function (extensionInfoArray) {
|
|
||||||
var ref
|
|
||||||
return (ref = this.devToolsWebContents) != null ? ref.executeJavaScript('DevToolsAPI.addExtensions(' + (JSON.stringify(extensionInfoArray)) + ');') : void 0
|
|
||||||
}
|
|
||||||
BrowserWindow.addDevToolsExtension = function (srcDirectory) {
|
BrowserWindow.addDevToolsExtension = function (srcDirectory) {
|
||||||
var extensionInfo, j, len1, ref, window
|
const manifest = getManifestFromPath(srcDirectory)
|
||||||
extensionInfo = getExtensionInfoFromPath(srcDirectory)
|
if (manifest) {
|
||||||
if (extensionInfo) {
|
for (const win of BrowserWindow.getAllWindows()) {
|
||||||
ref = BrowserWindow.getAllWindows()
|
loadDevToolsExtensions(win, [manifest])
|
||||||
for (j = 0, len1 = ref.length; j < len1; j++) {
|
|
||||||
window = ref[j]
|
|
||||||
window._loadDevToolsExtensions([extensionInfo])
|
|
||||||
}
|
}
|
||||||
return extensionInfo.name
|
return manifest.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BrowserWindow.removeDevToolsExtension = function (name) {
|
BrowserWindow.removeDevToolsExtension = function (name) {
|
||||||
return delete extensionInfoMap[name]
|
const manifest = manifestNameMap[name]
|
||||||
|
if (!manifest) return
|
||||||
|
|
||||||
|
removeBackgroundPages(manifest)
|
||||||
|
removeContentScripts(manifest)
|
||||||
|
delete manifestMap[manifest.extensionId]
|
||||||
|
delete manifestNameMap[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load persisted extensions when devtools is opened.
|
// Load extensions automatically when devtools is opened.
|
||||||
init = BrowserWindow.prototype._init
|
const init = BrowserWindow.prototype._init
|
||||||
BrowserWindow.prototype._init = function () {
|
BrowserWindow.prototype._init = function () {
|
||||||
init.call(this)
|
init.call(this)
|
||||||
return this.webContents.on('devtools-opened', () => {
|
hookWindowForTabEvents(this)
|
||||||
return this._loadDevToolsExtensions(Object.keys(extensionInfoMap).map(function (key) {
|
this.webContents.on('devtools-opened', () => {
|
||||||
return extensionInfoMap[key]
|
loadDevToolsExtensions(this, objectValues(manifestMap))
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const v8Util = process.atomBinding('v8_util')
|
const v8Util = process.atomBinding('v8_util')
|
||||||
const {ipcMain, isPromise} = electron
|
const {ipcMain, isPromise, webContents} = electron
|
||||||
|
|
||||||
const objectsRegistry = require('./objects-registry')
|
const objectsRegistry = require('./objects-registry')
|
||||||
|
|
||||||
|
@ -353,3 +353,17 @@ ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, request
|
||||||
event.returnValue = exceptionToMeta(error)
|
event.returnValue = exceptionToMeta(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.on('ELECTRON_BROWSER_SEND_TO', function (event, sendToAll, webContentsId, channel, ...args) {
|
||||||
|
let contents = webContents.fromId(webContentsId)
|
||||||
|
if (!contents) {
|
||||||
|
console.error(`Sending message to WebContents with unknown ID ${webContentsId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendToAll) {
|
||||||
|
contents.sendToAll(channel, ...args)
|
||||||
|
} else {
|
||||||
|
contents.send(channel, ...args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -18,4 +18,20 @@ ipcRenderer.sendToHost = function (...args) {
|
||||||
return binding.send('ipc-message-host', args)
|
return binding.send('ipc-message-host', args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
|
||||||
|
if (typeof webContentsId !== 'number') {
|
||||||
|
throw new TypeError('First argument has to be webContentsId')
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
|
||||||
|
if (typeof webContentsId !== 'number') {
|
||||||
|
throw new TypeError('First argument has to be webContentsId')
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = ipcRenderer
|
module.exports = ipcRenderer
|
||||||
|
|
|
@ -1,13 +1,200 @@
|
||||||
|
const {ipcRenderer} = require('electron')
|
||||||
const url = require('url')
|
const url = require('url')
|
||||||
const chrome = window.chrome = window.chrome || {}
|
|
||||||
|
|
||||||
chrome.extension = {
|
let nextId = 0
|
||||||
getURL: function (path) {
|
|
||||||
return url.format({
|
class Event {
|
||||||
protocol: window.location.protocol,
|
constructor () {
|
||||||
slashes: true,
|
this.listeners = []
|
||||||
hostname: window.location.hostname,
|
}
|
||||||
pathname: path
|
|
||||||
|
addListener (callback) {
|
||||||
|
this.listeners.push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListener (callback) {
|
||||||
|
const index = this.listeners.indexOf(callback)
|
||||||
|
if (index !== -1) {
|
||||||
|
this.listeners.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit (...args) {
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
listener(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tab {
|
||||||
|
constructor (tabId) {
|
||||||
|
this.id = tabId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageSender {
|
||||||
|
constructor (tabId, extensionId) {
|
||||||
|
this.tab = tabId ? new Tab(tabId) : null
|
||||||
|
this.id = extensionId
|
||||||
|
this.url = `chrome-extension://${extensionId}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Port {
|
||||||
|
constructor (tabId, portId, extensionId, name) {
|
||||||
|
this.tabId = tabId
|
||||||
|
this.portId = portId
|
||||||
|
this.disconnected = false
|
||||||
|
|
||||||
|
this.name = name
|
||||||
|
this.onDisconnect = new Event()
|
||||||
|
this.onMessage = new Event()
|
||||||
|
this.sender = new MessageSender(tabId, extensionId)
|
||||||
|
|
||||||
|
ipcRenderer.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
|
||||||
|
this._onDisconnect()
|
||||||
|
})
|
||||||
|
ipcRenderer.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (event, message) => {
|
||||||
|
const sendResponse = function () { console.error('sendResponse is not implemented') }
|
||||||
|
this.onMessage.emit(message, this.sender, sendResponse)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disconnect () {
|
||||||
|
if (this.disconnected) return
|
||||||
|
|
||||||
|
ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`)
|
||||||
|
this._onDisconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage (message) {
|
||||||
|
ipcRenderer.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDisconnect () {
|
||||||
|
this.disconnected = true
|
||||||
|
ipcRenderer.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`)
|
||||||
|
this.onDisconnect.emit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject chrome API to the |context|
|
||||||
|
exports.injectTo = function (extensionId, isBackgroundPage, context) {
|
||||||
|
const chrome = context.chrome = context.chrome || {}
|
||||||
|
|
||||||
|
ipcRenderer.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (event, tabId, portId, connectInfo) => {
|
||||||
|
chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name))
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (event, tabId, message) => {
|
||||||
|
chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId))
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('CHROME_TABS_ONCREATED', (event, tabId) => {
|
||||||
|
chrome.tabs.onCreated.emit(new Tab(tabId))
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('CHROME_TABS_ONREMOVED', (event, tabId) => {
|
||||||
|
chrome.tabs.onRemoved.emit(tabId)
|
||||||
|
})
|
||||||
|
|
||||||
|
chrome.runtime = {
|
||||||
|
getURL: function (path) {
|
||||||
|
return url.format({
|
||||||
|
protocol: 'chrome-extension',
|
||||||
|
slashes: true,
|
||||||
|
hostname: extensionId,
|
||||||
|
pathname: path
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
connect (...args) {
|
||||||
|
if (isBackgroundPage) {
|
||||||
|
console.error('chrome.runtime.connect is not supported in background page')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the optional args.
|
||||||
|
let targetExtensionId = extensionId
|
||||||
|
let connectInfo = {name: ''}
|
||||||
|
if (args.length === 1) {
|
||||||
|
connectInfo = args[0]
|
||||||
|
} else if (args.length === 2) {
|
||||||
|
[targetExtensionId, connectInfo] = args
|
||||||
|
}
|
||||||
|
|
||||||
|
const {tabId, portId} = ipcRenderer.sendSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo)
|
||||||
|
return new Port(tabId, portId, extensionId, connectInfo.name)
|
||||||
|
},
|
||||||
|
|
||||||
|
sendMessage (...args) {
|
||||||
|
if (isBackgroundPage) {
|
||||||
|
console.error('chrome.runtime.sendMessage is not supported in background page')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the optional args.
|
||||||
|
let targetExtensionId = extensionId
|
||||||
|
let message
|
||||||
|
if (args.length === 1) {
|
||||||
|
message = args[0]
|
||||||
|
} else if (args.length === 2) {
|
||||||
|
[targetExtensionId, message] = args
|
||||||
|
} else {
|
||||||
|
console.error('options and responseCallback are not supported')
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send('CHROME_RUNTIME_SENDMESSAGE', targetExtensionId, message)
|
||||||
|
},
|
||||||
|
|
||||||
|
onConnect: new Event(),
|
||||||
|
onMessage: new Event(),
|
||||||
|
onInstalled: new Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.tabs = {
|
||||||
|
executeScript (tabId, details, callback) {
|
||||||
|
const requestId = ++nextId
|
||||||
|
ipcRenderer.once(`CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, (event, result) => {
|
||||||
|
callback([event.result])
|
||||||
|
})
|
||||||
|
ipcRenderer.send('CHROME_TABS_EXECUTESCRIPT', requestId, tabId, extensionId, details)
|
||||||
|
},
|
||||||
|
|
||||||
|
sendMessage (tabId, message, options, responseCallback) {
|
||||||
|
if (responseCallback) {
|
||||||
|
console.error('responseCallback is not supported')
|
||||||
|
}
|
||||||
|
ipcRenderer.send('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, isBackgroundPage, message)
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdated: new Event(),
|
||||||
|
onCreated: new Event(),
|
||||||
|
onRemoved: new Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.extension = {
|
||||||
|
getURL: chrome.runtime.getURL,
|
||||||
|
connect: chrome.runtime.connect,
|
||||||
|
onConnect: chrome.runtime.onConnect,
|
||||||
|
sendMessage: chrome.runtime.sendMessage,
|
||||||
|
onMessage: chrome.runtime.onMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.storage = {
|
||||||
|
sync: {
|
||||||
|
get () {},
|
||||||
|
set () {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.pageAction = {
|
||||||
|
show () {},
|
||||||
|
hide () {},
|
||||||
|
setTitle () {},
|
||||||
|
getTitle () {},
|
||||||
|
setIcon () {},
|
||||||
|
setPopup () {},
|
||||||
|
getPopup () {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
61
lib/renderer/content-scripts-injector.js
Normal file
61
lib/renderer/content-scripts-injector.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
const {ipcRenderer} = require('electron')
|
||||||
|
const {runInThisContext} = require('vm')
|
||||||
|
|
||||||
|
// Check whether pattern matches.
|
||||||
|
// https://developer.chrome.com/extensions/match_patterns
|
||||||
|
const matchesPattern = function (pattern) {
|
||||||
|
if (pattern === '<all_urls>') return true
|
||||||
|
|
||||||
|
const regexp = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$')
|
||||||
|
return location.href.match(regexp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the code with chrome API integrated.
|
||||||
|
const runContentScript = function (extensionId, url, code) {
|
||||||
|
const context = {}
|
||||||
|
require('./chrome-api').injectTo(extensionId, false, context)
|
||||||
|
const wrapper = `(function (chrome) {\n ${code}\n })`
|
||||||
|
const compiledWrapper = runInThisContext(wrapper, {
|
||||||
|
filename: url,
|
||||||
|
lineOffset: 1,
|
||||||
|
displayErrors: true
|
||||||
|
})
|
||||||
|
return compiledWrapper.call(this, context.chrome)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run injected scripts.
|
||||||
|
// https://developer.chrome.com/extensions/content_scripts
|
||||||
|
const injectContentScript = function (extensionId, script) {
|
||||||
|
for (const match of script.matches) {
|
||||||
|
if (!matchesPattern(match)) return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const {url, code} of script.js) {
|
||||||
|
const fire = runContentScript.bind(window, extensionId, url, code)
|
||||||
|
if (script.runAt === 'document_start') {
|
||||||
|
process.once('document-start', fire)
|
||||||
|
} else if (script.runAt === 'document_end') {
|
||||||
|
process.once('document-end', fire)
|
||||||
|
} else if (script.runAt === 'document_idle') {
|
||||||
|
document.addEventListener('DOMContentLoaded', fire)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the request of chrome.tabs.executeJavaScript.
|
||||||
|
ipcRenderer.on('CHROME_TABS_EXECUTESCRIPT', function (event, senderWebContentsId, requestId, extensionId, url, code) {
|
||||||
|
const result = runContentScript.call(window, extensionId, url, code)
|
||||||
|
ipcRenderer.sendToAll(senderWebContentsId, `CHROME_TABS_EXECUTESCRIPT_RESULT_${requestId}`, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Read the renderer process preferences.
|
||||||
|
const preferences = process.getRenderProcessPreferences()
|
||||||
|
if (preferences) {
|
||||||
|
for (const pref of preferences) {
|
||||||
|
if (pref.contentScripts) {
|
||||||
|
for (const script of pref.contentScripts) {
|
||||||
|
injectContentScript(pref.extensionId, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,8 +41,9 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev
|
||||||
})
|
})
|
||||||
|
|
||||||
// Process command line arguments.
|
// Process command line arguments.
|
||||||
var nodeIntegration = 'false'
|
let nodeIntegration = 'false'
|
||||||
var preloadScript = null
|
let preloadScript = null
|
||||||
|
let isBackgroundPage = false
|
||||||
for (let arg of process.argv) {
|
for (let arg of process.argv) {
|
||||||
if (arg.indexOf('--guest-instance-id=') === 0) {
|
if (arg.indexOf('--guest-instance-id=') === 0) {
|
||||||
// This is a guest web view.
|
// This is a guest web view.
|
||||||
|
@ -54,6 +55,8 @@ for (let arg of process.argv) {
|
||||||
nodeIntegration = arg.substr(arg.indexOf('=') + 1)
|
nodeIntegration = arg.substr(arg.indexOf('=') + 1)
|
||||||
} else if (arg.indexOf('--preload=') === 0) {
|
} else if (arg.indexOf('--preload=') === 0) {
|
||||||
preloadScript = arg.substr(arg.indexOf('=') + 1)
|
preloadScript = arg.substr(arg.indexOf('=') + 1)
|
||||||
|
} else if (arg === '--background-page') {
|
||||||
|
isBackgroundPage = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,12 +66,15 @@ if (window.location.protocol === 'chrome-devtools:') {
|
||||||
nodeIntegration = 'true'
|
nodeIntegration = 'true'
|
||||||
} else if (window.location.protocol === 'chrome-extension:') {
|
} else if (window.location.protocol === 'chrome-extension:') {
|
||||||
// Add implementations of chrome API.
|
// Add implementations of chrome API.
|
||||||
require('./chrome-api')
|
require('./chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
|
||||||
nodeIntegration = 'true'
|
nodeIntegration = 'false'
|
||||||
} else {
|
} else {
|
||||||
// Override default web functions.
|
// Override default web functions.
|
||||||
require('./override')
|
require('./override')
|
||||||
|
|
||||||
|
// Inject content scripts.
|
||||||
|
require('./content-scripts-injector')
|
||||||
|
|
||||||
// Load webview tag implementation.
|
// Load webview tag implementation.
|
||||||
if (nodeIntegration === 'true' && process.guestInstanceId == null) {
|
if (nodeIntegration === 'true' && process.guestInstanceId == null) {
|
||||||
require('./web-view/web-view')
|
require('./web-view/web-view')
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
// Make sure |window.chrome| is defined for devtools extensions.
|
|
||||||
hijackSetInjectedScript(window.InspectorFrontendHost)
|
|
||||||
|
|
||||||
// Use menu API to show context menu.
|
// Use menu API to show context menu.
|
||||||
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu
|
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu
|
||||||
|
|
||||||
|
@ -9,18 +6,6 @@ window.onload = function () {
|
||||||
window.WebInspector.createFileSelectorElement = createFileSelectorElement
|
window.WebInspector.createFileSelectorElement = createFileSelectorElement
|
||||||
}
|
}
|
||||||
|
|
||||||
const hijackSetInjectedScript = function (InspectorFrontendHost) {
|
|
||||||
const {setInjectedScriptForOrigin} = InspectorFrontendHost
|
|
||||||
InspectorFrontendHost.setInjectedScriptForOrigin = function (origin, source) {
|
|
||||||
const wrapped = `(function (...args) {
|
|
||||||
window.chrome = {}
|
|
||||||
const original = ${source}
|
|
||||||
original(...args)
|
|
||||||
})`
|
|
||||||
setInjectedScriptForOrigin(origin, wrapped)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var convertToMenuTemplate = function (items) {
|
var convertToMenuTemplate = function (items) {
|
||||||
var fn, i, item, len, template
|
var fn, i, item, len, template
|
||||||
template = []
|
template = []
|
||||||
|
|
|
@ -3,11 +3,8 @@
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const ipcRenderer = require('electron').ipcRenderer
|
const {ipcRenderer, remote} = require('electron')
|
||||||
const remote = require('electron').remote
|
const {ipcMain, webContents, BrowserWindow} = remote
|
||||||
|
|
||||||
const ipcMain = remote.require('electron').ipcMain
|
|
||||||
const BrowserWindow = remote.require('electron').BrowserWindow
|
|
||||||
|
|
||||||
const comparePaths = function (path1, path2) {
|
const comparePaths = function (path1, path2) {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
@ -244,6 +241,30 @@ describe('ipc module', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('ipcRenderer.sendTo', function () {
|
||||||
|
let contents = null
|
||||||
|
beforeEach(function () {
|
||||||
|
contents = webContents.create({})
|
||||||
|
})
|
||||||
|
afterEach(function () {
|
||||||
|
ipcRenderer.removeAllListeners('pong')
|
||||||
|
contents.destroy()
|
||||||
|
contents = null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends message to WebContents', function (done) {
|
||||||
|
const webContentsId = remote.getCurrentWebContents().id
|
||||||
|
ipcRenderer.once('pong', function (event, id) {
|
||||||
|
assert.equal(webContentsId, id)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
contents.once('did-finish-load', function () {
|
||||||
|
ipcRenderer.sendTo(contents.id, 'ping', webContentsId)
|
||||||
|
})
|
||||||
|
contents.loadURL('file://' + path.join(fixtures, 'pages', 'ping-pong.html'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('remote listeners', function () {
|
describe('remote listeners', function () {
|
||||||
var w = null
|
var w = null
|
||||||
|
|
||||||
|
|
11
spec/fixtures/pages/ping-pong.html
vendored
Normal file
11
spec/fixtures/pages/ping-pong.html
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
const {ipcRenderer} = require('electron')
|
||||||
|
ipcRenderer.on('ping', function (event, id) {
|
||||||
|
ipcRenderer.sendTo(id, 'pong', id)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue