mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 13:55:43 +00:00
merge backout
This commit is contained in:
commit
4912b653ef
5
.hgtags
5
.hgtags
@ -73,3 +73,8 @@ c0983049bcaa9551e5f276d5a77ce154c151e0b0 AURORA_BASE_20110927
|
||||
54bfd8bf682e295ffd7f22fa921ca343957b6c1c AURORA_BASE_20111108
|
||||
a8506ab2c65480cf2f85f54e203ea746522c62bb AURORA_BASE_20111220
|
||||
462c726144bc1fb45b61e774f64ac5d61b4e047c UPDATE_PACKAGING_R16
|
||||
bbc7014db2de49e2301680d2a86be8a53108a88a AURORA_BASE_20120131
|
||||
bbc7014db2de49e2301680d2a86be8a53108a88a AURORA_BASE_20120131
|
||||
0000000000000000000000000000000000000000 AURORA_BASE_20120131
|
||||
0000000000000000000000000000000000000000 AURORA_BASE_20120131
|
||||
bbc7014db2de49e2301680d2a86be8a53108a88a AURORA_BASE_20120131
|
||||
|
@ -45,9 +45,7 @@ include $(DEPTH)/config/autoconf.mk
|
||||
MODULE = accessibility
|
||||
DIRS = public src build
|
||||
|
||||
ifdef ENABLE_TESTS
|
||||
DIRS += tests
|
||||
endif
|
||||
TEST_DIRS += tests
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
|
@ -62,10 +62,12 @@ XPIDLSRCS = \
|
||||
nsIAccessibleProvider.idl \
|
||||
nsIAccessibleSelectable.idl \
|
||||
nsIAccessNode.idl \
|
||||
nsIAccessibleCursorable.idl \
|
||||
nsIAccessibleEvent.idl \
|
||||
nsIAccessibleEditableText.idl \
|
||||
nsIAccessibleHyperLink.idl \
|
||||
nsIAccessibleHyperText.idl \
|
||||
nsIAccessiblePivot.idl \
|
||||
nsIAccessibleTable.idl \
|
||||
nsIAccessibleText.idl \
|
||||
nsIAccessibleValue.idl \
|
||||
|
59
accessible/public/nsIAccessibleCursorable.idl
Normal file
59
accessible/public/nsIAccessibleCursorable.idl
Normal file
@ -0,0 +1,59 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2012
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Eitan Isaacson <eitan@monotonous.org> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIAccessiblePivot;
|
||||
|
||||
/**
|
||||
* An interface implemented by an accessible object that has an associated
|
||||
* virtual cursor. Typically, a top-level application or content document.
|
||||
* A virtual cursor is an implementation of nsIAccessiblePivot that provides an
|
||||
* exclusive spot in the cursorable's subtree, this could be used to create a
|
||||
* pseudo-focus or caret browsing experience that is centered around the
|
||||
* accessibility API.
|
||||
*/
|
||||
[scriptable, uuid(5452dea5-d235-496f-8757-3ca016ff49ff)]
|
||||
interface nsIAccessibleCursorable : nsISupports
|
||||
{
|
||||
/**
|
||||
* The virtual cursor pivot this object manages.
|
||||
*/
|
||||
readonly attribute nsIAccessiblePivot virtualCursor;
|
||||
};
|
@ -441,10 +441,15 @@ interface nsIAccessibleEvent : nsISupports
|
||||
*/
|
||||
const unsigned long EVENT_OBJECT_ATTRIBUTE_CHANGED = 0x0055;
|
||||
|
||||
/**
|
||||
* A cursorable's virtual cursor has changed.
|
||||
*/
|
||||
const unsigned long EVENT_VIRTUALCURSOR_CHANGED = 0x0056;
|
||||
|
||||
/**
|
||||
* Help make sure event map does not get out-of-line.
|
||||
*/
|
||||
const unsigned long EVENT_LAST_ENTRY = 0x0056;
|
||||
const unsigned long EVENT_LAST_ENTRY = 0x0057;
|
||||
|
||||
/**
|
||||
* The type of event, based on the enumerated event values
|
||||
|
221
accessible/public/nsIAccessiblePivot.idl
Normal file
221
accessible/public/nsIAccessiblePivot.idl
Normal file
@ -0,0 +1,221 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Eitan Isaacson <eitan@monotonous.org> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
typedef short TextBoundaryType;
|
||||
|
||||
interface nsIAccessible;
|
||||
interface nsIAccessibleText;
|
||||
interface nsIAccessibleTraversalRule;
|
||||
interface nsIAccessiblePivotObserver;
|
||||
|
||||
/**
|
||||
* The pivot interface encapsulates a reference to a single place in an accessible
|
||||
* subtree. The pivot is a point or a range in the accessible tree. This interface
|
||||
* provides traversal methods to move the pivot to next/prev state that complies
|
||||
* to a given rule.
|
||||
*/
|
||||
[scriptable, uuid(689058ae-e301-444f-acb0-b5c2b189f350)]
|
||||
interface nsIAccessiblePivot : nsISupports
|
||||
{
|
||||
const TextBoundaryType CHAR_BOUNDARY = 0;
|
||||
const TextBoundaryType WORD_BOUNDARY = 1;
|
||||
const TextBoundaryType LINE_BOUNDARY = 2;
|
||||
const TextBoundaryType ATTRIBUTE_RANGE_BOUNDARY = 3;
|
||||
|
||||
/**
|
||||
* The accessible the pivot is currently pointed at.
|
||||
*/
|
||||
attribute nsIAccessible position;
|
||||
|
||||
/**
|
||||
* The root of the subtree in which the pivot traverses.
|
||||
*/
|
||||
readonly attribute nsIAccessible root;
|
||||
|
||||
/**
|
||||
* The start offset of the text range the pivot points at, otherwise -1.
|
||||
*/
|
||||
readonly attribute long startOffset;
|
||||
|
||||
/**
|
||||
* The end offset of the text range the pivot points at, otherwise -1.
|
||||
*/
|
||||
readonly attribute long endOffset;
|
||||
|
||||
/**
|
||||
* Set the pivot's text range in a text accessible.
|
||||
*
|
||||
* @param aTextAccessible [in] the text accessible that contains the desired
|
||||
* range.
|
||||
* @param aStartOffset [in] the start offset to set.
|
||||
* @param aEndOffset [in] the end offset to set.
|
||||
* @throws NS_ERROR_INVALID_ARG when the offset exceeds the accessible's
|
||||
* character count.
|
||||
*/
|
||||
void setTextRange(in nsIAccessibleText aTextAccessible,
|
||||
in long aStartOffset, in long aEndOffset);
|
||||
|
||||
/**
|
||||
* Move pivot to next object complying to given traversal rule.
|
||||
*
|
||||
* @param aRule [in] traversal rule to use.
|
||||
* @return true on success, false if there are no new nodes to traverse to.
|
||||
*/
|
||||
boolean moveNext(in nsIAccessibleTraversalRule aRule);
|
||||
|
||||
/**
|
||||
* Move pivot to previous object complying to given traversal rule.
|
||||
*
|
||||
* @param aRule [in] traversal rule to use.
|
||||
* @return true on success, false if there are no new nodes to traverse to.
|
||||
*/
|
||||
boolean movePrevious(in nsIAccessibleTraversalRule aRule);
|
||||
|
||||
/**
|
||||
* Move pivot to first object in subtree complying to given traversal rule.
|
||||
*
|
||||
* @param aRule [in] traversal rule to use.
|
||||
* @return true on success, false if there are no new nodes to traverse to.
|
||||
*/
|
||||
boolean moveFirst(in nsIAccessibleTraversalRule aRule);
|
||||
|
||||
/**
|
||||
* Move pivot to last object in subtree complying to given traversal rule.
|
||||
*
|
||||
* @param aRule [in] traversal rule to use.
|
||||
* @return true on success, false if there are no new nodes to traverse to.
|
||||
*/
|
||||
boolean moveLast(in nsIAccessibleTraversalRule aRule);
|
||||
|
||||
/**
|
||||
* Move pivot to next text range.
|
||||
*
|
||||
* @param aBoundary [in] type of boundary for next text range, character, word,
|
||||
* etc.
|
||||
* @return true on success, false if there are is no more text.
|
||||
*/
|
||||
boolean moveNextByText(in TextBoundaryType aBoundary);
|
||||
|
||||
/**
|
||||
* Move pivot to previous text range.
|
||||
*
|
||||
* @param aBoundary [in] type of boundary for previous text range, character,
|
||||
* word, etc.
|
||||
* @return true on success, false if there are is no more text.
|
||||
*/
|
||||
boolean movePreviousByText(in TextBoundaryType aBoundary);
|
||||
|
||||
/**
|
||||
* Add an observer for pivot changes.
|
||||
*
|
||||
* @param aObserver [in] the observer object to be notified of pivot changes.
|
||||
*/
|
||||
void addObserver(in nsIAccessiblePivotObserver aObserver);
|
||||
|
||||
/**
|
||||
* Remove an observer for pivot changes.
|
||||
*
|
||||
* @param aObserver [in] the observer object to remove from being notified.
|
||||
*/
|
||||
void removeObserver(in nsIAccessiblePivotObserver aObserver);
|
||||
};
|
||||
|
||||
/**
|
||||
* An observer interface for pivot changes.
|
||||
*/
|
||||
[scriptable, uuid(b6508c5e-c081-467d-835c-613eedf9ee9b)]
|
||||
interface nsIAccessiblePivotObserver : nsISupports
|
||||
{
|
||||
/**
|
||||
* Called when the pivot changes.
|
||||
*
|
||||
* @param aPivot [in] the pivot that has changed.
|
||||
* @param aOldAccessible [in] the old pivot position before the change, or null.
|
||||
* @param aOldStart [in] the old start offset, or -1.
|
||||
* @param aOldEnd [in] the old end offset, or -1.
|
||||
*/
|
||||
void onPivotChanged(in nsIAccessiblePivot aPivot,
|
||||
in nsIAccessible aOldAccessible,
|
||||
in long aOldStart, in long aOldEnd);
|
||||
};
|
||||
|
||||
[scriptable, uuid(307d98b6-dba9-49cf-ba17-ef8b053044eb)]
|
||||
interface nsIAccessibleTraversalRule : nsISupports
|
||||
{
|
||||
/* Ignore this accessible object */
|
||||
const unsigned short FILTER_IGNORE = 0x0;
|
||||
/* Accept this accessible object */
|
||||
const unsigned short FILTER_MATCH = 0x1;
|
||||
/* Don't traverse accessibles children */
|
||||
const unsigned short FILTER_IGNORE_SUBTREE = 0x2;
|
||||
|
||||
/* Pre-filters */
|
||||
const unsigned long PREFILTER_INVISIBLE = 0x00000001;
|
||||
const unsigned long PREFILTER_OFFSCREEN = 0x00000002;
|
||||
const unsigned long PREFILTER_NOT_FOCUSABLE = 0x00000004;
|
||||
|
||||
/**
|
||||
* Pre-filter bitfield to filter out obviously ignorable nodes and lighten
|
||||
* the load on match().
|
||||
*/
|
||||
readonly attribute unsigned long preFilter;
|
||||
|
||||
/**
|
||||
* Retrieve a list of roles that the traversal rule should test for. Any node
|
||||
* with a role not in this list will automatically be ignored. An empty list
|
||||
* will match all roles. It should be assumed that this method is called once
|
||||
* at the start of a traversal, so changing the method's return result after
|
||||
* that would have no affect.
|
||||
*
|
||||
* @param aRoles [out] an array of the roles to match.
|
||||
* @param aCount [out] the length of the array.
|
||||
*/
|
||||
void getMatchRoles([array, size_is(aCount)]out unsigned long aRoles,
|
||||
[retval]out unsigned long aCount);
|
||||
|
||||
/**
|
||||
* Determines if a given accessible is to be accepted in our traversal rule
|
||||
*
|
||||
* @param aAccessible [in] accessible to examine.
|
||||
* @return a bitfield of FILTER_MATCH and FILTER_IGNORE_SUBTREE.
|
||||
*/
|
||||
unsigned short match(in nsIAccessible aAccessible);
|
||||
};
|
@ -45,7 +45,7 @@ interface nsIPresShell;
|
||||
interface nsIDOMWindow;
|
||||
interface nsIAccessNode;
|
||||
interface nsIDOMDOMStringList;
|
||||
|
||||
interface nsIAccessiblePivot;
|
||||
|
||||
/**
|
||||
* An interface for in-process accessibility clients
|
||||
@ -112,6 +112,14 @@ interface nsIAccessibleRetrieval : nsISupports
|
||||
* @return cached accessible for the given DOM node if any
|
||||
*/
|
||||
nsIAccessible getAccessibleFromCache(in nsIDOMNode aNode);
|
||||
|
||||
/**
|
||||
* Create a new pivot for tracking a position and traversing a subtree.
|
||||
*
|
||||
* @param aRoot [in] the accessible root for the pivot
|
||||
* @return a new pivot
|
||||
*/
|
||||
nsIAccessiblePivot createAccessiblePivot(in nsIAccessible aRoot);
|
||||
};
|
||||
|
||||
|
||||
|
@ -65,6 +65,7 @@ CPPSRCS = \
|
||||
nsAccUtils.cpp \
|
||||
nsAccessibilityService.cpp \
|
||||
nsAccessible.cpp \
|
||||
nsAccessiblePivot.cpp \
|
||||
nsAccTreeWalker.cpp \
|
||||
nsBaseWidgetAccessible.cpp \
|
||||
nsEventShell.cpp \
|
||||
|
@ -56,7 +56,13 @@ namespace statistics {
|
||||
* Report that ISimpleDOM* has been used.
|
||||
*/
|
||||
inline void ISimpleDOMUsed()
|
||||
{ Telemetry::Accumulate(Telemetry::ISIMPLE_DOM_USAGE, 1); }
|
||||
{
|
||||
static bool firstTime = true;
|
||||
if (firstTime) {
|
||||
Telemetry::Accumulate(Telemetry::ISIMPLE_DOM_USAGE, 1);
|
||||
firstTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report that IAccessibleTable has been used.
|
||||
|
@ -105,7 +105,7 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
|
||||
roles::PUSHBUTTON,
|
||||
kUseMapRole,
|
||||
eNoValue,
|
||||
eClickAction,
|
||||
ePressAction,
|
||||
eNoLiveAttr,
|
||||
kNoReqStates,
|
||||
eARIAPressed
|
||||
@ -219,15 +219,6 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
|
||||
eNoLiveAttr,
|
||||
kNoReqStates
|
||||
},
|
||||
{
|
||||
"label",
|
||||
roles::LABEL,
|
||||
kUseMapRole,
|
||||
eNoValue,
|
||||
eNoAction,
|
||||
eNoLiveAttr,
|
||||
kNoReqStates
|
||||
},
|
||||
{
|
||||
"link",
|
||||
roles::LINK,
|
||||
|
@ -78,6 +78,7 @@ enum EActionRule
|
||||
eNoAction,
|
||||
eActivateAction,
|
||||
eClickAction,
|
||||
ePressAction,
|
||||
eCheckUncheckAction,
|
||||
eExpandAction,
|
||||
eJumpAction,
|
||||
|
@ -40,6 +40,7 @@
|
||||
|
||||
// NOTE: alphabetically ordered
|
||||
#include "nsAccessibilityService.h"
|
||||
#include "nsAccessiblePivot.h"
|
||||
#include "nsCoreUtils.h"
|
||||
#include "nsAccUtils.h"
|
||||
#include "nsApplicationAccessibleWrap.h"
|
||||
@ -864,6 +865,23 @@ nsAccessibilityService::GetAccessibleFromCache(nsIDOMNode* aNode,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessibilityService::CreateAccessiblePivot(nsIAccessible* aRoot,
|
||||
nsIAccessiblePivot** aPivot)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aPivot);
|
||||
NS_ENSURE_ARG(aRoot);
|
||||
*aPivot = nsnull;
|
||||
|
||||
nsRefPtr<nsAccessible> accessibleRoot(do_QueryObject(aRoot));
|
||||
NS_ENSURE_TRUE(accessibleRoot, NS_ERROR_INVALID_ARG);
|
||||
|
||||
nsAccessiblePivot* pivot = new nsAccessiblePivot(accessibleRoot);
|
||||
NS_ADDREF(*aPivot = pivot);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIAccesibilityService
|
||||
nsAccessible*
|
||||
nsAccessibilityService::GetAccessibleInShell(nsINode* aNode,
|
||||
|
@ -534,6 +534,7 @@ static const char kEventTypeNames[][40] = {
|
||||
"hypertext changed", // EVENT_HYPERTEXT_CHANGED
|
||||
"hypertext links count changed", // EVENT_HYPERTEXT_NLINKS_CHANGED
|
||||
"object attribute changed", // EVENT_OBJECT_ATTRIBUTE_CHANGED
|
||||
"virtual cursor changed" // EVENT_VIRTUALCURSOR_CHANGED
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -593,30 +593,13 @@ nsAccessible::VisibilityState()
|
||||
{
|
||||
PRUint64 vstates = states::INVISIBLE | states::OFFSCREEN;
|
||||
|
||||
// We need to check the parent chain for visibility.
|
||||
nsAccessible* accessible = this;
|
||||
do {
|
||||
// We don't want background tab page content to be aggressively invisible.
|
||||
// Otherwise this foils screen reader virtual buffer caches.
|
||||
roles::Role role = accessible->Role();
|
||||
if (role == roles::PROPERTYPAGE || role == roles::PANE)
|
||||
break;
|
||||
|
||||
nsIFrame* frame = accessible->GetFrame();
|
||||
if (!frame)
|
||||
return vstates;
|
||||
|
||||
const nsIView* view = frame->GetView();
|
||||
if (view && view->GetVisibility() == nsViewVisibility_kHide)
|
||||
return vstates;
|
||||
|
||||
} while (accessible = accessible->Parent());
|
||||
|
||||
nsIFrame* frame = GetFrame();
|
||||
if (!frame)
|
||||
return vstates;
|
||||
|
||||
const nsCOMPtr<nsIPresShell> shell(GetPresShell());
|
||||
if (!shell)
|
||||
return vstates;
|
||||
|
||||
// We need to know if at least a kMinPixels around the object is visible,
|
||||
// otherwise it will be marked states::OFFSCREEN.
|
||||
@ -644,6 +627,10 @@ nsAccessible::VisibilityState()
|
||||
|
||||
}
|
||||
|
||||
// XXX Do we really need to cross from content to chrome ancestor?
|
||||
if (!frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY))
|
||||
return vstates;
|
||||
|
||||
// Assume we are visible enough.
|
||||
return vstates &= ~states::INVISIBLE;
|
||||
}
|
||||
@ -1387,6 +1374,30 @@ nsAccessible::GetAttributesInternal(nsIPersistentProperties *aAttributes)
|
||||
if (NS_SUCCEEDED(rv))
|
||||
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textIndent, value);
|
||||
|
||||
// Expose 'margin-left' attribute.
|
||||
rv = GetComputedStyleValue(EmptyString(), NS_LITERAL_STRING("margin-left"),
|
||||
value);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::marginLeft, value);
|
||||
|
||||
// Expose 'margin-right' attribute.
|
||||
rv = GetComputedStyleValue(EmptyString(), NS_LITERAL_STRING("margin-right"),
|
||||
value);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::marginRight, value);
|
||||
|
||||
// Expose 'margin-top' attribute.
|
||||
rv = GetComputedStyleValue(EmptyString(), NS_LITERAL_STRING("margin-top"),
|
||||
value);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::marginTop, value);
|
||||
|
||||
// Expose 'margin-bottom' attribute.
|
||||
rv = GetComputedStyleValue(EmptyString(), NS_LITERAL_STRING("margin-bottom"),
|
||||
value);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::marginBottom, value);
|
||||
|
||||
// Expose draggable object attribute?
|
||||
nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mContent);
|
||||
if (htmlElement) {
|
||||
@ -1841,6 +1852,10 @@ nsAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
|
||||
aName.AssignLiteral("click");
|
||||
return NS_OK;
|
||||
|
||||
case ePressAction:
|
||||
aName.AssignLiteral("press");
|
||||
return NS_OK;
|
||||
|
||||
case eCheckUncheckAction:
|
||||
if (states & states::CHECKED)
|
||||
aName.AssignLiteral("uncheck");
|
||||
|
526
accessible/src/base/nsAccessiblePivot.cpp
Normal file
526
accessible/src/base/nsAccessiblePivot.cpp
Normal file
@ -0,0 +1,526 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Eitan Isaacson <eitan@monotonous.org> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsAccessiblePivot.h"
|
||||
|
||||
#include "nsAccessible.h"
|
||||
#include "nsAccUtils.h"
|
||||
#include "nsHyperTextAccessible.h"
|
||||
#include "States.h"
|
||||
|
||||
#include "nsArrayUtils.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
|
||||
/**
|
||||
* An object that stores a given traversal rule during
|
||||
*/
|
||||
class RuleCache
|
||||
{
|
||||
public:
|
||||
RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule),
|
||||
mAcceptRoles(nsnull) { }
|
||||
~RuleCache () {
|
||||
if (mAcceptRoles)
|
||||
nsMemory::Free(mAcceptRoles);
|
||||
}
|
||||
|
||||
nsresult ApplyFilter(nsAccessible* aAccessible, PRUint16* aResult);
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIAccessibleTraversalRule> mRule;
|
||||
PRUint32* mAcceptRoles;
|
||||
PRUint32 mAcceptRolesLength;
|
||||
PRUint32 mPreFilter;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsAccessiblePivot
|
||||
|
||||
nsAccessiblePivot::nsAccessiblePivot(nsAccessible* aRoot) :
|
||||
mRoot(aRoot), mPosition(nsnull),
|
||||
mStartOffset(-1), mEndOffset(-1)
|
||||
{
|
||||
NS_ASSERTION(aRoot, "A root accessible is required");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsISupports
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccessiblePivot)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAccessiblePivot)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRoot, nsIAccessible)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mPosition, nsIAccessible)
|
||||
PRUint32 i, length = tmp->mObservers.Length(); \
|
||||
for (i = 0; i < length; ++i) {
|
||||
cb.NoteXPCOMChild(tmp->mObservers.ElementAt(i).get());
|
||||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAccessiblePivot)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRoot)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPosition)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mObservers)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot)
|
||||
NS_INTERFACE_MAP_ENTRY(nsAccessiblePivot)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIAccessiblePivot
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::GetRoot(nsIAccessible** aRoot)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRoot);
|
||||
|
||||
NS_IF_ADDREF(*aRoot = mRoot);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::GetPosition(nsIAccessible** aPosition)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aPosition);
|
||||
|
||||
NS_IF_ADDREF(*aPosition = mPosition);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::SetPosition(nsIAccessible* aPosition)
|
||||
{
|
||||
nsRefPtr<nsAccessible> secondPosition;
|
||||
|
||||
if (aPosition) {
|
||||
secondPosition = do_QueryObject(aPosition);
|
||||
if (!secondPosition || !IsRootDescendant(secondPosition))
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Swap old position with new position, saves us an AddRef/Release.
|
||||
mPosition.swap(secondPosition);
|
||||
PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset;
|
||||
mStartOffset = mEndOffset = -1;
|
||||
NotifyPivotChanged(secondPosition, oldStart, oldEnd);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::GetStartOffset(PRInt32* aStartOffset)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aStartOffset);
|
||||
|
||||
*aStartOffset = mStartOffset;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::GetEndOffset(PRInt32* aEndOffset)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aEndOffset);
|
||||
|
||||
*aEndOffset = mEndOffset;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible,
|
||||
PRInt32 aStartOffset, PRInt32 aEndOffset)
|
||||
{
|
||||
NS_ENSURE_ARG(aTextAccessible);
|
||||
|
||||
// Check that start offset is smaller than end offset, and that if a value is
|
||||
// smaller than 0, both should be -1.
|
||||
NS_ENSURE_TRUE(aStartOffset <= aEndOffset &&
|
||||
(aStartOffset >= 0 || (aStartOffset != -1 && aEndOffset != -1)),
|
||||
NS_ERROR_INVALID_ARG);
|
||||
|
||||
nsRefPtr<nsHyperTextAccessible> newPosition = do_QueryObject(aTextAccessible);
|
||||
if (!newPosition || !IsRootDescendant(newPosition))
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
// Make sure the given offsets don't exceed the character count.
|
||||
PRInt32 charCount = newPosition->CharacterCount();
|
||||
|
||||
if (aEndOffset > charCount)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset;
|
||||
mStartOffset = aStartOffset;
|
||||
mEndOffset = aEndOffset;
|
||||
|
||||
nsRefPtr<nsAccessible> oldPosition = mPosition.forget();
|
||||
mPosition = newPosition.forget();
|
||||
|
||||
NotifyPivotChanged(oldPosition, oldStart, oldEnd);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Traversal functions
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule, bool* aResult)
|
||||
{
|
||||
NS_ENSURE_ARG(aResult);
|
||||
NS_ENSURE_ARG(aRule);
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
nsAccessible* accessible = SearchForward(mPosition, aRule, false, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aResult = accessible;
|
||||
if (*aResult)
|
||||
MovePivotInternal(accessible);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule, bool* aResult)
|
||||
{
|
||||
NS_ENSURE_ARG(aResult);
|
||||
NS_ENSURE_ARG(aRule);
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
nsAccessible* accessible = SearchBackward(mPosition, aRule, false, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aResult = accessible;
|
||||
if (*aResult)
|
||||
MovePivotInternal(accessible);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule, bool* aResult)
|
||||
{
|
||||
NS_ENSURE_ARG(aResult);
|
||||
NS_ENSURE_ARG(aRule);
|
||||
nsresult rv = NS_OK;
|
||||
nsAccessible* accessible = SearchForward(mRoot, aRule, true, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aResult = accessible;
|
||||
if (*aResult)
|
||||
MovePivotInternal(accessible);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule, bool* aResult)
|
||||
{
|
||||
NS_ENSURE_ARG(aResult);
|
||||
NS_ENSURE_ARG(aRule);
|
||||
|
||||
*aResult = false;
|
||||
nsresult rv = NS_OK;
|
||||
nsAccessible* lastAccessible = mRoot;
|
||||
nsAccessible* accessible = nsnull;
|
||||
|
||||
// First got to the last accessible in pre-order
|
||||
while (lastAccessible->HasChildren())
|
||||
lastAccessible = lastAccessible->LastChild();
|
||||
|
||||
// Search backwards from last accessible and find the last occurrence in the doc
|
||||
accessible = SearchBackward(lastAccessible, aRule, true, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aResult = accessible;
|
||||
if (*aResult)
|
||||
MovePivotInternal(accessible);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// TODO: Implement
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult)
|
||||
{
|
||||
NS_ENSURE_ARG(aResult);
|
||||
|
||||
*aResult = false;
|
||||
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// TODO: Implement
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult)
|
||||
{
|
||||
NS_ENSURE_ARG(aResult);
|
||||
|
||||
*aResult = false;
|
||||
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// Observer functions
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver* aObserver)
|
||||
{
|
||||
NS_ENSURE_ARG(aObserver);
|
||||
|
||||
mObservers.AppendElement(aObserver);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver* aObserver)
|
||||
{
|
||||
NS_ENSURE_ARG(aObserver);
|
||||
|
||||
return mObservers.RemoveElement(aObserver) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Private utility methods
|
||||
|
||||
bool
|
||||
nsAccessiblePivot::IsRootDescendant(nsAccessible* aAccessible)
|
||||
{
|
||||
nsAccessible* accessible = aAccessible;
|
||||
do {
|
||||
if (accessible == mRoot)
|
||||
return true;
|
||||
} while ((accessible = accessible->Parent()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
nsAccessiblePivot::MovePivotInternal(nsAccessible* aPosition)
|
||||
{
|
||||
nsRefPtr<nsAccessible> oldPosition = mPosition.forget();
|
||||
mPosition = aPosition;
|
||||
PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset;
|
||||
mStartOffset = mEndOffset = -1;
|
||||
|
||||
NotifyPivotChanged(oldPosition, oldStart, oldEnd);
|
||||
}
|
||||
|
||||
nsAccessible*
|
||||
nsAccessiblePivot::SearchBackward(nsAccessible* aAccessible,
|
||||
nsIAccessibleTraversalRule* aRule,
|
||||
bool searchCurrent,
|
||||
nsresult* rv)
|
||||
{
|
||||
*rv = NS_OK;
|
||||
|
||||
// Initial position could be unset, in that case return null.
|
||||
if (!aAccessible)
|
||||
return nsnull;
|
||||
|
||||
RuleCache cache(aRule);
|
||||
nsAccessible* accessible = aAccessible;
|
||||
|
||||
PRUint16 filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
|
||||
|
||||
if (searchCurrent) {
|
||||
*rv = cache.ApplyFilter(accessible, &filtered);
|
||||
NS_ENSURE_SUCCESS(*rv, nsnull);
|
||||
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
|
||||
return accessible;
|
||||
}
|
||||
|
||||
while (accessible != mRoot) {
|
||||
nsAccessible* parent = accessible->Parent();
|
||||
PRInt32 idxInParent = accessible->IndexInParent();
|
||||
while (idxInParent > 0) {
|
||||
if (!(accessible = parent->GetChildAt(--idxInParent)))
|
||||
continue;
|
||||
|
||||
*rv = cache.ApplyFilter(accessible, &filtered);
|
||||
NS_ENSURE_SUCCESS(*rv, nsnull);
|
||||
|
||||
nsAccessible* lastChild;
|
||||
while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
|
||||
(lastChild = accessible->LastChild())) {
|
||||
parent = accessible;
|
||||
accessible = lastChild;
|
||||
*rv = cache.ApplyFilter(accessible, &filtered);
|
||||
NS_ENSURE_SUCCESS(*rv, nsnull);
|
||||
}
|
||||
|
||||
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
|
||||
return accessible;
|
||||
}
|
||||
|
||||
if (!(accessible = parent))
|
||||
break;
|
||||
|
||||
*rv = cache.ApplyFilter(accessible, &filtered);
|
||||
NS_ENSURE_SUCCESS(*rv, nsnull);
|
||||
|
||||
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
|
||||
return accessible;
|
||||
}
|
||||
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
nsAccessible*
|
||||
nsAccessiblePivot::SearchForward(nsAccessible* aAccessible,
|
||||
nsIAccessibleTraversalRule* aRule,
|
||||
bool searchCurrent,
|
||||
nsresult* rv)
|
||||
{
|
||||
*rv = NS_OK;
|
||||
|
||||
// Initial position could be not set, in that case begin search from root.
|
||||
nsAccessible *accessible = (!aAccessible) ? mRoot.get() : aAccessible;
|
||||
|
||||
RuleCache cache(aRule);
|
||||
|
||||
PRUint16 filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
|
||||
*rv = cache.ApplyFilter(accessible, &filtered);
|
||||
NS_ENSURE_SUCCESS(*rv, nsnull);
|
||||
if (searchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH))
|
||||
return accessible;
|
||||
|
||||
while (true) {
|
||||
nsAccessible* firstChild = nsnull;
|
||||
while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
|
||||
(firstChild = accessible->FirstChild())) {
|
||||
accessible = firstChild;
|
||||
*rv = cache.ApplyFilter(accessible, &filtered);
|
||||
NS_ENSURE_SUCCESS(*rv, nsnull);
|
||||
|
||||
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
|
||||
return accessible;
|
||||
}
|
||||
|
||||
nsAccessible* sibling = nsnull;
|
||||
nsAccessible* temp = accessible;
|
||||
do {
|
||||
if (temp == mRoot)
|
||||
break;
|
||||
|
||||
sibling = temp->NextSibling();
|
||||
|
||||
if (sibling)
|
||||
break;
|
||||
} while ((temp = temp->Parent()));
|
||||
|
||||
if (!sibling)
|
||||
break;
|
||||
|
||||
accessible = sibling;
|
||||
*rv = cache.ApplyFilter(accessible, &filtered);
|
||||
NS_ENSURE_SUCCESS(*rv, nsnull);
|
||||
|
||||
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
|
||||
return accessible;
|
||||
}
|
||||
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
void
|
||||
nsAccessiblePivot::NotifyPivotChanged(nsAccessible* aOldPosition,
|
||||
PRInt32 aOldStart, PRInt32 aOldEnd)
|
||||
{
|
||||
nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> >::ForwardIterator iter(mObservers);
|
||||
while (iter.HasMore()) {
|
||||
nsIAccessiblePivotObserver* obs = iter.GetNext();
|
||||
obs->OnPivotChanged(this, aOldPosition, aOldStart, aOldEnd);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
RuleCache::ApplyFilter(nsAccessible* aAccessible, PRUint16* aResult)
|
||||
{
|
||||
*aResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
|
||||
|
||||
if (!mAcceptRoles) {
|
||||
nsresult rv = mRule->GetMatchRoles(&mAcceptRoles, &mAcceptRolesLength);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mRule->GetPreFilter(&mPreFilter);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
if (mPreFilter) {
|
||||
PRUint64 state = aAccessible->State();
|
||||
|
||||
if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) &&
|
||||
(state & states::INVISIBLE))
|
||||
return NS_OK;
|
||||
|
||||
if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) &&
|
||||
(state & states::OFFSCREEN))
|
||||
return NS_OK;
|
||||
|
||||
if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) &&
|
||||
!(state & states::FOCUSABLE))
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (mAcceptRolesLength > 0) {
|
||||
PRUint32 accessibleRole = aAccessible->Role();
|
||||
bool matchesRole = false;
|
||||
for (PRUint32 idx = 0; idx < mAcceptRolesLength; idx++) {
|
||||
matchesRole = mAcceptRoles[idx] == accessibleRole;
|
||||
if (matchesRole)
|
||||
break;
|
||||
}
|
||||
if (!matchesRole)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return mRule->Match(aAccessible, aResult);
|
||||
}
|
134
accessible/src/base/nsAccessiblePivot.h
Normal file
134
accessible/src/base/nsAccessiblePivot.h
Normal file
@ -0,0 +1,134 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Eitan Isaacson <eitan@monotonous.org> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef _nsAccessiblePivot_H_
|
||||
#define _nsAccessiblePivot_H_
|
||||
|
||||
#include "nsIAccessiblePivot.h"
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsTObserverArray.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
class nsAccessible;
|
||||
class nsIAccessibleTraversalRule;
|
||||
|
||||
/**
|
||||
* Class represents an accessible pivot.
|
||||
*/
|
||||
class nsAccessiblePivot: public nsIAccessiblePivot
|
||||
{
|
||||
public:
|
||||
nsAccessiblePivot(nsAccessible* aRoot);
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsAccessiblePivot, nsIAccessiblePivot)
|
||||
|
||||
NS_DECL_NSIACCESSIBLEPIVOT
|
||||
|
||||
/*
|
||||
* A simple getter for the pivot's position.
|
||||
*/
|
||||
nsAccessible* Position() { return mPosition; }
|
||||
|
||||
private:
|
||||
nsAccessiblePivot() MOZ_DELETE;
|
||||
nsAccessiblePivot(const nsAccessiblePivot&) MOZ_DELETE;
|
||||
void operator = (const nsAccessiblePivot&) MOZ_DELETE;
|
||||
|
||||
/*
|
||||
* Notify all observers on a pivot change.
|
||||
*/
|
||||
void NotifyPivotChanged(nsAccessible* aOldAccessible,
|
||||
PRInt32 aOldStart, PRInt32 aOldEnd);
|
||||
|
||||
/*
|
||||
* Check to see that the given accessible is in the pivot's subtree.
|
||||
*/
|
||||
bool IsRootDescendant(nsAccessible* aAccessible);
|
||||
|
||||
|
||||
/*
|
||||
* Search in preorder for the first accessible to match the rule.
|
||||
*/
|
||||
nsAccessible* SearchForward(nsAccessible* aAccessible,
|
||||
nsIAccessibleTraversalRule* aRule,
|
||||
bool searchCurrent,
|
||||
nsresult* rv);
|
||||
|
||||
/*
|
||||
* Reverse search in preorder for the first accessible to match the rule.
|
||||
*/
|
||||
nsAccessible* SearchBackward(nsAccessible* aAccessible,
|
||||
nsIAccessibleTraversalRule* aRule,
|
||||
bool searchCurrent,
|
||||
nsresult* rv);
|
||||
|
||||
/*
|
||||
* Update the pivot, and notify observers.
|
||||
*/
|
||||
void MovePivotInternal(nsAccessible* aPosition);
|
||||
|
||||
/*
|
||||
* The root accessible.
|
||||
*/
|
||||
nsRefPtr<nsAccessible> mRoot;
|
||||
|
||||
/*
|
||||
* The current pivot position.
|
||||
*/
|
||||
nsRefPtr<nsAccessible> mPosition;
|
||||
|
||||
/*
|
||||
* The text start offset ofthe pivot.
|
||||
*/
|
||||
PRInt32 mStartOffset;
|
||||
|
||||
/*
|
||||
* The text end offset ofthe pivot.
|
||||
*/
|
||||
PRInt32 mEndOffset;
|
||||
|
||||
/*
|
||||
* The list of pivot-changed observers.
|
||||
*/
|
||||
nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> > mObservers;
|
||||
};
|
||||
|
||||
#endif
|
@ -39,6 +39,7 @@
|
||||
#include "AccIterator.h"
|
||||
#include "nsAccCache.h"
|
||||
#include "nsAccessibilityService.h"
|
||||
#include "nsAccessiblePivot.h"
|
||||
#include "nsAccTreeWalker.h"
|
||||
#include "nsAccUtils.h"
|
||||
#include "nsRootAccessible.h"
|
||||
@ -105,7 +106,8 @@ nsDocAccessible::
|
||||
nsIWeakReference *aShell) :
|
||||
nsHyperTextAccessibleWrap(aRootContent, aShell),
|
||||
mDocument(aDocument), mScrollPositionChangedTicks(0),
|
||||
mLoadState(eTreeConstructionPending), mLoadEventType(0)
|
||||
mLoadState(eTreeConstructionPending), mLoadEventType(0),
|
||||
mVirtualCursor(nsnull)
|
||||
{
|
||||
mFlags |= eDocAccessible;
|
||||
|
||||
@ -125,6 +127,10 @@ nsDocAccessible::
|
||||
// nsAccDocManager creates document accessible when scrollable frame is
|
||||
// available already, it should be safe time to add scroll listener.
|
||||
AddScrollListener();
|
||||
|
||||
// We provide a virtual cursor if this is a root doc or if it's a tab doc.
|
||||
mIsCursorable = (!(mDocument->GetParentDocument()) ||
|
||||
nsCoreUtils::IsTabDocument(mDocument));
|
||||
}
|
||||
|
||||
nsDocAccessible::~nsDocAccessible()
|
||||
@ -142,6 +148,11 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mNotificationController,
|
||||
NotificationController)
|
||||
|
||||
if (tmp->mVirtualCursor) {
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mVirtualCursor,
|
||||
nsAccessiblePivot)
|
||||
}
|
||||
|
||||
PRUint32 i, length = tmp->mChildDocuments.Length();
|
||||
for (i = 0; i < length; ++i) {
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mChildDocuments[i],
|
||||
@ -154,6 +165,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNotificationController)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mVirtualCursor)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildDocuments)
|
||||
tmp->mDependentIDsHash.Clear();
|
||||
tmp->mNodeToAccessibleMap.Clear();
|
||||
@ -167,7 +179,10 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDocAccessible)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleDocument)
|
||||
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleCursorable,
|
||||
mIsCursorable)
|
||||
foundInterface = 0;
|
||||
|
||||
nsresult status;
|
||||
@ -516,6 +531,27 @@ nsDocAccessible::GetChildDocumentAt(PRUint32 aIndex,
|
||||
return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// nsIAccessibleVirtualCursor method
|
||||
NS_IMETHODIMP
|
||||
nsDocAccessible::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aVirtualCursor);
|
||||
*aVirtualCursor = nsnull;
|
||||
|
||||
if (IsDefunct())
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
NS_ENSURE_TRUE(mIsCursorable, NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
if (!mVirtualCursor) {
|
||||
mVirtualCursor = new nsAccessiblePivot(this);
|
||||
mVirtualCursor->AddObserver(this);
|
||||
}
|
||||
|
||||
NS_ADDREF(*aVirtualCursor = mVirtualCursor);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIAccessibleHyperText method
|
||||
NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor)
|
||||
{
|
||||
@ -637,6 +673,11 @@ nsDocAccessible::Shutdown()
|
||||
|
||||
mChildDocuments.Clear();
|
||||
|
||||
if (mVirtualCursor) {
|
||||
mVirtualCursor->RemoveObserver(this);
|
||||
mVirtualCursor = nsnull;
|
||||
}
|
||||
|
||||
mWeakShell = nsnull; // Avoid reentrancy
|
||||
|
||||
mDependentIDsHash.Clear();
|
||||
@ -888,6 +929,20 @@ NS_IMETHODIMP nsDocAccessible::Observe(nsISupports *aSubject, const char *aTopic
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIAccessiblePivotObserver
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
|
||||
nsIAccessible* aOldAccessible,
|
||||
PRInt32 aOldStart, PRInt32 aOldEnd)
|
||||
{
|
||||
nsRefPtr<AccEvent> event = new AccEvent(nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, this);
|
||||
nsEventShell::FireEvent(event);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIDocumentObserver
|
||||
|
||||
|
@ -39,7 +39,9 @@
|
||||
#ifndef _nsDocAccessible_H_
|
||||
#define _nsDocAccessible_H_
|
||||
|
||||
#include "nsIAccessibleCursorable.h"
|
||||
#include "nsIAccessibleDocument.h"
|
||||
#include "nsIAccessiblePivot.h"
|
||||
|
||||
#include "nsEventShell.h"
|
||||
#include "nsHyperTextAccessibleWrap.h"
|
||||
@ -58,6 +60,7 @@
|
||||
#include "nsIDocShellTreeNode.h"
|
||||
|
||||
class nsIScrollableView;
|
||||
class nsAccessiblePivot;
|
||||
|
||||
const PRUint32 kDefaultCacheSize = 256;
|
||||
|
||||
@ -74,8 +77,10 @@ class nsDocAccessible : public nsHyperTextAccessibleWrap,
|
||||
public nsIDocumentObserver,
|
||||
public nsIObserver,
|
||||
public nsIScrollPositionListener,
|
||||
public nsSupportsWeakReference
|
||||
{
|
||||
public nsSupportsWeakReference,
|
||||
public nsIAccessibleCursorable,
|
||||
public nsIAccessiblePivotObserver
|
||||
{
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDocAccessible, nsAccessible)
|
||||
|
||||
@ -84,6 +89,10 @@ class nsDocAccessible : public nsHyperTextAccessibleWrap,
|
||||
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
NS_DECL_NSIACCESSIBLECURSORABLE
|
||||
|
||||
NS_DECL_NSIACCESSIBLEPIVOTOBSERVER
|
||||
|
||||
public:
|
||||
using nsAccessible::GetParent;
|
||||
|
||||
@ -595,6 +604,16 @@ protected:
|
||||
|
||||
nsTArray<nsRefPtr<nsDocAccessible> > mChildDocuments;
|
||||
|
||||
/**
|
||||
* Whether we support nsIAccessibleCursorable, used when querying the interface.
|
||||
*/
|
||||
bool mIsCursorable;
|
||||
|
||||
/**
|
||||
* The virtual cursor of the document when it supports nsIAccessibleCursorable.
|
||||
*/
|
||||
nsRefPtr<nsAccessiblePivot> mVirtualCursor;
|
||||
|
||||
/**
|
||||
* A storage class for pairing content with one of its relation attributes.
|
||||
*/
|
||||
|
@ -574,59 +574,6 @@ nsRootAccessible::Shutdown()
|
||||
nsDocAccessibleWrap::Shutdown();
|
||||
}
|
||||
|
||||
// nsRootAccessible protected member
|
||||
already_AddRefed<nsIDocShellTreeItem>
|
||||
nsRootAccessible::GetContentDocShell(nsIDocShellTreeItem *aStart)
|
||||
{
|
||||
if (!aStart) {
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
PRInt32 itemType;
|
||||
aStart->GetItemType(&itemType);
|
||||
if (itemType == nsIDocShellTreeItem::typeContent) {
|
||||
nsDocAccessible *accDoc = nsAccUtils::GetDocAccessibleFor(aStart);
|
||||
|
||||
// Hidden documents don't have accessibles (like SeaMonkey's sidebar),
|
||||
// they are of no interest for a11y.
|
||||
if (!accDoc)
|
||||
return nsnull;
|
||||
|
||||
// If ancestor chain of accessibles is not completely visible,
|
||||
// don't use this one. This happens for example if it's inside
|
||||
// a background tab (tabbed browsing)
|
||||
nsAccessible* parent = accDoc->Parent();
|
||||
while (parent) {
|
||||
if (parent->State() & states::INVISIBLE)
|
||||
return nsnull;
|
||||
|
||||
if (parent == this)
|
||||
break; // Don't check past original root accessible we started with
|
||||
|
||||
parent = parent->Parent();
|
||||
}
|
||||
|
||||
NS_ADDREF(aStart);
|
||||
return aStart;
|
||||
}
|
||||
nsCOMPtr<nsIDocShellTreeNode> treeNode(do_QueryInterface(aStart));
|
||||
if (treeNode) {
|
||||
PRInt32 subDocuments;
|
||||
treeNode->GetChildCount(&subDocuments);
|
||||
for (PRInt32 count = 0; count < subDocuments; count ++) {
|
||||
nsCOMPtr<nsIDocShellTreeItem> treeItemChild, contentTreeItem;
|
||||
treeNode->GetChildAt(count, getter_AddRefs(treeItemChild));
|
||||
NS_ENSURE_TRUE(treeItemChild, nsnull);
|
||||
contentTreeItem = GetContentDocShell(treeItemChild);
|
||||
if (contentTreeItem) {
|
||||
NS_ADDREF(aStart = contentTreeItem);
|
||||
return aStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
// nsIAccessible method
|
||||
Relation
|
||||
nsRootAccessible::RelationByType(PRUint32 aType)
|
||||
@ -634,14 +581,25 @@ nsRootAccessible::RelationByType(PRUint32 aType)
|
||||
if (!mDocument || aType != nsIAccessibleRelation::RELATION_EMBEDS)
|
||||
return nsDocAccessibleWrap::RelationByType(aType);
|
||||
|
||||
nsCOMPtr<nsIDocShellTreeItem> treeItem =
|
||||
nsCoreUtils::GetDocShellTreeItemFor(mDocument);
|
||||
nsCOMPtr<nsIDocShellTreeItem> contentTreeItem = GetContentDocShell(treeItem);
|
||||
// there may be no content area, so we need a null check
|
||||
if (!contentTreeItem)
|
||||
return Relation();
|
||||
nsIDOMWindow* rootWindow = mDocument->GetWindow();
|
||||
if (rootWindow) {
|
||||
nsCOMPtr<nsIDOMWindow> contentWindow;
|
||||
rootWindow->GetContent(getter_AddRefs(contentWindow));
|
||||
if (contentWindow) {
|
||||
nsCOMPtr<nsIDOMDocument> contentDOMDocument;
|
||||
contentWindow->GetDocument(getter_AddRefs(contentDOMDocument));
|
||||
nsCOMPtr<nsIDocument> contentDocumentNode =
|
||||
do_QueryInterface(contentDOMDocument);
|
||||
if (contentDocumentNode) {
|
||||
nsDocAccessible* contentDocument =
|
||||
GetAccService()->GetDocAccessible(contentDocumentNode);
|
||||
if (contentDocument)
|
||||
return Relation(contentDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Relation(nsAccUtils::GetDocAccessibleFor(contentTreeItem));
|
||||
return Relation();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -127,8 +127,7 @@ protected:
|
||||
|
||||
PRUint32 GetChromeFlags();
|
||||
#endif
|
||||
already_AddRefed<nsIDocShellTreeItem>
|
||||
GetContentDocShell(nsIDocShellTreeItem *aStart);
|
||||
|
||||
nsRefPtr<nsCaretAccessible> mCaretAccessible;
|
||||
};
|
||||
|
||||
|
@ -63,14 +63,6 @@
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
/// the accessible library and cached methods
|
||||
HINSTANCE nsAccessNodeWrap::gmAccLib = nsnull;
|
||||
HINSTANCE nsAccessNodeWrap::gmUserLib = nsnull;
|
||||
LPFNACCESSIBLEOBJECTFROMWINDOW nsAccessNodeWrap::gmAccessibleObjectFromWindow = nsnull;
|
||||
LPFNLRESULTFROMOBJECT nsAccessNodeWrap::gmLresultFromObject = NULL;
|
||||
LPFNNOTIFYWINEVENT nsAccessNodeWrap::gmNotifyWinEvent = nsnull;
|
||||
LPFNGETGUITHREADINFO nsAccessNodeWrap::gmGetGUIThreadInfo = nsnull;
|
||||
|
||||
AccTextChangeEvent* nsAccessNodeWrap::gTextEvent = nsnull;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -591,17 +583,6 @@ __try {
|
||||
|
||||
void nsAccessNodeWrap::InitAccessibility()
|
||||
{
|
||||
if (!gmUserLib) {
|
||||
gmUserLib =::LoadLibraryW(L"USER32.DLL");
|
||||
}
|
||||
|
||||
if (gmUserLib) {
|
||||
if (!gmNotifyWinEvent)
|
||||
gmNotifyWinEvent = (LPFNNOTIFYWINEVENT)GetProcAddress(gmUserLib,"NotifyWinEvent");
|
||||
if (!gmGetGUIThreadInfo)
|
||||
gmGetGUIThreadInfo = (LPFNGETGUITHREADINFO)GetProcAddress(gmUserLib,"GetGUIThreadInfo");
|
||||
}
|
||||
|
||||
Compatibility::Init();
|
||||
|
||||
nsWinUtils::MaybeStartWindowEmulation();
|
||||
@ -678,8 +659,8 @@ nsAccessNodeWrap::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
IAccessible* msaaAccessible = NULL;
|
||||
document->GetNativeInterface((void**)&msaaAccessible); // does an addref
|
||||
if (msaaAccessible) {
|
||||
LRESULT result = LresultFromObject(IID_IAccessible, wParam,
|
||||
msaaAccessible); // does an addref
|
||||
LRESULT result = ::LresultFromObject(IID_IAccessible, wParam,
|
||||
msaaAccessible); // does an addref
|
||||
msaaAccessible->Release(); // release extra addref
|
||||
return result;
|
||||
}
|
||||
@ -698,21 +679,3 @@ nsAccessNodeWrap::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
|
||||
return ::DefWindowProcW(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
STDMETHODIMP_(LRESULT)
|
||||
nsAccessNodeWrap::LresultFromObject(REFIID riid, WPARAM wParam, LPUNKNOWN pAcc)
|
||||
{
|
||||
// open the dll dynamically
|
||||
if (!gmAccLib)
|
||||
gmAccLib =::LoadLibraryW(L"OLEACC.DLL");
|
||||
|
||||
if (gmAccLib) {
|
||||
if (!gmLresultFromObject)
|
||||
gmLresultFromObject = (LPFNLRESULTFROMOBJECT)GetProcAddress(gmAccLib,"LresultFromObject");
|
||||
|
||||
if (gmLresultFromObject)
|
||||
return gmLresultFromObject(riid, wParam, pAcc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -67,8 +67,6 @@
|
||||
|
||||
#include "nsRefPtrHashtable.h"
|
||||
|
||||
typedef LRESULT (STDAPICALLTYPE *LPFNNOTIFYWINEVENT)(DWORD event,HWND hwnd,LONG idObjectType,LONG idObject);
|
||||
typedef LRESULT (STDAPICALLTYPE *LPFNGETGUITHREADINFO)(DWORD idThread, GUITHREADINFO* pgui);
|
||||
|
||||
class AccTextChangeEvent;
|
||||
|
||||
@ -149,18 +147,8 @@ public: // construction, destruction
|
||||
static void InitAccessibility();
|
||||
static void ShutdownAccessibility();
|
||||
|
||||
/// the accessible library and cached methods
|
||||
static HINSTANCE gmAccLib;
|
||||
static HINSTANCE gmUserLib;
|
||||
static LPFNACCESSIBLEOBJECTFROMWINDOW gmAccessibleObjectFromWindow;
|
||||
static LPFNLRESULTFROMOBJECT gmLresultFromObject;
|
||||
static LPFNNOTIFYWINEVENT gmNotifyWinEvent;
|
||||
static LPFNGETGUITHREADINFO gmGetGUIThreadInfo;
|
||||
|
||||
static int FilterA11yExceptions(unsigned int aCode, EXCEPTION_POINTERS *aExceptionInfo);
|
||||
|
||||
static STDMETHODIMP_(LRESULT) LresultFromObject(REFIID riid, WPARAM wParam, LPUNKNOWN pAcc);
|
||||
|
||||
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg,
|
||||
WPARAM WParam, LPARAM lParam);
|
||||
|
||||
|
@ -163,38 +163,6 @@ __try {
|
||||
// IAccessible methods
|
||||
//-----------------------------------------------------
|
||||
|
||||
|
||||
STDMETHODIMP nsAccessibleWrap::AccessibleObjectFromWindow(HWND hwnd,
|
||||
DWORD dwObjectID,
|
||||
REFIID riid,
|
||||
void **ppvObject)
|
||||
{
|
||||
// open the dll dynamically
|
||||
if (!gmAccLib)
|
||||
gmAccLib =::LoadLibraryW(L"OLEACC.DLL");
|
||||
|
||||
if (gmAccLib) {
|
||||
if (!gmAccessibleObjectFromWindow)
|
||||
gmAccessibleObjectFromWindow = (LPFNACCESSIBLEOBJECTFROMWINDOW)GetProcAddress(gmAccLib,"AccessibleObjectFromWindow");
|
||||
|
||||
if (gmAccessibleObjectFromWindow)
|
||||
return gmAccessibleObjectFromWindow(hwnd, dwObjectID, riid, ppvObject);
|
||||
}
|
||||
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
STDMETHODIMP nsAccessibleWrap::NotifyWinEvent(DWORD event,
|
||||
HWND hwnd,
|
||||
LONG idObjectType,
|
||||
LONG idObject)
|
||||
{
|
||||
if (gmNotifyWinEvent)
|
||||
return gmNotifyWinEvent(event, hwnd, idObjectType, idObject);
|
||||
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
STDMETHODIMP nsAccessibleWrap::get_accParent( IDispatch __RPC_FAR *__RPC_FAR *ppdispParent)
|
||||
{
|
||||
__try {
|
||||
@ -211,9 +179,9 @@ __try {
|
||||
nsWinUtils::IsWindowEmulationStarted() &&
|
||||
nsCoreUtils::IsTabDocument(doc->GetDocumentNode())) {
|
||||
HWND hwnd = static_cast<HWND>(doc->GetNativeWindow());
|
||||
if (hwnd && SUCCEEDED(AccessibleObjectFromWindow(hwnd, OBJID_WINDOW,
|
||||
IID_IAccessible,
|
||||
(void**)ppdispParent))) {
|
||||
if (hwnd && SUCCEEDED(::AccessibleObjectFromWindow(hwnd, OBJID_WINDOW,
|
||||
IID_IAccessible,
|
||||
(void**)ppdispParent))) {
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
@ -1590,13 +1558,13 @@ nsAccessibleWrap::FirePlatformEvent(AccEvent* aEvent)
|
||||
#endif
|
||||
|
||||
// Fire MSAA event for client area window.
|
||||
NotifyWinEvent(winEvent, hWnd, OBJID_CLIENT, childID);
|
||||
::NotifyWinEvent(winEvent, hWnd, OBJID_CLIENT, childID);
|
||||
|
||||
// JAWS announces collapsed combobox navigation based on focus events.
|
||||
if (Compatibility::IsJAWS()) {
|
||||
if (eventType == nsIAccessibleEvent::EVENT_SELECTION &&
|
||||
accessible->Role() == roles::COMBOBOX_OPTION) {
|
||||
NotifyWinEvent(EVENT_OBJECT_FOCUS, hWnd, OBJID_CLIENT, childID);
|
||||
::NotifyWinEvent(EVENT_OBJECT_FOCUS, hWnd, OBJID_CLIENT, childID);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1729,8 +1697,9 @@ IDispatch *nsAccessibleWrap::NativeAccessible(nsIAccessible *aXPAccessible)
|
||||
accObject->GetHwnd(&hwnd);
|
||||
if (hwnd) {
|
||||
IDispatch *retval = nsnull;
|
||||
AccessibleObjectFromWindow(reinterpret_cast<HWND>(hwnd),
|
||||
OBJID_WINDOW, IID_IAccessible, (void **) &retval);
|
||||
::AccessibleObjectFromWindow(reinterpret_cast<HWND>(hwnd),
|
||||
OBJID_WINDOW, IID_IAccessible,
|
||||
(void **) &retval);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
@ -331,11 +331,6 @@ public: // construction, destruction
|
||||
|
||||
NS_IMETHOD GetNativeInterface(void **aOutAccessible);
|
||||
|
||||
// NT4 does not have the oleacc that defines these methods. So we define copies here that automatically
|
||||
// load the library only if needed.
|
||||
static STDMETHODIMP AccessibleObjectFromWindow(HWND hwnd,DWORD dwObjectID,REFIID riid,void **ppvObject);
|
||||
static STDMETHODIMP NotifyWinEvent(DWORD event,HWND hwnd,LONG idObjectType,LONG idObject);
|
||||
|
||||
static IDispatch *NativeAccessible(nsIAccessible *aXPAccessible);
|
||||
|
||||
/**
|
||||
|
@ -131,6 +131,7 @@ static const PRUint32 gWinEventMap[] = {
|
||||
IA2_EVENT_HYPERTEXT_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_CHANGED
|
||||
IA2_EVENT_HYPERTEXT_NLINKS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_NLINKS_CHANGED
|
||||
IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED
|
||||
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED
|
||||
kEVENT_LAST_ENTRY // nsIAccessibleEvent::EVENT_LAST_ENTRY
|
||||
};
|
||||
|
||||
|
@ -52,6 +52,7 @@ DIRS = \
|
||||
hyperlink \
|
||||
hypertext \
|
||||
name \
|
||||
pivot \
|
||||
relations \
|
||||
selectable \
|
||||
states \
|
||||
@ -81,6 +82,7 @@ _TEST_FILES =\
|
||||
grid.js \
|
||||
layout.js \
|
||||
name.js \
|
||||
pivot.js \
|
||||
relations.js \
|
||||
role.js \
|
||||
selectable.js \
|
||||
|
@ -49,6 +49,15 @@
|
||||
new scrollingChecker(getAccessible("bottom1"))
|
||||
]
|
||||
},
|
||||
{ // jump again (test for bug 437607)
|
||||
ID: "anchor1",
|
||||
actionName: "jump",
|
||||
actionIndex: 0,
|
||||
events: CLICK_EVENTS,
|
||||
eventSeq: [
|
||||
new scrollingChecker(getAccessible("bottom1"))
|
||||
]
|
||||
},
|
||||
{
|
||||
ID: "anchor2",
|
||||
actionName: "jump",
|
||||
|
@ -27,7 +27,7 @@
|
||||
},
|
||||
{
|
||||
ID: "button",
|
||||
actionName: "click",
|
||||
actionName: "press",
|
||||
events: CLICK_EVENTS
|
||||
},
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=439566
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=460932
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=689540
|
||||
-->
|
||||
<head>
|
||||
<title>CSS-like attributes tests</title>
|
||||
@ -25,7 +26,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=460932
|
||||
var attrs = {
|
||||
"display": computedStyle.display,
|
||||
"text-align": computedStyle.textAlign,
|
||||
"text-indent": computedStyle.textIndent
|
||||
"text-indent": computedStyle.textIndent,
|
||||
"margin-left": computedStyle.marginLeft,
|
||||
"margin-right": computedStyle.marginRight,
|
||||
"margin-top": computedStyle.marginTop,
|
||||
"margin-bottom": computedStyle.marginBottom
|
||||
};
|
||||
testAttrs(aID, attrs, true);
|
||||
}
|
||||
@ -34,7 +39,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=460932
|
||||
{
|
||||
testCSSAttrs("span");
|
||||
testCSSAttrs("div");
|
||||
|
||||
testCSSAttrs("p");
|
||||
testCSSAttrs("p2");
|
||||
|
||||
testCSSAttrs("pml");
|
||||
testCSSAttrs("pmr");
|
||||
testCSSAttrs("pmt");
|
||||
testCSSAttrs("pmb");
|
||||
|
||||
testCSSAttrs("input");
|
||||
testCSSAttrs("table");
|
||||
testCSSAttrs("tr");
|
||||
@ -59,6 +72,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=460932
|
||||
title="text-indent and text-align should really be object attribute">
|
||||
Mozilla Bug 460932
|
||||
</a>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=689540"
|
||||
title="Expose IA2 margin- object attributes">
|
||||
Mozilla Bug 689540
|
||||
</a>
|
||||
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
@ -67,7 +85,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=460932
|
||||
|
||||
<span id="span" role="group">It's span</span>
|
||||
<div id="div">It's div</div>
|
||||
|
||||
<p id="p">It's paragraph"</p>
|
||||
<p id="p2" style="text-indent: 5px">It's another paragraph</p>
|
||||
|
||||
<p id="pml" style="margin-left : 11px;">It's a paragraph with left margin</p>
|
||||
<p id="pmr" style="margin-right : 21px;">It's a paragraph with right margin</p>
|
||||
<p id="pmt" style="margin-top : 31px;">It's a paragraph with top margin</p>
|
||||
<p id="pmb" style="margin-bottom : 41px;">It's a paragraph with bottom margin</p>
|
||||
|
||||
<input id="input"/>
|
||||
<table id="table">
|
||||
<tr id="tr" role="group">
|
||||
|
@ -25,6 +25,14 @@ function browserWindow()
|
||||
return gBrowserContext.browserWnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the document of the browser window.
|
||||
*/
|
||||
function browserDocument()
|
||||
{
|
||||
return browserWindow().document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return tab browser object.
|
||||
*/
|
||||
@ -49,6 +57,22 @@ function currentTabDocument()
|
||||
return currentBrowser().contentDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return browser element of the tab at the given index.
|
||||
*/
|
||||
function browserAt(aIndex)
|
||||
{
|
||||
return tabBrowser().getBrowserAtIndex(aIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return DOM document of the tab at the given index.
|
||||
*/
|
||||
function tabDocumentAt(aIndex)
|
||||
{
|
||||
return browserAt(aIndex).contentDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return input element of address bar.
|
||||
*/
|
||||
|
@ -30,10 +30,13 @@ const nsIAccessibleEditableText = Components.interfaces.nsIAccessibleEditableTex
|
||||
const nsIAccessibleHyperLink = Components.interfaces.nsIAccessibleHyperLink;
|
||||
const nsIAccessibleHyperText = Components.interfaces.nsIAccessibleHyperText;
|
||||
|
||||
const nsIAccessibleCursorable = Components.interfaces.nsIAccessibleCursorable;
|
||||
const nsIAccessibleImage = Components.interfaces.nsIAccessibleImage;
|
||||
const nsIAccessiblePivot = Components.interfaces.nsIAccessiblePivot;
|
||||
const nsIAccessibleSelectable = Components.interfaces.nsIAccessibleSelectable;
|
||||
const nsIAccessibleTable = Components.interfaces.nsIAccessibleTable;
|
||||
const nsIAccessibleTableCell = Components.interfaces.nsIAccessibleTableCell;
|
||||
const nsIAccessibleTraversalRule = Components.interfaces.nsIAccessibleTraversalRule;
|
||||
const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue;
|
||||
|
||||
const nsIObserverService = Components.interfaces.nsIObserverService;
|
||||
|
@ -27,6 +27,7 @@ const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
|
||||
const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
|
||||
const EVENT_TEXT_SELECTION_CHANGED = nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
|
||||
const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
|
||||
const EVENT_VIRTUALCURSOR_CHANGED = nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// General
|
||||
|
@ -45,8 +45,6 @@ relativesrcdir = accessible/events
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# test_scroll.xul disabled for misusing <tabbrowser> (bug 715857)
|
||||
|
||||
_TEST_FILES =\
|
||||
docload_wnd.html \
|
||||
focus.html \
|
||||
@ -82,6 +80,7 @@ _TEST_FILES =\
|
||||
test_menu.xul \
|
||||
test_mutation.html \
|
||||
test_mutation.xhtml \
|
||||
test_scroll.xul \
|
||||
test_selection_aria.html \
|
||||
test_selection.html \
|
||||
test_selection.xul \
|
||||
|
@ -1,13 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
|
||||
<!-- Firefox tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css"
|
||||
type="text/css"?>
|
||||
<!-- SeaMonkey tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://navigator/content/navigator.css"
|
||||
type="text/css"?>
|
||||
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
@ -15,6 +8,8 @@
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/chrome-harness.js"/>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js" />
|
||||
@ -24,95 +19,74 @@
|
||||
src="../states.js" />
|
||||
<script type="application/javascript"
|
||||
src="../events.js" />
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/chrome-harness.js"/>
|
||||
src="../browser.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Hacks to make xul:tabbrowser work
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const CC = Components.classes;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var handleDroppedLink = null;
|
||||
|
||||
var XULBrowserWindow = {
|
||||
isBusy: false,
|
||||
setOverLink: function (link, b) {
|
||||
}
|
||||
};
|
||||
|
||||
var gURLBar = {
|
||||
focused: false
|
||||
};
|
||||
|
||||
var gFindBarInitialized = false;
|
||||
|
||||
function goSetCommandEnabled() {}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
|
||||
function getTabDocument()
|
||||
function getAnchorJumpInTabDocument(aTabIdx)
|
||||
{
|
||||
return getNode("tabBrowser").selectedBrowser.contentDocument;
|
||||
var tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument();
|
||||
return tabDoc.querySelector("a[name='link1']");
|
||||
}
|
||||
|
||||
function getAnchorJumpInTabDocument()
|
||||
function loadTab(aURL)
|
||||
{
|
||||
return getTabDocument().querySelector("a[name='link1']");
|
||||
}
|
||||
|
||||
function loadTab(aTabBrowserID, aURL)
|
||||
{
|
||||
function loadTabChecker()
|
||||
{
|
||||
this.type = EVENT_REORDER;
|
||||
this.match = function loadTabChecker_match(aEvent)
|
||||
{
|
||||
var target = aEvent.accessible;
|
||||
if (target.role == ROLE_INTERNAL_FRAME &&
|
||||
target.parent.parent == getAccessible(getNode(aTabBrowserID).mTabBox.tabpanels)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.eventSeq = [ new loadTabChecker() ];
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
|
||||
new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument)
|
||||
];
|
||||
|
||||
this.invoke = function loadTab_invoke()
|
||||
{
|
||||
getNode(aTabBrowserID).loadURI(aURL);
|
||||
tabBrowser().loadURI(aURL);
|
||||
}
|
||||
|
||||
this.getID = function loadTab_getID()
|
||||
{
|
||||
return "load tab " + aURL + " for " + prettyName(aTabBrowserID);
|
||||
return "load tab: " + aURL;
|
||||
}
|
||||
}
|
||||
|
||||
function advanceFocusIntoTab(aTabBrowserID)
|
||||
function loadTabInBackground(aURL)
|
||||
{
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
|
||||
];
|
||||
|
||||
this.unexpectedEventSeq = [
|
||||
new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument, 1)
|
||||
];
|
||||
|
||||
this.invoke = function loadTabInBackground_invoke()
|
||||
{
|
||||
tabBrowser().loadOneTab(aURL, null, "", null, true);
|
||||
}
|
||||
|
||||
this.getID = function loadTabInBackground_getID()
|
||||
{
|
||||
return "load tab in background: " + aURL;
|
||||
}
|
||||
}
|
||||
|
||||
function switchToBackgroundTab()
|
||||
{
|
||||
this.eventSeq = [
|
||||
new focusChecker(getTabDocument),
|
||||
new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument)
|
||||
];
|
||||
|
||||
this.invoke = function advanceFocusIntoTab_invoke()
|
||||
this.invoke = function switchToBackgroundTab_invoke()
|
||||
{
|
||||
var tabDoc = getAccessible(getTabDocument());
|
||||
tabDoc.takeFocus();
|
||||
tabBrowser().selectTabAtIndex(1);
|
||||
}
|
||||
|
||||
this.getID = function advanceFocusIntoTab_getID()
|
||||
this.getID = function switchToBackgroundTab_getID()
|
||||
{
|
||||
return "advance focus into loaded tab";
|
||||
return "switch to background tab";
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,28 +110,22 @@
|
||||
var url = rootDir + "scroll.html#link1";
|
||||
|
||||
gQueue = new eventQueue();
|
||||
gQueue.push(new loadTab("tabBrowser", url));
|
||||
gQueue.push(new advanceFocusIntoTab("tabBrowser"));
|
||||
|
||||
gQueue.push(new loadTab(url));
|
||||
gQueue.push(new loadTabInBackground(url));
|
||||
gQueue.push(new switchToBackgroundTab());
|
||||
gQueue.onFinish = function() { closeBrowserWindow(); }
|
||||
|
||||
gQueue.invoke(); // Will call SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
openBrowserWindow(doTest);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<hbox flex="1" style="overflow: auto;">
|
||||
<vbox flex="1" style="overflow: auto;">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=437607"
|
||||
title="Clicking the 'Skip to main content' link once works, second time fails to initiate a V cursor jump">
|
||||
Mozilla Bug 437607
|
||||
</a><br/>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=519303"
|
||||
title="Same page links to targets with content fires scrolling start accessible event on leaf text node">
|
||||
Mozilla Bug 519303
|
||||
</a>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=691734"
|
||||
title="Make sure scrolling start event is fired when document receive focus">
|
||||
@ -171,33 +139,6 @@
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
<vbox flex="1">
|
||||
<!-- Hack to make xul:tabbrowser work -->
|
||||
<menubar>
|
||||
<menu label="menu">
|
||||
<menupopup>
|
||||
<menuitem label="close window hook" id="menu_closeWindow"/>
|
||||
<menuitem label="close hook" id="menu_close"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
</menubar>
|
||||
<keyset>
|
||||
<key id="key_close"/>
|
||||
</keyset>
|
||||
|
||||
<hbox>
|
||||
<tabs id="tabbrowser-tabs" class="tabbrowser-tabs"
|
||||
tabbrowser="tabBrowser"
|
||||
flex="1">
|
||||
<tab class="tabbrowser-tab" selected="true" label="tab"/>
|
||||
</tabs>
|
||||
</hbox>
|
||||
<tabbrowser id="tabBrowser"
|
||||
type="content-primary"
|
||||
tabcontainer="tabbrowser-tabs"
|
||||
flex="1"/>
|
||||
</vbox>
|
||||
<toolbar id="addon-bar"/>
|
||||
</hbox>
|
||||
|
||||
<vbox id="eventdump"></vbox>
|
||||
</vbox>
|
||||
</window>
|
||||
|
@ -45,8 +45,6 @@ relativesrcdir = accessible/name
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# test_nsRootAcc.xul, nsRootAcc_wnd.xul disabled for misusing <tabbrowser> (bug 715857)
|
||||
|
||||
_TEST_FILES =\
|
||||
general.css \
|
||||
general.xbl \
|
||||
@ -57,6 +55,7 @@ _TEST_FILES =\
|
||||
test_link.html \
|
||||
test_list.html \
|
||||
test_markup.html \
|
||||
test_browserui.xul \
|
||||
test_tree.xul \
|
||||
markuprules.xml \
|
||||
$(NULL)
|
||||
|
@ -1,125 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
|
||||
<!-- Firefox tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css"
|
||||
type="text/css"?>
|
||||
<!-- SeaMonkey tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://navigator/content/navigator.css"
|
||||
type="text/css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
var gOpenerWnd = window.opener.wrappedJSObject;
|
||||
|
||||
function ok(aCond, aMsg) {
|
||||
gOpenerWnd.SimpleTest.ok(aCond, aMsg);
|
||||
}
|
||||
|
||||
function is(aExpected, aActual, aMsg) {
|
||||
gOpenerWnd.SimpleTest.is(aExpected, aActual, aMsg);
|
||||
}
|
||||
|
||||
// Hacks to make xul:tabbrowser work.
|
||||
var handleDroppedLink = null; // needed for tabbrowser usage
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
var XULBrowserWindow = {
|
||||
isBusy: false,
|
||||
setOverLink: function (link, b) {
|
||||
}
|
||||
};
|
||||
|
||||
gFindBarInitialized = false;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Invoker implementation.
|
||||
|
||||
function switchTabSelectChecker(aInvoker)
|
||||
{
|
||||
this.type = "select";
|
||||
Object.defineProperty(this, "target", { get: function() { return aInvoker.getTabsElm(); }});
|
||||
this.getID = function() { return "switch tab, select event"; }
|
||||
}
|
||||
|
||||
function switchTabFocusChecker(aInvoker)
|
||||
{
|
||||
this.type = gOpenerWnd.EVENT_FOCUS;
|
||||
Object.defineProperty(this, "target", { get: function() { return aInvoker.getContentDoc(); }});
|
||||
this.check = function(aEvent)
|
||||
{
|
||||
is(gOpenerWnd.getAccessible(document).name, "about:mozilla" + aEvent.accessible.name,
|
||||
"Oops almost :)");
|
||||
}
|
||||
this.getID = function() { return "switch tab, focus event"; }
|
||||
}
|
||||
|
||||
function switchTabInvoker(aTabBrowser, aWindow)
|
||||
{
|
||||
this.invoke = function switchTabInvoker_invoke()
|
||||
{
|
||||
gOpenerWnd.synthesizeKey("VK_TAB", { ctrlKey: true }, aWindow);
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new switchTabSelectChecker(this),
|
||||
new switchTabFocusChecker(this)
|
||||
];
|
||||
|
||||
this.getContentDoc = function switchTabInvoker_getContentDoc()
|
||||
{
|
||||
return aTabBrowser.getBrowserAtIndex(1).contentDocument;
|
||||
}
|
||||
this.getTabsElm = function switchTabInvoker_getTabsElm()
|
||||
{
|
||||
return aTabBrowser.tabContainer;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
|
||||
var gQueue = null;
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
function doTest()
|
||||
{
|
||||
var tabBrowser = document.getElementById("content");
|
||||
tabBrowser.loadURI("about:");
|
||||
tabBrowser.addTab("about:mozilla");
|
||||
|
||||
gQueue = new gOpenerWnd.eventQueue();
|
||||
gQueue.push(new switchTabInvoker(tabBrowser, window));
|
||||
gQueue.onFinish = function() { window.close(); }
|
||||
gQueue.invoke();
|
||||
}
|
||||
|
||||
gOpenerWnd.addA11yLoadEvent(doTest);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<!-- Hack to make xul:tabbrowser work -->
|
||||
<menubar>
|
||||
<menu label="menu">
|
||||
<menupopup>
|
||||
<menuitem label="close window hook" id="menu_closeWindow"/>
|
||||
<menuitem label="close hook" id="menu_close"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
</menubar>
|
||||
|
||||
<tabs id="tabbrowser-tabs" class="tabbrowser-tabs"
|
||||
tabbrowser="content"
|
||||
flex="1"
|
||||
setfocus="false">
|
||||
<tab class="tabbrowser-tab" selected="true"/>
|
||||
</tabs>
|
||||
<tabbrowser id="content"
|
||||
type="content-primary"
|
||||
tabcontainer="tabbrowser-tabs"
|
||||
flex="1"/>
|
||||
|
||||
</window>
|
106
accessible/tests/mochitest/name/test_browserui.xul
Normal file
106
accessible/tests/mochitest/name/test_browserui.xul
Normal file
@ -0,0 +1,106 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Accessibility Name Calculating Test.">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../role.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../states.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../events.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../browser.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
function addTab(aURL)
|
||||
{
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
|
||||
];
|
||||
|
||||
this.invoke = function addTab_invoke()
|
||||
{
|
||||
tabBrowser().addTab(aURL);
|
||||
}
|
||||
|
||||
this.getID = function addTab_getID()
|
||||
{
|
||||
return "add tab: " + aURL;
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(aTabBrowser, aWindow)
|
||||
{
|
||||
this.invoke = function switchTab_invoke()
|
||||
{
|
||||
synthesizeKey("VK_TAB", { ctrlKey: true }, browserWindow());
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new focusChecker(tabDocumentAt, 1)
|
||||
];
|
||||
|
||||
this.check = function switchTab_check(aEvent)
|
||||
{
|
||||
var title = getAccessible(browserDocument()).name;
|
||||
ok(title.indexOf(aEvent.accessible.name) != -1,
|
||||
"Window title contains the name of active tab document");
|
||||
}
|
||||
|
||||
this.getID = function switchTab_getID() { return "switch tab"; }
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
|
||||
//gA11yEventDumpID = "eventdump"; // debug stuff
|
||||
//gA11yEventDumpToConsole = true; // debug
|
||||
|
||||
var gQueue = null;
|
||||
function doTests()
|
||||
{
|
||||
gQueue = new eventQueue();
|
||||
gQueue.push(new addTab("about:mozilla"));
|
||||
gQueue.push(new switchTab());
|
||||
gQueue.onFinish = function()
|
||||
{
|
||||
closeBrowserWindow();
|
||||
}
|
||||
|
||||
gQueue.invoke();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
openBrowserWindow(doTests, "about:");
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<vbox flex="1" style="overflow: auto;">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=507382"
|
||||
title="focus is fired earlier than root accessible name is changed when switching between tabs">
|
||||
Mozilla Bug
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
<vbox id="eventdump"></vbox>
|
||||
</vbox>
|
||||
</window>
|
@ -1,63 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Accessibility Name Calculating Test.">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../role.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../events.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
// var gA11yEventDumpID = "eventdump"; // debug stuff
|
||||
|
||||
function doTest()
|
||||
{
|
||||
todo(false, "Disabled test. (Bug 586818)");
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
|
||||
if (LINUX) {
|
||||
todo(false, "Skip test on Linux. (Bug 525175)");
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var w = window.openDialog("nsRootAcc_wnd.xul",
|
||||
"nsRootAcc_name_test",
|
||||
"chrome,width=600,height=600");
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(doTest);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<vbox flex="1" style="overflow: auto;">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=507382"
|
||||
title="focus is fired earlier than root accessible name is changed when switching between tabs">
|
||||
Mozilla Bug
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
<vbox id="eventdump"></vbox>
|
||||
</vbox>
|
||||
</window>
|
217
accessible/tests/mochitest/pivot.js
Normal file
217
accessible/tests/mochitest/pivot.js
Normal file
@ -0,0 +1,217 @@
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants
|
||||
|
||||
const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE;
|
||||
const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH;
|
||||
const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE;
|
||||
const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Traversal rules
|
||||
|
||||
/**
|
||||
* Rule object to traverse all focusable nodes and text nodes.
|
||||
*/
|
||||
var HeadersTraversalRule =
|
||||
{
|
||||
getMatchRoles: function(aRules)
|
||||
{
|
||||
aRules.value = [ROLE_HEADING];
|
||||
return aRules.value.length;
|
||||
},
|
||||
|
||||
preFilter: PREFILTER_INVISIBLE,
|
||||
|
||||
match: function(aAccessible)
|
||||
{
|
||||
return FILTER_MATCH;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule])
|
||||
}
|
||||
|
||||
/**
|
||||
* Traversal rule for all focusable nodes or leafs.
|
||||
*/
|
||||
var ObjectTraversalRule =
|
||||
{
|
||||
getMatchRoles: function(aRules)
|
||||
{
|
||||
aRules.value = [];
|
||||
return 0;
|
||||
},
|
||||
|
||||
preFilter: PREFILTER_INVISIBLE,
|
||||
|
||||
match: function(aAccessible)
|
||||
{
|
||||
var rv = FILTER_IGNORE;
|
||||
var role = aAccessible.role;
|
||||
if (hasState(aAccessible, STATE_FOCUSABLE) &&
|
||||
(role != ROLE_DOCUMENT && role != ROLE_INTERNAL_FRAME))
|
||||
rv = FILTER_IGNORE_SUBTREE | FILTER_MATCH;
|
||||
else if (aAccessible.childCount == 0 &&
|
||||
role != ROLE_STATICTEXT && aAccessible.name.trim())
|
||||
rv = FILTER_MATCH;
|
||||
|
||||
return rv;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule])
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Virtual state invokers and checkers
|
||||
|
||||
/**
|
||||
* A checker for virtual cursor changed events.
|
||||
*/
|
||||
function virtualCursorChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets)
|
||||
{
|
||||
this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc);
|
||||
|
||||
this.check = function virtualCursorChangedChecker_check(aEvent)
|
||||
{
|
||||
var position = aDocAcc.virtualCursor.position;
|
||||
position.QueryInterface(nsIAccessNode);
|
||||
|
||||
var idMatches = position.DOMNode.id == aIdOrNameOrAcc;
|
||||
var nameMatches = position.name == aIdOrNameOrAcc;
|
||||
var accMatches = position == aIdOrNameOrAcc;
|
||||
|
||||
SimpleTest.ok(idMatches || nameMatches || accMatches, "id or name matches",
|
||||
"expecting " + aIdOrNameOrAcc + ", got '" +
|
||||
prettyName(position));
|
||||
|
||||
if (aTextOffsets) {
|
||||
SimpleTest.is(aDocAcc.virtualCursor.startOffset, aTextOffsets[0],
|
||||
"wrong start offset");
|
||||
SimpleTest.is(aDocAcc.virtualCursor.endOffset, aTextOffsets[1],
|
||||
"wrong end offset");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a text range in the pivot and wait for virtual cursor change event.
|
||||
*
|
||||
* @param aDocAcc document that manages the virtual cursor
|
||||
* @param aTextAccessible accessible to set to virtual cursor's position
|
||||
* @param aTextOffsets start and end offsets of text range to set in virtual
|
||||
* cursor
|
||||
*/
|
||||
function setVirtualCursorRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets)
|
||||
{
|
||||
this.invoke = function virtualCursorChangedInvoker_invoke()
|
||||
{
|
||||
SimpleTest.info(prettyName(aTextAccessible) + " " + aTextOffsets);
|
||||
aDocAcc.virtualCursor.setTextRange(aTextAccessible,
|
||||
aTextOffsets[0],
|
||||
aTextOffsets[1]);
|
||||
};
|
||||
|
||||
this.getID = function setVirtualCursorRangeInvoker_getID()
|
||||
{
|
||||
return "Set offset in " + prettyName(aTextAccessible) +
|
||||
" to (" + aTextOffsets[0] + ", " + aTextOffsets[1] + ")";
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new virtualCursorChangedChecker(aDocAcc, aTextAccessible, aTextOffsets)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the pivot and wait for virtual cursor change event.
|
||||
*
|
||||
* @param aDocAcc document that manages the virtual cursor
|
||||
* @param aPivotMoveMethod method to test (ie. "moveNext", "moveFirst", etc.)
|
||||
* @param aRule traversal rule object
|
||||
* @param aIdOrNameOrAcc id, accessivle or accessible name to expect virtual
|
||||
* cursor to land on after performing move method.
|
||||
*/
|
||||
function setVirtualCursorPosInvoker(aDocAcc, aPivotMoveMethod, aRule,
|
||||
aIdOrNameOrAcc)
|
||||
{
|
||||
this.invoke = function virtualCursorChangedInvoker_invoke()
|
||||
{
|
||||
var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule);
|
||||
SimpleTest.ok((aIdOrNameOrAcc && moved) || (!aIdOrNameOrAcc && !moved),
|
||||
"moved pivot");
|
||||
};
|
||||
|
||||
this.getID = function setVirtualCursorPosInvoker_getID()
|
||||
{
|
||||
return "Do " + (aIdOrNameOrAcc ? "" : "no-op ") + aPivotMoveMethod;
|
||||
}
|
||||
|
||||
if (aIdOrNameOrAcc) {
|
||||
this.eventSeq = [ new virtualCursorChangedChecker(aDocAcc, aIdOrNameOrAcc) ];
|
||||
} else {
|
||||
this.eventSeq = [];
|
||||
this.unexpectedEventSeq = [
|
||||
new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add invokers to a queue to test a rule and an expected sequence of element ids
|
||||
* or accessible names for that rule in the given document.
|
||||
*
|
||||
* @param aQueue event queue in which to push invoker sequence.
|
||||
* @param aDocAcc the managing document of the virtual cursor we are testing
|
||||
* @param aRule the traversal rule to use in the invokers
|
||||
* @param aSequence a sequence of accessible names or elemnt ids to expect with
|
||||
* the given rule in the given document
|
||||
*/
|
||||
function queueTraversalSequence(aQueue, aDocAcc, aRule, aSequence)
|
||||
{
|
||||
aDocAcc.virtualCursor.position = null;
|
||||
|
||||
for (var i = 0; i < aSequence.length; i++) {
|
||||
var invoker = new setVirtualCursorPosInvoker(aDocAcc, "moveNext",
|
||||
aRule, aSequence[i]);
|
||||
aQueue.push(invoker);
|
||||
}
|
||||
|
||||
// No further more matches for given rule, expect no virtual cursor changes.
|
||||
aQueue.push(new setVirtualCursorPosInvoker(aDocAcc, "moveNext", aRule, null));
|
||||
|
||||
for (var i = aSequence.length-2; i >= 0; i--) {
|
||||
var invoker = new setVirtualCursorPosInvoker(aDocAcc, "movePrevious",
|
||||
aRule, aSequence[i])
|
||||
aQueue.push(invoker);
|
||||
}
|
||||
|
||||
// No previous more matches for given rule, expect no virtual cursor changes.
|
||||
aQueue.push(new setVirtualCursorPosInvoker(aDocAcc, "movePrevious", aRule, null));
|
||||
|
||||
aQueue.push(new setVirtualCursorPosInvoker(
|
||||
aDocAcc, "moveLast", aRule, aSequence[aSequence.length - 1]));
|
||||
|
||||
// No further more matches for given rule, expect no virtual cursor changes.
|
||||
aQueue.push(new setVirtualCursorPosInvoker(aDocAcc, "moveNext", aRule, null));
|
||||
|
||||
aQueue.push(new setVirtualCursorPosInvoker(
|
||||
aDocAcc, "moveFirst", aRule, aSequence[0]));
|
||||
|
||||
// No previous more matches for given rule, expect no virtual cursor changes.
|
||||
aQueue.push(new setVirtualCursorPosInvoker(aDocAcc, "movePrevious", aRule, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* A debug utility for writing proper sequences for queueTraversalSequence.
|
||||
*/
|
||||
function dumpTraversalSequence(aPivot, aRule)
|
||||
{
|
||||
var sequence = []
|
||||
if (aPivot.moveFirst(aRule)) {
|
||||
do {
|
||||
sequence.push("'" + prettyName(aPivot.position) + "'");
|
||||
} while (aPivot.moveNext(aRule))
|
||||
}
|
||||
SimpleTest.info("\n[" + sequence.join(", ") + "]\n");
|
||||
}
|
54
accessible/tests/mochitest/pivot/Makefile.in
Normal file
54
accessible/tests/mochitest/pivot/Makefile.in
Normal file
@ -0,0 +1,54 @@
|
||||
#
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is mozilla.org code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2010
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Eitan Isaacson <eitan@monotonous.org> (original author)
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
DEPTH = ../../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = accessible/pivot
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TEST_FILES = \
|
||||
doc_virtualcursor.html \
|
||||
test_virtualcursor.html \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TEST_FILES)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
|
26
accessible/tests/mochitest/pivot/doc_virtualcursor.html
Normal file
26
accessible/tests/mochitest/pivot/doc_virtualcursor.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Pivot test document</title>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="heading-1-1">Main Title</h1>
|
||||
<h2 id="heading-2-1">First Section Title</h2>
|
||||
<p id="paragraph-1">
|
||||
Lorem ipsum <strong>dolor</strong> sit amet. Integer vitae urna
|
||||
leo, id <a href="#">semper</a> nulla.
|
||||
</p>
|
||||
<h2 id="heading-2-2">Second Section Title</h2>
|
||||
<p id="paragraph-2">
|
||||
Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.</p>
|
||||
<iframe
|
||||
src="data:text/html,<html><body>An <i>embedded</i> document.</body></html>">
|
||||
</iframe>
|
||||
<p>
|
||||
<a href="http://mozilla.org" title="Link 1 title">Link 1</a>
|
||||
<a href="http://mozilla.org" title="Link 2 title">Link 2</a>
|
||||
<a href="http://mozilla.org" title="Link 3 title">Link 3</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
95
accessible/tests/mochitest/pivot/test_virtualcursor.html
Normal file
95
accessible/tests/mochitest/pivot/test_virtualcursor.html
Normal file
@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tests pivot functionality in virtual cursors</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
|
||||
</script>
|
||||
|
||||
<script type="application/javascript" src="../common.js"></script>
|
||||
<script type="application/javascript" src="../browser.js"></script>
|
||||
<script type="application/javascript" src="../events.js"></script>
|
||||
<script type="application/javascript" src="../role.js"></script>
|
||||
<script type="application/javascript" src="../states.js"></script>
|
||||
<script type="application/javascript" src="../pivot.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
var gBrowserWnd = null;
|
||||
var gQueue = null;
|
||||
|
||||
function doTest()
|
||||
{
|
||||
var rootAcc = getRootAccessible(browserWindow().document);
|
||||
try {
|
||||
rootAcc.QueryInterface(nsIAccessibleCursorable);
|
||||
} catch (e) {
|
||||
ok(false, "Root accessible does not support nsIAccessibleCursorable");
|
||||
}
|
||||
var doc = currentTabDocument();
|
||||
var docAcc = getAccessible(doc, [nsIAccessibleDocument,
|
||||
nsIAccessibleCursorable]);
|
||||
|
||||
// Test that embedded documents don't have their own virtual cursor.
|
||||
is(docAcc.childDocumentCount, 1, "Expecting one child document");
|
||||
var childDoc = docAcc.getChildDocumentAt(0);
|
||||
var supportsVC = true;
|
||||
try {
|
||||
childDoc.QueryInterface(nsIAccessibleCursorable);
|
||||
} catch (e) {
|
||||
supportsVC = false;
|
||||
}
|
||||
|
||||
ok(!supportsVC, "no nsIAccessibleCursorable support in child document");
|
||||
|
||||
gQueue = new eventQueue();
|
||||
|
||||
gQueue.onFinish = function onFinish()
|
||||
{
|
||||
closeBrowserWindow();
|
||||
}
|
||||
|
||||
queueTraversalSequence(gQueue, docAcc, HeadersTraversalRule,
|
||||
['heading-1-1', 'heading-2-1', 'heading-2-2']);
|
||||
|
||||
queueTraversalSequence(
|
||||
gQueue, docAcc, ObjectTraversalRule,
|
||||
['Main Title', 'First Section Title', 'Lorem ipsum ',
|
||||
'dolor', ' sit amet. Integer vitae urna leo, id ',
|
||||
'semper', ' nulla. ', 'Second Section Title',
|
||||
'Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.',
|
||||
'An ', 'embedded', ' document.', 'Link 1', 'Link 2', 'Link 3']);
|
||||
|
||||
// Just a random smoke test to see if our setTextRange works.
|
||||
gQueue.push(
|
||||
new setVirtualCursorRangeInvoker(
|
||||
docAcc,
|
||||
getAccessible(doc.getElementById('paragraph-2'), nsIAccessibleText),
|
||||
[2,6]));
|
||||
|
||||
gQueue.invoke();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function () {
|
||||
/* We open a new browser because we need to test with a top-level content
|
||||
document. */
|
||||
openBrowserWindow(
|
||||
doTest,
|
||||
"chrome://mochitests/content/a11y/accessible/pivot/doc_virtualcursor.html");
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body id="body">
|
||||
|
||||
<a target="_blank"
|
||||
title="Introduce virtual cursor/soft focus functionality to a11y API"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=698823">Mozilla Bug 698823</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -45,11 +45,11 @@ relativesrcdir = accessible/relations
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# test_tabbrowser.xul disabled for misusing <tabbrowser> (bug 715857)
|
||||
|
||||
_TEST_FILES =\
|
||||
test_embeds.xul \
|
||||
test_general.html \
|
||||
test_general.xul \
|
||||
test_tabbrowser.xul \
|
||||
test_tree.xul \
|
||||
test_update.html \
|
||||
$(NULL)
|
||||
|
152
accessible/tests/mochitest/relations/test_embeds.xul
Normal file
152
accessible/tests/mochitest/relations/test_embeds.xul
Normal file
@ -0,0 +1,152 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Embeds relation tests">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../role.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../states.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../events.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../relations.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Helpers
|
||||
|
||||
function tabBrowser()
|
||||
{
|
||||
return gBrowserWnd.gBrowser;
|
||||
}
|
||||
|
||||
function currentBrowser()
|
||||
{
|
||||
return tabBrowser().selectedBrowser;
|
||||
}
|
||||
|
||||
function currentTabDocument()
|
||||
{
|
||||
return currentBrowser().contentDocument;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Invokers
|
||||
|
||||
function loadURI(aURI)
|
||||
{
|
||||
this.invoke = function loadURI_invoke()
|
||||
{
|
||||
tabBrowser().loadURI(aURI);
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_REORDER, currentBrowser)
|
||||
];
|
||||
|
||||
this.finalCheck = function loadURI_finalCheck()
|
||||
{
|
||||
testRelation(gBrowserWnd.document, RELATION_EMBEDS,
|
||||
getAccessible(currentTabDocument()));
|
||||
}
|
||||
|
||||
this.getID = function loadURI_getID()
|
||||
{
|
||||
return "load uri " + aURI;
|
||||
}
|
||||
}
|
||||
|
||||
function loadOneTab(aURI)
|
||||
{
|
||||
this.invoke = function loadOneTab_invoke()
|
||||
{
|
||||
tabBrowser().loadOneTab(aURI, null, null, null, false);
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_REORDER, currentBrowser)
|
||||
];
|
||||
|
||||
this.finalCheck = function loadURI_finalCheck()
|
||||
{
|
||||
testRelation(gBrowserWnd.document, RELATION_EMBEDS,
|
||||
getAccessible(currentTabDocument()));
|
||||
}
|
||||
|
||||
this.getID = function loadOneTab_getID()
|
||||
{
|
||||
return "load uri '" + aURI + "' in new tab";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Testing
|
||||
|
||||
var gBrowserWnd = null;
|
||||
function loadBrowser()
|
||||
{
|
||||
gBrowserWnd = window.openDialog("chrome://browser/content/", "_blank",
|
||||
"chrome,all,dialog=no", "about:");
|
||||
|
||||
addA11yLoadEvent(startTests, gBrowserWnd);
|
||||
}
|
||||
|
||||
function startTests()
|
||||
{
|
||||
// Wait for tab load.
|
||||
var browser = currentBrowser();
|
||||
addA11yLoadEvent(doTests, browser.contentWindow);
|
||||
}
|
||||
|
||||
//gA11yEventDumpToConsole = true; // debug
|
||||
|
||||
var gQueue = null;
|
||||
function doTests()
|
||||
{
|
||||
testRelation(gBrowserWnd.document, RELATION_EMBEDS,
|
||||
getAccessible(currentTabDocument()));
|
||||
|
||||
gQueue = new eventQueue();
|
||||
|
||||
gQueue.push(new loadURI("about:about"));
|
||||
gQueue.push(new loadOneTab("about:mozilla"));
|
||||
|
||||
gQueue.onFinish = function()
|
||||
{
|
||||
gBrowserWnd.close();
|
||||
}
|
||||
gQueue.invoke();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(loadBrowser);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<vbox flex="1" style="overflow: auto;">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=707654"
|
||||
title="Embeds relation on root accessible can return not content document">
|
||||
Mozilla Bug 707654
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</vbox>
|
||||
</window>
|
@ -126,19 +126,6 @@
|
||||
testRelation("legend", RELATION_LABEL_FOR, "fieldset");
|
||||
testRelation("fieldset", RELATION_LABELLED_BY, "legend");
|
||||
|
||||
// 'embeds' relation for root accessible
|
||||
var docAcc = null;
|
||||
var parentOfDocAcc = null;
|
||||
var parentDocAcc = getAccessible(document);
|
||||
do {
|
||||
docAcc = parentDocAcc;
|
||||
parentOfDocAcc = getAccessible(docAcc.parent, [nsIAccessNode]);
|
||||
parentDocAcc = getAccessible(parentOfDocAcc.document,
|
||||
[nsIAccessible]);
|
||||
} while (getRole(parentDocAcc) != ROLE_CHROME_WINDOW)
|
||||
|
||||
testRelation(parentDocAcc, RELATION_EMBEDS, docAcc);
|
||||
|
||||
// finish test
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
@ -103,19 +103,6 @@
|
||||
// 'default button' relation
|
||||
testRelation("textbox", RELATION_DEFAULT_BUTTON, "submit");
|
||||
|
||||
// 'embeds' relation for root accessible
|
||||
var docAcc = null;
|
||||
var parentOfDocAcc = null;
|
||||
var parentDocAcc = getAccessible(document);
|
||||
do {
|
||||
docAcc = parentDocAcc;
|
||||
parentOfDocAcc = getAccessible(docAcc.parent, [nsIAccessNode]);
|
||||
parentDocAcc = getAccessible(parentOfDocAcc.document,
|
||||
[nsIAccessible]);
|
||||
} while (getRole(parentDocAcc) != ROLE_CHROME_WINDOW)
|
||||
|
||||
testRelation(parentDocAcc, RELATION_EMBEDS, docAcc);
|
||||
|
||||
// 'labelled by'/'label for' relation for xul:goupbox and xul:label of
|
||||
// xul:caption
|
||||
var groupboxAcc = getAccessible("groupbox");
|
||||
|
@ -3,18 +3,13 @@
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<!-- Firefox tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css"
|
||||
type="text/css"?>
|
||||
<!-- SeaMonkey tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://navigator/content/navigator.css"
|
||||
type="text/css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Accessible XUL tabbrowser relation tests">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js" />
|
||||
@ -24,75 +19,63 @@
|
||||
src="../relations.js" />
|
||||
<script type="application/javascript"
|
||||
src="../events.js" />
|
||||
<script type="application/javascript"
|
||||
src="../browser.js"></script>
|
||||
|
||||
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Test
|
||||
// Invoker
|
||||
function testTabRelations()
|
||||
{
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 0),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
|
||||
];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
// Hack to make xul:tabbrowser work.
|
||||
var handleDroppedLink = null;
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
var XULBrowserWindow = {
|
||||
isBusy: false,
|
||||
setOverLink: function (link, b) {
|
||||
this.invoke = function testTabRelations_invoke()
|
||||
{
|
||||
var docURIs = ["about:", "about:mozilla"];
|
||||
tabBrowser().loadTabs(docURIs, false, true);
|
||||
}
|
||||
};
|
||||
var gFindBar = {
|
||||
hidden: true
|
||||
};
|
||||
|
||||
this.finalCheck = function testTabRelations_finalCheck(aEvent)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
|
||||
|
||||
var tabs = tabBrowser().tabContainer.childNodes;
|
||||
var panels = tabBrowser().mTabBox.tabpanels.childNodes;
|
||||
|
||||
testRelation(panels[0], RELATION_LABELLED_BY, tabs[0]);
|
||||
testRelation(tabs[0], RELATION_LABEL_FOR, panels[0]);
|
||||
testRelation(panels[1], RELATION_LABELLED_BY, tabs[1]);
|
||||
testRelation(tabs[1], RELATION_LABEL_FOR, panels[1]);
|
||||
}
|
||||
|
||||
this.getID = function testTabRelations_getID()
|
||||
{
|
||||
return "relations of tabs";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Test
|
||||
var gQueue = null;
|
||||
function doTest()
|
||||
{
|
||||
var tabBrowser = document.getElementById("tabbrowser");
|
||||
// Load documents into tabs and wait for DocLoadComplete events caused by
|
||||
// these documents load before we start the test.
|
||||
|
||||
// Load documents into tabs and wait for reorder events caused by these
|
||||
// documents load before we start the test.
|
||||
var docURIs = ["about:", "about:mozilla"];
|
||||
gQueue = new eventQueue();
|
||||
|
||||
var handler = {
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
var target = aEvent.accessible;
|
||||
if (target.role == ROLE_INTERNAL_FRAME &&
|
||||
target.parent.parent == getAccessible(this.tabBrowser.mTabBox.tabpanels)) {
|
||||
this.reorderCnt++;
|
||||
}
|
||||
|
||||
if (this.reorderCnt == docURIs.length) {
|
||||
unregisterA11yEventListener(EVENT_REORDER, this);
|
||||
testRelations();
|
||||
}
|
||||
},
|
||||
|
||||
tabBrowser: tabBrowser,
|
||||
reorderCnt: 0
|
||||
};
|
||||
registerA11yEventListener(EVENT_REORDER, handler);
|
||||
|
||||
tabBrowser.loadTabs(docURIs, false, true);
|
||||
}
|
||||
|
||||
function testRelations()
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
|
||||
|
||||
var tabs = getNode("tabbrowser").tabContainer.childNodes;
|
||||
var panels = getNode("tabbrowser").mTabBox.tabpanels.childNodes;
|
||||
|
||||
testRelation(panels[0], RELATION_LABELLED_BY, tabs[0]);
|
||||
testRelation(tabs[0], RELATION_LABEL_FOR, panels[0]);
|
||||
testRelation(panels[1], RELATION_LABELLED_BY, tabs[1]);
|
||||
testRelation(tabs[1], RELATION_LABEL_FOR, panels[1]);
|
||||
|
||||
SimpleTest.finish();
|
||||
gQueue.push(new testTabRelations());
|
||||
gQueue.onFinish = function() { closeBrowserWindow(); }
|
||||
gQueue.invoke(); // Will call SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
openBrowserWindow(doTest);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
@ -110,26 +93,7 @@
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
<!-- Hack to make xul:tabbrowser work -->
|
||||
<menubar>
|
||||
<menu label="menu">
|
||||
<menupopup>
|
||||
<menuitem label="close window hook" id="menu_closeWindow"/>
|
||||
<menuitem label="close hook" id="menu_close"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
</menubar>
|
||||
|
||||
<tabs id="tabbrowser-tabs" class="tabbrowser-tabs"
|
||||
tabbrowser="tabbrowser"
|
||||
setfocus="false">
|
||||
<tab class="tabbrowser-tab" selected="true"/>
|
||||
</tabs>
|
||||
<tabbrowser id="tabbrowser"
|
||||
type="content-primary"
|
||||
tabcontainer="tabbrowser-tabs"
|
||||
flex="1"/>
|
||||
<toolbar id="addon-bar"/>
|
||||
<vbox id="eventdump"></vbox>
|
||||
</vbox>
|
||||
|
||||
</window>
|
||||
|
@ -131,7 +131,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=529289
|
||||
<tr><td>hi<td></tr></table>
|
||||
|
||||
<!-- test gEmptyRoleMap -->
|
||||
<table role="label">
|
||||
<table role="button">
|
||||
<tr>
|
||||
<td id="cell">cell</td>
|
||||
</tr>
|
||||
|
@ -37,15 +37,15 @@
|
||||
testChildAtPoint(txt, -10000, 10000, false, null);
|
||||
testChildAtPoint(txt, -10000, 10000, true, null);
|
||||
|
||||
// Not specific case, point is inside of label accessible.
|
||||
var label = getAccessible("label");
|
||||
var labelText = label.firstChild;
|
||||
testChildAtPoint(label, 1, 1, false, labelText);
|
||||
testChildAtPoint(label, 1, 1, true, labelText);
|
||||
// Not specific case, point is inside of btn accessible.
|
||||
var btn = getAccessible("btn");
|
||||
var btnText = btn.firstChild;
|
||||
testChildAtPoint(btn, 1, 1, false, btnText);
|
||||
testChildAtPoint(btn, 1, 1, true, btnText);
|
||||
|
||||
// Not specific case, point is outside of label accessible.
|
||||
testChildAtPoint(label, -1, 1, false, null);
|
||||
testChildAtPoint(label, -1, 1, true, null);
|
||||
// Not specific case, point is outside of btn accessible.
|
||||
testChildAtPoint(btn, -1, 1, false, null);
|
||||
testChildAtPoint(btn, -1, 1, true, null);
|
||||
|
||||
// Out of flow accessible testing, do not return out of flow accessible
|
||||
// because it's not a child of the accessible even visually it is.
|
||||
@ -78,7 +78,7 @@
|
||||
<div role="listitem" id="listitem"><span role="image" id="image">img</span>item</div>
|
||||
</div>
|
||||
|
||||
<span role="label">label1</span><span role="label" id="label">label2</span>
|
||||
<span role="button">button1</span><span role="button" id="btn">button2</span>
|
||||
|
||||
<span role="textbox">textbox1</span><span role="textbox" id="txt">textbox2</span>
|
||||
|
||||
|
@ -45,8 +45,6 @@ relativesrcdir = accessible/tree
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# test_tabbrowser.xul disabled for misusing <tabbrowser> (bug 715857)
|
||||
|
||||
_TEST_FILES =\
|
||||
dockids.html \
|
||||
$(warning test_applicationacc.xul temporarily disabled, see bug 561508) \
|
||||
@ -72,6 +70,7 @@ _TEST_FILES =\
|
||||
test_media.html \
|
||||
test_select.html \
|
||||
test_tabbox.xul \
|
||||
test_tabbrowser.xul \
|
||||
test_table.html \
|
||||
test_tree.xul \
|
||||
test_txtcntr.html \
|
||||
|
@ -1,13 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
|
||||
<!-- Firefox tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css"
|
||||
type="text/css"?>
|
||||
<!-- SeaMonkey tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://navigator/content/navigator.css"
|
||||
type="text/css"?>
|
||||
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
@ -16,6 +9,8 @@
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js" />
|
||||
@ -23,201 +18,186 @@
|
||||
src="../role.js" />
|
||||
<script type="application/javascript"
|
||||
src="../events.js" />
|
||||
<script type="application/javascript"
|
||||
src="../browser.js"></script>
|
||||
|
||||
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Test
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
// Hack to make xul:tabbrowser work.
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
var handleDroppedLink = null;
|
||||
var XULBrowserWindow = {
|
||||
isBusy: false,
|
||||
setOverLink: function (link, b) {}
|
||||
};
|
||||
var gFindBar = {
|
||||
hidden: true
|
||||
};
|
||||
|
||||
function doTest()
|
||||
// invoker
|
||||
function testTabHierarchy()
|
||||
{
|
||||
var tabBrowser = document.getElementById("tabbrowser");
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 0),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
|
||||
];
|
||||
|
||||
// Load documents into tabs and wait for reorder events caused by these
|
||||
// documents load before we start the test.
|
||||
var docURIs = ["about:", "about:mozilla"];
|
||||
|
||||
var handler = {
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
var target = aEvent.accessible;
|
||||
if (target.role == ROLE_INTERNAL_FRAME &&
|
||||
target.parent.parent == getAccessible(this.tabBrowser.mTabBox.tabpanels)) {
|
||||
this.reorderCnt++;
|
||||
}
|
||||
|
||||
if (this.reorderCnt == docURIs.length) {
|
||||
unregisterA11yEventListener(EVENT_REORDER, this);
|
||||
testAccTree();
|
||||
}
|
||||
},
|
||||
|
||||
tabBrowser: tabBrowser,
|
||||
reorderCnt: 0
|
||||
};
|
||||
registerA11yEventListener(EVENT_REORDER, handler);
|
||||
|
||||
// Test XUL and HTML documents.
|
||||
tabBrowser.loadTabs(docURIs, false, true);
|
||||
}
|
||||
|
||||
function testAccTree()
|
||||
{
|
||||
var tabBrowser = document.getElementById("tabbrowser");
|
||||
|
||||
////////////////////
|
||||
// Tab bar
|
||||
////////////////////
|
||||
var tabsAccTree = {
|
||||
// xul:tabs
|
||||
role: ROLE_PAGETABLIST,
|
||||
children: [
|
||||
// Children depend on application (UI): see below.
|
||||
]
|
||||
};
|
||||
|
||||
// SeaMonkey and Firefox tabbrowser UIs differ.
|
||||
if ("restoreTab" in tabBrowser) {
|
||||
SimpleTest.ok(true, "Testing SeaMonkey tabbrowser UI.");
|
||||
|
||||
tabsAccTree.children.splice(0, 0,
|
||||
{
|
||||
// xul:toolbarbutton ("Open a new tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
// xul:tab ("about:")
|
||||
role: ROLE_PAGETAB,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
// tab ("about:mozilla")
|
||||
role: ROLE_PAGETAB,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
// xul:toolbarbutton ("List all tabs")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: [
|
||||
{
|
||||
// xul:menupopup
|
||||
role: ROLE_MENUPOPUP,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// xul:toolbarbutton ("Close current tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
);
|
||||
} else {
|
||||
SimpleTest.ok(true, "Testing Firefox tabbrowser UI.");
|
||||
|
||||
// NB: The (3) buttons are not visible, unless manually hovered,
|
||||
// probably due to size reduction in this test.
|
||||
tabsAccTree.children.splice(0, 0,
|
||||
{
|
||||
// xul:tab ("about:")
|
||||
role: ROLE_PAGETAB,
|
||||
children: [
|
||||
{
|
||||
// xul:toolbarbutton ("Close Tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// tab ("about:mozilla")
|
||||
role: ROLE_PAGETAB,
|
||||
children: [
|
||||
{
|
||||
// xul:toolbarbutton ("Close Tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// xul:toolbarbutton ("Open a new tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
// "List all tabs" dropdown
|
||||
// XXX: This child(?) is not present in this test.
|
||||
// I'm not sure why (though probably expected).
|
||||
);
|
||||
this.invoke = function testTabHierarchy_invoke()
|
||||
{
|
||||
var docURIs = ["about:", "about:mozilla"];
|
||||
tabBrowser().loadTabs(docURIs, false, true);
|
||||
}
|
||||
|
||||
testAccessibleTree(tabBrowser.tabContainer, tabsAccTree);
|
||||
this.finalCheck = function testTabHierarchy_finalCheck(aEvent)
|
||||
{
|
||||
////////////////////
|
||||
// Tab bar
|
||||
////////////////////
|
||||
var tabsAccTree = {
|
||||
// xul:tabs
|
||||
role: ROLE_PAGETABLIST,
|
||||
children: [
|
||||
// Children depend on application (UI): see below.
|
||||
]
|
||||
};
|
||||
|
||||
////////////////////
|
||||
// Tab contents
|
||||
////////////////////
|
||||
var tabboxAccTree = {
|
||||
// xul:tabpanels
|
||||
role: ROLE_PANE,
|
||||
children: [
|
||||
{
|
||||
// xul:notificationbox
|
||||
role: ROLE_PROPERTYPAGE,
|
||||
children: [
|
||||
{
|
||||
// xul:browser
|
||||
role: ROLE_INTERNAL_FRAME,
|
||||
children: [
|
||||
{
|
||||
// #document ("about:")
|
||||
role: ROLE_DOCUMENT
|
||||
// children: [ ... ] // Ignore document content.
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// notificationbox
|
||||
role: ROLE_PROPERTYPAGE,
|
||||
children: [
|
||||
{
|
||||
// browser
|
||||
role: ROLE_INTERNAL_FRAME,
|
||||
children: [
|
||||
{
|
||||
// #document ("about:mozilla")
|
||||
role: ROLE_DOCUMENT
|
||||
// children: [ ... ] // Ignore document content.
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
// SeaMonkey and Firefox tabbrowser UIs differ.
|
||||
if ("restoreTab" in tabBrowser) {
|
||||
SimpleTest.ok(true, "Testing SeaMonkey tabbrowser UI.");
|
||||
|
||||
testAccessibleTree(tabBrowser.mTabBox.tabpanels, tabboxAccTree);
|
||||
tabsAccTree.children.splice(0, 0,
|
||||
{
|
||||
// xul:toolbarbutton ("Open a new tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
// xul:tab ("about:")
|
||||
role: ROLE_PAGETAB,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
// tab ("about:mozilla")
|
||||
role: ROLE_PAGETAB,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
// xul:toolbarbutton ("List all tabs")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: [
|
||||
{
|
||||
// xul:menupopup
|
||||
role: ROLE_MENUPOPUP,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// xul:toolbarbutton ("Close current tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
);
|
||||
} else {
|
||||
SimpleTest.ok(true, "Testing Firefox tabbrowser UI.");
|
||||
|
||||
SimpleTest.finish();
|
||||
// NB: The (3) buttons are not visible, unless manually hovered,
|
||||
// probably due to size reduction in this test.
|
||||
tabsAccTree.children.splice(0, 0,
|
||||
{
|
||||
// xul:tab ("about:")
|
||||
role: ROLE_PAGETAB,
|
||||
children: [
|
||||
{
|
||||
// xul:toolbarbutton ("Close Tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// tab ("about:mozilla")
|
||||
role: ROLE_PAGETAB,
|
||||
children: [
|
||||
{
|
||||
// xul:toolbarbutton ("Close Tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// xul:toolbarbutton ("Open a new tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
// "List all tabs" dropdown
|
||||
// XXX: This child(?) is not present in this test.
|
||||
// I'm not sure why (though probably expected).
|
||||
);
|
||||
}
|
||||
|
||||
testAccessibleTree(tabBrowser().tabContainer, tabsAccTree);
|
||||
|
||||
////////////////////
|
||||
// Tab contents
|
||||
////////////////////
|
||||
var tabboxAccTree = {
|
||||
// xul:tabpanels
|
||||
role: ROLE_PANE,
|
||||
children: [
|
||||
{
|
||||
// xul:notificationbox
|
||||
role: ROLE_PROPERTYPAGE,
|
||||
children: [
|
||||
{
|
||||
// xul:browser
|
||||
role: ROLE_INTERNAL_FRAME,
|
||||
children: [
|
||||
{
|
||||
// #document ("about:")
|
||||
role: ROLE_DOCUMENT
|
||||
// children: [ ... ] // Ignore document content.
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// notificationbox
|
||||
role: ROLE_PROPERTYPAGE,
|
||||
children: [
|
||||
{
|
||||
// browser
|
||||
role: ROLE_INTERNAL_FRAME,
|
||||
children: [
|
||||
{
|
||||
// #document ("about:mozilla")
|
||||
role: ROLE_DOCUMENT
|
||||
// children: [ ... ] // Ignore document content.
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
testAccessibleTree(tabBrowser().mTabBox.tabpanels, tabboxAccTree);
|
||||
}
|
||||
|
||||
this.getID = function testTabHierarchy_getID()
|
||||
{
|
||||
return "hierarchy of tabs";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Test
|
||||
var gQueue = null;
|
||||
function doTest()
|
||||
{
|
||||
// Load documents into tabs and wait for docLoadComplete events caused by these
|
||||
// documents load before we start the test.
|
||||
gQueue = new eventQueue();
|
||||
|
||||
gQueue.push(new testTabHierarchy());
|
||||
gQueue.onFinish = function() { closeBrowserWindow(); }
|
||||
gQueue.invoke(); // Will call SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
openBrowserWindow(doTest);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
@ -240,26 +220,7 @@
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
<!-- Hack to make xul:tabbrowser work -->
|
||||
<menubar>
|
||||
<menu label="menu">
|
||||
<menupopup>
|
||||
<menuitem label="close window hook" id="menu_closeWindow"/>
|
||||
<menuitem label="close hook" id="menu_close"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
</menubar>
|
||||
|
||||
<tabs id="tabbrowser-tabs" class="tabbrowser-tabs"
|
||||
tabbrowser="tabbrowser"
|
||||
setfocus="false">
|
||||
<tab class="tabbrowser-tab" selected="true" fadein="true"/>
|
||||
</tabs>
|
||||
<tabbrowser id="tabbrowser"
|
||||
type="content-primary"
|
||||
tabcontainer="tabbrowser-tabs"
|
||||
flex="1"/>
|
||||
<toolbar id="addon-bar"/>
|
||||
<vbox id="eventdump"></vbox>
|
||||
</vbox>
|
||||
|
||||
</window>
|
||||
|
@ -38,7 +38,6 @@
|
||||
#filter substitution
|
||||
|
||||
pref("toolkit.defaultChromeURI", "chrome://browser/content/shell.xul");
|
||||
pref("general.useragent.compatMode.firefox", true);
|
||||
pref("browser.chromeURL", "chrome://browser/content/");
|
||||
#ifdef MOZ_OFFICIAL_BRANDING
|
||||
pref("browser.homescreenURL", "file:///system/home/homescreen.html");
|
||||
@ -394,6 +393,15 @@ pref("layers.acceleration.force-enabled", true);
|
||||
pref("dom.screenEnabledProperty.enabled", true);
|
||||
pref("dom.screenBrightnessProperty.enabled", true);
|
||||
|
||||
// handle links targeting new windows
|
||||
// 1=current window/tab, 2=new window, 3=new tab in most recent window
|
||||
pref("browser.link.open_newwindow", 3);
|
||||
|
||||
// 0: no restrictions - divert everything
|
||||
// 1: don't divert window.open at all
|
||||
// 2: don't divert window.open with features
|
||||
pref("browser.link.open_newwindow.restriction", 0);
|
||||
|
||||
// Enable browser frame
|
||||
pref("dom.mozBrowserFramesEnabled", true);
|
||||
pref("dom.mozBrowserFramesWhitelist", "http://localhost:6666");
|
||||
@ -401,6 +409,7 @@ pref("dom.mozBrowserFramesWhitelist", "http://localhost:6666");
|
||||
// Temporary permission hack for WebSMS
|
||||
pref("dom.sms.enabled", true);
|
||||
pref("dom.sms.whitelist", "file://,http://localhost:6666");
|
||||
|
||||
// Ignore X-Frame-Options headers.
|
||||
pref("b2g.ignoreXFrameOptions", true);
|
||||
|
||||
@ -412,3 +421,6 @@ pref("b2g.ignoreXFrameOptions", true);
|
||||
// secondary bug isn't really worth investigating since it's obseleted
|
||||
// by bug 710563.
|
||||
pref("layout.frame_rate.precise", true);
|
||||
|
||||
// Screen timeout in minutes
|
||||
pref("power.screen.timeout", 60);
|
||||
|
@ -47,17 +47,19 @@ const LocalFile = CC('@mozilla.org/file/local;1',
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyGetter(Services, 'env', function() {
|
||||
return Cc['@mozilla.org/process/environment;1']
|
||||
.getService(Ci.nsIEnvironment);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(Services, 'ss', function() {
|
||||
return Cc['@mozilla.org/content/style-sheet-service;1']
|
||||
.getService(Ci.nsIStyleSheetService);
|
||||
});
|
||||
XPCOMUtils.defineLazyGetter(Services, 'fm', function() {
|
||||
return Cc['@mozilla.org/focus-manager;1']
|
||||
.getService(Ci.nsIFocusManager);
|
||||
XPCOMUtils.defineLazyGetter(Services, 'idle', function() {
|
||||
return Cc['@mozilla.org/widget/idleservice;1']
|
||||
.getService(Ci.nsIIdleService);
|
||||
});
|
||||
|
||||
// In order to use http:// scheme instead of file:// scheme
|
||||
@ -76,7 +78,7 @@ function startupHttpd(baseDir, port) {
|
||||
|
||||
// FIXME Bug 707625
|
||||
// until we have a proper security model, add some rights to
|
||||
// the pre-installed web applications
|
||||
// the pre-installed web applications
|
||||
function addPermissions(urls) {
|
||||
let permissions = [
|
||||
'indexedDB', 'indexedDB-unlimited', 'webapps-manage', 'offline-app'
|
||||
@ -84,7 +86,7 @@ function addPermissions(urls) {
|
||||
urls.forEach(function(url) {
|
||||
let uri = Services.io.newURI(url, null, null);
|
||||
let allow = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
|
||||
|
||||
permissions.forEach(function(permission) {
|
||||
Services.perms.add(uri, permission, allow);
|
||||
});
|
||||
@ -96,9 +98,9 @@ var shell = {
|
||||
// FIXME/bug 678695: this should be a system setting
|
||||
preferredScreenBrightness: 1.0,
|
||||
|
||||
get home() {
|
||||
delete this.home;
|
||||
return this.home = document.getElementById('homescreen');
|
||||
get contentBrowser() {
|
||||
delete this.contentBrowser;
|
||||
return this.contentBrowser = document.getElementById('homescreen');
|
||||
},
|
||||
|
||||
get homeURL() {
|
||||
@ -131,7 +133,7 @@ var shell = {
|
||||
window.controllers.appendController(this);
|
||||
window.addEventListener('keypress', this);
|
||||
window.addEventListener('MozApplicationManifest', this);
|
||||
this.home.addEventListener('load', this, true);
|
||||
this.contentBrowser.addEventListener('load', this, true);
|
||||
|
||||
try {
|
||||
Services.io.offline = false;
|
||||
@ -156,7 +158,15 @@ var shell = {
|
||||
return alert(msg);
|
||||
}
|
||||
|
||||
let browser = this.home;
|
||||
// Load webapi+apps.js as a frame script
|
||||
let frameScriptUrl = 'chrome://browser/content/webapi.js';
|
||||
try {
|
||||
messageManager.loadFrameScript(frameScriptUrl, true);
|
||||
} catch (e) {
|
||||
dump('Error when loading ' + frameScriptUrl + ' as a frame script: ' + e + '\n');
|
||||
}
|
||||
|
||||
let browser = this.contentBrowser;
|
||||
browser.homePage = homeURL;
|
||||
browser.goHome();
|
||||
},
|
||||
@ -187,7 +197,7 @@ var shell = {
|
||||
doCommand: function shell_doCommand(cmd) {
|
||||
switch (cmd) {
|
||||
case 'cmd_close':
|
||||
this.home.contentWindow.postMessage('appclose', '*');
|
||||
content.postMessage('appclose', '*');
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -197,7 +207,7 @@ var shell = {
|
||||
case 'keypress':
|
||||
switch (evt.keyCode) {
|
||||
case evt.DOM_VK_HOME:
|
||||
this.sendEvent(this.home.contentWindow, 'home');
|
||||
this.sendEvent(content, 'home');
|
||||
break;
|
||||
case evt.DOM_VK_SLEEP:
|
||||
this.toggleScreen();
|
||||
@ -205,7 +215,7 @@ var shell = {
|
||||
let details = {
|
||||
'enabled': screen.mozEnabled
|
||||
};
|
||||
this.sendEvent(this.home.contentWindow, 'sleep', details);
|
||||
this.sendEvent(content, 'sleep', details);
|
||||
break;
|
||||
case evt.DOM_VK_ESCAPE:
|
||||
if (evt.defaultPrevented)
|
||||
@ -215,8 +225,12 @@ var shell = {
|
||||
}
|
||||
break;
|
||||
case 'load':
|
||||
this.home.removeEventListener('load', this, true);
|
||||
this.contentBrowser.removeEventListener('load', this, true);
|
||||
this.turnScreenOn();
|
||||
|
||||
let chromeWindow = window.QueryInterface(Ci.nsIDOMChromeWindow);
|
||||
chromeWindow.browserDOMWindow = new nsBrowserAccess();
|
||||
|
||||
this.sendEvent(window, 'ContentStart');
|
||||
break;
|
||||
case 'MozApplicationManifest':
|
||||
@ -229,7 +243,7 @@ var shell = {
|
||||
if (!documentElement)
|
||||
return;
|
||||
|
||||
let manifest = documentElement.getAttribute("manifest");
|
||||
let manifest = documentElement.getAttribute('manifest');
|
||||
if (!manifest)
|
||||
return;
|
||||
|
||||
@ -273,78 +287,60 @@ var shell = {
|
||||
turnScreenOn: function shell_turnScreenOn() {
|
||||
screen.mozEnabled = true;
|
||||
screen.mozBrightness = this.preferredScreenBrightness;
|
||||
},
|
||||
};
|
||||
|
||||
(function VirtualKeyboardManager() {
|
||||
let activeElement = null;
|
||||
let isKeyboardOpened = false;
|
||||
|
||||
let constructor = {
|
||||
handleEvent: function vkm_handleEvent(evt) {
|
||||
let contentWindow = shell.home.contentWindow.wrappedJSObject;
|
||||
|
||||
switch (evt.type) {
|
||||
case 'ContentStart':
|
||||
contentWindow.navigator.mozKeyboard = new MozKeyboard();
|
||||
break;
|
||||
case 'keypress':
|
||||
if (evt.keyCode != evt.DOM_VK_ESCAPE || !isKeyboardOpened)
|
||||
return;
|
||||
|
||||
shell.sendEvent(contentWindow, 'hideime');
|
||||
isKeyboardOpened = false;
|
||||
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
break;
|
||||
case 'mousedown':
|
||||
if (evt.target != activeElement || isKeyboardOpened)
|
||||
return;
|
||||
|
||||
let type = activeElement.type;
|
||||
shell.sendEvent(contentWindow, 'showime', { type: type });
|
||||
isKeyboardOpened = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
observe: function vkm_observe(subject, topic, data) {
|
||||
let contentWindow = shell.home.contentWindow;
|
||||
|
||||
let shouldOpen = parseInt(data);
|
||||
if (shouldOpen && !isKeyboardOpened) {
|
||||
activeElement = Services.fm.focusedElement;
|
||||
if (!activeElement)
|
||||
return;
|
||||
|
||||
let type = activeElement.type;
|
||||
shell.sendEvent(contentWindow, 'showime', { type: type });
|
||||
} else if (!shouldOpen && isKeyboardOpened) {
|
||||
shell.sendEvent(contentWindow, 'hideime');
|
||||
}
|
||||
isKeyboardOpened = shouldOpen;
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(constructor, 'ime-enabled-state-changed', false);
|
||||
['ContentStart', 'keypress', 'mousedown'].forEach(function vkm_events(type) {
|
||||
window.addEventListener(type, constructor, true);
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
function MozKeyboard() {
|
||||
}
|
||||
|
||||
MozKeyboard.prototype = {
|
||||
sendKey: function mozKeyboardSendKey(keyCode, charCode) {
|
||||
charCode = (charCode == undefined) ? keyCode : charCode;
|
||||
|
||||
var utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
['keydown', 'keypress', 'keyup'].forEach(function sendKeyEvents(type) {
|
||||
utils.sendKeyEvent(type, keyCode, charCode, null);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
(function PowerManager() {
|
||||
let idleHandler = {
|
||||
observe: function(subject, topic, time) {
|
||||
if (topic === "idle") {
|
||||
// TODO: Check wakelock status. See bug 697132.
|
||||
shell.turnScreenOff();
|
||||
}
|
||||
},
|
||||
}
|
||||
let idleTimeout = Services.prefs.getIntPref("power.screen.timeout");
|
||||
if (idleTimeout) {
|
||||
Services.idle.addIdleObserver(idleHandler, idleTimeout);
|
||||
}
|
||||
})();
|
||||
|
||||
function nsBrowserAccess() {
|
||||
}
|
||||
|
||||
nsBrowserAccess.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]),
|
||||
|
||||
openURI: function openURI(uri, opener, where, context) {
|
||||
// TODO This should be replaced by an 'open-browser-window' intent
|
||||
let contentWindow = content.wrappedJSObject;
|
||||
if (!('getApplicationManager' in contentWindow))
|
||||
return null;
|
||||
|
||||
let applicationManager = contentWindow.getApplicationManager();
|
||||
if (!applicationManager)
|
||||
return null;
|
||||
|
||||
let url = uri ? uri.spec : 'about:blank';
|
||||
let window = applicationManager.launch(url, where);
|
||||
return window.contentWindow;
|
||||
},
|
||||
|
||||
openURIInFrame: function openURIInFrame(uri, opener, where, context) {
|
||||
throw new Error('Not Implemented');
|
||||
},
|
||||
|
||||
isTabContentWindow: function isTabContentWindow(contentWindow) {
|
||||
return contentWindow == window;
|
||||
}
|
||||
};
|
||||
|
||||
// Pipe `console` log messages to the nsIConsoleService which writes them
|
||||
// to logcat.
|
||||
Services.obs.addObserver(function onConsoleAPILogEvent(subject, topic, data) {
|
||||
let message = subject.wrappedJSObject;
|
||||
let prefix = "Content JS " + message.level.toUpperCase() +
|
||||
" at " + message.filename + ":" + message.lineNumber +
|
||||
" in " + (message.functionName || "anonymous") + ": ";
|
||||
Services.console.logStringMessage(prefix + Array.join(message.arguments, " "));
|
||||
}, "console-api-log-event", false);
|
||||
|
@ -66,12 +66,12 @@
|
||||
events: ['mousedown', 'mousemove', 'mouseup', 'click', 'unload'],
|
||||
start: function teh_start() {
|
||||
this.events.forEach((function(evt) {
|
||||
shell.home.addEventListener(evt, this, true);
|
||||
shell.contentBrowser.addEventListener(evt, this, true);
|
||||
}).bind(this));
|
||||
},
|
||||
stop: function teh_stop() {
|
||||
this.events.forEach((function(evt) {
|
||||
shell.home.removeEventListener(evt, this, true);
|
||||
shell.contentBrowser.removeEventListener(evt, this, true);
|
||||
}).bind(this));
|
||||
},
|
||||
handleEvent: function teh_handleEvent(evt) {
|
||||
@ -139,12 +139,7 @@
|
||||
return;
|
||||
|
||||
case 'click':
|
||||
if (!isNewTouchAction) {
|
||||
debug('click: cancel');
|
||||
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
} else {
|
||||
if (isNewTouchAction) {
|
||||
// Mouse events has been cancelled so dispatch a sequence
|
||||
// of events to where touchend has been fired
|
||||
if (preventMouseEvents) {
|
||||
|
1791
b2g/chrome/content/webapi.js
Normal file
1791
b2g/chrome/content/webapi.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ chrome.jar:
|
||||
content/touch.js (content/touch.js)
|
||||
content/commandUtil.js (content/commandUtil.js)
|
||||
content/httpd.js (content/httpd.js)
|
||||
content/webapi.js (content/webapi.js)
|
||||
content/content.css (content/content.css)
|
||||
|
||||
% override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
|
||||
|
@ -38,7 +38,8 @@
|
||||
MOZ_APP_BASENAME=B2G
|
||||
MOZ_APP_VENDOR=Mozilla
|
||||
|
||||
MOZ_APP_VERSION=11.0a1
|
||||
MOZ_APP_VERSION=13.0a1
|
||||
MOZ_APP_UA_NAME=Firefox
|
||||
|
||||
MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
|
||||
MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
|
||||
@ -47,6 +48,7 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
|
||||
MOZ_SAFE_BROWSING=
|
||||
MOZ_SERVICES_SYNC=
|
||||
|
||||
MOZ_WEBSMS_BACKEND=1
|
||||
MOZ_DISABLE_DOMCRYPTO=1
|
||||
MOZ_APP_STATIC_INI=1
|
||||
|
||||
|
@ -162,6 +162,7 @@
|
||||
@BINPATH@/components/dom_indexeddb.xpt
|
||||
@BINPATH@/components/dom_offline.xpt
|
||||
@BINPATH@/components/dom_json.xpt
|
||||
@BINPATH@/components/dom_power.xpt
|
||||
@BINPATH@/components/dom_range.xpt
|
||||
@BINPATH@/components/dom_sidebar.xpt
|
||||
@BINPATH@/components/dom_sms.xpt
|
||||
|
@ -107,11 +107,6 @@ ifdef _MSC_VER
|
||||
WIN32_EXE_LDFLAGS += -ENTRY:wmainCRTStartup
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
OS_LIBS += $(call EXPAND_LIBNAME,comctl32 comdlg32 uuid shell32 ole32 oleaut32 version winspool)
|
||||
OS_LIBS += $(call EXPAND_LIBNAME,usp10 msimg32)
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
RCINCLUDE = splash.rc
|
||||
ifndef GNU_CC
|
||||
|
@ -1,6 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1327073984000">
|
||||
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1328289666000">
|
||||
<emItems>
|
||||
<emItem blockID="i58" id="webmaster@buzzzzvideos.info">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i41" id="{99079a25-328f-4bd4-be04-00955acaa0a7}">
|
||||
<versionRange minVersion="0.1" maxVersion="4.3.1.00" severity="1">
|
||||
</versionRange>
|
||||
@ -27,6 +31,10 @@
|
||||
<versionRange minVersion="1.1b1" maxVersion="1.1b1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i54" id="applebeegifts@mozilla.doslash.org">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i16" id="{27182e60-b5f3-411c-b545-b44205977502}">
|
||||
<versionRange minVersion="1.0" maxVersion="1.0">
|
||||
</versionRange>
|
||||
@ -39,8 +47,10 @@
|
||||
<versionRange minVersion="0.1" maxVersion="14.4.0" severity="1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i53" id="{a3a5c777-f583-4fef-9380-ab4add1bc2a8}">
|
||||
<versionRange minVersion="2.0.3" maxVersion="2.0.3">
|
||||
<emItem blockID="i61" id="youtube@youtube3.com">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i10" id="{8CE11043-9A15-4207-A565-0C94C42D590D}">
|
||||
@ -81,6 +91,14 @@
|
||||
<versionRange minVersion="0.1" maxVersion="4.3.1.00" severity="1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i53" id="{a3a5c777-f583-4fef-9380-ab4add1bc2a8}">
|
||||
<versionRange minVersion="2.0.3" maxVersion="2.0.3">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i59" id="ghostviewer@youtube2.com">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i51" id="admin@youtubeplayer.com">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
@ -92,11 +110,16 @@
|
||||
</targetApplication>
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i23" id="firefox@bandoo.com">
|
||||
<versionRange minVersion="5.0" maxVersion="5.0" severity="1">
|
||||
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
|
||||
<versionRange minVersion="3.7a1pre" maxVersion="*" />
|
||||
</targetApplication>
|
||||
<emItem blockID="i60" id="youtb3@youtb3.com">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i56" id="flash@adobe.com">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i55" id="youtube@youtube7.com">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i11" id="yslow@yahoo-inc.com">
|
||||
@ -143,6 +166,13 @@
|
||||
<versionRange minVersion="2.2" maxVersion="2.2">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i23" id="firefox@bandoo.com">
|
||||
<versionRange minVersion="5.0" maxVersion="5.0" severity="1">
|
||||
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
|
||||
<versionRange minVersion="3.7a1pre" maxVersion="*" />
|
||||
</targetApplication>
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i45" id="{22119944-ED35-4ab1-910B-E619EA06A115}">
|
||||
<versionRange minVersion="0.1" maxVersion="7.6.1">
|
||||
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
|
||||
|
@ -65,6 +65,7 @@ pref("extensions.minCompatibleAppVersion", "4.0");
|
||||
pref("extensions.getAddons.cache.enabled", true);
|
||||
pref("extensions.getAddons.maxResults", 15);
|
||||
pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%");
|
||||
pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
|
||||
pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%");
|
||||
pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox");
|
||||
pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
|
||||
@ -280,7 +281,7 @@ pref("browser.urlbar.doubleClickSelectsAll", true);
|
||||
#else
|
||||
pref("browser.urlbar.doubleClickSelectsAll", false);
|
||||
#endif
|
||||
pref("browser.urlbar.autoFill", true);
|
||||
pref("browser.urlbar.autoFill", false);
|
||||
// 0: Match anywhere (e.g., middle of words)
|
||||
// 1: Match on word boundaries and then try matching anywhere
|
||||
// 2: Match only on word boundaries (e.g., after / or .)
|
||||
@ -293,7 +294,7 @@ pref("browser.urlbar.maxRichResults", 12);
|
||||
// The amount of time (ms) to wait after the user has stopped typing
|
||||
// before starting to perform autocomplete. 50 is the default set in
|
||||
// autocomplete.xml.
|
||||
pref("browser.urlbar.delay", 0);
|
||||
pref("browser.urlbar.delay", 50);
|
||||
|
||||
// The special characters below can be typed into the urlbar to either restrict
|
||||
// the search to visited history, bookmarked, tagged pages; or force a match on
|
||||
@ -1110,5 +1111,11 @@ pref("prompts.tab_modal.enabled", true);
|
||||
// Whether the Panorama should animate going in/out of tabs
|
||||
pref("browser.panorama.animate_zoom", true);
|
||||
|
||||
// Defines the url to be used for new tabs.
|
||||
pref("browser.newtab.url", "about:newtab");
|
||||
|
||||
// Toggles the content of 'about:newtab'. Shows the grid when enabled.
|
||||
pref("browser.newtabpage.enabled", true);
|
||||
|
||||
// Enable the DOM full-screen API.
|
||||
pref("full-screen-api.enabled", true);
|
||||
|
@ -49,9 +49,7 @@ abs_srcdir = $(call core_abspath,$(srcdir))
|
||||
|
||||
CHROME_DEPS += $(abs_srcdir)/content/overrides/app-license.html
|
||||
|
||||
ifdef ENABLE_TESTS
|
||||
DIRS += content/test
|
||||
endif
|
||||
TEST_DIRS += content/test
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
|
@ -225,7 +225,7 @@ var FullZoom = {
|
||||
return;
|
||||
|
||||
// Avoid the cps roundtrip and apply the default/global pref.
|
||||
if (aURI.spec == "about:blank") {
|
||||
if (isBlankPageURL(aURI.spec)) {
|
||||
this._applyPrefToSetting(undefined, aBrowser);
|
||||
return;
|
||||
}
|
||||
|
@ -1012,7 +1012,7 @@ var PlacesStarButton = {
|
||||
}
|
||||
|
||||
// We can load about:blank before the actual page, but there is no point in handling that page.
|
||||
if (this._uri.spec == "about:blank") {
|
||||
if (isBlankPageURL(this._uri.spec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -137,8 +137,9 @@ __defineGetter__("gPrefService", function() {
|
||||
});
|
||||
|
||||
__defineGetter__("AddonManager", function() {
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
return this.AddonManager;
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm", tmp);
|
||||
return this.AddonManager = tmp.AddonManager;
|
||||
});
|
||||
__defineSetter__("AddonManager", function (val) {
|
||||
delete this.AddonManager;
|
||||
@ -188,6 +189,7 @@ XPCOMUtils.defineLazyGetter(this, "Tilt", function() {
|
||||
|
||||
let gInitialPages = [
|
||||
"about:blank",
|
||||
"about:newtab",
|
||||
"about:privatebrowsing",
|
||||
"about:sessionrestore"
|
||||
];
|
||||
@ -1502,7 +1504,9 @@ function prepareForStartup() {
|
||||
}
|
||||
|
||||
function delayedStartup(isLoadingBlank, mustLoadSidebar) {
|
||||
Cu.import("resource:///modules/TelemetryTimestamps.jsm");
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/TelemetryTimestamps.jsm", tmp);
|
||||
let TelemetryTimestamps = tmp.TelemetryTimestamps;
|
||||
TelemetryTimestamps.add("delayedStartupStarted");
|
||||
gDelayedStartupTimeoutId = null;
|
||||
|
||||
@ -1765,6 +1769,23 @@ function delayedStartup(isLoadingBlank, mustLoadSidebar) {
|
||||
document.getElementById("appmenu_charsetMenu").hidden = true;
|
||||
#endif
|
||||
|
||||
let appMenuButton = document.getElementById("appmenu-button");
|
||||
let appMenuPopup = document.getElementById("appmenu-popup");
|
||||
if (appMenuButton && appMenuPopup) {
|
||||
let appMenuOpening = null;
|
||||
appMenuButton.addEventListener("mousedown", function(event) {
|
||||
if (event.button == 0)
|
||||
appMenuOpening = new Date();
|
||||
}, false);
|
||||
appMenuPopup.addEventListener("popupshown", function(event) {
|
||||
if (event.target != appMenuPopup || !appMenuOpening)
|
||||
return;
|
||||
let duration = new Date() - appMenuOpening;
|
||||
appMenuOpening = null;
|
||||
Services.telemetry.getHistogramById("FX_APP_MENU_OPEN_MS").add(duration);
|
||||
}, false);
|
||||
}
|
||||
|
||||
window.addEventListener("mousemove", MousePosTracker, false);
|
||||
window.addEventListener("dragover", MousePosTracker, false);
|
||||
|
||||
@ -2204,7 +2225,7 @@ function openLocation() {
|
||||
else {
|
||||
// If there are no open browser windows, open a new one
|
||||
win = window.openDialog("chrome://browser/content/", "_blank",
|
||||
"chrome,all,dialog=no", "about:blank");
|
||||
"chrome,all,dialog=no", BROWSER_NEW_TAB_URL);
|
||||
win.addEventListener("load", openLocationCallback, false);
|
||||
}
|
||||
return;
|
||||
@ -2222,7 +2243,7 @@ function openLocationCallback()
|
||||
|
||||
function BrowserOpenTab()
|
||||
{
|
||||
openUILinkIn("about:blank", "tab");
|
||||
openUILinkIn(BROWSER_NEW_TAB_URL, "tab");
|
||||
}
|
||||
|
||||
/* Called from the openLocation dialog. This allows that dialog to instruct
|
||||
@ -2557,7 +2578,7 @@ function URLBarSetURI(aURI) {
|
||||
else
|
||||
value = losslessDecodeURI(uri);
|
||||
|
||||
valid = (uri.spec != "about:blank");
|
||||
valid = !isBlankPageURL(uri.spec);
|
||||
}
|
||||
|
||||
gURLBar.value = value;
|
||||
@ -2874,7 +2895,7 @@ function getMeOutOfHere() {
|
||||
// Get the start page from the *default* pref branch, not the user's
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService).getDefaultBranch(null);
|
||||
var url = "about:blank";
|
||||
var url = BROWSER_NEW_TAB_URL;
|
||||
try {
|
||||
url = prefs.getComplexValue("browser.startup.homepage",
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
@ -3135,13 +3156,17 @@ var browserDragAndDrop = {
|
||||
}
|
||||
},
|
||||
|
||||
drop: function (aEvent, aName) Services.droppedLinkHandler.dropLink(aEvent, aName)
|
||||
drop: function (aEvent, aName, aDisallowInherit) {
|
||||
return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit);
|
||||
}
|
||||
};
|
||||
|
||||
var homeButtonObserver = {
|
||||
onDrop: function (aEvent)
|
||||
{
|
||||
setTimeout(openHomeDialog, 0, browserDragAndDrop.drop(aEvent, { }));
|
||||
// disallow setting home pages that inherit the principal
|
||||
let url = browserDragAndDrop.drop(aEvent, {}, true);
|
||||
setTimeout(openHomeDialog, 0, url);
|
||||
},
|
||||
|
||||
onDragOver: function (aEvent)
|
||||
@ -5192,7 +5217,7 @@ nsBrowserAccess.prototype = {
|
||||
case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
|
||||
// FIXME: Bug 408379. So how come this doesn't send the
|
||||
// referrer like the other loads do?
|
||||
var url = aURI ? aURI.spec : "about:blank";
|
||||
var url = aURI ? aURI.spec : BROWSER_NEW_TAB_URL;
|
||||
// Pass all params to openDialog to ensure that "url" isn't passed through
|
||||
// loadOneOrMoreURIs, which splits based on "|"
|
||||
newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
|
||||
@ -5225,7 +5250,7 @@ nsBrowserAccess.prototype = {
|
||||
let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
|
||||
let referrer = aOpener ? makeURI(aOpener.location.href) : null;
|
||||
|
||||
let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
|
||||
let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : BROWSER_NEW_TAB_URL, {
|
||||
referrerURI: referrer,
|
||||
fromExternal: isExternal,
|
||||
inBackground: loadInBackground});
|
||||
@ -5454,7 +5479,7 @@ var TabsInTitlebar = {
|
||||
if (!this._draghandle) {
|
||||
let tmp = {};
|
||||
Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
|
||||
this._draghandle = new tmp.WindowDraggingElement(tabsToolbar, window);
|
||||
this._draghandle = new tmp.WindowDraggingElement(tabsToolbar);
|
||||
this._draghandle.mouseDownCheck = function () {
|
||||
return !this._dragBindingAlive && TabsInTitlebar.enabled;
|
||||
};
|
||||
@ -7774,9 +7799,11 @@ function undoCloseWindow(aIndex) {
|
||||
*/
|
||||
function isTabEmpty(aTab) {
|
||||
let browser = aTab.linkedBrowser;
|
||||
let uri = browser.currentURI.spec;
|
||||
let body = browser.contentDocument.body;
|
||||
return browser.sessionHistory.count < 2 &&
|
||||
browser.currentURI.spec == "about:blank" &&
|
||||
!browser.contentDocument.body.hasChildNodes() &&
|
||||
isBlankPageURL(uri) &&
|
||||
(!body || !body.hasChildNodes()) &&
|
||||
!aTab.hasAttribute("busy");
|
||||
}
|
||||
|
||||
@ -8156,11 +8183,13 @@ var gIdentityHandler = {
|
||||
this._identityPopup.hidePopup();
|
||||
},
|
||||
|
||||
_popupOpenTime : null,
|
||||
|
||||
/**
|
||||
* Click handler for the identity-box element in primary chrome.
|
||||
*/
|
||||
handleIdentityButtonEvent : function(event) {
|
||||
|
||||
this._popupOpenTime = new Date();
|
||||
event.stopPropagation();
|
||||
|
||||
if ((event.type == "click" && event.button != 0) ||
|
||||
@ -8197,6 +8226,17 @@ var gIdentityHandler = {
|
||||
this._identityPopup.openPopup(this._identityBox, "bottomcenter topleft");
|
||||
},
|
||||
|
||||
onPopupShown : function(event) {
|
||||
let openingDuration = new Date() - this._popupOpenTime;
|
||||
this._popupOpenTime = null;
|
||||
try {
|
||||
Services.telemetry.getHistogramById("FX_IDENTITY_POPUP_OPEN_MS").add(openingDuration);
|
||||
} catch (ex) {
|
||||
Components.utils.reportError("Unable to report telemetry for FX_IDENTITY_POPUP_OPEN_MS.");
|
||||
}
|
||||
document.getElementById('identity-popup-more-info-button').focus();
|
||||
},
|
||||
|
||||
onDragStart: function (event) {
|
||||
if (gURLBar.getAttribute("pageproxystate") != "valid")
|
||||
return;
|
||||
|
@ -304,7 +304,7 @@
|
||||
type="arrow"
|
||||
hidden="true"
|
||||
noautofocus="true"
|
||||
onpopupshown="document.getElementById('identity-popup-more-info-button').focus();"
|
||||
onpopupshown="gIdentityHandler.onPopupShown(event);"
|
||||
level="top">
|
||||
<hbox id="identity-popup-container" align="top">
|
||||
<image id="identity-popup-icon"/>
|
||||
@ -391,7 +391,7 @@
|
||||
<panel id="customizeToolbarSheetPopup"
|
||||
noautohide="true">
|
||||
<iframe id="customizeToolbarSheetIFrame"
|
||||
style="&dialog.style;"
|
||||
style="&dialog.dimensions;"
|
||||
hidden="true"/>
|
||||
</panel>
|
||||
|
||||
@ -850,11 +850,6 @@
|
||||
label="&printButton.label;" command="cmd_print"
|
||||
tooltiptext="&printButton.tooltip;"/>
|
||||
|
||||
<toolbaritem id="navigator-throbber" title="&throbberItem.title;" align="center" pack="center"
|
||||
mousethrough="always">
|
||||
<image/>
|
||||
</toolbaritem>
|
||||
|
||||
<toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
observes="Tools:Downloads"
|
||||
ondrop="DownloadsButtonDNDObserver.onDrop(event)"
|
||||
@ -885,21 +880,6 @@
|
||||
ondragenter="newWindowButtonObserver.onDragOver(event)"
|
||||
ondragexit="newWindowButtonObserver.onDragExit(event)"/>
|
||||
|
||||
<toolbarbutton id="cut-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&cutCmd.label;"
|
||||
command="cmd_cut"
|
||||
tooltiptext="&cutButton.tooltip;"/>
|
||||
|
||||
<toolbarbutton id="copy-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="©Cmd.label;"
|
||||
command="cmd_copy"
|
||||
tooltiptext="©Button.tooltip;"/>
|
||||
|
||||
<toolbarbutton id="paste-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&pasteCmd.label;"
|
||||
command="cmd_paste"
|
||||
tooltiptext="&pasteButton.tooltip;"/>
|
||||
|
||||
<toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
observes="View:FullScreen"
|
||||
type="checkbox"
|
||||
@ -917,12 +897,7 @@
|
||||
command="cmd_fullZoomEnlarge"
|
||||
tooltiptext="&zoomInButton.tooltip;"/>
|
||||
</toolbaritem>
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<toolbarbutton id="sync-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&syncToolbarButton.label;"
|
||||
oncommand="gSyncUI.handleToolbarButton()"/>
|
||||
#endif
|
||||
|
||||
<toolbarbutton id="feed-button"
|
||||
type="menu"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
@ -937,6 +912,33 @@
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
</toolbarbutton>
|
||||
|
||||
<toolbarbutton id="cut-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&cutCmd.label;"
|
||||
command="cmd_cut"
|
||||
tooltiptext="&cutButton.tooltip;"/>
|
||||
|
||||
<toolbarbutton id="copy-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="©Cmd.label;"
|
||||
command="cmd_copy"
|
||||
tooltiptext="©Button.tooltip;"/>
|
||||
|
||||
<toolbarbutton id="paste-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&pasteCmd.label;"
|
||||
command="cmd_paste"
|
||||
tooltiptext="&pasteButton.tooltip;"/>
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
<toolbarbutton id="sync-button"
|
||||
class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&syncToolbarButton.label;"
|
||||
oncommand="gSyncUI.handleToolbarButton()"/>
|
||||
#endif
|
||||
|
||||
<toolbaritem id="navigator-throbber" title="&throbberItem.title;" align="center" pack="center"
|
||||
mousethrough="always">
|
||||
<image/>
|
||||
</toolbaritem>
|
||||
|
||||
<toolbarbutton id="tabview-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
label="&tabGroupsButton.label;"
|
||||
command="Browser:ToggleTabView"
|
||||
|
76
browser/base/content/newtab/batch.js
Normal file
76
browser/base/content/newtab/batch.js
Normal file
@ -0,0 +1,76 @@
|
||||
#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 class makes it easy to wait until a batch of callbacks has finished.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* let batch = new Batch(function () alert("finished"));
|
||||
* let pop = batch.pop.bind(batch);
|
||||
*
|
||||
* for (let i = 0; i < 5; i++) {
|
||||
* batch.push();
|
||||
* setTimeout(pop, i * 1000);
|
||||
* }
|
||||
*
|
||||
* batch.close();
|
||||
*/
|
||||
function Batch(aCallback) {
|
||||
this._callback = aCallback;
|
||||
}
|
||||
|
||||
Batch.prototype = {
|
||||
/**
|
||||
* The number of batch entries.
|
||||
*/
|
||||
_count: 0,
|
||||
|
||||
/**
|
||||
* Whether this batch is closed.
|
||||
*/
|
||||
_closed: false,
|
||||
|
||||
/**
|
||||
* Increases the number of batch entries by one.
|
||||
*/
|
||||
push: function Batch_push() {
|
||||
if (!this._closed)
|
||||
this._count++;
|
||||
},
|
||||
|
||||
/**
|
||||
* Decreases the number of batch entries by one.
|
||||
*/
|
||||
pop: function Batch_pop() {
|
||||
if (this._count)
|
||||
this._count--;
|
||||
|
||||
if (this._closed)
|
||||
this._check();
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the batch so that no new entries can be added.
|
||||
*/
|
||||
close: function Batch_close() {
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
this._closed = true;
|
||||
this._check();
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the batch has finished.
|
||||
*/
|
||||
_check: function Batch_check() {
|
||||
if (this._count == 0 && this._callback) {
|
||||
this._callback();
|
||||
this._callback = null;
|
||||
}
|
||||
}
|
||||
};
|
137
browser/base/content/newtab/cells.js
Normal file
137
browser/base/content/newtab/cells.js
Normal file
@ -0,0 +1,137 @@
|
||||
#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 class manages a cell's DOM node (not the actually cell content, a site).
|
||||
* It's mostly read-only, i.e. all manipulation of both position and content
|
||||
* aren't handled here.
|
||||
*/
|
||||
function Cell(aGrid, aNode) {
|
||||
this._grid = aGrid;
|
||||
this._node = 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);
|
||||
}, this);
|
||||
}
|
||||
|
||||
Cell.prototype = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_grid: null,
|
||||
|
||||
/**
|
||||
* The cell's DOM node.
|
||||
*/
|
||||
get node() this._node,
|
||||
|
||||
/**
|
||||
* The cell's offset in the grid.
|
||||
*/
|
||||
get index() {
|
||||
let index = this._grid.cells.indexOf(this);
|
||||
|
||||
// Cache this value, overwrite the getter.
|
||||
Object.defineProperty(this, "index", {value: index, enumerable: true});
|
||||
|
||||
return index;
|
||||
},
|
||||
|
||||
/**
|
||||
* The previous cell in the grid.
|
||||
*/
|
||||
get previousSibling() {
|
||||
let prev = this.node.previousElementSibling;
|
||||
prev = prev && prev._newtabCell;
|
||||
|
||||
// Cache this value, overwrite the getter.
|
||||
Object.defineProperty(this, "previousSibling", {value: prev, enumerable: true});
|
||||
|
||||
return prev;
|
||||
},
|
||||
|
||||
/**
|
||||
* The next cell in the grid.
|
||||
*/
|
||||
get nextSibling() {
|
||||
let next = this.node.nextElementSibling;
|
||||
next = next && next._newtabCell;
|
||||
|
||||
// Cache this value, overwrite the getter.
|
||||
Object.defineProperty(this, "nextSibling", {value: next, enumerable: true});
|
||||
|
||||
return next;
|
||||
},
|
||||
|
||||
/**
|
||||
* The site contained in the cell, if any.
|
||||
*/
|
||||
get site() {
|
||||
let firstChild = this.node.firstElementChild;
|
||||
return firstChild && firstChild._newtabSite;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the cell contains a pinned site.
|
||||
* @return Whether the cell contains a pinned site.
|
||||
*/
|
||||
containsPinnedSite: function Cell_containsPinnedSite() {
|
||||
let site = this.site;
|
||||
return site && site.isPinned();
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the cell contains a site (is empty).
|
||||
* @return Whether the cell is empty.
|
||||
*/
|
||||
isEmpty: function Cell_isEmpty() {
|
||||
return !this.site;
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for the 'dragenter' event.
|
||||
* @param aEvent The dragenter event.
|
||||
*/
|
||||
onDragEnter: function Cell_onDragEnter(aEvent) {
|
||||
if (gDrag.isValid(aEvent)) {
|
||||
aEvent.preventDefault();
|
||||
gDrop.enter(this, aEvent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
};
|
140
browser/base/content/newtab/drag.js
Normal file
140
browser/base/content/newtab/drag.js
Normal file
@ -0,0 +1,140 @@
|
||||
#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 implements site dragging functionality.
|
||||
*/
|
||||
let gDrag = {
|
||||
/**
|
||||
* The site offset to the drag start point.
|
||||
*/
|
||||
_offsetX: null,
|
||||
_offsetY: null,
|
||||
|
||||
/**
|
||||
* The site that is dragged.
|
||||
*/
|
||||
_draggedSite: null,
|
||||
get draggedSite() this._draggedSite,
|
||||
|
||||
/**
|
||||
* The cell width/height at the point the drag started.
|
||||
*/
|
||||
_cellWidth: null,
|
||||
_cellHeight: null,
|
||||
get cellWidth() this._cellWidth,
|
||||
get cellHeight() this._cellHeight,
|
||||
|
||||
/**
|
||||
* Start a new drag operation.
|
||||
* @param aSite The site that's being dragged.
|
||||
* @param aEvent The 'dragstart' event.
|
||||
*/
|
||||
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");
|
||||
|
||||
this._setDragData(aSite, aEvent);
|
||||
|
||||
// Store the cursor offset.
|
||||
let node = aSite.node;
|
||||
let rect = node.getBoundingClientRect();
|
||||
this._offsetX = aEvent.clientX - rect.left;
|
||||
this._offsetY = aEvent.clientY - rect.top;
|
||||
|
||||
// Store the cell dimensions.
|
||||
let cellNode = aSite.cell.node;
|
||||
this._cellWidth = cellNode.offsetWidth;
|
||||
this._cellHeight = cellNode.offsetHeight;
|
||||
|
||||
gTransformation.freezeSitePosition(aSite);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the 'drag' event.
|
||||
* @param aSite The site that's being dragged.
|
||||
* @param aEvent The 'drag' event.
|
||||
*/
|
||||
drag: function Drag_drag(aSite, aEvent) {
|
||||
// Get the viewport size.
|
||||
let {clientWidth, clientHeight} = document.documentElement;
|
||||
|
||||
// We'll want a padding of 5px.
|
||||
let border = 5;
|
||||
|
||||
// Enforce minimum constraints to keep the drag image inside the window.
|
||||
let left = Math.max(scrollX + aEvent.clientX - this._offsetX, border);
|
||||
let top = Math.max(scrollY + aEvent.clientY - this._offsetY, border);
|
||||
|
||||
// Enforce maximum constraints to keep the drag image inside the window.
|
||||
left = Math.min(left, scrollX + clientWidth - this.cellWidth - border);
|
||||
top = Math.min(top, scrollY + clientHeight - this.cellHeight - border);
|
||||
|
||||
// Update the drag image's position.
|
||||
gTransformation.setSitePosition(aSite, {left: left, top: top});
|
||||
},
|
||||
|
||||
/**
|
||||
* Ends the current drag operation.
|
||||
* @param aSite The site that's being dragged.
|
||||
* @param aEvent The 'dragend' event.
|
||||
*/
|
||||
end: function Drag_end(aSite, aEvent) {
|
||||
aSite.node.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")
|
||||
});
|
||||
|
||||
this._draggedSite = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether we're responsible for a given drag event.
|
||||
* @param aEvent The drag event to check.
|
||||
* @return Whether we should handle this drag and drop operation.
|
||||
*/
|
||||
isValid: function Drag_isValid(aEvent) {
|
||||
let dt = aEvent.dataTransfer;
|
||||
return dt && dt.types.contains("text/x-moz-url");
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the drag data for the current drag operation.
|
||||
* @param aSite The site that's being dragged.
|
||||
* @param aEvent The 'dragstart' event.
|
||||
*/
|
||||
_setDragData: function Drag_setDragData(aSite, aEvent) {
|
||||
let {url, title} = aSite;
|
||||
|
||||
let dt = aEvent.dataTransfer;
|
||||
dt.mozCursor = "default";
|
||||
dt.effectAllowed = "move";
|
||||
dt.setData("text/plain", url);
|
||||
dt.setData("text/uri-list", url);
|
||||
dt.setData("text/x-moz-url", url + "\n" + title);
|
||||
dt.setData("text/html", "<a href=\"" + url + "\">" + url + "</a>");
|
||||
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
};
|
147
browser/base/content/newtab/drop.js
Normal file
147
browser/base/content/newtab/drop.js
Normal file
@ -0,0 +1,147 @@
|
||||
#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
|
||||
|
||||
// A little delay that prevents the grid from being too sensitive when dragging
|
||||
// sites around.
|
||||
const DELAY_REARRANGE_MS = 100;
|
||||
|
||||
/**
|
||||
* This singleton implements site dropping functionality.
|
||||
*/
|
||||
let gDrop = {
|
||||
/**
|
||||
* The last drop target.
|
||||
*/
|
||||
_lastDropTarget: null,
|
||||
|
||||
/**
|
||||
* Handles the 'dragenter' event.
|
||||
* @param aCell The drop target cell.
|
||||
*/
|
||||
enter: function Drop_enter(aCell) {
|
||||
this._delayedRearrange(aCell);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the 'dragexit' event.
|
||||
* @param aCell The drop target cell.
|
||||
* @param aEvent The 'dragexit' event.
|
||||
*/
|
||||
exit: function Drop_exit(aCell, aEvent) {
|
||||
if (aEvent.dataTransfer && !aEvent.dataTransfer.mozUserCancelled) {
|
||||
this._delayedRearrange();
|
||||
} else {
|
||||
// The drag operation has been cancelled.
|
||||
this._cancelDelayedArrange();
|
||||
this._rearrange();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the 'drop' event.
|
||||
* @param aCell The drop target cell.
|
||||
* @param aEvent The 'dragexit' event.
|
||||
* @param aCallback The callback to call when the drop is finished.
|
||||
*/
|
||||
drop: function Drop_drop(aCell, aEvent, aCallback) {
|
||||
// The cell that is the drop target could contain a pinned site. We need
|
||||
// to find out where that site has gone and re-pin it there.
|
||||
if (aCell.containsPinnedSite())
|
||||
this._repinSitesAfterDrop(aCell);
|
||||
|
||||
// Pin the dragged or insert the new site.
|
||||
this._pinDraggedSite(aCell, aEvent);
|
||||
|
||||
this._cancelDelayedArrange();
|
||||
|
||||
// Update the grid and move all sites to their new places.
|
||||
gUpdater.updateGrid(aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-pins all pinned sites in their (new) positions.
|
||||
* @param aCell The drop target cell.
|
||||
*/
|
||||
_repinSitesAfterDrop: function Drop_repinSitesAfterDrop(aCell) {
|
||||
let sites = gDropPreview.rearrange(aCell);
|
||||
|
||||
// Filter out pinned sites.
|
||||
let pinnedSites = sites.filter(function (aSite) {
|
||||
return aSite && aSite.isPinned();
|
||||
});
|
||||
|
||||
// Re-pin all shifted pinned cells.
|
||||
pinnedSites.forEach(function (aSite) aSite.pin(sites.indexOf(aSite)), this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Pins the dragged site in its new place.
|
||||
* @param aCell The drop target cell.
|
||||
* @param aEvent The 'dragexit' event.
|
||||
*/
|
||||
_pinDraggedSite: function Drop_pinDraggedSite(aCell, aEvent) {
|
||||
let index = aCell.index;
|
||||
let draggedSite = gDrag.draggedSite;
|
||||
|
||||
if (draggedSite) {
|
||||
// Pin the dragged site at its new place.
|
||||
if (aCell != draggedSite.cell)
|
||||
draggedSite.pin(index);
|
||||
} else {
|
||||
// A new link was dragged onto the grid. Create it by pinning its URL.
|
||||
let dt = aEvent.dataTransfer;
|
||||
let [url, title] = dt.getData("text/x-moz-url").split(/[\r\n]+/);
|
||||
gPinnedLinks.pin({url: url, title: title}, index);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Time a rearrange with a little delay.
|
||||
* @param aCell The drop target cell.
|
||||
*/
|
||||
_delayedRearrange: function Drop_delayedRearrange(aCell) {
|
||||
// The last drop target didn't change so there's no need to re-arrange.
|
||||
if (this._lastDropTarget == aCell)
|
||||
return;
|
||||
|
||||
let self = this;
|
||||
|
||||
function callback() {
|
||||
self._rearrangeTimeout = null;
|
||||
self._rearrange(aCell);
|
||||
}
|
||||
|
||||
this._cancelDelayedArrange();
|
||||
this._rearrangeTimeout = setTimeout(callback, DELAY_REARRANGE_MS);
|
||||
|
||||
// Store the last drop target.
|
||||
this._lastDropTarget = aCell;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancels a timed rearrange, if any.
|
||||
*/
|
||||
_cancelDelayedArrange: function Drop_cancelDelayedArrange() {
|
||||
if (this._rearrangeTimeout) {
|
||||
clearTimeout(this._rearrangeTimeout);
|
||||
this._rearrangeTimeout = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearrange all sites in the grid depending on the current drop target.
|
||||
* @param aCell The drop target cell.
|
||||
*/
|
||||
_rearrange: function Drop_rearrange(aCell) {
|
||||
let sites = gGrid.sites;
|
||||
|
||||
// We need to rearrange the grid only if there's a current drop target.
|
||||
if (aCell)
|
||||
sites = gDropPreview.rearrange(aCell);
|
||||
|
||||
gTransformation.rearrangeSites(sites, {unfreeze: !aCell});
|
||||
}
|
||||
};
|
222
browser/base/content/newtab/dropPreview.js
Normal file
222
browser/base/content/newtab/dropPreview.js
Normal file
@ -0,0 +1,222 @@
|
||||
#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 provides the ability to re-arrange the current grid to
|
||||
* indicate the transformation that results from dropping a cell at a certain
|
||||
* position.
|
||||
*/
|
||||
let gDropPreview = {
|
||||
/**
|
||||
* Rearranges the sites currently contained in the grid when a site would be
|
||||
* dropped onto the given cell.
|
||||
* @param aCell The drop target cell.
|
||||
* @return The re-arranged array of sites.
|
||||
*/
|
||||
rearrange: function DropPreview_rearrange(aCell) {
|
||||
let sites = gGrid.sites;
|
||||
|
||||
// Insert the dragged site into the current grid.
|
||||
this._insertDraggedSite(sites, aCell);
|
||||
|
||||
// After the new site has been inserted we need to correct the positions
|
||||
// of all pinned tabs that have been moved around.
|
||||
this._repositionPinnedSites(sites, aCell);
|
||||
|
||||
return sites;
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts the currently dragged site into the given array of sites.
|
||||
* @param aSites The array of sites to insert into.
|
||||
* @param aCell The drop target cell.
|
||||
*/
|
||||
_insertDraggedSite: function DropPreview_insertDraggedSite(aSites, aCell) {
|
||||
let dropIndex = aCell.index;
|
||||
let draggedSite = gDrag.draggedSite;
|
||||
|
||||
// We're currently dragging a site.
|
||||
if (draggedSite) {
|
||||
let dragCell = draggedSite.cell;
|
||||
let dragIndex = dragCell.index;
|
||||
|
||||
// Move the dragged site into its new position.
|
||||
if (dragIndex != dropIndex) {
|
||||
aSites.splice(dragIndex, 1);
|
||||
aSites.splice(dropIndex, 0, draggedSite);
|
||||
}
|
||||
// We're handling an external drag item.
|
||||
} else {
|
||||
aSites.splice(dropIndex, 0, null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Correct the position of all pinned sites that might have been moved to
|
||||
* different positions after the dragged site has been inserted.
|
||||
* @param aSites The array of sites containing the dragged site.
|
||||
* @param aCell The drop target cell.
|
||||
*/
|
||||
_repositionPinnedSites:
|
||||
function DropPreview_repositionPinnedSites(aSites, aCell) {
|
||||
|
||||
// Collect all pinned sites.
|
||||
let pinnedSites = this._filterPinnedSites(aSites, aCell);
|
||||
|
||||
// Correct pinned site positions.
|
||||
pinnedSites.forEach(function (aSite) {
|
||||
aSites[aSites.indexOf(aSite)] = aSites[aSite.cell.index];
|
||||
aSites[aSite.cell.index] = aSite;
|
||||
}, this);
|
||||
|
||||
// There might be a pinned cell that got pushed out of the grid, try to
|
||||
// sneak it in by removing a lower-priority cell.
|
||||
if (this._hasOverflowedPinnedSite(aSites, aCell))
|
||||
this._repositionOverflowedPinnedSite(aSites, aCell);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter pinned sites out of the grid that are still on their old positions
|
||||
* and have not moved.
|
||||
* @param aSites The array of sites to filter.
|
||||
* @param aCell The drop target cell.
|
||||
* @return The filtered array of sites.
|
||||
*/
|
||||
_filterPinnedSites: function DropPreview_filterPinnedSites(aSites, aCell) {
|
||||
let draggedSite = gDrag.draggedSite;
|
||||
|
||||
// When dropping on a cell that contains a pinned site make sure that all
|
||||
// pinned cells surrounding the drop target are moved as well.
|
||||
let range = this._getPinnedRange(aCell);
|
||||
|
||||
return aSites.filter(function (aSite, aIndex) {
|
||||
// The site must be valid, pinned and not the dragged site.
|
||||
if (!aSite || aSite == draggedSite || !aSite.isPinned())
|
||||
return false;
|
||||
|
||||
let index = aSite.cell.index;
|
||||
|
||||
// If it's not in the 'pinned range' it's a valid pinned site.
|
||||
return (index > range.end || index < range.start);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the range of pinned sites surrounding the drop target cell.
|
||||
* @param aCell The drop target cell.
|
||||
* @return The range of pinned cells.
|
||||
*/
|
||||
_getPinnedRange: function DropPreview_getPinnedRange(aCell) {
|
||||
let dropIndex = aCell.index;
|
||||
let range = {start: dropIndex, end: dropIndex};
|
||||
|
||||
// We need a pinned range only when dropping on a pinned site.
|
||||
if (aCell.containsPinnedSite()) {
|
||||
let links = gPinnedLinks.links;
|
||||
|
||||
// Find all previous siblings of the drop target that are pinned as well.
|
||||
while (range.start && links[range.start - 1])
|
||||
range.start--;
|
||||
|
||||
let maxEnd = links.length - 1;
|
||||
|
||||
// Find all next siblings of the drop target that are pinned as well.
|
||||
while (range.end < maxEnd && links[range.end + 1])
|
||||
range.end++;
|
||||
}
|
||||
|
||||
return range;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the given array of sites contains a pinned site that has
|
||||
* been pushed out of the grid.
|
||||
* @param aSites The array of sites to check.
|
||||
* @param aCell The drop target cell.
|
||||
* @return Whether there is an overflowed pinned cell.
|
||||
*/
|
||||
_hasOverflowedPinnedSite:
|
||||
function DropPreview_hasOverflowedPinnedSite(aSites, aCell) {
|
||||
|
||||
// If the drop target isn't pinned there's no way a pinned site has been
|
||||
// pushed out of the grid so we can just exit here.
|
||||
if (!aCell.containsPinnedSite())
|
||||
return false;
|
||||
|
||||
let cells = gGrid.cells;
|
||||
|
||||
// No cells have been pushed out of the grid, nothing to do here.
|
||||
if (aSites.length <= cells.length)
|
||||
return false;
|
||||
|
||||
let overflowedSite = aSites[cells.length];
|
||||
|
||||
// Nothing to do if the site that got pushed out of the grid is not pinned.
|
||||
return (overflowedSite && overflowedSite.isPinned());
|
||||
},
|
||||
|
||||
/**
|
||||
* We have a overflowed pinned site that we need to re-position so that it's
|
||||
* visible again. We try to find a lower-priority cell (empty or containing
|
||||
* an unpinned site) that we can move it to.
|
||||
* @param aSites The array of sites.
|
||||
* @param aCell The drop target cell.
|
||||
*/
|
||||
_repositionOverflowedPinnedSite:
|
||||
function DropPreview_repositionOverflowedPinnedSite(aSites, aCell) {
|
||||
|
||||
// Try to find a lower-priority cell (empty or containing an unpinned site).
|
||||
let index = this._indexOfLowerPrioritySite(aSites, aCell);
|
||||
|
||||
if (index > -1) {
|
||||
let cells = gGrid.cells;
|
||||
let dropIndex = aCell.index;
|
||||
|
||||
// Move all pinned cells to their new positions to let the overflowed
|
||||
// site fit into the grid.
|
||||
for (let i = index + 1, lastPosition = index; i < aSites.length; i++) {
|
||||
if (i != dropIndex) {
|
||||
aSites[lastPosition] = aSites[i];
|
||||
lastPosition = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, remove the overflowed site from its previous position.
|
||||
aSites.splice(cells.length, 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds the index of the last cell that is empty or contains an unpinned
|
||||
* site. These are considered to be of a lower priority.
|
||||
* @param aSites The array of sites.
|
||||
* @param aCell The drop target cell.
|
||||
* @return The cell's index.
|
||||
*/
|
||||
_indexOfLowerPrioritySite:
|
||||
function DropPreview_indexOfLowerPrioritySite(aSites, aCell) {
|
||||
|
||||
let cells = gGrid.cells;
|
||||
let dropIndex = aCell.index;
|
||||
|
||||
// Search (beginning with the last site in the grid) for a site that is
|
||||
// empty or unpinned (an thus lower-priority) and can be pushed out of the
|
||||
// grid instead of the pinned site.
|
||||
for (let i = cells.length - 1; i >= 0; i--) {
|
||||
// The cell that is our drop target is not a good choice.
|
||||
if (i == dropIndex)
|
||||
continue;
|
||||
|
||||
let site = aSites[i];
|
||||
|
||||
// We can use the cell only if it's empty or the site is un-pinned.
|
||||
if (!site || !site.isPinned())
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
};
|
178
browser/base/content/newtab/dropTargetShim.js
Normal file
178
browser/base/content/newtab/dropTargetShim.js
Normal file
@ -0,0 +1,178 @@
|
||||
#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 provides a custom drop target detection. We need this because
|
||||
* the default DnD target detection relies on the cursor's position. We want
|
||||
* to pick a drop target based on the dragged site's position.
|
||||
*/
|
||||
let gDropTargetShim = {
|
||||
/**
|
||||
* Cache for the position of all cells, cleaned after drag finished.
|
||||
*/
|
||||
_cellPositions: null,
|
||||
|
||||
/**
|
||||
* The last drop target that was hovered.
|
||||
*/
|
||||
_lastDropTarget: null,
|
||||
|
||||
/**
|
||||
* Initializes the drop target shim.
|
||||
*/
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the 'dragstart' event.
|
||||
* @param aEvent The 'dragstart' event.
|
||||
*/
|
||||
_start: function DropTargetShim_start(aEvent) {
|
||||
gGrid.lock();
|
||||
|
||||
// XXX bug 505521 - Listen for dragover on the document.
|
||||
document.documentElement.addEventListener("dragover", this._dragover, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the 'drag' event and determines the current drop target.
|
||||
* @param aEvent The 'drag' event.
|
||||
*/
|
||||
_drag: function DropTargetShim_drag(aEvent) {
|
||||
// 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 (this._lastDropTarget)
|
||||
// We left the last drop target.
|
||||
this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
|
||||
|
||||
if (target)
|
||||
// We're now hovering a (new) drop target.
|
||||
this._dispatchEvent(aEvent, "dragenter", target);
|
||||
|
||||
if (this._lastDropTarget)
|
||||
// We left the last drop target.
|
||||
this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
|
||||
|
||||
this._lastDropTarget = target;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the 'dragover' event as long as bug 505521 isn't fixed to get
|
||||
* current mouse cursor coordinates while dragging.
|
||||
* @param aEvent The 'dragover' event.
|
||||
*/
|
||||
_dragover: function DropTargetShim_dragover(aEvent) {
|
||||
let sourceNode = aEvent.dataTransfer.mozSourceNode;
|
||||
gDrag.drag(sourceNode._newtabSite, aEvent);
|
||||
|
||||
this._drag(aEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the 'dragend' event.
|
||||
* @param aEvent The 'dragend' event.
|
||||
*/
|
||||
_end: function DropTargetShim_end(aEvent) {
|
||||
// Make sure to determine the current drop target in case the dragenter
|
||||
// event hasn't been fired.
|
||||
this._drag(aEvent);
|
||||
|
||||
if (this._lastDropTarget) {
|
||||
if (aEvent.dataTransfer.mozUserCancelled) {
|
||||
// The drag operation was cancelled.
|
||||
this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
|
||||
this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
|
||||
} else {
|
||||
// A site was successfully dropped.
|
||||
this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
this._lastDropTarget = null;
|
||||
this._cellPositions = null;
|
||||
}
|
||||
|
||||
gGrid.unlock();
|
||||
|
||||
// XXX bug 505521 - Remove the document's dragover listener.
|
||||
document.documentElement.removeEventListener("dragover", this._dragover, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the current drop target by matching the dragged site's position
|
||||
* against all cells in the grid.
|
||||
* @return The currently hovered drop target or null.
|
||||
*/
|
||||
_findDropTarget: function DropTargetShim_findDropTarget() {
|
||||
// These are the minimum intersection values - we want to use the cell if
|
||||
// the site is >= 50% hovering its position.
|
||||
let minWidth = gDrag.cellWidth / 2;
|
||||
let minHeight = gDrag.cellHeight / 2;
|
||||
|
||||
let cellPositions = this._getCellPositions();
|
||||
let rect = gTransformation.getNodePosition(gDrag.draggedSite.node);
|
||||
|
||||
// Compare each cell's position to the dragged site's position.
|
||||
for (let i = 0; i < cellPositions.length; i++) {
|
||||
let inter = rect.intersect(cellPositions[i].rect);
|
||||
|
||||
// If the intersection is big enough we found a drop target.
|
||||
if (inter.width >= minWidth && inter.height >= minHeight)
|
||||
return cellPositions[i].cell;
|
||||
}
|
||||
|
||||
// No drop target found.
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the positions of all cell nodes.
|
||||
* @return The (cached) cell positions.
|
||||
*/
|
||||
_getCellPositions: function DropTargetShim_getCellPositions() {
|
||||
if (this._cellPositions)
|
||||
return this._cellPositions;
|
||||
|
||||
return this._cellPositions = gGrid.cells.map(function (cell) {
|
||||
return {cell: cell, rect: gTransformation.getNodePosition(cell.node)};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches a custom DragEvent on the given target node.
|
||||
* @param aEvent The source event.
|
||||
* @param aType The event type.
|
||||
* @param aTarget The target node that receives the event.
|
||||
*/
|
||||
_dispatchEvent:
|
||||
function DropTargetShim_dispatchEvent(aEvent, aType, aTarget) {
|
||||
|
||||
let node = aTarget.node;
|
||||
let event = document.createEvent("DragEvents");
|
||||
|
||||
event.initDragEvent(aType, true, true, window, 0, 0, 0, 0, 0, false, false,
|
||||
false, false, 0, node, aEvent.dataTransfer);
|
||||
|
||||
node.dispatchEvent(event);
|
||||
}
|
||||
};
|
132
browser/base/content/newtab/grid.js
Normal file
132
browser/base/content/newtab/grid.js
Normal file
@ -0,0 +1,132 @@
|
||||
#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 grid that contains all sites.
|
||||
*/
|
||||
let gGrid = {
|
||||
/**
|
||||
* The DOM node of the grid.
|
||||
*/
|
||||
_node: null,
|
||||
get node() this._node,
|
||||
|
||||
/**
|
||||
* The cached DOM fragment for sites.
|
||||
*/
|
||||
_siteFragment: null,
|
||||
|
||||
/**
|
||||
* All cells contained in the grid.
|
||||
*/
|
||||
get cells() {
|
||||
let children = this.node.querySelectorAll("li");
|
||||
let cells = [new Cell(this, child) for each (child in children)];
|
||||
|
||||
// Replace the getter with our cached value.
|
||||
Object.defineProperty(this, "cells", {value: cells, enumerable: true});
|
||||
|
||||
return cells;
|
||||
},
|
||||
|
||||
/**
|
||||
* All sites contained in the grid's cells. Sites may be empty.
|
||||
*/
|
||||
get sites() [cell.site for each (cell in this.cells)],
|
||||
|
||||
/**
|
||||
* Initializes the grid.
|
||||
* @param aSelector The query selector of the grid.
|
||||
*/
|
||||
init: function Grid_init(aSelector) {
|
||||
this._node = document.querySelector(aSelector);
|
||||
this._createSiteFragment();
|
||||
this._draw();
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new site in the grid.
|
||||
* @param aLink The new site's link.
|
||||
* @param aCell The cell that will contain the new site.
|
||||
* @return The newly created site.
|
||||
*/
|
||||
createSite: function Grid_createSite(aLink, aCell) {
|
||||
let node = aCell.node;
|
||||
node.appendChild(this._siteFragment.cloneNode(true));
|
||||
return new Site(node.firstElementChild, aLink);
|
||||
},
|
||||
|
||||
/**
|
||||
* Refreshes the grid and re-creates all sites.
|
||||
*/
|
||||
refresh: function Grid_refresh() {
|
||||
// Remove all sites.
|
||||
this.cells.forEach(function (cell) {
|
||||
let node = cell.node;
|
||||
let child = node.firstElementChild;
|
||||
|
||||
if (child)
|
||||
node.removeChild(child);
|
||||
}, this);
|
||||
|
||||
// Draw the grid again.
|
||||
this._draw();
|
||||
},
|
||||
|
||||
/**
|
||||
* Locks the grid to block all pointer events.
|
||||
*/
|
||||
lock: function Grid_lock() {
|
||||
this.node.setAttribute("locked", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Unlocks the grid to allow all pointer events.
|
||||
*/
|
||||
unlock: function Grid_unlock() {
|
||||
this.node.removeAttribute("locked");
|
||||
},
|
||||
|
||||
/**
|
||||
* 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");
|
||||
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>';
|
||||
|
||||
this._siteFragment = document.createDocumentFragment();
|
||||
this._siteFragment.appendChild(site);
|
||||
},
|
||||
|
||||
/**
|
||||
* Draws the grid, creates all sites and puts them into their cells.
|
||||
*/
|
||||
_draw: function Grid_draw() {
|
||||
let cells = this.cells;
|
||||
|
||||
// Put sites into the cells.
|
||||
let links = gLinks.getLinks();
|
||||
let length = Math.min(links.length, cells.length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (links[i])
|
||||
this.createSite(links[i], cells[i]);
|
||||
}
|
||||
}
|
||||
};
|
173
browser/base/content/newtab/newTab.css
Normal file
173
browser/base/content/newtab/newTab.css
Normal file
@ -0,0 +1,173 @@
|
||||
:root {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
#scrollbox:not([page-disabled]) {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#body {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
min-width: 675px;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* TOOLBAR */
|
||||
#toolbar {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#toolbar[page-disabled] {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#toolbar:-moz-locale-dir(rtl) {
|
||||
left: 8px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.toolbar-button {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
-moz-transition: opacity 200ms ease-out;
|
||||
}
|
||||
|
||||
#toolbar-button-show,
|
||||
#toolbar-button-reset {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#toolbar-button-reset[modified],
|
||||
#toolbar-button-show[page-disabled] {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#toolbar-button-hide[page-disabled],
|
||||
#toolbar-button-reset[page-disabled] {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* GRID */
|
||||
#grid {
|
||||
width: 637px;
|
||||
height: 411px;
|
||||
overflow: hidden;
|
||||
list-style-type: none;
|
||||
-moz-transition: opacity 200ms ease-out;
|
||||
}
|
||||
|
||||
#grid[page-disabled] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#grid[page-disabled],
|
||||
#grid[locked] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* SITES */
|
||||
.site {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 201px;
|
||||
height: 127px;
|
||||
}
|
||||
|
||||
.site[frozen] {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.site[ontop] {
|
||||
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 {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 195px;
|
||||
height: 17px;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
-moz-transition: opacity 200ms ease-out;
|
||||
}
|
||||
|
||||
.site:hover:not([frozen]) > .site-strip {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.strip-button-pin,
|
||||
.strip-button-block:-moz-locale-dir(rtl) {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.strip-button-block,
|
||||
.strip-button-pin:-moz-locale-dir(rtl) {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* DRAG & DROP */
|
||||
|
||||
/*
|
||||
* This is just a temporary drag element used for dataTransfer.setDragImage()
|
||||
* 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 {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background-color: #fff;
|
||||
opacity: 0.01;
|
||||
}
|
50
browser/base/content/newtab/newTab.js
Normal file
50
browser/base/content/newtab/newTab.js
Normal file
@ -0,0 +1,50 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
let Cu = Components.utils;
|
||||
let Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/PageThumbs.jsm");
|
||||
Cu.import("resource:///modules/NewTabUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Rect",
|
||||
"resource://gre/modules/Geometry.jsm");
|
||||
|
||||
let {
|
||||
links: gLinks,
|
||||
allPages: gAllPages,
|
||||
pinnedLinks: gPinnedLinks,
|
||||
blockedLinks: gBlockedLinks
|
||||
} = NewTabUtils;
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
|
||||
return Services.strings.
|
||||
createBundle("chrome://browser/locale/newTab.properties");
|
||||
});
|
||||
|
||||
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
|
||||
#include drag.js
|
||||
#include drop.js
|
||||
#include dropTargetShim.js
|
||||
#include dropPreview.js
|
||||
#include updater.js
|
||||
|
||||
// Everything is loaded. Initialize the New Tab Page.
|
||||
gPage.init("#toolbar", "#grid");
|
40
browser/base/content/newtab/newTab.xul
Normal file
40
browser/base/content/newtab/newTab.xul
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
# 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/.
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/global.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"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
|
||||
%newTabDTD;
|
||||
]>
|
||||
|
||||
<xul: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;"/>
|
||||
</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>
|
||||
|
||||
<xul:script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/newtab/newTab.js"/>
|
||||
</body>
|
||||
</xul:vbox>
|
||||
</xul:window>
|
173
browser/base/content/newtab/page.js
Normal file
173
browser/base/content/newtab/page.js
Normal file
@ -0,0 +1,173 @@
|
||||
#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 whole 'New Tab Page' and takes care of
|
||||
* initializing all its components.
|
||||
*/
|
||||
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;
|
||||
|
||||
// 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);
|
||||
|
||||
// Check if the new tab feature is enabled.
|
||||
if (gAllPages.enabled)
|
||||
this._init();
|
||||
else
|
||||
this._updateAttributes(false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens for notifications specific to this page.
|
||||
*/
|
||||
observe: function Page_observe() {
|
||||
let enabled = gAllPages.enabled;
|
||||
this._updateAttributes(enabled);
|
||||
|
||||
// Initialize the whole page if we haven't done that, yet.
|
||||
if (enabled)
|
||||
this._init();
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
_init: function Page_init() {
|
||||
if (this._initialized)
|
||||
return;
|
||||
|
||||
this._initialized = true;
|
||||
|
||||
gLinks.populateCache(function () {
|
||||
// Check if the grid is modified.
|
||||
this.updateModifiedFlag();
|
||||
|
||||
// Initialize and render the grid.
|
||||
gGrid.init(this._gridSelector);
|
||||
|
||||
// Initialize the drop target shim.
|
||||
gDropTargetShim.init();
|
||||
|
||||
// Workaround to prevent a delay on MacOSX due to a slow drop animation.
|
||||
let doc = document.documentElement;
|
||||
doc.addEventListener("dragover", this.onDragOver, false);
|
||||
doc.addEventListener("drop", this.onDrop, false);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the 'page-disabled' attributes of the respective DOM nodes.
|
||||
* @param aValue Whether to set or remove attributes.
|
||||
*/
|
||||
_updateAttributes: function Page_updateAttributes(aValue) {
|
||||
let nodes = document.querySelectorAll("#grid, #scrollbox, #toolbar, .toolbar-button");
|
||||
|
||||
// Set the nodes' states.
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i];
|
||||
if (aValue)
|
||||
node.removeAttribute("page-disabled");
|
||||
else
|
||||
node.setAttribute("page-disabled", "true");
|
||||
}
|
||||
|
||||
this._updateTabIndices(aValue, this._isModified());
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the page is modified.
|
||||
* @return Whether the page is modified or not.
|
||||
*/
|
||||
_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))
|
||||
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)) {
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
};
|
209
browser/base/content/newtab/sites.js
Normal file
209
browser/base/content/newtab/sites.js
Normal file
@ -0,0 +1,209 @@
|
||||
#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 class represents a site that is contained in a cell and can be pinned,
|
||||
* moved around or deleted.
|
||||
*/
|
||||
function Site(aNode, aLink) {
|
||||
this._node = aNode;
|
||||
this._node._newtabSite = this;
|
||||
|
||||
this._link = aLink;
|
||||
|
||||
this._render();
|
||||
this._addEventHandlers();
|
||||
}
|
||||
|
||||
Site.prototype = {
|
||||
/**
|
||||
* The site's DOM node.
|
||||
*/
|
||||
get node() this._node,
|
||||
|
||||
/**
|
||||
* The site's link.
|
||||
*/
|
||||
get link() this._link,
|
||||
|
||||
/**
|
||||
* The url of the site's link.
|
||||
*/
|
||||
get url() this.link.url,
|
||||
|
||||
/**
|
||||
* The title of the site's link.
|
||||
*/
|
||||
get title() this.link.title,
|
||||
|
||||
/**
|
||||
* The site's parent cell.
|
||||
*/
|
||||
get cell() {
|
||||
let parentNode = this.node.parentNode;
|
||||
return parentNode && parentNode._newtabCell;
|
||||
},
|
||||
|
||||
/**
|
||||
* Pins the site on its current or a given index.
|
||||
* @param aIndex The pinned index (optional).
|
||||
*/
|
||||
pin: function Site_pin(aIndex) {
|
||||
if (typeof aIndex == "undefined")
|
||||
aIndex = this.cell.index;
|
||||
|
||||
this._updateAttributes(true);
|
||||
gPinnedLinks.pin(this._link, aIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unpins the site and calls the given callback when done.
|
||||
* @param aCallback The callback to be called when finished.
|
||||
*/
|
||||
unpin: function Site_unpin(aCallback) {
|
||||
if (this.isPinned()) {
|
||||
this._updateAttributes(false);
|
||||
gPinnedLinks.unpin(this._link);
|
||||
gUpdater.updateGrid(aCallback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether this site is pinned.
|
||||
* @return Whether this site is pinned.
|
||||
*/
|
||||
isPinned: function Site_isPinned() {
|
||||
return gPinnedLinks.isPinned(this._link);
|
||||
},
|
||||
|
||||
/**
|
||||
* Blocks the site (removes it from the grid) and calls the given callback
|
||||
* when done.
|
||||
* @param aCallback The callback to be called when finished.
|
||||
*/
|
||||
block: function Site_block(aCallback) {
|
||||
gBlockedLinks.block(this._link);
|
||||
gUpdater.updateGrid(aCallback);
|
||||
gPage.updateModifiedFlag();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the DOM node specified by the given query selector.
|
||||
* @param aSelector The query selector.
|
||||
* @return The DOM node we found.
|
||||
*/
|
||||
_querySelector: function Site_querySelector(aSelector) {
|
||||
return this.node.querySelector(aSelector);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates attributes for all nodes which status depends on this site being
|
||||
* pinned or unpinned.
|
||||
* @param aPinned Whether this site is now pinned or unpinned.
|
||||
*/
|
||||
_updateAttributes: function (aPinned) {
|
||||
let buttonPin = this._querySelector(".strip-button-pin");
|
||||
|
||||
if (aPinned) {
|
||||
this.node.setAttribute("pinned", true);
|
||||
buttonPin.setAttribute("title", newTabString("unpin"));
|
||||
} else {
|
||||
this.node.removeAttribute("pinned");
|
||||
buttonPin.setAttribute("title", newTabString("pin"));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the site's data (fills the HTML fragment).
|
||||
*/
|
||||
_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;
|
||||
|
||||
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));
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds event handlers for the site and its buttons.
|
||||
*/
|
||||
_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);
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for the 'dragstart' event.
|
||||
* @param aEvent The drag event.
|
||||
*/
|
||||
_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);
|
||||
}
|
||||
};
|
87
browser/base/content/newtab/toolbar.js
Normal file
87
browser/base/content/newtab/toolbar.js
Normal file
@ -0,0 +1,87 @@
|
||||
#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();
|
||||
}
|
||||
};
|
||||
|
226
browser/base/content/newtab/transformations.js
Normal file
226
browser/base/content/newtab/transformations.js
Normal file
@ -0,0 +1,226 @@
|
||||
#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 allows to transform the grid by repositioning a site's node
|
||||
* in the DOM and by showing or hiding the node. It additionally provides
|
||||
* convenience methods to work with a site's DOM node.
|
||||
*/
|
||||
let gTransformation = {
|
||||
/**
|
||||
* Gets a DOM node's position.
|
||||
* @param aNode The DOM node.
|
||||
* @return A Rect instance with the position.
|
||||
*/
|
||||
getNodePosition: function Transformation_getNodePosition(aNode) {
|
||||
let {left, top, width, height} = aNode.getBoundingClientRect();
|
||||
return new Rect(left + scrollX, top + scrollY, width, height);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given node from zero to full opacity.
|
||||
* @param aNode The node to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
fadeNodeIn: function Transformation_fadeNodeIn(aNode, aCallback) {
|
||||
this._setNodeOpacity(aNode, 1, function () {
|
||||
// Clear the style property.
|
||||
aNode.style.opacity = "";
|
||||
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given node from full to zero opacity.
|
||||
* @param aNode The node to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
fadeNodeOut: function Transformation_fadeNodeOut(aNode, aCallback) {
|
||||
this._setNodeOpacity(aNode, 0, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given site from zero to full opacity.
|
||||
* @param aSite The site to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
showSite: function Transformation_showSite(aSite, aCallback) {
|
||||
this.fadeNodeIn(aSite.node, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given site from full to zero opacity.
|
||||
* @param aSite The site to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
hideSite: function Transformation_hideSite(aSite, aCallback) {
|
||||
this.fadeNodeOut(aSite.node, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Allows to set a site's position.
|
||||
* @param aSite The site to re-position.
|
||||
* @param aPosition The desired position for the given site.
|
||||
*/
|
||||
setSitePosition: function Transformation_setSitePosition(aSite, aPosition) {
|
||||
let style = aSite.node.style;
|
||||
let {top, left} = aPosition;
|
||||
|
||||
style.top = top + "px";
|
||||
style.left = left + "px";
|
||||
},
|
||||
|
||||
/**
|
||||
* Freezes a site in its current position by positioning it absolute.
|
||||
* @param aSite The site to freeze.
|
||||
*/
|
||||
freezeSitePosition: function Transformation_freezeSitePosition(aSite) {
|
||||
aSite.node.setAttribute("frozen", "true");
|
||||
this.setSitePosition(aSite, this.getNodePosition(aSite.node));
|
||||
},
|
||||
|
||||
/**
|
||||
* Unfreezes a site by removing its absolute positioning.
|
||||
* @param aSite The site to unfreeze.
|
||||
*/
|
||||
unfreezeSitePosition: function Transformation_unfreezeSitePosition(aSite) {
|
||||
let style = aSite.node.style;
|
||||
style.left = style.top = "";
|
||||
aSite.node.removeAttribute("frozen");
|
||||
},
|
||||
|
||||
/**
|
||||
* Slides the given site to the target node's position.
|
||||
* @param aSite The site to move.
|
||||
* @param aTarget The slide target.
|
||||
* @param aOptions Set of options (see below).
|
||||
* unfreeze - unfreeze the site after sliding
|
||||
* callback - the callback to call when finished
|
||||
*/
|
||||
slideSiteTo: function Transformation_slideSiteTo(aSite, aTarget, aOptions) {
|
||||
let currentPosition = this.getNodePosition(aSite.node);
|
||||
let targetPosition = this.getNodePosition(aTarget.node)
|
||||
let callback = aOptions && aOptions.callback;
|
||||
|
||||
let self = this;
|
||||
|
||||
function finish() {
|
||||
if (aOptions && aOptions.unfreeze)
|
||||
self.unfreezeSitePosition(aSite);
|
||||
|
||||
if (callback)
|
||||
callback();
|
||||
}
|
||||
|
||||
// Nothing to do here if the positions already match.
|
||||
if (currentPosition.equals(targetPosition)) {
|
||||
finish();
|
||||
} else {
|
||||
this.setSitePosition(aSite, targetPosition);
|
||||
this._whenTransitionEnded(aSite.node, finish);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearranges a given array of sites and moves them to their new positions or
|
||||
* fades in/out new/removed sites.
|
||||
* @param aSites An array of sites to rearrange.
|
||||
* @param aOptions Set of options (see below).
|
||||
* unfreeze - unfreeze the site after rearranging
|
||||
* callback - the callback to call when finished
|
||||
*/
|
||||
rearrangeSites: function Transformation_rearrangeSites(aSites, aOptions) {
|
||||
let batch;
|
||||
let cells = gGrid.cells;
|
||||
let callback = aOptions && aOptions.callback;
|
||||
let unfreeze = aOptions && aOptions.unfreeze;
|
||||
|
||||
if (callback) {
|
||||
batch = new Batch(callback);
|
||||
callback = function () batch.pop();
|
||||
}
|
||||
|
||||
aSites.forEach(function (aSite, aIndex) {
|
||||
// Do not re-arrange empty cells or the dragged site.
|
||||
if (!aSite || aSite == gDrag.draggedSite)
|
||||
return;
|
||||
|
||||
if (batch)
|
||||
batch.push();
|
||||
|
||||
if (!cells[aIndex])
|
||||
// The site disappeared from the grid, hide it.
|
||||
this.hideSite(aSite, callback);
|
||||
else if (this._getNodeOpacity(aSite.node) != 1)
|
||||
// The site disappeared before but is now back, show it.
|
||||
this.showSite(aSite, callback);
|
||||
else
|
||||
// The site's position has changed, move it around.
|
||||
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: callback});
|
||||
}, this);
|
||||
|
||||
if (batch)
|
||||
batch.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens for the 'transitionend' event on a given node and calls the given
|
||||
* callback.
|
||||
* @param aNode The node that is transitioned.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_whenTransitionEnded:
|
||||
function Transformation_whenTransitionEnded(aNode, aCallback) {
|
||||
|
||||
aNode.addEventListener("transitionend", function onEnd() {
|
||||
aNode.removeEventListener("transitionend", onEnd, false);
|
||||
aCallback();
|
||||
}, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a given node's opacity value.
|
||||
* @param aNode The node to get the opacity value from.
|
||||
* @return The node's opacity value.
|
||||
*/
|
||||
_getNodeOpacity: function Transformation_getNodeOpacity(aNode) {
|
||||
let cstyle = window.getComputedStyle(aNode, null);
|
||||
return cstyle.getPropertyValue("opacity");
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a given node's opacity.
|
||||
* @param aNode The node to set the opacity value for.
|
||||
* @param aOpacity The opacity value to set.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_setNodeOpacity:
|
||||
function Transformation_setNodeOpacity(aNode, aOpacity, aCallback) {
|
||||
|
||||
if (this._getNodeOpacity(aNode) == aOpacity) {
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
} else {
|
||||
if (aCallback)
|
||||
this._whenTransitionEnded(aNode, aCallback);
|
||||
|
||||
aNode.style.opacity = aOpacity;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves a site to the cell with the given index.
|
||||
* @param aSite The site to move.
|
||||
* @param aIndex The target cell's index.
|
||||
* @param aOptions Options that are directly passed to slideSiteTo().
|
||||
*/
|
||||
_moveSite: function Transformation_moveSite(aSite, aIndex, aOptions) {
|
||||
this.freezeSitePosition(aSite);
|
||||
this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
|
||||
}
|
||||
};
|
182
browser/base/content/newtab/updater.js
Normal file
182
browser/base/content/newtab/updater.js
Normal file
@ -0,0 +1,182 @@
|
||||
#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 provides functionality to update the current grid to a new
|
||||
* set of pinned and blocked sites. It adds, moves and removes sites.
|
||||
*/
|
||||
let gUpdater = {
|
||||
/**
|
||||
* Updates the current grid according to its pinned and blocked sites.
|
||||
* This removes old, moves existing and creates new sites to fill gaps.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
updateGrid: function Updater_updateGrid(aCallback) {
|
||||
let links = gLinks.getLinks().slice(0, gGrid.cells.length);
|
||||
|
||||
// Find all sites that remain in the grid.
|
||||
let sites = this._findRemainingSites(links);
|
||||
|
||||
let self = this;
|
||||
|
||||
// Remove sites that are no longer in the grid.
|
||||
this._removeLegacySites(sites, function () {
|
||||
// Freeze all site positions so that we can move their DOM nodes around
|
||||
// without any visual impact.
|
||||
self._freezeSitePositions(sites);
|
||||
|
||||
// Move the sites' DOM nodes to their new position in the DOM. This will
|
||||
// have no visual effect as all the sites have been frozen and will
|
||||
// remain in their current position.
|
||||
self._moveSiteNodes(sites);
|
||||
|
||||
// Now it's time to animate the sites actually moving to their new
|
||||
// positions.
|
||||
self._rearrangeSites(sites, function () {
|
||||
// Try to fill empty cells and finish.
|
||||
self._fillEmptyCells(links, aCallback);
|
||||
|
||||
// Update other pages that might be open to keep them synced.
|
||||
gAllPages.update(gPage);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an array of links and tries to correlate them to sites contained in
|
||||
* the current grid. If no corresponding site can be found (i.e. the link is
|
||||
* new and a site will be created) then just set it to null.
|
||||
* @param aLinks The array of links to find sites for.
|
||||
* @return Array of sites mapped to the given links (can contain null values).
|
||||
*/
|
||||
_findRemainingSites: function Updater_findRemainingSites(aLinks) {
|
||||
let map = {};
|
||||
|
||||
// Create a map to easily retrieve the site for a given URL.
|
||||
gGrid.sites.forEach(function (aSite) {
|
||||
if (aSite)
|
||||
map[aSite.url] = aSite;
|
||||
});
|
||||
|
||||
// Map each link to its corresponding site, if any.
|
||||
return aLinks.map(function (aLink) {
|
||||
return aLink && (aLink.url in map) && map[aLink.url];
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Freezes the given sites' positions.
|
||||
* @param aSites The array of sites to freeze.
|
||||
*/
|
||||
_freezeSitePositions: function Updater_freezeSitePositions(aSites) {
|
||||
aSites.forEach(function (aSite) {
|
||||
if (aSite)
|
||||
gTransformation.freezeSitePosition(aSite);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves the given sites' DOM nodes to their new positions.
|
||||
* @param aSites The array of sites to move.
|
||||
*/
|
||||
_moveSiteNodes: function Updater_moveSiteNodes(aSites) {
|
||||
let cells = gGrid.cells;
|
||||
|
||||
// Truncate the given array of sites to not have more sites than cells.
|
||||
// This can happen when the user drags a bookmark (or any other new kind
|
||||
// of link) onto the grid.
|
||||
let sites = aSites.slice(0, cells.length);
|
||||
|
||||
sites.forEach(function (aSite, aIndex) {
|
||||
let cell = cells[aIndex];
|
||||
let cellSite = cell.site;
|
||||
|
||||
// The site's position didn't change.
|
||||
if (!aSite || cellSite != aSite) {
|
||||
let cellNode = cell.node;
|
||||
|
||||
// Empty the cell if necessary.
|
||||
if (cellSite)
|
||||
cellNode.removeChild(cellSite.node);
|
||||
|
||||
// Put the new site in place, if any.
|
||||
if (aSite)
|
||||
cellNode.appendChild(aSite.node);
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearranges the given sites and slides them to their new positions.
|
||||
* @param aSites The array of sites to re-arrange.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_rearrangeSites: function Updater_rearrangeSites(aSites, aCallback) {
|
||||
let options = {callback: aCallback, unfreeze: true};
|
||||
gTransformation.rearrangeSites(aSites, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all sites from the grid that are not in the given links array or
|
||||
* exceed the grid.
|
||||
* @param aSites The array of sites remaining in the grid.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_removeLegacySites: function Updater_removeLegacySites(aSites, aCallback) {
|
||||
let batch = new Batch(aCallback);
|
||||
|
||||
// Delete sites that were removed from the grid.
|
||||
gGrid.sites.forEach(function (aSite) {
|
||||
// The site must be valid and not in the current grid.
|
||||
if (!aSite || aSites.indexOf(aSite) != -1)
|
||||
return;
|
||||
|
||||
batch.push();
|
||||
|
||||
// Fade out the to-be-removed site.
|
||||
gTransformation.hideSite(aSite, function () {
|
||||
let node = aSite.node;
|
||||
|
||||
// Remove the site from the DOM.
|
||||
node.parentNode.removeChild(node);
|
||||
batch.pop();
|
||||
});
|
||||
});
|
||||
|
||||
batch.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to fill empty cells with new links if available.
|
||||
* @param aLinks The array of links.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_fillEmptyCells: function Updater_fillEmptyCells(aLinks, aCallback) {
|
||||
let {cells, sites} = gGrid;
|
||||
let batch = new Batch(aCallback);
|
||||
|
||||
// Find empty cells and fill them.
|
||||
sites.forEach(function (aSite, aIndex) {
|
||||
if (aSite || !aLinks[aIndex])
|
||||
return;
|
||||
|
||||
batch.push();
|
||||
|
||||
// Create the new site and fade it in.
|
||||
let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
|
||||
|
||||
// Set the site's initial opacity to zero.
|
||||
site.node.style.opacity = 0;
|
||||
|
||||
// Without the setTimeout() the node would just appear instead of fade in.
|
||||
setTimeout(function () {
|
||||
gTransformation.showSite(site, function () batch.pop());
|
||||
}, 0);
|
||||
});
|
||||
|
||||
batch.close();
|
||||
}
|
||||
};
|
@ -341,6 +341,9 @@ function onLoadPageInfo()
|
||||
gStrings.notSet = gBundle.getString("notset");
|
||||
gStrings.mediaImg = gBundle.getString("mediaImg");
|
||||
gStrings.mediaBGImg = gBundle.getString("mediaBGImg");
|
||||
gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg");
|
||||
gStrings.mediaListImg = gBundle.getString("mediaListImg");
|
||||
gStrings.mediaCursor = gBundle.getString("mediaCursor");
|
||||
gStrings.mediaObject = gBundle.getString("mediaObject");
|
||||
gStrings.mediaEmbed = gBundle.getString("mediaEmbed");
|
||||
gStrings.mediaLink = gBundle.getString("mediaLink");
|
||||
@ -663,13 +666,35 @@ function addImage(url, type, alt, elem, isBg)
|
||||
|
||||
function grabAll(elem)
|
||||
{
|
||||
// check for background images, any node may have multiple
|
||||
// check for images defined in CSS (e.g. background, borders), any node may have multiple
|
||||
var computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
|
||||
|
||||
if (computedStyle) {
|
||||
Array.forEach(computedStyle.getPropertyCSSValue("background-image"), function (url) {
|
||||
if (url.primitiveType == CSSPrimitiveValue.CSS_URI)
|
||||
addImage(url.getStringValue(), gStrings.mediaBGImg, gStrings.notSet, elem, true);
|
||||
});
|
||||
var addImgFunc = function (label, val) {
|
||||
if (val.primitiveType == CSSPrimitiveValue.CSS_URI) {
|
||||
addImage(val.getStringValue(), label, gStrings.notSet, elem, true);
|
||||
}
|
||||
else if (val.primitiveType == CSSPrimitiveValue.CSS_STRING) {
|
||||
// This is for -moz-image-rect.
|
||||
// TODO: Reimplement once bug 714757 is fixed
|
||||
var strVal = val.getStringValue();
|
||||
if (strVal.search(/^.*url\(\"?/) > -1) {
|
||||
url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,"");
|
||||
addImage(url, label, gStrings.notSet, elem, true);
|
||||
}
|
||||
}
|
||||
else if (val.cssValueType == CSSValue.CSS_VALUE_LIST) {
|
||||
// recursively resolve multiple nested CSS value lists
|
||||
for (var i = 0; i < val.length; i++)
|
||||
addImgFunc(label, val.item(i));
|
||||
}
|
||||
};
|
||||
|
||||
addImgFunc(gStrings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
|
||||
addImgFunc(gStrings.mediaBorderImg, computedStyle.getPropertyCSSValue("-moz-border-image-source"));
|
||||
// TODO: support unprefixed "border-image" once bug 713643 is fixed.
|
||||
addImgFunc(gStrings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
|
||||
addImgFunc(gStrings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
|
||||
}
|
||||
|
||||
// one swi^H^H^Hif-else to rule them all
|
||||
|
@ -260,23 +260,24 @@
|
||||
value="&addDevice.showMeHow.label;"
|
||||
href="https://services.mozilla.com/sync/help/easy-setup"/>
|
||||
</description>
|
||||
<description>&addDevice.setup.enterCode.label;</description>
|
||||
<label value="&addDevice.setup.enterCode.label;"
|
||||
control="easySetupPIN1"/>
|
||||
<spacer flex="1"/>
|
||||
<vbox align="center" flex="1">
|
||||
<textbox id="easySetupPIN1"
|
||||
class="pin"
|
||||
value=""
|
||||
disabled="true"
|
||||
readonly="true"
|
||||
/>
|
||||
<textbox id="easySetupPIN2"
|
||||
class="pin"
|
||||
value=""
|
||||
disabled="true"
|
||||
readonly="true"
|
||||
/>
|
||||
<textbox id="easySetupPIN3"
|
||||
class="pin"
|
||||
value=""
|
||||
disabled="true"
|
||||
readonly="true"
|
||||
/>
|
||||
</vbox>
|
||||
<spacer flex="3"/>
|
||||
|
@ -632,7 +632,7 @@
|
||||
autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
|
||||
delete this.mBrowser.registeredOpenURI;
|
||||
}
|
||||
if (aLocation.spec != "about:blank") {
|
||||
if (!isBlankPageURL(aLocation.spec)) {
|
||||
autocomplete.registerOpenPage(aLocation);
|
||||
this.mBrowser.registeredOpenURI = aLocation;
|
||||
}
|
||||
@ -1065,7 +1065,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (title && title != "about:blank") {
|
||||
if (title && !isBlankPageURL(title)) {
|
||||
// At this point, we now have a URI.
|
||||
// Let's try to unescape it using a character set
|
||||
// in case the URI is not ASCII.
|
||||
@ -1589,7 +1589,7 @@
|
||||
aTab.closing = true;
|
||||
this._removingTabs.push(aTab);
|
||||
if (newTab)
|
||||
this.addTab("about:blank", {skipAnimation: true});
|
||||
this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
|
||||
else
|
||||
this.tabContainer.updateVisibility();
|
||||
|
||||
|
@ -40,6 +40,10 @@ srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = browser/base/content/test
|
||||
|
||||
DIRS += \
|
||||
newtab \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
@ -89,6 +93,7 @@ endif
|
||||
# browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
|
||||
|
||||
# browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
|
||||
# browser_urlbarAutoFillTrimURLs.js is disabled till bug 720792 is fixed
|
||||
|
||||
_BROWSER_FILES = \
|
||||
head.js \
|
||||
@ -118,6 +123,7 @@ _BROWSER_FILES = \
|
||||
browser_bug441778.js \
|
||||
browser_popupNotification.js \
|
||||
browser_bug455852.js \
|
||||
browser_bug460146.js \
|
||||
browser_bug462673.js \
|
||||
browser_bug477014.js \
|
||||
browser_bug479408.js \
|
||||
@ -177,6 +183,7 @@ _BROWSER_FILES = \
|
||||
browser_bug719271.js \
|
||||
browser_canonizeURL.js \
|
||||
browser_findbarClose.js \
|
||||
browser_homeDrop.js \
|
||||
browser_keywordBookmarklets.js \
|
||||
browser_contextSearchTabPosition.js \
|
||||
browser_ctrlTab.js \
|
||||
@ -216,7 +223,6 @@ _BROWSER_FILES = \
|
||||
browser_tabfocus.js \
|
||||
browser_tabs_isActive.js \
|
||||
browser_tabs_owner.js \
|
||||
browser_urlbarAutoFillTrimURLs.js \
|
||||
browser_urlbarCopying.js \
|
||||
browser_urlbarEnter.js \
|
||||
browser_urlbarTrimURLs.js \
|
||||
@ -259,7 +265,7 @@ _BROWSER_FILES = \
|
||||
test_wyciwyg_copying.html \
|
||||
authenticate.sjs \
|
||||
browser_minimize.js \
|
||||
browser_aboutSyncProgress.js \
|
||||
browser_aboutSyncProgress.js \
|
||||
browser_middleMouse_inherit.js \
|
||||
redirect_bug623155.sjs \
|
||||
$(NULL)
|
||||
|
51
browser/base/content/test/browser_bug460146.js
Normal file
51
browser/base/content/test/browser_bug460146.js
Normal file
@ -0,0 +1,51 @@
|
||||
/* Check proper image url retrieval from all kinds of elements/styles */
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
|
||||
gBrowser.selectedBrowser.addEventListener("load", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
|
||||
var doc = gBrowser.contentDocument;
|
||||
var pageInfo = BrowserPageInfo(doc, "mediaTab");
|
||||
|
||||
pageInfo.addEventListener("load", function () {
|
||||
pageInfo.removeEventListener("load", arguments.callee, true);
|
||||
pageInfo.onFinished.push(function () {
|
||||
executeSoon(function () {
|
||||
var imageTree = pageInfo.document.getElementById("imagetree");
|
||||
var imageRowsNum = imageTree.view.rowCount;
|
||||
|
||||
ok(imageTree, "Image tree is null (media tab is broken)");
|
||||
|
||||
ok(imageRowsNum == 7, "Number of images listed: " +
|
||||
imageRowsNum + ", should be 7");
|
||||
|
||||
pageInfo.close();
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
});
|
||||
}, true);
|
||||
}, true);
|
||||
|
||||
content.location =
|
||||
"data:text/html," +
|
||||
"<html>" +
|
||||
" <head>" +
|
||||
" <title>Test for media tab</title>" +
|
||||
" <link rel='shortcut icon' href='file:///dummy_icon.ico'>" + // Icon
|
||||
" </head>" +
|
||||
" <body style='background-image:url(about:logo?a);'>" + // Background
|
||||
" <img src='file:///dummy_image.gif'>" + // Image
|
||||
" <ul>" +
|
||||
" <li style='list-style:url(about:logo?b);'>List Item 1</li>" + // Bullet
|
||||
" </ul> " +
|
||||
" <div style='-moz-border-image: url(about:logo?c) 20 20 20 20;'>test</div>" + // Border
|
||||
" <a href='' style='cursor: url(about:logo?d),default;'>test link</a>" + // Cursor
|
||||
" <object type='image/svg+xml' width=20 height=20 data='file:///dummy_object.svg'></object>" + // Object
|
||||
" </body>" +
|
||||
"</html>";
|
||||
}
|
77
browser/base/content/test/browser_homeDrop.js
Normal file
77
browser/base/content/test/browser_homeDrop.js
Normal file
@ -0,0 +1,77 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
// Open a new tab, since starting a drag from the home button activates it and
|
||||
// we don't want to interfere with future tests by loading the home page.
|
||||
let newTab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
registerCleanupFunction(function () {
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
||||
|
||||
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
|
||||
getService(Ci.mozIJSSubScriptLoader);
|
||||
let chromeUtils = {};
|
||||
scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", chromeUtils);
|
||||
|
||||
let homeButton = document.getElementById("home-button");
|
||||
ok(homeButton, "home button present");
|
||||
|
||||
let dialogListener = new WindowListener("chrome://global/content/commonDialog.xul", function (domwindow) {
|
||||
ok(true, "dialog appeared in response to home button drop");
|
||||
domwindow.document.documentElement.cancelDialog();
|
||||
Services.wm.removeListener(dialogListener);
|
||||
|
||||
// Now trigger the invalid URI test
|
||||
executeSoon(function () {
|
||||
let consoleListener = {
|
||||
observe: function (m) {
|
||||
if (m.message.indexOf("NS_ERROR_DOM_BAD_URI") > -1) {
|
||||
Services.console.unregisterListener(consoleListener);
|
||||
ok(true, "drop was blocked");
|
||||
executeSoon(finish);
|
||||
}
|
||||
}
|
||||
}
|
||||
Services.console.registerListener(consoleListener);
|
||||
|
||||
// The drop handler throws an exception when dragging URIs that inherit
|
||||
// principal, e.g. javascript:
|
||||
expectUncaughtException();
|
||||
chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window, EventUtils);
|
||||
})
|
||||
});
|
||||
|
||||
Services.wm.addListener(dialogListener);
|
||||
|
||||
chromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "http://mochi.test:8888/"}]], "copy", window, EventUtils);
|
||||
}
|
||||
|
||||
function WindowListener(aURL, aCallback) {
|
||||
this.callback = aCallback;
|
||||
this.url = aURL;
|
||||
}
|
||||
WindowListener.prototype = {
|
||||
onOpenWindow: function(aXULWindow) {
|
||||
var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
var self = this;
|
||||
domwindow.addEventListener("load", function() {
|
||||
domwindow.removeEventListener("load", arguments.callee, false);
|
||||
|
||||
ok(true, "domwindow.document.location.href: " + domwindow.document.location.href);
|
||||
if (domwindow.document.location.href != self.url)
|
||||
return;
|
||||
|
||||
// Allow other window load listeners to execute before passing to callback
|
||||
executeSoon(function() {
|
||||
self.callback(domwindow);
|
||||
});
|
||||
}, false);
|
||||
},
|
||||
onCloseWindow: function(aXULWindow) {},
|
||||
onWindowTitleChange: function(aXULWindow, aNewTitle) {}
|
||||
}
|
||||
|
@ -130,8 +130,11 @@ function triggerCommand(aClick, aEvent) {
|
||||
gURLBar.value = TEST_VALUE;
|
||||
gURLBar.focus();
|
||||
|
||||
if (aClick)
|
||||
if (aClick) {
|
||||
is(gURLBar.getAttribute("pageproxystate"), "invalid",
|
||||
"page proxy state must be invalid for go button to be visible");
|
||||
EventUtils.synthesizeMouseAtCenter(gGoButton, aEvent);
|
||||
}
|
||||
else
|
||||
EventUtils.synthesizeKey("VK_RETURN", aEvent);
|
||||
}
|
||||
|
28
browser/base/content/test/newtab/Makefile.in
Normal file
28
browser/base/content/test/newtab/Makefile.in
Normal file
@ -0,0 +1,28 @@
|
||||
# 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/.
|
||||
|
||||
DEPTH = ../../../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = browser/base/content/test/newtab
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_BROWSER_FILES = \
|
||||
browser_newtab_block.js \
|
||||
browser_newtab_disable.js \
|
||||
browser_newtab_drag_drop.js \
|
||||
browser_newtab_drop_preview.js \
|
||||
browser_newtab_private_browsing.js \
|
||||
browser_newtab_reset.js \
|
||||
browser_newtab_tabsync.js \
|
||||
browser_newtab_unpin.js \
|
||||
browser_newtab_bug723102.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_BROWSER_FILES)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
|
61
browser/base/content/test/newtab/browser_newtab_block.js
Normal file
61
browser/base/content/test/newtab/browser_newtab_block.js
Normal file
@ -0,0 +1,61 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests make sure that blocking/removing sites from the grid works
|
||||
* as expected. Pinned tabs should not be moved. Gaps will be re-filled
|
||||
* if more sites are available.
|
||||
*/
|
||||
function runTests() {
|
||||
// we remove sites and expect the gaps to be filled as long as there still
|
||||
// are some sites available
|
||||
setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield blockCell(cells[4]);
|
||||
checkGrid("0,1,2,3,5,6,7,8,9");
|
||||
|
||||
yield blockCell(cells[4]);
|
||||
checkGrid("0,1,2,3,6,7,8,9,");
|
||||
|
||||
yield blockCell(cells[4]);
|
||||
checkGrid("0,1,2,3,7,8,9,,");
|
||||
|
||||
// we removed a pinned site
|
||||
reset();
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",1");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1p,2,3,4,5,6,7,8");
|
||||
|
||||
yield blockCell(cells[1]);
|
||||
checkGrid("0,2,3,4,5,6,7,8,");
|
||||
|
||||
// we remove the last site on the grid (which is pinned) and expect the gap
|
||||
// to be re-filled and the new site to be unpinned
|
||||
reset();
|
||||
setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks(",,,,,,,,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
|
||||
yield blockCell(cells[8]);
|
||||
checkGrid("0,1,2,3,4,5,6,7,9");
|
||||
|
||||
// we remove the first site on the grid with the last one pinned. all cells
|
||||
// but the last one should shift to the left and a new site fades in
|
||||
reset();
|
||||
setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks(",,,,,,,,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
|
||||
yield blockCell(cells[0]);
|
||||
checkGrid("1,2,3,4,5,6,7,9,8p");
|
||||
}
|
17
browser/base/content/test/newtab/browser_newtab_bug723102.js
Normal file
17
browser/base/content/test/newtab/browser_newtab_bug723102.js
Normal file
@ -0,0 +1,17 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
// create a new tab page and hide it.
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
let firstTab = gBrowser.selectedTab;
|
||||
|
||||
yield addNewTabPageTab();
|
||||
gBrowser.removeTab(firstTab);
|
||||
|
||||
cw.gToolbar.hide();
|
||||
ok(cw.gGrid.node.hasAttribute("page-disabled"), "page is disabled");
|
||||
}
|
34
browser/base/content/test/newtab/browser_newtab_disable.js
Normal file
34
browser/base/content/test/newtab/browser_newtab_disable.js
Normal file
@ -0,0 +1,34 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests make sure that the 'New Tab Page' feature can be disabled if the
|
||||
* decides not to use it.
|
||||
*/
|
||||
function runTests() {
|
||||
// create a new tab page and hide it.
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
let gridNode = cw.gGrid.node;
|
||||
|
||||
ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
|
||||
|
||||
cw.gToolbar.hide();
|
||||
ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
|
||||
|
||||
let oldGridNode = cw.gGrid.node;
|
||||
|
||||
// create a second new tage page and make sure it's disabled. enable it
|
||||
// again and check if the former page gets enabled as well.
|
||||
yield addNewTabPageTab();
|
||||
ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
|
||||
|
||||
// check that no sites have been rendered
|
||||
is(0, cw.document.querySelectorAll(".site").length, "no sites have been rendered");
|
||||
|
||||
cw.gToolbar.show();
|
||||
ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
|
||||
ok(!oldGridNode.hasAttribute("page-disabled"), "old page is not disabled");
|
||||
}
|
115
browser/base/content/test/newtab/browser_newtab_drag_drop.js
Normal file
115
browser/base/content/test/newtab/browser_newtab_drag_drop.js
Normal file
@ -0,0 +1,115 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests make sure that dragging and dropping sites works as expected.
|
||||
* Sites contained in the grid need to shift around to indicate the result
|
||||
* of the drag-and-drop operation. If the grid is full and we're dragging
|
||||
* a new site into it another one gets pushed out.
|
||||
*/
|
||||
function runTests() {
|
||||
// test a simple drag-and-drop scenario
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield simulateDrop(cells[1], cells[0]);
|
||||
checkGrid("1,0p,2,3,4,5,6,7,8");
|
||||
|
||||
// drag a cell to its current cell and make sure it's not pinned afterwards
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield simulateDrop(cells[0], cells[0]);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
// ensure that pinned pages aren't moved if that's not necessary
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",1,2");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1p,2p,3,4,5,6,7,8");
|
||||
|
||||
yield simulateDrop(cells[3], cells[0]);
|
||||
checkGrid("3,1p,2p,0p,4,5,6,7,8");
|
||||
|
||||
// pinned sites should always be moved around as blocks. if a pinned site is
|
||||
// moved around, neighboring pinned are affected as well
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2,3,4,5,6,7,8");
|
||||
|
||||
yield simulateDrop(cells[0], cells[2]);
|
||||
checkGrid("2p,0p,1p,3,4,5,6,7,8");
|
||||
|
||||
// pinned sites should not be pushed out of the grid (unless there are only
|
||||
// pinned ones left on the grid)
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateDrop(cells[8], cells[2]);
|
||||
checkGrid("0,1,3,4,5,6,7p,8p,2p");
|
||||
|
||||
// make sure that pinned sites are re-positioned correctly
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1,2,,,5");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2p,3,4,5p,6,7,8");
|
||||
|
||||
yield simulateDrop(cells[4], cells[0]);
|
||||
checkGrid("3,1p,2p,4,0p,5p,6,7,8");
|
||||
|
||||
// drag a new site onto the very first cell
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateDrop(cells[0]);
|
||||
checkGrid("99p,0,1,2,3,4,5,7p,8p");
|
||||
|
||||
// drag a new site onto the grid and make sure that pinned cells don't get
|
||||
// pushed out
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateDrop(cells[7]);
|
||||
checkGrid("0,1,2,3,4,5,7p,99p,8p");
|
||||
|
||||
// drag a new site beneath a pinned cell and make sure the pinned cell is
|
||||
// not moved
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
|
||||
yield simulateDrop(cells[7]);
|
||||
checkGrid("0,1,2,3,4,5,6,99p,8p");
|
||||
|
||||
// drag a new site onto a block of pinned sites and make sure they're shifted
|
||||
// around accordingly
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1,2,,,,,,");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2p");
|
||||
|
||||
yield simulateDrop(cells[1]);
|
||||
checkGrid("0p,99p,1p,2p,3,4,5,6,7");
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests ensure that the drop preview correctly arranges sites when
|
||||
* dragging them around.
|
||||
*/
|
||||
function runTests() {
|
||||
// the first three sites are pinned - make sure they're re-arranged correctly
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1,2,,,5");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2p,3,4,5p,6,7,8");
|
||||
|
||||
cw.gDrag._draggedSite = cells[0].site;
|
||||
let sites = cw.gDropPreview.rearrange(cells[4]);
|
||||
cw.gDrag._draggedSite = null;
|
||||
|
||||
checkGrid("3,1p,2p,4,0p,5p,6,7,8", sites);
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests ensure that all changes made to the new tab page in private
|
||||
* browsing mode are discarded after switching back to normal mode again.
|
||||
* The private browsing mode should start with the current grid shown in normal
|
||||
* mode.
|
||||
*/
|
||||
let pb = Cc["@mozilla.org/privatebrowsing;1"]
|
||||
.getService(Ci.nsIPrivateBrowsingService);
|
||||
|
||||
function runTests() {
|
||||
// prepare the grid
|
||||
setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
ok(!pb.privateBrowsingEnabled, "private browsing is disabled");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
pinCell(cells[0]);
|
||||
checkGrid("0p,1,2,3,4,5,6,7,8");
|
||||
|
||||
// enter private browsing mode
|
||||
yield togglePrivateBrowsing();
|
||||
ok(pb.privateBrowsingEnabled, "private browsing is enabled");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1,2,3,4,5,6,7,8");
|
||||
|
||||
// modify the grid while we're in pb mode
|
||||
yield blockCell(cells[1]);
|
||||
checkGrid("0p,2,3,4,5,6,7,8");
|
||||
|
||||
yield unpinCell(cells[0]);
|
||||
checkGrid("0,2,3,4,5,6,7,8");
|
||||
|
||||
// exit private browsing mode
|
||||
yield togglePrivateBrowsing();
|
||||
ok(!pb.privateBrowsingEnabled, "private browsing is disabled");
|
||||
|
||||
// check that the grid is the same as before entering pb mode
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1,2,3,4,5,6,7,8");
|
||||
}
|
||||
|
||||
function togglePrivateBrowsing() {
|
||||
let topic = "private-browsing-transition-complete";
|
||||
|
||||
Services.obs.addObserver(function observe() {
|
||||
Services.obs.removeObserver(observe, topic);
|
||||
executeSoon(TestRunner.next);
|
||||
}, topic, false);
|
||||
|
||||
pb.privateBrowsingEnabled = !pb.privateBrowsingEnabled;
|
||||
}
|
25
browser/base/content/test/newtab/browser_newtab_reset.js
Normal file
25
browser/base/content/test/newtab/browser_newtab_reset.js
Normal file
@ -0,0 +1,25 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests make sure that resetting the 'New Tage Page' works as expected.
|
||||
*/
|
||||
function runTests() {
|
||||
// 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("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
let resetButton = cw.document.getElementById("toolbar-button-reset");
|
||||
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
ok(!resetButton.hasAttribute("modified"), "page is not modified");
|
||||
|
||||
yield blockCell(cells[4]);
|
||||
checkGrid("0,1,2,3,5,6,7,8,");
|
||||
ok(resetButton.hasAttribute("modified"), "page is modified");
|
||||
|
||||
yield cw.gToolbar.reset(TestRunner.next);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
ok(!resetButton.hasAttribute("modified"), "page is not modified");
|
||||
}
|
58
browser/base/content/test/newtab/browser_newtab_tabsync.js
Normal file
58
browser/base/content/test/newtab/browser_newtab_tabsync.js
Normal file
@ -0,0 +1,58 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests make sure that all changes that are made to a specific
|
||||
* 'New Tab Page' are synchronized with all other open 'New Tab Pages'
|
||||
* automatically. All about:newtab pages should always be in the same
|
||||
* state.
|
||||
*/
|
||||
function runTests() {
|
||||
setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks(",1");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1p,2,3,4,5,6,7,8");
|
||||
|
||||
let resetButton = cw.document.getElementById("toolbar-button-reset");
|
||||
ok(!resetButton.hasAttribute("modified"), "page is not modified");
|
||||
|
||||
let oldCw = cw;
|
||||
let oldResetButton = resetButton;
|
||||
|
||||
// create the new tab page
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1p,2,3,4,5,6,7,8");
|
||||
|
||||
resetButton = cw.document.getElementById("toolbar-button-reset");
|
||||
ok(!resetButton.hasAttribute("modified"), "page is not modified");
|
||||
|
||||
// unpin a cell
|
||||
yield unpinCell(cells[1]);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
checkGrid("0,1,2,3,4,5,6,7,8", oldCw.gGrid.sites);
|
||||
|
||||
// remove a cell
|
||||
yield blockCell(cells[1]);
|
||||
checkGrid("0,2,3,4,5,6,7,8,9");
|
||||
checkGrid("0,2,3,4,5,6,7,8,9", oldCw.gGrid.sites);
|
||||
ok(resetButton.hasAttribute("modified"), "page is modified");
|
||||
ok(oldResetButton.hasAttribute("modified"), "page is modified");
|
||||
|
||||
// insert a new cell by dragging
|
||||
yield simulateDrop(cells[1]);
|
||||
checkGrid("0,99p,2,3,4,5,6,7,8");
|
||||
checkGrid("0,99p,2,3,4,5,6,7,8", oldCw.gGrid.sites);
|
||||
|
||||
// drag a cell around
|
||||
yield simulateDrop(cells[1], cells[2]);
|
||||
checkGrid("0,2p,99p,3,4,5,6,7,8");
|
||||
checkGrid("0,2p,99p,3,4,5,6,7,8", oldCw.gGrid.sites);
|
||||
|
||||
// reset the new tab page
|
||||
yield cw.gToolbar.reset(TestRunner.next);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
checkGrid("0,1,2,3,4,5,6,7,8", oldCw.gGrid.sites);
|
||||
ok(!resetButton.hasAttribute("modified"), "page is not modified");
|
||||
ok(!oldResetButton.hasAttribute("modified"), "page is not modified");
|
||||
}
|
56
browser/base/content/test/newtab/browser_newtab_unpin.js
Normal file
56
browser/base/content/test/newtab/browser_newtab_unpin.js
Normal file
@ -0,0 +1,56 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests make sure that when a site gets unpinned it is either moved to
|
||||
* its actual place in the grid or removed in case it's not on the grid anymore.
|
||||
*/
|
||||
function runTests() {
|
||||
// we have a pinned link that didn't change its position since it was pinned.
|
||||
// nothing should happend when we unpin it.
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",1");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1p,2,3,4,5,6,7,8");
|
||||
|
||||
yield unpinCell(cells[1]);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
// we have a pinned link that is not anymore in the list of the most-visited
|
||||
// links. this should disappear, the remaining links adjust their positions
|
||||
// and a new link will appear at the end of the grid.
|
||||
setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",99");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
|
||||
yield unpinCell(cells[1]);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
// we have a pinned link that changed its position since it was pinned. it
|
||||
// should be moved to its new position after being unpinned.
|
||||
setLinks("0,1,2,3,4,5,6,7");
|
||||
setPinnedLinks(",1,,,,,,,0");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("2,1p,3,4,5,6,7,,0p");
|
||||
|
||||
yield unpinCell(cells[1]);
|
||||
checkGrid("1,2,3,4,5,6,7,,0p");
|
||||
|
||||
yield unpinCell(cells[8]);
|
||||
checkGrid("0,1,2,3,4,5,6,7,");
|
||||
|
||||
// we have pinned link that changed its position since it was pinned. the
|
||||
// link will disappear from the grid because it's now a much lower priority
|
||||
setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks("9");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("9p,0,1,2,3,4,5,6,7");
|
||||
|
||||
yield unpinCell(cells[0]);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
}
|
264
browser/base/content/test/newtab/head.js
Normal file
264
browser/base/content/test/newtab/head.js
Normal file
@ -0,0 +1,264 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
|
||||
|
||||
Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
|
||||
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/NewTabUtils.jsm", tmp);
|
||||
let NewTabUtils = tmp.NewTabUtils;
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
reset();
|
||||
|
||||
while (gBrowser.tabs.length > 1)
|
||||
gBrowser.removeTab(gBrowser.tabs[1]);
|
||||
|
||||
Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
|
||||
});
|
||||
|
||||
/**
|
||||
* Global variables that are accessed by tests.
|
||||
*/
|
||||
let cw;
|
||||
let cells;
|
||||
|
||||
/**
|
||||
* We'll want to restore the original links provider later.
|
||||
*/
|
||||
let originalProvider = NewTabUtils.links._provider;
|
||||
|
||||
/**
|
||||
* Provide the default test function to start our test runner.
|
||||
*/
|
||||
function test() {
|
||||
TestRunner.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* The test runner that controls the execution flow of our tests.
|
||||
*/
|
||||
let TestRunner = {
|
||||
/**
|
||||
* Starts the test runner.
|
||||
*/
|
||||
run: function () {
|
||||
waitForExplicitFinish();
|
||||
|
||||
this._iter = runTests();
|
||||
this.next();
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs the next available test or finishes if there's no test left.
|
||||
*/
|
||||
next: function () {
|
||||
try {
|
||||
TestRunner._iter.next();
|
||||
} catch (e if e instanceof StopIteration) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows to provide a list of links that is used to construct the grid.
|
||||
* @param aLinksPattern the pattern (see below)
|
||||
*
|
||||
* Example: setLinks("1,2,3")
|
||||
* Result: [{url: "about:blank#1", title: "site#1"},
|
||||
* {url: "about:blank#2", title: "site#2"}
|
||||
* {url: "about:blank#3", title: "site#3"}]
|
||||
*/
|
||||
function setLinks(aLinksPattern) {
|
||||
let links = aLinksPattern.split(/\s*,\s*/).map(function (id) {
|
||||
return {url: "about:blank#" + id, title: "site#" + id};
|
||||
});
|
||||
|
||||
NewTabUtils.links._provider = {getLinks: function (c) c(links)};
|
||||
NewTabUtils.links._links = links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to specify the list of pinned links (that have a fixed position in
|
||||
* the grid.
|
||||
* @param aLinksPattern the pattern (see below)
|
||||
*
|
||||
* Example: setPinnedLinks("3,,1")
|
||||
* Result: 'about:blank#3' is pinned in the first cell. 'about:blank#1' is
|
||||
* pinned in the third cell.
|
||||
*/
|
||||
function setPinnedLinks(aLinksPattern) {
|
||||
let pinnedLinks = [];
|
||||
|
||||
aLinksPattern.split(/\s*,\s*/).forEach(function (id, index) {
|
||||
let link;
|
||||
|
||||
if (id)
|
||||
link = {url: "about:blank#" + id, title: "site#" + id};
|
||||
|
||||
pinnedLinks[index] = link;
|
||||
});
|
||||
|
||||
// Inject the list of pinned links to work with.
|
||||
NewTabUtils.pinnedLinks._links = pinnedLinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the lists of blocked and pinned links and clears the storage.
|
||||
*/
|
||||
function reset() {
|
||||
NewTabUtils.reset();
|
||||
|
||||
// Restore the old provider to prevent memory leaks.
|
||||
NewTabUtils.links._provider = originalProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tab containing 'about:newtab'.
|
||||
*/
|
||||
function addNewTabPageTab() {
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab("about:newtab");
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
// Wait for the new tab page to be loaded.
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
|
||||
cw = browser.contentWindow;
|
||||
|
||||
if (NewTabUtils.allPages.enabled) {
|
||||
cells = cw.gGrid.cells;
|
||||
|
||||
// Continue when the link cache has been populated.
|
||||
NewTabUtils.links.populateCache(TestRunner.next);
|
||||
} else {
|
||||
TestRunner.next();
|
||||
}
|
||||
|
||||
}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current grid arrangement with the given pattern.
|
||||
* @param the pattern (see below)
|
||||
* @param the array of sites to compare with (optional)
|
||||
*
|
||||
* Example: checkGrid("3p,2,,1p")
|
||||
* Result: We expect the first cell to contain the pinned site 'about:blank#3'.
|
||||
* The second cell contains 'about:blank#2'. The third cell is empty.
|
||||
* The fourth cell contains the pinned site 'about:blank#4'.
|
||||
*/
|
||||
function checkGrid(aSitesPattern, aSites) {
|
||||
let valid = true;
|
||||
|
||||
aSites = aSites || cw.gGrid.sites;
|
||||
|
||||
aSitesPattern.split(/\s*,\s*/).forEach(function (id, index) {
|
||||
let site = aSites[index];
|
||||
let match = id.match(/^\d+/);
|
||||
|
||||
// We expect the cell to be empty.
|
||||
if (!match) {
|
||||
if (site) {
|
||||
valid = false;
|
||||
ok(false, "expected cell#" + index + " to be empty");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We expect the cell to contain a site.
|
||||
if (!site) {
|
||||
valid = false;
|
||||
ok(false, "didn't expect cell#" + index + " to be empty");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let num = match[0];
|
||||
|
||||
// Check the site's url.
|
||||
if (site.url != "about:blank#" + num) {
|
||||
valid = false;
|
||||
is(site.url, "about:blank#" + num, "cell#" + index + " has the wrong url");
|
||||
}
|
||||
|
||||
let shouldBePinned = /p$/.test(id);
|
||||
let cellContainsPinned = site.isPinned();
|
||||
let cssClassPinned = site.node && site.node.hasAttribute("pinned");
|
||||
|
||||
// Check if the site should be and is pinned.
|
||||
if (shouldBePinned) {
|
||||
if (!cellContainsPinned) {
|
||||
valid = false;
|
||||
ok(false, "expected cell#" + index + " to be pinned");
|
||||
} else if (!cssClassPinned) {
|
||||
valid = false;
|
||||
ok(false, "expected cell#" + index + " to have css class 'pinned'");
|
||||
}
|
||||
} else {
|
||||
if (cellContainsPinned) {
|
||||
valid = false;
|
||||
ok(false, "didn't expect cell#" + index + " to be pinned");
|
||||
} else if (cssClassPinned) {
|
||||
valid = false;
|
||||
ok(false, "didn't expect cell#" + index + " to have css class 'pinned'");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If every test passed, say so.
|
||||
if (valid)
|
||||
ok(true, "grid status = " + aSitesPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks the given cell's site from the grid.
|
||||
* @param aCell the cell that contains the site to block
|
||||
*/
|
||||
function blockCell(aCell) {
|
||||
aCell.site.block(function () executeSoon(TestRunner.next));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins a given cell's site on a given position.
|
||||
* @param aCell the cell that contains the site to pin
|
||||
* @param aIndex the index the defines where the site should be pinned
|
||||
*/
|
||||
function pinCell(aCell, aIndex) {
|
||||
aCell.site.pin(aIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpins the given cell's site.
|
||||
* @param aCell the cell that contains the site to unpin
|
||||
*/
|
||||
function unpinCell(aCell) {
|
||||
aCell.site.unpin(function () executeSoon(TestRunner.next));
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a drop and drop operation.
|
||||
* @param aDropTarget the cell that is the drop target
|
||||
* @param aDragSource the cell that contains the dragged site (optional)
|
||||
*/
|
||||
function simulateDrop(aDropTarget, aDragSource) {
|
||||
let event = {
|
||||
dataTransfer: {
|
||||
mozUserCancelled: false,
|
||||
setData: function () null,
|
||||
setDragImage: function () null,
|
||||
getData: function () "about:blank#99\nblank"
|
||||
}
|
||||
};
|
||||
|
||||
if (aDragSource)
|
||||
cw.gDrag.start(aDragSource.site, event);
|
||||
|
||||
cw.gDrop.drop(aDropTarget, event, function () executeSoon(TestRunner.next));
|
||||
|
||||
if (aDragSource)
|
||||
cw.gDrag.end(aDragSource.site);
|
||||
}
|
@ -41,11 +41,23 @@
|
||||
|
||||
// Services = object with smart getters for common XPCOM services
|
||||
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";
|
||||
});
|
||||
|
||||
var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
|
||||
|
||||
var gBidiUI = false;
|
||||
|
||||
/**
|
||||
* Determines whether the given url is considered a special URL for new tabs.
|
||||
*/
|
||||
function isBlankPageURL(aURL) {
|
||||
return aURL == "about:blank" || aURL == BROWSER_NEW_TAB_URL;
|
||||
}
|
||||
|
||||
function getBrowserURL()
|
||||
{
|
||||
return "chrome://browser/content/browser.xul";
|
||||
@ -299,7 +311,7 @@ function openLinkIn(url, where, params) {
|
||||
else
|
||||
w.gBrowser.selectedBrowser.focus();
|
||||
|
||||
if (!loadInBackground && url == "about:blank")
|
||||
if (!loadInBackground && isBlankPageURL(url))
|
||||
w.focusAndSelectUrlBar();
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user