new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/actions/index.js
@@ -0,0 +1,32 @@
+/* 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";
+
+// This file lists all of the actions available in responsive design. This
+// central list of constants makes it easy to see all possible action names at
+// a glance. Please add a comment with each new action type.
+
+createEnum([
+
+ // The location of the page has changed. This may be triggered by the user
+ // directly entering a new URL, navigating with links, etc.
+ "CHANGE_LOCATION",
+
+ // Add an additional viewport to display the document.
+ "ADD_VIEWPORT",
+
+], module.exports);
+
+/**
+ * Create a simple enum-like object with keys mirrored to values from an array.
+ * This makes comparison to a specfic value simpler without having to repeat and
+ * mis-type the value.
+ */
+function createEnum(array, target) {
+ for (let key of array) {
+ target[key] = key;
+ }
+ return target;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/actions/location.js
@@ -0,0 +1,22 @@
+/* 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 { CHANGE_LOCATION } = require("./index");
+
+module.exports = {
+
+ /**
+ * The location of the page has changed. This may be triggered by the user
+ * directly entering a new URL, navigating with links, etc.
+ */
+ changeLocation(location) {
+ return {
+ type: CHANGE_LOCATION,
+ location,
+ };
+ },
+
+};
copy from devtools/client/responsive.html/moz.build
copy to devtools/client/responsive.html/actions/moz.build
--- a/devtools/client/responsive.html/moz.build
+++ b/devtools/client/responsive.html/actions/moz.build
@@ -1,10 +1,11 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
- 'index.css',
- 'manager.js',
+ 'index.js',
+ 'location.js',
+ 'viewports.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/actions/viewports.js
@@ -0,0 +1,20 @@
+/* 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 { ADD_VIEWPORT } = require("./index");
+
+module.exports = {
+
+ /**
+ * Add an additional viewport to display the document.
+ */
+ addViewport() {
+ return {
+ type: ADD_VIEWPORT,
+ };
+ },
+
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/app.js
@@ -0,0 +1,36 @@
+/* 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 { createClass, createFactory } =
+ require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+const Models = require("./models");
+const Viewports = createFactory(require("./components/viewports"));
+
+let App = createClass({
+
+ displayName: "App",
+
+ propTypes: Models.app,
+
+ render() {
+ let {
+ location,
+ viewports,
+ } = this.props;
+
+ // For the moment, the app is just the viewports. This seems likely to
+ // change assuming we add a global toolbar or something similar.
+ return Viewports({
+ location,
+ viewports,
+ });
+ },
+
+});
+
+module.exports = connect(state => state)(App);
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/browser.js
@@ -0,0 +1,37 @@
+/* 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 { DOM: dom, createClass } =
+ require("devtools/client/shared/vendor/react");
+
+const Models = require("../models");
+
+module.exports = createClass({
+
+ displayName: "Browser",
+
+ propTypes: {
+ location: Models.app.location,
+ width: Models.viewport.width,
+ height: Models.viewport.height,
+ },
+
+ render() {
+ let {
+ location,
+ width,
+ height,
+ } = this.props;
+
+ return dom.iframe({
+ className: "browser",
+ src: location,
+ width,
+ height,
+ });
+ },
+
+});
copy from devtools/client/responsive.html/moz.build
copy to devtools/client/responsive.html/components/moz.build
--- a/devtools/client/responsive.html/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -1,10 +1,11 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
- 'index.css',
- 'manager.js',
+ 'browser.js',
+ 'viewport.js',
+ 'viewports.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/viewport.js
@@ -0,0 +1,39 @@
+/* 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 { DOM: dom, createClass, createFactory, PropTypes } =
+ require("devtools/client/shared/vendor/react");
+
+const Models = require("../models");
+const Browser = createFactory(require("./browser"));
+
+module.exports = createClass({
+
+ displayName: "Viewport",
+
+ propTypes: {
+ location: Models.app.location,
+ viewport: PropTypes.shape(Models.viewport).isRequired,
+ },
+
+ render() {
+ let {
+ location,
+ viewport,
+ } = this.props;
+
+ // Additional elements will soon appear here around the Browser, like drag
+ // handles, etc.
+ return dom.div({
+ className: "viewport",
+ }, Browser({
+ location,
+ width: viewport.width,
+ height: viewport.height,
+ }));
+ },
+
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/viewports.js
@@ -0,0 +1,38 @@
+/* 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 { DOM: dom, createClass, createFactory } =
+ require("devtools/client/shared/vendor/react");
+
+const Models = require("../models");
+const Viewport = createFactory(require("./viewport"));
+
+module.exports = createClass({
+
+ displayName: "Viewports",
+
+ propTypes: Models.app,
+
+ render() {
+ let {
+ location,
+ viewports,
+ } = this.props;
+
+ let children = viewports.map((viewport, index) => {
+ return Viewport({
+ key: index,
+ location,
+ viewport,
+ });
+ });
+
+ return dom.div({
+ id: "viewports",
+ }, children);
+ },
+
+});
--- a/devtools/client/responsive.html/index.js
+++ b/devtools/client/responsive.html/index.js
@@ -1,17 +1,83 @@
/* 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/. */
/* eslint-env browser */
"use strict";
-window.addViewport = contentURI => {
+const { utils: Cu } = Components;
+const { BrowserLoader } =
+ Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+const { require } =
+ BrowserLoader("resource://devtools/client/responsive.html/", this);
+const Telemetry = require("devtools/client/shared/telemetry");
+
+const { createFactory, createElement } =
+ require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+
+const App = createFactory(require("./app"));
+const Store = require("./store");
+const { changeLocation } = require("./actions/location");
+const { addViewport } = require("./actions/viewports");
+
+let bootstrap = {
+
+ telemetry: new Telemetry(),
+
+ store: null,
+
+ init() {
+ // TODO: Should we track this as a separate tool from the old version?
+ // See bug 1242057.
+ this.telemetry.toolOpened("responsive");
+ let store = this.store = Store();
+ let app = createElement(App);
+ let provider = createElement(Provider, { store }, app);
+ ReactDOM.render(provider, document.querySelector("#app"));
+ },
+
+ destroy() {
+ ReactDOM.unmountComponentAtNode(document.querySelector("#app"));
+ this.store = null;
+ this.telemetry.toolClosed("responsive");
+ this.telemetry = null;
+ },
+
+ /**
+ * While most actions will be dispatched by React components, some external
+ * APIs that coordinate with the larger browser UI may also have actions to
+ * to dispatch. They can do so here.
+ */
+ dispatch(action) {
+ this.store.dispatch(action);
+ },
+
+};
+
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad);
+ bootstrap.init();
+});
+
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload);
+ bootstrap.destroy();
+});
+
+// Allows quick testing of actions from the console
+window.dispatch = action => bootstrap.dispatch(action);
+
+/**
+ * Called by manager.js to add the initial viewport based on the original page.
+ */
+window.addInitialViewport = contentURI => {
try {
- let frame = document.createElement("iframe");
- frame.setAttribute("src", contentURI);
- document.body.appendChild(frame);
+ bootstrap.dispatch(changeLocation(contentURI));
+ bootstrap.dispatch(addViewport());
} catch (e) {
console.error(e);
}
};
--- a/devtools/client/responsive.html/index.xhtml
+++ b/devtools/client/responsive.html/index.xhtml
@@ -11,10 +11,11 @@
<link rel="stylesheet" type="text/css"
href="resource://devtools/client/responsive.html/index.css"/>
<script type="application/javascript;version=1.8"
src="chrome://devtools/content/shared/theme-switching.js"></script>
<script type="application/javascript;version=1.8"
src="./index.js"></script>
</head>
<body class="theme-body" role="application">
+ <div id="app"/>
</body>
</html>
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -157,17 +157,17 @@ ResponsiveUI.prototype = {
* bug 1238160 about <iframe mozbrowser> for more details.
*/
init: Task.async(function*() {
let tabBrowser = this.tab.linkedBrowser;
let contentURI = tabBrowser.documentURI.spec;
tabBrowser.loadURI(TOOL_URL);
yield tabLoaded(this.tab);
let toolWindow = tabBrowser.contentWindow;
- toolWindow.addViewport(contentURI);
+ toolWindow.addInitialViewport(contentURI);
}),
destroy() {
let tabBrowser = this.tab.linkedBrowser;
tabBrowser.goBack();
this.window = null;
this.tab = null;
},
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/models.js
@@ -0,0 +1,30 @@
+/* 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 { PropTypes } = require("devtools/client/shared/vendor/react");
+
+// These models use React PropTypes to describe the expected "shape" of various
+// objects that get passed down as props to components.
+
+let viewport = exports.viewport = {
+
+ // The width of the viewport
+ width: PropTypes.number.isRequired,
+
+ // The height of the viewport
+ height: PropTypes.number.isRequired,
+
+};
+
+exports.app = {
+
+ // The location of the document displayed in the viewport(s)
+ location: PropTypes.string.isRequired,
+
+ // Array of one or more viewports to display the document
+ viewports: PropTypes.arrayOf(PropTypes.shape(viewport)).isRequired,
+
+};
--- a/devtools/client/responsive.html/moz.build
+++ b/devtools/client/responsive.html/moz.build
@@ -1,10 +1,20 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
+DIRS += [
+ 'actions',
+ 'components',
+ 'reducers',
+]
+
DevToolsModules(
+ 'app.js',
'index.css',
'manager.js',
+ 'models.js',
+ 'reducers.js',
+ 'store.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/reducers.js
@@ -0,0 +1,8 @@
+/* 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";
+
+exports.location = require("./reducers/location");
+exports.viewports = require("./reducers/viewports");
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/reducers/location.js
@@ -0,0 +1,25 @@
+/* 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 { CHANGE_LOCATION } = require("../actions/index");
+
+const INITIAL_LOCATION = "about:blank";
+
+let reducers = {
+
+ [CHANGE_LOCATION](_, action) {
+ return action.location;
+ },
+
+};
+
+module.exports = function(location = INITIAL_LOCATION, action) {
+ let reducer = reducers[action.type];
+ if (!reducer) {
+ return location;
+ }
+ return reducer(location, action);
+};
copy from devtools/client/responsive.html/moz.build
copy to devtools/client/responsive.html/reducers/moz.build
--- a/devtools/client/responsive.html/moz.build
+++ b/devtools/client/responsive.html/reducers/moz.build
@@ -1,10 +1,10 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
- 'index.css',
- 'manager.js',
+ 'location.js',
+ 'viewports.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/reducers/viewports.js
@@ -0,0 +1,33 @@
+/* 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 { ADD_VIEWPORT } = require("../actions/index");
+
+const INITIAL_VIEWPORTS = [];
+const INITIAL_VIEWPORT = {
+ width: 320,
+ height: 480,
+};
+
+let reducers = {
+
+ [ADD_VIEWPORT](viewports) {
+ // For the moment, there can be at most one viewport.
+ if (viewports.length === 1) {
+ return viewports;
+ }
+ return [...viewports, INITIAL_VIEWPORT];
+ },
+
+};
+
+module.exports = function(viewports = INITIAL_VIEWPORTS, action) {
+ let reducer = reducers[action.type];
+ if (!reducer) {
+ return viewports;
+ }
+ return reducer(viewports, action);
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/store.js
@@ -0,0 +1,33 @@
+/* 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 { combineReducers } = require("devtools/client/shared/vendor/redux");
+const createStore = require("devtools/client/shared/redux/create-store");
+const reducers = require("./reducers");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+module.exports = function() {
+ let shouldLog = false;
+ let history;
+
+ // If testing, store the action history in an array
+ // we'll later attach to the store
+ if (DevToolsUtils.testing) {
+ history = [];
+ shouldLog = true;
+ }
+
+ let store = createStore({
+ log: shouldLog,
+ history
+ })(combineReducers(reducers), {});
+
+ if (history) {
+ store.history = history;
+ }
+
+ return store;
+};