Merge pull request #610 from atom/devtools-extension

Add support for chrome devtools extension
This commit is contained in:
Cheng Zhao 2014-08-28 23:12:21 +08:00
commit 45228493eb
17 changed files with 285 additions and 23 deletions

View file

@ -28,6 +28,7 @@
'atom/browser/api/lib/protocol.coffee',
'atom/browser/api/lib/tray.coffee',
'atom/browser/api/lib/web-contents.coffee',
'atom/browser/lib/chrome-extension.coffee',
'atom/browser/lib/init.coffee',
'atom/browser/lib/objects-registry.coffee',
'atom/browser/lib/rpc-server.coffee',
@ -38,6 +39,7 @@
'atom/common/api/lib/screen.coffee',
'atom/common/api/lib/shell.coffee',
'atom/common/lib/init.coffee',
'atom/renderer/lib/chrome-api.coffee',
'atom/renderer/lib/init.coffee',
'atom/renderer/lib/inspector.coffee',
'atom/renderer/lib/override.coffee',
@ -46,6 +48,8 @@
'atom/renderer/api/lib/web-view.coffee',
],
'lib_sources': [
'atom/app/atom_content_client.cc',
'atom/app/atom_content_client.h',
'atom/app/atom_main_delegate.cc',
'atom/app/atom_main_delegate.h',
'atom/app/atom_main_delegate_mac.mm',

View file

@ -0,0 +1,24 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/app/atom_content_client.h"
#include <string>
#include <vector>
namespace atom {
AtomContentClient::AtomContentClient() {
}
AtomContentClient::~AtomContentClient() {
}
void AtomContentClient::AddAdditionalSchemes(
std::vector<std::string>* standard_schemes,
std::vector<std::string>* savable_schemes) {
standard_schemes->push_back("chrome-extension");
}
} // namespace atom

View file

@ -0,0 +1,32 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_APP_ATOM_CONTENT_CLIENT_H_
#define ATOM_APP_ATOM_CONTENT_CLIENT_H_
#include <string>
#include <vector>
#include "brightray/common/content_client.h"
namespace atom {
class AtomContentClient : public brightray::ContentClient {
public:
AtomContentClient();
virtual ~AtomContentClient();
protected:
// content::ContentClient:
virtual void AddAdditionalSchemes(
std::vector<std::string>* standard_schemes,
std::vector<std::string>* savable_schemes) OVERRIDE;
private:
DISALLOW_COPY_AND_ASSIGN(AtomContentClient);
};
} // namespace atom
#endif // ATOM_APP_ATOM_CONTENT_CLIENT_H_

View file

@ -6,6 +6,7 @@
#include <string>
#include "atom/app/atom_content_client.h"
#include "atom/browser/atom_browser_client.h"
#include "atom/renderer/atom_renderer_client.h"
#include "base/command_line.h"
@ -22,18 +23,6 @@ AtomMainDelegate::AtomMainDelegate() {
AtomMainDelegate::~AtomMainDelegate() {
}
void AtomMainDelegate::AddDataPackFromPath(
ui::ResourceBundle* bundle, const base::FilePath& pak_dir) {
#if defined(OS_WIN)
bundle->AddDataPackFromPath(
pak_dir.Append(FILE_PATH_LITERAL("ui_resources_200_percent.pak")),
ui::SCALE_FACTOR_200P);
bundle->AddDataPackFromPath(
pak_dir.Append(FILE_PATH_LITERAL("webkit_resources_200_percent.pak")),
ui::SCALE_FACTOR_200P);
#endif
}
bool AtomMainDelegate::BasicStartupComplete(int* exit_code) {
// Disable logging out to debug.log on Windows
#if defined(OS_WIN)
@ -99,4 +88,20 @@ content::ContentRendererClient*
return renderer_client_.get();
}
scoped_ptr<brightray::ContentClient> AtomMainDelegate::CreateContentClient() {
return scoped_ptr<brightray::ContentClient>(new AtomContentClient).Pass();
}
void AtomMainDelegate::AddDataPackFromPath(
ui::ResourceBundle* bundle, const base::FilePath& pak_dir) {
#if defined(OS_WIN)
bundle->AddDataPackFromPath(
pak_dir.Append(FILE_PATH_LITERAL("ui_resources_200_percent.pak")),
ui::SCALE_FACTOR_200P);
bundle->AddDataPackFromPath(
pak_dir.Append(FILE_PATH_LITERAL("webkit_resources_200_percent.pak")),
ui::SCALE_FACTOR_200P);
#endif
}
} // namespace atom

View file

@ -16,24 +16,23 @@ class AtomMainDelegate : public brightray::MainDelegate {
~AtomMainDelegate();
protected:
// brightray::MainDelegate:
virtual void AddDataPackFromPath(
ui::ResourceBundle* bundle, const base::FilePath& pak_dir) OVERRIDE;
// content::ContentMainDelegate:
virtual bool BasicStartupComplete(int* exit_code) OVERRIDE;
virtual void PreSandboxStartup() OVERRIDE;
virtual content::ContentBrowserClient* CreateContentBrowserClient() OVERRIDE;
virtual content::ContentRendererClient*
CreateContentRendererClient() OVERRIDE;
// brightray::MainDelegate:
virtual scoped_ptr<brightray::ContentClient> CreateContentClient() OVERRIDE;
virtual void AddDataPackFromPath(
ui::ResourceBundle* bundle, const base::FilePath& pak_dir) OVERRIDE;
#if defined(OS_MACOSX)
virtual void OverrideChildProcessPath() OVERRIDE;
virtual void OverrideFrameworkBundlePath() OVERRIDE;
#endif
private:
virtual content::ContentBrowserClient* CreateContentBrowserClient() OVERRIDE;
virtual content::ContentRendererClient*
CreateContentRendererClient() OVERRIDE;
brightray::ContentClient content_client_;
scoped_ptr<content::ContentBrowserClient> browser_client_;
scoped_ptr<content::ContentRendererClient> renderer_client_;

View file

@ -35,6 +35,10 @@ BrowserWindow::openDevTools = ->
@devToolsWebContents = @getDevToolsWebContents()
@devToolsWebContents.once 'destroyed', => @devToolsWebContents = null
# Emit devtools events.
@devToolsWebContents.once 'did-finish-load', => @emit 'devtools-opened'
@devToolsWebContents.once 'destroyed', => @emit 'devtools-closed'
BrowserWindow::toggleDevTools = ->
if @isDevToolsOpened() then @closeDevTools() else @openDevTools()

View file

@ -85,6 +85,12 @@ void AtomBrowserClient::OverrideWebkitPrefs(
prefs->allow_displaying_insecure_content = true;
prefs->allow_running_insecure_content = true;
// Turn off web security for devtools.
if (url.SchemeIs("chrome-devtools")) {
prefs->web_security_enabled = false;
return;
}
NativeWindow* window = NativeWindow::FromRenderView(
render_view_host->GetProcess()->GetID(),
render_view_host->GetRoutingID());

View file

@ -0,0 +1,88 @@
app = require 'app'
fs = require 'fs'
path = require 'path'
url = require 'url'
# Mapping between hostname and file path.
hostPathMap = {}
hostPathMapNextKey = 0
getHostForPath = (path) ->
key = "extension-#{++hostPathMapNextKey}"
hostPathMap[key] = path
key
getPathForHost = (host) ->
hostPathMap[host]
# Cache extensionInfo.
extensionInfoMap = {}
getExtensionInfoFromPath = (srcDirectory) ->
manifest = JSON.parse fs.readFileSync(path.join(srcDirectory, 'manifest.json'))
unless extensionInfoMap[manifest.name]?
# 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
srcDirectory: srcDirectory
extensionInfoMap[manifest.name]
# Load persistented extensions.
loadedExtensionsPath = path.join app.getDataPath(), 'DevTools Extensions'
try
loadedExtensions = JSON.parse fs.readFileSync(loadedExtensionsPath)
loadedExtensions = [] unless Array.isArray loadedExtensions
# Preheat the extensionInfo cache.
getExtensionInfoFromPath srcDirectory for srcDirectory in loadedExtensions
catch e
# Persistent loaded extensions.
app.on 'will-quit', ->
try
loadedExtensions = Object.keys(extensionInfoMap).map (key) -> extensionInfoMap[key].srcDirectory
try
fs.mkdirSync path.dirname(loadedExtensionsPath)
catch e
fs.writeFileSync loadedExtensionsPath, JSON.stringify(loadedExtensions)
catch e
# We can not use protocol or BrowserWindow until app is ready.
app.once 'ready', ->
protocol = require 'protocol'
BrowserWindow = require 'browser-window'
# The chrome-extension: can map a extension URL request to real file path.
protocol.registerProtocol 'chrome-extension', (request) ->
parsed = url.parse request.url
return unless parsed.hostname and parsed.path?
return unless /extension-\d+/.test parsed.hostname
directory = getPathForHost parsed.hostname
return unless directory?
return new protocol.RequestFileJob(path.join(directory, parsed.path))
BrowserWindow::_loadDevToolsExtensions = (extensionInfoArray) ->
@devToolsWebContents?.executeJavaScript "WebInspector.addExtensions(#{JSON.stringify(extensionInfoArray)});"
BrowserWindow.addDevToolsExtension = (srcDirectory) ->
extensionInfo = getExtensionInfoFromPath srcDirectory
window._loadDevToolsExtensions [extensionInfo] for window in BrowserWindow.getAllWindows()
extensionInfo.name
BrowserWindow.removeDevToolsExtension = (name) ->
delete extensionInfoMap[name]
# Load persistented extensions when devtools is opened.
init = BrowserWindow::_init
BrowserWindow::_init = ->
init.call this
@on 'devtools-opened', ->
@_loadDevToolsExtensions Object.keys(extensionInfoMap).map (key) -> extensionInfoMap[key]

View file

@ -77,5 +77,8 @@ setImmediate ->
else if packageJson.name?
app.setName packageJson.name
# Load the chrome extension support.
require './chrome-extension.js'
# Finally load app's main.js and transfer control to C++.
module._load path.join(packagePath, packageJson.main), module, true

View file

@ -199,6 +199,10 @@ bool AtomRendererClient::IsNodeBindingEnabled(blink::WebFrame* frame) {
// Node integration is enabled in main frame unless explictly disabled.
else if (frame == main_frame_)
return true;
// Enable node integration in chrome extensions.
else if (frame != NULL &&
GURL(frame->document().url()).SchemeIs("chrome-extension"))
return true;
else if (node_integration_ == MANUAL_ENABLE_IFRAME &&
frame != NULL &&
frame->uniqueName().utf8().find(kSecurityEnableNodeIntegration)

View file

@ -0,0 +1,10 @@
url = require 'url'
chrome = window.chrome = window.chrome || {}
chrome.extension =
getURL: (path) ->
url.format
protocol: location.protocol
slashes: true
hostname: location.hostname
pathname: path

View file

@ -46,6 +46,9 @@ else
if location.protocol is 'chrome-devtools:'
# Override some inspector APIs.
require path.join(__dirname, 'inspector')
else if location.protocol is 'chrome-extension:'
# Add implementations of chrome API.
require path.join(__dirname, 'chrome-api')
else
# Override default web functions.
require path.join(__dirname, 'override')

View file

@ -4,6 +4,7 @@
* [Application distribution](tutorial/application-distribution.md)
* [Use native node modules](tutorial/use-native-node-modules.md)
* [Debugging browser process](tutorial/debugging-browser-process.md)
* [DevTools extension](tutorial/devtools-extension.md)
## API references

View file

@ -138,6 +138,14 @@ Emitted when window loses focus.
Emitted when window gains focus.
### Event: 'devtools-opened'
Emitted when devtools is opened.
### Event: 'devtools-closed'
Emitted when devtools is closed.
### Class Method: BrowserWindow.getAllWindows()
Returns an array of all opened browser windows.
@ -158,6 +166,21 @@ Find a window according to the `webContents` it owns
Find a window according to its ID.
### Class Method: BrowserWindow.addDevToolsExtension(path)
* `path` String
Adds devtools extension located at `path`, and returns extension's name.
The extension will be remembered so you only need to call this API once, this
API is not for programming use.
### Class Method: BrowserWindow.removeDevToolsExtension(name)
* `name` String
Remove the devtools extension whose name is `name`.
### BrowserWindow.webContents
The `WebContents` object this window owns, all web page related events and
@ -341,7 +364,7 @@ Returns the title of the native window.
window.
### BrowserWindow.flashFrame(flag)
* `flag` Boolean
Starts or stops flashing the window to attract user's attention.

View file

@ -0,0 +1,56 @@
# DevTools extension
To make debugging more easy, atom-shell has added basic support for
[Chrome DevTools Extension][devtools-extension].
For most devtools extensions, you can simply download their source codes 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.
For example to use the React DevTools Extension, first you need to download its
source code:
```bash
$ cd /some-directory
$ git clone --recursive https://github.com/facebook/react-devtools.git
```
Then you can load it in atom-shell by opening the devtools in arbitray window,
and run this code in the console of devtools:
```javascript
require('remote').require('browser-window').addDevToolsExtension('/some-directory/react-devtools')
```
To unload the extension, you can call `BrowserWindow.removeDevToolsExtension`
API with its name and it will disappear the next time you open the devtools:
```javascript
require('remote').require('browser-window').removeDevToolsExtension('React Developer Tools')
```
## Format of devtools extension
Ideally all devtools extension written for Chrome browser can be loaded by
atom-shell, but they have to be in a plain directory, for those packaged `crx`
extensions, there is no way in atom-shell to load them unless you find a way to
extract them into a directory.
## Background pages
Currently atom-shell doesn't support the background pages of chrome extensions,
so for some devtools extensions that rely on this feature, they may not work
well in atom-shell
## `chrome.*` APIs
Some chrome extensions use `chrome.*` APIs for some features, there is some
effort to implement those APIs in atom-shell to make them work, but we have
only implemented few for now.
So if the devtools extension is using APIs other than `chrome.devtools.*`, it is
very likely to fail, but you can report those extensions in the issues tracker
so we can add support for those APIs.
[devtools-extension]: https://developer.chrome.com/extensions/devtools

View file

@ -5,7 +5,7 @@ import sys
NODE_VERSION = 'v0.11.13'
BASE_URL = 'https://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent'
LIBCHROMIUMCONTENT_COMMIT = '432720d4613e3aac939f127fe55b9d44fea349e5'
LIBCHROMIUMCONTENT_COMMIT = 'afb4570ceee2ad10f3caf5a81335a2ee11ec68a5'
ARCH = {
'cygwin': '32bit',

2
vendor/brightray vendored

@ -1 +1 @@
Subproject commit 602403baae04c28ab04ea6f8b4f0b1aa697a3630
Subproject commit de769889118c6e68ecb0c729be5ab6caef333bf6