зеркало из https://github.com/mozilla/gecko-dev.git
Bug 700194 - Speed up style inspector creation and refresh. r=msucan
This commit is contained in:
Родитель
04e5c9080f
Коммит
65894629d3
|
@ -43,6 +43,9 @@
|
||||||
const Cu = Components.utils;
|
const Cu = Components.utils;
|
||||||
const FILTER_CHANGED_TIMEOUT = 300;
|
const FILTER_CHANGED_TIMEOUT = 300;
|
||||||
|
|
||||||
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
@ -51,6 +54,94 @@ Cu.import("resource:///modules/devtools/Templater.jsm");
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["CssHtmlTree", "PropertyView"];
|
var EXPORTED_SYMBOLS = ["CssHtmlTree", "PropertyView"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for long-running processes that should yield occasionally to
|
||||||
|
* the mainloop.
|
||||||
|
*
|
||||||
|
* @param {Window} aWin
|
||||||
|
* Timeouts will be set on this window when appropriate.
|
||||||
|
* @param {Generator} aGenerator
|
||||||
|
* Will iterate this generator.
|
||||||
|
* @param {object} aOptions
|
||||||
|
* Options for the update process:
|
||||||
|
* onItem {function} Will be called with the value of each iteration.
|
||||||
|
* onBatch {function} Will be called after each batch of iterations,
|
||||||
|
* before yielding to the main loop.
|
||||||
|
* onDone {function} Will be called when iteration is complete.
|
||||||
|
* onCancel {function} Will be called if the process is canceled.
|
||||||
|
* threshold {int} How long to process before yielding, in ms.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function UpdateProcess(aWin, aGenerator, aOptions)
|
||||||
|
{
|
||||||
|
this.win = aWin;
|
||||||
|
this.iter = Iterator(aGenerator);
|
||||||
|
this.onItem = aOptions.onItem || function() {};
|
||||||
|
this.onBatch = aOptions.onBatch || function () {};
|
||||||
|
this.onDone = aOptions.onDone || function() {};
|
||||||
|
this.onCancel = aOptions.onCancel || function() {};
|
||||||
|
this.threshold = aOptions.threshold || 45;
|
||||||
|
|
||||||
|
this.canceled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateProcess.prototype = {
|
||||||
|
/**
|
||||||
|
* Schedule a new batch on the main loop.
|
||||||
|
*/
|
||||||
|
schedule: function UP_schedule()
|
||||||
|
{
|
||||||
|
if (this.cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._timeout = this.win.setTimeout(this._timeoutHandler.bind(this), 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the running process. onItem will not be called again,
|
||||||
|
* and onCancel will be called.
|
||||||
|
*/
|
||||||
|
cancel: function UP_cancel()
|
||||||
|
{
|
||||||
|
if (this._timeout) {
|
||||||
|
this.win.clearTimeout(this._timeout);
|
||||||
|
this._timeout = 0;
|
||||||
|
}
|
||||||
|
this.canceled = true;
|
||||||
|
this.onCancel();
|
||||||
|
},
|
||||||
|
|
||||||
|
_timeoutHandler: function UP_timeoutHandler() {
|
||||||
|
this._timeout = null;
|
||||||
|
try {
|
||||||
|
this._runBatch();
|
||||||
|
this.schedule();
|
||||||
|
} catch(e) {
|
||||||
|
if (e instanceof StopIteration) {
|
||||||
|
this.onBatch();
|
||||||
|
this.onDone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_runBatch: function Y_runBatch()
|
||||||
|
{
|
||||||
|
let time = Date.now();
|
||||||
|
while(!this.cancelled) {
|
||||||
|
// Continue until iter.next() throws...
|
||||||
|
let next = this.iter.next();
|
||||||
|
this.onItem(next[1]);
|
||||||
|
if ((Date.now() - time) > this.threshold) {
|
||||||
|
this.onBatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CssHtmlTree is a panel that manages the display of a table sorted by style.
|
* CssHtmlTree is a panel that manages the display of a table sorted by style.
|
||||||
* There should be one instance of CssHtmlTree per style display (of which there
|
* There should be one instance of CssHtmlTree per style display (of which there
|
||||||
|
@ -78,7 +169,6 @@ function CssHtmlTree(aStyleInspector)
|
||||||
this.templateRoot = this.styleDocument.getElementById("templateRoot");
|
this.templateRoot = this.styleDocument.getElementById("templateRoot");
|
||||||
this.templatePath = this.styleDocument.getElementById("templatePath");
|
this.templatePath = this.styleDocument.getElementById("templatePath");
|
||||||
this.propertyContainer = this.styleDocument.getElementById("propertyContainer");
|
this.propertyContainer = this.styleDocument.getElementById("propertyContainer");
|
||||||
this.templateProperty = this.styleDocument.getElementById("templateProperty");
|
|
||||||
this.panel = aStyleInspector.panel;
|
this.panel = aStyleInspector.panel;
|
||||||
|
|
||||||
// No results text.
|
// No results text.
|
||||||
|
@ -135,6 +225,10 @@ CssHtmlTree.processTemplate = function CssHtmlTree_processTemplate(aTemplate,
|
||||||
XPCOMUtils.defineLazyGetter(CssHtmlTree, "_strings", function() Services.strings
|
XPCOMUtils.defineLazyGetter(CssHtmlTree, "_strings", function() Services.strings
|
||||||
.createBundle("chrome://browser/locale/devtools/styleinspector.properties"));
|
.createBundle("chrome://browser/locale/devtools/styleinspector.properties"));
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(CssHtmlTree, "HELP_LINK_TITLE", function() {
|
||||||
|
return CssHtmlTree.HELP_LINK_TITLE = CssHtmlTree.l10n("helpLinkTitle");
|
||||||
|
});
|
||||||
|
|
||||||
CssHtmlTree.prototype = {
|
CssHtmlTree.prototype = {
|
||||||
// Cache the list of properties that have matched and unmatched properties.
|
// Cache the list of properties that have matched and unmatched properties.
|
||||||
_matchedProperties: null,
|
_matchedProperties: null,
|
||||||
|
@ -181,46 +275,38 @@ CssHtmlTree.prototype = {
|
||||||
if (this.htmlComplete) {
|
if (this.htmlComplete) {
|
||||||
this.refreshPanel();
|
this.refreshPanel();
|
||||||
} else {
|
} else {
|
||||||
if (this._panelRefreshTimeout) {
|
if (this._refreshProcess) {
|
||||||
this.win.clearTimeout(this._panelRefreshTimeout);
|
this._refreshProcess.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
|
CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
|
||||||
|
|
||||||
// We use a setTimeout loop to display the properties in batches of 15 at a
|
this.numVisibleProperties = 0;
|
||||||
// time. This results in a perceptibly more responsive UI.
|
let fragment = this.doc.createDocumentFragment();
|
||||||
let i = 0;
|
this._refreshProcess = new UpdateProcess(this.win, CssHtmlTree.propertyNames, {
|
||||||
let batchSize = 15;
|
onItem: function(aPropertyName) {
|
||||||
let max = CssHtmlTree.propertyNames.length - 1;
|
// Per-item callback.
|
||||||
function displayProperties() {
|
if (this.viewedElement != aElement || !this.styleInspector.isOpen()) {
|
||||||
if (this.viewedElement == aElement && this.styleInspector.isOpen()) {
|
return false;
|
||||||
// Display the next 15 properties
|
|
||||||
for (let step = i + batchSize; i < step && i <= max; i++) {
|
|
||||||
let name = CssHtmlTree.propertyNames[i];
|
|
||||||
let propView = new PropertyView(this, name);
|
|
||||||
CssHtmlTree.processTemplate(this.templateProperty,
|
|
||||||
this.propertyContainer, propView, true);
|
|
||||||
if (propView.visible) {
|
|
||||||
this.numVisibleProperties++;
|
|
||||||
}
|
|
||||||
propView.refreshAllSelectors();
|
|
||||||
this.propertyViews.push(propView);
|
|
||||||
}
|
}
|
||||||
if (i < max) {
|
let propView = new PropertyView(this, aPropertyName);
|
||||||
// There are still some properties to display. We loop here to display
|
fragment.appendChild(propView.build());
|
||||||
// the next batch of 15.
|
if (propView.visible) {
|
||||||
this._panelRefreshTimeout =
|
this.numVisibleProperties++;
|
||||||
this.win.setTimeout(displayProperties.bind(this), 15);
|
|
||||||
} else {
|
|
||||||
this.htmlComplete = true;
|
|
||||||
this._panelRefreshTimeout = null;
|
|
||||||
this.noResults.hidden = this.numVisibleProperties > 0;
|
|
||||||
Services.obs.notifyObservers(null, "StyleInspector-populated", null);
|
|
||||||
}
|
}
|
||||||
}
|
propView.refreshAllSelectors();
|
||||||
}
|
this.propertyViews.push(propView);
|
||||||
this._panelRefreshTimeout =
|
}.bind(this),
|
||||||
this.win.setTimeout(displayProperties.bind(this), 15);
|
onDone: function() {
|
||||||
|
// Completed callback.
|
||||||
|
this.htmlComplete = true;
|
||||||
|
this.propertyContainer.appendChild(fragment);
|
||||||
|
this.noResults.hidden = this.numVisibleProperties > 0;
|
||||||
|
this._refreshProcess = null;
|
||||||
|
Services.obs.notifyObservers(null, "StyleInspector-populated", null);
|
||||||
|
}.bind(this)});
|
||||||
|
|
||||||
|
this._refreshProcess.schedule();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -229,8 +315,8 @@ CssHtmlTree.prototype = {
|
||||||
*/
|
*/
|
||||||
refreshPanel: function CssHtmlTree_refreshPanel()
|
refreshPanel: function CssHtmlTree_refreshPanel()
|
||||||
{
|
{
|
||||||
if (this._panelRefreshTimeout) {
|
if (this._refreshProcess) {
|
||||||
this.win.clearTimeout(this._panelRefreshTimeout);
|
this._refreshProcess.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.noResults.hidden = true;
|
this.noResults.hidden = true;
|
||||||
|
@ -241,27 +327,18 @@ CssHtmlTree.prototype = {
|
||||||
// Reset zebra striping.
|
// Reset zebra striping.
|
||||||
this._darkStripe = true;
|
this._darkStripe = true;
|
||||||
|
|
||||||
// We use a setTimeout loop to display the properties in batches of 15 at a
|
let display = this.propertyContainer.style.display;
|
||||||
// time. This results in a perceptibly more responsive UI.
|
this._refreshProcess = new UpdateProcess(this.win, this.propertyViews, {
|
||||||
let i = 0;
|
onItem: function(aPropView) {
|
||||||
let batchSize = 15;
|
aPropView.refresh();
|
||||||
let max = this.propertyViews.length - 1;
|
}.bind(this),
|
||||||
function refreshView() {
|
onDone: function() {
|
||||||
// Refresh the next 15 property views
|
this._refreshProcess = null;
|
||||||
for (let step = i + batchSize; i < step && i <= max; i++) {
|
this.noResults.hidden = this.numVisibleProperties > 0
|
||||||
this.propertyViews[i].refresh();
|
|
||||||
}
|
|
||||||
if (i < max) {
|
|
||||||
// There are still some property views to refresh. We loop here to
|
|
||||||
// display the next batch of 15.
|
|
||||||
this._panelRefreshTimeout = this.win.setTimeout(refreshView.bind(this), 15);
|
|
||||||
} else {
|
|
||||||
this._panelRefreshTimeout = null;
|
|
||||||
this.noResults.hidden = this.numVisibleProperties > 0;
|
|
||||||
Services.obs.notifyObservers(null, "StyleInspector-populated", null);
|
Services.obs.notifyObservers(null, "StyleInspector-populated", null);
|
||||||
}
|
}.bind(this)
|
||||||
}
|
});
|
||||||
this._panelRefreshTimeout = this.win.setTimeout(refreshView.bind(this), 15);
|
this._refreshProcess.schedule();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -423,7 +500,6 @@ CssHtmlTree.prototype = {
|
||||||
delete this.path;
|
delete this.path;
|
||||||
delete this.templatePath;
|
delete this.templatePath;
|
||||||
delete this.propertyContainer;
|
delete this.propertyContainer;
|
||||||
delete this.templateProperty;
|
|
||||||
delete this.panel;
|
delete this.panel;
|
||||||
|
|
||||||
// The document in which we display the results (csshtmltree.xul).
|
// The document in which we display the results (csshtmltree.xul).
|
||||||
|
@ -570,6 +646,50 @@ PropertyView.prototype = {
|
||||||
return "property-view-hidden";
|
return "property-view-hidden";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
build: function PropertyView_build()
|
||||||
|
{
|
||||||
|
let doc = this.tree.doc;
|
||||||
|
this.element = doc.createElementNS(HTML_NS, "div");
|
||||||
|
this.element.setAttribute("class", this.className);
|
||||||
|
|
||||||
|
this.propertyHeader = doc.createElementNS(XUL_NS, "hbox");
|
||||||
|
this.element.appendChild(this.propertyHeader);
|
||||||
|
this.propertyHeader.setAttribute("class", "property-header");
|
||||||
|
this.propertyHeader.addEventListener("click", this.propertyHeaderClick.bind(this), false);
|
||||||
|
|
||||||
|
this.matchedExpander = doc.createElementNS(HTML_NS, "div");
|
||||||
|
this.propertyHeader.appendChild(this.matchedExpander);
|
||||||
|
this.matchedExpander.setAttribute("class", "match expander");
|
||||||
|
|
||||||
|
let name = doc.createElementNS(HTML_NS, "div");
|
||||||
|
this.propertyHeader.appendChild(name);
|
||||||
|
name.setAttribute("class", "property-name");
|
||||||
|
name.textContent = this.name;
|
||||||
|
|
||||||
|
let helpcontainer = doc.createElementNS(HTML_NS, "div");
|
||||||
|
this.propertyHeader.appendChild(helpcontainer);
|
||||||
|
helpcontainer.setAttribute("class", "helplink-container");
|
||||||
|
|
||||||
|
let helplink = doc.createElementNS(HTML_NS, "a");
|
||||||
|
helpcontainer.appendChild(helplink);
|
||||||
|
helplink.setAttribute("class", "helplink");
|
||||||
|
helplink.setAttribute("title", CssHtmlTree.HELP_LINK_TITLE);
|
||||||
|
helplink.textContent = CssHtmlTree.HELP_LINK_TITLE;
|
||||||
|
helplink.addEventListener("click", this.mdnLinkClick.bind(this), false);
|
||||||
|
|
||||||
|
this.valueNode = doc.createElementNS(HTML_NS, "div");
|
||||||
|
this.propertyHeader.appendChild(this.valueNode);
|
||||||
|
this.valueNode.setAttribute("class", "property-value");
|
||||||
|
this.valueNode.setAttribute("dir", "ltr");
|
||||||
|
this.valueNode.textContent = this.value;
|
||||||
|
|
||||||
|
this.matchedSelectorsContainer = doc.createElementNS(HTML_NS, "div");
|
||||||
|
this.element.appendChild(this.matchedSelectorsContainer);
|
||||||
|
this.matchedSelectorsContainer.setAttribute("class", "rulelink");
|
||||||
|
|
||||||
|
return this.element;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the panel's CSS property value.
|
* Refresh the panel's CSS property value.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -118,30 +118,6 @@ To visually debug the templates without running firefox, alter the display:none
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
|
||||||
TemplateProperty lists the properties themselves. Each needs data like this:
|
|
||||||
{
|
|
||||||
property: ... // PropertyView from CssHtmlTree.jsm
|
|
||||||
}
|
|
||||||
-->
|
|
||||||
<div id="templateProperty">
|
|
||||||
<div class="${className}" save="${element}">
|
|
||||||
<xul:hbox class="property-header" save="${propertyHeader}"
|
|
||||||
onclick="${propertyHeaderClick}">
|
|
||||||
<div save="${matchedExpander}" class="match expander"/>
|
|
||||||
<div class="property-name">${name}</div>
|
|
||||||
<div class="helplink-container">
|
|
||||||
<a href="${link}" class="helplink" title="&helpLinkTitle;" onclick="${mdnLinkClick}">
|
|
||||||
&helpLinkTitle;
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div save="${valueNode}" class="property-value" dir="ltr">${value}</div>
|
|
||||||
</xul:hbox>
|
|
||||||
|
|
||||||
<div save="${matchedSelectorsContainer}" class="rulelink">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
A templateMatchedSelectors sits inside each templateProperties showing the
|
A templateMatchedSelectors sits inside each templateProperties showing the
|
||||||
|
|
|
@ -12,12 +12,6 @@
|
||||||
- tree. -->
|
- tree. -->
|
||||||
<!ENTITY selectedElementLabel "Selected element:">
|
<!ENTITY selectedElementLabel "Selected element:">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (helpLinkTitle): For each style property
|
|
||||||
- the user can hover it and get a help link button which allows one to
|
|
||||||
- quickly jump to the documentation from the Mozilla Developer Network site.
|
|
||||||
- This is the link title shown in the hover tooltip. -->
|
|
||||||
<!ENTITY helpLinkTitle "Read the documentation for this property">
|
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (noPropertiesFound): In the case where there are no CSS
|
<!-- LOCALIZATION NOTE (noPropertiesFound): In the case where there are no CSS
|
||||||
- properties to display e.g. due to search criteria this message is
|
- properties to display e.g. due to search criteria this message is
|
||||||
- displayed. -->
|
- displayed. -->
|
||||||
|
|
|
@ -41,3 +41,8 @@ style.highlighter.button.label1=Properties
|
||||||
style.highlighter.accesskey1=P
|
style.highlighter.accesskey1=P
|
||||||
style.highlighter.button.tooltip=Inspect element styles
|
style.highlighter.button.tooltip=Inspect element styles
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (helpLinkTitle): For each style property
|
||||||
|
# the user can hover it and get a help link button which allows one to
|
||||||
|
# quickly jump to the documentation from the Mozilla Developer Network site.
|
||||||
|
# This is the link title shown in the hover tooltip.
|
||||||
|
helpLinkTitle=Read the documentation for this property
|
||||||
|
|
Загрузка…
Ссылка в новой задаче