Bug 1336198 - Part 7: Refactor box model logic into box-model.js. r=jdescottes

This commit is contained in:
Gabriel Luong 2017-02-19 22:52:01 -05:00
Родитель 6998b44518
Коммит c1527a2908
6 изменённых файлов: 300 добавлений и 204 удалений

Просмотреть файл

@ -0,0 +1,285 @@
/* 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 { Task } = require("devtools/shared/task");
const { getCssProperties } = require("devtools/shared/fronts/css-properties");
const { ReflowFront } = require("devtools/shared/fronts/reflow");
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
const { updateLayout } = require("./actions/box-model");
const EditingSession = require("./utils/editing-session");
const NUMERIC = /^-?[\d\.]+$/;
/**
* A singleton instance of the box model controllers.
*
* @param {Inspector} inspector
* An instance of the Inspector currently loaded in the toolbox.
* @param {Window} window
* The document window of the toolbox.
*/
function BoxModel(inspector, window) {
this.document = window.document;
this.inspector = inspector;
this.store = inspector.store;
this.updateBoxModel = this.updateBoxModel.bind(this);
this.onHideBoxModelHighlighter = this.onHideBoxModelHighlighter.bind(this);
this.onNewSelection = this.onNewSelection.bind(this);
this.onShowBoxModelEditor = this.onShowBoxModelEditor.bind(this);
this.onShowBoxModelHighlighter = this.onShowBoxModelHighlighter.bind(this);
this.onSidebarSelect = this.onSidebarSelect.bind(this);
this.inspector.selection.on("new-node-front", this.onNewSelection);
this.inspector.sidebar.on("select", this.onSidebarSelect);
}
BoxModel.prototype = {
/**
* Destruction function called when the inspector is destroyed. Removes event listeners
* and cleans up references.
*/
destroy() {
this.inspector.selection.off("new-node-front", this.onNewSelection);
this.inspector.sidebar.off("select", this.onSidebarSelect);
if (this.reflowFront) {
this.untrackReflows();
this.reflowFront.destroy();
this.reflowFront = null;
}
this.document = null;
this.inspector = null;
this.walker = null;
},
/**
* Returns an object containing the box model's handler functions used in the box
* model's React component props.
*/
getComponentProps() {
return {
onHideBoxModelHighlighter: this.onHideBoxModelHighlighter,
onShowBoxModelEditor: this.onShowBoxModelEditor,
onShowBoxModelHighlighter: this.onShowBoxModelHighlighter,
};
},
/**
* Returns true if the layout panel is visible, and false otherwise.
*/
isPanelVisible() {
return this.inspector.toolbox.currentToolId === "inspector" &&
this.inspector.sidebar &&
this.inspector.sidebar.getCurrentTabID() === "layoutview";
},
/**
* Returns true if the layout panel is visible and the current node is valid to
* be displayed in the view.
*/
isPanelVisibleAndNodeValid() {
return this.isPanelVisible() &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode();
},
/**
* Starts listening to reflows in the current tab.
*/
trackReflows() {
if (!this.reflowFront) {
let { target } = this.inspector;
if (target.form.reflowActor) {
this.reflowFront = ReflowFront(target.client,
target.form);
} else {
return;
}
}
this.reflowFront.on("reflows", this.updateBoxModel);
this.reflowFront.start();
},
/**
* Stops listening to reflows in the current tab.
*/
untrackReflows() {
if (!this.reflowFront) {
return;
}
this.reflowFront.off("reflows", this.updateBoxModel);
this.reflowFront.stop();
},
/**
* Updates the box model panel by dispatching the new layout data.
*/
updateBoxModel() {
let lastRequest = Task.spawn((function* () {
if (!(this.isPanelVisible() &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode())) {
return null;
}
let node = this.inspector.selection.nodeFront;
let layout = yield this.inspector.pageStyle.getLayout(node, {
autoMargins: true,
});
let styleEntries = yield this.inspector.pageStyle.getApplied(node, {});
this.elementRules = styleEntries.map(e => e.rule);
// Update the redux store with the latest layout properties and update the box
// model view.
this.store.dispatch(updateLayout(layout));
// If a subsequent request has been made, wait for that one instead.
if (this._lastRequest != lastRequest) {
return this._lastRequest;
}
this._lastRequest = null;
this.inspector.emit("boxmodel-view-updated");
return null;
}).bind(this)).catch(console.error);
this._lastRequest = lastRequest;
},
/**
* Selection 'new-node-front' event handler.
*/
onNewSelection: function () {
if (!this.isPanelVisibleAndNodeValid()) {
return;
}
this.updateBoxModel();
},
/**
* Hides the box-model highlighter on the currently selected element.
*/
onHideBoxModelHighlighter() {
let toolbox = this.inspector.toolbox;
toolbox.highlighterUtils.unhighlight();
},
/**
* Shows the inplace editor when a box model editable value is clicked on the
* box model panel.
*
* @param {DOMNode} element
* The element that was clicked.
* @param {Event} event
* The event object.
* @param {String} property
* The name of the property.
*/
onShowBoxModelEditor(element, event, property) {
let session = new EditingSession({
inspector: this.inspector,
doc: this.document,
elementRules: this.elementRules,
});
let initialValue = session.getProperty(property);
let editor = new InplaceEditor({
element: element,
initial: initialValue,
contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
property: {
name: property
},
start: self => {
self.elt.parentNode.classList.add("boxmodel-editing");
},
change: value => {
if (NUMERIC.test(value)) {
value += "px";
}
let properties = [
{ name: property, value: value }
];
if (property.substring(0, 7) == "border-") {
let bprop = property.substring(0, property.length - 5) + "style";
let style = session.getProperty(bprop);
if (!style || style == "none" || style == "hidden") {
properties.push({ name: bprop, value: "solid" });
}
}
session.setProperties(properties).catch(e => console.error(e));
},
done: (value, commit) => {
editor.elt.parentNode.classList.remove("boxmodel-editing");
if (!commit) {
session.revert().then(() => {
session.destroy();
}, e => console.error(e));
return;
}
let node = this.inspector.selection.nodeFront;
this.inspector.pageStyle.getLayout(node, {
autoMargins: true,
}).then(layout => {
this.store.dispatch(updateLayout(layout));
}, e => console.error(e));
},
contextMenu: this.inspector.onTextBoxContextMenu,
cssProperties: getCssProperties(this.inspector.toolbox)
}, event);
},
/**
* Shows the box-model highlighter on the currently selected element.
*
* @param {Object} options
* Options passed to the highlighter actor.
*/
onShowBoxModelHighlighter(options = {}) {
let toolbox = this.inspector.toolbox;
let nodeFront = this.inspector.selection.nodeFront;
toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
},
/**
* Handler for the inspector sidebar select event. Starts listening for
* "grid-layout-changed" if the layout panel is visible. Otherwise, stop
* listening for grid layout changes. Finally, refresh the layout view if
* it is visible.
*/
onSidebarSelect() {
if (!this.isPanelVisible()) {
this.untrackReflows();
return;
}
if (this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode()) {
this.trackReflows();
}
this.updateBoxModel();
},
};
module.exports = BoxModel;

