Merge pull request #11300 from electron/external-devtools
Add API to set arbitrary WebContents as devtools
This commit is contained in:
commit
d598aa1a67
10 changed files with 199 additions and 47 deletions
|
@ -895,10 +895,11 @@ void WebContents::DevToolsOpened() {
|
||||||
managed_web_contents()->CallClientFunction(
|
managed_web_contents()->CallClientFunction(
|
||||||
"DevToolsAPI.setInspectedTabId", &tab_id, nullptr, nullptr);
|
"DevToolsAPI.setInspectedTabId", &tab_id, nullptr, nullptr);
|
||||||
|
|
||||||
// Inherit owner window in devtools.
|
// Inherit owner window in devtools when it doesn't have one.
|
||||||
if (owner_window())
|
auto* devtools = managed_web_contents()->GetDevToolsWebContents();
|
||||||
handle->SetOwnerWindow(managed_web_contents()->GetDevToolsWebContents(),
|
bool has_window = devtools->GetUserData(NativeWindowRelay::UserDataKey());
|
||||||
owner_window());
|
if (owner_window() && !has_window)
|
||||||
|
handle->SetOwnerWindow(devtools, owner_window());
|
||||||
|
|
||||||
Emit("devtools-opened");
|
Emit("devtools-opened");
|
||||||
}
|
}
|
||||||
|
@ -1178,7 +1179,8 @@ void WebContents::OpenDevTools(mate::Arguments* args) {
|
||||||
std::string state;
|
std::string state;
|
||||||
if (type_ == WEB_VIEW || !owner_window()) {
|
if (type_ == WEB_VIEW || !owner_window()) {
|
||||||
state = "detach";
|
state = "detach";
|
||||||
} else if (args && args->Length() == 1) {
|
}
|
||||||
|
if (args && args->Length() == 1) {
|
||||||
bool detach = false;
|
bool detach = false;
|
||||||
mate::Dictionary options;
|
mate::Dictionary options;
|
||||||
if (args->GetNext(&options)) {
|
if (args->GetNext(&options)) {
|
||||||
|
@ -1808,6 +1810,11 @@ void WebContents::SetEmbedder(const WebContents* embedder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebContents::SetDevToolsWebContents(const WebContents* devtools) {
|
||||||
|
if (managed_web_contents())
|
||||||
|
managed_web_contents()->SetDevToolsWebContents(devtools->web_contents());
|
||||||
|
}
|
||||||
|
|
||||||
v8::Local<v8::Value> WebContents::GetNativeView() const {
|
v8::Local<v8::Value> WebContents::GetNativeView() const {
|
||||||
gfx::NativeView ptr = web_contents()->GetNativeView();
|
gfx::NativeView ptr = web_contents()->GetNativeView();
|
||||||
auto buffer = node::Buffer::Copy(
|
auto buffer = node::Buffer::Copy(
|
||||||
|
@ -1929,6 +1936,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
|
||||||
.SetMethod("copyImageAt", &WebContents::CopyImageAt)
|
.SetMethod("copyImageAt", &WebContents::CopyImageAt)
|
||||||
.SetMethod("capturePage", &WebContents::CapturePage)
|
.SetMethod("capturePage", &WebContents::CapturePage)
|
||||||
.SetMethod("setEmbedder", &WebContents::SetEmbedder)
|
.SetMethod("setEmbedder", &WebContents::SetEmbedder)
|
||||||
|
.SetMethod("setDevToolsWebContents", &WebContents::SetDevToolsWebContents)
|
||||||
.SetMethod("getNativeView", &WebContents::GetNativeView)
|
.SetMethod("getNativeView", &WebContents::GetNativeView)
|
||||||
.SetMethod("setWebRTCIPHandlingPolicy",
|
.SetMethod("setWebRTCIPHandlingPolicy",
|
||||||
&WebContents::SetWebRTCIPHandlingPolicy)
|
&WebContents::SetWebRTCIPHandlingPolicy)
|
||||||
|
|
|
@ -126,6 +126,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||||
void Print(mate::Arguments* args);
|
void Print(mate::Arguments* args);
|
||||||
std::vector<printing::PrinterBasicInfo> GetPrinterList();
|
std::vector<printing::PrinterBasicInfo> GetPrinterList();
|
||||||
void SetEmbedder(const WebContents* embedder);
|
void SetEmbedder(const WebContents* embedder);
|
||||||
|
void SetDevToolsWebContents(const WebContents* devtools);
|
||||||
v8::Local<v8::Value> GetNativeView() const;
|
v8::Local<v8::Value> GetNativeView() const;
|
||||||
|
|
||||||
// Print current page as PDF.
|
// Print current page as PDF.
|
||||||
|
|
|
@ -391,6 +391,10 @@ class NativeWindowRelay :
|
||||||
explicit NativeWindowRelay(base::WeakPtr<NativeWindow> window)
|
explicit NativeWindowRelay(base::WeakPtr<NativeWindow> window)
|
||||||
: key(UserDataKey()), window(window) {}
|
: key(UserDataKey()), window(window) {}
|
||||||
|
|
||||||
|
static void* UserDataKey() {
|
||||||
|
return content::WebContentsUserData<NativeWindowRelay>::UserDataKey();
|
||||||
|
}
|
||||||
|
|
||||||
void* key;
|
void* key;
|
||||||
base::WeakPtr<NativeWindow> window;
|
base::WeakPtr<NativeWindow> window;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ class InspectableWebContents {
|
||||||
virtual void SetDelegate(InspectableWebContentsDelegate* delegate) = 0;
|
virtual void SetDelegate(InspectableWebContentsDelegate* delegate) = 0;
|
||||||
virtual InspectableWebContentsDelegate* GetDelegate() const = 0;
|
virtual InspectableWebContentsDelegate* GetDelegate() const = 0;
|
||||||
|
|
||||||
|
virtual void SetDevToolsWebContents(content::WebContents* devtools) = 0;
|
||||||
virtual void SetDockState(const std::string& state) = 0;
|
virtual void SetDockState(const std::string& state) = 0;
|
||||||
virtual void ShowDevTools() = 0;
|
virtual void ShowDevTools() = 0;
|
||||||
virtual void CloseDevTools() = 0;
|
virtual void CloseDevTools() = 0;
|
||||||
|
|
|
@ -242,13 +242,14 @@ InspectableWebContentsImpl::InspectableWebContentsImpl(
|
||||||
|
|
||||||
InspectableWebContentsImpl::~InspectableWebContentsImpl() {
|
InspectableWebContentsImpl::~InspectableWebContentsImpl() {
|
||||||
// Unsubscribe from devtools and Clean up resources.
|
// Unsubscribe from devtools and Clean up resources.
|
||||||
if (devtools_web_contents_) {
|
if (GetDevToolsWebContents()) {
|
||||||
devtools_web_contents_->SetDelegate(nullptr);
|
if (managed_devtools_web_contents_)
|
||||||
|
managed_devtools_web_contents_->SetDelegate(nullptr);
|
||||||
// Calling this also unsubscribes the observer, so WebContentsDestroyed
|
// Calling this also unsubscribes the observer, so WebContentsDestroyed
|
||||||
// won't be called again.
|
// won't be called again.
|
||||||
WebContentsDestroyed();
|
WebContentsDestroyed();
|
||||||
}
|
}
|
||||||
// Let destructor destroy devtools_web_contents_.
|
// Let destructor destroy managed_devtools_web_contents_.
|
||||||
}
|
}
|
||||||
|
|
||||||
InspectableWebContentsView* InspectableWebContentsImpl::GetView() const {
|
InspectableWebContentsView* InspectableWebContentsImpl::GetView() const {
|
||||||
|
@ -261,7 +262,10 @@ content::WebContents* InspectableWebContentsImpl::GetWebContents() const {
|
||||||
|
|
||||||
content::WebContents* InspectableWebContentsImpl::GetDevToolsWebContents()
|
content::WebContents* InspectableWebContentsImpl::GetDevToolsWebContents()
|
||||||
const {
|
const {
|
||||||
return devtools_web_contents_.get();
|
if (external_devtools_web_contents_)
|
||||||
|
return external_devtools_web_contents_;
|
||||||
|
else
|
||||||
|
return managed_devtools_web_contents_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectableWebContentsImpl::InspectElement(int x, int y) {
|
void InspectableWebContentsImpl::InspectElement(int x, int y) {
|
||||||
|
@ -288,43 +292,56 @@ void InspectableWebContentsImpl::SetDockState(const std::string& state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InspectableWebContentsImpl::SetDevToolsWebContents(
|
||||||
|
content::WebContents* devtools) {
|
||||||
|
if (!managed_devtools_web_contents_)
|
||||||
|
external_devtools_web_contents_ = devtools;
|
||||||
|
}
|
||||||
|
|
||||||
void InspectableWebContentsImpl::ShowDevTools() {
|
void InspectableWebContentsImpl::ShowDevTools() {
|
||||||
|
if (embedder_message_dispatcher_) {
|
||||||
|
if (managed_devtools_web_contents_)
|
||||||
|
view_->ShowDevTools();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Show devtools only after it has done loading, this is to make sure the
|
// Show devtools only after it has done loading, this is to make sure the
|
||||||
// SetIsDocked is called *BEFORE* ShowDevTools.
|
// SetIsDocked is called *BEFORE* ShowDevTools.
|
||||||
if (!devtools_web_contents_) {
|
embedder_message_dispatcher_.reset(
|
||||||
embedder_message_dispatcher_.reset(
|
DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this));
|
||||||
DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this));
|
|
||||||
|
|
||||||
content::WebContents::CreateParams create_params(
|
if (!external_devtools_web_contents_) { // no external devtools
|
||||||
web_contents_->GetBrowserContext());
|
managed_devtools_web_contents_.reset(
|
||||||
devtools_web_contents_.reset(content::WebContents::Create(create_params));
|
content::WebContents::Create(
|
||||||
|
content::WebContents::CreateParams(
|
||||||
Observe(devtools_web_contents_.get());
|
web_contents_->GetBrowserContext())));
|
||||||
devtools_web_contents_->SetDelegate(this);
|
managed_devtools_web_contents_->SetDelegate(this);
|
||||||
|
|
||||||
AttachTo(content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get()));
|
|
||||||
|
|
||||||
devtools_web_contents_->GetController().LoadURL(
|
|
||||||
GetDevToolsURL(can_dock_),
|
|
||||||
content::Referrer(),
|
|
||||||
ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
|
|
||||||
std::string());
|
|
||||||
} else {
|
|
||||||
view_->ShowDevTools();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Observe(GetDevToolsWebContents());
|
||||||
|
AttachTo(content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get()));
|
||||||
|
|
||||||
|
GetDevToolsWebContents()->GetController().LoadURL(
|
||||||
|
GetDevToolsURL(can_dock_),
|
||||||
|
content::Referrer(),
|
||||||
|
ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
|
||||||
|
std::string());
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectableWebContentsImpl::CloseDevTools() {
|
void InspectableWebContentsImpl::CloseDevTools() {
|
||||||
if (devtools_web_contents_) {
|
if (GetDevToolsWebContents()) {
|
||||||
frontend_loaded_ = false;
|
frontend_loaded_ = false;
|
||||||
view_->CloseDevTools();
|
if (managed_devtools_web_contents_) {
|
||||||
devtools_web_contents_.reset();
|
view_->CloseDevTools();
|
||||||
|
managed_devtools_web_contents_.reset();
|
||||||
|
}
|
||||||
|
embedder_message_dispatcher_.reset();
|
||||||
web_contents_->Focus();
|
web_contents_->Focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InspectableWebContentsImpl::IsDevToolsViewShowing() {
|
bool InspectableWebContentsImpl::IsDevToolsViewShowing() {
|
||||||
return devtools_web_contents_ && view_->IsDevToolsViewShowing();
|
return managed_devtools_web_contents_ && view_->IsDevToolsViewShowing();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectableWebContentsImpl::AttachTo(
|
void InspectableWebContentsImpl::AttachTo(
|
||||||
|
@ -347,7 +364,7 @@ void InspectableWebContentsImpl::CallClientFunction(
|
||||||
const base::Value* arg1,
|
const base::Value* arg1,
|
||||||
const base::Value* arg2,
|
const base::Value* arg2,
|
||||||
const base::Value* arg3) {
|
const base::Value* arg3) {
|
||||||
if (!devtools_web_contents_)
|
if (!GetDevToolsWebContents())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string javascript = function_name + "(";
|
std::string javascript = function_name + "(";
|
||||||
|
@ -365,7 +382,7 @@ void InspectableWebContentsImpl::CallClientFunction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
javascript.append(");");
|
javascript.append(");");
|
||||||
devtools_web_contents_->GetMainFrame()->ExecuteJavaScript(
|
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(
|
||||||
base::UTF8ToUTF16(javascript));
|
base::UTF8ToUTF16(javascript));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,7 +417,8 @@ void InspectableWebContentsImpl::CloseWindow() {
|
||||||
|
|
||||||
void InspectableWebContentsImpl::LoadCompleted() {
|
void InspectableWebContentsImpl::LoadCompleted() {
|
||||||
frontend_loaded_ = true;
|
frontend_loaded_ = true;
|
||||||
view_->ShowDevTools();
|
if (managed_devtools_web_contents_)
|
||||||
|
view_->ShowDevTools();
|
||||||
|
|
||||||
// If the devtools can dock, "SetIsDocked" will be called by devtools itself.
|
// If the devtools can dock, "SetIsDocked" will be called by devtools itself.
|
||||||
if (!can_dock_) {
|
if (!can_dock_) {
|
||||||
|
@ -415,7 +433,7 @@ void InspectableWebContentsImpl::LoadCompleted() {
|
||||||
}
|
}
|
||||||
base::string16 javascript = base::UTF8ToUTF16(
|
base::string16 javascript = base::UTF8ToUTF16(
|
||||||
"Components.dockController.setDockSide(\"" + dock_state_ + "\");");
|
"Components.dockController.setDockSide(\"" + dock_state_ + "\");");
|
||||||
devtools_web_contents_->GetMainFrame()->ExecuteJavaScript(javascript);
|
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(javascript);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view_->GetDelegate())
|
if (view_->GetDelegate())
|
||||||
|
@ -428,15 +446,17 @@ void InspectableWebContentsImpl::SetInspectedPageBounds(const gfx::Rect& rect) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
contents_resizing_strategy_.CopyFrom(strategy);
|
contents_resizing_strategy_.CopyFrom(strategy);
|
||||||
view_->SetContentsResizingStrategy(contents_resizing_strategy_);
|
if (managed_devtools_web_contents_)
|
||||||
|
view_->SetContentsResizingStrategy(contents_resizing_strategy_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectableWebContentsImpl::InspectElementCompleted() {
|
void InspectableWebContentsImpl::InspectElementCompleted() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectableWebContentsImpl::InspectedURLChanged(const std::string& url) {
|
void InspectableWebContentsImpl::InspectedURLChanged(const std::string& url) {
|
||||||
view_->SetTitle(base::UTF8ToUTF16(base::StringPrintf(kTitleFormat,
|
if (managed_devtools_web_contents_)
|
||||||
url.c_str())));
|
view_->SetTitle(base::UTF8ToUTF16(base::StringPrintf(kTitleFormat,
|
||||||
|
url.c_str())));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectableWebContentsImpl::LoadNetworkResource(
|
void InspectableWebContentsImpl::LoadNetworkResource(
|
||||||
|
@ -452,8 +472,8 @@ void InspectableWebContentsImpl::LoadNetworkResource(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto browser_context =
|
auto* browser_context = static_cast<BrowserContext*>(
|
||||||
static_cast<BrowserContext*>(devtools_web_contents_->GetBrowserContext());
|
GetDevToolsWebContents()->GetBrowserContext());
|
||||||
|
|
||||||
net::URLFetcher* fetcher =
|
net::URLFetcher* fetcher =
|
||||||
(net::URLFetcher::Create(gurl, net::URLFetcher::GET, this)).release();
|
(net::URLFetcher::Create(gurl, net::URLFetcher::GET, this)).release();
|
||||||
|
@ -468,7 +488,8 @@ void InspectableWebContentsImpl::LoadNetworkResource(
|
||||||
|
|
||||||
void InspectableWebContentsImpl::SetIsDocked(const DispatchCallback& callback,
|
void InspectableWebContentsImpl::SetIsDocked(const DispatchCallback& callback,
|
||||||
bool docked) {
|
bool docked) {
|
||||||
view_->SetIsDocked(docked);
|
if (managed_devtools_web_contents_)
|
||||||
|
view_->SetIsDocked(docked);
|
||||||
if (!callback.is_null())
|
if (!callback.is_null())
|
||||||
callback.Run(nullptr);
|
callback.Run(nullptr);
|
||||||
}
|
}
|
||||||
|
@ -640,7 +661,7 @@ void InspectableWebContentsImpl::DispatchProtocolMessage(
|
||||||
if (message.length() < kMaxMessageChunkSize) {
|
if (message.length() < kMaxMessageChunkSize) {
|
||||||
base::string16 javascript = base::UTF8ToUTF16(
|
base::string16 javascript = base::UTF8ToUTF16(
|
||||||
"DevToolsAPI.dispatchMessage(" + message + ");");
|
"DevToolsAPI.dispatchMessage(" + message + ");");
|
||||||
devtools_web_contents_->GetMainFrame()->ExecuteJavaScript(javascript);
|
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(javascript);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,13 +685,15 @@ void InspectableWebContentsImpl::RenderFrameHostChanged(
|
||||||
frontend_host_.reset(content::DevToolsFrontendHost::Create(
|
frontend_host_.reset(content::DevToolsFrontendHost::Create(
|
||||||
new_host,
|
new_host,
|
||||||
base::Bind(&InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend,
|
base::Bind(&InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend,
|
||||||
base::Unretained(this))));
|
weak_factory_.GetWeakPtr())));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectableWebContentsImpl::WebContentsDestroyed() {
|
void InspectableWebContentsImpl::WebContentsDestroyed() {
|
||||||
frontend_loaded_ = false;
|
frontend_loaded_ = false;
|
||||||
|
external_devtools_web_contents_ = nullptr;
|
||||||
Observe(nullptr);
|
Observe(nullptr);
|
||||||
Detach();
|
Detach();
|
||||||
|
embedder_message_dispatcher_.reset();
|
||||||
|
|
||||||
for (const auto& pair : pending_requests_)
|
for (const auto& pair : pending_requests_)
|
||||||
delete pair.first;
|
delete pair.first;
|
||||||
|
@ -758,7 +781,7 @@ void InspectableWebContentsImpl::ReadyToCommitNavigation(
|
||||||
content::NavigationHandle* navigation_handle) {
|
content::NavigationHandle* navigation_handle) {
|
||||||
if (navigation_handle->IsInMainFrame()) {
|
if (navigation_handle->IsInMainFrame()) {
|
||||||
if (navigation_handle->GetRenderFrameHost() ==
|
if (navigation_handle->GetRenderFrameHost() ==
|
||||||
devtools_web_contents_->GetMainFrame() &&
|
GetDevToolsWebContents()->GetMainFrame() &&
|
||||||
frontend_host_) {
|
frontend_host_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ class InspectableWebContentsImpl :
|
||||||
|
|
||||||
void SetDelegate(InspectableWebContentsDelegate* delegate) override;
|
void SetDelegate(InspectableWebContentsDelegate* delegate) override;
|
||||||
InspectableWebContentsDelegate* GetDelegate() const override;
|
InspectableWebContentsDelegate* GetDelegate() const override;
|
||||||
|
void SetDevToolsWebContents(content::WebContents* devtools) override;
|
||||||
void SetDockState(const std::string& state) override;
|
void SetDockState(const std::string& state) override;
|
||||||
void ShowDevTools() override;
|
void ShowDevTools() override;
|
||||||
void CloseDevTools() override;
|
void CloseDevTools() override;
|
||||||
|
@ -192,7 +193,13 @@ class InspectableWebContentsImpl :
|
||||||
PrefService* pref_service_; // weak reference.
|
PrefService* pref_service_; // weak reference.
|
||||||
|
|
||||||
std::unique_ptr<content::WebContents> web_contents_;
|
std::unique_ptr<content::WebContents> web_contents_;
|
||||||
std::unique_ptr<content::WebContents> devtools_web_contents_;
|
|
||||||
|
// The default devtools created by this class when we don't have an external
|
||||||
|
// one assigned by SetDevToolsWebContents.
|
||||||
|
std::unique_ptr<content::WebContents> managed_devtools_web_contents_;
|
||||||
|
// The external devtools assigned by SetDevToolsWebContents.
|
||||||
|
content::WebContents* external_devtools_web_contents_ = nullptr;
|
||||||
|
|
||||||
std::unique_ptr<InspectableWebContentsView> view_;
|
std::unique_ptr<InspectableWebContentsView> view_;
|
||||||
|
|
||||||
using ExtensionsAPIs = std::map<std::string, std::string>;
|
using ExtensionsAPIs = std::map<std::string, std::string>;
|
||||||
|
|
|
@ -1074,6 +1074,68 @@ win.webContents.on('devtools-opened', () => {
|
||||||
|
|
||||||
Removes the specified path from DevTools workspace.
|
Removes the specified path from DevTools workspace.
|
||||||
|
|
||||||
|
#### `contents.setDevToolsWebContents(devToolsWebContents)`
|
||||||
|
|
||||||
|
* `devToolsWebContents` WebContents
|
||||||
|
|
||||||
|
Uses the `devToolsWebContents` as the target `WebContents` to show devtools.
|
||||||
|
|
||||||
|
The `devToolsWebContents` must not have done any navigation, and it should not
|
||||||
|
be used for other purposes after the call.
|
||||||
|
|
||||||
|
By default Electron manages the devtools by creating an internal `WebContents`
|
||||||
|
with native view, which developers have very limited control of. With the
|
||||||
|
`setDevToolsWebContents` method, developers can use any `WebContents` to show
|
||||||
|
the devtools in it, including `BrowserWindow`, `BrowserView` and `<webview>`
|
||||||
|
tag.
|
||||||
|
|
||||||
|
Note that closing the devtools does not destory the `devToolsWebContents`, it
|
||||||
|
is caller's responsibility to destroy `devToolsWebContents`.
|
||||||
|
|
||||||
|
An example of showing devtools in a `<webview>` tag:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style type="text/css">
|
||||||
|
* { margin: 0; }
|
||||||
|
#browser { height: 70%; }
|
||||||
|
#devtools { height: 30%; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<webview id="browser" src="https://github.com"></webview>
|
||||||
|
<webview id="devtools"></webview>
|
||||||
|
<script>
|
||||||
|
const browserView = document.getElementById('browser')
|
||||||
|
const devtoolsView = document.getElementById('devtools')
|
||||||
|
browserView.addEventListener('dom-ready', () => {
|
||||||
|
const browser = browserView.getWebContents()
|
||||||
|
browser.setDevToolsWebContents(devtoolsView.getWebContents())
|
||||||
|
browser.openDevTools()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
An example of showing devtools in a `BrowserWindow`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const {app, BrowserWindow} = require('electron')
|
||||||
|
|
||||||
|
let win = null
|
||||||
|
let devtools = null
|
||||||
|
|
||||||
|
app.once('ready', () => {
|
||||||
|
win = new BrowserWindow()
|
||||||
|
devtools = new BrowserWindow()
|
||||||
|
win.loadURL('https://github.com')
|
||||||
|
win.webContents.setDevToolsWebContents(devtools.webContents)
|
||||||
|
win.webContents.openDevTools({mode: 'detach'})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
#### `contents.openDevTools([options])`
|
#### `contents.openDevTools([options])`
|
||||||
|
|
||||||
* `options` Object (optional)
|
* `options` Object (optional)
|
||||||
|
@ -1083,6 +1145,9 @@ Removes the specified path from DevTools workspace.
|
||||||
|
|
||||||
Opens the devtools.
|
Opens the devtools.
|
||||||
|
|
||||||
|
When `contents` is a `<webview>` tag, the `mode` would be `detach` by default,
|
||||||
|
explicitly passing an empty `mode` can force using last used dock state.
|
||||||
|
|
||||||
#### `contents.closeDevTools()`
|
#### `contents.closeDevTools()`
|
||||||
|
|
||||||
Closes the devtools.
|
Closes the devtools.
|
||||||
|
|
|
@ -230,7 +230,7 @@ class SrcAttribute extends WebViewAttribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
parse () {
|
parse () {
|
||||||
if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) {
|
if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.webViewImpl.guestInstanceId == null) {
|
if (this.webViewImpl.guestInstanceId == null) {
|
||||||
|
@ -240,6 +240,9 @@ class SrcAttribute extends WebViewAttribute {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!this.getValue()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Navigate to |this.src|.
|
// Navigate to |this.src|.
|
||||||
const opts = {}
|
const opts = {}
|
||||||
|
|
|
@ -92,6 +92,22 @@ describe('webContents module', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('setDevToolsWebCotnents() API', () => {
|
||||||
|
it('sets arbitry webContents as devtools', (done) => {
|
||||||
|
let devtools = new BrowserWindow({show: false})
|
||||||
|
devtools.webContents.once('dom-ready', () => {
|
||||||
|
assert.ok(devtools.getURL().startsWith('chrome-devtools://devtools'))
|
||||||
|
devtools.webContents.executeJavaScript('InspectorFrontendHost.constructor.name', (name) => {
|
||||||
|
assert.ok(name, 'InspectorFrontendHostImpl')
|
||||||
|
devtools.destroy()
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
w.webContents.setDevToolsWebContents(devtools.webContents)
|
||||||
|
w.webContents.openDevTools()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('isFocused() API', () => {
|
describe('isFocused() API', () => {
|
||||||
it('returns false when the window is hidden', () => {
|
it('returns false when the window is hidden', () => {
|
||||||
BrowserWindow.getAllWindows().forEach((window) => {
|
BrowserWindow.getAllWindows().forEach((window) => {
|
||||||
|
|
|
@ -643,6 +643,30 @@ describe('<webview> tag', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('setDevToolsWebCotnents() API', () => {
|
||||||
|
it('sets webContents of webview as devtools', (done) => {
|
||||||
|
const webview2 = new WebView()
|
||||||
|
webview2.addEventListener('did-attach', () => {
|
||||||
|
webview2.addEventListener('dom-ready', () => {
|
||||||
|
const devtools = webview2.getWebContents()
|
||||||
|
assert.ok(devtools.getURL().startsWith('chrome-devtools://devtools'))
|
||||||
|
devtools.executeJavaScript('InspectorFrontendHost.constructor.name', (name) => {
|
||||||
|
assert.ok(name, 'InspectorFrontendHostImpl')
|
||||||
|
document.body.removeChild(webview2)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
webview.addEventListener('dom-ready', () => {
|
||||||
|
webview.getWebContents().setDevToolsWebContents(webview2.getWebContents())
|
||||||
|
webview.getWebContents().openDevTools()
|
||||||
|
})
|
||||||
|
webview.src = 'about:blank'
|
||||||
|
document.body.appendChild(webview)
|
||||||
|
})
|
||||||
|
document.body.appendChild(webview2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('devtools-opened event', () => {
|
describe('devtools-opened event', () => {
|
||||||
it('should fire when webview.openDevTools() is called', (done) => {
|
it('should fire when webview.openDevTools() is called', (done) => {
|
||||||
const listener = () => {
|
const listener = () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue