mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 20:35:50 +00:00
Merge m-c to elm
This commit is contained in:
commit
e16fe14d12
2
CLOBBER
2
CLOBBER
@ -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
|
||||
|
20
Makefile.in
20
Makefile.in
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -9,4 +9,3 @@ endif
|
||||
ifeq (,$(filter aurora beta release esr,$(MOZ_UPDATE_CHANNEL)))
|
||||
A11Y_LOG = 1
|
||||
endif
|
||||
export A11Y_LOG
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
|
134
accessible/src/xpcom/xpcAccessibleSelectable.cpp
Normal file
134
accessible/src/xpcom/xpcAccessibleSelectable.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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;
|
||||
}
|
41
accessible/src/xpcom/xpcAccessibleSelectable.h
Normal file
41
accessible/src/xpcom/xpcAccessibleSelectable.h
Normal 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
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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, [ ]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
||||
|
@ -636,7 +636,7 @@ let FormAssistant = {
|
||||
},
|
||||
|
||||
showKeyboard: function fa_showKeyboard(target) {
|
||||
if (this.isKeyboardOpened)
|
||||
if (this.focusedElement === target)
|
||||
return;
|
||||
|
||||
if (target instanceof HTMLOptionElement)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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");
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "d2dbad943faf566fe36dbe79086127da837af6a3",
|
||||
"revision": "e9d3946c6e4c26c60f67b8efac40e14785b634d3",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -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"
|
||||
],
|
||||
|
@ -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)\" \
|
||||
|
@ -10,3 +10,7 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
SOURCES += [
|
||||
'run-b2g.cpp',
|
||||
]
|
||||
else:
|
||||
SOURCES += [
|
||||
'run-b2g.c',
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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}">
|
||||
|
@ -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);
|
||||
|
@ -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 := ""
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
// ----------
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -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":
|
||||
|
31
browser/base/content/newtab/preloaderContent.js
Normal file
31
browser/base/content/newtab/preloaderContent.js
Normal 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");
|
||||
}
|
||||
|
||||
})();
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -95,5 +95,6 @@ function part8() {
|
||||
ok(objLoadingContent.activated, "plugin should be activated now");
|
||||
|
||||
gNewWindow.close();
|
||||
gNewWindow = null;
|
||||
finish();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 () {
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
14
browser/base/content/test/general/plugin_syncRemoved.html
Normal file
14
browser/base/content/test/general/plugin_syncRemoved.html
Normal 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>
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
},
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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();">
|
||||
|
@ -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.
|
||||
|
@ -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();">
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
});
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
68
browser/components/sessionstore/src/TabAttributes.jsm
Normal file
68
browser/components/sessionstore/src/TabAttributes.jsm
Normal 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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
422
browser/components/sessionstore/src/TabState.jsm
Normal file
422
browser/components/sessionstore/src/TabState.jsm
Normal 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;
|
||||
}
|
||||
};
|
39
browser/components/sessionstore/src/Utils.jsm
Normal file
39
browser/components/sessionstore/src/Utils.jsm
Normal 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 == "/");
|
||||
}
|
||||
});
|
@ -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',
|
||||
]
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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))));
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user