Bug 699978 - Tools should notify the highlighter when they've modified a node. r=robcee

This commit is contained in:
Dave Camp 2011-11-06 19:02:08 -08:00
parent bd3c81c7c9
commit 995c21565d
8 changed files with 279 additions and 12 deletions

View File

@ -577,6 +577,7 @@ TreePanel.prototype = {
this.editingContext.attrObj.innerHTML = editorInput.value;
this.IUI.isDirty = true;
this.IUI.nodeChanged(this.registrationObject);
// event notification
Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED,

View File

@ -1158,6 +1158,19 @@ InspectorUI.prototype = {
this.toolsSelect(aScroll);
},
/**
* Called when the highlighted node is changed by a tool.
*
* @param object aUpdater
* The tool that triggered the update (if any), that tool's
* onChanged will not be called.
*/
nodeChanged: function IUI_nodeChanged(aUpdater)
{
this.highlighter.highlight();
this.toolsOnChanged(aUpdater);
},
/////////////////////////////////////////////////////////////////////////
//// Event Handling
@ -1340,6 +1353,10 @@ InspectorUI.prototype = {
iframe.removeEventListener("load", boundLoadListener, true);
let doc = iframe.contentDocument;
this.ruleView = new CssRuleView(doc);
this.boundRuleViewChanged = this.ruleViewChanged.bind(this);
this.ruleView.element.addEventListener("CssRuleViewChanged",
this.boundRuleViewChanged);
doc.documentElement.appendChild(this.ruleView.element);
this.ruleView.highlight(this.selection);
Services.obs.notifyObservers(null,
@ -1370,6 +1387,12 @@ InspectorUI.prototype = {
this.ruleView.highlight(aNode);
},
ruleViewChanged: function IUI_ruleViewChanged()
{
this.isDirty = true;
this.nodeChanged(this.ruleViewObject);
},
/**
* Destroy the rule view.
*/
@ -1379,6 +1402,9 @@ InspectorUI.prototype = {
iframe.parentNode.removeChild(iframe);
if (this.ruleView) {
this.ruleView.element.removeEventListener("CssRuleViewChanged",
this.boundRuleViewChanged);
delete boundRuleViewChanged;
this.ruleView.clear();
delete this.ruleView;
}
@ -1685,8 +1711,6 @@ InspectorUI.prototype = {
*/
toolShow: function IUI_toolShow(aTool)
{
aTool.show.call(aTool.context, this.selection);
let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id));
btn.setAttribute("checked", "true");
if (aTool.sidebar) {
@ -1697,6 +1721,8 @@ InspectorUI.prototype = {
this.getToolbarButtonId(other.id)).removeAttribute("checked");
}.bind(this));
}
aTool.show.call(aTool.context, this.selection);
},
/**
@ -1848,13 +1874,29 @@ InspectorUI.prototype = {
*/
toolsDim: function IUI_toolsDim(aState)
{
this.toolsDo(function IUI_toolsOnSelect(aTool) {
this.toolsDo(function IUI_toolsDim(aTool) {
if (aTool.isOpen && "dim" in aTool) {
aTool.dim.call(aTool.context, aState);
}
});
},
/**
* Notify registered tools of changes to the highlighted element.
*
* @param object aUpdater
* The tool that triggered the update (if any), that tool's
* onChanged will not be called.
*/
toolsOnChanged: function IUI_toolsChanged(aUpdater)
{
this.toolsDo(function IUI_toolsOnChanged(aTool) {
if (aTool.isOpen && ("onChanged" in aTool) && aTool != aUpdater) {
aTool.onChanged.call(aTool.context);
}
});
},
/**
* Loop through all registered tools and pass each into the provided function
* @param aFunction The function to which each tool is to be passed

View File

@ -66,6 +66,7 @@ _BROWSER_FILES = \
browser_inspector_breadcrumbs.html \
browser_inspector_breadcrumbs.js \
browser_inspector_bug_699308_iframe_navigation.js \
browser_inspector_changes.js \
$(NULL)
# Disabled due to constant failures

View File

@ -0,0 +1,158 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Inspect Tests.
*
* The Initial Developer of the Original Code is
* The Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dave Camp <dcamp@mozilla.com>
* Rob Campbell <rcampbell@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
let doc;
let testDiv;
function createDocument()
{
doc.body.innerHTML = '<div id="testdiv">Test div!</div>';
doc.title = "Inspector Change Test";
startInspectorTests();
}
function getInspectorProp(aName)
{
for each (let view in InspectorUI.stylePanel.cssHtmlTree.propertyViews) {
if (view.name == aName) {
return view;
}
}
return null;
}
function startInspectorTests()
{
ok(InspectorUI, "InspectorUI variable exists");
Services.obs.addObserver(runInspectorTests,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
InspectorUI.toggleInspectorUI();
}
function runInspectorTests()
{
Services.obs.removeObserver(runInspectorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
testDiv = doc.getElementById("testdiv");
testDiv.style.fontSize = "10px";
InspectorUI.inspectNode(testDiv);
InspectorUI.stopInspecting();
// Start up the style inspector panel...
Services.obs.addObserver(stylePanelTests, "StyleInspector-populated", false);
executeSoon(function() {
InspectorUI.showSidebar();
document.getElementById(InspectorUI.getToolbarButtonId("styleinspector")).click();
});
}
function stylePanelTests()
{
Services.obs.removeObserver(stylePanelTests, "StyleInspector-populated");
ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
ok(InspectorUI.stylePanel.cssHtmlTree, "Style Panel has a cssHtmlTree");
let propView = getInspectorProp("font-size");
is(propView.value, "10px", "Style inspector should be showing the correct font size.");
Services.obs.addObserver(stylePanelAfterChange, "StyleInspector-populated", false);
testDiv.style.fontSize = "15px";
InspectorUI.nodeChanged();
}
function stylePanelAfterChange()
{
Services.obs.removeObserver(stylePanelAfterChange, "StyleInspector-populated");
let propView = getInspectorProp("font-size");
is(propView.value, "15px", "Style inspector should be showing the new font size.");
stylePanelNotActive();
}
function stylePanelNotActive()
{
// Tests changes made while the style panel is not active.
InspectorUI.ruleButton.click();
executeSoon(function() {
testDiv.style.fontSize = "20px";
Services.obs.addObserver(stylePanelAfterSwitch, "StyleInspector-populated", false);
document.getElementById(InspectorUI.getToolbarButtonId("styleinspector")).click();
});
}
function stylePanelAfterSwitch()
{
Services.obs.removeObserver(stylePanelAfterSwitch, "StyleInspector-populated");
let propView = getInspectorProp("font-size");
is(propView.value, "20px", "Style inspector should be showing the newest font size.");
Services.obs.addObserver(finishTest, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
executeSoon(function() {
InspectorUI.closeInspectorUI(true);
});
}
function finishTest()
{
Services.obs.removeObserver(finishTest,
InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
gBrowser.removeCurrentTab();
finish();
}
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function() {
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
doc = content.document;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html,basic tests for inspector";
}

View File

@ -172,10 +172,6 @@ CssHtmlTree.prototype = {
*/
highlight: function CssHtmlTree_highlight(aElement)
{
if (this.viewedElement == aElement) {
return;
}
this.viewedElement = aElement;
this._unmatchedProperties = null;
this._matchedProperties = null;
@ -529,7 +525,7 @@ PropertyView.prototype = {
*/
get hasMatchedSelectors()
{
return this.tree.matchedProperties[this.name];
return this.name in this.tree.matchedProperties;
},
/**
@ -537,7 +533,7 @@ PropertyView.prototype = {
*/
get hasUnmatchedSelectors()
{
return this.tree.hasUnmatchedSelectors(this.name);
return this.name in this.tree.hasUnmatchedSelectors;
},
/**

View File

@ -117,6 +117,17 @@ ElementStyle.prototype = {
domUtils: Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils),
/**
* Called by the Rule object when it has been changed through the
* setProperty* methods.
*/
_changed: function ElementStyle_changed()
{
if (this.onChanged) {
this.onChanged();
}
},
/**
* Refresh the list of rules to be displayed for the active element.
* Upon completion, this.rules[] will hold a list of Rule objects.
@ -388,6 +399,7 @@ Rule.prototype = {
prop.priority = this.style.getPropertyPriority(prop.name);
prop.updateComputed();
}
this.elementStyle._changed();
this.elementStyle.markOverridden();
},
@ -627,7 +639,15 @@ CssRuleView.prototype = {
return;
}
if (this._elementStyle) {
delete this._elementStyle.onChanged;
}
this._elementStyle = new ElementStyle(aElement);
this._elementStyle.onChanged = function() {
this._changed();
}.bind(this);
this._createEditors();
},
@ -643,6 +663,17 @@ CssRuleView.prototype = {
this._elementStyle = null;
},
/**
* Called when the user has made changes to the ElementStyle.
* Emits an event that clients can listen to.
*/
_changed: function CssRuleView_changed()
{
var evt = this.doc.createEvent("Events");
evt.initEvent("CssRuleViewChanged", true, false);
this.element.dispatchEvent(evt);
},
/**
* Creates editor UI for each of the rules in _elementStyle.
*/
@ -1003,10 +1034,12 @@ TextPropertyEditor.prototype = {
*/
_parseValue: function TextPropertyEditor_parseValue(aValue)
{
let [value, priority] = aValue.split("!", 2);
let pieces = aValue.split("!", 2);
let value = pieces[0];
let priority = pieces.length > 1 ? pieces[1] : "";
return {
value: value.trim(),
priority: (priority ? priority.trim() : "")
value: pieces[0].trim(),
priority: (pieces.length > 1 ? pieces[1].trim() : "")
};
},

View File

@ -87,6 +87,7 @@ StyleInspector.prototype = {
context: this,
get isOpen() isOpen(),
onSelect: this.selectNode,
onChanged: this.updateNode,
show: this.open,
hide: this.close,
dim: this.dimTool,
@ -247,6 +248,17 @@ StyleInspector.prototype = {
}
},
/**
* Update the display for the currently-selected node.
*/
updateNode: function SI_updateNode()
{
if (this.isOpen() && !this.dimmed) {
this.cssLogic.highlight(this.selectedNode);
this.cssHtmlTree.refreshPanel();
}
},
/**
* Dim or undim a panel by setting or removing a dimmed attribute.
* @param aState

View File

@ -32,6 +32,18 @@ function waitForEditorBlur(aEditor, aCallback)
}, false);
}
var gRuleViewChanged = false;
function ruleViewChanged()
{
gRuleViewChanged = true;
}
function expectChange()
{
ok(gRuleViewChanged, "Rule view should have fired a change event.");
gRuleViewChanged = false;
}
function startTest()
{
let style = '' +
@ -54,6 +66,7 @@ function startTest()
let doc = ruleDialog.document;
ruleView = new CssRuleView(doc);
doc.documentElement.appendChild(ruleView.element);
ruleView.element.addEventListener("CssRuleViewChanged", ruleViewChanged, false);
ruleView.highlight(testElement);
waitForFocus(testCancelNew, ruleDialog);
}, true);
@ -69,6 +82,7 @@ function testCancelNew()
is(elementRuleEditor.newPropSpan.inplaceEditor, aEditor, "Next focused editor should be the new property editor.");
let input = aEditor.input;
waitForEditorBlur(aEditor, function () {
ok(!gRuleViewChanged, "Shouldn't get a change event after a cancel.");
is(elementRuleEditor.rule.textProps.length, 0, "Should have canceled creating a new text property.");
ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties.");
testCreateNew();
@ -91,6 +105,7 @@ function testCreateNew()
input.value = "background-color";
waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) {
expectChange();
is(elementRuleEditor.rule.textProps.length, 1, "Should have created a new text property.");
is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
let textProp = elementRuleEditor.rule.textProps[0];
@ -98,6 +113,7 @@ function testCreateNew()
aEditor.input.value = "purple";
waitForEditorBlur(aEditor, function() {
expectChange();
is(textProp.value, "purple", "Text prop should have been changed.");
testEditProperty();
});
@ -120,10 +136,12 @@ function testEditProperty()
is(propEditor.nameSpan.inplaceEditor, aEditor, "Next focused editor should be the name editor.");
let input = aEditor.input;
waitForEditorFocus(propEditor.element, function onNewName(aEditor) {
expectChange();
input = aEditor.input;
is(propEditor.valueSpan.inplaceEditor, aEditor, "Focus should have moved to the value.");
waitForEditorBlur(aEditor, function() {
expectChange();
is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red",
"border-color should have been set.");
testDisableProperty();
@ -150,14 +168,20 @@ function testDisableProperty()
propEditor.enable.click();
is(idRuleEditor.rule.style.getPropertyValue("border-color"), "", "Border-color should have been unset.");
expectChange();
propEditor.enable.click();
is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red",
"Border-color should have been reset.");
expectChange();
finishTest();
}
function finishTest()
{
ruleView.element.removeEventListener("CssRuleViewChanged", ruleViewChanged, false);
ruleView.clear();
ruleDialog.close();
ruleDialog = ruleView = null;
doc = null;