Bug 1291049 - Attempt to bundle the inspector with webpack
MozReview-Commit-ID: Lk5pRrDCLV4
* * *
Bug 1291049 - Some more work towards bundling the inspector
--- a/.gitignore
+++ b/.gitignore
@@ -71,16 +71,17 @@ python/psutil/**/*.pyd
python/psutil/build/
# Ignore chrome.manifest files from the devtools loader
devtools/client/chrome.manifest
devtools/shared/chrome.manifest
# Ignore node_modules directories in devtools
devtools/**/node_modules
+devtools/client/inspector/inspector.bundle.js
# Tag files generated by GNU Global
GTAGS
GRTAGS
GSYMS
GPATH
# Git clone directory for updating web-platform-tests
--- a/.hgignore
+++ b/.hgignore
@@ -75,16 +75,17 @@
.git/
# Ignore chrome.manifest files from the devtools loader
^devtools/client/chrome.manifest$
^devtools/shared/chrome.manifest$
# Ignore node_modules directories in devtools
^devtools/.*/node_modules/
+^devtools/client/inspector/inspector.bundle.js
# git checkout of libstagefright
^media/libstagefright/android$
# Tag files generated by GNU Global
GTAGS
GRTAGS
GSYMS
--- a/addon-sdk/source/lib/sdk/util/object.js
+++ b/addon-sdk/source/lib/sdk/util/object.js
@@ -29,17 +29,17 @@ const { flatten } = require('./array');
* b.name // 'b'
*/
function merge(source) {
let descriptor = {};
// `Boolean` converts the first parameter to a boolean value. Any object is
// converted to `true` where `null` and `undefined` becames `false`. Therefore
// the `filter` method will keep only objects that are defined and not null.
- Array.slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
+ [].slice.call(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
getOwnPropertyIdentifiers(properties).forEach(function(name) {
descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
});
});
return Object.defineProperties(source, descriptor);
}
exports.merge = merge;
@@ -67,17 +67,17 @@ function each(obj, fn) {
exports.each = each;
/**
* Like `merge`, except no property descriptors are manipulated, for use
* with platform objects. Identical to underscore's `extend`. Useful for
* merging XPCOM objects
*/
function safeMerge(source) {
- Array.slice(arguments, 1).forEach(function onEach (obj) {
+ [].slice.call(arguments, 1).forEach(function onEach (obj) {
for (let prop in obj) source[prop] = obj[prop];
});
return source;
}
exports.safeMerge = safeMerge;
/*
* Returns a copy of the object without omitted properties
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -765,16 +765,22 @@ const Require = iced(function Require(lo
}
// Expose the `resolve` function for this `Require` instance
require.resolve = _require.resolve = function resolve(id) {
let { uri } = getRequirements(id);
return uri;
}
+ require.context = (prefix) => {
+ return (id) => {
+ return require(prefix + id);
+ };
+ };
+
// Make `require.main === module` evaluate to true in main module scope.
require.main = loader.main === requirer ? requirer : undefined;
return iced(require);
});
Loader.Require = Require;
const main = iced(function main(loader, id) {
// If no main entry provided, and native loader is used,
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -3,18 +3,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global window */
"use strict";
-var Cu = Components.utils;
-var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
var Services = require("Services");
var promise = require("promise");
var defer = require("devtools/shared/defer");
var EventEmitter = require("devtools/shared/event-emitter");
const {executeSoon} = require("devtools/shared/DevToolsUtils");
var {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
var {Task} = require("devtools/shared/task");
const {initCssProperties} = require("devtools/shared/fronts/css-properties");
--- a/devtools/client/inspector/inspector.xhtml
+++ b/devtools/client/inspector/inspector.xhtml
@@ -20,17 +20,31 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" dir="">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script type="application/javascript;version=1.8"
src="chrome://devtools/content/shared/theme-switching.js"></script>
- <script type="application/javascript;version=1.8" src="inspector.js" defer="true"></script>
+ <script>
+ let loadInChrome = window.location.href.includes("chrome:");
+ let inspectorScript = document.createElement("script");
+ inspectorScript.setAttribute("type", "application/javascript;version=1.8");
+ inspectorScript.setAttribute("defer", "true");
+
+ if (loadInChrome) {
+ var Cu = Components.utils;
+ var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ inspectorScript.src = "inspector.js";
+ } else {
+ inspectorScript.src = "inspector.bundle.js";
+ }
+ document.head.appendChild(inspectorScript);
+ </script>
</head>
<body class="theme-body" role="application">
<div class="inspector-responsive-container theme-body inspector">
<!-- Main Panel Content -->
<div id="inspector-main-content" class="devtools-main-content">
<div id="inspector-toolbar" class="devtools-toolbar" nowindowdrag="true"
data-localization-bundle="devtools/locale/inspector.properties">
--- a/devtools/client/shared/undo.js
+++ b/devtools/client/shared/undo.js
@@ -149,27 +149,27 @@ UndoStack.prototype = {
* ViewController implementation for undo/redo.
*/
/**
* Install this object as a command controller.
*/
installController: function (controllerWindow) {
this._controllerWindow = controllerWindow;
- controllerWindow.controllers.appendController(this);
+ // controllerWindow.controllers.appendController(this);
},
/**
* Uninstall this object from the command controller.
*/
uninstallController: function () {
if (!this._controllerWindow) {
return;
}
- this._controllerWindow.controllers.removeController(this);
+ // this._controllerWindow.controllers.removeController(this);
},
supportsCommand: function (command) {
return (command == "cmd_undo" ||
command == "cmd_redo");
},
isCommandEnabled: function (command) {
--- a/devtools/client/webpack.config.js
+++ b/devtools/client/webpack.config.js
@@ -1,14 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
+const path = require("path");
+const webpack = require("webpack");
+
module.exports = [{
bail: true,
entry: [
"./sourceeditor/codemirror/addon/dialog/dialog.js",
"./sourceeditor/codemirror/addon/search/searchcursor.js",
"./sourceeditor/codemirror/addon/search/search.js",
"./sourceeditor/codemirror/addon/edit/matchbrackets.js",
"./sourceeditor/codemirror/addon/edit/closebrackets.js",
@@ -31,9 +34,108 @@ module.exports = [{
"./sourceeditor/codemirror/addon/fold/foldgutter.js",
"./sourceeditor/codemirror/lib/codemirror.js",
],
output: {
filename: "./sourceeditor/codemirror/codemirror.bundle.js",
libraryTarget: "var",
library: "CodeMirror",
},
+}, {
+ bail: true,
+
+ entry: './inspector/inspector.js',
+ output: {
+ filename: './inspector/inspector.bundle.js',
+ library: 'Inspector',
+ },
+ module: {
+ // Disable handling of unknown requires
+ unknownContextRegExp: /$^/,
+ unknownContextCritical: false,
+
+ // Disable handling of requires with a single expression
+ exprContextRegExp: /$^/,
+ exprContextCritical: false,
+
+ // Warn for every expression in require
+ wrappedContextCritical: true,
+
+ loaders: [
+ {
+ test: /event-emitter/,
+ exclude: /node_modules/,
+ loaders: [__dirname + '/webpack/rewrite-event-emitter'],
+ },
+ ]
+ },
+ resolveLoader: {
+ root: [
+ path.resolve('./node_modules'),
+ path.resolve("./webpack"),
+ ]
+ },
+ resolve: {
+ alias: {
+ "devtools-shared/locale": path.join(__dirname, "../shared/locales/en-US"),
+ "devtools/locale": path.join(__dirname, "./locales/en-US"),
+ "global/locale": path.join(__dirname, "../../toolkit/locales/en-US/chrome/global"),
+ "devtools/shared/platform": path.join(__dirname, "../shared/platform/content"),
+ devtools: path.join(__dirname, "../"),
+ Services: path.join(__dirname, "./shared/shim/Services.js"),
+ gcli: path.join(__dirname, "../shared/gcli/source/lib/gcli"),
+ acorn: path.join(__dirname, "../shared/acorn"),
+ "acorn/util/walk": path.join(__dirname, "../shared/acorn/walk"),
+ sdk: path.join(__dirname, "../../addon-sdk/source/lib/sdk"),
+ method: path.join(__dirname, "../../addon-sdk/source/lib/method"),
+ "modules/libpref/init/all": path.join(__dirname,
+ "../../modules/libpref/init/all.js"),
+ },
+ },
+
+ plugins: [
+ new webpack.DefinePlugin({
+ "isWorker": JSON.stringify(false),
+ "reportError": "console.error",
+ "AppConstants": "{ DEBUG: true, DEBUG_JS_MODULES: true }",
+ "loader": "{ lazyRequireGetter: () => {} }",
+ "dump": "console.log",
+ }),
+ ],
+
+ externals: [
+ /codemirror\//,
+ {
+ "promise": "var Promise",
+ "devtools/server/main": "{}",
+
+ // Just trying to get build to work. These should be removed eventually:
+ "chrome": "{}",
+
+ // In case you end up in chrome-land you can use this to help track down issues.
+ // SDK for instance does a bunch of this so if you somehow end up importing an SDK
+ // dependency this might help for debugging:
+ // "chrome": `{
+ // Cc: {
+ // '@mozilla.org/uuid-generator;1': { getService: () => { return {} } },
+ // '@mozilla.org/observer-service;1': { getService: () => { return {} } },
+ // },
+ // Ci: {},
+ // Cr: {},
+ // Cm: {},
+ // components: { classesByID: () => {} , ID: () => {} }
+ // }`,
+
+ "resource://gre/modules/XPCOMUtils.jsm": "{}",
+ "resource://devtools/client/styleeditor/StyleEditorUI.jsm": "{}",
+ "resource://devtools/client/styleeditor/StyleEditorUtil.jsm": "{}",
+ "devtools/client/inspector/inspector-panel": "{}",
+ "devtools/server/actors/utils/audionodes.json": "{}",
+
+ "devtools/client/shared/developer-toolbar": "{}",
+
+ // From sdk.
+ "resource://gre/modules/Preferences.jsm": "{}",
+ "@loader/options": "{}",
+ "@loader/unload": "{}",
+ },
+ ],
}];
new file mode 100644
--- /dev/null
+++ b/devtools/client/webpack/prefs-loader.js
@@ -0,0 +1,53 @@
+// Rewrite devtools.js or all.js, leaving just the relevant pref() calls.
+
+"use strict";
+
+const PREF_RX = new RegExp("^ *pref\\(\"devtools");
+
+module.exports = function (content) {
+ this.cacheable && this.cacheable();
+
+ // If we're reading devtools.js we have to do some reprocessing.
+ // If we're reading all.js we just assume we can dump all the
+ // conditionals.
+ let isDevtools = this.request.endsWith("/devtools.js");
+
+ // This maps the text of a "#if" to its truth value. This has to
+ // cover all uses of #if in devtools.js.
+ const ifMap = {
+ "#if MOZ_UPDATE_CHANNEL == beta": false,
+ "#if defined(NIGHTLY_BUILD)": false,
+ "#ifdef NIGHTLY_BUILD": false,
+ "#ifdef MOZ_DEV_EDITION": false,
+ "#ifdef RELEASE_BUILD": true,
+ };
+
+ let lines = content.split("\n");
+ let ignoring = false;
+ let newLines = [];
+ let continuation = false;
+ for (let line of lines) {
+ if (line.startsWith("sticky_pref")) {
+ line = line.slice(7);
+ }
+
+ if (isDevtools) {
+ if (line.startsWith("#if")) {
+ if (!(line in ifMap)) {
+ throw new Error("missing line in ifMap: " + line);
+ }
+ ignoring = !ifMap[line];
+ } else if (line.startsWith("#else")) {
+ ignoring = !ignoring;
+ }
+ }
+
+ if (continuation || (!ignoring && PREF_RX.test(line))) {
+ newLines.push(line);
+
+ // The call to pref(...); might span more than one line.
+ continuation = !/\);/.test(line);
+ }
+ }
+ return newLines.join("\n");
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/webpack/rewrite-event-emitter.js
@@ -0,0 +1,22 @@
+// Remove the header code from event-emitter.js. This code confuses
+// webpack.
+
+"use strict";
+
+module.exports = function (content) {
+ this.cacheable && this.cacheable();
+
+ let lines = content.split("\n");
+ let ignoring = false;
+ let newLines = [];
+ for (let line of lines) {
+ if (/function \(factory\)/.test(line)) {
+ ignoring = true;
+ } else if (/call\(this, function /.test(line)) {
+ ignoring = false;
+ } else if (!ignoring && line !== "});") {
+ newLines.push(line);
+ }
+ }
+ return newLines.join("\n");
+};
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -21,17 +21,18 @@ const ThreadSafeDevToolsUtils = require(
for (let key of Object.keys(ThreadSafeDevToolsUtils)) {
exports[key] = ThreadSafeDevToolsUtils[key];
}
/**
* Waits for the next tick in the event loop to execute a callback.
*/
exports.executeSoon = function executeSoon(aFn) {
- if (isWorker) {
+ // XXX: Move setImmmediate chrome implementation to loader
+ if (typeof setImmediate !== "undefined") {
setImmediate(aFn);
} else {
let executor;
// Only enable async stack reporting when DEBUG_JS_MODULES is set
// (customized local builds) to avoid a performance penalty.
if (AppConstants.DEBUG_JS_MODULES || flags.testing) {
let stack = getStack();
executor = () => {
--- a/devtools/shared/l10n.js
+++ b/devtools/shared/l10n.js
@@ -3,27 +3,54 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const parsePropertiesFile = require("devtools/shared/node-properties/node-properties");
const { sprintf } = require("devtools/shared/sprintfjs/sprintf");
const propertiesMap = {};
+// Some shenanigans are needed for LocalizationHelper's dynamic
+// require to work with Webpack. Here we a create different context
+// require for each possible locale directory. Then later we use
+// these functions, rather than plain |require|, to load the resource.
+const reqShared = require.context("raw!devtools-shared/locale/",
+ true, /^.*\.properties$/);
+const reqClient = require.context("raw!devtools/locale/",
+ true, /^.*\.properties$/);
+const reqGlobal = require.context("raw!global/locale/",
+ true, /^.*\.properties$/);
+
/**
* Memoized getter for properties files that ensures a given url is only required and
* parsed once.
*
* @param {String} url
* The URL of the properties file to parse.
* @return {Object} parsed properties mapped in an object.
*/
function getProperties(url) {
if (!propertiesMap[url]) {
- propertiesMap[url] = parsePropertiesFile(require(`raw!${url}`));
+ // More shenanigans. Here we take an input like
+ // "devtools-shared/locale/debugger.properties" and decide which
+ // context require function to use. Despite the string processing
+ // here, in the end a string identical to |url| ends up being
+ // passed to "require".
+ let index = url.lastIndexOf("/");
+ // Turn "mumble/locale/resource.properties" => "./resource.properties".
+ let baseName = "." + url.substr(index);
+ let reqFn;
+ if (/^global/.test(url)) {
+ reqFn = reqGlobal;
+ } else if (/^devtools-shared/.test(url)) {
+ reqFn = reqShared;
+ } else {
+ reqFn = reqClient;
+ }
+ propertiesMap[url] = parsePropertiesFile(reqFn(baseName));
}
return propertiesMap[url];
}
/**
* Localization convenience methods.
*