зеркало из 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 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/PluralForm.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
@ -51,6 +54,94 @@ Cu.import("resource:///modules/devtools/Templater.jsm");
|
|||
|
||||
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.
|
||||
* 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.templatePath = this.styleDocument.getElementById("templatePath");
|
||||
this.propertyContainer = this.styleDocument.getElementById("propertyContainer");
|
||||
this.templateProperty = this.styleDocument.getElementById("templateProperty");
|
||||
this.panel = aStyleInspector.panel;
|
||||
|
||||
// No results text.
|
||||
|
@ -135,6 +225,10 @@ CssHtmlTree.processTemplate = function CssHtmlTree_processTemplate(aTemplate,
|
|||
XPCOMUtils.defineLazyGetter(CssHtmlTree, "_strings", function() Services.strings
|
||||
.createBundle("chrome://browser/locale/devtools/styleinspector.properties"));
|
||||
|
||||
XPCOMUtils.defineLazyGetter(CssHtmlTree, "HELP_LINK_TITLE", function() {
|
||||
return CssHtmlTree.HELP_LINK_TITLE = CssHtmlTree.l10n("helpLinkTitle");
|
||||
});
|
||||
|
||||
CssHtmlTree.prototype = {
|
||||
// Cache the list of properties that have matched and unmatched properties.
|
||||
_matchedProperties: null,
|
||||
|
@ -181,46 +275,38 @@ CssHtmlTree.prototype = {
|
|||
if (this.htmlComplete) {
|
||||
this.refreshPanel();
|
||||
} else {
|
||||
if (this._panelRefreshTimeout) {
|
||||
this.win.clearTimeout(this._panelRefreshTimeout);
|
||||
if (this._refreshProcess) {
|
||||
this._refreshProcess.cancel();
|
||||
}
|
||||
|
||||
CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
|
||||
|
||||
// We use a setTimeout loop to display the properties in batches of 15 at a
|
||||
// time. This results in a perceptibly more responsive UI.
|
||||
let i = 0;
|
||||
let batchSize = 15;
|
||||
let max = CssHtmlTree.propertyNames.length - 1;
|
||||
function displayProperties() {
|
||||
if (this.viewedElement == aElement && this.styleInspector.isOpen()) {
|
||||
// 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);
|
||||
this.numVisibleProperties = 0;
|
||||
let fragment = this.doc.createDocumentFragment();
|
||||
this._refreshProcess = new UpdateProcess(this.win, CssHtmlTree.propertyNames, {
|
||||
onItem: function(aPropertyName) {
|
||||
// Per-item callback.
|
||||
if (this.viewedElement != aElement || !this.styleInspector.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
if (i < max) {
|
||||
// There are still some properties to display. We loop here to display
|
||||
// the next batch of 15.
|
||||
this._panelRefreshTimeout =
|
||||
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);
|
||||
let propView = new PropertyView(this, aPropertyName);
|
||||
fragment.appendChild(propView.build());
|
||||
if (propView.visible) {
|
||||
this.numVisibleProperties++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._panelRefreshTimeout =
|
||||
this.win.setTimeout(displayProperties.bind(this), 15);
|
||||
propView.refreshAllSelectors();
|
||||
this.propertyViews.push(propView);
|
||||
}.bind(this),
|
||||
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()
|
||||
{
|
||||
if (this._panelRefreshTimeout) {
|
||||
this.win.clearTimeout(this._panelRefreshTimeout);
|
||||
if (this._refreshProcess) {
|
||||
this._refreshProcess.cancel();
|
||||
}
|
||||
|
||||
this.noResults.hidden = true;
|
||||
|
@ -241,27 +327,18 @@ CssHtmlTree.prototype = {
|
|||
// Reset zebra striping.
|
||||
this._darkStripe = true;
|
||||
|
||||
// We use a setTimeout loop to display the properties in batches of 15 at a
|
||||
// time. This results in a perceptibly more responsive UI.
|
||||
let i = 0;
|
||||
let batchSize = 15;
|
||||
let max = this.propertyViews.length - 1;
|
||||
function refreshView() {
|
||||
// Refresh the next 15 property views
|
||||
for (let step = i + batchSize; i < step && i <= max; i++) {
|
||||
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;
|
||||
let display = this.propertyContainer.style.display;
|
||||
this._refreshProcess = new UpdateProcess(this.win, this.propertyViews, {
|
||||
onItem: function(aPropView) {
|
||||
aPropView.refresh();
|
||||
}.bind(this),
|
||||
onDone: function() {
|
||||
this._refreshProcess = null;
|
||||
this.noResults.hidden = this.numVisibleProperties > 0
|
||||
Services.obs.notifyObservers(null, "StyleInspector-populated", null);
|
||||
}
|
||||
}
|
||||
this._panelRefreshTimeout = this.win.setTimeout(refreshView.bind(this), 15);
|
||||
}.bind(this)
|
||||
});
|
||||
this._refreshProcess.schedule();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -423,7 +500,6 @@ CssHtmlTree.prototype = {
|
|||
delete this.path;
|
||||
delete this.templatePath;
|
||||
delete this.propertyContainer;
|
||||
delete this.templateProperty;
|
||||
delete this.panel;
|
||||
|
||||
// The document in which we display the results (csshtmltree.xul).
|
||||
|
@ -570,6 +646,50 @@ PropertyView.prototype = {
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -118,30 +118,6 @@ To visually debug the templates without running firefox, alter the display:none
|
|||
</ol>
|
||||
</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
|
||||
|
|
|
@ -12,12 +12,6 @@
|
|||
- tree. -->
|
||||
<!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
|
||||
- properties to display e.g. due to search criteria this message is
|
||||
- displayed. -->
|
||||
|
|
|
@ -41,3 +41,8 @@ style.highlighter.button.label1=Properties
|
|||
style.highlighter.accesskey1=P
|
||||
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче