Skip to content

Commit

Permalink
fix(extensions): devtools now open for background pages
Browse files Browse the repository at this point in the history
refactor(extensions): remove unused InitWithBrowserContext method

fix(extensions): release background page WebContents to avoid crash

The background page WebContents instance is managed by the ExtensionHost.

fix(extensions): open background page devtools detached by default

test(extensions): add background page devtools test

chore: test fix for null web_contents()

fix: close background page devtools in test after opening
  • Loading branch information
samuelmaddock committed Mar 6, 2020
1 parent 3e2cec8 commit 6d2a859
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 16 deletions.
81 changes: 73 additions & 8 deletions shell/browser/api/electron_api_web_contents.cc
Expand Up @@ -116,6 +116,7 @@

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/browser/script_executor.h"
#include "extensions/browser/view_type_utils.h"
#include "shell/browser/extensions/electron_extension_web_contents_observer.h"
#endif

Expand Down Expand Up @@ -376,21 +377,49 @@ base::string16 GetDefaultPrinterAsync() {

} // namespace

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)

WebContents::Type GetTypeFromViewType(extensions::ViewType view_type) {
switch (view_type) {
case extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE:
return WebContents::Type::BACKGROUND_PAGE;

case extensions::VIEW_TYPE_APP_WINDOW:
case extensions::VIEW_TYPE_COMPONENT:
case extensions::VIEW_TYPE_EXTENSION_DIALOG:
case extensions::VIEW_TYPE_EXTENSION_POPUP:
case extensions::VIEW_TYPE_BACKGROUND_CONTENTS:
case extensions::VIEW_TYPE_EXTENSION_GUEST:
case extensions::VIEW_TYPE_TAB_CONTENTS:
case extensions::VIEW_TYPE_INVALID:
return WebContents::Type::REMOTE;
}
}

#endif

