author | Kyle Huey <khuey@kylehuey.com> |
Wed, 02 Nov 2011 08:53:04 -0400 | |
changeset 79564 | 631b19db4eb5a58084fc3153a46a968fcbf80dd6 |
parent 79552 | be8056caccb4db8b1ff89dba474847d2a65b9184 (current diff) |
parent 79563 | 06292dba305205f823d4043be55f35988806fc1d (diff) |
child 79565 | a64b3abb31c4053aa4e79b3b6cf973f802f13fa7 |
push id | 3053 |
push user | khuey@mozilla.com |
push date | Wed, 02 Nov 2011 12:53:44 +0000 |
treeherder | mozilla-inbound@80af665378fd [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 10.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -982,26 +982,36 @@ nowindowdrag="true" hidden="true"> <vbox flex="1"> <resizer id="inspector-top-resizer" flex="1" class="inspector-resizer" dir="top" disabled="true" element="inspector-tree-box"/> <hbox> +#ifdef XP_MACOSX + <toolbarbutton id="highlighter-closebutton" + oncommand="InspectorUI.closeInspectorUI(false);" + tooltiptext="&inspectCloseButton.tooltiptext;"/> +#endif <toolbarbutton id="inspector-inspect-toolbutton" label="&inspectButton.label;" accesskey="&inspectButton.accesskey;" command="Inspector:Inspect"/> <arrowscrollbox id="inspector-breadcrumbs" flex="1" orient="horizontal" clicktoscroll="true"/> <hbox id="inspector-tools"> <!-- registered tools go here --> </hbox> +#ifndef XP_MACOSX + <toolbarbutton id="highlighter-closebutton" + oncommand="InspectorUI.closeInspectorUI(false);" + tooltiptext="&inspectCloseButton.tooltiptext;"/> +#endif <resizer id="inspector-end-resizer" class="inspector-resizer" dir="top" disabled="true" element="inspector-tree-box"/> </hbox> </vbox> </toolbar> <toolbar id="addon-bar"
--- a/browser/base/content/highlighter.css +++ b/browser/base/content/highlighter.css @@ -25,24 +25,27 @@ #highlighter-veil-rightbox { -moz-box-flex: 1; } #highlighter-veil-middlebox:-moz-locale-dir(rtl) { -moz-box-direction: reverse; } -#highlighter-close-button { - position: absolute; - pointer-events: auto; - z-index: 1; +.inspector-breadcrumbs-button { + direction: ltr; } -.inspector-breadcrumbs-button { - direction: ltr; +.inspector-resizer { + display: none; +} + +#inspector-toolbar[treepanel-open] > vbox > #inspector-top-resizer, +#inspector-toolbar[treepanel-open] > vbox > hbox > #inspector-end-resizer { + display: -moz-box; } /* * Node Infobar */ #highlighter-nodeinfobar-container { position: absolute;
--- a/browser/devtools/highlighter/TreePanel.jsm +++ b/browser/devtools/highlighter/TreePanel.jsm @@ -226,22 +226,17 @@ TreePanel.prototype = { this.IUI.browser.ownerDocument.getElementById("browser-bottombox"); treeBox = this.document.createElement("vbox"); treeBox.id = "inspector-tree-box"; treeBox.state = "open"; // for the registerTools API. treeBox.minHeight = 10; treeBox.flex = 1; toolbarParent.insertBefore(treeBox, toolbar); - let resizerTop = - this.IUI.browser.ownerDocument.getElementById("inspector-top-resizer"); - let resizerEnd = - this.IUI.browser.ownerDocument.getElementById("inspector-end-resizer"); - resizerTop.removeAttribute("disabled"); - resizerEnd.removeAttribute("disabled"); + this.IUI.toolbar.setAttribute("treepanel-open", "true"); treeBox.appendChild(this.treeIFrame); let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel() { this.treeIFrame.removeEventListener("load", boundLoadedInitializeTreePanel, true); this.initializeIFrame(); @@ -259,22 +254,17 @@ TreePanel.prototype = { }, /** * Close the TreePanel. */ close: function TP_close() { if (this.openInDock) { - let resizerTop = - this.IUI.browser.ownerDocument.getElementById("inspector-top-resizer"); - let resizerEnd = - this.IUI.browser.ownerDocument.getElementById("inspector-end-resizer"); - resizerTop.setAttribute("disabled", "true"); - resizerEnd.setAttribute("disabled", "true"); + this.IUI.toolbar.removeAttribute("treepanel-open"); let treeBox = this.container; let treeBoxParent = treeBox.parentNode; treeBoxParent.removeChild(treeBox); } else { this.container.hidePopup(); }
--- a/browser/devtools/highlighter/inspector.jsm +++ b/browser/devtools/highlighter/inspector.jsm @@ -119,30 +119,30 @@ Highlighter.prototype = { this._highlighting = false; this.highlighterContainer = this.chromeDoc.createElement("stack"); this.highlighterContainer.id = "highlighter-container"; this.veilContainer = this.chromeDoc.createElement("vbox"); this.veilContainer.id = "highlighter-veil-container"; + // The controlsBox will host the different interactive + // elements of the highlighter (buttons, toolbars, ...). let controlsBox = this.chromeDoc.createElement("box"); controlsBox.id = "highlighter-controls"; this.highlighterContainer.appendChild(this.veilContainer); this.highlighterContainer.appendChild(controlsBox); stack.appendChild(this.highlighterContainer); // The veil will make the whole page darker except // for the region of the selected box. this.buildVeil(this.veilContainer); - // The controlsBox will host the different interactive - // elements of the highlighter (buttons, toolbars, ...). - this.buildControls(controlsBox); + this.buildInfobar(controlsBox); this.browser.addEventListener("resize", this, true); this.browser.addEventListener("scroll", this, true); this.handleResize(); }, /** @@ -196,30 +196,16 @@ Highlighter.prototype = { this.veilMiddleBox.appendChild(veilRightBox); aParent.appendChild(this.veilTopBox); aParent.appendChild(this.veilMiddleBox); aParent.appendChild(veilBottomBox); }, /** - * Build the controls: - * - * <box id="highlighter-close-button"/> - * - * @param nsIDOMElement aParent - * The container of the controls elements. - */ - buildControls: function Highlighter_buildControls(aParent) - { - this.buildCloseButton(aParent); - this.buildInfobar(aParent); - }, - - /** * Build the node Infobar. * * <box id="highlighter-nodeinfobar-container"> * <box id="Highlighter-nodeinfobar-arrow-top"/> * <vbox id="highlighter-nodeinfobar"> * <label id="highlighter-nodeinfobar-tagname"/> * <label id="highlighter-nodeinfobar-id"/> * <vbox id="highlighter-nodeinfobar-classes"/> @@ -275,48 +261,23 @@ Highlighter.prototype = { idLabel: idLabel, classesBox: classesBox, container: container, barHeight: barHeight, }; }, /** - * Build the close button. - * - * @param nsIDOMElement aParent - * The container of the close-button. - */ - buildCloseButton: function Highlighter_buildCloseButton(aParent) - { - let closeButton = this.chromeDoc.createElement("box"); - closeButton.id = "highlighter-close-button"; - closeButton.appendChild(this.chromeDoc.createElement("image")); - - let boundCloseEventHandler = this.IUI.closeInspectorUI.bind(this.IUI, false); - - closeButton.addEventListener("click", boundCloseEventHandler, false); - - aParent.appendChild(closeButton); - - this.boundCloseEventHandler = boundCloseEventHandler; - this.closeButton = closeButton; - }, - - /** * Destroy the nodes. */ destroy: function Highlighter_destroy() { this.browser.removeEventListener("scroll", this, true); this.browser.removeEventListener("resize", this, true); - this.closeButton.removeEventListener("click", this.boundCloseEventHandler, false); this.boundCloseEventHandler = null; - this.closeButton.parentNode.removeChild(this.closeButton); - this.closeButton = null; this._contentRect = null; this._highlightRect = null; this._highlighting = false; this.veilTopBox = null; this.veilLeftBox = null; this.veilMiddleBox = null; this.veilTransparentBox = null; this.veilContainer = null;
--- a/browser/devtools/highlighter/test/browser_inspector_bug_690361.js +++ b/browser/devtools/highlighter/test/browser_inspector_bug_690361.js @@ -77,17 +77,17 @@ function runInspectorTests() ok(!InspectorUI.toolbar.hidden, "toolbar is visible"); ok(InspectorUI.inspecting, "Inspector is inspecting"); ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open"); ok(InspectorUI.highlighter, "Highlighter is up"); salutation = doc.getElementById("salutation"); InspectorUI.inspectNode(salutation); - let button = document.getElementById("highlighter-close-button"); + let button = document.getElementById("highlighter-closebutton"); button.click(); } function closeInspectorTests() { Services.obs.removeObserver(closeInspectorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED); Services.obs.addObserver(inspectorOpenedTrap,
--- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -1,10 +1,11 @@ browser.jar: * content/browser/inspector.html (highlighter/inspector.html) content/browser/NetworkPanel.xhtml (webconsole/NetworkPanel.xhtml) * content/browser/scratchpad.xul (scratchpad/scratchpad.xul) * content/browser/scratchpad.js (scratchpad/scratchpad.js) content/browser/csshtmltree.xhtml (styleinspector/csshtmltree.xhtml) + content/browser/devtools/cssruleview.xhtml (styleinspector/cssruleview.xhtml) + content/browser/devtools/styleinspector.css (styleinspector/styleinspector.css) content/browser/orion.js (sourceeditor/orion/orion.js) content/browser/orion.css (sourceeditor/orion/orion.css) content/browser/orion-mozilla.css (sourceeditor/orion/mozilla.css) -
--- a/browser/devtools/sourceeditor/orion/mozilla.css +++ b/browser/devtools/sourceeditor/orion/mozilla.css @@ -1,11 +1,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ +.viewContainer { + font-size: inherit; /* inherit browser's default monospace font size */ +} .rulerLines { background: -moz-Dialog; color: -moz-DialogText; min-width: 1.4em; padding-left: 4px; padding-right: 4px; text-align: end;
--- a/browser/devtools/styleinspector/CssLogic.jsm +++ b/browser/devtools/styleinspector/CssLogic.jsm @@ -816,16 +816,46 @@ CssLogic.sheetMediaAllowed = function Cs } else { result = true; } return result; }; /** + * Return a shortened version of a style sheet's source. + * + * @param {CSSStyleSheet} aSheet the DOM object for the style sheet. + */ +CssLogic.shortSource = function CssLogic_shortSource(aSheet) +{ + // Use a string like "inline" if there is no source href + if (!aSheet || !aSheet.href) { + return CssLogic.l10n("rule.sourceInline"); + } + + // We try, in turn, the filename, filePath, query string, whole thing + let url = Services.io.newURI(aSheet.href, null, null); + url = url.QueryInterface(Ci.nsIURL); + if (url.fileName) { + return url.fileName; + } + + if (url.filePath) { + return url.filePath; + } + + if (url.query) { + return url.query; + } + + return this.domSheet.href; +} + +/** * A safe way to access cached bits of information about a stylesheet. * * @constructor * @param {CssLogic} aCssLogic pointer to the CssLogic instance working with * this CssSheet object. * @param {CSSStyleSheet} aDomSheet reference to a DOM CSSStyleSheet object. * @param {boolean} aSystemSheet tells if the stylesheet is system-provided. * @param {number} aIndex tells the index/position of the stylesheet within the @@ -880,41 +910,17 @@ CssSheet.prototype = { * @return {string} the shorthand source of the stylesheet. */ get shortSource() { if (this._shortSource) { return this._shortSource; } - // Use a string like "inline" if there is no source href - if (!this.domSheet.href) { - this._shortSource = CssLogic.l10n("rule.sourceInline"); - return this._shortSource; - } - - // We try, in turn, the filename, filePath, query string, whole thing - let url = Services.io.newURI(this.domSheet.href, null, null); - url = url.QueryInterface(Ci.nsIURL); - if (url.fileName) { - this._shortSource = url.fileName; - return this._shortSource; - } - - if (url.filePath) { - this._shortSource = url.filePath; - return this._shortSource; - } - - if (url.query) { - this._shortSource = url.query; - return this._shortSource; - } - - this._shortSource = this.domSheet.href; + this._shortSource = CssLogic.shortSource(this.domSheet); return this._shortSource; }, /** * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter. * * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or * false otherwise.
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/CssRuleView.jsm @@ -0,0 +1,1248 @@ +/* -*- Mode: javascript; 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 the Mozilla Inspector Module. + * + * 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) (Original Author) + * + * 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 ***** */ + +"use strict" + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +const HTML_NS = "http://www.w3.org/1999/xhtml"; +const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + +const FOCUS_FORWARD = Ci.nsIFocusManager.MOVEFOCUS_FORWARD; +const FOCUS_BACKWARD = Ci.nsIFocusManager.MOVEFOCUS_BACKWARD; + +/** + * These regular expressions are adapted from firebug's css.js, and are + * used to parse CSSStyleDeclaration's cssText attribute. + */ + +// Used to split on css line separators +const CSS_LINE_RE = /(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g; + +// Used to parse a single property line. +const CSS_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(?:! (important))?;?$/; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource:///modules/devtools/CssLogic.jsm"); + +var EXPORTED_SYMBOLS = ["CssRuleView", + "_ElementStyle", + "_editableField"]; + +/** + * Our model looks like this: + * + * ElementStyle: + * Responsible for keeping track of which properties are overridden. + * Maintains a list of Rule objects that apply to the element. + * Rule: + * Manages a single style declaration or rule. + * Responsible for applying changes to the properties in a rule. + * Maintains a list of TextProperty objects. + * TextProperty: + * Manages a single property from the cssText attribute of the + * relevant declaration. + * Maintains a list of computed properties that come from this + * property declaration. + * Changes to the TextProperty are sent to its related Rule for + * application. + */ + +/** + * ElementStyle maintains a list of Rule objects for a given element. + * + * @constructor + */ +function ElementStyle(aElement) +{ + this.element = aElement; + let doc = aElement.ownerDocument; + + // To figure out how shorthand properties are interpreted by the + // engine, we will set properties on a dummy element and observe + // how their .style attribute reflects them as computed values. + this.dummyElement = doc.createElementNS(this.element.namespaceURI, + this.element.tagName); + this._populate(); +} +// We're exporting _ElementStyle for unit tests. +var _ElementStyle = ElementStyle; + +ElementStyle.prototype = { + + // The element we're looking at. + element: null, + + // Empty, unconnected element of the same type as this node, used + // to figure out how shorthand properties will be parsed. + dummyElement: null, + + domUtils: Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils), + + /** + * Refresh the list of rules to be displayed for the active element. + * Upon completion, this.rules[] will hold a list of Rule objects. + */ + _populate: function ElementStyle_populate() + { + this.rules = []; + + // Include the element's style first. + this.rules.push(new Rule(this, { + style: this.element.style, + selectorText: CssLogic.l10n("rule.sourceElement") + })); + + // Get the styles that apply to the element. + try { + var domRules = this.domUtils.getCSSStyleRules(this.element); + } catch (ex) { + Services.console.logStringMessage("ElementStyle_populate error: " + ex); + return; + } + + // getCSStyleRules returns ordered from least-specific to + // most-specific. + for (let i = domRules.Count() - 1; i >= 0; i--) { + let domRule = domRules.GetElementAt(i); + + // XXX: Optionally provide access to system sheets. + let systemSheet = CssLogic.isSystemStyleSheet(domRule.parentStyleSheet); + if (systemSheet) { + continue; + } + + // XXX: non-style rules. + if (domRule.type === Ci.nsIDOMCSSRule.STYLE_RULE) { + this.rules.push(new Rule(this, { domRule: domRule })); + } + } + + // Mark overridden computed styles. + this.markOverridden(); + }, + + /** + * Mark the properties listed in this.rules with an overridden flag + * if an earlier property overrides it. + */ + markOverridden: function ElementStyle_markOverridden() + { + // Gather all the text properties applied by these rules, ordered + // from more- to less-specific. + let textProps = []; + for each (let rule in this.rules) { + textProps = textProps.concat(rule.textProps.slice(0).reverse()); + } + + // Gather all the computed properties applied by those text + // properties. + let computedProps = []; + for each (let textProp in textProps) { + computedProps = computedProps.concat(textProp.computed); + }; + + // Walk over the computed properties. As we see a property name + // for the first time, mark that property's name as taken by this + // property. + // + // If we come across a property whose name is already taken, check + // its priority against the property that was found first: + // + // If the new property is a higher priority, mark the old + // property overridden and mark the property name as taken by + // the new property. + // + // If the new property is a lower or equal priority, mark it as + // overridden. + // + // _overriddenDirty will be set on each prop, indicating whether its + // dirty status changed during this pass. + let taken = {}; + for each (let computedProp in computedProps) { + let earlier = taken[computedProp.name]; + let overridden; + if (earlier + && computedProp.priority === "important" + && earlier.priority !== "important") { + // New property is higher priority. Mark the earlier property + // overridden (which will reverse its dirty state). + earlier._overriddenDirty = !earlier._overriddenDirty; + earlier.overridden = true; + overridden = false; + } else { + overridden = !!earlier; + } + + computedProp._overriddenDirty = (!!computedProp.overridden != overridden); + computedProp.overridden = overridden; + if (!computedProp.overridden) { + taken[computedProp.name] = computedProp; + } + } + + // For each TextProperty, mark it overridden if all of its + // computed properties are marked overridden. Update the text + // property's associated editor, if any. This will clear the + // _overriddenDirty state on all computed properties. + for each (let textProp in textProps) { + // _updatePropertyOverridden will return true if the + // overridden state has changed for the text property. + if (this._updatePropertyOverridden(textProp)) { + textProp.updateEditor(); + } + } + }, + + /** + * Mark a given TextProperty as overridden or not depending on the + * state of its computed properties. Clears the _overriddenDirty state + * on all computed properties. + * + * @param {TextProperty} aProp + * The text property to update. + * + * @return True if the TextProperty's overridden state (or any of its + * computed properties overridden state) changed. + */ + _updatePropertyOverridden: function ElementStyle_updatePropertyOverridden(aProp) + { + let overridden = true; + let dirty = false; + for each (let computedProp in aProp.computed) { + if (!computedProp.overridden) { + overridden = false; + } + dirty = computedProp._overriddenDirty || dirty; + delete computedProp._overriddenDirty; + } + + dirty = (!!aProp.overridden != overridden) || dirty; + aProp.overridden = overridden; + return dirty; + } +} + +/** + * A single style rule or declaration. + * + * @param {ElementStyle} aElementStyle + * The ElementStyle to which this rule belongs. + * @param {object} aOptions + * The information used to construct this rule. Properties include: + * domRule: the nsIDOMCSSStyleRule to view, if any. + * style: the nsIDOMCSSStyleDeclaration to view. If omitted, + * the domRule's style will be used. + * selectorText: selector text to display. If omitted, the domRule's + * selectorText will be used. + * @constructor + */ +function Rule(aElementStyle, aOptions) +{ + this.elementStyle = aElementStyle; + this.domRule = aOptions.domRule || null; + this.style = aOptions.style || this.domRule.style; + this.selectorText = aOptions.selectorText || this.domRule.selectorText; + + this._getTextProperties(); +} + +Rule.prototype = { + get title() + { + if (this._title) { + return this._title; + } + let sheet = this.domRule ? this.domRule.parentStyleSheet : null; + this._title = CssLogic.shortSource(sheet); + if (this.domRule) { + let line = this.elementStyle.domUtils.getRuleLine(this.domRule); + this._title += ":" + line; + } + return this._title; + }, + + /** + * Create a new TextProperty to include in the rule. + * + * @param {string} aName + * The text property name (such as "background" or "border-top"). + * @param {string} aValue + * The property's value (not including priority). + * @param {string} aPriority + * The property's priority (either "important" or an empty string). + */ + createProperty: function Rule_createProperty(aName, aValue, aPriority) + { + let prop = new TextProperty(this, aName, aValue, aPriority); + this.textProps.push(prop); + this.applyProperties(); + return prop; + }, + + /** + * Reapply all the properties in this rule, and update their + * computed styles. Will re-mark overridden properties. + */ + applyProperties: function Rule_applyProperties() + { + for each (let prop in this.textProps) { + if (!prop.enabled) { + continue; + } + + this.style.setProperty(prop.name, prop.value, prop.priority); + // Refresh the property's value from the style, to reflect + // any changes made during parsing. + prop.value = this.style.getPropertyValue(prop.name); + prop.priority = this.style.getPropertyPriority(prop.name); + prop.updateComputed(); + } + + this.elementStyle.markOverridden(); + }, + + /** + * Renames a property. + * + * @param {TextProperty} aProperty + * The property to rename. + * @param {string} aName + * The new property name (such as "background" or "border-top"). + */ + setPropertyName: function Rule_setPropertyName(aProperty, aName) + { + if (aName === aProperty.name) { + return; + } + this.style.removeProperty(aProperty.name); + aProperty.name = aName; + this.applyProperties(); + }, + + /** + * Sets the value and priority of a property. + * + * @param {TextProperty} aProperty + * The property to manipulate. + * @param {string} aValue + * The property's value (not including priority). + * @param {string} aPriority + * The property's priority (either "important" or an empty string). + */ + setPropertyValue: function Rule_setPropertyValue(aProperty, aValue, aPriority) + { + if (aValue === aProperty.value && aPriority === aProperty.priority) { + return; + } + aProperty.value = aValue; + aProperty.priority = aPriority; + this.applyProperties(); + }, + + /** + * Disables or enables given TextProperty. + */ + setPropertyEnabled: function Rule_enableProperty(aProperty, aValue) + { + aProperty.enabled = !!aValue; + if (!aProperty.enabled) { + this.style.removeProperty(aProperty.name); + } + this.applyProperties(); + }, + + /** + * Remove a given TextProperty from the rule and update the rule + * accordingly. + */ + removeProperty: function Rule_removeProperty(aProperty) + { + this.textProps = this.textProps.filter(function(prop) prop != aProperty); + this.style.removeProperty(aProperty); + // Need to re-apply properties in case removing this TextProperty + // exposes another one. + this.applyProperties(); + }, + + /** + * Get the list of TextProperties from the style. Needs + * to parse the style's cssText. + */ + _getTextProperties: function Rule_getTextProperties() + { + this.textProps = []; + let lines = this.style.cssText.match(CSS_LINE_RE); + for each (let line in lines) { + let matches = CSS_PROP_RE.exec(line); + if(!matches || !matches[2]) + continue; + + let prop = new TextProperty(this, matches[1], matches[2], matches[3] || ""); + this.textProps.push(prop); + } + }, +} + +/** + * A single property in a rule's cssText. + * + * @param {Rule} aRule + * The rule this TextProperty came from. + * @param {string} aName + * The text property name (such as "background" or "border-top"). + * @param {string} aValue + * The property's value (not including priority). + * @param {string} aPriority + * The property's priority (either "important" or an empty string). + * + */ +function TextProperty(aRule, aName, aValue, aPriority) +{ + this.rule = aRule; + this.name = aName; + this.value = aValue; + this.priority = aPriority; + this.enabled = true; + this.updateComputed(); +} + +TextProperty.prototype = { + /** + * Update the editor associated with this text property, + * if any. + */ + updateEditor: function TextProperty_updateEditor() + { + if (this.editor) { + this.editor.update(); + } + }, + + /** + * Update the list of computed properties for this text property. + */ + updateComputed: function TextProperty_updateComputed() + { + if (!this.name) { + return; + } + + // This is a bit funky. To get the list of computed properties + // for this text property, we'll set the property on a dummy element + // and see what the computed style looks like. + let dummyElement = this.rule.elementStyle.dummyElement; + let dummyStyle = dummyElement.style; + dummyStyle.cssText = ""; + dummyStyle.setProperty(this.name, this.value, this.priority); + + this.computed = []; + for (let i = 0, n = dummyStyle.length; i < n; i++) { + let prop = dummyStyle.item(i); + this.computed.push({ + name: prop, + value: dummyStyle.getPropertyValue(prop), + priority: dummyStyle.getPropertyPriority(prop), + }); + } + }, + + setValue: function TextProperty_setValue(aValue, aPriority) + { + this.rule.setPropertyValue(this, aValue, aPriority); + this.updateEditor(); + }, + + setName: function TextProperty_setName(aName) + { + this.rule.setPropertyName(this, aName); + this.updateEditor(); + }, + + setEnabled: function TextProperty_setEnabled(aValue) + { + this.rule.setPropertyEnabled(this, aValue); + this.updateEditor(); + }, + + remove: function TextProperty_remove() + { + this.rule.removeProperty(this); + } +} + + +/** + * View hierarchy mostly follows the model hierarchy. + * + * CssRuleView: + * Owns an ElementStyle and creates a list of RuleEditors for its + * Rules. + * RuleEditor: + * Owns a Rule object and creates a list of TextPropertyEditors + * for its TextProperties. + * Manages creation of new text properties. + * TextPropertyEditor: + * Owns a TextProperty object. + * Manages changes to the TextProperty. + * Can be expanded to display computed properties. + * Can mark a property disabled or enabled. + */ + +/** + * CssRuleView is a view of the style rules and declarations that + * apply to a given element. After construction, the 'element' + * property will be available with the user interface. + * + * @param Document aDocument + * The document that will contain the rule view. + * @constructor + */ +function CssRuleView(aDoc) +{ + this.doc = aDoc; + + this.element = this.doc.createElementNS(HTML_NS, "div"); + this.element.setAttribute("tabindex", "0"); + this.element.classList.add("ruleview"); +} + +CssRuleView.prototype = { + // The element that we're inspecting. + _viewedElement: null, + + /** + * Update the highlighted element. + * + * @param {nsIDOMElement} aElement + * The node whose style rules we'll inspect. + */ + highlight: function CssRuleView_highlight(aElement) + { + if (this._viewedElement === aElement) { + return; + } + + this.clear(); + + this._viewedElement = aElement; + if (!this._viewedElement) { + return; + } + + this._elementStyle = new ElementStyle(aElement); + this._createEditors(); + }, + + /** + * Clear the rule view. + */ + clear: function CssRuleView_clear() + { + while (this.element.hasChildNodes()) { + this.element.removeChild(this.element.lastChild); + } + this._viewedElement = null; + this._elementStyle = null; + }, + + /** + * Creates editor UI for each of the rules in _elementStyle. + */ + _createEditors: function CssRuleView_createEditors() + { + for each (let rule in this._elementStyle.rules) { + // Don't hold a reference to this editor beyond the one held + // by the node. + let editor = new RuleEditor(this.doc, rule); + this.element.appendChild(editor.element); + } + }, +}; + +/** + * Create a RuleEditor. + * + * @param object aDoc + * The document holding this rule editor. + * @param Rule aRule + * The Rule object we're editing. + * @constructor + */ +function RuleEditor(aDoc, aRule) +{ + this.doc = aDoc; + this.rule = aRule; + + this._onNewProperty = this._onNewProperty.bind(this); + + this._create(); +} + +RuleEditor.prototype = { + _create: function RuleEditor_create() + { + this.element = this.doc.createElementNS(HTML_NS, "div"); + this.element._ruleEditor = this; + + // Add the source link. + let source = createChild(this.element, "div", { + class: "ruleview-rule-source", + textContent: this.rule.title + }); + + let code = createChild(this.element, "div", { + class: "ruleview-code" + }); + + let header = createChild(code, "div", {}); + + let selectors = createChild(header, "span", { + class: "ruleview-selector", + textContent: this.rule.selectorText + }); + appendText(header, " {"); + + this.propertyList = createChild(code, "ul", { + class: "ruleview-propertylist" + }); + + for each (let prop in this.rule.textProps) { + let propEditor = new TextPropertyEditor(this, prop); + this.propertyList.appendChild(propEditor.element); + } + + this.closeBrace = createChild(code, "div", { + class: "ruleview-ruleclose", + tabindex: "0", + textContent: "}" + }); + + // We made the close brace focusable, tabbing to it + // or clicking on it should start the new property editor. + this.closeBrace.addEventListener("focus", function() { + this.newProperty(); + }.bind(this), true); + }, + + /** + * Create a text input for a property name. If a non-empty property + * name is given, we'll create a real TextProperty and add it to the + * rule. + */ + newProperty: function RuleEditor_newProperty() + { + // While we're editing a new property, it doesn't make sense to + // start a second new property editor, so disable focusing the + // close brace for now. + this.closeBrace.removeAttribute("tabindex"); + + this.newPropItem = createChild(this.propertyList, "li", { + class: "ruleview-property ruleview-newproperty", + }); + + this.newPropSpan = createChild(this.newPropItem, "span", { + class: "ruleview-propertyname" + }); + + new InplaceEditor({ + element: this.newPropSpan, + done: this._onNewProperty, + advanceChars: ":" + }); + }, + + _onNewProperty: function RuleEditor_onNewProperty(aValue, aCommit) + { + // We're done, make the close brace focusable again. + this.closeBrace.setAttribute("tabindex", "0"); + + this.propertyList.removeChild(this.newPropItem); + delete this.newPropItem; + delete this.newPropSpan; + + if (!aValue || !aCommit) { + return; + } + + // Create an empty-valued property and start editing it. + let prop = this.rule.createProperty(aValue, "", ""); + let editor = new TextPropertyEditor(this, prop); + this.propertyList.appendChild(editor.element); + editor.valueSpan.focus(); + }, +}; + +/** + * Create a TextPropertyEditor. + * + * @param {RuleEditor} aRuleEditor + * The rule editor that owns this TextPropertyEditor. + * @param {TextProperty} aProperty + * The text property to edit. + * @constructor + */ +function TextPropertyEditor(aRuleEditor, aProperty) +{ + this.doc = aRuleEditor.doc; + this.prop = aProperty; + this.prop.editor = this; + + this._onEnableClicked = this._onEnableClicked.bind(this); + this._onExpandClicked = this._onExpandClicked.bind(this); + this._onStartEditing = this._onStartEditing.bind(this); + this._onNameDone = this._onNameDone.bind(this); + this._onValueDone = this._onValueDone.bind(this); + + this._create(); + this.update(); +} + +TextPropertyEditor.prototype = { + get editing() { + return !!(this.nameSpan.inplaceEditor || this.valueSpan.inplaceEditor); + }, + + /** + * Create the property editor's DOM. + */ + _create: function TextPropertyEditor_create() + { + this.element = this.doc.createElementNS(HTML_NS, "li"); + this.element.classList.add("ruleview-property"); + + // The enable checkbox will disable or enable the rule. + this.enable = createChild(this.element, "input", { + class: "ruleview-enableproperty", + type: "checkbox", + tabindex: "-1" + }); + this.enable.addEventListener("click", this._onEnableClicked, true); + + // Click to expand the computed properties of the text property. + this.expander = createChild(this.element, "span", { + class: "ruleview-expander" + }); + this.expander.addEventListener("click", this._onExpandClicked, true); + + // Property name, editable when focused. Property name + // is committed when the editor is unfocused. + this.nameSpan = createChild(this.element, "span", { + class: "ruleview-propertyname", + tabindex: "0", + }); + editableField({ + start: this._onStartEditing, + element: this.nameSpan, + done: this._onNameDone, + advanceChars: ':' + }); + + appendText(this.element, ": "); + + // Property value, editable when focused. Changes to the + // property value are applied as they are typed, and reverted + // if the user presses escape. + this.valueSpan = createChild(this.element, "span", { + class: "ruleview-propertyvalue", + tabindex: "0", + }); + + editableField({ + start: this._onStartEditing, + element: this.valueSpan, + done: this._onValueDone, + advanceChars: ';' + }); + + // Save the initial value as the last committed value, + // for restoring after pressing escape. + this.committed = { name: this.prop.name, + value: this.prop.value, + priority: this.prop.priority }; + + appendText(this.element, ";"); + + // Holds the viewers for the computed properties. + // will be populated in |_updateComputed|. + this.computed = createChild(this.element, "ul", { + class: "ruleview-computedlist", + }); + }, + + /** + * Populate the span based on changes to the TextProperty. + */ + update: function TextPropertyEditor_update() + { + if (this.prop.enabled) { + this.enable.style.removeProperty("visibility"); + this.enable.setAttribute("checked", ""); + } else { + this.enable.style.visibility = "visible"; + this.enable.removeAttribute("checked"); + } + + if (this.prop.overridden && !this.editing) { + this.element.classList.add("ruleview-overridden"); + } else { + this.element.classList.remove("ruleview-overridden"); + } + + this.nameSpan.textContent = this.prop.name; + + // Combine the property's value and priority into one string for + // the value. + let val = this.prop.value; + if (this.prop.priority) { + val += " !" + this.prop.priority; + } + this.valueSpan.textContent = val; + + // Populate the computed styles. + this._updateComputed(); + }, + + _onStartEditing: function TextPropertyEditor_onStartEditing() + { + this.element.classList.remove("ruleview-overridden"); + }, + + /** + * Populate the list of computed styles. + */ + _updateComputed: function TextPropertyEditor_updateComputed() + { + // Clear out existing viewers. + while (this.computed.hasChildNodes()) { + this.computed.removeChild(this.computed.lastChild); + } + + let showExpander = false; + for each (let computed in this.prop.computed) { + // Don't bother to duplicate information already + // shown in the text property. + if (computed.name === this.prop.name) { + continue; + } + + showExpander = true; + + let li = createChild(this.computed, "li", { + class: "ruleview-computed" + }); + + if (computed.overridden) { + li.classList.add("ruleview-overridden"); + } + + createChild(li, "span", { + class: "ruleview-propertyname", + textContent: computed.name + }); + appendText(li, ": "); + createChild(li, "span", { + class: "ruleview-propertyvalue", + textContent: computed.value + }); + appendText(li, ";"); + } + + // Show or hide the expander as needed. + if (showExpander) { + this.expander.style.visibility = "visible"; + } else { + this.expander.style.visibility = "hidden"; + } + }, + + /** + * Handles clicks on the disabled property. + */ + _onEnableClicked: function TextPropertyEditor_onEnableClicked() + { + this.prop.setEnabled(this.enable.checked); + }, + + /** + * Handles clicks on the computed property expander. + */ + _onExpandClicked: function TextPropertyEditor_onExpandClicked() + { + this.expander.classList.toggle("styleinspector-open"); + this.computed.classList.toggle("styleinspector-open"); + }, + + /** + * Called when the property name's inplace editor is closed. + * Ignores the change if the user pressed escape, otherwise + * commits it. + * + * @param {string} aValue + * The value contained in the editor. + * @param {boolean} aCommit + * True if the change should be applied. + */ + _onNameDone: function TextPropertyEditor_onNameDone(aValue, aCommit) + { + if (!aCommit) { + return; + } + if (!aValue) { + this.prop.remove(); + this.element.parentNode.removeChild(this.element); + return; + } + this.prop.setName(aValue); + }, + + /** + * Pull priority (!important) out of the value provided by a + * value editor. + * + * @param {string} aValue + * The value from the text editor. + * @return an object with 'value' and 'priority' properties. + */ + _parseValue: function TextPropertyEditor_parseValue(aValue) + { + let [value, priority] = aValue.split("!", 2); + return { + value: value.trim(), + priority: (priority ? priority.trim() : "") + }; + }, + + /** + * Called when a value editor closes. If the user pressed escape, + * revert to the value this property had before editing. + * + * @param {string} aValue + * The value contained in the editor. + * @param {boolean} aCommit + * True if the change should be applied. + */ + _onValueDone: function PropertyEditor_onValueDone(aValue, aCommit) + { + if (aCommit) { + let val = this._parseValue(aValue); + this.prop.setValue(val.value, val.priority); + this.committed.value = this.prop.value; + this.committed.priority = this.prop.priority; + } else { + this.prop.setValue(this.committed.value, this.committed.priority); + } + }, +}; + +/** + * Mark a span editable. |editableField| will listen for the span to + * be focused and create an InlineEditor to handle text input. + * Changes will be committed when the InlineEditor's input is blurred + * or dropped when the user presses escape. + * + * @param {object} aOptions + * Options for the editable field, including: + * {Element} element: + * (required) The span to be edited on focus. + * {function} start: + * Will be called when the inplace editor is initialized. + * {function} change: + * Will be called when the text input changes. Will be called + * with the current value of the text input. + * {function} done: + * Called when input is committed or blurred. Called with + * current value and a boolean telling the caller whether to + * commit the change. + * {string} advanceChars: + * If any characters in advanceChars are typed, focus will advance + * to the next element. + */ +function editableField(aOptions) +{ + aOptions.element.addEventListener("focus", function() { + new InplaceEditor(aOptions); + }, false); +} +var _editableField = editableField; + +function InplaceEditor(aOptions) +{ + this.elt = aOptions.element; + this.elt.inplaceEditor = this; + + this.change = aOptions.change; + this.done = aOptions.done; + this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent; + this.doc = this.elt.ownerDocument; + + this._onBlur = this._onBlur.bind(this); + this._onKeyPress = this._onKeyPress.bind(this); + this._onInput = this._onInput.bind(this); + + this._createInput(); + this._autosize(); + + // Pull out character codes for advanceChars, listing the + // characters that should trigger a blur. + this._advanceCharCodes = {}; + let advanceChars = aOptions.advanceChars || ''; + for (let i = 0; i < advanceChars.length; i++) { + this._advanceCharCodes[advanceChars.charCodeAt(i)] = true; + } + + // Hide the provided element and add our editor. + this.originalDisplay = this.elt.style.display; + this.elt.style.display = "none"; + this.elt.parentNode.insertBefore(this.input, this.elt); + + this.input.select(); + this.input.focus(); + + this.input.addEventListener("blur", this._onBlur, false); + this.input.addEventListener("keypress", this._onKeyPress, false); + this.input.addEventListener("input", this._onInput, false); + + if (aOptions.start) { + aOptions.start(); + } +} + +InplaceEditor.prototype = { + _createInput: function InplaceEditor_createEditor() + { + this.input = this.doc.createElementNS(HTML_NS, "input"); + this.input.inplaceEditor = this; + this.input.classList.add("styleinspector-propertyeditor"); + this.input.value = this.initial; + + copyTextStyles(this.elt, this.input); + }, + + /** + * Get rid of the editor. + */ + _clear: function InplaceEditor_clear() + { + this.input.removeEventListener("blur", this._onBlur, false); + this.input.removeEventListener("keypress", this._onKeyPress, false); + this.input.removeEventListener("oninput", this._onInput, false); + this._stopAutosize(); + + this.elt.parentNode.removeChild(this.input); + this.elt.style.display = this.originalDisplay; + this.input = null; + + delete this.elt.inplaceEditor; + delete this.elt; + }, + + /** + * Keeps the editor close to the size of its input string. This is pretty + * crappy, suggestions for improvement welcome. + */ + _autosize: function InplaceEditor_autosize() + { + // Create a hidden, absolutely-positioned span to measure the text + // in the input. Boo. + + // We can't just measure the original element because a) we don't + // change the underlying element's text ourselves (we leave that + // up to the client), and b) without tweaking the style of the + // original element, it might wrap differently or something. + this._measurement = this.doc.createElementNS(HTML_NS, "span"); + this.elt.parentNode.appendChild(this._measurement); + let style = this._measurement.style; + style.visibility = "hidden"; + style.position = "absolute"; + style.top = "0"; + style.left = "0"; + copyTextStyles(this.input, this._measurement); + this._updateSize(); + }, + + /** + * Clean up the mess created by _autosize(). + */ + _stopAutosize: function InplaceEditor_stopAutosize() + { + if (!this._measurement) { + return; + } + this._measurement.parentNode.removeChild(this._measurement); + delete this._measurement; + }, + + /** + * Size the editor to fit its current contents. + */ + _updateSize: function InplaceEditor_updateSize() + { + // Replace spaces with non-breaking spaces. Otherwise setting + // the span's textContent will collapse spaces and the measurement + // will be wrong. + this._measurement.textContent = this.input.value.replace(' ', '\u00a0', 'g'); + + // We add a bit of padding to the end. Should be enough to fit + // any letter that could be typed, otherwise we'll scroll before + // we get a chance to resize. Yuck. + let width = this._measurement.offsetWidth + 10; + + this.input.style.width = width + "px"; + }, + + /** + * Handle loss of focus by calling the client's done handler and + * clearing out. + */ + _onBlur: function InplaceEditor_onBlur(aEvent) + { + if (this.done) { + this.done(this.cancelled ? this.initial : this.input.value.trim(), + !this.cancelled); + } + this._clear(); + }, + + _onKeyPress: function InplaceEditor_onKeyPress(aEvent) + { + let prevent = false; + if (aEvent.charCode in this._advanceCharCodes + || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) { + // Focus the next element, triggering a blur which + // will eventually shut us down (making return roughly equal + // tab). + prevent = true; + moveFocus(this.input.ownerDocument.defaultView, FOCUS_FORWARD); + } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) { + // Cancel and blur ourselves. |_onBlur| will call the user's + // done handler for us. + prevent = true; + this.cancelled = true; + this.input.blur(); + } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) { + // No need for leading spaces here. This is particularly + // noticable when adding a property: it's very natural to type + // <name>: (which advances to the next property) then spacebar. + prevent = !this.input.value; + } + + if (prevent) { + aEvent.preventDefault(); + } + }, + + /** + * Handle changes the input text. + */ + _onInput: function InplaceEditor_onInput(aEvent) + { + // Update size if we're autosizing. + if (this._measurement) { + this._updateSize(); + } + + // Call the user's change handler if available. + if (this.change) { + this.change(this.input.value.trim()); + } + } +}; + +/** + * Helper functions + */ + +/** + * Create a child element with a set of attributes. + * + * @param {Element} aParent + * The parent node. + * @param {string} aTag + * The tag name. + * @param {object} aAttributes + * A set of attributes to set on the node. + */ +function createChild(aParent, aTag, aAttributes) +{ + let elt = aParent.ownerDocument.createElementNS(HTML_NS, aTag); + for (let attr in aAttributes) { + if (aAttributes.hasOwnProperty(attr)) { + if (attr === "textContent") { + elt.textContent = aAttributes[attr]; + } else { + elt.setAttribute(attr, aAttributes[attr]); + } + } + } + aParent.appendChild(elt); + return elt; +} + +/** + * Append a text node to an element. + */ +function appendText(aParent, aText) +{ + aParent.appendChild(aParent.ownerDocument.createTextNode(aText)); +} + +/** + * Copy text-related styles from one element to another. + */ +function copyTextStyles(aFrom, aTo) +{ + let win = aFrom.ownerDocument.defaultView; + let style = win.getComputedStyle(aFrom); + aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText; + aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText; + aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText; + aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText; +} + +/** + * Trigger a focus change similar to pressing tab/shift-tab. + */ +function moveFocus(aWin, aDirection) +{ + let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + fm.moveFocus(aWin, null, aDirection, 0); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/cssruleview.xhtml @@ -0,0 +1,53 @@ +<!DOCTYPE html [ + <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> + %htmlDTD; + <!ENTITY % inspectorDTD SYSTEM "chrome://browser/locale/styleinspector.dtd"> + %inspectorDTD; +]> +<!-- ***** 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 the Mozilla Inspector Module. + - + - 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) (original author) + - + - 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 LGPL or the GPL. 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 ***** --> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> + <meta http-equiv="Content-Type" + content="application/xhtml+xml; charset=UTF-8" /> + <link rel="stylesheet" type="text/css" + href="chrome://browser/content/devtools/styleinspector.css" /> + <link rel="stylesheet" type="text/css" + href="chrome://browser/skin/devtools/csshtmltree.css" /> +</head> +<body role="application" id="ruleview-body"></body> +</html>
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/styleinspector.css @@ -0,0 +1,40 @@ +/* ***** 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 the Mozilla Inspector Module. + * + * 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> + * + * 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 ***** */ + +.ruleview-computedlist:not(.styleinspector-open) { + display: none; +}
--- a/browser/devtools/styleinspector/test/browser/Makefile.in +++ b/browser/devtools/styleinspector/test/browser/Makefile.in @@ -47,16 +47,20 @@ include $(topsrcdir)/config/rules.mk _BROWSER_TEST_FILES = \ browser_styleinspector.js \ browser_styleinspector_webconsole.js \ browser_bug683672.js \ browser_styleinspector_bug_672746_default_styles.js \ browser_styleinspector_bug_672744_search_filter.js \ browser_bug_692400_element_style.js \ + browser_ruleview_editor.js \ + browser_ruleview_manipulation.js \ + browser_ruleview_override.js \ + browser_ruleview_ui.js \ head.js \ $(NULL) _BROWSER_TEST_PAGES = \ browser_styleinspector_webconsole.htm \ browser_bug683672.html \ $(NULL)
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_editor.js @@ -0,0 +1,121 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource:///modules/devtools/CssRuleView.jsm"); + +let doc = content.document; + +function expectDone(aValue, aCommit, aNext) +{ + return function(aDoneValue, aDoneCommit) { + dump("aDoneValue: " + aDoneValue + " commit: " + aDoneCommit + "\n"); + + is(aDoneValue, aValue, "Should get expected value"); + is(aDoneCommit, aDoneCommit, "Should get expected commit value"); + aNext(); + } +} + +function clearBody() +{ + doc.body.innerHTML = ""; +} + +function createSpan() +{ + let span = doc.createElement("span"); + span.setAttribute("tabindex", "0"); + span.textContent = "Edit Me!"; + doc.body.appendChild(span); + return span; +} + +function testReturnCommit() +{ + clearBody(); + let span = createSpan(); + _editableField({ + element: span, + initial: "explicit initial", + start: function() { + is(span.inplaceEditor.input.value, "explicit initial", "Explicit initial value should be used."); + span.inplaceEditor.input.value = "Test Value"; + EventUtils.sendKey("return", span.inplaceEditor.input); + }, + done: expectDone("Test Value", true, testBlurCommit) + }); + span.focus(); +} + +function testBlurCommit() +{ + clearBody(); + let span = createSpan(); + _editableField({ + element: span, + start: function() { + is(span.inplaceEditor.input.value, "Edit Me!", "textContent of the span used."); + span.inplaceEditor.input.value = "Test Value"; + span.inplaceEditor.input.blur(); + }, + done: expectDone("Test Value", true, testAdvanceCharCommit) + }); + span.focus(); +} + +function testAdvanceCharCommit() +{ + clearBody(); + let span = createSpan(); + _editableField({ + element: span, + advanceChars: ":", + start: function() { + let input = span.inplaceEditor.input; + for each (let ch in "Test:") { + EventUtils.sendChar(ch, input); + } + }, + done: expectDone("Test", true, testEscapeCancel) + }); + span.focus(); +} + +function testEscapeCancel() +{ + clearBody(); + let span = createSpan(); + _editableField({ + element: span, + initial: "initial text", + start: function() { + span.inplaceEditor.input.value = "Test Value"; + EventUtils.sendKey("escape", span.inplaceEditor.input); + }, + done: expectDone("initial text", false, finishTest) + }); + span.focus(); +} + + +function finishTest() +{ + doc = null; + gBrowser.removeCurrentTab(); + finish(); +} + + +function test() +{ + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); + doc = content.document; + waitForFocus(testReturnCommit, content); + }, true); + + content.location = "data:text/html,inline editor tests"; +} \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_manipulation.js @@ -0,0 +1,70 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource:///modules/devtools/CssRuleView.jsm"); + +let doc; + +function simpleOverride() +{ + doc.body.innerHTML = '<div id="testid">Styled Node</div>'; + let element = doc.getElementById("testid"); + let elementStyle = new _ElementStyle(element); + + let elementRule = elementStyle.rules[0]; + let firstProp = elementRule.createProperty("background-color", "green", ""); + let secondProp = elementRule.createProperty("background-color", "blue", ""); + is(elementRule.textProps[0], firstProp, "Rules should be in addition order."); + is(elementRule.textProps[1], secondProp, "Rules should be in addition order."); + + is(element.style.getPropertyValue("background-color"), "blue", "Second property should have been used."); + + secondProp.remove(); + is(element.style.getPropertyValue("background-color"), "green", "After deleting second property, first should be used."); + + secondProp = elementRule.createProperty("background-color", "blue", ""); + is(element.style.getPropertyValue("background-color"), "blue", "New property should be used."); + + is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places."); + is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places."); + + secondProp.setEnabled(false); + is(element.style.getPropertyValue("background-color"), "green", "After disabling second property, first value should be used"); + + firstProp.setEnabled(false); + is(element.style.getPropertyValue("background-color"), "", "After disabling both properties, value should be empty."); + + secondProp.setEnabled(true); + is(element.style.getPropertyValue("background-color"), "blue", "Value should be set correctly after re-enabling"); + + firstProp.setEnabled(true); + is(element.style.getPropertyValue("background-color"), "blue", "Re-enabling an earlier property shouldn't make it override a later property."); + is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places."); + is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places."); + + firstProp.setValue("purple", ""); + is(element.style.getPropertyValue("background-color"), "blue", "Modifying an earlier property shouldn't override a later property."); + + finishTest(); +} + +function finishTest() +{ + doc = null; + gBrowser.removeCurrentTab(); + finish(); +} + +function test() +{ + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); + doc = content.document; + waitForFocus(simpleOverride, content); + }, true); + + content.location = "data:text/html,basic style inspector tests"; +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_override.js @@ -0,0 +1,134 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource:///modules/devtools/CssRuleView.jsm"); + +let doc; + +function simpleOverride() +{ + let style = '' + + '#testid {' + + ' background-color: blue;' + + '} ' + + '.testclass {' + + ' background-color: green;' + + '}'; + + let styleNode = addStyle(doc, style); + doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>'; + + let elementStyle = new _ElementStyle(doc.getElementById("testid")); + + let idRule = elementStyle.rules[1]; + let idProp = idRule.textProps[0]; + is(idProp.name, "background-color", "First ID prop should be background-color"); + ok(!idProp.overridden, "ID prop should not be overridden."); + + let classRule = elementStyle.rules[2]; + let classProp = classRule.textProps[0]; + is(classProp.name, "background-color", "First class prop should be background-color"); + ok(classProp.overridden, "Class property should be overridden."); + + // Override background-color by changing the element style. + let elementRule = elementStyle.rules[0]; + elementRule.createProperty("background-color", "purple", ""); + let elementProp = elementRule.textProps[0]; + is(classProp.name, "background-color", "First element prop should now be background-color"); + + ok(!elementProp.overridden, "Element style property should not be overridden"); + ok(idProp.overridden, "ID property should be overridden"); + ok(classProp.overridden, "Class property should be overridden"); + + styleNode.parentNode.removeChild(styleNode); + + partialOverride(); +} + +function partialOverride() +{ + let style = '' + + // Margin shorthand property... + '.testclass {' + + ' margin: 2px;' + + '}' + + // ... will be partially overridden. + '#testid {' + + ' margin-left: 1px;' + + '}'; + + let styleNode = addStyle(doc, style); + doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>'; + + let elementStyle = new _ElementStyle(doc.getElementById("testid")); + + let classRule = elementStyle.rules[2]; + let classProp = classRule.textProps[0]; + ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used."); + for each (let computed in classProp.computed) { + if (computed.name.indexOf("margin-left") == 0) { + ok(computed.overridden, "margin-left props should be overridden."); + } else { + ok(!computed.overridden, "Non-margin-left props should not be overridden."); + } + } + + styleNode.parentNode.removeChild(styleNode); + + importantOverride(); +} + +function importantOverride() +{ + let style = '' + + // Margin shorthand property... + '.testclass {' + + ' background-color: green !important;' + + '}' + + // ... will be partially overridden. + '#testid {' + + ' background-color: blue;' + + '}'; + let styleNode = addStyle(doc, style); + doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>'; + + let elementStyle = new _ElementStyle(doc.getElementById("testid")); + + let idRule = elementStyle.rules[1]; + let idProp = idRule.textProps[0]; + ok(idProp.overridden, "Not-important rule should be overridden."); + + let classRule = elementStyle.rules[2]; + let classProp = classRule.textProps[0]; + ok(!classProp.overridden, "Important rule should not be overridden."); + + styleNode.parentNode.removeChild(styleNode); + + let elementRule = elementStyle.rules[0]; + let elementProp = elementRule.createProperty("background-color", "purple", "important"); + ok(classProp.overridden, "New important prop should override class property."); + ok(!elementProp.overridden, "New important prop should not be overriden."); + + finishTest(); +} + +function finishTest() +{ + doc = null; + gBrowser.removeCurrentTab(); + finish(); +} + +function test() +{ + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); + doc = content.document; + waitForFocus(simpleOverride, content); + }, true); + + content.location = "data:text/html,basic style inspector tests"; +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_ui.js @@ -0,0 +1,180 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource:///modules/devtools/CssRuleView.jsm"); + +let doc; +let ruleDialog; +let ruleView; + +function waitForEditorFocus(aParent, aCallback) +{ + aParent.addEventListener("focus", function onFocus(evt) { + if (evt.target.inplaceEditor) { + aParent.removeEventListener("focus", onFocus, true); + let editor = evt.target.inplaceEditor; + executeSoon(function() { + aCallback(editor); + }); + } + }, true); +} + +function waitForEditorBlur(aEditor, aCallback) +{ + let input = aEditor.input; + input.addEventListener("blur", function onBlur() { + input.removeEventListener("blur", onBlur, false); + executeSoon(function() { + aCallback(); + }); + }, false); +} + +function startTest() +{ + let style = '' + + '#testid {' + + ' background-color: blue;' + + '} ' + + '.testclass {' + + ' background-color: green;' + + '}'; + + let styleNode = addStyle(doc, style); + doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>'; + let testElement = doc.getElementById("testid"); + + ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xhtml", + "cssruleviewtest", + "width=200,height=350"); + ruleDialog.addEventListener("load", function onLoad(evt) { + ruleDialog.removeEventListener("load", onLoad); + let doc = ruleDialog.document; + let body = doc.getElementById("ruleview-body"); + ruleView = new CssRuleView(doc); + body.appendChild(ruleView.element); + ruleView.highlight(testElement); + waitForFocus(testCancelNew, ruleDialog); + }, true); +} + +function testCancelNew() +{ + // Start at the beginning: start to add a rule to the element's style + // declaration, but leave it empty. + + let elementRuleEditor = ruleView.element.children[0]._ruleEditor; + waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) { + is(elementRuleEditor.newPropSpan.inplaceEditor, aEditor, "Next focused editor should be the new property editor."); + let input = aEditor.input; + waitForEditorBlur(aEditor, function () { + is(elementRuleEditor.rule.textProps.length, 0, "Should have canceled creating a new text property."); + ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties."); + testCreateNew(); + }); + aEditor.input.blur(); + }); + + EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1, + { }, + ruleDialog); +} + +function testCreateNew() +{ + // Create a new property. + let elementRuleEditor = ruleView.element.children[0]._ruleEditor; + waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) { + is(elementRuleEditor.newPropSpan.inplaceEditor, aEditor, "Next focused editor should be the new property editor."); + let input = aEditor.input; + input.value = "background-color"; + + waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) { + 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]; + is(aEditor, textProp.editor.valueSpan.inplaceEditor, "Should be editing the value span now."); + + aEditor.input.value = "purple"; + waitForEditorBlur(aEditor, function() { + is(textProp.value, "purple", "Text prop should have been changed."); + testEditProperty(); + }); + + aEditor.input.blur(); + }); + EventUtils.sendKey("return", input); + }); + + EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1, + { }, + ruleDialog); +} + +function testEditProperty() +{ + let idRuleEditor = ruleView.element.children[1]._ruleEditor; + let propEditor = idRuleEditor.rule.textProps[0].editor; + waitForEditorFocus(propEditor.element, function onNewElement(aEditor) { + is(propEditor.nameSpan.inplaceEditor, aEditor, "Next focused editor should be the name editor."); + let input = aEditor.input; + waitForEditorFocus(propEditor.element, function onNewName(aEditor) { + input = aEditor.input; + is(propEditor.valueSpan.inplaceEditor, aEditor, "Focus should have moved to the value."); + + waitForEditorBlur(aEditor, function() { + is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red", + "border-color should have been set."); + testDisableProperty(); + }); + + for each (let ch in "red;") { + EventUtils.sendChar(ch, input); + } + }); + for each (let ch in "border-color:") { + EventUtils.sendChar(ch, input); + } + }); + + EventUtils.synthesizeMouse(propEditor.nameSpan, 1, 1, + { }, + ruleDialog); +} + +function testDisableProperty() +{ + let idRuleEditor = ruleView.element.children[1]._ruleEditor; + let propEditor = idRuleEditor.rule.textProps[0].editor; + + propEditor.enable.click(); + is(idRuleEditor.rule.style.getPropertyValue("border-color"), "", "Border-color should have been unset."); + propEditor.enable.click(); + is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red", + "Border-color should have been reset."); + finishTest(); +} + +function finishTest() +{ + ruleDialog.close(); + ruleDialog = ruleView = null; + doc = null; + gBrowser.removeCurrentTab(); + finish(); +} + +function test() +{ + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); + doc = content.document; + waitForFocus(startTest, content); + }, true); + + content.location = "data:text/html,basic style inspector tests"; +}
--- a/browser/devtools/styleinspector/test/browser/head.js +++ b/browser/devtools/styleinspector/test/browser/head.js @@ -145,16 +145,25 @@ function testLogEntry(aOutputNode, aMatc * @param string aString * The string to find. */ function findLogEntry(aString) { testLogEntry(outputNode, aString, "found " + aString); } +function addStyle(aDocument, aString) +{ + let node = aDocument.createElement('style'); + node.setAttribute("type", "text/css"); + node.textContent = aString; + aDocument.getElementsByTagName("head")[0].appendChild(node); + return node; +} + function openConsole() { HUDService.activateHUDForContext(tab); } function closeConsole() { HUDService.deactivateHUDForContext(tab);
--- a/browser/devtools/webconsole/HUDService.jsm +++ b/browser/devtools/webconsole/HUDService.jsm @@ -80,16 +80,22 @@ XPCOMUtils.defineLazyGetter(this, "gcli" }); XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () { var obj = {}; Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj); return obj.StyleInspector; }); +XPCOMUtils.defineLazyGetter(this, "CssRuleView", function() { + let tmp = {}; + Cu.import("resource:///modules/devtools/CssRuleView.jsm", tmp); + return tmp.CssRuleView; +}); + XPCOMUtils.defineLazyGetter(this, "NetUtil", function () { var obj = {}; Cu.import("resource://gre/modules/NetUtil.jsm", obj); return obj.NetUtil; }); XPCOMUtils.defineLazyGetter(this, "PropertyPanel", function () { var obj = {}; @@ -4595,16 +4601,63 @@ function JSTermHelper(aJSTerm) styleInspector.panel.setAttribute("hudToolId", aJSTerm.hudId); styleInspector.open(aNode); }); } else { aJSTerm.writeOutput(errstr + "\n", CATEGORY_OUTPUT, SEVERITY_ERROR); } }; + aJSTerm.sandbox.inspectrules = function JSTH_inspectrules(aNode) + { + aJSTerm.helperEvaluated = true; + let doc = aJSTerm.parentNode.ownerDocument; + let win = doc.defaultView; + let panel = createElement(doc, "panel", { + label: "CSS Rules", + titlebar: "normal", + noautofocus: "true", + noautohide: "true", + close: "true", + width: 350, + height: (win.screen.height / 2) + }); + + let iframe = createAndAppendElement(panel, "iframe", { + src: "chrome://browser/content/devtools/cssruleview.xhtml", + flex: "1", + }); + + panel.addEventListener("load", function onLoad() { + panel.removeEventListener("load", onLoad, true); + let doc = iframe.contentDocument; + let view = new CssRuleView(doc); + let body = doc.getElementById("ruleview-body"); + body.appendChild(view.element); + view.highlight(aNode); + }, true); + + let parent = doc.getElementById("mainPopupSet"); + parent.appendChild(panel); + + panel.addEventListener("popuphidden", function onHide() { + panel.removeEventListener("popuphidden", onHide); + parent.removeChild(panel); + }); + + let footer = createElement(doc, "hbox", { align: "end" }); + createAndAppendElement(footer, "spacer", { flex: 1}); + createAndAppendElement(footer, "resizer", { dir: "bottomend" }); + panel.appendChild(footer); + + let anchor = win.gBrowser.selectedBrowser; + panel.openPopup(anchor, "end_before", 0, 0, false, false); + + } + /** * Prints aObject to the output. * * @param object aObject * Object to print to the output. * @returns void */ aJSTerm.sandbox.pprint = function JSTH_pprint(aObject)
--- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -208,19 +208,20 @@ can reach it easily. --> - "Scratchpad" in your locale. You should feel free to find a close - approximation to it or choose a word (or words) that means - "simple discardable text editor". --> <!ENTITY scratchpad.label "Scratchpad"> <!ENTITY scratchpad.accesskey "s"> <!ENTITY scratchpad.keycode "VK_F4"> <!ENTITY scratchpad.keytext "F4"> -<!ENTITY inspectPanelTitle.label "HTML"> -<!ENTITY inspectButton.label "Inspect"> -<!ENTITY inspectButton.accesskey "I"> +<!ENTITY inspectPanelTitle.label "HTML"> +<!ENTITY inspectButton.label "Inspect"> +<!ENTITY inspectButton.accesskey "I"> +<!ENTITY inspectCloseButton.tooltiptext "Close Inspector"> <!ENTITY getMoreDevtoolsCmd.label "Get More Tools"> <!ENTITY getMoreDevtoolsCmd.accesskey "M"> <!ENTITY fileMenu.label "File"> <!ENTITY fileMenu.accesskey "F"> <!ENTITY newNavigatorCmd.label "New Window"> <!ENTITY newNavigatorCmd.key "N">
--- a/browser/themes/gnomestripe/browser/browser.css +++ b/browser/themes/gnomestripe/browser/browser.css @@ -1946,26 +1946,26 @@ panel[dimmed="true"] { } /* Highlighter */ .highlighter-veil { background-color: rgba(0, 0, 0, 0.5); } -#highlighter-close-button { - list-style-image: url("chrome://browser/skin/KUI-close.png"); - top: 12px; - right: 12px; - cursor: pointer; +#highlighter-closebutton { + list-style-image: url("moz-icon://stock/gtk-close?size=menu"); + margin-top: 0; + margin-bottom: 0; } -#highlighter-close-button:-moz-locale-dir(rtl) { - right: auto; - left: 12px; +#highlighter-closebutton > .toolbarbutton-icon { + /* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must + use evil CSS to give the impression of smaller content */ + margin: -4px; } #highlighter-veil-transparentbox { box-shadow: 0 0 0 1px rgba(0,0,0,0.5); outline: 1px dashed rgba(255,255,255,0.5); outline-offset: -1px; } @@ -1973,22 +1973,27 @@ panel[dimmed="true"] { box-shadow: 0 0 0 1px black; outline-color: white; } /* Highlighter toolbar */ #inspector-toolbar { -moz-appearance: none; - padding: 0 3px 4px; + padding: 4px 3px; border-top: 1px solid hsla(210, 8%, 5%, .65); box-shadow: 0 1px 0 0 hsla(210, 16%, 76%, .2) inset; background-image: -moz-linear-gradient(top, hsl(210,11%,36%), hsl(210,11%,18%)); } +#inspector-toolbar[treepanel-open] { + padding-top: 0; + -moz-padding-end: 0; +} + #inspector-inspect-toolbutton, #inspector-tools > toolbarbutton { -moz-appearance: none; min-width: 78px; min-height: 22px; color: hsl(210,30%,85%); text-shadow: 0 -1px 0 hsla(210,8%,5%,.45); border: 1px solid hsla(210,8%,5%,.45); @@ -2024,20 +2029,16 @@ panel[dimmed="true"] { /* Highlighter - toolbar resizers */ .inspector-resizer { -moz-appearance: none; cursor: n-resize; } -.inspector-resizer[disabled] { - visibility: hidden; -} - #inspector-top-resizer { background: none; height: 4px; } #inspector-end-resizer { width: 12px; height: 8px;
--- a/browser/themes/gnomestripe/browser/devtools/csshtmltree.css +++ b/browser/themes/gnomestripe/browser/devtools/csshtmltree.css @@ -182,9 +182,94 @@ a.link:visited { .userStyles { position: relative; top: 3px; } .userStyles, .userStylesLabel { cursor: pointer; -} \ No newline at end of file +} + +/** + * CSS Rule View + */ + +.ruleview { + background-color: #FFF; +} + +.ruleview-rule-source { + background-color: -moz-dialog; + padding: 2px; +} + +.ruleview-code { + padding: 2px; +} + +.ruleview-propertylist { + list-style: none; + padding: 0; + margin: 0; +} + +.ruleview-enableproperty { + height: 10px; + width: 10px; + -moz-margin-start: 2px; + -moz-margin-end: 0; +} + +.ruleview-expander { + display: inline-block; + width: 8px; + height: 8px; + background: url("chrome://browser/skin/devtools/arrows.png") 24px 0; + cursor: pointer; + -moz-margin-start: 2px; + -moz-margin-end: 5px; +} + +.ruleview-expander[dir="rtl"] { + background-position: 16px 0; +} + +.ruleview-expander.styleinspector-open { + background-position: 8px 0; +} + +.ruleview-newproperty { + /* (enable checkbox width: 12px) + (expander width: 15px) */ + -moz-margin-start: 27px; +} + +.ruleview-propertyname { + display: inline-block; + padding: 1px 0; + cursor: text; + color: #0060C0; + text-decoration: inherit; +} + +.ruleview-propertyvalue { + cursor: text; + text-decoration: inherit; +} + +.ruleview-computedlist { + list-style: none; + padding: 0; +} + +.ruleview-computed { + -moz-margin-start: 4em; +} + +.ruleview-overridden { + text-decoration: line-through; +} + +.styleinspector-propertyeditor { + border: 1px solid #CCC; + padding: 0; + -moz-box-shadow: 2px 2px 2px #CCC; +}
--- a/browser/themes/pinstripe/browser/browser.css +++ b/browser/themes/pinstripe/browser/browser.css @@ -2517,26 +2517,34 @@ panel[dimmed="true"] { /* Highlighter */ .highlighter-veil { background-color: rgba(0, 0, 0, 0.5); } -#highlighter-close-button { - list-style-image: url("chrome://browser/skin/KUI-close.png"); - top: 12px; - right: 12px; - cursor: pointer; -} - -#highlighter-close-button:-moz-locale-dir(rtl) { - right: auto; - left: 12px; +#highlighter-closebutton { + list-style-image: url("chrome://browser/skin/devtools/toolbarbutton-close.png"); + -moz-image-region: rect(0, 16px, 16px, 0); + min-width: 16px; + width: 16px; + margin: 0 4px; +} + +#highlighter-closebutton > .toolbarbutton-text { + display: none; +} + +#highlighter-closebutton:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +#highlighter-closebutton:active { + -moz-image-region: rect(0, 48px, 16px, 32px); } #highlighter-veil-transparentbox { box-shadow: 0 0 0 1px rgba(0,0,0,0.5); outline: 1px dashed rgba(255,255,255,0.5); outline-offset: -1px; } @@ -2544,20 +2552,24 @@ panel[dimmed="true"] { box-shadow: 0 0 0 1px black; outline-color: white; } /* Highlighter toolbar */ #inspector-toolbar { -moz-appearance: none; - padding: 0 3px 4px; border-top: 1px solid hsla(210, 8%, 5%, .65); box-shadow: 0 1px 0 0 hsla(210, 16%, 76%, .2) inset; background-image: -moz-linear-gradient(top, hsl(210,11%,36%), hsl(210,11%,18%)); + padding: 4px 16px 4px 0; /* use -moz-padding-end: 16px when/if bug 631729 gets fixed */ +} + +#inspector-toolbar[treepanel-open] { + padding: 0 0 4px; } #inspector-inspect-toolbutton, #inspector-tools > toolbarbutton { -moz-appearance: none; min-width: 78px; min-height: 22px; color: hsl(210,30%,85%); @@ -2595,20 +2607,16 @@ panel[dimmed="true"] { /* Highlighter - toolbar resizers */ .inspector-resizer { -moz-appearance: none; cursor: n-resize; } -.inspector-resizer[disabled] { - visibility: hidden; -} - #inspector-top-resizer { background: none; height: 4px; } #inspector-end-resizer { width: 12px; height: 8px;
--- a/browser/themes/pinstripe/browser/devtools/csshtmltree.css +++ b/browser/themes/pinstripe/browser/devtools/csshtmltree.css @@ -182,9 +182,94 @@ a.link:visited { .userStyles { position: relative; top: 3px; } .userStyles, .userStylesLabel { cursor: pointer; -} \ No newline at end of file +} + +/** + * CSS Rule View + */ + +.ruleview { + background-color: #FFF; +} + +.ruleview-rule-source { + background-color: -moz-dialog; + padding: 2px; +} + +.ruleview-code { + padding: 2px; +} + +.ruleview-propertylist { + list-style: none; + padding: 0; + margin: 0; +} + +.ruleview-enableproperty { + height: 10px; + width: 10px; + -moz-margin-start: 2px; + -moz-margin-end: 0; +} + +.ruleview-expander { + display: inline-block; + width: 8px; + height: 8px; + background: url("chrome://browser/skin/devtools/arrows.png") 24px 0; + cursor: pointer; + -moz-margin-start: 2px; + -moz-margin-end: 5px; +} + +.ruleview-expander[dir="rtl"] { + background-position: 16px 0; +} + +.ruleview-expander.styleinspector-open { + background-position: 8px 0; +} + +.ruleview-newproperty { + /* (enable checkbox width: 12px) + (expander width: 15px) */ + -moz-margin-start: 27px; +} + +.ruleview-propertyname { + display: inline-block; + padding: 1px 0; + cursor: text; + color: #0060C0; + text-decoration: inherit; +} + +.ruleview-propertyvalue { + cursor: text; + text-decoration: inherit; +} + +.ruleview-computedlist { + list-style: none; + padding: 0; +} + +.ruleview-computed { + -moz-margin-start: 4em; +} + +.ruleview-overridden { + text-decoration: line-through; +} + +.styleinspector-propertyeditor { + border: 1px solid #CCC; + padding: 0; + -moz-box-shadow: 2px 2px 2px #CCC; +}
new file mode 100644 index 0000000000000000000000000000000000000000..887daf052a6eda0b463bd75ca65dd998449cd330 GIT binary patch literal 1031 zc$@(T1o-=jP)<h;3K|Lk000e1NJLTq001xm000mO1^@s6P_F#3000BdNkl<Zc-qyL z`%6;+6vrzxC|2N<)IIeV(`|0ri^4K3dk{pC6+J16%F13^Nf9Jj8A_#<W<vDRirxho zK3ZkYHudh_HIt>573nYalmCEyzi@?1xZPj;#UDO+IOlt|^L6KJcihm>(3oaaUPSC3 z7!Sh0B%lFPf}o*>TJ|XV$pjD%CaORUwWDevZ2rQ<nMQMbO>~UzJ#Yr$JvG#_N6|mQ znw*lM(;Mrc=?eul)Uszp0}zpPFqN^424sL>1}5W$U!r~sRDcJd3Y5uQ2M<}JhFbP0 z`om*%1}|z|;IlR|s(V^=tRJyN7$eui8a33iCzydnyY?M^ot!fBqHIEAu{lci<mSId zyflb`DX2XL7r_18L-`)7&B0Hi6qF<;S)B*-j<?YSHPo_4(LWKnZXjy4Q9TKXmbY4M zWG@DnNJM1Rx9Lf?rU-3RFE!M%XG8-|Pv<5SJ6<)lx@dyKzo>6%Vb1CHZwq0d1{9Z; zRn+$X8T7l_B++8Ew<cO_uI5jI$Xt0v^#f|C9l<~Vh-vAWH=Q2;vi(O2j>5!u4BeM= zAisdQ1({hlsG&Bf0Td#H!0$)HCWePc?m7NOhA?msoKH!eb*HQMSL49nL9wMx^ftQ% zFXOL${cmT@nRk~4s10JkYcR!mj-M*ZZgfd$dk-DU+nt+tjPa?WlGP@2yoZ{Q2Ds<N zmnseQ&B8klPVfTFXG^cfv8H0+45+l(Q{H&Iou4qcmk9oV+mSl6jx}mR7!bf`ox$YW zx#z&P2A4RG<QzP@S#LB+tWl$8K%-n6<vQ^uR16e?%hQvr!iUz5b__3&7Pq(EVzcv& z%c%)rpaVPa!`l4ZynT0W{YPOo*}5Yq2S@yaHEKc{n47k6rE+bQ>$D&vD_g}t5svo> zcb;n#`2M@;OV7^+d7UJ?W5!czLVW_$qIG_(O##>DWMT2Dyc4Ih8S^HhV`BZ(sNKL? z<<2W_0&G+<P>e&p*Z+6WhvA)An?`H0qk%WUTxIp`T9q3R(-$qNYHIbRbLVjnlHOpJ zcoWQJW-YIvM)d|zT+iVd6PjR-vC6m5d2pxn^0oV^GiE<We3K;GQ=c)%n##A37j(^; zJHOm$ifc#QN%Tf@2Xm~c9I+}vf5`BL9PR?R1a5$<;GE1A=2)YK+W&vZ^ahh#{_jfi z;65M_#&S-ghFbQFrh)JcTXw9$VAUK{rV3ON#>07PsAZ4xQDWJORqJFEUleo_WsVwZ z*)!S?a~cp25>%i@Gs45%Bwz$)6^zs>ALhn1{{Yzl!O-c}?LPnj002ovPDHLkV1g8J B@QeTe
--- a/browser/themes/pinstripe/browser/jar.mn +++ b/browser/themes/pinstripe/browser/jar.mn @@ -121,16 +121,17 @@ browser.jar: skin/classic/browser/tabview/search.png (tabview/search.png) skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png) skin/classic/browser/tabview/tabview.png (tabview/tabview.png) skin/classic/browser/tabview/tabview.css (tabview/tabview.css) skin/classic/browser/devtools/arrows.png (devtools/arrows.png) skin/classic/browser/devtools/search.png (devtools/search.png) skin/classic/browser/devtools/csshtmltree.css (devtools/csshtmltree.css) skin/classic/browser/devtools/gcli.css (devtools/gcli.css) + skin/classic/browser/devtools/toolbarbutton-close.png (devtools/toolbarbutton-close.png) skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png (devtools/breadcrumbs/ltr-end-pressed.png) skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png (devtools/breadcrumbs/ltr-end-selected-pressed.png) skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png (devtools/breadcrumbs/ltr-end-selected.png) skin/classic/browser/devtools/breadcrumbs/ltr-end.png (devtools/breadcrumbs/ltr-end.png) skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png (devtools/breadcrumbs/ltr-middle-pressed.png) skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png (devtools/breadcrumbs/ltr-middle-selected-pressed.png) skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png (devtools/breadcrumbs/ltr-middle-selected.png) skin/classic/browser/devtools/breadcrumbs/ltr-middle.png (devtools/breadcrumbs/ltr-middle.png)
--- a/browser/themes/winstripe/browser/browser.css +++ b/browser/themes/winstripe/browser/browser.css @@ -2615,26 +2615,29 @@ panel[dimmed="true"] { } /* Highlighter */ .highlighter-veil { background-color: rgba(0, 0, 0, 0.5); } -#highlighter-close-button { - list-style-image: url("chrome://browser/skin/KUI-close.png"); - top: 12px; - right: 12px; - cursor: pointer; -} - -#highlighter-close-button:-moz-locale-dir(rtl) { - right: auto; - left: 12px; +#highlighter-closebutton { + list-style-image: url("chrome://browser/skin/devtools/toolbarbutton-close.png"); + -moz-image-region: rect(0, 16px, 16px, 0); + min-width: 16px; + width: 16px; +} + +#highlighter-closebutton:hover { + -moz-image-region: rect(0, 32px, 16px, 16px); +} + +#highlighter-closebutton:active { + -moz-image-region: rect(0, 48px, 16px, 32px); } #highlighter-veil-transparentbox { box-shadow: 0 0 0 1px rgba(0,0,0,0.5); outline: 1px dashed rgba(255,255,255,0.5); outline-offset: -1px; } @@ -2642,22 +2645,27 @@ panel[dimmed="true"] { box-shadow: 0 0 0 1px black; outline-color: white; } /* Highlighter toolbar */ #inspector-toolbar { -moz-appearance: none; - padding: 0 3px 4px; + padding: 4px 3px; border-top: 1px solid hsla(211,68%,6%,.65) !important; box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset; background-image: -moz-linear-gradient(top, hsl(209,18%,34%), hsl(210,24%,16%)); } +#inspector-toolbar[treepanel-open] { + padding-top: 0; + -moz-padding-end: 0; +} + #inspector-inspect-toolbutton, #inspector-tools > toolbarbutton { -moz-appearance: none; min-width: 78px; min-height: 22px; color: hsl(210,30%,85%); text-shadow: 0 -1px 0 hsla(210,8%,5%,.45); border: 1px solid hsla(211,68%,6%,.5); @@ -2693,20 +2701,16 @@ panel[dimmed="true"] { /* Highlighter - toolbar resizers */ .inspector-resizer { -moz-appearance: none; cursor: n-resize; } -.inspector-resizer[disabled] { - visibility: hidden; -} - #inspector-top-resizer { background: none; height: 4px; } #inspector-end-resizer { width: 12px; height: 8px;
--- a/browser/themes/winstripe/browser/devtools/csshtmltree.css +++ b/browser/themes/winstripe/browser/devtools/csshtmltree.css @@ -182,9 +182,94 @@ a.link:visited { .userStyles { position: relative; top: 3px; } .userStyles, .userStylesLabel { cursor: pointer; -} \ No newline at end of file +} + +/** + * CSS Rule View + */ + +.ruleview { + background-color: #FFF; +} + +.ruleview-rule-source { + background-color: -moz-dialog; + padding: 2px; +} + +.ruleview-code { + padding: 2px; +} + +.ruleview-propertylist { + list-style: none; + padding: 0; + margin: 0; +} + +.ruleview-enableproperty { + height: 10px; + width: 10px; + -moz-margin-start: 2px; + -moz-margin-end: 0; +} + +.ruleview-expander { + display: inline-block; + width: 8px; + height: 8px; + background: url("chrome://browser/skin/devtools/arrows.png") 24px 0; + cursor: pointer; + -moz-margin-start: 2px; + -moz-margin-end: 5px; +} + +.ruleview-expander[dir="rtl"] { + background-position: 16px 0; +} + +.ruleview-expander.styleinspector-open { + background-position: 8px 0; +} + +.ruleview-newproperty { + /* (enable checkbox width: 12px) + (expander width: 15px) */ + -moz-margin-start: 27px; +} + +.ruleview-propertyname { + display: inline-block; + padding: 1px 0; + cursor: text; + color: #0060C0; + text-decoration: inherit; +} + +.ruleview-propertyvalue { + cursor: text; + text-decoration: inherit; +} + +.ruleview-computedlist { + list-style: none; + padding: 0; +} + +.ruleview-computed { + -moz-margin-start: 4em; +} + +.ruleview-overridden { + text-decoration: line-through; +} + +.styleinspector-propertyeditor { + border: 1px solid #CCC; + padding: 0; + -moz-box-shadow: 2px 2px 2px #CCC; +}
new file mode 100644 index 0000000000000000000000000000000000000000..4a58465b9b7ea3b3c7c14b36214524fb9b0e38b7 GIT binary patch literal 898 zc$@)(1AY97P)<h;3K|Lk000e1NJLTq001xm000mO1^@s6P_F#30009?Nkl<Zc-qaD z-%C?*7{<E^ys6=GX#TF*%%jfDG@>G?$RO%(D9DVE2wO>|IdyGXXbKuAMWsXumi@rW zizxG=3lSo^$*@vs{TpxZ=io>?+i~PBx_CVA-uLse=lObewsj)1Carg5ulx8l>ikEo z`ThB`CSjy(=l*>gv)qjuW-uASvcvAuAyldwlHG@8CuIRJG;-)bbn4h)nLJiwD;QXI zVgb^ed4Z!F9MR(rht#I0ORduxSIl5w*-2RdPGw^6Zh7SQ%fnR!1Itb<0CjyvhP3AA zOXt?Da^3BYD`qgT?4&fn^PSZ)tmf|fd~&a%LWcCpYeY?t8LySK1h|l$Enzj-?eR$0 zwr%G1W^plE(Sw0a7-0I->}l1E>J%SPL`{!bq5xynRi??w&KnY4h|1e=hurac5j8z# zCZnt+Kw$G`xu)h`UyI03Wyjk>a-*mSQPX2)GO!5)=%A`8+|&1d`tt{jBWilg5(OCD zQ7O08Y`<a1%hr%Aep{3``i6*_9y1taEdd(TRNIy<5>kUtnu9Vww;(Tq?TDHlGn0X} z3$P-@a`Vg*1sJLH%V2rA45)fbN@QFOFQ{=uO^+FjvX%hnva%#tSSS%a@m4kYcqs&; zrpF8h);0hd93E|&n3{QqsOi}Sc;NR*e_5IIsd^2=H2r<{2co9O%w&|c1UQ?SDa{21 z((ZPfW=ES_WbVs%L`{#G$-vqMSbp0eYI=47hI|#$Q(7wBdeW~#VLkZ;c@}I()byB{ z46NlTq^2(G6XvQK9cyVbPkyAS1yR#uW-|450sg+{(ee<90^F_eN|#}nM(5S=Q#Bs( zcn~!`W+tPoCBPXq)u`sif`80k($hw<5X@j;?fwIms=C-ckErP}OBCR?-Wr|Tw@XML zcR{zCE3f1Bl(ht?bGamtn=6fZd2(K_7xj8Y?{`E^j~NVXioXX4P%0hz%#N%g7-cO1 zPB@)XuOH5aoE*%?6*CxEc2fCMB5>U>76;VWty05QFtF^z0;K17E*^2DFCJH8C-qyl z*5!&TW-zeqq%44U*TFp)O7_PXEIY9P{yjB&F_i2+EIaJ}4?m}M|5uu7lPWt&!KShM Y8%Lp~>GYClRsaA107*qoM6N<$g8i4Sr2qf`
--- a/browser/themes/winstripe/browser/jar.mn +++ b/browser/themes/winstripe/browser/jar.mn @@ -105,16 +105,17 @@ browser.jar: skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png) skin/classic/browser/tabview/tabview.png (tabview/tabview.png) skin/classic/browser/tabview/tabview-inverted.png (tabview/tabview-inverted.png) skin/classic/browser/tabview/tabview.css (tabview/tabview.css) skin/classic/browser/devtools/arrows.png (devtools/arrows.png) skin/classic/browser/devtools/search.png (devtools/search.png) skin/classic/browser/devtools/csshtmltree.css (devtools/csshtmltree.css) skin/classic/browser/devtools/gcli.css (devtools/gcli.css) + skin/classic/browser/devtools/toolbarbutton-close.png (devtools/toolbarbutton-close.png) skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png (devtools/breadcrumbs/ltr-end-pressed.png) skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png (devtools/breadcrumbs/ltr-end-selected-pressed.png) skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png (devtools/breadcrumbs/ltr-end-selected.png) skin/classic/browser/devtools/breadcrumbs/ltr-end.png (devtools/breadcrumbs/ltr-end.png) skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png (devtools/breadcrumbs/ltr-middle-pressed.png) skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png (devtools/breadcrumbs/ltr-middle-selected-pressed.png) skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png (devtools/breadcrumbs/ltr-middle-selected.png) skin/classic/browser/devtools/breadcrumbs/ltr-middle.png (devtools/breadcrumbs/ltr-middle.png) @@ -256,16 +257,17 @@ browser.jar: skin/classic/aero/browser/tabview/stack-expander.png (tabview/stack-expander.png) skin/classic/aero/browser/tabview/tabview.png (tabview/tabview.png) skin/classic/aero/browser/tabview/tabview-inverted.png (tabview/tabview-inverted.png) skin/classic/aero/browser/tabview/tabview.css (tabview/tabview.css) skin/classic/aero/browser/devtools/arrows.png (devtools/arrows.png) skin/classic/aero/browser/devtools/search.png (devtools/search.png) skin/classic/aero/browser/devtools/csshtmltree.css (devtools/csshtmltree.css) skin/classic/aero/browser/devtools/gcli.css (devtools/gcli.css) + skin/classic/aero/browser/devtools/toolbarbutton-close.png (devtools/toolbarbutton-close.png) skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-pressed.png (devtools/breadcrumbs/ltr-end-pressed.png) skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png (devtools/breadcrumbs/ltr-end-selected-pressed.png) skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected.png (devtools/breadcrumbs/ltr-end-selected.png) skin/classic/aero/browser/devtools/breadcrumbs/ltr-end.png (devtools/breadcrumbs/ltr-end.png) skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-pressed.png (devtools/breadcrumbs/ltr-middle-pressed.png) skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png (devtools/breadcrumbs/ltr-middle-selected-pressed.png) skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-selected.png (devtools/breadcrumbs/ltr-middle-selected.png) skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle.png (devtools/breadcrumbs/ltr-middle.png)
--- a/dom/system/NetworkGeolocationProvider.js +++ b/dom/system/NetworkGeolocationProvider.js @@ -212,17 +212,17 @@ WifiGeoPositionProvider.prototype = { function encode(ap) { // make sure that the ssid doesn't contain any | chars. ap.ssid = ap.ssid.replace("|", "\\|"); // gls service parses the | as fields return "&wifi=mac:"+ap.mac+"|ssid:"+ap.ssid+"|ss:"+ap.signal; }; if (accessPoints) { - accessPoints.sort(sort).map(encode).join(""); + providerUrl = providerUrl + accessPoints.sort(sort).map(encode).join(""); // max length is 2k. make sure we are under that let x = providerUrl.length - 2000; if (x >= 0) { // we need to trim let doomed = providerUrl.lastIndexOf("&", 2000); LOG("Doomed:"+doomed); providerUrl = providerUrl.substring(0, doomed); }
--- a/layout/generic/nsObjectFrame.cpp +++ b/layout/generic/nsObjectFrame.cpp @@ -822,26 +822,32 @@ nsObjectFrame::CallSetWindow(bool aCheck nsIFrame* rootFrame = rootPC->PresShell()->FrameManager()->GetRootFrame(); nsRect bounds = GetContentRectRelativeToSelf() + GetOffsetToCrossDoc(rootFrame); nsIntRect intBounds = bounds.ToNearestPixels(appUnitsPerDevPixel); window->x = intBounds.x; window->y = intBounds.y; window->width = intBounds.width; window->height = intBounds.height; - // this will call pi->SetWindow and take care of window subclassing - // if needed, see bug 132759. + // Calling SetWindow might destroy this frame. We need to use the instance + // owner to clean up so hold a ref. + nsRefPtr<nsPluginInstanceOwner> instanceOwnerRef(mInstanceOwner); + + // This will call pi->SetWindow and take care of window subclassing + // if needed, see bug 132759. Calling SetWindow can destroy this frame + // so check for that before doing anything else with this frame's memory. if (mInstanceOwner->UseAsyncRendering()) { rv = pi->AsyncSetWindow(window); } else { rv = window->CallSetWindow(pi); } - mInstanceOwner->ReleasePluginPort(window->window); + instanceOwnerRef->ReleasePluginPort(window->window); + return rv; } bool nsObjectFrame::IsFocusable(PRInt32 *aTabIndex, bool aWithMouse) { if (aTabIndex) *aTabIndex = -1;
--- a/toolkit/content/widgets/videocontrols.xml +++ b/toolkit/content/widgets/videocontrols.xml @@ -472,16 +472,17 @@ this.setupStatusFader(); break; case "ended": this.setPlayButtonState(true); // We throttle timechange events, so the thumb might not be // exactly at the end when the video finishes. this.showPosition(Math.round(this.video.currentTime * 1000), Math.round(this.video.duration * 1000)); + this.startFadeIn(this.controlBar); this.setupStatusFader(); break; case "volumechange": var volume = this.video.muted ? 0 : Math.round(this.video.volume * 100); this.setMuteButtonState(this.video.muted); this.volumeControl.value = volume; break; case "loadedmetadata": @@ -1018,17 +1019,18 @@ else newval = oldval + maxtime / 10; this.video.currentTime = (newval <= maxtime ? newval : maxtime); break; case "home": /* Seek to beginning */ this.video.currentTime = 0; break; case "end": /* Seek to end */ - this.video.currentTime = (this.video.duration || this.maxCurrentTimeSeen / 1000); + if (this.video.currentTime != this.video.duration) + this.video.currentTime = (this.video.duration || this.maxCurrentTimeSeen / 1000); break; default: return; } } catch(e) { /* ignore any exception from setting .currentTime */ } event.preventDefault(); // Prevent page scrolling },