Merge from mozilla-central.

This commit is contained in:
David Anderson 2012-03-13 16:10:05 -07:00
commit fc580d8237
589 changed files with 12611 additions and 4914 deletions

View File

@ -78,3 +78,4 @@ bbc7014db2de49e2301680d2a86be8a53108a88a AURORA_BASE_20120131
0000000000000000000000000000000000000000 AURORA_BASE_20120131
0000000000000000000000000000000000000000 AURORA_BASE_20120131
bbc7014db2de49e2301680d2a86be8a53108a88a AURORA_BASE_20120131
b6627f28b7ec17e1b46a594df0f780d3a40847e4 FIREFOX_AURORA_13_BASE

View File

@ -59,7 +59,7 @@ interface nsIAccessibleRelation;
* Mozilla creates the implementations of nsIAccessible on demand.
* See http://www.mozilla.org/projects/ui/accessibility for more information.
*/
[scriptable, uuid(3126544c-826c-4694-a2ed-67bfe56a1f37)]
[scriptable, uuid(e7c44e0d-736e-4ead-afee-b51f4b574020)]
interface nsIAccessible : nsISupports
{
/**
@ -110,26 +110,6 @@ interface nsIAccessible : nsISupports
*/
readonly attribute DOMString innerHTML;
/**
* Retrieve the computed style value for this DOM node, if it is a DOM element.
* Note: the meanings of width, height and other size measurements depend
* on the version of CSS being used. Therefore, for bounds information,
* it is better to use nsIAccessible::accGetBounds.
*
* @param pseudoElt [in] The pseudo element to retrieve style for, or NULL
* for general computed style information for this node.
* @param propertyName [in] Retrieve the computed style value for this property name,
* for example "border-bottom".
*/
DOMString getComputedStyleValue(in DOMString pseudoElt, in DOMString propertyName);
/**
* The method is similar to getComputedStyleValue() excepting that this one
* returns nsIDOMCSSPrimitiveValue.
*/
nsIDOMCSSPrimitiveValue getComputedStyleCSSValue(in DOMString pseudoElt,
in DOMString propertyName);
/**
* The DOM node this nsIAccessible is associated with.
*/

View File

@ -66,6 +66,8 @@ public:
role != mozilla::a11y::roles::OPTION &&
role != mozilla::a11y::roles::LISTITEM &&
role != mozilla::a11y::roles::MENUITEM &&
role != mozilla::a11y::roles::COMBOBOX_OPTION &&
role != mozilla::a11y::roles::PARENT_MENUITEM &&
role != mozilla::a11y::roles::CHECK_MENU_ITEM &&
role != mozilla::a11y::roles::RADIO_MENU_ITEM &&
role != mozilla::a11y::roles::RADIOBUTTON &&
@ -83,6 +85,7 @@ private:
static mozilla::a11y::role BaseRole(mozilla::a11y::role aRole)
{
if (aRole == mozilla::a11y::roles::CHECK_MENU_ITEM ||
aRole == mozilla::a11y::roles::PARENT_MENUITEM ||
aRole == mozilla::a11y::roles::RADIO_MENU_ITEM)
return mozilla::a11y::roles::MENUITEM;
return aRole;

View File

@ -51,32 +51,6 @@
using namespace mozilla;
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// Constants and structures
/**
* Item of the gCSSTextAttrsMap map.
*/
struct nsCSSTextAttrMapItem
{
const char* mCSSName;
const char* mCSSValue;
nsIAtom** mAttrName;
const char* mAttrValue;
};
/**
* 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
{ "vertical-align", kAnyValue, &nsGkAtoms::textPosition, kCopyValue }
};
////////////////////////////////////////////////////////////////////////////////
// TextAttrsMgr
////////////////////////////////////////////////////////////////////////////////
@ -139,62 +113,61 @@ TextAttrsMgr::GetAttributes(nsIPersistentProperties* aAttributes,
frame = offsetElm->GetPrimaryFrame();
}
nsTArray<TextAttr*> textAttrArray(9);
// "language" text attribute
LangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode);
textAttrArray.AppendElement(&langTextAttr);
// "text-position" text attribute
CSSTextAttr posTextAttr(0, hyperTextElm, offsetElm);
textAttrArray.AppendElement(&posTextAttr);
// "background-color" text attribute
BGColorTextAttr bgColorTextAttr(rootFrame, frame);
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
FontSizeTextAttr fontSizeTextAttr(rootFrame, frame);
textAttrArray.AppendElement(&fontSizeTextAttr);
// "font-style" text attribute
FontStyleTextAttr fontStyleTextAttr(rootFrame, frame);
textAttrArray.AppendElement(&fontStyleTextAttr);
// "font-weight" text attribute
FontWeightTextAttr fontWeightTextAttr(rootFrame, frame);
textAttrArray.AppendElement(&fontWeightTextAttr);
// "text-underline(line-through)-style(color)" text attributes
TextDecorTextAttr textDecorTextAttr(rootFrame, frame);
textAttrArray.AppendElement(&textDecorTextAttr);
// "text-position" text attribute
TextPosTextAttr textPosTextAttr(rootFrame, frame);
TextAttr* attrArray[] =
{
&langTextAttr,
&bgColorTextAttr,
&colorTextAttr,
&fontFamilyTextAttr,
&fontSizeTextAttr,
&fontStyleTextAttr,
&fontWeightTextAttr,
&textDecorTextAttr,
&textPosTextAttr
};
// Expose text attributes if applicable.
if (aAttributes) {
PRUint32 len = textAttrArray.Length();
for (PRUint32 idx = 0; idx < len; idx++)
textAttrArray[idx]->Expose(aAttributes, mIncludeDefAttrs);
for (PRUint32 idx = 0; idx < ArrayLength(attrArray); idx++)
attrArray[idx]->Expose(aAttributes, mIncludeDefAttrs);
}
// Expose text attributes range where they are applied if applicable.
if (mOffsetAcc)
GetRange(textAttrArray, aStartHTOffset, aEndHTOffset);
GetRange(attrArray, ArrayLength(attrArray), aStartHTOffset, aEndHTOffset);
}
void
TextAttrsMgr::GetRange(const nsTArray<TextAttr*>& aTextAttrArray,
TextAttrsMgr::GetRange(TextAttr* aAttrArray[], PRUint32 aAttrArrayLen,
PRInt32* aStartHTOffset, PRInt32* aEndHTOffset)
{
PRUint32 attrLen = aTextAttrArray.Length();
// Navigate backward from anchor accessible to find start offset.
for (PRInt32 childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) {
nsAccessible *currAcc = mHyperTextAcc->GetChildAt(childIdx);
@ -209,8 +182,8 @@ TextAttrsMgr::GetRange(const nsTArray<TextAttr*>& aTextAttrArray,
return;
bool offsetFound = false;
for (PRUint32 attrIdx = 0; attrIdx < attrLen; attrIdx++) {
TextAttr* textAttr = aTextAttrArray[attrIdx];
for (PRUint32 attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
TextAttr* textAttr = aAttrArray[attrIdx];
if (!textAttr->Equal(currElm)) {
offsetFound = true;
break;
@ -235,8 +208,8 @@ TextAttrsMgr::GetRange(const nsTArray<TextAttr*>& aTextAttrArray,
return;
bool offsetFound = false;
for (PRUint32 attrIdx = 0; attrIdx < attrLen; attrIdx++) {
TextAttr* textAttr = aTextAttrArray[attrIdx];
for (PRUint32 attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
TextAttr* textAttr = aAttrArray[attrIdx];
// Alter the end offset when text attribute changes its value and stop
// the search.
@ -293,60 +266,6 @@ TextAttrsMgr::LangTextAttr::
}
////////////////////////////////////////////////////////////////////////////////
// CSSTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::CSSTextAttr::
CSSTextAttr(PRUint32 aIndex, nsIContent* aRootElm, nsIContent* aElm) :
TTextAttr<nsString>(!aElm), mIndex(aIndex)
{
mIsRootDefined = GetValueFor(aRootElm, &mRootNativeValue);
if (aElm)
mIsDefined = GetValueFor(aElm, &mNativeValue);
}
bool
TextAttrsMgr::CSSTextAttr::
GetValueFor(nsIContent* aElm, nsString* aValue)
{
nsCOMPtr<nsIDOMCSSStyleDeclaration> currStyleDecl =
nsCoreUtils::GetComputedStyleDeclaration(EmptyString(), aElm);
if (!currStyleDecl)
return false;
NS_ConvertASCIItoUTF16 cssName(gCSSTextAttrsMap[mIndex].mCSSName);
nsresult rv = currStyleDecl->GetPropertyValue(cssName, *aValue);
if (NS_FAILED(rv))
return true;
const char *cssValue = gCSSTextAttrsMap[mIndex].mCSSValue;
if (cssValue != kAnyValue && !aValue->EqualsASCII(cssValue))
return false;
return true;
}
void
TextAttrsMgr::CSSTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const nsString& aValue)
{
const char* attrValue = gCSSTextAttrsMap[mIndex].mAttrValue;
if (attrValue != kCopyValue) {
nsAutoString formattedValue;
AppendASCIItoUTF16(attrValue, formattedValue);
nsAccUtils::SetAccAttr(aAttributes, *gCSSTextAttrsMap[mIndex].mAttrName,
formattedValue);
return;
}
nsAccUtils::SetAccAttr(aAttributes, *gCSSTextAttrsMap[mIndex].mAttrName,
aValue);
}
////////////////////////////////////////////////////////////////////////////////
// BGColorTextAttr
////////////////////////////////////////////////////////////////////////////////
@ -742,3 +661,104 @@ TextAttrsMgr::TextDecorTextAttr::
formattedColor);
}
}
////////////////////////////////////////////////////////////////////////////////
// TextPosTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::TextPosTextAttr::
TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<TextPosValue>(!aFrame)
{
mRootNativeValue = GetTextPosValue(aRootFrame);
mIsRootDefined = mRootNativeValue != eTextPosNone;
if (aFrame) {
mNativeValue = GetTextPosValue(aFrame);
mIsDefined = mNativeValue != eTextPosNone;
}
}
bool
TextAttrsMgr::TextPosTextAttr::
GetValueFor(nsIContent* aContent, TextPosValue* aValue)
{
nsIFrame* frame = aContent->GetPrimaryFrame();
if (frame) {
*aValue = GetTextPosValue(frame);
return *aValue != eTextPosNone;
}
return false;
}
void
TextAttrsMgr::TextPosTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const TextPosValue& aValue)
{
switch (aValue) {
case eTextPosBaseline:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
NS_LITERAL_STRING("baseline"));
break;
case eTextPosSub:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
NS_LITERAL_STRING("sub"));
break;
case eTextPosSuper:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
NS_LITERAL_STRING("super"));
break;
}
}
TextAttrsMgr::TextPosValue
TextAttrsMgr::TextPosTextAttr::
GetTextPosValue(nsIFrame* aFrame) const
{
const nsStyleCoord& styleCoord = aFrame->GetStyleTextReset()->mVerticalAlign;
switch (styleCoord.GetUnit()) {
case eStyleUnit_Enumerated:
switch (styleCoord.GetIntValue()) {
case NS_STYLE_VERTICAL_ALIGN_BASELINE:
return eTextPosBaseline;
case NS_STYLE_VERTICAL_ALIGN_SUB:
return eTextPosSub;
case NS_STYLE_VERTICAL_ALIGN_SUPER:
return eTextPosSuper;
// No good guess for these:
// NS_STYLE_VERTICAL_ALIGN_TOP
// NS_STYLE_VERTICAL_ALIGN_TEXT_TOP
// NS_STYLE_VERTICAL_ALIGN_MIDDLE
// NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM
// NS_STYLE_VERTICAL_ALIGN_BOTTOM
// NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE
// Do not expose value of text-position attribute.
default:
break;
}
return eTextPosNone;
case eStyleUnit_Percent:
{
float percentValue = styleCoord.GetPercentValue();
return percentValue > 0 ?
eTextPosSuper :
(percentValue < 0 ? eTextPosSub : eTextPosBaseline);
}
case eStyleUnit_Coord:
{
nscoord coordValue = styleCoord.GetCoordValue();
return coordValue > 0 ?
eTextPosSuper :
(coordValue < 0 ? eTextPosSub : eTextPosBaseline);
}
}
return eTextPosNone;
}

View File

@ -107,11 +107,12 @@ protected:
* its value before or after the given offsets.
*
* @param aTextAttrArray [in] text attributes array
* @param aAttrArrayLen [in] text attributes array length
* @param aStartHTOffset [in, out] the start offset
* @param aEndHTOffset [in, out] the end offset
*/
class TextAttr;
void GetRange(const nsTArray<TextAttr*>& aTextAttrArray,
void GetRange(TextAttr* aAttrArray[], PRUint32 aAttrArrayLen,
PRInt32* aStartHTOffset, PRInt32* aEndHTOffset);
private:
@ -157,7 +158,7 @@ protected:
public:
TTextAttr(bool aGetRootValue) : mGetRootValue(aGetRootValue) {}
// ITextAttr
// TextAttr
virtual void Expose(nsIPersistentProperties* aAttributes,
bool aIncludeDefAttrValue)
{
@ -242,27 +243,6 @@ protected:
};
/**
* Class is used for the work with CSS based text attributes.
*/
class CSSTextAttr : public TTextAttr<nsString>
{
public:
CSSTextAttr(PRUint32 aIndex, nsIContent* aRootElm, nsIContent* aElm);
virtual ~CSSTextAttr() { }
protected:
// TextAttr
virtual bool GetValueFor(nsIContent* aElm, nsString* aValue);
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const nsString& aValue);
private:
PRInt32 mIndex;
};
/**
* Class is used for the work with 'background-color' text attribute.
*/
@ -435,6 +415,34 @@ protected:
const TextDecorValue& aValue);
};
/**
* Class is used for the work with "text-position" text attribute.
*/
enum TextPosValue {
eTextPosNone = 0,
eTextPosBaseline,
eTextPosSub,
eTextPosSuper
};
class TextPosTextAttr : public TTextAttr<TextPosValue>
{
public:
TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~TextPosTextAttr() { }
protected:
// TextAttr
virtual bool GetValueFor(nsIContent* aElm, TextPosValue* aValue);
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const TextPosValue& aValue);
private:
TextPosValue GetTextPosValue(nsIFrame* aFrame) const;
};
}; // TextAttrMgr
} // namespace a11y

View File

@ -175,70 +175,6 @@ nsAccUtils::GetPositionAndSizeForXULSelectControlItem(nsIContent *aContent,
(*aPosInSet)++; // group position is 1-index based.
}
void
nsAccUtils::GetPositionAndSizeForXULContainerItem(nsIContent *aContent,
PRInt32 *aPosInSet,
PRInt32 *aSetSize)
{
nsCOMPtr<nsIDOMXULContainerItemElement> item(do_QueryInterface(aContent));
if (!item)
return;
nsCOMPtr<nsIDOMXULContainerElement> container;
item->GetParentContainer(getter_AddRefs(container));
if (!container)
return;
// Get item count.
PRUint32 itemsCount = 0;
container->GetItemCount(&itemsCount);
// Get item index.
PRInt32 indexOf = 0;
container->GetIndexOfItem(item, &indexOf);
// Calculate set size and position in the set.
*aSetSize = 0, *aPosInSet = 0;
for (PRInt32 index = indexOf; index >= 0; index--) {
nsCOMPtr<nsIDOMXULElement> item;
container->GetItemAtIndex(index, getter_AddRefs(item));
nsCOMPtr<nsINode> itemNode(do_QueryInterface(item));
nsAccessible* itemAcc = itemNode ?
GetAccService()->GetAccessible(itemNode, nsnull) : nsnull;
if (itemAcc) {
PRUint32 itemRole = Role(itemAcc);
if (itemRole == nsIAccessibleRole::ROLE_SEPARATOR)
break; // We reached the beginning of our group.
if (!(itemAcc->State() & states::INVISIBLE)) {
(*aSetSize)++;
(*aPosInSet)++;
}
}
}
for (PRInt32 index = indexOf + 1; index < static_cast<PRInt32>(itemsCount);
index++) {
nsCOMPtr<nsIDOMXULElement> item;
container->GetItemAtIndex(index, getter_AddRefs(item));
nsCOMPtr<nsINode> itemNode(do_QueryInterface(item));
nsAccessible* itemAcc =
itemNode ? GetAccService()->GetAccessible(itemNode, nsnull) : nsnull;
if (itemAcc) {
PRUint32 itemRole = Role(itemAcc);
if (itemRole == nsIAccessibleRole::ROLE_SEPARATOR)
break; // We reached the end of our group.
if (!(itemAcc->State() & states::INVISIBLE))
(*aSetSize)++;
}
}
}
PRInt32
nsAccUtils::GetLevelForXULContainerItem(nsIContent *aContent)
{

View File

@ -115,14 +115,6 @@ public:
PRInt32 *aPosInSet,
PRInt32 *aSetSize);
/**
* Compute group position and group size (posinset and setsize) for
* nsIDOMXULContainerItemElement node.
*/
static void GetPositionAndSizeForXULContainerItem(nsIContent *aContent,
PRInt32 *aPosInSet,
PRInt32 *aSetSize);
/**
* Compute group level for nsIDOMXULContainerItemElement node.
*/

View File

@ -231,42 +231,6 @@ nsAccessible::SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry)
mRoleMapEntry = aRoleMapEntry;
}
NS_IMETHODIMP
nsAccessible::GetComputedStyleValue(const nsAString& aPseudoElt,
const nsAString& aPropertyName,
nsAString& aValue)
{
if (IsDefunct())
return NS_ERROR_FAILURE;
nsCOMPtr<nsIDOMCSSStyleDeclaration> styleDecl =
nsCoreUtils::GetComputedStyleDeclaration(aPseudoElt, mContent);
NS_ENSURE_TRUE(styleDecl, NS_ERROR_FAILURE);
return styleDecl->GetPropertyValue(aPropertyName, aValue);
}
NS_IMETHODIMP
nsAccessible::GetComputedStyleCSSValue(const nsAString& aPseudoElt,
const nsAString& aPropertyName,
nsIDOMCSSPrimitiveValue **aCSSValue) {
NS_ENSURE_ARG_POINTER(aCSSValue);
*aCSSValue = nsnull;
if (IsDefunct())
return NS_ERROR_FAILURE;
nsCOMPtr<nsIDOMCSSStyleDeclaration> styleDecl =
nsCoreUtils::GetComputedStyleDeclaration(aPseudoElt, mContent);
NS_ENSURE_STATE(styleDecl);
nsCOMPtr<nsIDOMCSSValue> cssValue;
styleDecl->GetPropertyCSSValue(aPropertyName, getter_AddRefs(cssValue));
NS_ENSURE_TRUE(cssValue, NS_ERROR_FAILURE);
return CallQueryInterface(cssValue, aCSSValue);
}
NS_IMETHODIMP
nsAccessible::GetDocument(nsIAccessibleDocument **aDocument)
{

View File

@ -467,24 +467,6 @@ nsApplicationAccessible::ScrollToPoint(PRUint32 aCoordinateType,
return NS_OK;
}
NS_IMETHODIMP
nsApplicationAccessible::GetComputedStyleValue(const nsAString &aPseudoElt,
const nsAString &aPropertyName,
nsAString &aValue)
{
return NS_OK;
}
NS_IMETHODIMP
nsApplicationAccessible::GetComputedStyleCSSValue(const nsAString &aPseudoElt,
const nsAString &aPropertyName,
nsIDOMCSSPrimitiveValue **aCSSPrimitiveValue)
{
NS_ENSURE_ARG_POINTER(aCSSPrimitiveValue);
*aCSSPrimitiveValue = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsApplicationAccessible::GetLanguage(nsAString &aLanguage)
{

View File

@ -76,12 +76,6 @@ public:
NS_SCRIPTABLE NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML);
NS_SCRIPTABLE NS_IMETHOD ScrollTo(PRUint32 aScrollType);
NS_SCRIPTABLE NS_IMETHOD ScrollToPoint(PRUint32 aCoordinateType, PRInt32 aX, PRInt32 aY);
NS_SCRIPTABLE NS_IMETHOD GetComputedStyleValue(const nsAString& aPseudoElt,
const nsAString& aPropertyName,
nsAString& aValue NS_OUTPARAM);
NS_SCRIPTABLE NS_IMETHOD GetComputedStyleCSSValue(const nsAString& aPseudoElt,
const nsAString& aPropertyName,
nsIDOMCSSPrimitiveValue** aValue NS_OUTPARAM);
NS_SCRIPTABLE NS_IMETHOD GetLanguage(nsAString& aLanguage);
NS_IMETHOD GetParent(nsIAccessible **aParent);
NS_IMETHOD GetNextSibling(nsIAccessible **aNextSibling);

View File

@ -570,26 +570,6 @@ nsCoreUtils::GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
walkUp = walkUp->GetParent();
}
already_AddRefed<nsIDOMCSSStyleDeclaration>
nsCoreUtils::GetComputedStyleDeclaration(const nsAString& aPseudoElt,
nsIContent *aContent)
{
nsIContent* content = GetDOMElementFor(aContent);
if (!content)
return nsnull;
// Returns number of items in style declaration
nsCOMPtr<nsIDOMWindow> window =
do_QueryInterface(content->OwnerDoc()->GetWindow());
if (!window)
return nsnull;
nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl;
nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(content));
window->GetComputedStyle(domElement, aPseudoElt, getter_AddRefs(cssDecl));
return cssDecl.forget();
}
already_AddRefed<nsIBoxObject>
nsCoreUtils::GetTreeBodyBoxObject(nsITreeBoxObject *aTreeBoxObj)
{

View File

@ -288,13 +288,6 @@ public:
static void GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
nsAString& aLanguage);
/**
* Return computed styles declaration for the given node.
*/
static already_AddRefed<nsIDOMCSSStyleDeclaration>
GetComputedStyleDeclaration(const nsAString& aPseudoElt,
nsIContent *aContent);
/**
* Return box object for XUL treechildren element by tree box object.
*/

