diff --git a/atom.gyp b/atom.gyp index df9cf202a516..b725bad156e7 100644 --- a/atom.gyp +++ b/atom.gyp @@ -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', diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc new file mode 100644 index 000000000000..13bbd777326f --- /dev/null +++ b/atom/app/atom_content_client.cc @@ -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 +#include + +namespace atom { + +AtomContentClient::AtomContentClient() { +} + +AtomContentClient::~AtomContentClient() { +} + +void AtomContentClient::AddAdditionalSchemes( + std::vector* standard_schemes, + std::vector* savable_schemes) { + standard_schemes->push_back("chrome-extension"); +} + +} // namespace atom diff --git a/atom/app/atom_content_client.h b/atom/app/atom_content_client.h new file mode 100644 index 000000000000..5c029e20d0e8 --- /dev/null +++ b/atom/app/atom_content_client.h @@ -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 +#include + +#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* standard_schemes, + std::vector* savable_schemes) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(AtomContentClient); +}; + +} // namespace atom + +#endif // ATOM_APP_ATOM_CONTENT_CLIENT_H_ diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index e8dbd0bd9830..f754a1b347f0 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -6,6 +6,7 @@ #include +#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 AtomMainDelegate::CreateContentClient() { + return scoped_ptr(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 diff --git a/atom/app/atom_main_delegate.h b/atom/app/atom_main_delegate.h index 72bc0f745af8..4afbf846eaa8 100644 --- a/atom/app/atom_main_delegate.h +++ b/atom/app/atom_main_delegate.h @@ -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 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 browser_client_; scoped_ptr renderer_client_; diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee index bf90f4776473..360f069e59aa 100644 --- a/atom/browser/api/lib/browser-window.coffee +++ b/atom/browser/api/lib/browser-window.coffee @@ -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() diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 8b06e6ddda2c..bfc2ce59b883 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -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()); diff --git a/atom/browser/lib/chrome-extension.coffee b/atom/browser/lib/chrome-extension.coffee new file mode 100644 index 000000000000..724ed5cadd5a --- /dev/null +++ b/atom/browser/lib/chrome-extension.coffee @@ -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] diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index ff6416d0f9cd..e164b8801b15 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -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 diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index bd9139a3954a..4c0a3f201ca2 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -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) diff --git a/atom/renderer/lib/chrome-api.coffee b/atom/renderer/lib/chrome-api.coffee new file mode 100644 index 000000000000..288afaf55457 --- /dev/null +++ b/atom/renderer/lib/chrome-api.coffee @@ -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 diff --git a/atom/renderer/lib/init.coffee b/atom/renderer/lib/init.coffee index 09474c96d575..94d8679ca019 100644 --- a/atom/renderer/lib/init.coffee +++ b/atom/renderer/lib/init.coffee @@ -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') diff --git a/docs/README.md b/docs/README.md index aaf27d6d95a9..12300f428dc6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index b7726b590a78..f50021e464fa 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -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. diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md new file mode 100644 index 000000000000..2421a4236e48 --- /dev/null +++ b/docs/tutorial/devtools-extension.md @@ -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 diff --git a/script/lib/config.py b/script/lib/config.py index f74f3c4a885b..1bec67c2419c 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -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', diff --git a/vendor/brightray b/vendor/brightray index 602403baae04..de769889118c 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 602403baae04c28ab04ea6f8b4f0b1aa697a3630 +Subproject commit de769889118c6e68ecb0c729be5ab6caef333bf6