Bug 740543 - Rule view does not update when window is resized. r=robcee

This commit is contained in:
Dave Camp 2012-06-02 15:18:33 -07:00
Родитель 7985fc62a3
Коммит 8f60bcb608
6 изменённых файлов: 359 добавлений и 15 удалений

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

@ -44,6 +44,10 @@ const INSPECTOR_NOTIFICATIONS = {
const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
// Timer, in milliseconds, between change events fired by
// things like resize events.
const LAYOUT_CHANGE_TIMER = 250;
/**
* Represents an open instance of the Inspector for a tab.
* This is the object handed out to sidebars and other API consumers.
@ -61,7 +65,10 @@ function Inspector(aIUI)
{
this._IUI = aIUI;
this._winID = aIUI.winID;
this._browser = aIUI.browser;
this._listeners = {};
this._browser.addEventListener("resize", this, true);
}
Inspector.prototype = {
@ -106,18 +113,96 @@ Inspector.prototype = {
*/
change: function Inspector_change(aContext)
{
this._cancelLayoutChange();
this._IUI.nodeChanged(aContext);
},
/**
* Returns true if a given sidebar panel is currently visible.
* @param string aPanelName
* The panel name as registered with registerSidebar
*/
isPanelVisible: function Inspector_isPanelVisible(aPanelName)
{
return this._IUI.sidebar.visible &&
this._IUI.sidebar.activePanel === aPanelName;
},
/**
* Called by the InspectorUI when the inspector is being destroyed.
*/
_destroy: function Inspector__destroy()
{
this._cancelLayoutChange();
this._browser.removeEventListener("resize", this, true);
delete this._IUI;
delete this._listeners;
},
/**
* Event handler for DOM events.
*
* @param DOMEvent aEvent
*/
handleEvent: function Inspector_handleEvent(aEvent)
{
switch(aEvent.type) {
case "resize":
this._scheduleLayoutChange();
}
},
/**
* Schedule a low-priority change event for things like paint
* and resize.
*/
_scheduleLayoutChange: function Inspector_scheduleLayoutChange()
{
if (this._timer) {
return null;
}
this._timer = this._IUI.win.setTimeout(function() {
this.change("layout");
}.bind(this), LAYOUT_CHANGE_TIMER);
},
/**
* Cancel a pending low-priority change event if any is
* scheduled.
*/
_cancelLayoutChange: function Inspector_cancelLayoutChange()
{
if (this._timer) {
this._IUI.win.clearTimeout(this._timer);
delete this._timer;
}
},
/**
* Called by InspectorUI after a tab switch, when the
* inspector is no longer the active tab.
*/
_freeze: function Inspector__freeze()
{
this._cancelLayoutChange();
this._browser.removeEventListener("resize", this, true);
this._frozen = true;
},
/**
* Called by InspectorUI after a tab switch when the
* inspector is back to being the active tab.
*/
_thaw: function Inspector__thaw()
{
if (!this._frozen) {
return;
}
this._browser.addEventListener("resize", this, true);
delete this._frozen;
},
/// Event stuff. Would like to refactor this eventually.
/// Emulates the jetpack event source, which has a nice API.
@ -176,10 +261,23 @@ Inspector.prototype = {
{
if (!(aEvent in this._listeners))
return;
for each (let listener in this._listeners[aEvent]) {
let originalListeners = this._listeners[aEvent];
for (let listener of this._listeners[aEvent]) {
// If the inspector was destroyed during event emission, stop
// emitting.
if (!this._listeners) {
break;
}
// If listeners were removed during emission, make sure the
// event handler we're going to fire wasn't removed.
if (originalListeners === this._listeners[aEvent] ||
this._listeners[aEvent].some(function(l) l === listener)) {
listener.apply(null, arguments);
}
}
}
}
///////////////////////////////////////////////////////////////////////////
@ -513,6 +611,7 @@ InspectorUI.prototype = {
// Has this windowID been inspected before?
if (this.store.hasID(this.winID)) {
this._currentInspector = this.store.getInspector(this.winID);
this._currentInspector._thaw();
let selectedNode = this.currentInspector._selectedNode;
if (selectedNode) {
this.inspectNode(selectedNode);
@ -646,9 +745,12 @@ InspectorUI.prototype = {
this.breadcrumbs = null;
}
delete this._currentInspector;
if (!aKeepInspector)
if (aKeepInspector) {
this._currentInspector._freeze();
} else {
this.store.deleteInspector(this.winID);
}
delete this._currentInspector;
this.inspectorUICommand.setAttribute("checked", "false");
@ -691,6 +793,7 @@ InspectorUI.prototype = {
_notifySelected: function IUI__notifySelected(aFrom)
{
this._currentInspector._cancelLayoutChange();
this._currentInspector._emit("select", aFrom);
},
@ -1477,9 +1580,6 @@ InspectorStyleSidebar.prototype = {
// wire up button to show the iframe
let onClick = function() {
this.activatePanel(aRegObj.id);
// Cheat a little bit and trigger a refresh
// when switching panels.
this._inspector.change("activatepanel-" + aRegObj.id);
}.bind(this);
btn.addEventListener("click", onClick, true);
@ -1636,7 +1736,13 @@ InspectorStyleSidebar.prototype = {
aTool.context = aTool.registration.load(this._inspector, aTool.frame);
this._inspector._emit("sidebaractivated", aTool.id);
this._inspector._emit("sidebaractivated-" + aTool.id);
// Send an event specific to the activation of this panel. For
// this initial event, include a "createpanel" argument
// to let panels watch sidebaractivated to refresh themselves
// but ignore the one immediately after their load.
// I don't really like this, we should find a better solution.
this._inspector._emit("sidebaractivated-" + aTool.id, "createpanel");
}.bind(this);
aTool.frame.addEventListener("load", aTool.onLoad, true);
aTool.frame.setAttribute("src", aTool.registration.contentURL);

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

@ -46,6 +46,8 @@ include $(topsrcdir)/config/rules.mk
_BROWSER_FILES = \
browser_responsiveui.js \
browser_responsiveruleview.js \
browser_responsivecomputedview.js \
$(NULL)

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

@ -0,0 +1,111 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let instance;
let computedView;
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(startTest, content);
}, true);
content.location = "data:text/html,<html><style>" +
"div {" +
" width: 500px;" +
" height: 10px;" +
" background: purple;" +
"} " +
"@media screen and (max-width: 200px) {" +
" div { " +
" width: 100px;" +
" }" +
"};" +
"</style><div></div></html>"
function computedWidth() {
for (let prop of computedView.propertyViews) {
if (prop.name === "width") {
return prop.valueNode.textContent;
}
}
return null;
}
function startTest() {
document.getElementById("Tools:ResponsiveUI").doCommand();
executeSoon(onUIOpen);
}
function onUIOpen() {
instance = gBrowser.selectedTab.responsiveUI;
ok(instance, "instance of the module is attached to the tab.");
instance.stack.setAttribute("notransition", "true");
registerCleanupFunction(function() {
instance.stack.removeAttribute("notransition");
});
instance.setSize(500, 500);
Services.obs.addObserver(onInspectorUIOpen,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
InspectorUI.openInspectorUI();
}
function onInspectorUIOpen() {
Services.obs.removeObserver(onInspectorUIOpen,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
let div = content.document.getElementsByTagName("div")[0];
InspectorUI.inspectNode(div);
InspectorUI.stopInspecting();
Services.obs.addObserver(testShrink, "StyleInspector-populated", false);
InspectorUI.sidebar.show();
InspectorUI.sidebar.activatePanel("computedview");
}
function testShrink() {
Services.obs.removeObserver(testShrink, "StyleInspector-populated", false);
computedView = InspectorUI.sidebar._toolContext("computedview").view;
is(computedWidth(), "500px", "Should show 500px initially.");
Services.obs.addObserver(function onShrink() {
Services.obs.removeObserver(onShrink, "StyleInspector-populated");
is(computedWidth(), "100px", "div should be 100px after shrinking.");
testGrow();
}, "StyleInspector-populated", false);
instance.setSize(100, 100);
}
function testGrow() {
Services.obs.addObserver(function onGrow() {
Services.obs.removeObserver(onGrow, "StyleInspector-populated");
is(computedWidth(), "500px", "Should be 500px after growing.");
finishUp();
}, "StyleInspector-populated", false);
instance.setSize(500, 500);
}
function finishUp() {
document.getElementById("Tools:ResponsiveUI").doCommand();
// Menus are correctly updated?
is(document.getElementById("Tools:ResponsiveUI").getAttribute("checked"), "false", "menu unchecked");
InspectorUI.closeInspectorUI();
gBrowser.removeCurrentTab();
finish();
}
}

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

@ -0,0 +1,103 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let instance;
let ruleView;
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(startTest, content);
}, true);
content.location = "data:text/html,<html><style>" +
"div {" +
" width: 500px;" +
" height: 10px;" +
" background: purple;" +
"} " +
"@media screen and (max-width: 200px) {" +
" div { " +
" width: 100px;" +
" }" +
"};" +
"</style><div></div></html>"
function numberOfRules() {
return ruleView.element.querySelectorAll(".ruleview-code").length;
}
function startTest() {
document.getElementById("Tools:ResponsiveUI").doCommand();
executeSoon(onUIOpen);
}
function onUIOpen() {
instance = gBrowser.selectedTab.responsiveUI;
ok(instance, "instance of the module is attached to the tab.");
instance.stack.setAttribute("notransition", "true");
registerCleanupFunction(function() {
instance.stack.removeAttribute("notransition");
});
instance.setSize(500, 500);
Services.obs.addObserver(onInspectorUIOpen,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
InspectorUI.openInspectorUI();
}
function onInspectorUIOpen() {
Services.obs.removeObserver(onInspectorUIOpen,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
let div = content.document.getElementsByTagName("div")[0];
InspectorUI.inspectNode(div);
InspectorUI.stopInspecting();
InspectorUI.currentInspector.once("sidebaractivated-ruleview", testShrink);
InspectorUI.sidebar.show();
InspectorUI.sidebar.activatePanel("ruleview");
}
function testShrink() {
ruleView = InspectorUI.sidebar._toolContext("ruleview").view;
is(numberOfRules(), 2, "Should have two rules initially.");
ruleView.element.addEventListener("CssRuleViewRefreshed", function refresh() {
ruleView.element.removeEventListener("CssRuleViewRefreshed", refresh, false);
is(numberOfRules(), 3, "Should have three rules after shrinking.");
testGrow();
}, false);
instance.setSize(100, 100);
}
function testGrow() {
ruleView.element.addEventListener("CssRuleViewRefreshed", function refresh() {
ruleView.element.removeEventListener("CssRuleViewRefreshed", refresh, false);
is(numberOfRules(), 2, "Should have two rules after growing.");
finishUp();
}, false);
instance.setSize(500, 500);
}
function finishUp() {
document.getElementById("Tools:ResponsiveUI").doCommand();
// Menus are correctly updated?
is(document.getElementById("Tools:ResponsiveUI").getAttribute("checked"), "false", "menu unchecked");
InspectorUI.closeInspectorUI();
gBrowser.removeCurrentTab();
finish();
}
}

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

@ -880,6 +880,13 @@ CssRuleView.prototype = {
// The element that we're inspecting.
_viewedElement: null,
/**
* Returns true if the rule view currently has an input editor visible.
*/
get isEditing() {
return this.element.querySelectorAll(".styleinspector-propertyeditor").length > 0;
},
destroy: function CssRuleView_destroy()
{
this.clear();
@ -946,11 +953,21 @@ CssRuleView.prototype = {
*/
nodeChanged: function CssRuleView_nodeChanged()
{
// Ignore refreshes during editing.
if (this.isEditing) {
return;
}
// Repopulate the element style.
this._elementStyle.populate();
// Refresh the rule editors.
this._createEditors();
// Notify anyone that cares that we refreshed.
var evt = this.doc.createEvent("Events");
evt.initEvent("CssRuleViewRefreshed", true, false);
this.element.dispatchEvent(evt);
},
/**

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

@ -4,6 +4,7 @@
* 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/. */
const Cc = Components.classes;
const Cu = Components.utils;
const Ci = Components.interfaces;
@ -116,6 +117,7 @@ function RuleViewTool(aInspector, aFrame)
this._onChange = this.onChange.bind(this);
this.inspector.on("change", this._onChange);
this.inspector.on("sidebaractivated-ruleview", this._onChange);
this.onSelect();
}
@ -134,11 +136,11 @@ RuleViewTool.prototype = {
},
onChange: function RVT_onChange(aEvent, aFrom) {
if (aFrom == "ruleview") {
if (aFrom == "ruleview" || aFrom == "createpanel") {
return;
}
if (this.inspector.locked) {
if (this.inspector.locked && this.inspector.isPanelVisible("ruleview")) {
this.view.nodeChanged();
}
},
@ -146,6 +148,7 @@ RuleViewTool.prototype = {
destroy: function RVT_destroy() {
this.inspector.removeListener("select", this._onSelect);
this.inspector.removeListener("change", this._onChange);
this.inspector.removeListener("sidebaractivated-ruleview", this._onChange);
this.view.element.removeEventListener("CssRuleViewChanged",
this._changeHandler);
this.view.element.removeEventListener("CssRuleViewCSSLinkClicked",
@ -172,14 +175,13 @@ function ComputedViewTool(aInspector, aFrame)
this._onSelect = this.onSelect.bind(this);
this.inspector.on("select", this._onSelect);
this._onChange = this.onChange.bind(this);
this.inspector.on("change", this._onChange);
// Since refreshes of the computed view are non-destructive,
// refresh when the tab is changed so we can notice script-driven
// changes.
this.inspector.on("sidebaractivated", this._onChange);
this.inspector.on("sidebaractivated-computedview", this._onChange);
this.cssLogic.highlight(null);
this.view.highlight(null);
@ -199,19 +201,22 @@ ComputedViewTool.prototype = {
onChange: function CVT_change(aEvent, aFrom)
{
if (aFrom == "computedview" ||
aFrom == "createpanel" ||
this.inspector.selection != this.cssLogic.viewedElement) {
return;
}
if (this.inspector.locked && this.inspector.isPanelVisible("computedview")) {
this.cssLogic.highlight(this.inspector.selection);
this.view.refreshPanel();
}
},
destroy: function CVT_destroy(aContext)
{
this.inspector.removeListener("select", this._onSelect);
this.inspector.removeListener("change", this._onChange);
this.inspector.removeListener("sidebaractivated", this._onChange);
this.inspector.removeListener("sidebaractivated-computedview", this._onChange);
this.view.destroy();
delete this.view;