View File

@ -1441,9 +1441,26 @@ nsHTMLTableAccessible::IsProbablyForLayout(bool *aIsProbablyForLayout)
if (rowElm->IsHTML() && rowElm->Tag() == nsGkAtoms::tr) {
for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
cellElm = cellElm->GetNextSibling()) {
if (cellElm->IsHTML() && cellElm->Tag() == nsGkAtoms::th) {
RETURN_LAYOUT_ANSWER(false,
"Has th -- legitimate table structures");
if (cellElm->IsHTML()) {
if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
RETURN_LAYOUT_ANSWER(false,
"Has th -- legitimate table structures");
}
if (cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) {
RETURN_LAYOUT_ANSWER(false,
"Has headers, scope, or abbr attribute -- legitimate table structures");
}
nsAccessible* cell = mDoc->GetAccessible(cellElm);
if (cell && cell->GetChildCount() == 1 &&
cell->FirstChild()->IsAbbreviation()) {
RETURN_LAYOUT_ANSWER(false,
"has abbr -- legitimate table structures");
}
}
}
}

View File

@ -43,6 +43,8 @@
#include "AccessibleComponent_i.c"
#include "nsAccessible.h"
#include "nsCoreUtils.h"
#include "nsWinUtils.h"
#include "States.h"
#include "nsString.h"
@ -156,17 +158,23 @@ __try {
*aColorValue = 0;
nsRefPtr<nsAccessible> acc(do_QueryObject(this));
if (!acc)
if (acc->IsDefunct())
return E_FAIL;
nsCOMPtr<nsIDOMCSSPrimitiveValue> cssValue;
nsresult rv = acc->GetComputedStyleCSSValue(EmptyString(), aPropName,
getter_AddRefs(cssValue));
if (NS_FAILED(rv) || !cssValue)
return GetHRESULT(rv);
nsCOMPtr<nsIDOMCSSStyleDeclaration> styleDecl =
nsWinUtils::GetComputedStyleDeclaration(acc->GetContent());
NS_ENSURE_STATE(styleDecl);
nsCOMPtr<nsIDOMCSSValue> cssGenericValue;
styleDecl->GetPropertyCSSValue(aPropName, getter_AddRefs(cssGenericValue));
nsCOMPtr<nsIDOMCSSPrimitiveValue> cssValue =
do_QueryInterface(cssGenericValue);
if (!cssValue)
return E_FAIL;
nsCOMPtr<nsIDOMRGBColor> rgbColor;
rv = cssValue->GetRGBColorValue(getter_AddRefs(rgbColor));
nsresult rv = cssValue->GetRGBColorValue(getter_AddRefs(rgbColor));
if (NS_FAILED(rv) || !rgbColor)
return GetHRESULT(rv);

View File

@ -339,7 +339,7 @@ __try{
return E_FAIL;
nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
nsCoreUtils::GetComputedStyleDeclaration(EmptyString(), mContent);
nsWinUtils::GetComputedStyleDeclaration(mContent);
NS_ENSURE_TRUE(cssDecl, E_FAIL);
PRUint32 length;
@ -374,7 +374,7 @@ __try {
return E_FAIL;
nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
nsCoreUtils::GetComputedStyleDeclaration(EmptyString(), mContent);
nsWinUtils::GetComputedStyleDeclaration(mContent);
NS_ENSURE_TRUE(cssDecl, E_FAIL);
PRUint32 index;

View File

@ -55,6 +55,25 @@ using namespace mozilla::a11y;
// tab windows.
const PRUnichar* kPropNameTabContent = L"AccessibleTabWindow";
already_AddRefed<nsIDOMCSSStyleDeclaration>
nsWinUtils::GetComputedStyleDeclaration(nsIContent* aContent)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aContent);
if (!elm)
return nsnull;
// Returns number of items in style declaration
nsCOMPtr<nsIDOMWindow> window =
do_QueryInterface(elm->OwnerDoc()->GetWindow());
if (!window)
return nsnull;
nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl;
nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(elm));
window->GetComputedStyle(domElement, EmptyString(), getter_AddRefs(cssDecl));
return cssDecl.forget();
}
HRESULT
nsWinUtils::ConvertToIA2Array(nsIArray *aGeckoArray, IUnknown ***aIA2Array,
long *aIA2ArrayLen)

View File