WebContents::WebContents(v8::Isolate* isolate,
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
type_(Type::REMOTE),
weak_factory_(this) {
web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent(),
false);
Init(isolate);
AttachAsUserData(web_contents);
InitZoomController(web_contents, gin::Dictionary::CreateEmpty(isolate));
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
// WebContents created by extension host will have valid ViewType set.
extensions::ViewType view_type = extensions::GetViewType(web_contents);
if (view_type != extensions::VIEW_TYPE_INVALID) {
InitWithExtensionView(isolate, web_contents, view_type);
}

extensions::ElectronExtensionWebContentsObserver::CreateForWebContents(
web_contents);
script_executor_.reset(new extensions::ScriptExecutor(web_contents));
#endif

web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent(),
false);
Init(isolate);
AttachAsUserData(web_contents);
InitZoomController(web_contents, gin::Dictionary::CreateEmpty(isolate));
registry_.AddInterface(base::BindRepeating(&WebContents::BindElectronBrowser,
base::Unretained(this)));
bindings_.set_connection_error_handler(base::BindRepeating(
Expand Down Expand Up @@ -579,12 +608,38 @@ void WebContents::InitWithSessionAndOptions(
AttachAsUserData(web_contents());
}

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
void WebContents::InitWithExtensionView(v8::Isolate* isolate,
content::WebContents* web_contents,
extensions::ViewType view_type) {
// Must reassign type prior to calling `Init`.
type_ = GetTypeFromViewType(view_type);
if (GetType() == Type::REMOTE)
return;

// Allow toggling DevTools for background pages
Observe(web_contents);
InitWithWebContents(web_contents, GetBrowserContext(), IsGuest());
managed_web_contents()->GetView()->SetDelegate(this);
SecurityStateTabHelper::CreateForWebContents(web_contents);
}
#endif

WebContents::~WebContents() {
// The destroy() is called.
if (managed_web_contents()) {
managed_web_contents()->GetView()->SetDelegate(nullptr);
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
if (type_ == Type::BACKGROUND_PAGE) {
// Background pages are owned by extensions::ExtensionHost
managed_web_contents()->ReleaseWebContents();
}
#endif

RenderViewDeleted(web_contents()->GetRenderViewHost());
managed_web_contents()->GetView()->SetDelegate(nullptr);

if (web_contents()) {
RenderViewDeleted(web_contents()->GetRenderViewHost());
}

if (type_ == Type::BROWSER_WINDOW && owner_window()) {
// For BrowserWindow we should close the window and clean up everything
Expand Down Expand Up @@ -1261,6 +1316,7 @@ void WebContents::DevToolsFocused() {
void WebContents::DevToolsOpened() {
v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
DCHECK(managed_web_contents());
auto handle =
FromOrCreate(isolate(), managed_web_contents()->GetDevToolsWebContents());
devtools_web_contents_.Reset(isolate(), handle.ToV8());
Expand Down Expand Up @@ -1573,7 +1629,8 @@ void WebContents::OpenDevTools(gin_helper::Arguments* args) {
return;

std::string state;
if (type_ == Type::WEB_VIEW || !owner_window()) {
if (type_ == Type::WEB_VIEW || type_ == Type::BACKGROUND_PAGE ||
!owner_window()) {
state = "detach";
}
bool activate = true;
Expand All @@ -1584,6 +1641,8 @@ void WebContents::OpenDevTools(gin_helper::Arguments* args) {
options.Get("activate", &activate);
}
}

DCHECK(managed_web_contents());
managed_web_contents()->SetDockState(state);
managed_web_contents()->ShowDevTools(activate);
}
Expand All @@ -1592,20 +1651,23 @@ void WebContents::CloseDevTools() {
if (type_ == Type::REMOTE)
return;

DCHECK(managed_web_contents());
managed_web_contents()->CloseDevTools();
}

bool WebContents::IsDevToolsOpened() {
if (type_ == Type::REMOTE)
return false;

DCHECK(managed_web_contents());
return managed_web_contents()->IsDevToolsViewShowing();
}

bool WebContents::IsDevToolsFocused() {
if (type_ == Type::REMOTE)
return false;

DCHECK(managed_web_contents());
return managed_web_contents()->GetView()->IsDevToolsViewFocused();
}

Expand All @@ -1614,6 +1676,7 @@ void WebContents::EnableDeviceEmulation(
if (type_ == Type::REMOTE)
return;

DCHECK(web_contents());
auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) {
auto* widget_host =
Expand All @@ -1629,6 +1692,7 @@ void WebContents::DisableDeviceEmulation() {
if (type_ == Type::REMOTE)
return;

DCHECK(web_contents());
auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) {
auto* widget_host =
Expand All @@ -1654,6 +1718,7 @@ void WebContents::InspectElement(int x, int y) {
if (!enable_devtools_)
return;

DCHECK(managed_web_contents());
if (!managed_web_contents()->GetDevToolsWebContents())
OpenDevTools(nullptr);
managed_web_contents()->InspectElement(x, y);
Expand Down
10 changes: 9 additions & 1 deletion shell/browser/api/electron_api_web_contents.h
Expand Up @@ -42,6 +42,8 @@
#endif

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/common/view_type.h"

namespace extensions {
class ScriptExecutor;
}
Expand Down Expand Up @@ -98,7 +100,7 @@ class WebContents : public gin_helper::TrackableObject<WebContents>,
public mojom::ElectronBrowser {
public:
enum class Type {
BACKGROUND_PAGE, // A DevTools extension background page.
BACKGROUND_PAGE, // An extension background page.
BROWSER_WINDOW, // Used by BrowserWindow.
BROWSER_VIEW, // Used by BrowserView.
REMOTE, // Thin wrap around an existing WebContents.
Expand Down Expand Up @@ -373,6 +375,12 @@ class WebContents : public gin_helper::TrackableObject<WebContents>,
gin::Handle<class Session> session,
const gin_helper::Dictionary& options);

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
void InitWithExtensionView(v8::Isolate* isolate,
content::WebContents* web_contents,
extensions::ViewType view_type);
#endif

// content::WebContentsDelegate:
bool DidAddMessageToConsole(content::WebContents* source,
blink::mojom::ConsoleMessageLevel level,
Expand Down
7 changes: 0 additions & 7 deletions shell/browser/extensions/electron_extensions_browser_client.h
Expand Up @@ -31,8 +31,6 @@ namespace electron {

// An ExtensionsBrowserClient that supports a single content::BrowserContext
// with no related incognito context.
// Must be initialized via InitWithBrowserContext() once the BrowserContext is
// created.
class ElectronExtensionsBrowserClient
: public extensions::ExtensionsBrowserClient {
public:
Expand Down Expand Up @@ -121,11 +119,6 @@ class ElectronExtensionsBrowserClient
content::RenderFrameHost* render_frame_host,
const extensions::Extension* extension) const override;

// |context| is the single BrowserContext used for IsValidContext().
// |pref_service| is used for GetPrefServiceForContext().
void InitWithBrowserContext(content::BrowserContext* context,
PrefService* pref_service);

// Sets the API client.
void SetAPIClientForTest(extensions::ExtensionsAPIClient* api_client);

Expand Down
12 changes: 12 additions & 0 deletions spec-main/extensions-spec.ts
Expand Up @@ -262,6 +262,18 @@ ifdescribe(process.electronBinding('features').isExtensionsEnabled())('chrome ex
const receivedMessage = await w.webContents.executeJavaScript(`window.completionPromise`)
expect(receivedMessage).to.deep.equal({ some: 'message' })
})

it('can open devtools of background page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`)
await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'))
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } })
const promise = emittedOnce(app, 'web-contents-created')
await w.loadURL(`about:blank`)
const [, bgPageContents] = await promise
expect(bgPageContents.getType()).to.equal('backgroundPage')
bgPageContents.openDevTools()
bgPageContents.closeDevTools()
})
})

describe('devtools extensions', () => {
Expand Down
@@ -0,0 +1 @@
/* eslint-disable no-undef */
@@ -0,0 +1,9 @@
{
"name": "persistent-background-page",
"version": "1.0",
"background": {
"scripts": ["background.js"],
"persistent": true
},
"manifest_version": 2
}

0 comments on commit 6d2a859

Please sign in to comment.