Просмотреть файл

@ -12,5 +12,6 @@ DIRS += [
] ]
DevToolsModules( DevToolsModules(
'box-model.js',
'types.js', 'types.js',
) )

Просмотреть файл

@ -23,6 +23,7 @@ const Menu = require("devtools/client/framework/menu");
const MenuItem = require("devtools/client/framework/menu-item"); const MenuItem = require("devtools/client/framework/menu-item");
const {HTMLBreadcrumbs} = require("devtools/client/inspector/breadcrumbs"); const {HTMLBreadcrumbs} = require("devtools/client/inspector/breadcrumbs");
const BoxModel = require("devtools/client/inspector/boxmodel/box-model");
const {ComputedViewTool} = require("devtools/client/inspector/computed/computed"); const {ComputedViewTool} = require("devtools/client/inspector/computed/computed");
const {FontInspector} = require("devtools/client/inspector/fonts/fonts"); const {FontInspector} = require("devtools/client/inspector/fonts/fonts");
const {InspectorSearch} = require("devtools/client/inspector/inspector-search"); const {InspectorSearch} = require("devtools/client/inspector/inspector-search");
@ -573,6 +574,7 @@ Inspector.prototype = {
this.ruleview = new RuleViewTool(this, this.panelWin); this.ruleview = new RuleViewTool(this, this.panelWin);
this.computedview = new ComputedViewTool(this, this.panelWin); this.computedview = new ComputedViewTool(this, this.panelWin);
this.boxmodel = new BoxModel(this, this.panelWin);
if (Services.prefs.getBoolPref("devtools.layoutview.enabled")) { if (Services.prefs.getBoolPref("devtools.layoutview.enabled")) {
const LayoutView = this.browserRequire("devtools/client/inspector/layout/layout"); const LayoutView = this.browserRequire("devtools/client/inspector/layout/layout");

Просмотреть файл

@ -11,9 +11,10 @@ const { connect } = require("devtools/client/shared/vendor/react-redux");
const { LocalizationHelper } = require("devtools/shared/l10n"); const { LocalizationHelper } = require("devtools/shared/l10n");
const Accordion = createFactory(require("./Accordion")); const Accordion = createFactory(require("./Accordion"));
const BoxModel = createFactory(require("./BoxModel"));
const Grid = createFactory(require("./Grid")); const Grid = createFactory(require("./Grid"));
const BoxModel = createFactory(require("devtools/client/inspector/boxmodel/components/BoxModel"));
const Types = require("../types"); const Types = require("../types");
const { getStr } = require("../utils/l10n"); const { getStr } = require("../utils/l10n");

Просмотреть файл

@ -6,16 +6,10 @@
const Services = require("Services"); const Services = require("Services");
const { Task } = require("devtools/shared/task"); const { Task } = require("devtools/shared/task");
const { getCssProperties } = require("devtools/shared/fronts/css-properties");
const { ReflowFront } = require("devtools/shared/fronts/reflow");
const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
const { createFactory, createElement } = require("devtools/client/shared/vendor/react"); const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
const { Provider } = require("devtools/client/shared/vendor/react-redux"); const { Provider } = require("devtools/client/shared/vendor/react-redux");
const {
updateLayout,
} = require("./actions/box-model");
const { const {
updateGridHighlighted, updateGridHighlighted,
updateGrids, updateGrids,
@ -27,13 +21,10 @@ const {
const App = createFactory(require("./components/App")); const App = createFactory(require("./components/App"));
const EditingSession = require("./utils/editing-session");
const { LocalizationHelper } = require("devtools/shared/l10n"); const { LocalizationHelper } = require("devtools/shared/l10n");
const INSPECTOR_L10N = const INSPECTOR_L10N =
new LocalizationHelper("devtools/client/locales/inspector.properties"); new LocalizationHelper("devtools/client/locales/inspector.properties");
const NUMERIC = /^-?[\d\.]+$/;
const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers"; const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines"; const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
@ -44,18 +35,14 @@ function LayoutView(inspector, window) {
this.store = inspector.store; this.store = inspector.store;
this.walker = this.inspector.walker; this.walker = this.inspector.walker;
this.updateBoxModel = this.updateBoxModel.bind(this);
this.onGridLayoutChange = this.onGridLayoutChange.bind(this); this.onGridLayoutChange = this.onGridLayoutChange.bind(this);
this.onHighlighterChange = this.onHighlighterChange.bind(this); this.onHighlighterChange = this.onHighlighterChange.bind(this);
this.onNewSelection = this.onNewSelection.bind(this);
this.onSidebarSelect = this.onSidebarSelect.bind(this); this.onSidebarSelect = this.onSidebarSelect.bind(this);
this.init(); this.init();
this.highlighters.on("grid-highlighter-hidden", this.onHighlighterChange); this.highlighters.on("grid-highlighter-hidden", this.onHighlighterChange);
this.highlighters.on("grid-highlighter-shown", this.onHighlighterChange); this.highlighters.on("grid-highlighter-shown", this.onHighlighterChange);
this.inspector.selection.on("new-node-front", this.onNewSelection);
this.inspector.sidebar.on("select", this.onSidebarSelect); this.inspector.sidebar.on("select", this.onSidebarSelect);
} }
@ -70,6 +57,12 @@ LayoutView.prototype = {
return; return;
} }
let {
onHideBoxModelHighlighter,
onShowBoxModelEditor,
onShowBoxModelHighlighter,
} = this.inspector.boxmodel.getComponentProps();
this.layoutInspector = yield this.inspector.walker.getLayoutInspector(); this.layoutInspector = yield this.inspector.walker.getLayoutInspector();
this.loadHighlighterSettings(); this.loadHighlighterSettings();
@ -81,95 +74,9 @@ LayoutView.prototype = {
*/ */
showBoxModelProperties: true, showBoxModelProperties: true,
/** onHideBoxModelHighlighter,
* Hides the box-model highlighter on the currently selected element. onShowBoxModelEditor,
*/ onShowBoxModelHighlighter,
onHideBoxModelHighlighter: () => {
let toolbox = this.inspector.toolbox;
toolbox.highlighterUtils.unhighlight();
},
/**
* Shows the inplace editor when a box model editable value is clicked on the
* box model panel.
*
* @param {DOMNode} element
* The element that was clicked.
* @param {Event} event
* The event object.
* @param {String} property
* The name of the property.
*/
onShowBoxModelEditor: (element, event, property) => {
let session = new EditingSession({
inspector: this.inspector,
doc: this.document,
elementRules: this.elementRules,
});
let initialValue = session.getProperty(property);
let editor = new InplaceEditor({
element: element,
initial: initialValue,
contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
property: {
name: property
},
start: self => {
self.elt.parentNode.classList.add("boxmodel-editing");
},
change: value => {
if (NUMERIC.test(value)) {
value += "px";
}
let properties = [
{ name: property, value: value }
];
if (property.substring(0, 7) == "border-") {
let bprop = property.substring(0, property.length - 5) + "style";
let style = session.getProperty(bprop);
if (!style || style == "none" || style == "hidden") {
properties.push({ name: bprop, value: "solid" });
}
}
session.setProperties(properties).catch(e => console.error(e));
},
done: (value, commit) => {
editor.elt.parentNode.classList.remove("boxmodel-editing");
if (!commit) {
session.revert().then(() => {
session.destroy();
}, e => console.error(e));
return;
}
let node = this.inspector.selection.nodeFront;
this.inspector.pageStyle.getLayout(node, {
autoMargins: true,
}).then(layout => {
this.store.dispatch(updateLayout(layout));
}, e => console.error(e));
},
contextMenu: this.inspector.onTextBoxContextMenu,
cssProperties: getCssProperties(this.inspector.toolbox)
}, event);
},
/**
* Shows the box-model highlighter on the currently selected element.
*
* @param {Object} options
* Options passed to the highlighter actor.
*/
onShowBoxModelHighlighter: (options = {}) => {
let toolbox = this.inspector.toolbox;
let nodeFront = this.inspector.selection.nodeFront;
toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
},
/** /**
* Handler for a change in the input checkboxes in the GridList component. * Handler for a change in the input checkboxes in the GridList component.
@ -253,16 +160,9 @@ LayoutView.prototype = {
destroy() { destroy() {
this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange); this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange);
this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange); this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange);
this.inspector.selection.off("new-node-front", this.onNewSelection);
this.inspector.sidebar.off("select", this.onSidebarSelect); this.inspector.sidebar.off("select", this.onSidebarSelect);
this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange); this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange);
if (this.reflowFront) {
this.untrackReflows();
this.reflowFront.destroy();
this.reflowFront = null;
}
this.document = null; this.document = null;
this.inspector = null; this.inspector = null;
this.layoutInspector = null; this.layoutInspector = null;
@ -279,16 +179,6 @@ LayoutView.prototype = {
this.inspector.sidebar.getCurrentTabID() === "layoutview"; this.inspector.sidebar.getCurrentTabID() === "layoutview";
}, },
/**
* Returns true if the layout panel is visible and the current node is valid to
* be displayed in the view.
*/
isPanelVisibleAndNodeValid() {
return this.isPanelVisible() &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode();
},
/** /**
* Load the grid highligher display settings into the store from the stored preferences. * Load the grid highligher display settings into the store from the stored preferences.
*/ */
@ -302,72 +192,6 @@ LayoutView.prototype = {
dispatch(updateShowInfiniteLines(showInfinteLines)); dispatch(updateShowInfiniteLines(showInfinteLines));
}, },
/**
* Starts listening to reflows in the current tab.
*/
trackReflows() {
if (!this.reflowFront) {
let { target } = this.inspector;
if (target.form.reflowActor) {
this.reflowFront = ReflowFront(target.client,
target.form);
} else {
return;
}
}
this.reflowFront.on("reflows", this.updateBoxModel);
this.reflowFront.start();
},
/**
* Stops listening to reflows in the current tab.
*/
untrackReflows() {
if (!this.reflowFront) {
return;
}
this.reflowFront.off("reflows", this.updateBoxModel);
this.reflowFront.stop();
},
/**
* Updates the box model panel by dispatching the new layout data.
*/
updateBoxModel() {
let lastRequest = Task.spawn((function* () {
if (!(this.isPanelVisible() &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode())) {
return null;
}
let node = this.inspector.selection.nodeFront;
let layout = yield this.inspector.pageStyle.getLayout(node, {
autoMargins: true,
});
let styleEntries = yield this.inspector.pageStyle.getApplied(node, {});
this.elementRules = styleEntries.map(e => e.rule);
// Update the redux store with the latest layout properties and update the box
// model view.
this.store.dispatch(updateLayout(layout));
// If a subsequent request has been made, wait for that one instead.
if (this._lastRequest != lastRequest) {
return this._lastRequest;
}
this._lastRequest = null;
this.inspector.emit("boxmodel-view-updated");
return null;
}).bind(this)).catch(console.error);
this._lastRequest = lastRequest;
},
/** /**
* Updates the grid panel by dispatching the new grid data. This is called when the * Updates the grid panel by dispatching the new grid data. This is called when the
* layout view becomes visible or the view needs to be updated with new grid data. * layout view becomes visible or the view needs to be updated with new grid data.
@ -429,17 +253,6 @@ LayoutView.prototype = {
this.store.dispatch(updateGridHighlighted(nodeFront, highlighted)); this.store.dispatch(updateGridHighlighted(nodeFront, highlighted));
}, },
/**
* Selection 'new-node-front' event handler.
*/
onNewSelection: function () {
if (!this.isPanelVisibleAndNodeValid()) {
return;
}
this.updateBoxModel();
},
/** /**
* Handler for the inspector sidebar select event. Starts listening for * Handler for the inspector sidebar select event. Starts listening for
* "grid-layout-changed" if the layout panel is visible. Otherwise, stop * "grid-layout-changed" if the layout panel is visible. Otherwise, stop
@ -449,17 +262,10 @@ LayoutView.prototype = {
onSidebarSelect() { onSidebarSelect() {
if (!this.isPanelVisible()) { if (!this.isPanelVisible()) {
this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange); this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange);
this.untrackReflows();
return; return;
} }
if (this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode()) {
this.trackReflows();
}
this.layoutInspector.on("grid-layout-changed", this.onGridLayoutChange); this.layoutInspector.on("grid-layout-changed", this.onGridLayoutChange);
this.updateBoxModel();
this.updateGridPanel(); this.updateGridPanel();
}, },

Просмотреть файл

@ -12,6 +12,7 @@ const Services = devtools.require("Services");
const { AppConstants } = devtools.require("resource://gre/modules/AppConstants.jsm"); const { AppConstants } = devtools.require("resource://gre/modules/AppConstants.jsm");
const BROWSER_BASED_DIRS = [ const BROWSER_BASED_DIRS = [
"resource://devtools/client/inspector/boxmodel",
"resource://devtools/client/inspector/layout", "resource://devtools/client/inspector/layout",
"resource://devtools/client/jsonview", "resource://devtools/client/jsonview",
"resource://devtools/client/shared/vendor", "resource://devtools/client/shared/vendor",