Merge m-c to elm

This commit is contained in:
Nick Alexander 2013-11-04 21:25:12 -08:00
commit e16fe14d12
1520 changed files with 37336 additions and 22797 deletions

View File

@ -18,4 +18,4 @@
# Modifying this file will now automatically clobber the buildbot machines \o/
#
Bug 918345 needs a clobber due to WebIDL binding dependency issues (bug 928195).
Bug 906990 needs a clobber because of bug 928195

View File

@ -60,6 +60,24 @@ config.status: $(topsrcdir)/configure
@echo "but your build might not succeed."
@exit 1
# Regenerate the build backend if it is out of date. We only have this rule in
# this main make file because having it in rules.mk and applied to partial tree
# builds resulted in a world of hurt. Gory details are in bug 877308.
#
# The mach build driver will ensure the backend is up to date for partial tree
# builds. This cleanly avoids most of the pain.
backend.RecursiveMakeBackend:
@echo "Build configuration changed. Regenerating backend."
./config.status
Makefile: backend.RecursiveMakeBackend
@$(TOUCH) $@
include backend.RecursiveMakeBackend.pp
default:: backend.RecursiveMakeBackend
ifndef LIBXUL_SDK
.PHONY: js-config-status
js-config-status:
@ -241,7 +259,7 @@ endif
ifdef MOZ_PSEUDO_DERECURSE
# Interdependencies for parallel export.
js/xpconnect/src/export: dom/bindings/export
js/xpconnect/src/export: dom/bindings/export xpcom/xpidl/export
accessible/src/xpcom/export: xpcom/xpidl/export
js/src/export: mfbt/export
ifdef ENABLE_CLANG_PLUGIN

View File

@ -10,16 +10,6 @@ GARBAGE += $(MIDL_GENERATED_FILES) done_gen dlldata.c
FORCE_SHARED_LIB = 1
CSRCS = \
dlldata.c \
ISimpleDOMNode_p.c \
ISimpleDOMNode_i.c \
ISimpleDOMDocument_p.c \
ISimpleDOMDocument_i.c \
ISimpleDOMText_p.c \
ISimpleDOMText_i.c \
$(NULL)
MIDL_GENERATED_FILES = \
ISimpleDOMNode.h \
ISimpleDOMNode_p.c \

View File

@ -8,3 +8,12 @@ MODULE = 'accessibility'
LIBRARY_NAME = 'AccessibleMarshal'
GENERATED_SOURCES += [
'dlldata.c',
'ISimpleDOMDocument_i.c',
'ISimpleDOMDocument_p.c',
'ISimpleDOMNode_i.c',
'ISimpleDOMNode_p.c',
'ISimpleDOMText_i.c',
'ISimpleDOMText_p.c',
]

View File

