Merge last green changeset from inbound to mozilla-central

This commit is contained in:
Matt Brubeck 2012-01-27 08:29:23 -08:00
commit f2c7a1ea2b
80 changed files with 2850 additions and 1121 deletions

2
.gitignore vendored
View File

@ -23,7 +23,7 @@ ID
security/manager/.nss.checkout
# Build directories
obj*/
/obj*/
# Build directories for js shell
*/_DBG.OBJ/

View File

@ -195,12 +195,6 @@ public:
*/
virtual bool IsPrimaryForNode() const;
/**
* Return the string bundle
*/
static nsIStringBundle* GetStringBundle()
{ return gStringBundle; }
protected:
nsPresContext* GetPresContext();

View File

@ -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);

View File

@ -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)

View File

@ -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

View 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

View 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);
}
}
}
}

View File

@ -57,8 +57,9 @@ CMMSRCS = nsAccessNodeWrap.mm \
mozActionElements.mm \
mozTextAccessible.mm \
mozHTMLAccessible.mm \
MacUtils.mm \
$(NULL)
EXPORTS = \
nsAccessNodeWrap.h \

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -75,6 +75,7 @@ _TEST_FILES =\
actions.js \
attributes.js \
autocomplete.js \
browser.js \
common.js \
events.js \
grid.js \

View 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();
}

View File

@ -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);
}
/**

View File

@ -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 \

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 =

View File

@ -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];
}

View File

@ -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>

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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"

View File

@ -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");

View File

@ -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:

View File

@ -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

View File

@ -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.
}

View File

@ -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;
};

View File

@ -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);

View File

@ -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

View File

@ -80,6 +80,7 @@ _TEST_FILES = \
test_browserFrame3.html \
test_browserFrame4.html \
test_browserFrame5.html \
test_browserFrame6.html \
$(NULL)
_CHROME_FILES = \

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
ERROR

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View 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>

View File

@ -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

View 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>

View 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
View 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_ */

View File

@ -45,6 +45,7 @@ EXPORTS_mozilla += \
Assertions.h \
Attributes.h \
GuardObjects.h \
LinkedList.h \
MSStdInt.h \
RangedPtr.h \
RefPtr.h \

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}
});
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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.

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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):

View File

@ -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;
}

View File

@ -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)

View File

@ -46,6 +46,7 @@ include $(topsrcdir)/config/rules.mk
_CHROME_FILES = \
test_aboutmemory.xul \
test_aboutmemory2.xul \
test_sqliteMultiReporter.xul \
$(NULL)

View File

@ -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() {

View 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>

View File

@ -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;

View File

@ -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();

View File

@ -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.
*

View File

@ -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)

View File

@ -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;
}
};

View 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

View File

@ -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

View File

@ -70,6 +70,7 @@ CPPSRCS = \
nsStackWalk.cpp \
nsMemoryReporterManager.cpp \
FunctionTimer.cpp \
ClearOnShutdown.cpp \
$(NULL)
ifeq ($(OS_ARCH),Linux)

View File

@ -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 \

View File

@ -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.

View File

@ -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.. \

View File

@ -163,7 +163,6 @@ include $(topsrcdir)/config/rules.mk
LOCAL_INCLUDES = \
-I$(srcdir)/../ds \
-I$(srcdir)/services \
$(NULL)
libs::