Merge m-c to s-c.
Merge m-c to s-c.
--- a/accessible/src/atk/nsAccessibleWrap.cpp
+++ b/accessible/src/atk/nsAccessibleWrap.cpp
@@ -866,20 +866,21 @@ refChildCB(AtkObject *aAtkObj, gint aChi
return nsnull;
AtkObject* childAtkObj = nsAccessibleWrap::GetAtkObject(accChild);
NS_ASSERTION(childAtkObj, "Fail to get AtkObj");
if (!childAtkObj)
return nsnull;
g_object_ref(childAtkObj);
-
- //this will addref parent
+
+ if (aAtkObj != childAtkObj->accessible_parent)
atk_object_set_parent(childAtkObj, aAtkObj);
- return childAtkObj;
+
+ return childAtkObj;
}
gint
getIndexInParentCB(AtkObject *aAtkObj)
{
// We don't use nsIAccessible::GetIndexInParent() because
// for ATK we don't want to include text leaf nodes as children
nsAccessibleWrap *accWrap = GetAccessibleWrap(aAtkObj);
--- a/accessible/src/atk/nsMaiInterfaceText.cpp
+++ b/accessible/src/atk/nsMaiInterfaceText.cpp
@@ -38,16 +38,18 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nsMaiInterfaceText.h"
#include "nsHyperTextAccessible.h"
#include "nsRoleMap.h"
+#include "nsIPersistentProperties2.h"
+
AtkAttributeSet* ConvertToAtkAttributeSet(nsIPersistentProperties* aAttributes);
void
textInterfaceInitCB(AtkTextIface *aIface)
{
NS_ASSERTION(aIface, "Invalid aIface");
if (!aIface)
return;
--- a/accessible/src/base/StyleInfo.cpp
+++ b/accessible/src/base/StyleInfo.cpp
@@ -105,8 +105,29 @@ void
StyleInfo::Margin(css::Side aSide, nsAString& aValue)
{
aValue.Truncate();
nscoord coordVal = mElement->GetPrimaryFrame()->GetUsedMargin().Side(aSide);
aValue.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(coordVal));
aValue.AppendLiteral("px");
}
+
+void
+StyleInfo::FormatColor(const nscolor& aValue, nsString& aFormattedValue)
+{
+ // Combine the string like rgb(R, G, B) from nscolor.
+ aFormattedValue.AppendLiteral("rgb(");
+ aFormattedValue.AppendInt(NS_GET_R(aValue));
+ aFormattedValue.AppendLiteral(", ");
+ aFormattedValue.AppendInt(NS_GET_G(aValue));
+ aFormattedValue.AppendLiteral(", ");
+ aFormattedValue.AppendInt(NS_GET_B(aValue));
+ aFormattedValue.Append(')');
+}
+
+void
+StyleInfo::FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue)
+{
+ nsCSSKeyword keyword =
+ nsCSSProps::ValueToKeywordEnum(aValue, nsCSSProps::kFontStyleKTable);
+ AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(keyword), aFormattedValue);
+}
--- a/accessible/src/base/StyleInfo.h
+++ b/accessible/src/base/StyleInfo.h
@@ -55,16 +55,19 @@ public:
void Display(nsAString& aValue);
void TextAlign(nsAString& aValue);
void TextIndent(nsAString& aValue);
void MarginLeft(nsAString& aValue) { Margin(css::eSideLeft, aValue); }
void MarginRight(nsAString& aValue) { Margin(css::eSideRight, aValue); }
void MarginTop(nsAString& aValue) { Margin(css::eSideTop, aValue); }
void MarginBottom(nsAString& aValue) { Margin(css::eSideBottom, aValue); }
+ static void FormatColor(const nscolor& aValue, nsString& aFormattedValue);
+ static void FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue);
+
private:
StyleInfo() MOZ_DELETE;
StyleInfo(const StyleInfo&) MOZ_DELETE;
StyleInfo& operator = (const StyleInfo&) MOZ_DELETE;
void Margin(css::Side aSide, nsAString& aValue);
dom::Element* mElement;
--- a/accessible/src/base/nsAccDocManager.cpp
+++ b/accessible/src/base/nsAccDocManager.cpp
@@ -359,19 +359,19 @@ nsDocAccessible*
nsAccDocManager::CreateDocOrRootAccessible(nsIDocument *aDocument)
{
// Ignore temporary, hiding, resource documents and documents without
// docshell.
if (aDocument->IsInitialDocument() || !aDocument->IsVisible() ||
aDocument->IsResourceDoc() || !aDocument->IsActive())
return nsnull;
- // Ignore documents without presshell.
- nsIPresShell *presShell = aDocument->GetShell();
- if (!presShell)
+ // Ignore documents without presshell and not having root frame.
+ nsIPresShell* presShell = aDocument->GetShell();
+ if (!presShell || !presShell->GetRootFrame())
return nsnull;
// Do not create document accessible until role content is loaded, otherwise
// we get accessible document with wrong role.
nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument);
if (!rootElm)
return nsnull;
--- a/accessible/src/base/nsTextAttrs.cpp
+++ b/accessible/src/base/nsTextAttrs.cpp
@@ -36,22 +36,26 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nsTextAttrs.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "nsHyperTextAccessibleWrap.h"
+#include "StyleInfo.h"
#include "gfxFont.h"
#include "gfxUserFontSet.h"
#include "nsFontMetrics.h"
#include "nsLayoutUtils.h"
+using namespace mozilla;
+using namespace mozilla::a11y;
+
////////////////////////////////////////////////////////////////////////////////
// Constants and structures
/**
* Item of the gCSSTextAttrsMap map.
*/
struct nsCSSTextAttrMapItem
{
@@ -65,19 +69,16 @@ struct nsCSSTextAttrMapItem
* The map of CSS properties to text attributes.
*/
const char* const kAnyValue = nsnull;
const char* const kCopyValue = nsnull;
static nsCSSTextAttrMapItem gCSSTextAttrsMap[] =
{
// CSS name CSS value Attribute name Attribute value
- { "color", kAnyValue, &nsGkAtoms::color, kCopyValue },
- { "font-family", kAnyValue, &nsGkAtoms::font_family, kCopyValue },
- { "font-style", kAnyValue, &nsGkAtoms::font_style, kCopyValue },
{ "text-decoration", "line-through", &nsGkAtoms::textLineThroughStyle, "solid" },
{ "text-decoration", "underline", &nsGkAtoms::textUnderlineStyle, "solid" },
{ "vertical-align", kAnyValue, &nsGkAtoms::textPosition, kCopyValue }
};
////////////////////////////////////////////////////////////////////////////////
// nsTextAttrs
@@ -147,53 +148,53 @@ nsTextAttrsMgr::GetAttributes(nsIPersist
offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode);
frame = offsetElm->GetPrimaryFrame();
}
nsTArray<nsITextAttr*> textAttrArray(10);
// "language" text attribute
nsLangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&langTextAttr));
-
- // "color" text attribute
- nsCSSTextAttr colorTextAttr(0, hyperTextElm, offsetElm);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&colorTextAttr));
-
- // "font-family" text attribute
- nsCSSTextAttr fontFamilyTextAttr(1, hyperTextElm, offsetElm);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&fontFamilyTextAttr));
-
- // "font-style" text attribute
- nsCSSTextAttr fontStyleTextAttr(2, hyperTextElm, offsetElm);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&fontStyleTextAttr));
+ textAttrArray.AppendElement(&langTextAttr);
// "text-line-through-style" text attribute
- nsCSSTextAttr lineThroughTextAttr(3, hyperTextElm, offsetElm);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&lineThroughTextAttr));
+ nsCSSTextAttr lineThroughTextAttr(0, hyperTextElm, offsetElm);
+ textAttrArray.AppendElement(&lineThroughTextAttr);
// "text-underline-style" text attribute
- nsCSSTextAttr underlineTextAttr(4, hyperTextElm, offsetElm);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&underlineTextAttr));
+ nsCSSTextAttr underlineTextAttr(1, hyperTextElm, offsetElm);
+ textAttrArray.AppendElement(&underlineTextAttr);
// "text-position" text attribute
- nsCSSTextAttr posTextAttr(5, hyperTextElm, offsetElm);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&posTextAttr));
+ nsCSSTextAttr posTextAttr(2, hyperTextElm, offsetElm);
+ textAttrArray.AppendElement(&posTextAttr);
// "background-color" text attribute
nsBGColorTextAttr bgColorTextAttr(rootFrame, frame);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&bgColorTextAttr));
+ textAttrArray.AppendElement(&bgColorTextAttr);
+
+ // "color" text attribute
+ ColorTextAttr colorTextAttr(rootFrame, frame);
+ textAttrArray.AppendElement(&colorTextAttr);
+
+ // "font-family" text attribute
+ FontFamilyTextAttr fontFamilyTextAttr(rootFrame, frame);
+ textAttrArray.AppendElement(&fontFamilyTextAttr);
// "font-size" text attribute
nsFontSizeTextAttr fontSizeTextAttr(rootFrame, frame);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&fontSizeTextAttr));
+ textAttrArray.AppendElement(&fontSizeTextAttr);
+
+ // "font-style" text attribute
+ FontStyleTextAttr fontStyleTextAttr(rootFrame, frame);
+ textAttrArray.AppendElement(&fontStyleTextAttr);
// "font-weight" text attribute
nsFontWeightTextAttr fontWeightTextAttr(rootFrame, frame);
- textAttrArray.AppendElement(static_cast<nsITextAttr*>(&fontWeightTextAttr));
+ textAttrArray.AppendElement(&fontWeightTextAttr);
// Expose text attributes if applicable.
if (aAttributes) {
PRUint32 len = textAttrArray.Length();
for (PRUint32 idx = 0; idx < len; idx++) {
nsITextAttr *textAttr = textAttrArray[idx];
nsAutoString value;
@@ -358,17 +359,17 @@ nsCSSTextAttr::Format(const nsAutoString
if (attrValue != kCopyValue)
AppendASCIItoUTF16(attrValue, aFormattedValue);
else
aFormattedValue = aValue;
}
////////////////////////////////////////////////////////////////////////////////
-// nsBackgroundTextAttr
+// nsBGColorTextAttr
////////////////////////////////////////////////////////////////////////////////
nsBGColorTextAttr::nsBGColorTextAttr(nsIFrame *aRootFrame, nsIFrame *aFrame) :
nsTextAttr<nscolor>(aFrame == nsnull), mRootFrame(aRootFrame)
{
mIsRootDefined = GetColor(mRootFrame, &mRootNativeValue);
if (aFrame)
mIsDefined = GetColor(aFrame, &mNativeValue);
@@ -382,26 +383,18 @@ nsBGColorTextAttr::GetValueFor(nsIConten
return false;
return GetColor(frame, aValue);
}
void
nsBGColorTextAttr::Format(const nscolor& aValue, nsAString& aFormattedValue)
{
- // Combine the string like rgb(R, G, B) from nscolor.
nsAutoString value;
- value.AppendLiteral("rgb(");
- value.AppendInt(NS_GET_R(aValue));
- value.AppendLiteral(", ");
- value.AppendInt(NS_GET_G(aValue));
- value.AppendLiteral(", ");
- value.AppendInt(NS_GET_B(aValue));
- value.Append(')');
-
+ StyleInfo::FormatColor(aValue, value);
aFormattedValue = value;
}
bool
nsBGColorTextAttr::GetColor(nsIFrame *aFrame, nscolor *aColor)
{
const nsStyleBackground *styleBackground = aFrame->GetStyleBackground();
@@ -422,16 +415,97 @@ nsBGColorTextAttr::GetColor(nsIFrame *aF
if (parentFrame == mRootFrame)
return false;
return GetColor(parentFrame, aColor);
}
////////////////////////////////////////////////////////////////////////////////
+// ColorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+ColorTextAttr::ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ nsTextAttr<nscolor>(!aFrame)
+{
+ mRootNativeValue = aRootFrame->GetStyleColor()->mColor;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->GetStyleColor()->mColor;
+ mIsDefined = true;
+ }
+}
+
+bool
+ColorTextAttr::GetValueFor(nsIContent* aContent, nscolor* aValue)
+{
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame) {
+ *aValue = frame->GetStyleColor()->mColor;
+ return true;
+ }
+
+ return false;
+}
+
+void
+ColorTextAttr::Format(const nscolor& aValue, nsAString& aFormattedValue)
+{
+ nsAutoString value;
+ StyleInfo::FormatColor(aValue, value);
+ aFormattedValue = value;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// FontFamilyTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+FontFamilyTextAttr::FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ nsTextAttr<nsAutoString>(aFrame == nsnull)
+{
+ mIsRootDefined = GetFontFamily(aRootFrame, mRootNativeValue);
+
+ if (aFrame)
+ mIsDefined = GetFontFamily(aFrame, mNativeValue);
+}
+
+bool
+FontFamilyTextAttr::GetValueFor(nsIContent* aElm, nsAutoString* aValue)
+{
+ nsIFrame* frame = aElm->GetPrimaryFrame();
+ if (!frame)
+ return false;
+
+ return GetFontFamily(frame, *aValue);
+}
+
+void
+FontFamilyTextAttr::Format(const nsAutoString& aValue,
+ nsAString& aFormattedValue)
+{
+ aFormattedValue = aValue;
+}
+
+bool
+FontFamilyTextAttr::GetFontFamily(nsIFrame* aFrame, nsAutoString& aFamily)
+{
+ nsRefPtr<nsFontMetrics> fm;
+ nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm));
+
+ gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
+ gfxFont* font = fontGroup->GetFontAt(0);
+ gfxFontEntry* fontEntry = font->GetFontEntry();
+ aFamily = fontEntry->FamilyName();
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
// nsFontSizeTextAttr
////////////////////////////////////////////////////////////////////////////////
nsFontSizeTextAttr::nsFontSizeTextAttr(nsIFrame *aRootFrame, nsIFrame *aFrame) :
nsTextAttr<nscoord>(aFrame == nsnull)
{
mDC = aRootFrame->PresContext()->DeviceContext();
@@ -445,29 +519,29 @@ nsFontSizeTextAttr::nsFontSizeTextAttr(n
}
bool
nsFontSizeTextAttr::GetValueFor(nsIContent *aContent, nscoord *aValue)
{
nsIFrame *frame = aContent->GetPrimaryFrame();
if (!frame)
return false;
-
+
*aValue = GetFontSize(frame);
return true;
}
void
nsFontSizeTextAttr::Format(const nscoord& aValue, nsAString& aFormattedValue)
{
// Convert from nscoord to pt.
//
// Note: according to IA2, "The conversion doesn't have to be exact.
// The intent is to give the user a feel for the size of the text."
- //
+ //
// ATK does not specify a unit and will likely follow IA2 here.
//
// XXX todo: consider sharing this code with layout module? (bug 474621)
float px =
NSAppUnitsToFloatPixels(aValue, nsDeviceContext::AppUnitsPerCSSPixel());
// Each pt is 4/3 of a CSS pixel.
int pts = NS_lround(px*3/4);
@@ -480,16 +554,51 @@ nsFontSizeTextAttr::Format(const nscoord
nscoord
nsFontSizeTextAttr::GetFontSize(nsIFrame *aFrame)
{
return aFrame->GetStyleFont()->mSize;
}
////////////////////////////////////////////////////////////////////////////////
+// FontStyleTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+FontStyleTextAttr::FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ nsTextAttr<nscoord>(!aFrame)
+{
+ mRootNativeValue = aRootFrame->GetStyleFont()->mFont.style;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->GetStyleFont()->mFont.style;
+ mIsDefined = true;
+ }
+}
+
+bool
+FontStyleTextAttr::GetValueFor(nsIContent* aContent, nscoord* aValue)
+{
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame) {
+ *aValue = frame->GetStyleFont()->mFont.style;
+ return true;
+ }
+
+ return false;
+}
+
+void
+FontStyleTextAttr::Format(const nscoord& aValue, nsAString& aFormattedValue)
+{
+ StyleInfo::FormatFontStyle(aValue, aFormattedValue);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
// nsFontWeightTextAttr
////////////////////////////////////////////////////////////////////////////////
nsFontWeightTextAttr::nsFontWeightTextAttr(nsIFrame *aRootFrame,
nsIFrame *aFrame) :
nsTextAttr<PRInt32>(aFrame == nsnull)
{
mRootNativeValue = GetFontWeight(aRootFrame);
--- a/accessible/src/base/nsTextAttrs.h
+++ b/accessible/src/base/nsTextAttrs.h
@@ -36,27 +36,20 @@
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsTextAttrs_h_
#define nsTextAttrs_h_
class nsHyperTextAccessible;
-
-#include "nsIDOMNode.h"
-#include "nsIDOMElement.h"
-
#include "nsIContent.h"
#include "nsIFrame.h"
#include "nsIPersistentProperties2.h"
-#include "nsCOMPtr.h"
-#include "nsString.h"
-
class nsITextAttr;
/**
* Used to expose text attributes for the hyper text accessible (see
* nsHyperTextAccessible class). It is indended for the work with 'language' and
* CSS based text attributes.
*
* @note "invalid: spelling" text attrbiute is implemented entirerly in
@@ -95,17 +88,16 @@ public:
* @param aStartHTOffset [out, optional] start hyper text offset
* @param aEndHTOffset [out, optional] end hyper text offset
*/
nsresult GetAttributes(nsIPersistentProperties *aAttributes,
PRInt32 *aStartHTOffset = nsnull,
PRInt32 *aEndHTOffset = nsnull);
protected:
-
/**
* Calculates range (start and end offsets) of text where the text attributes
* are stretched. New offsets may be smaller if one of text attributes changes
* its value before or after the given offsets.
*
* @param aTextAttrArray [in] text attributes array
* @param aStartHTOffset [in, out] the start offset
* @param aEndHTOffset [in, out] the end offset
@@ -303,16 +295,59 @@ protected:
private:
bool GetColor(nsIFrame *aFrame, nscolor *aColor);
nsIFrame *mRootFrame;
};
/**
+ * Class is used for the work with 'color' text attribute in nsTextAttrsMgr
+ * class.
+ */
+class ColorTextAttr : public nsTextAttr<nscolor>
+{
+public:
+ ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+
+ // nsITextAttr
+ virtual nsIAtom* GetName() const { return nsGkAtoms::color; }
+
+protected:
+ // nsTextAttr
+ virtual bool GetValueFor(nsIContent* aContent, nscolor* aValue);
+ virtual void Format(const nscolor& aValue, nsAString& aFormattedValue);
+};
+
+
+/**
+ * Class is used for the work with "font-family" text attribute in
+ * nsTextAttrsMgr class.
+ */
+class FontFamilyTextAttr : public nsTextAttr<nsAutoString>
+{
+public:
+ FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+
+ // nsITextAttr
+ virtual nsIAtom* GetName() const { return nsGkAtoms::font_family; }
+
+protected:
+
+ // nsTextAttr
+ virtual bool GetValueFor(nsIContent* aContent, nsAutoString* aValue);
+ virtual void Format(const nsAutoString& aValue, nsAString& aFormattedValue);
+
+private:
+
+ bool GetFontFamily(nsIFrame* aFrame, nsAutoString& aFamily);
+};
+
+
+/**
* Class is used for the work with "font-size" text attribute in nsTextAttrsMgr
* class.
*/
class nsFontSizeTextAttr : public nsTextAttr<nscoord>
{
public:
nsFontSizeTextAttr(nsIFrame *aRootFrame, nsIFrame *aFrame);
@@ -335,16 +370,36 @@ private:
*/
nscoord GetFontSize(nsIFrame *aFrame);
nsDeviceContext *mDC;
};
/**
+ * Class is used for the work with "font-style" text attribute in nsTextAttrsMgr
+ * class.
+ */
+class FontStyleTextAttr : public nsTextAttr<nscoord>
+{
+public:
+ FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+
+ // nsITextAttr
+ virtual nsIAtom* GetName() const { return nsGkAtoms::font_style; }
+
+protected:
+
+ // nsTextAttr
+ virtual bool GetValueFor(nsIContent* aContent, nscoord* aValue);
+ virtual void Format(const nscoord &aValue, nsAString &aFormattedValue);
+};
+
+
+/**
* Class is used for the work with "font-weight" text attribute in
* nsTextAttrsMgr class.
*/
class nsFontWeightTextAttr : public nsTextAttr<PRInt32>
{
public:
nsFontWeightTextAttr(nsIFrame *aRootFrame, nsIFrame *aFrame);
--- a/accessible/src/html/nsHyperTextAccessible.h
+++ b/accessible/src/html/nsHyperTextAccessible.h
@@ -41,17 +41,16 @@
#define _nsHyperTextAccessible_H_
#include "nsIAccessibleText.h"
#include "nsIAccessibleHyperText.h"
#include "nsIAccessibleEditableText.h"
#include "AccCollector.h"
#include "nsAccessibleWrap.h"
-#include "nsTextAttrs.h"
#include "nsFrameSelection.h"
#include "nsISelectionController.h"
enum EGetTextType { eGetBefore=-1, eGetAt=0, eGetAfter=1 };
// This character marks where in the text returned via nsIAccessibleText(),
// that embedded object characters exist
--- a/accessible/src/msaa/CAccessibleText.cpp
+++ b/accessible/src/msaa/CAccessibleText.cpp
@@ -40,16 +40,18 @@
#include "CAccessibleText.h"
#include "Accessible2.h"
#include "AccessibleText_i.c"
#include "nsHyperTextAccessible.h"
+#include "nsIPersistentProperties2.h"
+
// IUnknown
STDMETHODIMP
CAccessibleText::QueryInterface(REFIID iid, void** ppv)
{
*ppv = NULL;
if (IID_IAccessibleText == iid) {
--- a/accessible/tests/mochitest/attributes.js
+++ b/accessible/tests/mochitest/attributes.js
@@ -203,38 +203,71 @@ const kNormalFontWeight =
const kBoldFontWeight =
function equalsToBold(aWeight) { return aWeight > 400; }
// The pt font size of the input element can vary by Linux distro.
const kInputFontSize = WIN ?
"10pt" : (MAC ? "8pt" : function() { return true; });
+const kAbsentFontFamily =
+ function(aFontFamily) { return aFontFamily != "sans-serif"; }
+const kInputFontFamily =
+ function(aFontFamily) { return aFontFamily != "sans-serif"; }
+
+const kMonospaceFontFamily =
+ function(aFontFamily) { return aFontFamily != "monospace"; }
+const kSansSerifFontFamily =
+ function(aFontFamily) { return aFontFamily != "sans-serif"; }
+const kSerifFontFamily =
+ function(aFontFamily) { return aFontFamily != "serif"; }
+
+const kCursiveFontFamily = WIN ? "Comic Sans MS" :
+ (LINUX ? "DejaVu Serif" : "MacFont");
+
+/**
+ * Return used font from the given computed style.
+ */
+function fontFamily(aComputedStyle)
+{
+ var name = aComputedStyle.fontFamily;
+ switch (name) {
+ case "monospace":
+ return kMonospaceFontFamily;
+ case "sans-serif":
+ return kSansSerifFontFamily;
+ case "serif":
+ return kSerifFontFamily;
+ default:
+ return name;
+ }
+}
+
/**
* Build an object of default text attributes expected for the given accessible.
*
* @param aID [in] identifier of accessible
* @param aFontSize [in] font size
* @param aFontWeight [in, optional] kBoldFontWeight or kNormalFontWeight,
* default value is kNormalFontWeight
*/
-function buildDefaultTextAttrs(aID, aFontSize, aFontWeight)
+function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily)
{
var elm = getNode(aID);
var computedStyle = document.defaultView.getComputedStyle(elm, "");
var bgColor = computedStyle.backgroundColor == "transparent" ?
"rgb(255, 255, 255)" : computedStyle.backgroundColor;
var defAttrs = {
"font-style": computedStyle.fontStyle,
"font-size": aFontSize,
"background-color": bgColor,
"font-weight": aFontWeight ? aFontWeight : kNormalFontWeight,
"color": computedStyle.color,
- "font-family": computedStyle.fontFamily,
+ "font-family": aFontFamily ? aFontFamily : fontFamily(computedStyle),
"text-position": computedStyle.verticalAlign
};
return defAttrs;
}
////////////////////////////////////////////////////////////////////////////////
// Private.
--- a/accessible/tests/mochitest/attributes/test_text.html
+++ b/accessible/tests/mochitest/attributes/test_text.html
@@ -258,17 +258,17 @@
testTextAttrs(ID, 45, attrs, defAttrs, 44, 61);
attrs = {};
testTextAttrs(ID, 62, attrs, defAttrs, 61, 69);
// Walk from span with font-style to the one with font-family.
tempElem = tempElem.nextSibling.nextSibling;
gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
- attrs = { "font-family": gComputedStyle.fontFamily };
+ attrs = { "font-family": kMonospaceFontFamily };
testTextAttrs(ID, 70, attrs, defAttrs, 69, 83);
attrs = {};
testTextAttrs(ID, 84, attrs, defAttrs, 83, 91);
attrs = { "text-underline-style": "solid" };
testTextAttrs(ID, 92, attrs, defAttrs, 91, 101);
@@ -276,16 +276,17 @@
testTextAttrs(ID, 102, attrs, defAttrs, 101, 109);
attrs = { "text-line-through-style": "solid" };
testTextAttrs(ID, 110, attrs, defAttrs, 109, 122);
attrs = {};
testTextAttrs(ID, 123, attrs, defAttrs, 122, 130);
+ //////////////////////////////////////////////////////////////////////////
// area10, different single style spans in non-styled paragraph
ID = "area10";
defAttrs = buildDefaultTextAttrs(ID, "12pt");
testDefaultTextAttrs(ID, defAttrs);
attrs = {};
testTextAttrs(ID, 0, attrs, defAttrs, 0, 7);
@@ -311,17 +312,17 @@
testTextAttrs(ID, 46, attrs, defAttrs, 45, 62);
attrs = {};
testTextAttrs(ID, 63, attrs, defAttrs, 62, 70);
// Walk from span with font-style to the one with font-family.
tempElem = tempElem.nextSibling.nextSibling;
gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
- attrs = {"font-family": gComputedStyle.fontFamily};
+ attrs = { "font-family": kMonospaceFontFamily };
testTextAttrs(ID, 71, attrs, defAttrs, 70, 84);
attrs = {};
testTextAttrs(ID, 85, attrs, defAttrs, 84, 92);
attrs = { "text-underline-style": "solid" };
testTextAttrs(ID, 93, attrs, defAttrs, 92, 102);
@@ -329,16 +330,17 @@
testTextAttrs(ID, 103, attrs, defAttrs, 102, 110);
attrs = { "text-line-through-style": "solid" };
testTextAttrs(ID, 111, attrs, defAttrs, 110, 123);
attrs = {};
testTextAttrs(ID, 124, attrs, defAttrs, 123, 131);
+ //////////////////////////////////////////////////////////////////////////
// area11, "font-weight" tests
ID = "area11";
defAttrs = buildDefaultTextAttrs(ID, "12pt", kBoldFontWeight);
testDefaultTextAttrs(ID, defAttrs);
attrs = { };
testTextAttrs(ID, 0, attrs, defAttrs, 0, 13);
@@ -368,17 +370,18 @@
//////////////////////////////////////////////////////////////////////////
// test zero offset on empty hypertext accessibles
ID = "area13";
defAttrs = buildDefaultTextAttrs(ID, "12pt");
attrs = { };
testTextAttrs(ID, 0, attrs, defAttrs, 0, 0);
ID = "area14";
- defAttrs = buildDefaultTextAttrs(ID, kInputFontSize);
+ defAttrs = buildDefaultTextAttrs(ID, kInputFontSize,
+ kNormalFontWeight, kInputFontFamily);
attrs = { };
testTextAttrs(ID, 0, attrs, defAttrs, 0, 0);
//////////////////////////////////////////////////////////////////////////
// area15, embed char tests, "*plain*plain**bold*bold*"
ID = "area15";
defAttrs = buildDefaultTextAttrs(ID, "12pt");
@@ -399,29 +402,64 @@
// p
testTextAttrs(ID, 18, { }, { }, 18, 19);
// bold
attrs = { "font-weight": kBoldFontWeight };
testTextAttrs(ID, 19, attrs, defAttrs, 19, 23);
// p
testTextAttrs(ID, 23, { }, { }, 23, 24);
+ //////////////////////////////////////////////////////////////////////////
+ // area16, "font-family" tests
+ ID = "area16";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = { "font-family": kMonospaceFontFamily };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 4);
+
+ attrs = { };
+ testTextAttrs(ID, 4, attrs, defAttrs, 4, 9);
+
+ attrs = { "font-family": kSerifFontFamily };
+ testTextAttrs(ID, 9, attrs, defAttrs, 9, 13);
+
+ attrs = { };
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 18);
+
+ attrs = { "font-family": kAbsentFontFamily };
+ testTextAttrs(ID, 18, attrs, defAttrs, 18, 22);
+
+ attrs = { };
+ testTextAttrs(ID, 22, attrs, defAttrs, 22, 27);
+
+ attrs = { "font-family": kCursiveFontFamily };
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 31);
+
+ attrs = { };
+ testTextAttrs(ID, 31, attrs, defAttrs, 31, 45);
+
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body style="font-size: 12pt">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759"
title="Implement text attributes">
Mozilla Bug 345759
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=473576"
+ title="font-family text attribute should expose actual font used">
+ Mozilla Bug 473576
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<p id="area1" style="font-size: smaller">Normal <b>Bold</b> Normal</p>
<p id="area2" style="font-size: 120%">Normal <b>Bold <i>Italic </i>Bold</b> Normal</p>
@@ -464,26 +502,26 @@
<span style="color: magenta">Magenta<b>Bold</b>Magenta</span>
</span>
</p>
<p id="area9" style="font-size: smaller">Small
<span style="font-size: 120%">bigger</span> smaller
<span style="background-color: blue;">background blue</span> normal
<span style="font-style: italic;">Different styling</span> normal
- <span style="font-family: tahoma;">Different font</span> normal
+ <span style="font-family: monospace;">Different font</span> normal
<span style="text-decoration: underline;">underlined</span> normal
<span style="text-decoration: line-through;">strikethrough</span> normal
</p>
<p id="area10">Normal
<span style="font-size: 120%">bigger</span> smaller
<span style="background-color: blue;">background blue</span> normal
<span style="font-style: italic;">Different styling</span> normal
- <span style="font-family: tahoma;">Different font</span> normal
+ <span style="font-family: monospace;">Different font</span> normal
<span style="text-decoration: underline;">underlined</span> normal
<span style="text-decoration: line-through;">strikethrough</span> normal
</p>
<p id="area11" style="font-weight: bolder;">
<span style="font-weight: bolder;">bolder</span>bolder
<span style="font-weight: lighter;">lighter</span>bolder
<span style="font-weight: normal;">normal</span>bolder
@@ -495,10 +533,18 @@
</p>
<p id="area12">hello</p>
<p id="area13"></p>
<input id="area14">
<!-- *plain*plain**bold*bold*-->
<div id="area15"><p>embed</p>plain<p>embed</p>plain<p>embed</p><img src="../moz.png" alt="image"/><b>bold</b><p>embed</p><b>bold</b><p>embed</p></div>
+
+ <p id="area16" style="font-family: sans-serif;">
+ <span style="font-family: monospace;">text</span>text
+ <span style="font-family: serif;">text</span>text
+ <span style="font-family: BodoniThatDoesntExist;">text</span>text
+ <span style="font-family: Comic Sans MS, cursive;">text</span>text
+ <span style="font-family: sans-serif, fantasy;">text</span>text
+ </p>
</body>
</html>
--- a/accessible/tests/mochitest/events/Makefile.in
+++ b/accessible/tests/mochitest/events/Makefile.in
@@ -60,16 +60,17 @@ include $(topsrcdir)/config/rules.mk
test_contextmenu.html \
test_docload.html \
test_docload.xul \
test_dragndrop.html \
test_flush.html \
test_focus_aria_activedescendant.html \
test_focus_autocomplete.xul \
test_focus_browserui.xul \
+ test_focus_canvas.html \
test_focus_contextmenu.xul \
test_focus_controls.html \
test_focus_dialog.html \
test_focus_doc.html \
test_focus_general.html \
test_focus_general.xul \
test_focus_listcontrols.xul \
test_focus_menu.xul \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_canvas.html
@@ -0,0 +1,59 @@
+<html>
+
+<head>
+ <title>Accessible focus testing in canvas subdom</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("button"));
+ gQueue.push(new synthTab("button", new focusChecker("textbox")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="Expose content in Canvas element"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">
+ Mozilla Bug 495912
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <canvas>
+ <input id="button" type="button">
+ <input id="textbox">
+ </canvas>
+
+ <div id="eventdump"></div>
+</body>
+</html>
--- a/accessible/tests/mochitest/events/test_textattrchange.html
+++ b/accessible/tests/mochitest/events/test_textattrchange.html
@@ -44,17 +44,19 @@
//var spellchecker = editor.getInlineSpellChecker(true);
//spellchecker.enableRealTimeSpell = true;
this.DOMNode.value = "valid text inalid tixt";
}
this.finalCheck = function spelledTextInvoker_finalCheck()
{
- var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize);
+ var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize,
+ kNormalFontWeight,
+ kInputFontFamily);
testDefaultTextAttrs(aID, defAttrs);
var attrs = { };
var misspelledAttrs = {
"invalid": "spelling"
};
testTextAttrs(aID, 0, attrs, defAttrs, 0, 11);
--- a/accessible/tests/mochitest/tree/test_canvas.html
+++ b/accessible/tests/mochitest/tree/test_canvas.html
@@ -13,23 +13,23 @@ https://bugzilla.mozilla.org/show_bug.cg
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript">
function doTest()
{
- var accTree = {
- role: ROLE_CANVAS,
- children: [
- ]
- };
+ var accTree =
+ { CANVAS: [
+ { CHECKBUTTON: [] },
+ { ENTRY: [] }
+ ] };
+
testAccessibleTree("canvas", accTree);
-
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
@@ -37,19 +37,17 @@ https://bugzilla.mozilla.org/show_bug.cg
<a target="_blank"
title="Expose alternative content in Canvas element to ATs"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">Mozilla Bug 495912</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
- <canvas id="canvas" tabindex="0">
- fallback content.
- </canvas>
+ <canvas id="canvas" tabindex="0"><input type="checkbox"><input></canvas>
<script type="text/javascript">
var c=document.getElementById("canvas");
var cxt=c.getContext("2d");
cxt.fillStyle="#005500";
cxt.fillRect(0,0,150,75);
</script>
--- a/accessible/tests/mochitest/treeupdate/Makefile.in
+++ b/accessible/tests/mochitest/treeupdate/Makefile.in
@@ -42,16 +42,17 @@ srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = accessible/treeupdate
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_TEST_FILES =\
test_ariadialog.html \
+ test_canvas.html \
test_colorpicker.xul \
test_cssoverflow.html \
test_contextmenu.xul \
test_doc.html \
test_gencontent.html \
test_list_editabledoc.html \
test_list.html \
test_menu.xul \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_canvas.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Canvas subdom mutation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function addSubtree(aID)
+ {
+ this.node = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.node)
+ ];
+
+ this.invoke = function addSubtree_invoke()
+ {
+ // ensure we start with no subtree
+ testAccessibleTree("canvas", { CANVAS: [] });
+ getNode("dialog").style.display = "block";
+ }
+
+ this.finalCheck = function addSubtree_finalCheck() {
+ testAccessibleTree("dialog", { DIALOG: [] });
+ }
+
+ this.getID = function addSubtree_getID()
+ {
+ return "show canvas subdom";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ // make the subdom come alive!
+ gQueue.push(new addSubtree("dialog"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose content in Canvas element"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">
+ Mozilla Bug 495912
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <canvas id="canvas">
+ <div id="dialog" role="dialog" style="display: none;">
+ </div>
+ </canvas>
+
+ <div id="eventdump"></div>
+</body>
+</html>
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -405,16 +405,20 @@ pref("browser.link.open_newwindow.restri
// Enable browser frame
pref("dom.mozBrowserFramesEnabled", true);
pref("dom.mozBrowserFramesWhitelist", "http://localhost:7777");
// Temporary permission hack for WebSMS
pref("dom.sms.enabled", true);
pref("dom.sms.whitelist", "file://,http://localhost:7777");
+// Temporary permission hack for WebContacts
+pref("dom.mozContacts.enabled", true);
+pref("dom.mozContacts.whitelist", "http://localhost:7777");
+
// Ignore X-Frame-Options headers.
pref("b2g.ignoreXFrameOptions", true);
// controls if we want camera support
pref("device.camera.enabled", true);
pref("media.realtime_decoder.enabled", true);
// "Preview" landing of bug 710563, which is bogged down in analysis
deleted file mode 100644
--- a/b2g/chrome/content/commandUtil.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * Command Updater
- */
-let CommandUpdater = {
- /**
- * Gets a controller that can handle a particular command.
- * @param {string} command
- * A command to locate a controller for, preferring controllers that
- * show the command as enabled.
- * @return {object} In this order of precedence:
- * - the first controller supporting the specified command
- * associated with the focused element that advertises the
- * command as ENABLED.
- * - the first controller supporting the specified command
- * associated with the global window that advertises the
- * command as ENABLED.
- * - the first controller supporting the specified command
- * associated with the focused element.
- * - the first controller supporting the specified command
- * associated with the global window.
- */
- _getControllerForCommand: function(command) {
- try {
- let commandDispatcher = top.document.commandDispatcher;
- let controller = commandDispatcher.getControllerForCommand(command);
- if (controller && controller.isCommandEnabled(command))
- return controller;
- }
- catch (e) { }
-
- let controllerCount = window.controllers.getControllerCount();
- for (let i = 0; i < controllerCount; ++i) {
- let current = window.controllers.getControllerAt(i);
- try {
- if (current.supportsCommand(command) &&
- current.isCommandEnabled(command))
- return current;
- }
- catch (e) { }
- }
- return controller || window.controllers.getControllerForCommand(command);
- },
-
- /**
- * Updates the state of a XUL <command> element for the specified command
- * depending on its state.
- * @param {string} command
- * The name of the command to update the XUL <command> element for.
- */
- updateCommand: function(command) {
- let enabled = false;
- try {
- let controller = this._getControllerForCommand(command);
- if (controller) {
- enabled = controller.isCommandEnabled(command);
- }
- }
- catch (ex) { }
-
- this.enableCommand(command, enabled);
- },
-
- /**
- * Updates the state of a XUL <command> element for the specified command
- * depending on its state.
- * @param {string} command
- * The name of the command to update the XUL <command> element for.
- */
- updateCommands: function(_commands) {
- let commands = _commands.split(',');
- for (let command in commands) {
- this.updateCommand(commands[command]);
- }
- },
-
- /**
- * Enables or disables a XUL <command> element.
- * @param {string} command
- * The name of the command to enable or disable.
- * @param {bool} enabled
- * true if the command should be enabled, false otherwise.
- */
- enableCommand: function(command, enabled) {
- let element = document.getElementById(command);
- if (!element)
- return;
-
- if (enabled)
- element.removeAttribute('disabled');
- else
- element.setAttribute('disabled', 'true');
- },
-
- /**
- * Performs the action associated with a specified command using the most
- * relevant controller.
- * @param {string} command
- * The command to perform.
- */
- doCommand: function(command) {
- let controller = this._getControllerForCommand(command);
- if (!controller)
- return;
- controller.doCommand(command);
- },
-
- /**
- * Changes the label attribute for the specified command.
- * @param {string} command
- * The command to update.
- * @param {string} labelAttribute
- * The label value to use.
- */
- setMenuValue: function(command, labelAttribute) {
- let commandNode = top.document.getElementById(command);
- if (commandNode) {
- let label = commandNode.getAttribute(labelAttribute);
- if (label)
- commandNode.setAttribute('label', label);
- }
- },
-
- /**
- * Changes the accesskey attribute for the specified command.
- * @param {string} command
- * The command to update.
- * @param {string} valueAttribute
- * The value attribute to use.
- */
- setAccessKey: function(command, valueAttribute) {
- let commandNode = top.document.getElementById(command);
- if (commandNode) {
- let value = commandNode.getAttribute(valueAttribute);
- if (value)
- commandNode.setAttribute('accesskey', value);
- }
- },
-
- /**
- * Inform all the controllers attached to a node that an event has occurred
- * (e.g. the tree controllers need to be informed of blur events so that they
- * can change some of the menu items back to their default values)
- * @param {node} node
- * The node receiving the event.
- * @param {event} event
- * The event.
- */
- onEvent: function(node, event) {
- let numControllers = node.controllers.getControllerCount();
- let controller;
-
- for (let i = 0; i < numControllers; i++) {
- controller = node.controllers.getControllerAt(i);
- if (controller)
- controller.onEvent(event);
- }
- }
-};
-
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -11,38 +11,43 @@ const CC = Components.Constructor;
const Cr = Components.results;
const LocalFile = CC('@mozilla.org/file/local;1',
'nsILocalFile',
'initWithPath');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/ContactService.jsm');
XPCOMUtils.defineLazyGetter(Services, 'env', function() {
return Cc['@mozilla.org/process/environment;1']
.getService(Ci.nsIEnvironment);
});
XPCOMUtils.defineLazyGetter(Services, 'ss', function() {
return Cc['@mozilla.org/content/style-sheet-service;1']
.getService(Ci.nsIStyleSheetService);
});
XPCOMUtils.defineLazyGetter(Services, 'idle', function() {
return Cc['@mozilla.org/widget/idleservice;1']
.getService(Ci.nsIIdleService);
});
-XPCOMUtils.defineLazyServiceGetter(Services, 'fm', function(){
- return Cc['@mozilla.org/focus-managr;1']
+XPCOMUtils.defineLazyGetter(Services, 'audioManager', function() {
+ return Cc['@mozilla.org/telephony/audiomanager;1']
+ .getService(Ci.nsIAudioManager);
+});
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'fm', function() {
+ return Cc['@mozilla.org/focus-manager;1']
.getService(Ci.nsFocusManager);
});
-
#ifndef MOZ_WIDGET_GONK
// In order to use http:// scheme instead of file:// scheme
// (that is much more restricted) the following code kick-off
// a local http server listening on http://127.0.0.1:7777 and
// http://localhost:7777.
function startupHttpd(baseDir, port) {
const httpdURL = 'chrome://browser/content/httpd.js';
let httpd = {};
@@ -55,32 +60,29 @@ function startupHttpd(baseDir, port) {
#endif
// FIXME Bug 707625
// until we have a proper security model, add some rights to
// the pre-installed web applications
// XXX never grant 'content-camera' to non-gaia apps
function addPermissions(urls) {
let permissions = [
- 'indexedDB', 'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'content-camera'
+ 'indexedDB', 'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'content-camera', 'webcontacts-manage'
];
urls.forEach(function(url) {
let uri = Services.io.newURI(url, null, null);
let allow = Ci.nsIPermissionManager.ALLOW_ACTION;
permissions.forEach(function(permission) {
Services.perms.add(uri, permission, allow);
});
});
}
var shell = {
- // FIXME/bug 678695: this should be a system setting
- preferredScreenBrightness: 1.0,
-
isDebug: false,
get contentBrowser() {
delete this.contentBrowser;
return this.contentBrowser = document.getElementById('homescreen');
},
get homeURL() {
@@ -105,23 +107,33 @@ var shell = {
start: function shell_init() {
let homeURL = this.homeURL;
if (!homeURL) {
let msg = 'Fatal error during startup: [No homescreen found]';
return alert(msg);
}
- window.controllers.appendController(this);
- window.addEventListener('keypress', this);
+ ['keydown', 'keypress', 'keyup'].forEach((function listenKey(type) {
+ window.addEventListener(type, this, false, true);
+ window.addEventListener(type, this, true, true);
+ }).bind(this));
+
window.addEventListener('MozApplicationManifest', this);
- window.addEventListener("AppCommand", this);
window.addEventListener('mozfullscreenchange', this);
this.contentBrowser.addEventListener('load', this, true);
+ // Until the volume can be set from the content side, set it to a
+ // a specific value when the device starts. This way the front-end
+ // can display a notification when the volume change and show a volume
+ // level modified from this point.
+ try {
+ Services.audioManager.masterVolume = 0.5;
+ } catch(e) {}
+
try {
Services.io.offline = false;
let fileScheme = 'file://';
if (homeURL.substring(0, fileScheme.length) == fileScheme) {
#ifndef MOZ_WIDGET_GONK
homeURL = homeURL.replace(fileScheme, '');
@@ -153,128 +165,116 @@ var shell = {
}
let browser = this.contentBrowser;
browser.homePage = homeURL;
browser.goHome();
},
stop: function shell_stop() {
- window.controllers.removeController(this);
- window.removeEventListener('keypress', this);
window.removeEventListener('MozApplicationManifest', this);
- window.removeEventListener('AppCommand', this);
- },
-
- supportsCommand: function shell_supportsCommand(cmd) {
- let isSupported = false;
- switch (cmd) {
- case 'cmd_close':
- isSupported = true;
- break;
- default:
- isSupported = false;
- break;
- }
- return isSupported;
- },
-
- isCommandEnabled: function shell_isCommandEnabled(cmd) {
- return true;
- },
-
- doCommand: function shell_doCommand(cmd) {
- switch (cmd) {
- case 'cmd_close':
- content.postMessage('appclose', '*');
- break;
- }
+ window.removeEventListener('mozfullscreenchange', this);
},
toggleDebug: function shell_toggleDebug() {
this.isDebug = !this.isDebug;
if (this.isDebug) {
Services.prefs.setBoolPref("layers.acceleration.draw-fps", true);
Services.prefs.setBoolPref("nglayout.debug.paint_flashing", true);
} else {
Services.prefs.setBoolPref("layers.acceleration.draw-fps", false);
Services.prefs.setBoolPref("nglayout.debug.paint_flashing", false);
}
},
- changeVolume: function shell_changeVolume(aDelta) {
- let audioManager = Cc["@mozilla.org/telephony/audiomanager;1"].getService(Ci.nsIAudioManager);
-
+ changeVolume: function shell_changeVolume(delta) {
let steps = 10;
try {
steps = Services.prefs.getIntPref("media.volume.steps");
if (steps <= 0)
steps = 1;
} catch(e) {}
- let volume = audioManager.masterVolume + aDelta / steps;
+ let audioManager = Services.audioManager;
+ if (!audioManager)
+ return;
+
+ let volume = audioManager.masterVolume + delta / steps;
if (volume > 1)
volume = 1;
if (volume < 0)
volume = 0;
audioManager.masterVolume = volume;
},
+ forwardKeyToHomescreen: function shell_forwardKeyToHomescreen(evt) {
+ let generatedEvent = content.document.createEvent('KeyboardEvent');
+ generatedEvent.initKeyEvent(evt.type, true, true, evt.view, evt.ctrlKey,
+ evt.altKey, evt.shiftKey, evt.metaKey,
+ evt.keyCode, evt.charCode);
+
+ content.dispatchEvent(generatedEvent);
+ },
+
handleEvent: function shell_handleEvent(evt) {
switch (evt.type) {
+ case 'keydown':
+ case 'keyup':
case 'keypress':
- switch (evt.keyCode) {
- case evt.DOM_VK_HOME:
- this.sendEvent(content, 'home');
- break;
- case evt.DOM_VK_SLEEP:
- this.toggleScreen();
+ // If the home key is pressed, always forward it to the homescreen
+ if (evt.eventPhase == evt.CAPTURING_PHASE) {
+ if (evt.keyCode == evt.VK_DOM_HOME) {
+ window.setTimeout(this.forwardKeyToHomescreen, 0, evt);
+ evt.preventDefault();
+ evt.stopPropagation();
+ }
+ return;
+ }
+
+ // If one of the other keys is used in an application and is
+ // cancelled via preventDefault, do nothing.
+ let homescreen = (evt.target.ownerDocument.defaultView == content);
+ if (!homescreen && evt.defaultPrevented)
+ return;
- let details = {
- 'enabled': screen.mozEnabled
- };
- this.sendEvent(content, 'sleep', details);
- break;
- case evt.DOM_VK_ESCAPE:
- if (evt.defaultPrevented)
- return;
- this.doCommand('cmd_close');
- break;
- }
- break;
- case 'AppCommand':
- switch (evt.command) {
- case 'Menu':
- if (Services.prefs.getBoolPref('b2g.keys.menu.enabled'))
- this.sendEvent(content, 'menu');
- break;
- case 'Search':
- if (Services.prefs.getBoolPref('b2g.keys.search.enabled'))
- this.toggleDebug();
- break;
- case 'VolumeUp':
- this.changeVolume(1);
- break;
- case 'VolumeDown':
- this.changeVolume(-1);
- break;
+ // If one of the other keys is used in an application and is
+ // not used forward it to the homescreen
+ if (!homescreen)
+ window.setTimeout(this.forwardKeyToHomescreen, 0, evt);
+
+ // For debug purposes and because some of the APIs are not yet exposed
+ // to the content, let's react on some of the keyup events.
+ if (evt.type == 'keyup') {
+ switch (evt.keyCode) {
+ case evt.DOM_VK_F5:
+ if (Services.prefs.getBoolPref('b2g.keys.search.enabled'))
+ this.toggleDebug();
+ break;
+
+ case evt.DOM_VK_PAGE_DOWN:
+ this.changeVolume(-1);
+ break;
+
+ case evt.DOM_VK_PAGE_UP:
+ this.changeVolume(1);
+ break;
+ }
}
break;
case 'mozfullscreenchange':
// When the screen goes fullscreen make sure to set the focus to the
// main window so noboby can prevent the ESC key to get out fullscreen
// mode
if (document.mozFullScreen)
Services.fm.focusedWindow = window;
break;
case 'load':
this.contentBrowser.removeEventListener('load', this, true);
- this.turnScreenOn();
let chromeWindow = window.QueryInterface(Ci.nsIDOMChromeWindow);
chromeWindow.browserDOMWindow = new nsBrowserAccess();
this.sendEvent(window, 'ContentStart');
break;
case 'MozApplicationManifest':
try {
@@ -311,39 +311,25 @@ var shell = {
}
break;
}
},
sendEvent: function shell_sendEvent(content, type, details) {
let event = content.document.createEvent('CustomEvent');
event.initCustomEvent(type, true, true, details ? details : {});
content.dispatchEvent(event);
- },
- toggleScreen: function shell_toggleScreen() {
- if (screen.mozEnabled)
- this.turnScreenOff();
- else
- this.turnScreenOn();
- },
- turnScreenOff: function shell_turnScreenOff() {
- screen.mozEnabled = false;
- screen.mozBrightness = 0.0;
- },
- turnScreenOn: function shell_turnScreenOn() {
- screen.mozEnabled = true;
- screen.mozBrightness = this.preferredScreenBrightness;
}
};
(function PowerManager() {
let idleHandler = {
observe: function(subject, topic, time) {
if (topic === "idle") {
// TODO: Check wakelock status. See bug 697132.
- shell.turnScreenOff();
+ screen.mozEnabled = false;
}
},
}
let idleTimeout = Services.prefs.getIntPref("power.screen.timeout");
if (idleTimeout) {
Services.idle.addIdleObserver(idleHandler, idleTimeout);
}
})();
--- a/b2g/chrome/content/shell.xul
+++ b/b2g/chrome/content/shell.xul
@@ -10,28 +10,23 @@
windowtype="navigator:browser"
#ifdef ANDROID
sizemode="fullscreen"
#endif
style="background: black; overflow: hidden;"
onload="shell.start();"
onunload="shell.stop();">
- <script type="application/javascript" src="chrome://browser/content/commandUtil.js"/>
<script type="application/javascript" src="chrome://browser/content/shell.js"/>
#ifndef MOZ_TOUCH
<script type="application/javascript" src="chrome://browser/content/touch.js"/>
#endif
#ifndef MOZ_WIDGET_GONK
<script type="application/javascript" src="chrome://browser/content/httpd.js"/>
#endif
- <commandset id="mainCommandSet">
- <command id="cmd_close" oncommand="CommandUpdater.doCommand(this.id);"/>
- </commandset>
-
<browser id="homescreen"
type="content-primary"
flex="1"
style="overflow: hidden;"
src="data:text/html,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/>
</window>
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -4,22 +4,21 @@ chrome.jar:
% content branding %content/branding/
% content browser %content/
* content/shell.xul (content/shell.xul)
* content/shell.js (content/shell.js)
#ifndef MOZ_TOUCH
content/touch.js (content/touch.js)
#endif
- content/commandUtil.js (content/commandUtil.js)
#ifndef MOZ_WIDGET_GONK
content/httpd.js (content/httpd.js)
#endif
content/webapi.js (content/webapi.js)
content/content.css (content/content.css)
% override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
% override chrome://global/skin/netError.css chrome://browser/content/netError.css
content/netError.xhtml (content/netError.xhtml)
content/netError.css (content/netError.css)
content/images/errorpage-larry-black.png (content/images/errorpage-larry-black.png)
content/images/errorpage-larry-white.png (content/images/errorpage-larry-white.png)
- content/images/errorpage-warning.png (content/images/errorpage-warning.png)
\ No newline at end of file
+ content/images/errorpage-warning.png (content/images/errorpage-warning.png)
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -150,16 +150,17 @@
@BINPATH@/components/dom_wifi.xpt
@BINPATH@/components/dom_system_b2g.xpt
#endif
@BINPATH@/components/dom_battery.xpt
#ifdef MOZ_B2G_BT
@BINPATH@/components/dom_bluetooth.xpt
#endif
@BINPATH@/components/dom_canvas.xpt
+@BINPATH@/components/dom_contacts.xpt
@BINPATH@/components/dom_core.xpt
@BINPATH@/components/dom_css.xpt
@BINPATH@/components/dom_events.xpt
@BINPATH@/components/dom_geolocation.xpt
@BINPATH@/components/dom_network.xpt
@BINPATH@/components/dom_notification.xpt
@BINPATH@/components/dom_html.xpt
@BINPATH@/components/dom_indexeddb.xpt
@@ -187,16 +188,17 @@
@BINPATH@/components/fastfind.xpt
@BINPATH@/components/feeds.xpt
#ifdef MOZ_GTK2
@BINPATH@/components/filepicker.xpt
#endif
@BINPATH@/components/find.xpt
@BINPATH@/components/fuel.xpt
@BINPATH@/components/gfx.xpt
+@BINPATH@/components/html5.xpt
@BINPATH@/components/htmlparser.xpt
@BINPATH@/components/imglib2.xpt
@BINPATH@/components/imgicon.xpt
@BINPATH@/components/inspector.xpt
@BINPATH@/components/intl.xpt
@BINPATH@/components/jar.xpt
@BINPATH@/components/jetpack.xpt
@BINPATH@/components/jsdservice.xpt
@@ -288,16 +290,18 @@
@BINPATH@/components/xuldoc.xpt
@BINPATH@/components/xultmpl.xpt
@BINPATH@/components/zipwriter.xpt
@BINPATH@/components/webapps.xpt
; JavaScript components
@BINPATH@/components/ConsoleAPI.manifest
@BINPATH@/components/ConsoleAPI.js
+@BINPATH@/components/ContactManager.js
+@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/FeedProcessor.manifest
@BINPATH@/components/FeedProcessor.js
@BINPATH@/components/BrowserFeeds.manifest
@BINPATH@/components/FeedConverter.js
@BINPATH@/components/FeedWriter.js
@BINPATH@/components/fuelApplication.manifest
@BINPATH@/components/fuelApplication.js
@BINPATH@/components/WebContentConverter.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -315,16 +315,18 @@ pref("browser.urlbar.match.url", "@");
// 64: javascript, 128: tabs
// E.g., 0 = show all results (no filtering), 1 = only visited pages in history,
// 2 = only bookmarks, 3 = visited bookmarks, 1+16 = history matching in the url
pref("browser.urlbar.default.behavior", 0);
pref("browser.urlbar.formatting.enabled", true);
pref("browser.urlbar.trimURLs", true);
+pref("browser.altClickSave", false);
+
// Number of milliseconds to wait for the http headers (and thus
// the Content-Disposition filename) before giving up and falling back to
// picking a filename without that info in hand so that the user sees some
// feedback from their action.
pref("browser.download.saveLinkAsFilenameTimeout", 4000);
pref("browser.download.useDownloadDir", true);
@@ -953,16 +955,17 @@ pref("services.sync.prefs.sync.addons.ig
// uncompromised Sync-connected devices.
pref("services.sync.prefs.sync.app.update.mode", true);
pref("services.sync.prefs.sync.browser.download.manager.closeWhenDone", true);
pref("services.sync.prefs.sync.browser.download.manager.retention", true);
pref("services.sync.prefs.sync.browser.download.manager.scanWhenDone", true);
pref("services.sync.prefs.sync.browser.download.manager.showWhenStarting", true);
pref("services.sync.prefs.sync.browser.formfill.enable", true);
pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
+pref("services.sync.prefs.sync.browser.newtabpage.enabled", true);
pref("services.sync.prefs.sync.browser.offline-apps.notify", true);
pref("services.sync.prefs.sync.browser.safebrowsing.enabled", true);
pref("services.sync.prefs.sync.browser.safebrowsing.malware.enabled", true);
pref("services.sync.prefs.sync.browser.search.selectedEngine", true);
pref("services.sync.prefs.sync.browser.search.update", true);
pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true);
pref("services.sync.prefs.sync.browser.startup.homepage", true);
pref("services.sync.prefs.sync.browser.startup.page", true);
@@ -1023,16 +1026,19 @@ pref("services.sync.prefs.sync.xpinstall
#endif
// Disable the error console
pref("devtools.errorconsole.enabled", false);
// Enable the Inspector
pref("devtools.inspector.enabled", true);
pref("devtools.inspector.htmlHeight", 112);
+pref("devtools.inspector.htmlPanelOpen", false);
+pref("devtools.inspector.sidebarOpen", false);
+pref("devtools.inspector.activeSidebar", "ruleview");
// Enable the Debugger
pref("devtools.debugger.enabled", false);
// The default Debugger UI height
pref("devtools.debugger.ui.height", 250);
// Enable the style inspector
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -96,35 +96,35 @@ var StarUI = {
case "popuphidden":
if (aEvent.originalTarget == this.panel) {
if (!this._element("editBookmarkPanelContent").hidden)
this.quitEditMode();
this._restoreCommandsState();
this._itemId = -1;
if (this._batching) {
- PlacesUIUtils.ptm.endBatch();
+ PlacesUtils.transactionManager.endBatch();
this._batching = false;
}
switch (this._actionOnHide) {
case "cancel": {
- PlacesUIUtils.ptm.undoTransaction();
+ PlacesUtils.transactionManager.undoTransaction();
break;
}
case "remove": {
// Remove all bookmarks for the bookmark's url, this also removes
// the tags for the url.
- PlacesUIUtils.ptm.beginBatch();
+ PlacesUtils.transactionManager.beginBatch();
let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
for (let i = 0; i < itemIds.length; i++) {
- let txn = PlacesUIUtils.ptm.removeItem(itemIds[i]);
- PlacesUIUtils.ptm.doTransaction(txn);
+ let txn = new PlacesRemoveItemTransaction(itemIds[i]);
+ PlacesUtils.transactionManager.doTransaction(txn);
}
- PlacesUIUtils.ptm.endBatch();
+ PlacesUtils.transactionManager.endBatch();
break;
}
}
this._actionOnHide = "";
}
break;
case "keypress":
if (aEvent.defaultPrevented) {
@@ -270,17 +270,17 @@ var StarUI = {
removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
this._actionOnHide = "remove";
this.panel.hidePopup();
},
beginBatch: function SU_beginBatch() {
if (!this._batching) {
- PlacesUIUtils.ptm.beginBatch();
+ PlacesUtils.transactionManager.beginBatch();
this._batching = true;
}
}
}
var PlacesCommandHook = {
/**
* Adds a bookmark to the page loaded in the given browser.
@@ -320,19 +320,20 @@ var PlacesCommandHook = {
// but open right into the "edit" state, start batching here, so
// "Cancel" in that state removes the bookmark.
StarUI.beginBatch();
}
var parent = aParent != undefined ?
aParent : PlacesUtils.unfiledBookmarksFolderId;
var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
- var txn = PlacesUIUtils.ptm.createItem(uri, parent, -1,
- title, null, [descAnno]);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesCreateBookmarkTransaction(uri, parent,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title, null, [descAnno]);
+ PlacesUtils.transactionManager.doTransaction(txn);
// Set the character-set
if (charset)
PlacesUtils.history.setCharsetForURI(uri, charset);
itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
}
// Revert the contents of the location bar
if (gURLBar)
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -20,16 +20,17 @@
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Ben Goodger <ben@bengoodger.com> (v2.0)
# Blake Ross <blakeross@telocity.com>
# Shawn Wilsher <me@shawnwilsher.com>
# Ehsan Akhgari <ehsan.akhgari@gmail.com>
# Rob Campbell <rcampbell@mozilla.com>
+# Paul Rouget <paul@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
@@ -148,31 +149,39 @@
<commandset id="inspectorCommands">
<command id="Inspector:Inspect"
oncommand="InspectorUI.toggleInspection();"/>
<command id="Inspector:Sidebar"
oncommand="InspectorUI.toggleSidebar();"/>
<command id="Inspector:Tilt"
oncommand="Tilt.initialize();"/>
+ <command id="Inspector:HTMLPanel"
+ oncommand="InspectorUI.toggleHTMLPanel();"/>
+ <command id="Inspector:CopyInner"
+ oncommand="InspectorUI.copyInnerHTML();"/>
+ <command id="Inspector:CopyOuter"
+ oncommand="InspectorUI.copyOuterHTML();"/>
+ <command id="Inspector:DeleteNode"
+ oncommand="InspectorUI.deleteNode();"/>
</commandset>
<broadcasterset id="mainBroadcasterSet">
<broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
oncommand="toggleSidebar('viewBookmarksSidebar');"/>
<!-- for both places and non-places, the sidebar lives at
chrome://browser/content/history/history-panel.xul so there are no
problems when switching between versions -->
<broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
type="checkbox" group="sidebar"
sidebarurl="chrome://browser/content/history/history-panel.xul"
oncommand="toggleSidebar('viewHistorySidebar');"/>
-
+
<broadcaster id="viewWebPanelsSidebar" autoCheck="false"
type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/web-panels.xul"
oncommand="toggleSidebar('viewWebPanelsSidebar');"/>
<!-- popup blocking menu items -->
<broadcaster id="blockedPopupAllowSite"
accesskey="&allowPopups.accesskey;"
oncommand="gPopupBlockerObserver.toggleAllowPopupsForSite(event);"/>
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -286,41 +286,41 @@ let gSyncUI = {
* "reset" -- reset sync
*/
openSetup: function SUI_openSetup(wizardType) {
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
if (win)
win.focus();
else {
- window.openDialog("chrome://browser/content/syncSetup.xul",
+ window.openDialog("chrome://browser/content/sync/setup.xul",
"weaveSetup", "centerscreen,chrome,resizable=no",
wizardType);
}
},
openAddDevice: function () {
if (!Weave.Utils.ensureMPUnlocked())
return;
let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
if (win)
win.focus();
else
- window.openDialog("chrome://browser/content/syncAddDevice.xul",
+ window.openDialog("chrome://browser/content/sync/addDevice.xul",
"syncAddDevice", "centerscreen,chrome,resizable=no");
},
openQuotaDialog: function SUI_openQuotaDialog() {
let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
if (win)
win.focus();
else
Services.ww.activeWindow.openDialog(
- "chrome://browser/content/syncQuota.xul", "",
+ "chrome://browser/content/sync/quota.xul", "",
"centerscreen,chrome,dialog,modal");
},
openPrefs: function SUI_openPrefs() {
openPreferences("paneSync");
},
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -324,22 +324,22 @@ window[chromehidden~="toolbar"] toolbar:
#status-bar ,
#mainPopupSet {
min-width: 1px;
}
%ifdef MOZ_SERVICES_SYNC
/* Sync notification UI */
#sync-notifications {
- -moz-binding: url("chrome://browser/content/syncNotification.xml#notificationbox");
+ -moz-binding: url("chrome://browser/content/sync/notification.xml#notificationbox");
overflow-y: visible !important;
}
#sync-notifications notification {
- -moz-binding: url("chrome://browser/content/syncNotification.xml#notification");
+ -moz-binding: url("chrome://browser/content/sync/notification.xml#notification");
}
%endif
/* Identity UI */
#identity-popup-content-box.unknownIdentity > #identity-popup-connectedToLabel ,
#identity-popup-content-box.unknownIdentity > #identity-popup-runByLabel ,
#identity-popup-content-box.unknownIdentity > #identity-popup-content-host ,
#identity-popup-content-box.unknownIdentity > #identity-popup-content-owner ,
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1657,22 +1657,24 @@ function delayedStartup(isLoadingBlank,
// auto-resume downloads begin (such as after crashing or quitting with
// active downloads) and speeds up the first-load of the download manager UI.
// If the user manually opens the download manager before the timeout, the
// downloads will start right away, and getting the service again won't hurt.
setTimeout(function() {
gDownloadMgr = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager);
+#ifdef XP_WIN
if (Win7Features) {
let tempScope = {};
Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm",
tempScope);
tempScope.DownloadTaskbarProgress.onBrowserWindowLoad(window);
}
+#endif
}, 10000);
#ifndef XP_MACOSX
updateEditUIVisibility();
let placesContext = document.getElementById("placesContext");
placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
#endif
@@ -6987,21 +6989,23 @@ function getPluginInfo(pluginElement)
}
}
return {mimetype: tagMimetype, pluginsPage: pluginsPage};
}
var gPluginHandler = {
+#ifdef MOZ_CRASHREPORTER
get CrashSubmit() {
delete this.CrashSubmit;
Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
return this.CrashSubmit;
},
+#endif
// Map the plugin's name to a filtered version more suitable for user UI.
makeNicePluginName : function (aName, aFilename) {
if (aName == "Shockwave Flash")
return "Adobe Flash";
// Clean up the plugin name by stripping off any trailing version numbers
// or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
@@ -7131,24 +7135,26 @@ var gPluginHandler = {
{plugins: missingPluginsArray, browser: gBrowser.selectedBrowser});
},
// Callback for user clicking on a disabled plugin
managePlugins: function (aEvent) {
BrowserOpenAddonsMgr("addons://list/plugin");
},
+#ifdef MOZ_CRASHREPORTER
// Callback for user clicking "submit a report" link
submitReport : function(pluginDumpID, browserDumpID) {
// The crash reporter wants a DOM element it can append an IFRAME to,
// which it uses to submit a form. Let's just give it gBrowser.
this.CrashSubmit.submit(pluginDumpID);
if (browserDumpID)
this.CrashSubmit.submit(browserDumpID);
},
+#endif
// Callback for user clicking a "reload page" link
reloadPage: function (browser) {
browser.reload();
},
// Callback for user clicking the help icon
openHelpPage: function () {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -33,16 +33,17 @@
# Dão Gottwald <dao@mozilla.com>
# Ehsan Akhgari <ehsan.akhgari@gmail.com>
# Robert Strong <robert.bugzilla@gmail.com>
# Rob Campbell <rcampbell@mozilla.com>
# Patrick Walton <pcwalton@mozilla.com>
# David Dahl <ddahl@mozilla.com>
# Frank Yan <fyan@mozilla.com>
# Victor Porof <vporof@mozilla.com>
+# Paul Rouget <paul@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
@@ -222,30 +223,31 @@
class="editBookmarkPanelBottomButton"
label="&editBookmark.done.label;"
default="true"
oncommand="StarUI.panel.hidePopup();"/>
#endif
</hbox>
</panel>
- <panel id="inspector-tree-panel"
- orient="vertical"
- hidden="true"
- ignorekeys="true"
- noautofocus="true"
- noautohide="true"
- titlebar="normal"
- close="true"
- label="&inspectPanelTitle.label;">
- <hbox id="tree-panel-resizer-box" align="end">
- <spacer flex="1" />
- <resizer dir="bottomend" />
- </hbox>
- </panel>
+ <menupopup id="inspector-node-popup">
+ <menuitem id="inspectorHTMLCopyInner"
+ label="&inspectorHTMLCopyInner.label;"
+ accesskey="&inspectorHTMLCopyInner.accesskey;"
+ command="Inspector:CopyInner"/>
+ <menuitem id="inspectorHTMLCopyOuter"
+ label="&inspectorHTMLCopyOuter.label;"
+ accesskey="&inspectorHTMLCopyOuter.accesskey;"
+ command="Inspector:CopyOuter"/>
+ <menuseparator/>
+ <menuitem id="inspectorHTMLDelete"
+ label="&inspectorHTMLDelete.label;"
+ accesskey="&inspectorHTMLDelete.accesskey;"
+ command="Inspector:DeleteNode"/>
+ </menupopup>
<menupopup id="toolbar-context-menu"
onpopupshowing="onViewToolbarsPopupShowing(event);">
<menuseparator/>
<menuitem command="cmd_ToggleTabsOnTop"
type="checkbox"
label="&viewTabsOnTop.label;"
accesskey="&viewTabsOnTop.accesskey;"/>
@@ -989,55 +991,54 @@
</hbox>
</hbox>
<vbox id="browser-bottombox" layer="true">
<toolbar id="inspector-toolbar"
class="devtools-toolbar"
nowindowdrag="true"
hidden="true">
- <vbox flex="1">
- <resizer id="inspector-top-resizer" flex="1"
- dir="top" disabled="true"
- element="inspector-tree-box"/>
- <hbox>
#ifdef XP_MACOSX
- <toolbarbutton id="highlighter-closebutton"
- oncommand="InspectorUI.closeInspectorUI(false);"
- tooltiptext="&inspectCloseButton.tooltiptext;"/>
+ <toolbarbutton id="highlighter-closebutton"
+ oncommand="InspectorUI.closeInspectorUI(false);"
+ tooltiptext="&inspectCloseButton.tooltiptext;"/>
#endif
- <toolbarbutton id="inspector-inspect-toolbutton"
- class="devtools-toolbarbutton"
- label="&inspectButton.label;"
- accesskey="&inspectButton.accesskey;"
- command="Inspector:Inspect"/>
- <arrowscrollbox id="inspector-breadcrumbs"
- flex="1" orient="horizontal"
- clicktoscroll="true"/>
- <hbox id="inspector-tools">
- <toolbarbutton id="inspector-3D-button"
- class="devtools-toolbarbutton"
- hidden="true"
- label="&inspect3DViewButton.label;"
- accesskey="&inspect3DViewButton.accesskey;"
- command="Inspector:Tilt"/>
- <toolbarbutton id="inspector-style-button"
- class="devtools-toolbarbutton"
- label="&inspectStyleButton.label;"
- accesskey="&inspectStyleButton.accesskey;"
- command="Inspector:Sidebar"/>
- <!-- registered tools go here -->
- </hbox>
+ <toolbarbutton id="inspector-inspect-toolbutton"
+ class="devtools-toolbarbutton"
+ label="&inspectButton.label;"
+ accesskey="&inspectButton.accesskey;"
+ command="Inspector:Inspect"/>
+ <toolbarbutton id="inspector-treepanel-toolbutton"
+ class="devtools-toolbarbutton"
+ label="&htmlPanel.label;"
+ accesskey="&htmlPanel.accesskey;"
+ tooltiptext="&htmlPanel.tooltiptext;"
+ command="Inspector:HTMLPanel"/>
+ <arrowscrollbox id="inspector-breadcrumbs"
+ flex="1" orient="horizontal"
+ clicktoscroll="true"/>
+ <hbox id="inspector-tools">
+ <toolbarbutton id="inspector-3D-button"
+ class="devtools-toolbarbutton"
+ hidden="true"
+ label="&inspect3DViewButton.label;"
+ accesskey="&inspect3DViewButton.accesskey;"
+ command="Inspector:Tilt"/>
+ <toolbarbutton id="inspector-style-button"
+ class="devtools-toolbarbutton"
+ label="&inspectStyleButton.label;"
+ accesskey="&inspectStyleButton.accesskey;"
+ command="Inspector:Sidebar"/>
+ <!-- registered tools go here -->
+ </hbox>
#ifndef XP_MACOSX
- <toolbarbutton id="highlighter-closebutton"
- oncommand="InspectorUI.closeInspectorUI(false);"
- tooltiptext="&inspectCloseButton.tooltiptext;"/>
+ <toolbarbutton id="highlighter-closebutton"
+ oncommand="InspectorUI.closeInspectorUI(false);"
+ tooltiptext="&inspectCloseButton.tooltiptext;"/>
#endif
- </hbox>
- </vbox>
</toolbar>
<toolbar id="addon-bar"
toolbarname="&addonBarCmd.label;" accesskey="&addonBarCmd.accesskey;"
collapsed="true"
class="toolbar-primary chromeclass-toolbar"
context="toolbar-context-menu" toolboxid="navigator-toolbox"
mode="icons" iconsize="small" defaulticonsize="small"
lockiconsize="true"
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -29,24 +29,16 @@
#highlighter-veil-middlebox:-moz-locale-dir(rtl) {
-moz-box-direction: reverse;
}
.inspector-breadcrumbs-button {
direction: ltr;
}
-#inspector-top-resizer {
- display: none;
-}
-
-#inspector-toolbar[treepanel-open] > vbox > #inspector-top-resizer {
- display: -moz-box;
-}
-
/*
* Node Infobar
*/
#highlighter-nodeinfobar-container {
position: absolute;
max-width: 95%;
}
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -284,19 +284,24 @@ nsContextMenu.prototype = {
// (or is in a frame), or a canvas.
this.showItem("context-viewimage", (this.onImage &&
(!this.inSyntheticDoc || this.inFrame)) || this.onCanvas);
// View video depends on not having a standalone video.
this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL);
- // View background image depends on whether there is one.
- this.showItem("context-viewbgimage", shouldShow && !this._hasMultipleBGImages);
- this.showItem("context-sep-viewbgimage", shouldShow && !this._hasMultipleBGImages);
+ // View background image depends on whether there is one, but don't make
+ // background images of a stand-alone media document available.
+ this.showItem("context-viewbgimage", shouldShow &&
+ !this._hasMultipleBGImages &&
+ !this.inSyntheticDoc);
+ this.showItem("context-sep-viewbgimage", shouldShow &&
+ !this._hasMultipleBGImages &&
+ !this.inSyntheticDoc);
document.getElementById("context-viewbgimage")
.disabled = !this.hasBGImage;
this.showItem("context-viewimageinfo", this.onImage);
},
initMiscItems: function CM_initMiscItems() {
var isTextSelected = this.isTextSelected;
@@ -382,17 +387,19 @@ nsContextMenu.prototype = {
this.showItem("context-sep-undo", this.onTextInput);
this.showItem("context-cut", this.onTextInput);
this.showItem("context-copy",
this.isContentSelected || this.onTextInput);
this.showItem("context-paste", this.onTextInput);
this.showItem("context-delete", this.onTextInput);
this.showItem("context-sep-paste", this.onTextInput);
this.showItem("context-selectall", !(this.onLink || this.onImage ||
- this.onVideo || this.onAudio) || this.isDesignMode);
+ this.onVideo || this.onAudio ||
+ this.inSyntheticDoc) ||
+ this.isDesignMode);
this.showItem("context-sep-selectall", this.isContentSelected );
// XXX dr
// ------
// nsDocumentViewer.cpp has code to determine whether we're
// on a link or an image. we really ought to be using that...
// Copy email link depends on whether we're on an email link.
--- a/browser/base/content/overrides/app-license.html
+++ b/browser/base/content/overrides/app-license.html
@@ -1,3 +1,3 @@
<p><b>Binaries</b> of this product have been made available to you by the
<a href="http://www.mozilla.org/">Mozilla Project</a> under the Mozilla
- Public License. <a href="about:rights">Know your rights</a>.</p>
+ Public License 2.0 (MPL). <a href="about:rights">Know your rights</a>.</p>
rename from browser/base/content/aboutSyncTabs-bindings.xml
rename to browser/base/content/sync/aboutSyncTabs-bindings.xml
rename from browser/base/content/aboutSyncTabs.css
rename to browser/base/content/sync/aboutSyncTabs.css
--- a/browser/base/content/aboutSyncTabs.css
+++ b/browser/base/content/sync/aboutSyncTabs.css
@@ -1,7 +1,7 @@
richlistitem[type="tab"] {
- -moz-binding: url(chrome://browser/content/aboutSyncTabs-bindings.xml#tab-listing);
+ -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#tab-listing);
}
richlistitem[type="client"] {
- -moz-binding: url(chrome://browser/content/aboutSyncTabs-bindings.xml#client-listing);
+ -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#client-listing);
}
rename from browser/base/content/aboutSyncTabs.js
rename to browser/base/content/sync/aboutSyncTabs.js
rename from browser/base/content/aboutSyncTabs.xul
rename to browser/base/content/sync/aboutSyncTabs.xul
--- a/browser/base/content/aboutSyncTabs.xul
+++ b/browser/base/content/sync/aboutSyncTabs.xul
@@ -35,30 +35,30 @@
# 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 *****
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/aboutSyncTabs.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/content/aboutSyncTabs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/sync/aboutSyncTabs.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://browser/locale/aboutSyncTabs.dtd">
%aboutSyncTabsDTD;
]>
<window id="tabs-display"
onload="RemoteTabViewer.init()"
onunload="RemoteTabViewer.uninit()"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="&tabs.otherComputers.label;">
- <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSyncTabs.js"/>
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/sync/aboutSyncTabs.js"/>
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<html:head>
<html:link rel="icon" href="chrome://browser/skin/sync-16.png"/>
</html:head>
<popupset id="contextmenus">
<menupopup id="tabListContext">
<menuitem label="&tabs.context.openTab.label;"
rename from browser/base/content/syncAddDevice.js
rename to browser/base/content/sync/addDevice.js
rename from browser/base/content/syncAddDevice.xul
rename to browser/base/content/sync/addDevice.xul
--- a/browser/base/content/syncAddDevice.xul
+++ b/browser/base/content/sync/addDevice.xul
@@ -56,19 +56,19 @@
windowtype="Sync:AddDevice"
persist="screenX screenY"
onwizardnext="return gSyncAddDevice.onWizardAdvance();"
onwizardback="return gSyncAddDevice.onWizardBack();"
onwizardcancel="gSyncAddDevice.onWizardCancel();"
onload="gSyncAddDevice.init();">
<script type="application/javascript"
- src="chrome://browser/content/syncAddDevice.js"/>
+ src="chrome://browser/content/sync/addDevice.js"/>
<script type="application/javascript"
- src="chrome://browser/content/syncUtils.js"/>
+ src="chrome://browser/content/sync/utils.js"/>
<script type="application/javascript"
src="chrome://browser/content/utilityOverlay.js"/>
<script type="application/javascript"
src="chrome://global/content/printUtils.js"/>
<wizardpage id="addDevicePage"
label="&pairDevice.title.label;"
onpageshow="gSyncAddDevice.onPageShow();">
rename from browser/base/content/syncGenericChange.js
rename to browser/base/content/sync/genericChange.js
rename from browser/base/content/syncGenericChange.xul
rename to browser/base/content/sync/genericChange.xul
--- a/browser/base/content/syncGenericChange.xul
+++ b/browser/base/content/sync/genericChange.xul
@@ -55,19 +55,19 @@
xmlns:html="http://www.w3.org/1999/xhtml"
id="change-dialog"
windowtype="Weave:ChangeSomething"
persist="screenX screenY"
onwizardnext="Change.onLoad()"
onwizardfinish="return Change.onDialogAccept();">
<script type="application/javascript"
- src="chrome://browser/content/syncGenericChange.js"/>
+ src="chrome://browser/content/sync/genericChange.js"/>
<script type="application/javascript"
- src="chrome://browser/content/syncUtils.js"/>
+ src="chrome://browser/content/sync/utils.js"/>
<script type="application/javascript"
src="chrome://global/content/printUtils.js"/>
<wizardpage id="change-page"
label="">
<description id="introText">
</description>
rename from browser/base/content/syncKey.xhtml
rename to browser/base/content/sync/key.xhtml
rename from browser/base/content/syncNotification.xml
rename to browser/base/content/sync/notification.xml
rename from browser/base/content/syncProgress.js
rename to browser/base/content/sync/progress.js
rename from browser/base/content/syncProgress.xhtml
rename to browser/base/content/sync/progress.xhtml
--- a/browser/base/content/syncProgress.xhtml
+++ b/browser/base/content/sync/progress.xhtml
@@ -59,17 +59,17 @@
<link rel="stylesheet" type="text/css" media="all"
href="chrome://browser/skin/syncProgress.css"/>
<link rel="icon" type="image/png" id="favicon"
href="chrome://browser/skin/sync-16.png"/>
<script type="text/javascript;version=1.8"
- src="chrome://browser/content/syncProgress.js"/>
+ src="chrome://browser/content/sync/progress.js"/>
</head>
<body onload="onLoad(event)" onunload="onUnload(event)" dir="&locale.dir;">
<title>&setup.successPage.title;</title>
<div id="floatingBox" class="main-content">
<div id="title">
<h1>&setup.successPage.title;</h1>
</div>
<div id="successLogo">
rename from browser/base/content/syncQuota.js
rename to browser/base/content/sync/quota.js
rename from browser/base/content/syncQuota.xul
rename to browser/base/content/sync/quota.xul
--- a/browser/base/content/syncQuota.xul
+++ b/browser/base/content/sync/quota.xul
@@ -55,17 +55,17 @@
xmlns:html="http://www.w3.org/1999/xhtml"
onload="gSyncQuota.init()"
buttons="accept,cancel"
title=""a.dialogTitle.label;"
ondialogcancel="return gSyncQuota.onCancel();"
ondialogaccept="return gSyncQuota.onAccept();">
<script type="application/javascript"
- src="chrome://browser/content/syncQuota.js"/>
+ src="chrome://browser/content/sync/quota.js"/>
<stringbundleset id="stringbundleset">
<stringbundle id="quotaStrings"
src="chrome://browser/locale/syncQuota.properties"/>
</stringbundleset>
<vbox flex="1">
<label id="usageLabel"
rename from browser/base/content/syncSetup.js
rename to browser/base/content/sync/setup.js
rename from browser/base/content/syncSetup.xul
rename to browser/base/content/sync/setup.xul
--- a/browser/base/content/syncSetup.xul
+++ b/browser/base/content/sync/setup.xul
@@ -60,19 +60,19 @@
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onwizardnext="return gSyncSetup.onWizardAdvance()"
onwizardback="return gSyncSetup.onWizardBack()"
onwizardcancel="gSyncSetup.onWizardCancel()"
onload="gSyncSetup.init()">
<script type="application/javascript"
- src="chrome://browser/content/syncSetup.js"/>
+ src="chrome://browser/content/sync/setup.js"/>
<script type="application/javascript"
- src="chrome://browser/content/syncUtils.js"/>
+ src="chrome://browser/content/sync/utils.js"/>
<script type="application/javascript"
src="chrome://browser/content/utilityOverlay.js"/>
<script type="application/javascript"
src="chrome://global/content/printUtils.js"/>
<wizardpage id="addDevicePage"
label="&pairDevice.title.label;"
onpageshow="gSyncSetup.onPageShow()">
rename from browser/base/content/syncUtils.js
rename to browser/base/content/sync/utils.js
--- a/browser/base/content/syncUtils.js
+++ b/browser/base/content/sync/utils.js
@@ -71,17 +71,17 @@ let gSyncUtils = {
// Just re-show the dialog if it's already open
let openedDialog = Services.wm.getMostRecentWindow("Sync:" + type);
if (openedDialog != null) {
openedDialog.focus();
return;
}
// Open up the change dialog
- let changeXUL = "chrome://browser/content/syncGenericChange.xul";
+ let changeXUL = "chrome://browser/content/sync/genericChange.xul";
let changeOpt = "centerscreen,chrome,resizable=no";
Services.ww.activeWindow.openDialog(changeXUL, "", changeOpt,
type, duringSetup);
},
changePassword: function () {
if (Weave.Utils.ensureMPUnlocked())
this.openChange("ChangePassword");
@@ -120,17 +120,17 @@ let gSyncUtils = {
* @param elid : ID of the form element containing the passphrase.
* @param callback : Function called once the iframe has loaded.
*/
_preparePPiframe: function(elid, callback) {
let pp = document.getElementById(elid).value;
// Create an invisible iframe whose contents we can print.
let iframe = document.createElement("iframe");
- iframe.setAttribute("src", "chrome://browser/content/syncKey.xhtml");
+ iframe.setAttribute("src", "chrome://browser/content/sync/key.xhtml");
iframe.collapsed = true;
document.documentElement.appendChild(iframe);
iframe.contentWindow.addEventListener("load", function() {
iframe.contentWindow.removeEventListener("load", arguments.callee, false);
// Insert the Sync Key into the page.
let el = iframe.contentDocument.getElementById("synckey");
el.firstChild.nodeValue = pp;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1413,17 +1413,19 @@
]]>
</body>
</method>
<method name="warnAboutClosingTabs">
<parameter name="aAll"/>
<body>
<![CDATA[
- var tabsToClose = (aAll ? this.tabs.length : this.visibleTabs.length - 1)
+ var tabsToClose = (aAll ?
+ this.tabs.length - this._removingTabs.length :
+ this.visibleTabs.length - 1)
- gBrowser._numPinnedTabs;
if (tabsToClose <= 1)
return true;
const pref = "browser.tabs.warnOnClose";
var shouldPrompt = Services.prefs.getBoolPref(pref);
if (!shouldPrompt)
@@ -1521,16 +1523,17 @@
if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
this.tabContainer._lockTabSizing(aTab);
else
this.tabContainer._unlockTabSizing();
if (!animate /* the caller didn't opt in */ ||
isLastTab ||
aTab.pinned ||
+ aTab.hidden ||
this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
!Services.prefs.getBoolPref("browser.tabs.animate")) {
this._endRemoveTab(aTab);
return;
}
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -88,17 +88,16 @@ endif
#
# browser_sanitizeDialog_treeView.js is disabled until the tree view is added
# back to the clear recent history dialog (sanitize.xul), if it ever is (bug
# 480169)
# browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
# browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
-# browser_urlbarAutoFillTrimURLs.js is disabled till bug 720792 is fixed
_BROWSER_FILES = \
head.js \
browser_typeAheadFind.js \
browser_keywordSearch.js \
browser_allTabsPanel.js \
browser_alltabslistener.js \
browser_bug304198.js \
@@ -218,16 +217,17 @@ endif
browser_scope.js \
browser_selectTabAtIndex.js \
browser_tab_dragdrop.js \
browser_tab_dragdrop2.js \
browser_tab_dragdrop2_frame1.xul \
browser_tabfocus.js \
browser_tabs_isActive.js \
browser_tabs_owner.js \
+ browser_urlbarAutoFillTrimURLs.js \
browser_urlbarCopying.js \
browser_urlbarEnter.js \
browser_urlbarRevert.js \
browser_urlbarTrimURLs.js \
browser_urlHighlight.js \
browser_visibleFindSelection.js \
browser_visibleTabs.js \
browser_visibleTabs_contextMenu.js \
--- a/browser/base/content/test/browser_contentAreaClick.js
+++ b/browser/base/content/test/browser_contentAreaClick.js
@@ -68,29 +68,37 @@ let gTests = [
expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
preventDefault: true,
},
// The next test was once handling feedService.forcePreview(). Now it should
// just be like Alt click.
{
desc: "Shift+Alt left click",
- setup: function() {},
- clean: function() {},
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
event: { shiftKey: true,
altKey: true },
targets: [ "commonlink", "maplink" ],
expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
preventDefault: true,
},
{
desc: "Shift+Alt left click on XLinks",
- setup: function() {},
- clean: function() {},
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
event: { shiftKey: true,
altKey: true },
targets: [ "mathxlink", "svgxlink"],
expectedInvokedMethods: [ "saveURL" ],
preventDefault: true,
},
{
@@ -100,28 +108,36 @@ let gTests = [
event: { shiftKey: true },
targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
preventDefault: true,
},
{
desc: "Alt click",
- setup: function() {},
- clean: function() {},
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
event: { altKey: true },
targets: [ "commonlink", "maplink" ],
expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
preventDefault: true,
},
{
desc: "Alt click on XLinks",
- setup: function() {},
- clean: function() {},
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
event: { altKey: true },
targets: [ "mathxlink", "svgxlink" ],
expectedInvokedMethods: [ "saveURL" ],
preventDefault: true,
},
{
desc: "Panel click",
@@ -144,39 +160,33 @@ let gTests = [
},
{
desc: "Simple middle click openwin",
setup: function() {
gPrefService.setBoolPref("browser.tabs.opentabfor.middleclick", false);
},
clean: function() {
- try {
- gPrefService.clearUserPref("browser.tabs.opentabfor.middleclick");
- } catch(ex) {}
+ gPrefService.clearUserPref("browser.tabs.opentabfor.middleclick");
},
event: { button: 1 },
targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
preventDefault: true,
},
{
desc: "Middle mouse paste",
setup: function() {
gPrefService.setBoolPref("middlemouse.contentLoadURL", true);
gPrefService.setBoolPref("general.autoScroll", false);
},
clean: function() {
- try {
- gPrefService.clearUserPref("middlemouse.contentLoadURL");
- } catch(ex) {}
- try {
- gPrefService.clearUserPref("general.autoScroll");
- } catch(ex) {}
+ gPrefService.clearUserPref("middlemouse.contentLoadURL");
+ gPrefService.clearUserPref("general.autoScroll");
},
event: { button: 1 },
targets: [ "emptylink" ],
expectedInvokedMethods: [ "middleMousePaste" ],
preventDefault: true,
},
];
--- a/browser/base/content/test/browser_homeDrop.js
+++ b/browser/base/content/test/browser_homeDrop.js
@@ -23,48 +23,33 @@ function test() {
ok(true, "dialog appeared in response to home button drop");
domwindow.document.documentElement.cancelDialog();
Services.wm.removeListener(dialogListener);
// Now trigger the invalid URI test
executeSoon(function () {
let consoleListener = {
observe: function (m) {
- info("m.message: " + m.message + "\n");
if (m.message.indexOf("NS_ERROR_DOM_BAD_URI") > -1) {
ok(true, "drop was blocked");
executeSoon(finish);
}
}
}
Services.console.registerListener(consoleListener);
registerCleanupFunction(function () {
Services.console.unregisterListener(consoleListener);
});
executeSoon(function () {
info("Attempting second drop, of a javascript: URI");
// The drop handler throws an exception when dragging URIs that inherit
// principal, e.g. javascript:
expectUncaughtException();
- let originalHandler = homeButtonObserver.onDrop;
- homeButtonObserver.onDrop = function (aEvent) {
- info("homeButtonObserver.onDrop called");
- try {
- originalHandler(aEvent);
- } catch (ex) {
- info("originalHandler threw an exception: " + ex);
- throw ex;
- }
- };
- registerCleanupFunction(function () {
- homeButtonObserver.onDrop = originalHandler;
- });
chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window, EventUtils);
- info("Triggered the second drop of a javascript: URI");
});
})
});
Services.wm.addListener(dialogListener);
chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "http://mochi.test:8888/"}]], "copy", window, EventUtils);
}
--- a/browser/base/content/test/browser_locationBarCommand.js
+++ b/browser/base/content/test/browser_locationBarCommand.js
@@ -4,16 +4,22 @@
const TEST_VALUE = "example.com";
const START_VALUE = "example.org";
let gFocusManager = Cc["@mozilla.org/focus-manager;1"].
getService(Ci.nsIFocusManager);
function test() {
waitForExplicitFinish();
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.altClickSave");
+ });
+ Services.prefs.setBoolPref("browser.altClickSave", true);
+
runAltLeftClickTest();
}
// Monkey patch saveURL to avoid dealing with file save code paths
var oldSaveURL = saveURL;
saveURL = function() {
ok(true, "SaveURL was called");
is(gURLBar.value, "", "Urlbar reverted to original value");
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -142,17 +142,17 @@ function whereToOpenLink( e, ignoreButto
#ifdef XP_MACOSX
if (meta || (middle && middleUsesTabs))
#else
if (ctrl || (middle && middleUsesTabs))
#endif
return shift ? "tabshifted" : "tab";
- if (alt)
+ if (alt && getBoolPref("browser.altClickSave", false))
return "save";
if (shift || (middle && !middleUsesTabs))
return "window";
return "current";
}
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -36,51 +36,51 @@ browser.jar:
* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
* content/browser/pageinfo/pageInfo.js (content/pageinfo/pageInfo.js)
* content/browser/pageinfo/pageInfo.css (content/pageinfo/pageInfo.css)
* content/browser/pageinfo/pageInfo.xml (content/pageinfo/pageInfo.xml)
* content/browser/pageinfo/feeds.js (content/pageinfo/feeds.js)
* content/browser/pageinfo/feeds.xml (content/pageinfo/feeds.xml)
* content/browser/pageinfo/permissions.js (content/pageinfo/permissions.js)
* content/browser/pageinfo/security.js (content/pageinfo/security.js)
+#ifdef MOZ_SERVICES_SYNC
+* content/browser/sync/aboutSyncTabs.xul (content/sync/aboutSyncTabs.xul)
+ content/browser/sync/aboutSyncTabs.js (content/sync/aboutSyncTabs.js)
+ content/browser/sync/aboutSyncTabs.css (content/sync/aboutSyncTabs.css)
+* content/browser/sync/aboutSyncTabs-bindings.xml (content/sync/aboutSyncTabs-bindings.xml)
+* content/browser/sync/setup.xul (content/sync/setup.xul)
+ content/browser/sync/addDevice.js (content/sync/addDevice.js)
+* content/browser/sync/addDevice.xul (content/sync/addDevice.xul)
+ content/browser/sync/setup.js (content/sync/setup.js)
+* content/browser/sync/genericChange.xul (content/sync/genericChange.xul)
+ content/browser/sync/genericChange.js (content/sync/genericChange.js)
+* content/browser/sync/key.xhtml (content/sync/key.xhtml)
+* content/browser/sync/notification.xml (content/sync/notification.xml)
+* content/browser/sync/quota.xul (content/sync/quota.xul)
+ content/browser/sync/quota.js (content/sync/quota.js)
+ content/browser/sync/utils.js (content/sync/utils.js)
+ content/browser/sync/progress.js (content/sync/progress.js)
+* content/browser/sync/progress.xhtml (content/sync/progress.xhtml)
+#endif
* content/browser/openLocation.js (content/openLocation.js)
* content/browser/openLocation.xul (content/openLocation.xul)
* content/browser/safeMode.js (content/safeMode.js)
* content/browser/safeMode.xul (content/safeMode.xul)
* content/browser/sanitize.js (content/sanitize.js)
* content/browser/sanitize.xul (content/sanitize.xul)
* content/browser/sanitizeDialog.js (content/sanitizeDialog.js)
content/browser/sanitizeDialog.css (content/sanitizeDialog.css)
* content/browser/tabbrowser.css (content/tabbrowser.css)
* content/browser/tabbrowser.xml (content/tabbrowser.xml)
* content/browser/urlbarBindings.xml (content/urlbarBindings.xml)
* content/browser/utilityOverlay.js (content/utilityOverlay.js)
* content/browser/web-panels.js (content/web-panels.js)
* content/browser/web-panels.xul (content/web-panels.xul)
* content/browser/baseMenuOverlay.xul (content/baseMenuOverlay.xul)
* content/browser/nsContextMenu.js (content/nsContextMenu.js)
-#ifdef MOZ_SERVICES_SYNC
-* content/browser/aboutSyncTabs.xul (content/aboutSyncTabs.xul)
- content/browser/aboutSyncTabs.js (content/aboutSyncTabs.js)
- content/browser/aboutSyncTabs.css (content/aboutSyncTabs.css)
-* content/browser/aboutSyncTabs-bindings.xml (content/aboutSyncTabs-bindings.xml)
-* content/browser/syncSetup.xul (content/syncSetup.xul)
- content/browser/syncAddDevice.js (content/syncAddDevice.js)
-* content/browser/syncAddDevice.xul (content/syncAddDevice.xul)
- content/browser/syncSetup.js (content/syncSetup.js)
-* content/browser/syncGenericChange.xul (content/syncGenericChange.xul)
- content/browser/syncGenericChange.js (content/syncGenericChange.js)
-* content/browser/syncKey.xhtml (content/syncKey.xhtml)
-* content/browser/syncNotification.xml (content/syncNotification.xml)
-* content/browser/syncQuota.xul (content/syncQuota.xul)
- content/browser/syncQuota.js (content/syncQuota.js)
- content/browser/syncUtils.js (content/syncUtils.js)
- content/browser/syncProgress.js (content/syncProgress.js)
-* content/browser/syncProgress.xhtml (content/syncProgress.xhtml)
-#endif
# XXX: We should exclude this one as well (bug 71895)
* content/browser/hiddenWindow.xul (content/hiddenWindow.xul)
#ifdef XP_MACOSX
* content/browser/macBrowserOverlay.xul (content/macBrowserOverlay.xul)
* content/browser/downloadManagerOverlay.xul (content/downloadManagerOverlay.xul)
* content/browser/jsConsoleOverlay.xul (content/jsConsoleOverlay.xul)
* content/browser/softwareUpdateOverlay.xul (content/softwareUpdateOverlay.xul)
#endif
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -92,19 +92,19 @@ static RedirEntry kRedirMap[] = {
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT },
{ "robots", "chrome://browser/content/aboutRobots.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT },
{ "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
#ifdef MOZ_SERVICES_SYNC
- { "sync-progress", "chrome://browser/content/syncProgress.xhtml",
+ { "sync-progress", "chrome://browser/content/sync/progress.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
- { "sync-tabs", "chrome://browser/content/aboutSyncTabs.xul",
+ { "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
nsIAboutModule::ALLOW_SCRIPT },
#endif
{ "home", "chrome://browser/content/aboutHome.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT },
{ "newtab", "chrome://browser/content/newtab/newTab.xul",
nsIAboutModule::ALLOW_SCRIPT },
{ "permissions", "chrome://browser/content/preferences/aboutPermissions.xul",
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1,8 +1,9 @@
+# -*- indent-tabs-mode: nil -*-
# ***** 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/
#
@@ -769,51 +770,94 @@ BrowserGlue.prototype = {
#ifdef MOZ_TELEMETRY_REPORTING
_showTelemetryNotification: function BG__showTelemetryNotification() {
const PREF_TELEMETRY_PROMPTED = "toolkit.telemetry.prompted";
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
const PREF_TELEMETRY_REJECTED = "toolkit.telemetry.rejected";
const PREF_TELEMETRY_INFOURL = "toolkit.telemetry.infoURL";
const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner";
+ const PREF_TELEMETRY_ENABLED_BY_DEFAULT = "toolkit.telemetry.enabledByDefault";
+ const PREF_TELEMETRY_NOTIFIED_OPTOUT = "toolkit.telemetry.notifiedOptOut";
// This is used to reprompt users when privacy message changes
const TELEMETRY_PROMPT_REV = 2;
- function appendTelemetryNotification(notifyBox, message, buttons, hideclose) {
+ // Stick notifications onto the selected tab of the active browser window.
+ var win = this.getMostRecentBrowserWindow();
+ var tabbrowser = win.gBrowser;
+ var notifyBox = tabbrowser.getNotificationBox();
+
+ var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+ var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ var productName = brandBundle.GetStringFromName("brandFullName");
+ var serverOwner = Services.prefs.getCharPref(PREF_TELEMETRY_SERVER_OWNER);
+
+ function appendTelemetryNotification(message, buttons, hideclose) {
let notification = notifyBox.appendNotification(message, "telemetry", null,
- notifyBox.PRIORITY_INFO_LOW,
- buttons);
- notification.setAttribute("hideclose", hideclose);
+ notifyBox.PRIORITY_INFO_LOW,
+ buttons);
+ if (hideclose)
+ notification.setAttribute("hideclose", hideclose);
notification.persistence = -1; // Until user closes it
return notification;
}
+ function appendLearnMoreLink(notification) {
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let link = notification.ownerDocument.createElementNS(XULNS, "label");
+ link.className = "text-link telemetry-text-link";
+ link.setAttribute("value", browserBundle.GetStringFromName("telemetryLinkLabel"));
+ let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
+ description.appendChild(link);
+ return link;
+ }
+
+ var telemetryEnabledByDefault = false;
+ try {
+ telemetryEnabledByDefault = Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED_BY_DEFAULT);
+ } catch(e) {}
+ if (telemetryEnabledByDefault) {
+ var telemetryNotifiedOptOut = false;
+ try {
+ telemetryNotifiedOptOut = Services.prefs.getBoolPref(PREF_TELEMETRY_NOTIFIED_OPTOUT);
+ } catch(e) {}
+ if (telemetryNotifiedOptOut)
+ return;
+
+ var telemetryPrompt = browserBundle.formatStringFromName("telemetryOptOutPrompt",
+ [productName, serverOwner, productName], 3);
+
+ Services.prefs.setBoolPref(PREF_TELEMETRY_NOTIFIED_OPTOUT, true);
+
+ let notification = appendTelemetryNotification(telemetryPrompt, null, false);
+ let link = appendLearnMoreLink(notification);
+ link.addEventListener('click', function() {
+ // Open the learn more url in a new tab
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ url += "how-can-i-help-submitting-performance-data";
+ tabbrowser.selectedTab = tabbrowser.addTab(url);
+ // Remove the notification on which the user clicked
+ notification.parentNode.removeNotification(notification, true);
+ }, false);
+ return;
+ }
+
var telemetryPrompted = null;
try {
telemetryPrompted = Services.prefs.getIntPref(PREF_TELEMETRY_PROMPTED);
} catch(e) {}
// If the user has seen the latest telemetry prompt, do not prompt again
// else clear old prefs and reprompt
if (telemetryPrompted === TELEMETRY_PROMPT_REV)
return;
Services.prefs.clearUserPref(PREF_TELEMETRY_PROMPTED);
Services.prefs.clearUserPref(PREF_TELEMETRY_ENABLED);
- // Stick the notification onto the selected tab of the active browser window.
- var win = this.getMostRecentBrowserWindow();
- var browser = win.gBrowser; // for closure in notification bar callback
- var notifyBox = browser.getNotificationBox();
-
- var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
- var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
-
- var productName = brandBundle.GetStringFromName("brandFullName");
- var serverOwner = Services.prefs.getCharPref(PREF_TELEMETRY_SERVER_OWNER);
- var telemetryPrompt = browserBundle.formatStringFromName("telemetryPrompt", [productName, serverOwner], 2);
+ var telemetryPrompt = browserBundle.formatStringFromName("telemetryPrompt", [productName, serverOwner], 2);
var buttons = [
{
label: browserBundle.GetStringFromName("telemetryYesButtonLabel2"),
accessKey: browserBundle.GetStringFromName("telemetryYesButtonAccessKey"),
popup: null,
callback: function(aNotificationBar, aButton) {
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
@@ -827,33 +871,27 @@ BrowserGlue.prototype = {
Services.prefs.setBoolPref(PREF_TELEMETRY_REJECTED, true);
}
}
];
// Set pref to indicate we've shown the notification.
Services.prefs.setIntPref(PREF_TELEMETRY_PROMPTED, TELEMETRY_PROMPT_REV);
- let notification = appendTelemetryNotification(notifyBox, telemetryPrompt,
- buttons, true);
- let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
- let link = notification.ownerDocument.createElementNS(XULNS, "label");
- link.className = "text-link telemetry-text-link";
- link.setAttribute("value", browserBundle.GetStringFromName("telemetryLinkLabel"));
+ let notification = appendTelemetryNotification(telemetryPrompt, buttons, true);
+ let link = appendLearnMoreLink(notification);
link.addEventListener('click', function() {
// Open the learn more url in a new tab
- browser.selectedTab = browser.addTab(Services.prefs.getCharPref(PREF_TELEMETRY_INFOURL));
+ tabbrowser.selectedTab = tabbrowser.addTab(Services.prefs.getCharPref(PREF_TELEMETRY_INFOURL));
// Remove the notification on which the user clicked
notification.parentNode.removeNotification(notification, true);
// Add a new notification to that tab, with no "Learn more" link
- notifyBox = browser.getNotificationBox();
- appendTelemetryNotification(notifyBox, telemetryPrompt, buttons, true);
+ notifyBox = tabbrowser.getNotificationBox();
+ appendTelemetryNotification(telemetryPrompt, buttons, true);
}, false);
- let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
- description.appendChild(link);
},
#endif
_showPluginUpdatePage: function BG__showPluginUpdatePage() {
Services.prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false);
var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
getService(Ci.nsIURLFormatter);
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -431,25 +431,25 @@ var BookmarkPropertiesPanel = {
break;
}
},
_beginBatch: function BPP__beginBatch() {
if (this._batching)
return;
- PlacesUIUtils.ptm.beginBatch();
+ PlacesUtils.transactionManager.beginBatch();
this._batching = true;
},
_endBatch: function BPP__endBatch() {
if (!this._batching)
return;
- PlacesUIUtils.ptm.endBatch();
+ PlacesUtils.transactionManager.endBatch();
this._batching = false;
},
_fillEditProperties: function BPP__fillEditProperties() {
gEditItemOverlay.initPanel(this._itemId,
{ hiddenRows: this._hiddenRows,
forceReadOnly: this._readOnly });
},
@@ -509,17 +509,17 @@ var BookmarkPropertiesPanel = {
onDialogCancel: function BPP_onDialogCancel() {
// The order here is important! We have to uninit the panel first, otherwise
// changes done as part of Undo may change the panel contents and by
// that force it to commit more transactions.
gEditItemOverlay.uninitPanel(true);
gEditItemOverlay = null;
this._endBatch();
- PlacesUIUtils.ptm.undoTransaction();
+ PlacesUtils.transactionManager.undoTransaction();
window.arguments[0].performed = false;
},
/**
* This method checks to see if the input fields are in a valid state.
*
* @returns true if the input is valid, false otherwise
*/
@@ -572,54 +572,69 @@ var BookmarkPropertiesPanel = {
* various fields and opening arguments of the dialog.
*/
_getCreateNewBookmarkTransaction:
function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) {
var annotations = [];
var childTransactions = [];
if (this._description) {
- childTransactions.push(
- PlacesUIUtils.ptm.editItemDescription(-1, this._description));
+ let annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO,
+ type : Ci.nsIAnnotationService.TYPE_STRING,
+ flags : 0,
+ value : this._description,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ let editItemTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
+ childTransactions.push(editItemTxn);
}
if (this._loadInSidebar) {
- childTransactions.push(
- PlacesUIUtils.ptm.setLoadInSidebar(-1, this._loadInSidebar));
+ let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+ type : Ci.nsIAnnotationService.TYPE_INT32,
+ flags : 0,
+ value : this._loadInSidebar,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
+ childTransactions.push(setLoadTxn);
}
if (this._postData) {
- childTransactions.push(
- PlacesUIUtils.ptm.editBookmarkPostData(-1, this._postData));
+ let postDataTxn = new PlacesEditBookmarkPostDataTransaction(-1, this._postData);
+ childTransactions.push(postDataTxn);
}
//XXX TODO: this should be in a transaction!
if (this._charSet)
PlacesUtils.history.setCharsetForURI(this._uri, this._charSet);
- var transactions = [PlacesUIUtils.ptm.createItem(this._uri,
- aContainer, aIndex,
- this._title, this._keyword,
- annotations,
- childTransactions)];
+ let createTxn = new PlacesCreateBookmarkTransaction(this._uri,
+ aContainer,
+ aIndex,
+ this._title,
+ this._keyword,
+ annotations,
+ childTransactions);
- return PlacesUIUtils.ptm.aggregateTransactions(this._getDialogTitle(),
- transactions);
+ return new PlacesAggregatedTransaction(this._getDialogTitle(),
+ [createTxn]);
},
/**
* Returns a childItems-transactions array representing the URIList with
* which the dialog has been opened.
*/
_getTransactionsForURIList: function BPP__getTransactionsForURIList() {
var transactions = [];
for (var i = 0; i < this._URIs.length; ++i) {
var uri = this._URIs[i];
var title = this._getURITitleFromHistory(uri);
- transactions.push(PlacesUIUtils.ptm.createItem(uri, -1, -1, title));
+ var createTxn = new PlacesCreateBookmarkTransaction(uri, -1,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title);
+ transactions.push(createTxn);
}
return transactions;
},
/**
* Returns a transaction for creating a new folder item representing the
* various fields and opening arguments of the dialog.
*/
@@ -628,29 +643,30 @@ var BookmarkPropertiesPanel = {
var annotations = [];
var childItemsTransactions;
if (this._URIs.length)
childItemsTransactions = this._getTransactionsForURIList();
if (this._description)
annotations.push(this._getDescriptionAnnotation(this._description));
- return PlacesUIUtils.ptm.createFolder(this._title, aContainer, aIndex,
- annotations, childItemsTransactions);
+ return new PlacesCreateFolderTransaction(this._title, aContainer,
+ aIndex, annotations,
+ childItemsTransactions);
},
/**
* Returns a transaction for creating a new live-bookmark item representing
* the various fields and opening arguments of the dialog.
*/
_getCreateNewLivemarkTransaction:
function BPP__getCreateNewLivemarkTransaction(aContainer, aIndex) {
- return PlacesUIUtils.ptm.createLivemark(this._feedURI, this._siteURI,
- this._title,
- aContainer, aIndex);
+ return new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI,
+ this._title,
+ aContainer, aIndex);
},
/**
* Dialog-accept code-path for creating a new item (any type)
*/
_createNewItem: function BPP__getCreateItemTransaction() {
var [container, index] = this._getInsertionPointDetails();
var txn;
@@ -661,12 +677,12 @@ var BookmarkPropertiesPanel = {
break;
case LIVEMARK_CONTAINER:
txn = this._getCreateNewLivemarkTransaction(container, index);
break;
default: // BOOKMARK_ITEM
txn = this._getCreateNewBookmarkTransaction(container, index);
}
- PlacesUIUtils.ptm.doTransaction(txn);
+ PlacesUtils.transactionManager.doTransaction(txn);
this._itemId = PlacesUtils.bookmarks.getIdForItemAt(container, index);
}
};
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1853,23 +1853,26 @@ PlacesMenu.prototype = {
this._onPopupHidden(aEvent);
break;
}
},
_onPopupHidden: function PM__onPopupHidden(aEvent) {
// Avoid handling popuphidden of inner views.
let popup = aEvent.originalTarget;
- if (!popup._placesNode || PlacesUIUtils.getViewForNode(popup) != this)
+ let placesNode = popup._placesNode;
+ if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this)
return;
// UI performance: folder queries are cheap, keep the resultnode open
// so we don't rebuild its contents whenever the popup is reopened.
- if (!PlacesUtils.nodeIsFolder(popup._placesNode))
- popup._placesNode.containerOpen = false;
+ // Though, we want to always close feed containers so their expiration
+ // status will be checked at next opening.
+ if (!PlacesUtils.nodeIsFolder(placesNode) || placesNode._feedURI)
+ placesNode.containerOpen = false;
// The autoopened attribute is set for folders which have been
// automatically opened when dragged over. Turn off this attribute
// when the folder closes because it is no longer applicable.
popup.removeAttribute("autoopened");
popup.removeAttribute("dragstart");
}
};
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -152,19 +152,19 @@ PlacesController.prototype = {
// filters out other commands that we do _not_ support (see 329587).
const CMD_PREFIX = "placesCmd_";
return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX);
},
isCommandEnabled: function PC_isCommandEnabled(aCommand) {
switch (aCommand) {
case "cmd_undo":
- return PlacesUIUtils.ptm.numberOfUndoItems > 0;
+ return PlacesUtils.transactionManager.numberOfUndoItems > 0;
case "cmd_redo":
- return PlacesUIUtils.ptm.numberOfRedoItems > 0;
+ return PlacesUtils.transactionManager.numberOfRedoItems > 0;
case "cmd_cut":
case "placesCmd_cut":
var nodes = this._view.selectedNodes;
// If selection includes history nodes there's no reason to allow cut.
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].itemId == -1)
return false;
}
@@ -225,20 +225,20 @@ PlacesController.prototype = {
default:
return false;
}
},
doCommand: function PC_doCommand(aCommand) {
switch (aCommand) {
case "cmd_undo":
- PlacesUIUtils.ptm.undoTransaction();
+ PlacesUtils.transactionManager.undoTransaction();
break;
case "cmd_redo":
- PlacesUIUtils.ptm.redoTransaction();
+ PlacesUtils.transactionManager.redoTransaction();
break;
case "cmd_cut":
case "placesCmd_cut":
this.cut();
break;
case "cmd_copy":
case "placesCmd_copy":
this.copy();
@@ -780,18 +780,18 @@ PlacesController.prototype = {
/**
* Create a new Bookmark separator somewhere.
*/
newSeparator: function PC_newSeparator() {
var ip = this._view.insertionPoint;
if (!ip)
throw Cr.NS_ERROR_NOT_AVAILABLE;
- var txn = PlacesUIUtils.ptm.createSeparator(ip.itemId, ip.index);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesCreateSeparatorTransaction(ip.itemId, ip.index);
+ PlacesUtils.transactionManager.doTransaction(txn);
// select the new item
var insertedNodeId = PlacesUtils.bookmarks
.getIdForItemAt(ip.itemId, ip.index);
this._view.selectItems([insertedNodeId], false);
},
/**
* Opens a dialog for moving the selected nodes.
@@ -802,18 +802,18 @@ PlacesController.prototype = {
this._view.selectedNodes);
},
/**
* Sort the selected folder by name
*/
sortFolderByName: function PC_sortFolderByName() {
var itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
- var txn = PlacesUIUtils.ptm.sortFolderByName(itemId);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesSortFolderByNameTransaction(itemId);
+ PlacesUtils.transactionManager.doTransaction(txn);
},
/**
* Walk the list of folders we're removing in this delete operation, and
* see if the selected node specified is already implicitly being removed
* because it is a child of that folder.
* @param node
* Node to check for containment.
@@ -867,30 +867,33 @@ PlacesController.prototype = {
if (this._shouldSkipNode(node, removedFolders))
continue;
if (PlacesUtils.nodeIsTagQuery(node.parent)) {
// This is a uri node inside a tag container. It needs a special
// untag transaction.
var tagItemId = PlacesUtils.getConcreteItemId(node.parent);
var uri = NetUtil.newURI(node.uri);
- transactions.push(PlacesUIUtils.ptm.untagURI(uri, [tagItemId]));
+ let txn = new PlacesUntagURITransaction(uri, [tagItemId]);
+ transactions.push(txn);
}
else if (PlacesUtils.nodeIsTagQuery(node) && node.parent &&
PlacesUtils.nodeIsQuery(node.parent) &&
PlacesUtils.asQuery(node.parent).queryOptions.resultType ==
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
// This is a tag container.
// Untag all URIs tagged with this tag only if the tag container is
// child of the "Tags" query in the library, in all other places we
// must only remove the query node.
var tag = node.title;
var URIs = PlacesUtils.tagging.getURIsForTag(tag);
- for (var j = 0; j < URIs.length; j++)
- transactions.push(PlacesUIUtils.ptm.untagURI(URIs[j], [tag]));
+ for (var j = 0; j < URIs.length; j++) {
+ let txn = new PlacesUntagURITransaction(URIs[j], [tag]);
+ transactions.push(txn);
+ }
}
else if (PlacesUtils.nodeIsURI(node) &&
PlacesUtils.nodeIsQuery(node.parent) &&
PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
// This is a uri node inside an history query.
PlacesUtils.bhistory.removePage(NetUtil.newURI(node.uri));
// History deletes are not undoable, so we don't have a transaction.
@@ -907,17 +910,18 @@ PlacesController.prototype = {
}
else {
// This is a common bookmark item.
if (PlacesUtils.nodeIsFolder(node)) {
// If this is a folder we add it to our array of folders, used
// to skip nodes that are children of an already removed folder.
removedFolders.push(node);
}
- transactions.push(PlacesUIUtils.ptm.removeItem(node.itemId));
+ let txn = new PlacesRemoveItemTransaction(node.itemId);
+ transactions.push(txn);
}
}
},
/**
* Removes the set of selected ranges from bookmarks.
* @param txnName
* See |remove|.
@@ -926,18 +930,18 @@ PlacesController.prototype = {
var ranges = this._view.removableSelectionRanges;
var transactions = [];
var removedFolders = [];
for (var i = 0; i < ranges.length; i++)
this._removeRange(ranges[i], transactions, removedFolders);
if (transactions.length > 0) {
- var txn = PlacesUIUtils.ptm.aggregateTransactions(txnName, transactions);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesAggregatedTransaction(txnName, transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
}
},
/**
* Removes the set of selected ranges from history.
*
* @note history deletes are not undoable.
*/
@@ -1273,37 +1277,35 @@ PlacesController.prototype = {
}
let transactions = [];
let insertionIndex = ip.index;
for (let i = 0; i < items.length; ++i) {
if (ip.isTag) {
// Pasting into a tag container means tagging the item, regardless of
// the requested action.
- transactions.push(
- new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
- [ip.itemId])
- );
+ let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
+ [ip.itemId]);
+ transactions.push(tagTxn);
continue;
}
// Adjust index to make sure items are pasted in the correct position.
// If index is DEFAULT_INDEX, items are just appended.
if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
insertionIndex = ip.index + i;
transactions.push(
PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
insertionIndex, action == "copy")
);
}
- PlacesUtils.transactionManager.doTransaction(
- new PlacesAggregatedTransaction("Paste", transactions)
- );
+ let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions);
+ PlacesUtils.transactionManager.doTransaction(aggregatedTxn);
// Cut/past operations are not repeatable, so clear the clipboard.
if (action == "cut") {
this._clearClipboard();
}
// Select the pasted items, they should be consecutive.
let insertedNodeIds = [];
@@ -1543,27 +1545,28 @@ let PlacesControllerDragHelper = {
if (index != -1 && dragginUp)
index+= movedCount++;
// If dragging over a tag container we should tag the item.
if (insertionPoint.isTag &&
insertionPoint.orientation == Ci.nsITreeView.DROP_ON) {
let uri = NetUtil.newURI(unwrapped.uri);
let tagItemId = insertionPoint.itemId;
- transactions.push(PlacesUIUtils.ptm.tagURI(uri,[tagItemId]));
+ let tagTxn = new PlacesTagURITransaction(uri, [tagItemId]);
+ transactions.push(tagTxn);
}
else {
transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
flavor, insertionPoint.itemId,
index, doCopy));
}
}
- let txn = PlacesUIUtils.ptm.aggregateTransactions("DropItems", transactions);
- PlacesUIUtils.ptm.doTransaction(txn);
+ let txn = new PlacesAggregatedTransaction("DropItems", transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
},
/**
* Checks if we can insert into a container.
* @param aContainer
* The container were we are want to drop
*/
disallowInsertion: function(aContainer) {
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -430,25 +430,28 @@ var gEditItemOverlay = {
if (tags.indexOf(currentTags[i]) == -1)
tagsToRemove.push(currentTags[i]);
}
for (var i = 0; i < tags.length; i++) {
if (currentTags.indexOf(tags[i]) == -1)
tagsToAdd.push(tags[i]);
}
- if (tagsToRemove.length > 0)
- txns.push(PlacesUIUtils.ptm.untagURI(this._uri, tagsToRemove));
- if (tagsToAdd.length > 0)
- txns.push(PlacesUIUtils.ptm.tagURI(this._uri, tagsToAdd));
+ if (tagsToRemove.length > 0) {
+ let untagTxn = new PlacesUntagURITransaction(this._uri, tagsToRemove);
+ txns.push(untagTxn);
+ }
+ if (tagsToAdd.length > 0) {
+ let tagTxn = new PlacesTagURITransaction(this._uri, tagsToAdd);
+ txns.push(tagTxn);
+ }
if (txns.length > 0) {
- var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update tags",
- txns);
- PlacesUIUtils.ptm.doTransaction(aggregate);
+ let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
+ PlacesUtils.transactionManager.doTransaction(aggregate);
// Ensure the tagsField is in sync, clean it up from empty tags
var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
this._initTextField("tagsField", tags, false);
return true;
}
}
return false;
@@ -490,105 +493,116 @@ var gEditItemOverlay = {
tagsToAdd[i] = [];
for (var j = 0; j < tags.length; j++) {
if (this._tags[i].indexOf(tags[j]) == -1)
tagsToAdd[i].push(tags[j]);
}
}
if (tagsToAdd.length > 0) {
- for (i = 0; i < this._uris.length; i++) {
- if (tagsToAdd[i].length > 0)
- txns.push(PlacesUIUtils.ptm.tagURI(this._uris[i], tagsToAdd[i]));
+ for (let i = 0; i < this._uris.length; i++) {
+ if (tagsToAdd[i].length > 0) {
+ let tagTxn = new PlacesTagURITransaction(this._uris[i],
+ tagsToAdd[i]);
+ txns.push(tagTxn);
+ }
}
}
if (tagsToRemove.length > 0) {
- for (var i = 0; i < this._uris.length; i++)
- txns.push(PlacesUIUtils.ptm.untagURI(this._uris[i], tagsToRemove));
+ for (let i = 0; i < this._uris.length; i++) {
+ let untagTxn = new PlacesUntagURITransaction(this._uris[i],
+ tagsToRemove);
+ txns.push(untagTxn);
+ }
}
if (txns.length > 0) {
- var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update tags",
- txns);
- PlacesUIUtils.ptm.doTransaction(aggregate);
+ let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
+ PlacesUtils.transactionManager.doTransaction(aggregate);
this._allTags = tags;
this._tags = [];
- for (i = 0; i < this._uris.length; i++)
+ for (let i = 0; i < this._uris.length; i++) {
this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
+ }
// Ensure the tagsField is in sync, clean it up from empty tags
this._initTextField("tagsField", tags, false);
return true;
}
}
return false;
},
onNamePickerChange: function EIO_onNamePickerChange() {
if (this._itemId == -1)
return;
var namePicker = this._element("namePicker")
- var txns = [];
- const ptm = PlacesUIUtils.ptm;
// Here we update either the item title or its cached static title
var newTitle = namePicker.value;
if (!newTitle &&
PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) == PlacesUtils.tagsFolderId) {
// We don't allow setting an empty title for a tag, restore the old one.
this._initNamePicker();
}
else if (this._getItemStaticTitle() != newTitle) {
this._mayUpdateFirstEditField("namePicker");
- txns.push(ptm.editItemTitle(this._itemId, newTitle));
+ let txn = new PlacesEditItemTitleTransaction(this._itemId, newTitle);
+ PlacesUtils.transactionManager.doTransaction(txn);
}
-
- var aggregate = ptm.aggregateTransactions("Edit Item Title", txns);
- ptm.doTransaction(aggregate);
},
onDescriptionFieldBlur: function EIO_onDescriptionFieldBlur() {
var description = this._element("descriptionField").value;
if (description != PlacesUIUtils.getItemDescription(this._itemId)) {
- var txn = PlacesUIUtils.ptm
- .editItemDescription(this._itemId, description);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO,
+ type : Ci.nsIAnnotationService.TYPE_STRING,
+ flags : 0,
+ value : description,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ var txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj);
+ PlacesUtils.transactionManager.doTransaction(txn);
}
},
onLocationFieldBlur: function EIO_onLocationFieldBlur() {
var uri;
try {
uri = PlacesUIUtils.createFixedURI(this._element("locationField").value);
}
catch(ex) { return; }
if (!this._uri.equals(uri)) {
- var txn = PlacesUIUtils.ptm.editBookmarkURI(this._itemId, uri);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesEditBookmarkURITransaction(this._itemId, uri);
+ PlacesUtils.transactionManager.doTransaction(txn);
this._uri = uri;
}
},
onKeywordFieldBlur: function EIO_onKeywordFieldBlur() {
var keyword = this._element("keywordField").value;
if (keyword != PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId)) {
- var txn = PlacesUIUtils.ptm.editBookmarkKeyword(this._itemId, keyword);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesEditBookmarkKeywordTransaction(this._itemId, keyword);
+ PlacesUtils.transactionManager.doTransaction(txn);
}
},
onLoadInSidebarCheckboxCommand:
function EIO_onLoadInSidebarCheckboxCommand() {
var loadInSidebarChecked = this._element("loadInSidebarCheckbox").checked;
- var txn = PlacesUIUtils.ptm.setLoadInSidebar(this._itemId,
- loadInSidebarChecked);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+ type : Ci.nsIAnnotationService.TYPE_INT32,
+ flags : 0,
+ value : loadInSidebarChecked,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ var txn = new PlacesSetItemAnnotationTransaction(this._itemId,
+ annoObj);
+ PlacesUtils.transactionManager.doTransaction(txn);
},
toggleFolderTreeVisibility: function EIO_toggleFolderTreeVisibility() {
var expander = this._element("foldersExpander");
var folderTreeRow = this._element("folderTreeRow");
if (!folderTreeRow.collapsed) {
expander.className = "expander-down";
expander.setAttribute("tooltiptext",
@@ -667,18 +681,20 @@ var gEditItemOverlay = {
// menulist right away
setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this);
return;
}
// Move the item
var container = this._getFolderIdFromMenuList();
if (PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) != container) {
- var txn = PlacesUIUtils.ptm.moveItem(this._itemId, container, -1);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesMoveItemTransaction(this._itemId,
+ container,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.transactionManager.doTransaction(txn);
// Mark the containing folder as recently-used if it isn't in the
// static list
if (container != PlacesUtils.unfiledBookmarksFolderId &&
container != PlacesUtils.toolbarFolderId &&
container != PlacesUtils.bookmarksMenuFolderId)
this._markFolderAsRecentlyUsed(container);
}
@@ -715,25 +731,27 @@ var gEditItemOverlay = {
_markFolderAsRecentlyUsed:
function EIO__markFolderAsRecentlyUsed(aFolderId) {
var txns = [];
// Expire old unused recent folders
var anno = this._getLastUsedAnnotationObject(false);
while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
var folderId = this._recentFolders.pop().folderId;
- txns.push(PlacesUIUtils.ptm.setItemAnnotation(folderId, anno));
+ let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno);
+ txns.push(annoTxn);
}
// Mark folder as recently used
anno = this._getLastUsedAnnotationObject(true);
- txns.push(PlacesUIUtils.ptm.setItemAnnotation(aFolderId, anno));
+ let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno);
+ txns.push(annoTxn);
- var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update last used folders", txns);
- PlacesUIUtils.ptm.doTransaction(aggregate);
+ let aggregate = new PlacesAggregatedTransaction("Update last used folders", txns);
+ PlacesUtils.transactionManager.doTransaction(aggregate);
},
/**
* Returns an object which could then be used to set/unset the
* LAST_USED_ANNO annotation for a folder.
*
* @param aLastUsed
* Whether to set or unset the LAST_USED_ANNO annotation.
@@ -835,18 +853,18 @@ var gEditItemOverlay = {
if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX,
Ci.nsITreeView.DROP_ON);
}
// XXXmano: add a separate "New Folder" string at some point...
var defaultLabel = this._element("newFolderButton").label;
- var txn = PlacesUIUtils.ptm.createFolder(defaultLabel, ip.itemId, ip.index);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index);
+ PlacesUtils.transactionManager.doTransaction(txn);
this._folderTree.focus();
this._folderTree.selectItems([this._lastNewItem]);
this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
this._folderTree.columns.getFirstColumn());
},
// nsIDOMEventListener
handleEvent: function EIO_nsIDOMEventListener(aEvent) {
--- a/browser/components/places/content/moveBookmarks.js
+++ b/browser/components/places/content/moveBookmarks.js
@@ -62,23 +62,25 @@ var gMoveBookmarksDialog = {
var selectedFolderID = PlacesUtils.getConcreteItemId(selectedNode);
var transactions = [];
for (var i=0; i < this._nodes.length; i++) {
// Nothing to do if the node is already under the selected folder
if (this._nodes[i].parent.itemId == selectedFolderID)
continue;
- transactions.push(new
- PlacesUIUtils.ptm.moveItem(this._nodes[i].itemId, selectedFolderID, -1));
+ let txn = new PlacesMoveItemTransaction(this._nodes[i].itemId,
+ selectedFolderID,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ transactions.push(txn);
}
if (transactions.length != 0) {
- var txn = PlacesUIUtils.ptm.aggregateTransactions("Move Items", transactions);
- PlacesUIUtils.ptm.doTransaction(txn);
+ let txn = new PlacesAggregatedTransaction("Move Items", transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
}
},
newFolder: function MBD_newFolder() {
// The command is disabled when the tree is not focused
this.foldersTree.focus();
goDoCommand("placesCmd_new:folder");
}
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -823,21 +823,21 @@ var PlacesOrganizer = {
var input = {value: defaultText};
var save = prompts.prompt(null, title, inputLabel, input, null, check);
// Don't add the query if the user cancels or clears the seach name.
if (!save || input.value == "")
return;
// Add the place: uri as a bookmark under the bookmarks root.
- var txn = PlacesUIUtils.ptm.createItem(placeURI,
- PlacesUtils.bookmarksMenuFolderId,
- PlacesUtils.bookmarks.DEFAULT_INDEX,
- input.value);
- PlacesUIUtils.ptm.doTransaction(txn);
+ var txn = new PlacesCreateBookmarkTransaction(placeURI,
+ PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ input.value);
+ PlacesUtils.transactionManager.doTransaction(txn);
// select and load the new query
this._places.selectPlaceURI(placeSpec);
}
};
/**
* A set of utilities relating to search within Bookmarks and History.
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1655,18 +1655,18 @@ PlacesTreeView.prototype = {
return true;
},
setCellText: function PTV_setCellText(aRow, aColumn, aText) {
// We may only get here if the cell is editable.
let node = this._rows[aRow];
if (node.title != aText) {
- let txn = PlacesUIUtils.ptm.editItemTitle(node.itemId, aText);
- PlacesUIUtils.ptm.doTransaction(txn);
+ let txn = new PlacesEditItemTitleTransaction(node.itemId, aText);
+ PlacesUtils.transactionManager.doTransaction(txn);
}
},
selectionChanged: function() { },
cycleCell: function(aRow, aColumn) { },
isSelectable: function(aRow, aColumn) { return false; },
performAction: function(aAction) { },
performActionOnRow: function(aAction, aRow) { },
--- a/browser/components/places/tests/browser/Makefile.in
+++ b/browser/components/places/tests/browser/Makefile.in
@@ -59,17 +59,17 @@ include $(topsrcdir)/config/rules.mk
browser_history_sidebar_search.js \
browser_bookmarksProperties.js \
$(warning browser_forgetthissite_single.js temporarily disabled because of very frequent oranges, see bug 551540) \
browser_library_left_pane_commands.js \
browser_drag_bookmarks_on_toolbar.js \
browser_library_middleclick.js \
browser_library_views_liveupdate.js \
browser_views_liveupdate.js \
- browser_sidebarpanels_click.js \
+ $(warning browser_sidebarpanels_click.js temporarily disabled cause it breaks the treeview, see bug 658744) \
sidebarpanels_click_test_page.html \
browser_library_infoBox.js \
browser_markPageAsFollowedLink.js \
framedPage.html \
frameLeft.html \
frameRight.html \
browser_toolbar_migration.js \
browser_library_batch_delete.js \
--- a/browser/components/places/tests/browser/browser_425884.js
+++ b/browser/components/places/tests/browser/browser_425884.js
@@ -81,40 +81,40 @@ function test() {
folderANode.containerOpen = false;
var transaction = PlacesUIUtils.makeTransaction(rawNode,
PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
testRootId,
-1,
true);
ok(transaction, "create transaction");
- PlacesUIUtils.ptm.doTransaction(transaction);
+ PlacesUtils.transactionManager.doTransaction(transaction);
// confirm copy
is(testRootNode.childCount, 2, "create test folder via copy");
// validate the copy
var folderBNode = testRootNode.getChild(1);
validate(folderBNode);
// undo the transaction, confirm the removal
- PlacesUIUtils.ptm.undoTransaction();
+ PlacesUtils.transactionManager.undoTransaction();
is(testRootNode.childCount, 1, "confirm undo removed the copied folder");
// redo the transaction
- PlacesUIUtils.ptm.redoTransaction();
+ PlacesUtils.transactionManager.redoTransaction();
is(testRootNode.childCount, 2, "confirm redo re-copied the folder");
folderBNode = testRootNode.getChild(1);
validate(folderBNode);
// Close containers, cleaning up their observers.
testRootNode.containerOpen = false;
toolbarNode.containerOpen = false;
// clean up
- PlacesUIUtils.ptm.undoTransaction();
+ PlacesUtils.transactionManager.undoTransaction();
PlacesUtils.bookmarks.removeItem(folderAId);
}
function populate(aFolderId) {
var folderId = PlacesUtils.bookmarks.createFolder(aFolderId, "test folder", -1);
PlacesUtils.bookmarks.insertBookmark(folderId, PlacesUtils._uri("http://foo"), -1, "test bookmark");
PlacesUtils.bookmarks.insertSeparator(folderId, -1);
}
--- a/browser/components/places/tests/browser/browser_457473_no_copy_guid.js
+++ b/browser/components/places/tests/browser/browser_457473_no_copy_guid.js
@@ -80,43 +80,43 @@ function test() {
// Create a copy transaction from the serialization.
// this exercises the guid-filtering
var transaction = PlacesUIUtils.makeTransaction(rawNode,
PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
testRootId, -1, true);
ok(transaction, "create transaction");
// execute it, copying to the test root folder
- PlacesUIUtils.ptm.doTransaction(transaction);
+ PlacesUtils.transactionManager.doTransaction(transaction);
is(testRootNode.childCount, 2, "create test folder via copy");
// check GUIDs are different
var folderBNode = testRootNode.getChild(1);
ok(checkGUIDs(folderBNode, folderAGUIDs, false), "confirm folder A GUIDs don't match folder B GUIDs");
var folderBGUIDs = getGUIDs(folderBNode);
ok(checkGUIDs(folderBNode, folderBGUIDs, true), "confirm test of new GUIDs");
// undo the transaction, confirm the removal
- PlacesUIUtils.ptm.undoTransaction();
+ PlacesUtils.transactionManager.undoTransaction();
is(testRootNode.childCount, 1, "confirm undo removed the copied folder");
// redo the transaction
// confirming GUIDs persist through undo/redo
- PlacesUIUtils.ptm.redoTransaction();
+ PlacesUtils.transactionManager.redoTransaction();
is(testRootNode.childCount, 2, "confirm redo re-copied the folder");
folderBNode = testRootNode.getChild(1);
ok(checkGUIDs(folderBNode, folderAGUIDs, false), "folder B GUIDs after undo/redo don't match folder A GUIDs"); // sanity check
ok(checkGUIDs(folderBNode, folderBGUIDs, true), "folder B GUIDs after under/redo should match pre-undo/redo folder B GUIDs");
// Close containers, cleaning up their observers.
testRootNode.containerOpen = false;
toolbarNode.containerOpen = false;
// clean up
- PlacesUIUtils.ptm.undoTransaction();
+ PlacesUtils.transactionManager.undoTransaction();
PlacesUtils.bookmarks.removeItem(testRootId);
}
function getGUIDs(aNode) {
PlacesUtils.asContainer(aNode);
aNode.containerOpen = true;
var GUIDs = {
folder: PlacesUtils.bookmarks.getItemGUID(aNode.itemId),
--- a/browser/components/places/tests/unit/xpcshell.ini
+++ b/browser/components/places/tests/unit/xpcshell.ini
@@ -14,11 +14,9 @@ tail =
[test_browserGlue_distribution.js]
[test_browserGlue_migrate.js]
[test_browserGlue_prefs.js]
[test_browserGlue_restore.js]
[test_browserGlue_shutdown.js]
[test_browserGlue_smartBookmarks.js]
[test_clearHistory_shutdown.js]
[test_leftpane_corruption_handling.js]
-[test_placesTxn.js]
[test_PUIU_makeTransaction.js]
-[test_txnGUIDs.js]
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -152,40 +152,40 @@ let gSyncPane = {
* "pair" -- pair a device first
* "reset" -- reset sync
*/
openSetup: function (wizardType) {
var win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
if (win)
win.focus();
else {
- window.openDialog("chrome://browser/content/syncSetup.xul",
+ window.openDialog("chrome://browser/content/sync/setup.xul",
"weaveSetup", "centerscreen,chrome,resizable=no",
wizardType);
}
},
openQuotaDialog: function () {
let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
if (win)
win.focus();
else
- window.openDialog("chrome://browser/content/syncQuota.xul", "",
+ window.openDialog("chrome://browser/content/sync/quota.xul", "",
"centerscreen,chrome,dialog,modal");
},
openAddDevice: function () {
if (!Weave.Utils.ensureMPUnlocked())
return;
let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
if (win)
win.focus();
else
- window.openDialog("chrome://browser/content/syncAddDevice.xul",
+ window.openDialog("chrome://browser/content/sync/addDevice.xul",
"syncAddDevice", "centerscreen,chrome,resizable=no");
},
resetSync: function () {
this.openSetup("reset");
}
}
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -64,17 +64,17 @@
<preference id="engine.prefs" name="services.sync.engine.prefs" type="bool"/>
<preference id="engine.passwords" name="services.sync.engine.passwords" type="bool"/>
</preferences>
<script type="application/javascript"
src="chrome://browser/content/preferences/sync.js"/>
<script type="application/javascript"
- src="chrome://browser/content/syncUtils.js"/>
+ src="chrome://browser/content/sync/utils.js"/>
<deck id="weavePrefsDeck">
<vbox id="noAccount" align="center">
<spacer flex="1"/>
<description id="syncDesc">
&weaveDesc.label;
</description>
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -1676,21 +1676,26 @@ SessionStoreService.prototype = {
if (groupsData.totalNumber > 1)
return [false, false];
}
// Step 2 of processing:
// If we're still here, then the window is usable. Look at the open tabs in
// comparison to home pages. If all the tabs are home pages then we'll end
// up overwriting all of them. Otherwise we'll just close the tabs that
- // match home pages.
- let homePages = aWindow.gHomeButton.getHomePage().split("|");
+ // match home pages. Tabs with the about:blank URI will always be
+ // overwritten.
+ let homePages = ["about:blank"];
let removableTabs = [];
let tabbrowser = aWindow.gBrowser;
let normalTabsLen = tabbrowser.tabs.length - tabbrowser._numPinnedTabs;
+ let startupPref = this._prefBranch.getIntPref("startup.page");
+ if (startupPref == 1)
+ homePages = homePages.concat(aWindow.gHomeButton.getHomePage().split("|"));
+
for (let i = tabbrowser._numPinnedTabs; i < tabbrowser.tabs.length; i++) {
let tab = tabbrowser.tabs[i];
if (homePages.indexOf(tab.linkedBrowser.currentURI.spec) != -1) {
removableTabs.push(tab);
}
}
if (tabbrowser.tabs.length == removableTabs.length) {
--- a/browser/config/mozconfigs/linux32/debug
+++ b/browser/config/mozconfigs/linux32/debug
@@ -1,10 +1,11 @@
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
. $topsrcdir/build/unix/mozconfig.linux
# Avoid dependency on libstdc++ 4.5
ac_add_options --enable-stdcxx-compat
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
--- a/browser/config/mozconfigs/linux32/nightly
+++ b/browser/config/mozconfigs/linux32/nightly
@@ -1,11 +1,12 @@
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
ac_add_options --enable-codesighs
+ac_add_options --enable-signmar
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics
. $topsrcdir/build/unix/mozconfig.linux
# Avoid dependency on libstdc++ 4.5
ac_add_options --enable-stdcxx-compat
--- a/browser/config/mozconfigs/linux64/debug
+++ b/browser/config/mozconfigs/linux64/debug
@@ -1,10 +1,11 @@
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
. $topsrcdir/build/unix/mozconfig.linux
# Avoid dependency on libstdc++ 4.5
ac_add_options --enable-stdcxx-compat
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
--- a/browser/config/mozconfigs/linux64/nightly
+++ b/browser/config/mozconfigs/linux64/nightly
@@ -1,11 +1,12 @@
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
ac_add_options --enable-codesighs
+ac_add_options --enable-signmar
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics
. $topsrcdir/build/unix/mozconfig.linux
# Avoid dependency on libstdc++ 4.5
ac_add_options --enable-stdcxx-compat
--- a/browser/config/mozconfigs/macosx-lion-universal/nightly
+++ b/browser/config/mozconfigs/macosx-lion-universal/nightly
@@ -2,16 +2,17 @@
# Universal builds override the default of browser (bug 575283 comment 29)
ac_add_options --enable-application=browser
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
ac_add_options --enable-codesighs
ac_add_options --disable-install-strip
+ac_add_options --enable-signmar
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
--- a/browser/config/mozconfigs/macosx-universal/nightly
+++ b/browser/config/mozconfigs/macosx-universal/nightly
@@ -2,16 +2,17 @@
# Universal builds override the default of browser (bug 575283 comment 29)
ac_add_options --enable-application=browser
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
ac_add_options --enable-codesighs
ac_add_options --disable-install-strip
+ac_add_options --enable-signmar
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
--- a/browser/config/mozconfigs/macosx32-lion/debug
+++ b/browser/config/mozconfigs/macosx32-lion/debug
@@ -1,11 +1,12 @@
. $topsrcdir/build/macosx/mozconfig.leopard
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j12"
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
ac_add_options --with-macbundlename-prefix=Firefox
--- a/browser/config/mozconfigs/macosx32/debug
+++ b/browser/config/mozconfigs/macosx32/debug
@@ -1,11 +1,12 @@
. $topsrcdir/build/macosx/mozconfig.leopard
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j4"
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
ac_add_options --with-macbundlename-prefix=Firefox
--- a/browser/config/mozconfigs/macosx64-lion/debug
+++ b/browser/config/mozconfigs/macosx64-lion/debug
@@ -1,13 +1,14 @@
. $topsrcdir/build/macosx/common
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
ac_add_options --enable-accessibility
+ac_add_options --enable-signmar
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j12"
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
ac_add_options --with-macbundlename-prefix=Firefox
--- a/browser/config/mozconfigs/macosx64/debug
+++ b/browser/config/mozconfigs/macosx64/debug
@@ -1,13 +1,14 @@
. $topsrcdir/build/macosx/common
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
ac_add_options --enable-accessibility
+ac_add_options --enable-signmar
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j4"
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
ac_add_options --with-macbundlename-prefix=Firefox
--- a/browser/config/mozconfigs/win32/debug
+++ b/browser/config/mozconfigs/win32/debug
@@ -1,9 +1,10 @@
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
mk_add_options MOZ_MAKE_FLAGS=-j1
. $topsrcdir/browser/config/mozconfigs/win32/vs2010-mozconfig
--- a/browser/config/mozconfigs/win32/nightly
+++ b/browser/config/mozconfigs/win32/nightly
@@ -1,14 +1,15 @@
# for pgo
mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) $(MOZ_OBJDIR)/_profile/pgo/profileserver.py'
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
--- a/browser/config/mozconfigs/win64/debug
+++ b/browser/config/mozconfigs/win64/debug
@@ -1,10 +1,11 @@
ac_add_options --target=x86_64-pc-mingw32
ac_add_options --host=x86_64-pc-mingw32
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
mk_add_options MOZ_MAKE_FLAGS=-j1
--- a/browser/config/mozconfigs/win64/nightly
+++ b/browser/config/mozconfigs/win64/nightly
@@ -2,16 +2,17 @@ ac_add_options --target=x86_64-pc-mingw3
ac_add_options --host=x86_64-pc-mingw32
# for pgo
mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) $(MOZ_OBJDIR)/_profile/pgo/profileserver.py'
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -37,30 +37,35 @@
# ***** END LICENSE BLOCK *****
MOZ_APP_BASENAME=Firefox
MOZ_APP_VENDOR=Mozilla
MOZ_UPDATER=1
MOZ_PHOENIX=1
if test "$OS_ARCH" = "WINNT"; then
- MOZ_VERIFY_MAR_SIGNATURE=1
if ! test "$HAVE_64BIT_OS"; then
+ MOZ_VERIFY_MAR_SIGNATURE=1
MOZ_MAINTENANCE_SERVICE=1
fi
fi
MOZ_CHROME_FILE_FORMAT=omni
MOZ_SAFE_BROWSING=1
MOZ_SERVICES_SYNC=1
MOZ_APP_VERSION=$FIREFOX_VERSION
MOZ_EXTENSIONS_DEFAULT=" gnomevfs"
# MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
-# Changing either of these values requires a clobber to ensure correct results,
+# Changing MOZ_*BRANDING_DIRECTORY requires a clobber to ensure correct results,
# because branding dependencies are broken.
+# MOZ_BRANDING_DIRECTORY is the default branding directory used when none is
+# specified. It should never point to the "official" branding directory.
+# For mozilla-beta, mozilla-release, or mozilla-central repositories, use
+# "nightly" branding (until bug 659568 is fixed).
+# For the mozilla-aurora repository, use "aurora".
MOZ_BRANDING_DIRECTORY=browser/branding/nightly
MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official
MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
# This should usually be the same as the value MAR_CHANNEL_ID.
# If more than one ID is needed, then you should use a comma separated list
# of values.
ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
# The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -145,45 +145,45 @@ DebuggerView.Stackframes = {
/**
* Adds a frame to the stackframes container.
* If the frame already exists (was previously added), null is returned.
* Otherwise, the newly created element is returned.
*
* @param number aDepth
* The frame depth specified by the debugger.
- * @param string aFrameIdText
- * The id to be displayed in the list.
* @param string aFrameNameText
* The name to be displayed in the list.
+ * @param string aFrameDetailsText
+ * The details to be displayed in the list.
* @return object
* The newly created html node representing the added frame.
*/
- addFrame: function DVF_addFrame(aDepth, aFrameIdText, aFrameNameText) {
+ addFrame: function DVF_addFrame(aDepth, aFrameNameText, aFrameDetailsText) {
// make sure we don't duplicate anything
if (document.getElementById("stackframe-" + aDepth)) {
return null;
}
let frame = document.createElement("div");
- let frameId = document.createElement("span");
let frameName = document.createElement("span");
+ let frameDetails = document.createElement("span");
// create a list item to be added to the stackframes container
frame.id = "stackframe-" + aDepth;
frame.className = "dbg-stackframe list-item";
- // this list should display the id and name of the frame
- frameId.className = "dbg-stackframe-id";
+ // this list should display the name and details for the frame
frameName.className = "dbg-stackframe-name";
- frameId.appendChild(document.createTextNode(aFrameIdText));
+ frameDetails.className = "dbg-stackframe-details";
frameName.appendChild(document.createTextNode(aFrameNameText));
+ frameDetails.appendChild(document.createTextNode(aFrameDetailsText));
- frame.appendChild(frameId);
frame.appendChild(frameName);
+ frame.appendChild(frameDetails);
this._frames.appendChild(frame);
// return the element for later use if necessary
return frame;
},
/**
@@ -344,17 +344,17 @@ DebuggerView.Properties = {
*/
_addScope: function DVP__addScope(aName, aId) {
// make sure the parent container exists
if (!this._vars) {
return null;
}
// compute the id of the element if not specified
- aId = aId || (aName + "-scope");
+ aId = aId || (aName.toLowerCase().trim().replace(" ", "-") + "-scope");
// contains generic nodes and functionality
let element = this._createPropertyElement(aName, aId, "scope", this._vars);
// make sure the element was created successfully
if (!element) {
dump("The debugger scope container wasn't created properly: " + aId);
return null;
@@ -536,22 +536,22 @@ DebuggerView.Properties = {
* default id set as aVar.id->aKey-property.
*
* @param object aVar
* The parent variable element.
* @param {Array} aProperty
* An array containing the key and grip properties, specifying
* the value and/or type & class of the variable (if the type
* is not specified, it will be inferred from the value).
- * e.g. ["someProp0": 42]
- * ["someProp1": true]
- * ["someProp2": "nasu"]
- * ["someProp3": { type: "undefined" }]
- * ["someProp4": { type: "null" }]
- * ["someProp5": { type: "object", class: "Object" }]
+ * e.g. ["someProp0", 42]
+ * ["someProp1", true]
+ * ["someProp2", "nasu"]
+ * ["someProp3", { type: "undefined" }]
+ * ["someProp4", { type: "null" }]
+ * ["someProp5", { type: "object", class: "Object" }]
* @param string aName
* Optional, the property name.
* @paarm string aId
* Optional, an id for the property html node.
* @return object
* The newly created html node representing the added prop.
*/
_addProperty: function DVP__addProperty(aVar, aProperty, aName, aId) {
@@ -1069,25 +1069,41 @@ DebuggerView.Scripts = {
},
/**
* Checks whether the script with the specified URL is among the scripts
* known to the debugger and shown in the list.
*
* @param string aUrl
* The script URL.
+ * @return boolean
*/
contains: function DVS_contains(aUrl) {
if (this._scripts.getElementsByAttribute("value", aUrl).length > 0) {
return true;
}
return false;
},
/**
+ * Checks whether the script with the specified label is among the scripts
+ * known to the debugger and shown in the list.
+ *
+ * @param string aLabel
+ * The script label.
+ * @return boolean
+ */
+ containsLabel: function DVS_containsLabel(aLabel) {
+ if (this._scripts.getElementsByAttribute("label", aLabel).length > 0) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
* Checks whether the script with the specified URL is selected in the list.
*
* @param string aUrl
* The script URL.
*/
isSelected: function DVS_isSelected(aUrl) {
if (this._scripts.selectedItem &&
this._scripts.selectedItem.value == aUrl) {
@@ -1104,40 +1120,40 @@ DebuggerView.Scripts = {
*/
selectScript: function DVS_selectScript(aUrl) {
for (let i = 0; i < this._scripts.itemCount; i++) {
if (this._scripts.getItemAtIndex(i).value == aUrl) {
this._scripts.selectedIndex = i;
break;
}
}
- },
+ },
/**
* Adds a script to the scripts container.
* If the script already exists (was previously added), null is returned.
* Otherwise, the newly created element is returned.
*
- * @param string aUrl
- * The script url.
+ * @param string aLabel
+ * The simplified script location to be shown.
* @param string aScript
* The source script.
- * @param string aScriptNameText
- * Optional, title displayed instead of url.
* @return object
* The newly created html node representing the added script.
*/
- addScript: function DVS_addScript(aUrl, aSource, aScriptNameText) {
+ addScript: function DVS_addScript(aLabel, aScript) {
// make sure we don't duplicate anything
- if (this.contains(aUrl)) {
+ if (this.containsLabel(aLabel)) {
return null;
}
- let script = this._scripts.appendItem(aScriptNameText || aUrl, aUrl);
- script.setUserData("sourceScript", aSource, null);
+ let script = this._scripts.appendItem(aLabel, aScript.url);
+ script.setAttribute("tooltiptext", aScript.url);
+ script.setUserData("sourceScript", aScript, null);
+
this._scripts.selectedItem = script;
return script;
},
/**
* Returns the list of URIs for scripts in the page.
*/
scriptLocations: function DVS_scriptLocations() {
--- a/browser/devtools/debugger/debugger.css
+++ b/browser/devtools/debugger/debugger.css
@@ -62,16 +62,36 @@
#stack {
width: 200px;
}
#stackframes {
overflow: auto;
}
+.dbg-stackframe {
+ display: block;
+}
+
+.dbg-stackframe-name {
+ float: left;
+}
+
+.dbg-stackframe-details {
+ float: right;
+}
+
+.dbg-stackframe-name:-moz-locale-dir(rtl) {
+ float: right;
+}
+
+.dbg-stackframe-details:-moz-locale-dir(rtl) {
+ float: left;
+}
+
/**
* Properties elements
*/
#properties {
width: 250px;
}
--- a/browser/devtools/debugger/debugger.js
+++ b/browser/devtools/debugger/debugger.js
@@ -365,20 +365,22 @@ var StackFrames = {
/**
* Adds the specified stack frame to the list.
*
* @param Debugger.Frame aFrame
* The new frame to add.
*/
_addFramePanel: function SF_addFramePanel(aFrame) {
let depth = aFrame.depth;
- let idText = "#" + aFrame.depth + " ";
- let nameText = this._frameTitle(aFrame);
+ let label = SourceScripts._getScriptLabel(aFrame.where.url);
- let panel = DebuggerView.Stackframes.addFrame(depth, idText, nameText);
+ let startText = this._frameTitle(aFrame);
+ let endText = label + ":" + aFrame.where.line;
+
+ let panel = DebuggerView.Stackframes.addFrame(depth, startText, endText);
if (panel) {
panel.stackFrame = aFrame;
}
},
/**
* Loads more stack frames from the debugger server cache.
@@ -392,17 +394,17 @@ var StackFrames = {
* Create a textual representation for the stack frame specified, for
* displaying in the stack frame list.
*
* @param Debugger.Frame aFrame
* The stack frame to label.
*/
_frameTitle: function SF_frameTitle(aFrame) {
if (aFrame.type == "call") {
- return aFrame["calleeName"] ? aFrame["calleeName"] + "()" : "(anonymous)";
+ return aFrame["calleeName"] ? aFrame["calleeName"] : "(anonymous)";
}
return "(" + aFrame.type + ")";
}
};
StackFrames.onPaused = StackFrames.onPaused.bind(StackFrames);
StackFrames.onFrames = StackFrames.onFrames.bind(StackFrames);
@@ -411,31 +413,33 @@ StackFrames.onClick = StackFrames.onClic
/**
* Keeps the source script list up-to-date, using the thread client's
* source script cache.
*/
var SourceScripts = {
pageSize: 25,
activeThread: null,
+ _labelsCache: null,
/**
* Watch a given thread client.
* @param object aThreadClient
* The thread client.
* @param function aCallback
* The next function in the initialization sequence.
*/
connect: function SS_connect(aThreadClient, aCallback) {
DebuggerView.Scripts.addChangeListener(this.onChange);
this.activeThread = aThreadClient;
aThreadClient.addListener("paused", this.onPaused);
aThreadClient.addListener("scriptsadded", this.onScripts);
aThreadClient.addListener("scriptscleared", this.onScriptsCleared);
+ this.clearLabelsCache();
this.onScriptsCleared();
aCallback && aCallback();
},
/**
* Disconnect from the client.
*/
disconnect: function TS_disconnect() {
@@ -504,36 +508,96 @@ var SourceScripts = {
if (/javascript/.test(aContentType)) {
window.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
} else {
window.editor.setMode(SourceEditor.MODES.HTML);
}
return;
}
- let url = aUrl;
- // Trim the query part.
- let q = url.indexOf('?');
- if (q > -1) {
- url = url.slice(0, q);
- }
-
- if (url.slice(-3) == ".js") {
+ if (this._trimUrlQuery(aUrl).slice(-3) == ".js") {
window.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
} else {
window.editor.setMode(SourceEditor.MODES.HTML);
}
},
/**
+ * Trims the query part of a url string, if necessary.
+ *
+ * @param string aUrl
+ * The script url.
+ * @return string
+ */
+ _trimUrlQuery: function SS_trimUrlQuery(aUrl) {
+ let q = aUrl.indexOf('?');
+ if (q > -1) {
+ return aUrl.slice(0, q);
+ }
+ return aUrl;
+ },
+
+ /**
+ * Gets a unique, simplified label from a script url.
+ * ex: a). ici://some.address.com/random/subrandom/
+ * b). ni://another.address.org/random/subrandom/page.html
+ * c). san://interesting.address.gro/random/script.js
+ * d). si://interesting.address.moc/random/another/script.js
+ * =>
+ * a). subrandom/
+ * b). page.html
+ * c). script.js
+ * d). another/script.js
+ *
+ * @param string aUrl
+ * The script url.
+ * @param string aHref
+ * The content location href to be used. If unspecified, it will
+ * defalult to debugged panrent window location.
+ * @return string
+ * The simplified label.
+ */
+ _getScriptLabel: function SS_getScriptLabel(aUrl, aHref) {
+ let url = this._trimUrlQuery(aUrl);
+
+ if (this._labelsCache[url]) {
+ return this._labelsCache[url];
+ }
+
+ let href = aHref || window.parent.content.location.href;
+ let pathElements = url.split("/");
+ let label = pathElements.pop() || (pathElements.pop() + "/");
+
+ // if the label as a leaf name is alreay present in the scripts list
+ if (DebuggerView.Scripts.containsLabel(label)) {
+ label = url.replace(href.substring(0, href.lastIndexOf("/") + 1), "");
+
+ // if the path/to/script is exactly the same, we're in different domains
+ if (DebuggerView.Scripts.containsLabel(label)) {
+ label = url;
+ }
+ }
+
+ return this._labelsCache[url] = label;
+ },
+
+ /**
+ * Clears the labels cache, populated by SS_getScriptLabel().
+ * This should be done every time the content location changes.
+ */
+ clearLabelsCache: function SS_clearLabelsCache() {
+ this._labelsCache = {};
+ },
+
+ /**
* Add the specified script to the list and display it in the editor if the
* editor is empty.
*/
_addScript: function SS_addScript(aScript) {
- DebuggerView.Scripts.addScript(aScript.url, aScript);
+ DebuggerView.Scripts.addScript(this._getScriptLabel(aScript.url), aScript);
if (window.editor.getCharCount() == 0) {
this._showScript(aScript);
}
},
/**
* Load the editor with the script text if available, otherwise fire an event
--- a/browser/devtools/debugger/test/browser_dbg_clean-exit.js
+++ b/browser/devtools/debugger/test/browser_dbg_clean-exit.js
@@ -5,19 +5,17 @@
// Test that closing a tab with the debugger in a paused state exits cleanly.
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
-const DEBUGGER_TAB_URL = "http://example.com/browser/browser/devtools/" +
- "debugger/test/" +
- "browser_dbg_debuggerstatement.html";
+const DEBUGGER_TAB_URL = EXAMPLE_URL + "browser_dbg_debuggerstatement.html";
function test() {
debug_tab_pane(DEBUGGER_TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.debuggerWindow;
--- a/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js
+++ b/browser/devtools/debugger/test/browser_dbg_debuggerstatement.js
@@ -3,19 +3,17 @@
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Tests the behavior of the debugger statement.
var gClient = null;
var gTab = null;
-const DEBUGGER_TAB_URL = "http://example.com/browser/browser/devtools/" +
- "debugger/test/" +
- "browser_dbg_debuggerstatement.html";
+const DEBUGGER_TAB_URL = EXAMPLE_URL + "browser_dbg_debuggerstatement.html";
function test()
{
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
gClient.connect(function(aType, aTraits) {
gTab = addTab(DEBUGGER_TAB_URL, function() {
attach_tab_actor_for_url(gClient, DEBUGGER_TAB_URL, function(actor, response) {
@@ -29,17 +27,18 @@ function test_early_debugger_statement(a
{
let paused = function(aEvent, aPacket) {
ok(false, "Pause shouldn't be called before we've attached!\n");
finish_test();
};
gClient.addListener("paused", paused);
// This should continue without nesting an event loop and calling
// the onPaused hook, because we haven't attached yet.
- gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement();
+ // TODO: uncomment this when bug 723563 is fixed.
+ //gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement();
gClient.removeListener("paused", paused);
// Now attach and resume...
gClient.request({ to: aActor.threadActor, type: "attach" }, function(aResponse) {
gClient.request({ to: aActor.threadActor, type: "resume" }, function(aResponse) {
test_debugger_statement(aActor);
});
--- a/browser/devtools/debugger/test/browser_dbg_listtabs.js
+++ b/browser/devtools/debugger/test/browser_dbg_listtabs.js
@@ -82,17 +82,17 @@ function test_attach_removed_tab()
removeTab(gTab2);
gTab2 = null;
gClient.addListener("paused", function(aEvent, aPacket) {
ok(false, "Attaching to an exited tab actor shouldn't generate a pause.");
finish_test();
});
gClient.request({ to: gTab2Actor, type: "attach" }, function(aResponse) {
- is(aResponse.type, "exited", "Tab should consider itself exited.");
+ is(aResponse.error, "noSuchActor", "Tab should be gone.");
finish_test();
});
}
function finish_test()
{
gClient.close(function() {
finish();
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
@@ -78,12 +78,65 @@ function testSimpleCall() {
}}, 0);
});
gDebuggee.simpleCall();
}
function resumeAndFinish() {
gDebugger.StackFrames.activeThread.resume(function() {
- removeTab(gTab);
- finish();
+ let vs = gDebugger.DebuggerView.Scripts;
+ let ss = gDebugger.SourceScripts;
+
+ is(ss._trimUrlQuery("a/b/c.d?test=1&random=4"), "a/b/c.d",
+ "Trimming the url query isn't done properly.");
+
+ let urls = [
+ { href: "ici://some.address.com/random/", leaf: "subrandom/" },
+ { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" },
+ { href: "san://interesting.address.gro/random/", leaf: "script.js" },
+ { href: "si://interesting.address.moc/random/", leaf: "script.js" },
+ { href: "si://interesting.address.moc/random/", leaf: "x/script.js" },
+ { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" },
+ { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" },
+ { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" }
+ ];
+
+ vs._scripts.removeEventListener("select", vs._onScriptsChange, false);
+
+ urls.forEach(function(url) {
+ executeSoon(function() {
+ let loc = url.href + url.leaf;
+ vs.addScript(ss._getScriptLabel(loc, url.href), { url: loc });
+ });
+ });
+
+ executeSoon(function() {
+ for (let i = 0; i < vs._scripts.itemCount; i++) {
+ let lab = vs._scripts.getItemAtIndex(i).getAttribute("label");
+ let loc = urls[i].href + urls[i].leaf;
+
+ info("label: " + i + " " + lab);
+ ok(vs.contains(loc), "Script url is incorrect: " + loc);
+ }
+
+ ok(gDebugger.DebuggerView.Scripts.containsLabel("subrandom/"),
+ "Script (0) label is incorrect.");
+ ok(gDebugger.DebuggerView.Scripts.containsLabel("page.html"),
+ "Script (1) label is incorrect.");
+ ok(gDebugger.DebuggerView.Scripts.containsLabel("script.js"),
+ "Script (2) label is incorrect.");
+ ok(gDebugger.DebuggerView.Scripts.containsLabel("si://interesting.address.moc/random/script.js"),
+ "Script (3) label is incorrect.");
+ ok(gDebugger.DebuggerView.Scripts.containsLabel("x/script.js"),
+ "Script (4) label is incorrect.");
+ ok(gDebugger.DebuggerView.Scripts.containsLabel("x/y/script.js"),
+ "Script (5) label is incorrect.");
+
+ is(vs._scripts.itemCount, 6,
+ "Got too many script items in the list!");
+
+
+ removeTab(gTab);
+ finish();
+ });
});
}
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
@@ -1,18 +1,17 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view displays function parameters.
*/
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/test/" +
- "browser_dbg_frame-parameters.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -1,18 +1,17 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view displays the properties of objects.
*/
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/test/" +
- "browser_dbg_frame-parameters.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
--- a/browser/devtools/debugger/test/browser_dbg_script-switching.js
+++ b/browser/devtools/debugger/test/browser_dbg_script-switching.js
@@ -1,18 +1,18 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that switching the displayed script in the UI works as advertised.
*/
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/" +
- "test/browser_dbg_script-switching.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+
let tempScope = {};
Cu.import("resource:///modules/source-editor.jsm", tempScope);
let SourceEditor = tempScope.SourceEditor;
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
@@ -35,16 +35,34 @@ function testScriptsDisplay() {
Services.tm.currentThread.dispatch({ run: function() {
gScripts = gDebugger.DebuggerView.Scripts._scripts;
is(gDebugger.StackFrames.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
is(gScripts.itemCount, 2, "Found the expected number of scripts.");
+ for (let i = 0; i < gScripts.itemCount; i++) {
+ info("label: " + i + " " + gScripts.getItemAtIndex(i).getAttribute("label"));
+ }
+
+ let label1 = "test-script-switching-01.js";
+ let label2 = "test-script-switching-02.js";
+
+ ok(gDebugger.DebuggerView.Scripts.contains(EXAMPLE_URL +
+ label1), "First script url is incorrect.");
+ ok(gDebugger.DebuggerView.Scripts.contains(EXAMPLE_URL +
+ label2), "Second script url is incorrect.");
+
+ ok(gDebugger.DebuggerView.Scripts.containsLabel(
+ label1), "First script label is incorrect.");
+ ok(gDebugger.DebuggerView.Scripts.containsLabel(
+ label2), "Second script label is incorrect.");
+
+
ok(gDebugger.editor.getText().search(/debugger/) != -1,
"The correct script was loaded initially.");
gDebugger.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
function onChange() {
gDebugger.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
onChange);
testSwitchPaused();
--- a/browser/devtools/debugger/test/browser_dbg_select-line.js
+++ b/browser/devtools/debugger/test/browser_dbg_select-line.js
@@ -1,18 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that selecting a stack frame loads the right script in the editor
* pane and highlights the proper line.
*/
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/" +
- "test/browser_dbg_script-switching.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+
let tempScope = {};
Cu.import("resource:///modules/source-editor.jsm", tempScope);
let SourceEditor = tempScope.SourceEditor;
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
--- a/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
+++ b/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
@@ -1,18 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that updating the editor mode sets the right highlighting engine,
* and script URIs with extra query parameters also get the right engine.
*/
-const TAB_URL = "http://example.com/browser/browser/devtools/debugger/" +
- "test/browser_dbg_update-editor-mode.html";
+const TAB_URL = EXAMPLE_URL + "browser_dbg_update-editor-mode.html";
+
let tempScope = {};
Cu.import("resource:///modules/source-editor.jsm", tempScope);
let SourceEditor = tempScope.SourceEditor;
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -9,21 +9,21 @@ let tempScope = {};
Cu.import("resource:///modules/devtools/dbg-server.jsm", tempScope);
Cu.import("resource:///modules/devtools/dbg-client.jsm", tempScope);
Cu.import("resource:///modules/Services.jsm", tempScope);
let DebuggerServer = tempScope.DebuggerServer;
let DebuggerTransport = tempScope.DebuggerTransport;
let DebuggerClient = tempScope.DebuggerClient;
let Services = tempScope.Services;
-const TAB1_URL = "http://example.com/browser/browser/devtools/debugger/test/browser_dbg_tab1.html";
+const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
-const TAB2_URL = "http://example.com/browser/browser/devtools/debugger/test/browser_dbg_tab2.html";
-
-const STACK_URL = "http://example.com/browser/browser/devtools/debugger/test/browser_dbg_stack.html";
+const TAB1_URL = EXAMPLE_URL + "browser_dbg_tab1.html";
+const TAB2_URL = EXAMPLE_URL + "browser_dbg_tab2.html";
+const STACK_URL = EXAMPLE_URL + "browser_dbg_stack.html";
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
waitForExplicitFinish();
--- a/browser/devtools/highlighter/TreePanel.jsm
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -59,74 +59,51 @@ const INSPECTOR_URI = "chrome://browser/
*/
function TreePanel(aContext, aIUI) {
this._init(aContext, aIUI);
};
TreePanel.prototype = {
showTextNodesWithWhitespace: false,
id: "treepanel", // DO NOT LOCALIZE
- openInDock: true,
+ _open: false,
/**
* The tree panel container element.
* @returns xul:panel|xul:vbox|null
* xul:panel is returned when the tree panel is not docked, or
* xul:vbox when when the tree panel is docked.
* null is returned when no container is available.
*/
get container()
{
- if (this.openInDock) {
- return this.document.getElementById("inspector-tree-box");
- }
-
- return this.document.getElementById("inspector-tree-panel");
+ return this.document.getElementById("inspector-tree-box");
},
/**
* Main TreePanel boot-strapping method. Initialize the TreePanel with the
* originating context and the InspectorUI global.
* @param aContext nsIDOMWindow (xulwindow)
* @param aIUI global InspectorUI object
*/
_init: function TP__init(aContext, aIUI)
{
this.IUI = aIUI;
this.window = aContext;
this.document = this.window.document;
+ this.button =
+ this.IUI.chromeDoc.getElementById("inspector-treepanel-toolbutton");
domplateUtils.setDOM(this.window);
this.DOMHelpers = new DOMHelpers(this.window);
let isOpen = this.isOpen.bind(this);
- this.registrationObject = {
- id: this.id,
- label: this.IUI.strings.GetStringFromName("htmlPanel.label"),
- tooltiptext: this.IUI.strings.GetStringFromName("htmlPanel.tooltiptext"),
- accesskey: this.IUI.strings.GetStringFromName("htmlPanel.accesskey"),
- context: this,
- get isOpen() isOpen(),
- show: this.open,
- hide: this.close,
- onSelect: this.select,
- panel: this.openInDock ? null : this.container,
- unregister: this.destroy,
- };
this.editingEvents = {};
-
- if (!this.openInDock) {
- this._boundClose = this.close.bind(this);
- this.container.addEventListener("popuphiding", this._boundClose, false);
- }
-
- // Register the HTML panel with the highlighter
- this.IUI.registerTool(this.registrationObject);
},
/**
* Initialization function for the TreePanel.
*/
initializeIFrame: function TP_initializeIFrame()
{
if (!this.initializingTreePanel || this.treeLoaded) {
@@ -149,138 +126,96 @@ TreePanel.prototype = {
this.select(this.IUI.selection, true);
},
/**
* Open the inspector's tree panel and initialize it.
*/
open: function TP_open()
{
- if (this.initializingTreePanel && !this.treeLoaded) {
+ if (this._open) {
return;
}
+ this._open = true;
+
+ this.button.setAttribute("checked", true);
this.initializingTreePanel = true;
- if (!this.openInDock)
- this.container.hidden = false;
this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
if (!this.treeIFrame) {
this.treeIFrame = this.document.createElement("iframe");
this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
this.treeIFrame.flex = 1;
this.treeIFrame.setAttribute("type", "content");
- }
-
- if (this.openInDock) { // Create vbox
- this.openDocked();
- return;
+ this.treeIFrame.setAttribute("context", "inspector-node-popup");
}
- let resizerBox = this.document.getElementById("tree-panel-resizer-box");
- this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox);
-
- let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
- {
- this.treeIFrame.removeEventListener("load",
- boundLoadedInitializeTreePanel, true);
- this.initializeIFrame();
- }.bind(this);
-
- let boundTreePanelShown = function treePanelShown()
- {
- this.container.removeEventListener("popupshown",
- boundTreePanelShown, false);
-
- this.treeIFrame.addEventListener("load",
- boundLoadedInitializeTreePanel, true);
-
- let src = this.treeIFrame.getAttribute("src");
- if (src != INSPECTOR_URI) {
- this.treeIFrame.setAttribute("src", INSPECTOR_URI);
- } else {
- this.treeIFrame.contentWindow.location.reload();
- }
- }.bind(this);
-
- this.container.addEventListener("popupshown", boundTreePanelShown, false);
-
- const panelWidthRatio = 7 / 8;
- const panelHeightRatio = 1 / 5;
-
- let width = parseInt(this.IUI.win.outerWidth * panelWidthRatio);
- let height = parseInt(this.IUI.win.outerHeight * panelHeightRatio);
- let y = Math.min(this.document.defaultView.screen.availHeight - height,
- this.IUI.win.innerHeight);
-
- this.container.openPopup(this.browser, "overlap", 0, 0,
- false, false);
-
- this.container.moveTo(80, y);
- this.container.sizeTo(width, height);
- },
-
- openDocked: function TP_openDocked()
- {
let treeBox = null;
- let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically
- let toolbarParent =
- 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.state = "open";
try {
treeBox.height =
Services.prefs.getIntPref("devtools.inspector.htmlHeight");
} catch(e) {
treeBox.height = 112;
}
treeBox.minHeight = 64;
- treeBox.flex = 1;
- toolbarParent.insertBefore(treeBox, toolbar);
+
+ this.splitter = this.document.createElement("splitter");
+ this.splitter.id = "inspector-tree-splitter";
- this.IUI.toolbar.setAttribute("treepanel-open", "true");
+ let container = this.document.getElementById("appcontent");
+ container.appendChild(this.splitter);
+ container.appendChild(treeBox);
treeBox.appendChild(this.treeIFrame);
- let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
+ this._boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
{
this.treeIFrame.removeEventListener("load",
- boundLoadedInitializeTreePanel, true);
+ this._boundLoadedInitializeTreePanel, true);
+ delete this._boundLoadedInitializeTreePanel;
this.initializeIFrame();
}.bind(this);
this.treeIFrame.addEventListener("load",
- boundLoadedInitializeTreePanel, true);
+ this._boundLoadedInitializeTreePanel, true);
let src = this.treeIFrame.getAttribute("src");
if (src != INSPECTOR_URI) {
this.treeIFrame.setAttribute("src", INSPECTOR_URI);
} else {
this.treeIFrame.contentWindow.location.reload();
}
},
/**
* Close the TreePanel.
*/
close: function TP_close()
{
- if (this.openInDock) {
- this.IUI.toolbar.removeAttribute("treepanel-open");
+ this._open = false;
- let treeBox = this.container;
- Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
- let treeBoxParent = treeBox.parentNode;
- treeBoxParent.removeChild(treeBox);
- } else {
- this.container.hidePopup();
+ // Stop caring about the tree iframe load if it's in progress.
+ if (this._boundLoadedInitializeTreePanel) {
+ this.treeIFrame.removeEventListener("load",
+ this._boundLoadedInitializeTreePanel, true);
+ delete this._boundLoadedInitializeTreePanel;
}
+ this.button.removeAttribute("checked");
+ let treeBox = this.container;
+ Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
+ let treeBoxParent = treeBox.parentNode;
+ treeBoxParent.removeChild(this.splitter);
+ treeBoxParent.removeChild(treeBox);
+
if (this.treePanelDiv) {
this.treePanelDiv.ownerPanel = null;
let parent = this.treePanelDiv.parentNode;
parent.removeChild(this.treePanelDiv);
delete this.treePanelDiv;
delete this.treeBrowserDocument;
}
@@ -288,20 +223,25 @@ TreePanel.prototype = {
},
/**
* Is the TreePanel open?
* @returns boolean
*/
isOpen: function TP_isOpen()
{
- if (this.openInDock)
- return this.treeLoaded && this.container;
+ return this._open;
+ },
- return this.treeLoaded && this.container.state == "open";
+ /**
+ * Toggle the TreePanel.
+ */
+ toggle: function TP_toggle()
+ {
+ this.isOpen() ? this.close() : this.open();
},
/**
* Create the ObjectBox for the given object.
* @param object nsIDOMNode
* @param isRoot boolean - Is this the root object?
* @returns InsideOutBox
*/
@@ -665,16 +605,30 @@ TreePanel.prototype = {
else
return child.repObject;
}
}
return null;
},
/**
+ * Remove a node box from the tree view.
+ * @param aElement
+ * The DOM node to remove from the HTML IOBox.
+ */
+ deleteChildBox: function TP_deleteChildBox(aElement)
+ {
+ let childBox = this.ioBox.findObjectBox(aElement);
+ if (!childBox) {
+ return;
+ }
+ childBox.parentNode.removeChild(childBox);
+ },
+
+ /**
* Destructor function. Cleanup.
*/
destroy: function TP_destroy()
{
if (this.isOpen()) {
this.close();
}
@@ -700,21 +654,16 @@ TreePanel.prototype = {
parent.removeChild(this.treeIFrame);
delete this.treeIFrame;
}
if (this.ioBox) {
this.ioBox.destroy();
delete this.ioBox;
}
-
- if (!this.openInDock) {
- this.container.removeEventListener("popuphiding", this._boundClose, false);
- delete this._boundClose;
- }
}
};
/**
* DOMHelpers
* Makes DOM traversal easier. Goes through iframes.
*
--- a/browser/devtools/highlighter/highlighter.jsm
+++ b/browser/devtools/highlighter/highlighter.jsm
@@ -38,32 +38,39 @@
* 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 ***** */
const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var EXPORTED_SYMBOLS = ["Highlighter"];
const INSPECTOR_INVISIBLE_ELEMENTS = {
"head": true,
"base": true,
"basefont": true,
"isindex": true,
"link": true,
"meta": true,
"script": true,
"style": true,
"title": true,
};
+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
+ // add ":visited" and ":link" after bug 713106 is fixed
+
/**
* A highlighter mechanism.
*
* The highlighter is built dynamically into the browser element.
* The caller is in charge of destroying the highlighter (ie, the highlighter
* won't be destroyed if a new tab is selected for example).
*
* API:
@@ -104,16 +111,18 @@ const INSPECTOR_INVISIBLE_ELEMENTS = {
*
* Events:
*
* "closed" - Highlighter is closing
* "nodeselected" - A new node has been selected
* "highlighting" - Highlighter is highlighting
* "locked" - The selected node has been locked
* "unlocked" - The selected ndoe has been unlocked
+ * "pseudoclasstoggled" - A pseudo-class lock has changed on the selected node
+
*
* Structure:
*
* <stack id="highlighter-container">
* <vbox id="highlighter-veil-container">...</vbox>
* <box id="highlighter-controls>...</vbox>
* </stack>
*
@@ -234,16 +243,27 @@ Highlighter.prototype = {
this.invalidateSize(!!aScroll);
if (oldNode !== this.node) {
this.emitEvent("nodeselected");
}
},
/**
+ * Notify that a pseudo-class lock was toggled on the highlighted element
+ *
+ * @param aPseudo - The pseudo-class to toggle, e.g. ":hover".
+ */
+ pseudoClassLockToggled: function Highlighter_pseudoClassLockToggled(aPseudo)
+ {
+ this.emitEvent("pseudoclasstoggled", [aPseudo]);
+ this.updateInfobar();
+ },
+
+ /**
* Update the highlighter size and position.
*/
invalidateSize: function Highlighter_invalidateSize(aScroll)
{
let rect = null;
if (this.node && this.isNodeHighlightable(this.node)) {
@@ -441,40 +461,91 @@ Highlighter.prototype = {
let tagNameLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
tagNameLabel.id = "highlighter-nodeinfobar-tagname";
let idLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
idLabel.id = "highlighter-nodeinfobar-id";
let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
classesBox.id = "highlighter-nodeinfobar-classes";
+
+ let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ pseudoClassesBox.id = "highlighter-nodeinfobar-pseudo-classes";
+
// Add some content to force a better boundingClientRect down below.
- classesBox.textContent = " ";
+ pseudoClassesBox.textContent = " ";
nodeInfobar.appendChild(tagNameLabel);
nodeInfobar.appendChild(idLabel);
nodeInfobar.appendChild(classesBox);
+ nodeInfobar.appendChild(pseudoClassesBox);
container.appendChild(arrowBoxTop);
container.appendChild(nodeInfobar);
container.appendChild(arrowBoxBottom);
aParent.appendChild(container);
+ nodeInfobar.onclick = (function _onInfobarRightClick(aEvent) {
+ if (aEvent.button == 2) {
+ this.openPseudoClassMenu();
+ }
+ }).bind(this);
+
let barHeight = container.getBoundingClientRect().height;
this.nodeInfo = {
tagNameLabel: tagNameLabel,
idLabel: idLabel,
classesBox: classesBox,
+ pseudoClassesBox: pseudoClassesBox,
container: container,
barHeight: barHeight,
};
},
/**
+ * Open the infobar's pseudo-class context menu.
+ */
+ openPseudoClassMenu: function Highlighter_openPseudoClassMenu()
+ {
+ let menu = this.chromeDoc.createElement("menupopup");
+ menu.id = "infobar-context-menu";
+
+ let popupSet = this.chromeDoc.getElementById("mainPopupSet");
+ popupSet.appendChild(menu);
+
+ let fragment = this.buildPseudoClassMenu();
+ menu.appendChild(fragment);
+
+ menu.openPopup(this.nodeInfo.pseudoClassesBox, "end_before", 0, 0, true, false);
+ },
+
+ /**
+ * Create the menuitems for toggling the selection's pseudo-class state
+ *
+ * @returns DocumentFragment. The menuitems for toggling pseudo-classes.
+ */
+ buildPseudoClassMenu: function IUI_buildPseudoClassesMenu()
+ {
+ let fragment = this.chromeDoc.createDocumentFragment();
+ for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
+ let pseudo = PSEUDO_CLASSES[i];
+ let item = this.chromeDoc.createElement("menuitem");
+ item.setAttribute("type", "checkbox");
+ item.setAttribute("label", pseudo);
+ item.addEventListener("command",
+ this.pseudoClassLockToggled.bind(this, pseudo), false);
+ item.setAttribute("checked", DOMUtils.hasPseudoClassLock(this.node,
+ pseudo));
+ fragment.appendChild(item);
+ }
+ return fragment;
+ },
+
+ /**
* Highlight a rectangular region.
*
* @param object aRect
* The rectangle region to highlight.
* @returns boolean
* True if the rectangle was highlighted, false otherwise.
*/
highlightRectangle: function Highlighter_highlightRectangle(aRect)
@@ -538,16 +609,24 @@ Highlighter.prototype = {
// ID
this.nodeInfo.idLabel.textContent = this.node.id ? "#" + this.node.id : "";
// Classes
let classes = this.nodeInfo.classesBox;
classes.textContent = this.node.classList.length ?
"." + Array.join(this.node.classList, ".") : "";
+
+ // Pseudo-classes
+ let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
+ return DOMUtils.hasPseudoClassLock(this.node, pseudo);
+ }, this);
+
+ let pseudoBox = this.nodeInfo.pseudoClassesBox;
+ pseudoBox.textContent = pseudos.join("");
},
/**
* Move the Infobar to the right place in the highlighter.
*/
moveInfobar: function Highlighter_moveInfobar()
{
if (this._highlightRect) {
@@ -612,18 +691,18 @@ Highlighter.prototype = {
}
},
/**
* Store page zoom factor.
*/
computeZoomFactor: function Highlighter_computeZoomFactor() {
this.zoom =
- this.win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
- .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ this.win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
.screenPixelsPerCSSPixel;
},
/////////////////////////////////////////////////////////////////////////
//// Event Emitter Mechanism
addListener: function Highlighter_addListener(aEvent, aListener)
{
@@ -800,8 +879,11 @@ Highlighter.prototype = {
if (element && element != this.node) {
this.highlight(element);
}
},
};
///////////////////////////////////////////////////////////////////////////
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+ return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
+});
--- a/browser/devtools/highlighter/inspector.html
+++ b/browser/devtools/highlighter/inspector.html
@@ -1,14 +1,14 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <link rel="stylesheet" href="chrome://browser/skin/inspector.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://browser/skin/devtools/htmlpanel.css" type="text/css"/>
</head>
<body role="application">
<div id="attribute-editor">
<input id="attribute-editor-input" />
</div>
</body>
</html>
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -37,16 +37,17 @@
* 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 ***** */
+const Cc = Components.classes;
const Cu = Components.utils;
const Ci = Components.interfaces;
const Cr = Components.results;
var EXPORTED_SYMBOLS = ["InspectorUI"];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -77,16 +78,18 @@ const INSPECTOR_NOTIFICATIONS = {
RULEVIEWREADY: "inspector-ruleview-ready",
// Event notifications for the attribute-value editor
EDITOR_OPENED: "inspector-editor-opened",
EDITOR_CLOSED: "inspector-editor-closed",
EDITOR_SAVED: "inspector-editor-saved",
};
+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
+
///////////////////////////////////////////////////////////////////////////
//// InspectorUI
/**
* Main controller class for the Inspector.
*
* @constructor
* @param nsIDOMWindow aWindow
@@ -103,17 +106,16 @@ function InspectorUI(aWindow)
this.INSPECTOR_NOTIFICATIONS = INSPECTOR_NOTIFICATIONS;
}
InspectorUI.prototype = {
browser: null,
tools: null,
toolEvents: null,
inspecting: false,
- treePanelEnabled: true,
ruleViewEnabled: true,
isDirty: false,
store: null,
/**
* Toggle the inspector interface elements on or off.
*
* @param aEvent
@@ -132,49 +134,87 @@ InspectorUI.prototype = {
* Show the Sidebar.
*/
showSidebar: function IUI_showSidebar()
{
this.sidebarBox.removeAttribute("hidden");
this.sidebarSplitter.removeAttribute("hidden");
this.stylingButton.checked = true;
- // Activate the first tool in the sidebar, only if none previously-
- // selected. We'll want to do a followup to remember selected tool-states.
+ // If no tool is already selected, show the last-used sidebar if available,
+ // otherwise just show the first.
+
if (!Array.some(this.sidebarToolbar.children,
function(btn) btn.hasAttribute("checked"))) {
- let firstButtonId = this.getToolbarButtonId(this.sidebarTools[0].id);
- this.chromeDoc.getElementById(firstButtonId).click();
+
+ let activePanel = this.sidebarTools[0];
+ let activeId = this.store.getValue(this.winID, "activeSidebar");
+ if (activeId && this.tools[activeId]) {
+ activePanel = this.tools[activeId];
+ }
+ this.activateSidebarPanel(activePanel.id);
}
+
+ this.store.setValue(this.winID, "sidebarOpen", true);
+ Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true);
},
/**
- * Hide the Sidebar.
+ * Tear down the sidebar.
*/
- hideSidebar: function IUI_hideSidebar()
+ _destroySidebar: function IUI_destroySidebar()
{
this.sidebarBox.setAttribute("hidden", "true");
this.sidebarSplitter.setAttribute("hidden", "true");
this.stylingButton.checked = false;
},
/**
+ * Hide the sidebar.
+ */
+ hideSidebar: function IUI_hideSidebar()
+ {
+ this._destroySidebar();
+ this.store.setValue(this.winID, "sidebarOpen", false);
+ Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", false);
+ },
+
+ /**
* Show or hide the sidebar. Called from the Styling button on the
* highlighter toolbar.
*/
toggleSidebar: function IUI_toggleSidebar()
{
if (!this.isSidebarOpen) {
this.showSidebar();
} else {
this.hideSidebar();
}
},
/**
+ * Activate a sidebar panel by id.
+ */
+ activateSidebarPanel: function IUI_activateSidebarPanel(aID)
+ {
+ let buttonId = this.getToolbarButtonId(aID);
+ this.chromeDoc.getElementById(buttonId).click();
+ },
+
+ get activeSidebarPanel()
+ {
+ for each (let tool in this.sidebarTools) {
+ if (this.sidebarDeck.selectedPanel == this.getToolIframe(tool)) {
+ return tool.id;
+ }
+ }
+ return null;
+ },
+
+ /**
* Getter to test if the Sidebar is open or not.
*/
get isSidebarOpen()
{
return this.stylingButton.checked &&
!this.sidebarBox.hidden &&
!this.sidebarSplitter.hidden;
},
@@ -188,16 +228,32 @@ InspectorUI.prototype = {
if (this.inspecting) {
this.stopInspecting();
} else {
this.startInspecting();
}
},
/**
+ * Toggle the TreePanel.
+ */
+ toggleHTMLPanel: function TP_toggle()
+ {
+ if (this.treePanel.isOpen()) {
+ this.treePanel.close();
+ Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false);
+ this.store.setValue(this.winID, "htmlPanelOpen", false);
+ } else {
+ this.treePanel.open();
+ Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true);
+ this.store.setValue(this.winID, "htmlPanelOpen", true);
+ }
+ },
+
+ /**
* Is the inspector UI open? Simply check if the toolbar is visible or not.
*
* @returns boolean
*/
get isInspectorOpen()
{
return this.toolbar && !this.toolbar.hidden && this.highlighter;
},
@@ -255,19 +311,17 @@ InspectorUI.prototype = {
this.toolbar = this.chromeDoc.getElementById("inspector-toolbar");
this.inspectMenuitem = this.chromeDoc.getElementById("Tools:Inspect");
this.inspectToolbutton =
this.chromeDoc.getElementById("inspector-inspect-toolbutton");
this.initTools();
this.chromeWin.Tilt.setup();
- if (this.treePanelEnabled) {
- this.treePanel = new TreePanel(this.chromeWin, this);
- }
+ this.treePanel = new TreePanel(this.chromeWin, this);
if (Services.prefs.getBoolPref("devtools.ruleview.enabled") &&
!this.toolRegistered("ruleview")) {
this.registerRuleView();
}
if (Services.prefs.getBoolPref("devtools.styleinspector.enabled") &&
!this.toolRegistered("styleinspector")) {
@@ -305,16 +359,17 @@ InspectorUI.prototype = {
label: this.strings.GetStringFromName("ruleView.label"),
tooltiptext: this.strings.GetStringFromName("ruleView.tooltiptext"),
accesskey: this.strings.GetStringFromName("ruleView.accesskey"),
context: this,
get isOpen() isOpen(),
show: this.openRuleView,
hide: this.closeRuleView,
onSelect: this.selectInRuleView,
+ onChanged: this.changeInRuleView,
panel: null,
unregister: this.destroyRuleView,
sidebar: true,
};
this.registerTool(this.ruleViewObject);
},
@@ -344,16 +399,26 @@ InspectorUI.prototype = {
}
this.isDirty = this.store.getValue(this.winID, "isDirty");
} else {
// First time inspecting, set state to no selection + live inspection.
this.store.addStore(this.winID);
this.store.setValue(this.winID, "selectedNode", null);
this.store.setValue(this.winID, "inspecting", true);
this.store.setValue(this.winID, "isDirty", this.isDirty);
+
+ this.store.setValue(this.winID, "htmlPanelOpen",
+ Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen"));
+
+ this.store.setValue(this.winID, "sidebarOpen",
+ Services.prefs.getBoolPref("devtools.inspector.sidebarOpen"));
+
+ this.store.setValue(this.winID, "activeSidebar",
+ Services.prefs.getCharPref("devtools.inspector.activeSidebar"));
+
this.win.addEventListener("pagehide", this, true);
}
},
/**
* Browse nodes according to the breadcrumbs layout, only for some specific
* elements of the UI.
*/
@@ -395,16 +460,18 @@ InspectorUI.prototype = {
*/
closeInspectorUI: function IUI_closeInspectorUI(aKeepStore)
{
// if currently editing an attribute value, closing the
// highlighter/HTML panel dismisses the editor
if (this.treePanel && this.treePanel.editingContext)
this.treePanel.closeEditor();
+ this.treePanel.destroy();
+
if (this.closing || !this.win || !this.browser) {
return;
}
let winId = new String(this.winID); // retain this to notify observers.
this.closing = true;
this.toolbar.hidden = true;
@@ -412,16 +479,17 @@ InspectorUI.prototype = {
this.removeNavigationKeys();
this.progressListener.destroy();
delete this.progressListener;
if (!aKeepStore) {
this.store.deleteStore(this.winID);
this.win.removeEventListener("pagehide", this, true);
+ this.clearPseudoClassLocks();
} else {
// Update the store before closing.
if (this.selection) {
this.store.setValue(this.winID, "selectedNode",
this.selection);
}
this.store.setValue(this.winID, "inspecting", this.inspecting);
this.store.setValue(this.winID, "isDirty", this.isDirty);
@@ -430,23 +498,22 @@ InspectorUI.prototype = {
if (this.store.isEmpty()) {
this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
}
this.chromeWin.removeEventListener("keypress", this, false);
this.stopInspecting();
- this.saveToolState(this.winID);
this.toolsDo(function IUI_toolsHide(aTool) {
this.unregisterTool(aTool);
}.bind(this));
// close the sidebar
- this.hideSidebar();
+ this._destroySidebar();
if (this.highlighter) {
this.highlighter.destroy();
this.highlighter = null;
}
if (this.breadcrumbs) {
this.breadcrumbs.destroy();
@@ -498,65 +565,108 @@ InspectorUI.prototype = {
return;
}
this.inspectToolbutton.checked = false;
this.inspecting = false;
this.toolsDim(false);
if (this.highlighter.getNode()) {
- this.select(this.highlighter.getNode(), true, true, !aPreventScroll);
+ this.select(this.highlighter.getNode(), true, !aPreventScroll);
} else {
this.select(null, true, true);
}
this.highlighter.lock();
},
/**
- * Select an object in the tree view.
+ * Select an object in the inspector.
* @param aNode
* node to inspect
* @param forceUpdate
* force an update?
* @param aScroll boolean
* scroll the tree panel?
+ * @param aFrom [optional] string
+ * which part of the UI the selection occured from
*/
- select: function IUI_select(aNode, forceUpdate, aScroll)
+ select: function IUI_select(aNode, forceUpdate, aScroll, aFrom)
{
// if currently editing an attribute value, using the
// highlighter dismisses the editor
if (this.treePanel && this.treePanel.editingContext)
this.treePanel.closeEditor();
if (!aNode)
aNode = this.defaultSelection;
if (forceUpdate || aNode != this.selection) {
+ if (aFrom != "breadcrumbs") {
+ this.clearPseudoClassLocks();
+ }
+
this.selection = aNode;
if (!this.inspecting) {
this.highlighter.highlight(this.selection);
}
}
this.breadcrumbs.update();
this.chromeWin.Tilt.update(aNode);
+ this.treePanel.select(aNode, aScroll);
this.toolsSelect(aScroll);
},
+
+ /**
+ * Toggle the pseudo-class lock on the currently inspected element. If the
+ * pseudo-class is :hover or :active, that pseudo-class will also be toggled
+ * on every ancestor of the element, mirroring real :hover and :active
+ * behavior.
+ *
+ * @param aPseudo the pseudo-class lock to toggle, e.g. ":hover"
+ */
+ togglePseudoClassLock: function IUI_togglePseudoClassLock(aPseudo)
+ {
+ if (DOMUtils.hasPseudoClassLock(this.selection, aPseudo)) {
+ this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
+ DOMUtils.removePseudoClassLock(crumb.node, aPseudo);
+ });
+ } else {
+ let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
+ let node = this.selection;
+ do {
+ DOMUtils.addPseudoClassLock(node, aPseudo);
+ node = node.parentNode;
+ } while (hierarchical && node.parentNode)
+ }
+ this.nodeChanged();
+ },
+
+ /**
+ * Clear all pseudo-class locks applied to elements in the node hierarchy
+ */
+ clearPseudoClassLocks: function IUI_clearPseudoClassLocks()
+ {
+ this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
+ DOMUtils.clearPseudoClassLocks(crumb.node);
+ });
+ },
/**
* Called when the highlighted node is changed by a tool.
*
* @param object aUpdater
* The tool that triggered the update (if any), that tool's
* onChanged will not be called.
*/
nodeChanged: function IUI_nodeChanged(aUpdater)
{
this.highlighter.invalidateSize();
+ this.breadcrumbs.updateSelectors();
this.toolsOnChanged(aUpdater);
},
/////////////////////////////////////////////////////////////////////////
//// Event Handling
highlighterReady: function IUI_highlighterReady()
{
@@ -572,27 +682,42 @@ InspectorUI.prototype = {
this.highlighter.addListener("unlocked", function() {
self.startInspecting();
});
this.highlighter.addListener("nodeselected", function() {
self.select(self.highlighter.getNode(), false, false);
});
+ this.highlighter.addListener("pseudoclasstoggled", function(aPseudo) {
+ self.togglePseudoClassLock(aPseudo);
+ });
+
if (this.store.getValue(this.winID, "inspecting")) {
this.startInspecting();
+ this.highlighter.unlock();
+ } else {
+ this.highlighter.lock();
}
- this.restoreToolState(this.winID);
+ Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
this.win.focus();
+ this.highlighter.highlight();
+
+ if (this.store.getValue(this.winID, "htmlPanelOpen")) {
+ this.treePanel.open();
+ }
+
+ if (this.store.getValue(this.winID, "sidebarOpen")) {
+ this.showSidebar();
+ }
+
Services.obs.notifyObservers({wrappedJSObject: this},
INSPECTOR_NOTIFICATIONS.OPENED, null);
-
- this.highlighter.highlight();
},
/**
* Main callback handler for events.
*
* @param event
* The event to be handled.
*/
@@ -709,16 +834,57 @@ InspectorUI.prototype = {
this.highlighter.highlight(node, true);
}
event.preventDefault();
event.stopPropagation();
break;
}
},
+ /**
+ * Copy the innerHTML of the selected Node to the clipboard. Called via the
+ * Inspector:CopyInner command.
+ */
+ copyInnerHTML: function IUI_copyInnerHTML()
+ {
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(this.selection.innerHTML);
+ },
+
+ /**
+ * Copy the outerHTML of the selected Node to the clipboard. Called via the
+ * Inspector:CopyOuter command.
+ */
+ copyOuterHTML: function IUI_copyOuterHTML()
+ {
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(this.selection.outerHTML);
+ },
+
+ /**
+ * Delete the selected node. Called via the Inspector:DeleteNode command.
+ */
+ deleteNode: function IUI_deleteNode()
+ {
+ let selection = this.selection;
+ let parent = this.selection.parentNode;
+
+ // remove the node from the treepanel
+ if (this.treePanel.isOpen())
+ this.treePanel.deleteChildBox(selection);
+
+ // remove the node from content
+ parent.removeChild(selection);
+ this.breadcrumbs.invalidateHierarchy();
+
+ // select the parent node in the highlighter, treepanel, breadcrumbs
+ this.inspectNode(parent);
+ },
/////////////////////////////////////////////////////////////////////////
//// CssRuleView methods
/**
* Is the cssRuleView open?
*/
isRuleViewOpen: function IUI_isRuleViewOpen()
@@ -791,16 +957,25 @@ InspectorUI.prototype = {
* Update the selected node in the Css Rule View.
* @param {nsIDOMnode} the selected node.
*/
selectInRuleView: function IUI_selectInRuleView(aNode)
{
if (this.ruleView)
this.ruleView.highlight(aNode);
},
+
+ /**
+ * Update the rules for the current node in the Css Rule View.
+ */
+ changeInRuleView: function IUI_selectInRuleView()
+ {
+ if (this.ruleView)
+ this.ruleView.nodeChanged();
+ },
ruleViewChanged: function IUI_ruleViewChanged()
{
this.isDirty = true;
this.nodeChanged(this.ruleViewObject);
},
/**
@@ -1055,16 +1230,17 @@ InspectorUI.prototype = {
btn.setAttribute("type", "radio");
btn.setAttribute("group", "sidebar-tools");
this.sidebarToolbar.appendChild(btn);
// create tool iframe
let iframe = this.chromeDoc.createElement("iframe");
iframe.id = "devtools-sidebar-iframe-" + aRegObj.id;
iframe.setAttribute("flex", "1");
+ iframe.setAttribute("tooltip", "aHTMLTooltip");
this.sidebarDeck.appendChild(iframe);
// wire up button to show the iframe
this.bindToolEvent(btn, "click", function showIframe() {
this.toolShow(aRegObj);
}.bind(this));
},
@@ -1082,16 +1258,18 @@ InspectorUI.prototype = {
* Show the specified tool.
* @param aTool Object (see comment for IUI_registerTool)
*/
toolShow: function IUI_toolShow(aTool)
{
let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id));
btn.setAttribute("checked", "true");
if (aTool.sidebar) {
+ Services.prefs.setCharPref("devtools.inspector.activeSidebar", aTool.id);
+ this.store.setValue(this.winID, "activeSidebar", aTool.id);
this.sidebarDeck.selectedPanel = this.getToolIframe(aTool);
this.sidebarTools.forEach(function(other) {
if (other != aTool)
this.chromeDoc.getElementById(
this.getToolbarButtonId(other.id)).removeAttribute("checked");
}.bind(this));
}
@@ -1176,67 +1354,16 @@ InspectorUI.prototype = {
// the iframe.
if (aRegObj.unregister)
aRegObj.unregister.call(aRegObj.context);
delete this.tools[aRegObj.id];
},
/**
- * Save a list of open tools to the inspector store.
- *
- * @param aWinID The ID of the window used to save the associated tools
- */
- saveToolState: function IUI_saveToolState(aWinID)
- {
- let openTools = {};
- this.toolsDo(function IUI_toolsSetId(aTool) {
- if (aTool.isOpen) {
- openTools[aTool.id] = true;
- }
- });
- this.store.setValue(aWinID, "openTools", openTools);
- },
-
- /**
- * Restore tools previously save using saveToolState().
- *
- * @param aWinID The ID of the window to which the associated tools are to be
- * restored.
- */
- restoreToolState: function IUI_restoreToolState(aWinID)
- {
- let openTools = this.store.getValue(aWinID, "openTools");
- let activeSidebarTool;
- if (openTools) {
- this.toolsDo(function IUI_toolsOnShow(aTool) {
- if (aTool.id in openTools) {
- if (aTool.sidebar && !this.isSidebarOpen) {
- this.showSidebar();
- activeSidebarTool = aTool;
- }
- this.toolShow(aTool);
- }
- }.bind(this));
- this.sidebarTools.forEach(function(tool) {
- if (tool != activeSidebarTool)
- this.chromeDoc.getElementById(
- this.getToolbarButtonId(tool.id)).removeAttribute("checked");
- }.bind(this));
- }
- if (this.store.getValue(this.winID, "inspecting")) {
- this.highlighter.unlock();
- } else {
- this.highlighter.lock();
- }
-
- Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
- },
-
- /**
* For each tool in the tools collection select the current node that is
* selected in the highlighter
* @param aScroll boolean
* Do you want to scroll the treepanel?
*/
toolsSelect: function IUI_toolsSelect(aScroll)
{
let selection = this.selection;
@@ -1249,33 +1376,33 @@ InspectorUI.prototype = {
/**
* Dim or undim each tool in the tools collection
* @param aState true = dim, false = undim
*/
toolsDim: function IUI_toolsDim(aState)
{
this.toolsDo(function IUI_toolsDim(aTool) {
- if (aTool.isOpen && "dim" in aTool) {
+ if ("dim" in aTool) {
aTool.dim.call(aTool.context, aState);
}
});
},
/**
* Notify registered tools of changes to the highlighted element.
*
* @param object aUpdater
* The tool that triggered the update (if any), that tool's
* onChanged will not be called.
*/
toolsOnChanged: function IUI_toolsChanged(aUpdater)
{
this.toolsDo(function IUI_toolsOnChanged(aTool) {
- if (aTool.isOpen && ("onChanged" in aTool) && aTool != aUpdater) {
+ if (("onChanged" in aTool) && aTool != aUpdater) {
aTool.onChanged.call(aTool.context);
}
});
},
/**
* Loop through all registered tools and pass each into the provided function
* @param aFunction The function to which each tool is to be passed
@@ -1659,16 +1786,23 @@ HTMLBreadcrumbs.prototype = {
{
let text = aNode.tagName.toLowerCase();
if (aNode.id) {
text += "#" + aNode.id;
}
for (let i = 0; i < aNode.classList.length; i++) {
text += "." + aNode.classList[i];
}
+ for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
+ let pseudo = PSEUDO_CLASSES[i];
+ if (DOMUtils.hasPseudoClassLock(aNode, pseudo)) {
+ text += pseudo;
+ }
+ }
+
return text;
},
/**
* Build <label>s that represent the node:
* <label class="inspector-breadcrumbs-tag">tagName</label>
* <label class="inspector-breadcrumbs-id">#id</label>
@@ -1684,29 +1818,38 @@ HTMLBreadcrumbs.prototype = {
let tagLabel = this.IUI.chromeDoc.createElement("label");
tagLabel.className = "inspector-breadcrumbs-tag plain";
let idLabel = this.IUI.chromeDoc.createElement("label");
idLabel.className = "inspector-breadcrumbs-id plain";
let classesLabel = this.IUI.chromeDoc.createElement("label");
classesLabel.className = "inspector-breadcrumbs-classes plain";
+
+ let pseudosLabel = this.IUI.chromeDoc.createElement("label");
+ pseudosLabel.className = "inspector-breadcrumbs-pseudo-classes plain";
tagLabel.textContent = aNode.tagName.toLowerCase();
idLabel.textContent = aNode.id ? ("#" + aNode.id) : "";
let classesText = "";
for (let i = 0; i < aNode.classList.length; i++) {
classesText += "." + aNode.classList[i];
}
classesLabel.textContent = classesText;
+ let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
+ return DOMUtils.hasPseudoClassLock(aNode, pseudo);
+ }, this);
+ pseudosLabel.textContent = pseudos.join("");
+
fragment.appendChild(tagLabel);
fragment.appendChild(idLabel);
fragment.appendChild(classesLabel);
+ fragment.appendChild(pseudosLabel);
return fragment;
},
/**
* Open the sibling menu.
*
* @param aButton the button representing the node.
@@ -1736,17 +1879,17 @@ HTMLBreadcrumbs.prototype = {
item.setAttribute("checked", "true");
}
item.setAttribute("type", "radio");
item.setAttribute("label", this.prettyPrintNodeAsText(nodes[i]));
item.onmouseup = (function(aNode) {
return function() {
- inspector.select(aNode, true, true);
+ inspector.select(aNode, true, true, "breadcrumbs");
}
})(nodes[i]);
fragment.appendChild(item);
}
}
this.menu.appendChild(fragment);
this.menu.openPopup(aButton, "before_start", 0, 0, true, false);
@@ -1890,17 +2033,17 @@ HTMLBreadcrumbs.prototype = {
let inspector = this.IUI;
button.appendChild(this.prettyPrintNodeAsXUL(aNode));
button.className = "inspector-breadcrumbs-button";
button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(aNode));
button.onBreadcrumbsClick = function onBreadcrumbsClick() {
inspector.stopInspecting();
- inspector.select(aNode, true, true);
+ inspector.select(aNode, true, true, "breadcrumbs");
};
button.onclick = (function _onBreadcrumbsRightClick(aEvent) {
if (aEvent.button == 2) {
this.openSiblingMenu(button, aNode);
}
}).bind(this);
@@ -2005,16 +2148,30 @@ HTMLBreadcrumbs.prototype = {
scroll: function BC_scroll()
{
// FIXME bug 684352: make sure its immediate neighbors are visible too.
let scrollbox = this.container;
let element = this.nodeHierarchy[this.currentIndex].button;
scrollbox.ensureElementIsVisible(element);
},
+
+ updateSelectors: function BC_updateSelectors()
+ {
+ for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
+ let crumb = this.nodeHierarchy[i];
+ let button = crumb.button;
+
+ while(button.hasChildNodes()) {
+ button.removeChild(button.firstChild);
+ }
+ button.appendChild(this.prettyPrintNodeAsXUL(crumb.node));
+ button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(crumb.node));
+ }
+ },
/**
* Update the breadcrumbs display when a new node is selected.
*/
update: function BC_update()
{
this.menu.hidePopup();
@@ -2046,16 +2203,18 @@ HTMLBreadcrumbs.prototype = {
idx = this.indexOf(selection);
this.setCursor(idx);
}
// Add the first child of the very last node of the breadcrumbs if possible.
this.ensureFirstChild();
// Make sure the selected node and its neighbours are visible.
this.scroll();
+
+ this.updateSelectors();
},
}
/////////////////////////////////////////////////////////////////////////
//// Initializers
XPCOMUtils.defineLazyGetter(InspectorUI.prototype, "strings",
@@ -2065,8 +2224,11 @@ XPCOMUtils.defineLazyGetter(InspectorUI.
});
XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
var obj = {};
Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
return obj.StyleInspector;
});
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+ return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -65,16 +65,19 @@ include $(topsrcdir)/config/rules.mk
browser_inspector_keybindings.js \
browser_inspector_breadcrumbs.html \
browser_inspector_breadcrumbs.js \
browser_inspector_bug_699308_iframe_navigation.js \
browser_inspector_changes.js \
browser_inspector_ruleviewstore.js \
browser_inspector_duplicate_ruleview.js \
browser_inspector_invalidate.js \
+ browser_inspector_sidebarstate.js \
+ browser_inspector_treePanel_menu.js \
+ browser_inspector_pseudoclass_lock.js \
head.js \
$(NULL)
# Disabled due to constant failures
# browser_inspector_treePanel_click.js \
libs:: $(_BROWSER_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/highlighter/test/browser_inspector_editor.js
+++ b/browser/devtools/highlighter/test/browser_inspector_editor.js
@@ -29,17 +29,17 @@ function setupEditorTests()
Services.obs.addObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
InspectorUI.toggleInspectorUI();
}
function setupHTMLPanel()
{
Services.obs.removeObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
Services.obs.addObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
- InspectorUI.toolShow(InspectorUI.treePanel.registrationObject);
+ InspectorUI.toggleHTMLPanel();
}
function runEditorTests()
{
Services.obs.removeObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
InspectorUI.stopInspecting();
InspectorUI.inspectNode(doc.body, true);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_pseudoclass_lock.js
@@ -0,0 +1,154 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+
+let doc;
+let div;
+
+let pseudo = ":hover";
+
+function test()
+{
+ waitForExplicitFinish();
+ ignoreAllUncaughtExceptions();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ doc = content.document;
+ waitForFocus(createDocument, content);
+ }, true);
+
+ content.location = "data:text/html,pseudo-class lock tests";
+}
+
+function createDocument()
+{
+ div = doc.createElement("div");
+ div.textContent = "test div";
+
+ let head = doc.getElementsByTagName('head')[0];
+ let style = doc.createElement('style');
+ let rules = doc.createTextNode('div { color: red; } div:hover { color: blue; }');
+
+ style.appendChild(rules);
+ head.appendChild(style);
+ doc.body.appendChild(div);
+
+ setupTests();
+}
+
+function setupTests()
+{
+ Services.obs.addObserver(selectNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+ InspectorUI.openInspectorUI();
+}
+
+function selectNode()
+{
+ Services.obs.removeObserver(selectNode,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+
+ executeSoon(function() {
+ InspectorUI.highlighter.addListener("nodeselected", openRuleView);
+ InspectorUI.inspectNode(div);
+ });
+}
+
+function openRuleView()
+{
+ Services.obs.addObserver(performTests,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+
+ InspectorUI.showSidebar();
+ InspectorUI.openRuleView();
+}
+
+function performTests()
+{
+ Services.obs.removeObserver(performTests,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
+
+ InspectorUI.highlighter.removeListener("nodeselected", performTests);
+
+ // toggle the class
+ InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
+
+ testAdded();
+
+ // toggle the lock off
+ InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
+
+ testRemoved();
+ testRemovedFromUI();
+
+ // toggle it back on
+ InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
+
+ // close the inspector
+ Services.obs.addObserver(testInspectorClosed,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+ InspectorUI.closeInspectorUI();
+}
+
+function testAdded()
+{
+ // lock is applied to it and ancestors
+ let node = div;
+ do {
+ is(DOMUtils.hasPseudoClassLock(node, pseudo), true,
+ "pseudo-class lock has been applied");
+ node = node.parentNode;
+ } while (node.parentNode)
+
+ // infobar selector contains pseudo-class
+ let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes");
+ is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector");
+
+ // ruleview contains pseudo-class rule
+ is(InspectorUI.ruleView.element.children.length, 3,
+ "rule view is showing 3 rules for pseudo-class locked div");
+
+ is(InspectorUI.ruleView.element.children[1]._ruleEditor.rule.selectorText,
+ "div:hover", "rule view is showing " + pseudo + " rule");
+}
+
+function testRemoved()
+{
+ // lock removed from node and ancestors
+ let node = div;
+ do {
+ is(DOMUtils.hasPseudoClassLock(node, pseudo), false,
+ "pseudo-class lock has been removed");
+ node = node.parentNode;
+ } while (node.parentNode)
+}
+
+function testRemovedFromUI()
+{
+ // infobar selector doesn't contain pseudo-class
+ let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes");
+ is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector");
+
+ // ruleview no longer contains pseudo-class rule
+ is(InspectorUI.ruleView.element.children.length, 2,
+ "rule view is showing 2 rules after removing lock");
+}
+
+function testInspectorClosed()
+{
+ Services.obs.removeObserver(testInspectorClosed,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+
+ testRemoved();
+
+ finishUp();
+}
+
+function finishUp()
+{
+ doc = div = null;
+ gBrowser.removeCurrentTab();
+ finish();
+}
--- a/browser/devtools/highlighter/test/browser_inspector_registertools.js
+++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js
@@ -143,55 +143,16 @@ function startToolTests(evt)
InspectorUI.toolShow(tool1);
InspectorUI.toolShow(tool3);
info("Checking panel states 4");
ok(tool1.isOpen, "Panel 1 is open");
ok(!tool2.isOpen, "Panel 2 is closed");
ok(tool3.isOpen, "Panel 3 is open");
- gBrowser.selectedTab = gBrowser.addTab();
- gBrowser.selectedBrowser.addEventListener("load", function() {
- gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
- waitForFocus(testSecondTab, content);
- }, true);
-
- content.location = "data:text/html,registertool new tab test for inspector";
-}
-
-function testSecondTab()
-{
- info("Opened second tab");
- info("Checking panel states 5");
-
- let tools = InspectorUI.tools;
- ok(!(tool1 in tools), "Panel 1 not in tools");
- ok(!(tool2 in tools), "Panel 2 not in tools");
- ok(!(tool3 in tools), "Panel 3 not in tools");
-
- info("Closing current tab");
- Services.obs.addObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
- gBrowser.removeCurrentTab();
-}
-
-function testOriginalTab()
-{
- Services.obs.removeObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
- info("Checking panel states 6");
-
- info("Tools: " + InspectorUI.tools);
- // reacquaint ourselves with our tools
- tool1 = InspectorUI.tools["tool_1"];
- tool2 = InspectorUI.tools["tool_2"];
- tool3 = InspectorUI.tools["tool_3"];
-
- ok(tool1.isOpen, "Panel 1 is open after reactivation");
- ok(!tool2.isOpen, "Panel 2 is closed after reactivation");
- ok(tool3.isOpen, "Panel 3 is open after reactivation");
-
Services.obs.addObserver(unregisterTools, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
InspectorUI.closeInspectorUI(true);
}
function unregisterTools()
{
Services.obs.removeObserver(unregisterTools, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
let tools = InspectorUI.tools;
--- a/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
+++ b/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
@@ -123,18 +123,18 @@ function inspectorFocusTab1()
}
function ruleViewOpened2()
{
let prop = InspectorUI.ruleView._elementStyle.rules[0].textProps[0];
is(prop.name, "background-color", "First prop is the background color prop.");
ok(!prop.enabled, "First prop should be disabled.");
+ InspectorUI.closeInspectorUI();
gBrowser.removeCurrentTab();
- InspectorUI.closeInspectorUI();
finish();
}
function test()
{
waitForExplicitFinish();
tab1 = gBrowser.addTab();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_sidebarstate.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let doc;
+
+function createDocument()
+{
+ doc.body.innerHTML = '<h1>Sidebar state test</h1>';
+ doc.title = "Sidebar State Test";
+
+ // Open the sidebar and wait for the default view (the rule view) to show.
+ Services.obs.addObserver(inspectorRuleViewOpened,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+
+ InspectorUI.openInspectorUI();
+ InspectorUI.showSidebar();
+}
+
+function inspectorRuleViewOpened()
+{
+ Services.obs.removeObserver(inspectorRuleViewOpened,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
+ is(InspectorUI.activeSidebarPanel, "ruleview", "Rule View is selected by default");
+
+ // Select the computed view and turn off the inspector.
+ InspectorUI.activateSidebarPanel("styleinspector");
+
+ Services.obs.addObserver(inspectorClosed,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+ InspectorUI.closeInspectorUI();
+}
+
+function inspectorClosed()
+{
+ // Reopen the inspector, expect the computed view to be loaded.
+ Services.obs.removeObserver(inspectorClosed,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+
+ Services.obs.addObserver(computedViewPopulated,
+ "StyleInspector-populated", false);
+
+ InspectorUI.openInspectorUI();
+}
+
+function computedViewPopulated()
+{
+ Services.obs.removeObserver(computedViewPopulated,
+ "StyleInspector-populated");
+ is(InspectorUI.activeSidebarPanel, "styleinspector", "Computed view is selected by default.");
+
+ finishTest();
+}
+
+
+function finishTest()
+{
+ InspectorUI.closeInspectorUI();
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function test()
+{
+ waitForExplicitFinish();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ doc = content.document;
+ waitForFocus(createDocument, content);
+ }, true);
+
+ content.location = "data:text/html,basic tests for inspector";
+}
+
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -91,16 +91,17 @@ function inspectorTabOpen2()
ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
// Activate the inspector again.
executeSoon(function() {
Services.obs.addObserver(inspectorUIOpen2,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+ clearUserPrefs();
InspectorUI.openInspectorUI();
});
}
function inspectorUIOpen2()
{
Services.obs.removeObserver(inspectorUIOpen2,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
@@ -131,34 +132,34 @@ function inspectorFocusTab1()
ok(InspectorUI.inspecting, "Inspector is highlighting");
ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
is(InspectorUI.selection, div, "selection matches the div element");
Services.obs.addObserver(inspectorOpenTreePanelTab1,
InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
- InspectorUI.treePanel.open();
+ InspectorUI.toggleHTMLPanel();
}
function inspectorOpenTreePanelTab1()
{
Services.obs.removeObserver(inspectorOpenTreePanelTab1,
InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
ok(InspectorUI.inspecting, "Inspector is highlighting");
ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
is(InspectorUI.selection, div, "selection matches the div element");
Services.obs.addObserver(inspectorSidebarStyleView1, "StyleInspector-opened", false);
executeSoon(function() {
InspectorUI.showSidebar();
- InspectorUI.toolShow(InspectorUI.stylePanel.registrationObject);
+ InspectorUI.activateSidebarPanel("styleinspector");
});
}
function inspectorSidebarStyleView1()
{
Services.obs.removeObserver(inspectorSidebarStyleView1, "StyleInspector-opened");
ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
ok(InspectorUI.stylePanel, "Inspector Has a Style Panel Instance");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+function test() {
+
+ waitForExplicitFinish();
+
+ let doc;
+ let node1;
+ let div;
+
+ function createDocument() {
+ div = doc.createElement("div");
+ let h1 = doc.createElement("h1");
+ let p1 = doc.createElement("p");
+ let p2 = doc.createElement("p");
+ doc.title = "Inspector Tree Menu Test";
+ h1.textContent = "Inspector Tree Menu Test";
+ p1.textContent = "This is some example text";
+ div.appendChild(h1);
+ div.appendChild(p1);
+ doc.body.appendChild(div);
+ node1 = p1;
+ setupTest();
+ }
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ doc = content.document;
+ waitForFocus(createDocument, content);
+ }, true);
+
+ content.location = content.location = "data:text/html,basic tests for inspector";;
+
+ function setupTest() {
+ Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+ InspectorUI.toggleInspectorUI();
+ }
+
+ function runTests() {
+ Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+ Services.obs.addObserver(testCopyInnerMenu, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+ InspectorUI.stopInspecting();
+ InspectorUI.inspectNode(node1, true);
+ InspectorUI.treePanel.open();
+ }
+
+ function testCopyInnerMenu() {
+ let copyInner = document.getElementById("inspectorHTMLCopyInner");
+ ok(copyInner, "the popup menu has a copy inner html menu item");
+
+ waitForClipboard("This is some example text",
+ function() { copyInner.doCommand(); },
+ testCopyOuterMenu, testCopyOuterMenu);
+ }
+
+ function testCopyOuterMenu() {
+ let copyOuter = document.getElementById("inspectorHTMLCopyOuter");
+ ok(copyOuter, "the popup menu has a copy outer html menu item");
+
+ waitForClipboard("<p>This is some example text</p>",
+ function() { copyOuter.doCommand(); },
+ testDeleteNode, testDeleteNode);
+ }
+
+ function testDeleteNode() {
+ let deleteNode = document.getElementById("inspectorHTMLDelete");
+ ok(deleteNode, "the popup menu has a delete menu item");
+
+ InspectorUI.highlighter.addListener("nodeselected", deleteTest);
+
+ let commandEvent = document.createEvent("XULCommandEvent");
+ commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
+ false, false, null);
+ deleteNode.dispatchEvent(commandEvent);
+ }
+
+ function deleteTest() {
+ InspectorUI.highlighter.removeListener("nodeSelected", deleteTest);
+ Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+ is(InspectorUI.selection, div, "parent node selected");
+ let p = doc.querySelector("P");
+ is(p, null, "node deleted");
+ executeSoon(function() {
+ InspectorUI.closeInspectorUI();
+ });
+ }
+
+ function finishUp() {
+ Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+ doc = node1 = div = null;
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+}
--- a/browser/devtools/highlighter/test/head.js
+++ b/browser/devtools/highlighter/test/head.js
@@ -36,16 +36,26 @@
*
* ***** END LICENSE BLOCK ***** */
const Cu = Components.utils;
let tempScope = {};
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tempScope);
let LayoutHelpers = tempScope.LayoutHelpers;
+// Clear preferences that may be set during the course of tests.
+function clearUserPrefs()
+{
+ Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
+ Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
+ Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
+}
+
+registerCleanupFunction(clearUserPrefs);
+
function isHighlighting()
{
let veil = InspectorUI.highlighter.veilTransparentBox;
return !(veil.style.visibility == "hidden");
}
function getHighlitNode()
{
@@ -73,8 +83,9 @@ function getHighlitNode()
function midPoint(aPointA, aPointB)
{
let pointC = { };
pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
return pointC;
}
+
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -70,16 +70,18 @@ const DEVTOOLS_CHROME_ENABLED = "devtool
const BUTTON_POSITION_SAVE = 0;
const BUTTON_POSITION_CANCEL = 1;
const BUTTON_POSITION_DONT_SAVE = 2;
/**
* The scratchpad object handles the Scratchpad window functionality.
*/
var Scratchpad = {
+ _initialWindowTitle: document.title,
+
/**
* The script execution context. This tells Scratchpad in which context the
* script shall execute.
*
* Possible values:
* - SCRATCHPAD_CONTEXT_CONTENT to execute code in the context of the current
* tab content window object.
* - SCRATCHPAD_CONTEXT_BROWSER to execute code in the context of the
@@ -146,50 +148,67 @@ var Scratchpad = {
/**
* Set the filename in the scratchpad UI and object
*
* @param string aFilename
* The new filename
*/
setFilename: function SP_setFilename(aFilename)
{
- document.title = this.filename = aFilename;
+ this.filename = aFilename;
+ this._updateTitle();
+ },
+
+ /**
+ * Update the Scratchpad window title based on the current state.
+ * @private
+ */
+ _updateTitle: function SP__updateTitle()
+ {
+ if (this.filename) {
+ document.title = (this.editor && this.editor.dirty ? "*" : "") +
+ this.filename;
+ } else {
+ document.title = this._initialWindowTitle;
+ }
},
/**
* Get the current state of the scratchpad. Called by the
* Scratchpad Manager for session storing.
*
* @return object
* An object with 3 properties: filename, text, and
* executionContext.
*/
getState: function SP_getState()
{
return {
filename: this.filename,
text: this.getText(),
executionContext: this.executionContext,
- saved: this.saved
+ saved: !this.editor.dirty,
};
},
/**
* Set the filename and execution context using the given state. Called
* when scratchpad is being restored from a previous session.
*
* @param object aState
* An object with filename and executionContext properties.
*/
setState: function SP_getState(aState)
{
if (aState.filename) {
this.setFilename(aState.filename);
}
- this.saved = aState.saved;
+ if (this.editor) {
+ this.editor.dirty = !aState.saved;
+ }
if (aState.executionContext == SCRATCHPAD_CONTEXT_BROWSER) {
this.setBrowserContext();
}
else {
this.setContentContext();
}
},
@@ -436,28 +455,29 @@ var Scratchpad = {
if (!error) {
this.writeAsComment(result);
} else {
this.writeAsErrorComment(error);
}
},
/**
- * Write out a value at the current insertion point as a block comment
+ * Write out a value at the next line from the current insertion point.
+ * The comment block will always be preceded by a newline character.
* @param object aValue
* The Object to write out as a string
*/
writeAsComment: function SP_writeAsComment(aValue)
{
let selection = this.getSelectionRange();
let insertionPoint = selection.start != selection.end ?
selection.end : // after selected text
this.editor.getCharCount(); // after text end
- let newComment = "/*\n" + aValue + "\n*/";
+ let newComment = "\n/*\n" + aValue + "\n*/";
this.setText(newComment, insertionPoint, insertionPoint);
// Select the new comment.
this.selectRange(insertionPoint, insertionPoint + newComment.length);
},
/**
@@ -632,17 +652,17 @@ var Scratchpad = {
openFile: function SP_openFile()
{
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, this.strings.GetStringFromName("openFile.title"),
Ci.nsIFilePicker.modeOpen);
fp.defaultString = "";
if (fp.show() != Ci.nsIFilePicker.returnCancel) {
this.setFilename(fp.file.path);
- this.importFromFile(fp.file, false, this.onTextSaved.bind(this));
+ this.importFromFile(fp.file, false);
}
},
/**
* Save the textbox content to the currently open file.
*
* @param function aCallback
* Optional function you want to call when file is saved
@@ -652,17 +672,19 @@ var Scratchpad = {
if (!this.filename) {
return this.saveFileAs(aCallback);
}
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(this.filename);
this.exportToFile(file, true, false, function(aStatus) {
- this.onTextSaved();
+ if (Components.isSuccessCode(aStatus)) {
+ this.editor.dirty = false;
+ }
if (aCallback) {
aCallback(aStatus);
}
});
},
/**
* Save the textbox content to a new file.
@@ -675,17 +697,19 @@ var Scratchpad = {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, this.strings.GetStringFromName("saveFileAs"),
Ci.nsIFilePicker.modeSave);
fp.defaultString = "scratchpad.js";
if (fp.show() != Ci.nsIFilePicker.returnCancel) {
this.setFilename(fp.file.path);
this.exportToFile(fp.file, true, false, function(aStatus) {
- this.onTextSaved();
+ if (Components.isSuccessCode(aStatus)) {
+ this.editor.dirty = false;
+ }
if (aCallback) {
aCallback(aStatus);
}
});
}
},
/**
@@ -777,67 +801,69 @@ var Scratchpad = {
*
* @param nsIDOMEvent aEvent
*/
onLoad: function SP_onLoad(aEvent)
{
if (aEvent.target != document) {
return;
}
-
let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
if (chrome) {
let environmentMenu = document.getElementById("sp-environment-menu");
let errorConsoleCommand = document.getElementById("sp-cmd-errorConsole");
let chromeContextCommand = document.getElementById("sp-cmd-browserContext");
environmentMenu.removeAttribute("hidden");
chromeContextCommand.removeAttribute("disabled");
errorConsoleCommand.removeAttribute("disabled");
}
+ let state = null;
let initialText = this.strings.GetStringFromName("scratchpadIntro");
if ("arguments" in window &&
window.arguments[0] instanceof Ci.nsIDialogParamBlock) {
- let state = JSON.parse(window.arguments[0].GetString(0));
+ state = JSON.parse(window.arguments[0].GetString(0));
this.setState(state);
initialText = state.text;
}
this.editor = new SourceEditor();
let config = {
mode: SourceEditor.MODES.JAVASCRIPT,
showLineNumbers: true,
initialText: initialText,
+ contextMenu: "scratchpad-text-popup",
};
let editorPlaceholder = document.getElementById("scratchpad-editor");
- this.editor.init(editorPlaceholder, config, this.onEditorLoad.bind(this));
+ this.editor.init(editorPlaceholder, config,
+ this._onEditorLoad.bind(this, state));
},
/**
* The load event handler for the source editor. This method does post-load
* editor initialization.
+ *
+ * @private
+ * @param object aState
+ * The initial Scratchpad state object.
*/
- onEditorLoad: function SP_onEditorLoad()
+ _onEditorLoad: function SP__onEditorLoad(aState)
{
- this.editor.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
- this.onContextMenu);
+ this.editor.addEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
+ this._onDirtyChanged);
this.editor.focus();
this.editor.setCaretOffset(this.editor.getCharCount());
+ if (aState) {
+ this.editor.dirty = !aState.saved;
+ }
this.initialized = true;
- if (this.filename && !this.saved) {
- this.onTextChanged();
- }
- else if (this.filename && this.saved) {
- this.onTextSaved();
- }
-
this._triggerObservers("Ready");
},
/**
* Insert text at the current caret location.
*
* @param string aText
* The text you want to insert.
@@ -845,46 +871,27 @@ var Scratchpad = {
insertTextAtCaret: function SP_insertTextAtCaret(aText)
{
let caretOffset = this.editor.getCaretOffset();
this.setText(aText, caretOffset, caretOffset);
this.editor.setCaretOffset(caretOffset + aText.length);
},
/**
- * The contextmenu event handler for the source editor. This method opens the
- * Scratchpad context menu popup at the pointer location.
+ * The Source Editor DirtyChanged event handler. This function updates the
+ * Scratchpad window title to show an asterisk when there are unsaved changes.
*
+ * @private
+ * @see SourceEditor.EVENTS.DIRTY_CHANGED
* @param object aEvent
- * An event object coming from the SourceEditor. This object needs to
- * hold the screenX and screenY properties.
+ * The DirtyChanged event object.
*/
- onContextMenu: function SP_onContextMenu(aEvent)
+ _onDirtyChanged: function SP__onDirtyChanged(aEvent)
{
- let menu = document.getElementById("scratchpad-text-popup");
- if (menu.state == "closed") {
- menu.openPopupAtScreen(aEvent.screenX, aEvent.screenY, true);
- }
- },
-
- /**
- * The popupshowing event handler for the Edit menu. This method updates the
- * enabled/disabled state of the Undo and Redo commands, based on the editor
- * state such that the menu items render correctly for the user when the menu
- * shows.
- */
- onEditPopupShowing: function SP_onEditPopupShowing()
- {
- goUpdateGlobalEditMenuItems();
-
- let undo = document.getElementById("sp-cmd-undo");
- undo.setAttribute("disabled", !this.editor.canUndo());
-
- let redo = document.getElementById("sp-cmd-redo");
- redo.setAttribute("disabled", !this.editor.canRedo());
+ Scratchpad._updateTitle();
},
/**
* Undo the last action of the user.
*/
undo: function SP_undo()
{
this.editor.undo();
@@ -894,123 +901,133 @@ var Scratchpad = {
* Redo the previously undone action.
*/
redo: function SP_redo()
{
this.editor.redo();
},
/**
- * This method adds a listener to the editor for text changes. Called when
- * a scratchpad is saved, opened from file, or restored from a saved file.
- */
- onTextSaved: function SP_onTextSaved(aStatus)
- {
- if (aStatus && !Components.isSuccessCode(aStatus)) {
- return;
- }
- if (!document || !this.initialized) {
- return; // file saved to disk after window has closed
- }
- document.title = document.title.replace(/^\*/, "");
- this.saved = true;
- this.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
- this.onTextChanged);
- },
-
- /**
- * The scratchpad handler for editor text change events. This handler
- * indicates that there are unsaved changes in the UI.
- */
- onTextChanged: function SP_onTextChanged()
- {
- document.title = "*" + document.title;
- Scratchpad.saved = false;
- Scratchpad.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
- Scratchpad.onTextChanged);
- },
-
- /**
* The Scratchpad window unload event handler. This method unloads/destroys
* the source editor.
*
* @param nsIDOMEvent aEvent
*/
onUnload: function SP_onUnload(aEvent)
{
if (aEvent.target != document) {
return;
}
this.resetContext();
- this.editor.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
- this.onContextMenu);
+ this.editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
+ this._onDirtyChanged);
this.editor.destroy();
this.editor = null;
this.initialized = false;
},
/**
* Prompt to save scratchpad if it has unsaved changes.
*
* @param function aCallback
- * Optional function you want to call when file is saved
+ * Optional function you want to call when file is saved. The callback
+ * receives three arguments:
+ * - toClose (boolean) - tells if the window should be closed.
+ * - saved (boolen) - tells if the file has been saved.
+ * - status (number) - the file save status result (if the file was
+ * saved).
* @return boolean
* Whether the window should be closed
*/
promptSave: function SP_promptSave(aCallback)
{
- if (this.filename && !this.saved) {
+ if (this.filename && this.editor.dirty) {
let ps = Services.prompt;
let flags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_SAVE +
ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL +
ps.BUTTON_POS_2 * ps.BUTTON_TITLE_DONT_SAVE;
let button = ps.confirmEx(window,
this.strings.GetStringFromName("confirmClose.title"),
this.strings.GetStringFromName("confirmClose"),
flags, null, null, null, null, {});
if (button == BUTTON_POSITION_CANCEL) {
+ if (aCallback) {
+ aCallback(false, false);
+ }
return false;
}
+
if (button == BUTTON_POSITION_SAVE) {
- this.saveFile(aCallback);
+ this.saveFile(function(aStatus) {
+ if (aCallback) {
+ aCallback(true, true, aStatus);
+ }
+ });
+ return true;
}
}
+
+ if (aCallback) {
+ aCallback(true, false);
+ }
return true;
},
/**
* Handler for window close event. Prompts to save scratchpad if
* there are unsaved changes.
*
* @param nsIDOMEvent aEvent
*/
onClose: function SP_onClose(aEvent)
{
- let toClose = this.promptSave();
- if (!toClose) {
- aEvent.preventDefault();
+ if (this._skipClosePrompt) {
+ return;
}
+
+ this.promptSave(function(aShouldClose, aSaved, aStatus) {
+ let shouldClose = aShouldClose;
+ if (aSaved && !Components.isSuccessCode(aStatus)) {
+ shouldClose = false;
+ }
+
+ if (shouldClose) {
+ this._skipClosePrompt = true;
+ window.close();
+ }
+ }.bind(this));
+ aEvent.preventDefault();
},
/**
* Close the scratchpad window. Prompts before closing if the scratchpad
* has unsaved changes.
*
* @param function aCallback
* Optional function you want to call when file is saved
*/
close: function SP_close(aCallback)
{
- let toClose = this.promptSave(aCallback);
- if (toClose) {
- window.close();
- }
+ this.promptSave(function(aShouldClose, aSaved, aStatus) {
+ let shouldClose = aShouldClose;
+ if (aSaved && !Components.isSuccessCode(aStatus)) {
+ shouldClose = false;
+ }
+
+ if (shouldClose) {
+ this._skipClosePrompt = true;
+ window.close();
+ }
+ if (aCallback) {
+ aCallback();
+ }
+ }.bind(this));
},
_observers: [],
/**
* Add an observer for Scratchpad events.
*
* The observer implements IScratchpadObserver := {
--- a/browser/devtools/scratchpad/scratchpad.xul
+++ b/browser/devtools/scratchpad/scratchpad.xul
@@ -76,21 +76,21 @@
<command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
<command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
<command id="sp-cmd-display" oncommand="Scratchpad.display();"/>
<command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
<command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
<command id="sp-cmd-resetContext" oncommand="Scratchpad.resetContext();"/>
<command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
<command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
- <command id="sp-cmd-undo" oncommand="Scratchpad.undo();" disabled="true"/>
- <command id="sp-cmd-redo" oncommand="Scratchpad.redo();" disabled="true"/>
<command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
</commandset>
+<keyset id="sourceEditorKeys"/>
+
<keyset id="sp-keyset">
<key id="sp-key-window"
key="&newWindowCmd.commandkey;"
command="sp-cmd-newWindow"
modifiers="accel"/>
<key id="sp-key-open"
key="&openFileCmd.commandkey;"
command="sp-cmd-openFile"
@@ -118,19 +118,19 @@
<key id="key_copy"
key="©Cmd.key;"
modifiers="accel"/>
<key id="key_paste"
key="&pasteCmd.key;"
modifiers="accel"/>
<key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
<key id="key_undo" key="&undoCmd.key;" modifiers="accel"
- oncommand="Scratchpad.undo();"/>
+ command="se-cmd-undo"/>
<key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"
- oncommand="Scratchpad.redo();"/>
+ command="se-cmd-redo"/>
<key id="sp-key-run"
key="&run.key;"
command="sp-cmd-run"
modifiers="accel"/>
<key id="sp-key-inspect"
key="&inspect.key;"
command="sp-cmd-inspect"
modifiers="accel"/>
@@ -163,20 +163,16 @@
<key id="key_findAgain"
keycode="VK_F3"
command="cmd_findAgain"/>
<key id="key_findPrevious"
keycode="VK_F3"
command="cmd_findPrevious"
modifiers="shift"/>
#endif
- <key id="key_gotoLine"
- key="&gotoLineCmd.key;"
- command="cmd_gotoLine"
- modifiers="accel"/>
<key id="key_openHelp"
keycode="VK_F1"
command="sp-cmd-documentationLink"/>
</keyset>
<menubar id="sp-menubar">
<menu id="sp-file-menu" label="&fileMenu.label;"
@@ -218,27 +214,27 @@
accesskey="&closeCmd.accesskey;"
command="sp-cmd-close"/>
</menupopup>
</menu>
<menu id="sp-edit-menu" label="&editMenu.label;"
accesskey="&editMenu.accesskey;">
<menupopup id="sp-menu_editpopup"
- onpopupshowing="Scratchpad.onEditPopupShowing()">
+ onpopupshowing="goUpdateGlobalEditMenuItems()">
<menuitem id="sp-menu-undo"
label="&undoCmd.label;"
key="key_undo"
accesskey="&undoCmd.accesskey;"
- command="sp-cmd-undo"/>
+ command="se-cmd-undo"/>
<menuitem id="sp-menu-redo"
label="&redoCmd.label;"
key="key_redo"
accesskey="&redoCmd.accesskey;"
- command="sp-cmd-redo"/>
+ command="se-cmd-redo"/>
<menuseparator/>
<menuitem id="sp-menu-cut"
label="&cutCmd.label;"
key="key_cut"
accesskey="&cutCmd.accesskey;"
command="cmd_cut"/>
<menuitem id="sp-menu-copy"
label="©Cmd.label;"
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js
@@ -16,17 +16,17 @@ function test()
"comments for 'display' and not sent to the console in Scratchpad";
}
function runTests()
{
var scratchpad = gScratchpadWindow.Scratchpad;
var message = "\"Hello World!\""
- var openComment = "/*\n";
+ var openComment = "\n/*\n";
var closeComment = "\n*/";
var error = "throw new Error(\"Ouch!\")";
let messageArray = {};
let count = {};
scratchpad.setText(message);
scratchpad.display();
is(scratchpad.getText(),
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js
@@ -41,94 +41,92 @@ function test()
testSavedFile();
content.location = "data:text/html,<p>test scratchpad save file prompt on closing";
}
function testNew()
{
openScratchpad(function(win) {
- win.Scratchpad.close();
- ok(win.closed, "new scratchpad window should close without prompting")
- done();
+ win.Scratchpad.close(function() {
+ ok(win.closed, "new scratchpad window should close without prompting")
+ done();
+ });
}, {noFocus: true});
}
function testSavedFile()
{
openScratchpad(function(win) {
win.Scratchpad.filename = "test.js";
- win.Scratchpad.saved = true;
- win.Scratchpad.close();
-
- ok(win.closed, "scratchpad from file with no changes should close")
- done();
+ win.Scratchpad.editor.dirty = false;
+ win.Scratchpad.close(function() {
+ ok(win.closed, "scratchpad from file with no changes should close")
+ done();
+ });
}, {noFocus: true});
}
function testUnsaved()
{
testUnsavedFileCancel();
testUnsavedFileSave();
testUnsavedFileDontSave();
}
function testUnsavedFileCancel()
{
openScratchpad(function(win) {
- win.Scratchpad.filename = "test.js";
- win.Scratchpad.saved = false;
+ win.Scratchpad.setFilename("test.js");
+ win.Scratchpad.editor.dirty = true;
promptButton = win.BUTTON_POSITION_CANCEL;
- win.Scratchpad.close();
-
- ok(!win.closed, "cancelling dialog shouldn't close scratchpad");
-
- win.close();
- done();
+ win.Scratchpad.close(function() {
+ ok(!win.closed, "cancelling dialog shouldn't close scratchpad");
+ win.close();
+ done();
+ });
}, {noFocus: true});
}
function testUnsavedFileSave()
{
openScratchpad(function(win) {
win.Scratchpad.importFromFile(gFile, true, function(status, content) {
- win.Scratchpad.filename = gFile.path;
- win.Scratchpad.onTextSaved();
+ win.Scratchpad.setFilename(gFile.path);
let text = "new text";
win.Scratchpad.setText(text);
promptButton = win.BUTTON_POSITION_SAVE;
win.Scratchpad.close(function() {
+ ok(win.closed, 'pressing "Save" in dialog should close scratchpad');
readFile(gFile, function(savedContent) {
is(savedContent, text, 'prompted "Save" worked when closing scratchpad');
done();
});
});
-
- ok(win.closed, 'pressing "Save" in dialog should close scratchpad');
});
}, {noFocus: true});
}
function testUnsavedFileDontSave()
{
openScratchpad(function(win) {
- win.Scratchpad.filename = gFile.path;
- win.Scratchpad.saved = false;
+ win.Scratchpad.setFilename(gFile.path);
+ win.Scratchpad.editor.dirty = true;
promptButton = win.BUTTON_POSITION_DONT_SAVE;
- win.Scratchpad.close();
-
- ok(win.closed, 'pressing "Don\'t Save" in dialog should close scratchpad');
- done();
+ win.Scratchpad.close(function() {
+ ok(win.closed, 'pressing "Don\'t Save" in dialog should close scratchpad');
+ done();
+ });
}, {noFocus: true});
}
function cleanup()
{
Services.prompt = oldPrompt;
gFile.remove(false);
gFile = null;
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
@@ -1,74 +1,67 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// only finish() when correct number of tests are done
-const expected = 5;
+const expected = 4;
var count = 0;
function done()
{
if (++count == expected) {
finish();
}
}
var ScratchpadManager = Scratchpad.ScratchpadManager;
function test()
{
waitForExplicitFinish();
testListeners();
- testErrorStatus();
testRestoreNotFromFile();
testRestoreFromFileSaved();
testRestoreFromFileUnsaved();
content.location = "data:text/html,<p>test star* UI for unsaved file changes";
}
function testListeners()
{
openScratchpad(function(aWin, aScratchpad) {
aScratchpad.setText("new text");
ok(!isStar(aWin), "no star if scratchpad isn't from a file");
- aScratchpad.onTextSaved();
+ aScratchpad.editor.dirty = false;
ok(!isStar(aWin), "no star before changing text");
+ aScratchpad.setFilename("foo.js");
aScratchpad.setText("new text2");
ok(isStar(aWin), "shows star if scratchpad text changes");
- aScratchpad.onTextSaved();
+ aScratchpad.editor.dirty = false;
ok(!isStar(aWin), "no star if scratchpad was just saved");
+ aScratchpad.setText("new text3");
+ ok(isStar(aWin), "shows star if scratchpad has more changes");
+
aScratchpad.undo();
- ok(isStar(aWin), "star if scratchpad undo");
+ ok(!isStar(aWin), "no star if scratchpad undo to save point");
+
+ aScratchpad.undo();
+ ok(isStar(aWin), "star if scratchpad undo past save point");
aWin.close();
done();
}, {noFocus: true});
}
-function testErrorStatus()
-{
- openScratchpad(function(aWin, aScratchpad) {
- aScratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE);
- aScratchpad.setText("new text");
- ok(!isStar(aWin), "no star if file save failed");
-
- aWin.close();
- done();
- }, {noFocus: true});
-}
-
-
function testRestoreNotFromFile()
{
let session = [{
text: "test1",
executionContext: 1
}];
let [win] = ScratchpadManager.restoreSession(session);
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
@@ -25,30 +25,30 @@ function testFalsy()
finish();
}
function verifyFalsies(sp)
{
sp.setText("undefined");
sp.display();
- is(sp.selectedText, "/*\nundefined\n*/", "'undefined' is displayed");
+ is(sp.selectedText, "\n/*\nundefined\n*/", "'undefined' is displayed");
sp.setText("false");
sp.display();
- is(sp.selectedText, "/*\nfalse\n*/", "'false' is displayed");
+ is(sp.selectedText, "\n/*\nfalse\n*/", "'false' is displayed");
sp.setText("0");
sp.display();
- is(sp.selectedText, "/*\n0\n*/", "'0' is displayed");
+ is(sp.selectedText, "\n/*\n0\n*/", "'0' is displayed");
sp.setText("null");
sp.display();
- is(sp.selectedText, "/*\nnull\n*/", "'null' is displayed");
+ is(sp.selectedText, "\n/*\nnull\n*/", "'null' is displayed");
sp.setText("NaN");
sp.display();
- is(sp.selectedText, "/*\nNaN\n*/", "'NaN' is displayed");
+ is(sp.selectedText, "\n/*\nNaN\n*/", "'NaN' is displayed");
sp.setText("''");
sp.display();
- is(sp.selectedText, "/*\n\n*/", "empty string is displayed");
+ is(sp.selectedText, "\n/*\n\n*/", "empty string is displayed");
}
--- a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
@@ -35,23 +35,23 @@ function runTests()
is(content.wrappedJSObject.foobarBug636725, 2,
"run() updated window.foobarBug636725");
sp.display();
is(content.wrappedJSObject.foobarBug636725, 3,
"display() updated window.foobarBug636725");
- is(sp.getText(), "++window.foobarBug636725/*\n3\n*/",
+ is(sp.getText(), "++window.foobarBug636725\n/*\n3\n*/",
"display() shows evaluation result in the textbox");
- is(sp.selectedText, "/*\n3\n*/", "selectedText is correct");
+ is(sp.selectedText, "\n/*\n3\n*/", "selectedText is correct");
let selection = sp.getSelectionRange();
is(selection.start, 24, "selection.start is correct");
- is(selection.end, 31, "selection.end is correct");
+ is(selection.end, 32, "selection.end is correct");
// Test selection run() and display().
sp.setText("window.foobarBug636725 = 'a';\n" +
"window.foobarBug636725 = 'b';");
sp.selectRange(1, 2);
@@ -89,26 +89,26 @@ function runTests()
sp.selectRange(0, 22);
sp.display();
is(content.wrappedJSObject.foobarBug636725, "a",
"display() worked for the selected range");
is(sp.getText(), "window.foobarBug636725" +
- "/*\na\n*/" +
+ "\n/*\na\n*/" +
" = 'c';\n" +
"window.foobarBug636725 = 'b';",
"display() shows evaluation result in the textbox");
- is(sp.selectedText, "/*\na\n*/", "selectedText is correct");
+ is(sp.selectedText, "\n/*\na\n*/", "selectedText is correct");
selection = sp.getSelectionRange();
is(selection.start, 22, "selection.start is correct");
- is(selection.end, 29, "selection.end is correct");
+ is(selection.end, 30, "selection.end is correct");
sp.deselect();
ok(!sp.selectedText, "selectedText is empty");
selection = sp.getSelectionRange();
is(selection.start, selection.end, "deselect() works");
--- a/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
@@ -27,18 +27,16 @@ function runTests()
"sp-menu-save": "saveFile",
"sp-menu-saveas": "saveFileAs",
"sp-text-run": "run",
"sp-text-inspect": "inspect",
"sp-text-display": "display",
"sp-text-resetContext": "resetContext",
"sp-menu-content": "setContentContext",
"sp-menu-browser": "setBrowserContext",
- "sp-menu-undo": "undo",
- "sp-menu-redo": "redo",
};
let lastMethodCalled = null;
sp.__noSuchMethod__ = function(aMethodName) {
lastMethodCalled = aMethodName;
};
for (let id in methodsAndItems) {
--- a/browser/devtools/sourceeditor/orion/orion.js
+++ b/browser/devtools/sourceeditor/orion/orion.js
@@ -6652,17 +6652,16 @@ define("orion/textview/textView", ['orio
this._viewDiv = viewDiv;
viewDiv.id = "viewDiv";
viewDiv.tabIndex = -1;
viewDiv.style.overflow = "auto";
viewDiv.style.position = "absolute";
viewDiv.style.top = "0px";
viewDiv.style.borderWidth = "0px";
viewDiv.style.margin = "0px";
- viewDiv.style.MozOutline = "none";
viewDiv.style.outline = "none";
body.appendChild(viewDiv);
var scrollDiv = frameDocument.createElement("DIV");
this._scrollDiv = scrollDiv;
scrollDiv.id = "scrollDiv";
scrollDiv.style.margin = "0px";
scrollDiv.style.borderWidth = "0px";
@@ -6695,17 +6694,16 @@ define("orion/textview/textView", ['orio
clientDiv.className = "viewContent";
this._clientDiv = clientDiv;
clientDiv.id = "clientDiv";
clientDiv.style.whiteSpace = "pre";
clientDiv.style.position = this._clipDiv ? "absolute" : "fixed";
clientDiv.style.borderWidth = "0px";
clientDiv.style.margin = "0px";
clientDiv.style.padding = "0px";
- clientDiv.style.MozOutline = "none";
clientDiv.style.outline = "none";
clientDiv.style.zIndex = "1";
if (isPad) {
clientDiv.style.WebkitTapHighlightColor = "transparent";
}
(this._clipDiv || scrollDiv).appendChild(clientDiv);
if (isFirefox && !clientDiv.setCapture) {
@@ -8375,45 +8373,42 @@ define("orion/textview/textView", ['orio
this._hightlightRGB = "Highlight";
var selDiv1 = frameDocument.createElement("DIV");
this._selDiv1 = selDiv1;
selDiv1.id = "selDiv1";
selDiv1.style.position = this._clipDiv ? "absolute" : "fixed";
selDiv1.style.borderWidth = "0px";
selDiv1.style.margin = "0px";
selDiv1.style.padding = "0px";
- selDiv1.style.MozOutline = "none";
selDiv1.style.outline = "none";
selDiv1.style.background = this._hightlightRGB;
selDiv1.style.width = "0px";
selDiv1.style.height = "0px";
selDiv1.style.zIndex = "0";
parent.appendChild(selDiv1);
var selDiv2 = frameDocument.createElement("DIV");
this._selDiv2 = selDiv2;
selDiv2.id = "selDiv2";
selDiv2.style.position = this._clipDiv ? "absolute" : "fixed";
selDiv2.style.borderWidth = "0px";
selDiv2.style.margin = "0px";
selDiv2.style.padding = "0px";
- selDiv2.style.MozOutline = "none";
selDiv2.style.outline = "none";
selDiv2.style.background = this._hightlightRGB;
selDiv2.style.width = "0px";
selDiv2.style.height = "0px";
selDiv2.style.zIndex = "0";
parent.appendChild(selDiv2);
var selDiv3 = frameDocument.createElement("DIV");
this._selDiv3 = selDiv3;
selDiv3.id = "selDiv3";
selDiv3.style.position = this._clipDiv ? "absolute" : "fixed";
selDiv3.style.borderWidth = "0px";
selDiv3.style.margin = "0px";
selDiv3.style.padding = "0px";
- selDiv3.style.MozOutline = "none";
selDiv3.style.outline = "none";
selDiv3.style.background = this._hightlightRGB;
selDiv3.style.width = "0px";
selDiv3.style.height = "0px";
selDiv3.style.zIndex = "0";
parent.appendChild(selDiv3);
/*
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -141,16 +141,18 @@ function SourceEditor() {
// Update the SourceEditor defaults from user preferences.
SourceEditor.DEFAULTS.tabSize =
Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE);
SourceEditor.DEFAULTS.expandTab =
Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB);
this._onOrionSelection = this._onOrionSelection.bind(this);
+ this._onTextChanged = this._onTextChanged.bind(this);
+ this._onOrionContextMenu = this._onOrionContextMenu.bind(this);
this._eventTarget = {};
this._eventListenersQueue = [];
this.ui = new SourceEditorUI(this);
}
SourceEditor.prototype = {
_view: null,
@@ -167,16 +169,18 @@ SourceEditor.prototype = {
_currentLineAnnotation: null,
_primarySelectionTimeout: null,
_mode: null,
_expandTab: null,
_tabSize: null,
_iframeWindow: null,
_eventTarget: null,
_eventListenersQueue: null,
+ _contextMenu: null,
+ _dirty: false,
/**
* The Source Editor user interface manager.
* @type object
* An instance of the SourceEditorUI.
*/
ui: null,
@@ -274,17 +278,31 @@ SourceEditor.prototype = {
let onOrionLoad = function() {
this._view.removeEventListener("Load", onOrionLoad);
this._onOrionLoad();
}.bind(this);
this._view.addEventListener("Load", onOrionLoad);
if (config.highlightCurrentLine || Services.appinfo.OS == "Linux") {
- this._view.addEventListener("Selection", this._onOrionSelection);
+ this.addEventListener(SourceEditor.EVENTS.SELECTION,
+ this._onOrionSelection);
+ }
+ this.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+ this._onTextChanged);
+
+ if (typeof config.contextMenu == "string") {
+ let chromeDocument = this.parentElement.ownerDocument;
+ this._contextMenu = chromeDocument.getElementById(config.contextMenu);
+ } else if (typeof config.contextMenu == "object" ) {
+ this._contextMenu = config._contextMenu;
+ }
+ if (this._contextMenu) {
+ this.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
+ this._onOrionContextMenu);
}
let KeyBinding = window.require("orion/textview/keyBinding").KeyBinding;
let TextDND = window.require("orion/textview/textDND").TextDND;
let Rulers = window.require("orion/textview/rulers");
let LineNumberRuler = Rulers.LineNumberRuler;
let AnnotationRuler = Rulers.AnnotationRuler;
let OverviewRuler = Rulers.OverviewRuler;
@@ -583,16 +601,54 @@ SourceEditor.prototype = {
}
this._primarySelectionTimeout =
window.setTimeout(this._updatePrimarySelection.bind(this),
PRIMARY_SELECTION_DELAY);
}
},
/**
+ * The TextChanged event handler which tracks the dirty state of the editor.
+ *
+ * @see SourceEditor.EVENTS.TEXT_CHANGED
+ * @see SourceEditor.EVENTS.DIRTY_CHANGED
+ * @see SourceEditor.dirty
+ * @private
+ */
+ _onTextChanged: function SE__onTextChanged()
+ {
+ this._updateDirty();
+ },
+
+ /**
+ * The Orion contextmenu event handler. This method opens the default or
+ * the custom context menu popup at the pointer location.
+ *
+ * @param object aEvent
+ * The contextmenu event object coming from Orion. This object should
+ * hold the screenX and screenY properties.
+ */
+ _onOrionContextMenu: function SE__onOrionContextMenu(aEvent)
+ {
+ if (this._contextMenu.state == "closed") {
+ this._contextMenu.openPopupAtScreen(aEvent.screenX || 0,
+ aEvent.screenY || 0, true);
+ }
+ },
+
+ /**
+ * Update the dirty state of the editor based on the undo stack.
+ * @private
+ */
+ _updateDirty: function SE__updateDirty()
+ {
+ this.dirty = !this._undoStack.isClean();
+ },
+
+ /**
* Update the X11 PRIMARY buffer to hold the current selection.
* @private
*/
_updatePrimarySelection: function SE__updatePrimarySelection()
{
this._primarySelectionTimeout = null;
let text = this.getSelectedText();
@@ -861,28 +917,38 @@ SourceEditor.prototype = {
this._eventTarget.removeEventListener(aEventType, aCallback);
} else {
this._eventListenersQueue.push(["remove", aEventType, aCallback]);
}
},
/**
* Undo a change in the editor.
+ *
+ * @return boolean
+ * True if there was a change undone, false otherwise.
*/
undo: function SE_undo()
{
- return this._undoStack.undo();
+ let result = this._undoStack.undo();
+ this.ui._onUndoRedo();
+ return result;
},
/**
* Redo a change in the editor.
+ *
+ * @return boolean
+ * True if there was a change redone, false otherwise.
*/
redo: function SE_redo()
{
- return this._undoStack.redo();
+ let result = this._undoStack.redo();
+ this.ui._onUndoRedo();
+ return result;
},
/**
* Check if there are changes that can be undone.
*
* @return boolean
* True if there are changes that can be undone, false otherwise.
*/
@@ -898,21 +964,64 @@ SourceEditor.prototype = {
* True if there are changes that can be repeated, false otherwise.
*/
canRedo: function SE_canRedo()
{
return this._undoStack.canRedo();
},
/**
- * Reset the Undo stack
+ * Reset the Undo stack.
*/
resetUndo: function SE_resetUndo()
{
this._undoStack.reset();
+ this._updateDirty();
+ this.ui._onUndoRedo();
+ },
+
+ /**
+ * Set the "dirty" state of the editor. Set this to false when you save the
+ * text being edited. The dirty state will become true once the user makes
+ * changes to the text.
+ *
+ * @param boolean aNewValue
+ * The new dirty state: true if the text is not saved, false if you
+ * just saved the text.
+ */
+ set dirty(aNewValue)
+ {
+ if (aNewValue == this._dirty) {
+ return;
+ }
+
+ let event = {
+ type: SourceEditor.EVENTS.DIRTY_CHANGED,
+ oldValue: this._dirty,
+ newValue: aNewValue,
+ };
+
+ this._dirty = aNewValue;
+ if (!this._dirty && !this._undoStack.isClean()) {
+ this._undoStack.markClean();
+ }
+ this._dispatchEvent(event);
+ },
+
+ /**
+ * Get the editor "dirty" state. This tells if the text is considered saved or
+ * not.
+ *
+ * @see SourceEditor.EVENTS.DIRTY_CHANGED
+ * @return boolean
+ * True if there are changes which are not saved, false otherwise.
+ */
+ get dirty()
+ {
+ return this._dirty;
},
/**
* Start a compound change in the editor. Compound changes are grouped into
* only one change that you can undo later, after you invoke
* endCompoundChange().
*/
startCompoundChange: function SE_startCompoundChange()
@@ -1321,20 +1430,32 @@ SourceEditor.prototype = {
},
/**
* Destroy/uninitialize the editor.
*/
destroy: function SE_destroy()
{
if (this._config.highlightCurrentLine || Services.appinfo.OS == "Linux") {
- this._view.removeEventListener("Selection", this._onOrionSelection);
+ this.removeEventListener(SourceEditor.EVENTS.SELECTION,
+ this._onOrionSelection);
}
this._onOrionSelection = null;
+ this.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+ this._onTextChanged);
+ this._onTextChanged = null;
+
+ if (this._contextMenu) {
+ this.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
+ this._onOrionContextMenu);
+ this._contextMenu = null;
+ }
+ this._onOrionContextMenu = null;
+
if (this._primarySelectionTimeout) {
let window = this.parentElement.ownerDocument.defaultView;
window.clearTimeout(this._primarySelectionTimeout);
this._primarySelectionTimeout = null;
}
this._view.destroy();
this.ui.destroy();
--- a/browser/devtools/sourceeditor/source-editor-overlay.xul
+++ b/browser/devtools/sourceeditor/source-editor-overlay.xul
@@ -30,18 +30,85 @@
- 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 ***** -->
-
+<!DOCTYPE overlay SYSTEM "chrome://browser/locale/devtools/sourceeditor.dtd">
<overlay id="sourceEditorOverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <!-- This Source Editor overlay requires the editMenuOverlay.xul to be loaded.
+ The globalOverlay.js script is also required in the XUL document where
+ the source-editor-overlay.xul is loaded. -->
+
<commandset id="sourceEditorCommands">
<command id="cmd_find" oncommand="goDoCommand('cmd_find')"/>
<command id="cmd_findAgain" oncommand="goDoCommand('cmd_findAgain')" disabled="true"/>
<command id="cmd_findPrevious" oncommand="goDoCommand('cmd_findPrevious')" disabled="true"/>
<command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
+ <command id="se-cmd-undo" oncommand="goDoCommand('se-cmd-undo')" disabled="true"/>
+ <command id="se-cmd-redo" oncommand="goDoCommand('se-cmd-redo')" disabled="true"/>
</commandset>
+
+ <keyset id="sourceEditorKeys">
+ <key id="key_gotoLine"
+ key="&gotoLineCmd.key;"
+ command="cmd_gotoLine"
+ modifiers="accel"/>
+ </keyset>
+
+ <menupopup id="sourceEditorContextMenu"
+ onpopupshowing="goUpdateGlobalEditMenuItems()">
+ <menuitem id="se-menu-undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ accesskey="&undoCmd.accesskey;"
+ command="se-cmd-undo"/>
+ <menuseparator/>
+ <menuitem id="se-menu-cut"
+ label="&cutCmd.label;"
+ key="key_cut"
+ accesskey="&cutCmd.accesskey;"
+ command="cmd_cut"/>
+ <menuitem id="se-menu-copy"
+ label="©Cmd.label;"
+ key="key_copy"
+ accesskey="©Cmd.accesskey;"
+ command="cmd_copy"/>
+ <menuitem id="se-menu-paste"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ accesskey="&pasteCmd.accesskey;"
+ command="cmd_paste"/>
+ <menuitem id="se-menu-delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ accesskey="&deleteCmd.accesskey;"
+ command="cmd_delete"/>
+ <menuseparator/>
+ <menuitem id="se-menu-selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem id="se-menu-find"
+ label="&findCmd.label;"
+ accesskey="&findCmd.accesskey;"
+ key="key_find"
+ command="cmd_find"/>
+ <menuitem id="se-menu-findAgain"
+ label="&findAgainCmd.label;"
+ accesskey="&findAgainCmd.accesskey;"
+ key="key_findAgain"
+ command="cmd_findAgain"/>
+ <menuseparator/>
+ <menuitem id="se-menu-gotoLine"
+ label="&gotoLineCmd.label;"
+ accesskey="&gotoLineCmd.accesskey;"
+ key="key_gotoLine"
+ command="cmd_gotoLine"/>
+ </menupopup>
</overlay>
--- a/browser/devtools/sourceeditor/source-editor-ui.jsm
+++ b/browser/devtools/sourceeditor/source-editor-ui.jsm
@@ -45,16 +45,17 @@ Cu.import("resource://gre/modules/Servic
var EXPORTED_SYMBOLS = ["SourceEditorUI"];
/**
* The Source Editor component user interface.
*/
function SourceEditorUI(aEditor)
{
this.editor = aEditor;
+ this._onDirtyChanged = this._onDirtyChanged.bind(this);
}
SourceEditorUI.prototype = {
/**
* Initialize the user interface. This is called by the SourceEditor.init()
* method.
*/
init: function SEU_init()
@@ -67,16 +68,18 @@ SourceEditorUI.prototype = {
* initialization and it is ready for usage. Currently this code sets up the
* nsIController.
*/
onReady: function SEU_onReady()
{
if (this._ownerWindow.controllers) {
this._controller = new SourceEditorController(this.editor);
this._ownerWindow.controllers.insertControllerAt(0, this._controller);
+ this.editor.addEventListener(this.editor.EVENTS.DIRTY_CHANGED,
+ this._onDirtyChanged);
}
},
/**
* The "go to line" command UI. This displays a prompt that allows the user to
* input the line number to jump to.
*/
gotoLine: function SEU_gotoLine()
@@ -173,21 +176,49 @@ SourceEditorUI.prototype = {
if (this._ownerWindow.goUpdateCommand) {
this._ownerWindow.goUpdateCommand("cmd_findAgain");
this._ownerWindow.goUpdateCommand("cmd_findPrevious");
}
},
/**
+ * This is executed after each undo/redo operation.
+ * @private
+ */
+ _onUndoRedo: function SEU__onUndoRedo()
+ {
+ if (this._ownerWindow.goUpdateCommand) {
+ this._ownerWindow.goUpdateCommand("se-cmd-undo");
+ this._ownerWindow.goUpdateCommand("se-cmd-redo");
+ }
+ },
+
+ /**
+ * The DirtyChanged event handler for the editor. This tracks the editor state
+ * changes to make sure the Source Editor overlay Undo/Redo commands are kept
+ * up to date.
+ * @private
+ */
+ _onDirtyChanged: function SEU__onDirtyChanged()
+ {
+ this._onUndoRedo();
+ },
+
+ /**
* Destroy the SourceEditorUI instance. This is called by the
* SourceEditor.destroy() method.
*/
destroy: function SEU_destroy()
{
+ if (this._ownerWindow.controllers) {
+ this.editor.removeEventListener(this.editor.EVENTS.DIRTY_CHANGED,
+ this._onDirtyChanged);
+ }
+
this._ownerWindow = null;
this.editor = null;
this._controller = null;
},
};
/**
* The Source Editor nsIController implements features that need to be available
@@ -215,16 +246,18 @@ SourceEditorController.prototype = {
{
let result;
switch (aCommand) {
case "cmd_find":
case "cmd_findAgain":
case "cmd_findPrevious":
case "cmd_gotoLine":
+ case "se-cmd-undo":
+ case "se-cmd-redo":
result = true;
break;
default:
result = false;
break;
}
return result;
@@ -246,16 +279,22 @@ SourceEditorController.prototype = {
case "cmd_find":
case "cmd_gotoLine":
result = true;
break;
case "cmd_findAgain":
case "cmd_findPrevious":
result = this._editor.lastFind && this._editor.lastFind.lastFound != -1;
break;
+ case "se-cmd-undo":
+ result = this._editor.canUndo();
+ break;
+ case "se-cmd-redo":
+ result = this._editor.canRedo();
+ break;
default:
result = false;
break;
}
return result;
},
@@ -276,13 +315,19 @@ SourceEditorController.prototype = {
this._editor.ui.findNext();
break;
case "cmd_findPrevious":
this._editor.ui.findPrevious();
break;
case "cmd_gotoLine":
this._editor.ui.gotoLine();
break;
+ case "se-cmd-undo":
+ this._editor.undo();
+ break;
+ case "se-cmd-redo":
+ this._editor.redo();
+ break;
}
},
onEvent: function() { }
};
--- a/browser/devtools/sourceeditor/source-editor.jsm
+++ b/browser/devtools/sourceeditor/source-editor.jsm
@@ -194,16 +194,32 @@ SourceEditor.DEFAULTS = {
* - accel - boolean for the Accel key (Cmd on Macs, Ctrl on Linux/Windows).
* - shift - boolean for the Shift key.
* - alt - boolean for the Alt key.
* - callback - optional function to invoke, if the action is not predefined
* in the editor.
* @type array
*/
keys: null,
+
+ /**
+ * The editor context menu you want to display when the user right-clicks
+ * within the editor. This property can be:
+ * - a string that tells the ID of the xul:menupopup you want. This needs to
+ * be available within the editor parentElement.ownerDocument.
+ * - an nsIDOMElement object reference pointing to the xul:menupopup you
+ * want to open when the contextmenu event is fired.
+ *
+ * Set this property to a falsey value to disable the default context menu.
+ *
+ * @see SourceEditor.EVENTS.CONTEXT_MENU for more control over the contextmenu
+ * event.
+ * @type string|nsIDOMElement
+ */
+ contextMenu: "sourceEditorContextMenu",
};
/**
* Known editor events you can listen for.
*/
SourceEditor.EVENTS = {
/**
* The contextmenu event is fired when the editor context menu is invoked. The
@@ -211,16 +227,18 @@ SourceEditor.EVENTS = {
* - x - the pointer location on the x axis, relative to the document the
* user is editing.
* - y - the pointer location on the y axis, relative to the document the
* user is editing.
* - screenX - the pointer location on the x axis, relative to the screen.
* This value comes from the DOM contextmenu event.screenX property.
* - screenY - the pointer location on the y axis, relative to the screen.
* This value comes from the DOM contextmenu event.screenY property.
+ *
+ * @see SourceEditor.DEFAULTS.contextMenu
*/
CONTEXT_MENU: "ContextMenu",
/**
* The TextChanged event is fired when the editor content changes. The event
* object properties:
* - start - the character offset in the document where the change has
* occured.
@@ -277,16 +295,25 @@ SourceEditor.EVENTS = {
* a breakpoint is removed - either through API use or through the editor UI.
* Event object properties:
* - added - array that holds the new breakpoints.
* - removed - array that holds the breakpoints that have been removed.
* Each object in the added/removed arrays holds two properties: line and
* condition.
*/
BREAKPOINT_CHANGE: "BreakpointChange",
+
+ /**
+ * The DirtyChanged event is fired when the dirty state of the editor is
+ * changed. The dirty state of the editor tells if the are text changes that
+ * have not been saved yet. Event object properties: oldValue and newValue.
+ * Both are booleans telling the old dirty state and the new state,
+ * respectively.
+ */
+ DIRTY_CHANGED: "DirtyChanged",
};
/**
* Extend a destination object with properties from a source object.
*
* @param object aDestination
* @param object aSource
*/
@@ -298,16 +325,22 @@ function extend(aDestination, aSource)
}
}
}
/**
* Add methods common to all components.
*/
extend(SourceEditor.prototype, {
+ // Expose the static constants on the SourceEditor instances.
+ EVENTS: SourceEditor.EVENTS,
+ MODES: SourceEditor.MODES,
+ THEMES: SourceEditor.THEMES,
+ DEFAULTS: SourceEditor.DEFAULTS,
+
_lastFind: null,
/**
* Find a string in the editor.
*
* @param string aString
* The string you want to search for. If |aString| is not given the
* currently selected text is used.
--- a/browser/devtools/sourceeditor/test/Makefile.in
+++ b/browser/devtools/sourceeditor/test/Makefile.in
@@ -53,12 +53,13 @@ include $(topsrcdir)/config/rules.mk
browser_bug684546_reset_undo.js \
browser_bug695035_middle_click_paste.js \
browser_bug687160_line_api.js \
browser_bug650345_find.js \
browser_bug703692_focus_blur.js \
browser_bug725388_mouse_events.js \
browser_bug707987_debugger_breakpoints.js \
browser_bug712982_line_ruler_click.js \
+ browser_bug700893_dirty_state.js \
head.js \
libs:: $(_BROWSER_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug700893_dirty_state.js
@@ -0,0 +1,94 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test() {
+
+ let temp = {};
+ Cu.import("resource:///modules/source-editor.jsm", temp);
+ let SourceEditor = temp.SourceEditor;
+
+ let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
+ if (component == "textarea") {
+ ok(true, "skip test for bug 700893: only applicable for non-textarea components");
+ return;
+ }
+
+ waitForExplicitFinish();
+
+ let editor;
+
+ const windowUrl = "data:text/xml,<?xml version='1.0'?>" +
+ "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+ " title='test for bug 700893' width='600' height='500'><hbox flex='1'/></window>";
+ const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+ let testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+ testWin.addEventListener("load", function onWindowLoad() {
+ testWin.removeEventListener("load", onWindowLoad, false);
+ waitForFocus(initEditor, testWin);
+ }, false);
+
+ function initEditor()
+ {
+ let hbox = testWin.document.querySelector("hbox");
+ editor = new SourceEditor();
+ editor.init(hbox, {initialText: "foobar"}, editorLoaded);
+ }
+
+ function editorLoaded()
+ {
+ editor.focus();
+
+ is(editor.dirty, false, "editory is not dirty");
+
+ let event = null;
+ let eventHandler = function(aEvent) {
+ event = aEvent;
+ };
+ editor.addEventListener(SourceEditor.EVENTS.DIRTY_CHANGED, eventHandler);
+
+ editor.setText("omg");
+
+ is(editor.dirty, true, "editor is dirty");
+ ok(event, "DirtyChanged event fired")
+ is(event.oldValue, false, "event.oldValue is correct");
+ is(event.newValue, true, "event.newValue is correct");
+
+ event = null;
+ editor.setText("foo 2");
+ ok(!event, "no DirtyChanged event fired");
+
+ editor.dirty = false;
+
+ is(editor.dirty, false, "editor marked as clean");
+ ok(event, "DirtyChanged event fired")
+ is(event.oldValue, true, "event.oldValue is correct");
+ is(event.newValue, false, "event.newValue is correct");
+
+ event = null;
+ editor.setText("foo 3");
+
+ is(editor.dirty, true, "editor is dirty after changes");
+ ok(event, "DirtyChanged event fired")
+ is(event.oldValue, false, "event.oldValue is correct");
+ is(event.newValue, true, "event.newValue is correct");
+
+ editor.undo();
+ is(editor.dirty, false, "editor is not dirty after undo");
+ ok(event, "DirtyChanged event fired")
+ is(event.oldValue, true, "event.oldValue is correct");
+ is(event.newValue, false, "event.newValue is correct");
+
+ editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED, eventHandler);
+
+ editor.destroy();
+
+ testWin.close();
+ testWin = editor = null;
+
+ waitForFocus(finish, window);
+ }
+}
--- a/browser/devtools/styleeditor/styleeditor.xul
+++ b/browser/devtools/styleeditor/styleeditor.xul
@@ -40,29 +40,39 @@
%styleEditorDTD;
]>
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/splitview.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/splitview.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/styleeditor.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/styleeditor.css" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/source-editor-overlay.xul"?>
<xul:window xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.w3.org/1999/xhtml"
id="style-editor-chrome-window"
title="&window.title;"
windowtype="Tools:StyleEditor"
width="800" height="280"
persist="screenX screenY width height sizemode">
<xul:script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+<xul:popupset id="style-editor-popups">
+ <xul:menupopup id="sourceEditorContextMenu"/>
+</xul:popupset>
+
+<xul:commandset id="editMenuCommands"/>
+<xul:commandset id="sourceEditorCommands"/>
<xul:commandset id="style-editor-commandset">
<xul:command id="style-editor-cmd-close" oncommand="window.close();"/>
</xul:commandset>
+<xul:keyset id="editMenuKeys"/>
+<xul:keyset id="sourceEditorKeys"/>
<xul:keyset id="style-editor-keyset">
<xul:key id="style-editor-key-close"
key="&closeCmd.key;"
command="style-editor-cmd-close"
modifiers="accel"/>
</xul:keyset>
<xul:box id="style-editor-chrome" class="splitview-root loading">
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -72,20 +72,28 @@
* - why their expectations may not have been fulfilled
* - how browsers process CSS
* @constructor
*/
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
+const RX_UNIVERSAL_SELECTOR = /\s*\*\s*/g;
+const RX_NOT = /:not\((.*?)\)/g;
+const RX_PSEUDO_CLASS_OR_ELT = /(:[\w-]+\().*?\)/g;
+const RX_CONNECTORS = /\s*[\s>+~]\s*/g;
+const RX_ID = /\s*#\w+\s*/g;
+const RX_CLASS_OR_ATTRIBUTE = /\s*(?:\.\w+|\[.+?\])\s*/g;
+const RX_PSEUDO = /\s*:?:([\w-]+)(\(?\)?)\s*/g;
+
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-var EXPORTED_SYMBOLS = ["CssLogic"];
+var EXPORTED_SYMBOLS = ["CssLogic", "CssSelector"];
function CssLogic()
{
// The cache of examined CSS properties.
_propertyInfos: {};
}
/**
@@ -1427,61 +1435,107 @@ CssSelector.prototype = {
* stylesheet.
*/
get ruleLine()
{
return this._cssRule.line;
},
/**
+ * Retrieve the pseudo-elements that we support. This list should match the
+ * elements specified in layout/style/nsCSSPseudoElementList.h
+ */
+ get pseudoElements()
+ {
+ if (!CssSelector._pseudoElements) {
+ let pseudos = CssSelector._pseudoElements = new Set();
+ pseudos.add("after");
+ pseudos.add("before");
+ pseudos.add("first-letter");
+ pseudos.add("first-line");
+ pseudos.add("selection");
+ pseudos.add("-moz-focus-inner");
+ pseudos.add("-moz-focus-outer");
+ pseudos.add("-moz-list-bullet");
+ pseudos.add("-moz-list-number");
+ pseudos.add("-moz-math-anonymous");
+ pseudos.add("-moz-math-stretchy");
+ pseudos.add("-moz-progress-bar");
+ pseudos.add("-moz-selection");
+ }
+ return CssSelector._pseudoElements;
+ },
+
+ /**
* Retrieve specificity information for the current selector.
*
* @see http://www.w3.org/TR/css3-selectors/#specificity
* @see http://www.w3.org/TR/CSS2/selector.html
*
* @return {object} an object holding specificity information for the current
* selector.
*/
get specificity()
{
if (this._specificity) {
return this._specificity;
}
- let specificity = {};
+ let specificity = {
+ ids: 0,
+ classes: 0,
+ tags: 0
+ };
- specificity.ids = 0;
- specificity.classes = 0;
- specificity.tags = 0;
+ let text = this.text;
- // Split on CSS combinators (section 5.2).
- // TODO: We need to properly parse the selector. See bug 592743.
if (!this.elementStyle) {
- this.text.split(/[ >+]/).forEach(function(aSimple) {
- // The regex leaves empty nodes combinators like ' > '
- if (!aSimple) {
- return;
- }
- // See http://www.w3.org/TR/css3-selectors/#specificity
- // We can count the IDs by counting the '#' marks.
- specificity.ids += (aSimple.match(/#/g) || []).length;
- // Similar with class names and attribute matchers
- specificity.classes += (aSimple.match(/\./g) || []).length;
- specificity.classes += (aSimple.match(/\[/g) || []).length;
- // Pseudo elements count as elements.
- specificity.tags += (aSimple.match(/:/g) || []).length;
- // If we have anything of substance before we get into ids/classes/etc
- // then it must be a tag if it isn't '*'.
- let tag = aSimple.split(/[#.[:]/)[0];
- if (tag && tag != "*") {
+ // Remove universal selectors as they are not relevant as far as specificity
+ // is concerned.
+ text = text.replace(RX_UNIVERSAL_SELECTOR, "");
+
+ // not() is ignored but any selectors contained by it are counted. Let's
+ // remove the not() and keep the contents.
+ text = text.replace(RX_NOT, " $1");
+
+ // Simplify remaining psuedo classes & elements.
+ text = text.replace(RX_PSEUDO_CLASS_OR_ELT, " $1)");
+
+ // Replace connectors with spaces
+ text = text.replace(RX_CONNECTORS, " ");
+
+ text.split(/\s/).forEach(function(aSimple) {
+ // Count IDs.
+ aSimple = aSimple.replace(RX_ID, function() {
+ specificity.ids++;
+ return "";
+ });
+
+ // Count class names and attribute matchers.
+ aSimple = aSimple.replace(RX_CLASS_OR_ATTRIBUTE, function() {
+ specificity.classes++;
+ return "";
+ });
+
+ aSimple = aSimple.replace(RX_PSEUDO, function(aDummy, aPseudoName) {
+ if (this.pseudoElements.has(aPseudoName)) {
+ // Pseudo elements count as tags.
+ specificity.tags++;
+ } else {
+ // Pseudo classes count as classes.
+ specificity.classes++;
+ }
+ return "";
+ }.bind(this));
+
+ if (aSimple) {
specificity.tags++;
}
}, this);
}
-
this._specificity = specificity;
return this._specificity;
},
toString: function CssSelector_toString()
{
return this.text;
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -98,30 +98,37 @@ var EXPORTED_SYMBOLS = ["CssRuleView",
* set of disabled properties.
*
* @constructor
*/
function ElementStyle(aElement, aStore)
{
this.element = aElement;
this.store = aStore || {};
+
+ // We don't want to overwrite this.store.userProperties so we only create it
+ // if it doesn't already exist.
+ if (!("userProperties" in this.store)) {
+ this.store.userProperties = new UserProperties();
+ }
+
if (this.store.disabled) {
this.store.disabled = aStore.disabled;
} else {
this.store.disabled = WeakMap();
}
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();
+ this.populate();
}
// We're exporting _ElementStyle for unit tests.
var _ElementStyle = ElementStyle;
ElementStyle.prototype = {
// The element we're looking at.
element: null,
@@ -142,17 +149,17 @@ ElementStyle.prototype = {
this.onChanged();
}
},
/**
* Refresh the list of rules to be displayed for the active element.
* Upon completion, this.rules[] will hold a list of Rule objects.
*/
- _populate: function ElementStyle_populate()
+ populate: function ElementStyle_populate()
{
this.rules = [];
let element = this.element;
do {
this._addElementRules(element);
} while ((element = element.parentNode) &&
element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
@@ -417,31 +424,33 @@ Rule.prototype = {
/**
* Reapply all the properties in this rule, and update their
* computed styles. Store disabled properties in the element
* style's store. Will re-mark overridden properties.
*/
applyProperties: function Rule_applyProperties()
{
let disabledProps = [];
+ let store = this.elementStyle.store;
for each (let prop in this.textProps) {
if (!prop.enabled) {
disabledProps.push({
name: prop.name,
value: prop.value,
priority: prop.priority
});
continue;
}
+ store.userProperties.setProperty(this.style, prop.name, prop.value);
+
this.style.setProperty(prop.name, prop.value, prop.priority);
- // Refresh the property's value from the style, to reflect
+ // Refresh the property's priority 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._changed();
// Store disabled properties in the disabled store.
let disabled = this.elementStyle.store.disabled;
disabled.set(this.style, disabledProps);
@@ -514,41 +523,42 @@ Rule.prototype = {
/**
* Get the list of TextProperties from the style. Needs
* to parse the style's cssText.
*/
_getTextProperties: function Rule_getTextProperties()
{
this.textProps = [];
+ let store = this.elementStyle.store;
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 name = matches[1];
if (this.inherited &&
!this.elementStyle.domUtils.isInheritedProperty(name)) {
continue;
}
-
- let prop = new TextProperty(this, name, matches[2], matches[3] || "");
+ let value = store.userProperties.getProperty(this.style, name, matches[2]);
+ let prop = new TextProperty(this, name, value, matches[3] || "");
this.textProps.push(prop);
}
// Include properties from the disabled property store, if any.
let disabledProps = this.elementStyle.store.disabled.get(this.style);
if (!disabledProps) {
return;
}
for each (let prop in disabledProps) {
- let textProp = new TextProperty(this, prop.name,
- prop.value, prop.priority);
+ let value = store.userProperties.getProperty(this.style, prop.name, prop.value);
+ let textProp = new TextProperty(this, prop.name, value, prop.priority);
textProp.enabled = false;
this.textProps.push(textProp);
}
},
};
/**
* A single property in a rule's cssText.
@@ -708,25 +718,43 @@ CssRuleView.prototype = {
this._elementStyle = new ElementStyle(aElement, this.store);
this._elementStyle.onChanged = function() {
this._changed();
}.bind(this);
this._createEditors();
},
+
+ /**
+ * Update the rules for the currently highlighted element.
+ */
+ nodeChanged: function CssRuleView_nodeChanged()
+ {
+ this._clearRules();
+ this._elementStyle.populate();
+ this._createEditors();
+ },
+
+ /**
+ * Clear the rules.
+ */
+ _clearRules: function CssRuleView_clearRules()
+ {
+ while (this.element.hasChildNodes()) {
+ this.element.removeChild(this.element.lastChild);
+ }
+ },
/**
* Clear the rule view.
*/
clear: function CssRuleView_clear()
{
- while (this.element.hasChildNodes()) {
- this.element.removeChild(this.element.lastChild);
- }
+ this._clearRules();
this._viewedElement = null;
this._elementStyle = null;
},
/**
* Called when the user has made changes to the ElementStyle.
* Emits an event that clients can listen to.
*/
@@ -970,16 +998,22 @@ TextPropertyEditor.prototype = {
// 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, ";");
+ this.warning = createChild(this.element, "div", {
+ hidden: "",
+ class: "ruleview-warning",
+ title: CssLogic.l10n("rule.warning.title"),
+ });
+
// Holds the viewers for the computed properties.
// will be populated in |_updateComputed|.
this.computed = createChild(this.element, "ul", {
class: "ruleview-computedlist",
});
},
/**
@@ -1005,16 +1039,17 @@ TextPropertyEditor.prototype = {
// 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;
+ this.warning.hidden = this._validate();
// Populate the computed styles.
this._updateComputed();
},
_onStartEditing: function TextPropertyEditor_onStartEditing()
{
this.element.classList.remove("ruleview-overridden");
@@ -1140,16 +1175,33 @@ TextPropertyEditor.prototype = {
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);
}
},
+
+ /**
+ * Validate this property.
+ *
+ * @returns {Boolean}
+ * True if the property value is valid, false otherwise.
+ */
+ _validate: function TextPropertyEditor_validate()
+ {
+ let name = this.prop.name;
+ let value = this.prop.value;
+ let style = this.doc.createElementNS(HTML_NS, "div").style;
+
+ style.setProperty(name, value, null);
+
+ return !!style.getPropertyValue(name);
+ },
};
/**
* 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.
*
@@ -1359,16 +1411,71 @@ InplaceEditor.prototype = {
// Call the user's change handler if available.
if (this.change) {
this.change(this.input.value.trim());
}
}
};
/**
+ * Store of CSSStyleDeclarations mapped to properties that have been changed by
+ * the user.
+ */
+function UserProperties()
+{
+ this.weakMap = new WeakMap();
+}
+
+UserProperties.prototype = {
+ /**
+ * Get a named property for a given CSSStyleDeclaration.
+ *
+ * @param {CSSStyleDeclaration} aStyle
+ * The CSSStyleDeclaration against which the property is mapped.
+ * @param {String} aName
+ * The name of the property to get.
+ * @param {Boolean} aDefault
+ * Indicates whether the property value is one entered by a user.
+ * @returns {String}
+ * The property value if it has previously been set by the user, null
+ * otherwise.
+ */
+ getProperty: function UP_getProperty(aStyle, aName, aDefault) {
+ let entry = this.weakMap.get(aStyle, null);
+
+ if (entry && aName in entry) {
+ return entry[aName];
+ }
+ return typeof aDefault != "undefined" ? aDefault : null;
+
+ },
+
+ /**
+ * Set a named property for a given CSSStyleDeclaration.
+ *
+ * @param {CSSStyleDeclaration} aStyle
+ * The CSSStyleDeclaration against which the property is to be mapped.
+ * @param {String} aName
+ * The name of the property to set.
+ * @param {String} aValue
+ * The value of the property to set.
+ */
+ setProperty: function UP_setProperty(aStyle, aName, aValue) {
+ let entry = this.weakMap.get(aStyle, null);
+ if (entry) {
+ entry[aName] = aValue;
+ } else {
+ let props = {};
+ props[aName] = aValue;
+ this.weakMap.set(aStyle, props);
+ }
+ },
+};
+
+/**
* Helper functions
*/
/**
* Create a child element with a set of attributes.
*
* @param {Element} aParent
* The parent node.
--- a/browser/devtools/styleinspector/StyleInspector.jsm
+++ b/browser/devtools/styleinspector/StyleInspector.jsm
@@ -77,17 +77,17 @@ StyleInspector.prototype = {
// Were we invoked from the Highlighter?
if (this.IUI) {
this.openDocked = true;
let isOpen = this.isOpen.bind(this);
this.registrationObject = {
id: "styleinspector",
label: this.l10n("style.highlighter.button.label2"),
- tooltiptext: this.l10n("style.highlighter.button.tooltip"),
+ tooltiptext: this.l10n("style.highlighter.button.tooltip2"),
accesskey: this.l10n("style.highlighter.accesskey2"),
context: this,
get isOpen() isOpen(),
onSelect: this.selectNode,
onChanged: this.updateNode,
show: this.open,
hide: this.close,
dim: this.dimTool,
@@ -115,16 +115,21 @@ StyleInspector.prototype = {
this.iframe.getAttribute("src") ==
"chrome://browser/content/devtools/csshtmltree.xul") {
let selectedNode = this.selectedNode || null;
this.cssHtmlTree = new CssHtmlTree(this);
this.cssLogic.highlight(selectedNode);
this.cssHtmlTree.highlight(selectedNode);
this.iframe.removeEventListener("load", boundIframeOnLoad, true);
this.iframeReady = true;
+
+ // Now that we've loaded, select any node we were previously asked
+ // to show.
+ this.selectNode(this.selectedNode);
+
Services.obs.notifyObservers(null, "StyleInspector-opened", null);
}
}.bind(this);
this.iframe = this.IUI.getToolIframe(this.registrationObject);
this.iframe.addEventListener("load", boundIframeOnLoad, true);
},
@@ -210,21 +215,26 @@ StyleInspector.prototype = {
},
/**
* Check if the style inspector is open.
* @returns boolean
*/
isOpen: function SI_isOpen()
{
- return this.openDocked ? this.iframeReady && this.IUI.isSidebarOpen &&
+ return this.openDocked ? this.IUI.isSidebarOpen &&
(this.IUI.sidebarDeck.selectedPanel == this.iframe) :
this.panel && this.panel.state && this.panel.state == "open";
},
+ isLoaded: function SI_isLoaded()
+ {
+ return this.openDocked ? this.iframeReady : this.iframeReady && this.panelReady;
+ },
+
/**
* Select from Path (via CssHtmlTree_pathClick)
* @param aNode The node to inspect.
*/
selectFromPath: function SI_selectFromPath(aNode)
{
if (this.IUI && this.IUI.selection) {
if (aNode != this.IUI.selection) {
@@ -237,28 +247,28 @@ StyleInspector.prototype = {
/**
* Select a node to inspect in the Style Inspector panel
* @param aNode The node to inspect.
*/
selectNode: function SI_selectNode(aNode)
{
this.selectedNode = aNode;
- if (this.isOpen() && !this.dimmed) {
+ if (this.isLoaded() && !this.dimmed) {
this.cssLogic.highlight(aNode);
this.cssHtmlTree.highlight(aNode);
}
},
/**
* Update the display for the currently-selected node.
*/
updateNode: function SI_updateNode()
{
- if (this.isOpen() && !this.dimmed) {
+ if (this.isLoaded() && !this.dimmed) {
this.cssLogic.highlight(this.selectedNode);
this.cssHtmlTree.refreshPanel();
}
},
/**
* Dim or undim a panel by setting or removing a dimmed attribute.
* @param aState
--- a/browser/devtools/styleinspector/test/Makefile.in
+++ b/browser/devtools/styleinspector/test/Makefile.in
@@ -50,21 +50,23 @@ include $(topsrcdir)/config/rules.mk
browser_bug683672.js \
browser_styleinspector_bug_672746_default_styles.js \
browser_styleinspector_bug_672744_search_filter.js \
browser_bug589375_keybindings.js \
browser_styleinspector_bug_689759_no_results_placeholder.js \
browser_bug_692400_element_style.js \
browser_csslogic_inherited.js \
browser_ruleview_editor.js \
+ browser_ruleview_editor_changedvalues.js \
browser_ruleview_inherit.js \
browser_ruleview_manipulation.js \
browser_ruleview_override.js \
browser_ruleview_ui.js \
browser_bug705707_is_content_stylesheet.js \
+ browser_bug_592743_specificity.js \
head.js \
$(NULL)
_BROWSER_TEST_PAGES = \
browser_bug683672.html \
browser_bug705707_is_content_stylesheet.html \
browser_bug705707_is_content_stylesheet_imported.css \
browser_bug705707_is_content_stylesheet_imported2.css \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug_592743_specificity.js
@@ -0,0 +1,49 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that CSS specificity is properly calculated.
+
+let tempScope = {};
+Cu.import("resource:///modules/devtools/CssLogic.jsm", tempScope);
+let CssLogic = tempScope.CssLogic;
+let CssSelector = tempScope.CssSelector;
+
+function test()
+{
+ let tests = [
+ {text: "*", expected: "000"},
+ {text: "LI", expected: "001"},
+ {text: "UL LI", expected: "002"},
+ {text: "UL OL+LI", expected: "003"},
+ {text: "H1 + *[REL=up]", expected: "011"},
+ {text: "UL OL LI.red", expected: "013"},
+ {text: "LI.red.level", expected: "021"},
+ {text: ".red .level", expected: "020"},
+ {text: "#x34y", expected: "100"},
+ {text: "#s12:not(FOO)", expected: "101"},
+ {text: "body#home div#warning p.message", expected: "213"},
+ {text: "* body#home div#warning p.message", expected: "213"},
+ {text: "#footer *:not(nav) li", expected: "102"},
+ {text: "bar:nth-child(1n+0)", expected: "011"},
+ {text: "li::-moz-list-number", expected: "002"},
+ {text: "a:hover", expected: "011"},
+ ];
+
+ tests.forEach(function(aTest) {
+ let selector = new CssSelector(null, aTest.text);
+ let specificity = selector.specificity;
+
+ let result = "" + specificity.ids + specificity.classes + specificity.tags;
+ is(result, aTest.expected, "selector \"" + aTest.text +
+ "\" produces expected result");
+ });
+
+ finishUp();
+}
+
+function finishUp()
+{
+ CssLogic = CssSelector = null;
+ finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
@@ -0,0 +1,190 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let tempScope = {};
+Cu.import("resource:///modules/devtools/CssRuleView.jsm", tempScope);
+let CssRuleView = tempScope.CssRuleView;
+let _ElementStyle = tempScope._ElementStyle;
+let _editableField = tempScope._editableField;
+
+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);
+}
+
+var gRuleViewChanged = false;
+function ruleViewChanged()
+{
+ gRuleViewChanged = true;
+}
+
+function expectChange()
+{
+ ok(gRuleViewChanged, "Rule view should have fired a change event.");
+ gRuleViewChanged = false;
+}
+
+function startTest()
+{
+ let style = '' +
+ '#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.xul",
+ "cssruleviewtest",
+ "width=200,height=350");
+ ruleDialog.addEventListener("load", function onLoad(evt) {
+ ruleDialog.removeEventListener("load", onLoad, true);
+ let doc = ruleDialog.document;
+ ruleView = new CssRuleView(doc);
+ doc.documentElement.appendChild(ruleView.element);
+ ruleView.element.addEventListener("CssRuleViewChanged", ruleViewChanged, false);
+ ruleView.highlight(testElement);
+ waitForFocus(testCancelNew, ruleDialog);
+ }, true);
+}
+
+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 () {
+ ok(!gRuleViewChanged, "Shouldn't get a change event after a cancel.");
+ is(elementRuleEditor.rule.textProps.length, 0, "Should have canceled creating a new text property.");
+ ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties.");
+ testCreateNew();
+ });
+ 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) {
+ expectChange();
+ is(elementRuleEditor.rule.textProps.length, 1, "Should have created a new text property.");
+ is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
+ let textProp = elementRuleEditor.rule.textProps[0];
+ is(aEditor, textProp.editor.valueSpan.inplaceEditor, "Should be editing the value span now.");
+
+ aEditor.input.value = "#XYZ";
+ waitForEditorBlur(aEditor, function() {
+ expectChange();
+ is(textProp.value, "#XYZ", "Text prop should have been changed.");
+ is(textProp.editor._validate(), false, "#XYZ should not be a valid entry");
+ testEditProperty();
+ });
+ aEditor.input.blur();
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, ruleDialog);
+ });
+
+ 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) {
+ expectChange();
+ input = aEditor.input;
+ is(propEditor.valueSpan.inplaceEditor, aEditor, "Focus should have moved to the value.");
+
+ waitForEditorBlur(aEditor, function() {
+ expectChange();
+ let value = idRuleEditor.rule.style.getPropertyValue("border-color");
+ is(value, "red", "border-color should have been set.");
+ is(propEditor._validate(), true, "red should be a valid entry");
+ finishTest();
+ });
+
+ for each (let ch in "red;") {
+ EventUtils.sendChar(ch, ruleDialog);
+ }
+ });
+ for each (let ch in "border-color:") {
+ EventUtils.sendChar(ch, ruleDialog);
+ }
+ });
+
+ EventUtils.synthesizeMouse(propEditor.nameSpan, 1, 1,
+ { },
+ ruleDialog);}
+
+function finishTest()
+{
+ ruleView.element.removeEventListener("CssRuleViewChanged", ruleViewChanged, false);
+ ruleView.clear();
+ ruleDialog.close();
+ ruleDialog = ruleView = null;
+ doc = null;
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function test()
+{
+ waitForExplicitFinish();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function changedValues_load(evt) {
+ gBrowser.selectedBrowser.removeEventListener(evt.type, changedValues_load, true);
+ doc = content.document;
+ waitForFocus(startTest, content);
+ }, true);
+
+ content.location = "data:text/html,test rule view user changes";
+}
--- a/browser/devtools/tilt/Tilt.jsm
+++ b/browser/devtools/tilt/Tilt.jsm
@@ -122,76 +122,81 @@ Tilt.prototype = {
return;
}
// create a visualizer instance for the current tab
this.visualizers[id] = new TiltVisualizer({
chromeWindow: this.chromeWindow,
contentWindow: this.chromeWindow.gBrowser.selectedBrowser.contentWindow,
parentNode: this.chromeWindow.gBrowser.selectedBrowser.parentNode,
- requestAnimationFrame: this.chromeWindow.mozRequestAnimationFrame,
notifications: this.NOTIFICATIONS
});
// make sure the visualizer object was initialized properly
if (!this.visualizers[id].isInitialized()) {
this.destroy(id);
return;
}
Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.INITIALIZING, null);
},
/**
- * Destroys a specific instance of the visualizer.
+ * Starts destroying a specific instance of the visualizer.
*
* @param {String} aId
* the identifier of the instance in the visualizers array
* @param {Boolean} aAnimateFlag
* optional, set to true to display a destruction transition
*/
destroy: function T_destroy(aId, aAnimateFlag)
{
- // if the visualizer is already destroyed, don't do anything
- if (!this.visualizers[aId]) {
+ // if the visualizer is destroyed or destroying, don't do anything
+ if (!this.visualizers[aId] || this._isDestroying) {
+ return;
+ }
+ this._isDestroying = true;
+
+ let controller = this.visualizers[aId].controller;
+ let presenter = this.visualizers[aId].presenter;
+
+ let content = presenter.contentWindow;
+ let pageXOffset = content.pageXOffset * presenter.transforms.zoom;
+ let pageYOffset = content.pageYOffset * presenter.transforms.zoom;
+ TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom);
+
+ // if we're not doing any outro animation, just finish destruction directly
+ if (!aAnimateFlag) {
+ this._finish(aId);
return;
}
- if (!this.isDestroying) {
- this.isDestroying = true;
+ // otherwise, trigger the outro animation and notify necessary observers
+ Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYING, null);
- let finalize = function T_finalize(aId) {
- this.visualizers[aId].removeOverlay();
- this.visualizers[aId].cleanup();
- this.visualizers[aId] = null;
-
- this.isDestroying = false;
- this.chromeWindow.gBrowser.selectedBrowser.focus();
- Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYED, null);
- };
+ controller.removeEventListeners();
+ controller.arcball.reset([-pageXOffset, -pageYOffset]);
+ presenter.executeDestruction(this._finish.bind(this, aId));
+ },
- if (!aAnimateFlag) {
- finalize.call(this, aId);
- return;
- }
-
- let controller = this.visualizers[aId].controller;
- let presenter = this.visualizers[aId].presenter;
+ /**
+ * Finishes detroying a specific instance of the visualizer.
+ *
+ * @param {String} aId
+ * the identifier of the instance in the visualizers array
+ */
+ _finish: function T__finish(aId)
+ {
+ this.visualizers[aId].removeOverlay();
+ this.visualizers[aId].cleanup();
+ this.visualizers[aId] = null;
- let content = presenter.contentWindow;
- let pageXOffset = content.pageXOffset * presenter.transforms.zoom;
- let pageYOffset = content.pageYOffset * presenter.transforms.zoom;
-
- Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYING, null);
- TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom);
-
- controller.removeEventListeners();
- controller.arcball.reset([-pageXOffset, -pageYOffset]);
- presenter.executeDestruction(finalize.bind(this, aId));
- }
+ this._isDestroying = false;
+ this.chromeWindow.gBrowser.selectedBrowser.focus();
+ Services.obs.notifyObservers(null, TILT_NOTIFICATIONS.DESTROYED, null);
},
/**
* Handles any supplementary post-initialization work, done immediately
* after a TILT_NOTIFICATIONS.INITIALIZING notification.
*/
_whenInitializing: function T__whenInitializing()
{
@@ -281,26 +286,28 @@ Tilt.prototype = {
this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.DESTROYED, false);
this.chromeWindow.gBrowser.tabContainer.addEventListener("TabSelect",
this._onTabSelect.bind(this), false);
// FIXME: this shouldn't be done here, see bug #705131
let onOpened = function() {
- if (this.currentInstance) {
- this.chromeWindow.InspectorUI.stopInspecting();
- this.inspectButton.disabled = true;
- this.highlighterContainer.style.display = "none";
+ if (this.inspector && this.highlighter && this.currentInstance) {
+ this.inspector.stopInspecting();
+ this.inspector.inspectToolbutton.disabled = true;
+ this.highlighter.hide();
}
}.bind(this);
let onClosed = function() {
- this.inspectButton.disabled = false;
- this.highlighterContainer.style.display = "";
+ if (this.inspector && this.highlighter) {
+ this.inspector.inspectToolbutton.disabled = false;
+ this.highlighter.show();
+ }
}.bind(this);
Services.obs.addObserver(onOpened,
this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
Services.obs.addObserver(onClosed,
this.chromeWindow.InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
Services.obs.addObserver(onOpened,
TILT_NOTIFICATIONS.INITIALIZING, false);
@@ -333,36 +340,31 @@ Tilt.prototype = {
* Gets the visualizer instance for the current tab.
*/
get currentInstance()
{
return this.visualizers[this.currentWindowId];
},
/**
+ * Gets the current InspectorUI instance.
+ */
+ get inspector()
+ {
+ return this.chromeWindow.InspectorUI;
+ },
+
+ /**
+ * Gets the current Highlighter instance from the InspectorUI.
+ */
+ get highlighter()
+ {
+ return this.inspector.highlighter;
+ },
+
+ /**
* Gets the Tilt button in the Inspector toolbar.
*/
get tiltButton()
{
- return this.chromeWindow.document.getElementById(
- "inspector-3D-button");
- },
-
- /**
- * Gets the Inspect button in the Inspector toolbar.
- * FIXME: this shouldn't be needed here, remove after bug #705131
- */
- get inspectButton()
- {
- return this.chromeWindow.document.getElementById(
- "inspector-inspect-toolbutton");
- },
-
- /**
- * Gets the Highlighter contaniner stack.
- * FIXME: this shouldn't be needed here, remove after bug #705131
- */
- get highlighterContainer()
- {
- return this.chromeWindow.document.getElementById(
- "highlighter-container");
+ return this.chromeWindow.document.getElementById("inspector-3D-button");
}
};
--- a/browser/devtools/tilt/TiltGL.jsm
+++ b/browser/devtools/tilt/TiltGL.jsm
@@ -87,16 +87,18 @@ TiltGL.Renderer = function TGL_Renderer(
this.context.clearColor(0, 0, 0, 0);
this.context.clearDepth(1);
/**
* Variables representing the current framebuffer width and height.
*/
this.width = aCanvas.width;
this.height = aCanvas.height;
+ this.initialWidth = this.width;
+ this.initialHeight = this.height;
/**
* The current model view matrix.
*/
this.mvMatrix = mat4.identity(mat4.create());
/**
* The current projection matrix.
@@ -859,32 +861,40 @@ TiltGL.Program.prototype = {
let utils = TiltGL.ProgramUtils;
// check if the program wasn't already active
if (utils._activeProgram !== id) {
utils._activeProgram = id;
// use the the program if it wasn't already set
this._context.useProgram(this._ref);
-
- // check if the required vertex attributes aren't already set
- if (utils._enabledAttributes < this._attributes.length) {
- utils._enabledAttributes = this._attributes.length;
+ this.cleanupVertexAttrib();
- // enable any necessary vertex attributes using the cache
- for (let i in this._attributes) {
- if (this._attributes.hasOwnProperty(i)) {
- this._context.enableVertexAttribArray(this._attributes[i]);
- }
- }
+ // enable any necessary vertex attributes using the cache
+ for each (let attribute in this._attributes) {
+ this._context.enableVertexAttribArray(attribute);
+ utils._enabledAttributes.push(attribute);
}
}
},
/**
+ * Disables all currently enabled vertex attribute arrays.
+ */
+ cleanupVertexAttrib: function TGLP_cleanupVertexAttrib()
+ {
+ let utils = TiltGL.ProgramUtils;
+
+ for each (let attribute in utils._enabledAttributes) {
+ this._context.disableVertexAttribArray(attribute);
+ }
+ utils._enabledAttributes = [];
+ },
+
+ /**
* Binds a vertex buffer as an array buffer for a specific shader attribute.
*
* @param {String} aAtribute
* the attribute name obtained from the shader
* @param {Float32Array} aBuffer
* the buffer to be bound
*/
bindVertexBuffer: function TGLP_bindVertexBuffer(aAtribute, aBuffer)
@@ -944,19 +954,19 @@ TiltGL.Program.prototype = {
* the sampler name to bind the texture to
* @param {TiltGL.Texture} aTexture
* the texture to be bound
*/
bindTexture: function TGLP_bindTexture(aSampler, aTexture)
{
let gl = this._context;
- gl.uniform1i(this._uniforms[aSampler], 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, aTexture._ref);
+ gl.uniform1i(this._uniforms[aSampler], 0);
},
/**
* Function called when this object is destroyed.
*/
finalize: function TGLP_finalize()
{
if (this._context) {
@@ -1172,17 +1182,17 @@ TiltGL.ProgramUtils = {
/**
* Represents the current active shader, identified by an id.
*/
_activeProgram: -1,
/**
* Represents the current enabled attributes.
*/
- _enabledAttributes: -1
+ _enabledAttributes: []
};
/**
* This constructor creates a texture from an Image.
*
* @param {Object} aContext
* a WebGL context
* @param {Object} aProperties
@@ -1410,17 +1420,17 @@ TiltGL.TextureUtils = {
// generate mipmap if necessary
if (aProperties.mipmap) {
gl.generateMipmap(gl.TEXTURE_2D);
}
},
/**
* This shim renders a content window to a canvas element, but clamps the
- * maximum width and height of the canvas to half the WebGL MAX_TEXTURE_SIZE.
+ * maximum width and height of the canvas to the WebGL MAX_TEXTURE_SIZE.
*
* @param {Window} aContentWindow
* the content window to get a texture from
* @param {Number} aMaxImageSize
* the maximum image size to be used
*
* @return {Image} the new content window image
*/
@@ -1610,10 +1620,10 @@ TiltGL.create3DContext = function TGL_cr
};
/**
* Clears the cache and sets all the variables to default.
*/
TiltGL.clearCache = function TGL_clearCache()
{
TiltGL.ProgramUtils._activeProgram = -1;
- TiltGL.ProgramUtils._enabledAttributes = -1;
+ TiltGL.ProgramUtils._enabledAttributes = [];
};
--- a/browser/devtools/tilt/TiltUtils.jsm
+++ b/browser/devtools/tilt/TiltUtils.jsm
@@ -513,18 +513,18 @@ TiltUtils.bindObjectFunc = function TU_b
*/
TiltUtils.destroyObject = function TU_destroyObject(aScope)
{
if (!aScope) {
return;
}
// objects in Tilt usually use a function to handle internal destruction
- if ("function" === typeof aScope.finalize) {
- aScope.finalize();
+ if ("function" === typeof aScope._finalize) {
+ aScope._finalize();
}
for (let i in aScope) {
if (aScope.hasOwnProperty(i)) {
delete aScope[i];
}
}
};
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -50,33 +50,40 @@ const INVISIBLE_ELEMENTS = {
"link": true,
"meta": true,
"option": true,
"script": true,
"style": true,
"title": true
};
+// a node is represented in the visualization mesh as a rectangular stack
+// of 5 quads composed of 12 vertices; we draw these as triangles using an
+// index buffer of 12 unsigned int elements, obviously one for each vertex;
+// if a webpage has enough nodes to overflow the index buffer elements size,
+// weird things may happen; thus, when necessary, we'll split into groups
+const MAX_GROUP_NODES = Math.pow(2, Uint16Array.BYTES_PER_ELEMENT * 8) / 12 - 1;
+
const STACK_THICKNESS = 15;
const WIREFRAME_COLOR = [0, 0, 0, 0.25];
-const INTRO_TRANSITION_DURATION = 50;
-const OUTRO_TRANSITION_DURATION = 40;
+const INTRO_TRANSITION_DURATION = 1000;
+const OUTRO_TRANSITION_DURATION = 800;
const INITIAL_Z_TRANSLATION = 400;
const MOVE_INTO_VIEW_ACCURACY = 50;
const MOUSE_CLICK_THRESHOLD = 10;
-const MOUSE_INTRO_DELAY = 10;
+const MOUSE_INTRO_DELAY = 200;
const ARCBALL_SENSITIVITY = 0.5;
const ARCBALL_ROTATION_STEP = 0.15;
const ARCBALL_TRANSLATION_STEP = 35;
const ARCBALL_ZOOM_STEP = 0.1;
const ARCBALL_ZOOM_MIN = -3000;
const ARCBALL_ZOOM_MAX = 500;
-const ARCBALL_RESET_FACTOR = 0.9;
-const ARCBALL_RESET_INTERVAL = 1000 / 60;
+const ARCBALL_RESET_SPHERICAL_FACTOR = 0.1;
+const ARCBALL_RESET_LINEAR_FACTOR = 0.01;
const TILT_CRAFTER = "resource:///modules/devtools/TiltWorkerCrafter.js";
const TILT_PICKER = "resource:///modules/devtools/TiltWorkerPicker.js";
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/devtools/TiltGL.jsm");
Cu.import("resource:///modules/devtools/TiltMath.jsm");
Cu.import("resource:///modules/devtools/TiltUtils.jsm");
@@ -87,17 +94,16 @@ let EXPORTED_SYMBOLS = ["TiltVisualizer"
/**
* Initializes the visualization presenter and controller.
*
* @param {Object} aProperties
* an object containing the following properties:
* {Window} chromeWindow: a reference to the top level window
* {Window} contentWindow: the content window holding the visualized doc
* {Element} parentNode: the parent node to hold the visualization
- * {Function} requestAnimationFrame: responsible with scheduling loops
* {Object} notifications: necessary notifications for Tilt
* {Function} onError: optional, function called if initialization failed
* {Function} onLoad: optional, function called if initialization worked
*/
function TiltVisualizer(aProperties)
{
// make sure the properties parameter is a valid object
aProperties = aProperties || {};
@@ -116,17 +122,16 @@ function TiltVisualizer(aProperties)
});
/**
* Visualization logic and drawing loop.
*/
this.presenter = new TiltVisualizer.Presenter(this.canvas,
aProperties.chromeWindow,
aProperties.contentWindow,
- aProperties.requestAnimationFrame,
aProperties.notifications,
aProperties.onError || null,
aProperties.onLoad || null);
/**
* Visualization mouse and keyboard controller.
*/
this.controller = new TiltVisualizer.Controller(this.canvas, this.presenter);
@@ -179,28 +184,25 @@ TiltVisualizer.prototype = {
* This object manages the visualization logic and drawing loop.
*
* @param {HTMLCanvasElement} aCanvas
* the canvas element used for rendering
* @param {Window} aChromeWindow
* a reference to the top-level window
* @param {Window} aContentWindow
* the content window holding the document to be visualized
- * @param {Function} aRequestAnimationFrame
- * function responsible with scheduling loop frames
* @param {Object} aNotifications
* necessary notifications for Tilt
* @param {Function} onError
* function called if initialization failed
* @param {Function} onLoad
* function called if initialization worked
*/
TiltVisualizer.Presenter = function TV_Presenter(
- aCanvas, aChromeWindow, aContentWindow, aRequestAnimationFrame, aNotifications,
- onError, onLoad)
+ aCanvas, aChromeWindow, aContentWindow, aNotifications, onError, onLoad)
{
/**
* A canvas overlay used for drawing the visualization.
*/
this.canvas = aCanvas;
/**
* Save a reference to the top-level window, to access InspectorUI or Tilt.
@@ -215,35 +217,36 @@ TiltVisualizer.Presenter = function TV_P
/**
* Shortcut for accessing notifications strings.
*/
this.NOTIFICATIONS = aNotifications;
/**
* Create the renderer, containing useful functions for easy drawing.
*/
- this.renderer = new TiltGL.Renderer(aCanvas, onError, onLoad);
+ this._renderer = new TiltGL.Renderer(aCanvas, onError, onLoad);
/**
* A custom shader used for drawing the visualization mesh.
*/
- this.visualizationProgram = null;
+ this._visualizationProgram = null;
/**
* The combined mesh representing the document visualization.
*/
- this.texture = null;
- this.meshStacks = null;
- this.meshWireframe = null;
- this.traverseData = null;
+ this._texture = null;
+ this._meshData = null;
+ this._meshStacks = null;
+ this._meshWireframe = null;
+ this._traverseData = null;
/**
* A highlight quad drawn over a stacked dom node.
*/
- this.highlight = {
+ this._highlight = {
disabled: true,
v0: vec3.create(),
v1: vec3.create(),
v2: vec3.create(),
v3: vec3.create()
};
/**
@@ -263,396 +266,435 @@ TiltVisualizer.Presenter = function TV_P
this._currentSelection = -1; // the selected node index
this._initialSelection = false; // true if an initial selection was made
this._initialMeshConfiguration = false; // true if the 3D mesh was configured
/**
* Variable specifying if the scene should be redrawn.
* This should happen usually when the visualization is translated/rotated.
*/
- this.redraw = true;
+ this._redraw = true;
+
+ /**
+ * Total time passed since the rendering started.
+ * If the rendering is paused, this property won't get updated.
+ */
+ this._time = 0;
/**
- * A frame counter, incremented each time the scene is redrawn.
+ * Frame delta time (the ammount of time passed for each frame).
+ * This is used to smoothly interpolate animation transfroms.
*/
- this.frames = 0;
+ this._delta = 0;
+ this._prevFrameTime = 0;
+ this._currFrameTime = 0;
+
+
+ this._setup();
+ this._loop();
+};
+
+TiltVisualizer.Presenter.prototype = {
/**
* The initialization logic.
*/
- let setup = function TVP_setup()
+ _setup: function TVP__setup()
{
- let renderer = this.renderer;
+ let renderer = this._renderer;
let inspector = this.chromeWindow.InspectorUI;
// if the renderer was destroyed, don't continue setup
if (!renderer || !renderer.context) {
return;
}
// create the visualization shaders and program to draw the stacks mesh
- this.visualizationProgram = new renderer.Program({
+ this._visualizationProgram = new renderer.Program({
vs: TiltVisualizer.MeshShader.vs,
fs: TiltVisualizer.MeshShader.fs,