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());
|
||||
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.
|
||||
if (owner_window())
|
||||
handle->SetOwnerWindow(managed_web_contents()->GetDevToolsWebContents(),
|
||||
|
@ -1083,9 +1088,10 @@ void WebContents::TabTraverse(bool 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) {
|
||||
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,
|
||||
|
@ -1333,6 +1339,8 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
|
|||
mate::Dictionary dict(isolate, exports);
|
||||
dict.SetMethod("create", &atom::api::WebContents::Create);
|
||||
dict.SetMethod("_setWrapWebContents", &atom::api::SetWrapWebContents);
|
||||
dict.SetMethod("fromId",
|
||||
&mate::TrackableObject<atom::api::WebContents>::FromWeakMapID);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -122,7 +122,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
|||
void TabTraverse(bool reverse);
|
||||
|
||||
// Send messages to browser.
|
||||
bool SendIPCMessage(const base::string16& channel,
|
||||
bool SendIPCMessage(bool all_frames,
|
||||
const base::string16& channel,
|
||||
const base::ListValue& args);
|
||||
|
||||
// Send WebInputEvent to the page.
|
||||
|
|
|
@ -73,6 +73,17 @@ AtomBrowserClient::AtomBrowserClient() : delegate_(nullptr) {
|
|||
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(
|
||||
content::RenderProcessHost* host) {
|
||||
int process_id = host->GetID();
|
||||
|
@ -172,14 +183,7 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
|||
}
|
||||
#endif
|
||||
|
||||
// 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.
|
||||
content::WebContents* web_contents =
|
||||
WebContentsPreferences::GetWebContentsFromProcessID(process_id);
|
||||
content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
|
||||
if (!web_contents)
|
||||
return;
|
||||
|
||||
|
|
|
@ -34,6 +34,9 @@ class AtomBrowserClient : public brightray::BrowserClient,
|
|||
using Delegate = content::ContentBrowserClient;
|
||||
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.
|
||||
static void SuppressRendererProcessRestartForOnce();
|
||||
|
||||
|
|
|
@ -8,10 +8,24 @@
|
|||
|
||||
#include "atom/common/atom_constants.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"
|
||||
|
||||
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(
|
||||
net::URLRequest* request, net::NetworkDelegate* network_delegate)
|
||||
: JsAsker<net::URLRequestSimpleJob>(request, network_delegate),
|
||||
|
@ -30,6 +44,15 @@ void URLRequestBufferJob::StartAsync(std::unique_ptr<base::Value> options) {
|
|||
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) {
|
||||
NotifyStartError(net::URLRequestStatus(
|
||||
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);
|
||||
#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.
|
||||
std::string blink_features;
|
||||
if (web_preferences.GetString(options::kBlinkFeatures, &blink_features))
|
||||
|
|
|
@ -29,6 +29,7 @@ class WebContentsPreferences
|
|||
: public content::WebContentsUserData<WebContentsPreferences> {
|
||||
public:
|
||||
// Get WebContents according to process ID.
|
||||
// FIXME(zcbenz): This method does not belong here.
|
||||
static content::WebContents* GetWebContentsFromProcessID(int process_id);
|
||||
|
||||
// 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::string16 /* result (in JSON) */)
|
||||
|
||||
IPC_MESSAGE_ROUTED2(AtomViewMsg_Message,
|
||||
IPC_MESSAGE_ROUTED3(AtomViewMsg_Message,
|
||||
bool /* send_to_all */,
|
||||
base::string16 /* channel */,
|
||||
base::ListValue /* arguments */)
|
||||
|
||||
// Sent by the renderer when the draggable regions are updated.
|
||||
IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions,
|
||||
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::ListValue args;
|
||||
args.AppendInteger(object_id_);
|
||||
Send(new AtomViewMsg_Message(routing_id(), channel, args));
|
||||
Send(new AtomViewMsg_Message(routing_id(), false, channel, args));
|
||||
|
||||
Observe(nullptr);
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ REFERENCE_MODULE(atom_browser_power_monitor);
|
|||
REFERENCE_MODULE(atom_browser_power_save_blocker);
|
||||
REFERENCE_MODULE(atom_browser_protocol);
|
||||
REFERENCE_MODULE(atom_browser_global_shortcut);
|
||||
REFERENCE_MODULE(atom_browser_render_process_preferences);
|
||||
REFERENCE_MODULE(atom_browser_session);
|
||||
REFERENCE_MODULE(atom_browser_system_preferences);
|
||||
REFERENCE_MODULE(atom_browser_tray);
|
||||
|
|
|
@ -59,6 +59,34 @@ std::vector<v8::Local<v8::Value>> ListValueToVector(
|
|||
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) {
|
||||
if (key == IDR_DIR_HEADER_HTML) {
|
||||
base::StringPiece html_data =
|
||||
|
@ -123,7 +151,8 @@ bool AtomRenderViewObserver::OnMessageReceived(const IPC::Message& message) {
|
|||
return handled;
|
||||
}
|
||||
|
||||
void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel,
|
||||
void AtomRenderViewObserver::OnBrowserMessage(bool send_to_all,
|
||||
const base::string16& channel,
|
||||
const base::ListValue& args) {
|
||||
if (!document_created_)
|
||||
return;
|
||||
|
@ -135,20 +164,13 @@ void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel,
|
|||
if (!frame || frame->isWebRemoteFrame())
|
||||
return;
|
||||
|
||||
v8::Isolate* isolate = blink::mainThreadIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
EmitIPCEvent(frame, channel, args);
|
||||
|
||||
v8::Local<v8::Context> context = frame->mainWorldScriptContext();
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
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);
|
||||
// Also send the message to all sub-frames.
|
||||
if (send_to_all) {
|
||||
for (blink::WebFrame* child = frame->firstChild(); child;
|
||||
child = child->nextSibling())
|
||||
EmitIPCEvent(child, channel, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ class AtomRenderViewObserver : public content::RenderViewObserver {
|
|||
void DraggableRegionsChanged(blink::WebFrame* frame) 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);
|
||||
|
||||
// Weak reference to renderer client.
|
||||
|
|
|
@ -11,12 +11,14 @@
|
|||
#include "atom/common/api/atom_bindings.h"
|
||||
#include "atom/common/api/event_emitter_caller.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_includes.h"
|
||||
#include "atom/common/options_switches.h"
|
||||
#include "atom/renderer/atom_render_view_observer.h"
|
||||
#include "atom/renderer/guest_view_container.h"
|
||||
#include "atom/renderer/node_array_buffer_bridge.h"
|
||||
#include "atom/renderer/preferences_manager.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/strings/utf_string_conversions.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_view.h"
|
||||
#include "ipc/ipc_message_macros.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "net/base/net_errors.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/WebLocalFrame.h"
|
||||
#include "third_party/WebKit/public/web/WebPluginParams.h"
|
||||
|
@ -59,6 +63,7 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
|
|||
AtomRenderFrameObserver(content::RenderFrame* frame,
|
||||
AtomRendererClient* renderer_client)
|
||||
: content::RenderFrameObserver(frame),
|
||||
render_frame_(frame),
|
||||
world_id_(-1),
|
||||
renderer_client_(renderer_client) {}
|
||||
|
||||
|
@ -69,22 +74,45 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
|
|||
if (world_id_ != -1 && world_id_ != world_id)
|
||||
return;
|
||||
world_id_ = world_id;
|
||||
renderer_client_->DidCreateScriptContext(context);
|
||||
renderer_client_->DidCreateScriptContext(context, render_frame_);
|
||||
}
|
||||
void WillReleaseScriptContext(v8::Local<v8::Context> context,
|
||||
int world_id) override {
|
||||
if (world_id_ != world_id)
|
||||
return;
|
||||
renderer_client_->WillReleaseScriptContext(context);
|
||||
renderer_client_->WillReleaseScriptContext(context, render_frame_);
|
||||
}
|
||||
|
||||
private:
|
||||
content::RenderFrame* render_frame_;
|
||||
int world_id_;
|
||||
AtomRendererClient* renderer_client_;
|
||||
|
||||
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
|
||||
|
||||
AtomRendererClient::AtomRendererClient()
|
||||
|
@ -101,6 +129,8 @@ void AtomRendererClient::RenderThreadStarted() {
|
|||
|
||||
OverrideNodeArrayBuffer();
|
||||
|
||||
preferences_manager_.reset(new PreferencesManager);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// Set ApplicationUserModelID in renderer process.
|
||||
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
||||
|
@ -128,15 +158,11 @@ void AtomRendererClient::RenderThreadStarted() {
|
|||
void AtomRendererClient::RenderFrameCreated(
|
||||
content::RenderFrame* render_frame) {
|
||||
new PepperHelper(render_frame);
|
||||
new AtomRenderFrameObserver(render_frame, this);
|
||||
|
||||
// Allow file scheme to handle service worker by default.
|
||||
// FIXME(zcbenz): Can this be moved elsewhere?
|
||||
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) {
|
||||
|
@ -164,6 +190,23 @@ void AtomRendererClient::RunScriptsAtDocumentStart(
|
|||
// Make sure every page will get a script context created.
|
||||
render_frame->GetWebFrame()->executeScript(
|
||||
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(
|
||||
|
@ -186,7 +229,12 @@ bool AtomRendererClient::OverrideCreatePlugin(
|
|||
}
|
||||
|
||||
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.
|
||||
bool first_time = node_bindings_->uv_env() == nullptr;
|
||||
|
||||
|
@ -201,6 +249,8 @@ void AtomRendererClient::DidCreateScriptContext(
|
|||
|
||||
// Add atom-shell extended APIs.
|
||||
atom_bindings_->BindTo(env->isolate(), env->process_object());
|
||||
AddRenderBindings(env->isolate(), env->process_object(),
|
||||
preferences_manager_.get());
|
||||
|
||||
// Load everything.
|
||||
node_bindings_->LoadEnvironment(env);
|
||||
|
@ -215,9 +265,10 @@ void AtomRendererClient::DidCreateScriptContext(
|
|||
}
|
||||
|
||||
void AtomRendererClient::WillReleaseScriptContext(
|
||||
v8::Handle<v8::Context> context) {
|
||||
v8::Handle<v8::Context> context, content::RenderFrame* render_frame) {
|
||||
node::Environment* env = node::Environment::GetCurrent(context);
|
||||
mate::EmitEvent(env->isolate(), env->process_object(), "exit");
|
||||
if (env)
|
||||
mate::EmitEvent(env->isolate(), env->process_object(), "exit");
|
||||
}
|
||||
|
||||
bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame,
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace atom {
|
||||
|
||||
class AtomBindings;
|
||||
class PreferencesManager;
|
||||
class NodeBindings;
|
||||
|
||||
class AtomRendererClient : public content::ContentRendererClient {
|
||||
|
@ -20,8 +21,10 @@ class AtomRendererClient : public content::ContentRendererClient {
|
|||
AtomRendererClient();
|
||||
virtual ~AtomRendererClient();
|
||||
|
||||
void DidCreateScriptContext(v8::Handle<v8::Context> context);
|
||||
void WillReleaseScriptContext(v8::Handle<v8::Context> context);
|
||||
void DidCreateScriptContext(
|
||||
v8::Handle<v8::Context> context, content::RenderFrame* render_frame);
|
||||
void WillReleaseScriptContext(
|
||||
v8::Handle<v8::Context> context, content::RenderFrame* render_frame);
|
||||
|
||||
private:
|
||||
enum NodeIntegration {
|
||||
|
@ -36,6 +39,7 @@ class AtomRendererClient : public content::ContentRendererClient {
|
|||
void RenderFrameCreated(content::RenderFrame*) override;
|
||||
void RenderViewCreated(content::RenderView*) override;
|
||||
void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
|
||||
void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override;
|
||||
blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer(
|
||||
blink::WebSpeechSynthesizerClient* client) override;
|
||||
bool OverrideCreatePlugin(content::RenderFrame* render_frame,
|
||||
|
@ -61,6 +65,7 @@ class AtomRendererClient : public content::ContentRendererClient {
|
|||
|
||||
std::unique_ptr<NodeBindings> node_bindings_;
|
||||
std::unique_ptr<AtomBindings> atom_bindings_;
|
||||
std::unique_ptr<PreferencesManager> preferences_manager_;
|
||||
|
||||
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
|
||||
|
||||
To make debugging easier, Electron has basic support for the
|
||||
[Chrome DevTools Extension][devtools-extension].
|
||||
Electron supports the [Chrome DevTools Extension][devtools-extension], which can
|
||||
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
|
||||
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.
|
||||
## How to load a DevTools Extension
|
||||
|
||||
** 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)
|
||||
, first you need to download its source code:
|
||||
Using the [React Developer Tools][react-devtools] as example:
|
||||
|
||||
```bash
|
||||
$ cd /some-directory
|
||||
$ git clone --recursive https://github.com/facebook/react-devtools.git
|
||||
```
|
||||
1. Install it in Chrome browser.
|
||||
1. Navigate to `chrome://extensions`, and find its extension ID, which is a hash
|
||||
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,
|
||||
and running the following code in the DevTools console:
|
||||
## Supported DevTools Extensions
|
||||
|
||||
```javascript
|
||||
const BrowserWindow = require('electron').remote.BrowserWindow;
|
||||
BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome');
|
||||
```
|
||||
Electron only supports a limited set of `chrome.*` APIs, so some extensions
|
||||
using unsupported `chrome.*` APIs for chrome extension features may not work.
|
||||
Following Devtools Extensions are tested and guaranteed to work in Electron:
|
||||
|
||||
To unload the extension, you can call the `BrowserWindow.removeDevToolsExtension`
|
||||
API with its name and it will not load the next time you open the DevTools:
|
||||
* [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
||||
* [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
|
||||
BrowserWindow.removeDevToolsExtension('React Developer Tools');
|
||||
```
|
||||
### What should I do if a DevTools Extension is not working?
|
||||
|
||||
## 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
|
||||
Electron, but they have to be in a plain directory. For those packaged with
|
||||
`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.
|
||||
Then file a bug at Electron's issues list, and describe which part of the
|
||||
extension is not working as expected.
|
||||
|
||||
[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/reset-search-paths.js',
|
||||
'lib/renderer/chrome-api.js',
|
||||
'lib/renderer/content-scripts-injector.js',
|
||||
'lib/renderer/init.js',
|
||||
'lib/renderer/inspector.js',
|
||||
'lib/renderer/override.js',
|
||||
|
@ -109,6 +110,8 @@
|
|||
'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.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.h',
|
||||
'atom/browser/api/atom_api_screen.cc',
|
||||
|
@ -220,6 +223,8 @@
|
|||
'atom/browser/net/url_request_fetch_job.h',
|
||||
'atom/browser/node_debugger.cc',
|
||||
'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.h',
|
||||
'atom/browser/ui/accelerator_util_mac.mm',
|
||||
|
@ -396,6 +401,8 @@
|
|||
'atom/renderer/guest_view_container.h',
|
||||
'atom/renderer/node_array_buffer_bridge.cc',
|
||||
'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.h',
|
||||
'chromium_src/chrome/browser/browser_process.cc',
|
||||
|
|
|
@ -71,12 +71,15 @@ let wrapWebContents = function (webContents) {
|
|||
webContents.setMaxListeners(0)
|
||||
|
||||
// WebContents::send(channel, args..)
|
||||
webContents.send = function (channel, ...args) {
|
||||
// WebContents::sendToAll(channel, args..)
|
||||
const sendWrapper = (allFrames, channel, ...args) => {
|
||||
if (channel == null) {
|
||||
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.
|
||||
controller = new NavigationController(webContents)
|
||||
|
@ -218,9 +221,12 @@ binding._setWrapWebContents(wrapWebContents)
|
|||
debuggerBinding._setWrapDebugger(wrapDebugger)
|
||||
sessionBinding._setWrapSession(wrapSession)
|
||||
|
||||
module.exports.create = function (options) {
|
||||
if (options == null) {
|
||||
options = {}
|
||||
module.exports = {
|
||||
create (options = {}) {
|
||||
return binding.create(options)
|
||||
},
|
||||
|
||||
fromId (id) {
|
||||
return binding.fromId(id)
|
||||
}
|
||||
return binding.create(options)
|
||||
}
|
||||
|
|
|
@ -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 path = require('path')
|
||||
const url = require('url')
|
||||
|
||||
// Mapping between hostname and file path.
|
||||
var hostPathMap = {}
|
||||
var hostPathMapNextKey = 0
|
||||
|
||||
var getHostForPath = function (path) {
|
||||
var key
|
||||
key = 'extension-' + (++hostPathMapNextKey)
|
||||
hostPathMap[key] = path
|
||||
return key
|
||||
// TODO(zcbenz): Remove this when we have Object.values().
|
||||
const objectValues = function (object) {
|
||||
return Object.keys(object).map(function (key) { return object[key] })
|
||||
}
|
||||
|
||||
var getPathForHost = function (host) {
|
||||
return hostPathMap[host]
|
||||
// Mapping between extensionId(hostname) and manifest.
|
||||
const manifestMap = {} // extensionId => manifest
|
||||
const manifestNameMap = {} // name => manifest
|
||||
|
||||
const generateExtensionIdFromName = function (name) {
|
||||
return name.replace(/[\W_]+/g, '-').toLowerCase()
|
||||
}
|
||||
|
||||
// Cache extensionInfo.
|
||||
var extensionInfoMap = {}
|
||||
|
||||
var getExtensionInfoFromPath = function (srcDirectory) {
|
||||
var manifest, page
|
||||
manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json')))
|
||||
if (extensionInfoMap[manifest.name] == null) {
|
||||
// We can not use 'file://' directly because all resources in the extension
|
||||
// will be treated as relative to the root in Chrome.
|
||||
page = url.format({
|
||||
protocol: 'chrome-extension',
|
||||
slashes: true,
|
||||
hostname: getHostForPath(srcDirectory),
|
||||
pathname: manifest.devtools_page
|
||||
})
|
||||
extensionInfoMap[manifest.name] = {
|
||||
startPage: page,
|
||||
name: manifest.name,
|
||||
// Create or get manifest object from |srcDirectory|.
|
||||
const getManifestFromPath = function (srcDirectory) {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json')))
|
||||
if (!manifestNameMap[manifest.name]) {
|
||||
const extensionId = generateExtensionIdFromName(manifest.name)
|
||||
console.log(extensionId)
|
||||
manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
|
||||
Object.assign(manifest, {
|
||||
srcDirectory: srcDirectory,
|
||||
exposeExperimentalAPIs: true
|
||||
}
|
||||
return extensionInfoMap[manifest.name]
|
||||
extensionId: extensionId,
|
||||
// We can not use 'file://' directly because all resources in the extension
|
||||
// will be treated as relative to the root in Chrome.
|
||||
startPage: url.format({
|
||||
protocol: 'chrome-extension',
|
||||
slashes: true,
|
||||
hostname: extensionId,
|
||||
pathname: manifest.devtools_page
|
||||
})
|
||||
})
|
||||
return manifest
|
||||
}
|
||||
}
|
||||
|
||||
// The loaded extensions cache and its persistent path.
|
||||
var loadedExtensions = null
|
||||
var loadedExtensionsPath = null
|
||||
// Manage the background pages.
|
||||
const backgroundPages = {}
|
||||
|
||||
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 () {
|
||||
try {
|
||||
loadedExtensions = Object.keys(extensionInfoMap).map(function (key) {
|
||||
return extensionInfoMap[key].srcDirectory
|
||||
const loadedExtensions = objectValues(manifestMap).map(function (manifest) {
|
||||
return manifest.srcDirectory
|
||||
})
|
||||
if (loadedExtensions.length > 0) {
|
||||
try {
|
||||
|
@ -69,74 +237,78 @@ app.on('will-quit', function () {
|
|||
|
||||
// We can not use protocol or BrowserWindow until app is ready.
|
||||
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.
|
||||
loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
|
||||
try {
|
||||
loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath))
|
||||
if (!Array.isArray(loadedExtensions)) {
|
||||
loadedExtensions = []
|
||||
}
|
||||
|
||||
// Preheat the extensionInfo cache.
|
||||
for (i = 0, len = loadedExtensions.length; i < len; i++) {
|
||||
srcDirectory = loadedExtensions[i]
|
||||
getExtensionInfoFromPath(srcDirectory)
|
||||
const loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath))
|
||||
if (Array.isArray(loadedExtensions)) {
|
||||
for (const srcDirectory of loadedExtensions) {
|
||||
// Start background pages and set content scripts.
|
||||
const manifest = getManifestFromPath(srcDirectory)
|
||||
loadExtension(manifest)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
}
|
||||
|
||||
// The chrome-extension: can map a extension URL request to real file path.
|
||||
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
|
||||
}
|
||||
// The public API to add/remove extensions.
|
||||
BrowserWindow.addDevToolsExtension = function (srcDirectory) {
|
||||
var extensionInfo, j, len1, ref, window
|
||||
extensionInfo = getExtensionInfoFromPath(srcDirectory)
|
||||
if (extensionInfo) {
|
||||
ref = BrowserWindow.getAllWindows()
|
||||
for (j = 0, len1 = ref.length; j < len1; j++) {
|
||||
window = ref[j]
|
||||
window._loadDevToolsExtensions([extensionInfo])
|
||||
const manifest = getManifestFromPath(srcDirectory)
|
||||
if (manifest) {
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
loadDevToolsExtensions(win, [manifest])
|
||||
}
|
||||
return extensionInfo.name
|
||||
return manifest.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.
|
||||
init = BrowserWindow.prototype._init
|
||||
// Load extensions automatically when devtools is opened.
|
||||
const init = BrowserWindow.prototype._init
|
||||
BrowserWindow.prototype._init = function () {
|
||||
init.call(this)
|
||||
return this.webContents.on('devtools-opened', () => {
|
||||
return this._loadDevToolsExtensions(Object.keys(extensionInfoMap).map(function (key) {
|
||||
return extensionInfoMap[key]
|
||||
}))
|
||||
hookWindowForTabEvents(this)
|
||||
this.webContents.on('devtools-opened', () => {
|
||||
loadDevToolsExtensions(this, objectValues(manifestMap))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const electron = require('electron')
|
||||
const v8Util = process.atomBinding('v8_util')
|
||||
const {ipcMain, isPromise} = electron
|
||||
const {ipcMain, isPromise, webContents} = electron
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -1,13 +1,200 @@
|
|||
const {ipcRenderer} = require('electron')
|
||||
const url = require('url')
|
||||
const chrome = window.chrome = window.chrome || {}
|
||||
|
||||
chrome.extension = {
|
||||
getURL: function (path) {
|
||||
return url.format({
|
||||
protocol: window.location.protocol,
|
||||
slashes: true,
|
||||
hostname: window.location.hostname,
|
||||
pathname: path
|
||||
})
|
||||
let nextId = 0
|
||||
|
||||
class Event {
|
||||
constructor () {
|
||||
this.listeners = []
|
||||
}
|
||||
|
||||
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.
|
||||
var nodeIntegration = 'false'
|
||||
var preloadScript = null
|
||||
let nodeIntegration = 'false'
|
||||
let preloadScript = null
|
||||
let isBackgroundPage = false
|
||||
for (let arg of process.argv) {
|
||||
if (arg.indexOf('--guest-instance-id=') === 0) {
|
||||
// This is a guest web view.
|
||||
|
@ -54,6 +55,8 @@ for (let arg of process.argv) {
|
|||
nodeIntegration = arg.substr(arg.indexOf('=') + 1)
|
||||
} else if (arg.indexOf('--preload=') === 0) {
|
||||
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'
|
||||
} else if (window.location.protocol === 'chrome-extension:') {
|
||||
// Add implementations of chrome API.
|
||||
require('./chrome-api')
|
||||
nodeIntegration = 'true'
|
||||
require('./chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
|
||||
nodeIntegration = 'false'
|
||||
} else {
|
||||
// Override default web functions.
|
||||
require('./override')
|
||||
|
||||
// Inject content scripts.
|
||||
require('./content-scripts-injector')
|
||||
|
||||
// Load webview tag implementation.
|
||||
if (nodeIntegration === 'true' && process.guestInstanceId == null) {
|
||||
require('./web-view/web-view')
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
window.onload = function () {
|
||||
// Make sure |window.chrome| is defined for devtools extensions.
|
||||
hijackSetInjectedScript(window.InspectorFrontendHost)
|
||||
|
||||
// Use menu API to show context menu.
|
||||
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu
|
||||
|
||||
|
@ -9,18 +6,6 @@ window.onload = function () {
|
|||
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 fn, i, item, len, template
|
||||
template = []
|
||||
|
|
|
@ -3,11 +3,8 @@
|
|||
const assert = require('assert')
|
||||
const path = require('path')
|
||||
|
||||
const ipcRenderer = require('electron').ipcRenderer
|
||||
const remote = require('electron').remote
|
||||
|
||||
const ipcMain = remote.require('electron').ipcMain
|
||||
const BrowserWindow = remote.require('electron').BrowserWindow
|
||||
const {ipcRenderer, remote} = require('electron')
|
||||
const {ipcMain, webContents, BrowserWindow} = remote
|
||||
|
||||
const comparePaths = function (path1, path2) {
|
||||
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 () {
|
||||
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