mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Merge last green changeset from inbound to mozilla-central
This commit is contained in:
commit
f2c7a1ea2b
2
.gitignore
vendored
2
.gitignore
vendored
@ -23,7 +23,7 @@ ID
|
||||
security/manager/.nss.checkout
|
||||
|
||||
# Build directories
|
||||
obj*/
|
||||
/obj*/
|
||||
|
||||
# Build directories for js shell
|
||||
*/_DBG.OBJ/
|
||||
|
@ -195,12 +195,6 @@ public:
|
||||
*/
|
||||
virtual bool IsPrimaryForNode() const;
|
||||
|
||||
/**
|
||||
* Return the string bundle
|
||||
*/
|
||||
static nsIStringBundle* GetStringBundle()
|
||||
{ return gStringBundle; }
|
||||
|
||||
protected:
|
||||
nsPresContext* GetPresContext();
|
||||
|
||||
|
@ -395,7 +395,7 @@ nsAccessibilityService::CreateHTMLObjectFrameAccessible(nsObjectFrame* aFrame,
|
||||
nsCString plugId;
|
||||
nsresult rv = pluginInstance->GetValueFromPlugin(
|
||||
NPPVpluginNativeAccessibleAtkPlugId, &plugId);
|
||||
if (NS_SUCCEEDED(rv) && !plugId.IsVoid()) {
|
||||
if (NS_SUCCEEDED(rv) && !plugId.IsEmpty()) {
|
||||
AtkSocketAccessible* socketAccessible =
|
||||
new AtkSocketAccessible(aContent, weakShell, plugId);
|
||||
|
||||
|
@ -579,16 +579,13 @@ nsAccessible::GetIndexInParent(PRInt32 *aIndexInParent)
|
||||
return *aIndexInParent != -1 ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult nsAccessible::GetTranslatedString(const nsAString& aKey, nsAString& aStringOut)
|
||||
void
|
||||
nsAccessible::TranslateString(const nsAString& aKey, nsAString& aStringOut)
|
||||
{
|
||||
nsXPIDLString xsValue;
|
||||
|
||||
if (!gStringBundle ||
|
||||
NS_FAILED(gStringBundle->GetStringFromName(PromiseFlatString(aKey).get(), getter_Copies(xsValue))))
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
gStringBundle->GetStringFromName(PromiseFlatString(aKey).get(), getter_Copies(xsValue));
|
||||
aStringOut.Assign(xsValue);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PRUint64
|
||||
@ -1896,7 +1893,8 @@ nsAccessible::GetActionDescription(PRUint8 aIndex, nsAString& aDescription)
|
||||
nsresult rv = GetActionName(aIndex, name);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return GetTranslatedString(name, aDescription);
|
||||
TranslateString(name, aDescription);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// void doAction(in PRUint8 index)
|
||||
|
@ -611,6 +611,11 @@ public:
|
||||
*/
|
||||
virtual nsAccessible* ContainerWidget() const;
|
||||
|
||||
/**
|
||||
* Return the localized string for the given key.
|
||||
*/
|
||||
static void TranslateString(const nsAString& aKey, nsAString& aStringOut);
|
||||
|
||||
protected:
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@ -703,7 +708,6 @@ protected:
|
||||
|
||||
// helper method to verify frames
|
||||
static nsresult GetFullKeyName(const nsAString& aModifierName, const nsAString& aKeyName, nsAString& aStringOut);
|
||||
static nsresult GetTranslatedString(const nsAString& aKey, nsAString& aStringOut);
|
||||
|
||||
/**
|
||||
* Return an accessible for the given DOM node, or if that node isn't
|
||||
|
60
accessible/src/mac/MacUtils.h
Normal file
60
accessible/src/mac/MacUtils.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2012
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Original Author: Hubert Figuiere <hub@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef _MacUtils_H_
|
||||
#define _MacUtils_H_
|
||||
|
||||
@class NSString;
|
||||
class nsString;
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
namespace utils {
|
||||
|
||||
/**
|
||||
* Get a localized string from the string bundle.
|
||||
* Return nil if not found.
|
||||
*/
|
||||
NSString* LocalizedString(const nsString& aString);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
66
accessible/src/mac/MacUtils.mm
Normal file
66
accessible/src/mac/MacUtils.mm
Normal file
@ -0,0 +1,66 @@
|
||||
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2012
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Original Author: Hubert Figuiere <hub@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#import "MacUtils.h"
|
||||
|
||||
#include "nsAccessible.h"
|
||||
|
||||
#include "nsCocoaUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
namespace utils {
|
||||
|
||||
/**
|
||||
* Get a localized string from the a11y string bundle.
|
||||
* Return nil if not found.
|
||||
*/
|
||||
NSString*
|
||||
LocalizedString(const nsString& aString)
|
||||
{
|
||||
nsString text;
|
||||
|
||||
nsAccessible::TranslateString(aString, text);
|
||||
|
||||
return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -57,8 +57,9 @@ CMMSRCS = nsAccessNodeWrap.mm \
|
||||
mozActionElements.mm \
|
||||
mozTextAccessible.mm \
|
||||
mozHTMLAccessible.mm \
|
||||
MacUtils.mm \
|
||||
$(NULL)
|
||||
|
||||
|
||||
|
||||
EXPORTS = \
|
||||
nsAccessNodeWrap.h \
|
||||
|
@ -38,7 +38,7 @@
|
||||
|
||||
#import "mozAccessible.h"
|
||||
|
||||
// to get the mozView formal protocol, that all gecko's ChildViews implement.
|
||||
#import "MacUtils.h"
|
||||
#import "mozView.h"
|
||||
#import "nsRoleMap.h"
|
||||
|
||||
@ -127,24 +127,6 @@ GetNativeFromGeckoAccessible(nsIAccessible *anAccessible)
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a localized string from the string bundle.
|
||||
* Return nil is not found.
|
||||
*/
|
||||
static NSString*
|
||||
GetLocalizedString(const nsString& aString)
|
||||
{
|
||||
if (!nsAccessNode::GetStringBundle())
|
||||
return nil;
|
||||
|
||||
nsXPIDLString text;
|
||||
nsresult rv = nsAccessNode::GetStringBundle()->GetStringFromName(aString.get(),
|
||||
getter_Copies(text));
|
||||
NS_ENSURE_SUCCESS(rv, nil);
|
||||
|
||||
return !text.IsEmpty() ? nsCocoaUtils::ToNSString(text) : nil;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation mozAccessible
|
||||
@ -219,6 +201,9 @@ GetLocalizedString(const nsString& aString)
|
||||
NSAccessibilityTitleUIElementAttribute,
|
||||
NSAccessibilityTopLevelUIElementAttribute,
|
||||
NSAccessibilityDescriptionAttribute,
|
||||
#if DEBUG
|
||||
@"AXMozDescription",
|
||||
#endif
|
||||
nil];
|
||||
}
|
||||
|
||||
@ -233,6 +218,11 @@ GetLocalizedString(const nsString& aString)
|
||||
|
||||
if (mIsExpired)
|
||||
return nil;
|
||||
|
||||
#if DEBUG
|
||||
if ([attribute isEqualToString:@"AXMozDescription"])
|
||||
return [NSString stringWithFormat:@"role = %u", mRole];
|
||||
#endif
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
|
||||
return [self children];
|
||||
@ -254,8 +244,8 @@ GetLocalizedString(const nsString& aString)
|
||||
if ([attribute isEqualToString:NSAccessibilityValueAttribute])
|
||||
return [self value];
|
||||
if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
|
||||
if (mRole == roles::INTERNAL_FRAME || mRole == roles::DOCUMENT_FRAME)
|
||||
return GetLocalizedString(NS_LITERAL_STRING("htmlContent")) ? : @"HTML Content";
|
||||
if (mRole == roles::DOCUMENT)
|
||||
return utils::LocalizedString(NS_LITERAL_STRING("htmlContent"));
|
||||
|
||||
return NSAccessibilityRoleDescription([self role], nil);
|
||||
}
|
||||
|
@ -43,6 +43,7 @@
|
||||
|
||||
@interface mozButtonAccessible : mozAccessible
|
||||
- (void)click;
|
||||
- (BOOL)isTab;
|
||||
@end
|
||||
|
||||
@interface mozCheckboxAccessible : mozButtonAccessible
|
||||
@ -53,3 +54,11 @@
|
||||
/* Used for buttons that may pop up a menu. */
|
||||
@interface mozPopupButtonAccessible : mozButtonAccessible
|
||||
@end
|
||||
|
||||
/* Class for tabs - not individual tabs */
|
||||
@interface mozTabsAccessible : mozAccessible
|
||||
{
|
||||
NSMutableArray* mTabs;
|
||||
}
|
||||
-(id)tabs;
|
||||
@end
|
||||
|
@ -37,7 +37,11 @@
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#import "mozActionElements.h"
|
||||
|
||||
#import "MacUtils.h"
|
||||
|
||||
#import "nsIAccessible.h"
|
||||
#import "nsXULTabAccessible.h"
|
||||
|
||||
#include "nsObjCExceptions.h"
|
||||
|
||||
@ -60,6 +64,7 @@ enum CheckboxValue {
|
||||
if (!attributes) {
|
||||
attributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required
|
||||
NSAccessibilityRoleAttribute, // required
|
||||
NSAccessibilityRoleDescriptionAttribute,
|
||||
NSAccessibilityPositionAttribute, // required
|
||||
NSAccessibilitySizeAttribute, // required
|
||||
NSAccessibilityWindowAttribute, // required
|
||||
@ -83,6 +88,13 @@ enum CheckboxValue {
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
|
||||
return nil;
|
||||
if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
|
||||
if ([self isTab])
|
||||
return utils::LocalizedString(NS_LITERAL_STRING("tab"));
|
||||
|
||||
return NSAccessibilityRoleDescription([self role], nil);
|
||||
}
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
@ -109,9 +121,13 @@ enum CheckboxValue {
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
if ([action isEqualToString:NSAccessibilityPressAction])
|
||||
if ([action isEqualToString:NSAccessibilityPressAction]) {
|
||||
if ([self isTab])
|
||||
return utils::LocalizedString(NS_LITERAL_STRING("switch"));
|
||||
|
||||
return @"press button"; // XXX: localize this later?
|
||||
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
@ -134,6 +150,11 @@ enum CheckboxValue {
|
||||
mGeckoAccessible->DoAction(0);
|
||||
}
|
||||
|
||||
- (BOOL)isTab
|
||||
{
|
||||
return (mGeckoAccessible && (mGeckoAccessible->Role() == roles::PAGETAB));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation mozCheckboxAccessible
|
||||
@ -258,3 +279,85 @@ enum CheckboxValue {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation mozTabsAccessible
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[mTabs release];
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSArray*)accessibilityAttributeNames
|
||||
{
|
||||
// standard attributes that are shared and supported by root accessible (AXMain) elements.
|
||||
static NSMutableArray* attributes = nil;
|
||||
|
||||
if (!attributes) {
|
||||
attributes = [[super accessibilityAttributeNames] mutableCopy];
|
||||
[attributes addObject:NSAccessibilityContentsAttribute];
|
||||
[attributes addObject:NSAccessibilityTabsAttribute];
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString *)attribute
|
||||
{
|
||||
if ([attribute isEqualToString:NSAccessibilityContentsAttribute])
|
||||
return [super children];
|
||||
if ([attribute isEqualToString:NSAccessibilityTabsAttribute])
|
||||
return [self tabs];
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected tab (the mozAccessible)
|
||||
*/
|
||||
- (id)value
|
||||
{
|
||||
if (!mGeckoAccessible)
|
||||
return nil;
|
||||
|
||||
nsAccessible* accessible = mGeckoAccessible->GetSelectedItem(0);
|
||||
if (!accessible)
|
||||
return nil;
|
||||
|
||||
mozAccessible* nativeAcc = nil;
|
||||
nsresult rv = accessible->GetNativeInterface((void**)&nativeAcc);
|
||||
NS_ENSURE_SUCCESS(rv, nil);
|
||||
|
||||
return nativeAcc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mozAccessibles that are the tabs.
|
||||
*/
|
||||
- (id)tabs
|
||||
{
|
||||
if (mTabs)
|
||||
return mTabs;
|
||||
|
||||
NSArray* children = [self children];
|
||||
NSEnumerator* enumerator = [children objectEnumerator];
|
||||
mTabs = [[NSMutableArray alloc] init];
|
||||
|
||||
id obj;
|
||||
while ((obj = [enumerator nextObject]))
|
||||
if ([obj isTab])
|
||||
[mTabs addObject:obj];
|
||||
|
||||
return mTabs;
|
||||
}
|
||||
|
||||
- (void)invalidateChildren
|
||||
{
|
||||
[super invalidateChildren];
|
||||
|
||||
[mTabs release];
|
||||
mTabs = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -104,6 +104,9 @@ nsAccessibleWrap::GetNativeType ()
|
||||
[mozButtonAccessible class];
|
||||
}
|
||||
|
||||
case roles::PAGETAB:
|
||||
return [mozButtonAccessible class];
|
||||
|
||||
case roles::CHECKBUTTON:
|
||||
return [mozCheckboxAccessible class];
|
||||
|
||||
@ -113,6 +116,9 @@ nsAccessibleWrap::GetNativeType ()
|
||||
case roles::HEADING:
|
||||
return [mozHeadingAccessible class];
|
||||
|
||||
case roles::PAGETABLIST:
|
||||
return [mozTabsAccessible class];
|
||||
|
||||
case roles::ENTRY:
|
||||
case roles::STATICTEXT:
|
||||
case roles::LABEL:
|
||||
|
@ -52,12 +52,12 @@ static const NSString* AXRoles [] = {
|
||||
NSAccessibilityUnknownRole, // ROLE_CARET. unused on OS X
|
||||
NSAccessibilityWindowRole, // ROLE_ALERT
|
||||
NSAccessibilityWindowRole, // ROLE_WINDOW. irrelevant on OS X; all window a11y is handled by the system.
|
||||
@"AXWebArea", // ROLE_INTERNAL_FRAME
|
||||
NSAccessibilityScrollAreaRole, // ROLE_INTERNAL_FRAME
|
||||
NSAccessibilityMenuRole, // ROLE_MENUPOPUP. the parent of menuitems
|
||||
NSAccessibilityMenuItemRole, // ROLE_MENUITEM.
|
||||
@"AXHelpTag", // ROLE_TOOLTIP. 10.4+ only, so we re-define the constant.
|
||||
NSAccessibilityGroupRole, // ROLE_APPLICATION. unused on OS X. the system will take care of this.
|
||||
NSAccessibilityGroupRole, // ROLE_DOCUMENT
|
||||
@"AXWebArea", // ROLE_DOCUMENT
|
||||
NSAccessibilityGroupRole, // ROLE_PANE
|
||||
NSAccessibilityUnknownRole, // ROLE_CHART
|
||||
NSAccessibilityWindowRole, // ROLE_DIALOG. there's a dialog subrole.
|
||||
@ -79,7 +79,7 @@ static const NSString* AXRoles [] = {
|
||||
NSAccessibilityRowRole, // ROLE_LISTITEM
|
||||
NSAccessibilityOutlineRole, // ROLE_OUTLINE
|
||||
NSAccessibilityRowRole, // ROLE_OUTLINEITEM. XXX: use OutlineRow as subrole.
|
||||
NSAccessibilityGroupRole, // ROLE_PAGETAB
|
||||
NSAccessibilityRadioButtonRole, // ROLE_PAGETAB
|
||||
NSAccessibilityGroupRole, // ROLE_PROPERTYPAGE
|
||||
NSAccessibilityUnknownRole, // ROLE_INDICATOR
|
||||
NSAccessibilityImageRole, // ROLE_GRAPHIC
|
||||
@ -102,7 +102,7 @@ static const NSString* AXRoles [] = {
|
||||
NSAccessibilityMenuButtonRole, // ROLE_BUTTONMENU
|
||||
NSAccessibilityGroupRole, // ROLE_BUTTONDROPDOWNGRID
|
||||
NSAccessibilityUnknownRole, // ROLE_WHITESPACE
|
||||
NSAccessibilityGroupRole, // ROLE_PAGETABLIST
|
||||
NSAccessibilityTabGroupRole, // ROLE_PAGETABLIST
|
||||
NSAccessibilityUnknownRole, // ROLE_CLOCK. unused on OS X
|
||||
NSAccessibilityButtonRole, // ROLE_SPLITBUTTON
|
||||
NSAccessibilityUnknownRole, // ROLE_IPADDRESS
|
||||
@ -146,9 +146,9 @@ static const NSString* AXRoles [] = {
|
||||
NSAccessibilityTextFieldRole, // ROLE_EDITBAR
|
||||
NSAccessibilityTextFieldRole, // ROLE_ENTRY
|
||||
NSAccessibilityStaticTextRole, // ROLE_CAPTION
|
||||
@"AXWebArea", // ROLE_DOCUMENT_FRAME
|
||||
NSAccessibilityScrollAreaRole, // ROLE_DOCUMENT_FRAME
|
||||
@"AXHeading", // ROLE_HEADING
|
||||
NSAccessibilityGroupRole, // ROLE_PAGE
|
||||
NSAccessibilityGroupRole, // ROLE_PAG
|
||||
NSAccessibilityGroupRole, // ROLE_SECTION
|
||||
NSAccessibilityUnknownRole, // ROLE_REDUNDANT_OBJECT
|
||||
NSAccessibilityGroupRole, // ROLE_FORM
|
||||
|
@ -75,6 +75,7 @@ _TEST_FILES =\
|
||||
actions.js \
|
||||
attributes.js \
|
||||
autocomplete.js \
|
||||
browser.js \
|
||||
common.js \
|
||||
events.js \
|
||||
grid.js \
|
||||
|
96
accessible/tests/mochitest/browser.js
Normal file
96
accessible/tests/mochitest/browser.js
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Load the browser with the given url and then invokes the given function.
|
||||
*/
|
||||
function openBrowserWindow(aFunc, aURL)
|
||||
{
|
||||
gBrowserContext.testFunc = aFunc;
|
||||
gBrowserContext.startURL = aURL;
|
||||
|
||||
addLoadEvent(openBrowserWindowIntl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the browser window.
|
||||
*/
|
||||
function closeBrowserWindow()
|
||||
{
|
||||
gBrowserContext.browserWnd.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the browser window object.
|
||||
*/
|
||||
function browserWindow()
|
||||
{
|
||||
return gBrowserContext.browserWnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return tab browser object.
|
||||
*/
|
||||
function tabBrowser()
|
||||
{
|
||||
return browserWindow().gBrowser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return browser element of the current tab.
|
||||
*/
|
||||
function currentBrowser()
|
||||
{
|
||||
return tabBrowser().selectedBrowser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return DOM document of the current tab.
|
||||
*/
|
||||
function currentTabDocument()
|
||||
{
|
||||
return currentBrowser().contentDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return input element of address bar.
|
||||
*/
|
||||
function urlbarInput()
|
||||
{
|
||||
return browserWindow().document.getElementById("urlbar").inputField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return reload button.
|
||||
*/
|
||||
function reloadButton()
|
||||
{
|
||||
return browserWindow().document.getElementById("urlbar-reload-button");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private section
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var gBrowserContext =
|
||||
{
|
||||
browserWnd: null,
|
||||
testFunc: null,
|
||||
startURL: ""
|
||||
};
|
||||
|
||||
function openBrowserWindowIntl()
|
||||
{
|
||||
gBrowserContext.browserWnd =
|
||||
window.openDialog(Services.prefs.getCharPref("browser.chromeURL"),
|
||||
"_blank", "chrome,all,dialog=no",
|
||||
gBrowserContext.startURL);
|
||||
|
||||
addA11yLoadEvent(startBrowserTests, browserWindow());
|
||||
}
|
||||
|
||||
function startBrowserTests()
|
||||
{
|
||||
if (gBrowserContext.startURL) // wait for load
|
||||
addA11yLoadEvent(gBrowserContext.testFunc, currentBrowser().contentWindow);
|
||||
else
|
||||
gBrowserContext.testFunc();
|
||||
}
|
@ -174,7 +174,7 @@ const DO_NOT_FINISH_TEST = 1;
|
||||
* // phase getter: function() {},
|
||||
* //
|
||||
* // * Callback, called to match handled event. *
|
||||
* // match : function() {},
|
||||
* // match : function(aEvent) {},
|
||||
* //
|
||||
* // * Callback, called when event is handled
|
||||
* // check: function(aEvent) {},
|
||||
@ -1340,10 +1340,10 @@ function caretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg)
|
||||
* State change checker.
|
||||
*/
|
||||
function stateChangeChecker(aState, aIsExtraState, aIsEnabled,
|
||||
aTargetOrFunc, aTargetFuncArg)
|
||||
aTargetOrFunc, aTargetFuncArg, aIsAsync)
|
||||
{
|
||||
this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
|
||||
aTargetFuncArg);
|
||||
aTargetFuncArg, aIsAsync);
|
||||
|
||||
this.check = function stateChangeChecker_check(aEvent)
|
||||
{
|
||||
@ -1370,6 +1370,22 @@ function stateChangeChecker(aState, aIsExtraState, aIsEnabled,
|
||||
var unxpdExtraState = aIsEnabled ? 0 : (aIsExtraState ? aState : 0);
|
||||
testStates(event.accessible, state, extraState, unxpdState, unxpdExtraState);
|
||||
}
|
||||
|
||||
this.match = function stateChangeChecker_match(aEvent)
|
||||
{
|
||||
if (aEvent instanceof nsIAccessibleStateChangeEvent) {
|
||||
var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
|
||||
return aEvent.accessible = this.target && scEvent.state == aState;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function asyncStateChangeChecker(aState, aIsExtraState, aIsEnabled,
|
||||
aTargetOrFunc, aTargetFuncArg)
|
||||
{
|
||||
this.__proto__ = new stateChangeChecker(aState, aIsExtraState, aIsEnabled,
|
||||
aTargetOrFunc, aTargetFuncArg, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +45,7 @@ relativesrcdir = accessible/events
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# test_docload.xul, docload_wnd.xul, test_scroll.xul disabled for misusing <tabbrowser> (bug 715857)
|
||||
# test_scroll.xul disabled for misusing <tabbrowser> (bug 715857)
|
||||
|
||||
_TEST_FILES =\
|
||||
docload_wnd.html \
|
||||
@ -61,6 +61,7 @@ _TEST_FILES =\
|
||||
test_coalescence.html \
|
||||
test_contextmenu.html \
|
||||
test_docload.html \
|
||||
test_docload.xul \
|
||||
test_dragndrop.html \
|
||||
test_flush.html \
|
||||
test_focus_aria_activedescendant.html \
|
||||
|
@ -1,275 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
|
||||
<!-- Firefox tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://browser/content/browser.css"
|
||||
type="text/css"?>
|
||||
<!-- SeaMonkey tabbrowser -->
|
||||
<?xml-stylesheet href="chrome://navigator/content/navigator.css"
|
||||
type="text/css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// SimpleTest stuffs
|
||||
|
||||
var gOpenerWnd = window.opener.wrappedJSObject;
|
||||
|
||||
function ok(aCond, aMsg) {
|
||||
gOpenerWnd.SimpleTest.ok(aCond, aMsg);
|
||||
}
|
||||
|
||||
function is(aExpected, aActual, aMsg) {
|
||||
gOpenerWnd.SimpleTest.is(aExpected, aActual, aMsg);
|
||||
}
|
||||
|
||||
function testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
|
||||
aAbsentExtraState)
|
||||
{
|
||||
gOpenerWnd.testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
|
||||
aAbsentExtraState);
|
||||
}
|
||||
|
||||
var invokerChecker = gOpenerWnd.invokerChecker;
|
||||
var asyncInvokerChecker = gOpenerWnd.asyncInvokerChecker;
|
||||
|
||||
const STATE_BUSY = gOpenerWnd.STATE_BUSY;
|
||||
const EVENT_DOCUMENT_LOAD_COMPLETE =
|
||||
gOpenerWnd.EVENT_DOCUMENT_LOAD_COMPLETE;
|
||||
const EVENT_DOCUMENT_RELOAD = gOpenerWnd.EVENT_DOCUMENT_RELOAD;
|
||||
const EVENT_DOCUMENT_LOAD_STOPPED =
|
||||
gOpenerWnd.EVENT_DOCUMENT_LOAD_STOPPED;
|
||||
const EVENT_REORDER = gOpenerWnd.EVENT_REORDER;
|
||||
const EVENT_STATE_CHANGE = gOpenerWnd.EVENT_STATE_CHANGE;
|
||||
const nsIAccessibleStateChangeEvent =
|
||||
gOpenerWnd.nsIAccessibleStateChangeEvent;
|
||||
|
||||
//gOpenerWnd.gA11yEventDumpToConsole = true; // debug
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Hacks to make xul:tabbrowser work.
|
||||
|
||||
var handleDroppedLink = null; // needed for tabbrowser usage
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
var XULBrowserWindow = {
|
||||
isBusy: false,
|
||||
setOverLink: function (link, b) {
|
||||
}
|
||||
};
|
||||
|
||||
gFindBarInitialized = false;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Helpers.
|
||||
|
||||
function getContainer()
|
||||
{
|
||||
var idx = gTabBrowser.tabContainer.selectedIndex;
|
||||
return gTabBrowser.getBrowserAtIndex(idx);
|
||||
}
|
||||
|
||||
function getDocument()
|
||||
{
|
||||
return getContainer().contentDocument;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Invoker checkers.
|
||||
function stateBusyChecker(aIsEnabled)
|
||||
{
|
||||
this.type = EVENT_STATE_CHANGE;
|
||||
this.__defineGetter__("target", getDocument);
|
||||
|
||||
this.check = function stateBusyChecker_check(aEvent)
|
||||
{
|
||||
var event = null;
|
||||
try {
|
||||
var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
|
||||
} catch (e) {
|
||||
ok(false, "State change event was expected");
|
||||
}
|
||||
|
||||
if (!event)
|
||||
return;
|
||||
|
||||
is(event.state, STATE_BUSY, "Wrong state of statechange event.");
|
||||
is(event.isEnabled(), aIsEnabled,
|
||||
"Wrong value of state of statechange event");
|
||||
|
||||
testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0,
|
||||
(aIsEnabled ? 0 : STATE_BUSY), 0);
|
||||
}
|
||||
}
|
||||
|
||||
function documentReloadChecker(aIsFromUserInput)
|
||||
{
|
||||
this.type = EVENT_DOCUMENT_RELOAD;
|
||||
this.__defineGetter__("target", getDocument);
|
||||
|
||||
this.check = function documentReloadChecker_check(aEvent)
|
||||
{
|
||||
is(aEvent.isFromUserInput, aIsFromUserInput,
|
||||
"Wrong value of isFromUserInput");
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Invokers.
|
||||
|
||||
/**
|
||||
* Load URI.
|
||||
*/
|
||||
function loadURIInvoker(aURI)
|
||||
{
|
||||
this.invoke = function loadURIInvoker_invoke()
|
||||
{
|
||||
gTabBrowser.loadURI(aURI);
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
// We don't expect state change event for busy true since things happen
|
||||
// quickly and it's coalesced.
|
||||
new asyncInvokerChecker(EVENT_REORDER, getContainer),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
|
||||
new stateBusyChecker(false)
|
||||
];
|
||||
|
||||
this.getID = function loadURIInvoker_getID()
|
||||
{
|
||||
return "load uri " + aURI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Click reload page button.
|
||||
*/
|
||||
function clickReloadBtnInvoker()
|
||||
{
|
||||
this.invoke = function clickReloadBtnInvoker_invoke()
|
||||
{
|
||||
synthesizeMouse(document.getElementById("reloadbtn"), 5, 5, {});
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new documentReloadChecker(true),
|
||||
new asyncInvokerChecker(EVENT_REORDER, getContainer),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
|
||||
new stateBusyChecker(false)
|
||||
];
|
||||
|
||||
this.getID = function reloadInvoker_getID()
|
||||
{
|
||||
return "click reload page button";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the page.
|
||||
*/
|
||||
function reloadInvoker()
|
||||
{
|
||||
this.invoke = function reloadInvoker_invoke()
|
||||
{
|
||||
gTabBrowser.reload();
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new documentReloadChecker(false),
|
||||
new asyncInvokerChecker(EVENT_REORDER, getContainer),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
|
||||
new stateBusyChecker(false)
|
||||
];
|
||||
|
||||
this.getID = function reloadInvoker_getID()
|
||||
{
|
||||
return "reload page";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load wrong URI what results in error page loading.
|
||||
*/
|
||||
function loadErrorPageInvoker(aURL, aURLDescr)
|
||||
{
|
||||
this.invoke = function loadErrorPageInvoker_invoke()
|
||||
{
|
||||
gTabBrowser.loadURI(aURL);
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
// We don't expect state change for busy true, load stopped events since
|
||||
// things happen quickly and it's coalesced.
|
||||
new asyncInvokerChecker(EVENT_REORDER, getContainer),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
|
||||
new stateBusyChecker(false)
|
||||
];
|
||||
|
||||
this.getID = function loadErrorPageInvoker_getID()
|
||||
{
|
||||
return "load error page: '" + aURLDescr + "'";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
|
||||
var gQueue = null;
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
var gTabBrowser = null;
|
||||
function doTest()
|
||||
{
|
||||
gTabBrowser = document.getElementById("content");
|
||||
|
||||
gQueue = new gOpenerWnd.eventQueue();
|
||||
gQueue.push(new loadURIInvoker("about:"));
|
||||
gQueue.push(new clickReloadBtnInvoker());
|
||||
gQueue.push(new loadURIInvoker("about:mozilla"));
|
||||
gQueue.push(new reloadInvoker());
|
||||
gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri",
|
||||
"Server not found"));
|
||||
gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443",
|
||||
"Untrusted Connection"));
|
||||
|
||||
gQueue.onFinish = function() { window.close(); }
|
||||
gQueue.invoke();
|
||||
}
|
||||
|
||||
gOpenerWnd.addA11yLoadEvent(doTest);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<!-- Hack to make xul:tabbrowser work -->
|
||||
<menubar>
|
||||
<menu label="menu">
|
||||
<menupopup>
|
||||
<menuitem label="close window hook" id="menu_closeWindow"/>
|
||||
<menuitem label="close hook" id="menu_close"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
</menubar>
|
||||
|
||||
<button id="reloadbtn" label="reload page"
|
||||
oncommand="gTabBrowser.reload();"/>
|
||||
|
||||
<toolbar>
|
||||
<tabs id="tabbrowser-tabs" class="tabbrowser-tabs"
|
||||
tabbrowser="content"
|
||||
flex="1"
|
||||
setfocus="false">
|
||||
<tab class="tabbrowser-tab" selected="true"/>
|
||||
</tabs>
|
||||
</toolbar>
|
||||
<tabbrowser id="content"
|
||||
type="content-primary"
|
||||
tabcontainer="tabbrowser-tabs"
|
||||
flex="1"/>
|
||||
|
||||
</window>
|
@ -19,20 +19,172 @@
|
||||
src="../states.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../events.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../browser.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
// var gA11yEventDumpID = "eventdump"; // debug stuff
|
||||
|
||||
function doTest()
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Invoker checkers.
|
||||
function stateBusyChecker(aIsEnabled)
|
||||
{
|
||||
var w = window.openDialog("../events/docload_wnd.xul",
|
||||
"docload_test",
|
||||
"chrome,width=600,height=600");
|
||||
this.type = EVENT_STATE_CHANGE;
|
||||
this.__defineGetter__("target", currentTabDocument);
|
||||
|
||||
this.check = function stateBusyChecker_check(aEvent)
|
||||
{
|
||||
var event = null;
|
||||
try {
|
||||
var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
|
||||
} catch (e) {
|
||||
ok(false, "State change event was expected");
|
||||
}
|
||||
|
||||
if (!event)
|
||||
return;
|
||||
|
||||
is(event.state, STATE_BUSY, "Wrong state of statechange event.");
|
||||
is(event.isEnabled(), aIsEnabled,
|
||||
"Wrong value of state of statechange event");
|
||||
|
||||
testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0,
|
||||
(aIsEnabled ? 0 : STATE_BUSY), 0);
|
||||
}
|
||||
}
|
||||
|
||||
function documentReloadChecker(aIsFromUserInput)
|
||||
{
|
||||
this.type = EVENT_DOCUMENT_RELOAD;
|
||||
this.__defineGetter__("target", currentTabDocument);
|
||||
|
||||
this.check = function documentReloadChecker_check(aEvent)
|
||||
{
|
||||
is(aEvent.isFromUserInput, aIsFromUserInput,
|
||||
"Wrong value of isFromUserInput");
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Invokers.
|
||||
|
||||
/**
|
||||
* Load URI.
|
||||
*/
|
||||
function loadURIInvoker(aURI)
|
||||
{
|
||||
this.invoke = function loadURIInvoker_invoke()
|
||||
{
|
||||
tabBrowser().loadURI(aURI);
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
// We don't expect state change event for busy true since things happen
|
||||
// quickly and it's coalesced.
|
||||
new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
|
||||
new stateBusyChecker(false)
|
||||
];
|
||||
|
||||
this.getID = function loadURIInvoker_getID()
|
||||
{
|
||||
return "load uri " + aURI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the page by F5 (isFromUserInput flag is true).
|
||||
*/
|
||||
function userReloadInvoker()
|
||||
{
|
||||
this.invoke = function userReloadInvoker_invoke()
|
||||
{
|
||||
synthesizeKey("VK_F5", {}, browserWindow());
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new documentReloadChecker(true),
|
||||
new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
|
||||
new stateBusyChecker(false)
|
||||
];
|
||||
|
||||
this.getID = function userReloadInvoker_getID()
|
||||
{
|
||||
return "user reload page";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the page (isFromUserInput flag is false).
|
||||
*/
|
||||
function reloadInvoker()
|
||||
{
|
||||
this.invoke = function reloadInvoker_invoke()
|
||||
{
|
||||
tabBrowser().reload();
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
new documentReloadChecker(false),
|
||||
new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
|
||||
new stateBusyChecker(false)
|
||||
];
|
||||
|
||||
this.getID = function reloadInvoker_getID()
|
||||
{
|
||||
return "reload page";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load wrong URI what results in error page loading.
|
||||
*/
|
||||
function loadErrorPageInvoker(aURL, aURLDescr)
|
||||
{
|
||||
this.invoke = function loadErrorPageInvoker_invoke()
|
||||
{
|
||||
tabBrowser().loadURI(aURL);
|
||||
}
|
||||
|
||||
this.eventSeq = [
|
||||
// We don't expect state change for busy true, load stopped events since
|
||||
// things happen quickly and it's coalesced.
|
||||
new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
|
||||
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
|
||||
new stateBusyChecker(false)
|
||||
];
|
||||
|
||||
this.getID = function loadErrorPageInvoker_getID()
|
||||
{
|
||||
return "load error page: '" + aURLDescr + "'";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
|
||||
gA11yEventDumpToConsole = true; // debug
|
||||
|
||||
var gQueue = null;
|
||||
function doTests()
|
||||
{
|
||||
gQueue = new eventQueue();
|
||||
gQueue.push(new loadURIInvoker("about:"));
|
||||
gQueue.push(new userReloadInvoker());
|
||||
gQueue.push(new loadURIInvoker("about:mozilla"));
|
||||
gQueue.push(new reloadInvoker());
|
||||
gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri",
|
||||
"Server not found"));
|
||||
gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443",
|
||||
"Untrusted Connection"));
|
||||
|
||||
gQueue.onFinish = function() { closeBrowserWindow(); }
|
||||
gQueue.invoke();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(doTest);
|
||||
openBrowserWindow(doTests);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
|
@ -19,40 +19,20 @@
|
||||
src="../states.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../events.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../browser.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Helpers
|
||||
|
||||
function tabBrowser()
|
||||
{
|
||||
return gBrowserWnd.gBrowser;
|
||||
}
|
||||
|
||||
function currentBrowser()
|
||||
{
|
||||
return tabBrowser().selectedBrowser;
|
||||
}
|
||||
|
||||
function currentTabDocument()
|
||||
{
|
||||
return currentBrowser().contentDocument;
|
||||
}
|
||||
|
||||
function inputInDocument()
|
||||
{
|
||||
var tabdoc = currentTabDocument();
|
||||
return tabdoc.getElementById("input");
|
||||
}
|
||||
|
||||
function urlbarInput()
|
||||
{
|
||||
return gBrowserWnd.document.getElementById("urlbar").inputField;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Invokers
|
||||
|
||||
@ -96,22 +76,6 @@
|
||||
var gInputDocURI = "data:text/html,<html><input id='input'></html>";
|
||||
var gButtonDocURI = "data:text/html,<html><input id='input' type='button' value='button'></html>";
|
||||
|
||||
var gBrowserWnd = null;
|
||||
function loadBrowser()
|
||||
{
|
||||
gBrowserWnd = window.openDialog(Services.prefs.getCharPref("browser.chromeURL"),
|
||||
"_blank", "chrome,all,dialog=no", gInputDocURI);
|
||||
|
||||
addA11yLoadEvent(startTests, gBrowserWnd);
|
||||
}
|
||||
|
||||
function startTests()
|
||||
{
|
||||
// Wait for tab load.
|
||||
var browser = gBrowserWnd.gBrowser.selectedBrowser;
|
||||
addA11yLoadEvent(doTests, browser.contentWindow);
|
||||
}
|
||||
|
||||
//gA11yEventDumpToConsole = true; // debug
|
||||
|
||||
var gQueue = null;
|
||||
@ -123,7 +87,8 @@
|
||||
var input = inputInDocument();
|
||||
|
||||
// move focus to input inside tab document
|
||||
gQueue.push(new synthTab(tabDocument, new focusChecker(input), gBrowserWnd));
|
||||
gQueue.push(new synthTab(tabDocument, new focusChecker(input),
|
||||
browserWindow()));
|
||||
|
||||
// open new url, focus moves to new document
|
||||
gQueue.push(new loadURI(gButtonDocURI));
|
||||
@ -132,22 +97,22 @@
|
||||
gQueue.push(new goBack());
|
||||
|
||||
// open new tab, focus moves to urlbar
|
||||
gQueue.push(new synthKey(tabDocument, "t", { ctrlKey: true, window: gBrowserWnd },
|
||||
gQueue.push(new synthKey(tabDocument, "t", { ctrlKey: true, window: browserWindow() },
|
||||
new focusChecker(urlbarInput)));
|
||||
|
||||
// close open tab, focus goes on input of tab document
|
||||
gQueue.push(new synthKey(tabDocument, "w", { ctrlKey: true, window: gBrowserWnd },
|
||||
gQueue.push(new synthKey(tabDocument, "w", { ctrlKey: true, window: browserWindow() },
|
||||
new focusChecker(inputInDocument)));
|
||||
|
||||
gQueue.onFinish = function()
|
||||
{
|
||||
gBrowserWnd.close();
|
||||
closeBrowserWindow();
|
||||
}
|
||||
gQueue.invoke();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(loadBrowser);
|
||||
openBrowserWindow(doTests, gInputDocURI);
|
||||
]]>
|
||||
</script>
|
||||
|
||||
|
@ -37,9 +37,11 @@ const STATE_UNAVAILABLE = nsIAccessibleStates.STATE_UNAVAILABLE;
|
||||
const EXT_STATE_ACTIVE = nsIAccessibleStates.EXT_STATE_ACTIVE;
|
||||
const EXT_STATE_DEFUNCT = nsIAccessibleStates.EXT_STATE_DEFUNCT;
|
||||
const EXT_STATE_EDITABLE = nsIAccessibleStates.EXT_STATE_EDITABLE;
|
||||
const EXT_STATE_ENABLED = nsIAccessibleStates.EXT_STATE_ENABLED;
|
||||
const EXT_STATE_EXPANDABLE = nsIAccessibleStates.EXT_STATE_EXPANDABLE;
|
||||
const EXT_STATE_HORIZONTAL = nsIAccessibleStates.EXT_STATE_HORIZONTAL;
|
||||
const EXT_STATE_MULTI_LINE = nsIAccessibleStates.EXT_STATE_MULTI_LINE;
|
||||
const EXT_STATE_SENSITIVE = nsIAccessibleStates.EXT_STATE_SENSITIVE;
|
||||
const EXT_STATE_SINGLE_LINE = nsIAccessibleStates.EXT_STATE_SINGLE_LINE;
|
||||
const EXT_STATE_STALE = nsIAccessibleStates.EXT_STATE_STALE;
|
||||
const EXT_STATE_SUPPORTS_AUTOCOMPLETION =
|
||||
|
@ -42,17 +42,18 @@
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
//******** define a js object to implement nsITreeView
|
||||
function pageInfoTreeView(copycol)
|
||||
function pageInfoTreeView(treeid, copycol)
|
||||
{
|
||||
// copycol is the index number for the column that we want to add to
|
||||
// the copy-n-paste buffer when the user hits accel-c
|
||||
this.treeid = treeid;
|
||||
this.copycol = copycol;
|
||||
this.rows = 0;
|
||||
this.tree = null;
|
||||
this.data = [ ];
|
||||
this.selection = null;
|
||||
this.sortcol = null;
|
||||
this.sortdir = 0;
|
||||
this.sortcol = -1;
|
||||
this.sortdir = false;
|
||||
}
|
||||
|
||||
pageInfoTreeView.prototype = {
|
||||
@ -121,6 +122,25 @@ pageInfoTreeView.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
onPageMediaSort : function(columnname)
|
||||
{
|
||||
var tree = document.getElementById(this.treeid);
|
||||
var treecol = tree.columns.getNamedColumn(columnname);
|
||||
|
||||
this.sortdir =
|
||||
gTreeUtils.sort(
|
||||
tree,
|
||||
this,
|
||||
this.data,
|
||||
treecol.index,
|
||||
function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); },
|
||||
this.sortcol,
|
||||
this.sortdir
|
||||
);
|
||||
|
||||
this.sortcol = treecol.index;
|
||||
},
|
||||
|
||||
getRowProperties: function(row, prop) { },
|
||||
getCellProperties: function(row, column, prop) { },
|
||||
getColumnProperties: function(column, prop) { },
|
||||
@ -166,9 +186,8 @@ const COPYCOL_META_CONTENT = 1;
|
||||
const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
|
||||
|
||||
// one nsITreeView for each tree in the window
|
||||
var gMetaView = new pageInfoTreeView(COPYCOL_META_CONTENT);
|
||||
var gImageView = new pageInfoTreeView(COPYCOL_IMAGE);
|
||||
|
||||
var gMetaView = new pageInfoTreeView('metatree', COPYCOL_META_CONTENT);
|
||||
var gImageView = new pageInfoTreeView('imagetree', COPYCOL_IMAGE);
|
||||
|
||||
var atomSvc = Components.classes["@mozilla.org/atom-service;1"]
|
||||
.getService(Components.interfaces.nsIAtomService);
|
||||
@ -187,6 +206,44 @@ gImageView.getCellProperties = function(row, col, props) {
|
||||
props.AppendElement(this._ltrAtom);
|
||||
};
|
||||
|
||||
gImageView.getCellText = function(row, column) {
|
||||
var value = this.data[row][column.index];
|
||||
if (column.index == COL_IMAGE_SIZE) {
|
||||
if (value == -1) {
|
||||
return gStrings.unknown;
|
||||
} else {
|
||||
var kbSize = Number(Math.round(value / 1024 * 100) / 100);
|
||||
return gBundle.getFormattedString("mediaFileSize", [kbSize]);
|
||||
}
|
||||
}
|
||||
return value || "";
|
||||
};
|
||||
|
||||
gImageView.onPageMediaSort = function(columnname) {
|
||||
var tree = document.getElementById(this.treeid);
|
||||
var treecol = tree.columns.getNamedColumn(columnname);
|
||||
|
||||
var comparator;
|
||||
if (treecol.index == COL_IMAGE_SIZE) {
|
||||
comparator = function numComparator(a, b) { return a - b; };
|
||||
} else {
|
||||
comparator = function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); };
|
||||
}
|
||||
|
||||
this.sortdir =
|
||||
gTreeUtils.sort(
|
||||
tree,
|
||||
this,
|
||||
this.data,
|
||||
treecol.index,
|
||||
comparator,
|
||||
this.sortcol,
|
||||
this.sortdir
|
||||
);
|
||||
|
||||
this.sortcol = treecol.index;
|
||||
};
|
||||
|
||||
var gImageHash = { };
|
||||
|
||||
// localized strings (will be filled in when the document is loaded)
|
||||
@ -585,15 +642,8 @@ function addImage(url, type, alt, elem, isBg)
|
||||
catch(ex2) { }
|
||||
}
|
||||
|
||||
var sizeText;
|
||||
if (cacheEntryDescriptor) {
|
||||
var pageSize = cacheEntryDescriptor.dataSize;
|
||||
var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
|
||||
sizeText = gBundle.getFormattedString("mediaFileSize", [kbSize]);
|
||||
}
|
||||
else
|
||||
sizeText = gStrings.unknown;
|
||||
gImageView.addRow([url, type, sizeText, alt, 1, elem, isBg]);
|
||||
var dataSize = (cacheEntryDescriptor) ? cacheEntryDescriptor.dataSize : -1;
|
||||
gImageView.addRow([url, type, dataSize, alt, 1, elem, isBg]);
|
||||
|
||||
// Add the observer, only once.
|
||||
if (gImageView.data.length == 1) {
|
||||
@ -700,7 +750,10 @@ function getSelectedImage(tree)
|
||||
return null;
|
||||
|
||||
// Only works if only one item is selected
|
||||
var clickedRow = tree.currentIndex;
|
||||
var clickedRow = tree.view.selection.currentIndex;
|
||||
if (clickedRow == -1)
|
||||
return null;
|
||||
|
||||
// image-node
|
||||
return gImageView.data[clickedRow][COL_IMAGE_NODE];
|
||||
}
|
||||
|
@ -66,6 +66,7 @@
|
||||
|
||||
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
|
||||
<script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/pageinfo/feeds.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
|
||||
@ -192,10 +193,12 @@
|
||||
<tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
|
||||
<treecols>
|
||||
<treecol id="meta-name" label="&generalMetaName;"
|
||||
persist="width" flex="1"/>
|
||||
persist="width" flex="1"
|
||||
onclick="gMetaView.onPageMediaSort('meta-name');"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol id="meta-content" label="&generalMetaContent;"
|
||||
persist="width" flex="4"/>
|
||||
persist="width" flex="4"
|
||||
onclick="gMetaView.onPageMediaSort('meta-content');"/>
|
||||
</treecols>
|
||||
<treechildren flex="1"/>
|
||||
</tree>
|
||||
@ -218,19 +221,24 @@
|
||||
ondragstart="onBeginLinkDrag(event,'image-address','image-alt')">
|
||||
<treecols>
|
||||
<treecol sortSeparators="true" primary="true" persist="width" flex="10"
|
||||
width="10" id="image-address" label="&mediaAddress;"/>
|
||||
width="10" id="image-address" label="&mediaAddress;"
|
||||
onclick="gImageView.onPageMediaSort('image-address');"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol sortSeparators="true" persist="hidden width" flex="2"
|
||||
width="2" id="image-type" label="&mediaType;"/>
|
||||
width="2" id="image-type" label="&mediaType;"
|
||||
onclick="gImageView.onPageMediaSort('image-type');"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol sortSeparators="true" hidden="true" persist="hidden width" flex="2"
|
||||
width="2" id="image-size" label="&mediaSize;"/>
|
||||
width="2" id="image-size" label="&mediaSize;" value="size"
|
||||
onclick="gImageView.onPageMediaSort('image-size');"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4"
|
||||
width="4" id="image-alt" label="&mediaAltHeader;"/>
|
||||
width="4" id="image-alt" label="&mediaAltHeader;"
|
||||
onclick="gImageView.onPageMediaSort('image-alt');"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1"
|
||||
width="1" id="image-count" label="&mediaCount;"/>
|
||||
width="1" id="image-count" label="&mediaCount;"
|
||||
onclick="gImageView.onPageMediaSort('image-count');"/>
|
||||
</treecols>
|
||||
<treechildren flex="1"/>
|
||||
</tree>
|
||||
|
@ -52,7 +52,7 @@
|
||||
persist="screenX screenY width height"
|
||||
onkeypress="gCookiesWindow.onWindowKeyPress(event);">
|
||||
|
||||
<script src="chrome://browser/content/preferences/permissionsutils.js"/>
|
||||
<script src="chrome://global/content/treeUtils.js"/>
|
||||
<script src="chrome://browser/content/preferences/cookies.js"/>
|
||||
|
||||
<stringbundle id="bundlePreferences"
|
||||
|
@ -27,7 +27,6 @@ browser.jar:
|
||||
* content/browser/preferences/main.js
|
||||
* content/browser/preferences/permissions.xul
|
||||
* content/browser/preferences/permissions.js
|
||||
* content/browser/preferences/permissionsutils.js
|
||||
* content/browser/preferences/preferences.xul
|
||||
* content/browser/preferences/privacy.xul
|
||||
* content/browser/preferences/privacy.js
|
||||
|
@ -239,7 +239,8 @@ var gPermissionManager = {
|
||||
++this._view._rowCount;
|
||||
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1);
|
||||
// Re-do the sort, since we inserted this new item at the end.
|
||||
gTreeUtils.sort(this._tree, this._view, this._permissions,
|
||||
gTreeUtils.sort(this._tree, this._view, this._permissions,
|
||||
this._permissionsComparator,
|
||||
this._lastPermissionSortColumn,
|
||||
this._lastPermissionSortAscending);
|
||||
}
|
||||
@ -254,7 +255,8 @@ var gPermissionManager = {
|
||||
// or vice versa, since if we're sorted on status, we may no
|
||||
// longer be in order.
|
||||
if (this._lastPermissionSortColumn.id == "statusCol") {
|
||||
gTreeUtils.sort(this._tree, this._view, this._permissions,
|
||||
gTreeUtils.sort(this._tree, this._view, this._permissions,
|
||||
this._permissionsComparator,
|
||||
this._lastPermissionSortColumn,
|
||||
this._lastPermissionSortAscending);
|
||||
}
|
||||
@ -311,13 +313,19 @@ var gPermissionManager = {
|
||||
|
||||
_lastPermissionSortColumn: "",
|
||||
_lastPermissionSortAscending: false,
|
||||
_permissionsComparator : function (a, b)
|
||||
{
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
},
|
||||
|
||||
|
||||
onPermissionSort: function (aColumn)
|
||||
{
|
||||
this._lastPermissionSortAscending = gTreeUtils.sort(this._tree,
|
||||
this._view,
|
||||
this._permissions,
|
||||
aColumn,
|
||||
aColumn,
|
||||
this._permissionsComparator,
|
||||
this._lastPermissionSortColumn,
|
||||
this._lastPermissionSortAscending);
|
||||
this._lastPermissionSortColumn = aColumn;
|
||||
|
@ -54,7 +54,7 @@
|
||||
persist="screenX screenY width height"
|
||||
onkeypress="gPermissionManager.onWindowKeyPress(event);">
|
||||
|
||||
<script src="chrome://browser/content/preferences/permissionsutils.js"/>
|
||||
<script src="chrome://global/content/treeUtils.js"/>
|
||||
<script src="chrome://browser/content/preferences/permissions.js"/>
|
||||
|
||||
<stringbundle id="bundlePreferences"
|
||||
|
@ -64,8 +64,8 @@ function test() {
|
||||
"window value was set before the window was overwritten");
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
|
||||
// use setTimeout(..., 0) to mirror sss_restoreWindowFeatures
|
||||
setTimeout(function() {
|
||||
// use newWin.setTimeout(..., 0) to mirror sss_restoreWindowFeatures
|
||||
newWin.setTimeout(function() {
|
||||
is(ss.getWindowValue(newWin, uniqueKey), "",
|
||||
"window value was implicitly cleared");
|
||||
|
||||
@ -78,7 +78,7 @@ function test() {
|
||||
delete newState.windows[0].sizemode;
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
|
||||
setTimeout(function() {
|
||||
newWin.setTimeout(function() {
|
||||
is(JSON.parse(ss.getClosedTabData(newWin)).length, 0,
|
||||
"closed tabs were implicitly cleared");
|
||||
|
||||
@ -87,7 +87,7 @@ function test() {
|
||||
newState.windows[0].sizemode = "normal";
|
||||
ss.setWindowState(newWin, JSON.stringify(newState), true);
|
||||
|
||||
setTimeout(function() {
|
||||
newWin.setTimeout(function() {
|
||||
isnot(newWin.windowState, newWin.STATE_MAXIMIZED,
|
||||
"the window was explicitly unmaximized");
|
||||
|
||||
|
@ -66,10 +66,10 @@ def abstractmethod(method):
|
||||
line = method.func_code.co_firstlineno
|
||||
filename = method.func_code.co_filename
|
||||
def not_implemented(*args, **kwargs):
|
||||
raise NotImplementedError('Abstract method %s at File "%s", line %s \
|
||||
should be implemented by a concrete class' %
|
||||
raise NotImplementedError('Abstract method %s at File "%s", line %s '
|
||||
'should be implemented by a concrete class' %
|
||||
(repr(method), filename,line))
|
||||
return not_implemented
|
||||
return not_implemented
|
||||
|
||||
class DeviceManager:
|
||||
|
||||
@ -236,14 +236,14 @@ class DeviceManager:
|
||||
pieces = appname.split(' ')
|
||||
parts = pieces[0].split('/')
|
||||
app = parts[-1]
|
||||
procre = re.compile('.*' + app + '.*')
|
||||
|
||||
procList = self.getProcessList()
|
||||
if (procList == []):
|
||||
return None
|
||||
|
||||
for proc in procList:
|
||||
if (procre.match(proc[1])):
|
||||
procName = proc[1].split('/')[-1]
|
||||
if (procName == app):
|
||||
pid = proc[0]
|
||||
break
|
||||
return pid
|
||||
@ -357,7 +357,7 @@ class DeviceManager:
|
||||
hexval = mdsum.hexdigest()
|
||||
if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
|
||||
return hexval
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def getDeviceRoot(self):
|
||||
"""
|
||||
@ -373,12 +373,13 @@ class DeviceManager:
|
||||
/xpcshell
|
||||
/reftest
|
||||
/mochitest
|
||||
external
|
||||
external
|
||||
returns:
|
||||
success: path for device root
|
||||
failure: None
|
||||
"""
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def getAppRoot(self):
|
||||
"""
|
||||
Either we will have /tests/fennec or /tests/firefox but we will never have
|
||||
@ -389,26 +390,6 @@ class DeviceManager:
|
||||
success: path for app root
|
||||
failure: None
|
||||
"""
|
||||
|
||||
devroot = self.getDeviceRoot()
|
||||
if (devroot == None):
|
||||
return None
|
||||
|
||||
if (self.dirExists(devroot + '/fennec')):
|
||||
return devroot + '/fennec'
|
||||
elif (self.dirExists(devroot + '/firefox')):
|
||||
return devroot + '/firefox'
|
||||
elif (self.dirExsts('/data/data/org.mozilla.fennec')):
|
||||
return 'org.mozilla.fennec'
|
||||
elif (self.dirExists('/data/data/org.mozilla.firefox')):
|
||||
return 'org.mozilla.firefox'
|
||||
elif (self.dirExists('/data/data/org.mozilla.fennec_aurora')):
|
||||
return 'org.mozilla.fennec_aurora'
|
||||
elif (self.dirExists('/data/data/org.mozilla.firefox_beta')):
|
||||
return 'org.mozilla.firefox_beta'
|
||||
|
||||
# Failure (either not installed or not a recognized platform)
|
||||
return None
|
||||
|
||||
def getTestRoot(self, type):
|
||||
"""
|
||||
@ -514,7 +495,7 @@ class DeviceManager:
|
||||
success: output from agent for inst command
|
||||
failure: None
|
||||
"""
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def uninstallAppAndReboot(self, appName, installPath=None):
|
||||
"""
|
||||
@ -542,7 +523,7 @@ class DeviceManager:
|
||||
success: time in ms
|
||||
failure: None
|
||||
"""
|
||||
|
||||
|
||||
class NetworkTools:
|
||||
def __init__(self):
|
||||
pass
|
||||
@ -562,7 +543,10 @@ class NetworkTools:
|
||||
return None
|
||||
|
||||
def getLanIp(self):
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
try:
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
except socket.gaierror:
|
||||
ip = socket.gethostbyname(socket.gethostname() + ".local") # for Mac OS X
|
||||
if ip.startswith("127.") and os.name != "nt":
|
||||
interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
|
||||
for ifname in interfaces:
|
||||
|
@ -4630,7 +4630,7 @@ USE_ARM_KUSER=
|
||||
BUILD_CTYPES=1
|
||||
MOZ_USE_NATIVE_POPUP_WINDOWS=
|
||||
MOZ_ANDROID_HISTORY=
|
||||
MOZ_WEBSMS_BACKEND=1
|
||||
MOZ_WEBSMS_BACKEND=
|
||||
MOZ_GRAPHITE=1
|
||||
|
||||
case "${target}" in
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "nsIDOMCustomEvent.h"
|
||||
#include "nsIVariant.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "nsWeakPtr.h"
|
||||
#include "nsVariant.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDOMMemoryReporter.h"
|
||||
@ -37,10 +38,13 @@ NS_INTERFACE_TABLE_HEAD(nsGenericHTMLFrameElement)
|
||||
NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
|
||||
|
||||
NS_IMPL_INT_ATTR(nsGenericHTMLFrameElement, TabIndex, tabindex)
|
||||
NS_IMPL_BOOL_ATTR(nsGenericHTMLFrameElement, Mozbrowser, mozbrowser)
|
||||
|
||||
nsGenericHTMLFrameElement::~nsGenericHTMLFrameElement()
|
||||
{
|
||||
if (mTitleChangedListener) {
|
||||
mTitleChangedListener->Unregister();
|
||||
}
|
||||
|
||||
if (mFrameLoader) {
|
||||
mFrameLoader->Destroy();
|
||||
}
|
||||
@ -112,17 +116,7 @@ nsGenericHTMLFrameElement::EnsureFrameLoader()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Register ourselves as a web progress listener on the frameloader's
|
||||
// docshell.
|
||||
nsCOMPtr<nsIDocShell> docShell;
|
||||
mFrameLoader->GetDocShell(getter_AddRefs(docShell));
|
||||
nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(docShell);
|
||||
NS_ENSURE_TRUE(webProgress, NS_OK);
|
||||
|
||||
// This adds a weak ref, so we don't have to worry about unregistering.
|
||||
webProgress->AddProgressListener(this,
|
||||
nsIWebProgress::NOTIFY_LOCATION |
|
||||
nsIWebProgress::NOTIFY_STATE_WINDOW);
|
||||
MaybeEnsureBrowserFrameListenersRegistered();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -285,6 +279,84 @@ nsGenericHTMLFrameElement::SizeOf() const
|
||||
return size;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGenericHTMLFrameElement::GetMozbrowser(bool *aValue)
|
||||
{
|
||||
return GetBoolAttr(nsGkAtoms::mozbrowser, aValue);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGenericHTMLFrameElement::SetMozbrowser(bool aValue)
|
||||
{
|
||||
nsresult rv = SetBoolAttr(nsGkAtoms::mozbrowser, aValue);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
MaybeEnsureBrowserFrameListenersRegistered();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this frame element is allowed to be a browser frame (because it passes
|
||||
* BrowserFrameSecurityCheck()), then make sure that it has the appropriate
|
||||
* event listeners enabled.
|
||||
*/
|
||||
void
|
||||
nsGenericHTMLFrameElement::MaybeEnsureBrowserFrameListenersRegistered()
|
||||
{
|
||||
if (mBrowserFrameListenersRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this frame passes the browser frame security check, ensure that its
|
||||
// listeners are active.
|
||||
if (!BrowserFrameSecurityCheck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Not much we can do without a frameLoader. But EnsureFrameLoader will call
|
||||
// this function, so we'll get a chance to pass this test.
|
||||
if (!mFrameLoader) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBrowserFrameListenersRegistered = true;
|
||||
|
||||
// Register ourselves as a web progress listener on the frameloader's
|
||||
// docshell.
|
||||
nsCOMPtr<nsIDocShell> docShell;
|
||||
mFrameLoader->GetDocShell(getter_AddRefs(docShell));
|
||||
nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(docShell);
|
||||
|
||||
// This adds a weak ref, so we don't have to worry about unregistering.
|
||||
if (webProgress) {
|
||||
webProgress->AddProgressListener(this,
|
||||
nsIWebProgress::NOTIFY_LOCATION |
|
||||
nsIWebProgress::NOTIFY_STATE_WINDOW);
|
||||
}
|
||||
|
||||
// Register a listener for DOMTitleChanged on the window's chrome event
|
||||
// handler. The chrome event handler outlives this iframe, so we'll have to
|
||||
// unregister when the iframe is destroyed.
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(docShell);
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(window->IsOuterWindow());
|
||||
|
||||
nsIDOMEventTarget *chromeHandler = window->GetChromeEventHandler();
|
||||
if (!chromeHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mTitleChangedListener);
|
||||
mTitleChangedListener = new TitleChangedListener(this, chromeHandler);
|
||||
chromeHandler->AddSystemEventListener(NS_LITERAL_STRING("DOMTitleChanged"),
|
||||
mTitleChangedListener,
|
||||
/* useCapture = */ false,
|
||||
/* wantsUntrusted = */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this frame element has permission to send mozbrowser
|
||||
* events, and false otherwise.
|
||||
@ -449,3 +521,77 @@ nsGenericHTMLFrameElement::OnSecurityChange(nsIWebProgress *aWebProgress,
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(nsGenericHTMLFrameElement::TitleChangedListener,
|
||||
nsIDOMEventListener)
|
||||
|
||||
nsGenericHTMLFrameElement::TitleChangedListener::TitleChangedListener(
|
||||
nsGenericHTMLFrameElement *aElement,
|
||||
nsIDOMEventTarget *aChromeHandler)
|
||||
{
|
||||
mElement =
|
||||
do_GetWeakReference(NS_ISUPPORTS_CAST(nsIDOMMozBrowserFrame*, aElement));
|
||||
mChromeHandler = do_GetWeakReference(aChromeHandler);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGenericHTMLFrameElement::TitleChangedListener::HandleEvent(nsIDOMEvent *aEvent)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
{
|
||||
nsString eventType;
|
||||
aEvent->GetType(eventType);
|
||||
MOZ_ASSERT(eventType.EqualsLiteral("DOMTitleChanged"));
|
||||
}
|
||||
#endif
|
||||
|
||||
nsCOMPtr<nsIDOMMozBrowserFrame> element = do_QueryReferent(mElement);
|
||||
if (!element) {
|
||||
// Hm, our element is gone, but somehow we weren't unregistered?
|
||||
Unregister();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsGenericHTMLFrameElement* frameElement =
|
||||
static_cast<nsGenericHTMLFrameElement*>(element.get());
|
||||
|
||||
nsCOMPtr<nsIDOMDocument> frameDocument;
|
||||
frameElement->GetContentDocument(getter_AddRefs(frameDocument));
|
||||
NS_ENSURE_STATE(frameDocument);
|
||||
|
||||
nsCOMPtr<nsIDOMEventTarget> target;
|
||||
aEvent->GetTarget(getter_AddRefs(target));
|
||||
nsCOMPtr<nsIDOMDocument> targetDocument = do_QueryInterface(target);
|
||||
NS_ENSURE_STATE(targetDocument);
|
||||
|
||||
if (frameDocument != targetDocument) {
|
||||
// This is a titlechange event for the wrong document!
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsString newTitle;
|
||||
nsresult rv = targetDocument->GetTitle(newTitle);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
frameElement->MaybeFireBrowserEvent(
|
||||
NS_LITERAL_STRING("titlechange"),
|
||||
NS_LITERAL_STRING("customevent"),
|
||||
newTitle);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsGenericHTMLFrameElement::TitleChangedListener::Unregister()
|
||||
{
|
||||
nsCOMPtr<nsIDOMEventTarget> chromeHandler = do_QueryReferent(mChromeHandler);
|
||||
if (!chromeHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
chromeHandler->RemoveSystemEventListener(NS_LITERAL_STRING("DOMTitleChanged"),
|
||||
this, /* useCapture = */ false);
|
||||
|
||||
// Careful; the call above may have removed the last strong reference to this
|
||||
// class, so don't dereference |this| here.
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsIDOMHTMLFrameElement.h"
|
||||
#include "nsIDOMMozBrowserFrame.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsIWebProgressListener.h"
|
||||
|
||||
/**
|
||||
@ -22,9 +23,11 @@ public:
|
||||
nsGenericHTMLFrameElement(already_AddRefed<nsINodeInfo> aNodeInfo,
|
||||
mozilla::dom::FromParser aFromParser)
|
||||
: nsGenericHTMLElement(aNodeInfo)
|
||||
, mNetworkCreated(aFromParser == mozilla::dom::FROM_PARSER_NETWORK)
|
||||
, mBrowserFrameListenersRegistered(false)
|
||||
{
|
||||
mNetworkCreated = aFromParser == mozilla::dom::FROM_PARSER_NETWORK;
|
||||
}
|
||||
|
||||
virtual ~nsGenericHTMLFrameElement();
|
||||
|
||||
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
|
||||
@ -60,6 +63,28 @@ public:
|
||||
nsGenericHTMLElement)
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Listens to titlechanged events from the document inside the iframe and
|
||||
* forwards them along to the iframe so it can fire a mozbrowsertitlechange
|
||||
* event if appropriate.
|
||||
*/
|
||||
class TitleChangedListener : public nsIDOMEventListener
|
||||
{
|
||||
public:
|
||||
TitleChangedListener(nsGenericHTMLFrameElement *aElement,
|
||||
nsIDOMEventTarget *aChromeHandler);
|
||||
|
||||
/* Unregister this listener. */
|
||||
void Unregister();
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIDOMEVENTLISTENER
|
||||
|
||||
private:
|
||||
nsWeakPtr mElement; /* nsGenericHTMLFrameElement */
|
||||
nsWeakPtr mChromeHandler; /* nsIDOMEventTarget */
|
||||
};
|
||||
|
||||
// This doesn't really ensure a frame loade in all cases, only when
|
||||
// it makes sense.
|
||||
nsresult EnsureFrameLoader();
|
||||
@ -67,14 +92,19 @@ protected:
|
||||
nsresult GetContentDocument(nsIDOMDocument** aContentDocument);
|
||||
nsresult GetContentWindow(nsIDOMWindow** aContentWindow);
|
||||
|
||||
void MaybeEnsureBrowserFrameListenersRegistered();
|
||||
bool BrowserFrameSecurityCheck();
|
||||
nsresult MaybeFireBrowserEvent(const nsAString &aEventName,
|
||||
const nsAString &aEventType,
|
||||
const nsAString &aValue = EmptyString());
|
||||
|
||||
nsRefPtr<nsFrameLoader> mFrameLoader;
|
||||
nsRefPtr<TitleChangedListener> mTitleChangedListener;
|
||||
|
||||
// True when the element is created by the parser
|
||||
// using NS_FROM_PARSER_NETWORK flag.
|
||||
// If the element is modified, it may lose the flag.
|
||||
bool mNetworkCreated;
|
||||
|
||||
bool mBrowserFrameListenersRegistered;
|
||||
};
|
||||
|
@ -4579,7 +4579,7 @@ nsGlobalWindow::Dump(const nsAString& aStr)
|
||||
|
||||
if (cstr) {
|
||||
#ifdef ANDROID
|
||||
__android_log_print(ANDROID_LOG_INFO, "Gecko", cstr);
|
||||
__android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
|
||||
#endif
|
||||
FILE *fp = gDumpFile ? gDumpFile : stdout;
|
||||
fputs(cstr, fp);
|
||||
|
@ -16,3 +16,5 @@ cycle = Cycle
|
||||
# (Mac Only)
|
||||
# The Role Description for AXWebArea (the web widget). Like in Safari.
|
||||
htmlContent = HTML Content
|
||||
# The Role Description for the Tab button.
|
||||
tab = tab
|
||||
|
@ -80,6 +80,7 @@ _TEST_FILES = \
|
||||
test_browserFrame3.html \
|
||||
test_browserFrame4.html \
|
||||
test_browserFrame5.html \
|
||||
test_browserFrame6.html \
|
||||
$(NULL)
|
||||
|
||||
_CHROME_FILES = \
|
||||
|
@ -25,7 +25,10 @@ SimpleTest.waitForExplicitFinish();
|
||||
function runTest() {
|
||||
browserFrameHelpers.setEnabledPref(false);
|
||||
|
||||
var iframe = document.getElementById('iframe');
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.mozbrowser = true;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
iframe.addEventListener('mozbrowserloadstart', function() {
|
||||
ok(false, 'Should not send mozbrowserloadstart event.');
|
||||
});
|
||||
@ -42,7 +45,5 @@ addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
|
||||
|
||||
</script>
|
||||
|
||||
<iframe id='iframe' mozbrowser></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -26,7 +26,10 @@ function runTest() {
|
||||
browserFrameHelpers.setEnabledPref(true);
|
||||
browserFrameHelpers.setWhitelistPref(' http://foobar.com');
|
||||
|
||||
var iframe = document.getElementById('iframe');
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.mozbrowser = true;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
iframe.addEventListener('mozbrowserloadstart', function() {
|
||||
ok(false, 'Should not send mozbrowserloadstart event.');
|
||||
});
|
||||
@ -43,7 +46,5 @@ addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
|
||||
|
||||
</script>
|
||||
|
||||
<iframe id='iframe' mozbrowser></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -18,7 +18,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=710231
|
||||
-->
|
||||
|
||||
<script type="application/javascript;version=1.7">
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
@ -31,8 +30,24 @@ function runTest() {
|
||||
browserFrameHelpers.setEnabledPref(true);
|
||||
browserFrameHelpers.addToWhitelist();
|
||||
|
||||
var iframe = document.getElementById('iframe');
|
||||
// Load example.org into the iframe, wait for that to load, then call
|
||||
// runTest2. This would *almost* work if we just had a <iframe mozbrowser>
|
||||
// in the HTML, except that we have to set the prefs before we create the
|
||||
// iframe!
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.id = 'iframe';
|
||||
document.body.appendChild(iframe);
|
||||
iframe.src = 'data:text/html,1';
|
||||
iframe.addEventListener('load', function() {
|
||||
iframe.removeEventListener('load', arguments.callee);
|
||||
SimpleTest.executeSoon(runTest2);
|
||||
});
|
||||
}
|
||||
|
||||
function runTest2() {
|
||||
var iframe = document.getElementById('iframe');
|
||||
iframe.mozbrowser = true;
|
||||
iframe.addEventListener('mozbrowserloadstart', function() {
|
||||
ok(!seenLoadStart, 'Just one loadstart event.');
|
||||
seenLoadStart = true;
|
||||
@ -82,7 +97,5 @@ addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
|
||||
|
||||
</script>
|
||||
|
||||
<iframe id='iframe' mozbrowser></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -17,7 +17,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=720157
|
||||
-->
|
||||
|
||||
<script type="application/javascript;version=1.7">
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
@ -25,32 +24,62 @@ function runTest() {
|
||||
browserFrameHelpers.setEnabledPref(true);
|
||||
browserFrameHelpers.addToWhitelist();
|
||||
|
||||
var iframe = document.getElementById('iframe');
|
||||
var iframe1 = document.createElement('iframe');
|
||||
document.body.appendChild(iframe1);
|
||||
iframe1.id = 'iframe1';
|
||||
iframe1.addEventListener('load', function() {
|
||||
iframe1.removeEventListener('load', arguments.callee);
|
||||
SimpleTest.executeSoon(runTest2);
|
||||
});
|
||||
iframe1.src = 'http://example.org';
|
||||
}
|
||||
|
||||
function runTest2() {
|
||||
var iframe1 = document.getElementById('iframe1');
|
||||
iframe1.mozbrowser = true;
|
||||
|
||||
var iframe2 = document.getElementById('iframe2');
|
||||
|
||||
var sawLoad = false;
|
||||
var sawLocationChange = false;
|
||||
|
||||
iframe.addEventListener('mozbrowserlocationchange', function(e) {
|
||||
iframe1.addEventListener('mozbrowserlocationchange', function(e) {
|
||||
ok(!sawLocationChange, 'Just one locationchange event.');
|
||||
ok(!sawLoad, 'locationchange before load.');
|
||||
is(e.detail, 'data:text/html,1', "event's reported location");
|
||||
sawLocationChange = true;
|
||||
});
|
||||
|
||||
iframe.addEventListener('load', function() {
|
||||
iframe1.addEventListener('load', function() {
|
||||
ok(sawLocationChange, 'Load after locationchange.');
|
||||
ok(!sawLoad, 'Just one load event.');
|
||||
sawLoad = true;
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
iframe.src = 'data:text/html,1';
|
||||
function iframe2Load() {
|
||||
if (!sawLoad || !sawLocationChange) {
|
||||
// Spin if iframe1 hasn't loaded yet.
|
||||
SimpleTest.executeSoon(iframe2Load);
|
||||
return;
|
||||
}
|
||||
ok(true, 'Got iframe2 load.');
|
||||
SimpleTest.finish();
|
||||
}
|
||||
iframe2.addEventListener('load', iframe2Load);
|
||||
|
||||
|
||||
iframe1.src = 'data:text/html,1';
|
||||
|
||||
// Load something into iframe2 to check that it doesn't trigger a
|
||||
// locationchange for our iframe1 listener.
|
||||
iframe2.src = 'http://example.com';
|
||||
}
|
||||
|
||||
addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
|
||||
|
||||
</script>
|
||||
|
||||
<iframe id='iframe' mozbrowser></iframe>
|
||||
<iframe id='iframe2'></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
72
dom/tests/mochitest/general/test_browserFrame6.html
Normal file
72
dom/tests/mochitest/general/test_browserFrame6.html
Normal file
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=720157
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 720157</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="browserFrameHelpers.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=720157">Mozilla Bug 720157</a>
|
||||
|
||||
<!--
|
||||
Test that the onmozbrowsertitlechange event works.
|
||||
-->
|
||||
|
||||
<script type="application/javascript;version=1.7">
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
browserFrameHelpers.setEnabledPref(true);
|
||||
browserFrameHelpers.addToWhitelist();
|
||||
|
||||
var iframe1 = document.createElement('iframe');
|
||||
iframe1.mozbrowser = true;
|
||||
document.body.appendChild(iframe1);
|
||||
|
||||
// iframe2 is a red herring; we modify its title but don't listen for
|
||||
// titlechanges; we want to make sure that its titlechange events aren't
|
||||
// picked up by the listener on iframe1.
|
||||
var iframe2 = document.createElement('iframe');
|
||||
iframe2.mozbrowser = true;
|
||||
document.body.appendChild(iframe2);
|
||||
|
||||
var numTitleChanges = 0;
|
||||
|
||||
iframe1.addEventListener('mozbrowsertitlechange', function(e) {
|
||||
numTitleChanges++;
|
||||
|
||||
if (numTitleChanges == 1) {
|
||||
is(e.detail, 'Title');
|
||||
iframe1.contentDocument.title = 'New title';
|
||||
iframe2.contentDocument.title = 'BAD TITLE 2';
|
||||
}
|
||||
else if (numTitleChanges == 2) {
|
||||
is(e.detail, 'New title');
|
||||
iframe1.src = 'data:text/html,<html><head><title>Title 3</title></head><body></body></html>';
|
||||
}
|
||||
else if (numTitleChanges == 3) {
|
||||
is(e.detail, 'Title 3');
|
||||
SimpleTest.finish();
|
||||
}
|
||||
else {
|
||||
ok(false, 'Too many titlechange events.');
|
||||
}
|
||||
});
|
||||
|
||||
iframe1.src = 'data:text/html,<html><head><title>Title</title></head><body></body></html>';
|
||||
iframe2.src = 'data:text/html,<html><head><title>BAD TITLE</title></head><body></body></html>';
|
||||
}
|
||||
|
||||
addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -27,7 +27,7 @@ of the License or (at your option) any later version.
|
||||
// JSON debug logging
|
||||
// Author: Tim Eves
|
||||
|
||||
#include <cstdio>
|
||||
#include <stdio.h>
|
||||
#include "inc/json.h"
|
||||
|
||||
using namespace graphite2;
|
||||
|
@ -71,6 +71,7 @@
|
||||
#include "mozilla/StdInt.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
@ -176,6 +177,8 @@ DiscardingEnabled()
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/* static */ nsRefPtr<RasterImage::DecodeWorker> RasterImage::DecodeWorker::sSingleton;
|
||||
|
||||
#ifndef DEBUG
|
||||
NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
|
||||
nsISupportsWeakReference)
|
||||
@ -194,7 +197,7 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
|
||||
mObserver(nsnull),
|
||||
mLockCount(0),
|
||||
mDecoder(nsnull),
|
||||
mWorker(nsnull),
|
||||
mDecodeRequest(this),
|
||||
mBytesDecoded(0),
|
||||
mDecodeCount(0),
|
||||
#ifdef DEBUG
|
||||
@ -207,7 +210,6 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
|
||||
mHasSourceData(false),
|
||||
mDecoded(false),
|
||||
mHasBeenDecoded(false),
|
||||
mWorkerPending(false),
|
||||
mInDecoder(false),
|
||||
mAnimationFinished(false)
|
||||
{
|
||||
@ -1496,11 +1498,9 @@ RasterImage::AddSourceData(const char *aBuffer, PRUint32 aCount)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
// If there's a decoder open, that means we want to do more decoding.
|
||||
// Wake up the worker if it's not up already
|
||||
if (mDecoder && !mWorkerPending) {
|
||||
NS_ABORT_IF_FALSE(mWorker, "We should have a worker here!");
|
||||
rv = mWorker->Run();
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
// Wake up the worker.
|
||||
if (mDecoder) {
|
||||
DecodeWorker::Singleton()->RequestDecode(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1563,19 +1563,21 @@ RasterImage::SourceDataComplete()
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
}
|
||||
|
||||
// If there's a decoder open, we need to wake up the worker if it's not
|
||||
// already. This is so the worker can account for the fact that the source
|
||||
// data is complete. For some decoders, DecodingComplete() is only called
|
||||
// when the decoder is Close()-ed, and thus the SourceDataComplete() call
|
||||
// is the only way we can transition to a 'decoded' state. Furthermore,
|
||||
// it's always possible for any image type to have the data stream stop
|
||||
// abruptly at any point, in which case we need to trigger an error.
|
||||
if (mDecoder && !mWorkerPending) {
|
||||
NS_ABORT_IF_FALSE(mWorker, "We should have a worker here!");
|
||||
nsresult rv = mWorker->Run();
|
||||
// If there's a decoder open, synchronously decode the beginning of the image
|
||||
// to check for errors and get the image's size. (If we already have the
|
||||
// image's size, this does nothing.) Then kick off an async decode of the
|
||||
// rest of the image.
|
||||
if (mDecoder) {
|
||||
nsresult rv = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this);
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
}
|
||||
|
||||
// If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker
|
||||
// finish decoding this image.
|
||||
if (mDecoder) {
|
||||
DecodeWorker::Singleton()->RequestDecode(this);
|
||||
}
|
||||
|
||||
// Free up any extra space in the backing buffer
|
||||
mSourceData.Compact();
|
||||
|
||||
@ -2281,15 +2283,11 @@ RasterImage::InitDecoder(bool aDoSizeDecode)
|
||||
mDecoder->Init();
|
||||
CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
|
||||
|
||||
// Create a decode worker
|
||||
mWorker = new imgDecodeWorker(this);
|
||||
|
||||
if (!aDoSizeDecode) {
|
||||
Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Subtract(mDecodeCount);
|
||||
mDecodeCount++;
|
||||
Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(mDecodeCount);
|
||||
}
|
||||
CONTAINER_ENSURE_TRUE(mWorker, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -2325,16 +2323,16 @@ RasterImage::ShutdownDecoder(eShutdownIntent aIntent)
|
||||
decoder->Finish();
|
||||
mInDecoder = false;
|
||||
|
||||
// Kill off our decode request, if it's pending. (If not, this call is
|
||||
// harmless.)
|
||||
DecodeWorker::Singleton()->StopDecoding(this);
|
||||
|
||||
nsresult decoderStatus = decoder->GetDecoderError();
|
||||
if (NS_FAILED(decoderStatus)) {
|
||||
DoError();
|
||||
return decoderStatus;
|
||||
}
|
||||
|
||||
// Kill off the worker
|
||||
mWorker = nsnull;
|
||||
mWorkerPending = false;
|
||||
|
||||
// We just shut down the decoder. If we didn't get what we want, but expected
|
||||
// to, flag an error
|
||||
bool failed = false;
|
||||
@ -2468,10 +2466,6 @@ RasterImage::RequestDecode()
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
}
|
||||
|
||||
// If we already have a pending worker, we're done
|
||||
if (mWorkerPending)
|
||||
return NS_OK;
|
||||
|
||||
// If we've read all the data we have, we're done
|
||||
if (mBytesDecoded == mSourceData.Length())
|
||||
return NS_OK;
|
||||
@ -2483,7 +2477,9 @@ RasterImage::RequestDecode()
|
||||
// If we get this far, dispatch the worker. We do this instead of starting
|
||||
// any immediate decoding to guarantee that all our decode notifications are
|
||||
// dispatched asynchronously, and to ensure we stay responsive.
|
||||
return mWorker->Dispatch();
|
||||
DecodeWorker::Singleton()->RequestDecode(this);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Synchronously decodes as much data as possible
|
||||
@ -2590,6 +2586,10 @@ RasterImage::Draw(gfxContext *aContext,
|
||||
// We use !mDecoded && mHasSourceData to mean discarded.
|
||||
if (!mDecoded && mHasSourceData) {
|
||||
mDrawStartTime = TimeStamp::Now();
|
||||
|
||||
// We're drawing this image, so indicate that we should decode it as soon
|
||||
// as possible.
|
||||
DecodeWorker::Singleton()->MarkAsASAP(this);
|
||||
}
|
||||
|
||||
// If a synchronous draw is requested, flush anything that might be sitting around
|
||||
@ -2767,143 +2767,7 @@ RasterImage::DoError()
|
||||
LOG_CONTAINER_ERROR;
|
||||
}
|
||||
|
||||
// Decodes some data, then re-posts itself to the end of the event queue if
|
||||
// there's more processing to be done
|
||||
NS_IMETHODIMP
|
||||
imgDecodeWorker::Run()
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
// If we shutdown the decoder in this function, we could lose ourselves
|
||||
nsCOMPtr<nsIRunnable> kungFuDeathGrip(this);
|
||||
|
||||
// The container holds a strong reference to us. Cycles are bad.
|
||||
nsCOMPtr<imgIContainer> iContainer(do_QueryReferent(mContainer));
|
||||
if (!iContainer)
|
||||
return NS_OK;
|
||||
RasterImage* image = static_cast<RasterImage*>(iContainer.get());
|
||||
|
||||
NS_ABORT_IF_FALSE(image->mInitialized,
|
||||
"Worker active for uninitialized container!");
|
||||
|
||||
// If we were pending, we're not anymore
|
||||
image->mWorkerPending = false;
|
||||
|
||||
// If an error is flagged, it probably happened while we were waiting
|
||||
// in the event queue. Bail early, but no need to bother the run queue
|
||||
// by returning an error.
|
||||
if (image->mError)
|
||||
return NS_OK;
|
||||
|
||||
// If we don't have a decoder, we must have finished already (for example,
|
||||
// a synchronous decode request came while the worker was pending).
|
||||
if (!image->mDecoder)
|
||||
return NS_OK;
|
||||
|
||||
nsRefPtr<Decoder> decoderKungFuDeathGrip = image->mDecoder;
|
||||
|
||||
// Size decodes are cheap and we more or less want them to be
|
||||
// synchronous. Write all the data in that case, otherwise write a
|
||||
// chunk
|
||||
PRUint32 maxBytes = image->mDecoder->IsSizeDecode()
|
||||
? image->mSourceData.Length() : gDecodeBytesAtATime;
|
||||
|
||||
// Loop control
|
||||
bool haveMoreData = true;
|
||||
PRInt32 chunkCount = 0;
|
||||
TimeStamp start = TimeStamp::Now();
|
||||
TimeStamp deadline = start + TimeDuration::FromMilliseconds(gMaxMSBeforeYield);
|
||||
|
||||
// We keep decoding chunks until one of three possible events occur:
|
||||
// 1) We don't have any data left to decode
|
||||
// 2) The decode completes
|
||||
// 3) We hit the deadline and need to yield to keep the UI snappy
|
||||
while (haveMoreData && !image->IsDecodeFinished() &&
|
||||
(TimeStamp::Now() < deadline)) {
|
||||
|
||||
// Decode a chunk of data
|
||||
chunkCount++;
|
||||
rv = image->DecodeSomeData(maxBytes);
|
||||
if (NS_FAILED(rv)) {
|
||||
image->DoError();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Figure out if we still have more data
|
||||
haveMoreData =
|
||||
image->mSourceData.Length() > image->mBytesDecoded;
|
||||
}
|
||||
|
||||
TimeDuration decodeLatency = TimeStamp::Now() - start;
|
||||
if (chunkCount && !image->mDecoder->IsSizeDecode()) {
|
||||
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY, PRInt32(decodeLatency.ToMicroseconds()));
|
||||
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, chunkCount);
|
||||
}
|
||||
// accumulate the total decode time
|
||||
mDecodeTime += decodeLatency;
|
||||
|
||||
// Flush invalidations _after_ we've written everything we're going to.
|
||||
// Furthermore, if we have all of the data, we don't want to do progressive
|
||||
// display at all. In that case, let Decoder::PostFrameStop() do the
|
||||
// flush once the whole frame is ready.
|
||||
if (!image->mHasSourceData) {
|
||||
image->mInDecoder = true;
|
||||
image->mDecoder->FlushInvalidations();
|
||||
image->mInDecoder = false;
|
||||
}
|
||||
|
||||
// If the decode finished, shutdown the decoder
|
||||
if (image->mDecoder && image->IsDecodeFinished()) {
|
||||
|
||||
if (!image->mDecoder->IsSizeDecode()) {
|
||||
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, PRInt32(mDecodeTime.ToMicroseconds()));
|
||||
|
||||
// We only record the speed for some decoders. The rest have SpeedHistogram return HistogramCount.
|
||||
Telemetry::ID id = image->mDecoder->SpeedHistogram();
|
||||
if (id < Telemetry::HistogramCount) {
|
||||
PRInt32 KBps = PRInt32((image->mBytesDecoded/1024.0)/mDecodeTime.ToSeconds());
|
||||
Telemetry::Accumulate(id, KBps);
|
||||
}
|
||||
}
|
||||
|
||||
rv = image->ShutdownDecoder(RasterImage::eShutdownIntent_Done);
|
||||
if (NS_FAILED(rv)) {
|
||||
image->DoError();
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// If Conditions 1 & 2 are still true, then the only reason we bailed was
|
||||
// because we hit the deadline. Repost ourselves to the end of the event
|
||||
// queue.
|
||||
if (image->mDecoder && !image->IsDecodeFinished() && haveMoreData)
|
||||
return this->Dispatch();
|
||||
|
||||
// Otherwise, return success
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Queues the worker up at the end of the event queue
|
||||
NS_METHOD imgDecodeWorker::Dispatch()
|
||||
{
|
||||
// The container holds a strong reference to us. Cycles are bad.
|
||||
nsCOMPtr<imgIContainer> iContainer(do_QueryReferent(mContainer));
|
||||
if (!iContainer)
|
||||
return NS_OK;
|
||||
RasterImage* image = static_cast<RasterImage*>(iContainer.get());
|
||||
|
||||
// We should not be called if there's already a pending worker
|
||||
NS_ABORT_IF_FALSE(!image->mWorkerPending,
|
||||
"Trying to queue up worker with one already pending!");
|
||||
|
||||
// Flag that we're pending
|
||||
image->mWorkerPending = true;
|
||||
|
||||
// Dispatch
|
||||
return NS_DispatchToCurrentThread(this);
|
||||
}
|
||||
|
||||
// nsIInputStream callback to copy the incoming image data directly to the
|
||||
// nsIInputStream callback to copy the incoming image data directly to the
|
||||
// RasterImage without processing. The RasterImage is passed as the closure.
|
||||
// Always reads everything it gets, even if the data is erroneous.
|
||||
NS_METHOD
|
||||
@ -2940,7 +2804,6 @@ RasterImage::ShouldAnimate()
|
||||
!mAnimationFinished;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
/* readonly attribute PRUint32 framesNotified; */
|
||||
#ifdef DEBUG
|
||||
NS_IMETHODIMP
|
||||
@ -2954,5 +2817,249 @@ RasterImage::GetFramesNotified(PRUint32 *aFramesNotified)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* static */ RasterImage::DecodeWorker*
|
||||
RasterImage::DecodeWorker::Singleton()
|
||||
{
|
||||
if (!sSingleton) {
|
||||
sSingleton = new DecodeWorker();
|
||||
ClearOnShutdown(&sSingleton);
|
||||
}
|
||||
|
||||
return sSingleton;
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::DecodeWorker::MarkAsASAP(RasterImage* aImg)
|
||||
{
|
||||
DecodeRequest* request = &aImg->mDecodeRequest;
|
||||
|
||||
// If we're already an ASAP request, there's nothing to do here.
|
||||
if (request->mIsASAP) {
|
||||
return;
|
||||
}
|
||||
|
||||
request->mIsASAP = true;
|
||||
|
||||
if (request->isInList()) {
|
||||
// If the decode request is in a list, it must be in the normal decode
|
||||
// requests list -- if it had been in the ASAP list, then mIsASAP would
|
||||
// have been true above. Move the request to the ASAP list.
|
||||
request->remove();
|
||||
mASAPDecodeRequests.insertBack(request);
|
||||
|
||||
// Since request is in a list, one of the decode worker's lists is
|
||||
// non-empty, so the worker should be pending in the event loop.
|
||||
//
|
||||
// (Note that this invariant only holds while we are not in Run(), because
|
||||
// DecodeSomeOfImage adds requests to the decode worker using
|
||||
// AddDecodeRequest, not RequestDecode, and AddDecodeRequest does not call
|
||||
// EnsurePendingInEventLoop. Therefore, it is an error to call MarkAsASAP
|
||||
// from within DecodeWorker::Run.)
|
||||
MOZ_ASSERT(mPendingInEventLoop);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::DecodeWorker::AddDecodeRequest(DecodeRequest* aRequest)
|
||||
{
|
||||
if (aRequest->isInList()) {
|
||||
// The image is already in our list of images to decode, so we don't have
|
||||
// to do anything here.
|
||||
return;
|
||||
}
|
||||
|
||||
if (aRequest->mIsASAP) {
|
||||
mASAPDecodeRequests.insertBack(aRequest);
|
||||
} else {
|
||||
mNormalDecodeRequests.insertBack(aRequest);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::DecodeWorker::RequestDecode(RasterImage* aImg)
|
||||
{
|
||||
AddDecodeRequest(&aImg->mDecodeRequest);
|
||||
EnsurePendingInEventLoop();
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::DecodeWorker::EnsurePendingInEventLoop()
|
||||
{
|
||||
if (!mPendingInEventLoop) {
|
||||
mPendingInEventLoop = true;
|
||||
NS_DispatchToCurrentThread(this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::DecodeWorker::StopDecoding(RasterImage* aImg)
|
||||
{
|
||||
DecodeRequest* request = &aImg->mDecodeRequest;
|
||||
if (request->isInList()) {
|
||||
request->remove();
|
||||
}
|
||||
request->mDecodeTime = TimeDuration(0);
|
||||
request->mIsASAP = false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
RasterImage::DecodeWorker::Run()
|
||||
{
|
||||
// We just got called back by the event loop; therefore, we're no longer
|
||||
// pending.
|
||||
mPendingInEventLoop = false;
|
||||
|
||||
TimeStamp eventStart = TimeStamp::Now();
|
||||
|
||||
// Now decode until we either run out of time or run out of images.
|
||||
do {
|
||||
// Try to get an ASAP request to handle. If there isn't one, try to get a
|
||||
// normal request. If no normal request is pending either, then we're done
|
||||
// here.
|
||||
DecodeRequest* request = mASAPDecodeRequests.popFirst();
|
||||
if (!request)
|
||||
request = mNormalDecodeRequests.popFirst();
|
||||
if (!request)
|
||||
break;
|
||||
|
||||
RasterImage *image = request->mImage;
|
||||
DecodeSomeOfImage(image);
|
||||
|
||||
// If we aren't yet finished decoding and we have more data in hand, add
|
||||
// this request to the back of the list.
|
||||
if (image->mDecoder &&
|
||||
!image->mError &&
|
||||
!image->IsDecodeFinished() &&
|
||||
image->mSourceData.Length() > image->mBytesDecoded) {
|
||||
AddDecodeRequest(request);
|
||||
}
|
||||
|
||||
} while ((TimeStamp::Now() - eventStart).ToMilliseconds() <= gMaxMSBeforeYield);
|
||||
|
||||
// If decode requests are pending, re-post ourself to the event loop.
|
||||
if (!mASAPDecodeRequests.isEmpty() || !mNormalDecodeRequests.isEmpty()) {
|
||||
EnsurePendingInEventLoop();
|
||||
}
|
||||
|
||||
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY,
|
||||
PRUint32((TimeStamp::Now() - eventStart).ToMilliseconds()));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
RasterImage::DecodeWorker::DecodeUntilSizeAvailable(RasterImage* aImg)
|
||||
{
|
||||
return DecodeSomeOfImage(aImg, DECODE_TYPE_UNTIL_SIZE);
|
||||
}
|
||||
|
||||
nsresult
|
||||
RasterImage::DecodeWorker::DecodeSomeOfImage(
|
||||
RasterImage* aImg,
|
||||
DecodeType aDecodeType /* = DECODE_TYPE_NORMAL */)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(aImg->mInitialized,
|
||||
"Worker active for uninitialized container!");
|
||||
|
||||
if (aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize)
|
||||
return NS_OK;
|
||||
|
||||
// If an error is flagged, it probably happened while we were waiting
|
||||
// in the event queue.
|
||||
if (aImg->mError)
|
||||
return NS_OK;
|
||||
|
||||
// If we don't have a decoder, we must have finished already (for example,
|
||||
// a synchronous decode request came while the worker was pending).
|
||||
if (!aImg->mDecoder)
|
||||
return NS_OK;
|
||||
|
||||
nsRefPtr<Decoder> decoderKungFuDeathGrip = aImg->mDecoder;
|
||||
|
||||
PRUint32 maxBytes;
|
||||
if (aImg->mDecoder->IsSizeDecode()) {
|
||||
// Decode all available data if we're a size decode; they're cheap, and we
|
||||
// want them to be more or less synchronous.
|
||||
maxBytes = aImg->mSourceData.Length();
|
||||
} else {
|
||||
// We're only guaranteed to decode this many bytes, so in particular,
|
||||
// gDecodeBytesAtATime should be set high enough for us to read the size
|
||||
// from most images.
|
||||
maxBytes = gDecodeBytesAtATime;
|
||||
}
|
||||
|
||||
// Loop control
|
||||
PRInt32 chunkCount = 0;
|
||||
TimeStamp start = TimeStamp::Now();
|
||||
TimeStamp deadline = start + TimeDuration::FromMilliseconds(gMaxMSBeforeYield);
|
||||
|
||||
// We keep decoding chunks until one of events occurs:
|
||||
// 1) We don't have any data left to decode
|
||||
// 2) The decode completes
|
||||
// 3) We're an UNTIL_SIZE decode and we get the size
|
||||
// 4) We hit the deadline and yield to keep the UI snappy
|
||||
while (aImg->mSourceData.Length() > aImg->mBytesDecoded &&
|
||||
!aImg->IsDecodeFinished() &&
|
||||
TimeStamp::Now() < deadline) {
|
||||
|
||||
// Decode a chunk of data.
|
||||
chunkCount++;
|
||||
nsresult rv = aImg->DecodeSomeData(maxBytes);
|
||||
if (NS_FAILED(rv)) {
|
||||
aImg->DoError();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// If we're an UNTIL_SIZE decode and we got the image's size, we're done
|
||||
// here.
|
||||
if (aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize)
|
||||
break;
|
||||
}
|
||||
|
||||
aImg->mDecodeRequest.mDecodeTime += (TimeStamp::Now() - start);
|
||||
|
||||
if (chunkCount && !aImg->mDecoder->IsSizeDecode()) {
|
||||
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, chunkCount);
|
||||
}
|
||||
|
||||
// Flush invalidations _after_ we've written everything we're going to.
|
||||
// Furthermore, if we have all of the data, we don't want to do progressive
|
||||
// display at all. In that case, let Decoder::PostFrameStop() do the
|
||||
// flush once the whole frame is ready.
|
||||
if (!aImg->mHasSourceData) {
|
||||
aImg->mInDecoder = true;
|
||||
aImg->mDecoder->FlushInvalidations();
|
||||
aImg->mInDecoder = false;
|
||||
}
|
||||
|
||||
// If the decode finished, shut down the decoder.
|
||||
if (aImg->mDecoder && aImg->IsDecodeFinished()) {
|
||||
|
||||
// Do some telemetry if this isn't a size decode.
|
||||
DecodeRequest* request = &aImg->mDecodeRequest;
|
||||
if (!aImg->mDecoder->IsSizeDecode()) {
|
||||
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME,
|
||||
PRInt32(request->mDecodeTime.ToMicroseconds()));
|
||||
|
||||
// We record the speed for only some decoders. The rest have
|
||||
// SpeedHistogram return HistogramCount.
|
||||
Telemetry::ID id = aImg->mDecoder->SpeedHistogram();
|
||||
if (id < Telemetry::HistogramCount) {
|
||||
PRInt32 KBps = PRInt32(request->mImage->mBytesDecoded /
|
||||
(1024 * request->mDecodeTime.ToSeconds()));
|
||||
Telemetry::Accumulate(id, KBps);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = aImg->ShutdownDecoder(RasterImage::eShutdownIntent_Done);
|
||||
if (NS_FAILED(rv)) {
|
||||
aImg->DoError();
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
@ -66,6 +66,7 @@
|
||||
#include "DiscardTracker.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#ifdef DEBUG
|
||||
#include "imgIContainerDebug.h"
|
||||
#endif
|
||||
@ -164,7 +165,6 @@ class ImageContainer;
|
||||
}
|
||||
namespace image {
|
||||
|
||||
class imgDecodeWorker;
|
||||
class Decoder;
|
||||
|
||||
class RasterImage : public Image
|
||||
@ -376,6 +376,123 @@ private:
|
||||
~Anim() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* DecodeWorker keeps a linked list of DecodeRequests to keep track of the
|
||||
* images it needs to decode.
|
||||
*
|
||||
* Each RasterImage has a single DecodeRequest member.
|
||||
*/
|
||||
struct DecodeRequest : public LinkedListElement<DecodeRequest>
|
||||
{
|
||||
DecodeRequest(RasterImage* aImage)
|
||||
: mImage(aImage)
|
||||
, mIsASAP(false)
|
||||
{
|
||||
}
|
||||
|
||||
RasterImage* const mImage;
|
||||
|
||||
/* Keeps track of how much time we've burned decoding this particular decode
|
||||
* request. */
|
||||
TimeDuration mDecodeTime;
|
||||
|
||||
/* True if we need to handle this decode as soon as possible. */
|
||||
bool mIsASAP;
|
||||
};
|
||||
|
||||
/*
|
||||
* DecodeWorker is a singleton class we use when decoding large images.
|
||||
*
|
||||
* When we wish to decode an image larger than
|
||||
* image.mem.max_bytes_for_sync_decode, we call DecodeWorker::RequestDecode()
|
||||
* for the image. This adds the image to a queue of pending requests and posts
|
||||
* the DecodeWorker singleton to the event queue, if it's not already pending
|
||||
* there.
|
||||
*
|
||||
* When the DecodeWorker is run from the event queue, it decodes the image (and
|
||||
* all others it's managing) in chunks, periodically yielding control back to
|
||||
* the event loop.
|
||||
*
|
||||
* An image being decoded may have one of two priorities: normal or ASAP. ASAP
|
||||
* images are always decoded before normal images. (We currently give ASAP
|
||||
* priority to images which appear onscreen but are not yet decoded.)
|
||||
*/
|
||||
class DecodeWorker : public nsRunnable
|
||||
{
|
||||
public:
|
||||
static DecodeWorker* Singleton();
|
||||
|
||||
/**
|
||||
* Ask the DecodeWorker to asynchronously decode this image.
|
||||
*/
|
||||
void RequestDecode(RasterImage* aImg);
|
||||
|
||||
/**
|
||||
* Give this image ASAP priority; it will be decoded before all non-ASAP
|
||||
* images. You can call MarkAsASAP before or after you call RequestDecode
|
||||
* for the image, but if you MarkAsASAP before you call RequestDecode, you
|
||||
* still need to call RequestDecode.
|
||||
*
|
||||
* StopDecoding() resets the image's ASAP flag.
|
||||
*/
|
||||
void MarkAsASAP(RasterImage* aImg);
|
||||
|
||||
/**
|
||||
* Ask the DecodeWorker to stop decoding this image. Internally, we also
|
||||
* call this function when we finish decoding an image.
|
||||
*
|
||||
* Since the DecodeWorker keeps raw pointers to RasterImages, make sure you
|
||||
* call this before a RasterImage is destroyed!
|
||||
*/
|
||||
void StopDecoding(RasterImage* aImg);
|
||||
|
||||
/**
|
||||
* Synchronously decode the beginning of the image until we run out of
|
||||
* bytes or we get the image's size. Note that this done on a best-effort
|
||||
* basis; if the size is burried too deep in the image, we'll give up.
|
||||
*
|
||||
* @return NS_ERROR if an error is encountered, and NS_OK otherwise. (Note
|
||||
* that we return NS_OK even when the size was not found.)
|
||||
*/
|
||||
nsresult DecodeUntilSizeAvailable(RasterImage* aImg);
|
||||
|
||||
NS_IMETHOD Run();
|
||||
|
||||
private: /* statics */
|
||||
static nsRefPtr<DecodeWorker> sSingleton;
|
||||
|
||||
private: /* methods */
|
||||
DecodeWorker()
|
||||
: mPendingInEventLoop(false)
|
||||
{}
|
||||
|
||||
/* Post ourselves to the event loop if we're not currently pending. */
|
||||
void EnsurePendingInEventLoop();
|
||||
|
||||
/* Add the given request to the appropriate list of decode requests, but
|
||||
* don't ensure that we're pending in the event loop. */
|
||||
void AddDecodeRequest(DecodeRequest* aRequest);
|
||||
|
||||
enum DecodeType {
|
||||
DECODE_TYPE_NORMAL,
|
||||
DECODE_TYPE_UNTIL_SIZE
|
||||
};
|
||||
|
||||
/* Decode some chunks of the given image. If aDecodeType is UNTIL_SIZE,
|
||||
* decode until we have the image's size, then stop. */
|
||||
nsresult DecodeSomeOfImage(RasterImage* aImg,
|
||||
DecodeType aDecodeType = DECODE_TYPE_NORMAL);
|
||||
|
||||
private: /* members */
|
||||
|
||||
LinkedList<DecodeRequest> mASAPDecodeRequests;
|
||||
LinkedList<DecodeRequest> mNormalDecodeRequests;
|
||||
|
||||
/* True if we've posted ourselves to the event loop and expect Run() to
|
||||
* be called sometime in the future. */
|
||||
bool mPendingInEventLoop;
|
||||
};
|
||||
|
||||
/**
|
||||
* Advances the animation. Typically, this will advance a single frame, but it
|
||||
* may advance multiple frames. This may happen if we have infrequently
|
||||
@ -524,12 +641,11 @@ private: // data
|
||||
nsCString mSourceDataMimeType;
|
||||
nsCString mURIString;
|
||||
|
||||
friend class imgDecodeWorker;
|
||||
friend class DiscardTracker;
|
||||
|
||||
// Decoder and friends
|
||||
nsRefPtr<Decoder> mDecoder;
|
||||
nsRefPtr<imgDecodeWorker> mWorker;
|
||||
DecodeRequest mDecodeRequest;
|
||||
PRUint32 mBytesDecoded;
|
||||
|
||||
// How many times we've decoded this image.
|
||||
@ -554,8 +670,6 @@ private: // data
|
||||
bool mDecoded:1;
|
||||
bool mHasBeenDecoded:1;
|
||||
|
||||
// Helpers for decoder
|
||||
bool mWorkerPending:1;
|
||||
bool mInDecoder:1;
|
||||
|
||||
// Whether the animation can stop, due to running out
|
||||
@ -591,29 +705,6 @@ protected:
|
||||
bool ShouldAnimate();
|
||||
};
|
||||
|
||||
// XXXdholbert These helper classes should move to be inside the
|
||||
// scope of the RasterImage class.
|
||||
// Decoding Helper Class
|
||||
//
|
||||
// We use this class to mimic the interactivity benefits of threading
|
||||
// in a single-threaded event loop. We want to progressively decode
|
||||
// and keep a responsive UI while we're at it, so we have a runnable
|
||||
// class that does a bit of decoding, and then "yields" by dispatching
|
||||
// itself to the end of the event queue.
|
||||
class imgDecodeWorker : public nsRunnable
|
||||
{
|
||||
public:
|
||||
imgDecodeWorker(imgIContainer* aContainer) {
|
||||
mContainer = do_GetWeakReference(aContainer);
|
||||
}
|
||||
NS_IMETHOD Run();
|
||||
NS_METHOD Dispatch();
|
||||
|
||||
private:
|
||||
nsWeakPtr mContainer;
|
||||
TimeDuration mDecodeTime; // the default constructor initializes to 0
|
||||
};
|
||||
|
||||
// Asynchronous Decode Requestor
|
||||
//
|
||||
// We use this class when someone calls requestDecode() from within a decode
|
||||
|
@ -86,6 +86,9 @@ _TEST_FILES = imgutils.js \
|
||||
bug671906-iframe.html \
|
||||
bug671906.sjs \
|
||||
test_bug671906.html \
|
||||
test_error_events.html \
|
||||
error-early.png \
|
||||
error-late.png \
|
||||
$(NULL)
|
||||
|
||||
# Tests disabled due to intermittent orange
|
||||
|
1
image/test/mochitest/error-early.png
Normal file
1
image/test/mochitest/error-early.png
Normal file
@ -0,0 +1 @@
|
||||
ERROR
|
BIN
image/test/mochitest/error-late.png
Normal file
BIN
image/test/mochitest/error-late.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
75
image/test/mochitest/test_error_events.html
Normal file
75
image/test/mochitest/test_error_events.html
Normal file
@ -0,0 +1,75 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=715308
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 715308 comment 93</title>
|
||||
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Test for Bug 715308 comment 93:
|
||||
|
||||
- For a valid image, onload is fired and onerror is never fired.
|
||||
|
||||
- For an image with errors (either early or late in the image data), onerror
|
||||
is fired, but onload is never fired.
|
||||
|
||||
- For any image, either onload or onerror is fired, but never both.
|
||||
|
||||
-->
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var numCallbacks = 0;
|
||||
|
||||
function image_error(name)
|
||||
{
|
||||
numCallbacks++;
|
||||
ok(name == 'error-early' || name == 'error-late', "Got onerror for " + name);
|
||||
}
|
||||
|
||||
function image_load(name)
|
||||
{
|
||||
numCallbacks++;
|
||||
ok(name == 'shaver', "Got onload for " + name);
|
||||
}
|
||||
|
||||
function page_load()
|
||||
{
|
||||
ok(numCallbacks == 3, 'Got page load before all onload/onerror callbacks?');
|
||||
|
||||
// Spin the event loop a few times to let image_error run if it's going to,
|
||||
// then finish the test.
|
||||
SimpleTest.executeSoon(function() {
|
||||
SimpleTest.executeSoon(function() {
|
||||
SimpleTest.executeSoon(function() {
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addEventListener('load', page_load);
|
||||
|
||||
</script>
|
||||
|
||||
<div id="content">
|
||||
<img src='shaver.png' onerror='image_error("shaver")' onload='image_load("shaver")'>
|
||||
<img src='error-early.png' onerror='image_error("error-early")' onload='image_load("error-early")'>
|
||||
|
||||
<!-- This image has invalid data (hopefully triggering a decode error)
|
||||
relatively late in the bitstream. Compare to shaver.png with a binary
|
||||
diff tool. -->
|
||||
<img src='error-late.png' onerror='image_error("error-late")' onload='image_load("error-late")'>
|
||||
</div>
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -45,6 +45,7 @@ fails-if(Android) skip-if(d2d||cocoaWidget) == subpixel-glyphs-x-1a.html subpixe
|
||||
# subpixel.
|
||||
# D2D/DirectWrite results depend on the rendering mode chosen, so considering this as random for now.
|
||||
skip-if(!(d2d||cocoaWidget)) random-if(d2d) != subpixel-glyphs-x-2a.html subpixel-glyphs-x-2b.html
|
||||
HTTP(..) == subpixel-glyphs-x-3a.html subpixel-glyphs-x-3b.html
|
||||
# No platforms do subpixel positioning vertically
|
||||
== subpixel-glyphs-y-1a.html subpixel-glyphs-y-1b.html
|
||||
== subpixel-lineheight-1a.html subpixel-lineheight-1b.html
|
||||
|
33
layout/reftests/text/subpixel-glyphs-x-3a.html
Normal file
33
layout/reftests/text/subpixel-glyphs-x-3a.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<title>Test for consistent kerning, bug 716402</title>
|
||||
<style type="text/css">
|
||||
@font-face {
|
||||
font-family: mplus;
|
||||
src: url(../fonts/mplus/mplus-1p-regular-no-OT.ttf);
|
||||
/* a copy of M+ with OpenType tables removed,
|
||||
so only legacy 'kern' is present */
|
||||
}
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: mplus;
|
||||
font-size: 15px;
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
AVAV
|
||||
</div>
|
||||
<div class="right">
|
||||
AVAV
|
||||
</div>
|
||||
</body>
|
36
layout/reftests/text/subpixel-glyphs-x-3b.html
Normal file
36
layout/reftests/text/subpixel-glyphs-x-3b.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<title>Reference for consistent kerning, bug 716402</title>
|
||||
<style type="text/css">
|
||||
@font-face {
|
||||
font-family: mplus;
|
||||
src: url(../fonts/mplus/mplus-1p-regular-no-OT.ttf);
|
||||
/* a copy of M+ with OpenType tables removed,
|
||||
so only legacy 'kern' is present */
|
||||
}
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: mplus;
|
||||
font-size: 15px;
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
span {
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
AVAV<span>AV</span>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span>AV</span>AVAV
|
||||
</div>
|
||||
</body>
|
400
mfbt/LinkedList.h
Normal file
400
mfbt/LinkedList.h
Normal file
@ -0,0 +1,400 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=4 sw=4 tw=80 et cin:
|
||||
*
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at:
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* The Mozilla Foundation
|
||||
* Portions created by the Initial Developer are Copyright (C) 2012
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Justin Lebar <justin.lebar@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/* A type-safe doubly-linked list class. */
|
||||
|
||||
/*
|
||||
* The classes LinkedList<T> and LinkedListElement<T> together form a
|
||||
* convenient, type-safe doubly-linked list implementation.
|
||||
*
|
||||
* The class T which will be inserted into the linked list must inherit from
|
||||
* LinkedListElement<T>. A given object may be in only one linked list at a
|
||||
* time.
|
||||
*
|
||||
* For example, you might use LinkedList in a simple observer list class as
|
||||
* follows.
|
||||
*
|
||||
* class Observer : public LinkedListElement<Observer>
|
||||
* {
|
||||
* void observe(char* topic) { ... }
|
||||
* };
|
||||
*
|
||||
* class ObserverContainer
|
||||
* {
|
||||
* private:
|
||||
* LinkedList<ElemType> list;
|
||||
*
|
||||
* public:
|
||||
*
|
||||
* void addObserver(Observer* observer)
|
||||
* {
|
||||
* // Will assert if |observer| is part of another list.
|
||||
* list.insertBack(observer);
|
||||
* }
|
||||
*
|
||||
* void removeObserver(Observer* observer)
|
||||
* {
|
||||
* // Will assert if |observer| is not part of some list.
|
||||
* observer.remove();
|
||||
* }
|
||||
*
|
||||
* void notifyObservers(char* topic)
|
||||
* {
|
||||
* for (Observer* o = list.getFirst();
|
||||
* o != NULL;
|
||||
* o = o->getNext()) {
|
||||
* o->Observe(topic);
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef mozilla_LinkedList_h_
|
||||
#define mozilla_LinkedList_h_
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
template<typename T>
|
||||
class LinkedList;
|
||||
|
||||
template<typename T>
|
||||
class LinkedListElement
|
||||
{
|
||||
/*
|
||||
* It's convenient that we return NULL when getNext() or getPrevious() hits
|
||||
* the end of the list, but doing so costs an extra word of storage in each
|
||||
* linked list node (to keep track of whether |this| is the sentinel node)
|
||||
* and a branch on this value in getNext/getPrevious.
|
||||
*
|
||||
* We could get rid of the extra word of storage by shoving the "is
|
||||
* sentinel" bit into one of the pointers, although this would, of course,
|
||||
* have performance implications of its own.
|
||||
*
|
||||
* But the goal here isn't to win an award for the fastest or slimmest
|
||||
* linked list; rather, we want a *convenient* linked list. So we won't
|
||||
* waste time guessing which micro-optimization strategy is best.
|
||||
*/
|
||||
|
||||
private:
|
||||
LinkedListElement* next;
|
||||
LinkedListElement* prev;
|
||||
const bool isSentinel;
|
||||
|
||||
public:
|
||||
LinkedListElement()
|
||||
: next(this)
|
||||
, prev(this)
|
||||
, isSentinel(false)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the next element in the list, or NULL if this is the last element in
|
||||
* the list.
|
||||
*/
|
||||
T* getNext()
|
||||
{
|
||||
return next->asT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the previous element in the list, or NULL if this is the first element
|
||||
* in the list.
|
||||
*/
|
||||
T* getPrevious()
|
||||
{
|
||||
return prev->asT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert elem after this element in the list. |this| must be part of a
|
||||
* linked list when you call setNext(); otherwise, this method will assert.
|
||||
*/
|
||||
void setNext(T* elem)
|
||||
{
|
||||
MOZ_ASSERT(isInList());
|
||||
setNextUnsafe(elem);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert elem before this element in the list. |this| must be part of a
|
||||
* linked list when you call setPrevious(); otherwise, this method will
|
||||
* assert.
|
||||
*/
|
||||
void setPrevious(T* elem)
|
||||
{
|
||||
MOZ_ASSERT(isInList());
|
||||
setPreviousUnsafe(elem);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove this element from the list which contains it. If this element is
|
||||
* not currently part of a linked list, this method asserts.
|
||||
*/
|
||||
void remove()
|
||||
{
|
||||
MOZ_ASSERT(isInList());
|
||||
|
||||
prev->next = next;
|
||||
next->prev = prev;
|
||||
next = this;
|
||||
prev = this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if |this| part is of a linked list, and false otherwise.
|
||||
*/
|
||||
bool isInList()
|
||||
{
|
||||
MOZ_ASSERT((next == this) == (prev == this));
|
||||
return next != this;
|
||||
}
|
||||
|
||||
private:
|
||||
LinkedListElement& operator=(const LinkedList<T>& other) MOZ_DELETE;
|
||||
LinkedListElement(const LinkedList<T>& other) MOZ_DELETE;
|
||||
|
||||
friend class LinkedList<T>;
|
||||
|
||||
enum NodeKind {
|
||||
NODE_TYPE_NORMAL,
|
||||
NODE_TYPE_SENTINEL
|
||||
};
|
||||
|
||||
LinkedListElement(NodeKind nodeType)
|
||||
: next(this)
|
||||
, prev(this)
|
||||
, isSentinel(nodeType == NODE_TYPE_SENTINEL)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Return |this| cast to T* if we're a normal node, or return NULL if we're
|
||||
* a sentinel node.
|
||||
*/
|
||||
T* asT()
|
||||
{
|
||||
if (isSentinel)
|
||||
return NULL;
|
||||
|
||||
return static_cast<T*>(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert elem after this element, but don't check that this element is in
|
||||
* the list. This is called by LinkedList::insertFront().
|
||||
*/
|
||||
void setNextUnsafe(T* elem)
|
||||
{
|
||||
LinkedListElement *listElem = static_cast<LinkedListElement*>(elem);
|
||||
MOZ_ASSERT(!listElem->isInList());
|
||||
|
||||
listElem->next = this->next;
|
||||
listElem->prev = this;
|
||||
this->next->prev = listElem;
|
||||
this->next = listElem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert elem before this element, but don't check that this element is in
|
||||
* the list. This is called by LinkedList::insertBack().
|
||||
*/
|
||||
void setPreviousUnsafe(T* elem)
|
||||
{
|
||||
LinkedListElement<T>* listElem = static_cast<LinkedListElement<T>*>(elem);
|
||||
MOZ_ASSERT(!listElem->isInList());
|
||||
|
||||
listElem->next = this;
|
||||
listElem->prev = this->prev;
|
||||
this->prev->next = listElem;
|
||||
this->prev = listElem;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class LinkedList
|
||||
{
|
||||
private:
|
||||
LinkedListElement<T> sentinel;
|
||||
|
||||
public:
|
||||
LinkedList& operator=(const LinkedList<T>& other) MOZ_DELETE;
|
||||
LinkedList(const LinkedList<T>& other) MOZ_DELETE;
|
||||
|
||||
LinkedList()
|
||||
: sentinel(LinkedListElement<T>::NODE_TYPE_SENTINEL)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Add elem to the front of the list.
|
||||
*/
|
||||
void insertFront(T* elem)
|
||||
{
|
||||
/* Bypass setNext()'s this->isInList() assertion. */
|
||||
sentinel.setNextUnsafe(elem);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add elem to the back of the list.
|
||||
*/
|
||||
void insertBack(T* elem)
|
||||
{
|
||||
sentinel.setPreviousUnsafe(elem);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the first element of the list, or NULL if the list is empty.
|
||||
*/
|
||||
T* getFirst()
|
||||
{
|
||||
return sentinel.getNext();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the last element of the list, or NULL if the list is empty.
|
||||
*/
|
||||
T* getLast()
|
||||
{
|
||||
return sentinel.getPrevious();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get and remove the first element of the list. If the list is empty,
|
||||
* return NULL.
|
||||
*/
|
||||
T* popFirst()
|
||||
{
|
||||
T* ret = sentinel.getNext();
|
||||
if (ret)
|
||||
static_cast<LinkedListElement<T>*>(ret)->remove();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get and remove the last element of the list. If the list is empty,
|
||||
* return NULL.
|
||||
*/
|
||||
T* popLast()
|
||||
{
|
||||
T* ret = sentinel.getPrevious();
|
||||
if (ret)
|
||||
static_cast<LinkedListElement<T>*>(ret)->remove();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the list is empty, or false otherwise.
|
||||
*/
|
||||
bool isEmpty()
|
||||
{
|
||||
return !sentinel.isInList();
|
||||
}
|
||||
|
||||
/*
|
||||
* In a debug build, make sure that the list is sane (no cycles, consistent
|
||||
* next/prev pointers, only one sentinel). Has no effect in release builds.
|
||||
*/
|
||||
void debugAssertIsSane()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
/*
|
||||
* Check for cycles in the forward singly-linked list using the
|
||||
* tortoise/hare algorithm.
|
||||
*/
|
||||
for (LinkedListElement<T>* slow = sentinel.next,
|
||||
* fast1 = sentinel.next->next,
|
||||
* fast2 = sentinel.next->next->next;
|
||||
slow != sentinel && fast1 != sentinel && fast2 != sentinel;
|
||||
slow = slow->next,
|
||||
fast1 = fast2->next,
|
||||
fast2 = fast1->next) {
|
||||
|
||||
MOZ_ASSERT(slow != fast1);
|
||||
MOZ_ASSERT(slow != fast2);
|
||||
}
|
||||
|
||||
/* Check for cycles in the backward singly-linked list. */
|
||||
for (LinkedListElement<T>* slow = sentinel.prev,
|
||||
* fast1 = sentinel.prev->prev,
|
||||
* fast2 = sentinel.prev->prev->prev;
|
||||
slow != sentinel && fast1 != sentinel && fast2 != sentinel;
|
||||
slow = slow->prev,
|
||||
fast1 = fast2->prev,
|
||||
fast2 = fast1->prev) {
|
||||
|
||||
MOZ_ASSERT(slow != fast1);
|
||||
MOZ_ASSERT(slow != fast2);
|
||||
}
|
||||
|
||||
/* Check that |sentinel| is the only root in the list. */
|
||||
for (LinkedListElement<T>* elem = sentinel.next;
|
||||
elem != sentinel;
|
||||
elem = elem->next) {
|
||||
|
||||
MOZ_ASSERT(!elem->isSentinel);
|
||||
}
|
||||
|
||||
/* Check that the next/prev pointers match up. */
|
||||
LinkedListElement<T>* prev = sentinel;
|
||||
LinkedListElement<T>* cur = sentinel.next;
|
||||
do {
|
||||
MOZ_ASSERT(cur->prev == prev);
|
||||
MOZ_ASSERT(prev->next == cur);
|
||||
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
} while (cur != sentinel);
|
||||
#endif /* ifdef DEBUG */
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace mozilla */
|
||||
|
||||
#endif /* ifdef __cplusplus */
|
||||
#endif /* ifdef mozilla_LinkedList_h_ */
|
@ -45,6 +45,7 @@ EXPORTS_mozilla += \
|
||||
Assertions.h \
|
||||
Attributes.h \
|
||||
GuardObjects.h \
|
||||
LinkedList.h \
|
||||
MSStdInt.h \
|
||||
RangedPtr.h \
|
||||
RefPtr.h \
|
||||
|
@ -604,7 +604,7 @@ pref("media.preload.auto", 2); // preload metadata if preload=auto
|
||||
// 0: don't show fullscreen keyboard
|
||||
// 1: always show fullscreen keyboard
|
||||
// -1: show fullscreen keyboard based on threshold pref
|
||||
pref("widget.ime.android.landscape_fullscreen", -1);
|
||||
pref("widget.ime.android.landscape_fullscreen", 1);
|
||||
pref("widget.ime.android.fullscreen_threshold", 250); // in hundreths of inches
|
||||
|
||||
// optimize images memory usage
|
||||
|
@ -46,6 +46,7 @@ import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@ -97,6 +98,14 @@ public class AboutHomeContent extends ScrollView {
|
||||
private static final int NUMBER_OF_COLS_PORTRAIT = 2;
|
||||
private static final int NUMBER_OF_COLS_LANDSCAPE = 3;
|
||||
|
||||
static enum UpdateFlags {
|
||||
TOP_SITES,
|
||||
PREVIOUS_TABS,
|
||||
RECOMMENDED_ADDONS;
|
||||
|
||||
public static final EnumSet<UpdateFlags> ALL = EnumSet.allOf(UpdateFlags.class);
|
||||
}
|
||||
|
||||
private Cursor mCursor;
|
||||
UriLoadCallback mUriLoadCallback = null;
|
||||
private LayoutInflater mInflater;
|
||||
@ -111,73 +120,63 @@ public class AboutHomeContent extends ScrollView {
|
||||
public void callback(String uriSpec);
|
||||
}
|
||||
|
||||
public AboutHomeContent(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setScrollContainer(true);
|
||||
setBackgroundResource(R.drawable.abouthome_bg_repeat);
|
||||
public AboutHomeContent(Context context) {
|
||||
super(context);
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
mInflater.inflate(R.layout.abouthome_content, this);
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
synchronized (this) {
|
||||
if (mTopSitesGrid != null && mAddonsLayout != null && mLastTabsLayout != null)
|
||||
return;
|
||||
mTopSitesGrid = (GridView)findViewById(R.id.top_sites_grid);
|
||||
mTopSitesGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
Cursor c = (Cursor) parent.getItemAtPosition(position);
|
||||
|
||||
mTopSitesGrid = (GridView)findViewById(R.id.top_sites_grid);
|
||||
mTopSitesGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
Cursor c = (Cursor) parent.getItemAtPosition(position);
|
||||
String spec = c.getString(c.getColumnIndex(URLColumns.URL));
|
||||
Log.i(LOGTAG, "clicked: " + spec);
|
||||
|
||||
String spec = c.getString(c.getColumnIndex(URLColumns.URL));
|
||||
Log.i(LOGTAG, "clicked: " + spec);
|
||||
|
||||
if (mUriLoadCallback != null)
|
||||
mUriLoadCallback.callback(spec);
|
||||
}
|
||||
});
|
||||
|
||||
mAddonsLayout = (LinearLayout) findViewById(R.id.recommended_addons);
|
||||
mLastTabsLayout = (LinearLayout) findViewById(R.id.last_tabs);
|
||||
|
||||
TextView allTopSitesText = (TextView) findViewById(R.id.all_top_sites_text);
|
||||
allTopSitesText.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
GeckoApp.mAppContext.showAwesomebar(AwesomeBar.Type.EDIT);
|
||||
}
|
||||
});
|
||||
|
||||
TextView allAddonsText = (TextView) findViewById(R.id.all_addons_text);
|
||||
allAddonsText.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (mUriLoadCallback != null)
|
||||
mUriLoadCallback.callback("about:addons");
|
||||
}
|
||||
});
|
||||
|
||||
TextView syncTextView = (TextView) findViewById(R.id.sync_text);
|
||||
String syncText = syncTextView.getText().toString() + " \u00BB";
|
||||
String boldName = getContext().getResources().getString(R.string.abouthome_sync_bold_name);
|
||||
int styleIndex = syncText.indexOf(boldName);
|
||||
|
||||
// Highlight any occurrence of "Firefox Sync" in the string
|
||||
// with a bold style.
|
||||
if (styleIndex >= 0) {
|
||||
SpannableString spannableText = new SpannableString(syncText);
|
||||
spannableText.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), styleIndex, styleIndex + 12, 0);
|
||||
syncTextView.setText(spannableText, TextView.BufferType.SPANNABLE);
|
||||
if (mUriLoadCallback != null)
|
||||
mUriLoadCallback.callback(spec);
|
||||
}
|
||||
});
|
||||
|
||||
RelativeLayout syncBox = (RelativeLayout) findViewById(R.id.sync_box);
|
||||
syncBox.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
Context context = v.getContext();
|
||||
Intent intent = new Intent(context, SetupSyncActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
});
|
||||
mAddonsLayout = (LinearLayout) findViewById(R.id.recommended_addons);
|
||||
mLastTabsLayout = (LinearLayout) findViewById(R.id.last_tabs);
|
||||
|
||||
TextView allTopSitesText = (TextView) findViewById(R.id.all_top_sites_text);
|
||||
allTopSitesText.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
GeckoApp.mAppContext.showAwesomebar(AwesomeBar.Type.EDIT);
|
||||
}
|
||||
});
|
||||
|
||||
TextView allAddonsText = (TextView) findViewById(R.id.all_addons_text);
|
||||
allAddonsText.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (mUriLoadCallback != null)
|
||||
mUriLoadCallback.callback("about:addons");
|
||||
}
|
||||
});
|
||||
|
||||
TextView syncTextView = (TextView) findViewById(R.id.sync_text);
|
||||
String syncText = syncTextView.getText().toString() + " \u00BB";
|
||||
String boldName = getContext().getResources().getString(R.string.abouthome_sync_bold_name);
|
||||
int styleIndex = syncText.indexOf(boldName);
|
||||
|
||||
// Highlight any occurrence of "Firefox Sync" in the string
|
||||
// with a bold style.
|
||||
if (styleIndex >= 0) {
|
||||
SpannableString spannableText = new SpannableString(syncText);
|
||||
spannableText.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), styleIndex, styleIndex + 12, 0);
|
||||
syncTextView.setText(spannableText, TextView.BufferType.SPANNABLE);
|
||||
}
|
||||
|
||||
RelativeLayout syncBox = (RelativeLayout) findViewById(R.id.sync_box);
|
||||
syncBox.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
Context context = v.getContext();
|
||||
Intent intent = new Intent(context, SetupSyncActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setTopSitesVisibility(boolean visible, boolean hasTopSites) {
|
||||
@ -251,59 +250,59 @@ public class AboutHomeContent extends ScrollView {
|
||||
return NUMBER_OF_COLS_PORTRAIT;
|
||||
}
|
||||
|
||||
void init(final Activity activity) {
|
||||
mInflater.inflate(R.layout.abouthome_content, this);
|
||||
final Runnable generateCursorsRunnable = new Runnable() {
|
||||
private void loadTopSites(final Activity activity) {
|
||||
if (mCursor != null)
|
||||
activity.stopManagingCursor(mCursor);
|
||||
|
||||
// Ensure we initialize GeckoApp's startup mode in
|
||||
// background thread before we use it when updating
|
||||
// the top sites section layout in main thread.
|
||||
final GeckoApp.StartupMode startupMode = GeckoApp.mAppContext.getStartupMode();
|
||||
|
||||
// The isSyncSetup method should not be called on
|
||||
// UI thread as it touches disk to access a sqlite DB.
|
||||
final boolean syncIsSetup = isSyncSetup();
|
||||
|
||||
ContentResolver resolver = GeckoApp.mAppContext.getContentResolver();
|
||||
mCursor = BrowserDB.getTopSites(resolver, NUMBER_OF_TOP_SITES_PORTRAIT);
|
||||
activity.startManagingCursor(mCursor);
|
||||
|
||||
GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
|
||||
public void run() {
|
||||
if (mCursor != null)
|
||||
activity.stopManagingCursor(mCursor);
|
||||
if (mTopSitesAdapter == null) {
|
||||
mTopSitesAdapter = new TopSitesCursorAdapter(activity,
|
||||
R.layout.abouthome_topsite_item,
|
||||
mCursor,
|
||||
new String[] { URLColumns.TITLE,
|
||||
URLColumns.THUMBNAIL },
|
||||
new int[] { R.id.title, R.id.thumbnail });
|
||||
|
||||
// Ensure we initialize GeckoApp's startup mode in
|
||||
// background thread before we use it when updating
|
||||
// the top sites section layout in main thread.
|
||||
final GeckoApp.StartupMode startupMode = GeckoApp.mAppContext.getStartupMode();
|
||||
mTopSitesAdapter.setViewBinder(new TopSitesViewBinder());
|
||||
mTopSitesGrid.setAdapter(mTopSitesAdapter);
|
||||
} else {
|
||||
mTopSitesAdapter.changeCursor(mCursor);
|
||||
}
|
||||
|
||||
// The isSyncSetup method should not be called on
|
||||
// UI thread as it touches disk to access a sqlite DB.
|
||||
final boolean syncIsSetup = isSyncSetup();
|
||||
mTopSitesGrid.setNumColumns(getNumberOfColumns());
|
||||
|
||||
ContentResolver resolver = GeckoApp.mAppContext.getContentResolver();
|
||||
mCursor = BrowserDB.getTopSites(resolver, NUMBER_OF_TOP_SITES_PORTRAIT);
|
||||
activity.startManagingCursor(mCursor);
|
||||
|
||||
mTopSitesAdapter = new TopSitesCursorAdapter(activity,
|
||||
R.layout.abouthome_topsite_item,
|
||||
mCursor,
|
||||
new String[] { URLColumns.TITLE,
|
||||
URLColumns.THUMBNAIL },
|
||||
new int[] { R.id.title, R.id.thumbnail });
|
||||
|
||||
GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
|
||||
public void run() {
|
||||
mTopSitesGrid.setNumColumns(getNumberOfColumns());
|
||||
|
||||
mTopSitesGrid.setAdapter(mTopSitesAdapter);
|
||||
mTopSitesAdapter.setViewBinder(new TopSitesViewBinder());
|
||||
|
||||
updateLayout(startupMode, syncIsSetup);
|
||||
}
|
||||
});
|
||||
|
||||
GeckoAppShell.getHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
readLastTabs(activity);
|
||||
readRecommendedAddons(activity);
|
||||
}
|
||||
});
|
||||
updateLayout(startupMode, syncIsSetup);
|
||||
}
|
||||
};
|
||||
Runnable finishInflateRunnable = new Runnable() {
|
||||
});
|
||||
}
|
||||
|
||||
void update(final Activity activity, final EnumSet<UpdateFlags> flags) {
|
||||
GeckoAppShell.getHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
onFinishInflate();
|
||||
GeckoAppShell.getHandler().post(generateCursorsRunnable);
|
||||
if (flags.contains(UpdateFlags.TOP_SITES))
|
||||
loadTopSites(activity);
|
||||
|
||||
if (flags.contains(UpdateFlags.PREVIOUS_TABS))
|
||||
readLastTabs(activity);
|
||||
|
||||
if (flags.contains(UpdateFlags.RECOMMENDED_ADDONS))
|
||||
readRecommendedAddons(activity);
|
||||
}
|
||||
};
|
||||
GeckoApp.mAppContext.mMainHandler.post(finishInflateRunnable);
|
||||
});
|
||||
}
|
||||
|
||||
public void setUriLoadCallback(UriLoadCallback uriLoadCallback) {
|
||||
@ -311,19 +310,7 @@ public class AboutHomeContent extends ScrollView {
|
||||
}
|
||||
|
||||
public void onActivityContentChanged(Activity activity) {
|
||||
GeckoAppShell.getHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
final GeckoApp.StartupMode startupMode = GeckoApp.mAppContext.getStartupMode();
|
||||
final boolean syncIsSetup = isSyncSetup();
|
||||
|
||||
GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
|
||||
public void run() {
|
||||
mTopSitesGrid.setAdapter(mTopSitesAdapter);
|
||||
updateLayout(startupMode, syncIsSetup);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
update(activity, EnumSet.of(UpdateFlags.TOP_SITES));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1102,8 +1102,8 @@ abstract public class GeckoApp
|
||||
public void run() {
|
||||
mAutoCompletePopup.hide();
|
||||
if (mAboutHomeContent == null && mShow) {
|
||||
mAboutHomeContent = new AboutHomeContent(GeckoApp.mAppContext, null);
|
||||
mAboutHomeContent.init(GeckoApp.mAppContext);
|
||||
mAboutHomeContent = new AboutHomeContent(GeckoApp.mAppContext);
|
||||
mAboutHomeContent.update(GeckoApp.mAppContext, AboutHomeContent.UpdateFlags.ALL);
|
||||
mAboutHomeContent.setUriLoadCallback(new AboutHomeContent.UriLoadCallback() {
|
||||
public void callback(String url) {
|
||||
mBrowserToolbar.setProgressVisibility(true);
|
||||
@ -1114,7 +1114,11 @@ abstract public class GeckoApp
|
||||
new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT,
|
||||
LayoutParams.FILL_PARENT);
|
||||
mGeckoLayout.addView(mAboutHomeContent, lp);
|
||||
} else if (mAboutHomeContent != null && mShow) {
|
||||
mAboutHomeContent.update(GeckoApp.mAppContext,
|
||||
EnumSet.of(AboutHomeContent.UpdateFlags.TOP_SITES));
|
||||
}
|
||||
|
||||
if (mAboutHomeContent != null)
|
||||
mAboutHomeContent.setVisibility(mShow ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
@ -288,12 +288,14 @@ public class Tabs implements GeckoEventListener {
|
||||
}
|
||||
|
||||
public void refreshThumbnails() {
|
||||
GeckoAppShell.getHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
Iterator<Tab> iterator = tabs.values().iterator();
|
||||
while (iterator.hasNext())
|
||||
GeckoApp.mAppContext.getAndProcessThumbnailForTab(iterator.next());
|
||||
}
|
||||
});
|
||||
Iterator<Tab> iterator = tabs.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final Tab tab = iterator.next();
|
||||
GeckoAppShell.getHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
GeckoApp.mAppContext.getAndProcessThumbnailForTab(tab);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,8 +210,52 @@ public class BrowserProvider extends ContentProvider {
|
||||
|
||||
private static final int GUID_ENCODE_FLAGS = Base64.URL_SAFE | Base64.NO_WRAP;
|
||||
|
||||
/**
|
||||
* taken from http://www.source-code.biz/base64coder/java/Base64Coder.java.txt and modified (MIT License)
|
||||
*/
|
||||
// Mapping table from 6-bit nibbles to Base64 characters.
|
||||
private static final byte[] map1 = new byte[64];
|
||||
static {
|
||||
int i=0;
|
||||
for (byte c='A'; c<='Z'; c++) map1[i++] = c;
|
||||
for (byte c='a'; c<='z'; c++) map1[i++] = c;
|
||||
for (byte c='0'; c<='9'; c++) map1[i++] = c;
|
||||
map1[i++] = '-'; map1[i++] = '_';
|
||||
}
|
||||
final static byte EQUALS_ASCII = (byte) '=';
|
||||
/**
|
||||
* Encodes a byte array into Base64 format.
|
||||
* No blanks or line breaks are inserted in the output.
|
||||
* @param in An array containing the data bytes to be encoded.
|
||||
* @return A character array containing the Base64 encoded data.
|
||||
*/
|
||||
public static byte[] encodeBase64(byte[] in) {
|
||||
if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.FROYO)
|
||||
return Base64.encode(in, GUID_ENCODE_FLAGS);
|
||||
int oDataLen = (in.length*4+2)/3; // output length without padding
|
||||
int oLen = ((in.length+2)/3)*4; // output length including padding
|
||||
byte[] out = new byte[oLen];
|
||||
int ip = 0;
|
||||
int iEnd = in.length;
|
||||
int op = 0;
|
||||
while (ip < iEnd) {
|
||||
int i0 = in[ip++] & 0xff;
|
||||
int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
|
||||
int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
|
||||
int o0 = i0 >>> 2;
|
||||
int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
|
||||
int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
|
||||
int o3 = i2 & 0x3F;
|
||||
out[op++] = map1[o0];
|
||||
out[op++] = map1[o1];
|
||||
out[op] = op < oDataLen ? map1[o2] : EQUALS_ASCII; op++;
|
||||
out[op] = op < oDataLen ? map1[o3] : EQUALS_ASCII; op++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static String generateGuid() {
|
||||
byte[] encodedBytes = Base64.encode(generateRandomBytes(9), GUID_ENCODE_FLAGS);
|
||||
byte[] encodedBytes = encodeBase64(generateRandomBytes(9));
|
||||
return new String(encodedBytes);
|
||||
}
|
||||
|
||||
|
@ -162,8 +162,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
|
||||
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
|
||||
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
|
||||
GeckoAppShell.registerGeckoEventListener("Document:Shown", this);
|
||||
GeckoAppShell.registerGeckoEventListener("Tab:Selected:Done", this);
|
||||
|
||||
sendResizeEventIfNecessary();
|
||||
}
|
||||
@ -252,7 +250,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
Point tileOrigin = new Point((origin.x / TILE_SIZE.width) * TILE_SIZE.width,
|
||||
(origin.y / TILE_SIZE.height) * TILE_SIZE.height);
|
||||
mRenderOffset.set(origin.x - tileOrigin.x, origin.y - tileOrigin.y);
|
||||
((MultiTileLayer)mTileLayer).invalidateBuffer();
|
||||
}
|
||||
|
||||
// If the window size has changed, reallocate the buffer to match.
|
||||
@ -292,9 +289,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
|
||||
LayerController controller = getLayerController();
|
||||
PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
|
||||
Point tileOrigin = PointUtils.round(displayportOrigin);
|
||||
tileOrigin.offset(-mRenderOffset.x, -mRenderOffset.y);
|
||||
mTileLayer.setOrigin(tileOrigin);
|
||||
mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
|
||||
mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
|
||||
|
||||
if (onlyUpdatePageSize) {
|
||||
@ -323,6 +318,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
Rect rect = new Rect(x, y, x + width, y + height);
|
||||
rect.offset(mRenderOffset.x, mRenderOffset.y);
|
||||
((MultiTileLayer)mTileLayer).invalidate(rect);
|
||||
((MultiTileLayer)mTileLayer).setRenderOffset(mRenderOffset);
|
||||
}
|
||||
} finally {
|
||||
endTransaction(mTileLayer);
|
||||
@ -520,16 +516,6 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
|
||||
GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect));
|
||||
} else if ("Viewport:UpdateLater".equals(event)) {
|
||||
mUpdateViewportOnEndDraw = true;
|
||||
} else if (("Document:Shown".equals(event) ||
|
||||
"Tab:Selected:Done".equals(event)) &&
|
||||
(mTileLayer instanceof MultiTileLayer)) {
|
||||
beginTransaction(mTileLayer);
|
||||
try {
|
||||
((MultiTileLayer)mTileLayer).invalidateTiles();
|
||||
((MultiTileLayer)mTileLayer).invalidateBuffer();
|
||||
} finally {
|
||||
endTransaction(mTileLayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,7 @@ public class MultiTileLayer extends Layer {
|
||||
private IntSize mBufferSize;
|
||||
private Region mDirtyRegion;
|
||||
private Region mValidRegion;
|
||||
private Point mRenderOffset;
|
||||
private final LinkedList<SubTile> mTiles;
|
||||
private final HashMap<Long, SubTile> mPositionHash;
|
||||
|
||||
@ -78,6 +79,7 @@ public class MultiTileLayer extends Layer {
|
||||
mBufferSize = new IntSize(0, 0);
|
||||
mDirtyRegion = new Region();
|
||||
mValidRegion = new Region();
|
||||
mRenderOffset = new Point();
|
||||
mTiles = new LinkedList<SubTile>();
|
||||
mPositionHash = new HashMap<Long, SubTile>();
|
||||
}
|
||||
@ -160,6 +162,12 @@ public class MultiTileLayer extends Layer {
|
||||
return new Long((((long)point.x) << 32) | point.y);
|
||||
}
|
||||
|
||||
private Point getOffsetOrigin() {
|
||||
Point origin = new Point(getOrigin());
|
||||
origin.offset(-mRenderOffset.x, -mRenderOffset.y);
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the necessary functions to update the specified properties of
|
||||
* a sub-tile.
|
||||
@ -172,7 +180,7 @@ public class MultiTileLayer extends Layer {
|
||||
// buffer. This is done as SingleTileLayer always updates the
|
||||
// entire width, regardless of the dirty-rect's width, and so
|
||||
// can override existing data.
|
||||
Point origin = getOrigin();
|
||||
Point origin = getOffsetOrigin();
|
||||
Rect validRect = tile.getValidTextureArea();
|
||||
validRect.offset(tileOrigin.x - origin.x, tileOrigin.y - origin.y);
|
||||
Region validRegion = new Region(validRect);
|
||||
@ -226,9 +234,9 @@ public class MultiTileLayer extends Layer {
|
||||
}
|
||||
|
||||
// Check that we're capable of updating from this origin.
|
||||
Point origin = getOrigin();
|
||||
Point origin = getOffsetOrigin();
|
||||
if ((origin.x % mTileSize.width) != 0 || (origin.y % mTileSize.height) != 0) {
|
||||
Log.e(LOGTAG, "MultiTileLayer doesn't support non tile-aligned origins! (" +
|
||||
Log.e(LOGTAG, "MultiTileLayer doesn't support non tile-aligned buffers! (" +
|
||||
origin.x + ", " + origin.y + ")");
|
||||
return true;
|
||||
}
|
||||
@ -325,7 +333,7 @@ public class MultiTileLayer extends Layer {
|
||||
}
|
||||
|
||||
// Dirty tile, find out if we already have this tile to reuse.
|
||||
boolean reusedTile = true;
|
||||
boolean reusedTile;
|
||||
Point tileOrigin = new Point(x, y);
|
||||
SubTile tile = mPositionHash.get(longFromPoint(tileOrigin));
|
||||
|
||||
@ -335,6 +343,10 @@ public class MultiTileLayer extends Layer {
|
||||
reusedTile = false;
|
||||
} else {
|
||||
mTiles.remove(tile);
|
||||
|
||||
// Reuse the tile (i.e. keep the texture data and metrics)
|
||||
// only if the resolution matches
|
||||
reusedTile = FloatUtils.fuzzyEquals(tile.getResolution(), getResolution());
|
||||
}
|
||||
|
||||
// Place tile at the end of the tile-list so it isn't re-used.
|
||||
@ -388,13 +400,43 @@ public class MultiTileLayer extends Layer {
|
||||
@Override
|
||||
public void draw(RenderContext context) {
|
||||
for (SubTile layer : mTiles) {
|
||||
// Skip invalid tiles
|
||||
if (layer.key == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid work, only draw tiles that intersect with the viewport
|
||||
RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
|
||||
if (RectF.intersects(layerBounds, context.viewport))
|
||||
if (RectF.intersects(layerBounds, context.viewport)) {
|
||||
layer.draw(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrigin(Point origin) {
|
||||
Point oldOrigin = getOrigin();
|
||||
|
||||
if (!origin.equals(oldOrigin)) {
|
||||
super.setOrigin(origin);
|
||||
invalidateBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResolution(float resolution) {
|
||||
float oldResolution = getResolution();
|
||||
|
||||
if (!FloatUtils.fuzzyEquals(resolution, oldResolution)) {
|
||||
super.setResolution(resolution);
|
||||
invalidateBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
public void setRenderOffset(Point offset) {
|
||||
mRenderOffset.set(offset.x, offset.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all sub-tiles. This should be called if the source backing
|
||||
* this layer has changed. This method is only valid inside a transaction.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:isScrollContainer="false"
|
||||
android:background="@drawable/abouthome_bg_repeat">
|
||||
|
||||
<LinearLayout android:layout_width="fill_parent"
|
||||
android:orientation="vertical"
|
||||
|
@ -420,7 +420,7 @@ pref("browser.ui.zoom.animationDuration", 200); // ms duration of double-tap zoo
|
||||
pref("browser.ui.zoom.reflow", false); // Change text wrapping on double-tap
|
||||
pref("browser.ui.zoom.reflow.fontSize", 720);
|
||||
|
||||
pref("font.size.inflation.minTwips", 120);
|
||||
pref("font.size.inflation.minTwips", 0);
|
||||
|
||||
// pinch gesture
|
||||
pref("browser.ui.pinch.maxGrowth", 150); // max pinch distance growth
|
||||
|
@ -722,6 +722,7 @@ nsSocketTransport::nsSocketTransport()
|
||||
, mInputClosed(true)
|
||||
, mOutputClosed(true)
|
||||
, mResolving(false)
|
||||
, mNetAddrIsSet(false)
|
||||
, mLock("nsSocketTransport.mLock")
|
||||
, mFD(nsnull)
|
||||
, mFDref(0)
|
||||
@ -858,6 +859,7 @@ nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const PRNetAddr *addr
|
||||
mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
|
||||
mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
|
||||
mState = STATE_TRANSFERRING;
|
||||
mNetAddrIsSet = true;
|
||||
|
||||
mFD = fd;
|
||||
mFDref = 1;
|
||||
@ -1399,6 +1401,10 @@ nsSocketTransport::OnSocketConnected()
|
||||
mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
|
||||
mState = STATE_TRANSFERRING;
|
||||
|
||||
// Set the mNetAddrIsSet flag only when state has reached TRANSFERRING
|
||||
// because we need to make sure its value does not change due to failover
|
||||
mNetAddrIsSet = true;
|
||||
|
||||
// assign mFD (must do this within the transport lock), but take care not
|
||||
// to trample over mFDref if mFD is already set.
|
||||
{
|
||||
@ -1915,7 +1921,11 @@ nsSocketTransport::GetPeerAddr(PRNetAddr *addr)
|
||||
// we can freely access mNetAddr from any thread without being
|
||||
// inside a critical section.
|
||||
|
||||
NS_ENSURE_TRUE(mState == STATE_TRANSFERRING, NS_ERROR_NOT_AVAILABLE);
|
||||
if (!mNetAddrIsSet) {
|
||||
SOCKET_LOG(("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
|
||||
"NOT_AVAILABLE because not yet connected.", this, mState));
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
memcpy(addr, &mNetAddr, sizeof(mNetAddr));
|
||||
return NS_OK;
|
||||
|
@ -226,7 +226,11 @@ private:
|
||||
|
||||
nsCOMPtr<nsICancelable> mDNSRequest;
|
||||
nsCOMPtr<nsIDNSRecord> mDNSRecord;
|
||||
|
||||
// mNetAddr is valid from GetPeerAddr() once we have
|
||||
// reached STATE_TRANSFERRING. It must not change after that.
|
||||
PRNetAddr mNetAddr;
|
||||
bool mNetAddrIsSet;
|
||||
|
||||
// socket methods (these can only be called on the socket thread):
|
||||
|
||||
|
@ -35,10 +35,29 @@
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
body.verbose {
|
||||
/* override setting in about.css */
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
body.non-verbose pre.tree {
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
background: #ddd;
|
||||
padding-left: .1em;
|
||||
}
|
||||
|
||||
.accuracyWarning {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.treeLine {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.mrValue {
|
||||
font-weight: bold;
|
||||
color: #400;
|
||||
@ -47,20 +66,23 @@
|
||||
.mrPerc {
|
||||
}
|
||||
|
||||
.mrName {
|
||||
color: #004;
|
||||
.mrSep {
|
||||
}
|
||||
|
||||
.hasDesc:hover {
|
||||
text-decoration: underline;
|
||||
.mrName {
|
||||
color: #004;
|
||||
}
|
||||
|
||||
.mrStar {
|
||||
color: #604;
|
||||
}
|
||||
|
||||
.treeLine {
|
||||
color: #888;
|
||||
.hasKids {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hasKids:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.option {
|
||||
@ -73,22 +95,6 @@
|
||||
-moz-user-select: none; /* no need to include this when cutting+pasting */
|
||||
}
|
||||
|
||||
body.verbose {
|
||||
/* override setting in about.css */
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
h2.tree {
|
||||
cursor: pointer;
|
||||
background: #ddd;
|
||||
padding-left: .1em;
|
||||
}
|
||||
|
||||
body.non-verbose pre.tree {
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
pre.collapsed {
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
@ -199,17 +199,6 @@ function sendHeapMinNotifications()
|
||||
sendHeapMinNotificationsInner();
|
||||
}
|
||||
|
||||
function toggleTreeVisibility(aEvent)
|
||||
{
|
||||
var headerElem = aEvent.target;
|
||||
|
||||
// Replace "header-" with "pre-" in the header element's id to get the id of
|
||||
// the corresponding pre element.
|
||||
var treeElem = $(headerElem.id.replace(/^header-/, 'pre-'));
|
||||
|
||||
treeElem.classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
function Reporter(aPath, aKind, aUnits, aAmount, aDescription)
|
||||
{
|
||||
this._path = aPath;
|
||||
@ -337,6 +326,7 @@ function update()
|
||||
}
|
||||
|
||||
// Memory-related actions.
|
||||
const UpDesc = "Re-measure.";
|
||||
const GCDesc = "Do a global garbage collection.";
|
||||
const CCDesc = "Do a cycle collection.";
|
||||
const MPDesc = "Send three \"heap-minimize\" notifications in a " +
|
||||
@ -345,7 +335,9 @@ function update()
|
||||
"process to reduce memory usage in other ways, e.g. by " +
|
||||
"flushing various caches.";
|
||||
|
||||
// The "Update" button has an id so it can be clicked in a test.
|
||||
text += "<div>" +
|
||||
"<button title='" + UpDesc + "' onclick='update()' id='updateButton'>Update</button>" +
|
||||
"<button title='" + GCDesc + "' onclick='doGlobalGC()'>GC</button>" +
|
||||
"<button title='" + CCDesc + "' onclick='doCC()'>CC</button>" +
|
||||
"<button title='" + MPDesc + "' onclick='sendHeapMinNotifications()'>" + "Minimize memory usage</button>" +
|
||||
@ -363,10 +355,12 @@ function update()
|
||||
"</div>";
|
||||
|
||||
text += "<div>" +
|
||||
"<span class='legend'>Hover the pointer over the name of a memory " +
|
||||
"reporter to see a detailed description of what it measures. Click a " +
|
||||
"heading to expand or collapse its tree.</span>" +
|
||||
"<span class='legend'>Click on a non-leaf node in a tree to expand ('++') " +
|
||||
"or collapse ('--') its children.</span>" +
|
||||
"</div>";
|
||||
text += "<div>" +
|
||||
"<span class='legend'>Hover the pointer over the name of a memory " +
|
||||
"reporter to see a description of what it measures.</span>";
|
||||
|
||||
var div = document.createElement("div");
|
||||
div.innerHTML = text;
|
||||
@ -374,7 +368,7 @@ function update()
|
||||
}
|
||||
|
||||
// There are two kinds of TreeNode.
|
||||
// - Leaf TreeNodes correspond to Reporters and have more properties.
|
||||
// - Leaf TreeNodes correspond to Reporters and have more properties.
|
||||
// - Non-leaf TreeNodes are just scaffolding nodes for the tree; their values
|
||||
// are derived from their children.
|
||||
function TreeNode(aName)
|
||||
@ -390,6 +384,9 @@ function TreeNode(aName)
|
||||
// - _kind
|
||||
// - _nMerged (if > 1)
|
||||
// - _hasProblem (only defined if true)
|
||||
//
|
||||
// Non-leaf TreeNodes have these properties added later:
|
||||
// - _hideKids (only defined if true)
|
||||
}
|
||||
|
||||
TreeNode.prototype = {
|
||||
@ -524,6 +521,25 @@ function buildTree(aReporters, aTreeName)
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore all the memory reporters that belong to a tree; this involves
|
||||
* explicitly marking them as done.
|
||||
*
|
||||
* @param aReporters
|
||||
* The table of Reporters, indexed by path.
|
||||
* @param aTreeName
|
||||
* The name of the tree being built.
|
||||
*/
|
||||
function ignoreTree(aReporters, aTreeName)
|
||||
{
|
||||
for (var path in aReporters) {
|
||||
var r = aReporters[path];
|
||||
if (r.treeNameMatches(aTreeName)) {
|
||||
var dummy = getBytes(aReporters, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some work which only makes sense for the 'explicit' tree.
|
||||
*
|
||||
@ -581,58 +597,81 @@ function fixUpExplicitTree(aT, aReporters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort all kid nodes from largest to smallest and aggregate insignificant
|
||||
* nodes.
|
||||
* Sort all kid nodes from largest to smallest, and insert aggregate nodes
|
||||
* where appropriate.
|
||||
*
|
||||
* @param aTotalBytes
|
||||
* The size of the tree's root node.
|
||||
* @param aT
|
||||
* The tree.
|
||||
*/
|
||||
function filterTree(aTotalBytes, aT)
|
||||
function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
|
||||
{
|
||||
const omitThresholdPerc = 0.5; /* percent */
|
||||
const kSignificanceThresholdPerc = 1;
|
||||
|
||||
function shouldOmit(aBytes)
|
||||
function isInsignificant(aT)
|
||||
{
|
||||
return !gVerbose &&
|
||||
aTotalBytes !== kUnknown &&
|
||||
(100 * aBytes / aTotalBytes) < omitThresholdPerc;
|
||||
(100 * aT._amount / aTotalBytes) < kSignificanceThresholdPerc;
|
||||
}
|
||||
|
||||
if (aT._kids.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
aT._kids.sort(TreeNode.compare);
|
||||
|
||||
for (var i = 0; i < aT._kids.length; i++) {
|
||||
if (shouldOmit(aT._kids[i]._amount)) {
|
||||
// This sub-tree is below the significance threshold
|
||||
// Remove it and all remaining (smaller) sub-trees, and
|
||||
// replace them with a single aggregate node.
|
||||
// If the first child is insignificant, they all are, and there's no point
|
||||
// creating an aggregate node that lacks siblings. Just set the parent's
|
||||
// _hideKids property and process all children.
|
||||
if (isInsignificant(aT._kids[0])) {
|
||||
aT._hideKids = true;
|
||||
for (var i = 0; i < aT._kids.length; i++) {
|
||||
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Look at all children except the last one.
|
||||
for (var i = 0; i < aT._kids.length - 1; i++) {
|
||||
if (isInsignificant(aT._kids[i])) {
|
||||
// This child is below the significance threshold. If there are other
|
||||
// (smaller) children remaining, move them under an aggregate node.
|
||||
var i0 = i;
|
||||
var nAgg = aT._kids.length - i0;
|
||||
// Create an aggregate node.
|
||||
var aggT = new TreeNode("(" + nAgg + " tiny)");
|
||||
var aggBytes = 0;
|
||||
for ( ; i < aT._kids.length; i++) {
|
||||
aggBytes += aT._kids[i]._amount;
|
||||
aggT._kids.push(aT._kids[i]);
|
||||
}
|
||||
aT._kids.splice(i0, aT._kids.length);
|
||||
var n = i - i0;
|
||||
var rSub = new TreeNode("(" + n + " omitted)");
|
||||
rSub._amount = aggBytes;
|
||||
rSub._description =
|
||||
n + " sub-trees that were below the " + omitThresholdPerc +
|
||||
"% significance threshold. Click 'More verbose' at the bottom of " +
|
||||
"this page to see them.";
|
||||
|
||||
// Add the "omitted" sub-tree at the end and then re-sort, because the
|
||||
// sum of the omitted sub-trees may be larger than some of the shown
|
||||
// sub-trees.
|
||||
aT._kids[i0] = rSub;
|
||||
aggT._hideKids = true;
|
||||
aggT._amount = aggBytes;
|
||||
aggT._description =
|
||||
nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
|
||||
"% significance threshold.";
|
||||
aT._kids.splice(i0, nAgg, aggT);
|
||||
aT._kids.sort(TreeNode.compare);
|
||||
break;
|
||||
|
||||
// Process the moved children.
|
||||
for (i = 0; i < aggT._kids.length; i++) {
|
||||
sortTreeAndInsertAggregateNodes(aTotalBytes, aggT._kids[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
filterTree(aTotalBytes, aT._kids[i]);
|
||||
|
||||
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
|
||||
}
|
||||
|
||||
// The first n-1 children were significant. Don't consider if the last child
|
||||
// is significant; there's no point creating an aggregate node that only has
|
||||
// one child. Just process it.
|
||||
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
|
||||
}
|
||||
|
||||
function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
|
||||
function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
|
||||
{
|
||||
var warningText = "";
|
||||
|
||||
@ -677,7 +716,7 @@ function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
|
||||
{
|
||||
var explicitTree = buildTree(aReporters, 'explicit');
|
||||
var hasKnownHeapAllocated = fixUpExplicitTree(explicitTree, aReporters);
|
||||
filterTree(explicitTree._amount, explicitTree);
|
||||
sortTreeAndInsertAggregateNodes(explicitTree._amount, explicitTree);
|
||||
var explicitText = genTreeText(explicitTree, aProcess);
|
||||
|
||||
// Generate any warnings about inaccuracies due to platform limitations.
|
||||
@ -687,14 +726,20 @@ function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
|
||||
var warningText =
|
||||
genWarningText(hasKnownHeapAllocated, aHasMozMallocUsableSize);
|
||||
|
||||
var mapTreeText = '';
|
||||
// We only show these breakdown trees in verbose mode.
|
||||
var mapTreeText = "";
|
||||
kMapTreePaths.forEach(function(t) {
|
||||
var tree = buildTree(aReporters, t);
|
||||
if (gVerbose) {
|
||||
var tree = buildTree(aReporters, t);
|
||||
|
||||
// |tree| will be null if we don't have any reporters for the given path.
|
||||
if (tree) {
|
||||
filterTree(tree._amount, tree);
|
||||
mapTreeText += genTreeText(tree, aProcess);
|
||||
// |tree| will be null if we don't have any reporters for the given path.
|
||||
if (tree) {
|
||||
sortTreeAndInsertAggregateNodes(tree._amount, tree);
|
||||
tree._hideKids = true; // map trees are always initially collapsed
|
||||
mapTreeText += genTreeText(tree, aProcess);
|
||||
}
|
||||
} else {
|
||||
ignoreTree(aReporters, t);
|
||||
}
|
||||
});
|
||||
|
||||
@ -835,9 +880,18 @@ function getDescription(aReporters, aPath)
|
||||
return r._description;
|
||||
}
|
||||
|
||||
// There's a subset of the Unicode "light" box-drawing chars that are widely
|
||||
// implemented in terminals, and this code sticks to that subset to maximize
|
||||
// the chance that cutting and pasting about:memory output to a terminal will
|
||||
// work correctly:
|
||||
const kHorizontal = "\u2500",
|
||||
kVertical = "\u2502",
|
||||
kUpAndRight = "\u2514",
|
||||
kVerticalAndRight = "\u251c";
|
||||
|
||||
function genMrValueText(aValue)
|
||||
{
|
||||
return "<span class='mrValue'>" + aValue + "</span>";
|
||||
return "<span class='mrValue'>" + aValue + " </span>";
|
||||
}
|
||||
|
||||
function kindToString(aKind)
|
||||
@ -861,7 +915,7 @@ function escapeAll(aStr)
|
||||
|
||||
// Compartment reporter names are URLs and so can include forward slashes. But
|
||||
// forward slash is the memory reporter path separator. So the memory
|
||||
// reporters change them to backslashes. Undo that here.
|
||||
// reporters change them to backslashes. Undo that here.
|
||||
function flipBackslashes(aStr)
|
||||
{
|
||||
return aStr.replace(/\\/g, '/');
|
||||
@ -877,25 +931,86 @@ function prepDesc(aStr)
|
||||
return escapeAll(flipBackslashes(aStr));
|
||||
}
|
||||
|
||||
function genMrNameText(aKind, aDesc, aName, aHasProblem, aNMerged)
|
||||
function genMrNameText(aKind, aShowSubtrees, aHasKids, aDesc, aName,
|
||||
aHasProblem, aNMerged)
|
||||
{
|
||||
var text = "-- <span class='mrName hasDesc' title='" +
|
||||
kindToString(aKind) + prepDesc(aDesc) +
|
||||
"'>" + prepName(aName) + "</span>";
|
||||
var text = "";
|
||||
if (aHasKids) {
|
||||
if (aShowSubtrees) {
|
||||
text += "<span class='mrSep hidden'>++ </span>";
|
||||
text += "<span class='mrSep'>-- </span>";
|
||||
} else {
|
||||
text += "<span class='mrSep'>++ </span>";
|
||||
text += "<span class='mrSep hidden'>-- </span>";
|
||||
}
|
||||
} else {
|
||||
text += "<span class='mrSep'>" + kHorizontal + kHorizontal + " </span>";
|
||||
}
|
||||
text += "<span class='mrName' title='" +
|
||||
kindToString(aKind) + prepDesc(aDesc) + "'>" +
|
||||
prepName(aName) + "</span>";
|
||||
if (aHasProblem) {
|
||||
const problemDesc =
|
||||
"Warning: this memory reporter was unable to compute a useful value. ";
|
||||
text += " <span class='mrStar' title=\"" + problemDesc + "\">[*]</span>";
|
||||
text += "<span class='mrStar' title=\"" + problemDesc + "\"> [*]</span>";
|
||||
}
|
||||
if (aNMerged) {
|
||||
const dupDesc = "This value is the sum of " + aNMerged +
|
||||
" memory reporters that all have the same path.";
|
||||
text += " <span class='mrStar' title=\"" + dupDesc + "\">[" +
|
||||
text += "<span class='mrStar' title=\"" + dupDesc + "\"> [" +
|
||||
aNMerged + "]</span>";
|
||||
}
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
// This is used to record which sub-trees have been toggled, so the
|
||||
// collapsed/expanded state can be replicated when the page is regenerated.
|
||||
// It can end up holding IDs of nodes that no longer exist, e.g. for
|
||||
// compartments that have been closed. This doesn't seem like a big deal,
|
||||
// because the number is limited by the number of entries the user has changed
|
||||
// from their original state.
|
||||
var gToggles = {};
|
||||
|
||||
function toggle(aEvent)
|
||||
{
|
||||
// This relies on each line being a span that contains at least five spans:
|
||||
// mrValue, mrPerc, mrSep ('++'), mrSep ('--'), mrName, and then zero or more
|
||||
// mrStars. All whitespace must be within one of these spans for this
|
||||
// function to find the right nodes. And the span containing the children of
|
||||
// this line must immediately follow. Assertions check this.
|
||||
|
||||
function assertClassName(span, className) {
|
||||
assert(span, "undefined " + className);
|
||||
assert(span.nodeName === "span", "non-span " + className);
|
||||
assert(span.classList.contains(className), "bad " + className);
|
||||
}
|
||||
|
||||
// |aEvent.target| will be one of the five spans. Get the outer span.
|
||||
var outerSpan = aEvent.target.parentNode;
|
||||
assertClassName(outerSpan, "hasKids");
|
||||
|
||||
// Toggle visibility of the '++' and '--' separators.
|
||||
var plusSpan = outerSpan.childNodes[2];
|
||||
var minusSpan = outerSpan.childNodes[3];
|
||||
assertClassName(plusSpan, "mrSep");
|
||||
assertClassName(minusSpan, "mrSep");
|
||||
plusSpan .classList.toggle("hidden");
|
||||
minusSpan.classList.toggle("hidden");
|
||||
|
||||
// Toggle visibility of the span containing this node's children.
|
||||
var subTreeSpan = outerSpan.nextSibling;
|
||||
assertClassName(subTreeSpan, "kids");
|
||||
subTreeSpan.classList.toggle("hidden");
|
||||
|
||||
// Record/unrecord that this sub-tree was toggled.
|
||||
var treeId = outerSpan.id;
|
||||
if (gToggles[treeId]) {
|
||||
delete gToggles[treeId];
|
||||
} else {
|
||||
gToggles[treeId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the text for the tree, including its heading.
|
||||
*
|
||||
@ -914,6 +1029,8 @@ function genTreeText(aT, aProcess)
|
||||
/**
|
||||
* Generates the text for a particular tree, without a heading.
|
||||
*
|
||||
* @param aPrePath
|
||||
* The partial path leading up to this node.
|
||||
* @param aT
|
||||
* The tree.
|
||||
* @param aIndentGuide
|
||||
@ -925,7 +1042,7 @@ function genTreeText(aT, aProcess)
|
||||
* The length of the formatted byte count of the top node in the tree.
|
||||
* @return The generated text.
|
||||
*/
|
||||
function genTreeText2(aT, aIndentGuide, aParentStringLength)
|
||||
function genTreeText2(aPrePath, aT, aIndentGuide, aParentStringLength)
|
||||
{
|
||||
function repeatStr(aC, aN)
|
||||
{
|
||||
@ -936,15 +1053,7 @@ function genTreeText(aT, aProcess)
|
||||
return s;
|
||||
}
|
||||
|
||||
// Generate the indent. There's a subset of the Unicode "light"
|
||||
// box-drawing chars that are widely implemented in terminals, and
|
||||
// this code sticks to that subset to maximize the chance that
|
||||
// cutting and pasting about:memory output to a terminal will work
|
||||
// correctly:
|
||||
const kHorizontal = "\u2500",
|
||||
kVertical = "\u2502",
|
||||
kUpAndRight = "\u2514",
|
||||
kVerticalAndRight = "\u251c";
|
||||
// Generate the indent.
|
||||
var indent = "<span class='treeLine'>";
|
||||
if (aIndentGuide.length > 0) {
|
||||
for (var i = 0; i < aIndentGuide.length - 1; i++) {
|
||||
@ -954,7 +1063,6 @@ function genTreeText(aT, aProcess)
|
||||
indent += aIndentGuide[i]._isLastKid ? kUpAndRight : kVerticalAndRight;
|
||||
indent += repeatStr(kHorizontal, aIndentGuide[i]._depth - 1);
|
||||
}
|
||||
|
||||
// Indent more if this entry is narrower than its parent, and update
|
||||
// aIndentGuide accordingly.
|
||||
var tString = aT.toString();
|
||||
@ -967,40 +1075,66 @@ function genTreeText(aT, aProcess)
|
||||
}
|
||||
indent += "</span>";
|
||||
|
||||
// Generate the percentage.
|
||||
var perc = "";
|
||||
// Generate the percentage, and determine if we should show subtrees.
|
||||
var percText = "";
|
||||
var showSubtrees = !aT._hideKids;
|
||||
if (aT._amount === treeBytes) {
|
||||
perc = "100.0";
|
||||
percText = "100.0";
|
||||
} else {
|
||||
perc = (100 * aT._amount / treeBytes).toFixed(2);
|
||||
perc = pad(perc, 5, '0');
|
||||
var perc = (100 * aT._amount / treeBytes);
|
||||
percText = (100 * aT._amount / treeBytes).toFixed(2);
|
||||
percText = pad(percText, 5, '0');
|
||||
}
|
||||
percText = "<span class='mrPerc'>(" + percText + "%) </span>";
|
||||
|
||||
// Reinstate any previous toggling of this sub-tree.
|
||||
var path = aPrePath + aT._name;
|
||||
var treeId = escapeAll(aProcess + ":" + path);
|
||||
if (gToggles[treeId]) {
|
||||
showSubtrees = !showSubtrees;
|
||||
}
|
||||
perc = "<span class='mrPerc'>(" + perc + "%)</span> ";
|
||||
|
||||
// We don't want to show '(nonheap)' on a tree like 'map/vsize', since the
|
||||
// whole tree is non-heap.
|
||||
var kind = isExplicitTree ? aT._kind : undefined;
|
||||
var text = indent + genMrValueText(tString) + " " + perc +
|
||||
genMrNameText(kind, aT._description, aT._name,
|
||||
aT._hasProblem, aT._nMerged);
|
||||
|
||||
// For non-leaf nodes, the entire sub-tree is put within a span so it can
|
||||
// be collapsed if the node is clicked on.
|
||||
var hasKids = aT._kids.length > 0;
|
||||
if (!hasKids) {
|
||||
assert(!aT._hideKids, "leaf node with _hideKids set")
|
||||
}
|
||||
var text = indent;
|
||||
if (hasKids) {
|
||||
text +=
|
||||
"<span onclick='toggle(event)' class='hasKids' id='" + treeId + "'>";
|
||||
}
|
||||
text += genMrValueText(tString) + percText;
|
||||
text += genMrNameText(kind, showSubtrees, hasKids, aT._description,
|
||||
aT._name, aT._hasProblem, aT._nMerged);
|
||||
if (hasKids) {
|
||||
var hiddenText = showSubtrees ? "" : " hidden";
|
||||
// The 'kids' class is just used for sanity checking in toggle().
|
||||
text += "</span><span class='kids" + hiddenText + "'>";
|
||||
}
|
||||
|
||||
for (var i = 0; i < aT._kids.length; i++) {
|
||||
// 3 is the standard depth, the callee adjusts it if necessary.
|
||||
aIndentGuide.push({ _isLastKid: (i === aT._kids.length - 1), _depth: 3 });
|
||||
text += genTreeText2(aT._kids[i], aIndentGuide, tString.length);
|
||||
text += genTreeText2(path + "/", aT._kids[i], aIndentGuide,
|
||||
tString.length);
|
||||
aIndentGuide.pop();
|
||||
}
|
||||
text += hasKids ? "</span>" : "";
|
||||
return text;
|
||||
}
|
||||
|
||||
var text = genTreeText2(aT, [], rootStringLength);
|
||||
var text = genTreeText2(/* prePath = */"", aT, [], rootStringLength);
|
||||
|
||||
// The explicit tree is not collapsed, but all other trees are, so pass
|
||||
// !isExplicitTree for genSectionMarkup's aCollapsed parameter.
|
||||
return genSectionMarkup(aProcess, aT._name, text, !isExplicitTree);
|
||||
return genSectionMarkup(aT._name, text);
|
||||
}
|
||||
|
||||
function OtherReporter(aPath, aUnits, aAmount, aDescription,
|
||||
function OtherReporter(aPath, aUnits, aAmount, aDescription,
|
||||
aNMerged)
|
||||
{
|
||||
// Nb: _kind is not needed, it's always KIND_OTHER.
|
||||
@ -1055,7 +1189,7 @@ function genOtherText(aReportersByProcess, aProcess)
|
||||
var r = aReportersByProcess[path];
|
||||
if (!r._done) {
|
||||
assert(r._kind === KIND_OTHER, "_kind !== KIND_OTHER for " + r._path);
|
||||
assert(r.nMerged === undefined); // we don't allow dup'd OTHER reporters
|
||||
assert(r.nMerged === undefined); // we don't allow dup'd OTHER reporters
|
||||
var hasProblem = false;
|
||||
if (r._amount === kUnknown) {
|
||||
hasProblem = true;
|
||||
@ -1073,29 +1207,19 @@ function genOtherText(aReportersByProcess, aProcess)
|
||||
var text = "";
|
||||
for (var i = 0; i < otherReporters.length; i++) {
|
||||
var o = otherReporters[i];
|
||||
text += genMrValueText(pad(o.asString, maxStringLength, ' ')) + " ";
|
||||
text += genMrNameText(KIND_OTHER, o._description, o._path, o._hasProblem);
|
||||
text += genMrValueText(pad(o.asString, maxStringLength, ' '));
|
||||
text += genMrNameText(KIND_OTHER, /* showSubtrees = */true,
|
||||
/* hasKids = */false, o._description, o._path,
|
||||
o._hasProblem);
|
||||
}
|
||||
|
||||
// Nb: the newlines give nice spacing if we cut+paste into a text buffer.
|
||||
const desc = "This list contains other memory measurements that cross-cut " +
|
||||
"the requested memory measurements above."
|
||||
|
||||
return genSectionMarkup(aProcess, 'other', text, false);
|
||||
return genSectionMarkup('other', text);
|
||||
}
|
||||
|
||||
function genSectionMarkup(aProcess, aName, aText, aCollapsed)
|
||||
function genSectionMarkup(aName, aText)
|
||||
{
|
||||
var headerId = 'header-' + aProcess + '-' + aName;
|
||||
var preId = 'pre-' + aProcess + '-' + aName;
|
||||
var elemClass = (aCollapsed ? 'collapsed' : '') + ' tree';
|
||||
|
||||
// Ugh.
|
||||
return '<h2 id="' + headerId + '" class="' + elemClass + '" ' +
|
||||
'onclick="toggleTreeVisibility(event)">' +
|
||||
kTreeNames[aName] +
|
||||
'</h2>\n' +
|
||||
'<pre id="' + preId + '" class="' + elemClass + '">' + aText + '</pre>\n';
|
||||
return "<h2 class='sectionHeader'>" + kTreeNames[aName] + "</h2>\n" +
|
||||
"<pre class='tree'>" + aText + "</pre>\n";
|
||||
}
|
||||
|
||||
function assert(aCond, aMsg)
|
||||
|
@ -46,6 +46,7 @@ include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_CHROME_FILES = \
|
||||
test_aboutmemory.xul \
|
||||
test_aboutmemory2.xul \
|
||||
test_sqliteMultiReporter.xul \
|
||||
$(NULL)
|
||||
|
||||
|
@ -123,6 +123,7 @@
|
||||
f("map/vsize/a", 19);
|
||||
f("map/swap/b/c", 10);
|
||||
f("map/resident/a", 42);
|
||||
f("map/pss/a", 43);
|
||||
},
|
||||
explicitNonHeap: 0
|
||||
}
|
||||
@ -186,50 +187,33 @@ Main Process\n\
|
||||
Explicit Allocations\n\
|
||||
623.58 MB (100.0%) -- explicit\n\
|
||||
├──232.00 MB (37.20%) -- b\n\
|
||||
│ ├───85.00 MB (13.63%) -- a\n\
|
||||
│ ├───75.00 MB (12.03%) -- b\n\
|
||||
│ ├───85.00 MB (13.63%) ── a\n\
|
||||
│ ├───75.00 MB (12.03%) ── b\n\
|
||||
│ └───72.00 MB (11.55%) -- c\n\
|
||||
│ ├──70.00 MB (11.23%) -- a\n\
|
||||
│ └───2.00 MB (00.32%) -- (1 omitted)\n\
|
||||
├──222.00 MB (35.60%) -- a\n\
|
||||
│ ├──70.00 MB (11.23%) ── a\n\
|
||||
│ └───2.00 MB (00.32%) ── b\n\
|
||||
├──222.00 MB (35.60%) ── a\n\
|
||||
├──100.00 MB (16.04%) -- c\n\
|
||||
│ ├───77.00 MB (12.35%) -- other\n\
|
||||
│ └───23.00 MB (03.69%) -- d [2]\n\
|
||||
├───23.00 MB (03.69%) -- cc [2]\n\
|
||||
│ ├───77.00 MB (12.35%) ── other\n\
|
||||
│ └───23.00 MB (03.69%) ── d [2]\n\
|
||||
├───23.00 MB (03.69%) ── cc [2]\n\
|
||||
├───20.00 MB (03.21%) -- f\n\
|
||||
│ └──20.00 MB (03.21%) -- g\n\
|
||||
│ └──20.00 MB (03.21%) -- h\n\
|
||||
│ └──20.00 MB (03.21%) -- i\n\
|
||||
├───15.00 MB (02.41%) -- g\n\
|
||||
│ ├───6.00 MB (00.96%) -- a\n\
|
||||
│ ├───5.00 MB (00.80%) -- b\n\
|
||||
│ └───4.00 MB (00.64%) -- other\n\
|
||||
├───11.00 MB (01.76%) -- heap-unclassified\n\
|
||||
└────0.58 MB (00.09%) -- (2 omitted)\n\
|
||||
\n\
|
||||
Resident Set Size (RSS) Breakdown\n\
|
||||
0.16 MB (100.0%) -- resident\n\
|
||||
└──0.16 MB (100.0%) -- a\n\
|
||||
\n\
|
||||
Virtual Size Breakdown\n\
|
||||
0.17 MB (100.0%) -- vsize\n\
|
||||
└──0.17 MB (100.0%) -- a [2]\n\
|
||||
\n\
|
||||
Swap Usage Breakdown\n\
|
||||
0.05 MB (100.0%) -- swap\n\
|
||||
├──0.04 MB (76.92%) -- b\n\
|
||||
│ └──0.04 MB (76.92%) -- c\n\
|
||||
└──0.01 MB (23.08%) -- a [2]\n\
|
||||
│ └──20.00 MB (03.21%) ── i\n\
|
||||
├───15.00 MB (02.41%) ++ g\n\
|
||||
├───11.00 MB (01.76%) ── heap-unclassified\n\
|
||||
└────0.58 MB (00.09%) ++ (2 tiny)\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
500.00 MB -- heap-allocated\n\
|
||||
100.00 MB -- heap-unallocated\n\
|
||||
111.00 MB -- other1\n\
|
||||
222.00 MB -- other2\n\
|
||||
777 -- other3\n\
|
||||
888 -- other4\n\
|
||||
45.67% -- perc1\n\
|
||||
100.00% -- perc2\n\
|
||||
500.00 MB ── heap-allocated\n\
|
||||
100.00 MB ── heap-unallocated\n\
|
||||
111.00 MB ── other1\n\
|
||||
222.00 MB ── other2\n\
|
||||
777 ── other3\n\
|
||||
888 ── other4\n\
|
||||
45.67% ── perc1\n\
|
||||
100.00% ── perc2\n\
|
||||
\n\
|
||||
2nd Process\n\
|
||||
\n\
|
||||
@ -237,16 +221,16 @@ Explicit Allocations\n\
|
||||
1,000.00 MB (100.0%) -- explicit\n\
|
||||
├────499.00 MB (49.90%) -- a\n\
|
||||
│ └──499.00 MB (49.90%) -- b\n\
|
||||
│ └──499.00 MB (49.90%) -- c [3]\n\
|
||||
├────200.00 MB (20.00%) -- flip/the/backslashes\n\
|
||||
├────200.00 MB (20.00%) -- compartment(compartment-url)\n\
|
||||
└────101.00 MB (10.10%) -- heap-unclassified\n\
|
||||
│ └──499.00 MB (49.90%) ── c [3]\n\
|
||||
├────200.00 MB (20.00%) ── flip/the/backslashes\n\
|
||||
├────200.00 MB (20.00%) ── compartment(compartment-url)\n\
|
||||
└────101.00 MB (10.10%) ── heap-unclassified\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
666.00 MB -- danger<script>window.alert(1)</script>\n\
|
||||
1,000.00 MB -- heap-allocated\n\
|
||||
100.00 MB -- heap-unallocated\n\
|
||||
111.00 MB -- other1\n\
|
||||
666.00 MB ── danger<script>window.alert(1)</script>\n\
|
||||
1,000.00 MB ── heap-allocated\n\
|
||||
100.00 MB ── heap-unallocated\n\
|
||||
111.00 MB ── other1\n\
|
||||
\n\
|
||||
3rd Process\n\
|
||||
\n\
|
||||
@ -255,14 +239,14 @@ WARNING: the 'heap-allocated' memory reporter does not work for this platform an
|
||||
Explicit Allocations\n\
|
||||
777.00 MB (100.0%) -- explicit\n\
|
||||
├──777.00 MB (100.0%) -- a\n\
|
||||
│ ├──444.00 MB (57.14%) -- c [2]\n\
|
||||
│ ├──333.00 MB (42.86%) -- b\n\
|
||||
│ └────0.00 MB (00.00%) -- (1 omitted)\n\
|
||||
└────0.00 MB (00.00%) -- (2 omitted)\n\
|
||||
│ ├──444.00 MB (57.14%) ── c [2]\n\
|
||||
│ ├──333.00 MB (42.86%) ── b\n\
|
||||
│ └────0.00 MB (00.00%) ── d [*] [2]\n\
|
||||
└────0.00 MB (00.00%) ++ (2 tiny)\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
0.00 MB -- heap-allocated [*]\n\
|
||||
0.00 MB -- other1 [*]\n\
|
||||
0.00 MB ── heap-allocated [*]\n\
|
||||
0.00 MB ── other1 [*]\n\
|
||||
\n\
|
||||
";
|
||||
|
||||
@ -273,51 +257,49 @@ Main Process\n\
|
||||
Explicit Allocations\n\
|
||||
653,876,224 B (100.0%) -- explicit\n\
|
||||
├──243,269,632 B (37.20%) -- b\n\
|
||||
│ ├───89,128,960 B (13.63%) -- a\n\
|
||||
│ ├───78,643,200 B (12.03%) -- b\n\
|
||||
│ ├───89,128,960 B (13.63%) ── a\n\
|
||||
│ ├───78,643,200 B (12.03%) ── b\n\
|
||||
│ └───75,497,472 B (11.55%) -- c\n\
|
||||
│ ├──73,400,320 B (11.23%) -- a\n\
|
||||
│ └───2,097,152 B (00.32%) -- b\n\
|
||||
├──232,783,872 B (35.60%) -- a\n\
|
||||
│ ├──73,400,320 B (11.23%) ── a\n\
|
||||
│ └───2,097,152 B (00.32%) ── b\n\
|
||||
├──232,783,872 B (35.60%) ── a\n\
|
||||
├──104,857,600 B (16.04%) -- c\n\
|
||||
│ ├───80,740,352 B (12.35%) -- other\n\
|
||||
│ └───24,117,248 B (03.69%) -- d [2]\n\
|
||||
├───24,117,248 B (03.69%) -- cc [2]\n\
|
||||
│ ├───80,740,352 B (12.35%) ── other\n\
|
||||
│ └───24,117,248 B (03.69%) ── d [2]\n\
|
||||
├───24,117,248 B (03.69%) ── cc [2]\n\
|
||||
├───20,971,520 B (03.21%) -- f\n\
|
||||
│ └──20,971,520 B (03.21%) -- g\n\
|
||||
│ └──20,971,520 B (03.21%) -- h\n\
|
||||
│ └──20,971,520 B (03.21%) -- i\n\
|
||||
│ └──20,971,520 B (03.21%) ── i\n\
|
||||
├───15,728,640 B (02.41%) -- g\n\
|
||||
│ ├───6,291,456 B (00.96%) -- a\n\
|
||||
│ ├───5,242,880 B (00.80%) -- b\n\
|
||||
│ └───4,194,304 B (00.64%) -- other\n\
|
||||
├───11,534,336 B (01.76%) -- heap-unclassified\n\
|
||||
├──────510,976 B (00.08%) -- d\n\
|
||||
└──────102,400 B (00.02%) -- e\n\
|
||||
│ ├───6,291,456 B (00.96%) ── a\n\
|
||||
│ ├───5,242,880 B (00.80%) ── b\n\
|
||||
│ └───4,194,304 B (00.64%) ── other\n\
|
||||
├───11,534,336 B (01.76%) ── heap-unclassified\n\
|
||||
├──────510,976 B (00.08%) ── d\n\
|
||||
└──────102,400 B (00.02%) ── e\n\
|
||||
\n\
|
||||
Resident Set Size (RSS) Breakdown\n\
|
||||
172,032 B (100.0%) -- resident\n\
|
||||
└──172,032 B (100.0%) -- a\n\
|
||||
172,032 B (100.0%) ++ resident\n\
|
||||
\n\
|
||||
Proportional Set Size (PSS) Breakdown\n\
|
||||
176,128 B (100.0%) ++ pss\n\
|
||||
\n\
|
||||
Virtual Size Breakdown\n\
|
||||
176,128 B (100.0%) -- vsize\n\
|
||||
└──176,128 B (100.0%) -- a [2]\n\
|
||||
176,128 B (100.0%) ++ vsize\n\
|
||||
\n\
|
||||
Swap Usage Breakdown\n\
|
||||
53,248 B (100.0%) -- swap\n\
|
||||
├──40,960 B (76.92%) -- b\n\
|
||||
│ └──40,960 B (76.92%) -- c\n\
|
||||
└──12,288 B (23.08%) -- a [2]\n\
|
||||
53,248 B (100.0%) ++ swap\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
524,288,000 B -- heap-allocated\n\
|
||||
104,857,600 B -- heap-unallocated\n\
|
||||
116,391,936 B -- other1\n\
|
||||
232,783,872 B -- other2\n\
|
||||
777 -- other3\n\
|
||||
888 -- other4\n\
|
||||
45.67% -- perc1\n\
|
||||
100.00% -- perc2\n\
|
||||
524,288,000 B ── heap-allocated\n\
|
||||
104,857,600 B ── heap-unallocated\n\
|
||||
116,391,936 B ── other1\n\
|
||||
232,783,872 B ── other2\n\
|
||||
777 ── other3\n\
|
||||
888 ── other4\n\
|
||||
45.67% ── perc1\n\
|
||||
100.00% ── perc2\n\
|
||||
\n\
|
||||
2nd Process\n\
|
||||
\n\
|
||||
@ -325,16 +307,16 @@ Explicit Allocations\n\
|
||||
1,048,576,000 B (100.0%) -- explicit\n\
|
||||
├────523,239,424 B (49.90%) -- a\n\
|
||||
│ └──523,239,424 B (49.90%) -- b\n\
|
||||
│ └──523,239,424 B (49.90%) -- c [3]\n\
|
||||
├────209,715,200 B (20.00%) -- flip/the/backslashes\n\
|
||||
├────209,715,200 B (20.00%) -- compartment(compartment-url)\n\
|
||||
└────105,906,176 B (10.10%) -- heap-unclassified\n\
|
||||
│ └──523,239,424 B (49.90%) ── c [3]\n\
|
||||
├────209,715,200 B (20.00%) ── flip/the/backslashes\n\
|
||||
├────209,715,200 B (20.00%) ── compartment(compartment-url)\n\
|
||||
└────105,906,176 B (10.10%) ── heap-unclassified\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
698,351,616 B -- danger<script>window.alert(1)</script>\n\
|
||||
1,048,576,000 B -- heap-allocated\n\
|
||||
104,857,600 B -- heap-unallocated\n\
|
||||
116,391,936 B -- other1\n\
|
||||
698,351,616 B ── danger<script>window.alert(1)</script>\n\
|
||||
1,048,576,000 B ── heap-allocated\n\
|
||||
104,857,600 B ── heap-unallocated\n\
|
||||
116,391,936 B ── other1\n\
|
||||
\n\
|
||||
3rd Process\n\
|
||||
\n\
|
||||
@ -343,15 +325,15 @@ WARNING: the 'heap-allocated' memory reporter does not work for this platform an
|
||||
Explicit Allocations\n\
|
||||
814,743,552 B (100.0%) -- explicit\n\
|
||||
├──814,743,552 B (100.0%) -- a\n\
|
||||
│ ├──465,567,744 B (57.14%) -- c [2]\n\
|
||||
│ ├──349,175,808 B (42.86%) -- b\n\
|
||||
│ └────────────0 B (00.00%) -- d [*] [2]\n\
|
||||
├────────────0 B (00.00%) -- b [*]\n\
|
||||
└────────────0 B (00.00%) -- heap-unclassified [*]\n\
|
||||
│ ├──465,567,744 B (57.14%) ── c [2]\n\
|
||||
│ ├──349,175,808 B (42.86%) ── b\n\
|
||||
│ └────────────0 B (00.00%) ── d [*] [2]\n\
|
||||
├────────────0 B (00.00%) ── b [*]\n\
|
||||
└────────────0 B (00.00%) ── heap-unclassified [*]\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
0 B -- heap-allocated [*]\n\
|
||||
0 B -- other1 [*]\n\
|
||||
0 B ── heap-allocated [*]\n\
|
||||
0 B ── other1 [*]\n\
|
||||
\n\
|
||||
"
|
||||
|
||||
@ -375,13 +357,18 @@ Other Measurements\n\
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
var gHaveDumped = false;
|
||||
|
||||
function checkClipboard(actual, expected) {
|
||||
if (actual != expected) {
|
||||
dump("*******ACTUAL*******\n");
|
||||
dump(actual);
|
||||
dump("******EXPECTED******\n");
|
||||
dump(expected);
|
||||
dump("********************\n");
|
||||
if (!gHaveDumped) {
|
||||
dump("******EXPECTED******\n");
|
||||
dump(expected);
|
||||
dump("*******ACTUAL*******\n");
|
||||
dump(actual);
|
||||
dump("********************\n");
|
||||
gHaveDumped = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -391,15 +378,8 @@ Other Measurements\n\
|
||||
// expect. This tests the output in general and also that the cutting and
|
||||
// pasting works as expected.
|
||||
function test(aFrame, aExpectedText, aNext) {
|
||||
// Click all h2.collapsed elements so they expand.
|
||||
var win = document.querySelector("#" + aFrame).contentWindow;
|
||||
var nodes = win.document.querySelectorAll("pre.collapsed");
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodes[i].classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
SimpleTest.executeSoon(function() {
|
||||
document.querySelector("#" + aFrame).focus();
|
||||
document.getElementById(aFrame).focus();
|
||||
SimpleTest.waitForClipboard(
|
||||
function(actual) { return checkClipboard(actual, aExpectedText) },
|
||||
function() {
|
||||
|
221
toolkit/components/aboutmemory/tests/test_aboutmemory2.xul
Normal file
221
toolkit/components/aboutmemory/tests/test_aboutmemory2.xul
Normal file
@ -0,0 +1,221 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
|
||||
<window title="about:memory"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
|
||||
<!-- This file tests the collapsing and expanding of sub-trees in
|
||||
about:memory. -->
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml"></body>
|
||||
|
||||
<!-- test code goes here -->
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
|
||||
getService(Ci.nsIMemoryReporterManager);
|
||||
|
||||
// Remove all the real reporters and multi-reporters; save them to
|
||||
// restore at the end.
|
||||
var e = mgr.enumerateReporters();
|
||||
var realReporters = [];
|
||||
while (e.hasMoreElements()) {
|
||||
var r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
|
||||
mgr.unregisterReporter(r);
|
||||
realReporters.push(r);
|
||||
}
|
||||
e = mgr.enumerateMultiReporters();
|
||||
var realMultiReporters = [];
|
||||
while (e.hasMoreElements()) {
|
||||
var r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
|
||||
mgr.unregisterMultiReporter(r);
|
||||
realMultiReporters.push(r);
|
||||
}
|
||||
|
||||
// Setup various fake-but-deterministic reporters.
|
||||
const KB = 1024;
|
||||
const MB = KB * KB;
|
||||
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
|
||||
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
|
||||
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
|
||||
|
||||
function f(aPath, aKind, aAmount) {
|
||||
return {
|
||||
process: "",
|
||||
path: aPath,
|
||||
kind: aKind,
|
||||
units: BYTES,
|
||||
description: "(description)",
|
||||
amount: aAmount
|
||||
};
|
||||
}
|
||||
|
||||
var fakeReporters = [
|
||||
f("heap-allocated", OTHER, 250 * MB),
|
||||
f("explicit/a/b", HEAP, 50 * MB),
|
||||
f("explicit/a/c/d", HEAP, 30 * MB),
|
||||
f("explicit/a/c/e", HEAP, 20 * MB),
|
||||
f("explicit/a/f", HEAP, 40 * MB),
|
||||
f("explicit/g", HEAP, 100 * MB)
|
||||
];
|
||||
|
||||
for (var i = 0; i < fakeReporters.length; i++) {
|
||||
mgr.registerReporter(fakeReporters[i]);
|
||||
}
|
||||
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<iframe id="amFrame" height="500" src="about:memory"></iframe>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
function finish()
|
||||
{
|
||||
// Unregister fake reporters and multi-reporters, re-register the real
|
||||
// reporters and multi-reporters, just in case subsequent tests rely on
|
||||
// them.
|
||||
for (var i = 0; i < fakeReporters.length; i++) {
|
||||
mgr.unregisterReporter(fakeReporters[i]);
|
||||
}
|
||||
for (var i = 0; i < realReporters.length; i++) {
|
||||
mgr.registerReporter(realReporters[i]);
|
||||
}
|
||||
for (var i = 0; i < realMultiReporters.length; i++) {
|
||||
mgr.registerMultiReporter(realMultiReporters[i]);
|
||||
}
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
var gHaveDumped = false;
|
||||
|
||||
function checkClipboard(actual, expected) {
|
||||
if (actual != expected) {
|
||||
if (!gHaveDumped) {
|
||||
dump("******EXPECTED******\n");
|
||||
dump(expected);
|
||||
dump("*******ACTUAL*******\n");
|
||||
dump(actual);
|
||||
dump("********************\n");
|
||||
gHaveDumped = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Click on the identified element, then cut+paste the entire page and
|
||||
// check that the cut text matches what we expect.
|
||||
function test(aId, aExpectedText, aNext) {
|
||||
var win = document.getElementById("amFrame").contentWindow;
|
||||
var node = win.document.getElementById(aId);
|
||||
|
||||
// Yuk: clicking a button is easy; but for tree entries we need to
|
||||
// click on a child of the span identified via |id|.
|
||||
if (node.nodeName === "button") {
|
||||
node.click();
|
||||
} else {
|
||||
node.childNodes[0].click();
|
||||
}
|
||||
|
||||
SimpleTest.executeSoon(function() {
|
||||
document.getElementById("amFrame").focus();
|
||||
SimpleTest.waitForClipboard(
|
||||
function(actual) { return checkClipboard(actual, aExpectedText) },
|
||||
function() {
|
||||
synthesizeKey("A", {accelKey: true});
|
||||
synthesizeKey("C", {accelKey: true});
|
||||
},
|
||||
aNext,
|
||||
function() {
|
||||
ok(false, "pasted text doesn't match");
|
||||
finish();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a function that chains together one test() call per id.
|
||||
function chain(ids) {
|
||||
var x = ids.shift();
|
||||
if (x) {
|
||||
return function() { test(x.id, x.expected, chain(ids)); }
|
||||
} else {
|
||||
return function() { finish(); };
|
||||
}
|
||||
}
|
||||
|
||||
var openExpected =
|
||||
"\
|
||||
Main Process\n\
|
||||
\n\
|
||||
Explicit Allocations\n\
|
||||
250.00 MB (100.0%) -- explicit\n\
|
||||
├──140.00 MB (56.00%) -- a\n\
|
||||
│ ├───50.00 MB (20.00%) ── b\n\
|
||||
│ ├───50.00 MB (20.00%) -- c\n\
|
||||
│ │ ├──30.00 MB (12.00%) ── d\n\
|
||||
│ │ └──20.00 MB (08.00%) ── e\n\
|
||||
│ └───40.00 MB (16.00%) ── f\n\
|
||||
├──100.00 MB (40.00%) ── g\n\
|
||||
└───10.00 MB (04.00%) ── heap-unclassified\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
250.00 MB ── heap-allocated\n\
|
||||
\n\
|
||||
";
|
||||
|
||||
var cClosedExpected =
|
||||
"\
|
||||
Main Process\n\
|
||||
\n\
|
||||
Explicit Allocations\n\
|
||||
250.00 MB (100.0%) -- explicit\n\
|
||||
├──140.00 MB (56.00%) -- a\n\
|
||||
│ ├───50.00 MB (20.00%) ── b\n\
|
||||
│ ├───50.00 MB (20.00%) ++ c\n\
|
||||
│ └───40.00 MB (16.00%) ── f\n\
|
||||
├──100.00 MB (40.00%) ── g\n\
|
||||
└───10.00 MB (04.00%) ── heap-unclassified\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
250.00 MB ── heap-allocated\n\
|
||||
\n\
|
||||
";
|
||||
|
||||
var aClosedExpected =
|
||||
"\
|
||||
Main Process\n\
|
||||
\n\
|
||||
Explicit Allocations\n\
|
||||
250.00 MB (100.0%) -- explicit\n\
|
||||
├──140.00 MB (56.00%) ++ a\n\
|
||||
├──100.00 MB (40.00%) ── g\n\
|
||||
└───10.00 MB (04.00%) ── heap-unclassified\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
250.00 MB ── heap-allocated\n\
|
||||
\n\
|
||||
";
|
||||
|
||||
// We close two sub-trees, hit the "Update" button, then reopen the two
|
||||
// sub-trees in reverse order. After each step, we check the output.
|
||||
var idsToClick = [
|
||||
{ id: "Main:explicit/a/c", expected: cClosedExpected },
|
||||
{ id: "Main:explicit/a", expected: aClosedExpected },
|
||||
{ id: "updateButton", expected: aClosedExpected },
|
||||
{ id: "Main:explicit/a", expected: cClosedExpected },
|
||||
{ id: "Main:explicit/a/c", expected: openExpected }
|
||||
];
|
||||
|
||||
addLoadEvent(chain(idsToClick));
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
]]>
|
||||
</script>
|
||||
</window>
|
@ -1391,7 +1391,7 @@ nsAutoCompleteController::ClearResults()
|
||||
nsresult
|
||||
nsAutoCompleteController::CompleteDefaultIndex(PRInt32 aResultIndex)
|
||||
{
|
||||
if (mDefaultIndexCompleted || mBackspaced || mSearchString.Length() == 0)
|
||||
if (mDefaultIndexCompleted || mBackspaced || mSearchString.Length() == 0 || !mInput)
|
||||
return NS_OK;
|
||||
|
||||
PRInt32 selectionStart;
|
||||
|
@ -42,6 +42,7 @@
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
// Fired by TelemetryPing when async telemetry data should be collected.
|
||||
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";
|
||||
@ -95,6 +96,13 @@ PlacesCategoriesStarter.prototype = {
|
||||
case PlacesUtils.TOPIC_SHUTDOWN:
|
||||
Services.obs.removeObserver(this, PlacesUtils.TOPIC_SHUTDOWN);
|
||||
Services.obs.removeObserver(this, TOPIC_GATHER_TELEMETRY);
|
||||
let globalObj =
|
||||
Cu.getGlobalForObject(PlacesCategoriesStarter.prototype);
|
||||
let descriptor =
|
||||
Object.getOwnPropertyDescriptor(globalObj, "PlacesDBUtils");
|
||||
if (descriptor.value !== undefined) {
|
||||
PlacesDBUtils.shutdown();
|
||||
}
|
||||
break;
|
||||
case TOPIC_GATHER_TELEMETRY:
|
||||
PlacesDBUtils.telemetry();
|
||||
|
@ -78,6 +78,11 @@ let PlacesDBUtils = {
|
||||
*/
|
||||
_executeTasks: function PDBU__executeTasks(aTasks)
|
||||
{
|
||||
if (PlacesDBUtils._isShuttingDown) {
|
||||
tasks.log("- We are shutting down. Will not schedule the tasks.");
|
||||
aTasks.clear();
|
||||
}
|
||||
|
||||
let task = aTasks.pop();
|
||||
if (task) {
|
||||
task.call(PlacesDBUtils, aTasks);
|
||||
@ -102,6 +107,11 @@ let PlacesDBUtils = {
|
||||
}
|
||||
},
|
||||
|
||||
_isShuttingDown : false,
|
||||
shutdown: function PDBU_shutdown() {
|
||||
PlacesDBUtils._isShuttingDown = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Executes integrity check and common maintenance tasks.
|
||||
*
|
||||
|
@ -37,6 +37,7 @@ toolkit.jar:
|
||||
*+ content/global/globalOverlay.js (globalOverlay.js)
|
||||
+ content/global/mozilla.xhtml (mozilla.xhtml)
|
||||
*+ content/global/nsDragAndDrop.js (nsDragAndDrop.js)
|
||||
* content/global/treeUtils.js (treeUtils.js)
|
||||
*+ content/global/viewZoomOverlay.js (viewZoomOverlay.js)
|
||||
*+ content/global/bindings/autocomplete.xml (widgets/autocomplete.xml)
|
||||
*+ content/global/bindings/browser.xml (widgets/browser.xml)
|
||||
|
@ -21,6 +21,7 @@
|
||||
#
|
||||
# Contributor(s):
|
||||
# Ben Goodger <ben@mozilla.org>
|
||||
# Felix Fung <felix.the.cheshire.cat@gmail.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
@ -46,12 +47,12 @@ var gTreeUtils = {
|
||||
aView._rowCount = 0;
|
||||
aTree.treeBoxObject.rowCountChanged(0, -oldCount);
|
||||
},
|
||||
|
||||
|
||||
deleteSelectedItems: function (aTree, aView, aItems, aDeletedItems)
|
||||
{
|
||||
var selection = aTree.view.selection;
|
||||
selection.selectEventsSuppressed = true;
|
||||
|
||||
|
||||
var rc = selection.getRangeCount();
|
||||
for (var i = 0; i < rc; ++i) {
|
||||
var min = { }; var max = { };
|
||||
@ -61,7 +62,7 @@ var gTreeUtils = {
|
||||
aItems[j] = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var nextSelection = 0;
|
||||
for (i = 0; i < aItems.length; ++i) {
|
||||
if (!aItems[i]) {
|
||||
@ -82,20 +83,28 @@ var gTreeUtils = {
|
||||
}
|
||||
selection.selectEventsSuppressed = false;
|
||||
},
|
||||
|
||||
sort: function (aTree, aView, aDataSet, aColumn,
|
||||
aLastSortColumn, aLastSortAscending)
|
||||
|
||||
sort: function (aTree, aView, aDataSet, aColumn, aComparator,
|
||||
aLastSortColumn, aLastSortAscending)
|
||||
{
|
||||
var ascending = (aColumn == aLastSortColumn) ? !aLastSortAscending : true;
|
||||
aDataSet.sort(function (a, b) { return a[aColumn].toLowerCase().localeCompare(b[aColumn].toLowerCase()); });
|
||||
if (aDataSet.length == 0)
|
||||
return ascending;
|
||||
|
||||
var numericSort = !isNaN(aDataSet[0][aColumn]);
|
||||
var sortFunction = null;
|
||||
if (aComparator) {
|
||||
sortFunction = function (a, b) { return aComparator(a[aColumn], b[aColumn]); };
|
||||
}
|
||||
aDataSet.sort(sortFunction);
|
||||
if (!ascending)
|
||||
aDataSet.reverse();
|
||||
|
||||
|
||||
aTree.view.selection.select(-1);
|
||||
aTree.view.selection.select(0);
|
||||
aTree.treeBoxObject.invalidate();
|
||||
aTree.treeBoxObject.ensureRowIsVisible(0);
|
||||
|
||||
|
||||
return ascending;
|
||||
}
|
||||
};
|
17
xpcom/base/ClearOnShutdown.cpp
Normal file
17
xpcom/base/ClearOnShutdown.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et ft=cpp : */
|
||||
|
||||
/* 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 "mozilla/ClearOnShutdown.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ClearOnShutdown_Internal {
|
||||
|
||||
bool sHasShutDown = false;
|
||||
LinkedList<ShutdownObserver> sShutdownObservers;
|
||||
|
||||
} // namespace ClearOnShutdown_Internal
|
||||
} // namespace mozilla
|
@ -38,15 +38,12 @@
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef mozilla_ClearOnShutdown_h
|
||||
#define mozilla_ClearOnShutdown_h
|
||||
|
||||
#include <nsAutoPtr.h>
|
||||
#include <nsCOMPtr.h>
|
||||
#include <nsIObserver.h>
|
||||
#include <nsIObserverService.h>
|
||||
#include <mozilla/Services.h>
|
||||
#include "mozilla/LinkedList.h"
|
||||
|
||||
/*
|
||||
* This header exports one method in the mozilla namespace:
|
||||
* This header exports one public method in the mozilla namespace:
|
||||
*
|
||||
* template<class SmartPtr>
|
||||
* void ClearOnShutdown(SmartPtr *aPtr)
|
||||
@ -64,72 +61,59 @@
|
||||
namespace mozilla {
|
||||
namespace ClearOnShutdown_Internal {
|
||||
|
||||
template<class SmartPtr>
|
||||
class ShutdownObserver : public nsIObserver
|
||||
class ShutdownObserver : public LinkedListElement<ShutdownObserver>
|
||||
{
|
||||
public:
|
||||
ShutdownObserver(SmartPtr *aPtr)
|
||||
virtual void Shutdown() = 0;
|
||||
};
|
||||
|
||||
template<class SmartPtr>
|
||||
class PointerClearer : public ShutdownObserver
|
||||
{
|
||||
public:
|
||||
PointerClearer(SmartPtr *aPtr)
|
||||
: mPtr(aPtr)
|
||||
{}
|
||||
|
||||
virtual ~ShutdownObserver()
|
||||
{}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_IMETHOD Observe(nsISupports *aSubject, const char *aTopic,
|
||||
const PRUnichar *aData)
|
||||
virtual void Shutdown()
|
||||
{
|
||||
MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
|
||||
|
||||
if (mPtr) {
|
||||
*mPtr = NULL;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
SmartPtr *mPtr;
|
||||
};
|
||||
|
||||
// Give the full namespace in the NS_IMPL macros because NS_IMPL_ADDREF/RELEASE
|
||||
// stringify the class name (using the "#" operator) and use this in
|
||||
// trace-malloc. If we didn't fully-qualify the class name and someone else
|
||||
// had a refcounted class named "ShutdownObserver<SmartPtr>" in any namespace,
|
||||
// trace-malloc would assert (bug 711602).
|
||||
//
|
||||
// (Note that because macros happen before templates, trace-malloc sees this
|
||||
// class name as "ShutdownObserver<SmartPtr>"; therefore, it would also assert
|
||||
// if ShutdownObserver<T> had a different size than ShutdownObserver<S>.)
|
||||
|
||||
template<class SmartPtr>
|
||||
NS_IMPL_ADDREF(mozilla::ClearOnShutdown_Internal::
|
||||
ShutdownObserver<SmartPtr>)
|
||||
|
||||
template<class SmartPtr>
|
||||
NS_IMPL_RELEASE(mozilla::ClearOnShutdown_Internal::
|
||||
ShutdownObserver<SmartPtr>)
|
||||
|
||||
template<class SmartPtr>
|
||||
NS_IMPL_QUERY_INTERFACE1(mozilla::ClearOnShutdown_Internal::
|
||||
ShutdownObserver<SmartPtr>,
|
||||
nsIObserver)
|
||||
extern bool sHasShutDown;
|
||||
extern LinkedList<ShutdownObserver> sShutdownObservers;
|
||||
|
||||
} // namespace ClearOnShutdown_Internal
|
||||
|
||||
template<class SmartPtr>
|
||||
void ClearOnShutdown(SmartPtr *aPtr)
|
||||
inline void ClearOnShutdown(SmartPtr *aPtr)
|
||||
{
|
||||
nsRefPtr<ClearOnShutdown_Internal::ShutdownObserver<SmartPtr> > observer =
|
||||
new ClearOnShutdown_Internal::ShutdownObserver<SmartPtr>(aPtr);
|
||||
using namespace ClearOnShutdown_Internal;
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (!os) {
|
||||
NS_WARNING("Could not get observer service!");
|
||||
return;
|
||||
MOZ_ASSERT(!sHasShutDown);
|
||||
ShutdownObserver *observer = new PointerClearer<SmartPtr>(aPtr);
|
||||
sShutdownObservers.insertBack(observer);
|
||||
}
|
||||
|
||||
// Called when XPCOM is shutting down, after all shutdown notifications have
|
||||
// been sent and after all threads' event loops have been purged.
|
||||
inline void KillClearOnShutdown()
|
||||
{
|
||||
using namespace ClearOnShutdown_Internal;
|
||||
|
||||
ShutdownObserver *observer;
|
||||
while ((observer = sShutdownObservers.popFirst())) {
|
||||
observer->Shutdown();
|
||||
delete observer;
|
||||
}
|
||||
os->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
|
||||
|
||||
sHasShutDown = true;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -70,6 +70,7 @@ CPPSRCS = \
|
||||
nsStackWalk.cpp \
|
||||
nsMemoryReporterManager.cpp \
|
||||
FunctionTimer.cpp \
|
||||
ClearOnShutdown.cpp \
|
||||
$(NULL)
|
||||
|
||||
ifeq ($(OS_ARCH),Linux)
|
||||
|
@ -99,8 +99,6 @@ LOCAL_INCLUDES = \
|
||||
-I$(srcdir)/../io \
|
||||
-I$(srcdir)/../components \
|
||||
-I$(srcdir)/../threads \
|
||||
-I$(srcdir)/../threads/_xpidlgen \
|
||||
-I$(srcdir)/../proxy/src \
|
||||
-I$(srcdir)/../reflect/xptinfo/src \
|
||||
-I$(topsrcdir)/chrome/src \
|
||||
-I$(srcdir)/../../docshell/base \
|
||||
|
@ -149,6 +149,7 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **)
|
||||
#include "mozilla/ipc/BrowserProcessSubThread.h"
|
||||
#include "mozilla/MapsMemoryReporter.h"
|
||||
#include "mozilla/AvailableMemoryTracker.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
|
||||
using base::AtExitManager;
|
||||
using mozilla::ipc::BrowserProcessSubThread;
|
||||
@ -640,6 +641,11 @@ ShutdownXPCOM(nsIServiceManager* servMgr)
|
||||
}
|
||||
}
|
||||
|
||||
// Free ClearOnShutdown()'ed smart pointers. This needs to happen *after*
|
||||
// we've finished notifying observers of XPCOM shutdown, because shutdown
|
||||
// observers themselves might call ClearOnShutdown().
|
||||
mozilla::KillClearOnShutdown();
|
||||
|
||||
// XPCOM is officially in shutdown mode NOW
|
||||
// Set this only after the observers have been notified as this
|
||||
// will cause servicemanager to become inaccessible.
|
||||
|
@ -81,7 +81,6 @@ SDK_XPIDLSRCS = \
|
||||
LOCAL_INCLUDES = \
|
||||
-I$(srcdir)/../reflect/xptinfo/src \
|
||||
-I$(srcdir)/../base \
|
||||
-I$(srcdir)/../thread \
|
||||
-I$(srcdir)/../ds \
|
||||
-I$(srcdir)/../build \
|
||||
-I.. \
|
||||
|
@ -163,7 +163,6 @@ include $(topsrcdir)/config/rules.mk
|
||||
|
||||
LOCAL_INCLUDES = \
|
||||
-I$(srcdir)/../ds \
|
||||
-I$(srcdir)/services \
|
||||
$(NULL)
|
||||
|
||||
libs::
|
||||
|
Loading…
Reference in New Issue
Block a user