@ -186,7 +186,7 @@ interface nsIAccessiblePivotObserver : nsISupports
in PivotMoveReason aReason);
};
[scriptable, uuid(366fe92b-44c9-4769-ae40-7c2a075d3b16)]
[scriptable, uuid(4d9c4352-20f5-4c54-9580-0c77bb6b1115)]
interface nsIAccessibleTraversalRule : nsISupports
{
/* Ignore this accessible object */
@ -201,6 +201,7 @@ interface nsIAccessibleTraversalRule : nsISupports
const unsigned long PREFILTER_OFFSCREEN = 0x00000002;
const unsigned long PREFILTER_NOT_FOCUSABLE = 0x00000004;
const unsigned long PREFILTER_ARIA_HIDDEN = 0x00000008;
const unsigned long PREFILTER_TRANSPARENT = 0x00000010;
/**
* Pre-filter bitfield to filter out obviously ignorable nodes and lighten

View File

@ -2,71 +2,63 @@
/* 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/. */
#include "nsISupports.idl"
#include "nsIAccessible.idl"
#include "nsIArray.idl"
interface nsIAccessible;
interface nsIArray;
/**
* An interface for the accessibility module and in-process accessibility clients
* for dealing with getting and changing the selection of accessible nodes.
* An accessibility interface for selectable widgets.
*/
[scriptable, uuid(34d268d6-1dd2-11b2-9d63-83a5e0ada290)]
[scriptable, uuid(3e507fc4-4fcc-4223-a674-a095f591eba1)]
interface nsIAccessibleSelectable : nsISupports
{
const unsigned long eSelection_Add = 0;
const unsigned long eSelection_Remove = 1;
const unsigned long eSelection_GetState = 2;
/**
* Return an nsIArray of selected items within the widget.
*/
readonly attribute nsIArray selectedItems;
/**
* Return an nsIArray of selected nsIAccessible children
*/
nsIArray GetSelectedChildren();
/**
* Returns the number of accessible children currently selected.
*/
readonly attribute long selectionCount;
/**
* Return the number of currently selected items.
*/
readonly attribute unsigned long selectedItemCount;
/**
* Adds the specified accessible child of the object to the
* object's selection.
* If the specified object is already selected, then it does nothing.
* @throws NS_ERROR_FAILURE if the specified object is not selectable.
*/
void addChildToSelection(in long index);
/**
* Return a nth selected item within the widget.
*/
nsIAccessible getSelectedItemAt(in unsigned long index);
/**
* Removes the specified child of the object from the object's selection.
* If the specified object was not selected, then it does nothing.
* @throws NS_ERROR_FAILURE if the specified object is not selectable.
*/
void removeChildFromSelection(in long index);
/**
* Return true if the given item is selected.
*/
[binaryname(ScriptableIsItemSelected)]
boolean isItemSelected(in unsigned long index);
/**
* Clears the selection in the object so that no children in the object
* are selected.
*/
void clearSelection();
/**
* Adds the specified item to the widget's selection.
*/
[binaryname(ScriptableAddItemToSelection)]
void addItemToSelection(in unsigned long index);
/**
* Returns a reference to the accessible object representing the specified
* selected child of the object.
* @param index Zero-based selected accessible child index
* @return The nth selected accessible child
*/
nsIAccessible refSelection(in long index);
/**
* Removes the specified item from the widget's selection.
*/
[binaryname(ScriptableRemoveItemFromSelection)]
void removeItemFromSelection(in unsigned long index);
/**
* Determines if the current child of this object is selected
* @param The zero-based accessible child index
* @return Returns true if the child is selected, false if not.
*/
boolean isChildSelected(in long index);
/**
* Select all items.
*
* @return false if the object does not accept multiple selection,
* otherwise true.
*/
[binaryname(ScriptableSelectAll)]
boolean selectAll();
/**
* Select all children
* @return If the object does not accept multiple selection, return false.
* Otherwise, returns true.
*/
boolean selectAllSelection();
/**
* Unselect all items.
*/
[binaryname(ScriptableUnselectAll)]
void unselectAll();
};

View File

@ -169,6 +169,9 @@ void
SelectionManager::ProcessSelectionChanged(nsISelection* aSelection)
{
Selection* selection = static_cast<Selection*>(aSelection);
if (!selection->GetPresShell())
return;
const nsRange* range = selection->GetAnchorFocusRange();
nsINode* cntrNode = nullptr;
if (range)

View File

@ -875,6 +875,15 @@ RuleCache::ApplyFilter(Accessible* aAccessible, uint16_t* aResult)
return NS_OK;
}
}
if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) &&
!(state & states::OPAQUE1)) {
nsIFrame* frame = aAccessible->GetFrame();
if (frame->StyleDisplay()->mOpacity == 0.0f) {
*aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
return NS_OK;
}
}
}
if (mAcceptRolesLength > 0) {

View File

@ -9,4 +9,3 @@ endif
ifeq (,$(filter aurora beta release esr,$(MOZ_UPDATE_CHANNEL)))
A11Y_LOG = 1
endif
export A11Y_LOG

View File

@ -1013,7 +1013,7 @@ Accessible::TakeSelection()
Accessible* select = nsAccUtils::GetSelectableContainer(this, State());
if (select) {
if (select->State() & states::MULTISELECTABLE)
select->ClearSelection();
select->UnselectAll();
return SetSelected(true);
}
@ -2332,118 +2332,6 @@ Accessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY)
return NS_OK;
}
// nsIAccessibleSelectable
NS_IMETHODIMP
Accessible::GetSelectedChildren(nsIArray** aSelectedAccessibles)
{
NS_ENSURE_ARG_POINTER(aSelectedAccessibles);
*aSelectedAccessibles = nullptr;
if (IsDefunct() || !IsSelect())
return NS_ERROR_FAILURE;
nsCOMPtr<nsIArray> items = SelectedItems();
if (items) {
uint32_t length = 0;
items->GetLength(&length);
if (length)
items.swap(*aSelectedAccessibles);
}
return NS_OK;
}
// return the nth selected descendant nsIAccessible object
NS_IMETHODIMP
Accessible::RefSelection(int32_t aIndex, nsIAccessible** aSelected)
{
NS_ENSURE_ARG_POINTER(aSelected);
*aSelected = nullptr;
if (IsDefunct() || !IsSelect())
return NS_ERROR_FAILURE;
if (aIndex < 0) {
return NS_ERROR_INVALID_ARG;
}
*aSelected = GetSelectedItem(aIndex);
if (*aSelected) {
NS_ADDREF(*aSelected);
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP
Accessible::GetSelectionCount(int32_t* aSelectionCount)
{
NS_ENSURE_ARG_POINTER(aSelectionCount);
*aSelectionCount = 0;
if (IsDefunct() || !IsSelect())
return NS_ERROR_FAILURE;
*aSelectionCount = SelectedItemCount();
return NS_OK;
}
NS_IMETHODIMP Accessible::AddChildToSelection(int32_t aIndex)
{
if (IsDefunct() || !IsSelect())
return NS_ERROR_FAILURE;
return aIndex >= 0 && AddItemToSelection(aIndex) ?
NS_OK : NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP Accessible::RemoveChildFromSelection(int32_t aIndex)
{
if (IsDefunct() || !IsSelect())
return NS_ERROR_FAILURE;
return aIndex >=0 && RemoveItemFromSelection(aIndex) ?
NS_OK : NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP Accessible::IsChildSelected(int32_t aIndex, bool *aIsSelected)
{
NS_ENSURE_ARG_POINTER(aIsSelected);
*aIsSelected = false;
if (IsDefunct() || !IsSelect())
return NS_ERROR_FAILURE;
NS_ENSURE_TRUE(aIndex >= 0, NS_ERROR_FAILURE);
*aIsSelected = IsItemSelected(aIndex);
return NS_OK;
}
NS_IMETHODIMP
Accessible::ClearSelection()
{
if (IsDefunct() || !IsSelect())
return NS_ERROR_FAILURE;
UnselectAll();
return NS_OK;
}
NS_IMETHODIMP
Accessible::SelectAllSelection(bool* aIsMultiSelect)
{
NS_ENSURE_ARG_POINTER(aIsMultiSelect);
*aIsMultiSelect = false;
if (IsDefunct() || !IsSelect())
return NS_ERROR_FAILURE;
*aIsMultiSelect = SelectAll();
return NS_OK;
}
// nsIAccessibleHyperLink
// Because of new-atk design, any embedded object in text can implement
// nsIAccessibleHyperLink, which helps determine where it is located

View File

@ -13,9 +13,9 @@
#include "nsIAccessible.h"
#include "nsIAccessibleHyperLink.h"
#include "nsIAccessibleSelectable.h"
#include "nsIAccessibleValue.h"
#include "nsIAccessibleStates.h"
#include "xpcAccessibleSelectable.h"
#include "nsIContent.h"
#include "nsString.h"
@ -104,7 +104,7 @@ typedef nsRefPtrHashtable<nsPtrHashKey<const void>, Accessible>
class Accessible : public nsIAccessible,
public nsIAccessibleHyperLink,
public nsIAccessibleSelectable,
public xpcAccessibleSelectable,
public nsIAccessibleValue
{
public:
@ -116,7 +116,6 @@ public:
NS_DECL_NSIACCESSIBLE
NS_DECL_NSIACCESSIBLEHYPERLINK
NS_DECL_NSIACCESSIBLESELECTABLE
NS_DECL_NSIACCESSIBLEVALUE
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCESSIBLE_IMPL_IID)

View File

@ -881,10 +881,11 @@ HyperTextAccessible::GetTextBeforeOffset(int32_t aOffset,
return NS_OK;
}
int32_t adjustedOffset = ConvertMagicOffset(aOffset);
if (adjustedOffset < 0)
int32_t convertedOffset = ConvertMagicOffset(aOffset);
if (convertedOffset < 0)
return NS_ERROR_INVALID_ARG;
int32_t adjustedOffset = convertedOffset;
if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
adjustedOffset = AdjustCaretOffset(adjustedOffset);
@ -913,7 +914,7 @@ HyperTextAccessible::GetTextBeforeOffset(int32_t aOffset,
case BOUNDARY_WORD_END: {
// Move word backward twice to find start and end offsets.
*aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eEndWord);
*aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord);
*aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
return GetText(*aStartOffset, *aEndOffset, aText);
}
@ -1009,10 +1010,11 @@ HyperTextAccessible::GetTextAfterOffset(int32_t aOffset,
return NS_OK;
}
int32_t adjustedOffset = ConvertMagicOffset(aOffset);
if (adjustedOffset < 0)
int32_t convertedOffset = ConvertMagicOffset(aOffset);
if (convertedOffset < 0)
return NS_ERROR_INVALID_ARG;
int32_t adjustedOffset = convertedOffset;
if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
adjustedOffset = AdjustCaretOffset(adjustedOffset);
@ -1031,13 +1033,13 @@ HyperTextAccessible::GetTextAfterOffset(int32_t aOffset,
// If the offset is a word end (except 0 offset) then move forward to find
// end offset (start offset is the given offset). Otherwise move forward
// twice to find both start and end offsets.
if (adjustedOffset == 0) {
*aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord);
if (convertedOffset == 0) {
*aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
*aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
} else {
*aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord);
*aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
*aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
if (*aStartOffset != adjustedOffset) {
if (*aStartOffset != convertedOffset) {
*aStartOffset = *aEndOffset;
*aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
}

View File

@ -216,7 +216,8 @@ this.OutputGenerator = {
}
let typeName = Utils.getAttributes(aAccessible)['text-input-type'];
if (!typeName) {
// Ignore the the input type="text" case.
if (!typeName || typeName === 'text') {
return;
}
aDesc.push(gStringBundle.GetStringFromName('textInputType_' + typeName));

View File

@ -74,7 +74,8 @@ BaseTraversalRule.prototype = {
preFilter: Ci.nsIAccessibleTraversalRule.PREFILTER_DEFUNCT |
Ci.nsIAccessibleTraversalRule.PREFILTER_INVISIBLE |
Ci.nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN,
Ci.nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN |
Ci.nsIAccessibleTraversalRule.PREFILTER_TRANSPARENT,
match: function BaseTraversalRule_match(aAccessible)
{

View File

@ -9,8 +9,6 @@ xpcaccevents_FILES := xpcAccEvents.h
xpcaccevents_DEST = $(DIST)/include
xpcaccevents_TARGET := export
CPPSRCS += xpcAccEvents.cpp
include $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
@ -20,8 +18,7 @@ endif
xpcAccEvents.cpp: $(srcdir)/AccEvents.conf \
$(srcdir)/AccEventGen.py \
$(LIBXUL_DIST)/sdk/bin/header.py \
$(LIBXUL_DIST)/sdk/bin/xpidl.py \
$(DEPTH)/js/src/js-confdefs.h
$(LIBXUL_DIST)/sdk/bin/xpidl.py
$(PYTHON) $(topsrcdir)/config/pythonpath.py \
-I$(LIBXUL_DIST)/sdk/bin \
$(srcdir)/AccEventGen.py \

View File

@ -6,12 +6,21 @@
MODULE = 'accessibility'
EXPORTS += [
'xpcAccessibleSelectable.h',
]
SOURCES += [
'nsAccessibleRelation.cpp',
'xpcAccessibleSelectable.cpp',
'xpcAccessibleTable.cpp',
'xpcAccessibleTableCell.cpp',
]
GENERATED_SOURCES += [
'xpcAccEvents.cpp',
]
LIBRARY_NAME = 'accessibility_xpcom_s'
LIBXUL_LIBRARY = True

View 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: */
/* 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/. */
#include "xpcAccessibleSelectable.h"
#include "Accessible-inl.h"
using namespace mozilla::a11y;
NS_IMETHODIMP
xpcAccessibleSelectable::GetSelectedItems(nsIArray** aSelectedItems)
{
NS_ENSURE_ARG_POINTER(aSelectedItems);
*aSelectedItems = nullptr;
Accessible* acc = static_cast<Accessible*>(this);
if (acc->IsDefunct())
return NS_ERROR_FAILURE;
NS_PRECONDITION(acc->IsSelect(), "Called on non selectable widget!");
nsCOMPtr<nsIArray> items = acc->SelectedItems();
if (items) {
uint32_t length = 0;
items->GetLength(&length);
if (length)
items.swap(*aSelectedItems);
}
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleSelectable::GetSelectedItemCount(uint32_t* aSelectionCount)
{
NS_ENSURE_ARG_POINTER(aSelectionCount);
*aSelectionCount = 0;
Accessible* acc = static_cast<Accessible*>(this);
if (acc->IsDefunct())
return NS_ERROR_FAILURE;
NS_PRECONDITION(acc->IsSelect(), "Called on non selectable widget!");
*aSelectionCount = acc->SelectedItemCount();
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleSelectable::GetSelectedItemAt(uint32_t aIndex,
nsIAccessible** aSelected)
{
NS_ENSURE_ARG_POINTER(aSelected);
*aSelected = nullptr;
Accessible* acc = static_cast<Accessible*>(this);
if (acc->IsDefunct())
return NS_ERROR_FAILURE;
NS_PRECONDITION(acc->IsSelect(), "Called on non selectable widget!");
*aSelected = acc->GetSelectedItem(aIndex);
if (*aSelected) {
NS_ADDREF(*aSelected);
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP
xpcAccessibleSelectable::ScriptableIsItemSelected(uint32_t aIndex,
bool* aIsSelected)
{
NS_ENSURE_ARG_POINTER(aIsSelected);
*aIsSelected = false;
Accessible* acc = static_cast<Accessible*>(this);
if (acc->IsDefunct())
return NS_ERROR_FAILURE;
NS_PRECONDITION(acc->IsSelect(), "Called on non selectable widget!");
*aIsSelected = acc->IsItemSelected(aIndex);
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleSelectable::ScriptableAddItemToSelection(uint32_t aIndex)
{
Accessible* acc = static_cast<Accessible*>(this);
if (acc->IsDefunct())
return NS_ERROR_FAILURE;
NS_PRECONDITION(acc->IsSelect(), "Called on non selectable widget!");
return acc->AddItemToSelection(aIndex) ? NS_OK : NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP
xpcAccessibleSelectable::ScriptableRemoveItemFromSelection(uint32_t aIndex)
{
Accessible* acc = static_cast<Accessible*>(this);
if (acc->IsDefunct())
return NS_ERROR_FAILURE;
NS_PRECONDITION(acc->IsSelect(), "Called on non selectable widget!");
return acc->RemoveItemFromSelection(aIndex) ? NS_OK : NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP
xpcAccessibleSelectable::ScriptableSelectAll(bool* aIsMultiSelect)
{
NS_ENSURE_ARG_POINTER(aIsMultiSelect);
*aIsMultiSelect = false;
Accessible* acc = static_cast<Accessible*>(this);
if (acc->IsDefunct())
return NS_ERROR_FAILURE;
NS_PRECONDITION(acc->IsSelect(), "Called on non selectable widget!");
*aIsMultiSelect = acc->SelectAll();
return NS_OK;
}
NS_IMETHODIMP
xpcAccessibleSelectable::ScriptableUnselectAll()
{
Accessible* acc = static_cast<Accessible*>(this);
if (acc->IsDefunct())
return NS_ERROR_FAILURE;
NS_PRECONDITION(acc->IsSelect(), "Called on non selectable widget!");
acc->UnselectAll();
return NS_OK;
}

View File

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_xpcAccessibleSelectable_h_
#define mozilla_a11y_xpcAccessibleSelectable_h_
#include "nsIAccessibleSelectable.h"
class nsIAccessible;
class nsIArray;
namespace mozilla {
namespace a11y {
class xpcAccessibleSelectable : public nsIAccessibleSelectable
{
public:
NS_IMETHOD GetSelectedItems(nsIArray** aSelectedItems) MOZ_FINAL;
NS_IMETHOD GetSelectedItemCount(uint32_t* aSelectedItemCount) MOZ_FINAL;
NS_IMETHOD GetSelectedItemAt(uint32_t aIndex, nsIAccessible** aItem) MOZ_FINAL;
NS_IMETHOD ScriptableIsItemSelected(uint32_t aIndex, bool* aIsSelected) MOZ_FINAL;
NS_IMETHOD ScriptableAddItemToSelection(uint32_t aIndex) MOZ_FINAL;
NS_IMETHOD ScriptableRemoveItemFromSelection(uint32_t aIndex) MOZ_FINAL;
NS_IMETHOD ScriptableSelectAll(bool* aIsMultiSelect) MOZ_FINAL;
NS_IMETHOD ScriptableUnselectAll() MOZ_FINAL;
private:
xpcAccessibleSelectable() { }
friend class Accessible;
xpcAccessibleSelectable(const xpcAccessibleSelectable&) MOZ_DELETE;
xpcAccessibleSelectable& operator =(const xpcAccessibleSelectable&) MOZ_DELETE;
};
} // namespace a11y
} // namespace mozilla
#endif

View File

@ -61,21 +61,14 @@ XULTreeGridAccessible::SelectedColCount()
// If all the row has been selected, then all the columns are selected,
// because we can't select a column alone.
int32_t selectedRowCount = 0;
nsresult rv = GetSelectionCount(&selectedRowCount);
NS_ENSURE_SUCCESS(rv, 0);
uint32_t selectedRowCount = SelectedItemCount();
return selectedRowCount > 0 && selectedRowCount == RowCount() ? ColCount() : 0;
}
uint32_t
XULTreeGridAccessible::SelectedRowCount()
{
int32_t selectedRowCount = 0;
nsresult rv = GetSelectionCount(&selectedRowCount);
NS_ENSURE_SUCCESS(rv, 0);
return selectedRowCount >= 0 ? selectedRowCount : 0;
return SelectedItemCount();
}
void
@ -164,12 +157,7 @@ XULTreeGridAccessible::IsColSelected(uint32_t aColIdx)
{
// If all the row has been selected, then all the columns are selected.
// Because we can't select a column alone.
int32_t selectedrowCount = 0;
nsresult rv = GetSelectionCount(&selectedrowCount);
NS_ENSURE_SUCCESS(rv, false);
return selectedrowCount == RowCount();
return SelectedItemCount() == RowCount();
}
bool

View File

@ -106,6 +106,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
["URL entry", "http://example.com"],
["http://example.com", "URL entry"]
]
}, {
accOrElmOrID: "textInput",
expected: [["entry", "This is text."], ["This is text.", "entry"]]
}, {
// Test pivot to list from li_one.
accOrElmOrID: "list",
@ -303,6 +306,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
<input id="search" type="search" value="This is a search" />
<input id="tel" type="tel" value="555-5555" />
<input id="url" type="url" value="http://example.com" />
<input id="textInput" type="text" value="This is text." />
</div>
</body>
</html>

View File

@ -5,6 +5,7 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE;
const PREFILTER_ARIA_HIDDEN = nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN;
const PREFILTER_TRANSPARENT = nsIAccessibleTraversalRule.PREFILTER_TRANSPARENT;
const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH;
const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE;
const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
@ -49,7 +50,7 @@ var ObjectTraversalRule =
return 0;
},
preFilter: PREFILTER_INVISIBLE | PREFILTER_ARIA_HIDDEN,
preFilter: PREFILTER_INVISIBLE | PREFILTER_ARIA_HIDDEN | PREFILTER_TRANSPARENT,
match: function(aAccessible)
{

View File

@ -18,6 +18,9 @@
<a href="#" id="hidden-link">Maybe</a> it was the other <i>George Michael</i>.
You know, the <a href="#">singer-songwriter</a>.
</p>
<p style="opacity: 0;" id="paragraph-4">
This is completely transparent
</p>
<iframe
src="data:text/html,<html><body>An <i>embedded</i> document.</body></html>">
</iframe>

View File

@ -14,7 +14,7 @@ function testSelectableSelection(aIdentifier, aSelectedChildren, aMsg)
var len = aSelectedChildren.length;
// getSelectedChildren
var selectedChildren = acc.GetSelectedChildren();
var selectedChildren = acc.selectedItems;
is(selectedChildren ? selectedChildren.length : 0, len,
msg + "getSelectedChildren: wrong selected children count for " +
prettyName(aIdentifier));
@ -28,28 +28,26 @@ function testSelectableSelection(aIdentifier, aSelectedChildren, aMsg)
prettyName(actualAcc) + ", expected: " + prettyName(expectedAcc) + "}");
}
// selectionCount
// XXX: nsIAccessibleText and nsIAccessibleSelectable both have
// selectionCount property.
//is(acc.selectionCount, aSelectedChildren.length,
// "selectionCount: wrong selected children count for " + prettyName(aIdentifier));
// selectedItemCount
is(acc.selectedItemCount, aSelectedChildren.length,
"selectedItemCount: wrong selected children count for " + prettyName(aIdentifier));
// refSelection
// getSelectedItemAt
for (var idx = 0; idx < len; idx++) {
var expectedAcc = getAccessible(aSelectedChildren[idx]);
is(acc.refSelection(idx), expectedAcc,
msg + "refSelection: wrong selected child at index " + idx + " for " +
is(acc.getSelectedItemAt(idx), expectedAcc,
msg + "getSelectedItemAt: wrong selected child at index " + idx + " for " +
prettyName(aIdentifier));
}
// isChildSelected
testIsChildSelected(acc, acc, { value: 0 }, aSelectedChildren, msg);
// isItemSelected
testIsItemSelected(acc, acc, { value: 0 }, aSelectedChildren, msg);
}
/**
* Test isChildSelected method, helper for testSelectableSelection
* Test isItemSelected method, helper for testSelectableSelection
*/
function testIsChildSelected(aSelectAcc, aTraversedAcc, aIndexObj, aSelectedChildren, aMsg)
function testIsItemSelected(aSelectAcc, aTraversedAcc, aIndexObj, aSelectedChildren, aMsg)
{
var childCount = aTraversedAcc.childCount;
for (var idx = 0; idx < childCount; idx++) {
@ -65,9 +63,9 @@ function testIsChildSelected(aSelectAcc, aTraversedAcc, aIndexObj, aSelectedChil
}
}
// isChildSelected
is(aSelectAcc.isChildSelected(aIndexObj.value++), isSelected,
aMsg + "isChildSelected: wrong selected child " + prettyName(child) +
// isItemSelected
is(aSelectAcc.isItemSelected(aIndexObj.value++), isSelected,
aMsg + "isItemSelected: wrong selected child " + prettyName(child) +
" for " + prettyName(aSelectAcc));
// selected state
@ -77,6 +75,6 @@ function testIsChildSelected(aSelectAcc, aTraversedAcc, aIndexObj, aSelectedChil
continue;
}
testIsChildSelected(aSelectAcc, child, aIndexObj, aSelectedChildren);
testIsItemSelected(aSelectAcc, child, aIndexObj, aSelectedChildren);
}
}

View File

@ -27,10 +27,10 @@
testSelectableSelection(acc, []);
acc.selectAllSelection();
acc.selectAll();
testSelectableSelection(acc, aSelectableChildren);
acc.clearSelection();
acc.unselectAll();
testSelectableSelection(acc, []);
}
@ -64,13 +64,13 @@
testSelectableSelection(id, [ ]);
select = getAccessible(id, [nsIAccessibleSelectable]);
select.addChildToSelection(0);
select.addItemToSelection(0);
testSelectableSelection(id, [ "listbox2_item1" ]);
select.removeChildFromSelection(0);
select.removeItemFromSelection(0);
testSelectableSelection(id, [ ]);
select.selectAllSelection();
select.selectAll();
testSelectableSelection(id, [ "listbox2_item1", "listbox2_item2" ]);
select.clearSelection();
select.unselectAll();
testSelectableSelection(id, [ ]);
//////////////////////////////////////////////////////////////////////////

View File

@ -41,20 +41,20 @@
var select = getAccessible(id, [nsIAccessibleSelectable]);
testSelectableSelection(select, [ ]);
select.addChildToSelection(1);
testSelectableSelection(select, [ "lb1_item2" ], "addChildToSelect(1): ");
select.addItemToSelection(1);
testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelect(1): ");
select.removeChildFromSelection(1);
select.removeItemFromSelection(1);
testSelectableSelection(select, [ ],
"removeChildFromSelection(1): ");
"removeItemFromSelection(1): ");
todo(select.selectAllSelection() == false,
todo(select.selectAll() == false,
"No way to select all items in listbox '" + id + "'");
testSelectableSelection(select, [ "lb1_item1" ], "selectAllSelection: ");
testSelectableSelection(select, [ "lb1_item1" ], "selectAll: ");
select.addChildToSelection(1);
select.clearSelection();
testSelectableSelection(select, [ ], "clearSelection: ");
select.addItemToSelection(1);
select.unselectAll();
testSelectableSelection(select, [ ], "unselectAll: ");
//////////////////////////////////////////////////////////////////////////
// multiple selectable listbox
@ -66,30 +66,30 @@
var select = getAccessible(id, [nsIAccessibleSelectable]);
testSelectableSelection(select, [ ]);
select.addChildToSelection(1);
testSelectableSelection(select, [ "lb2_item2" ], "addChildToSelect(1): ");
select.addItemToSelection(1);
testSelectableSelection(select, [ "lb2_item2" ], "addItemToSelect(1): ");
select.removeChildFromSelection(1);
select.removeItemFromSelection(1);
testSelectableSelection(select, [ ],
"removeChildFromSelection(1): ");
"removeItemFromSelection(1): ");
is(select.selectAllSelection(), true,
is(select.selectAll(), true,
"All items should be selected in listbox '" + id + "'");
testSelectableSelection(select, [ "lb2_item1", "lb2_item2" ],
"selectAllSelection: ");
"selectAll: ");
select.clearSelection();
testSelectableSelection(select, [ ], "clearSelection: ");
select.unselectAll();
testSelectableSelection(select, [ ], "unselectAll: ");
//////////////////////////////////////////////////////////////////////////
// listbox with headers
// XXX: addChildToSelection/removeChildFromSelection don't work correctly
// XXX: addItemToSelection/removeItemFromSelection don't work correctly
// on listboxes with headers because header is inserted into hierarchy
// and child indexes that are used in these methods are shifted (see bug
// 591939).
todo(false,
"Fix addChildToSelection/removeChildFromSelection on listboxes with headers.");
"Fix addItemToSelection/removeItemFromSelection on listboxes with headers.");
SimpleTest.finish();
}

View File

@ -43,20 +43,20 @@
var select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
testSelectableSelection(select, [ "cb1_item1" ]);
select.addChildToSelection(1);
testSelectableSelection(select, [ "cb1_item2" ], "addChildToSelect(1): ");
select.addItemToSelection(1);
testSelectableSelection(select, [ "cb1_item2" ], "addItemToSelection(1): ");
select.removeChildFromSelection(1);
select.removeItemFromSelection(1);
testSelectableSelection(select, [ ],
"removeChildFromSelection(1): ");
"removeItemFromSelection(1): ");
is(select.selectAllSelection(), false,
is(select.selectAll(), false,
"No way to select all items in combobox '" + id + "'");
testSelectableSelection(select, [ ], "selectAllSelection: ");
testSelectableSelection(select, [ ], "selectAll: ");
select.addChildToSelection(1);
select.clearSelection();
testSelectableSelection(select, [ ], "clearSelection: ");
select.addItemToSelection(1);
select.unselectAll();
testSelectableSelection(select, [ ], "unselectAll: ");
SimpleTest.finish();
}

View File

@ -37,22 +37,22 @@
testSelectableSelection(select, [ "cb1_item1" ]);
// select 2nd item
select.addChildToSelection(1);
testSelectableSelection(select, [ "cb1_item2" ], "addChildToSelect(1): ");
select.addItemToSelection(1);
testSelectableSelection(select, [ "cb1_item2" ], "addItemToSelection(1): ");
// unselect 2nd item, 1st item gets selected automatically
select.removeChildFromSelection(1);
select.removeItemFromSelection(1);
testSelectableSelection(select, [ "cb1_item1" ],
"removeChildFromSelection(1): ");
"removeItemFromSelection(1): ");
// doesn't change selection
is(select.selectAllSelection(), false,
is(select.selectAll(), false,
"No way to select all items in combobox '" + id + "'");
testSelectableSelection(select, [ "cb1_item1" ], "selectAllSelection: ");
testSelectableSelection(select, [ "cb1_item1" ], "selectAll: ");
// doesn't change selection
select.clearSelection();
testSelectableSelection(select, [ "cb1_item1" ], "clearSelection: ");
select.unselectAll();
testSelectableSelection(select, [ "cb1_item1" ], "unselectAll: ");
//////////////////////////////////////////////////////////////////////////
// select@size="1" with optgroups
@ -66,17 +66,17 @@
select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
testSelectableSelection(select, [ "cb2_item1" ]);
select.addChildToSelection(1);
select.addItemToSelection(1);
testSelectableSelection(select, [ "cb2_item2" ]);
select.removeChildFromSelection(1);
select.removeItemFromSelection(1);
testSelectableSelection(select, [ "cb2_item1" ]);
is(select.selectAllSelection(), false,
is(select.selectAll(), false,
"No way to select all items in combobox " + id + "'");
testSelectableSelection(select, [ "cb2_item1" ]);
select.clearSelection();
select.unselectAll();
testSelectableSelection(select, [ "cb2_item1" ]);
//////////////////////////////////////////////////////////////////////////
@ -90,22 +90,22 @@
testSelectableSelection(select, [ ]);
// select 2nd item
select.addChildToSelection(1);
testSelectableSelection(select, [ "lb1_item2" ], "addChildToSelect(1): ");
select.addItemToSelection(1);
testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelection(1): ");
// unselect 2nd item, 1st item gets selected automatically
select.removeChildFromSelection(1);
select.removeItemFromSelection(1);
testSelectableSelection(select, [ ],
"removeChildFromSelection(1): ");
"removeItemFromSelection(1): ");
// doesn't change selection
is(select.selectAllSelection(), false,
is(select.selectAll(), false,
"No way to select all items in single selectable listbox '" + id + "'");
testSelectableSelection(select, [ ], "selectAllSelection: ");
testSelectableSelection(select, [ ], "selectAll: ");
// doesn't change selection
select.clearSelection();
testSelectableSelection(select, [ ], "clearSelection: ");
select.unselectAll();
testSelectableSelection(select, [ ], "unselectAll: ");
//////////////////////////////////////////////////////////////////////////
// select@size="4" with optgroups, single selectable
@ -117,17 +117,17 @@
select = getAccessible(id, [nsIAccessibleSelectable]);
testSelectableSelection(select, [ ]);
select.addChildToSelection(1);
select.addItemToSelection(1);
testSelectableSelection(select, [ "lb2_item2" ]);
select.removeChildFromSelection(1);
select.removeItemFromSelection(1);
testSelectableSelection(select, [ ]);
is(select.selectAllSelection(), false,
is(select.selectAll(), false,
"No way to select all items in single selectable listbox " + id + "'");
testSelectableSelection(select, [ ]);
select.clearSelection();
select.unselectAll();
testSelectableSelection(select, [ ]);
//////////////////////////////////////////////////////////////////////////
@ -140,19 +140,19 @@
select = getAccessible(id, [nsIAccessibleSelectable]);
testSelectableSelection(select, [ ]);
select.addChildToSelection(0);
testSelectableSelection(select, [ "lb3_item1" ], "addChildToSelection: ");
select.addItemToSelection(0);
testSelectableSelection(select, [ "lb3_item1" ], "addItemToSelection: ");
select.removeChildFromSelection(0);
testSelectableSelection(select, [ ], "removeChildFromSelection: ");
select.removeItemFromSelection(0);
testSelectableSelection(select, [ ], "removeItemFromSelection: ");
is(select.selectAllSelection(), true,
is(select.selectAll(), true,
"All items in listbox '" + id + "' should be selected");
testSelectableSelection(select, [ "lb3_item1", "lb3_item2"],
"selectAllSelection: ");
"selectAll: ");
select.clearSelection();
testSelectableSelection(select, [ ], "clearSelection: ");
select.unselectAll();
testSelectableSelection(select, [ ], "unselectAll: ");
//////////////////////////////////////////////////////////////////////////
// select@size="4" multiselect with optgroups
@ -164,17 +164,17 @@
select = getAccessible(id, [nsIAccessibleSelectable]);
testSelectableSelection(select, [ ]);
select.addChildToSelection(0);
select.addItemToSelection(0);
testSelectableSelection(select, [ "lb4_item1" ]);
select.removeChildFromSelection(0);
select.removeItemFromSelection(0);
testSelectableSelection(select, [ ]);
is(select.selectAllSelection(), true,
is(select.selectAll(), true,
"All items in listbox '" + id + "' should be selected");
testSelectableSelection(select, [ "lb4_item1", "lb4_item2"]);
select.clearSelection();
select.unselectAll();
testSelectableSelection(select, [ ]);
SimpleTest.finish();

View File

@ -53,13 +53,13 @@
if (seltype != "single" && seltype != "cell" && seltype != "text")
isTreeMultiSelectable = true;
// selectAllSelection
// selectAll
var accSelectable = getAccessible(this.DOMNode,
[nsIAccessibleSelectable]);
ok(accSelectable, "tree is not selectable!");
if (accSelectable) {
is(accSelectable.selectAllSelection(), isTreeMultiSelectable,
"SelectAllSelection is not correct for seltype: " + seltype);
is(accSelectable.selectAll(), isTreeMultiSelectable,
"SelectAll is not correct for seltype: " + seltype);
}
var selectedChildren = [];
@ -72,29 +72,29 @@
}
}
testSelectableSelection(accSelectable, selectedChildren,
"selectAllSelection test. ");
"selectAll test. ");
// clearSelection
accSelectable.clearSelection();
testSelectableSelection(accSelectable, [], "clearSelection test. ");
// unselectAll
accSelectable.unselectAll();
testSelectableSelection(accSelectable, [], "unselectAll test. ");
// addChildToSelection
accSelectable.addChildToSelection(1);
accSelectable.addChildToSelection(3);
// addItemToSelection
accSelectable.addItemToSelection(1);
accSelectable.addItemToSelection(3);
selectedChildren = isTreeMultiSelectable ?
[ accSelectable.getChildAt(2), accSelectable.getChildAt(4) ] :
[ accSelectable.getChildAt(2) ];
testSelectableSelection(accSelectable, selectedChildren,
"addChildToSelection test. ");
"addItemToSelection test. ");
// removeChildFromSelection
accSelectable.removeChildFromSelection(1);
// removeItemFromSelection
accSelectable.removeItemFromSelection(1);
selectedChildren = isTreeMultiSelectable ?
[ accSelectable.getChildAt(4) ] : [ ];
testSelectableSelection(accSelectable, selectedChildren,
"removeChildFromSelection test. ");
"removeItemFromSelection test. ");
}
this.getID = function getID()

View File

@ -29,93 +29,144 @@
function traverseTextByLines(aQueue, aID, aLines)
{
var baseInvoker = new synthFocus(aID);
var baseInvokerID = "move to last line end";
var wholeText = "";
for (var i = 0; i < aLines.length ; i++)
wholeText += aLines[i][0] + aLines[i][1];
for (var i = aLines.length - 1; i >= 0 ; i--) {
var cLine = new line(wholeText, aLines, i);
var pLine = cLine.prevLine;
var ppLine = pLine.prevLine;
var nLine = cLine.nextLine;
var nnLine = nLine.nextLine;
// Shared line tests.
var lineTests = [
[ testTextBeforeOffset, BOUNDARY_LINE_START, pLine.start, cLine.start],
[ testTextBeforeOffset, BOUNDARY_LINE_END, ppLine.end, pLine.end],
[ testTextAtOffset, BOUNDARY_LINE_START, cLine.start, nLine.start],
[ testTextAtOffset, BOUNDARY_LINE_END, pLine.end, cLine.end],
[ testTextAfterOffset, BOUNDARY_LINE_START, nLine.start, nnLine.start],
[ testTextAfterOffset, BOUNDARY_LINE_END, cLine.end, nLine.end]
];
// Word tests for "caret at the end of the line".
var lastWord = cLine.lastWord;
var pLastWord = lastWord.prevWord;
var ppLastWord = pLastWord.prevWord;
var nLastWord = lastWord.nextWord;
var nnLastWord = nLastWord.nextWord;
var isAtEnd = (cLine.end == wholeText.length);
var isAtWordEnd = (cLine.end = lastWord.end);
var lineEndWordTests = [
[ testTextBeforeOffset, BOUNDARY_WORD_START, pLastWord.start, lastWord.start ],
[ testTextBeforeOffset, BOUNDARY_WORD_END, ppLastWord.end, pLastWord.end ],
[ testTextAtOffset, BOUNDARY_WORD_START, lastWord.start, nLastWord.start ],
[ testTextAtOffset, BOUNDARY_WORD_END,
(isAtEnd ? pLastWord : lastWord).end,
(isAtEnd ? lastWord : nLastWord).end ],
[ testTextAfterOffset, BOUNDARY_WORD_START, nLastWord.start, nnLastWord.start ],
[ testTextAfterOffset, BOUNDARY_WORD_END,
(isAtWordEnd ? lastWord : nLastWord).end,
(isAtWordEnd ? nLastWord : nnLastWord).end ]
];
// Add "caret at the end of the line" tests.
aQueue.push(new tmpl_moveTo(aID, baseInvoker, baseInvokerID, wholeText,
lineTests.concat(lineEndWordTests),
cLine.lineEndFailures));
// Word tests for "caret at the end of the line".
var firstWord = cLine.firstWord;
var pFirstWord = firstWord.prevWord;
var ppFirstWord = pFirstWord.prevWord;
var nFirstWord = firstWord.nextWord;
var nnFirstWord = nFirstWord.nextWord;
var isAtWordBegin = (cLine.start == firstWord.start);
var lineStartWordTests = [
[ testTextBeforeOffset, BOUNDARY_WORD_START,
(isAtWordBegin ? pFirstWord : ppFirstWord).start,
(isAtWordBegin ? firstWord : pFirstWord).start ],
[ testTextBeforeOffset, BOUNDARY_WORD_END, ppFirstWord.end, pFirstWord.end ],
[ testTextAtOffset, BOUNDARY_WORD_START,
(isAtWordBegin ? firstWord : pFirstWord).start,
(isAtWordBegin ? nFirstWord : firstWord).start ],
[ testTextAtOffset, BOUNDARY_WORD_END, pFirstWord.end, firstWord.end ],
[ testTextAfterOffset, BOUNDARY_WORD_START,
(isAtWordBegin ? nFirstWord : firstWord).start,
(isAtWordBegin ? nnFirstWord : nFirstWord).start ],
[ testTextAfterOffset, BOUNDARY_WORD_END, firstWord.end, nFirstWord.end ],
];
baseInvoker = new moveToLineStart(aID, cLine.start);
baseInvokerID = "move to " + i + "th line start";
// Add "caret at the start of the line" tests.
aQueue.push(new tmpl_moveTo(aID, baseInvoker, baseInvokerID, wholeText,
lineTests.concat(lineStartWordTests),
cLine.lineStartFailures));
// Next loop invoker to move caret at the end of prev line.
baseInvoker = new moveToPrevLineEnd(aID, pLine.end);
baseInvokerID = "move to " + (i - 1) + "th line end";
var baseInvokerFunc = synthClick;
var charIter = new charIterator(wholeText, aLines);
//charIter.debugOffset = 0;
while (charIter.next()) {
aQueue.push(new tmpl_moveTo(aID, baseInvokerFunc, wholeText, charIter));
baseInvokerFunc = synthRightKey;
}
}
/**
* Used to get test list for each traversed character.
*/
function charIterator(aWholeText, aLines)
{
this.next = function charIterator_next()
{
// Don't increment offset if we are at end of the wrapped line
// (offset is shared between end of this line and start of next line).
if (this.mAtWrappedLineEnd) {
this.mAtWrappedLineEnd = false;
this.mLine = this.mLine.nextLine;
return true;
}
this.mOffset++;
if (this.mOffset > aWholeText.length)
return false;
var nextLine = this.mLine.nextLine;
if (!nextLine.isFakeLine() && this.mOffset == nextLine.start) {
if (nextLine.start == this.mLine.end)
this.mAtWrappedLineEnd = true;
else
this.mLine = nextLine;
}
return true;
}
Object.defineProperty(this, "offset", { get: function()
{ return this.mOffset; }
});
Object.defineProperty(this, "offsetDescr", { get: function()
{
return this.mOffset + " offset (" + this.mLine.number + " line, " +
(this.mOffset - this.mLine.start) + " offset on the line)";
}
});
Object.defineProperty(this, "tests", { get: function()
{
// Line boundary tests.
var cLine = this.mLine;
var pLine = cLine.prevLine;
var ppLine = pLine.prevLine;
var nLine = cLine.nextLine;
var nnLine = nLine.nextLine;
var lineTests = [
[ testTextBeforeOffset, BOUNDARY_LINE_START, pLine.start, cLine.start],
[ testTextBeforeOffset, BOUNDARY_LINE_END, ppLine.end, pLine.end],
[ testTextAtOffset, BOUNDARY_LINE_START, cLine.start, nLine.start],
[ testTextAtOffset, BOUNDARY_LINE_END, pLine.end, cLine.end],
[ testTextAfterOffset, BOUNDARY_LINE_START, nLine.start, nnLine.start],
[ testTextAfterOffset, BOUNDARY_LINE_END, cLine.end, nLine.end]
];
// Word boundary tests.
var cWord = this.mLine.firstWord;
var nWord = cWord.nextWord, pWord = cWord.prevWord;
// The current word is a farthest word starting at or after the offset.
if (this.mOffset >= nWord.start) {
while (this.mOffset >= nWord.start && !this.mLine.isLastWord(cWord)) {
cWord = nWord;
nWord = nWord.nextWord;
}
pWord = cWord.prevWord;
} else if (this.mOffset < cWord.start) {
while (this.mOffset < cWord.start) {
cWord = pWord;
pWord = pWord.prevWord;
}
nWord = cWord.nextWord;
}
var nnWord = nWord.nextWord, ppWord = pWord.prevWord;
var isAfterWordEnd =
this.mOffset > cWord.end || cWord.line != this.mLine;
var isAtOrAfterWordEnd = (this.mOffset >= cWord.end);
var useNextWordForAtWordEnd =
isAtOrAfterWordEnd && this.mOffset != aWholeText.length;
var wordTests = [
[ testTextBeforeOffset, BOUNDARY_WORD_START,
pWord.start, cWord.start ],
[ testTextBeforeOffset, BOUNDARY_WORD_END,
(isAfterWordEnd ? pWord : ppWord).end,
(isAfterWordEnd ? cWord : pWord).end ],
[ testTextAtOffset, BOUNDARY_WORD_START,
cWord.start, nWord.start ],
[ testTextAtOffset, BOUNDARY_WORD_END,
(useNextWordForAtWordEnd ? cWord : pWord).end,
(useNextWordForAtWordEnd ? nWord : cWord).end ],
[ testTextAfterOffset, BOUNDARY_WORD_START,
nWord.start, nnWord.start ],
[ testTextAfterOffset, BOUNDARY_WORD_END,
(isAfterWordEnd ? nWord : cWord).end,
(isAfterWordEnd ? nnWord : nWord).end ]
];
return lineTests.concat(wordTests);
}
});
Object.defineProperty(this, "failures", { get: function()
{
if (this.mOffset == this.mLine.start)
return this.mLine.lineStartFailures;
if (this.mOffset == this.mLine.end)
return this.mLine.lineEndFailures;
return [];
}
});
this.mOffset = -1;
this.mLine = new line(aWholeText, aLines, 0);
this.mAtWrappedLineEnd = false;
this.mWord = this.mLine.firstWord;
}
/**
* A line object. Allows to navigate by lines and by words.
*/
@ -155,6 +206,17 @@
}
});
Object.defineProperty(this, "number", { get: function()
{ return aIndex; }
});
Object.defineProperty(this, "wholeText", { get: function()
{ return aWholeText; }
});
this.isFakeLine = function line_isFakeLine()
{
return aIndex < 0 || aIndex >= aLines.length;
}
Object.defineProperty(this, "lastWord", { get: function()
{
if (aIndex < 0)
@ -178,6 +240,12 @@
}
});
this.isLastWord = function line_isLastWord(aWord)
{
var lastWord = this.lastWord;
return lastWord.start == aWord.start && lastWord.end == aWord.end;
}
Object.defineProperty(this, "lineStartFailures", { get: function()
{
if (aIndex < 0 || aIndex >= aLines.length)
@ -212,7 +280,6 @@
return prevLineLastWord;
}
});
Object.defineProperty(this, "nextWord", { get: function()
{
if (aIndex + 2 < aWords.length)
@ -225,6 +292,8 @@
}
});
Object.defineProperty(this, "line", { get: function() { return aLine; } });
Object.defineProperty(this, "start", { get: function()
{
if (this.isFakeStartWord())
@ -244,6 +313,13 @@
}
});
this.toString = function word_toString()
{
var start = this.start, end = this.end;
return "'" + aLine.wholeText.substring(start, end) +
"' at [" + start + ", " + end + "]";
}
this.isFakeStartWord = function() { return aIndex < 0; }
this.isFakeEndWord = function() { return aIndex >= aWords.length; }
}
@ -251,23 +327,28 @@
/**
* A template invoker to move through the text.
*/
function tmpl_moveTo(aID, aInvoker, aInvokerID, aWholeText, aTests,
aFailures)
function tmpl_moveTo(aID, aInvokerFunc, aWholeText, aCharIter)
{
this.__proto__ = aInvoker;
this.offset = aCharIter.offset;
var checker = new caretMoveChecker(this.offset, aID);
this.__proto__ = new (aInvokerFunc)(aID, checker);
this.finalCheck = function genericMoveTo_finalCheck()
{
for (var i = 0; i < aTests.length; i++) {
var func = aTests[i][0];
var boundary = aTests[i][1];
var startOffset = aTests[i][2];
var endOffset = aTests[i][3];
if (this.noTests())
return;
for (var i = 0; i < this.tests.length; i++) {
var func = this.tests[i][0];
var boundary = this.tests[i][1];
var startOffset = this.tests[i][2];
var endOffset = this.tests[i][3];
var text = aWholeText.substring(startOffset, endOffset);
var isOk1 = kOk, isOk2 = kOk, isOk3 = kOk;
for (var fIdx = 0; fIdx < aFailures.length; fIdx++) {
var failure = aFailures[fIdx];
for (var fIdx = 0; fIdx < this.failures.length; fIdx++) {
var failure = this.failures[fIdx];
if (func.name.indexOf(failure[0]) != -1 && boundary == failure[1]) {
isOk1 = failure[2];
isOk2 = failure[3];
@ -282,8 +363,18 @@
this.getID = function genericMoveTo_getID()
{
return aInvokerID;
return "move to " + this.offsetDescr;
}
this.noTests = function tmpl_moveTo_noTests()
{
return ("debugOffset" in aCharIter) &&
(aCharIter.debugOffset != this.offset);
}
this.offsetDescr = aCharIter.offsetDescr;
this.tests = this.noTests() ? null : aCharIter.tests;
this.failures = aCharIter.failures;
}
var gQueue = null;

View File

@ -430,7 +430,7 @@ pref("services.push.requestTimeout", 10000);
pref("services.push.udp.wakeupEnabled", true);
// NetworkStats
#ifdef MOZ_B2G_RIL
#ifdef MOZ_WIDGET_GONK
pref("dom.mozNetworkStats.enabled", true);
pref("dom.webapps.firstRunWithSIM", true);
#endif
@ -825,22 +825,9 @@ pref("gfx.canvas.skiagl.dynamic-cache", true);
// enable fence with readpixels for SurfaceStream
pref("gfx.gralloc.fence-with-readpixels", true);
// Enable Telephony API
pref("dom.telephony.enabled", true);
// Cell Broadcast API
pref("dom.cellbroadcast.enabled", true);
pref("ril.cellbroadcast.disabled", false);
// ICC API
pref("dom.icc.enabled", true);
// Mobile Connection API
pref("dom.mobileconnection.enabled", true);
// Voice Mail API
pref("dom.voicemail.enabled", true);
// The url of the page used to display network error details.
pref("b2g.neterror.url", "app://system.gaiamobile.org/net_error.html");

View File

@ -636,7 +636,7 @@ let FormAssistant = {
},
showKeyboard: function fa_showKeyboard(target) {
if (this.isKeyboardOpened)
if (this.focusedElement === target)
return;
if (target instanceof HTMLOptionElement)

View File

@ -138,6 +138,11 @@ SettingsListener.observe('language.current', 'en-US', function(value) {
Services.prefs.setBoolPref('dom.mms.requestStatusReport', value);
});
SettingsListener.observe('ril.mms.requestReadReport.enabled', true,
function(value) {
Services.prefs.setBoolPref('dom.mms.requestReadReport', value);
});
SettingsListener.observe('ril.cellbroadcast.disabled', false,
function(value) {
Services.prefs.setBoolPref('ril.cellbroadcast.disabled', value);

View File

@ -18,7 +18,7 @@ Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
Cu.import('resource://gre/modules/Keyboard.jsm');
Cu.import('resource://gre/modules/ErrorPage.jsm');
#ifdef MOZ_B2G_RIL
#ifdef MOZ_WIDGET_GONK
Cu.import('resource://gre/modules/NetworkStatsService.jsm');
#endif
@ -322,6 +322,9 @@ var shell = {
ppmm.addMessageListener("mail-handler", this);
ppmm.addMessageListener("app-notification-send", AlertsHelper);
ppmm.addMessageListener("file-picker", this);
ppmm.addMessageListener("getProfD", function(message) {
return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
});
},
stop: function shell_stop() {
@ -611,7 +614,7 @@ var shell = {
this.sendEvent(window, 'ContentStart');
#ifdef MOZ_B2G_RIL
#ifdef MOZ_WIDGET_GONK
Cu.import('resource://gre/modules/OperatorApps.jsm');
#endif
@ -978,7 +981,7 @@ var WebappsHelper = {
});
break;
case "webapps-close":
shell.sendEvent(shell.getContentWindow(), "webapps-close",
shell.sendEvent(getContentWindow(), "webapps-close",
{
__exposedProps__: { "manifestURL": "r" },
"manifestURL": json.manifestURL

View File

@ -5,7 +5,7 @@
"use strict"
function debug(str) {
//dump("-*- ContentPermissionPrompt: " + str + "\n");
//dump("-*- ContentPermissionPrompt: " + s + "\n");
}
const Ci = Components.interfaces;
@ -13,14 +13,11 @@ const Cr = Components.results;
const Cu = Components.utils;
const Cc = Components.classes;
const PROMPT_FOR_UNKNOWN = ["audio-capture",
"desktop-notification",
"geolocation",
"video-capture"];
const PROMPT_FOR_UNKNOWN = ["geolocation", "desktop-notification",
"audio-capture"];
// Due to privary issue, permission requests like GetUserMedia should prompt
// every time instead of providing session persistence.
const PERMISSION_NO_SESSION = ["audio-capture", "video-capture"];
const ALLOW_MULTIPLE_REQUESTS = ["audio-capture", "video-capture"];
const PERMISSION_NO_SESSION = ["audio-capture"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -44,21 +41,7 @@ XPCOMUtils.defineLazyServiceGetter(this,
"@mozilla.org/telephony/audiomanager;1",
"nsIAudioManager");
/**
* aTypesInfo is an array of {permission, access, action, deny} which keeps
* the information of each permission. This arrary is initialized in
* ContentPermissionPrompt.prompt and used among functions.
*
* aTypesInfo[].permission : permission name
* aTypesInfo[].access : permission name + request.access
* aTypesInfo[].action : the default action of this permission
* aTypesInfo[].deny : true if security manager denied this app's origin
* principal.
* Note:
* aTypesInfo[].permission will be sent to prompt only when
* aTypesInfo[].action is PROMPT_ACTION and aTypesInfo[].deny is false.
*/
function rememberPermission(aTypesInfo, aPrincipal, aSession)
function rememberPermission(aPermission, aPrincipal, aSession)
{
function convertPermToAllow(aPerm, aPrincipal)
{
@ -66,13 +49,12 @@ function rememberPermission(aTypesInfo, aPrincipal, aSession)
permissionManager.testExactPermissionFromPrincipal(aPrincipal, aPerm);
if (type == Ci.nsIPermissionManager.PROMPT_ACTION ||
(type == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
PROMPT_FOR_UNKNOWN.indexOf(aPerm) >= 0)) {
debug("add " + aPerm + " to permission manager with ALLOW_ACTION");
PROMPT_FOR_UNKNOWN.indexOf(aPermission) >= 0)) {
if (!aSession) {
permissionManager.addFromPrincipal(aPrincipal,
aPerm,
Ci.nsIPermissionManager.ALLOW_ACTION);
} else if (PERMISSION_NO_SESSION.indexOf(aPerm) < 0) {
} else if (PERMISSION_NO_SESSION.indexOf(aPermission) < 0) {
permissionManager.addFromPrincipal(aPrincipal,
aPerm,
Ci.nsIPermissionManager.ALLOW_ACTION,
@ -81,18 +63,14 @@ function rememberPermission(aTypesInfo, aPrincipal, aSession)
}
}
for (let i in aTypesInfo) {
// Expand the permission to see if we have multiple access properties
// to convert
let perm = aTypesInfo[i].permission;
let access = PermissionsTable[perm].access;
if (access) {
for (let idx in access) {
convertPermToAllow(perm + "-" + access[idx], aPrincipal);
}
} else {
convertPermToAllow(perm, aPrincipal);
// Expand the permission to see if we have multiple access properties to convert
let access = PermissionsTable[aPermission].access;
if (access) {
for (let idx in access) {
convertPermToAllow(aPermission + "-" + access[idx], aPrincipal);
}
} else {
convertPermToAllow(aPermission, aPrincipal);
}
}
@ -100,63 +78,23 @@ function ContentPermissionPrompt() {}
ContentPermissionPrompt.prototype = {
handleExistingPermission: function handleExistingPermission(request,
typesInfo) {
typesInfo.forEach(function(type) {
type.action =
Services.perms.testExactPermissionFromPrincipal(request.principal,
type.access);
});
// If all permissions are allowed already, call allow() without prompting.
let checkAllowPermission = function(type) {
if (type.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
return true;
}
return false;
}
if (typesInfo.every(checkAllowPermission)) {
debug("all permission requests are allowed");
handleExistingPermission: function handleExistingPermission(request) {
let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access :
request.type;
let result = Services.perms.testExactPermissionFromPrincipal(request.principal, access);
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
request.allow();
return true;
}
// If all permissions are DENY_ACTION or UNKNOWN_ACTION, call cancel()
// without prompting.
let checkDenyPermission = function(type) {
if (type.action == Ci.nsIPermissionManager.DENY_ACTION ||
type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
PROMPT_FOR_UNKNOWN.indexOf(type.access) < 0) {
return true;
}
return false;
}
if (typesInfo.every(checkDenyPermission)) {
debug("all permission requests are denied");
if (result == Ci.nsIPermissionManager.DENY_ACTION ||
result == Ci.nsIPermissionManager.UNKNOWN_ACTION && PROMPT_FOR_UNKNOWN.indexOf(access) < 0) {
request.cancel();
return true;
}
return false;
},
// multiple requests should be audio and video
checkMultipleRequest: function checkMultipleRequest(typesInfo) {
if (typesInfo.length == 1) {
return true;
} else if (typesInfo.length > 1) {
let checkIfAllowMultiRequest = function(type) {
return (ALLOW_MULTIPLE_REQUESTS.indexOf(type.access) !== -1);
}
if (typesInfo.every(checkIfAllowMultiRequest)) {
debug("legal multiple requests");
return true;
}
}
return false;
},
handledByApp: function handledByApp(request, typesInfo) {
handledByApp: function handledByApp(request) {
if (request.principal.appId == Ci.nsIScriptSecurityManager.NO_APP_ID ||
request.principal.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
// This should not really happen
@ -168,94 +106,49 @@ ContentPermissionPrompt.prototype = {
.getService(Ci.nsIAppsService);
let app = appsService.getAppByLocalId(request.principal.appId);
// Check each permission if it's denied by permission manager with app's
// URL.
let checkIfDenyAppPrincipal = function(type) {
let url = Services.io.newURI(app.origin, null, null);
let principal = secMan.getAppCodebasePrincipal(url,
request.principal.appId,
/*mozbrowser*/false);
let result = Services.perms.testExactPermissionFromPrincipal(principal,
type.access);
let url = Services.io.newURI(app.origin, null, null);
let principal = secMan.getAppCodebasePrincipal(url, request.principal.appId,
/*mozbrowser*/false);
let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access :
request.type;
let result = Services.perms.testExactPermissionFromPrincipal(principal, access);
if (result == Ci.nsIPermissionManager.ALLOW_ACTION ||
result == Ci.nsIPermissionManager.PROMPT_ACTION) {
type.deny = false;
}
return type.deny;
}
if (typesInfo.every(checkIfDenyAppPrincipal)) {
request.cancel();
return true;
if (result == Ci.nsIPermissionManager.ALLOW_ACTION ||
result == Ci.nsIPermissionManager.PROMPT_ACTION) {
return false;
}
return false;
request.cancel();
return true;
},
handledByPermissionType: function handledByPermissionType(request, typesInfo) {
for (let i in typesInfo) {
if (permissionSpecificChecker.hasOwnProperty(typesInfo[i].permission) &&
permissionSpecificChecker[typesInfo[i].permission](request)) {
return true;
}
}
return false;
handledByPermissionType: function handledByPermissionType(request) {
return permissionSpecificChecker.hasOwnProperty(request.type)
? permissionSpecificChecker[request.type](request)
: false;
},
_id: 0,
prompt: function(request) {
if (secMan.isSystemPrincipal(request.principal)) {
request.allow();
return;
return true;
}
// Initialize the typesInfo and set the default value.
let typesInfo = [];
let perms = request.types.QueryInterface(Ci.nsIArray);
for (let idx = 0; idx < perms.length; idx++) {
let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
let tmp = {
permission: perm.type,
access: (perm.access && perm.access !== "unused") ?
perm.type + "-" + perm.access : perm.type,
deny: true,
action: Ci.nsIPermissionManager.UNKNOWN_ACTION
};
typesInfo.push(tmp);
}
if (typesInfo.length == 0) {
request.cancel();
return;
}
if(!this.checkMultipleRequest(typesInfo)) {
request.cancel();
return;
}
if (this.handledByApp(request, typesInfo) ||
this.handledByPermissionType(request, typesInfo)) {
if (this.handledByApp(request) ||
this.handledByPermissionType(request)) {
return;
}
// returns true if the request was handled
if (this.handleExistingPermission(request, typesInfo)) {
if (this.handleExistingPermission(request))
return;
}
// prompt PROMPT_ACTION request only.
typesInfo.forEach(function(aType, aIndex) {
if (aType.action != Ci.nsIPermissionManager.PROMPT_ACTION || aType.deny) {
typesInfo.splice(aIndex);
}
});
let frame = request.element;
let requestId = this._id++;
if (!frame) {
this.delegatePrompt(request, requestId, typesInfo);
this.delegatePrompt(request, requestId);
return;
}
@ -270,7 +163,7 @@ ContentPermissionPrompt.prototype = {
if (evt.detail.visible === true)
return;
self.cancelPrompt(request, requestId, typesInfo);
self.cancelPrompt(request, requestId);
cancelRequest();
}
@ -287,7 +180,7 @@ ContentPermissionPrompt.prototype = {
// away but the request is still here.
frame.addEventListener("mozbrowservisibilitychange", onVisibilityChange);
self.delegatePrompt(request, requestId, typesInfo, function onCallback() {
self.delegatePrompt(request, requestId, function onCallback() {
frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange);
});
};
@ -298,17 +191,22 @@ ContentPermissionPrompt.prototype = {
}
},
cancelPrompt: function(request, requestId, typesInfo) {
this.sendToBrowserWindow("cancel-permission-prompt", request, requestId,
typesInfo);
cancelPrompt: function(request, requestId) {
this.sendToBrowserWindow("cancel-permission-prompt", request, requestId);
},
delegatePrompt: function(request, requestId, typesInfo, callback) {
delegatePrompt: function(request, requestId, callback) {
let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access :
request.type;
let principal = request.principal;
this.sendToBrowserWindow("permission-prompt", request, requestId, typesInfo,
function(type, remember) {
this._permission = access;
this._uri = principal.URI.spec;
this._origin = principal.origin;
this.sendToBrowserWindow("permission-prompt", request, requestId, function(type, remember) {
if (type == "permission-allow") {
rememberPermission(typesInfo, request.principal, !remember);
rememberPermission(request.type, principal, !remember);
if (callback) {
callback();
}
@ -316,20 +214,14 @@ ContentPermissionPrompt.prototype = {
return;
}
let addDenyPermission = function(type) {
debug("add " + type.permission +
" to permission manager with DENY_ACTION");
if (remember) {
Services.perms.addFromPrincipal(request.principal, type.access,
Ci.nsIPermissionManager.DENY_ACTION);
} else {
Services.perms.addFromPrincipal(request.principal, type.access,
Ci.nsIPermissionManager.DENY_ACTION,
Ci.nsIPermissionManager.EXPIRE_SESSION,
0);
}
if (remember) {
Services.perms.addFromPrincipal(principal, access,
Ci.nsIPermissionManager.DENY_ACTION);
} else {
Services.perms.addFromPrincipal(principal, access,
Ci.nsIPermissionManager.DENY_ACTION,
Ci.nsIPermissionManager.EXPIRE_SESSION, 0);
}
typesInfo.forEach(addDenyPermission);
if (callback) {
callback();
@ -338,7 +230,7 @@ ContentPermissionPrompt.prototype = {
});
},
sendToBrowserWindow: function(type, request, requestId, typesInfo, callback) {
sendToBrowserWindow: function(type, request, requestId, callback) {
let browser = Services.wm.getMostRecentWindow("navigator:browser");
let content = browser.getContentWindow();
if (!content)
@ -361,15 +253,10 @@ ContentPermissionPrompt.prototype = {
principal.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED)
? true
: request.remember;
let permissions = [];
for (let i in typesInfo) {
debug("prompt " + typesInfo[i].permission);
permissions.push(typesInfo[i].permission);
}
let details = {
type: type,
permissions: permissions,
permission: request.type,
id: requestId,
origin: principal.origin,
isApp: isApp,
@ -402,5 +289,6 @@ ContentPermissionPrompt.prototype = {
};
})();
//module initialization
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentPermissionPrompt]);

View File

@ -28,6 +28,10 @@ XPCOMUtils.defineLazyServiceGetter(Services, "volumeService",
"@mozilla.org/telephony/volume-service;1",
"nsIVolumeService");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsISyncMessageSender");
XPCOMUtils.defineLazyGetter(this, "gExtStorage", function dp_gExtStorage() {
return Services.env.get("EXTERNAL_STORAGE");
});
@ -50,6 +54,8 @@ DirectoryProvider.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]),
_xpcom_factory: XPCOMUtils.generateSingletonFactory(DirectoryProvider),
_profD: null,
getFile: function dp_getFile(prop, persistent) {
#ifdef MOZ_WIDGET_GONK
let localProps = ["cachePDir", "webappsDir", "PrefD", "indexedDBPDir",
@ -88,6 +94,31 @@ DirectoryProvider.prototype = {
// of the sdcard.
return this.getUpdateDir(persistent, FOTA_DIR);
}
#else
// In desktop builds, coreAppsDir is the same as the profile directory.
// We just need to get the path from the parent, and it is then used to
// build jar:remoteopenfile:// uris.
if (prop == "coreAppsDir") {
let appsDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
appsDir.append("webapps");
persistent.value = true;
return appsDir;
} else if (prop == "ProfD") {
let inParent = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
if (inParent) {
// Just bail out to use the default from toolkit.
return null;
}
if (!this._profD) {
this._profD = cpmm.sendSyncMessage("getProfD", {})[0];
}
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(this._profD);
persistent.value = true;
return file;
}
#endif
return null;
},

View File

@ -353,26 +353,14 @@ UpdatePrompt.prototype = {
this.restartProcess();
return;
}
let osApplyToDir;
try {
this._update.QueryInterface(Ci.nsIWritablePropertyBag);
osApplyToDir = this._update.getProperty("osApplyToDir");
} catch (e) {}
if (!osApplyToDir) {
log("Error: Update has no osApplyToDir");
return;
Services.aus.applyOsUpdate(this._update);
}
let updateFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
updateFile.initWithPath(osApplyToDir + "/update.zip");
if (!updateFile.exists()) {
log("Error: FOTA update not found at " + updateFile.path);
return;
catch (e) {
this._update.errorCode = Cr.NS_ERROR_FAILURE;
this.showUpdateError(this._update);
}
this.finishOSUpdate(updateFile.path);
},
restartProcess: function UP_restartProcess() {
@ -390,25 +378,6 @@ UpdatePrompt.prototype = {
#endif
},
finishOSUpdate: function UP_finishOSUpdate(aOsUpdatePath) {
log("Rebooting into recovery to apply FOTA update: " + aOsUpdatePath);
try {
let recoveryService = Cc["@mozilla.org/recovery-service;1"]
.getService(Ci.nsIRecoveryService);
recoveryService.installFotaUpdate(aOsUpdatePath);
} catch(e) {
log("Error: Couldn't reboot into recovery to apply FOTA update " +
aOsUpdatePath);
aUpdate = Services.um.activeUpdate;
if (aUpdate) {
aUpdate.errorCode = Cr.NS_ERROR_FAILURE;
aUpdate.statusText = "fota-reboot-failed";
this.showUpdateError(aUpdate);
}
}
},
forceUpdateCheck: function UP_forceUpdateCheck() {
log("Forcing update check");

View File

@ -1,4 +1,4 @@
{
"revision": "d2dbad943faf566fe36dbe79086127da837af6a3",
"revision": "e9d3946c6e4c26c60f67b8efac40e14785b634d3",
"repo_path": "/integration/gaia-central"
}

View File

@ -7,6 +7,7 @@
"build_targets": [],
"upload_files": [
"{objdir}/dist/b2g-update/*.mar",
"{objdir}/dist/b2g-*.tar.gz",
"{objdir}/dist/b2g-*.crashreporter-symbols.zip",
"{workdir}/sources.xml"
],

View File

@ -10,7 +10,6 @@ DEFINES += \
-DGAIA_PATH=L\"$(subst /,\\\\,$(GAIA_PATH))\" \
$(NULL)
else # Non-windows machines use the same wrapper program
CSRCS = run-b2g.c
DEFINES += \
-DB2G_NAME=\"$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)\" \
-DGAIA_PATH=\"$(GAIA_PATH)\" \

View File

@ -10,3 +10,7 @@ if CONFIG['OS_ARCH'] == 'WINNT':
SOURCES += [
'run-b2g.cpp',
]
else:
SOURCES += [
'run-b2g.c',
]

View File

@ -164,10 +164,12 @@
@BINPATH@/components/dom_audiochannel.xpt
@BINPATH@/components/dom_base.xpt
@BINPATH@/components/dom_system.xpt
#ifdef MOZ_B2G_RIL
@BINPATH@/components/dom_voicemail.xpt
#ifdef MOZ_WIDGET_GONK
@BINPATH@/components/dom_wifi.xpt
@BINPATH@/components/dom_system_gonk.xpt
#endif
#ifdef MOZ_B2G_RIL
@BINPATH@/components/dom_voicemail.xpt
@BINPATH@/components/dom_icc.xpt
@BINPATH@/components/dom_cellbroadcast.xpt
@BINPATH@/components/dom_wappush.xpt
@ -399,6 +401,36 @@
@BINPATH@/components/nsDownloadManagerUI.js
@BINPATH@/components/nsSidebar.manifest
@BINPATH@/components/nsSidebar.js
; WiFi, NetworkManager, NetworkStats
#ifdef MOZ_WIDGET_GONK
@BINPATH@/components/DOMWifiManager.js
@BINPATH@/components/DOMWifiManager.manifest
@BINPATH@/components/NetworkInterfaceListService.js
@BINPATH@/components/NetworkInterfaceListService.manifest
@BINPATH@/components/NetworkManager.js
@BINPATH@/components/NetworkManager.manifest
@BINPATH@/components/NetworkStatsManager.js
@BINPATH@/components/NetworkStatsManager.manifest
@BINPATH@/components/NetworkStatsServiceProxy.js
@BINPATH@/components/NetworkStatsServiceProxy.manifest
@BINPATH@/components/WifiWorker.js
@BINPATH@/components/WifiWorker.manifest
#endif // MOZ_WIDGET_GONK
; RIL
#if defined(MOZ_WIDGET_GONK) && defined(MOZ_B2G_RIL)
@BINPATH@/components/MmsService.js
@BINPATH@/components/MmsService.manifest
@BINPATH@/components/MobileMessageDatabaseService.js
@BINPATH@/components/MobileMessageDatabaseService.manifest
@BINPATH@/components/RadioInterfaceLayer.js
@BINPATH@/components/RadioInterfaceLayer.manifest
@BINPATH@/components/RILContentHelper.js
@BINPATH@/components/TelephonyProvider.js
@BINPATH@/components/TelephonyProvider.manifest
#endif // MOZ_WIDGET_GONK && MOZ_B2G_RIL
#ifndef MOZ_WIDGET_GONK
@BINPATH@/components/extensions.manifest
@BINPATH@/components/addonManager.js
@ -464,29 +496,6 @@
@BINPATH@/components/webvtt.xpt
@BINPATH@/components/WebVTT.manifest
@BINPATH@/components/WebVTTParserWrapper.js
#ifdef MOZ_B2G_RIL
@BINPATH@/components/NetworkManager.manifest
@BINPATH@/components/NetworkManager.js
@BINPATH@/components/RadioInterfaceLayer.manifest
@BINPATH@/components/RadioInterfaceLayer.js
@BINPATH@/components/MmsService.manifest
@BINPATH@/components/MmsService.js
@BINPATH@/components/RILContentHelper.js
@BINPATH@/components/MobileMessageDatabaseService.manifest
@BINPATH@/components/MobileMessageDatabaseService.js
@BINPATH@/components/WifiWorker.js
@BINPATH@/components/WifiWorker.manifest
@BINPATH@/components/DOMWifiManager.js
@BINPATH@/components/DOMWifiManager.manifest
@BINPATH@/components/NetworkStatsManager.js
@BINPATH@/components/NetworkStatsManager.manifest
@BINPATH@/components/NetworkInterfaceListService.manifest
@BINPATH@/components/NetworkInterfaceListService.js
@BINPATH@/components/TelephonyProvider.manifest
@BINPATH@/components/TelephonyProvider.js
@BINPATH@/components/NetworkStatsServiceProxy.manifest
@BINPATH@/components/NetworkStatsServiceProxy.js
#endif
#ifdef MOZ_ENABLE_DBUS
@BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
#endif

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1382112975000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1383344509000">
<emItems>
<emItem blockID="i454" id="sqlmoz@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -259,6 +259,10 @@
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i465" id="trtv3@trtv.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i4" id="{4B3803EA-5230-4DC3-A7FC-33638F3D3542}">
<versionRange minVersion="1.2" maxVersion="1.2">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">

View File

@ -351,18 +351,8 @@ pref("browser.download.debug", false);
pref("browser.download.saveLinkAsFilenameTimeout", 4000);
pref("browser.download.useDownloadDir", true);
pref("browser.download.folderList", 1);
pref("browser.download.manager.showAlertOnComplete", true);
pref("browser.download.manager.showAlertInterval", 2000);
pref("browser.download.manager.retention", 2);
pref("browser.download.manager.showWhenStarting", true);
pref("browser.download.manager.closeWhenDone", false);
pref("browser.download.manager.focusWhenStarting", false);
pref("browser.download.manager.flashCount", 2);
pref("browser.download.manager.addToRecentDocs", true);
pref("browser.download.manager.quitBehavior", 0);
pref("browser.download.manager.scanWhenDone", true);
pref("browser.download.manager.resumeOnWakeDelay", 10000);
// This allows disabling the animated notifications shown by
@ -372,10 +362,6 @@ pref("browser.download.animateNotifications", true);
// This records whether or not the panel has been shown at least once.
pref("browser.download.panel.shown", false);
// This records whether or not at least one session with the Downloads Panel
// enabled has been completed already.
pref("browser.download.panel.firstSessionCompleted", false);
#ifndef XP_MACOSX
pref("browser.helperApps.deleteTempFileOnExit", true);
#endif
@ -588,7 +574,11 @@ pref("browser.gesture.twist.left", "cmd_gestureRotateLeft");
pref("browser.gesture.twist.end", "cmd_gestureRotateEnd");
pref("browser.gesture.tap", "cmd_fullZoomReset");
#ifndef RELEASE_BUILD
pref("browser.snapshots.limit", 5);
#else
pref("browser.snapshots.limit", 0);
#endif
// 0: Nothing happens
// 1: Scrolling contents
@ -795,7 +785,7 @@ pref("browser.safebrowsing.enabled", true);
pref("browser.safebrowsing.malware.enabled", true);
pref("browser.safebrowsing.debug", false);
pref("browser.safebrowsing.updateURL", "http://safebrowsing.clients.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.updateURL", "http://safebrowsing.clients.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&apikey=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.keyURL", "https://sb-ssl.google.com/safebrowsing/newkey?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.gethashURL", "http://safebrowsing.clients.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
pref("browser.safebrowsing.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/report?");
@ -806,11 +796,8 @@ pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mo
pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
pref("browser.safebrowsing.malware.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
// Since the application reputation query isn't hooked in anywhere yet, this
// preference does not matter. To be extra safe, don't turn this preference on
// for official builds without whitelisting (bug 842828).
#ifndef MOZILLA_OFFICIAL
pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download");
pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download&apikey=%GOOGLE_API_KEY%");
#endif
#ifdef MOZILLA_OFFICIAL
@ -1022,10 +1009,6 @@ pref("services.sync.prefs.sync.addons.ignoreUserEnabledChanges", true);
// source, and this would propagate automatically to other,
// uncompromised Sync-connected devices.
pref("services.sync.prefs.sync.app.update.mode", true);
pref("services.sync.prefs.sync.browser.download.manager.closeWhenDone", true);
pref("services.sync.prefs.sync.browser.download.manager.retention", true);
pref("services.sync.prefs.sync.browser.download.manager.scanWhenDone", true);
pref("services.sync.prefs.sync.browser.download.manager.showWhenStarting", true);
pref("services.sync.prefs.sync.browser.formfill.enable", true);
pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
pref("services.sync.prefs.sync.browser.offline-apps.notify", true);

View File

@ -7,8 +7,6 @@ include $(topsrcdir)/config/config.mk
abs_srcdir = $(abspath $(srcdir))
CHROME_DEPS += $(abs_srcdir)/content/overrides/app-license.html
include $(topsrcdir)/config/rules.mk
PRE_RELEASE_SUFFIX := ""

View File

@ -176,7 +176,8 @@ let gGestureSupport = {
},
/**
* Sets up the history swipe animations for a swipe gesture event, if enabled.
* Sets up swipe gestures. This includes setting up swipe animations for the
* gesture, if enabled.
*
* @param aEvent
* The swipe gesture start event.
@ -189,18 +190,22 @@ let gGestureSupport = {
}
let isVerticalSwipe = false;
if (gHistorySwipeAnimation.active) {
if (aEvent.direction == aEvent.DIRECTION_UP) {
if (content.pageYOffset > 0) {
return false;
}
isVerticalSwipe = true;
} else if (aEvent.direction == aEvent.DIRECTION_DOWN) {
if (content.pageYOffset < content.scrollMaxY) {
return false;
}
isVerticalSwipe = true;
if (aEvent.direction == aEvent.DIRECTION_UP) {
if (content.pageYOffset > 0) {
return false;
}
isVerticalSwipe = true;
} else if (aEvent.direction == aEvent.DIRECTION_DOWN) {
if (content.pageYOffset < content.scrollMaxY) {
return false;
}
isVerticalSwipe = true;
}
if (isVerticalSwipe && !gHistorySwipeAnimation.active) {
// Unlike horizontal swipes (which can navigate history even when
// swipe animations are turned off) vertical swipes should not be tracked
// if animations (bounce effect) aren't enabled.
return false;
}
let canGoBack = gHistorySwipeAnimation.canGoBack();
@ -946,7 +951,8 @@ let gHistorySwipeAnimation = {
let ctx = canvas.getContext("2d");
let zoom = browser.markupDocumentViewer.fullZoom;
ctx.scale(zoom, zoom);
ctx.drawWindow(browser.contentWindow, 0, 0, r.width, r.height, "white",
ctx.drawWindow(browser.contentWindow,
0, 0, r.width / zoom, r.height / zoom, "white",
ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_VIEW |
ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
ctx.DRAWWINDOW_USE_WIDGET_LAYERS);

View File

@ -72,12 +72,15 @@ var gPluginHandler = {
if (aName == "Shockwave Flash")
return "Adobe Flash";
// Clean up the plugin name by stripping off any trailing version numbers
// or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
// Clean up the plugin name by stripping off parenthetical clauses,
// trailing version numbers or "plugin".
// EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar"
// Do this by first stripping the numbers, etc. off the end, and then
// removing "Plugin" (and then trimming to get rid of any whitespace).
// (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
let newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
let newName = aName.replace(/\(.*?\)/g, "").
replace(/[\s\d\.\-\_\(\)]+$/, "").
replace(/\bplug-?in\b/i, "").trim();
return newName;
},
@ -214,6 +217,14 @@ var gPluginHandler = {
handleEvent : function(event) {
let eventType = event.type;
if (eventType == "PluginRemoved") {
let doc = event.target;
let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
this._setPluginNotificationIcon(browser);
return;
}
let plugin = event.target;
let doc = plugin.ownerDocument;
@ -313,6 +324,7 @@ var gPluginHandler = {
plugin.addEventListener("overflow", function(event) {
overlay.style.visibility = "hidden";
gPluginHandler._setPluginNotificationIcon(browser);
});
plugin.addEventListener("underflow", function(event) {
// this is triggered if only one dimension underflows,
@ -320,6 +332,7 @@ var gPluginHandler = {
if (!gPluginHandler.isTooSmall(plugin, overlay)) {
overlay.style.visibility = "visible";
}
gPluginHandler._setPluginNotificationIcon(browser);
});
}
}
@ -664,12 +677,16 @@ var gPluginHandler = {
let histogram =
Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION");
// Update the permission manager.
// Also update the current state of pluginInfo.fallbackType so that
// subsequent opening of the notification shows the current state.
switch (aNewState) {
case "allownow":
permission = Ci.nsIPermissionManager.ALLOW_ACTION;
expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000;
histogram.add(0);
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
break;
case "allowalways":
@ -678,6 +695,7 @@ var gPluginHandler = {
expireTime = Date.now() +
Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000;
histogram.add(1);
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
break;
case "block":
@ -685,11 +703,22 @@ var gPluginHandler = {
expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
expireTime = 0;
histogram.add(2);
switch (aPluginInfo.blocklistState) {
case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE;
break;
case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
break;
default:
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
}
break;
// In case a plugin has already been allowed in another tab, the "continue allowing" button
// shouldn't change any permissions but should run the plugin-enablement code below.
case "continue":
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
break;
default:
Cu.reportError(Error("Unexpected plugin state: " + aNewState));
@ -718,16 +747,18 @@ var gPluginHandler = {
let pluginFound = false;
for (let plugin of plugins) {
plugin.QueryInterface(Ci.nsIObjectLoadingContent);
// canActivatePlugin will return false if this isn't a known plugin type,
// so the pluginHost.getPermissionStringForType call is protected
if (gPluginHandler.canActivatePlugin(plugin) &&
aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
let overlay = this.getPluginUI(plugin, "main");
if (overlay) {
overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
}
plugin.playPlugin();
if (!gPluginHandler.isKnownPlugin(plugin)) {
continue;
}
if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
pluginFound = true;
if (gPluginHandler.canActivatePlugin(plugin)) {
let overlay = this.getPluginUI(plugin, "main");
if (overlay) {
overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
}
plugin.playPlugin();
}
}
}
@ -784,14 +815,6 @@ var gPluginHandler = {
continue;
}
// Assume that plugins are hidden and then set override later
pluginInfo.hidden = true;
let overlay = this.getPluginUI(plugin, "main");
if (overlay && overlay.style.visibility != "hidden" && overlay.style.visibility != "") {
pluginInfo.hidden = false;
}
let permissionObj = Services.perms.
getPermissionObject(principal, pluginInfo.permissionString, false);
if (permissionObj) {
@ -819,26 +842,6 @@ var gPluginHandler = {
centerActions.set(pluginInfo.permissionString, pluginInfo);
}
let pluginBlocked = false;
let pluginHidden = false;
for (let pluginInfo of centerActions.values()) {
let fallbackType = pluginInfo.fallbackType;
if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE ||
fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE ||
fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED) {
pluginBlocked = true;
pluginHidden = false;
break;
}
if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && pluginInfo.hidden) {
pluginHidden = true;
}
}
let iconClasses = document.getElementById("plugins-notification-icon").classList;
iconClasses.toggle("plugin-blocked", pluginBlocked);
iconClasses.toggle("plugin-hidden", pluginHidden);
let primaryPluginPermission = null;
if (aShowNow) {
primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString;
@ -850,6 +853,7 @@ var gPluginHandler = {
if (aShowNow) {
notification.options.primaryPlugin = primaryPluginPermission;
notification.reshow();
setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0);
}
return;
}
@ -863,6 +867,63 @@ var gPluginHandler = {
PopupNotifications.show(aBrowser, "click-to-play-plugins",
"", "plugins-notification-icon",
null, null, options);
setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0);
},
_setPluginNotificationIcon : function PH_setPluginNotificationIcon(aBrowser) {
// Because this is called on a timeout, sanity-check before continuing
if (!aBrowser.docShell || !aBrowser.contentWindow) {
return;
}
let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
if (!notification)
return;
let iconClasses = document.getElementById("plugins-notification-icon").classList;
// Make a copy so we can remove visible plugins
let actions = new Map(notification.options.centerActions);
for (let pluginInfo of actions.values()) {
let fallbackType = pluginInfo.fallbackType;
if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE ||
fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE ||
fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED) {
iconClasses.add("plugin-blocked");
iconClasses.remove("plugin-hidden");
return;
}
}
iconClasses.remove("plugin-blocked");
let contentWindow = aBrowser.contentWindow;
let contentDoc = aBrowser.contentDocument;
let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
for (let plugin of cwu.plugins) {
let fallbackType = plugin.pluginFallbackType;
if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) {
continue;
}
let info = this._getPluginInfo(plugin);
if (!actions.has(info.permissionString)) {
continue;
}
let overlay = this.getPluginUI(plugin, "main");
if (!overlay) {
continue;
}
if (!this.isTooSmall(plugin, overlay)) {
actions.delete(info.permissionString);
if (actions.size == 0) {
break;
}
}
}
iconClasses.toggle("plugin-hidden", actions.size != 0);
},
// Crashed-plugin observer. Notified once per plugin crash, before events

View File

@ -591,6 +591,12 @@ SocialShare = {
return this.panel.lastChild;
},
uninit: function () {
if (this.iframe) {
this.iframe.remove();
}
},
_createFrame: function() {
let panel = this.panel;
if (!SocialUI.enabled || this.iframe)

View File

@ -151,6 +151,15 @@ let TabView = {
"SSWindowStateReady", this._SSWindowStateReadyListener, false);
this._initialized = false;
if (this._window) {
this._window = null;
}
if (this._iframe) {
this._iframe.remove();
this._iframe = null;
}
},
// ----------
@ -253,10 +262,9 @@ let TabView = {
// ----------
hide: function TabView_hide() {
if (!this.isVisible())
return;
this._window.UI.exit();
if (this.isVisible() && this._window) {
this._window.UI.exit();
}
},
// ----------

View File

@ -756,6 +756,7 @@ var gBrowserInit = {
gBrowser.addEventListener("PluginCrashed", gPluginHandler, true);
gBrowser.addEventListener("PluginOutdated", gPluginHandler, true);
gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true);
gBrowser.addEventListener("PluginRemoved", gPluginHandler, true);
gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);
@ -764,7 +765,6 @@ var gBrowserInit = {
window.addEventListener("AppCommand", HandleAppCommandEvent, true);
messageManager.loadFrameScript("chrome://browser/content/content.js", true);
messageManager.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
// initialize observers and listeners
// and give C++ access to gBrowser
@ -1084,24 +1084,13 @@ var gBrowserInit = {
// auto-resume downloads begin (such as after crashing or quitting with
// active downloads) and speeds up the first-load of the download manager UI.
// If the user manually opens the download manager before the timeout, the
// downloads will start right away, and getting the service again won't hurt.
// downloads will start right away, and initializing again won't hurt.
setTimeout(function() {
try {
let DownloadsCommon =
Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon;
if (DownloadsCommon.useJSTransfer) {
// Open the data link without initalizing nsIDownloadManager.
DownloadsCommon.initializeAllDataLinks();
let DownloadsTaskbar =
Cu.import("resource:///modules/DownloadsTaskbar.jsm", {}).DownloadsTaskbar;
DownloadsTaskbar.registerIndicator(window);
} else {
// Initalizing nsIDownloadManager will trigger the data link.
Services.downloads;
let DownloadTaskbarProgress =
Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
DownloadTaskbarProgress.onBrowserWindowLoad(window);
}
Cu.import("resource:///modules/DownloadsCommon.jsm", {})
.DownloadsCommon.initializeAllDataLinks();
Cu.import("resource:///modules/DownloadsTaskbar.jsm", {})
.DownloadsTaskbar.registerIndicator(window);
} catch (ex) {
Cu.reportError(ex);
}
@ -3243,10 +3232,22 @@ function OpenBrowserWindow(options)
doc != document &&
doc.defaultView == win) {
Services.obs.removeObserver(newDocumentShown, "document-shown");
Services.obs.removeObserver(windowClosed, "domwindowclosed");
TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
}
};
}
function windowClosed(subject) {
if (subject == win) {
Services.obs.removeObserver(newDocumentShown, "document-shown");
Services.obs.removeObserver(windowClosed, "domwindowclosed");
}
}
// Make sure to remove the 'document-shown' observer in case the window
// is being closed right after it was opened to avoid leaking.
Services.obs.addObserver(newDocumentShown, "document-shown", false);
Services.obs.addObserver(windowClosed, "domwindowclosed", false);
var charsetArg = new String();
var handler = Components.classes["@mozilla.org/browser/clh;1"]
@ -4207,6 +4208,10 @@ var TabsProgressListener = {
if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
return;
// Filter out location changes in sub documents.
if (!aWebProgress.isTopLevel)
return;
// Only need to call locationChange if the PopupNotifications object
// for this window has already been initialized (i.e. its getter no
// longer exists)
@ -4215,10 +4220,7 @@ var TabsProgressListener = {
gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
// Filter out location changes in sub documents.
if (aWebProgress.isTopLevel) {
FullZoom.onLocationChange(aLocationURI, false, aBrowser);
}
FullZoom.onLocationChange(aLocationURI, false, aBrowser);
},
onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) {

View File

@ -139,7 +139,6 @@
footertype="promobox"
orient="vertical"
ignorekeys="true"
consumeoutsideclicks="true"
hidden="true"
onpopupshown="StarUI.panelShown(event);"
aria-labelledby="editBookmarkPanelTitle">
@ -188,7 +187,6 @@
<panel id="UITourTooltip"
type="arrow"
hidden="true"
consumeoutsideclicks="false"
noautofocus="true"
align="start"
orient="vertical"
@ -203,7 +201,6 @@
<panel id="socialActivatedNotification"
type="arrow"
hidden="true"
consumeoutsideclicks="true"
align="start"
orient="horizontal"
role="alert">
@ -234,7 +231,6 @@
orient="horizontal"
onpopupshowing="SocialShare.onShowing()"
onpopuphidden="SocialShare.onHidden()"
consumeoutsideclicks="true"
hidden="true">
<vbox class="social-share-toolbar">
<vbox id="social-share-provider-buttons" flex="1"/>
@ -255,7 +251,6 @@
hidden="true"
flip="slide"
rolluponmousewheel="true"
consumeoutsideclicks="false"
noautofocus="true"
position="topcenter topright"/>
@ -313,7 +308,6 @@
type="arrow"
hidden="true"
noautofocus="true"
consumeoutsideclicks="true"
onpopupshown="if (event.target == this)
gIdentityHandler.onPopupShown(event);"
orient="vertical"

View File

@ -31,6 +31,15 @@ let gPage = {
this._updateAttributes(enabled);
},
/**
* True if the page is allowed to capture thumbnails using the background
* thumbnail service.
*/
get allowBackgroundCaptures() {
return document.documentElement.getAttribute("allow-background-captures") ==
"true";
},
/**
* Listens for notifications specific to this page.
*/
@ -74,6 +83,20 @@ let gPage = {
this._initialized = true;
this._mutationObserver = new MutationObserver(() => {
if (this.allowBackgroundCaptures) {
for (let site of gGrid.sites) {
if (site) {
site.captureIfMissing();
}
}
}
});
this._mutationObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["allow-background-captures"],
});
gLinks.populateCache(function () {
// Initialize and render the grid.
gGrid.init();
@ -123,6 +146,7 @@ let gPage = {
handleEvent: function Page_handleEvent(aEvent) {
switch (aEvent.type) {
case "unload":
this._mutationObserver.disconnect();
gAllPages.unregister(this);
break;
case "click":

View File

@ -0,0 +1,31 @@
/* 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/. */
(function () { // bug 673569 workaround :(
const ALLOW_BG_CAPTURES_MSG = "BrowserNewTabPreloader:allowBackgroundCaptures";
addMessageListener(ALLOW_BG_CAPTURES_MSG, function onMsg(msg) {
removeMessageListener(ALLOW_BG_CAPTURES_MSG, onMsg);
if (content.document.readyState == "complete") {
setAllowBackgroundCaptures();
return;
}
// This is the case when preloading is disabled.
addEventListener("load", function onLoad(event) {
if (event.target == content.document) {
removeEventListener("load", onLoad, true);
setAllowBackgroundCaptures();
}
}, true);
});
function setAllowBackgroundCaptures() {
content.document.documentElement.setAttribute("allow-background-captures",
"true");
}
})();

View File

@ -133,11 +133,20 @@ Site.prototype = {
this._updateAttributes(true);
// Capture the page if the thumbnail is missing, which will cause page.js
// to be notified and call our refreshThumbnail() method.
BackgroundPageThumbs.captureIfMissing(this.url);
this.captureIfMissing();
// but still display whatever thumbnail might be available now.
this.refreshThumbnail();
},
/**
* Captures the site's thumbnail in the background, but only if there's no
* existing thumbnail and the page allows background captures.
*/
captureIfMissing: function Site_captureIfMissing() {
if (gPage.allowBackgroundCaptures)
BackgroundPageThumbs.captureIfMissing(this.url);
},
/**
* Refreshes the thumbnail for the site.
*/

View File

@ -316,48 +316,25 @@ Sanitizer.prototype = {
downloads: {
clear: function ()
{
if (DownloadsCommon.useJSTransfer) {
Task.spawn(function () {
let filterByTime = null;
if (this.range) {
// Convert microseconds back to milliseconds for date comparisons.
let rangeBeginMs = this.range[0] / 1000;
let rangeEndMs = this.range[1] / 1000;
filterByTime = download => download.startTime >= rangeBeginMs &&
download.startTime <= rangeEndMs;
}
// Clear all completed/cancelled downloads
let list = yield Downloads.getList(Downloads.ALL);
list.removeFinished(filterByTime);
}.bind(this)).then(null, Components.utils.reportError);
}
else {
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager);
Task.spawn(function () {
let filterByTime = null;
if (this.range) {
// First, remove the completed/cancelled downloads
dlMgr.removeDownloadsByTimeframe(this.range[0], this.range[1]);
// Convert microseconds back to milliseconds for date comparisons.
let rangeBeginMs = this.range[0] / 1000;
let rangeEndMs = this.range[1] / 1000;
filterByTime = download => download.startTime >= rangeBeginMs &&
download.startTime <= rangeEndMs;
}
else {
// Clear all completed/cancelled downloads
dlMgr.cleanUp();
dlMgr.cleanUpPrivate();
}
}
// Clear all completed/cancelled downloads
let list = yield Downloads.getList(Downloads.ALL);
list.removeFinished(filterByTime);
}.bind(this)).then(null, Components.utils.reportError);
},
canClear : function(aCallback, aArg)
{
if (DownloadsCommon.useJSTransfer) {
aCallback("downloads", true, aArg);
}
else {
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager);
aCallback("downloads", dlMgr.canCleanUp || dlMgr.canCleanUpPrivate, aArg);
}
aCallback("downloads", true, aArg);
return false;
}
},

View File

@ -2253,6 +2253,10 @@
// Make sure to unregister any open URIs.
this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
// Give others a chance to swap state.
let event = new CustomEvent("SwapDocShells", {"detail": aOtherBrowser});
ourBrowser.dispatchEvent(event);
// Swap the docshells
ourBrowser.swapDocShells(aOtherBrowser);

View File

@ -62,6 +62,7 @@ support-files =
plugin_data_url.html
plugin_hidden_to_visible.html
plugin_small.html
plugin_syncRemoved.html
plugin_test.html
plugin_test2.html
plugin_test3.html

View File

@ -95,5 +95,6 @@ function part8() {
ok(objLoadingContent.activated, "plugin should be activated now");
gNewWindow.close();
gNewWindow = null;
finish();
}

View File

@ -42,7 +42,14 @@ function finishTest() {
gPermissionManager.remove("127.0.0.1:8888", gSecondTestPermissionString);
Services.prefs.clearUserPref("plugins.click_to_play");
gBrowser.removeCurrentTab();
finish();
gPageInfo = null;
gNextTest = null;
gTestBrowser = null;
gPluginHost = null;
gPermissionManager = null;
executeSoon(finish);
}
function test() {

View File

@ -835,6 +835,14 @@ function test24d() {
function() {
clearAllPluginPermissions();
resetBlocklist();
finishTest();
prepareTest(test25, gTestRoot + "plugin_syncRemoved.html");
});
}
function test25() {
let notification = PopupNotifications.getNotification("click-to-play-plugins");
ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed");
ok(notification.dismissed, "Test 25: The notification should be dismissed by default");
finishTest();
}

View File

@ -829,23 +829,41 @@ var tests = [
});
}
},
{ // Test #28 - location change in embedded frame removes notification
{ // Test #28 - location change in an embedded frame should not remove a notification
run: function () {
loadURI("data:text/html,<iframe id='iframe' src='http://example.com/'>", function () {
let notifyObj = new basicNotification();
notifyObj.options.eventCallback = function (eventName) {
loadURI("data:text/html;charset=utf8,<iframe id='iframe' src='http://example.com/'>", function () {
this.notifyObj = new basicNotification();
this.notifyObj.options.eventCallback = function (eventName) {
if (eventName == "removed") {
ok(true, "Notification removed in background tab after reloading");
executeSoon(goNext);
ok(false, "Test 28: Notification removed from browser when subframe navigated");
}
};
showNotification(notifyObj);
executeSoon(function () {
content.document.getElementById("iframe")
.setAttribute("src", "http://example.org/");
});
});
}
showNotification(this.notifyObj);
}.bind(this));
},
onShown: function (popup) {
let self = this;
let progressListener = {
onLocationChange: function onLocationChange(aBrowser) {
if (aBrowser != gBrowser.selectedBrowser) {
return;
}
let notification = PopupNotifications.getNotification(self.notifyObj.id,
self.notifyObj.browser);
ok(notification != null, "Test 28: Notification remained when subframe navigated");
self.notifyObj.options.eventCallback = undefined;
notification.remove();
gBrowser.removeTabsProgressListener(progressListener);
},
};
info("Test 28: Adding progress listener and performing navigation");
gBrowser.addTabsProgressListener(progressListener);
content.document.getElementById("iframe")
.setAttribute("src", "http://example.org/");
},
onHidden: function () {}
},
{ // Test #29 - Popup Notifications should catch exceptions from callbacks
run: function () {

View File

@ -1,13 +1,12 @@
function test() {
waitForExplicitFinish();
var privateWin = OpenBrowserWindow({private: true});
privateWin.addEventListener("load", function onload() {
privateWin.removeEventListener("load", onload, false);
ok(true, "Load listener called");
whenDelayedStartupFinished(privateWin, function () {
privateWin.BrowserOpenTab();
privateWin.BrowserTryToCloseWindow();
ok(true, "didn't prompt");
finish();
}, false);
}
executeSoon(finish);
});
}

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<body>
<script type="text/javascript">
// create an embed, insert it in the doc and immediately remove it
var embed = document.createElement('embed');
embed.setAttribute("type", "application/x-test");
embed.setAttribute("style", "width: 0px; height: 0px;");
document.body.appendChild(embed);
window.getComputedStyle(embed, null).top;
document.body.remove(embed);
</script>

View File

@ -1,6 +1,7 @@
[DEFAULT]
support-files = head.js
[browser_newtab_background_captures.js]
[browser_newtab_block.js]
[browser_newtab_bug721442.js]
[browser_newtab_bug722273.js]

View File

@ -0,0 +1,100 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Verifies that hidden, pre-loaded newtabs don't allow background captures, and
* when unhidden, do allow background captures.
*/
const CAPTURE_PREF = "browser.pagethumbnails.capturing_disabled";
function runTests() {
let imports = {};
Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
Cu.import("resource:///modules/BrowserNewTabPreloader.jsm", imports);
// Disable captures.
let originalDisabledState = Services.prefs.getBoolPref(CAPTURE_PREF);
Services.prefs.setBoolPref(CAPTURE_PREF, true);
// Make sure the thumbnail doesn't exist yet.
let siteName = "newtab_background_captures";
let url = "http://example.com/#" + siteName;
let path = imports.PageThumbsStorage.getFilePathForURL(url);
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(path);
try {
file.remove(false);
}
catch (err) {}
// Add a top site.
yield setLinks(siteName);
// We need a handle to a hidden, pre-loaded newtab so we can verify that it
// doesn't allow background captures. Add a newtab, which triggers creation
// of a hidden newtab, and then keep calling BrowserNewTabPreloader.newTab
// until it returns true, meaning that it swapped the passed-in tab's docshell
// for the hidden newtab docshell.
let tab = gWindow.gBrowser.addTab("about:blank");
yield addNewTabPageTab();
let swapWaitCount = 0;
let swapped = imports.BrowserNewTabPreloader.newTab(tab);
while (!swapped) {
if (++swapWaitCount == 10) {
ok(false, "Timed out waiting for newtab docshell swap.");
return;
}
// Give the hidden newtab some time to finish loading.
yield wait(2000);
info("Checking newtab swap " + swapWaitCount);
swapped = imports.BrowserNewTabPreloader.newTab(tab);
}
// The tab's docshell is now the previously hidden newtab docshell.
let doc = tab.linkedBrowser.contentDocument;
isnot(doc.documentElement.getAttribute("allow-background-captures"), "true",
"Pre-loaded docshell just synchronously swapped, so background " +
"captures should not be allowed yet");
// Enable captures.
Services.prefs.setBoolPref(CAPTURE_PREF, false);
// Now that the newtab is visible, its allow-background-captures attribute
// should be set eventually.
let allowBackgroundCaptures = false;
let mutationObserver = new MutationObserver(() => {
mutationObserver.disconnect();
allowBackgroundCaptures = true;
is(doc.documentElement.getAttribute("allow-background-captures"), "true",
"allow-background-captures should now be true");
info("Waiting for thumbnail to be created after observing " +
"allow-background-captures change");
});
mutationObserver.observe(doc.documentElement, {
attributes: true,
attributeFilter: ["allow-background-captures"],
});
// And the allow-background-captures change should trigger the thumbnail
// capture.
Services.obs.addObserver(function onCreate(subj, topic, data) {
if (data != url)
return;
ok(allowBackgroundCaptures,
"page-thumbnail:create should be observed after " +
"allow-background-captures was set");
Services.obs.removeObserver(onCreate, "page-thumbnail:create");
// Test finished!
Services.prefs.setBoolPref(CAPTURE_PREF, originalDisabledState);
file.remove(false);
TestRunner.next();
}, "page-thumbnail:create", false);
info("Waiting for allow-background-captures change");
yield true;
}
function wait(ms) {
setTimeout(TestRunner.next, ms);
}

View File

@ -67,6 +67,7 @@ browser.jar:
content/browser/newtab/newTab.xul (content/newtab/newTab.xul)
* content/browser/newtab/newTab.js (content/newtab/newTab.js)
content/browser/newtab/newTab.css (content/newtab/newTab.css)
content/browser/newtab/preloaderContent.js (content/newtab/preloaderContent.js)
* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
content/browser/pageinfo/pageInfo.js (content/pageinfo/pageInfo.js)
content/browser/pageinfo/pageInfo.css (content/pageinfo/pageInfo.css)

View File

@ -134,14 +134,9 @@ const DownloadsPanel = {
window.addEventListener("unload", this.onWindowUnload, false);
// Ensure that the Download Manager service is running. This resumes
// active downloads if required. If there are downloads to be shown in the
// panel, starting the service will make us load their data asynchronously.
if (DownloadsCommon.useJSTransfer) {
DownloadsCommon.initializeAllDataLinks();
} else {
Services.downloads;
}
// Load and resume active downloads if required. If there are downloads to
// be shown in the panel, they will be loaded asynchronously.
DownloadsCommon.initializeAllDataLinks();
// Now that data loading has eventually started, load the required XUL
// elements and initialize our views.
@ -789,26 +784,6 @@ const DownloadsView = {
DownloadsPanel.onViewLoadCompleted();
},
/**
* Called when the downloads database becomes unavailable (for example,
* entering Private Browsing Mode). References to existing data should be
* discarded.
*/
onDataInvalidated: function DV_onDataInvalidated()
{
DownloadsCommon.log("Downloads data has been invalidated. Cleaning up",
"DownloadsView.");
DownloadsPanel.terminate();
// Clear the list by replacing with a shallow copy.
let emptyView = this.richListBox.cloneNode(false);
this.richListBox.parentNode.replaceChild(emptyView, this.richListBox);
this.richListBox = emptyView;
this._viewItems = {};
this._dataItems = [];
},
/**
* Called when a new download data item is available, either during the
* asynchronous data load or when a new download is started.
@ -1486,7 +1461,8 @@ DownloadsViewItemController.prototype = {
downloadsCmd_open: function DVIC_downloadsCmd_open()
{
this.dataItem.openLocalFile(window);
this.dataItem.openLocalFile();
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
// Otherwise, we'd have to wait for the file-type handler to execute

View File

@ -46,7 +46,6 @@
type="arrow"
orient="vertical"
level="top"
consumeoutsideclicks="true"
onpopupshown="DownloadsPanel.onPopupShown(event);"
onpopuphidden="DownloadsPanel.onPopupHidden(event);">
<!-- The following popup menu should be a child of the panel element,

View File

@ -56,16 +56,11 @@ const DownloadsButton = {
* This function is called asynchronously just after window initialization.
*
* NOTE: This function should limit the input/output it performs to improve
* startup time, and in particular should not cause the Download Manager
* service to start.
* startup time.
*/
initializeIndicator: function DB_initializeIndicator()
{
if (!DownloadsCommon.useToolkitUI) {
DownloadsIndicatorView.ensureInitialized();
} else {
DownloadsIndicatorView.ensureTerminated();
}
DownloadsIndicatorView.ensureInitialized();
},
/**
@ -95,11 +90,7 @@ const DownloadsButton = {
customizeDone: function DB_customizeDone()
{
this._customizing = false;
if (!DownloadsCommon.useToolkitUI) {
DownloadsIndicatorView.afterCustomize();
} else {
DownloadsIndicatorView.ensureTerminated();
}
DownloadsIndicatorView.afterCustomize();
},
/**
@ -490,14 +481,7 @@ const DownloadsIndicatorView = {
onCommand: function DIV_onCommand(aEvent)
{
if (DownloadsCommon.useToolkitUI) {
// The panel won't suppress attention for us, we need to clear now.
DownloadsCommon.getIndicatorData(window).attention = false;
BrowserDownloadsUI();
} else {
DownloadsPanel.showPanel();
}
DownloadsPanel.showPanel();
aEvent.stopPropagation();
},

View File

@ -63,8 +63,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsLogger",
@ -75,9 +73,6 @@ const nsIDM = Ci.nsIDownloadManager;
const kDownloadsStringBundleUrl =
"chrome://browser/locale/downloads/downloads.properties";
const kPrefBdmScanWhenDone = "browser.download.manager.scanWhenDone";
const kPrefBdmAlertOnExeOpen = "browser.download.manager.alertOnEXEOpen";
const kDownloadsStringsRequiringFormatting = {
sizeWithUnits: true,
shortTimeLeftSeconds: true,
@ -234,16 +229,6 @@ this.DownloadsCommon = {
return DownloadsCommon.strings["shortTimeLeftDays"](Math.min(days, 99));
},
/**
* Indicates that we should show the simplified panel interface instead of the
* full Download Manager window interface. The code associated with the
* Download Manager window interface will be removed in bug 899110.
*/
get useToolkitUI()
{
return false;
},
/**
* Indicates whether we should show visual notification on the indicator
* when a download event is triggered.
@ -269,39 +254,12 @@ this.DownloadsCommon = {
},
/**
* Initializes the data link for both the private and non-private downloads
* data objects.
*
* @param aDownloadManagerService
* Reference to the service implementing nsIDownloadManager. We need
* this because getService isn't available for us when this method is
* called, and we must ensure to register our listeners before the
* getService call for the Download Manager returns.
* Initializes the Downloads back-end and starts receiving events for both the
* private and non-private downloads data objects.
*/
initializeAllDataLinks: function DC_initializeAllDataLinks(aDownloadManagerService) {
DownloadsData.initializeDataLink(aDownloadManagerService);
PrivateDownloadsData.initializeDataLink(aDownloadManagerService);
},
/**
* Terminates the data link for both the private and non-private downloads
* data objects.
*/
terminateAllDataLinks: function DC_terminateAllDataLinks() {
DownloadsData.terminateDataLink();
PrivateDownloadsData.terminateDataLink();
},
/**
* Reloads the specified kind of downloads from the non-private store.
* This method must only be called when Private Browsing Mode is disabled.
*
* @param aActiveOnly
* True to load only active downloads from the database.
*/
ensureAllPersistentDataLoaded:
function DC_ensureAllPersistentDataLoaded(aActiveOnly) {
DownloadsData.ensurePersistentDataLoaded(aActiveOnly);
initializeAllDataLinks: function () {
DownloadsData.initializeDataLink();
PrivateDownloadsData.initializeDataLink();
},
/**
@ -566,15 +524,6 @@ XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
return parseFloat(sysInfo.getProperty("version")) >= 6;
});
/**
* Returns true to indicate that we should hook the panel to the JavaScript API
* for downloads instead of the nsIDownloadManager back-end. The code
* associated with nsIDownloadManager will be removed in bug 899110.
*/
XPCOMUtils.defineLazyGetter(DownloadsCommon, "useJSTransfer", function () {
return true;
});
////////////////////////////////////////////////////////////////////////////////
//// DownloadsData
@ -611,76 +560,36 @@ function DownloadsDataCtor(aPrivate) {
// data changes.
this._views = [];
if (DownloadsCommon.useJSTransfer) {
// Maps Download objects to DownloadDataItem objects.
this._downloadToDataItemMap = new Map();
}
// Maps Download objects to DownloadDataItem objects.
this._downloadToDataItemMap = new Map();
}
DownloadsDataCtor.prototype = {
/**
* Starts receiving events for current downloads.
*
* @param aDownloadManagerService
* Reference to the service implementing nsIDownloadManager. We need
* this because getService isn't available for us when this method is
* called, and we must ensure to register our listeners before the
* getService call for the Download Manager returns.
*/
initializeDataLink: function DD_initializeDataLink(aDownloadManagerService)
initializeDataLink: function ()
{
// Start receiving real-time events.
if (DownloadsCommon.useJSTransfer) {
if (!this._dataLinkInitialized) {
let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
: Downloads.PUBLIC);
promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
this._dataLinkInitialized = true;
}
} else {
aDownloadManagerService.addPrivacyAwareListener(this);
Services.obs.addObserver(this, "download-manager-remove-download-guid",
false);
if (!this._dataLinkInitialized) {
let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
: Downloads.PUBLIC);
promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
this._dataLinkInitialized = true;
}
},
_dataLinkInitialized: false,
/**
* Stops receiving events for current downloads and cancels any pending read.
*/
terminateDataLink: function DD_terminateDataLink()
{
if (DownloadsCommon.useJSTransfer) {
Cu.reportError("terminateDataLink not applicable with useJSTransfer");
return;
}
this._terminateDataAccess();
// Stop receiving real-time events.
Services.obs.removeObserver(this, "download-manager-remove-download-guid");
Services.downloads.removeListener(this);
},
/**
* True if there are finished downloads that can be removed from the list.
*/
get canRemoveFinished()
{
if (DownloadsCommon.useJSTransfer) {
for (let [, dataItem] of Iterator(this.dataItems)) {
if (dataItem && !dataItem.inProgress) {
return true;
}
}
return false;
} else {
if (this._isPrivate) {
return Services.downloads.canCleanUpPrivate;
} else {
return Services.downloads.canCleanUp;
for (let [, dataItem] of Iterator(this.dataItems)) {
if (dataItem && !dataItem.inProgress) {
return true;
}
}
return false;
},
/**
@ -688,18 +597,10 @@ DownloadsDataCtor.prototype = {
*/
removeFinished: function DD_removeFinished()
{
if (DownloadsCommon.useJSTransfer) {
let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
: Downloads.PUBLIC);
promiseList.then(list => list.removeFinished())
.then(null, Cu.reportError);
} else {
if (this._isPrivate) {
Services.downloads.cleanUpPrivate();
} else {
Services.downloads.cleanUp();
}
}
let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
: Downloads.PUBLIC);
promiseList.then(list => list.removeFinished())
.then(null, Cu.reportError);
},
//////////////////////////////////////////////////////////////////////////////
@ -753,7 +654,7 @@ DownloadsDataCtor.prototype = {
let wasInProgress = aDataItem.inProgress;
let wasDone = aDataItem.done;
aDataItem.updateFromJSDownload();
aDataItem.updateFromDownload();
if (wasInProgress && !aDataItem.inProgress) {
aDataItem.endTime = Date.now();
@ -835,394 +736,10 @@ DownloadsDataCtor.prototype = {
function (dataItem) aView.onDataItemAdded(dataItem, false)
);
// Notify the view that all data is available unless loading is in progress.
if (!this._pendingStatement) {
aView.onDataLoadCompleted();
}
// Notify the view that all data is available.
aView.onDataLoadCompleted();
},
//////////////////////////////////////////////////////////////////////////////
//// In-memory downloads data store
/**
* Clears the loaded data.
*/
clear: function DD_clear()
{
this._terminateDataAccess();
this.dataItems = {};
},
/**
* Returns the data item associated with the provided source object. The
* source can be a download object that we received from the Download Manager
* because of a real-time notification, or a row from the downloads database,
* during the asynchronous data load.
*
* In case we receive download status notifications while we are still
* populating the list of downloads from the database, we want the real-time
* status to take precedence over the state that is read from the database,
* which might be older. This is achieved by creating the download item if
* it's not already in the list, but never updating the returned object using
* the data from the database, if the object already exists.
*
* @param aSource
* Object containing the data with which the item should be initialized
* if it doesn't already exist in the list. This should implement
* either nsIDownload or mozIStorageRow. If the item exists, this
* argument is only used to retrieve the download identifier.
* @param aMayReuseGUID
* If false, indicates that the download should not be added if a
* download with the same identifier was removed in the meantime. This
* ensures that, while loading the list asynchronously, downloads that
* have been removed in the meantime do no reappear inadvertently.
*
* @return New or existing data item, or null if the item was deleted from the
* list of available downloads.
*/
_getOrAddDataItem: function DD_getOrAddDataItem(aSource, aMayReuseGUID)
{
let downloadGuid = (aSource instanceof Ci.nsIDownload)
? aSource.guid
: aSource.getResultByName("guid");
if (downloadGuid in this.dataItems) {
let existingItem = this.dataItems[downloadGuid];
if (existingItem || !aMayReuseGUID) {
// Returns null if the download was removed and we can't reuse the item.
return existingItem;
}
}
DownloadsCommon.log("Creating a new DownloadsDataItem with downloadGuid =",
downloadGuid);
let dataItem = new DownloadsDataItem(aSource);
this.dataItems[downloadGuid] = dataItem;
// Create the view items before returning.
let addToStartOfList = aSource instanceof Ci.nsIDownload;
this._views.forEach(
function (view) view.onDataItemAdded(dataItem, addToStartOfList)
);
return dataItem;
},
/**
* Removes the data item with the specified identifier.
*
* This method can be called at most once per download identifier.
*/
_removeDataItem: function DD_removeDataItem(aDownloadId)
{
if (aDownloadId in this.dataItems) {
let dataItem = this.dataItems[aDownloadId];
this.dataItems[aDownloadId] = null;
this._views.forEach(
function (view) view.onDataItemRemoved(dataItem)
);
}
},
//////////////////////////////////////////////////////////////////////////////
//// Persistent data loading
/**
* Represents an executing statement, allowing its cancellation.
*/
_pendingStatement: null,
/**
* Indicates which kind of items from the persistent downloads database have
* been fully loaded in memory and are available to the views. This can
* assume the value of one of the kLoad constants.
*/
_loadState: 0,
/** No downloads have been fully loaded yet. */
get kLoadNone() 0,
/** All the active downloads in the database are loaded in memory. */
get kLoadActive() 1,
/** All the downloads in the database are loaded in memory. */
get kLoadAll() 2,
/**
* Reloads the specified kind of downloads from the persistent database. This
* method must only be called when Private Browsing Mode is disabled.
*
* @param aActiveOnly
* True to load only active downloads from the database.
*/
ensurePersistentDataLoaded:
function DD_ensurePersistentDataLoaded(aActiveOnly)
{
if (this == PrivateDownloadsData) {
Cu.reportError("ensurePersistentDataLoaded should not be called on PrivateDownloadsData");
return;
}
if (this._pendingStatement) {
// We are already in the process of reloading all downloads.
return;
}
if (aActiveOnly) {
if (this._loadState == this.kLoadNone) {
DownloadsCommon.log("Loading only active downloads from the persistence database");
// Indicate to the views that a batch loading operation is in progress.
this._views.forEach(
function (view) view.onDataLoadStarting()
);
// Reload the list using the Download Manager service. The list is
// returned in no particular order.
let downloads = Services.downloads.activeDownloads;
while (downloads.hasMoreElements()) {
let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
this._getOrAddDataItem(download, true);
}
this._loadState = this.kLoadActive;
// Indicate to the views that the batch loading operation is complete.
this._views.forEach(
function (view) view.onDataLoadCompleted()
);
DownloadsCommon.log("Active downloads done loading.");
}
} else {
if (this._loadState != this.kLoadAll) {
// Load only the relevant columns from the downloads database. The
// columns are read in the _initFromDataRow method of DownloadsDataItem.
// Order by descending download identifier so that the most recent
// downloads are notified first to the listening views.
DownloadsCommon.log("Loading all downloads from the persistence database.");
let dbConnection = Services.downloads.DBConnection;
let statement = dbConnection.createAsyncStatement(
"SELECT guid, target, name, source, referrer, state, "
+ "startTime, endTime, currBytes, maxBytes "
+ "FROM moz_downloads "
+ "ORDER BY startTime DESC"
);
try {
this._pendingStatement = statement.executeAsync(this);
} finally {
statement.finalize();
}
}
}
},
/**
* Cancels any pending data access and ensures views are notified.
*/
_terminateDataAccess: function DD_terminateDataAccess()
{
if (this._pendingStatement) {
this._pendingStatement.cancel();
this._pendingStatement = null;
}
// Close all the views on the current data. Create a copy of the array
// because some views might unregister while processing this event.
Array.slice(this._views, 0).forEach(
function (view) view.onDataInvalidated()
);
},
//////////////////////////////////////////////////////////////////////////////
//// mozIStorageStatementCallback
handleResult: function DD_handleResult(aResultSet)
{
for (let row = aResultSet.getNextRow();
row;
row = aResultSet.getNextRow()) {
// Add the download to the list and initialize it with the data we read,
// unless we already received a notification providing more reliable
// information for this download.
this._getOrAddDataItem(row, false);
}
},
handleError: function DD_handleError(aError)
{
DownloadsCommon.error("Database statement execution error (",
aError.result, "): ", aError.message);
},
handleCompletion: function DD_handleCompletion(aReason)
{
DownloadsCommon.log("Loading all downloads from database completed with reason:",
aReason);
this._pendingStatement = null;
// To ensure that we don't inadvertently delete more downloads from the
// database than needed on shutdown, we should update the load state only if
// the operation completed successfully.
if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
this._loadState = this.kLoadAll;
}
// Indicate to the views that the batch loading operation is complete, even
// if the lookup failed or was canceled. The only possible glitch happens
// in case the database backend changes while loading data, when the views
// would open and immediately close. This case is rare enough not to need a
// special treatment.
this._views.forEach(
function (view) view.onDataLoadCompleted()
);
},
//////////////////////////////////////////////////////////////////////////////
//// nsIObserver
observe: function DD_observe(aSubject, aTopic, aData)
{
switch (aTopic) {
case "download-manager-remove-download-guid":
// If a single download was removed, remove the corresponding data item.
if (aSubject) {
let downloadGuid = aSubject.QueryInterface(Ci.nsISupportsCString).data;
DownloadsCommon.log("A single download with id",
downloadGuid, "was removed.");
this._removeDataItem(downloadGuid);
break;
}
// Multiple downloads have been removed. Iterate over known downloads
// and remove those that don't exist anymore.
DownloadsCommon.log("Multiple downloads were removed.");
for each (let dataItem in this.dataItems) {
if (dataItem) {
// Bug 449811 - We have to bind to the dataItem because Javascript
// doesn't do fresh let-bindings per loop iteration.
let dataItemBinding = dataItem;
Services.downloads.getDownloadByGUID(dataItemBinding.downloadGuid,
function(aStatus, aResult) {
if (aStatus == Components.results.NS_ERROR_NOT_AVAILABLE) {
DownloadsCommon.log("Removing download with id",
dataItemBinding.downloadGuid);
this._removeDataItem(dataItemBinding.downloadGuid);
}
}.bind(this));
}
}
break;
}
},
//////////////////////////////////////////////////////////////////////////////
//// nsIDownloadProgressListener
onDownloadStateChange: function DD_onDownloadStateChange(aOldState, aDownload)
{
if (aDownload.isPrivate != this._isPrivate) {
// Ignore the downloads with a privacy status other than what we are
// tracking.
return;
}
// When a new download is added, it may have the same identifier of a
// download that we previously deleted during this session, and we also
// want to provide a visible indication that the download started.
let isNew = aOldState == nsIDM.DOWNLOAD_NOTSTARTED ||
aOldState == nsIDM.DOWNLOAD_QUEUED;
let dataItem = this._getOrAddDataItem(aDownload, isNew);
if (!dataItem) {
return;
}
let wasInProgress = dataItem.inProgress;
DownloadsCommon.log("A download changed its state to:", aDownload.state);
dataItem.state = aDownload.state;
dataItem.referrer = aDownload.referrer && aDownload.referrer.spec;
dataItem.resumable = aDownload.resumable;
dataItem.startTime = Math.round(aDownload.startTime / 1000);
dataItem.currBytes = aDownload.amountTransferred;
dataItem.maxBytes = aDownload.size;
if (wasInProgress && !dataItem.inProgress) {
dataItem.endTime = Date.now();
}
// When a download is retried, we create a different download object from
// the database with the same ID as before. This means that the nsIDownload
// that the dataItem holds might now need updating.
//
// We only overwrite this in the event that _download exists, because if it
// doesn't, that means that no caller ever tried to get the nsIDownload,
// which means it was never retrieved and doesn't need to be overwritten.
if (dataItem._download) {
dataItem._download = aDownload;
}
for (let view of this._views) {
try {
view.getViewItem(dataItem).onStateChange(aOldState);
} catch (ex) {
Cu.reportError(ex);
}
}
if (isNew && !dataItem.newDownloadNotified) {
dataItem.newDownloadNotified = true;
this._notifyDownloadEvent("start");
}
// This is a final state of which we are only notified once.
if (dataItem.done) {
this._notifyDownloadEvent("finish");
}
// TODO Bug 830415: this isn't the right place to set these annotation.
// It should be set it in places' nsIDownloadHistory implementation.
if (!this._isPrivate && !dataItem.inProgress) {
let downloadMetaData = { state: dataItem.state,
endTime: dataItem.endTime };
if (dataItem.done)
downloadMetaData.fileSize = dataItem.maxBytes;
try {
PlacesUtils.annotations.setPageAnnotation(
NetUtil.newURI(dataItem.uri), "downloads/metaData", JSON.stringify(downloadMetaData), 0,
PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
}
catch(ex) {
Cu.reportError(ex);
}
}
},
onProgressChange: function DD_onProgressChange(aWebProgress, aRequest,
aCurSelfProgress,
aMaxSelfProgress,
aCurTotalProgress,
aMaxTotalProgress, aDownload)
{
if (aDownload.isPrivate != this._isPrivate) {
// Ignore the downloads with a privacy status other than what we are
// tracking.
return;
}
let dataItem = this._getOrAddDataItem(aDownload, false);
if (!dataItem) {
return;
}
dataItem.currBytes = aDownload.amountTransferred;
dataItem.maxBytes = aDownload.size;
dataItem.speed = aDownload.speed;
dataItem.percentComplete = aDownload.percentComplete;
this._views.forEach(
function (view) view.getViewItem(dataItem).onProgressChange()
);
},
onStateChange: function () { },
onSecurityChange: function () { },
//////////////////////////////////////////////////////////////////////////////
//// Notifications sent to the most recent browser window only
@ -1252,10 +769,6 @@ DownloadsDataCtor.prototype = {
_notifyDownloadEvent: function DD_notifyDownloadEvent(aType)
{
DownloadsCommon.log("Attempting to notify that a new download has started or finished.");
if (DownloadsCommon.useToolkitUI) {
DownloadsCommon.log("Cancelling notification - we're using the toolkit downloads manager.");
return;
}
// Show the panel in the most recent browser window, if present.
let browserWin = RecentWindow.getMostRecentBrowserWindow({ private: this._isPrivate });
@ -1288,25 +801,24 @@ XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
//// DownloadsDataItem
/**
* Represents a single item in the list of downloads. This object either wraps
* an existing nsIDownload from the Download Manager, or provides the same
* information read directly from the downloads database, with the possibility
* of querying the nsIDownload lazily, for performance reasons.
* Represents a single item in the list of downloads.
*
* @param aSource
* Object containing the data with which the item should be initialized.
* This should implement either nsIDownload or mozIStorageRow. If the
* JavaScript API for downloads is enabled, this is a Download object.
* The endTime property is initialized to the current date and time.
*
* @param aDownload
* The Download object with the current state.
*/
function DownloadsDataItem(aSource)
function DownloadsDataItem(aDownload)
{
if (DownloadsCommon.useJSTransfer) {
this._initFromJSDownload(aSource);
} else if (aSource instanceof Ci.nsIDownload) {
this._initFromDownload(aSource);
} else {
this._initFromDataRow(aSource);
}
this._download = aDownload;
this.downloadGuid = "id:" + this._autoIncrementId;
this.file = aDownload.target.path;
this.target = OS.Path.basename(aDownload.target.path);
this.uri = aDownload.source.url;
this.endTime = Date.now();
this.updateFromDownload();
}
DownloadsDataItem.prototype = {
@ -1318,30 +830,9 @@ DownloadsDataItem.prototype = {
__lastId: 0,
/**
* Initializes this object from the JavaScript API for downloads.
*
* The endTime property is initialized to the current date and time.
*
* @param aDownload
* The Download object with the current state.
* Updates this object from the underlying Download object.
*/
_initFromJSDownload: function (aDownload)
{
this._download = aDownload;
this.downloadGuid = "id:" + this._autoIncrementId;
this.file = aDownload.target.path;
this.target = OS.Path.basename(aDownload.target.path);
this.uri = aDownload.source.url;
this.endTime = Date.now();
this.updateFromJSDownload();
},
/**
* Updates this object from the JavaScript API for downloads.
*/
updateFromJSDownload: function ()
updateFromDownload: function ()
{
// Collapse state using the correct priority.
if (this._download.succeeded) {
@ -1370,115 +861,6 @@ DownloadsDataItem.prototype = {
this.percentComplete = this._download.progress;
},
/**
* Initializes this object from a download object of the Download Manager.
*
* The endTime property is initialized to the current date and time.
*
* @param aDownload
* The nsIDownload with the current state.
*/
_initFromDownload: function DDI_initFromDownload(aDownload)
{
this._download = aDownload;
// Fetch all the download properties eagerly.
this.downloadGuid = aDownload.guid;
this.file = aDownload.target.spec;
this.target = aDownload.displayName;
this.uri = aDownload.source.spec;
this.referrer = aDownload.referrer && aDownload.referrer.spec;
this.state = aDownload.state;
this.startTime = Math.round(aDownload.startTime / 1000);
this.endTime = Date.now();
this.currBytes = aDownload.amountTransferred;
this.maxBytes = aDownload.size;
this.resumable = aDownload.resumable;
this.speed = aDownload.speed;
this.percentComplete = aDownload.percentComplete;
},
/**
* Initializes this object from a data row in the downloads database, without
* querying the associated nsIDownload object, to improve performance when
* loading the list of downloads asynchronously.
*
* When this object is initialized in this way, accessing the "download"
* property loads the underlying nsIDownload object synchronously, and should
* be avoided unless the object is really required.
*
* @param aStorageRow
* The mozIStorageRow from the downloads database.
*/
_initFromDataRow: function DDI_initFromDataRow(aStorageRow)
{
// Get the download properties from the data row.
this._download = null;
this.downloadGuid = aStorageRow.getResultByName("guid");
this.file = aStorageRow.getResultByName("target");
this.target = aStorageRow.getResultByName("name");
this.uri = aStorageRow.getResultByName("source");
this.referrer = aStorageRow.getResultByName("referrer");
this.state = aStorageRow.getResultByName("state");
this.startTime = Math.round(aStorageRow.getResultByName("startTime") / 1000);
this.endTime = Math.round(aStorageRow.getResultByName("endTime") / 1000);
this.currBytes = aStorageRow.getResultByName("currBytes");
this.maxBytes = aStorageRow.getResultByName("maxBytes");
// Now we have to determine if the download is resumable, but don't want to
// access the underlying download object unnecessarily. The only case where
// the property is relevant is when we are currently downloading data, and
// in this case the download object is already loaded in memory or will be
// loaded very soon in any case. In all the other cases, including a paused
// download, we assume that the download is resumable. The property will be
// updated as soon as the underlying download state changes.
// We'll start by assuming we're resumable, and then if we're downloading,
// update resumable property in case we were wrong.
this.resumable = true;
if (this.state == nsIDM.DOWNLOAD_DOWNLOADING) {
this.getDownload(function(aDownload) {
this.resumable = aDownload.resumable;
}.bind(this));
}
// Compute the other properties without accessing the download object.
this.speed = 0;
this.percentComplete = this.maxBytes <= 0
? -1
: Math.round(this.currBytes / this.maxBytes * 100);
},
/**
* Asynchronous getter for the download object corresponding to this data item.
*
* @param aCallback
* A callback function which will be called when the download object is
* available. It should accept one argument which will be the download
* object.
*/
getDownload: function DDI_getDownload(aCallback) {
if (this._download) {
// Return the download object asynchronously to the caller
let download = this._download;
Services.tm.mainThread.dispatch(function () aCallback(download),
Ci.nsIThread.DISPATCH_NORMAL);
} else {
Services.downloads.getDownloadByGUID(this.downloadGuid,
function(aStatus, aResult) {
if (!Components.isSuccessCode(aStatus)) {
Cu.reportError(
new Components.Exception("Cannot retrieve download for GUID: " +
this.downloadGuid));
} else {
this._download = aResult;
aCallback(aResult);
}
}.bind(this));
}
},
/**
* Indicates whether the download is proceeding normally, and not finished
* yet. This includes paused downloads. When this property is true, the
@ -1600,22 +982,9 @@ DownloadsDataItem.prototype = {
/**
* Open the target file for this download.
*
* @param aOwnerWindow
* The window with which the required action is associated.
* @throws if the file cannot be opened.
*/
openLocalFile: function DDI_openLocalFile(aOwnerWindow) {
if (DownloadsCommon.useJSTransfer) {
this._download.launch().then(null, Cu.reportError);
return;
}
this.getDownload(function(aDownload) {
DownloadsCommon.openDownloadedFile(this.localFile,
aDownload.MIMEInfo,
aOwnerWindow);
}.bind(this));
openLocalFile: function () {
this._download.launch().then(null, Cu.reportError);
},
/**
@ -1630,26 +999,11 @@ DownloadsDataItem.prototype = {
* @throws if the download is not resumable or if has already done.
*/
togglePauseResume: function DDI_togglePauseResume() {
if (DownloadsCommon.useJSTransfer) {
if (this._download.stopped) {
this._download.start();
} else {
this._download.cancel();
}
return;
if (this._download.stopped) {
this._download.start();
} else {
this._download.cancel();
}
if (!this.inProgress || !this.resumable)
throw new Error("The given download cannot be paused or resumed");
this.getDownload(function(aDownload) {
if (this.inProgress) {
if (this.paused)
aDownload.resume();
else
aDownload.pause();
}
}.bind(this));
},
/**
@ -1657,73 +1011,25 @@ DownloadsDataItem.prototype = {
* @throws if we cannot.
*/
retry: function DDI_retry() {
if (DownloadsCommon.useJSTransfer) {
this._download.start();
return;
}
if (!this.canRetry)
throw new Error("Cannot retry this download");
this.getDownload(function(aDownload) {
aDownload.retry();
});
},
/**
* Support function that deletes the local file for a download. This is
* used in cases where the Download Manager service doesn't delete the file
* from disk when cancelling. See bug 732924.
*/
_ensureLocalFileRemoved: function DDI__ensureLocalFileRemoved()
{
try {
let localFile = this.localFile;
if (localFile.exists()) {
localFile.remove(false);
}
} catch (ex) { }
this._download.start();
},
/**
* Cancels the download.
* @throws if the download is already done.
*/
cancel: function() {
if (DownloadsCommon.useJSTransfer) {
this._download.cancel();
this._download.removePartialData().then(null, Cu.reportError);
return;
}
if (!this.inProgress)
throw new Error("Cannot cancel this download");
this.getDownload(function (aDownload) {
aDownload.cancel();
this._ensureLocalFileRemoved();
}.bind(this));
this._download.cancel();
this._download.removePartialData().then(null, Cu.reportError);
},
/**
* Remove the download.
*/
remove: function DDI_remove() {
if (DownloadsCommon.useJSTransfer) {
Downloads.getList(Downloads.ALL)
.then(list => list.remove(this._download))
.then(() => this._download.finalize(true))
.then(null, Cu.reportError);
return;
}
this.getDownload(function (aDownload) {
if (this.inProgress) {
aDownload.cancel();
this._ensureLocalFileRemoved();
}
aDownload.remove();
}.bind(this));
Downloads.getList(Downloads.ALL)
.then(list => list.remove(this._download))
.then(() => this._download.finalize(true))
.then(null, Cu.reportError);
}
};
@ -1838,18 +1144,6 @@ const DownloadsViewPrototype = {
this._loading = false;
},
/**
* Called when the downloads database becomes unavailable (for example, we
* entered Private Browsing Mode and the database backend changed).
* References to existing data should be discarded.
*
* @note Subclasses should override this.
*/
onDataInvalidated: function DVP_onDataInvalidated()
{
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
/**
* Called when a new download data item is available, either during the
* asynchronous data load or when a new download is started.
@ -1968,16 +1262,6 @@ DownloadsIndicatorDataCtor.prototype = {
this._updateViews();
},
/**
* Called when the downloads database becomes unavailable (for example, we
* entered Private Browsing Mode and the database backend changed).
* References to existing data should be discarded.
*/
onDataInvalidated: function DID_onDataInvalidated()
{
this._itemCount = 0;
},
/**
* Called when a new download data item is available, either during the
* asynchronous data load or when a new download is started.
@ -2271,11 +1555,6 @@ DownloadsSummaryData.prototype = {
this._updateViews();
},
onDataInvalidated: function DSD_onDataInvalidated()
{
this._dataItems = [];
},
onDataItemAdded: function DSD_onDataItemAdded(aDataItem, aNewest)
{
if (aNewest) {

View File

@ -1,16 +1,13 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* 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/. */
* 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/. */
/**
* This component listens to notifications for startup, shutdown and session
* restore, controlling which downloads should be loaded from the database.
*
* To avoid affecting startup performance, this component monitors the current
* session restore state, but defers the actual downloads data manipulation
* until the Download Manager service is loaded.
* This component enables the JavaScript API for downloads at startup. This
* will eventually be removed when nsIDownloadManager will not be available
* anymore (bug 851471).
*/
"use strict";
@ -23,44 +20,18 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
"resource:///modules/DownloadsCommon.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
"@mozilla.org/browser/sessionstartup;1",
"nsISessionStartup");
const kObservedTopics = [
"sessionstore-windows-restored",
"sessionstore-browser-state-restored",
"download-manager-initialized",
"download-manager-change-retention",
"last-pb-context-exited",
"browser-lastwindow-close-granted",
"quit-application",
"profile-change-teardown",
];
/**
* CID of our implementation of nsIDownloadManagerUI.
* CID and Contract ID of our implementation of nsIDownloadManagerUI.
*/
const kDownloadsUICid = Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}");
/**
* Contract ID of the service implementing nsIDownloadManagerUI.
*/
const kDownloadsUIContractId = "@mozilla.org/download-manager-ui;1";
/**
* CID of the JavaScript implementation of nsITransfer.
* CID and Contract ID of the JavaScript implementation of nsITransfer.
*/
const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
/**
* Contract ID of the service implementing nsITransfer.
*/
const kTransferContractId = "@mozilla.org/transfer;1";
////////////////////////////////////////////////////////////////////////////////
@ -76,204 +47,31 @@ DownloadsStartup.prototype = {
//////////////////////////////////////////////////////////////////////////////
//// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
//////////////////////////////////////////////////////////////////////////////
//// nsIObserver
observe: function DS_observe(aSubject, aTopic, aData)
{
switch (aTopic) {
case "profile-after-change":
// Override Toolkit's nsIDownloadManagerUI implementation with our own.
// This must be done at application startup and not in the manifest to
// ensure that our implementation overrides the original one.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kDownloadsUICid, "",
kDownloadsUIContractId, null);
// Override Toolkit's nsITransfer implementation with the one from the
// JavaScript API for downloads. This will eventually be removed when
// nsIDownloadManager will not be available anymore (bug 851471). The
// old code in this module will be removed in bug 899110.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kTransferCid, "",
kTransferContractId, null);
break;
case "sessionstore-windows-restored":
case "sessionstore-browser-state-restored":
// Unless there is no saved session, there is a chance that we are
// starting up after a restart or a crash. We should check the disk
// database to see if there are completed downloads to recover and show
// in the panel, in addition to in-progress downloads.
if (gSessionStartup.sessionType != Ci.nsISessionStartup.NO_SESSION) {
this._restoringSession = true;
}
this._ensureDataLoaded();
break;
case "download-manager-initialized":
// Don't initialize the JavaScript data and user interface layer if we
// are initializing the Download Manager service during shutdown.
if (this._shuttingDown) {
break;
}
// Start receiving events for active and new downloads before we return
// from this observer function. We can't defer the execution of this
// step, to ensure that we don't lose events raised in the meantime.
DownloadsCommon.initializeAllDataLinks(
aSubject.QueryInterface(Ci.nsIDownloadManager));
this._downloadsServiceInitialized = true;
// Since this notification is generated during the getService call and
// we need to get the Download Manager service ourselves, we must post
// the handler on the event queue to be executed later.
Services.tm.mainThread.dispatch(this._ensureDataLoaded.bind(this),
Ci.nsIThread.DISPATCH_NORMAL);
break;
case "download-manager-change-retention":
// If we're using the Downloads Panel, we override the retention
// preference to always retain downloads on completion.
if (!DownloadsCommon.useToolkitUI) {
aSubject.QueryInterface(Ci.nsISupportsPRInt32).data = 2;
}
break;
case "browser-lastwindow-close-granted":
// When using the panel interface, downloads that are already completed
// should be removed when the last full browser window is closed. This
// event is invoked only if the application is not shutting down yet.
// If the Download Manager service is not initialized, we don't want to
// initialize it just to clean up completed downloads, because they can
// be present only in case there was a browser crash or restart.
if (this._downloadsServiceInitialized &&
!DownloadsCommon.useToolkitUI) {
Services.downloads.cleanUp();
}
break;
case "last-pb-context-exited":
// Similar to the above notification, but for private downloads.
if (this._downloadsServiceInitialized &&
!DownloadsCommon.useToolkitUI) {
Services.downloads.cleanUpPrivate();
}
break;
case "quit-application":
// When the application is shutting down, we must free all resources in
// addition to cleaning up completed downloads. If the Download Manager
// service is not initialized, we don't want to initialize it just to
// clean up completed downloads, because they can be present only in
// case there was a browser crash or restart.
this._shuttingDown = true;
if (!this._downloadsServiceInitialized) {
break;
}
DownloadsCommon.terminateAllDataLinks();
// When using the panel interface, downloads that are already completed
// should be removed when quitting the application.
if (!DownloadsCommon.useToolkitUI && aData != "restart") {
this._cleanupOnShutdown = true;
}
break;
case "profile-change-teardown":
// If we need to clean up, we must do it synchronously after all the
// "quit-application" listeners are invoked, so that the Download
// Manager service has a chance to pause or cancel in-progress downloads
// before we remove completed downloads from the list. Note that, since
// "quit-application" was invoked, we've already exited Private Browsing
// Mode, thus we are always working on the disk database.
if (this._cleanupOnShutdown) {
Services.downloads.cleanUp();
}
if (!DownloadsCommon.useToolkitUI) {
// If we got this far, that means that we finished our first session
// with the Downloads Panel without crashing. This means that we don't
// have to force displaying only active downloads on the next startup
// now.
this._firstSessionCompleted = true;
}
break;
}
},
//////////////////////////////////////////////////////////////////////////////
//// Private
/**
* Indicates whether we're restoring a previous session. This is used by
* _recoverAllDownloads to determine whether or not we should load and
* display all downloads data, or restrict it to only the active downloads.
*/
_restoringSession: false,
/**
* Indicates whether the Download Manager service has been initialized. This
* flag is required because we want to avoid accessing the service immediately
* at browser startup. The service will start when the user first requests a
* download, or some time after browser startup.
*/
_downloadsServiceInitialized: false,
/**
* True while we are processing the "quit-application" event, and later.
*/
_shuttingDown: false,
/**
* True during shutdown if we need to remove completed downloads.
*/
_cleanupOnShutdown: false,
/**
* True if we should display all downloads, as opposed to just active
* downloads. We decide to display all downloads if we're restoring a session,
* or if we're using the Downloads Panel anytime after the first session with
* it has completed.
*/
get _recoverAllDownloads() {
return this._restoringSession ||
(!DownloadsCommon.useToolkitUI && this._firstSessionCompleted);
},
/**
* True if we've ever completed a session with the Downloads Panel enabled.
*/
get _firstSessionCompleted() {
return Services.prefs
.getBoolPref("browser.download.panel.firstSessionCompleted");
},
set _firstSessionCompleted(aValue) {
Services.prefs.setBoolPref("browser.download.panel.firstSessionCompleted",
aValue);
return aValue;
},
/**
* Ensures that persistent download data is reloaded at the appropriate time.
*/
_ensureDataLoaded: function DS_ensureDataLoaded()
{
if (!this._downloadsServiceInitialized) {
if (aTopic != "profile-after-change") {
Cu.reportError("Unexpected observer notification.");
return;
}
// If the previous session has been already restored, then we ensure that
// all the downloads are loaded. Otherwise, we only ensure that the active
// downloads from the previous session are loaded.
DownloadsCommon.ensureAllPersistentDataLoaded(!this._recoverAllDownloads);
}
// Override Toolkit's nsIDownloadManagerUI implementation with our own.
// This must be done at application startup and not in the manifest to
// ensure that our implementation overrides the original one.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kDownloadsUICid, "",
kDownloadsUIContractId, null);
// Override Toolkit's nsITransfer implementation with the one from the
// JavaScript API for downloads.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kTransferCid, "",
kTransferContractId, null);
},
};
////////////////////////////////////////////////////////////////////////////////

View File

@ -1,15 +1,12 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* 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/. */
* 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/. */
/**
* This component implements the nsIDownloadManagerUI interface and opens the
* downloads panel in the most recent browser window when requested.
*
* If a specific preference is set, this component transparently forwards all
* calls to the original implementation in Toolkit, that shows the window UI.
* Downloads view for the most recent browser window when requested.
*/
"use strict";
@ -40,11 +37,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
function DownloadsUI()
{
XPCOMUtils.defineLazyGetter(this, "_toolkitUI", function () {
// Create Toolkit's nsIDownloadManagerUI implementation.
return Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
.getService(Ci.nsIDownloadManagerUI);
});
}
DownloadsUI.prototype = {
@ -62,11 +54,6 @@ DownloadsUI.prototype = {
show: function DUI_show(aWindowContext, aDownload, aReason, aUsePrivateUI)
{
if (DownloadsCommon.useToolkitUI) {
this._toolkitUI.show(aWindowContext, aDownload, aReason, aUsePrivateUI);
return;
}
if (!aReason) {
aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED;
}
@ -92,27 +79,17 @@ DownloadsUI.prototype = {
}
},
get visible()
{
// If we're still using the toolkit downloads manager, delegate the call
// to it. Otherwise, return true for now, until we decide on how we want
// to indicate that a new download has started if a browser window is
// not available or minimized.
return DownloadsCommon.useToolkitUI ? this._toolkitUI.visible : true;
},
get visible() true,
getAttention: function DUI_getAttention()
{
if (DownloadsCommon.useToolkitUI) {
this._toolkitUI.getAttention();
}
},
getAttention: function () {},
//////////////////////////////////////////////////////////////////////////////
//// Private
/**
* Helper function that opens the download manager UI.
*/
_showDownloadManagerUI:
function DUI_showDownloadManagerUI(aWindowContext, aUsePrivateUI)
_showDownloadManagerUI: function (aWindowContext, aUsePrivateUI)
{
// If we weren't given a window context, try to find a browser window
// to use as our parent - and if that doesn't work, error out and give up.

View File

@ -342,29 +342,6 @@ Preferences.prototype = {
this._set("WebKitDisplayImagesKey", "permissions.default.image",
function(webkitVal) webkitVal ? 1 : 2);
// Default charset migration
this._set("WebKitDefaultTextEncodingName", "intl.charset.default",
function(webkitCharset) {
// We don't support x-mac-korean (see bug 713516), but it mostly matches
// EUC-KR.
if (webkitCharset == "x-mac-korean")
return "EUC-KR";
// getCharsetAlias throws if an invalid value is passed in.
try {
return Cc["@mozilla.org/charset-converter-manager;1"].
getService(Ci.nsICharsetConverterManager).
getCharsetAlias(webkitCharset);
}
catch(ex) {
Cu.reportError("Could not convert webkit charset '" + webkitCharset +
"' to a supported charset");
}
// Don't set the preference if we could not get the corresponding
// charset.
return undefined;
});
#ifdef XP_WIN
// Cookie-accept policy.
// For the OS X version, see WebFoundationCookieBehavior.

View File

@ -1287,15 +1287,47 @@ BrowserGlue.prototype = {
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 14;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
let wasCustomizedAndOnAustralis = Services.prefs.prefHasUserValue("browser.uiCustomization.state");
let currentUIVersion = 0;
try {
currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
} catch(ex) {}
if (currentUIVersion >= UI_VERSION)
if (!wasCustomizedAndOnAustralis && currentUIVersion >= UI_VERSION)
return;
this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
this._dataSource = this._rdf.GetDataSource("rdf:local-store");
// No version check for this as this code should run until we have Australis everywhere:
if (wasCustomizedAndOnAustralis) {
// This profile's been on australis! If it's missing the back/fwd button
// or go/stop/reload button, then put them back:
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
if (currentset.indexOf("unified-back-forward-button") == -1) {
currentset = currentset.replace("urlbar-container",
"unified-back-forward-button,urlbar-container");
}
if (currentset.indexOf("reload-button") == -1) {
currentset = currentset.replace("urlbar-container", "urlbar-container,reload-button");
}
if (currentset.indexOf("stop-button") == -1) {
currentset = currentset.replace("reload-button", "reload-button,stop-button");
}
this._setPersist(toolbarResource, currentsetResource, currentset);
Services.prefs.clearUserPref("browser.uiCustomization.state");
// If we don't have anything else to do, we can bail here:
if (currentUIVersion >= UI_VERSION) {
delete this._rdf;
delete this._dataSource;
return;
}
}
this._dirty = false;
if (currentUIVersion < 2) {
@ -1963,21 +1995,13 @@ ContentPermissionPrompt.prototype = {
prompt: function CPP_prompt(request) {
// Only allow exactly one permission rquest here.
let types = request.types.QueryInterface(Ci.nsIArray);
if (types.length != 1) {
request.cancel();
return;
}
let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
const kFeatureKeys = { "geolocation" : "geo",
"desktop-notification" : "desktop-notification",
"pointerLock" : "pointerLock",
};
// Make sure that we support the request.
if (!(perm.type in kFeatureKeys)) {
if (!(request.type in kFeatureKeys)) {
return;
}
@ -1989,7 +2013,7 @@ ContentPermissionPrompt.prototype = {
return;
var autoAllow = false;
var permissionKey = kFeatureKeys[perm.type];
var permissionKey = kFeatureKeys[request.type];
var result = Services.perms.testExactPermissionFromPrincipal(requestingPrincipal, permissionKey);
if (result == Ci.nsIPermissionManager.DENY_ACTION) {
@ -2000,14 +2024,14 @@ ContentPermissionPrompt.prototype = {
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
autoAllow = true;
// For pointerLock, we still want to show a warning prompt.
if (perm.type != "pointerLock") {
if (request.type != "pointerLock") {
request.allow();
return;
}
}
// Show the prompt.
switch (perm.type) {
switch (request.type) {
case "geolocation":
this._promptGeo(request);
break;

View File

@ -205,7 +205,7 @@ var bookmarksObserver = {
}
},
onItemRemoved: function PSB_onItemRemoved(aItemId, aFolder, aIndex,
onItemRemoved: function PSB_onItemRemoved(aItemId, aFolderId, aIndex,
aItemType) {
var views = getViewsForFolder(aFolderId);
ok(views.length > 0, "Found affected views (" + views.length + "): " + views);

View File

@ -69,8 +69,11 @@ tests.push({
// Create a bookmarks.html in the profile.
let profileBookmarksHTMLFile = create_bookmarks_html("bookmarks.glue.html");
// Get file lastModified and size.
let lastMod = profileBookmarksHTMLFile.lastModifiedTime;
// set the file's lastModifiedTime to one minute ago and get its size.
let lastMod = Date.now() - 60*1000;
profileBookmarksHTMLFile.lastModifiedTime = lastMod;
let fileSize = profileBookmarksHTMLFile.fileSize;
// Force nsBrowserGlue::_shutdownPlaces().
@ -80,16 +83,8 @@ tests.push({
// Check a new bookmarks.html has been created.
let profileBookmarksHTMLFile = check_bookmarks_html();
//XXX not working on Linux unit boxes. Could be filestats caching issue.
let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
if (!isLinux) {
//XXX this test does not working on Mac boxes as well.
let isOSX = ("nsILocalFileMac" in Ci);
if (!isOSX) {
do_check_true(profileBookmarksHTMLFile.lastModifiedTime > lastMod);
}
do_check_neq(profileBookmarksHTMLFile.fileSize, fileSize);
}
do_check_true(profileBookmarksHTMLFile.lastModifiedTime > lastMod);
do_check_neq(profileBookmarksHTMLFile.fileSize, fileSize);
// Check preferences have not been reverted.
do_check_true(ps.getBoolPref(PREF_AUTO_EXPORT_HTML));

View File

@ -8,15 +8,10 @@
//****************************************************************************//
// Constants & Enumeration Values
/*
#ifndef XP_MACOSX
*/
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
/*
#endif
*/
Components.utils.import('resource://gre/modules/Services.jsm');
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
@ -159,14 +154,14 @@ function isFeedType(t) {
* This object wraps nsIHandlerInfo with some additional functionality
* the Applications prefpane needs to display and allow modification of
* the list of handled types.
*
*
* We create an instance of this wrapper for each entry we might display
* in the prefpane, and we compose the instances from various sources,
* including navigator.plugins and the handler service.
* including plugins and the handler service.
*
* We don't implement all the original nsIHandlerInfo functionality,
* just the stuff that the prefpane needs.
*
*
* In theory, all of the custom functionality in this wrapper should get
* pushed down into nsIHandlerInfo eventually.
*/
@ -276,7 +271,7 @@ HandlerInfoWrapper.prototype = {
// What to do with content of this type.
get preferredAction() {
// If we have an enabled plugin, then the action is to use that plugin.
if (this.plugin && !this.isDisabledPluginType)
if (this.pluginName && !this.isDisabledPluginType)
return kActionUsePlugin;
// If the action is to use a helper app, but we don't have a preferred
@ -312,7 +307,7 @@ HandlerInfoWrapper.prototype = {
// of any user configuration, and the default in that case is to always ask,
// even though we never ask for content handled by a plugin, so special case
// plugin-handled types by returning false here.
if (this.plugin && this.handledOnlyByPlugin)
if (this.pluginName && this.handledOnlyByPlugin)
return false;
// If this is a protocol type and the preferred action is "save to disk",
@ -1092,10 +1087,17 @@ var gApplicationsPane = {
* check the pref ourselves to find out if it's enabled.
*/
_loadPluginHandlers: function() {
for (let i = 0; i < navigator.plugins.length; ++i) {
let plugin = navigator.plugins[i];
for (let j = 0; j < plugin.length; ++j) {
let type = plugin[j].type;
"use strict";
let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
let pluginTags = pluginHost.getPluginTags();
for (let i = 0; i < pluginTags.length; ++i) {
let pluginTag = pluginTags[i];
let mimeTypes = pluginTag.getMimeTypes();
for (let j = 0; j < mimeTypes.length; ++j) {
let type = mimeTypes[j];
let handlerInfoWrapper;
if (type in this._handledTypes)
@ -1108,7 +1110,7 @@ var gApplicationsPane = {
this._handledTypes[type] = handlerInfoWrapper;
}
handlerInfoWrapper.plugin = plugin;
handlerInfoWrapper.pluginName = pluginTag.name;
}
}
},
@ -1302,7 +1304,7 @@ var gApplicationsPane = {
case kActionUsePlugin:
return this._prefsBundle.getFormattedString("usePluginIn",
[aHandlerInfo.plugin.name,
[aHandlerInfo.pluginName,
this._brandShortName]);
}
},
@ -1484,11 +1486,11 @@ var gApplicationsPane = {
}
// Create a menu item for the plugin.
if (handlerInfo.plugin) {
if (handlerInfo.pluginName) {
var pluginMenuItem = document.createElement("menuitem");
pluginMenuItem.setAttribute("action", kActionUsePlugin);
let label = this._prefsBundle.getFormattedString("usePluginIn",
[handlerInfo.plugin.name,
[handlerInfo.pluginName,
this._brandShortName]);
pluginMenuItem.setAttribute("label", label);
pluginMenuItem.setAttribute("tooltiptext", label);
@ -1651,7 +1653,7 @@ var gApplicationsPane = {
// Set the plugin state if we're enabling or disabling a plugin.
if (action == kActionUsePlugin)
handlerInfo.enablePluginType();
else if (handlerInfo.plugin && !handlerInfo.isDisabledPluginType)
else if (handlerInfo.pluginName && !handlerInfo.isDisabledPluginType)
handlerInfo.disablePluginType();
// Set the preferred application handler.

View File

@ -98,18 +98,6 @@ var gFontsDialog = {
return defaultValue;
},
_charsetMenuInitialized: false,
readDefaultCharset: function ()
{
if (!this._charsetMenuInitialized) {
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.notifyObservers(null, "charsetmenu-selected", "other");
this._charsetMenuInitialized = true;
}
return undefined;
},
readUseDocumentFonts: function ()
{
var preference = document.getElementById("browser.display.use_document_fonts");

View File

@ -37,7 +37,7 @@
<preference id="browser.display.use_document_fonts"
name="browser.display.use_document_fonts"
type="int"/>
<preference id="intl.charset.default" name="intl.charset.default" type="wstring"/>
<preference id="intl.charset.fallback.override" name="intl.charset.fallback.override" type="string"/>
</preferences>
<stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
@ -262,14 +262,25 @@
<label value="&languages.customize.Fallback.label;"
accesskey="&languages.customize.Fallback.accesskey;"
control="DefaultCharsetList"/>
<menulist id="DefaultCharsetList" ref="NC:DecodersRoot" datasources="rdf:charset-menu"
preference="intl.charset.default"
onsyncfrompreference="return gFontsDialog.readDefaultCharset();">
<template>
<menupopup>
<menuitem label="rdf:http://home.netscape.com/NC-rdf#Name" value="..." uri="..."/>
</menupopup>
</template>
<menulist id="DefaultCharsetList" preference="intl.charset.fallback.override">
<menupopup>
<menuitem label="&languages.customize.Fallback.auto;" value=""/>
<menuitem label="&languages.customize.Fallback.arabic;" value="windows-1256"/>
<menuitem label="&languages.customize.Fallback.baltic;" value="windows-1257"/>
<menuitem label="&languages.customize.Fallback.ceiso;" value="ISO-8859-2"/>
<menuitem label="&languages.customize.Fallback.cewindows;" value="windows-1250"/>
<menuitem label="&languages.customize.Fallback.simplified;" value="gbk"/>
<menuitem label="&languages.customize.Fallback.traditional;" value="Big5"/>
<menuitem label="&languages.customize.Fallback.cyrillic;" value="windows-1251"/>
<menuitem label="&languages.customize.Fallback.greek;" value="ISO-8859-7"/>
<menuitem label="&languages.customize.Fallback.hebrew;" value="windows-1255"/>
<menuitem label="&languages.customize.Fallback.japanese;" value="Shift_JIS"/>
<menuitem label="&languages.customize.Fallback.korean;" value="EUC-KR"/>
<menuitem label="&languages.customize.Fallback.thai;" value="windows-874"/>
<menuitem label="&languages.customize.Fallback.turkish;" value="windows-1254"/>
<menuitem label="&languages.customize.Fallback.vietnamese;" value="windows-1258"/>
<menuitem label="&languages.customize.Fallback.other;" value="windows-1252"/>
</menupopup>
</menulist>
</hbox>
</groupbox>

View File

@ -146,14 +146,14 @@ function isFeedType(t) {
* This object wraps nsIHandlerInfo with some additional functionality
* the Applications prefpane needs to display and allow modification of
* the list of handled types.
*
*
* We create an instance of this wrapper for each entry we might display
* in the prefpane, and we compose the instances from various sources,
* including navigator.plugins and the handler service.
* including plugins and the handler service.
*
* We don't implement all the original nsIHandlerInfo functionality,
* just the stuff that the prefpane needs.
*
*
* In theory, all of the custom functionality in this wrapper should get
* pushed down into nsIHandlerInfo eventually.
*/
@ -263,7 +263,7 @@ HandlerInfoWrapper.prototype = {
// What to do with content of this type.
get preferredAction() {
// If we have an enabled plugin, then the action is to use that plugin.
if (this.plugin && !this.isDisabledPluginType)
if (this.pluginName && !this.isDisabledPluginType)
return kActionUsePlugin;
// If the action is to use a helper app, but we don't have a preferred
@ -299,7 +299,7 @@ HandlerInfoWrapper.prototype = {
// of any user configuration, and the default in that case is to always ask,
// even though we never ask for content handled by a plugin, so special case
// plugin-handled types by returning false here.
if (this.plugin && this.handledOnlyByPlugin)
if (this.pluginName && this.handledOnlyByPlugin)
return false;
// If this is a protocol type and the preferred action is "save to disk",
@ -1079,10 +1079,17 @@ var gApplicationsPane = {
* check the pref ourselves to find out if it's enabled.
*/
_loadPluginHandlers: function() {
for (let i = 0; i < navigator.plugins.length; ++i) {
let plugin = navigator.plugins[i];
for (let j = 0; j < plugin.length; ++j) {
let type = plugin[j].type;
"use strict";
let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
let pluginTags = pluginHost.getPluginTags();
for (let i = 0; i < pluginTags.length; ++i) {
let pluginTag = pluginTags[i];
let mimeTypes = pluginTag.getMimeTypes();
for (let j = 0; j < mimeTypes.length; ++j) {
let type = mimeTypes[j];
let handlerInfoWrapper;
if (type in this._handledTypes)
@ -1095,7 +1102,7 @@ var gApplicationsPane = {
this._handledTypes[type] = handlerInfoWrapper;
}
handlerInfoWrapper.plugin = plugin;
handlerInfoWrapper.pluginName = pluginTag.name;
}
}
},
@ -1289,7 +1296,7 @@ var gApplicationsPane = {
case kActionUsePlugin:
return this._prefsBundle.getFormattedString("usePluginIn",
[aHandlerInfo.plugin.name,
[aHandlerInfo.pluginName,
this._brandShortName]);
}
},
@ -1471,11 +1478,11 @@ var gApplicationsPane = {
}
// Create a menu item for the plugin.
if (handlerInfo.plugin) {
if (handlerInfo.pluginName) {
var pluginMenuItem = document.createElement("menuitem");
pluginMenuItem.setAttribute("action", kActionUsePlugin);
let label = this._prefsBundle.getFormattedString("usePluginIn",
[handlerInfo.plugin.name,
[handlerInfo.pluginName,
this._brandShortName]);
pluginMenuItem.setAttribute("label", label);
pluginMenuItem.setAttribute("tooltiptext", label);
@ -1638,7 +1645,7 @@ var gApplicationsPane = {
// Set the plugin state if we're enabling or disabling a plugin.
if (action == kActionUsePlugin)
handlerInfo.enablePluginType();
else if (handlerInfo.plugin && !handlerInfo.isDisabledPluginType)
else if (handlerInfo.pluginName && !handlerInfo.isDisabledPluginType)
handlerInfo.disablePluginType();
// Set the preferred application handler.

View File

@ -17,8 +17,6 @@ var gMainPane = {
this.updateBrowserStartupLastSession();
this.setupDownloadsWindowOptions();
// Notify observers that the UI is now ready
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
@ -39,17 +37,6 @@ var gMainPane = {
},
setupDownloadsWindowOptions: function ()
{
let showWhenDownloading = document.getElementById("showWhenDownloading");
let closeWhenDone = document.getElementById("closeWhenDone");
// These radio buttons should be hidden when the Downloads Panel is enabled.
let shouldHide = !DownloadsCommon.useToolkitUI;
showWhenDownloading.hidden = shouldHide;
closeWhenDone.hidden = shouldHide;
},
// HOME PAGE
/*
@ -201,12 +188,6 @@ var gMainPane = {
/*
* Preferences:
*
* browser.download.showWhenStarting - bool
* True if the Download Manager should be opened when a download is
* started, false if it shouldn't be opened.
* browser.download.closeWhenDone - bool
* True if the Download Manager should be closed when all downloads
* complete, false if it should be left open.
* browser.download.useDownloadDir - bool
* True - Save files directly to the folder configured via the
* browser.download.folderList preference.
@ -234,30 +215,6 @@ var gMainPane = {
* deprecated.
*/
/**
* Updates preferences which depend upon the value of the preference which
* determines whether the Downloads manager is opened at the start of a
* download.
*/
readShowDownloadsWhenStarting: function ()
{
this.showDownloadsWhenStartingPrefChanged();
// don't override the preference's value in UI
return undefined;
},
/**
* Enables or disables the "close Downloads manager when downloads finished"
* preference element, consequently updating the associated UI.
*/
showDownloadsWhenStartingPrefChanged: function ()
{
var showWhenStartingPref = document.getElementById("browser.download.manager.showWhenStarting");
var closeWhenDonePref = document.getElementById("browser.download.manager.closeWhenDone");
closeWhenDonePref.disabled = !showWhenStartingPref.value;
},
/**
* Enables/disables the folder field and Browse button based on whether a
* default download directory is being used.

View File

@ -31,13 +31,6 @@
onchange="gMainPane.updateBrowserStartupLastSession();"/>
<!-- Downloads -->
<preference id="browser.download.manager.showWhenStarting"
name="browser.download.manager.showWhenStarting"
type="bool"
onchange="gMainPane.showDownloadsWhenStartingPrefChanged();"/>
<preference id="browser.download.manager.closeWhenDone"
name="browser.download.manager.closeWhenDone"
type="bool"/>
<preference id="browser.download.useDownloadDir"
name="browser.download.useDownloadDir"
type="bool"/>
@ -160,19 +153,6 @@
<groupbox id="downloadsGroup" data-category="paneGeneral" hidden="true">
<caption label="&downloads.label;"/>
<checkbox id="showWhenDownloading"
label="&showWhenDownloading.label;"
accesskey="&showWhenDownloading.accesskey;"
preference="browser.download.manager.showWhenStarting"
onsyncfrompreference="return gMainPane.readShowDownloadsWhenStarting();"/>
<checkbox id="closeWhenDone"
label="&closeWhenDone.label;"
accesskey="&closeWhenDone.accesskey;"
class="indent"
preference="browser.download.manager.closeWhenDone"/>
<separator class="thin"/>
<radiogroup id="saveWhere"
preference="browser.download.useDownloadDir"
onsyncfrompreference="return gMainPane.readUseDownloadDir();">

View File

@ -23,25 +23,12 @@ var gMainPane = {
this.updateBrowserStartupLastSession();
this.setupDownloadsWindowOptions();
// Notify observers that the UI is now ready
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.notifyObservers(window, "main-pane-loaded", null);
},
setupDownloadsWindowOptions: function ()
{
let showWhenDownloading = document.getElementById("showWhenDownloading");
let closeWhenDone = document.getElementById("closeWhenDone");
// These radio buttons should be hidden when the Downloads Panel is enabled.
let shouldHide = !DownloadsCommon.useToolkitUI;
showWhenDownloading.hidden = shouldHide;
closeWhenDone.hidden = shouldHide;
},
// HOME PAGE
/*
@ -185,12 +172,6 @@ var gMainPane = {
/*
* Preferences:
*
* browser.download.showWhenStarting - bool
* True if the Download Manager should be opened when a download is
* started, false if it shouldn't be opened.
* browser.download.closeWhenDone - bool
* True if the Download Manager should be closed when all downloads
* complete, false if it should be left open.
* browser.download.useDownloadDir - bool
* True - Save files directly to the folder configured via the
* browser.download.folderList preference.
@ -218,30 +199,6 @@ var gMainPane = {
* deprecated.
*/
/**
* Updates preferences which depend upon the value of the preference which
* determines whether the Downloads manager is opened at the start of a
* download.
*/
readShowDownloadsWhenStarting: function ()
{
this.showDownloadsWhenStartingPrefChanged();
// don't override the preference's value in UI
return undefined;
},
/**
* Enables or disables the "close Downloads manager when downloads finished"
* preference element, consequently updating the associated UI.
*/
showDownloadsWhenStartingPrefChanged: function ()
{
var showWhenStartingPref = document.getElementById("browser.download.manager.showWhenStarting");
var closeWhenDonePref = document.getElementById("browser.download.manager.closeWhenDone");
closeWhenDonePref.disabled = !showWhenStartingPref.value;
},
/**
* Enables/disables the folder field and Browse button based on whether a
* default download directory is being used.

View File

@ -50,13 +50,6 @@
onchange="gMainPane.updateBrowserStartupLastSession();"/>
<!-- Downloads -->
<preference id="browser.download.manager.showWhenStarting"
name="browser.download.manager.showWhenStarting"
type="bool"
onchange="gMainPane.showDownloadsWhenStartingPrefChanged();"/>
<preference id="browser.download.manager.closeWhenDone"
name="browser.download.manager.closeWhenDone"
type="bool"/>
<preference id="browser.download.useDownloadDir"
name="browser.download.useDownloadDir"
type="bool"/>
@ -117,16 +110,6 @@
<groupbox id="downloadsGroup">
<caption label="&downloads.label;"/>
<checkbox id="showWhenDownloading" label="&showWhenDownloading.label;"
accesskey="&showWhenDownloading.accesskey;"
preference="browser.download.manager.showWhenStarting"
onsyncfrompreference="return gMainPane.readShowDownloadsWhenStarting();"/>
<checkbox id="closeWhenDone" label="&closeWhenDone.label;"
accesskey="&closeWhenDone.accesskey;" class="indent"
preference="browser.download.manager.closeWhenDone"/>
<separator class="thin"/>
<radiogroup id="saveWhere"
preference="browser.download.useDownloadDir"
onsyncfrompreference="return gMainPane.readUseDownloadDir();">

View File

@ -9,6 +9,9 @@ function debug(msg) {
}
let Cu = Components.utils;
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
@ -120,6 +123,48 @@ let MessageListener = {
}
};
/**
* If session data must be collected synchronously, we do it via
* method calls to this object (rather than via messages to
* MessageListener). When using multiple processes, these methods run
* in the content process, but the parent synchronously waits on them
* using cross-process object wrappers. Without multiple processes, we
* still use this code for synchronous collection.
*/
let SyncHandler = {
init: function () {
// Send this object as a CPOW to chrome. In single-process mode,
// the synchronous send ensures that the handler object is
// available in SessionStore.jsm immediately upon loading
// content-sessionStore.js.
sendSyncMessage("SessionStore:setupSyncHandler", {}, {handler: this});
},
collectSessionHistory: function (includePrivateData) {
let history = SessionHistory.read(docShell);
if ("index" in history) {
let tabIndex = history.index - 1;
TextAndScrollData.updateFrame(history.entries[tabIndex],
content,
docShell.isAppTab,
{includePrivateData: includePrivateData});
}
return history;
},
collectSessionStorage: function () {
return SessionStorage.serialize(docShell);
},
collectDocShellCapabilities: function () {
return DocShellCapabilities.collect(docShell);
},
collectPageStyle: function () {
return PageStyle.collect(docShell);
},
};
let ProgressListener = {
init: function() {
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
@ -140,4 +185,5 @@ let ProgressListener = {
EventListener.init();
MessageListener.init();
SyncHandler.init();
ProgressListener.init();

View File

@ -12,17 +12,14 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
"resource:///modules/sessionstore/Utils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
const MAX_EXPIRY = Math.pow(2, 62);
// Creates a new nsIURI object.
function makeURI(uri) {
return Services.io.newURI(uri, null, null);
}
/**
* The external API implemented by the SessionCookies module.
*/
@ -172,7 +169,7 @@ let SessionCookiesInternal = {
// urls in which case we don't need to do anything.
if (!host && !scheme) {
try {
let uri = makeURI(entry.url);
let uri = Utils.makeURI(entry.url);
host = uri.host;
scheme = uri.scheme;
this._extractHostsFromHostScheme(host, scheme, hosts, checkPrivacy, isPinned);

View File

@ -4,7 +4,7 @@
"use strict";
this.EXPORTED_SYMBOLS = ["_SessionFile"];
this.EXPORTED_SYMBOLS = ["SessionFile"];
/**
* Implementation of all the disk I/O required by the session store.
@ -49,7 +49,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm");
this._SessionFile = {
this.SessionFile = {
/**
* Read the contents of the session file, asynchronously.
*/
@ -100,7 +100,7 @@ this._SessionFile = {
}
};
Object.freeze(_SessionFile);
Object.freeze(SessionFile);
/**
* Utilities for dealing with promises and Task.jsm
@ -224,7 +224,7 @@ let SessionFileInternal = {
write: function (aData) {
if (this._isClosed) {
return Promise.reject(new Error("_SessionFile is closed"));
return Promise.reject(new Error("SessionFile is closed"));
}
let refObj = {};
@ -311,5 +311,5 @@ let SessionWorker = (function () {
AsyncShutdown.profileBeforeChange.addBlocker(
"SessionFile: Finish writing the latest sessionstore.js",
function() {
return _SessionFile._latestWrite;
return SessionFile._latestWrite;
});

View File

@ -109,7 +109,7 @@ let SessionMigration = {
return Task.spawn(function() {
let inState = yield SessionMigrationInternal.readState(aFromPath);
let outState = SessionMigrationInternal.convertState(inState);
// Unfortunately, we can't use SessionStore's own _SessionFile to
// Unfortunately, we can't use SessionStore's own SessionFile to
// write out the data because it has a dependency on the profile dir
// being known. When the migration runs, there is no guarantee that
// that's true.

View File

@ -18,8 +18,8 @@ Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
"resource:///modules/sessionstore/_SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
// Minimal interval between two save operations (in milliseconds).
XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
@ -290,7 +290,7 @@ let SessionSaverInternal = {
// Write (atomically) to a session file, using a tmp file. Once the session
// file is successfully updated, save the time stamp of the last save and
// notify the observers.
_SessionFile.write(data).then(() => {
SessionFile.write(data).then(() => {
this.updateLastSaveTime();
notify(null, "sessionstore-state-write-complete");
}, Cu.reportError);

View File

@ -65,7 +65,11 @@ const MESSAGES = [
// The content script tells us that a new page just started loading in a
// browser.
"SessionStore:loadStart"
"SessionStore:loadStart",
// The content script gives us a reference to an object that performs
// synchronous collection of session data.
"SessionStore:setupSyncHandler"
];
// These are tab events that we listen to.
@ -95,41 +99,32 @@ XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
"@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
/**
* Get nsIURI from string
* @param string
* @returns nsIURI
*/
function makeURI(aString) {
return Services.io.newURI(aString, null, null);
}
XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
"resource:///modules/devtools/scratchpad-manager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
"resource:///modules/sessionstore/DocShellCapabilities.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils",
"resource:///modules/sessionstore/DocumentUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
"resource:///modules/sessionstore/Messenger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
"resource:///modules/sessionstore/PageStyle.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
"resource:///modules/sessionstore/SessionSaver.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
"resource:///modules/sessionstore/SessionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
"resource:///modules/sessionstore/SessionCookies.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
"resource:///modules/sessionstore/SessionHistory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TextAndScrollData",
"resource:///modules/sessionstore/TextAndScrollData.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
"resource:///modules/sessionstore/_SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
"resource:///modules/sessionstore/TabAttributes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabState",
"resource:///modules/sessionstore/TabState.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
"resource:///modules/sessionstore/TabStateCache.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
"resource:///modules/sessionstore/Utils.jsm");
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
@ -495,12 +490,12 @@ let SessionStoreInternal = {
return Task.spawn(function task() {
try {
// Perform background backup
yield _SessionFile.createBackupCopy("-" + buildID);
yield SessionFile.createBackupCopy("-" + buildID);
this._prefBranch.setCharPref(PREF_UPGRADE, buildID);
// In case of success, remove previous backup.
yield _SessionFile.removeBackupCopy("-" + latestBackup);
yield SessionFile.removeBackupCopy("-" + latestBackup);
} catch (ex) {
debug("Could not perform upgrade backup " + ex);
debug(ex.stack);
@ -602,6 +597,9 @@ let SessionStoreInternal = {
case "SessionStore:loadStart":
TabStateCache.delete(browser);
break;
case "SessionStore:setupSyncHandler":
TabState.setSyncHandler(browser, aMessage.objects.handler);
break;
default:
debug("received unknown message '" + aMessage.name + "'");
break;
@ -620,17 +618,23 @@ let SessionStoreInternal = {
return;
var win = aEvent.currentTarget.ownerDocument.defaultView;
let browser;
switch (aEvent.type) {
case "load":
// If __SS_restore_data is set, then we need to restore the document
// (form data, scrolling, etc.). This will only happen when a tab is
// first restored.
let browser = aEvent.currentTarget;
browser = aEvent.currentTarget;
TabStateCache.delete(browser);
if (browser.__SS_restore_data)
this.restoreDocument(win, browser, aEvent);
this.onTabLoad(win, browser);
break;
case "SwapDocShells":
browser = aEvent.currentTarget;
let otherBrowser = aEvent.detail;
TabState.onSwapDocShells(browser, otherBrowser);
break;
case "TabOpen":
this.onTabAdd(win, aEvent.originalTarget);
break;
@ -733,7 +737,7 @@ let SessionStoreInternal = {
// _loadState changed from "stopped" to "running". Save the session's
// load state immediately so that crashes happening during startup
// are correctly counted.
_SessionFile.writeLoadStateOnceAfterStartup(STATE_RUNNING_STR);
SessionFile.writeLoadStateOnceAfterStartup(STATE_RUNNING_STR);
}
}
else {
@ -1069,7 +1073,7 @@ let SessionStoreInternal = {
* On purge of session history
*/
onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
_SessionFile.wipe();
SessionFile.wipe();
// If the browser is shutting down, simply return after clearing the
// session data on disk as this notification fires after the
// quit-application notification so the browser is about to exit.
@ -1116,12 +1120,9 @@ let SessionStoreInternal = {
onPurgeDomainData: function ssi_onPurgeDomainData(aData) {
// does a session history entry contain a url for the given domain?
function containsDomain(aEntry) {
try {
if (makeURI(aEntry.url).host.hasRootDomain(aData)) {
return true;
}
if (Utils.hasRootDomain(aEntry.url, aData)) {
return true;
}
catch (ex) { /* url had no host at all */ }
return aEntry.children && aEntry.children.some(containsDomain, this);
}
// remove all closed tabs containing a reference to the given domain
@ -1203,10 +1204,14 @@ let SessionStoreInternal = {
onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
let browser = aTab.linkedBrowser;
browser.addEventListener("load", this, true);
browser.addEventListener("SwapDocShells", this, true);
let mm = browser.messageManager;
MESSAGES.forEach(msg => mm.addMessageListener(msg, this));
// Load the frame script after registering listeners.
mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", false);
if (!aNoNotification) {
this.saveStateDelayed(aWindow);
}
@ -1226,6 +1231,7 @@ let SessionStoreInternal = {
onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
let browser = aTab.linkedBrowser;
browser.removeEventListener("load", this, true);
browser.removeEventListener("SwapDocShells", this, true);
let mm = browser.messageManager;
MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
@ -2543,6 +2549,10 @@ let SessionStoreInternal = {
Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
}
// Any data that's in the process of being collected for this tab will be
// out of date now that we're restoring it.
TabState.dropPendingCollections(tab);
browser.__SS_tabStillLoading = true;
// keep the data around to prevent dataloss in case
@ -2578,7 +2588,7 @@ let SessionStoreInternal = {
// the tab is restored. We'll reset this to about:blank when we try to
// restore the tab to ensure that docshell doeesn't get confused.
if (uri) {
browser.docShell.setCurrentURI(makeURI(uri));
browser.docShell.setCurrentURI(Utils.makeURI(uri));
}
// If the page has a title, set it.
@ -2754,7 +2764,7 @@ let SessionStoreInternal = {
// Reset currentURI. This creates a new session history entry with a new
// doc identifier, so we need to explicitly save and restore the old doc
// identifier (corresponding to the SHEntry at activeIndex) below.
browser.webNavigation.setCurrentURI(makeURI("about:blank"));
browser.webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
// Attach data that will be restored on "load" event, after tab is restored.
if (activeIndex > -1) {
// restore those aspects of the currently active documents which are not
@ -2846,7 +2856,7 @@ let SessionStoreInternal = {
var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
createInstance(Ci.nsISHEntry);
shEntry.setURI(makeURI(aEntry.url));
shEntry.setURI(Utils.makeURI(aEntry.url));
shEntry.setTitle(aEntry.title || aEntry.url);
if (aEntry.subframe)
shEntry.setIsSubFrame(aEntry.subframe || false);
@ -2854,7 +2864,7 @@ let SessionStoreInternal = {
if (aEntry.contentType)
shEntry.contentType = aEntry.contentType;
if (aEntry.referrer)
shEntry.referrerURI = makeURI(aEntry.referrer);
shEntry.referrerURI = Utils.makeURI(aEntry.referrer);
if (aEntry.isSrcdocEntry)
shEntry.srcdocData = aEntry.srcdocData;
@ -4066,52 +4076,6 @@ let DirtyWindows = {
// batch tab-closing operations.
let NumberOfTabsClosedLastPerWindow = new WeakMap();
// A set of tab attributes to persist. We will read a given list of tab
// attributes when collecting tab data and will re-set those attributes when
// the given tab data is restored to a new tab.
let TabAttributes = {
_attrs: new Set(),
// We never want to directly read or write those attributes.
// 'image' should not be accessed directly but handled by using the
// gBrowser.getIcon()/setIcon() methods.
// 'pending' is used internal by sessionstore and managed accordingly.
_skipAttrs: new Set(["image", "pending"]),
persist: function (name) {
if (this._attrs.has(name) || this._skipAttrs.has(name)) {
return false;
}
this._attrs.add(name);
return true;
},
get: function (tab) {
let data = {};
for (let name of this._attrs) {
if (tab.hasAttribute(name)) {
data[name] = tab.getAttribute(name);
}
}
return data;
},
set: function (tab, data = {}) {
// Clear attributes.
for (let name of this._attrs) {
tab.removeAttribute(name);
}
// Set attributes.
for (let name in data) {
tab.setAttribute(name, data[name]);
}
}
};
// This is used to help meter the number of restoring tabs. This is the control
// point for telling the next tab to restore. It gets attached to each gBrowser
// via gBrowser.addTabsProgressListener
@ -4173,363 +4137,6 @@ SessionStoreSHistoryListener.prototype = {
}
}
// see nsPrivateBrowsingService.js
String.prototype.hasRootDomain = function hasRootDomain(aDomain) {
let index = this.indexOf(aDomain);
if (index == -1)
return false;
if (this == aDomain)
return true;
let prevChar = this[index - 1];
return (index == (this.length - aDomain.length)) &&
(prevChar == "." || prevChar == "/");
};
function TabData(obj = null) {
if (obj) {
if (obj instanceof TabData) {
// FIXME: Can we get rid of this?
return obj;
}
for (let [key, value] in Iterator(obj)) {
this[key] = value;
}
}
return this;
}
/**
* Module that contains tab state collection methods.
*/
let TabState = {
// A map (xul:tab -> promise) that keeps track of tabs and
// their promises when collecting tab data asynchronously.
_pendingCollections: new WeakMap(),
/**
* Collect data related to a single tab, asynchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {Promise} A promise that will resolve to a TabData instance.
*/
collect: function (tab) {
if (!tab) {
throw new TypeError("Expecting a tab");
}
// Don't collect if we don't need to.
if (TabStateCache.has(tab)) {
return Promise.resolve(TabStateCache.get(tab));
}
// If the tab hasn't been restored yet, just return the data we
// have saved for it.
let browser = tab.linkedBrowser;
if (!browser.currentURI || (browser.__SS_data && browser.__SS_tabStillLoading)) {
let tabData = new TabData(this._collectBaseTabData(tab));
return Promise.resolve(tabData);
}
let promise = Task.spawn(function task() {
// Collect session history data asynchronously. Also collects
// text and scroll data.
let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
// Collected session storage data asynchronously.
let storage = yield Messenger.send(tab, "SessionStore:collectSessionStorage");
// Collect docShell capabilities asynchronously.
let disallow = yield Messenger.send(tab, "SessionStore:collectDocShellCapabilities");
let pageStyle = yield Messenger.send(tab, "SessionStore:collectPageStyle");
// Collect basic tab data, without session history and storage.
let options = {omitSessionHistory: true,
omitSessionStorage: true,
omitDocShellCapabilities: true};
let tabData = this._collectBaseTabData(tab, options);
// Apply collected data.
tabData.entries = history.entries;
if ("index" in history) {
tabData.index = history.index;
}
if (Object.keys(storage).length) {
tabData.storage = storage;
}
if (disallow.length > 0) {
tabData.disallow = disallow.join(",");
}
if (pageStyle) {
tabData.pageStyle = pageStyle;
}
// If we're still the latest async collection for the given tab and
// the cache hasn't been filled by collect() in the meantime, let's
// fill the cache with the data we received.
if (this._pendingCollections.get(tab) == promise) {
TabStateCache.set(tab, tabData);
this._pendingCollections.delete(tab);
}
throw new Task.Result(tabData);
}.bind(this));
// Save the current promise as the latest asynchronous collection that is
// running. This will be used to check whether the collected data is still
// valid and will be used to fill the tab state cache.
this._pendingCollections.set(tab, promise);
return promise;
},
/**
* Collect data related to a single tab, synchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {TabData} An object with the data for this tab. If the
* tab has not been invalidated since the last call to
* collectSync(aTab), the same object is returned.
*/
collectSync: function (tab) {
if (!tab) {
throw new TypeError("Expecting a tab");
}
if (TabStateCache.has(tab)) {
return TabStateCache.get(tab);
}
let tabData = this._collectBaseTabData(tab);
if (this._updateTextAndScrollDataForTab(tab, tabData)) {
TabStateCache.set(tab, tabData);
}
// Prevent all running asynchronous collections from filling the cache.
// Every asynchronous data collection started before a collectSync() call
// can't expect to retrieve different data than the sync call. That's why
// we just fill the cache with the data collected from the sync call and
// discard any data collected asynchronously.
this._pendingCollections.delete(tab);
return tabData;
},
/**
* Collect data related to a single tab, including private data.
* Use with caution.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the data for this tab. This data is never
* cached, it will always be read from the tab and thus be
* up-to-date.
*/
clone: function (tab) {
let options = { includePrivateData: true };
let tabData = this._collectBaseTabData(tab, options);
this._updateTextAndScrollDataForTab(tab, tabData, options);
return tabData;
},
/**
* Collects basic tab data for a given tab.
*
* @param tab
* tabbrowser tab
* @param options
* An object that will be passed to session history and session
* storage data collection methods.
* {omitSessionHistory: true} to skip collecting session history data
* {omitSessionStorage: true} to skip collecting session storage data
* {omitDocShellCapabilities: true} to skip collecting docShell allow* attributes
*
* The omit* options have been introduced to enable us collecting
* those parts of the tab data asynchronously. We will request basic
* tabData without the parts to omit and fill those holes later when
* the content script has responded.
*
* @returns {object} An object with the basic data for this tab.
*/
_collectBaseTabData: function (tab, options = {}) {
let tabData = {entries: [], lastAccessed: tab.lastAccessed };
let browser = tab.linkedBrowser;
if (!browser || !browser.currentURI) {
// can happen when calling this function right after .addTab()
return tabData;
}
if (browser.__SS_data && browser.__SS_tabStillLoading) {
// use the data to be restored when the tab hasn't been completely loaded
tabData = browser.__SS_data;
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// If __SS_extdata is set then we'll use that since it might be newer.
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
// If it exists but is empty then a key was likely deleted. In that case just
// delete extData.
if (tabData.extData && !Object.keys(tabData.extData).length)
delete tabData.extData;
return tabData;
}
// Collection session history data.
if (!options || !options.omitSessionHistory) {
this._collectTabHistory(tab, tabData, options);
}
// If there is a userTypedValue set, then either the user has typed something
// in the URL bar, or a new tab was opened with a URI to load. userTypedClear
// is used to indicate whether the tab was in some sort of loading state with
// userTypedValue.
if (browser.userTypedValue) {
tabData.userTypedValue = browser.userTypedValue;
tabData.userTypedClear = browser.userTypedClear;
} else {
delete tabData.userTypedValue;
delete tabData.userTypedClear;
}
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
if (!options || !options.omitDocShellCapabilities) {
let disallow = DocShellCapabilities.collect(browser.docShell);
if (disallow.length > 0)
tabData.disallow = disallow.join(",");
else if (tabData.disallow)
delete tabData.disallow;
}
// Save tab attributes.
tabData.attributes = TabAttributes.get(tab);
// Store the tab icon.
let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
tabData.image = tabbrowser.getIcon(tab);
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
else if (tabData.extData)
delete tabData.extData;
// Collect DOMSessionStorage data.
if (!options || !options.omitSessionStorage) {
this._collectTabSessionStorage(tab, tabData, options);
}
return tabData;
},
/**
* Collects session history data for a given tab.
*
* @param tab
* tabbrowser tab
* @param tabData
* An object that the session history data will be added to.
* @param options
* {includePrivateData: true} to always include private data
*/
_collectTabHistory: function (tab, tabData, options = {}) {
let includePrivateData = options && options.includePrivateData;
let docShell = tab.linkedBrowser.docShell;
if (docShell instanceof Ci.nsIDocShell) {
let history = SessionHistory.read(docShell, includePrivateData);
tabData.entries = history.entries;
// For blank tabs without any history entries,
// there will not be an 'index' property.
if ("index" in history) {
tabData.index = history.index;
}
}
},
/**
* Collects session history data for a given tab.
*
* @param tab
* tabbrowser tab
* @param tabData
* An object that the session storage data will be added to.
* @param options
* {includePrivateData: true} to always include private data
*/
_collectTabSessionStorage: function (tab, tabData, options = {}) {
let includePrivateData = options && options.includePrivateData;
let docShell = tab.linkedBrowser.docShell;
if (docShell instanceof Ci.nsIDocShell) {
let storageData = SessionStorage.serialize(docShell, includePrivateData)
if (Object.keys(storageData).length) {
tabData.storage = storageData;
}
}
},
/**
* Go through all frames and store the current scroll positions
* and innerHTML content of WYSIWYG editors
*
* @param tab
* tabbrowser tab
* @param tabData
* tabData object to add the information to
* @param options
* An optional object that may contain the following field:
* - includePrivateData: always return privacy sensitive data
* (use with care)
* @return false if data should not be cached because the tab
* has not been fully initialized yet.
*/
_updateTextAndScrollDataForTab: function (tab, tabData, options = null) {
let includePrivateData = options && options.includePrivateData;
let window = tab.ownerDocument.defaultView;
let browser = tab.linkedBrowser;
// we shouldn't update data for incompletely initialized tabs
if (!browser.currentURI
|| (browser.__SS_data && browser.__SS_tabStillLoading)) {
return false;
}
let tabIndex = (tabData.index || tabData.entries.length) - 1;
// entry data needn't exist for tabs just initialized with an incomplete session state
if (!tabData.entries[tabIndex]) {
return false;
}
let selectedPageStyle = PageStyle.collect(browser.docShell);
if (selectedPageStyle) {
tabData.pageStyle = selectedPageStyle;
}
TextAndScrollData.updateFrame(tabData.entries[tabIndex],
browser.contentWindow,
!!tabData.pinned,
{includePrivateData: includePrivateData});
return true;
},
};
// The state from the previous session (after restoring pinned tabs). This
// state is persisted and passed through to the next session during an app
// restart to make the third party add-on warning not trash the deferred

View File

@ -74,14 +74,14 @@ let Agent = {
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
/**
* This method is only intended to be called by _SessionFile.syncRead() and
* This method is only intended to be called by SessionFile.syncRead() and
* can be removed when we're not supporting synchronous SessionStore
* initialization anymore. When sessionstore.js is read from disk
* synchronously the state string must be supplied to the worker manually by
* calling this method.
*/
setInitialState: function (aState) {
// _SessionFile.syncRead() should not be called after startup has finished.
// SessionFile.syncRead() should not be called after startup has finished.
// Thus we also don't support any setInitialState() calls after we already
// wrote the loadState to disk.
if (this.hasWrittenLoadStateOnce) {
@ -89,7 +89,7 @@ let Agent = {
}
// Initial state might have been filled by read() already but yet we might
// be called by _SessionFile.syncRead() before SessionStore.jsm had a chance
// be called by SessionFile.syncRead() before SessionStore.jsm had a chance
// to call writeLoadStateOnceAfterStartup(). It's safe to ignore
// setInitialState() calls if this happens.
if (!this.initialState) {

View File

@ -0,0 +1,68 @@
/* 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";
this.EXPORTED_SYMBOLS = ["TabAttributes"];
// A set of tab attributes to persist. We will read a given list of tab
// attributes when collecting tab data and will re-set those attributes when
// the given tab data is restored to a new tab.
this.TabAttributes = Object.freeze({
persist: function (name) {
return TabAttributesInternal.persist(name);
},
get: function (tab) {
return TabAttributesInternal.get(tab);
},
set: function (tab, data = {}) {
TabAttributesInternal.set(tab, data);
}
});
let TabAttributesInternal = {
_attrs: new Set(),
// We never want to directly read or write those attributes.
// 'image' should not be accessed directly but handled by using the
// gBrowser.getIcon()/setIcon() methods.
// 'pending' is used internal by sessionstore and managed accordingly.
_skipAttrs: new Set(["image", "pending"]),
persist: function (name) {
if (this._attrs.has(name) || this._skipAttrs.has(name)) {
return false;
}
this._attrs.add(name);
return true;
},
get: function (tab) {
let data = {};
for (let name of this._attrs) {
if (tab.hasAttribute(name)) {
data[name] = tab.getAttribute(name);
}
}
return data;
},
set: function (tab, data = {}) {
// Clear attributes.
for (let name of this._attrs) {
tab.removeAttribute(name);
}
// Set attributes.
for (let name in data) {
tab.setAttribute(name, data[name]);
}
}
};

View File

@ -0,0 +1,422 @@
/* 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";
this.EXPORTED_SYMBOLS = ["TabState"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
"resource:///modules/sessionstore/Messenger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
"resource:///modules/sessionstore/TabStateCache.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
"resource:///modules/sessionstore/TabAttributes.jsm");
/**
* Module that contains tab state collection methods.
*/
this.TabState = Object.freeze({
setSyncHandler: function (browser, handler) {
TabStateInternal.setSyncHandler(browser, handler);
},
collect: function (tab) {
return TabStateInternal.collect(tab);
},
collectSync: function (tab) {
return TabStateInternal.collectSync(tab);
},
clone: function (tab) {
return TabStateInternal.clone(tab);
},
dropPendingCollections: function (tab) {
TabStateInternal.dropPendingCollections(tab);
}
});
let TabStateInternal = {
// A map (xul:tab -> promise) that keeps track of tabs and
// their promises when collecting tab data asynchronously.
_pendingCollections: new WeakMap(),
// A map (xul:browser -> handler) that maps a tab to the
// synchronous collection handler object for that tab.
// See SyncHandler in content-sessionStore.js.
_syncHandlers: new WeakMap(),
/**
* Install the sync handler object from a given tab.
*/
setSyncHandler: function (browser, handler) {
this._syncHandlers.set(browser, handler);
},
/**
* When a docshell swap happens, a xul:browser element will be
* associated with a different content-sessionStore.js script
* global. In this case, the sync handler for the element needs to
* be swapped just like the docshell.
*/
onSwapDocShells: function (browser, otherBrowser) {
// Make sure that one or the other of these has a sync handler,
// and let it be |browser|.
if (!this._syncHandlers.has(browser)) {
[browser, otherBrowser] = [otherBrowser, browser];
if (!this._syncHandlers.has(browser)) {
return;
}
}
// At this point, browser is guaranteed to have a sync handler,
// although otherBrowser may not. Perform the swap.
let handler = this._syncHandlers.get(browser);
if (this._syncHandlers.has(otherBrowser)) {
let otherHandler = this._syncHandlers.get(otherBrowser);
this._syncHandlers.set(browser, otherHandler);
this._syncHandlers.set(otherHandler, handler);
} else {
this._syncHandlers.set(otherBrowser, handler);
this._syncHandlers.delete(browser);
}
},
/**
* Collect data related to a single tab, asynchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {Promise} A promise that will resolve to a TabData instance.
*/
collect: function (tab) {
if (!tab) {
throw new TypeError("Expecting a tab");
}
// Don't collect if we don't need to.
if (TabStateCache.has(tab)) {
return Promise.resolve(TabStateCache.get(tab));
}
// If the tab was recently added, or if it's being restored, we
// just collect basic data about it and skip the cache.
if (!this._tabNeedsExtraCollection(tab)) {
let tabData = this._collectBaseTabData(tab);
return Promise.resolve(tabData);
}
let promise = Task.spawn(function task() {
// Collect session history data asynchronously. Also collects
// text and scroll data.
let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
// Collected session storage data asynchronously.
let storage = yield Messenger.send(tab, "SessionStore:collectSessionStorage");
// Collect docShell capabilities asynchronously.
let disallow = yield Messenger.send(tab, "SessionStore:collectDocShellCapabilities");
let pageStyle = yield Messenger.send(tab, "SessionStore:collectPageStyle");
// Collect basic tab data, without session history and storage.
let tabData = this._collectBaseTabData(tab);
// Apply collected data.
tabData.entries = history.entries;
if ("index" in history) {
tabData.index = history.index;
}
if (Object.keys(storage).length) {
tabData.storage = storage;
}
if (disallow.length > 0) {
tabData.disallow = disallow.join(",");
}
if (pageStyle) {
tabData.pageStyle = pageStyle;
}
// If we're still the latest async collection for the given tab and
// the cache hasn't been filled by collect() in the meantime, let's
// fill the cache with the data we received.
if (this._pendingCollections.get(tab) == promise) {
TabStateCache.set(tab, tabData);
this._pendingCollections.delete(tab);
}
throw new Task.Result(tabData);
}.bind(this));
// Save the current promise as the latest asynchronous collection that is
// running. This will be used to check whether the collected data is still
// valid and will be used to fill the tab state cache.
this._pendingCollections.set(tab, promise);
return promise;
},
/**
* Collect data related to a single tab, synchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {TabData} An object with the data for this tab. If the
* tab has not been invalidated since the last call to
* collectSync(aTab), the same object is returned.
*/
collectSync: function (tab) {
if (!tab) {
throw new TypeError("Expecting a tab");
}
if (TabStateCache.has(tab)) {
return TabStateCache.get(tab);
}
let tabData = this._collectSyncUncached(tab);
if (this._tabCachingAllowed(tab)) {
TabStateCache.set(tab, tabData);
}
// Prevent all running asynchronous collections from filling the cache.
// Every asynchronous data collection started before a collectSync() call
// can't expect to retrieve different data than the sync call. That's why
// we just fill the cache with the data collected from the sync call and
// discard any data collected asynchronously.
this.dropPendingCollections(tab);
return tabData;
},
/**
* Drop any pending calls to TabState.collect. These calls will
* continue to run, but they won't store their results in the
* TabStateCache.
*
* @param tab
* tabbrowser tab
*/
dropPendingCollections: function (tab) {
this._pendingCollections.delete(tab);
},
/**
* Collect data related to a single tab, including private data.
* Use with caution.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the data for this tab. This data is never
* cached, it will always be read from the tab and thus be
* up-to-date.
*/
clone: function (tab) {
return this._collectSyncUncached(tab, {includePrivateData: true});
},
/**
* Synchronously collect all session data for a tab. The
* TabStateCache is not consulted, and the resulting data is not put
* in the cache.
*/
_collectSyncUncached: function (tab, options = {}) {
// Collect basic tab data, without session history and storage.
let tabData = this._collectBaseTabData(tab);
// If we don't need any other data, return what we have.
if (!this._tabNeedsExtraCollection(tab)) {
return tabData;
}
// In multiprocess Firefox, there is a small window of time after
// tab creation when we haven't received a sync handler object. In
// this case the tab shouldn't have any history or cookie data, so we
// just return the base data already collected.
if (!this._syncHandlers.has(tab.linkedBrowser)) {
return tabData;
}
let syncHandler = this._syncHandlers.get(tab.linkedBrowser);
let includePrivateData = options && options.includePrivateData;
let history, storage, disallow, pageStyle;
try {
history = syncHandler.collectSessionHistory(includePrivateData);
storage = syncHandler.collectSessionStorage();
disallow = syncHandler.collectDocShellCapabilities();
pageStyle = syncHandler.collectPageStyle();
} catch (e) {
// This may happen if the tab has crashed.
Cu.reportError(e);
return tabData;
}
tabData.entries = history.entries;
if ("index" in history) {
tabData.index = history.index;
}
if (Object.keys(storage).length) {
tabData.storage = storage;
}
if (disallow.length > 0) {
tabData.disallow = disallow.join(",");
}
if (pageStyle) {
tabData.pageStyle = pageStyle;
}
return tabData;
},
/*
* Returns true if the xul:tab element is newly added (i.e., if it's
* showing about:blank with no history).
*/
_tabIsNew: function (tab) {
let browser = tab.linkedBrowser;
return (!browser || !browser.currentURI);
},
/*
* Returns true if the xul:tab element is in the process of being
* restored.
*/
_tabIsRestoring: function (tab) {
let browser = tab.linkedBrowser;
return (browser.__SS_data && browser.__SS_tabStillLoading);
},
/**
* This function returns true if we need to collect history, page
* style, and text and scroll data from the tab. Normally we do. The
* cases when we don't are:
* 1. the tab is about:blank with no history, or
* 2. the tab is waiting to be restored.
*
* @param tab A xul:tab element.
* @returns True if the tab is in the process of being restored.
*/
_tabNeedsExtraCollection: function (tab) {
if (this._tabIsNew(tab)) {
// Tab is about:blank with no history.
return false;
}
if (this._tabIsRestoring(tab)) {
// Tab is waiting to be restored.
return false;
}
// Otherwise we need the extra data.
return true;
},
/*
* Returns true if we should cache the tabData for the given the
* xul:tab element.
*/
_tabCachingAllowed: function (tab) {
if (this._tabIsNew(tab)) {
// No point in caching data for newly created tabs.
return false;
}
if (this._tabIsRestoring(tab)) {
// If the tab is being restored, we just return the data being
// restored. This data may be incomplete (if supplied by
// setBrowserState, for example), so we don't want to cache it.
return false;
}
return true;
},
/**
* Collects basic tab data for a given tab.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the basic data for this tab.
*/
_collectBaseTabData: function (tab) {
let tabData = {entries: [], lastAccessed: tab.lastAccessed };
let browser = tab.linkedBrowser;
if (!browser || !browser.currentURI) {
// can happen when calling this function right after .addTab()
return tabData;
}
if (browser.__SS_data && browser.__SS_tabStillLoading) {
// Use the data to be restored when the tab hasn't been
// completely loaded. We clone the data, since we're updating it
// here and the caller may update it further.
tabData = JSON.parse(JSON.stringify(browser.__SS_data));
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// If __SS_extdata is set then we'll use that since it might be newer.
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
// If it exists but is empty then a key was likely deleted. In that case just
// delete extData.
if (tabData.extData && !Object.keys(tabData.extData).length)
delete tabData.extData;
return tabData;
}
// If there is a userTypedValue set, then either the user has typed something
// in the URL bar, or a new tab was opened with a URI to load. userTypedClear
// is used to indicate whether the tab was in some sort of loading state with
// userTypedValue.
if (browser.userTypedValue) {
tabData.userTypedValue = browser.userTypedValue;
tabData.userTypedClear = browser.userTypedClear;
} else {
delete tabData.userTypedValue;
delete tabData.userTypedClear;
}
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// Save tab attributes.
tabData.attributes = TabAttributes.get(tab);
// Store the tab icon.
let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
tabData.image = tabbrowser.getIcon(tab);
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
else if (tabData.extData)
delete tabData.extData;
return tabData;
}
};

View File

@ -0,0 +1,39 @@
/* 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";
this.EXPORTED_SYMBOLS = ["Utils"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm", this);
this.Utils = Object.freeze({
makeURI: function (url) {
return Services.io.newURI(url, null, null);
},
hasRootDomain: function (url, domain) {
let host;
try {
host = this.makeURI(url).host;
} catch (e) {
// The given URL probably doesn't have a host.
return false;
}
let index = host.indexOf(domain);
if (index == -1)
return false;
if (host == domain)
return true;
let prevChar = host[index - 1];
return (index == (host.length - domain.length)) &&
(prevChar == "." || prevChar == "/");
}
});

View File

@ -13,7 +13,6 @@ EXTRA_COMPONENTS += [
JS_MODULES_PATH = 'modules/sessionstore'
EXTRA_JS_MODULES = [
'_SessionFile.jsm',
'DocShellCapabilities.jsm',
'DocumentUtils.jsm',
'Messenger.jsm',
@ -21,12 +20,16 @@ EXTRA_JS_MODULES = [
'PrivacyLevel.jsm',
'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
'SessionCookies.jsm',
'SessionFile.jsm',
'SessionHistory.jsm',
'SessionMigration.jsm',
'SessionStorage.jsm',
'SessionWorker.js',
'TabAttributes.jsm',
'TabState.jsm',
'TabStateCache.jsm',
'TextAndScrollData.jsm',
'Utils.jsm',
'XPathGenerator.jsm',
]

View File

@ -43,8 +43,8 @@ Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
"resource:///modules/sessionstore/_SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
const STATE_RUNNING_STR = "running";
@ -80,7 +80,7 @@ SessionStartup.prototype = {
return;
}
_SessionFile.read().then(
SessionFile.read().then(
this._onSessionFileRead.bind(this),
Cu.reportError
);
@ -284,7 +284,7 @@ SessionStartup.prototype = {
// Initialization is complete, nothing else to do
return;
}
let contents = _SessionFile.syncRead();
let contents = SessionFile.syncRead();
this._onSessionFileRead(contents);
} catch(ex) {
debug("ensureInitialized: could not read session " + ex + ", " + ex.stack);

View File

@ -5,7 +5,7 @@
function test() {
/** Test for Bug 484108 **/
waitForExplicitFinish();
requestLongerTimeout(4);
requestLongerTimeout(5);
// builds the tests state based on a few parameters
function buildTestState(num, selected, hidden, pinned) {
@ -162,48 +162,54 @@ function test() {
return;
}
info ("Starting test " + (++testIndex));
let test = tests.shift();
let state = buildTestState(test.totalTabs, test.selectedTab,
test.hiddenTabs, test.pinnedTabs);
let tabbarWidth = Math.floor((test.shownTabs - 0.5) * tabMinWidth);
let win = openDialog(location, "_blank", "chrome,all,dialog=no");
let tabsRestored = [];
let wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
wu.garbageCollect();
win.addEventListener("SSTabRestoring", function onSSTabRestoring(aEvent) {
let tab = aEvent.originalTarget;
let tabLink = tab.linkedBrowser.currentURI.spec;
let tabIndex =
tabLink.substring(tabLink.indexOf("?t=") + 3, tabLink.length);
setTimeout(function() {
info ("Starting test " + (++testIndex));
let test = tests.shift();
let state = buildTestState(test.totalTabs, test.selectedTab,
test.hiddenTabs, test.pinnedTabs);
let tabbarWidth = Math.floor((test.shownTabs - 0.5) * tabMinWidth);
let win = openDialog(location, "_blank", "chrome,all,dialog=no");
let tabsRestored = [];
// we need to compare with the tab's restoring index, no with the
// position index, since the pinned tabs change the positions in the
// tabbar.
tabsRestored.push(tabIndex);
win.addEventListener("SSTabRestoring", function onSSTabRestoring(aEvent) {
let tab = aEvent.originalTarget;
let tabLink = tab.linkedBrowser.currentURI.spec;
let tabIndex =
tabLink.substring(tabLink.indexOf("?t=") + 3, tabLink.length);
if (tabsRestored.length < state.windows[0].tabs.length)
return;
// we need to compare with the tab's restoring index, no with the
// position index, since the pinned tabs change the positions in the
// tabbar.
tabsRestored.push(tabIndex);
// all of the tabs should be restoring or restored by now
is(tabsRestored.length, state.windows[0].tabs.length,
"Test #" + testIndex + ": Number of restored tabs is as expected");
if (tabsRestored.length < state.windows[0].tabs.length)
return;
is(tabsRestored.join(" "), test.order.join(" "),
"Test #" + testIndex + ": 'visible' tabs restored first");
// all of the tabs should be restoring or restored by now
is(tabsRestored.length, state.windows[0].tabs.length,
"Test #" + testIndex + ": Number of restored tabs is as expected");
// cleanup
win.removeEventListener("SSTabRestoring", onSSTabRestoring, false);
win.close();
executeSoon(runNextTest);
}, false);
is(tabsRestored.join(" "), test.order.join(" "),
"Test #" + testIndex + ": 'visible' tabs restored first");
whenWindowLoaded(win, function(aEvent) {
let extent =
win.outerWidth - win.gBrowser.tabContainer.mTabstrip.scrollClientSize;
let windowWidth = tabbarWidth + extent;
win.resizeTo(windowWidth, win.outerHeight);
ss.setWindowState(win, JSON.stringify(state), true);
});
// cleanup
win.removeEventListener("SSTabRestoring", onSSTabRestoring, false);
win.close();
executeSoon(runNextTest);
}, false);
whenWindowLoaded(win, function(aEvent) {
let extent =
win.outerWidth - win.gBrowser.tabContainer.mTabstrip.scrollClientSize;
let windowWidth = tabbarWidth + extent;
win.resizeTo(windowWidth, win.outerHeight);
ss.setWindowState(win, JSON.stringify(state), true);
});
}, 1000);
};
runNextTest();

View File

@ -7,9 +7,9 @@
let tmp = {};
Cu.import("resource://gre/modules/osfile.jsm", tmp);
Cu.import("resource:///modules/sessionstore/_SessionFile.jsm", tmp);
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp);
const {OS, _SessionFile} = tmp;
const {OS, SessionFile} = tmp;
const PREF_SS_INTERVAL = "browser.sessionstore.interval";
// Full paths for sessionstore.js and sessionstore.bak.
@ -100,29 +100,29 @@ function testReadBackup() {
array = yield OS.File.read(path);
gSSData = gDecoder.decode(array);
// Read sessionstore.js with _SessionFile.read.
let ssDataRead = yield _SessionFile.read();
is(ssDataRead, gSSData, "_SessionFile.read read sessionstore.js correctly.");
// Read sessionstore.js with SessionFile.read.
let ssDataRead = yield SessionFile.read();
is(ssDataRead, gSSData, "SessionFile.read read sessionstore.js correctly.");
// Read sessionstore.js with _SessionFile.syncRead.
ssDataRead = _SessionFile.syncRead();
// Read sessionstore.js with SessionFile.syncRead.
ssDataRead = SessionFile.syncRead();
is(ssDataRead, gSSData,
"_SessionFile.syncRead read sessionstore.js correctly.");
"SessionFile.syncRead read sessionstore.js correctly.");
// Remove sessionstore.js to test fallback onto sessionstore.bak.
yield OS.File.remove(path);
ssExists = yield OS.File.exists(path);
ok(!ssExists, "sessionstore.js should be removed now.");
// Read sessionstore.bak with _SessionFile.read.
ssDataRead = yield _SessionFile.read();
// Read sessionstore.bak with SessionFile.read.
ssDataRead = yield SessionFile.read();
is(ssDataRead, gSSBakData,
"_SessionFile.read read sessionstore.bak correctly.");
"SessionFile.read read sessionstore.bak correctly.");
// Read sessionstore.bak with _SessionFile.syncRead.
ssDataRead = _SessionFile.syncRead();
// Read sessionstore.bak with SessionFile.syncRead.
ssDataRead = SessionFile.syncRead();
is(ssDataRead, gSSBakData,
"_SessionFile.syncRead read sessionstore.bak correctly.");
"SessionFile.syncRead read sessionstore.bak correctly.");
nextTest(testBackupUnchanged);
}

View File

@ -7,7 +7,7 @@ Cu.import("resource://gre/modules/osfile.jsm");
function run_test() {
do_get_profile();
Cu.import("resource:///modules/sessionstore/_SessionFile.jsm", toplevel);
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", toplevel);
pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
run_next_test();
}
@ -19,7 +19,7 @@ function pathBackup(ext) {
// Ensure that things proceed smoothly if there is no file to back up
add_task(function test_nothing_to_backup() {
yield _SessionFile.createBackupCopy("");
yield SessionFile.createBackupCopy("");
});
// Create a file, back it up, remove it
@ -29,14 +29,14 @@ add_task(function test_do_backup() {
yield OS.File.writeAtomic(pathStore, content, {tmpPath: pathStore + ".tmp"});
do_print("Ensuring that the backup is created");
yield _SessionFile.createBackupCopy(ext);
yield SessionFile.createBackupCopy(ext);
do_check_true((yield OS.File.exists(pathBackup(ext))));
let data = yield OS.File.read(pathBackup(ext));
do_check_eq((new TextDecoder()).decode(data), content);
do_print("Ensuring that we can remove the backup");
yield _SessionFile.removeBackupCopy(ext);
yield SessionFile.removeBackupCopy(ext);
do_check_false((yield OS.File.exists(pathBackup(ext))));
});

View File

@ -6,7 +6,7 @@ Cu.import("resource://gre/modules/osfile.jsm");
function run_test() {
let profd = do_get_profile();
Cu.import("resource:///modules/sessionstore/_SessionFile.jsm", toplevel);
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", toplevel);
decoder = new TextDecoder();
pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
pathBackup = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak");
@ -25,7 +25,7 @@ add_task(function test_first_write_backup() {
let initial_content = decoder.decode(yield OS.File.read(pathStore));
do_check_true(!(yield OS.File.exists(pathBackup)));
yield _SessionFile.write(content);
yield SessionFile.write(content);
do_check_true(yield OS.File.exists(pathBackup));
let backup_content = decoder.decode(yield OS.File.read(pathBackup));
@ -38,7 +38,7 @@ add_task(function test_second_write_no_backup() {
let initial_content = decoder.decode(yield OS.File.read(pathStore));
let initial_backup_content = decoder.decode(yield OS.File.read(pathBackup));
yield _SessionFile.write(content);
yield SessionFile.write(content);
let written_content = decoder.decode(yield OS.File.read(pathStore));
do_check_eq(content, written_content);

View File

@ -804,6 +804,8 @@ let TabItems = {
AllTabs.unregister(name, this._eventListeners[name]);
}
this.items.forEach(function(tabItem) {
delete tabItem.tab._tabViewTabItem;
for (let x in tabItem) {
if (typeof tabItem[x] == "object")
tabItem[x] = null;

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