@ -52,6 +52,15 @@ const LPCWSTR kClassNameTabContent = L"MozillaContentWindowClass";
class nsWinUtils
{
public:
/**
* Return computed styles declaration for the given node.
*
* @note Please use it carefully since it can shutdown the accessible tree
* you operate on.
*/
static already_AddRefed<nsIDOMCSSStyleDeclaration>
GetComputedStyleDeclaration(nsIContent* aContent);
/**
* Convert nsIArray array of accessible objects to an array of IUnknown*
* objects used in IA2 methods.

View File

@ -311,14 +311,6 @@ nsXULMenuitemAccessible::GetLevelInternal()
return nsAccUtils::GetLevelForXULContainerItem(mContent);
}
void
nsXULMenuitemAccessible::GetPositionAndSizeInternal(PRInt32 *aPosInSet,
PRInt32 *aSetSize)
{
nsAccUtils::GetPositionAndSizeForXULContainerItem(mContent, aPosInSet,
aSetSize);
}
bool
nsXULMenuitemAccessible::CanHaveAnonChildren()
{

View File

@ -63,8 +63,6 @@ public:
virtual mozilla::a11y::role NativeRole();
virtual PRUint64 NativeState();
virtual PRInt32 GetLevelInternal();
virtual void GetPositionAndSizeInternal(PRInt32 *aPosInSet,
PRInt32 *aSetSize);
virtual bool CanHaveAnonChildren();

View File

@ -101,7 +101,6 @@ _TEST_FILES =\
test_descr.html \
test_nsIAccessibleDocument.html \
test_nsIAccessibleImage.html \
test_nsIAccessNode_utils.html \
test_nsOuterDocAccessible.html \
test_role_nsHyperTextAcc.html \
test_textboxes.html \

View File

@ -139,6 +139,13 @@
testGroupAttrs("h5", 0, 0, 5);
testGroupAttrs("h6", 0, 0, 6);
//////////////////////////////////////////////////////////////////////////
// ARIA combobox
testGroupAttrs("combo1_opt1", 1, 4);
testGroupAttrs("combo1_opt2", 2, 4);
testGroupAttrs("combo1_opt3", 3, 4);
testGroupAttrs("combo1_opt4", 4, 4);
SimpleTest.finish();
}
@ -305,5 +312,12 @@
<h5 id="h5">heading5</h5>
<h6 id="h6">heading6</h6>
<ul id="combo1" role="combobox">Password
<li id="combo1_opt1" role="option">Xyzzy</li>
<li id="combo1_opt2" role="option">Plughs</li>
<li id="combo1_opt3" role="option">Shazaam</li>
<li id="combo1_opt4" role="option">JoeSentMe</li>
</ul>
</body>
</html>

View File

@ -90,6 +90,13 @@
testGroupAttrs("radio1", 1, 2);
testGroupAttrs("radio2", 2, 2);
//////////////////////////////////////////////////////////////////////////
// xul:menulist
testGroupAttrs("menulist1.1", 1);
testGroupAttrs("menulist1.2", 2);
testGroupAttrs("menulist1.3", 3);
testGroupAttrs("menulist1.4", 4);
//////////////////////////////////////////////////////////////////////////
// ARIA menu (bug 441888)
testGroupAttrs("aria-menuitem", 1, 3);
@ -175,6 +182,15 @@
<radio id="radio2" label="radio2"/>
</radiogroup>
<menulist id="menulist1" label="Vehicle">
<menupopup>
<menuitem id="menulist1.1" label="Car"/>
<menuitem id="menulist1.2" label="Taxi"/>
<menuitem id="menulist1.3" label="Bus" selected="true"/>
<menuitem id="menulist1.4" label="Train"/>
</menupopup>
</menulist>
<vbox>
<description role="menuitem" id="aria-menuitem"
value="conventional menuitem"/>

View File

@ -181,6 +181,30 @@
attrs = {"text-position": gComputedStyle.verticalAlign};
testTextAttrs(ID, 55, attrs, defAttrs, 55, 64);
attrs = {};
testTextAttrs(ID, 64, attrs, defAttrs, 64, 69);
attrs = { "text-position": "super" };
testTextAttrs(ID, 69, attrs, defAttrs, 69, 84);
attrs = {};
testTextAttrs(ID, 84, attrs, defAttrs, 84, 89);
attrs = { "text-position": "sub" };
testTextAttrs(ID, 89, attrs, defAttrs, 89, 102);
attrs = {};
testTextAttrs(ID, 102, attrs, defAttrs, 102, 107);
attrs = { "text-position": "super" };
testTextAttrs(ID, 107, attrs, defAttrs, 107, 123);
attrs = {};
testTextAttrs(ID, 123, attrs, defAttrs, 123, 128);
attrs = { "text-position": "sub" };
testTextAttrs(ID, 128, attrs, defAttrs, 128, 142);
//////////////////////////////////////////////////////////////////////////
// area7
ID = "area7";
@ -505,6 +529,11 @@
title="Implement text attributes">
Mozilla Bug 345759
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=473569"
title="Restrict text-position to allowed values">
Mozilla Bug 473569
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=473576"
title="font-family text attribute should expose actual font used">
@ -549,7 +578,11 @@
This <sup>sentence</sup> has the word
<span style="vertical-align:super;">sentence</span> in
<sub>superscript</sub> and
<span style="vertical-align:sub;">subscript</span>
<span style="vertical-align:sub;">subscript</span> and
<span style="vertical-align:20%;">superscript 20%</span> and
<span style="vertical-align:-20%;">subscript 20%</span> and
<span style="vertical-align:20px;">superscript 20px</span> and
<span style="vertical-align:-20px;">subscript 20px</span>
</p>
<p lang="en" id="area7">

View File

@ -1,53 +0,0 @@
<html>
<head>
<title>nsIAccessNode util methods testing</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">
function doTest()
{
var elmObj = {};
var acc = getAccessible("span", null, elmObj);
computedStyle = document.defaultView.getComputedStyle(elmObj.value, "");
// html:span element
is(acc.getComputedStyleValue("", "color"), computedStyle.color,
"Wrong color for element with ID 'span'");
// text child of html:span element
acc = getAccessible(acc.firstChild);
is(acc.getComputedStyleValue("", "color"), computedStyle.color,
"Wrong color for text child of element with ID 'span'");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=454211"
title="nsIAccessNode util methods testing">
Mozilla Bug 454211
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<span role="note" style="color: red" id="span">text</span>
</body>
</html>

View File

@ -46,7 +46,7 @@ pref("browser.homescreenURL", "file:///data/local/homescreen.html,file:///system
#endif
// URL for the dialer application.
pref("dom.telephony.app.phone.url", "http://localhost:7777/data/local/apps/dialer/dialer.html http://localhost:7777/data/local/apps/homescreen/homescreen.html http://localhost:7777/apps/dialer/dialer.html http://localhost:7777/apps/homescreen/homescreen.html");
pref("dom.telephony.app.phone.url", "http://localhost:7777/data/local/apps/dialer/dialer.html,http://localhost:7777/data/local/apps/homescreen/homescreen.html,http://localhost:7777/apps/dialer/dialer.html,http://localhost:7777/apps/homescreen/homescreen.html");
// Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density.
pref("browser.viewport.scaleRatio", -1);
@ -444,3 +444,12 @@ pref("power.screen.timeout", 60);
pref("full-screen-api.enabled", true);
pref("media.volume.steps", 10);
// Data connection settings. These will eventually live in the
// navigator.settings API, or even in a database where we can look
// it up automatically (bug 729440), but for this will have to do.
pref("ril.data.enabled", false);
pref("ril.data.roaming.enabled", false);
pref("ril.data.apn", "");
pref("ril.data.user", "");
pref("ril.data.passwd", "");

View File

@ -282,7 +282,7 @@ pref("browser.urlbar.doubleClickSelectsAll", true);
#else
pref("browser.urlbar.doubleClickSelectsAll", false);
#endif
pref("browser.urlbar.autoFill", false);
pref("browser.urlbar.autoFill", true);
pref("browser.urlbar.autoFill.typed", true);
// 0: Match anywhere (e.g., middle of words)
// 1: Match on word boundaries and then try matching anywhere
@ -1042,9 +1042,6 @@ pref("devtools.debugger.enabled", false);
// The default Debugger UI height
pref("devtools.debugger.ui.height", 250);
// Disable remote debugging protocol logging
pref("devtools.debugger.log", false);
// Enable the style inspector
pref("devtools.styleinspector.enabled", true);

View File

@ -440,7 +440,8 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-m
#notification-popup-box[anchorid="geo-notification-icon"] > #geo-notification-icon,
#notification-popup-box[anchorid="indexedDB-notification-icon"] > #indexedDB-notification-icon,
#notification-popup-box[anchorid="addons-notification-icon"] > #addons-notification-icon,
#notification-popup-box[anchorid="password-notification-icon"] > #password-notification-icon {
#notification-popup-box[anchorid="password-notification-icon"] > #password-notification-icon,
#notification-popup-box[anchorid="webapps-notification-icon"] > #webapps-notification-icon {
display: -moz-box;
}

View File

@ -4752,10 +4752,15 @@ var XULBrowserWindow = {
}
// Show or hide browser chrome based on the whitelist
if (this.hideChromeForLocation(location))
if (this.hideChromeForLocation(location)) {
document.documentElement.setAttribute("disablechrome", "true");
else
document.documentElement.removeAttribute("disablechrome");
} else {
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
if (ss.getTabValue(gBrowser.selectedTab, "appOrigin"))
document.documentElement.setAttribute("disablechrome", "true");
else
document.documentElement.removeAttribute("disablechrome");
}
// Disable find commands in documents that ask for them to be disabled.
let disableFind = false;
@ -9100,7 +9105,6 @@ XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
#endif
});
var MousePosTracker = {
_listeners: [],
_x: 0,

View File

@ -527,6 +527,7 @@
<image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="webapps-notification-icon" class="webapps-anchor-icon" role="button"/>
</box>
<!-- Use onclick instead of normal popup= syntax since the popup
code fires onmousedown, and hence eats our favicon drag events.
@ -1003,8 +1004,6 @@
#endif
<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"

View File

@ -87,3 +87,7 @@ html|*#highlighter-nodeinfobar-tagname {
html|*#highlighter-nodeinfobar-tagname {
text-transform: lowercase;
}
.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
display: none;
}

View File

@ -15,16 +15,14 @@ function Cell(aGrid, aNode) {
this._node._newtabCell = this;
// Register drag-and-drop event handlers.
["DragEnter", "DragOver", "DragExit", "Drop"].forEach(function (aType) {
let method = "on" + aType;
this[method] = this[method].bind(this);
this._node.addEventListener(aType.toLowerCase(), this[method], false);
["dragenter", "dragover", "dragexit", "drop"].forEach(function (aType) {
this._node.addEventListener(aType, this, false);
}, this);
}
Cell.prototype = {
/**
*
* The grid.
*/
_grid: null,
@ -97,41 +95,27 @@ Cell.prototype = {
},
/**
* Event handler for the 'dragenter' event.
* @param aEvent The dragenter event.
* Handles all cell events.
*/
onDragEnter: function Cell_onDragEnter(aEvent) {
if (gDrag.isValid(aEvent)) {
aEvent.preventDefault();
gDrop.enter(this, aEvent);
}
},
handleEvent: function Cell_handleEvent(aEvent) {
if (aEvent.type != "dragexit" && !gDrag.isValid(aEvent))
return;
/**
* Event handler for the 'dragover' event.
* @param aEvent The dragover event.
*/
onDragOver: function Cell_onDragOver(aEvent) {
if (gDrag.isValid(aEvent))
aEvent.preventDefault();
},
/**
* Event handler for the 'dragexit' event.
* @param aEvent The dragexit event.
*/
onDragExit: function Cell_onDragExit(aEvent) {
gDrop.exit(this, aEvent);
},
/**
* Event handler for the 'drop' event.
* @param aEvent The drop event.
*/
onDrop: function Cell_onDrop(aEvent) {
if (gDrag.isValid(aEvent)) {
aEvent.preventDefault();
gDrop.drop(this, aEvent);
switch (aEvent.type) {
case "dragenter":
aEvent.preventDefault();
gDrop.enter(this, aEvent);
break;
case "dragover":
aEvent.preventDefault();
break;
case "dragexit":
gDrop.exit(this, aEvent);
break;
case "drop":
aEvent.preventDefault();
gDrop.drop(this, aEvent);
break;
}
}
};

View File

@ -36,11 +36,11 @@ let gDrag = {
start: function Drag_start(aSite, aEvent) {
this._draggedSite = aSite;
// Prevent moz-transform for left, top.
aSite.node.setAttribute("dragged", "true");
// Make sure the dragged site is floating above the grid.
aSite.node.setAttribute("ontop", "true");
// Mark nodes as being dragged.
let selector = ".newtab-site, .newtab-control, .newtab-thumbnail";
let nodes = aSite.node.parentNode.querySelectorAll(selector);
for (let i = 0; i < nodes.length; i++)
nodes[i].setAttribute("dragged", "true");
this._setDragData(aSite, aEvent);
@ -88,13 +88,12 @@ let gDrag = {
* @param aEvent The 'dragend' event.
*/
end: function Drag_end(aSite, aEvent) {
aSite.node.removeAttribute("dragged");
let nodes = aSite.node.parentNode.querySelectorAll("[dragged]");
for (let i = 0; i < nodes.length; i++)
nodes[i].removeAttribute("dragged");
// Slide the dragged site back into its cell (may be the old or the new cell).
gTransformation.slideSiteTo(aSite, aSite.cell, {
unfreeze: true,
callback: function () aSite.node.removeAttribute("ontop")
});
gTransformation.slideSiteTo(aSite, aSite.cell, {unfreeze: true});
this._draggedSite = null;
},
@ -106,7 +105,11 @@ let gDrag = {
*/
isValid: function Drag_isValid(aEvent) {
let dt = aEvent.dataTransfer;
return dt && dt.types.contains("text/x-moz-url");
let mimeType = "text/x-moz-url";
// Check that the drag data is non-empty.
// Can happen when dragging places folders.
return dt && dt.types.contains(mimeType) && dt.getData(mimeType);
},
/**
@ -128,13 +131,13 @@ let gDrag = {
// Create and use an empty drag element. We don't want to use the default
// drag image with its default opacity.
let dragElement = document.createElementNS(HTML_NAMESPACE, "div");
dragElement.classList.add("drag-element");
let body = document.getElementById("body");
body.appendChild(dragElement);
dragElement.classList.add("newtab-drag");
let scrollbox = document.getElementById("newtab-scrollbox");
scrollbox.appendChild(dragElement);
dt.setDragImage(dragElement, 0, 0);
// After the 'dragstart' event has been processed we can remove the
// temporary drag element from the DOM.
setTimeout(function () body.removeChild(dragElement), 0);
setTimeout(function () scrollbox.removeChild(dragElement), 0);
}
};

View File

@ -26,13 +26,26 @@ let gDropTargetShim = {
init: function DropTargetShim_init() {
let node = gGrid.node;
this._dragover = this._dragover.bind(this);
// Add drag event handlers.
node.addEventListener("dragstart", this._start.bind(this), true);
// XXX bug 505521 - Don't listen for drag, it's useless at the moment.
//node.addEventListener("drag", this._drag.bind(this), false);
node.addEventListener("dragend", this._end.bind(this), true);
node.addEventListener("dragstart", this, true);
node.addEventListener("dragend", this, true);
},
/**
* Handles all shim events.
*/
handleEvent: function DropTargetShim_handleEvent(aEvent) {
switch (aEvent.type) {
case "dragstart":
this._start(aEvent);
break;
case "dragover":
this._dragover(aEvent);
break;
case "dragend":
this._end(aEvent);
break;
}
},
/**
@ -40,11 +53,11 @@ let gDropTargetShim = {
* @param aEvent The 'dragstart' event.
*/
_start: function DropTargetShim_start(aEvent) {
if (aEvent.target.classList.contains("site")) {
if (aEvent.target.classList.contains("newtab-link")) {
gGrid.lock();
// XXX bug 505521 - Listen for dragover on the document.
document.documentElement.addEventListener("dragover", this._dragover, false);
document.documentElement.addEventListener("dragover", this, false);
}
},
@ -56,12 +69,7 @@ let gDropTargetShim = {
// Let's see if we find a drop target.
let target = this._findDropTarget(aEvent);
if (target == this._lastDropTarget) {
// XXX bug 505521 - Don't fire dragover for now (causes recursion).
/*if (target)
// The last drop target is valid and didn't change.
this._dispatchEvent(aEvent, "dragover", target);*/
} else {
if (target != this._lastDropTarget) {
if (this._lastDropTarget)
// We left the last drop target.
this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
@ -84,7 +92,7 @@ let gDropTargetShim = {
* @param aEvent The 'dragover' event.
*/
_dragover: function DropTargetShim_dragover(aEvent) {
let sourceNode = aEvent.dataTransfer.mozSourceNode;
let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
gDrag.drag(sourceNode._newtabSite, aEvent);
this._drag(aEvent);
@ -117,7 +125,7 @@ let gDropTargetShim = {
gGrid.unlock();
// XXX bug 505521 - Remove the document's dragover listener.
document.documentElement.removeEventListener("dragover", this._dragover, false);
document.documentElement.removeEventListener("dragover", this, false);
},
/**

View File

@ -24,7 +24,7 @@ let gGrid = {
*/
get cells() {
let cells = [];
let children = this.node.querySelectorAll("li");
let children = this.node.querySelectorAll(".newtab-cell");
for (let i = 0; i < children.length; i++)
cells.push(new Cell(this, children[i]));
@ -43,8 +43,8 @@ let gGrid = {
* Initializes the grid.
* @param aSelector The query selector of the grid.
*/
init: function Grid_init(aSelector) {
this._node = document.querySelector(aSelector);
init: function Grid_init() {
this._node = document.getElementById("newtab-grid");
this._createSiteFragment();
this._draw();
},
@ -96,21 +96,20 @@ let gGrid = {
* Creates the DOM fragment that is re-used when creating sites.
*/
_createSiteFragment: function Grid_createSiteFragment() {
let site = document.createElementNS(HTML_NAMESPACE, "a");
site.classList.add("site");
let site = document.createElementNS(HTML_NAMESPACE, "div");
site.classList.add("newtab-site");
site.setAttribute("draggable", "true");
// Create the site's inner HTML code.
site.innerHTML =
'<img class="site-img" width="' + THUMB_WIDTH +'" ' +
' height="' + THUMB_HEIGHT + '" alt=""/>' +
'<span class="site-title"/>' +
'<span class="site-strip">' +
' <input class="button strip-button strip-button-pin" type="button"' +
' tabindex="-1" title="' + newTabString("pin") + '"/>' +
' <input class="button strip-button strip-button-block" type="button"' +
' tabindex="-1" title="' + newTabString("block") + '"/>' +
'</span>';
'<a class="newtab-link">' +
' <span class="newtab-thumbnail"/>' +
' <span class="newtab-title"/>' +
'</a>' +
'<input type="button" title="' + newTabString("pin") + '"' +
' class="newtab-control newtab-control-pin"/>' +
'<input type="button" title="' + newTabString("block") + '"' +
' class="newtab-control newtab-control-block"/>';
this._siteFragment = document.createDocumentFragment();
this._siteFragment.appendChild(site);

View File

@ -1,161 +1,174 @@
:root {
-moz-appearance: none;
-moz-user-focus: normal;
}
#scrollbox:not([page-disabled]) {
input[type=button] {
cursor: pointer;
}
/* SCROLLBOX */
#newtab-scrollbox {
display: -moz-box;
position: relative;
-moz-box-flex: 1;
}
#newtab-scrollbox:not([page-disabled]) {
overflow: auto;
}
#body {
position: relative;
margin: 0;
min-width: 675px;
-moz-user-select: none;
}
.button {
cursor: pointer;
}
/* TOOLBAR */
#toolbar {
/* TOGGLE */
#newtab-toggle {
position: absolute;
top: 12px;
right: 12px;
}
#toolbar[page-disabled] {
position: fixed;
}
#toolbar:-moz-locale-dir(rtl) {
left: 8px;
#newtab-toggle:-moz-locale-dir(rtl) {
left: 12px;
right: auto;
}
.toolbar-button {
position: absolute;
cursor: pointer;
-moz-transition: opacity 200ms ease-out;
/* MARGINS */
#newtab-vertical-margin {
display: -moz-box;
position: relative;
-moz-box-flex: 1;
-moz-box-orient: vertical;
}
#toolbar-button-show,
#toolbar-button-reset {
opacity: 0;
pointer-events: none;
#newtab-margin-top {
min-height: 50px;
max-height: 80px;
-moz-box-flex: 1;
}
#toolbar-button-reset[modified],
#toolbar-button-show[page-disabled] {
opacity: 1;
pointer-events: auto;
#newtab-margin-bottom {
min-height: 40px;
max-height: 100px;
-moz-box-flex: 1;
}
#toolbar-button-hide[page-disabled],
#toolbar-button-reset[page-disabled] {
opacity: 0;
pointer-events: none;
#newtab-horizontal-margin {
display: -moz-box;
-moz-box-flex: 5;
}
.newtab-side-margin {
min-width: 40px;
max-width: 300px;
-moz-box-flex: 1;
}
/* GRID */
#grid {
width: 637px;
height: 411px;
overflow: hidden;
list-style-type: none;
-moz-transition: opacity 200ms ease-out;
#newtab-grid {
display: -moz-box;
-moz-box-flex: 5;
-moz-box-orient: vertical;
min-width: 600px;
min-height: 400px;
-moz-transition: 100ms ease-out;
-moz-transition-property: opacity;
}
#grid[page-disabled] {
#newtab-grid[page-disabled] {
opacity: 0;
}
#grid[page-disabled],
#grid[locked] {
#newtab-grid[locked],
#newtab-grid[page-disabled] {
pointer-events: none;
}
/* ROWS */
.newtab-row {
display: -moz-box;
-moz-box-orient: horizontal;
-moz-box-direction: normal;
-moz-box-flex: 1;
}
/* CELLS */
.cell {
float: left;
width: 201px;
height: 127px;
margin-bottom: 15px;
-moz-margin-end: 16px;
}
.cell:-moz-locale-dir(rtl) {
float: right;
}
.cell:nth-child(3n+3) {
-moz-margin-end: 0;
.newtab-cell {
display: -moz-box;
-moz-box-flex: 1;
}
/* SITES */
.site {
display: block;
.newtab-site {
position: relative;
width: 201px;
height: 127px;
-moz-box-flex: 1;
-moz-transition: 100ms ease-out;
-moz-transition-property: top, left, opacity;
}
.site[frozen] {
.newtab-site[frozen] {
position: absolute;
pointer-events: none;
}
.site[ontop] {
.newtab-site[dragged] {
-moz-transition-property: none;
z-index: 10;
}
/* SITE IMAGE */
.site-img {
display: block;
opacity: 0.75;
-moz-transition: opacity 200ms ease-out;
}
.site:hover > .site-img,
.site[ontop] > .site-img,
.site:-moz-focusring > .site-img {
opacity: 1;
}
.site-img[loading] {
display: none;
}
/* SITE TITLE */
.site-title {
position: absolute;
left: 0;
bottom: 0;
overflow: hidden;
}
/* SITE STRIP */
.site-strip {
/* LINK + THUMBNAILS */
.newtab-link,
.newtab-thumbnail {
position: absolute;
left: 0;
top: 0;
width: 195px;
height: 17px;
overflow: hidden;
opacity: 0;
-moz-transition: opacity 200ms ease-out;
right: 0;
bottom: 0;
}
.site:hover:not([frozen]) > .site-strip {
.newtab-thumbnail {
opacity: .8;
-moz-transition: opacity 100ms ease-out;
}
.newtab-thumbnail[dragged],
.newtab-link:-moz-focusring > .newtab-thumbnail,
.newtab-site:hover > .newtab-link > .newtab-thumbnail {
opacity: 1;
}
.strip-button-pin,
.strip-button-block:-moz-locale-dir(rtl) {
float: left;
/* TITLES */
.newtab-title {
position: absolute;
left: 0;
right: 0;
bottom: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.strip-button-block,
.strip-button-pin:-moz-locale-dir(rtl) {
float: right;
/* CONTROLS */
.newtab-control {
position: absolute;
top: 4px;
opacity: 0;
-moz-transition: opacity 100ms ease-out;
}
.newtab-control:-moz-focusring,
.newtab-site:hover > .newtab-control {
opacity: 1;
}
.newtab-control[dragged] {
opacity: 0 !important;
}
.newtab-control-pin:-moz-locale-dir(ltr),
.newtab-control-block:-moz-locale-dir(rtl) {
left: 4px;
}
.newtab-control-block:-moz-locale-dir(ltr),
.newtab-control-pin:-moz-locale-dir(rtl) {
right: 4px;
}
/* DRAG & DROP */
@ -165,7 +178,7 @@
* so that we can use custom drag images and elements. It needs an opacity of
* 0.01 so that the core code detects that it's in fact a visible element.
*/
.drag-element {
.newtab-drag {
width: 1px;
height: 1px;
background-color: #fff;

View File

@ -30,13 +30,10 @@ XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
function newTabString(name) gStringBundle.GetStringFromName('newtab.' + name);
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
const THUMB_WIDTH = 201;
const THUMB_HEIGHT = 127;
#include batch.js
#include transformations.js
#include page.js
#include toolbar.js
#include grid.js
#include cells.js
#include sites.js
@ -47,4 +44,4 @@ const THUMB_HEIGHT = 127;
#include updater.js
// Everything is loaded. Initialize the New Tab Page.
gPage.init("#toolbar", "#grid");
gPage.init();

View File

@ -4,7 +4,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?>
@ -13,28 +13,44 @@
%newTabDTD;
]>
<xul:window xmlns="http://www.w3.org/1999/xhtml"
<xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
disablefastfind="true" title="&newtab.pageTitle;">
<xul:vbox id="scrollbox" flex="1" title=" ">
<body id="body">
<div id="toolbar">
<input class="button toolbar-button" id="toolbar-button-show"
type="button" title="&newtab.show;"/>
<input class="button toolbar-button" id="toolbar-button-hide"
type="button" title="&newtab.hide;"/>
<input class="button toolbar-button" id="toolbar-button-reset"
type="button" title="&newtab.reset;"/>
xul:disablefastfind="true" xul:title="&newtab.pageTitle;">
<div id="newtab-scrollbox">
<div id="newtab-vertical-margin">
<div id="newtab-margin-top"/>
<div id="newtab-horizontal-margin">
<div class="newtab-side-margin"/>
<div id="newtab-grid">
<div class="newtab-row">
<div class="newtab-cell"/>
<div class="newtab-cell"/>
<div class="newtab-cell"/>
</div>
<div class="newtab-row">
<div class="newtab-cell"/>
<div class="newtab-cell"/>
<div class="newtab-cell"/>
</div>
<div class="newtab-row">
<div class="newtab-cell"/>
<div class="newtab-cell"/>
<div class="newtab-cell"/>
</div>
</div>
<div class="newtab-side-margin"/>
</div>
<ul id="grid">
<li class="cell"/><li class="cell"/><li class="cell"/>
<li class="cell"/><li class="cell"/><li class="cell"/>
<li class="cell"/><li class="cell"/><li class="cell"/>
</ul>
<div id="newtab-margin-bottom"/>
</div>
<input id="newtab-toggle" type="button"/>
</div>
<xul:script type="text/javascript;version=1.8"
src="chrome://browser/content/newtab/newTab.js"/>
</body>
</xul:vbox>
<xul:script type="text/javascript;version=1.8"
src="chrome://browser/content/newtab/newTab.js"/>
</xul:window>

View File

@ -11,25 +11,24 @@
let gPage = {
/**
* Initializes the page.
* @param aToolbarSelector The query selector for the page toolbar.
* @param aGridSelector The query selector for the grid.
*/
init: function Page_init(aToolbarSelector, aGridSelector) {
gToolbar.init(aToolbarSelector);
this._gridSelector = aGridSelector;
init: function Page_init() {
// Add ourselves to the list of pages to receive notifications.
gAllPages.register(this);
// Listen for 'unload' to unregister this page.
function unload() { gAllPages.unregister(this); }
addEventListener("unload", unload.bind(this), false);
addEventListener("unload", this, false);
// Listen for toggle button clicks.
let button = document.getElementById("newtab-toggle");
button.addEventListener("click", this, false);
// Check if the new tab feature is enabled.
if (gAllPages.enabled)
let enabled = gAllPages.enabled;
if (enabled)
this._init();
else
this._updateAttributes(false);
this._updateAttributes(enabled);
},
/**
@ -48,25 +47,9 @@ let gPage = {
* Updates the whole page and the grid when the storage has changed.
*/
update: function Page_update() {
this.updateModifiedFlag();
gGrid.refresh();
},
/**
* Checks if the page is modified and sets the CSS class accordingly
*/
updateModifiedFlag: function Page_updateModifiedFlag() {
let node = document.getElementById("toolbar-button-reset");
let modified = this._isModified();
if (modified)
node.setAttribute("modified", "true");
else
node.removeAttribute("modified");
this._updateTabIndices(gAllPages.enabled, modified);
},
/**
* Internally initializes the page. This runs only when/if the feature
* is/gets enabled.
@ -78,29 +61,27 @@ let gPage = {
this._initialized = true;
gLinks.populateCache(function () {
// Check if the grid is modified.
this.updateModifiedFlag();
// Initialize and render the grid.
gGrid.init(this._gridSelector);
gGrid.init();
// Initialize the drop target shim.
gDropTargetShim.init();
#ifdef XP_MACOSX
// Workaround to prevent a delay on MacOSX due to a slow drop animation.
document.addEventListener("dragover", this.onDragOver, false);
document.addEventListener("drop", this.onDrop, false);
document.addEventListener("dragover", this, false);
document.addEventListener("drop", this, false);
#endif
}.bind(this));
},
/**
* Updates the 'page-disabled' attributes of the respective DOM nodes.
* @param aValue Whether to set or remove attributes.
* @param aValue Whether the New Tab Page is enabled or not.
*/
_updateAttributes: function Page_updateAttributes(aValue) {
let nodes = document.querySelectorAll("#grid, #scrollbox, #toolbar, .toolbar-button");
let selector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid";
let nodes = document.querySelectorAll(selector);
// Set the nodes' states.
for (let i = 0; i < nodes.length; i++) {
@ -111,64 +92,32 @@ let gPage = {
node.setAttribute("page-disabled", "true");
}
this._updateTabIndices(aValue, this._isModified());
// Update the toggle button's title.
let toggle = document.getElementById("newtab-toggle");
toggle.setAttribute("title", newTabString(aValue ? "hide" : "show"));
},
/**
* Checks whether the page is modified.
* @return Whether the page is modified or not.
* Handles all page events.
*/
_isModified: function Page_isModified() {
// The page is considered modified only if sites have been removed.
return !gBlockedLinks.isEmpty();
},
/**
* Updates the tab indices of focusable elements.
* @param aEnabled Whether the page is currently enabled.
* @param aModified Whether the page is currently modified.
*/
_updateTabIndices: function Page_updateTabIndices(aEnabled, aModified) {
function setFocusable(aNode, aFocusable) {
if (aFocusable)
aNode.removeAttribute("tabindex");
else
aNode.setAttribute("tabindex", "-1");
}
// Sites and the 'hide' button are always focusable when the grid is shown.
let nodes = document.querySelectorAll(".site, #toolbar-button-hide");
for (let i = 0; i < nodes.length; i++)
setFocusable(nodes[i], aEnabled);
// The 'show' button is focusable when the grid is hidden.
let btnShow = document.getElementById("toolbar-button-show");
setFocusable(btnShow, !aEnabled);
// The 'reset' button is focusable when the grid is shown and modified.
let btnReset = document.getElementById("toolbar-button-reset");
setFocusable(btnReset, aEnabled && aModified);
},
/**
* Handles the 'dragover' event. Workaround to prevent a delay on MacOSX
* due to a slow drop animation.
* @param aEvent The 'dragover' event.
*/
onDragOver: function Page_onDragOver(aEvent) {
if (gDrag.isValid(aEvent) && gDrag.draggedSite)
aEvent.preventDefault();
},
/**
* Handles the 'drop' event. Workaround to prevent a delay on MacOSX due to
* a slow drop animation.
* @param aEvent The 'drop' event.
*/
onDrop: function Page_onDrop(aEvent) {
if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
aEvent.preventDefault();
aEvent.stopPropagation();
handleEvent: function Page_handleEvent(aEvent) {
switch (aEvent.type) {
case "unload":
gAllPages.unregister(this);
break;
case "click":
gAllPages.enabled = !gAllPages.enabled;
break;
case "dragover":
if (gDrag.isValid(aEvent) && gDrag.draggedSite)
aEvent.preventDefault();
break;
case "drop":
if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
aEvent.preventDefault();
aEvent.stopPropagation();
}
break;
}
}
};

View File

@ -82,12 +82,16 @@ Site.prototype = {
/**
* Blocks the site (removes it from the grid) and calls the given callback
* when done.
* @param aCallback The callback to be called when finished.
* @param aCallback The function to be called when finished.
*/
block: function Site_block(aCallback) {
gBlockedLinks.block(this._link);
gUpdater.updateGrid(aCallback);
gPage.updateModifiedFlag();
if (gBlockedLinks.isBlocked(this._link)) {
if (aCallback)
aCallback();
} else {
gBlockedLinks.block(this._link);
gUpdater.updateGrid(aCallback);
}
},
/**
@ -105,14 +109,14 @@ Site.prototype = {
* @param aPinned Whether this site is now pinned or unpinned.
*/
_updateAttributes: function (aPinned) {
let buttonPin = this._querySelector(".strip-button-pin");
let control = this._querySelector(".newtab-control-pin");
if (aPinned) {
this.node.setAttribute("pinned", true);
buttonPin.setAttribute("title", newTabString("unpin"));
control.setAttribute("pinned", true);
control.setAttribute("title", newTabString("unpin"));
} else {
this.node.removeAttribute("pinned");
buttonPin.setAttribute("title", newTabString("pin"));
control.removeAttribute("pinned");
control.setAttribute("title", newTabString("pin"));
}
},
@ -121,32 +125,17 @@ Site.prototype = {
*/
_render: function Site_render() {
let title = this.title || this.url;
this.node.setAttribute("title", title);
this.node.setAttribute("href", this.url);
this._querySelector(".site-title").textContent = title;
let link = this._querySelector(".newtab-link");
link.setAttribute("title", title);
link.setAttribute("href", this.url);
this._querySelector(".newtab-title").textContent = title;
if (this.isPinned())
this._updateAttributes(true);
this._renderThumbnail();
},
/**
* Renders the site's thumbnail.
*/
_renderThumbnail: function Site_renderThumbnail() {
let img = this._querySelector(".site-img")
img.setAttribute("alt", this.title || this.url);
img.setAttribute("loading", "true");
// Wait until the image has loaded.
img.addEventListener("load", function onLoad() {
img.removeEventListener("load", onLoad, false);
img.removeAttribute("loading");
}, false);
// Set the thumbnail url.
img.setAttribute("src", PageThumbs.getThumbnailURL(this.url));
let thumbnailURL = PageThumbs.getThumbnailURL(this.url);
let thumbnail = this._querySelector(".newtab-thumbnail");
thumbnail.style.backgroundImage = "url(" + thumbnailURL + ")";
},
/**
@ -154,56 +143,37 @@ Site.prototype = {
*/
_addEventHandlers: function Site_addEventHandlers() {
// Register drag-and-drop event handlers.
["DragStart", /*"Drag",*/ "DragEnd"].forEach(function (aType) {
let method = "_on" + aType;
this[method] = this[method].bind(this);
this._node.addEventListener(aType.toLowerCase(), this[method], false);
}, this);
this._node.addEventListener("dragstart", this, false);
this._node.addEventListener("dragend", this, false);
let self = this;
function pin(aEvent) {
if (aEvent)
aEvent.preventDefault();
if (self.isPinned())
self.unpin();
else
self.pin();
}
function block(aEvent) {
if (aEvent)
aEvent.preventDefault();
self.block();
}
this._querySelector(".strip-button-pin").addEventListener("click", pin, false);
this._querySelector(".strip-button-block").addEventListener("click", block, false);
let controls = this.node.querySelectorAll(".newtab-control");
for (let i = 0; i < controls.length; i++)
controls[i].addEventListener("click", this, false);
},
/**
* Event handler for the 'dragstart' event.
* @param aEvent The drag event.
* Handles all site events.
*/
_onDragStart: function Site_onDragStart(aEvent) {
gDrag.start(this, aEvent);
},
/**
* Event handler for the 'drag' event.
* @param aEvent The drag event.
*/
_onDrag: function Site_onDrag(aEvent) {
gDrag.drag(this, aEvent);
},
/**
* Event handler for the 'dragend' event.
* @param aEvent The drag event.
*/
_onDragEnd: function Site_onDragEnd(aEvent) {
gDrag.end(this, aEvent);
handleEvent: function Site_handleEvent(aEvent) {
switch (aEvent.type) {
case "click":
aEvent.preventDefault();
if (aEvent.target.classList.contains("newtab-control-block"))
this.block();
else if (this.isPinned())
this.unpin();
else
this.pin();
break;
case "dragstart":
gDrag.start(this, aEvent);
break;
case "drag":
gDrag.drag(this, aEvent);
break;
case "dragend":
gDrag.end(this, aEvent);
break;
}
}
};

View File

@ -1,87 +0,0 @@
#ifdef 0
/* 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/. */
#endif
/**
* This singleton represents the page's toolbar that allows to enable/disable
* the 'New Tab Page' feature and to reset the whole page.
*/
let gToolbar = {
/**
* Initializes the toolbar.
* @param aSelector The query selector of the toolbar.
*/
init: function Toolbar_init(aSelector) {
this._node = document.querySelector(aSelector);
let buttons = this._node.querySelectorAll("input");
// Listen for 'click' events on the toolbar buttons.
["show", "hide", "reset"].forEach(function (aType, aIndex) {
let self = this;
let button = buttons[aIndex];
let handler = function () self[aType]();
button.addEventListener("click", handler, false);
#ifdef XP_MACOSX
// Per default buttons lose focus after being clicked on Mac OS X.
// So when the URL bar has focus and a toolbar button is clicked the
// URL bar regains focus and the history pops up. We need to prevent
// that by explicitly removing its focus.
button.addEventListener("mousedown", function () {
window.focus();
}, false);
#endif
}, this);
},
/**
* Enables the 'New Tab Page' feature.
*/
show: function Toolbar_show() {
this._passButtonFocus("show", "hide");
gAllPages.enabled = true;
},
/**
* Disables the 'New Tab Page' feature.
*/
hide: function Toolbar_hide() {
this._passButtonFocus("hide", "show");
gAllPages.enabled = false;
},
/**
* Resets the whole page and forces it to re-render its content.
* @param aCallback The callback to call when the page has been reset.
*/
reset: function Toolbar_reset(aCallback) {
this._passButtonFocus("reset", "hide");
let node = gGrid.node;
// animate the page reset
gTransformation.fadeNodeOut(node, function () {
NewTabUtils.reset();
gLinks.populateCache(function () {
gAllPages.update();
// Without the setTimeout() we have a strange flicker.
setTimeout(function () gTransformation.fadeNodeIn(node, aCallback));
}, true);
});
},
/**
* Passes the focus from the current button to the next.
* @param aCurrent The button that currently has focus.
* @param aNext The button that is focused next.
*/
_passButtonFocus: function Toolbar_passButtonFocus(aCurrent, aNext) {
if (document.querySelector("#toolbar-button-" + aCurrent + ":-moz-focusring"))
document.getElementById("toolbar-button-" + aNext).focus();
}
};

View File

@ -10,6 +10,24 @@
* convenience methods to work with a site's DOM node.
*/
let gTransformation = {
/**
* Returns the width of the left and top border of a cell. We need to take it
* into account when measuring and comparing site and cell positions.
*/
get _cellBorderWidths() {
let cstyle = window.getComputedStyle(gGrid.cells[0].node, null);
let widths = {
left: parseInt(cstyle.getPropertyValue("border-left-width")),
top: parseInt(cstyle.getPropertyValue("border-top-width"))
};
// Cache this value, overwrite the getter.
Object.defineProperty(this, "_cellBorderWidths",
{value: widths, enumerable: true});
return widths;
},
/**
* Gets a DOM node's position.
* @param aNode The DOM node.
@ -80,6 +98,14 @@ let gTransformation = {
* @param aSite The site to freeze.
*/
freezeSitePosition: function Transformation_freezeSitePosition(aSite) {
if (this._isFrozen(aSite))
return;
let style = aSite.node.style;
let comp = getComputedStyle(aSite.node, null);
style.width = comp.getPropertyValue("width")
style.height = comp.getPropertyValue("height");
aSite.node.setAttribute("frozen", "true");
this.setSitePosition(aSite, this.getNodePosition(aSite.node));
},
@ -89,8 +115,11 @@ let gTransformation = {
* @param aSite The site to unfreeze.
*/
unfreezeSitePosition: function Transformation_unfreezeSitePosition(aSite) {
if (!this._isFrozen(aSite))
return;
let style = aSite.node.style;
style.left = style.top = "";
style.left = style.top = style.width = style.height = "";
aSite.node.removeAttribute("frozen");
},
@ -117,8 +146,13 @@ let gTransformation = {
callback();
}
// We need to take the width of a cell's border into account.
targetPosition.left += this._cellBorderWidths.left;
targetPosition.top += this._cellBorderWidths.top;
// Nothing to do here if the positions already match.
if (currentPosition.equals(targetPosition)) {
if (currentPosition.left == targetPosition.left &&
currentPosition.top == targetPosition.top) {
finish();
} else {
this.setSitePosition(aSite, targetPosition);
@ -222,5 +256,14 @@ let gTransformation = {
_moveSite: function Transformation_moveSite(aSite, aIndex, aOptions) {
this.freezeSitePosition(aSite);
this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
},
/**
* Checks whether a site is currently frozen.
* @param aSite The site to check.
* @return Whether the given site is frozen.
*/
_isFrozen: function Transformation_isFrozen(aSite) {
return aSite.node.hasAttribute("frozen");
}
};

View File

@ -2,41 +2,33 @@ function test() {
waitForExplicitFinish();
var pageInfo;
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function () {
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
pageInfo = BrowserPageInfo();
gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
Services.obs.addObserver(observer, "page-info-dialog-loaded", false);
pageInfo = BrowserPageInfo();
}, true);
content.location =
"https://example.com/browser/browser/base/content/test/feed_tab.html";
function observer(win, topic, data) {
if (topic != "page-info-dialog-loaded")
return;
Services.obs.removeObserver(observer, topic);
Services.obs.removeObserver(observer, "page-info-dialog-loaded");
handlePageInfo();
}
function $(aId) { return pageInfo.document.getElementById(aId) };
function handlePageInfo() {
var feedTab = $("feedTab");
var feedListbox = $("feedListbox");
ok(feedListbox, "Feed list is null (feeds tab is broken)");
ok(pageInfo.document.getElementById("feedTab"), "Feed tab");
let feedListbox = pageInfo.document.getElementById("feedListbox");
ok(feedListbox, "Feed list");
var feedRowsNum = feedListbox.getRowCount();
ok(feedRowsNum == 3, "Number of feeds listed: " +
feedRowsNum + ", should be 3");
is(feedRowsNum, 3, "Number of feeds listed");
for (var i = 0; i < feedRowsNum; i++) {
let feedItem = feedListbox.getItemAtIndex(i);
ok(feedItem.getAttribute("name") == (i+1),
"Name given: " + feedItem.getAttribute("name") + ", should be " + (i+1));
is(feedItem.getAttribute("name"), i + 1, "Feed name");
}
pageInfo.close();

View File

@ -23,6 +23,8 @@ _BROWSER_FILES = \
browser_newtab_bug722273.js \
browser_newtab_bug723102.js \
browser_newtab_bug723121.js \
browser_newtab_bug725996.js \
browser_newtab_bug734043.js \
head.js \
$(NULL)

View File

@ -12,6 +12,7 @@ function runTests() {
yield addNewTabPageTab();
gBrowser.removeTab(firstTab);
cw.gToolbar.hide();
ok(NewTabUtils.allPages.enabled, true, "page is enabled");
NewTabUtils.allPages.enabled = false;
ok(cw.gGrid.node.hasAttribute("page-disabled"), "page is disabled");
}

View File

@ -10,15 +10,19 @@ function runTests() {
let cell = cells[0].node;
let site = cells[0].site.node;
let link = site.querySelector(".newtab-link");
sendDragEvent(site, "dragstart");
sendDragEvent(link, "dragstart");
checkGridLocked(true, "grid is now locked");
sendDragEvent(site, "dragend");
sendDragEvent(link, "dragend");
checkGridLocked(false, "grid isn't locked anymore");
sendDragEvent(cell, "dragstart");
checkGridLocked(false, "grid isn't locked - dragstart was ignored");
sendDragEvent(site, "dragstart");
checkGridLocked(false, "grid isn't locked - dragstart was ignored");
}
function checkGridLocked(aLocked, aMessage) {

View File

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function runTests() {
setLinks("0,1,2,3,4,5,6,7,8");
setPinnedLinks("");
yield addNewTabPageTab();
checkGrid("0,1,2,3,4,5,6,7,8");
let cell = cells[0].node;
sendDropEvent(cell, "about:blank#99\nblank");
is(NewTabUtils.pinnedLinks.links[0].url, "about:blank#99",
"first cell is pinned and contains the dropped site");
yield whenPagesUpdated();
checkGrid("99p,0,1,2,3,4,5,6,7");
sendDropEvent(cell, "");
is(NewTabUtils.pinnedLinks.links[0].url, "about:blank#99",
"first cell is still pinned with the site we dropped before");
}
function sendDropEvent(aNode, aData) {
let ifaceReq = cw.QueryInterface(Ci.nsIInterfaceRequestor);
let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
let dataTransfer = {
mozUserCancelled: false,
setData: function () null,
setDragImage: function () null,
getData: function () aData,
types: {
contains: function (aType) aType == "text/x-moz-url"
},
mozGetDataAt: function (aType, aIndex) {
if (aIndex || aType != "text/x-moz-url")
return null;
return aData;
},
};
let event = cw.document.createEvent("DragEvents");
event.initDragEvent("drop", true, true, cw, 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
windowUtils.dispatchDOMEventViaPresShell(aNode, event, true);
}

View File

@ -0,0 +1,30 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function runTests() {
// TODO Bug 735166 - Intermittent timeout in browser_newtab_bug734043.js
return;
setLinks("0,1,2,3,4,5,6,7,8");
setPinnedLinks("");
yield addNewTabPageTab();
let receivedError = false;
let block = cw.document.querySelector(".newtab-control-block");
function onError() {
receivedError = true;
}
cw.addEventListener("error", onError);
for (let i = 0; i < 3; i++) {
EventUtils.synthesizeMouseAtCenter(block, {}, cw);
yield executeSoon(TestRunner.next);
}
yield whenPagesUpdated();
ok(!receivedError, "we got here without any errors");
cw.removeEventListener("error", onError);
}

View File

@ -15,7 +15,7 @@ function runTests() {
ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
cw.gToolbar.hide();
NewTabUtils.allPages.enabled = false;
ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
let oldGridNode = cw.gGrid.node;
@ -28,7 +28,7 @@ function runTests() {
// check that no sites have been rendered
is(0, cw.document.querySelectorAll(".site").length, "no sites have been rendered");
cw.gToolbar.show();
NewTabUtils.allPages.enabled = true;
ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
ok(!oldGridNode.hasAttribute("page-disabled"), "old page is not disabled");
}

View File

@ -5,6 +5,9 @@
* These tests make sure that resetting the 'New Tage Page' works as expected.
*/
function runTests() {
// Disabled until bug 716543 is fixed.
return;
// create a new tab page and check its modified state after blocking a site
setLinks("0,1,2,3,4,5,6,7,8");
setPinnedLinks("");

View File

@ -8,6 +8,9 @@
* state.
*/
function runTests() {
// Disabled until bug 716543 is fixed.
return;
setLinks("0,1,2,3,4,5,6,7,8,9");
setPinnedLinks(",1");

View File

@ -188,7 +188,7 @@ function checkGrid(aSitesPattern, aSites) {
let shouldBePinned = /p$/.test(id);
let cellContainsPinned = site.isPinned();
let cssClassPinned = site.node && site.node.hasAttribute("pinned");
let cssClassPinned = site.node && site.node.querySelector(".newtab-control-pin").hasAttribute("pinned");
// Check if the site should be and is pinned.
if (shouldBePinned) {
@ -265,3 +265,20 @@ function simulateDrop(aDropTarget, aDragSource) {
if (aDragSource)
cw.gDrag.end(aDragSource.site);
}
/**
* Resumes testing when all pages have been updated.
*/
function whenPagesUpdated() {
let page = {
update: function () {
NewTabUtils.allPages.unregister(this);
executeSoon(TestRunner.next);
}
};
NewTabUtils.allPages.register(page);
registerCleanupFunction(function () {
NewTabUtils.allPages.unregister(page);
});
}

View File

@ -44,7 +44,23 @@ Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "BROWSER_NEW_TAB_URL", function () {
return Services.prefs.getCharPref("browser.newtab.url") || "about:blank";
const PREF = "browser.newtab.url";
function getNewTabPageURL() {
return Services.prefs.getCharPref(PREF) || "about:blank";
}
function update() {
BROWSER_NEW_TAB_URL = getNewTabPageURL();
}
Services.prefs.addObserver(PREF, update, false);
addEventListener("unload", function onUnload() {
removeEventListener("unload", onUnload);
Services.prefs.removeObserver(PREF, update);
});
return getNewTabPageURL();
});
var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";

View File

@ -98,6 +98,12 @@ source-package::
upload::
@$(MAKE) -C browser/installer upload
source-upload::
@$(MAKE) -C browser/installer source-upload
hg-bundle::
@$(MAKE) -C browser/installer hg-bundle
l10n-check::
@$(MAKE) -C browser/locales l10n-check

View File

@ -67,6 +67,9 @@ XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
XPCOMUtils.defineLazyModuleGetter(this, "KeywordURLResetPrompter",
"resource:///modules/KeywordURLResetPrompter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "webappsUI",
"resource://gre/modules/webappsUI.jsm");
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
@ -346,6 +349,7 @@ BrowserGlue.prototype = {
if (this._isPlacesShutdownObserver)
os.removeObserver(this, "places-shutdown");
os.removeObserver(this, "defaultURIFixup-using-keyword-pref");
webappsUI.uninit();
},
_onAppDefaults: function BG__onAppDefaults() {
@ -370,6 +374,9 @@ BrowserGlue.prototype = {
// handle any UI migration
this._migrateUI();
// Initialize webapps UI
webappsUI.init();
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
},
@ -405,7 +412,6 @@ BrowserGlue.prototype = {
#endif
}
// Show update notification, if needed.
if (Services.prefs.prefHasUserValue("app.update.postupdate"))
this._showUpdateNotification();

View File

@ -376,31 +376,26 @@ PlacesViewBase.prototype = {
_setLivemarkStatusMenuItem:
function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
let statusMenuitem = aPopup._statusMenuitem;
let stringId = "";
if (aStatus == Ci.mozILivemark.STATUS_LOADING)
stringId = "bookmarksLivemarkLoading";
else if (aStatus == Ci.mozILivemark.STATUS_FAILED)
stringId = "bookmarksLivemarkFailed";
if (stringId && !statusMenuitem) {
if (!statusMenuitem) {
// Create the status menuitem and cache it in the popup object.
statusMenuitem = document.createElement("menuitem");
statusMenuitem.setAttribute("livemarkStatus", stringId);
statusMenuitem.className = "livemarkstatus-menuitem";
statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
statusMenuitem.setAttribute("disabled", true);
aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling);
aPopup._statusMenuitem = statusMenuitem;
}
else if (stringId &&
statusMenuitem.getAttribute("livemarkStatus") != stringId) {
if (aStatus == Ci.mozILivemark.STATUS_LOADING ||
aStatus == Ci.mozILivemark.STATUS_FAILED) {
// Status has changed, update the cached status menuitem.
let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ?
"bookmarksLivemarkLoading" : "bookmarksLivemarkFailed";
statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
if (aPopup._startMarker.nextSibling != statusMenuitem)
aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling);
}
else if (!stringId && statusMenuitem) {
else {
// The livemark has finished loading.
aPopup.removeChild(aPopup._statusMenuitem);
aPopup._statusMenuitem = null;
}
},
@ -892,6 +887,14 @@ function PlacesToolbar(aPlace) {
this._addEventListeners(this._rootElt, ["overflow", "underflow"], true);
this._addEventListeners(window, ["resize", "unload"], false);
// If personal-bookmarks has been dragged to the tabs toolbar,
// we have to track addition and removals of tabs, to properly
// recalculate the available space for bookmarks.
// TODO (bug 734730): Use a performant mutation listener when available.
if (this._viewElt.parentNode.parentNode == document.getElementById("TabsToolbar")) {
this._addEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
}
PlacesViewBase.call(this, aPlace);
Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS")
@ -918,6 +921,7 @@ PlacesToolbar.prototype = {
true);
this._removeEventListeners(this._rootElt, ["overflow", "underflow"], true);
this._removeEventListeners(window, ["resize", "unload"], false);
this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
PlacesViewBase.prototype.uninit.apply(this, arguments);
},
@ -1017,9 +1021,10 @@ PlacesToolbar.prototype = {
_updateChevronPopupNodesVisibility:
function PT__updateChevronPopupNodesVisibility() {
for (let i = 0; i < this._chevronPopup.childNodes.length; i++) {
this._chevronPopup.childNodes[i].hidden =
this._rootElt.childNodes[i].style.visibility != "hidden";
for (let i = 0, node = this._chevronPopup._startMarker.nextSibling;
node != this._chevronPopup._endMarker;
i++, node = node.nextSibling) {
node.hidden = this._rootElt.childNodes[i].style.visibility != "hidden";
}
},
@ -1071,7 +1076,11 @@ PlacesToolbar.prototype = {
if (aEvent.detail == 0)
return;
this.updateChevron();
this._chevron.collapsed = true;
break;
case "TabOpen":
case "TabClose":
this.updateChevron();
break;
case "dragstart":

View File

@ -18,7 +18,7 @@ const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
* Hint: This is the default value because the 'New Tab Page' is the only
* client for now.
*/
const THUMBNAIL_WIDTH = 201;
const THUMBNAIL_WIDTH = 400;
/**
* The default height for page thumbnails.
@ -26,7 +26,7 @@ const THUMBNAIL_WIDTH = 201;
* Hint: This is the default value because the 'New Tab Page' is the only
* client for now.
*/
const THUMBNAIL_HEIGHT = 127;
const THUMBNAIL_HEIGHT = 225;
/**
* The default background color for page thumbnails.

View File

@ -18,3 +18,6 @@ ac_add_options --with-ccache=/usr/bin/ccache
# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
ac_add_options --enable-warnings-as-errors
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -27,3 +27,6 @@ ac_add_options --with-ccache=/usr/bin/ccache
# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
ac_add_options --enable-warnings-as-errors
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -21,3 +21,6 @@ ac_add_options --enable-warnings-as-errors
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j4"
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -15,3 +15,6 @@ mk_add_options MOZ_MAKE_FLAGS="-j4"
# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
ac_add_options --enable-warnings-as-errors
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -27,3 +27,6 @@ ac_add_options --with-ccache=/usr/bin/ccache
# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
ac_add_options --enable-warnings-as-errors
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -21,3 +21,6 @@ ac_add_options --enable-warnings-as-errors
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j4"
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -22,3 +22,6 @@ ac_add_options --with-macbundlename-prefix=Firefox
# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
ac_add_options --enable-warnings-as-errors
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -17,3 +17,6 @@ ac_add_options --enable-warnings-as-errors
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-j4"
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -10,3 +10,6 @@ mk_add_options MOZ_MAKE_FLAGS="-j4"
export MOZILLA_OFFICIAL=1
ac_add_options --with-macbundlename-prefix=Firefox
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -15,3 +15,6 @@ ac_add_options --with-macbundlename-prefix=Firefox
# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
ac_add_options --enable-warnings-as-errors
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -8,3 +8,6 @@ export MOZILLA_OFFICIAL=1
mk_add_options MOZ_MAKE_FLAGS=-j1
. $topsrcdir/browser/config/mozconfigs/win32/vs2010-mozconfig
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -17,3 +17,6 @@ export MOZ_TELEMETRY_REPORTING=1
mk_add_options MOZ_MAKE_FLAGS=-j1
. $topsrcdir/browser/config/mozconfigs/win32/vs2010-mozconfig
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -13,3 +13,6 @@ export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
. $topsrcdir/browser/config/mozconfigs/win32/vs2010-mozconfig
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1

View File

@ -9,3 +9,8 @@ ac_add_options --enable-signmar
export MOZILLA_OFFICIAL=1
mk_add_options MOZ_MAKE_FLAGS=-j1
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1
. $topsrcdir/browser/config/mozconfigs/win64/vs2010-mozconfig

View File

@ -18,3 +18,8 @@ export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
mk_add_options MOZ_MAKE_FLAGS=-j1
# Package js shell.
export MOZ_PACKAGE_JSSHELL=1
. $topsrcdir/browser/config/mozconfigs/win64/vs2010-mozconfig

View File

@ -0,0 +1,16 @@
export INCLUDE=/c/tools/msvs10/vc/include:/c/tools/msvs10/vc/atlmfc/include:/c/tools/sdks/v7.0/include:/c/tools/sdks/v7.0/include/atl:/c/tools/sdks/dx10/include
export LIBPATH=/c/tools/msvs10/vc/lib/amd64:/c/tools/msvs10/vc/atlmfc/lib/amd64
export LIB=/c/tools/msvs10/vc/lib/amd64:/c/tools/msvs10/vc/atlmfc/lib/amd64:/c/tools/sdks/v7.0/lib/x64:/c/tools/sdks/dx10/lib/x64
export PATH="/c/tools/msvs10/Common7/IDE:/c/tools/msvs10/VC/BIN/amd64:/c/tools/msvs10/VC/BIN/x86_amd64:/c/tools/msvs10/VC/BIN:/c/tools/msvs10/Common7/Tools:/c/tools/msvs10/VC/VCPackages:${PATH}"
export WIN32_REDIST_DIR=/c/tools/msvs10/VC/redist/x64/Microsoft.VC100.CRT
# Use 32bit linker for PGO crash bug.
# https://connect.microsoft.com/VisualStudio/feedback/details/686117/
export LD=/c/tools/msvs10/VC/BIN/x86_amd64/link.exe
mk_add_options "export LIB=$LIB"
mk_add_options "export LIBPATH=$LIBPATH"
mk_add_options "export PATH=$PATH"
mk_add_options "export INCLUDE=$INCLUDE"
mk_add_options "export WIN32_REDIST_DIR=$WIN32_REDIST_DIR"

View File

@ -1 +1 @@
13.0a1
14.0a1

View File

@ -71,10 +71,11 @@ _BROWSER_TEST_FILES = \
browser_dbg_script-switching.js \
browser_dbg_pause-resume.js \
browser_dbg_update-editor-mode.js \
browser_dbg_select-line.js \
$(warning browser_dbg_select-line.js temporarily disabled due to oranges, see bug 726609) \
browser_dbg_clean-exit.js \
browser_dbg_bug723069_editor-breakpoints.js \
browser_dbg_bug731394_editor-contextmenu.js \
browser_dbg_displayName.js \
head.js \
$(NULL)
@ -89,6 +90,7 @@ _BROWSER_TEST_PAGES = \
browser_dbg_frame-parameters.html \
browser_dbg_update-editor-mode.html \
test-editor-mode \
browser_dbg_displayName.html \
$(NULL)
libs:: $(_BROWSER_TEST_FILES)

View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html>
<head><title>Browser Debugger Test Tab</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript">
var a = function() {
return function() {
debugger;
}
}
var anon = a();
anon.displayName = "anonFunc";
function evalCall() {
eval("anon();");
}
</script>
</head>
<body></body>
</html>

View File

@ -0,0 +1,53 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
const TAB_URL = EXAMPLE_URL + "browser_dbg_displayName.html";
function test() {
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.debuggerWindow;
testAnonCall();
});
}
function testAnonCall() {
gPane.activeThread.addOneTimeListener("framesadded", function() {
Services.tm.currentThread.dispatch({ run: function() {
let frames = gDebugger.DebuggerView.Stackframes._frames;
is(gDebugger.StackFrames.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
is(frames.querySelectorAll(".dbg-stackframe").length, 3,
"Should have three frames.");
is(frames.querySelector("#stackframe-0 .dbg-stackframe-name").textContent,
"anonFunc", "Frame name should be anonFunc");
resumeAndFinish();
}}, 0);
});
gDebuggee.evalCall();
}
function resumeAndFinish() {
gDebugger.StackFrames.activeThread.resume(function() {
removeTab(gTab);
gPane = null;
gDebuggee = null;
finish();
});
}

View File

@ -309,7 +309,7 @@ TreePanel.prototype = {
/**
* Handle double-click events in the html tree panel.
* (double-clicking an attribute value allows it to be edited)
* Double-clicking an attribute name or value allows it to be edited.
* @param aEvent
* The mouse event.
*/
@ -322,19 +322,33 @@ TreePanel.prototype = {
let target = aEvent.target;
if (!this.hasClass(target, "editable")) {
return;
}
let repObj = this.getRepObject(target);
if (this.hasClass(target, "nodeValue")) {
let repObj = this.getRepObject(target);
let attrName = target.getAttribute("data-attributeName");
let attrVal = target.innerHTML;
this.editAttributeValue(target, repObj, attrName, attrVal);
this.editAttribute(target, repObj, attrName, attrVal);
}
if (this.hasClass(target, "nodeName")) {
let attrName = target.innerHTML;
let attrValNode = target.nextSibling.nextSibling; // skip 2 (=)
if (attrValNode)
this.editAttribute(target, repObj, attrName, attrValNode.innerHTML);
}
},
/**
* Starts the editor for an attribute value.
* Starts the editor for an attribute name or value.
* @param aAttrObj
* The DOM object representing the attribute value in the HTML Tree
* The DOM object representing the attribute name or value in the HTML
* Tree.
* @param aRepObj
* The original DOM (target) object being inspected/edited
* @param aAttrName
@ -342,8 +356,8 @@ TreePanel.prototype = {
* @param aAttrVal
* The current value of the attribute being edited
*/
editAttributeValue:
function TP_editAttributeValue(aAttrObj, aRepObj, aAttrName, aAttrVal)
editAttribute:
function TP_editAttribute(aAttrObj, aRepObj, aAttrName, aAttrVal)
{
let editor = this.treeBrowserDocument.getElementById("attribute-editor");
let editorInput =
@ -357,7 +371,8 @@ TreePanel.prototype = {
this.editingContext = {
attrObj: aAttrObj,
repObj: aRepObj,
attrName: aAttrName
attrName: aAttrName,
attrValue: aAttrVal
};
// highlight attribute-value node in tree while editing
@ -367,7 +382,7 @@ TreePanel.prototype = {
this.addClass(editor, "editing");
// offset the editor below the attribute-value node being edited
let editorVeritcalOffset = 2;
let editorVerticalOffset = 2;
// keep the editor comfortably within the bounds of the viewport
let editorViewportBoundary = 5;
@ -384,7 +399,7 @@ TreePanel.prototype = {
// center the editor against the attribute value
((editorDims.width - attrDims.width) / 2);
let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY +
attrDims.height + editorVeritcalOffset;
attrDims.height + editorVerticalOffset;
// but, make sure the editor stays within the visible viewport
editorLeft = Math.max(0, Math.min(
@ -403,8 +418,13 @@ TreePanel.prototype = {
editor.style.top = editorTop + "px";
// set and select the text
editorInput.value = aAttrVal;
editorInput.select();
if (this.hasClass(aAttrObj, "nodeValue")) {
editorInput.value = aAttrVal;
editorInput.select();
} else {
editorInput.value = aAttrName;
editorInput.select();
}
// listen for editor specific events
this.bindEditorEvent(editor, "click", function(aEvent) {
@ -510,15 +530,32 @@ TreePanel.prototype = {
{
let editorInput =
this.treeBrowserDocument.getElementById("attribute-editor-input");
let dirty = false;
// set the new attribute value on the original target DOM element
this.editingContext.repObj.setAttribute(this.editingContext.attrName,
editorInput.value);
if (this.hasClass(this.editingContext.attrObj, "nodeValue")) {
// set the new attribute value on the original target DOM element
this.editingContext.repObj.setAttribute(this.editingContext.attrName,
editorInput.value);
// update the HTML tree attribute value
this.editingContext.attrObj.innerHTML = editorInput.value;
// update the HTML tree attribute value
this.editingContext.attrObj.innerHTML = editorInput.value;
dirty = true;
}
this.IUI.isDirty = true;
if (this.hasClass(this.editingContext.attrObj, "nodeName")) {
// remove the original attribute from the original target DOM element
this.editingContext.repObj.removeAttribute(this.editingContext.attrName);
// set the new attribute value on the original target DOM element
this.editingContext.repObj.setAttribute(editorInput.value,
this.editingContext.attrValue);
// update the HTML tree attribute value
this.editingContext.attrObj.innerHTML = editorInput.value;
dirty = true;
}
this.IUI.isDirty = dirty;
this.IUI.nodeChanged(this.registrationObject);
// event notification

View File

@ -27,6 +27,7 @@
* Paul Rouget <paul@mozilla.com>
* Kyle Simpson <ksimpson@mozilla.com>
* Johan Charlez <johan.charlez@gmail.com>
* Mike Ratcliffe <mratcliffe@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
@ -104,6 +105,15 @@ function InspectorUI(aWindow)
this.toolEvents = {};
this.store = new InspectorStore();
this.INSPECTOR_NOTIFICATIONS = INSPECTOR_NOTIFICATIONS;
// Set the tooltip of the inspect button.
let keysbundle = Services.strings.createBundle(
"chrome://global/locale/keys.properties");
let returnString = keysbundle.GetStringFromName("VK_RETURN");
let tooltip = this.strings.formatStringFromName("inspectButton.tooltiptext",
[returnString], 1);
let button = this.chromeDoc.getElementById("inspector-inspect-toolbutton");
button.setAttribute("tooltiptext", tooltip);
}
InspectorUI.prototype = {
@ -845,9 +855,7 @@ InspectorUI.prototype = {
*/
copyInnerHTML: function IUI_copyInnerHTML()
{
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
clipboard.copyString(this.selection.innerHTML);
clipboardHelper.copyString(this.selection.innerHTML);
},
/**
@ -856,9 +864,7 @@ InspectorUI.prototype = {
*/
copyOuterHTML: function IUI_copyOuterHTML()
{
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
clipboard.copyString(this.selection.outerHTML);
clipboardHelper.copyString(this.selection.outerHTML);
},
/**
@ -926,12 +932,34 @@ InspectorUI.prototype = {
this.ruleView = new CssRuleView(doc, ruleViewStore);
// Add event handlers bound to this.
this.boundRuleViewChanged = this.ruleViewChanged.bind(this);
this.ruleView.element.addEventListener("CssRuleViewChanged",
this.boundRuleViewChanged);
this.cssRuleViewBoundCSSLinkClicked = this.ruleViewCSSLinkClicked.bind(this);
this.ruleView.element.addEventListener("CssRuleViewCSSLinkClicked",
this.cssRuleViewBoundCSSLinkClicked);
this.cssRuleViewBoundMouseDown = this.ruleViewMouseDown.bind(this);
this.ruleView.element.addEventListener("mousedown",
this.cssRuleViewBoundMouseDown);
this.cssRuleViewBoundMouseUp = this.ruleViewMouseUp.bind(this);
this.ruleView.element.addEventListener("mouseup",
this.cssRuleViewBoundMouseUp);
this.cssRuleViewBoundMouseMove = this.ruleViewMouseMove.bind(this);
this.cssRuleViewBoundMenuUpdate = this.ruleViewMenuUpdate.bind(this);
this.cssRuleViewBoundCopy = this.ruleViewCopy.bind(this);
iframe.addEventListener("copy", this.cssRuleViewBoundCopy);
this.cssRuleViewBoundCopyRule = this.ruleViewCopyRule.bind(this);
this.cssRuleViewBoundCopyDeclaration =
this.ruleViewCopyDeclaration.bind(this);
this.cssRuleViewBoundCopyProperty = this.ruleViewCopyProperty.bind(this);
this.cssRuleViewBoundCopyPropertyValue =
this.ruleViewCopyPropertyValue.bind(this);
// Add the rule view's context menu.
this.ruleViewAddContextMenu();
doc.documentElement.appendChild(this.ruleView.element);
this.ruleView.highlight(this.selection);
@ -1002,19 +1030,356 @@ InspectorUI.prototype = {
}
},
/**
* This is the mousedown handler for the rule view. We use it to track whether
* text is currently getting selected.
* .
* @param aEvent The event object
*/
ruleViewMouseDown: function IUI_ruleViewMouseDown(aEvent)
{
this.ruleView.element.addEventListener("mousemove",
this.cssRuleViewBoundMouseMove);
},
/**
* This is the mouseup handler for the rule view. We use it to track whether
* text is currently getting selected.
* .
* @param aEvent The event object
*/
ruleViewMouseUp: function IUI_ruleViewMouseUp(aEvent)
{
this.ruleView.element.removeEventListener("mousemove",
this.cssRuleViewBoundMouseMove);
this.ruleView._selectionMode = false;
},
/**
* This is the mousemove handler for the rule view. We use it to track whether
* text is currently getting selected.
* .
* @param aEvent The event object
*/
ruleViewMouseMove: function IUI_ruleViewMouseMove(aEvent)
{
this.ruleView._selectionMode = true;
},
/**
* Add a context menu to the rule view.
*/
ruleViewAddContextMenu: function IUI_ruleViewAddContextMenu()
{
let iframe = this.getToolIframe(this.ruleViewObject);
let popupSet = this.chromeDoc.getElementById("mainPopupSet");
let menu = this.chromeDoc.createElement("menupopup");
menu.addEventListener("popupshowing", this.cssRuleViewBoundMenuUpdate);
menu.id = "rule-view-context-menu";
// Copy selection
let label = styleInspectorStrings
.GetStringFromName("rule.contextmenu.copyselection");
let accessKey = styleInspectorStrings
.GetStringFromName("rule.contextmenu.copyselection.accesskey");
let item = this.chromeDoc.createElement("menuitem");
item.id = "rule-view-copy";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.cssRuleViewBoundCopy);
menu.appendChild(item);
// Copy rule
label = styleInspectorStrings.
GetStringFromName("rule.contextmenu.copyrule");
accessKey = styleInspectorStrings.
GetStringFromName("rule.contextmenu.copyrule.accesskey");
item = this.chromeDoc.createElement("menuitem");
item.id = "rule-view-copy-rule";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.cssRuleViewBoundCopyRule);
menu.appendChild(item);
// Copy declaration
label = styleInspectorStrings.
GetStringFromName("rule.contextmenu.copydeclaration");
accessKey = styleInspectorStrings.
GetStringFromName("rule.contextmenu.copydeclaration.accesskey");
item = this.chromeDoc.createElement("menuitem");
item.id = "rule-view-copy-declaration";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.cssRuleViewBoundCopyDeclaration);
menu.appendChild(item);
// Copy property name
label = styleInspectorStrings.
GetStringFromName("rule.contextmenu.copyproperty");
accessKey = styleInspectorStrings.
GetStringFromName("rule.contextmenu.copyproperty.accesskey");
item = this.chromeDoc.createElement("menuitem");
item.id = "rule-view-copy-property";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.cssRuleViewBoundCopyProperty);
menu.appendChild(item);
// Copy property value
label = styleInspectorStrings.
GetStringFromName("rule.contextmenu.copypropertyvalue");
accessKey = styleInspectorStrings.
GetStringFromName("rule.contextmenu.copypropertyvalue.accesskey");
item = this.chromeDoc.createElement("menuitem");
item.id = "rule-view-copy-property-value";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.cssRuleViewBoundCopyPropertyValue);
menu.appendChild(item);
popupSet.appendChild(menu);
iframe.setAttribute("context", menu.id);
},
/**
* Update the rule view's context menu by disabling irrelevant menuitems and
* enabling relevant ones.
*
* @param aEvent The event object
*/
ruleViewMenuUpdate: function IUI_ruleViewMenuUpdate(aEvent)
{
let iframe = this.getToolIframe(this.ruleViewObject);
let win = iframe.contentWindow;
// Copy selection.
let disable = win.getSelection().isCollapsed;
let menuitem = this.chromeDoc.getElementById("rule-view-copy");
menuitem.disabled = disable;
// Copy property, copy property name & copy property value.
let node = this.chromeDoc.popupNode;
if (!node.classList.contains("ruleview-property") &&
!node.classList.contains("ruleview-computed")) {
while (node = node.parentElement) {
if (node.classList.contains("ruleview-property") ||
node.classList.contains("ruleview-computed")) {
break;
}
}
}
let disablePropertyItems = !node || (node &&
!node.classList.contains("ruleview-property") &&
!node.classList.contains("ruleview-computed"));
menuitem = this.chromeDoc.querySelector("#rule-view-copy-declaration");
menuitem.disabled = disablePropertyItems;
menuitem = this.chromeDoc.querySelector("#rule-view-copy-property");
menuitem.disabled = disablePropertyItems;
menuitem = this.chromeDoc.querySelector("#rule-view-copy-property-value");
menuitem.disabled = disablePropertyItems;
},
/**
* Copy selected text from the rule view.
*
* @param aEvent The event object
*/
ruleViewCopy: function IUI_ruleViewCopy(aEvent)
{
let iframe = this.getToolIframe(this.ruleViewObject);
let win = iframe.contentWindow;
let text = win.getSelection().toString();
// Remove any double newlines.
text = text.replace(/(\r?\n)\r?\n/g, "$1");
// Remove "inline"
let inline = styleInspectorStrings.GetStringFromName("rule.sourceInline");
let rx = new RegExp("^" + inline + "\\r?\\n?", "g");
text = text.replace(rx, "");
// Remove file:line
text = text.replace(/[\w\.]+:\d+(\r?\n)/g, "$1");
// Remove inherited from: line
let inheritedFrom = styleInspectorStrings
.GetStringFromName("rule.inheritedSource");
inheritedFrom = inheritedFrom.replace(/\s%S\s\(%S\)/g, "");
rx = new RegExp("(\r?\n)" + inheritedFrom + ".*", "g");
text = text.replace(rx, "$1");
clipboardHelper.copyString(text);
if (aEvent) {
aEvent.preventDefault();
}
},
/**
* Copy a rule from the rule view.
*
* @param aEvent The event object
*/
ruleViewCopyRule: function IUI_ruleViewCopyRule(aEvent)
{
let node = this.chromeDoc.popupNode;
if (node.className != "ruleview-code") {
if (node.className == "ruleview-rule-source") {
node = node.nextElementSibling;
} else {
while (node = node.parentElement) {
if (node.className == "ruleview-code") {
break;
}
}
}
}
if (node.className == "ruleview-code") {
// We need to strip expanded properties from the node because we use
// node.textContent below, which also gets text from hidden nodes. The
// simplest way to do this is to clone the node and remove them from the
// clone.
node = node.cloneNode();
let computed = node.querySelector(".ruleview-computedlist");
if (computed) {
computed.parentNode.removeChild(computed);
}
}
let text = node.textContent;
// Format the rule
if (osString == "WINNT") {
text = text.replace(/{/g, "{\r\n ");
text = text.replace(/;/g, ";\r\n ");
text = text.replace(/\s*}/g, "\r\n}");
} else {
text = text.replace(/{/g, "{\n ");
text = text.replace(/;/g, ";\n ");
text = text.replace(/\s*}/g, "\n}");
}
clipboardHelper.copyString(text);
},
/**
* Copy a declaration from the rule view.
*
* @param aEvent The event object
*/
ruleViewCopyDeclaration: function IUI_ruleViewCopyDeclaration(aEvent)
{
let node = this.chromeDoc.popupNode;
if (!node.classList.contains("ruleview-property") &&
!node.classList.contains("ruleview-computed")) {
while (node = node.parentElement) {
if (node.classList.contains("ruleview-property") ||
node.classList.contains("ruleview-computed")) {
break;
}
}
}
// We need to strip expanded properties from the node because we use
// node.textContent below, which also gets text from hidden nodes. The
// simplest way to do this is to clone the node and remove them from the
// clone.
node = node.cloneNode();
let computed = node.querySelector(".ruleview-computedlist");
if (computed) {
computed.parentNode.removeChild(computed);
}
clipboardHelper.copyString(node.textContent);
},
/**
* Copy a property name from the rule view.
*
* @param aEvent The event object
*/
ruleViewCopyProperty: function IUI_ruleViewCopyProperty(aEvent)
{
let node = this.chromeDoc.popupNode;
if (!node.classList.contains("ruleview-propertyname")) {
node = node.querySelector(".ruleview-propertyname");
}
if (node) {
clipboardHelper.copyString(node.textContent);
}
},
/**
* Copy a property value from the rule view.
*
* @param aEvent The event object
*/
ruleViewCopyPropertyValue: function IUI_ruleViewCopyPropertyValue(aEvent)
{
let node = this.chromeDoc.popupNode;
if (!node.classList.contains("ruleview-propertyvalue")) {
node = node.querySelector(".ruleview-propertyvalue");
}
if (node) {
clipboardHelper.copyString(node.textContent);
}
},
/**
* Destroy the rule view.
*/
destroyRuleView: function IUI_destroyRuleView()
{
let iframe = this.getToolIframe(this.ruleViewObject);
iframe.removeEventListener("copy", this.cssRuleViewBoundCopy);
iframe.parentNode.removeChild(iframe);
if (this.ruleView) {
let menu = this.chromeDoc.querySelector("#rule-view-context-menu");
if (menu) {
// Copy
let menuitem = this.chromeDoc.querySelector("#rule-view-copy");
menuitem.removeEventListener("command", this.cssRuleViewBoundCopy);
// Copy rule
menuitem = this.chromeDoc.querySelector("#rule-view-copy-rule");
menuitem.removeEventListener("command", this.cssRuleViewBoundCopyRule);
// Copy property
menuitem = this.chromeDoc.querySelector("#rule-view-copy-declaration");
menuitem.removeEventListener("command",
this.cssRuleViewBoundCopyDeclaration);
// Copy property name
menuitem = this.chromeDoc.querySelector("#rule-view-copy-property");
menuitem.removeEventListener("command",
this.cssRuleViewBoundCopyProperty);
// Copy property value
menuitem = this.chromeDoc.querySelector("#rule-view-copy-property-value");
menuitem.removeEventListener("command",
this.cssRuleViewBoundCopyPropertyValue);
menu.removeEventListener("popupshowing", this.cssRuleViewBoundMenuUpdate);
menu.parentNode.removeChild(menu);
}
this.ruleView.element.removeEventListener("CssRuleViewChanged",
this.boundRuleViewChanged);
this.ruleView.element.removeEventListener("CssRuleViewCSSLinkClicked",
this.cssRuleViewBoundCSSLinkClicked);
this.ruleView.element.removeEventListener("mousedown",
this.cssRuleViewBoundMouseDown);
this.ruleView.element.removeEventListener("mouseup",
this.cssRuleViewBoundMouseUp);
this.ruleView.element.removeEventListener("mousemove",
this.cssRuleViewBoundMouseMove);
delete boundRuleViewChanged;
this.ruleView.clear();
delete this.ruleView;
@ -1236,6 +1601,7 @@ InspectorUI.prototype = {
iframe.id = "devtools-sidebar-iframe-" + aRegObj.id;
iframe.setAttribute("flex", "1");
iframe.setAttribute("tooltip", "aHTMLTooltip");
iframe.addEventListener("mousedown", iframe.focus);
this.sidebarDeck.appendChild(iframe);
// wire up button to show the iframe
@ -1347,6 +1713,10 @@ InspectorUI.prototype = {
let btn = this.chromeDoc.getElementById(buttonId);
this.unbindToolEvent(btn, "click");
// Remove focus listener
let iframe = this.getToolIframe(aRegObj);
iframe.removeEventListener("mousedown", iframe.focus);
// remove sidebar buttons and tools
this.sidebarToolbar.removeChild(btn);
@ -2232,3 +2602,17 @@ XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
return Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
});
XPCOMUtils.defineLazyGetter(this, "styleInspectorStrings", function() {
return Services.strings.createBundle(
"chrome://browser/locale/devtools/styleinspector.properties");
});
XPCOMUtils.defineLazyGetter(this, "osString", function() {
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
});

View File

@ -58,6 +58,7 @@ _BROWSER_FILES = \
browser_inspector_bug_665880.js \
browser_inspector_bug_674871.js \
browser_inspector_editor.js \
browser_inspector_editor_name.js \
browser_inspector_bug_566084_location_changed.js \
browser_inspector_infobar.js \
browser_inspector_bug_690361.js \

View File

@ -3,13 +3,8 @@
/* ***** BEGIN LICENSE BLOCK *****
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*
* Contributor(s):
* Rob Campbell <rcampbell@mozilla.com>
* Mihai Sucan <mihai.sucan@gmail.com>
* Kyle Simpson <ksimpson@mozilla.com>
*
* ***** END LICENSE BLOCK ***** */
* ***** END LICENSE BLOCK *****
*/
let doc;
let div;

View File

@ -0,0 +1,253 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* ***** BEGIN LICENSE BLOCK *****
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
* ***** END LICENSE BLOCK *****
*/
let doc;
let div;
let editorTestSteps;
function doNextStep() {
editorTestSteps.next();
}
function setupEditorTests()
{
div = doc.createElement("div");
div.setAttribute("id", "foobar");
div.setAttribute("class", "barbaz");
doc.body.appendChild(div);
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.toggleHTMLPanel();
}
function runEditorTests()
{
Services.obs.removeObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
InspectorUI.stopInspecting();
InspectorUI.inspectNode(doc.body, true);
// setup generator for async test steps
editorTestSteps = doEditorTestSteps();
// add step listeners
Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
// start the tests
doNextStep();
}
function highlighterTrap()
{
// bug 696107
InspectorUI.highlighter.removeListener("nodeselected", highlighterTrap);
ok(false, "Highlighter moved. Shouldn't be here!");
finishUp();
}
function doEditorTestSteps()
{
let treePanel = InspectorUI.treePanel;
let editor = treePanel.treeBrowserDocument.getElementById("attribute-editor");
let editorInput = treePanel.treeBrowserDocument.getElementById("attribute-editor-input");
// Step 1: grab and test the attribute-name nodes in the HTML panel, then open editor
let nodes = treePanel.treeBrowserDocument.querySelectorAll(".nodeName.editable");
let attrNameNode_id = nodes[0]
let attrNameNode_class = nodes[1];
is(attrNameNode_id.innerHTML, "id", "Step 1: we have the correct `id` attribute-name node in the HTML panel");
is(attrNameNode_class.innerHTML, "class", "we have the correct `class` attribute-name node in the HTML panel");
// double-click the `id` attribute-name node to open the editor
executeSoon(function() {
// firing 2 clicks right in a row to simulate a double-click
EventUtils.synthesizeMouse(attrNameNode_id, 2, 2, {clickCount: 2}, attrNameNode_id.ownerDocument.defaultView);
});
yield; // End of Step 1
// Step 2: validate editing session, enter new attribute value into editor, and save input
ok(InspectorUI.treePanel.editingContext, "Step 2: editor session started");
let selection = InspectorUI.selection;
ok(selection, "Selection is: " + selection);
let editorVisible = editor.classList.contains("editing");
ok(editorVisible, "editor popup visible");
// check if the editor popup is "near" the correct position
let editorDims = editor.getBoundingClientRect();
let attrNameNodeDims = attrNameNode_id.getBoundingClientRect();
let editorPositionOK = (editorDims.left >= (attrNameNodeDims.left - editorDims.width - 5)) &&
(editorDims.right <= (attrNameNodeDims.right + editorDims.width + 5)) &&
(editorDims.top >= (attrNameNodeDims.top - editorDims.height - 5)) &&
(editorDims.bottom <= (attrNameNodeDims.bottom + editorDims.height + 5));
ok(editorPositionOK, "editor position acceptable");
// check to make sure the attribute-value node being edited is properly highlighted
let attrNameNodeHighlighted = attrNameNode_id.classList.contains("editingAttributeValue");
ok(attrNameNodeHighlighted, "`id` attribute-name node is editor-highlighted");
is(treePanel.editingContext.repObj, div, "editor session has correct reference to div");
is(treePanel.editingContext.attrObj, attrNameNode_id, "editor session has correct reference to `id` attribute-name node in HTML panel");
is(treePanel.editingContext.attrName, "id", "editor session knows correct attribute-name");
editorInput.value = "burp";
editorInput.focus();
InspectorUI.highlighter.addListener("nodeselected", highlighterTrap);
// hit <enter> to save the textbox value
executeSoon(function() {
// Extra key to test that keyboard handlers have been removed. bug 696107.
EventUtils.synthesizeKey("VK_LEFT", {}, attrNameNode_id.ownerDocument.defaultView);
EventUtils.synthesizeKey("VK_RETURN", {}, attrNameNode_id.ownerDocument.defaultView);
});
// two `yield` statements, to trap both the "SAVED" and "CLOSED" events that will be triggered
yield;
yield; // End of Step 2
// remove this from previous step
InspectorUI.highlighter.removeListener("nodeselected", highlighterTrap);
// Step 3: validate that the previous editing session saved correctly, then open editor on `class` attribute value
ok(!treePanel.editingContext, "Step 3: editor session ended");
editorVisible = editor.classList.contains("editing");
ok(!editorVisible, "editor popup hidden");
attrNameNodeHighlighted = attrNameNode_id.classList.contains("editingAttributeValue");
ok(!attrNameNodeHighlighted, "`id` attribute-value node is no longer editor-highlighted");
is(div.getAttribute("burp"), "foobar", "`id` attribute-name successfully updated");
is(attrNameNode_id.innerHTML, "burp", "attribute-name node in HTML panel successfully updated");
// double-click the `class` attribute-value node to open the editor
executeSoon(function() {
// firing 2 clicks right in a row to simulate a double-click
EventUtils.synthesizeMouse(attrNameNode_class, 2, 2, {clickCount: 2}, attrNameNode_class.ownerDocument.defaultView);
});
yield; // End of Step 3
// Step 4: enter value into editor, then hit <escape> to discard it
ok(treePanel.editingContext, "Step 4: editor session started");
editorVisible = editor.classList.contains("editing");
ok(editorVisible, "editor popup visible");
is(treePanel.editingContext.attrObj, attrNameNode_class, "editor session has correct reference to `class` attribute-name node in HTML panel");
is(treePanel.editingContext.attrName, "class", "editor session knows correct attribute-name");
editorInput.value = "Hello World";
editorInput.focus();
// hit <escape> to discard the inputted value
executeSoon(function() {
EventUtils.synthesizeKey("VK_ESCAPE", {}, attrNameNode_class.ownerDocument.defaultView);
});
yield; // End of Step 4
// Step 5: validate that the previous editing session discarded correctly, then open editor on `id` attribute value again
ok(!treePanel.editingContext, "Step 5: editor session ended");
editorVisible = editor.classList.contains("editing");
ok(!editorVisible, "editor popup hidden");
is(div.getAttribute("class"), "barbaz", "`class` attribute-name *not* updated");
is(attrNameNode_class.innerHTML, "class", "attribute-name node in HTML panel *not* updated");
// double-click the `id` attribute-name node to open the editor
executeSoon(function() {
// firing 2 clicks right in a row to simulate a double-click
EventUtils.synthesizeMouse(attrNameNode_id, 2, 2, {clickCount: 2}, attrNameNode_id.ownerDocument.defaultView);
});
yield; // End of Step 5
// Step 6: validate that editor opened again, then test double-click inside of editor (should do nothing)
ok(treePanel.editingContext, "Step 6: editor session started");
editorVisible = editor.classList.contains("editing");
ok(editorVisible, "editor popup visible");
// double-click on the editor input box
executeSoon(function() {
// firing 2 clicks right in a row to simulate a double-click
EventUtils.synthesizeMouse(editorInput, 2, 2, {clickCount: 2}, editorInput.ownerDocument.defaultView);
// since the previous double-click is supposed to do nothing,
// wait a brief moment, then move on to the next step
executeSoon(function() {
doNextStep();
});
});
yield; // End of Step 6
// Step 7: validate that editing session is still correct, then enter a value and try a click
// outside of editor (should cancel the editing session)
ok(treePanel.editingContext, "Step 7: editor session still going");
editorVisible = editor.classList.contains("editing");
ok(editorVisible, "editor popup still visible");
editorInput.value = "all your base are belong to us";
// single-click the `class` attribute-value node
executeSoon(function() {
EventUtils.synthesizeMouse(attrNameNode_class, 2, 2, {}, attrNameNode_class.ownerDocument.defaultView);
});
yield; // End of Step 7
// Step 8: validate that the editor was closed and that the editing was not saved
ok(!treePanel.editingContext, "Step 8: editor session ended");
editorVisible = editor.classList.contains("editing");
ok(!editorVisible, "editor popup hidden");
is(div.getAttribute("burp"), "foobar", "`id` attribute-name *not* updated");
is(attrNameNode_id.innerHTML, "burp", "attribute-value node in HTML panel *not* updated");
// End of Step 8
executeSoon(finishUp);
}
function finishUp() {
// end of all steps, so clean up
Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
doc = div = null;
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(setupEditorTests, content);
}, true);
content.location = "data:text/html,basic tests for html panel attribute-value editor";
}

View File

@ -24,6 +24,7 @@
- Rob Campbell <robcee@mozilla.com> (original author)
- Mihai Sucan <mihai.sucan@gmail.com>
- Erik Vold <erikvvold@gmail.com>
- Mark Capella <markcapella@twcny.rr.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

View File

@ -53,6 +53,7 @@ copy({
ORION_EDITOR + "/orion/textview/rulers.js",
ORION_EDITOR + "/orion/textview/undoStack.js",
ORION_EDITOR + "/orion/textview/textModel.js",
ORION_EDITOR + "/orion/textview/projectionTextModel.js",
ORION_EDITOR + "/orion/textview/tooltip.js",
ORION_EDITOR + "/orion/textview/textView.js",
ORION_EDITOR + "/orion/textview/textDND.js",

View File

@ -22,6 +22,10 @@ Orion version: git clone from 2012-01-26
http://git.eclipse.org/c/orion/org.eclipse.orion.client.git/commit/?id=27177e9a3dc70c20b4877e3eab3adfff1d56e342
see https://bugs.eclipse.org/bugs/show_bug.cgi?id=370606
+ patch for Mozilla Bug 730532 - remove CSS2Properties aliases for MozOpacity
and MozOutline*
see https://bugzilla.mozilla.org/show_bug.cgi?id=730532#c3
# License
The following files are licensed according to the contents in the LICENSE

View File

@ -2563,6 +2563,590 @@ define("orion/textview/textModel", ['orion/textview/eventTarget'], function(mEve
return {TextModel: TextModel};
});/*******************************************************************************
* @license
* Copyright (c) 2010, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
* License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
*
* Contributors:
* Felipe Heidrich (IBM Corporation) - initial API and implementation
* Silenio Quarti (IBM Corporation) - initial API and implementation
******************************************************************************/
/*global define */
define("orion/textview/projectionTextModel", ['orion/textview/textModel', 'orion/textview/eventTarget'], function(mTextModel, mEventTarget) {
/**
* @class This object represents a projection range. A projection specifies a
* range of text and the replacement text. The range of text is relative to the
* base text model associated to a projection model.
* <p>
* <b>See:</b><br/>
* {@link orion.textview.ProjectionTextModel}<br/>
* {@link orion.textview.ProjectionTextModel#addProjection}<br/>
* </p>
* @name orion.textview.Projection
*
* @property {Number} start The start offset of the projection range.
* @property {Number} end The end offset of the projection range. This offset is exclusive.
* @property {String|orion.textview.TextModel} [text=""] The projection text to be inserted
*/
/**
* Constructs a new <code>ProjectionTextModel</code> based on the specified <code>TextModel</code>.
*
* @param {orion.textview.TextModel} baseModel The base text model.
*
* @name orion.textview.ProjectionTextModel
* @class The <code>ProjectionTextModel</code> represents a projection of its base text
* model. Projection ranges can be added to the projection text model to hide and/or insert
* ranges to the base text model.
* <p>
* The contents of the projection text model is modified when changes occur in the base model,
* projection model or by calls to {@link #addProjection} and {@link #removeProjection}.
* </p>
* <p>
* <b>See:</b><br/>
* {@link orion.textview.TextView}<br/>
* {@link orion.textview.TextModel}
* {@link orion.textview.TextView#setModel}
* </p>
* @borrows orion.textview.EventTarget#addEventListener as #addEventListener
* @borrows orion.textview.EventTarget#removeEventListener as #removeEventListener
* @borrows orion.textview.EventTarget#dispatchEvent as #dispatchEvent
*/
function ProjectionTextModel(baseModel) {
this._model = baseModel; /* Base Model */
this._projections = [];
}
ProjectionTextModel.prototype = /** @lends orion.textview.ProjectionTextModel.prototype */ {
/**
* Adds a projection range to the model.
* <p>
* The model must notify the listeners before and after the the text is
* changed by calling {@link #onChanging} and {@link #onChanged} respectively.
* </p>
* @param {orion.textview.Projection} projection The projection range to be added.
*
* @see #removeProjection
*/
addProjection: function(projection) {
if (!projection) {return;}
//start and end can't overlap any exist projection
var model = this._model, projections = this._projections;
projection._lineIndex = model.getLineAtOffset(projection.start);
projection._lineCount = model.getLineAtOffset(projection.end) - projection._lineIndex;
var text = projection.text;
if (!text) { text = ""; }
if (typeof text === "string") {
projection._model = new mTextModel.TextModel(text, model.getLineDelimiter());
} else {
projection._model = text;
}
var eventStart = this.mapOffset(projection.start, true);
var removedCharCount = projection.end - projection.start;
var removedLineCount = projection._lineCount;
var addedCharCount = projection._model.getCharCount();
var addedLineCount = projection._model.getLineCount() - 1;
var modelChangingEvent = {
type: "Changing",
text: projection._model.getText(),
start: eventStart,
removedCharCount: removedCharCount,
addedCharCount: addedCharCount,
removedLineCount: removedLineCount,
addedLineCount: addedLineCount
};
this.onChanging(modelChangingEvent);
var index = this._binarySearch(projections, projection.start);
projections.splice(index, 0, projection);
var modelChangedEvent = {
type: "Changed",
start: eventStart,
removedCharCount: removedCharCount,
addedCharCount: addedCharCount,
removedLineCount: removedLineCount,
addedLineCount: addedLineCount
};
this.onChanged(modelChangedEvent);
},
/**
* Returns all projection ranges of this model.
*
* @return {orion.textview.Projection[]} The projection ranges.
*
* @see #addProjection
*/
getProjections: function() {
return this._projections.slice(0);
},
/**
* Gets the base text model.
*
* @return {orion.textview.TextModel} The base text model.
*/
getBaseModel: function() {
return this._model;
},
/**
* Maps offsets between the projection model and its base model.
*
* @param {Number} offset The offset to be mapped.
* @param {Boolean} [baseOffset=false] <code>true</code> if <code>offset</code> is in base model and
* should be mapped to the projection model.
* @return {Number} The mapped offset
*/
mapOffset: function(offset, baseOffset) {
var projections = this._projections, delta = 0, i, projection;
if (baseOffset) {
for (i = 0; i < projections.length; i++) {
projection = projections[i];
if (projection.start > offset) { break; }
if (projection.end > offset) { return -1; }
delta += projection._model.getCharCount() - (projection.end - projection.start);
}
return offset + delta;
}
for (i = 0; i < projections.length; i++) {
projection = projections[i];
if (projection.start > offset - delta) { break; }
var charCount = projection._model.getCharCount();
if (projection.start + charCount > offset - delta) {
return -1;
}
delta += charCount - (projection.end - projection.start);
}
return offset - delta;
},
/**
* Removes a projection range from the model.
* <p>
* The model must notify the listeners before and after the the text is
* changed by calling {@link #onChanging} and {@link #onChanged} respectively.
* </p>
*
* @param {orion.textview.Projection} projection The projection range to be removed.
*
* @see #addProjection
*/
removeProjection: function(projection) {
//TODO remove listeners from model
var i, delta = 0;
for (i = 0; i < this._projections.length; i++) {
var p = this._projections[i];
if (p === projection) {
projection = p;
break;
}
delta += p._model.getCharCount() - (p.end - p.start);
}
if (i < this._projections.length) {
var model = this._model;
var eventStart = projection.start + delta;
var addedCharCount = projection.end - projection.start;
var addedLineCount = projection._lineCount;
var removedCharCount = projection._model.getCharCount();
var removedLineCount = projection._model.getLineCount() - 1;
var modelChangingEvent = {
type: "Changing",
text: model.getText(projection.start, projection.end),
start: eventStart,
removedCharCount: removedCharCount,
addedCharCount: addedCharCount,
removedLineCount: removedLineCount,
addedLineCount: addedLineCount
};
this.onChanging(modelChangingEvent);
this._projections.splice(i, 1);
var modelChangedEvent = {
type: "Changed",
start: eventStart,
removedCharCount: removedCharCount,
addedCharCount: addedCharCount,
removedLineCount: removedLineCount,
addedLineCount: addedLineCount
};
this.onChanged(modelChangedEvent);
}
},
/** @ignore */
_binarySearch: function (array, offset) {
var high = array.length, low = -1, index;
while (high - low > 1) {
index = Math.floor((high + low) / 2);
if (offset <= array[index].start) {
high = index;
} else {
low = index;
}
}
return high;
},
/**
* @see orion.textview.TextModel#getCharCount
*/
getCharCount: function() {
var count = this._model.getCharCount(), projections = this._projections;
for (var i = 0; i < projections.length; i++) {
var projection = projections[i];
count += projection._model.getCharCount() - (projection.end - projection.start);
}
return count;
},
/**
* @see orion.textview.TextModel#getLine
*/
getLine: function(lineIndex, includeDelimiter) {
if (lineIndex < 0) { return null; }
var model = this._model, projections = this._projections;
var delta = 0, result = [], offset = 0, i, lineCount, projection;
for (i = 0; i < projections.length; i++) {
projection = projections[i];
if (projection._lineIndex >= lineIndex - delta) { break; }
lineCount = projection._model.getLineCount() - 1;
if (projection._lineIndex + lineCount >= lineIndex - delta) {
var projectionLineIndex = lineIndex - (projection._lineIndex + delta);
if (projectionLineIndex < lineCount) {
return projection._model.getLine(projectionLineIndex, includeDelimiter);
} else {
result.push(projection._model.getLine(lineCount));
}
}
offset = projection.end;
delta += lineCount - projection._lineCount;
}
offset = Math.max(offset, model.getLineStart(lineIndex - delta));
for (; i < projections.length; i++) {
projection = projections[i];
if (projection._lineIndex > lineIndex - delta) { break; }
result.push(model.getText(offset, projection.start));
lineCount = projection._model.getLineCount() - 1;
if (projection._lineIndex + lineCount > lineIndex - delta) {
result.push(projection._model.getLine(0, includeDelimiter));
return result.join("");
}
result.push(projection._model.getText());
offset = projection.end;
delta += lineCount - projection._lineCount;
}
var end = model.getLineEnd(lineIndex - delta, includeDelimiter);
if (offset < end) {
result.push(model.getText(offset, end));
}
return result.join("");
},
/**
* @see orion.textview.TextModel#getLineAtOffset
*/
getLineAtOffset: function(offset) {
var model = this._model, projections = this._projections;
var delta = 0, lineDelta = 0;
for (var i = 0; i < projections.length; i++) {
var projection = projections[i];
if (projection.start > offset - delta) { break; }
var charCount = projection._model.getCharCount();
if (projection.start + charCount > offset - delta) {
var projectionOffset = offset - (projection.start + delta);
lineDelta += projection._model.getLineAtOffset(projectionOffset);
delta += projectionOffset;
break;
}
lineDelta += projection._model.getLineCount() - 1 - projection._lineCount;
delta += charCount - (projection.end - projection.start);
}
return model.getLineAtOffset(offset - delta) + lineDelta;
},
/**
* @see orion.textview.TextModel#getLineCount
*/
getLineCount: function() {
var model = this._model, projections = this._projections;
var count = model.getLineCount();
for (var i = 0; i < projections.length; i++) {
var projection = projections[i];
count += projection._model.getLineCount() - 1 - projection._lineCount;
}
return count;
},
/**
* @see orion.textview.TextModel#getLineDelimiter
*/
getLineDelimiter: function() {
return this._model.getLineDelimiter();
},
/**
* @see orion.textview.TextModel#getLineEnd
*/
getLineEnd: function(lineIndex, includeDelimiter) {
if (lineIndex < 0) { return -1; }
var model = this._model, projections = this._projections;
var delta = 0, offsetDelta = 0;
for (var i = 0; i < projections.length; i++) {
var projection = projections[i];
if (projection._lineIndex > lineIndex - delta) { break; }
var lineCount = projection._model.getLineCount() - 1;
if (projection._lineIndex + lineCount > lineIndex - delta) {
var projectionLineIndex = lineIndex - (projection._lineIndex + delta);
return projection._model.getLineEnd (projectionLineIndex, includeDelimiter) + projection.start + offsetDelta;
}
offsetDelta += projection._model.getCharCount() - (projection.end - projection.start);
delta += lineCount - projection._lineCount;
}
return model.getLineEnd(lineIndex - delta, includeDelimiter) + offsetDelta;
},
/**
* @see orion.textview.TextModel#getLineStart
*/
getLineStart: function(lineIndex) {
if (lineIndex < 0) { return -1; }
var model = this._model, projections = this._projections;
var delta = 0, offsetDelta = 0;
for (var i = 0; i < projections.length; i++) {
var projection = projections[i];
if (projection._lineIndex >= lineIndex - delta) { break; }
var lineCount = projection._model.getLineCount() - 1;
if (projection._lineIndex + lineCount >= lineIndex - delta) {
var projectionLineIndex = lineIndex - (projection._lineIndex + delta);
return projection._model.getLineStart (projectionLineIndex) + projection.start + offsetDelta;
}
offsetDelta += projection._model.getCharCount() - (projection.end - projection.start);
delta += lineCount - projection._lineCount;
}
return model.getLineStart(lineIndex - delta) + offsetDelta;
},
/**
* @see orion.textview.TextModel#getText
*/
getText: function(start, end) {
if (start === undefined) { start = 0; }
var model = this._model, projections = this._projections;
var delta = 0, result = [], i, projection, charCount;
for (i = 0; i < projections.length; i++) {
projection = projections[i];
if (projection.start > start - delta) { break; }
charCount = projection._model.getCharCount();
if (projection.start + charCount > start - delta) {
if (end !== undefined && projection.start + charCount > end - delta) {
return projection._model.getText(start - (projection.start + delta), end - (projection.start + delta));
} else {
result.push(projection._model.getText(start - (projection.start + delta)));
start = projection.end + delta + charCount - (projection.end - projection.start);
}
}
delta += charCount - (projection.end - projection.start);
}
var offset = start - delta;
if (end !== undefined) {
for (; i < projections.length; i++) {
projection = projections[i];
if (projection.start > end - delta) { break; }
result.push(model.getText(offset, projection.start));
charCount = projection._model.getCharCount();
if (projection.start + charCount > end - delta) {
result.push(projection._model.getText(0, end - (projection.start + delta)));
return result.join("");
}
result.push(projection._model.getText());
offset = projection.end;
delta += charCount - (projection.end - projection.start);
}
result.push(model.getText(offset, end - delta));
} else {
for (; i < projections.length; i++) {
projection = projections[i];
result.push(model.getText(offset, projection.start));
result.push(projection._model.getText());
offset = projection.end;
}
result.push(model.getText(offset));
}
return result.join("");
},
/** @ignore */
_onChanging: function(text, start, removedCharCount, addedCharCount, removedLineCount, addedLineCount) {
var model = this._model, projections = this._projections, i, projection, delta = 0, lineDelta;
var end = start + removedCharCount;
for (; i < projections.length; i++) {
projection = projections[i];
if (projection.start > start) { break; }
delta += projection._model.getCharCount() - (projection.end - projection.start);
}
/*TODO add stuff saved by setText*/
var mapStart = start + delta, rangeStart = i;
for (; i < projections.length; i++) {
projection = projections[i];
if (projection.start > end) { break; }
delta += projection._model.getCharCount() - (projection.end - projection.start);
lineDelta += projection._model.getLineCount() - 1 - projection._lineCount;
}
/*TODO add stuff saved by setText*/
var mapEnd = end + delta, rangeEnd = i;
this.onChanging(mapStart, mapEnd - mapStart, addedCharCount/*TODO add stuff saved by setText*/, removedLineCount + lineDelta/*TODO add stuff saved by setText*/, addedLineCount/*TODO add stuff saved by setText*/);
projections.splice(projections, rangeEnd - rangeStart);
var count = text.length - (mapEnd - mapStart);
for (; i < projections.length; i++) {
projection = projections[i];
projection.start += count;
projection.end += count;
projection._lineIndex = model.getLineAtOffset(projection.start);
}
},
/**
* @see orion.textview.TextModel#onChanging
*/
onChanging: function(modelChangingEvent) {
return this.dispatchEvent(modelChangingEvent);
},
/**
* @see orion.textview.TextModel#onChanged
*/
onChanged: function(modelChangedEvent) {
return this.dispatchEvent(modelChangedEvent);
},
/**
* @see orion.textview.TextModel#setLineDelimiter
*/
setLineDelimiter: function(lineDelimiter) {
this._model.setLineDelimiter(lineDelimiter);
},
/**
* @see orion.textview.TextModel#setText
*/
setText: function(text, start, end) {
if (text === undefined) { text = ""; }
if (start === undefined) { start = 0; }
var eventStart = start, eventEnd = end;
var model = this._model, projections = this._projections;
var delta = 0, lineDelta = 0, i, projection, charCount, startProjection, endProjection, startLineDelta = 0;
for (i = 0; i < projections.length; i++) {
projection = projections[i];
if (projection.start > start - delta) { break; }
charCount = projection._model.getCharCount();
if (projection.start + charCount > start - delta) {
if (end !== undefined && projection.start + charCount > end - delta) {
projection._model.setText(text, start - (projection.start + delta), end - (projection.start + delta));
//TODO events - special case
return;
} else {
startLineDelta = projection._model.getLineCount() - 1 - projection._model.getLineAtOffset(start - (projection.start + delta));
startProjection = {
projection: projection,
start: start - (projection.start + delta)
};
start = projection.end + delta + charCount - (projection.end - projection.start);
}
}
lineDelta += projection._model.getLineCount() - 1 - projection._lineCount;
delta += charCount - (projection.end - projection.start);
}
var mapStart = start - delta, rangeStart = i, startLine = model.getLineAtOffset(mapStart) + lineDelta - startLineDelta;
if (end !== undefined) {
for (; i < projections.length; i++) {
projection = projections[i];
if (projection.start > end - delta) { break; }
charCount = projection._model.getCharCount();
if (projection.start + charCount > end - delta) {
lineDelta += projection._model.getLineAtOffset(end - (projection.start + delta));
charCount = end - (projection.start + delta);
end = projection.end + delta;
endProjection = {
projection: projection,
end: charCount
};
break;
}
lineDelta += projection._model.getLineCount() - 1 - projection._lineCount;
delta += charCount - (projection.end - projection.start);
}
} else {
for (; i < projections.length; i++) {
projection = projections[i];
lineDelta += projection._model.getLineCount() - 1 - projection._lineCount;
delta += projection._model.getCharCount() - (projection.end - projection.start);
}
end = eventEnd = model.getCharCount() + delta;
}
var mapEnd = end - delta, rangeEnd = i, endLine = model.getLineAtOffset(mapEnd) + lineDelta;
//events
var removedCharCount = eventEnd - eventStart;
var removedLineCount = endLine - startLine;
var addedCharCount = text.length;
var addedLineCount = 0;
var cr = 0, lf = 0, index = 0;
while (true) {
if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); }
if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); }
if (lf === -1 && cr === -1) { break; }
if (cr !== -1 && lf !== -1) {
if (cr + 1 === lf) {
index = lf + 1;
} else {
index = (cr < lf ? cr : lf) + 1;
}
} else if (cr !== -1) {
index = cr + 1;
} else {
index = lf + 1;
}
addedLineCount++;
}
var modelChangingEvent = {
type: "Changing",
text: text,
start: eventStart,
removedCharCount: removedCharCount,
addedCharCount: addedCharCount,
removedLineCount: removedLineCount,
addedLineCount: addedLineCount
};
this.onChanging(modelChangingEvent);
// var changeLineCount = model.getLineAtOffset(mapEnd) - model.getLineAtOffset(mapStart) + addedLineCount;
model.setText(text, mapStart, mapEnd);
if (startProjection) {
projection = startProjection.projection;
projection._model.setText("", startProjection.start);
}
if (endProjection) {
projection = endProjection.projection;
projection._model.setText("", 0, endProjection.end);
projection.start = projection.end;
projection._lineCount = 0;
}
projections.splice(rangeStart, rangeEnd - rangeStart);
var changeCount = text.length - (mapEnd - mapStart);
for (i = rangeEnd; i < projections.length; i++) {
projection = projections[i];
projection.start += changeCount;
projection.end += changeCount;
// if (projection._lineIndex + changeLineCount !== model.getLineAtOffset(projection.start)) {
// log("here");
// }
projection._lineIndex = model.getLineAtOffset(projection.start);
// projection._lineIndex += changeLineCount;
}
var modelChangedEvent = {
type: "Changed",
start: eventStart,
removedCharCount: removedCharCount,
addedCharCount: addedCharCount,
removedLineCount: removedLineCount,
addedLineCount: addedLineCount
};
this.onChanged(modelChangedEvent);
}
};
mEventTarget.EventTarget.addMixin(ProjectionTextModel.prototype);
return {ProjectionTextModel: ProjectionTextModel};
});
/*******************************************************************************
* @license
* Copyright (c) 2010, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials are made

View File

@ -908,7 +908,8 @@ SourceEditor.prototype = {
*
* @private
* @param string aType
* The annotation type to filter annotations for.
* The annotation type to filter annotations for. Use one of the keys
* in ORION_ANNOTATION_TYPES.
* @param number aStart
* Offset from where to start finding the annotations.
* @param number aEnd

View File

@ -268,24 +268,24 @@ SourceEditor.EVENTS = {
BLUR: "Blur",
/**
* The MouseMove event is sent when the user moves the mouse over a line
* annotation. The event object properties:
* The MouseMove event is sent when the user moves the mouse over a line.
* The event object properties:
* - event - the DOM mousemove event object.
* - x and y - the mouse coordinates relative to the document being edited.
*/
MOUSE_MOVE: "MouseMove",
/**
* The MouseOver event is sent when the mouse pointer enters a line
* annotation. The event object properties:
* The MouseOver event is sent when the mouse pointer enters a line.
* The event object properties:
* - event - the DOM mouseover event object.
* - x and y - the mouse coordinates relative to the document being edited.
*/
MOUSE_OVER: "MouseOver",
/**
* This MouseOut event is sent when the mouse pointer exits a line
* annotation. The event object properties:
* This MouseOut event is sent when the mouse pointer exits a line.
* The event object properties:
* - event - the DOM mouseout event object.
* - x and y - the mouse coordinates relative to the document being edited.
*/

View File

@ -41,6 +41,7 @@
* ***** END LICENSE BLOCK ***** */
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const FILTER_CHANGED_TIMEOUT = 300;
@ -161,9 +162,18 @@ function CssHtmlTree(aStyleInspector)
this.getRTLAttr = this.win.getComputedStyle(this.win.gBrowser).direction;
this.propertyViews = [];
// Create bound methods.
this.siBoundMenuUpdate = this.computedViewMenuUpdate.bind(this);
this.siBoundCopy = this.computedViewCopy.bind(this);
this.siBoundCopyDeclaration = this.computedViewCopyDeclaration.bind(this);
this.siBoundCopyProperty = this.computedViewCopyProperty.bind(this);
this.siBoundCopyPropertyValue = this.computedViewCopyPropertyValue.bind(this);
// The document in which we display the results (csshtmltree.xul).
this.styleDocument = this.styleWin.contentWindow.document;
this.styleDocument.addEventListener("copy", this.siBoundCopy);
// Nodes used in templating
this.root = this.styleDocument.getElementById("root");
this.templateRoot = this.styleDocument.getElementById("templateRoot");
@ -176,6 +186,7 @@ function CssHtmlTree(aStyleInspector)
// The element that we're inspecting, and the document that it comes from.
this.viewedElement = null;
this.createStyleViews();
this.createContextMenu();
}
/**
@ -231,6 +242,11 @@ XPCOMUtils.defineLazyGetter(CssHtmlTree, "HELP_LINK_TITLE", function() {
return CssHtmlTree.HELP_LINK_TITLE = CssHtmlTree.l10n("helpLinkTitle");
});
XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
return Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
});
CssHtmlTree.prototype = {
// Cache the list of properties that have matched and unmatched properties.
_matchedProperties: null,
@ -469,6 +485,177 @@ CssHtmlTree.prototype = {
return this._unmatchedProperties[aProperty];
},
/**
* Create a context menu.
*/
createContextMenu: function SI_createContextMenu()
{
let popupSet = this.doc.getElementById("mainPopupSet");
let menu = this.doc.createElement("menupopup");
menu.addEventListener("popupshowing", this.siBoundMenuUpdate);
menu.id = "computed-view-context-menu";
popupSet.appendChild(menu);
// Copy selection
let label = CssHtmlTree.l10n("style.contextmenu.copyselection");
let accessKey = CssHtmlTree.l10n("style.contextmenu.copyselection.accesskey");
let item = this.doc.createElement("menuitem");
item.id = "computed-view-copy";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.siBoundCopy);
menu.appendChild(item);
// Copy declaration
label = CssHtmlTree.l10n("style.contextmenu.copydeclaration");
accessKey = CssHtmlTree.l10n("style.contextmenu.copydeclaration.accesskey");
item = this.doc.createElement("menuitem");
item.id = "computed-view-copy-declaration";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.siBoundCopyDeclaration);
menu.appendChild(item);
// Copy property name
label = CssHtmlTree.l10n("style.contextmenu.copyproperty");
accessKey = CssHtmlTree.l10n("style.contextmenu.copyproperty.accesskey");
item = this.doc.createElement("menuitem");
item.id = "computed-view-copy-property";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.siBoundCopyProperty);
menu.appendChild(item);
// Copy property value
label = CssHtmlTree.l10n("style.contextmenu.copypropertyvalue");
accessKey = CssHtmlTree.l10n("style.contextmenu.copypropertyvalue.accesskey");
item = this.doc.createElement("menuitem");
item.id = "computed-view-copy-property-value";
item.setAttribute("label", label);
item.setAttribute("accesskey", accessKey);
item.addEventListener("command", this.siBoundCopyPropertyValue);
menu.appendChild(item);
this.styleWin.setAttribute("context", menu.id);
},
/**
* Update the context menu by disabling irrelevant menuitems and enabling
* relevant ones.
*/
computedViewMenuUpdate: function si_computedViewMenuUpdate()
{
let win = this.styleDocument.defaultView;
let disable = win.getSelection().isCollapsed;
let menuitem = this.doc.querySelector("#computed-view-copy");
menuitem.disabled = disable;
let node = this.doc.popupNode;
if (!node.classList.contains("property-view")) {
while (node = node.parentElement) {
if (node.classList.contains("property-view")) {
break;
}
}
}
let disablePropertyItems = !node;
menuitem = this.doc.querySelector("#computed-view-copy-declaration");
menuitem.disabled = disablePropertyItems;
menuitem = this.doc.querySelector("#computed-view-copy-property");
menuitem.disabled = disablePropertyItems;
menuitem = this.doc.querySelector("#computed-view-copy-property-value");
menuitem.disabled = disablePropertyItems;
},
/**
* Copy selected text.
*
* @param aEvent The event object
*/
computedViewCopy: function si_computedViewCopy(aEvent)
{
let win = this.styleDocument.defaultView;
let text = win.getSelection().toString();
// Tidy up block headings by moving CSS property names and their values onto
// the same line and inserting a colon between them.
text = text.replace(/(.+)\r?\n\s+/g, "$1: ");
// Remove any MDN link titles
text = text.replace(CssHtmlTree.HELP_LINK_TITLE, "");
clipboardHelper.copyString(text);
if (aEvent) {
aEvent.preventDefault();
}
},
/**
* Copy declaration.
*
* @param aEvent The event object
*/
computedViewCopyDeclaration: function si_computedViewCopyDeclaration(aEvent)
{
let node = this.doc.popupNode;
if (!node.classList.contains("property-view")) {
while (node = node.parentElement) {
if (node.classList.contains("property-view")) {
break;
}
}
}
if (node) {
let name = node.querySelector(".property-name").textContent;
let value = node.querySelector(".property-value").textContent;
clipboardHelper.copyString(name + ": " + value + ";");
}
},
/**
* Copy property name.
*
* @param aEvent The event object
*/
computedViewCopyProperty: function si_computedViewCopyProperty(aEvent)
{
let node = this.doc.popupNode;
if (!node.classList.contains("property-view")) {
while (node = node.parentElement) {
if (node.classList.contains("property-view")) {
break;
}
}
}
if (node) {
node = node.querySelector(".property-name");
clipboardHelper.copyString(node.textContent);
}
},
/**
* Copy property value.
*
* @param aEvent The event object
*/
computedViewCopyPropertyValue: function si_computedViewCopyPropertyValue(aEvent)
{
let node = this.doc.popupNode;
if (!node.classList.contains("property-view")) {
while (node = node.parentElement) {
if (node.classList.contains("property-view")) {
break;
}
}
}
if (node) {
node = node.querySelector(".property-value");
clipboardHelper.copyString(node.textContent);
}
},
/**
* Destructor for CssHtmlTree.
*/
@ -486,6 +673,32 @@ CssHtmlTree.prototype = {
this._refreshProcess.cancel();
}
// Remove context menu
let menu = this.doc.querySelector("#computed-view-context-menu");
if (menu) {
// Copy selected
let menuitem = this.doc.querySelector("#computed-view-copy");
menuitem.removeEventListener("command", this.siBoundCopy);
// Copy property
menuitem = this.doc.querySelector("#computed-view-copy-declaration");
menuitem.removeEventListener("command", this.siBoundCopyDeclaration);
// Copy property name
menuitem = this.doc.querySelector("#computed-view-copy-property");
menuitem.removeEventListener("command", this.siBoundCopyProperty);
// Copy property value
menuitem = this.doc.querySelector("#computed-view-copy-property-value");
menuitem.removeEventListener("command", this.siBoundCopyPropertyValue);
menu.removeEventListener("popupshowing", this.siBoundMenuUpdate);
menu.parentNode.removeChild(menu);
}
// Remove bound listeners
this.styleDocument.removeEventListener("copy", this.siBoundCopy);
// Nodes used in templating
delete this.root;
delete this.propertyContainer;
@ -658,32 +871,35 @@ PropertyView.prototype = {
let doc = this.tree.doc;
this.element = doc.createElementNS(HTML_NS, "tr");
this.element.setAttribute("class", this.propertyHeaderClassName);
this.element.addEventListener("click", this.propertyRowClick.bind(this), false);
this.propertyHeader = doc.createElementNS(HTML_NS, "td");
this.element.appendChild(this.propertyHeader);
this.propertyHeader.setAttribute("class", "property-header");
this.matchedExpander = doc.createElementNS(HTML_NS, "div");
this.propertyHeader.appendChild(this.matchedExpander);
this.matchedExpander.setAttribute("class", "match expander");
this.nameNode = doc.createElementNS(HTML_NS, "div");
this.propertyHeader.appendChild(this.nameNode);
this.nameNode.setAttribute("tabindex", "0");
this.nameNode.addEventListener("keydown", function(aEvent) {
this.matchedExpander.setAttribute("tabindex", "0");
this.matchedExpander.addEventListener("click",
this.matchedExpanderClick.bind(this), false);
this.matchedExpander.addEventListener("keydown", function(aEvent) {
let keyEvent = Ci.nsIDOMKeyEvent;
if (aEvent.keyCode == keyEvent.DOM_VK_F1) {
this.mdnLinkClick();
}
if (aEvent.keyCode == keyEvent.DOM_VK_RETURN ||
aEvent.keyCode == keyEvent.DOM_VK_SPACE) {
this.propertyRowClick(aEvent);
aEvent.keyCode == keyEvent.DOM_VK_SPACE) {
this.matchedExpanderClick(aEvent);
}
}.bind(this), false);
this.propertyHeader.appendChild(this.matchedExpander);
this.nameNode = doc.createElementNS(HTML_NS, "div");
this.propertyHeader.appendChild(this.nameNode);
this.nameNode.setAttribute("class", "property-name");
this.nameNode.textContent = this.name;
this.nameNode.addEventListener("click", function(aEvent) {
this.matchedExpander.focus();
}.bind(this), false);
let helpcontainer = doc.createElementNS(HTML_NS, "td");
this.element.appendChild(helpcontainer);
@ -754,9 +970,9 @@ PropertyView.prototype = {
this.matchedSelectorsContainer.parentNode.hidden = !hasMatchedSelectors;
if (hasMatchedSelectors) {
this.propertyHeader.parentNode.classList.add("expandable");
this.matchedExpander.classList.add("expandable");
} else {
this.propertyHeader.parentNode.classList.remove("expandable");
this.matchedExpander.classList.remove("expandable");
}
if (this.matchedExpanded && hasMatchedSelectors) {
@ -852,17 +1068,13 @@ PropertyView.prototype = {
* The action when a user expands matched selectors.
*
* @param {Event} aEvent Used to determine the class name of the targets click
* event. If the class name is "helplink" then the event is allowed to bubble
* to the mdn link icon.
* event.
*/
propertyRowClick: function PropertyView_propertyRowClick(aEvent)
matchedExpanderClick: function PropertyView_matchedExpanderClick(aEvent)
{
if (aEvent.target.className != "helplink") {
this.matchedExpanded = !this.matchedExpanded;
this.refreshAllSelectors();
this.nameNode.focus();
aEvent.preventDefault();
}
this.matchedExpanded = !this.matchedExpanded;
this.refreshAllSelectors();
aEvent.preventDefault();
},
/**
@ -986,6 +1198,14 @@ SelectorView.prototype = {
return result;
},
maybeOpenStyleEditor: function(aEvent)
{
let keyEvent = Ci.nsIDOMKeyEvent;
if (aEvent.keyCode == keyEvent.DOM_VK_RETURN) {
this.openStyleEditor();
}
},
/**
* When a css link is clicked this method is called in order to either:
* 1. Open the link in view source (for element style attributes).

View File

@ -927,7 +927,8 @@ CssLogic.shortSource = function CssLogic_shortSource(aSheet)
return url.query;
}
return aSheet.href;
let dataUrl = aSheet.href.match(/^(data:[^,]*),/);
return dataUrl ? dataUrl[1] : aSheet.href;
}
/**

View File

@ -23,6 +23,7 @@
* Contributor(s):
* Dave Camp <dcamp@mozilla.com> (Original Author)
* Rob Campbell <rcampbell@mozilla.com>
* Mike Ratcliffe <mratcliffe@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
@ -807,7 +808,7 @@ CssRuleView.prototype = {
for each (let rule in this._elementStyle.rules) {
// Don't hold a reference to this editor beyond the one held
// by the node.
let editor = new RuleEditor(this.doc, rule);
let editor = new RuleEditor(this, rule);
this.element.appendChild(editor.element);
}
},
@ -816,15 +817,17 @@ CssRuleView.prototype = {
/**
* Create a RuleEditor.
*
* @param object aDoc
* The document holding this rule editor.
* @param CssRuleView aRuleView
* The CssRuleView containg the document holding this rule editor and the
* _selectionMode flag.
* @param Rule aRule
* The Rule object we're editing.
* @constructor
*/
function RuleEditor(aDoc, aRule)
function RuleEditor(aRuleView, aRule)
{
this.doc = aDoc;
this.ruleView = aRuleView;
this.doc = this.ruleView.doc;
this.rule = aRule;
this._onNewProperty = this._onNewProperty.bind(this);
@ -893,8 +896,16 @@ RuleEditor.prototype = {
// We made the close brace focusable, tabbing to it
// or clicking on it should start the new property editor.
this.closeBrace.addEventListener("focus", function() {
this.newProperty();
this.closeBrace.addEventListener("focus", function(aEvent) {
if (!this.ruleView._selectionMode) {
this.newProperty();
}
}.bind(this), true);
this.closeBrace.addEventListener("mousedown", function(aEvent) {
aEvent.preventDefault();
}.bind(this), true);
this.closeBrace.addEventListener("click", function(aEvent) {
this.closeBrace.focus();
}.bind(this), true);
},
@ -1261,6 +1272,21 @@ function editableField(aOptions)
aOptions.element.addEventListener("focus", function() {
new InplaceEditor(aOptions);
}, false);
// In order to allow selection on the element, prevent focus on
// mousedown. Focus on click instead.
aOptions.element.addEventListener("mousedown", function(evt) {
evt.preventDefault();
}, false);
aOptions.element.addEventListener("click", function(evt) {
let win = this.ownerDocument.defaultView;
let selection = win.getSelection();
if (selection.isCollapsed) {
aOptions.element.focus();
} else {
selection.removeAllRanges();
}
}, false);
}
var _editableField = editableField;

View File

@ -55,7 +55,7 @@ var EXPORTED_SYMBOLS = ["StyleInspector"];
function StyleInspector(aContext, aIUI)
{
this._init(aContext, aIUI);
};
}
StyleInspector.prototype = {

View File

@ -39,6 +39,7 @@
- ***** END LICENSE BLOCK ***** -->
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://browser/content/devtools/styleinspector.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/csshtmltree.css" type="text/css"?>
<!DOCTYPE window [
@ -114,8 +115,11 @@ To visually debug the templates without running firefox, alter the display:none
${selector.humanReadableText(__element)}
</td>
<td class="rule-link">
<a target="_blank" onclick="${selector.openStyleEditor}" class="link"
title="${selector.selectorInfo.href}">${selector.selectorInfo.source}</a>
<a target="_blank" class="link"
onclick="${selector.openStyleEditor}"
onkeydown="${selector.maybeOpenStyleEditor}"
title="${selector.selectorInfo.href}"
tabindex="0">${selector.selectorInfo.source}</a>
</td>
</tr>
</loop>

View File

@ -35,11 +35,39 @@
*
* ***** END LICENSE BLOCK ***** */
.ruleview {
overflow: auto;
#root {
display: -moz-box;
}
.ruleview-computedlist:not(.styleinspector-open) {
.helplink {
display: block;
}
.expander,
.property-name,
.ruleview-propertyname,
.ruleview-warning,
.ruleview-expander {
display: inline-block;
}
#propertyContainer {
display: -moz-box;
-moz-box-orient: vertical;
-moz-box-flex: 1;
overflow-y: auto;
-moz-user-select: text;
}
.ruleview {
overflow: auto;
-moz-user-select: text;
}
.property-view-hidden,
.property-content-hidden,
.ruleview-computedlist:not(.styleinspector-open),
.ruleview-warning[hidden] {
display: none;
}

View File

@ -65,6 +65,8 @@ _BROWSER_TEST_FILES = \
browser_bug722196_property_view_media_queries.js \
browser_bug722196_rule_view_media_queries.js \
browser_bug_592743_specificity.js \
browser_ruleview_bug_703643_context_menu_copy.js \
browser_computedview_bug_703643_context_menu_copy.js \
head.js \
$(NULL)

View File

@ -55,12 +55,12 @@ function SI_test()
let searchbar = stylePanel.cssHtmlTree.searchField;
let propView = getFirstVisiblePropertyView();
let rulesTable = propView.matchedSelectorsContainer;
let nameNode = propView.nameNode;
let matchedExpander = propView.matchedExpander;
info("Adding focus event handler to property name node");
nameNode.addEventListener("focus", function nameFocused() {
this.removeEventListener("focus", nameFocused);
info("property name is focused");
info("Adding focus event handler to property expander");
matchedExpander.addEventListener("focus", function expanderFocused() {
this.removeEventListener("focus", expanderFocused);
info("property expander is focused");
info("checking expand / collapse");
testKey(iframe.contentWindow, "VK_SPACE", rulesTable);
testKey(iframe.contentWindow, "VK_RETURN", rulesTable);
@ -74,7 +74,7 @@ function SI_test()
searchbar.addEventListener("focus", function searchbarFocused() {
this.removeEventListener("focus", searchbarFocused);
info("search filter is focused");
info("tabbing to property name node");
info("tabbing to property expander node");
EventUtils.synthesizeKey("VK_TAB", {}, iframe.contentWindow);
});

View File

@ -0,0 +1,163 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the style inspector works properly
let doc;
let stylePanel;
let cssHtmlTree;
function createDocument()
{
doc.body.innerHTML = '<style type="text/css"> ' +
'span { font-variant: small-caps; color: #000000; } ' +
'.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
'<h1>Some header text</h1>\n' +
'<p id="salutation" style="font-size: 12pt">hi.</p>\n' +
'<p id="body" style="font-size: 12pt">I am a test-case. This text exists ' +
'solely to provide some things to <span style="color: yellow">' +
'highlight</span> and <span style="font-weight: bold">count</span> ' +
'style list-items in the box at right. If you are reading this, ' +
'you should go do something else instead. Maybe read a book. Or better ' +
'yet, write some test-cases for another bit of code. ' +
'<span style="font-style: italic">some text</span></p>\n' +
'<p id="closing">more text</p>\n' +
'<p>even more text</p>' +
'</div>';
doc.title = "Computed view context menu test";
let span = doc.querySelector("span");
ok(span, "captain, we have the span");
stylePanel = new StyleInspector(window);
Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-populated", false);
stylePanel.createPanel(false, function() {
stylePanel.open(span);
});
}
function runStyleInspectorTests()
{
Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-populated", false);
ok(stylePanel.isOpen(), "style inspector is open");
cssHtmlTree = stylePanel.cssHtmlTree;
let contentDocument = stylePanel.iframe.contentDocument;
let prop = contentDocument.querySelector(".property-view");
ok(prop, "captain, we have the property-view node");
// We need the context menu to open in the correct place in order for
// popupNode to be propertly set.
EventUtils.synthesizeMouse(prop, 1, 1, { type: "contextmenu", button: 2 },
stylePanel.iframe.contentWindow);
checkCopyProperty()
}
function checkCopyProperty()
{
info("Checking that cssHtmlTree.siBoundCopyDeclaration() returns the " +
"correct clipboard value");
let expectedPattern = "color: rgb\\(255, 255, 0\\);";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function CS_boundCopyPropCheck() {
return checkClipboardData(expectedPattern);
},
cssHtmlTree.siBoundCopyDeclaration,
checkCopyPropertyName, checkCopyPropertyName);
}
function checkCopyPropertyName()
{
info("Checking that cssHtmlTree.siBoundCopyProperty() returns the " +
"correct clipboard value");
let expectedPattern = "color";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function CS_boundCopyPropNameCheck() {
return checkClipboardData(expectedPattern);
},
cssHtmlTree.siBoundCopyProperty,
checkCopyPropertyValue, checkCopyPropertyValue);
}
function checkCopyPropertyValue()
{
info("Checking that cssHtmlTree.siBoundCopyPropertyValue() returns the " +
"correct clipboard value");
let expectedPattern = "rgb\\(255, 255, 0\\)";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function CS_boundCopyPropValueCheck() {
return checkClipboardData(expectedPattern);
},
cssHtmlTree.siBoundCopyPropertyValue,
checkCopySelection, checkCopySelection);
}
function checkCopySelection()
{
let contentDocument = stylePanel.iframe.contentDocument;
let contentWindow = stylePanel.iframe.contentWindow;
let props = contentDocument.querySelectorAll(".property-view");
ok(props, "captain, we have the property-view nodes");
let range = document.createRange();
range.setStart(props[0], 0);
range.setEnd(props[3], 3);
contentWindow.getSelection().addRange(range);
info("Checking that cssHtmlTree.siBoundCopyPropertyValue() " +
" returns the correct clipboard value");
let expectedPattern = "color: rgb\\(255, 255, 0\\)[\\r\\n]+" +
"font-family: helvetica,sans-serif[\\r\\n]+" +
"font-size: 16px[\\r\\n]+" +
"font-variant: small-caps[\\r\\n]*";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function CS_boundCopyCheck() {
return checkClipboardData(expectedPattern);
},
cssHtmlTree.siBoundCopy, closeStyleInspector, closeStyleInspector);
}
function checkClipboardData(aExpectedPattern)
{
let actual = SpecialPowers.getClipboardData("text/unicode");
let expectedRegExp = new RegExp(aExpectedPattern, "g");
return expectedRegExp.test(actual);
}
function closeStyleInspector()
{
Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
stylePanel.close();
}
function finishUp()
{
Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
ok(!stylePanel.isOpen(), "style inspector is closed");
doc = stylePanel = cssHtmlTree = null;
gBrowser.removeCurrentTab();
finish();
}
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
doc = content.document;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html,computed view context menu test";
}

View File

@ -0,0 +1,209 @@
/* 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/ */
let doc;
function createDocument()
{
doc.body.innerHTML = '<style type="text/css"> ' +
'html { color: #000000; } ' +
'span { font-variant: small-caps; color: #000000; } ' +
'.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
'<h1>Some header text</h1>\n' +
'<p id="salutation" style="font-size: 12pt">hi.</p>\n' +
'<p id="body" style="font-size: 12pt">I am a test-case. This text exists ' +
'solely to provide some things to <span style="color: yellow">' +
'highlight</span> and <span style="font-weight: bold">count</span> ' +
'style list-items in the box at right. If you are reading this, ' +
'you should go do something else instead. Maybe read a book. Or better ' +
'yet, write some test-cases for another bit of code. ' +
'<span style="font-style: italic">some text</span></p>\n' +
'<p id="closing">more text</p>\n' +
'<p>even more text</p>' +
'</div>';
doc.title = "Rule view context menu test";
openInspector();
}
function openInspector()
{
ok(window.InspectorUI, "InspectorUI variable exists");
ok(!InspectorUI.inspecting, "Inspector is not highlighting");
ok(InspectorUI.store.isEmpty(), "Inspector.store is empty");
Services.obs.addObserver(inspectorUIOpen,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
InspectorUI.openInspectorUI();
}
function inspectorUIOpen()
{
Services.obs.removeObserver(inspectorUIOpen,
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
// Make sure the inspector is open.
ok(InspectorUI.inspecting, "Inspector is highlighting");
ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
ok(!InspectorUI.store.isEmpty(), "InspectorUI.store is not empty");
is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
// Highlight a node.
let div = content.document.getElementsByTagName("div")[0];
InspectorUI.inspectNode(div);
InspectorUI.stopInspecting();
is(InspectorUI.selection, div, "selection matches the div element");
Services.obs.addObserver(testClip,
InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
InspectorUI.showSidebar();
InspectorUI.openRuleView();
}
function testClip()
{
Services.obs.removeObserver(testClip,
InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
executeSoon(function() {
info("Checking that InspectorUI.ruleViewCopyRule() returns " +
"the correct clipboard value");
let expectedPattern = "element {[\\r\\n]+" +
" margin: 10em;[\\r\\n]+" +
" font-size: 14pt;[\\r\\n]+" +
" font-family: helvetica,sans-serif;[\\r\\n]+" +
" color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
"}[\\r\\n]*";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function IUI_boundCopyPropCheck() {
return checkClipboardData(expectedPattern);
},
checkCopyRule, checkCopyProperty, checkCopyProperty);
});
}
function checkCopyRule() {
let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
let contentDoc = ruleView.contentDocument;
let props = contentDoc.querySelectorAll(".ruleview-property");
is(props.length, 5, "checking property length");
let prop = props[2];
let propName = prop.querySelector(".ruleview-propertyname").textContent;
let propValue = prop.querySelector(".ruleview-propertyvalue").textContent;
is(propName, "font-family", "checking property name");
is(propValue, "helvetica,sans-serif", "checking property value");
// We need the context menu to open in the correct place in order for
// popupNode to be propertly set.
EventUtils.synthesizeMouse(prop, 1, 1, { type: "contextmenu", button: 2 },
ruleView.contentWindow);
InspectorUI.ruleViewCopyRule();
}
function checkCopyProperty()
{
info("Checking that InspectorUI.cssRuleViewBoundCopyDeclaration() returns " +
"the correct clipboard value");
let expectedPattern = "font-family: helvetica,sans-serif;";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function IUI_boundCopyPropCheck() {
return checkClipboardData(expectedPattern);
},
InspectorUI.cssRuleViewBoundCopyDeclaration,
checkCopyPropertyName, checkCopyPropertyName);
}
function checkCopyPropertyName()
{
info("Checking that InspectorUI.cssRuleViewBoundCopyProperty() returns " +
"the correct clipboard value");
let expectedPattern = "font-family";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function IUI_boundCopyPropNameCheck() {
return checkClipboardData(expectedPattern);
},
InspectorUI.cssRuleViewBoundCopyProperty,
checkCopyPropertyValue, checkCopyPropertyValue);
}
function checkCopyPropertyValue()
{
info("Checking that InspectorUI.cssRuleViewBoundCopyPropertyValue() " +
" returns the correct clipboard value");
let expectedPattern = "helvetica,sans-serif";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function IUI_boundCopyPropValueCheck() {
return checkClipboardData(expectedPattern);
},
InspectorUI.cssRuleViewBoundCopyPropertyValue,
checkCopySelection, checkCopySelection);
}
function checkCopySelection()
{
let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
let contentDoc = ruleView.contentDocument;
let props = contentDoc.querySelectorAll(".ruleview-property");
let range = document.createRange();
range.setStart(props[0], 0);
range.setEnd(props[4], 8);
ruleView.contentWindow.getSelection().addRange(range);
info("Checking that InspectorUI.cssRuleViewBoundCopy() returns the correct" +
"clipboard value");
let expectedPattern = " margin: 10em;[\\r\\n]+" +
" font-size: 14pt;[\\r\\n]+" +
" font-family: helvetica,sans-serif;[\\r\\n]+" +
" color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
"}[\\r\\n]+" +
"html {[\\r\\n]+" +
" color: rgb\\(0, 0, 0\\);[\\r\\n]*";
info("Expected pattern: " + expectedPattern);
SimpleTest.waitForClipboard(function IUI_boundCopyCheck() {
return checkClipboardData(expectedPattern);
},InspectorUI.cssRuleViewBoundCopy, finishup, finishup);
}
function checkClipboardData(aExpectedPattern)
{
let actual = SpecialPowers.getClipboardData("text/unicode");
let expectedRegExp = new RegExp(aExpectedPattern, "g");
return expectedRegExp.test(actual);
}
function finishup()
{
InspectorUI.closeInspectorUI();
gBrowser.removeCurrentTab();
doc = null;
finish();
}
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
true);
doc = content.document;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html,<p>rule view context menu test</p>";
}

View File

@ -144,6 +144,7 @@
@BINPATH@/components/directory.xpt
@BINPATH@/components/docshell.xpt
@BINPATH@/components/dom.xpt
@BINPATH@/components/dom_apps.xpt
@BINPATH@/components/dom_base.xpt
#ifdef MOZ_B2G_RIL
@BINPATH@/components/dom_telephony.xpt
@ -410,6 +411,8 @@
@BINPATH@/components/TelemetryPing.manifest
@BINPATH@/components/messageWakeupService.js
@BINPATH@/components/messageWakeupService.manifest
@BINPATH@/components/Webapps.js
@BINPATH@/components/Webapps.manifest
@BINPATH@/components/ContactManager.js
@BINPATH@/components/ContactManager.manifest

View File

@ -213,8 +213,6 @@ can reach it easily. -->
<!ENTITY scratchpad.keycode "VK_F4">
<!ENTITY scratchpad.keytext "F4">
<!ENTITY inspectButton.label "Inspect">
<!ENTITY inspectButton.accesskey "I">
<!ENTITY inspectCloseButton.tooltiptext "Close Inspector">
<!ENTITY inspectorHTMLCopyInner.label "Copy Inner HTML">

View File

@ -334,6 +334,12 @@ telemetryYesButtonAccessKey = Y
telemetryNoButtonLabel = No
telemetryNoButtonAccessKey = N
# Webapps notification popup
webapps.install = Install
webapps.install.accesskey = I
#LOCALIZATION NOTE (webapps.requestInstall) %1$S is the web app name, %2$S is the site from which the web app is installed
webapps.requestInstall = Do you want to install "%1$S" from this site (%2$S)?
# Keyword.URL reset prompt
# LOCALIZATION NOTE (keywordPrompt.message):
# - %1$S is brandShortName

Some files were not shown because too many files have changed in this diff Show More