fix(extensions): devtools now open for background pages (#22217)

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
This commit is contained in:
Samuel Maddock 2020-09-08 07:55:40 -04:00 committed by GitHub
parent ae5776041e
commit 5a8046c994
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 24 deletions

View file

@ -132,6 +132,7 @@
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/browser/script_executor.h" #include "extensions/browser/script_executor.h"
#include "extensions/browser/view_type_utils.h"
#include "shell/browser/extensions/electron_extension_web_contents_observer.h" #include "shell/browser/extensions/electron_extension_web_contents_observer.h"
#endif #endif
@ -409,12 +410,45 @@ const void* kElectronApiWebContentsKey = &kElectronApiWebContentsKey;
} // namespace } // 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, WebContents::WebContents(v8::Isolate* isolate,
content::WebContents* web_contents) content::WebContents* web_contents)
: content::WebContentsObserver(web_contents), : content::WebContentsObserver(web_contents),
type_(Type::REMOTE), type_(Type::REMOTE),
id_(GetAllWebContents().Add(this)), id_(GetAllWebContents().Add(this)),
weak_factory_(this) { weak_factory_(this) {
#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
auto session = Session::CreateFrom(isolate, GetBrowserContext()); auto session = Session::CreateFrom(isolate, GetBrowserContext());
session_.Reset(isolate, session.ToV8()); session_.Reset(isolate, session.ToV8());
@ -424,11 +458,7 @@ WebContents::WebContents(v8::Isolate* isolate,
web_contents->SetUserData(kElectronApiWebContentsKey, web_contents->SetUserData(kElectronApiWebContentsKey,
std::make_unique<UserDataLink>(GetWeakPtr())); std::make_unique<UserDataLink>(GetWeakPtr()));
InitZoomController(web_contents, gin::Dictionary::CreateEmpty(isolate)); InitZoomController(web_contents, gin::Dictionary::CreateEmpty(isolate));
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
extensions::ElectronExtensionWebContentsObserver::CreateForWebContents(
web_contents);
script_executor_.reset(new extensions::ScriptExecutor(web_contents));
#endif
registry_.AddInterface(base::BindRepeating(&WebContents::BindElectronBrowser, registry_.AddInterface(base::BindRepeating(&WebContents::BindElectronBrowser,
base::Unretained(this))); base::Unretained(this)));
receivers_.set_disconnect_handler(base::BindRepeating( receivers_.set_disconnect_handler(base::BindRepeating(
@ -636,13 +666,39 @@ void WebContents::InitWithSessionAndOptions(
std::make_unique<UserDataLink>(GetWeakPtr())); std::make_unique<UserDataLink>(GetWeakPtr()));
} }
#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() { WebContents::~WebContents() {
MarkDestroyed(); MarkDestroyed();
// The destroy() is called. // The destroy() is called.
if (managed_web_contents()) { if (managed_web_contents()) {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
if (type_ == Type::BACKGROUND_PAGE) {
// Background pages are owned by extensions::ExtensionHost
managed_web_contents()->ReleaseWebContents();
}
#endif
managed_web_contents()->GetView()->SetDelegate(nullptr); managed_web_contents()->GetView()->SetDelegate(nullptr);
RenderViewDeleted(web_contents()->GetRenderViewHost()); if (web_contents()) {
RenderViewDeleted(web_contents()->GetRenderViewHost());
}
if (type_ == Type::BROWSER_WINDOW && owner_window()) { if (type_ == Type::BROWSER_WINDOW && owner_window()) {
// For BrowserWindow we should close the window and clean up everything // For BrowserWindow we should close the window and clean up everything
@ -1379,6 +1435,7 @@ void WebContents::DevToolsOpened() {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::Locker locker(isolate); v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
DCHECK(managed_web_contents());
auto handle = auto handle =
FromOrCreate(isolate, managed_web_contents()->GetDevToolsWebContents()); FromOrCreate(isolate, managed_web_contents()->GetDevToolsWebContents());
devtools_web_contents_.Reset(isolate, handle.ToV8()); devtools_web_contents_.Reset(isolate, handle.ToV8());
@ -1720,7 +1777,8 @@ void WebContents::OpenDevTools(gin::Arguments* args) {
return; return;
std::string state; std::string state;
if (type_ == Type::WEB_VIEW || !owner_window()) { if (type_ == Type::WEB_VIEW || type_ == Type::BACKGROUND_PAGE ||
!owner_window()) {
state = "detach"; state = "detach";
} }
bool activate = true; bool activate = true;
@ -1731,6 +1789,8 @@ void WebContents::OpenDevTools(gin::Arguments* args) {
options.Get("activate", &activate); options.Get("activate", &activate);
} }
} }
DCHECK(managed_web_contents());
managed_web_contents()->SetDockState(state); managed_web_contents()->SetDockState(state);
managed_web_contents()->ShowDevTools(activate); managed_web_contents()->ShowDevTools(activate);
} }
@ -1739,6 +1799,7 @@ void WebContents::CloseDevTools() {
if (type_ == Type::REMOTE) if (type_ == Type::REMOTE)
return; return;
DCHECK(managed_web_contents());
managed_web_contents()->CloseDevTools(); managed_web_contents()->CloseDevTools();
} }
@ -1746,6 +1807,7 @@ bool WebContents::IsDevToolsOpened() {
if (type_ == Type::REMOTE) if (type_ == Type::REMOTE)
return false; return false;
DCHECK(managed_web_contents());
return managed_web_contents()->IsDevToolsViewShowing(); return managed_web_contents()->IsDevToolsViewShowing();
} }
@ -1753,6 +1815,7 @@ bool WebContents::IsDevToolsFocused() {
if (type_ == Type::REMOTE) if (type_ == Type::REMOTE)
return false; return false;
DCHECK(managed_web_contents());
return managed_web_contents()->GetView()->IsDevToolsViewFocused(); return managed_web_contents()->GetView()->IsDevToolsViewFocused();
} }
@ -1761,6 +1824,7 @@ void WebContents::EnableDeviceEmulation(
if (type_ == Type::REMOTE) if (type_ == Type::REMOTE)
return; return;
DCHECK(web_contents());
auto* frame_host = web_contents()->GetMainFrame(); auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) { if (frame_host) {
auto* widget_host_impl = auto* widget_host_impl =
@ -1778,6 +1842,7 @@ void WebContents::DisableDeviceEmulation() {
if (type_ == Type::REMOTE) if (type_ == Type::REMOTE)
return; return;
DCHECK(web_contents());
auto* frame_host = web_contents()->GetMainFrame(); auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) { if (frame_host) {
auto* widget_host_impl = auto* widget_host_impl =
@ -1805,6 +1870,7 @@ void WebContents::InspectElement(int x, int y) {
if (!enable_devtools_) if (!enable_devtools_)
return; return;
DCHECK(managed_web_contents());
if (!managed_web_contents()->GetDevToolsWebContents()) if (!managed_web_contents()->GetDevToolsWebContents())
OpenDevTools(nullptr); OpenDevTools(nullptr);
managed_web_contents()->InspectElement(x, y); managed_web_contents()->InspectElement(x, y);

View file

@ -48,6 +48,8 @@
#endif #endif
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/common/view_type.h"
namespace extensions { namespace extensions {
class ScriptExecutor; class ScriptExecutor;
} }
@ -144,7 +146,7 @@ class WebContents : public gin::Wrappable<WebContents>,
public mojom::ElectronBrowser { public mojom::ElectronBrowser {
public: public:
enum class Type { enum class Type {
BACKGROUND_PAGE, // A DevTools extension background page. BACKGROUND_PAGE, // An extension background page.
BROWSER_WINDOW, // Used by BrowserWindow. BROWSER_WINDOW, // Used by BrowserWindow.
BROWSER_VIEW, // Used by BrowserView. BROWSER_VIEW, // Used by BrowserView.
REMOTE, // Thin wrap around an existing WebContents. REMOTE, // Thin wrap around an existing WebContents.
@ -453,6 +455,12 @@ class WebContents : public gin::Wrappable<WebContents>,
gin::Handle<class Session> session, gin::Handle<class Session> session,
const gin_helper::Dictionary& options); 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: // content::WebContentsDelegate:
bool DidAddMessageToConsole(content::WebContents* source, bool DidAddMessageToConsole(content::WebContents* source,
blink::mojom::ConsoleMessageLevel level, blink::mojom::ConsoleMessageLevel level,

View file

@ -32,8 +32,6 @@ namespace electron {
// An ExtensionsBrowserClient that supports a single content::BrowserContext // An ExtensionsBrowserClient that supports a single content::BrowserContext
// with no related incognito context. // with no related incognito context.
// Must be initialized via InitWithBrowserContext() once the BrowserContext is
// created.
class ElectronExtensionsBrowserClient class ElectronExtensionsBrowserClient
: public extensions::ExtensionsBrowserClient { : public extensions::ExtensionsBrowserClient {
public: public:
@ -122,11 +120,6 @@ class ElectronExtensionsBrowserClient
content::RenderFrameHost* render_frame_host, content::RenderFrameHost* render_frame_host,
const extensions::Extension* extension) const override; 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. // Sets the API client.
void SetAPIClientForTest(extensions::ExtensionsAPIClient* api_client); void SetAPIClientForTest(extensions::ExtensionsAPIClient* api_client);

View file

@ -317,16 +317,28 @@ describe('chrome extensions', () => {
const receivedMessage = await w.webContents.executeJavaScript('window.completionPromise'); const receivedMessage = await w.webContents.executeJavaScript('window.completionPromise');
expect(receivedMessage).to.deep.equal({ some: 'message' }); expect(receivedMessage).to.deep.equal({ some: 'message' });
}); });
});
it('has session in background page', async () => { it('has session in background page', async () => {
const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
const promise = emittedOnce(app, 'web-contents-created'); const promise = emittedOnce(app, 'web-contents-created');
await w.loadURL('about:blank'); await w.loadURL('about:blank');
const [, bgPageContents] = await promise; const [, bgPageContents] = await promise;
expect(bgPageContents.session).to.not.equal(undefined); expect(bgPageContents.session).to.not.equal(undefined);
});
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', () => { describe('devtools extensions', () => {