mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to b2g-inbound. a=merge
This commit is contained in:
commit
66d2e31b68
@ -3,7 +3,7 @@
|
||||
* 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/. */
|
||||
|
||||
/* For documentation of the accessibility architecture,
|
||||
/* For documentation of the accessibility architecture,
|
||||
* see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
|
||||
*/
|
||||
|
||||
@ -87,7 +87,7 @@ private:
|
||||
|
||||
/**
|
||||
* Our native object. Private because its creation is done lazily.
|
||||
* Don't access it directly. Ever. Unless you are GetNativeObject() or
|
||||
* Don't access it directly. Ever. Unless you are GetNativeObject() or
|
||||
* Shutdown()
|
||||
*/
|
||||
#if defined(__OBJC__)
|
||||
@ -102,9 +102,15 @@ private:
|
||||
* This can never go back to false.
|
||||
* We need it because checking whether we need a native object cost time.
|
||||
*/
|
||||
bool mNativeInited;
|
||||
bool mNativeInited;
|
||||
};
|
||||
|
||||
#if defined(__OBJC__)
|
||||
void FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType);
|
||||
#else
|
||||
void FireNativeEvent(id aNativeAcc, uint32_t aEventType);
|
||||
#endif
|
||||
|
||||
Class GetTypeFromRole(roles::Role aRole);
|
||||
|
||||
} // namespace a11y
|
||||
|
@ -20,7 +20,7 @@ using namespace mozilla::a11y;
|
||||
|
||||
AccessibleWrap::
|
||||
AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
|
||||
Accessible(aContent, aDoc), mNativeObject(nil),
|
||||
Accessible(aContent, aDoc), mNativeObject(nil),
|
||||
mNativeInited(false)
|
||||
{
|
||||
}
|
||||
@ -29,20 +29,20 @@ AccessibleWrap::~AccessibleWrap()
|
||||
{
|
||||
}
|
||||
|
||||
mozAccessible*
|
||||
mozAccessible*
|
||||
AccessibleWrap::GetNativeObject()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
|
||||
if (!mNativeInited && !mNativeObject && !IsDefunct() && !AncestorIsFlat()) {
|
||||
uintptr_t accWrap = reinterpret_cast<uintptr_t>(this);
|
||||
mNativeObject = [[GetNativeType() alloc] initWithAccessible:accWrap];
|
||||
}
|
||||
|
||||
|
||||
mNativeInited = true;
|
||||
|
||||
|
||||
return mNativeObject;
|
||||
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ AccessibleWrap::GetNativeInterface(void** aOutInterface)
|
||||
// overridden in subclasses to create the right kind of object. by default we create a generic
|
||||
// 'mozAccessible' node.
|
||||
Class
|
||||
AccessibleWrap::GetNativeType ()
|
||||
AccessibleWrap::GetNativeType ()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
@ -112,18 +112,7 @@ AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
|
||||
if (!nativeAcc)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
switch (eventType) {
|
||||
case nsIAccessibleEvent::EVENT_FOCUS:
|
||||
[nativeAcc didReceiveFocus];
|
||||
break;
|
||||
case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
|
||||
[nativeAcc valueDidChange];
|
||||
break;
|
||||
case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED:
|
||||
case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
|
||||
[nativeAcc selectedTextDidChange];
|
||||
break;
|
||||
}
|
||||
FireNativeEvent(nativeAcc, eventType);
|
||||
|
||||
return NS_OK;
|
||||
|
||||
@ -165,14 +154,14 @@ AccessibleWrap::RemoveChild(Accessible* aAccessible)
|
||||
|
||||
// if we for some reason have no native accessible, we should be skipped over (and traversed)
|
||||
// when fetching all unignored children, etc. when counting unignored children, we will not be counted.
|
||||
bool
|
||||
AccessibleWrap::IsIgnored()
|
||||
bool
|
||||
AccessibleWrap::IsIgnored()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
||||
|
||||
|
||||
mozAccessible* nativeObject = GetNativeObject();
|
||||
return (!nativeObject) || [nativeObject accessibilityIsIgnored];
|
||||
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
|
||||
}
|
||||
|
||||
@ -203,9 +192,9 @@ AccessibleWrap::GetUnignoredParent() const
|
||||
{
|
||||
// Go up the chain to find a parent that is not ignored.
|
||||
AccessibleWrap* parentWrap = static_cast<AccessibleWrap*>(Parent());
|
||||
while (parentWrap && parentWrap->IsIgnored())
|
||||
while (parentWrap && parentWrap->IsIgnored())
|
||||
parentWrap = static_cast<AccessibleWrap*>(parentWrap->Parent());
|
||||
|
||||
|
||||
return parentWrap;
|
||||
}
|
||||
|
||||
@ -217,7 +206,7 @@ AccessibleWrap::AncestorIsFlat()
|
||||
{
|
||||
// We don't create a native object if we're child of a "flat" accessible;
|
||||
// for example, on OS X buttons shouldn't have any children, because that
|
||||
// makes the OS confused.
|
||||
// makes the OS confused.
|
||||
//
|
||||
// To maintain a scripting environment where the XPCOM accessible hierarchy
|
||||
// look the same on all platforms, we still let the C++ objects be created
|
||||
@ -234,8 +223,29 @@ AccessibleWrap::AncestorIsFlat()
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
a11y::FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||||
|
||||
switch (aEventType) {
|
||||
case nsIAccessibleEvent::EVENT_FOCUS:
|
||||
[aNativeAcc didReceiveFocus];
|
||||
break;
|
||||
case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
|
||||
[aNativeAcc valueDidChange];
|
||||
break;
|
||||
case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED:
|
||||
case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
|
||||
[aNativeAcc selectedTextDidChange];
|
||||
break;
|
||||
}
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||||
}
|
||||
|
||||
Class
|
||||
a11y::GetTypeFromRole(roles::Role aRole)
|
||||
a11y::GetTypeFromRole(roles::Role aRole)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
@ -247,13 +257,13 @@ a11y::GetTypeFromRole(roles::Role aRole)
|
||||
{
|
||||
return [mozButtonAccessible class];
|
||||
}
|
||||
|
||||
|
||||
case roles::PAGETAB:
|
||||
return [mozButtonAccessible class];
|
||||
|
||||
case roles::CHECKBUTTON:
|
||||
return [mozCheckboxAccessible class];
|
||||
|
||||
|
||||
case roles::HEADING:
|
||||
return [mozHeadingAccessible class];
|
||||
|
||||
@ -273,11 +283,11 @@ a11y::GetTypeFromRole(roles::Role aRole)
|
||||
|
||||
case roles::LINK:
|
||||
return [mozLinkAccessible class];
|
||||
|
||||
|
||||
default:
|
||||
return [mozAccessible class];
|
||||
}
|
||||
|
||||
|
||||
return nil;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
|
@ -18,13 +18,13 @@ namespace utils {
|
||||
* Get a localized string from the a11y string bundle.
|
||||
* Return nil if not found.
|
||||
*/
|
||||
NSString*
|
||||
NSString*
|
||||
LocalizedString(const nsString& aString)
|
||||
{
|
||||
nsString text;
|
||||
|
||||
|
||||
Accessible::TranslateString(aString, text);
|
||||
|
||||
|
||||
return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
|
||||
}
|
||||
|
||||
|
@ -47,29 +47,44 @@ ProxyCreated(ProxyAccessible* aProxy, uint32_t)
|
||||
void
|
||||
ProxyDestroyed(ProxyAccessible* aProxy)
|
||||
{
|
||||
mozAccessible* wrapper =
|
||||
reinterpret_cast<mozAccessible*>(aProxy->GetWrapper());
|
||||
mozAccessible* wrapper = GetNativeFromProxy(aProxy);
|
||||
[wrapper expire];
|
||||
[wrapper release];
|
||||
aProxy->SetWrapper(0);
|
||||
}
|
||||
|
||||
void
|
||||
ProxyEvent(ProxyAccessible*, uint32_t)
|
||||
ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType)
|
||||
{
|
||||
// ignore everything but focus-changed, value-changed, caret and selection
|
||||
// events for now.
|
||||
if (aEventType != nsIAccessibleEvent::EVENT_FOCUS &&
|
||||
aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
|
||||
aEventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
|
||||
aEventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED)
|
||||
return;
|
||||
|
||||
mozAccessible* wrapper = GetNativeFromProxy(aProxy);
|
||||
if (wrapper)
|
||||
FireNativeEvent(wrapper, aEventType);
|
||||
}
|
||||
|
||||
void
|
||||
ProxyStateChangeEvent(ProxyAccessible*, uint64_t, bool)
|
||||
ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t, bool)
|
||||
{
|
||||
// mac doesn't care about state change events
|
||||
}
|
||||
|
||||
void
|
||||
ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
|
||||
{
|
||||
mozAccessible* wrapper = GetNativeFromProxy(aTarget);
|
||||
if (wrapper)
|
||||
[wrapper selectedTextDidChange];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace a11y
|
||||
} // namespace mozilla
|
||||
|
||||
@interface GeckoNSApplication(a11y)
|
||||
-(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
|
||||
|
@ -3,7 +3,7 @@
|
||||
* 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/. */
|
||||
|
||||
/* For documentation of the accessibility architecture,
|
||||
/* For documentation of the accessibility architecture,
|
||||
* see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
|
||||
*/
|
||||
|
||||
@ -23,7 +23,7 @@ public:
|
||||
virtual ~RootAccessibleWrap();
|
||||
|
||||
Class GetNativeType ();
|
||||
|
||||
|
||||
// let's our native accessible get in touch with the
|
||||
// native cocoa view that is our accessible parent.
|
||||
void GetNativeWidget (void **aOutView);
|
||||
|
@ -46,7 +46,7 @@ RootAccessibleWrap::GetNativeWidget(void** aOutView)
|
||||
nsIWidget *widget = view->GetWidget();
|
||||
if (widget) {
|
||||
*aOutView = (void**)widget->GetNativeData (NS_NATIVE_WIDGET);
|
||||
NS_ASSERTION (*aOutView,
|
||||
NS_ASSERTION (*aOutView,
|
||||
"Couldn't get the native NSView parent we need to connect the accessibility hierarchy!");
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
|
||||
typedef class TextLeafAccessible TextLeafAccessibleWrap;
|
||||
|
||||
} // namespace a11y
|
||||
|
@ -4,6 +4,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "AccessibleWrap.h"
|
||||
#include "ProxyAccessible.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@ -31,6 +32,12 @@ GetNativeFromGeckoAccessible(mozilla::a11y::Accessible* aAccessible)
|
||||
return native;
|
||||
}
|
||||
|
||||
inline mozAccessible*
|
||||
GetNativeFromProxy(mozilla::a11y::ProxyAccessible* aProxy)
|
||||
{
|
||||
return reinterpret_cast<mozAccessible*>(aProxy->GetWrapper());
|
||||
}
|
||||
|
||||
// This is OR'd with the Accessible owner to indicate the wrap-ee is a proxy.
|
||||
static const uintptr_t IS_PROXY = 1;
|
||||
|
||||
@ -40,13 +47,13 @@ static const uintptr_t IS_PROXY = 1;
|
||||
* Weak reference; it owns us.
|
||||
*/
|
||||
uintptr_t mGeckoAccessible;
|
||||
|
||||
|
||||
/**
|
||||
* Strong ref to array of children
|
||||
*/
|
||||
NSMutableArray* mChildren;
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Weak reference to the parent
|
||||
*/
|
||||
mozAccessible* mParent;
|
||||
@ -122,7 +129,7 @@ static const uintptr_t IS_PROXY = 1;
|
||||
// invalidates and removes all our children from our cached array.
|
||||
- (void)invalidateChildren;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Append a child if they are already cached.
|
||||
*/
|
||||
- (void)appendChild:(mozilla::a11y::Accessible*)aAccessible;
|
||||
|
@ -2,7 +2,7 @@
|
||||
/* 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/. */
|
||||
|
||||
|
||||
#import "mozAccessible.h"
|
||||
|
||||
#import "MacUtils.h"
|
||||
@ -37,7 +37,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
// this object is not ignored, so let's return it.
|
||||
if (![anObject accessibilityIsIgnored])
|
||||
return GetObjectOrRepresentedView(anObject);
|
||||
|
||||
|
||||
// find the closest ancestor that is not ignored.
|
||||
id unignoredObject = anObject;
|
||||
while ((unignoredObject = [unignoredObject accessibilityAttributeValue:NSAccessibilityParentAttribute])) {
|
||||
@ -45,12 +45,12 @@ GetClosestInterestingAccessible(id anObject)
|
||||
// object is not ignored, so let's stop the search.
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// if it's a mozAccessible, we need to take care to maybe return the view we
|
||||
// represent, to the AT.
|
||||
if ([unignoredObject respondsToSelector:@selector(hasRepresentedView)])
|
||||
return GetObjectOrRepresentedView(unignoredObject);
|
||||
|
||||
|
||||
return unignoredObject;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
@ -59,7 +59,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
#pragma mark -
|
||||
|
||||
@implementation mozAccessible
|
||||
|
||||
|
||||
- (id)initWithAccessible:(uintptr_t)aGeckoAccessible
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
@ -104,7 +104,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
|
||||
return reinterpret_cast<ProxyAccessible*>(mGeckoAccessible & ~IS_PROXY);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (BOOL)accessibilityIsIgnored
|
||||
@ -128,12 +128,12 @@ GetClosestInterestingAccessible(id anObject)
|
||||
// if we're expired, we don't support any attributes.
|
||||
if (![self getGeckoAccessible])
|
||||
return [NSArray array];
|
||||
|
||||
|
||||
static NSArray *generalAttributes = nil;
|
||||
|
||||
|
||||
if (!generalAttributes) {
|
||||
// standard attributes that are shared and supported by all generic elements.
|
||||
generalAttributes = [[NSArray alloc] initWithObjects: NSAccessibilityChildrenAttribute,
|
||||
generalAttributes = [[NSArray alloc] initWithObjects: NSAccessibilityChildrenAttribute,
|
||||
NSAccessibilityParentAttribute,
|
||||
NSAccessibilityRoleAttribute,
|
||||
NSAccessibilityTitleAttribute,
|
||||
@ -161,7 +161,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute
|
||||
{
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
if (![self getGeckoAccessible])
|
||||
@ -171,19 +171,19 @@ GetClosestInterestingAccessible(id anObject)
|
||||
if ([attribute isEqualToString:@"AXMozDescription"])
|
||||
return [NSString stringWithFormat:@"role = %u native = %@", mRole, [self class]];
|
||||
#endif
|
||||
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
|
||||
return [self children];
|
||||
if ([attribute isEqualToString:NSAccessibilityParentAttribute])
|
||||
if ([attribute isEqualToString:NSAccessibilityParentAttribute])
|
||||
return [self parent];
|
||||
|
||||
|
||||
#ifdef DEBUG_hakan
|
||||
NSLog (@"(%@ responding to attr %@)", self, attribute);
|
||||
#endif
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
|
||||
return [self role];
|
||||
if ([attribute isEqualToString:NSAccessibilityPositionAttribute])
|
||||
if ([attribute isEqualToString:NSAccessibilityPositionAttribute])
|
||||
return [self position];
|
||||
if ([attribute isEqualToString:NSAccessibilitySubroleAttribute])
|
||||
return [self subrole];
|
||||
@ -191,8 +191,8 @@ GetClosestInterestingAccessible(id anObject)
|
||||
return [NSNumber numberWithBool:[self isEnabled]];
|
||||
if ([attribute isEqualToString:NSAccessibilityValueAttribute])
|
||||
return [self value];
|
||||
if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
|
||||
return [self roleDescription];
|
||||
if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
|
||||
return [self roleDescription];
|
||||
if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute])
|
||||
return [self customDescription];
|
||||
if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
|
||||
@ -213,7 +213,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
}
|
||||
if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
|
||||
return [self help];
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
NSLog (@"!!! %@ can't respond to attribute %@", self, attribute);
|
||||
#endif
|
||||
@ -228,7 +228,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
|
||||
return [self canBeFocused];
|
||||
|
||||
|
||||
return NO;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
|
||||
@ -241,7 +241,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
#ifdef DEBUG_hakan
|
||||
NSLog (@"[%@] %@='%@'", self, attribute, value);
|
||||
#endif
|
||||
|
||||
|
||||
// we only support focusing elements so far.
|
||||
if ([attribute isEqualToString:NSAccessibilityFocusedAttribute] && [value boolValue])
|
||||
[self focus];
|
||||
@ -275,22 +275,22 @@ GetClosestInterestingAccessible(id anObject)
|
||||
}
|
||||
|
||||
// if we didn't find anything, return ourself (or the first unignored ancestor).
|
||||
return GetClosestInterestingAccessible(self);
|
||||
return GetClosestInterestingAccessible(self);
|
||||
}
|
||||
|
||||
- (NSArray*)accessibilityActionNames
|
||||
- (NSArray*)accessibilityActionNames
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString*)accessibilityActionDescription:(NSString*)action
|
||||
- (NSString*)accessibilityActionDescription:(NSString*)action
|
||||
{
|
||||
// by default we return whatever the MacOS API know about.
|
||||
// if you have custom actions, override.
|
||||
return NSAccessibilityActionDescription(action);
|
||||
}
|
||||
|
||||
- (void)accessibilityPerformAction:(NSString*)action
|
||||
- (void)accessibilityPerformAction:(NSString*)action
|
||||
{
|
||||
}
|
||||
|
||||
@ -299,14 +299,14 @@ GetClosestInterestingAccessible(id anObject)
|
||||
AccessibleWrap* accWrap = [self getGeckoAccessible];
|
||||
if (!accWrap)
|
||||
return nil;
|
||||
|
||||
|
||||
Accessible* focusedGeckoChild = accWrap->FocusedChild();
|
||||
if (focusedGeckoChild) {
|
||||
mozAccessible *focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
|
||||
if (focusedChild)
|
||||
return GetClosestInterestingAccessible(focusedChild);
|
||||
}
|
||||
|
||||
|
||||
// return ourself if we can't get a native focused child.
|
||||
return GetClosestInterestingAccessible(self);
|
||||
}
|
||||
@ -324,9 +324,9 @@ GetClosestInterestingAccessible(id anObject)
|
||||
if (nativeParent)
|
||||
return GetClosestInterestingAccessible(nativeParent);
|
||||
}
|
||||
|
||||
|
||||
// GetUnignoredParent() returns null when there is no unignored accessible all the way up to
|
||||
// the root accessible. so we'll have to return whatever native accessible is above our root accessible
|
||||
// the root accessible. so we'll have to return whatever native accessible is above our root accessible
|
||||
// (which might be the owning NSWindow in the application, for example).
|
||||
//
|
||||
// get the native root accessible, and tell it to return its first parent unignored accessible.
|
||||
@ -380,7 +380,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
[mChildren addObject:GetObjectOrRepresentedView(curNative)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef DEBUG_hakan
|
||||
// make sure we're not returning any ignored accessibles.
|
||||
NSEnumerator *e = [mChildren objectEnumerator];
|
||||
@ -389,7 +389,7 @@ GetClosestInterestingAccessible(id anObject)
|
||||
NSAssert1(![m accessibilityIsIgnored], @"we should never return an ignored accessible! (%@)", m);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
return mChildren;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
@ -710,7 +710,7 @@ struct RoleDescrComparator
|
||||
// if mChildren is nil, then we don't even need to bother
|
||||
if (!mChildren)
|
||||
return;
|
||||
|
||||
|
||||
mozAccessible *curNative = GetNativeFromGeckoAccessible(aAccessible);
|
||||
if (curNative)
|
||||
[mChildren addObject:GetObjectOrRepresentedView(curNative)];
|
||||
@ -723,7 +723,7 @@ struct RoleDescrComparator
|
||||
[self invalidateChildren];
|
||||
|
||||
mGeckoAccessible = 0;
|
||||
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||||
}
|
||||
|
||||
@ -747,13 +747,13 @@ struct RoleDescrComparator
|
||||
NSAssert(![self accessibilityIsIgnored], @"can't sanity check children of an ignored accessible!");
|
||||
NSEnumerator *iter = [children objectEnumerator];
|
||||
mozAccessible *curObj = nil;
|
||||
|
||||
|
||||
NSLog(@"sanity checking %@", self);
|
||||
|
||||
|
||||
while ((curObj = [iter nextObject])) {
|
||||
id realSelf = GetObjectOrRepresentedView(self);
|
||||
NSLog(@"checking %@", realSelf);
|
||||
NSAssert2([curObj parent] == realSelf,
|
||||
NSAssert2([curObj parent] == realSelf,
|
||||
@"!!! %@ not returning %@ as AXParent, even though it is a AXChild of it!", curObj, realSelf);
|
||||
}
|
||||
|
||||
@ -783,26 +783,26 @@ struct RoleDescrComparator
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||||
|
||||
NSAssert(![self isExpired], @"!!! trying to print hierarchy of expired object!");
|
||||
|
||||
|
||||
// print this node
|
||||
NSMutableString *indent = [NSMutableString stringWithCapacity:level];
|
||||
unsigned i=0;
|
||||
for (;i<level;i++)
|
||||
[indent appendString:@" "];
|
||||
|
||||
|
||||
NSLog (@"%@(#%i) %@", indent, level, self);
|
||||
|
||||
|
||||
// use |children| method to make sure our children are lazily fetched first.
|
||||
NSArray *children = [self children];
|
||||
if (!children)
|
||||
return;
|
||||
|
||||
|
||||
if (![self accessibilityIsIgnored])
|
||||
[self sanityCheckChildren];
|
||||
|
||||
|
||||
NSEnumerator *iter = [children objectEnumerator];
|
||||
mozAccessible *object = nil;
|
||||
|
||||
|
||||
while (iter && (object = [iter nextObject]))
|
||||
// print every child node's subtree, increasing the indenting
|
||||
// by two for every level.
|
||||
|
@ -9,10 +9,10 @@
|
||||
|
||||
/* This protocol's primary use is so widget/cocoa can talk back to us
|
||||
properly.
|
||||
|
||||
ChildView owns the topmost mozRootAccessible, and needs to take care of setting up
|
||||
|
||||
ChildView owns the topmost mozRootAccessible, and needs to take care of setting up
|
||||
that parent/child relationship.
|
||||
|
||||
|
||||
This protocol is thus used to make sure it knows it's talking to us, and not
|
||||
just some random |id|.
|
||||
*/
|
||||
@ -23,7 +23,7 @@
|
||||
// root accessible per window.
|
||||
- (BOOL)isRoot;
|
||||
|
||||
// some mozAccessibles implement accessibility support in place of another object. for example,
|
||||
// some mozAccessibles implement accessibility support in place of another object. for example,
|
||||
// ChildView gets its support from us.
|
||||
//
|
||||
// instead of returning a mozAccessible to the OS when it wants an object, we need to pass the view we represent, so the
|
||||
|
@ -162,10 +162,10 @@ enum CheckboxValue {
|
||||
if ([action isEqualToString:NSAccessibilityPressAction]) {
|
||||
if ([self isChecked] != kUnchecked)
|
||||
return @"uncheck checkbox"; // XXX: localize this later?
|
||||
|
||||
|
||||
return @"check checkbox"; // XXX: localize this later?
|
||||
}
|
||||
|
||||
|
||||
return nil;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
@ -179,7 +179,7 @@ enum CheckboxValue {
|
||||
if (state & states::CHECKED) {
|
||||
return (state & states::MIXED) ? kMixed : kChecked;
|
||||
}
|
||||
|
||||
|
||||
return kUnchecked;
|
||||
}
|
||||
|
||||
@ -207,24 +207,24 @@ enum CheckboxValue {
|
||||
{
|
||||
// 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;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString *)attribute
|
||||
{
|
||||
{
|
||||
if ([attribute isEqualToString:NSAccessibilityContentsAttribute])
|
||||
return [super children];
|
||||
if ([attribute isEqualToString:NSAccessibilityTabsAttribute])
|
||||
return [self tabs];
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -256,7 +256,7 @@ enum CheckboxValue {
|
||||
NSArray* children = [self children];
|
||||
NSEnumerator* enumerator = [children objectEnumerator];
|
||||
mTabs = [[NSMutableArray alloc] init];
|
||||
|
||||
|
||||
id obj;
|
||||
while ((obj = [enumerator nextObject]))
|
||||
if ([obj isTab])
|
||||
|
@ -9,7 +9,7 @@
|
||||
// our protocol that we implement (so cocoa widgets can talk to us)
|
||||
#import "mozAccessibleProtocol.h"
|
||||
|
||||
/*
|
||||
/*
|
||||
The root accessible. There is one per window.
|
||||
Created by the RootAccessibleWrap.
|
||||
*/
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
static id <mozAccessible, mozView>
|
||||
static id <mozAccessible, mozView>
|
||||
getNativeViewFromRootAccessible(Accessible* aAccessible)
|
||||
{
|
||||
RootAccessibleWrap* root =
|
||||
@ -31,14 +31,14 @@ getNativeViewFromRootAccessible(Accessible* aAccessible)
|
||||
- (NSArray*)accessibilityAttributeNames
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
|
||||
// if we're expired, we don't support any attributes.
|
||||
if (![self getGeckoAccessible])
|
||||
return [NSArray array];
|
||||
|
||||
|
||||
// standard attributes that are shared and supported by root accessible (AXMain) elements.
|
||||
static NSMutableArray* attributes = nil;
|
||||
|
||||
|
||||
if (!attributes) {
|
||||
attributes = [[super accessibilityAttributeNames] mutableCopy];
|
||||
[attributes addObject:NSAccessibilityMainAttribute];
|
||||
@ -53,14 +53,14 @@ getNativeViewFromRootAccessible(Accessible* aAccessible)
|
||||
- (id)accessibilityAttributeValue:(NSString *)attribute
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilityMainAttribute])
|
||||
return [NSNumber numberWithBool:[[self window] isMainWindow]];
|
||||
if ([attribute isEqualToString:NSAccessibilityMinimizedAttribute])
|
||||
return [NSNumber numberWithBool:[[self window] isMiniaturized]];
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
}
|
||||
|
||||
@ -72,10 +72,10 @@ getNativeViewFromRootAccessible(Accessible* aAccessible)
|
||||
|
||||
if (!mParallelView)
|
||||
mParallelView = (id<mozView, mozAccessible>)[self representedView];
|
||||
|
||||
|
||||
if (mParallelView)
|
||||
return [mParallelView accessibilityAttributeValue:NSAccessibilityParentAttribute];
|
||||
|
||||
|
||||
NSAssert(mParallelView, @"we're a root accessible w/o native view?");
|
||||
return [super parent];
|
||||
|
||||
@ -94,9 +94,9 @@ getNativeViewFromRootAccessible(Accessible* aAccessible)
|
||||
|
||||
if (mParallelView)
|
||||
return (id)mParallelView;
|
||||
|
||||
|
||||
mParallelView = getNativeViewFromRootAccessible ([self getGeckoAccessible]);
|
||||
|
||||
|
||||
NSAssert(mParallelView, @"can't return root accessible's native parallel view.");
|
||||
return mParallelView;
|
||||
|
||||
|
@ -19,7 +19,7 @@ ToNSRange(id aValue, NSRange* aRange)
|
||||
{
|
||||
NS_PRECONDITION(aRange, "aRange is nil");
|
||||
|
||||
if ([aValue isKindOfClass:[NSValue class]] &&
|
||||
if ([aValue isKindOfClass:[NSValue class]] &&
|
||||
strcmp([(NSValue*)aValue objCType], @encode(NSRange)) == 0) {
|
||||
*aRange = [aValue rangeValue];
|
||||
return true;
|
||||
@ -104,7 +104,7 @@ ToNSString(id aValue)
|
||||
if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
|
||||
// Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText
|
||||
// object's AXSelectedText attribute. See bug 674612 for details.
|
||||
// Also if there is no selected text, we return the full text.
|
||||
// Also if there is no selected text, we return the full text.
|
||||
// See bug 369710 for details.
|
||||
if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) {
|
||||
NSString* selectedText = [self selectedText];
|
||||
@ -224,7 +224,7 @@ ToNSString(id aValue)
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilityValueAttribute])
|
||||
return ![self isReadOnly];
|
||||
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute] ||
|
||||
[attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] ||
|
||||
[attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
|
||||
@ -246,7 +246,7 @@ ToNSString(id aValue)
|
||||
|
||||
if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
|
||||
[self setText:ToNSString(value)];
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -282,7 +282,7 @@ ToNSString(id aValue)
|
||||
textAcc->ScrollSubstringTo(range.location, range.location + range.length,
|
||||
nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[super accessibilitySetValue:value forAttribute:attribute];
|
||||
|
||||
|
@ -8,36 +8,22 @@
|
||||
"itau.com.br": "\\(Mobile#(Android; Mobile",
|
||||
// bug 826510, r7.com
|
||||
"r7.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 826514, estadao.com.br
|
||||
"estadao.com.br": "\\(Mobile#(Android; Mobile",
|
||||
// bug 826711, bb.com.br
|
||||
"bb.com.br": "\\(Mobile#(Android; Mobile",
|
||||
// bug 827622, bing.com
|
||||
"bing.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 827626, magazineluiza.com.br
|
||||
"magazineluiza.com.br": "\\(Mobile#(Android; Mobile",
|
||||
// bug 827633, hao123.com
|
||||
"hao123.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 827573, webmotors.com.br
|
||||
"webmotors.com.br": "\\(Mobile#(Android; Mobile",
|
||||
// bug 827670, elpais.com.co
|
||||
"elpais.com.co": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
|
||||
// bug 827674, avianca.com
|
||||
"avianca.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 827678, marca.com
|
||||
"marca.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 828371, ingbank.pl
|
||||
"ingbank.pl": "\\(Mobile#(Android; Mobile",
|
||||
// bug 828416, loteriasyapuestas.es
|
||||
"loteriasyapuestas.es": "\\(Mobile#(Android; Mobile",
|
||||
// bug 828418, bbva.es
|
||||
"bbva.es": "\\(Mobile#(Android; Mobile",
|
||||
// bug 828422, publico.es
|
||||
"publico.es": "\\(Mobile#(Android; Mobile",
|
||||
// bug 828439, movistar.com.ve
|
||||
"movistar.com.ve": "\\(Mobile#(Android; Mobile",
|
||||
// bug 843129, 11870.com
|
||||
"iphonejuegosgratis.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 843132, comunio.es
|
||||
"comunio.es": "\\(Mobile#(Android; Mobile",
|
||||
// bug 843151, citibank.com
|
||||
@ -48,8 +34,6 @@
|
||||
"ehow.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878228, blikk.hu
|
||||
"blikk.hu": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878232, hazipatika.com
|
||||
"hazipatika.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878238, koponyeg.hu
|
||||
"koponyeg.hu": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878240, kuruc.info
|
||||
@ -60,24 +44,12 @@
|
||||
"port.hu": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878249, portfolio.hu
|
||||
"portfolio.hu": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878253, vatera.hu
|
||||
"vatera.hu": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878260, cdm.me
|
||||
"cdm.me": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878262, download.com
|
||||
"download.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878264, haber.ba
|
||||
"haber.ba": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878271, kurir-info.rs
|
||||
"kurir-info.rs": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878273, livescore.com
|
||||
"livescore.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878277, naslovi.net
|
||||
"naslovi.net": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878649, univision.com
|
||||
"univision.com": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878653, redstarbelgrade.info
|
||||
"redstarbelgrade.info": "\\(Mobile#(Android; Mobile",
|
||||
// bug 878655, vesti-online.com
|
||||
"vesti-online.com": "\\(Mobile#(Android; Mobile"
|
||||
"redstarbelgrade.info": "\\(Mobile#(Android; Mobile"
|
||||
}
|
||||
|
@ -123,8 +123,7 @@ const gXPInstallObserver = {
|
||||
}
|
||||
};
|
||||
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
||||
"find-and-install-add-ons";
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
|
||||
let messageString;
|
||||
let notification = document.getElementById("addon-install-confirmation-notification");
|
||||
@ -132,17 +131,20 @@ const gXPInstallObserver = {
|
||||
// None of the add-ons are verified
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
|
||||
notification.setAttribute("warning", "true");
|
||||
options.learnMoreURL += "unsigned-addons";
|
||||
}
|
||||
else if (unsigned.length == 0) {
|
||||
// All add-ons are verified or don't need to be verified
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
|
||||
notification.removeAttribute("warning");
|
||||
options.learnMoreURL += "find-and-install-add-ons";
|
||||
}
|
||||
else {
|
||||
// Some of the add-ons are unverified, the list of names will indicate
|
||||
// which
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
|
||||
notification.setAttribute("warning", "true");
|
||||
options.learnMoreURL += "unsigned-addons";
|
||||
}
|
||||
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
@ -310,8 +312,7 @@ const gXPInstallObserver = {
|
||||
|
||||
// Add Learn More link when refusing to install an unsigned add-on
|
||||
if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
|
||||
options.learnMoreURL =
|
||||
Services.prefs.getCharPref("xpinstall.signatures.infoURL");
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
|
||||
}
|
||||
|
||||
messageString = gNavigatorBundle.getFormattedString(error, args);
|
||||
|
@ -13,7 +13,17 @@ const kWhitelist = new Set([
|
||||
|
||||
let moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
|
||||
let {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
|
||||
let {Reflect} = Cu.import("resource://gre/modules/reflect.jsm", {});
|
||||
|
||||
// Normally we would use reflect.jsm to get Reflect.parse. However, if
|
||||
// we do that, then all the AST data is allocated in reflect.jsm's
|
||||
// zone. That exposes a bug in our GC. The GC collects reflect.jsm's
|
||||
// zone but not the zone in which our test code lives (since no new
|
||||
// data is being allocated in it). The cross-compartment wrappers in
|
||||
// our zone that point to the AST data never get collected, and so the
|
||||
// AST data itself is never collected. We need to GC both zones at
|
||||
// once to fix the problem.
|
||||
const init = Components.classes["@mozilla.org/jsreflect;1"].createInstance();
|
||||
init();
|
||||
|
||||
/**
|
||||
* Check if an error should be ignored due to matching one of the whitelist
|
||||
|
@ -10,9 +10,7 @@ add_task(function* () {
|
||||
// We must wait for the context menu code to build metadata.
|
||||
yield openContextMenuForContentSelector(browser, 'form > input[name="search"]');
|
||||
|
||||
yield withBookmarksDialog(function*() {
|
||||
AddKeywordForSearchField();
|
||||
}, function* (dialogWin) {
|
||||
yield withBookmarksDialog(AddKeywordForSearchField, function* (dialogWin) {
|
||||
let acceptBtn = dialogWin.document.documentElement.getButton("accept");
|
||||
ok(acceptBtn.disabled, "Accept button is disabled");
|
||||
|
||||
|
@ -310,7 +310,9 @@ let withBookmarksDialog = Task.async(function* (openFn, taskFn) {
|
||||
});
|
||||
|
||||
info("withBookmarksDialog: opening the dialog");
|
||||
yield openFn();
|
||||
// The dialog might be modal and could block our events loop, so executeSoon.
|
||||
executeSoon(openFn);
|
||||
|
||||
info("withBookmarksDialog: waiting for the dialog");
|
||||
let dialogWin = yield dialogPromise;
|
||||
|
||||
|
@ -32,6 +32,7 @@ support-files =
|
||||
[browser_toolbox_highlight.js]
|
||||
[browser_toolbox_hosts.js]
|
||||
[browser_toolbox_hosts_size.js]
|
||||
[browser_toolbox_minimize.js]
|
||||
[browser_toolbox_options.js]
|
||||
[browser_toolbox_options_disable_buttons.js]
|
||||
[browser_toolbox_options_disable_cache-01.js]
|
||||
|
75
browser/devtools/framework/test/browser_toolbox_minimize.js
Normal file
75
browser/devtools/framework/test/browser_toolbox_minimize.js
Normal file
@ -0,0 +1,75 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that when the toolbox is displayed in a bottom host, that host can be
|
||||
// minimized to just the tabbar height, and maximized again.
|
||||
// Also test that while minimized, switching to a tool, clicking on the
|
||||
// settings, or clicking on the selected tool's tab maximizes the toolbox again.
|
||||
// Finally test that the minimize button doesn't exist in other host types.
|
||||
|
||||
const URL = "data:text/html;charset=utf8,test page";
|
||||
|
||||
add_task(function*() {
|
||||
info("Create a test tab and open the toolbox");
|
||||
let tab = yield addTab(URL);
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "webconsole");
|
||||
|
||||
let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
ok(button, "The minimize button exists in the default bottom host");
|
||||
|
||||
info("Try to minimize the toolbox");
|
||||
yield minimize(toolbox);
|
||||
ok(parseInt(toolbox._host.frame.style.marginBottom, 10) < 0,
|
||||
"The toolbox host has been hidden away with a negative-margin");
|
||||
|
||||
info("Try to maximize again the toolbox");
|
||||
yield maximize(toolbox);
|
||||
ok(parseInt(toolbox._host.frame.style.marginBottom, 10) == 0,
|
||||
"The toolbox host is shown again");
|
||||
|
||||
info("Minimize again and switch to another tool");
|
||||
yield minimize(toolbox);
|
||||
let onMaximized = toolbox._host.once("maximized");
|
||||
yield toolbox.selectTool("inspector");
|
||||
yield onMaximized;
|
||||
|
||||
info("Minimize again and click on the tab of the current tool");
|
||||
yield minimize(toolbox);
|
||||
onMaximized = toolbox._host.once("maximized");
|
||||
let tabButton = toolbox.doc.querySelector("#toolbox-tab-inspector");
|
||||
EventUtils.synthesizeMouseAtCenter(tabButton, {}, toolbox.doc.defaultView);
|
||||
yield onMaximized;
|
||||
|
||||
info("Minimize again and click on the settings tab");
|
||||
yield minimize(toolbox);
|
||||
onMaximized = toolbox._host.once("maximized");
|
||||
let settingsButton = toolbox.doc.querySelector("#toolbox-tab-options");
|
||||
EventUtils.synthesizeMouseAtCenter(settingsButton, {}, toolbox.doc.defaultView);
|
||||
yield onMaximized;
|
||||
|
||||
info("Switch to a different host");
|
||||
yield toolbox.switchHost(devtools.Toolbox.HostType.SIDE);
|
||||
button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
ok(!button, "The minimize button doesn't exist in the side host");
|
||||
|
||||
Services.prefs.clearUserPref("devtools.toolbox.host");
|
||||
yield toolbox.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function* minimize(toolbox) {
|
||||
let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
let onMinimized = toolbox._host.once("minimized");
|
||||
EventUtils.synthesizeMouseAtCenter(button, {}, toolbox.doc.defaultView);
|
||||
yield onMinimized;
|
||||
}
|
||||
|
||||
function* maximize(toolbox) {
|
||||
let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
let onMaximized = toolbox._host.once("maximized");
|
||||
EventUtils.synthesizeMouseAtCenter(button, {}, toolbox.doc.defaultView);
|
||||
yield onMaximized;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
/* 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/. */
|
||||
/* globals DOMHelpers, Services */
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -49,7 +50,7 @@ BottomHost.prototype = {
|
||||
/**
|
||||
* Create a box at the bottom of the host tab.
|
||||
*/
|
||||
create: function BH_create() {
|
||||
create: function() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
|
||||
@ -90,21 +91,69 @@ BottomHost.prototype = {
|
||||
/**
|
||||
* Raise the host.
|
||||
*/
|
||||
raise: function BH_raise() {
|
||||
raise: function() {
|
||||
focusTab(this.hostTab);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the toolbox title.
|
||||
* Minimize this host so that only the toolbox tabbar remains visible.
|
||||
* @param {Number} height The height to minimize to. Defaults to 0, which
|
||||
* means that the toolbox won't be visible at all once minimized.
|
||||
*/
|
||||
setTitle: function BH_setTitle(title) {
|
||||
// Nothing to do for this host type.
|
||||
minimize: function(height=0) {
|
||||
if (this.isMinimized) {
|
||||
return;
|
||||
}
|
||||
this.isMinimized = true;
|
||||
|
||||
this.frame.style.marginBottom = -this.frame.height + height + "px";
|
||||
this._splitter.classList.add("disabled");
|
||||
|
||||
let onTransitionEnd = () => {
|
||||
this.frame.removeEventListener("transitionend", onTransitionEnd);
|
||||
this.emit("minimized");
|
||||
};
|
||||
this.frame.addEventListener("transitionend", onTransitionEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* If the host was minimized before, maximize it again (the host will be
|
||||
* maximized to the height it previously had).
|
||||
*/
|
||||
maximize: function() {
|
||||
if (!this.isMinimized) {
|
||||
return;
|
||||
}
|
||||
this.isMinimized = false;
|
||||
|
||||
this.frame.style.marginBottom = "0";
|
||||
this._splitter.classList.remove("disabled");
|
||||
|
||||
let onTransitionEnd = () => {
|
||||
this.frame.removeEventListener("transitionend", onTransitionEnd);
|
||||
this.emit("maximized");
|
||||
};
|
||||
this.frame.addEventListener("transitionend", onTransitionEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the minimize mode.
|
||||
* @param {Number} minHeight The height to minimize to.
|
||||
*/
|
||||
toggleMinimizeMode: function(minHeight) {
|
||||
this.isMinimized ? this.maximize() : this.minimize(minHeight);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the toolbox title.
|
||||
* Nothing to do for this host type.
|
||||
*/
|
||||
setTitle: function() {},
|
||||
|
||||
/**
|
||||
* Destroy the bottom dock.
|
||||
*/
|
||||
destroy: function BH_destroy() {
|
||||
destroy: function() {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
|
||||
@ -115,8 +164,7 @@ BottomHost.prototype = {
|
||||
|
||||
return promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Host object for the in-browser sidebar
|
||||
@ -135,7 +183,7 @@ SidebarHost.prototype = {
|
||||
/**
|
||||
* Create a box in the sidebar of the host tab.
|
||||
*/
|
||||
create: function SH_create() {
|
||||
create: function() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
|
||||
@ -175,21 +223,20 @@ SidebarHost.prototype = {
|
||||
/**
|
||||
* Raise the host.
|
||||
*/
|
||||
raise: function SH_raise() {
|
||||
raise: function() {
|
||||
focusTab(this.hostTab);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the toolbox title.
|
||||
* Nothing to do for this host type.
|
||||
*/
|
||||
setTitle: function SH_setTitle(title) {
|
||||
// Nothing to do for this host type.
|
||||
},
|
||||
setTitle: function() {},
|
||||
|
||||
/**
|
||||
* Destroy the sidebar.
|
||||
*/
|
||||
destroy: function SH_destroy() {
|
||||
destroy: function() {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
|
||||
@ -200,7 +247,7 @@ SidebarHost.prototype = {
|
||||
|
||||
return promise.resolve(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Host object for the toolbox in a separate window
|
||||
@ -219,14 +266,14 @@ WindowHost.prototype = {
|
||||
/**
|
||||
* Create a new xul window to contain the toolbox.
|
||||
*/
|
||||
create: function WH_create() {
|
||||
create: function() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let flags = "chrome,centerscreen,resizable,dialog=no";
|
||||
let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
|
||||
flags, null);
|
||||
|
||||
let frameLoad = (event) => {
|
||||
let frameLoad = () => {
|
||||
win.removeEventListener("load", frameLoad, true);
|
||||
win.focus();
|
||||
this.frame = win.document.getElementById("toolbox-iframe");
|
||||
@ -258,21 +305,21 @@ WindowHost.prototype = {
|
||||
/**
|
||||
* Raise the host.
|
||||
*/
|
||||
raise: function RH_raise() {
|
||||
raise: function() {
|
||||
this._window.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the toolbox title.
|
||||
*/
|
||||
setTitle: function WH_setTitle(title) {
|
||||
setTitle: function(title) {
|
||||
this._window.document.title = title;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the window.
|
||||
*/
|
||||
destroy: function WH_destroy() {
|
||||
destroy: function() {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
|
||||
@ -296,14 +343,14 @@ function CustomHost(hostTab, options) {
|
||||
CustomHost.prototype = {
|
||||
type: "custom",
|
||||
|
||||
_sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg, data) {
|
||||
_sendMessageToTopWindow: function(msg, data) {
|
||||
// It's up to the custom frame owner (parent window) to honor
|
||||
// "close" or "raise" instructions.
|
||||
let topWindow = this.frame.ownerDocument.defaultView;
|
||||
if (!topWindow) {
|
||||
return;
|
||||
}
|
||||
let json = {name:"toolbox-" + msg, uid: this.uid};
|
||||
let json = {name: "toolbox-" + msg, uid: this.uid};
|
||||
if (data) {
|
||||
json.data = data;
|
||||
}
|
||||
@ -313,35 +360,35 @@ CustomHost.prototype = {
|
||||
/**
|
||||
* Create a new xul window to contain the toolbox.
|
||||
*/
|
||||
create: function CH_create() {
|
||||
create: function() {
|
||||
return promise.resolve(this.frame);
|
||||
},
|
||||
|
||||
/**
|
||||
* Raise the host.
|
||||
*/
|
||||
raise: function CH_raise() {
|
||||
raise: function() {
|
||||
this._sendMessageToTopWindow("raise");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the toolbox title.
|
||||
*/
|
||||
setTitle: function CH_setTitle(title) {
|
||||
setTitle: function(title) {
|
||||
this._sendMessageToTopWindow("title", { value: title });
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the window.
|
||||
*/
|
||||
destroy: function WH_destroy() {
|
||||
destroy: function() {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
this._sendMessageToTopWindow("close");
|
||||
}
|
||||
return promise.resolve(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Switch to the given tab in a browser and focus the browser window
|
||||
|
@ -1,6 +1,9 @@
|
||||
/* 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/. */
|
||||
/* globals gDevTools, DOMHelpers, toolboxStrings, InspectorFront, Selection,
|
||||
getPerformanceActorsConnection, CommandUtils, DevToolsUtils, screenManager,
|
||||
oscpu, Hosts, is64Bit */
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -128,6 +131,10 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
|
||||
this._updateTextboxMenuItems = this._updateTextboxMenuItems.bind(this);
|
||||
this._onBottomHostMinimized = this._onBottomHostMinimized.bind(this);
|
||||
this._onBottomHostMaximized = this._onBottomHostMaximized.bind(this);
|
||||
this._onToolSelectWhileMinimized = this._onToolSelectWhileMinimized.bind(this);
|
||||
this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
|
||||
|
||||
this._target.on("close", this.destroy);
|
||||
|
||||
@ -314,7 +321,7 @@ Toolbox.prototype = {
|
||||
/**
|
||||
* Open the toolbox
|
||||
*/
|
||||
open: function () {
|
||||
open: function() {
|
||||
return Task.spawn(function*() {
|
||||
let iframe = yield this._host.create();
|
||||
let domReady = promise.defer();
|
||||
@ -391,13 +398,15 @@ Toolbox.prototype = {
|
||||
]);
|
||||
|
||||
// Lazily connect to the profiler here and don't wait for it to complete,
|
||||
// used to intercept console.profile calls before the performance tools are open.
|
||||
// used to intercept console.profile calls before the performance tools
|
||||
// are open.
|
||||
let profilerReady = this._connectProfiler();
|
||||
|
||||
// However, while testing, we must wait for the performance connection to finish,
|
||||
// as most tests shut down without waiting for a toolbox destruction event,
|
||||
// resulting in the shared profiler connection being opened and closed
|
||||
// outside of the test that originally opened the toolbox.
|
||||
// However, while testing, we must wait for the performance connection to
|
||||
// finish, as most tests shut down without waiting for a toolbox
|
||||
// destruction event, resulting in the shared profiler connection being
|
||||
// opened and closed outside of the test that originally opened the
|
||||
// toolbox.
|
||||
if (gDevTools.testing) {
|
||||
yield profilerReady;
|
||||
}
|
||||
@ -429,13 +438,13 @@ Toolbox.prototype = {
|
||||
* }
|
||||
*/
|
||||
_prefChanged: function(event, data) {
|
||||
switch(data.pref) {
|
||||
case "devtools.cache.disabled":
|
||||
this._applyCacheSettings();
|
||||
break;
|
||||
case "devtools.serviceWorkers.testing.enabled":
|
||||
this._applyServiceWorkersTestingSettings();
|
||||
break;
|
||||
switch (data.pref) {
|
||||
case "devtools.cache.disabled":
|
||||
this._applyCacheSettings();
|
||||
break;
|
||||
case "devtools.serviceWorkers.testing.enabled":
|
||||
this._applyServiceWorkersTestingSettings();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -628,7 +637,8 @@ Toolbox.prototype = {
|
||||
}
|
||||
|
||||
key.setAttribute("modifiers", toolDefinition.modifiers);
|
||||
key.setAttribute("oncommand", "void(0);"); // needed. See bug 371900
|
||||
// needed. See bug 371900
|
||||
key.setAttribute("oncommand", "void(0);");
|
||||
key.addEventListener("command", () => {
|
||||
this.selectTool(toolId).then(() => this.fireCustomKey(toolId));
|
||||
}, true);
|
||||
@ -642,7 +652,8 @@ Toolbox.prototype = {
|
||||
|
||||
key.setAttribute("key", toolboxStrings("browserConsoleCmd.commandkey"));
|
||||
key.setAttribute("modifiers", "accel,shift");
|
||||
key.setAttribute("oncommand", "void(0)"); // needed. See bug 371900
|
||||
// needed. See bug 371900
|
||||
key.setAttribute("oncommand", "void(0)");
|
||||
key.addEventListener("command", () => {
|
||||
HUDService.toggleBrowserConsole();
|
||||
}, true);
|
||||
@ -651,9 +662,10 @@ Toolbox.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle any custom key events. Returns true if there was a custom key binding run
|
||||
* @param {string} toolId
|
||||
* Which tool to run the command on (skip if not current)
|
||||
* Handle any custom key events. Returns true if there was a custom key
|
||||
* binding run.
|
||||
* @param {string} toolId Which tool to run the command on (skip if not
|
||||
* current)
|
||||
*/
|
||||
fireCustomKey: function(toolId) {
|
||||
let toolDefinition = gDevTools.getToolDefinition(toolId);
|
||||
@ -680,6 +692,32 @@ Toolbox.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bottom-type host can be minimized, add a button for this.
|
||||
if (this.hostType == Toolbox.HostType.BOTTOM) {
|
||||
let minimizeBtn = this.doc.createElement("toolbarbutton");
|
||||
minimizeBtn.id = "toolbox-dock-bottom-minimize";
|
||||
minimizeBtn.className = "maximized";
|
||||
minimizeBtn.setAttribute("tooltiptext",
|
||||
toolboxStrings("toolboxDockButtons.bottom.minimize"));
|
||||
// Calculate the height to which the host should be minimized so the
|
||||
// tabbar is still visible.
|
||||
let toolbarHeight = this.doc.querySelector(".devtools-tabbar")
|
||||
.getBoxQuads({box: "content"})[0]
|
||||
.bounds.height;
|
||||
minimizeBtn.addEventListener("command", () => {
|
||||
this._host.toggleMinimizeMode(toolbarHeight);
|
||||
});
|
||||
dockBox.appendChild(minimizeBtn);
|
||||
|
||||
// Update the label and icon when the state changes.
|
||||
this._host.on("minimized", this._onBottomHostMinimized);
|
||||
this._host.on("maximized", this._onBottomHostMaximized);
|
||||
// Maximize again when a tool gets selected.
|
||||
this.on("before-select", this._onToolSelectWhileMinimized);
|
||||
// Maximize and stop listening before the host type changes.
|
||||
this.once("host-will-change", this._onBottomHostWillChange);
|
||||
}
|
||||
|
||||
if (this.hostType == Toolbox.HostType.WINDOW) {
|
||||
this.closeButton.setAttribute("hidden", "true");
|
||||
} else {
|
||||
@ -709,6 +747,32 @@ Toolbox.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_onBottomHostMinimized: function() {
|
||||
let btn = this.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
btn.className = "minimized";
|
||||
btn.setAttribute("tooltiptext",
|
||||
toolboxStrings("toolboxDockButtons.bottom.maximize"));
|
||||
},
|
||||
|
||||
_onBottomHostMaximized: function() {
|
||||
let btn = this.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
btn.className = "maximized";
|
||||
btn.setAttribute("tooltiptext",
|
||||
toolboxStrings("toolboxDockButtons.bottom.minimize"));
|
||||
},
|
||||
|
||||
_onToolSelectWhileMinimized: function() {
|
||||
this._host.maximize();
|
||||
},
|
||||
|
||||
_onBottomHostWillChange: function() {
|
||||
this._host.maximize();
|
||||
|
||||
this._host.off("minimized", this._onBottomHostMinimized);
|
||||
this._host.off("maximized", this._onBottomHostMaximized);
|
||||
this.off("before-select", this._onToolSelectWhileMinimized);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add tabs to the toolbox UI for registered tools
|
||||
*/
|
||||
@ -835,8 +899,9 @@ Toolbox.prototype = {
|
||||
button: button,
|
||||
label: button.getAttribute("tooltiptext"),
|
||||
visibilityswitch: "devtools." + options.id + ".enabled",
|
||||
isTargetSupported: options.isTargetSupported ? options.isTargetSupported
|
||||
: target => target.isLocalTab
|
||||
isTargetSupported: options.isTargetSupported
|
||||
? options.isTargetSupported
|
||||
: target => target.isLocalTab
|
||||
};
|
||||
}).filter(button=>button);
|
||||
},
|
||||
@ -847,7 +912,7 @@ Toolbox.prototype = {
|
||||
*/
|
||||
setToolboxButtonsVisibility: function() {
|
||||
this.toolboxButtons.forEach(buttonSpec => {
|
||||
let { visibilityswitch, id, button, isTargetSupported } = buttonSpec;
|
||||
let { visibilityswitch, button, isTargetSupported } = buttonSpec;
|
||||
let on = true;
|
||||
try {
|
||||
on = Services.prefs.getBoolPref(visibilityswitch);
|
||||
@ -939,7 +1004,7 @@ Toolbox.prototype = {
|
||||
|
||||
if (toolDefinition.label && !toolDefinition.iconOnly) {
|
||||
let label = this.doc.createElement("label");
|
||||
label.setAttribute("value", toolDefinition.label)
|
||||
label.setAttribute("value", toolDefinition.label);
|
||||
label.setAttribute("crop", "end");
|
||||
label.setAttribute("flex", "1");
|
||||
radio.appendChild(label);
|
||||
@ -1018,7 +1083,7 @@ Toolbox.prototype = {
|
||||
|
||||
let definition = gDevTools.getToolDefinition(id);
|
||||
if (!definition) {
|
||||
deferred.reject(new Error("no such tool id "+id));
|
||||
deferred.reject(new Error("no such tool id " + id));
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
@ -1112,7 +1177,7 @@ Toolbox.prototype = {
|
||||
let callback = () => {
|
||||
iframe.removeEventListener("DOMContentLoaded", callback);
|
||||
onLoad();
|
||||
}
|
||||
};
|
||||
iframe.addEventListener("DOMContentLoaded", callback);
|
||||
}
|
||||
|
||||
@ -1126,6 +1191,8 @@ Toolbox.prototype = {
|
||||
* The id of the tool to switch to
|
||||
*/
|
||||
selectTool: function(id) {
|
||||
this.emit("before-select", id);
|
||||
|
||||
let selected = this.doc.querySelector(".devtools-tab[selected]");
|
||||
if (selected) {
|
||||
selected.removeAttribute("selected");
|
||||
@ -1364,7 +1431,7 @@ Toolbox.prototype = {
|
||||
this._host.setTitle(title);
|
||||
},
|
||||
|
||||
_listFrames: function (event) {
|
||||
_listFrames: function(event) {
|
||||
if (!this._target.activeTab || !this._target.activeTab.traits.frames) {
|
||||
// We are not targetting a regular TabActor
|
||||
// it can be either an addon or browser toolbox actor
|
||||
@ -1379,7 +1446,7 @@ Toolbox.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
selectFrame: function (event) {
|
||||
selectFrame: function(event) {
|
||||
let windowId = event.target.getAttribute("data-window-id");
|
||||
let packet = {
|
||||
to: this._target.form.actor,
|
||||
@ -1390,7 +1457,7 @@ Toolbox.prototype = {
|
||||
// Wait for frameUpdate event to update the UI
|
||||
},
|
||||
|
||||
_updateFrames: function (event, data) {
|
||||
_updateFrames: function(event, data) {
|
||||
if (!Services.prefs.getBoolPref("devtools.command-button-frames.enabled")) {
|
||||
return;
|
||||
}
|
||||
@ -1420,7 +1487,7 @@ Toolbox.prototype = {
|
||||
menu.removeAttribute("checked");
|
||||
}
|
||||
// Uncheck the previously selected frame
|
||||
let selected = menu.querySelector("menuitem[checked=true]")
|
||||
let selected = menu.querySelector("menuitem[checked=true]");
|
||||
if (selected) {
|
||||
selected.removeAttribute("checked");
|
||||
}
|
||||
@ -1458,8 +1525,8 @@ Toolbox.prototype = {
|
||||
* Create a host object based on the given host type.
|
||||
*
|
||||
* Warning: some hosts require that the toolbox target provides a reference to
|
||||
* the attached tab. Not all Targets have a tab property - make sure you correctly
|
||||
* mix and match hosts and targets.
|
||||
* the attached tab. Not all Targets have a tab property - make sure you
|
||||
* correctly mix and match hosts and targets.
|
||||
*
|
||||
* @param {string} hostType
|
||||
* The host type of the new host object
|
||||
@ -1490,6 +1557,8 @@ Toolbox.prototype = {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.emit("host-will-change", hostType);
|
||||
|
||||
let newHost = this._createHost(hostType);
|
||||
return newHost.create().then(iframe => {
|
||||
// change toolbox document's parent to the new host
|
||||
@ -1670,32 +1739,72 @@ Toolbox.prototype = {
|
||||
screenManager.primaryScreen.GetRect({}, {}, width, height);
|
||||
let dims = width.value + "x" + height.value;
|
||||
|
||||
if (width.value < 800 || height.value < 600) return 0;
|
||||
if (dims === "800x600") return 1;
|
||||
if (dims === "1024x768") return 2;
|
||||
if (dims === "1280x800") return 3;
|
||||
if (dims === "1280x1024") return 4;
|
||||
if (dims === "1366x768") return 5;
|
||||
if (dims === "1440x900") return 6;
|
||||
if (dims === "1920x1080") return 7;
|
||||
if (dims === "2560×1440") return 8;
|
||||
if (dims === "2560×1600") return 9;
|
||||
if (dims === "2880x1800") return 10;
|
||||
if (width.value > 2880 || height.value > 1800) return 12;
|
||||
if (width.value < 800 || height.value < 600) {
|
||||
return 0;
|
||||
}
|
||||
if (dims === "800x600") {
|
||||
return 1;
|
||||
}
|
||||
if (dims === "1024x768") {
|
||||
return 2;
|
||||
}
|
||||
if (dims === "1280x800") {
|
||||
return 3;
|
||||
}
|
||||
if (dims === "1280x1024") {
|
||||
return 4;
|
||||
}
|
||||
if (dims === "1366x768") {
|
||||
return 5;
|
||||
}
|
||||
if (dims === "1440x900") {
|
||||
return 6;
|
||||
}
|
||||
if (dims === "1920x1080") {
|
||||
return 7;
|
||||
}
|
||||
if (dims === "2560×1440") {
|
||||
return 8;
|
||||
}
|
||||
if (dims === "2560×1600") {
|
||||
return 9;
|
||||
}
|
||||
if (dims === "2880x1800") {
|
||||
return 10;
|
||||
}
|
||||
if (width.value > 2880 || height.value > 1800) {
|
||||
return 12;
|
||||
}
|
||||
|
||||
return 11; // Other dimension such as a VM.
|
||||
// Other dimension such as a VM.
|
||||
return 11;
|
||||
},
|
||||
|
||||
_getOsCpu: function() {
|
||||
if (oscpu.includes("NT 5.1") || oscpu.includes("NT 5.2")) return 0;
|
||||
if (oscpu.includes("NT 6.0")) return 1;
|
||||
if (oscpu.includes("NT 6.1")) return 2;
|
||||
if (oscpu.includes("NT 6.2")) return 3;
|
||||
if (oscpu.includes("NT 6.3")) return 4;
|
||||
if (oscpu.includes("OS X")) return 5;
|
||||
if (oscpu.includes("Linux")) return 6;
|
||||
if (oscpu.includes("NT 5.1") || oscpu.includes("NT 5.2")) {
|
||||
return 0;
|
||||
}
|
||||
if (oscpu.includes("NT 6.0")) {
|
||||
return 1;
|
||||
}
|
||||
if (oscpu.includes("NT 6.1")) {
|
||||
return 2;
|
||||
}
|
||||
if (oscpu.includes("NT 6.2")) {
|
||||
return 3;
|
||||
}
|
||||
if (oscpu.includes("NT 6.3")) {
|
||||
return 4;
|
||||
}
|
||||
if (oscpu.includes("OS X")) {
|
||||
return 5;
|
||||
}
|
||||
if (oscpu.includes("Linux")) {
|
||||
return 6;
|
||||
}
|
||||
|
||||
return 12; // Other OS.
|
||||
// Other OS.
|
||||
return 12;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1869,8 +1978,8 @@ Toolbox.prototype = {
|
||||
*/
|
||||
_updateTextboxMenuItems: function() {
|
||||
let window = this.doc.defaultView;
|
||||
['cmd_undo', 'cmd_delete', 'cmd_cut',
|
||||
'cmd_copy', 'cmd_paste','cmd_selectAll'].forEach(window.goUpdateCommand);
|
||||
["cmd_undo", "cmd_delete", "cmd_cut",
|
||||
"cmd_copy", "cmd_paste", "cmd_selectAll"].forEach(window.goUpdateCommand);
|
||||
},
|
||||
|
||||
getPerformanceActorsConnection: function() {
|
||||
@ -1920,7 +2029,7 @@ Toolbox.prototype = {
|
||||
* Opens source in style editor. Falls back to plain "view-source:".
|
||||
* @see browser/devtools/shared/source-utils.js
|
||||
*/
|
||||
viewSourceInStyleEditor: function (sourceURL, sourceLine) {
|
||||
viewSourceInStyleEditor: function(sourceURL, sourceLine) {
|
||||
return sourceUtils.viewSourceInStyleEditor(this, sourceURL, sourceLine);
|
||||
},
|
||||
|
||||
@ -1928,7 +2037,7 @@ Toolbox.prototype = {
|
||||
* Opens source in debugger. Falls back to plain "view-source:".
|
||||
* @see browser/devtools/shared/source-utils.js
|
||||
*/
|
||||
viewSourceInDebugger: function (sourceURL, sourceLine) {
|
||||
viewSourceInDebugger: function(sourceURL, sourceLine) {
|
||||
return sourceUtils.viewSourceInDebugger(this, sourceURL, sourceLine);
|
||||
},
|
||||
|
||||
@ -1941,7 +2050,7 @@ Toolbox.prototype = {
|
||||
*
|
||||
* @see browser/devtools/shared/source-utils.js
|
||||
*/
|
||||
viewSourceInScratchpad: function (sourceURL, sourceLine) {
|
||||
viewSourceInScratchpad: function(sourceURL, sourceLine) {
|
||||
return sourceUtils.viewSourceInScratchpad(sourceURL, sourceLine);
|
||||
},
|
||||
|
||||
@ -1949,7 +2058,7 @@ Toolbox.prototype = {
|
||||
* Opens source in plain "view-source:".
|
||||
* @see browser/devtools/shared/source-utils.js
|
||||
*/
|
||||
viewSource: function (sourceURL, sourceLine) {
|
||||
viewSource: function(sourceURL, sourceLine) {
|
||||
return sourceUtils.viewSource(this, sourceURL, sourceLine);
|
||||
},
|
||||
};
|
||||
|
@ -437,6 +437,7 @@
|
||||
@RESPATH@/components/nsUpdateTimerManager.js
|
||||
@RESPATH@/components/addoncompat.manifest
|
||||
@RESPATH@/components/multiprocessShims.js
|
||||
@RESPATH@/components/defaultShims.js
|
||||
@RESPATH@/components/remoteTagService.js
|
||||
@RESPATH@/components/pluginGlue.manifest
|
||||
@RESPATH@/components/ProcessSingleton.manifest
|
||||
|
@ -6,6 +6,20 @@ toolboxDockButtons.bottom.tooltip=Dock to bottom of browser window
|
||||
toolboxDockButtons.side.tooltip=Dock to side of browser window
|
||||
toolboxDockButtons.window.tooltip=Show in separate window
|
||||
|
||||
# LOCALIZATION NOTE (toolboxDockButtons.bottom.minimize): This string is shown
|
||||
# as a tooltip that appears in the toolbox when it is in "bottom host" mode and
|
||||
# when hovering over the minimize button in the toolbar. When clicked, the
|
||||
# button minimizes the toolbox so that just the toolbar is visible at the
|
||||
# bottom.
|
||||
toolboxDockButtons.bottom.minimize=Minimize the toolbox
|
||||
|
||||
# LOCALIZATION NOTE (toolboxDockButtons.bottom.maximize): This string is shown
|
||||
# as a tooltip that appears in the toolbox when it is in "bottom host" mode and
|
||||
# when hovering over the maximize button in the toolbar. When clicked, the
|
||||
# button maximizes the toolbox again (if it had been minimized before) so that
|
||||
# the whole toolbox is visible again.
|
||||
toolboxDockButtons.bottom.maximize=Maximize the toolbox
|
||||
|
||||
# LOCALIZATION NOTE (toolboxToggleButton.errors): Semi-colon list of plural
|
||||
# forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
|
@ -377,6 +377,8 @@ browser.jar:
|
||||
skin/classic/browser/devtools/toggle-tools.png (../shared/devtools/images/toggle-tools.png)
|
||||
skin/classic/browser/devtools/toggle-tools@2x.png (../shared/devtools/images/toggle-tools@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom@2x.png (../shared/devtools/images/dock-bottom@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom-minimize@2x.png (../shared/devtools/images/dock-bottom-minimize@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom-maximize@2x.png (../shared/devtools/images/dock-bottom-maximize@2x.png)
|
||||
skin/classic/browser/devtools/dock-side@2x.png (../shared/devtools/images/dock-side@2x.png)
|
||||
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
|
||||
|
@ -495,6 +495,8 @@ browser.jar:
|
||||
skin/classic/browser/devtools/toggle-tools.png (../shared/devtools/images/toggle-tools.png)
|
||||
skin/classic/browser/devtools/toggle-tools@2x.png (../shared/devtools/images/toggle-tools@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom@2x.png (../shared/devtools/images/dock-bottom@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom-minimize@2x.png (../shared/devtools/images/dock-bottom-minimize@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom-maximize@2x.png (../shared/devtools/images/dock-bottom-maximize@2x.png)
|
||||
skin/classic/browser/devtools/dock-side@2x.png (../shared/devtools/images/dock-side@2x.png)
|
||||
* skin/classic/browser/devtools/inspector.css (../shared/devtools/inspector.css)
|
||||
skin/classic/browser/devtools/profiler-stopwatch.svg (../shared/devtools/images/profiler-stopwatch.svg)
|
||||
|
@ -22,6 +22,11 @@
|
||||
%endif
|
||||
}
|
||||
|
||||
/* Bottom-docked toolbox minimize transition */
|
||||
.devtools-toolbox-bottom-iframe {
|
||||
transition: margin-bottom .1s;
|
||||
}
|
||||
|
||||
/* Splitters */
|
||||
.devtools-horizontal-splitter {
|
||||
-moz-appearance: none;
|
||||
@ -48,6 +53,11 @@
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
.devtools-horizontal-splitter.disabled,
|
||||
.devtools-side-splitter.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.devtools-toolbox-side-iframe {
|
||||
min-width: 465px;
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -24,6 +24,6 @@
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#pseudo-class-shape" id="pseudo-class" color="#edf0f1"/>
|
||||
<use xlink:href="#pseudo-class-shape" id="pseudo-class" color="#babec3"/>
|
||||
<use xlink:href="#pseudo-class-shape" id="pseudo-class-checked" color="#3089C9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@ -657,6 +657,14 @@
|
||||
background-image: url("chrome://browser/skin/devtools/undock@2x.png");
|
||||
}
|
||||
|
||||
#toolbox-dock-bottom-minimize > image {
|
||||
background-image: url("chrome://browser/skin/devtools/dock-bottom-minimize@2x.png");
|
||||
}
|
||||
|
||||
#toolbox-dock-bottom-minimize.minimized > image {
|
||||
background-image: url("chrome://browser/skin/devtools/dock-bottom-maximize@2x.png");
|
||||
}
|
||||
|
||||
#toolbox-dock-window,
|
||||
#toolbox-dock-bottom,
|
||||
#toolbox-dock-side {
|
||||
|
@ -467,6 +467,8 @@ browser.jar:
|
||||
skin/classic/browser/devtools/toggle-tools.png (../shared/devtools/images/toggle-tools.png)
|
||||
skin/classic/browser/devtools/toggle-tools@2x.png (../shared/devtools/images/toggle-tools@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom@2x.png (../shared/devtools/images/dock-bottom@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom-minimize@2x.png (../shared/devtools/images/dock-bottom-minimize@2x.png)
|
||||
skin/classic/browser/devtools/dock-bottom-maximize@2x.png (../shared/devtools/images/dock-bottom-maximize@2x.png)
|
||||
skin/classic/browser/devtools/dock-side@2x.png (../shared/devtools/images/dock-side@2x.png)
|
||||
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
|
||||
|
@ -49,14 +49,16 @@ this.mozIApplication = function(aApp) {
|
||||
|
||||
mozIApplication.prototype = {
|
||||
hasPermission: function(aPermission) {
|
||||
let uri = Services.io.newURI(this.origin, null, null);
|
||||
let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
|
||||
.getService(Ci.nsIScriptSecurityManager);
|
||||
// This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
|
||||
// specific permission. It is not checking if browsers inside |aApp| have such
|
||||
// permission.
|
||||
let principal = secMan.getAppCodebasePrincipal(uri, this.localId,
|
||||
/*mozbrowser*/false);
|
||||
let principal = this.principal;
|
||||
if (this.installerIsBrowser) {
|
||||
let uri = Services.io.newURI(this.origin, null, null);
|
||||
principal =
|
||||
Services.scriptSecurityManager.getAppCodebasePrincipal(uri, this.localId,
|
||||
/*mozbrowser*/false);
|
||||
}
|
||||
let perm = Services.perms.testExactPermissionFromPrincipal(principal,
|
||||
aPermission);
|
||||
return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
@ -71,6 +73,26 @@ mozIApplication.prototype = {
|
||||
return this.widgetPages.find(equalCriterion) !== undefined;
|
||||
},
|
||||
|
||||
get principal() {
|
||||
if (this._principal) {
|
||||
return this._principal;
|
||||
}
|
||||
|
||||
this._principal = null;
|
||||
|
||||
try {
|
||||
this._principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
|
||||
Services.io.newURI(this.origin, null, null),
|
||||
this.localId,
|
||||
this.installerIsBrowser
|
||||
);
|
||||
} catch(e) {
|
||||
dump("Could not create app principal " + e + "\n");
|
||||
}
|
||||
|
||||
return this._principal;
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Ci.mozIApplication) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
|
65
dom/apps/tests/unit/test_moziapplication.js
Normal file
65
dom/apps/tests/unit/test_moziapplication.js
Normal file
@ -0,0 +1,65 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource:///modules/AppsUtils.jsm");
|
||||
|
||||
add_test(() => {
|
||||
let app = {
|
||||
name: "TestApp",
|
||||
csp: "aCsp",
|
||||
installOrigin: "http://installorigin.com",
|
||||
origin: "http://www.example.com",
|
||||
installTime: Date.now(),
|
||||
manifestURL: "http://www.example.com/manifest.webapp",
|
||||
appStatus: Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED,
|
||||
removable: false,
|
||||
id: 123,
|
||||
localId: 123,
|
||||
basePath: "/",
|
||||
progress: 1.0,
|
||||
installState: "installed",
|
||||
downloadAvailable: false,
|
||||
downloading: false,
|
||||
lastUpdateCheck: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
etag: "aEtag",
|
||||
packageEtag: "aPackageEtag",
|
||||
manifestHash: "aManifestHash",
|
||||
packageHash: "aPackageHash",
|
||||
staged: false,
|
||||
installerAppId: 345,
|
||||
installerIsBrowser: false,
|
||||
storeId: "aStoreId",
|
||||
storeVersion: 1,
|
||||
role: "aRole",
|
||||
redirects: "aRedirects",
|
||||
kind: "aKind",
|
||||
enabled: true,
|
||||
sideloaded: false
|
||||
};
|
||||
|
||||
let mozapp = new mozIApplication(app);
|
||||
|
||||
Object.keys(app).forEach((key) => {
|
||||
if (key == "principal") {
|
||||
return;
|
||||
}
|
||||
Assert.equal(app[key], mozapp[key],
|
||||
"app[" + key + "] should be equal to mozapp[" + key + "]");
|
||||
});
|
||||
|
||||
Assert.ok(mozapp.principal, "app principal should exist");
|
||||
let expectedPrincipalOrigin = app.origin + "!appId=" + app.localId;
|
||||
Assert.equal(mozapp.principal.origin, expectedPrincipalOrigin,
|
||||
"app principal origin ok");
|
||||
Assert.equal(mozapp.principal.appId, app.localId, "app principal appId ok");
|
||||
Assert.equal(mozapp.principal.isInBrowserElement, app.installerIsBrowser,
|
||||
"app principal isInBrowserElement ok");
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
@ -6,3 +6,4 @@ tail =
|
||||
[test_inter_app_comm_service.js]
|
||||
[test_manifestSanitizer.js]
|
||||
[test_manifestHelper.js]
|
||||
[test_moziapplication.js]
|
||||
|
@ -253,6 +253,7 @@ bool nsContentUtils::sIsResourceTimingEnabled = false;
|
||||
bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
|
||||
bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
|
||||
bool nsContentUtils::sEncodeDecodeURLHash = false;
|
||||
bool nsContentUtils::sPrivacyResistFingerprinting = false;
|
||||
|
||||
uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
|
||||
|
||||
@ -533,6 +534,9 @@ nsContentUtils::Init()
|
||||
Preferences::AddBoolVarCache(&sEncodeDecodeURLHash,
|
||||
"dom.url.encode_decode_hash", false);
|
||||
|
||||
Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
|
||||
"privacy.resistFingerprinting", false);
|
||||
|
||||
Preferences::AddUintVarCache(&sHandlingInputTimeout,
|
||||
"dom.event.handling-user-input-time-limit",
|
||||
1000);
|
||||
@ -1989,6 +1993,16 @@ nsContentUtils::IsCallerChrome()
|
||||
return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
|
||||
}
|
||||
|
||||
bool
|
||||
nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell)
|
||||
{
|
||||
if (!aDocShell) {
|
||||
return false;
|
||||
}
|
||||
bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
|
||||
return !isChrome && sPrivacyResistFingerprinting;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace workers {
|
||||
@ -7773,3 +7787,16 @@ nsContentUtils::FirePageShowEvent(nsIDocShellTreeItem* aItem,
|
||||
doc->OnPageShow(true, aChromeEventHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<nsPIWindowRoot>
|
||||
nsContentUtils::GetWindowRoot(nsIDocument* aDoc)
|
||||
{
|
||||
if (aDoc) {
|
||||
nsPIDOMWindow* win = aDoc->GetWindow();
|
||||
if (win) {
|
||||
return win->GetTopWindowRoot();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ class nsViewportInfo;
|
||||
class nsWrapperCache;
|
||||
class nsAttrValue;
|
||||
class nsITransferable;
|
||||
class nsPIWindowRoot;
|
||||
|
||||
struct JSPropertyDescriptor;
|
||||
struct JSRuntime;
|
||||
@ -198,6 +199,9 @@ public:
|
||||
JS::Handle<jsid> aId,
|
||||
JS::MutableHandle<JSPropertyDescriptor> aDesc);
|
||||
|
||||
// Check whether we should avoid leaking distinguishing information to JS/CSS.
|
||||
static bool ShouldResistFingerprinting(nsIDocShell* aDocShell);
|
||||
|
||||
/**
|
||||
* Returns the parent node of aChild crossing document boundaries.
|
||||
* Uses the parent node in the composed document.
|
||||
@ -1912,6 +1916,16 @@ public:
|
||||
return sEncodeDecodeURLHash;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the browser should attempt to prevent content scripts
|
||||
* from collecting distinctive information about the browser that could
|
||||
* be used to "fingerprint" and track the user across websites.
|
||||
*/
|
||||
static bool ResistFingerprinting()
|
||||
{
|
||||
return sPrivacyResistFingerprinting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the doc tree branch which contains aDoc contains any
|
||||
* plugins which we don't control event dispatch for, i.e. do any plugins
|
||||
@ -2350,6 +2364,8 @@ public:
|
||||
static void FirePageHideEvent(nsIDocShellTreeItem* aItem,
|
||||
mozilla::dom::EventTarget* aChromeEventHandler);
|
||||
|
||||
static already_AddRefed<nsPIWindowRoot> GetWindowRoot(nsIDocument* aDoc);
|
||||
|
||||
private:
|
||||
static bool InitializeEventTable();
|
||||
|
||||
@ -2450,6 +2466,7 @@ private:
|
||||
static bool sIsUserTimingLoggingEnabled;
|
||||
static bool sIsExperimentalAutocompleteEnabled;
|
||||
static bool sEncodeDecodeURLHash;
|
||||
static bool sPrivacyResistFingerprinting;
|
||||
|
||||
static nsHtml5StringParser* sHTMLFragmentParser;
|
||||
static nsIParser* sXMLFragmentParser;
|
||||
|
@ -70,7 +70,6 @@
|
||||
#endif
|
||||
|
||||
#include "Layers.h"
|
||||
#include "mozilla/layers/ShadowLayers.h"
|
||||
#include "gfxPrefs.h"
|
||||
|
||||
#include "mozilla/dom/Element.h"
|
||||
@ -82,6 +81,8 @@
|
||||
#include "mozilla/dom/PermissionMessageUtils.h"
|
||||
#include "mozilla/dom/quota/PersistenceType.h"
|
||||
#include "mozilla/dom/quota/QuotaManager.h"
|
||||
#include "mozilla/layers/FrameUniformityData.h"
|
||||
#include "mozilla/layers/ShadowLayers.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsViewportInfo.h"
|
||||
#include "nsIFormControl.h"
|
||||
@ -3716,6 +3717,27 @@ nsDOMWindowUtils::SetChromeMargin(int32_t aTop,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::GetFrameUniformityTestData(JSContext* aContext,
|
||||
JS::MutableHandleValue aOutFrameUniformity)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
|
||||
nsIWidget* widget = GetWidget();
|
||||
if (!widget) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
nsRefPtr<LayerManager> manager = widget->GetLayerManager();
|
||||
if (!manager) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
FrameUniformityData outData;
|
||||
manager->GetFrameUniformity(&outData);
|
||||
outData.ToJS(aOutFrameUniformity, aContext);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::XpconnectArgument(nsIDOMWindowUtils* aThis)
|
||||
{
|
||||
|
@ -5003,6 +5003,12 @@ nsGlobalWindow::GetOuterSize(ErrorResult& aError)
|
||||
{
|
||||
MOZ_ASSERT(IsOuterWindow());
|
||||
|
||||
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
|
||||
CSSIntSize size;
|
||||
aError = GetInnerSize(size);
|
||||
return nsIntSize(size.width, size.height);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
||||
if (!treeOwnerAsWin) {
|
||||
aError.Throw(NS_ERROR_FAILURE);
|
||||
@ -5167,6 +5173,11 @@ nsGlobalWindow::GetScreenXY(ErrorResult& aError)
|
||||
{
|
||||
MOZ_ASSERT(IsOuterWindow());
|
||||
|
||||
// When resisting fingerprinting, always return (0,0)
|
||||
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
|
||||
return nsIntPoint(0, 0);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
|
||||
if (!treeOwnerAsWin) {
|
||||
aError.Throw(NS_ERROR_FAILURE);
|
||||
@ -5240,6 +5251,11 @@ nsGlobalWindow::GetMozInnerScreenX(ErrorResult& aError)
|
||||
{
|
||||
FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenX, (aError), aError, 0);
|
||||
|
||||
// When resisting fingerprinting, always return 0.
|
||||
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
nsRect r = GetInnerScreenRect();
|
||||
return nsPresContext::AppUnitsToFloatCSSPixels(r.x);
|
||||
}
|
||||
@ -5258,6 +5274,11 @@ nsGlobalWindow::GetMozInnerScreenY(ErrorResult& aError)
|
||||
{
|
||||
FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenY, (aError), aError, 0);
|
||||
|
||||
// Return 0 to prevent fingerprinting.
|
||||
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
nsRect r = GetInnerScreenRect();
|
||||
return nsPresContext::AppUnitsToFloatCSSPixels(r.y);
|
||||
}
|
||||
@ -5286,6 +5307,10 @@ nsGlobalWindow::GetDevicePixelRatio(ErrorResult& aError)
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
return float(nsPresContext::AppUnitsPerCSSPixel())/
|
||||
presContext->AppUnitsPerDevPixel();
|
||||
}
|
||||
|
@ -1059,6 +1059,8 @@ public:
|
||||
bool aShowDialog, mozilla::ErrorResult& aError);
|
||||
uint64_t GetMozPaintCount(mozilla::ErrorResult& aError);
|
||||
|
||||
bool ShouldResistFingerprinting();
|
||||
|
||||
mozilla::dom::MozSelfSupport* GetMozSelfSupport(mozilla::ErrorResult& aError);
|
||||
|
||||
already_AddRefed<nsIDOMWindow> OpenDialog(JSContext* aCx,
|
||||
|
@ -9,14 +9,21 @@
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "nsWeakReference.h"
|
||||
|
||||
class nsPIDOMWindow;
|
||||
class nsIControllers;
|
||||
class nsIController;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class TabParent;
|
||||
}
|
||||
}
|
||||
|
||||
#define NS_IWINDOWROOT_IID \
|
||||
{ 0x728a2682, 0x55c0, 0x4860, \
|
||||
{ 0x82, 0x6b, 0x0c, 0x30, 0x0a, 0xac, 0xaa, 0x60 } }
|
||||
{ 0x238edca0, 0xb30d, 0x46d3, \
|
||||
{ 0xb2, 0x6a, 0x17, 0xb6, 0x21, 0x28, 0x89, 0x7e } }
|
||||
|
||||
class nsPIWindowRoot : public mozilla::dom::EventTarget
|
||||
{
|
||||
@ -38,6 +45,15 @@ public:
|
||||
|
||||
virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) = 0;
|
||||
virtual mozilla::dom::EventTarget* GetParentTarget() = 0;
|
||||
|
||||
// Stores a weak reference to the browser.
|
||||
virtual void AddBrowser(mozilla::dom::TabParent* aBrowser) = 0;
|
||||
virtual void RemoveBrowser(mozilla::dom::TabParent* aBrowser) = 0;
|
||||
|
||||
typedef void (*BrowserEnumerator)(mozilla::dom::TabParent* aTab, void* aArg);
|
||||
|
||||
// Enumerate all stored browsers that for which the weak reference is valid.
|
||||
virtual void EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg) = 0;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsPIWindowRoot, NS_IWINDOWROOT_IID)
|
||||
|
@ -68,6 +68,11 @@ NS_IMPL_RELEASE_INHERITED(nsScreen, DOMEventTargetHelper)
|
||||
int32_t
|
||||
nsScreen::GetPixelDepth(ErrorResult& aRv)
|
||||
{
|
||||
// Return 24 to prevent fingerprinting.
|
||||
if (ShouldResistFingerprinting()) {
|
||||
return 24;
|
||||
}
|
||||
|
||||
nsDeviceContext* context = GetDeviceContext();
|
||||
|
||||
if (!context) {
|
||||
@ -111,6 +116,11 @@ nsScreen::GetDeviceContext()
|
||||
nsresult
|
||||
nsScreen::GetRect(nsRect& aRect)
|
||||
{
|
||||
// Return window inner rect to prevent fingerprinting.
|
||||
if (ShouldResistFingerprinting()) {
|
||||
return GetWindowInnerRect(aRect);
|
||||
}
|
||||
|
||||
nsDeviceContext *context = GetDeviceContext();
|
||||
|
||||
if (!context) {
|
||||
@ -130,6 +140,11 @@ nsScreen::GetRect(nsRect& aRect)
|
||||
nsresult
|
||||
nsScreen::GetAvailRect(nsRect& aRect)
|
||||
{
|
||||
// Return window inner rect to prevent fingerprinting.
|
||||
if (ShouldResistFingerprinting()) {
|
||||
return GetWindowInnerRect(aRect);
|
||||
}
|
||||
|
||||
nsDeviceContext *context = GetDeviceContext();
|
||||
|
||||
if (!context) {
|
||||
@ -166,22 +181,26 @@ nsScreen::Notify(const hal::ScreenConfiguration& aConfiguration)
|
||||
void
|
||||
nsScreen::GetMozOrientation(nsString& aOrientation)
|
||||
{
|
||||
switch (mOrientation) {
|
||||
case eScreenOrientation_PortraitPrimary:
|
||||
aOrientation.AssignLiteral("portrait-primary");
|
||||
break;
|
||||
case eScreenOrientation_PortraitSecondary:
|
||||
aOrientation.AssignLiteral("portrait-secondary");
|
||||
break;
|
||||
case eScreenOrientation_LandscapePrimary:
|
||||
if (ShouldResistFingerprinting()) {
|
||||
aOrientation.AssignLiteral("landscape-primary");
|
||||
break;
|
||||
case eScreenOrientation_LandscapeSecondary:
|
||||
aOrientation.AssignLiteral("landscape-secondary");
|
||||
break;
|
||||
case eScreenOrientation_None:
|
||||
default:
|
||||
MOZ_CRASH("Unacceptable mOrientation value");
|
||||
} else {
|
||||
switch (mOrientation) {
|
||||
case eScreenOrientation_PortraitPrimary:
|
||||
aOrientation.AssignLiteral("portrait-primary");
|
||||
break;
|
||||
case eScreenOrientation_PortraitSecondary:
|
||||
aOrientation.AssignLiteral("portrait-secondary");
|
||||
break;
|
||||
case eScreenOrientation_LandscapePrimary:
|
||||
aOrientation.AssignLiteral("landscape-primary");
|
||||
break;
|
||||
case eScreenOrientation_LandscapeSecondary:
|
||||
aOrientation.AssignLiteral("landscape-secondary");
|
||||
break;
|
||||
case eScreenOrientation_None:
|
||||
default:
|
||||
MOZ_CRASH("Unacceptable mOrientation value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,3 +392,27 @@ nsScreen::FullScreenEventListener::HandleEvent(nsIDOMEvent* aEvent)
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsScreen::GetWindowInnerRect(nsRect& aRect)
|
||||
{
|
||||
aRect.x = 0;
|
||||
aRect.y = 0;
|
||||
nsCOMPtr<nsIDOMWindow> win = GetOwner();
|
||||
if (!win) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsresult rv = win->GetInnerWidth(&aRect.width);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return win->GetInnerHeight(&aRect.height);
|
||||
}
|
||||
|
||||
bool nsScreen::ShouldResistFingerprinting() const
|
||||
{
|
||||
bool resist = false;
|
||||
nsCOMPtr<nsPIDOMWindow> owner = GetOwner();
|
||||
if (owner) {
|
||||
resist = nsContentUtils::ShouldResistFingerprinting(owner->GetDocShell());
|
||||
}
|
||||
return resist;
|
||||
}
|
||||
|
@ -131,6 +131,7 @@ protected:
|
||||
nsDeviceContext* GetDeviceContext();
|
||||
nsresult GetRect(nsRect& aRect);
|
||||
nsresult GetAvailRect(nsRect& aRect);
|
||||
nsresult GetWindowInnerRect(nsRect& aRect);
|
||||
|
||||
mozilla::dom::ScreenOrientation mOrientation;
|
||||
|
||||
@ -158,6 +159,8 @@ private:
|
||||
|
||||
bool IsDeviceSizePageSize();
|
||||
|
||||
bool ShouldResistFingerprinting() const;
|
||||
|
||||
nsRefPtr<FullScreenEventListener> mEventListener;
|
||||
};
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "nsIController.h"
|
||||
#include "xpcpublic.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "mozilla/dom/TabParent.h"
|
||||
|
||||
#ifdef MOZ_XUL
|
||||
#include "nsIDOMXULElement.h"
|
||||
@ -385,6 +386,46 @@ nsWindowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
return mozilla::dom::WindowRootBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void
|
||||
nsWindowRoot::AddBrowser(mozilla::dom::TabParent* aBrowser)
|
||||
{
|
||||
nsWeakPtr weakBrowser = do_GetWeakReference(static_cast<nsITabParent*>(aBrowser));
|
||||
mWeakBrowsers.PutEntry(weakBrowser);
|
||||
}
|
||||
|
||||
void
|
||||
nsWindowRoot::RemoveBrowser(mozilla::dom::TabParent* aBrowser)
|
||||
{
|
||||
nsWeakPtr weakBrowser = do_GetWeakReference(static_cast<nsITabParent*>(aBrowser));
|
||||
mWeakBrowsers.RemoveEntry(weakBrowser);
|
||||
}
|
||||
|
||||
static PLDHashOperator
|
||||
WeakBrowserEnumFunc(nsRefPtrHashKey<nsIWeakReference>* aKey, void* aArg)
|
||||
{
|
||||
nsTArray<nsRefPtr<TabParent>>* tabParents =
|
||||
static_cast<nsTArray<nsRefPtr<TabParent>>*>(aArg);
|
||||
nsCOMPtr<nsITabParent> tabParent(do_QueryReferent((*aKey).GetKey()));
|
||||
TabParent* tab = TabParent::GetFrom(tabParent);
|
||||
if (tab) {
|
||||
tabParents->AppendElement(tab);
|
||||
}
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
void
|
||||
nsWindowRoot::EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg)
|
||||
{
|
||||
// Collect strong references to all browsers in a separate array in
|
||||
// case aEnumFunc alters mWeakBrowsers.
|
||||
nsTArray<nsRefPtr<TabParent>> tabParents;
|
||||
mWeakBrowsers.EnumerateEntries(WeakBrowserEnumFunc, &tabParents);
|
||||
|
||||
for (uint32_t i = 0; i < tabParents.Length(); ++i) {
|
||||
aEnumFunc(tabParents[i], aArg);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
already_AddRefed<EventTarget>
|
||||
|
@ -69,6 +69,10 @@ public:
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsWindowRoot,
|
||||
nsIDOMEventTarget)
|
||||
|
||||
virtual void AddBrowser(mozilla::dom::TabParent* aBrowser) override;
|
||||
virtual void RemoveBrowser(mozilla::dom::TabParent* aBrowser) override;
|
||||
virtual void EnumerateBrowsers(BrowserEnumerator aEnumFunc, void *aArg) override;
|
||||
|
||||
protected:
|
||||
virtual ~nsWindowRoot();
|
||||
|
||||
@ -84,6 +88,10 @@ protected:
|
||||
nsCOMPtr<nsIDOMNode> mPopupNode; // [OWNER]
|
||||
|
||||
nsCOMPtr<mozilla::dom::EventTarget> mParent;
|
||||
|
||||
// The TabParents that are currently registered with this top-level window.
|
||||
typedef nsTHashtable<nsRefPtrHashKey<nsIWeakReference>> WeakBrowserTable;
|
||||
WeakBrowserTable mWeakBrowsers;
|
||||
};
|
||||
|
||||
extern already_AddRefed<mozilla::dom::EventTarget>
|
||||
|
71
dom/base/test/chrome/bug418986-1.js
Normal file
71
dom/base/test/chrome/bug418986-1.js
Normal file
@ -0,0 +1,71 @@
|
||||
// The main test function.
|
||||
let test = function (isContent) {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let { ww } = SpecialPowers.Services;
|
||||
window.chromeWindow = ww.activeWindow;
|
||||
|
||||
// The pairs of values expected to be the same when
|
||||
// fingerprinting resistance is enabled.
|
||||
let pairs = [
|
||||
["screenX", 0],
|
||||
["screenY", 0],
|
||||
["mozInnerScreenX", 0],
|
||||
["mozInnerScreenY", 0],
|
||||
["screen.pixelDepth", 24],
|
||||
["screen.colorDepth", 24],
|
||||
["screen.availWidth", "innerWidth"],
|
||||
["screen.availHeight", "innerHeight"],
|
||||
["screen.left", 0],
|
||||
["screen.top", 0],
|
||||
["screen.availLeft", 0],
|
||||
["screen.availTop", 0],
|
||||
["screen.width", "innerWidth"],
|
||||
["screen.height", "innerHeight"],
|
||||
["screen.mozOrientation", "'landscape-primary'"],
|
||||
["devicePixelRatio", 1]
|
||||
];
|
||||
|
||||
// checkPair: tests if members of pair [a, b] are equal when evaluated.
|
||||
let checkPair = function (a, b) {
|
||||
is(eval(a), eval(b), a + " should be equal to " + b);
|
||||
};
|
||||
|
||||
// Returns generator object that iterates through pref values.
|
||||
let prefVals = (for (prefVal of [false, true]) prefVal);
|
||||
|
||||
// The main test function, runs until all pref values are exhausted.
|
||||
let nextTest = function () {
|
||||
let {value : prefValue, done} = prefVals.next();
|
||||
if (done) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
SpecialPowers.pushPrefEnv({set : [["privacy.resistFingerprinting", prefValue]]},
|
||||
function () {
|
||||
// We will be resisting fingerprinting if the pref is enabled,
|
||||
// and we are in a content script (not chrome).
|
||||
let resisting = prefValue && isContent;
|
||||
// Check each of the pairs.
|
||||
pairs.map(function ([item, onVal]) {
|
||||
if (resisting) {
|
||||
checkPair("window." + item, onVal);
|
||||
} else {
|
||||
if (!item.startsWith("moz")) {
|
||||
checkPair("window." + item, "chromeWindow." + item);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!resisting) {
|
||||
// Hard to predict these values, but we can enforce constraints:
|
||||
ok(window.mozInnerScreenX >= chromeWindow.mozInnerScreenX,
|
||||
"mozInnerScreenX");
|
||||
ok(window.mozInnerScreenY >= chromeWindow.mozInnerScreenY,
|
||||
"mozInnerScreenY");
|
||||
}
|
||||
nextTest();
|
||||
});
|
||||
}
|
||||
|
||||
nextTest();
|
||||
}
|
@ -3,6 +3,7 @@ skip-if = buildapp == 'b2g'
|
||||
support-files =
|
||||
blockNoPlugins.xml
|
||||
blockPluginHard.xml
|
||||
bug418986-1.js
|
||||
cpows_child.js
|
||||
cpows_parent.xul
|
||||
file_bug391728.html
|
||||
@ -31,6 +32,7 @@ support-files =
|
||||
[test_bug380418.html^headers^]
|
||||
[test_bug383430.html]
|
||||
[test_bug391728.html]
|
||||
[test_bug418986-1.xul]
|
||||
[test_bug421622.xul]
|
||||
[test_bug429785.xul]
|
||||
[test_bug430050.xul]
|
||||
|
26
dom/base/test/chrome/test_bug418986-1.xul
Normal file
26
dom/base/test/chrome/test_bug418986-1.xul
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1
|
||||
-->
|
||||
<window title="Mozilla Bug 418986 (Part 1)"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1"
|
||||
target="_blank">Mozilla Bug 418986 (Part 1)</a>
|
||||
|
||||
<script type="application/javascript;version=1.7" src="bug418986-1.js"></script>
|
||||
<!-- test code goes here -->
|
||||
<script type="application/javascript"><![CDATA[
|
||||
window.onload = function() {
|
||||
test(false);
|
||||
};
|
||||
]]></script>
|
||||
</body>
|
||||
</window>
|
@ -56,6 +56,7 @@ support-files =
|
||||
bug704320.sjs
|
||||
bug704320_counter.sjs
|
||||
bug819051.sjs
|
||||
chrome/bug418986-1.js
|
||||
copypaste.js
|
||||
delayedServerEvents.sjs
|
||||
echo.sjs
|
||||
@ -453,6 +454,7 @@ support-files = test_bug402150.html^headers^
|
||||
[test_bug417255.html]
|
||||
[test_bug417384.html]
|
||||
[test_bug418214.html]
|
||||
[test_bug418986-1.html]
|
||||
[test_bug419132.html]
|
||||
[test_bug419527.xhtml]
|
||||
[test_bug420609.xhtml]
|
||||
|
24
dom/base/test/test_bug418986-1.html
Normal file
24
dom/base/test/test_bug418986-1.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=418986
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test 1/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript;version=1.7" src="chrome/bug418986-1.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
test(true);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -890,6 +890,13 @@ Event::GetScreenCoords(nsPresContext* aPresContext,
|
||||
WidgetEvent* aEvent,
|
||||
LayoutDeviceIntPoint aPoint)
|
||||
{
|
||||
if (!nsContentUtils::IsCallerChrome() &&
|
||||
nsContentUtils::ResistFingerprinting()) {
|
||||
// When resisting fingerprinting, return client coordinates instead.
|
||||
CSSIntPoint clientCoords = GetClientCoords(aPresContext, aEvent, aPoint, CSSIntPoint(0, 0));
|
||||
return LayoutDeviceIntPoint(clientCoords.x, clientCoords.y);
|
||||
}
|
||||
|
||||
if (EventStateManager::sIsPointerLocked) {
|
||||
return EventStateManager::sLastScreenPoint;
|
||||
}
|
||||
|
69
dom/events/test/bug418986-3.js
Normal file
69
dom/events/test/bug418986-3.js
Normal file
@ -0,0 +1,69 @@
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// The main testing function.
|
||||
let test = function (isContent) {
|
||||
// Each definition is [eventType, prefSetting]
|
||||
// Where we are setting the "privacy.resistFingerprinting" pref.
|
||||
let eventDefs = [["mousedown", true],
|
||||
["mouseup", true],
|
||||
["mousedown", false],
|
||||
["mouseup", false]];
|
||||
|
||||
let testCounter = 0;
|
||||
|
||||
// Declare ahead of time.
|
||||
let setup;
|
||||
|
||||
// This function is called when the event handler fires.
|
||||
let handleEvent = function (event, prefVal) {
|
||||
let resisting = prefVal && isContent;
|
||||
if (resisting) {
|
||||
is(event.screenX, event.clientX, "event.screenX and event.clientX should be the same");
|
||||
is(event.screenY, event.clientY, "event.screenY and event.clientY should be the same");
|
||||
} else {
|
||||
// We can't be sure about X coordinates not being equal, but we can test Y.
|
||||
isnot(event.screenY, event.clientY, "event.screenY !== event.clientY");
|
||||
}
|
||||
++testCounter;
|
||||
if (testCounter < eventDefs.length) {
|
||||
nextTest();
|
||||
} else {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
};
|
||||
|
||||
// In this function, we set up the nth div and event handler,
|
||||
// and then synthesize a mouse event in the div, to test
|
||||
// whether the resulting events resist fingerprinting by
|
||||
// suppressing absolute screen coordinates.
|
||||
nextTest = function () {
|
||||
let [eventType, prefVal] = eventDefs[testCounter];
|
||||
SpecialPowers.pushPrefEnv({set:[["privacy.resistFingerprinting", prefVal]]},
|
||||
function () {
|
||||
// The following code creates a new div for each event in eventDefs,
|
||||
// attaches a listener to listen for the event, and then generates
|
||||
// a fake event at the center of the div.
|
||||
let div = document.createElement("div");
|
||||
div.style.width = "10px";
|
||||
div.style.height = "10px";
|
||||
div.style.backgroundColor = "red";
|
||||
// Name the div after the event we're listening for.
|
||||
div.id = eventType;
|
||||
document.getElementById("body").appendChild(div);
|
||||
// Seems we can't add an event listener in chrome unless we run
|
||||
// it in a later task.
|
||||
window.setTimeout(function() {
|
||||
div.addEventListener(eventType, event => handleEvent(event, prefVal), false);
|
||||
// For some reason, the following synthesizeMouseAtCenter call only seems to run if we
|
||||
// wrap it in a window.setTimeout(..., 0).
|
||||
window.setTimeout(function () {
|
||||
synthesizeMouseAtCenter(div, {type : eventType});
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
// Now run by starting with the 0th event.
|
||||
nextTest();
|
||||
|
||||
};
|
@ -3,6 +3,7 @@ skip-if = buildapp == 'b2g'
|
||||
support-files =
|
||||
bug415498-doc1.html
|
||||
bug415498-doc2.html
|
||||
bug418986-3.js
|
||||
bug591249_iframe.xul
|
||||
bug602962.xul
|
||||
file_bug679494.html
|
||||
@ -12,6 +13,7 @@ support-files =
|
||||
[test_bug336682_2.xul]
|
||||
[test_bug368835.html]
|
||||
[test_bug415498.xul]
|
||||
[test_bug418986-3.xul]
|
||||
[test_bug524674.xul]
|
||||
[test_bug586961.xul]
|
||||
[test_bug591249.xul]
|
||||
|
@ -7,6 +7,7 @@ support-files =
|
||||
bug426082.html
|
||||
bug648573.html
|
||||
bug656379-1.html
|
||||
bug418986-3.js
|
||||
error_event_worker.js
|
||||
empty.js
|
||||
window_bug493251.html
|
||||
@ -38,6 +39,9 @@ support-files = test_bug336682.js
|
||||
[test_bug409604.html]
|
||||
skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
|
||||
[test_bug412567.html]
|
||||
[test_bug418986-3.html]
|
||||
# Sometimes fails to finish after tests pass on 'B2G ICS Emulator'.
|
||||
skip-if = (os == 'b2g')
|
||||
[test_bug422132.html]
|
||||
skip-if = buildapp == 'b2g' || e10s # b2g(2 failures out of 8, mousewheel test) b2g-debug(2 failures out of 8, mousewheel test) b2g-desktop(2 failures out of 8, mousewheel test)
|
||||
[test_bug426082.html]
|
||||
|
25
dom/events/test/test_bug418986-3.html
Normal file
25
dom/events/test/test_bug418986-3.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=418986
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test 3/3 for Bug 418986 - Resist fingerprinting by preventing exposure of screen and system info</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body id="body">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a>
|
||||
<p id="display"></p>
|
||||
<pre id="test"></pre>
|
||||
<script type="application/javascript;version=1.7" src="bug418986-3.js"></script>
|
||||
<script type="application/javascript;version=1.7">
|
||||
// This test produces fake mouse events and checks that the screenX and screenY
|
||||
// properties of the received event objects provide client window coordinates.
|
||||
// Run the test once the window has loaded.
|
||||
window.onload = () => test(true);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
27
dom/events/test/test_bug418986-3.xul
Normal file
27
dom/events/test/test_bug418986-3.xul
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
|
||||
<!--
|
||||
Bug 418986
|
||||
-->
|
||||
<window title="Mozilla Bug 418986"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
|
||||
<body id="body" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">
|
||||
Mozilla Bug 418986</a>
|
||||
</body>
|
||||
|
||||
<script type="application/javascript;version=1.7" src="bug418986-3.js"></script>
|
||||
<script type="application/javascript;version=1.7"><![CDATA[
|
||||
// This test produces fake mouse events and checks that the screenX and screenY
|
||||
// properties of the received event objects provide client window coordinates.
|
||||
// Run the test once the window has loaded.
|
||||
test(false);
|
||||
]]></script>
|
||||
|
||||
</window>
|
@ -105,6 +105,8 @@ static PRLogModuleInfo* gMediaElementEventsLog;
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsContentTypeParser.h"
|
||||
|
||||
#include "mozilla/EventStateManager.h"
|
||||
|
||||
using namespace mozilla::layers;
|
||||
using mozilla::net::nsMediaFragmentURIParser;
|
||||
|
||||
@ -2183,6 +2185,13 @@ HTMLMediaElement::ResetConnectionState()
|
||||
void
|
||||
HTMLMediaElement::Play(ErrorResult& aRv)
|
||||
{
|
||||
// Prevent media element from being auto-started by a script when
|
||||
// media.autoplay.enabled=false
|
||||
if (!IsAutoplayEnabled() && !EventStateManager::IsHandlingUserInput() && !nsContentUtils::IsCallerChrome()) {
|
||||
LOG(LogLevel::Debug, ("%p Blocked attempt to autoplay media.", this));
|
||||
return;
|
||||
}
|
||||
|
||||
StopSuspendingAfterFirstFrame();
|
||||
SetPlayedOrSeeked(true);
|
||||
|
||||
@ -2711,11 +2720,7 @@ nsresult HTMLMediaElement::InitializeDecoderAsClone(MediaDecoder* aOriginal)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
double duration = aOriginal->GetDuration();
|
||||
if (duration >= 0) {
|
||||
decoder->SetDuration(duration);
|
||||
decoder->SetMediaSeekable(aOriginal->IsMediaSeekable());
|
||||
}
|
||||
decoder->SetMediaSeekable(aOriginal->IsMediaSeekable());
|
||||
|
||||
nsRefPtr<MediaResource> resource = originalResource->CloneData(decoder);
|
||||
if (!resource) {
|
||||
|
@ -3256,7 +3256,6 @@ nsHTMLDocument::ExecCommand(const nsAString& commandID,
|
||||
|
||||
// if editing is not on, bail
|
||||
if (!isCutCopy && !IsEditingOnAfterFlush()) {
|
||||
rv.Throw(NS_ERROR_FAILURE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3293,7 +3292,6 @@ nsHTMLDocument::ExecCommand(const nsAString& commandID,
|
||||
|
||||
bool restricted = commandID.LowerCaseEqualsLiteral("paste");
|
||||
if (restricted && !nsContentUtils::IsCallerChrome()) {
|
||||
rv = NS_ERROR_DOM_SECURITY_ERR;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3390,7 +3388,6 @@ nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID, ErrorResult& rv)
|
||||
|
||||
// if editing is not on, bail
|
||||
if (!IsEditingOnAfterFlush()) {
|
||||
rv.Throw(NS_ERROR_FAILURE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3433,7 +3430,6 @@ nsHTMLDocument::QueryCommandIndeterm(const nsAString& commandID, ErrorResult& rv
|
||||
|
||||
// if editing is not on, bail
|
||||
if (!IsEditingOnAfterFlush()) {
|
||||
rv.Throw(NS_ERROR_FAILURE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3494,7 +3490,6 @@ nsHTMLDocument::QueryCommandState(const nsAString& commandID, ErrorResult& rv)
|
||||
|
||||
// if editing is not on, bail
|
||||
if (!IsEditingOnAfterFlush()) {
|
||||
rv.Throw(NS_ERROR_FAILURE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3617,7 +3612,6 @@ nsHTMLDocument::QueryCommandValue(const nsAString& commandID,
|
||||
|
||||
// if editing is not on, bail
|
||||
if (!IsEditingOnAfterFlush()) {
|
||||
rv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,6 @@
|
||||
"Editable b: execCommand() must not throw, uncanceled":true,
|
||||
"Editable b: beforeinput event, uncanceled":true,
|
||||
"Editable b: input event, uncanceled":true,
|
||||
"No editable content: execCommand() must not throw, canceled":true,
|
||||
"No editable content: execCommand() must not throw, uncanceled":true,
|
||||
"Changing selection from handler: beforeinput event, canceled":true,
|
||||
"Changing selection from handler: input event, canceled":true,
|
||||
"Changing selection from handler: beforeinput event, uncanceled":true,
|
||||
|
@ -7,11 +7,13 @@
|
||||
|
||||
#include "domstubs.idl"
|
||||
|
||||
interface nsIPrincipal;
|
||||
|
||||
/**
|
||||
* We expose Gecko-internal helpers related to "web apps" through this
|
||||
* sub-interface.
|
||||
*/
|
||||
[scriptable, uuid(1d856b11-ac29-47d3-bd52-a86e3d45fcf5)]
|
||||
[scriptable, uuid(e76aa5e0-80b2-404f-bccc-1067828bb6ed)]
|
||||
interface mozIApplication: nsISupports
|
||||
{
|
||||
/* Return true if this app has |permission|. */
|
||||
@ -58,4 +60,7 @@ interface mozIApplication: nsISupports
|
||||
|
||||
/* Returns the kind of the app. */
|
||||
readonly attribute DOMString kind;
|
||||
|
||||
/* Returns the app's principal */
|
||||
readonly attribute nsIPrincipal principal;
|
||||
};
|
||||
|
@ -49,7 +49,7 @@ interface nsIJSRAIIHelper;
|
||||
interface nsIContentPermissionRequest;
|
||||
interface nsIObserver;
|
||||
|
||||
[scriptable, uuid(098d9f0d-7809-4d3c-8fc6-e5b3fb71835b)]
|
||||
[scriptable, uuid(ec176f3b-2886-4090-938e-dded103c5f1c)]
|
||||
interface nsIDOMWindowUtils : nsISupports {
|
||||
|
||||
/**
|
||||
@ -1814,6 +1814,14 @@ interface nsIDOMWindowUtils : nsISupports {
|
||||
attribute boolean serviceWorkersTestingEnabled;
|
||||
|
||||
/**
|
||||
* Returns a JSObject which contains a list of frame uniformities
|
||||
* when the pref gfx.vsync.collect-scroll-data is enabled.
|
||||
* Every result contains a layer address and a frame uniformity for that layer.
|
||||
* A negative frame uniformity value indicates an invalid frame uniformity and an error has occured.
|
||||
*/
|
||||
[implicit_jscontext] jsval getFrameUniformityTestData();
|
||||
|
||||
/*
|
||||
* Increase the chaos mode activation level. An equivalent number of
|
||||
* calls to leaveChaosMode must be made in order to restore the original
|
||||
* chaos mode state. If the activation level is nonzero all chaos mode
|
||||
|
@ -25,6 +25,7 @@ using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
|
||||
using class mozilla::gfx::Matrix from "mozilla/gfx/Matrix.h";
|
||||
using struct gfxSize from "gfxPoint.h";
|
||||
using CSSRect from "Units.h";
|
||||
using CSSSize from "Units.h";
|
||||
using LayoutDeviceIntRect from "Units.h";
|
||||
using mozilla::LayoutDeviceIntPoint from "Units.h";
|
||||
using ScreenIntSize from "Units.h";
|
||||
@ -548,7 +549,7 @@ child:
|
||||
|
||||
CacheFileDescriptor(nsString path, FileDescriptor fd);
|
||||
|
||||
UpdateDimensions(IntRect rect, ScreenIntSize size, ScreenOrientation orientation,
|
||||
UpdateDimensions(CSSRect rect, CSSSize size, ScreenOrientation orientation,
|
||||
LayoutDeviceIntPoint chromeDisp) compressall;
|
||||
|
||||
UpdateFrame(FrameMetrics frame);
|
||||
|
@ -92,6 +92,7 @@
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "mozilla/EventForwards.h"
|
||||
#include "nsDeviceContext.h"
|
||||
|
||||
#define BROWSER_ELEMENT_CHILD_SCRIPT \
|
||||
NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
|
||||
@ -165,7 +166,6 @@ NS_IMPL_ISUPPORTS(TabChild::DelayedFireContextMenuEvent,
|
||||
TabChildBase::TabChildBase()
|
||||
: mContentDocumentIsDisplayed(false)
|
||||
, mTabChildGlobal(nullptr)
|
||||
, mInnerSize(0, 0)
|
||||
{
|
||||
mozilla::HoldJSObjects(this);
|
||||
}
|
||||
@ -236,9 +236,11 @@ TabChildBase::InitializeRootMetrics()
|
||||
mLastRootMetrics.SetViewport(CSSRect(CSSPoint(), kDefaultViewportSize));
|
||||
mLastRootMetrics.SetCompositionBounds(ParentLayerRect(
|
||||
ParentLayerPoint(),
|
||||
ParentLayerSize(ViewAs<ParentLayerPixel>(mInnerSize, PixelCastJustification::ScreenIsParentLayerForRoot))));
|
||||
ParentLayerSize(
|
||||
ViewAs<ParentLayerPixel>(GetInnerSize(),
|
||||
PixelCastJustification::ScreenIsParentLayerForRoot))));
|
||||
mLastRootMetrics.SetZoom(CSSToParentLayerScale2D(
|
||||
ConvertScaleForRoot(CalculateIntrinsicScale(mInnerSize, kDefaultViewportSize))));
|
||||
ConvertScaleForRoot(CalculateIntrinsicScale(GetInnerSize(), kDefaultViewportSize))));
|
||||
mLastRootMetrics.SetDevPixelsPerCSSPixel(WebWidget()->GetDefaultScale());
|
||||
// We use ParentLayerToLayerScale(1) below in order to turn the
|
||||
// async zoom amount into the gecko zoom amount.
|
||||
@ -299,14 +301,14 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
|
||||
}
|
||||
|
||||
TABC_LOG("HandlePossibleViewportChange aOldScreenSize=%s mInnerSize=%s\n",
|
||||
Stringify(aOldScreenSize).c_str(), Stringify(mInnerSize).c_str());
|
||||
Stringify(aOldScreenSize).c_str(), Stringify(GetInnerSize()).c_str());
|
||||
|
||||
nsCOMPtr<nsIDocument> document(GetDocument());
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, mInnerSize);
|
||||
nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, GetInnerSize());
|
||||
uint32_t presShellId = 0;
|
||||
mozilla::layers::FrameMetrics::ViewID viewId = FrameMetrics::NULL_SCROLL_ID;
|
||||
bool scrollIdentifiersValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
|
||||
@ -323,8 +325,8 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
|
||||
constraints);
|
||||
}
|
||||
|
||||
float screenW = mInnerSize.width;
|
||||
float screenH = mInnerSize.height;
|
||||
float screenW = GetInnerSize().width;
|
||||
float screenH = GetInnerSize().height;
|
||||
CSSSize viewport(viewportInfo.GetSize());
|
||||
|
||||
// We're not being displayed in any way; don't bother doing anything because
|
||||
@ -358,15 +360,15 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
|
||||
|
||||
ScreenIntSize oldScreenSize = aOldScreenSize;
|
||||
if (oldScreenSize == ScreenIntSize()) {
|
||||
oldScreenSize = mInnerSize;
|
||||
oldScreenSize = GetInnerSize();
|
||||
}
|
||||
|
||||
FrameMetrics metrics(mLastRootMetrics);
|
||||
metrics.SetViewport(CSSRect(CSSPoint(), viewport));
|
||||
|
||||
// Calculate the composition bounds based on mInnerSize, excluding the sizes
|
||||
// Calculate the composition bounds based on the inner size, excluding the sizes
|
||||
// of the scrollbars if they are not overlay scrollbars.
|
||||
ScreenSize compositionSize(mInnerSize);
|
||||
ScreenSize compositionSize(GetInnerSize());
|
||||
nsCOMPtr<nsIPresShell> shell = GetPresShell();
|
||||
if (shell) {
|
||||
nsMargin scrollbarsAppUnits =
|
||||
@ -380,7 +382,9 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
|
||||
|
||||
metrics.SetCompositionBounds(ParentLayerRect(
|
||||
ParentLayerPoint(),
|
||||
ParentLayerSize(ViewAs<ParentLayerPixel>(compositionSize, PixelCastJustification::ScreenIsParentLayerForRoot))));
|
||||
ParentLayerSize(
|
||||
ViewAs<ParentLayerPixel>(GetInnerSize(),
|
||||
PixelCastJustification::ScreenIsParentLayerForRoot))));
|
||||
metrics.SetRootCompositionSize(
|
||||
ScreenSize(compositionSize) * ScreenToLayoutDeviceScale(1.0f) / metrics.GetDevPixelsPerCSSPixel());
|
||||
|
||||
@ -397,7 +401,7 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
|
||||
// within the screen width. Note that "actual content" may be different with
|
||||
// respect to CSS pixels because of the CSS viewport size changing.
|
||||
CSSToScreenScale oldIntrinsicScale = CalculateIntrinsicScale(oldScreenSize, oldBrowserSize);
|
||||
CSSToScreenScale newIntrinsicScale = CalculateIntrinsicScale(mInnerSize, viewport);
|
||||
CSSToScreenScale newIntrinsicScale = CalculateIntrinsicScale(GetInnerSize(), viewport);
|
||||
metrics.ZoomBy(newIntrinsicScale.scale / oldIntrinsicScale.scale);
|
||||
|
||||
// Changing the zoom when we're not doing a first paint will get ignored
|
||||
@ -881,7 +885,6 @@ TabChild::TabChild(nsIContentChild* aManager,
|
||||
, mManager(aManager)
|
||||
, mChromeFlags(aChromeFlags)
|
||||
, mLayersId(0)
|
||||
, mOuterRect(0, 0, 0, 0)
|
||||
, mActivePointerId(-1)
|
||||
, mAppPackageFileDescriptorRecved(false)
|
||||
, mLastBackgroundColor(NS_RGB(255, 255, 255))
|
||||
@ -922,9 +925,9 @@ TabChild::HandleEvent(nsIDOMEvent* aEvent)
|
||||
if (eventType.EqualsLiteral("DOMMetaAdded")) {
|
||||
// This meta data may or may not have been a meta viewport tag. If it was,
|
||||
// we should handle it immediately.
|
||||
HandlePossibleViewportChange(mInnerSize);
|
||||
HandlePossibleViewportChange(GetInnerSize());
|
||||
} else if (eventType.EqualsLiteral("FullZoomChange")) {
|
||||
HandlePossibleViewportChange(mInnerSize);
|
||||
HandlePossibleViewportChange(GetInnerSize());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
@ -966,14 +969,14 @@ TabChild::Observe(nsISupports *aSubject,
|
||||
|
||||
// In some cases before-first-paint gets called before
|
||||
// RecvUpdateDimensions is called and therefore before we have an
|
||||
// mInnerSize value set. In such cases defer initializing the viewport
|
||||
// inner size value set. In such cases defer initializing the viewport
|
||||
// until we we get an inner size.
|
||||
if (HasValidInnerSize()) {
|
||||
InitializeRootMetrics();
|
||||
if (shell) {
|
||||
nsLayoutUtils::SetResolutionAndScaleTo(shell, mLastRootMetrics.GetPresShellResolution());
|
||||
}
|
||||
HandlePossibleViewportChange(mInnerSize);
|
||||
HandlePossibleViewportChange(GetInnerSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1313,17 +1316,18 @@ NS_IMETHODIMP
|
||||
TabChild::GetDimensions(uint32_t aFlags, int32_t* aX,
|
||||
int32_t* aY, int32_t* aCx, int32_t* aCy)
|
||||
{
|
||||
ScreenIntRect rect = GetOuterRect();
|
||||
if (aX) {
|
||||
*aX = mOuterRect.x;
|
||||
*aX = rect.x;
|
||||
}
|
||||
if (aY) {
|
||||
*aY = mOuterRect.y;
|
||||
*aY = rect.y;
|
||||
}
|
||||
if (aCx) {
|
||||
*aCx = mOuterRect.width;
|
||||
*aCx = rect.width;
|
||||
}
|
||||
if (aCy) {
|
||||
*aCy = mOuterRect.height;
|
||||
*aCy = rect.height;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
@ -2050,37 +2054,41 @@ TabChild::RecvShow(const ScreenIntSize& aSize,
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvUpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size,
|
||||
const ScreenOrientation& orientation, const LayoutDeviceIntPoint& chromeDisp)
|
||||
TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size,
|
||||
const ScreenOrientation& orientation,
|
||||
const LayoutDeviceIntPoint& chromeDisp)
|
||||
{
|
||||
if (!mRemoteFrame) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mOuterRect = rect;
|
||||
mUnscaledOuterRect = rect;
|
||||
mChromeDisp = chromeDisp;
|
||||
|
||||
bool initialSizing = !HasValidInnerSize()
|
||||
&& (size.width != 0 && size.height != 0);
|
||||
|
||||
mOrientation = orientation;
|
||||
ScreenIntSize oldScreenSize = GetInnerSize();
|
||||
SetUnscaledInnerSize(size);
|
||||
ScreenIntSize screenSize = GetInnerSize();
|
||||
bool sizeChanged = true;
|
||||
if (initialSizing) {
|
||||
mHasValidInnerSize = true;
|
||||
} else if (mInnerSize == size) {
|
||||
} else if (screenSize == oldScreenSize) {
|
||||
sizeChanged = false;
|
||||
}
|
||||
|
||||
mOrientation = orientation;
|
||||
ScreenIntSize oldScreenSize = mInnerSize;
|
||||
mInnerSize = size;
|
||||
mWidget->Resize(rect.x + chromeDisp.x, rect.y + chromeDisp.y, size.width, size.height,
|
||||
true);
|
||||
ScreenIntRect screenRect = GetOuterRect();
|
||||
mWidget->Resize(screenRect.x + chromeDisp.x, screenRect.y + chromeDisp.y,
|
||||
screenSize.width, screenSize.height, true);
|
||||
|
||||
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation());
|
||||
baseWin->SetPositionAndSize(0, 0, size.width, size.height,
|
||||
baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height,
|
||||
true);
|
||||
|
||||
if (initialSizing && mContentDocumentIsDisplayed) {
|
||||
// If this is the first time we're getting a valid mInnerSize, and the
|
||||
// If this is the first time we're getting a valid inner size, and the
|
||||
// before-first-paint event has already been handled, then we need to set
|
||||
// up our default viewport here. See the corresponding call to
|
||||
// InitializeRootMetrics in the before-first-paint handler.
|
||||
@ -3244,6 +3252,7 @@ TabChild::RecvRequestNotifyAfterRemotePaint()
|
||||
bool
|
||||
TabChild::RecvUIResolutionChanged()
|
||||
{
|
||||
ScreenIntSize oldScreenSize = GetInnerSize();
|
||||
mDPI = 0;
|
||||
mDefaultScale = 0;
|
||||
static_cast<PuppetWidget*>(mWidget.get())->ClearBackingScaleCache();
|
||||
@ -3252,9 +3261,21 @@ TabChild::RecvUIResolutionChanged()
|
||||
if (presShell) {
|
||||
nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
|
||||
if (presContext) {
|
||||
presContext->UIResolutionChanged();
|
||||
presContext->UIResolutionChangedSync();
|
||||
}
|
||||
}
|
||||
|
||||
ScreenIntSize screenSize = GetInnerSize();
|
||||
if (mHasValidInnerSize && oldScreenSize != screenSize) {
|
||||
ScreenIntRect screenRect = GetOuterRect();
|
||||
mWidget->Resize(screenRect.x + mChromeDisp.x, screenRect.y + mChromeDisp.y,
|
||||
screenSize.width, screenSize.height, true);
|
||||
|
||||
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation());
|
||||
baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height,
|
||||
true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -3316,6 +3337,22 @@ TabChild::CreatePluginWidget(nsIWidget* aParent, nsIWidget** aOut)
|
||||
return rv;
|
||||
}
|
||||
|
||||
ScreenIntSize
|
||||
TabChild::GetInnerSize()
|
||||
{
|
||||
LayoutDeviceIntSize innerSize =
|
||||
RoundedToInt(mUnscaledInnerSize * mWidget->GetDefaultScale());
|
||||
return ViewAs<ScreenPixel>(innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
|
||||
};
|
||||
|
||||
ScreenIntRect
|
||||
TabChild::GetOuterRect()
|
||||
{
|
||||
LayoutDeviceIntRect outerRect =
|
||||
RoundedToInt(mUnscaledOuterRect * mWidget->GetDefaultScale());
|
||||
return ViewAs<ScreenPixel>(outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
|
||||
}
|
||||
|
||||
TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
|
||||
: mTabChild(aTabChild)
|
||||
{
|
||||
|
@ -189,6 +189,8 @@ public:
|
||||
const bool& aIsRoot,
|
||||
const mozilla::layers::ZoomConstraints& aConstraints) = 0;
|
||||
|
||||
virtual ScreenIntSize GetInnerSize() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~TabChildBase();
|
||||
CSSSize GetPageSize(nsCOMPtr<nsIDocument> aDocument, const CSSSize& aViewport);
|
||||
@ -222,7 +224,6 @@ protected:
|
||||
CSSSize mOldViewportSize;
|
||||
bool mContentDocumentIsDisplayed;
|
||||
nsRefPtr<TabChildGlobal> mTabChildGlobal;
|
||||
ScreenIntSize mInnerSize;
|
||||
mozilla::layers::FrameMetrics mLastRootMetrics;
|
||||
nsCOMPtr<nsIWebBrowserChrome3> mWebBrowserChrome;
|
||||
};
|
||||
@ -319,8 +320,8 @@ public:
|
||||
const uint64_t& aLayersId,
|
||||
PRenderFrameChild* aRenderFrame,
|
||||
const bool& aParentIsActive) override;
|
||||
virtual bool RecvUpdateDimensions(const nsIntRect& rect,
|
||||
const ScreenIntSize& size,
|
||||
virtual bool RecvUpdateDimensions(const CSSRect& rect,
|
||||
const CSSSize& size,
|
||||
const ScreenOrientation& orientation,
|
||||
const LayoutDeviceIntPoint& chromeDisp) override;
|
||||
virtual bool RecvUpdateFrame(const layers::FrameMetrics& aFrameMetrics) override;
|
||||
@ -510,6 +511,8 @@ public:
|
||||
}
|
||||
bool AsyncPanZoomEnabled() { return mAsyncPanZoomEnabled; }
|
||||
|
||||
virtual ScreenIntSize GetInnerSize() override;
|
||||
|
||||
protected:
|
||||
virtual ~TabChild();
|
||||
|
||||
@ -599,6 +602,12 @@ private:
|
||||
|
||||
void SetTabId(const TabId& aTabId);
|
||||
|
||||
ScreenIntRect GetOuterRect();
|
||||
|
||||
void SetUnscaledInnerSize(const CSSSize& aSize) {
|
||||
mUnscaledInnerSize = aSize;
|
||||
}
|
||||
|
||||
class CachedFileDescriptorInfo;
|
||||
class CachedFileDescriptorCallbackRunnable;
|
||||
class DelayedDeleteRunnable;
|
||||
@ -611,7 +620,7 @@ private:
|
||||
nsRefPtr<nsIContentChild> mManager;
|
||||
uint32_t mChromeFlags;
|
||||
uint64_t mLayersId;
|
||||
nsIntRect mOuterRect;
|
||||
CSSRect mUnscaledOuterRect;
|
||||
// When we're tracking a possible tap gesture, this is the "down"
|
||||
// point of the touchstart.
|
||||
LayoutDevicePoint mGestureDownPoint;
|
||||
@ -646,6 +655,7 @@ private:
|
||||
bool mIPCOpen;
|
||||
bool mParentIsActive;
|
||||
bool mAsyncPanZoomEnabled;
|
||||
CSSSize mUnscaledInnerSize;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(TabChild);
|
||||
};
|
||||
|
@ -90,6 +90,7 @@
|
||||
#include "nsPIWindowRoot.h"
|
||||
#include "gfxDrawable.h"
|
||||
#include "ImageOps.h"
|
||||
#include "UnitTransforms.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace mozilla::dom;
|
||||
@ -333,9 +334,27 @@ TabParent::SetOwnerElement(Element* aElement)
|
||||
// If we held previous content then unregister for its events.
|
||||
RemoveWindowListeners();
|
||||
|
||||
// If we change top-level documents then we need to change our
|
||||
// registration with them.
|
||||
nsRefPtr<nsPIWindowRoot> curTopLevelWin, newTopLevelWin;
|
||||
if (mFrameElement) {
|
||||
curTopLevelWin = nsContentUtils::GetWindowRoot(mFrameElement->OwnerDoc());
|
||||
}
|
||||
if (aElement) {
|
||||
newTopLevelWin = nsContentUtils::GetWindowRoot(aElement->OwnerDoc());
|
||||
}
|
||||
bool isSameTopLevelWin = curTopLevelWin == newTopLevelWin;
|
||||
if (curTopLevelWin && !isSameTopLevelWin) {
|
||||
curTopLevelWin->RemoveBrowser(this);
|
||||
}
|
||||
|
||||
// Update to the new content, and register to listen for events from it.
|
||||
mFrameElement = aElement;
|
||||
|
||||
if (newTopLevelWin && !isSameTopLevelWin) {
|
||||
newTopLevelWin->AddBrowser(this);
|
||||
}
|
||||
|
||||
AddWindowListeners();
|
||||
TryCacheDPIAndScale();
|
||||
}
|
||||
@ -966,7 +985,21 @@ TabParent::UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size)
|
||||
mOrientation = orientation;
|
||||
mChromeOffset = chromeOffset;
|
||||
|
||||
unused << SendUpdateDimensions(mRect, mDimensions, mOrientation, mChromeOffset);
|
||||
CSSToLayoutDeviceScale widgetScale;
|
||||
if (widget) {
|
||||
widgetScale = widget->GetDefaultScale();
|
||||
}
|
||||
|
||||
LayoutDeviceIntRect devicePixelRect =
|
||||
ViewAs<LayoutDevicePixel>(mRect,
|
||||
PixelCastJustification::LayoutDeviceIsScreenForTabDims);
|
||||
LayoutDeviceIntSize devicePixelSize =
|
||||
ViewAs<LayoutDevicePixel>(mDimensions.ToUnknownSize(),
|
||||
PixelCastJustification::LayoutDeviceIsScreenForTabDims);
|
||||
|
||||
CSSRect unscaledRect = devicePixelRect / widgetScale;
|
||||
CSSSize unscaledSize = devicePixelSize / widgetScale;
|
||||
unused << SendUpdateDimensions(unscaledRect, unscaledSize, orientation, chromeOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@ -985,6 +1018,7 @@ TabParent::UIResolutionChanged()
|
||||
// TryCacheDPIAndScale()'s cache is keyed off of
|
||||
// mDPI being greater than 0, so this invalidates it.
|
||||
mDPI = -1;
|
||||
TryCacheDPIAndScale();
|
||||
unused << SendUIResolutionChanged();
|
||||
}
|
||||
}
|
||||
|
@ -74,9 +74,6 @@ public:
|
||||
// Return the duration of the media in microseconds.
|
||||
virtual int64_t GetMediaDuration() = 0;
|
||||
|
||||
// Set the duration of the media in microseconds.
|
||||
virtual void SetMediaDuration(int64_t aDuration) = 0;
|
||||
|
||||
// Sets the duration of the media in microseconds. The MediaDecoder
|
||||
// fires a durationchange event to its owner (e.g., an HTML audio
|
||||
// tag).
|
||||
@ -100,9 +97,6 @@ public:
|
||||
|
||||
virtual void RemoveMediaTracks() = 0;
|
||||
|
||||
// Set the media end time in microseconds
|
||||
virtual void SetMediaEndTime(int64_t aTime) = 0;
|
||||
|
||||
// May be called by the reader to notify this decoder that the metadata from
|
||||
// the media file has been read. Call on the decode thread only.
|
||||
virtual void OnReadMetadataCompleted() = 0;
|
||||
|
@ -69,10 +69,8 @@ private:
|
||||
bool mStreamFinishedOnMainThread;
|
||||
};
|
||||
|
||||
DecodedStreamData::DecodedStreamData(int64_t aInitialTime,
|
||||
SourceMediaStream* aStream)
|
||||
DecodedStreamData::DecodedStreamData(SourceMediaStream* aStream)
|
||||
: mAudioFramesWritten(0)
|
||||
, mInitialTime(aInitialTime)
|
||||
, mNextVideoTime(-1)
|
||||
, mNextAudioTime(-1)
|
||||
, mStreamInitialized(false)
|
||||
@ -103,9 +101,9 @@ DecodedStreamData::IsFinished() const
|
||||
}
|
||||
|
||||
int64_t
|
||||
DecodedStreamData::GetClock() const
|
||||
DecodedStreamData::GetPosition() const
|
||||
{
|
||||
return mInitialTime + mListener->GetLastOutputTime();
|
||||
return mListener->GetLastOutputTime();
|
||||
}
|
||||
|
||||
class OutputStreamListener : public MediaStreamListener {
|
||||
@ -223,7 +221,7 @@ DecodedStream::DestroyData()
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::RecreateData(int64_t aInitialTime, MediaStreamGraph* aGraph)
|
||||
DecodedStream::RecreateData(MediaStreamGraph* aGraph)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
@ -235,7 +233,7 @@ DecodedStream::RecreateData(int64_t aInitialTime, MediaStreamGraph* aGraph)
|
||||
}
|
||||
auto source = aGraph->CreateSourceStream(nullptr);
|
||||
DestroyData();
|
||||
mData.reset(new DecodedStreamData(aInitialTime, source));
|
||||
mData.reset(new DecodedStreamData(source));
|
||||
|
||||
// Note that the delay between removing ports in DestroyDecodedStream
|
||||
// and adding new ones won't cause a glitch since all graph operations
|
||||
|
@ -37,19 +37,16 @@ class Image;
|
||||
*/
|
||||
class DecodedStreamData {
|
||||
public:
|
||||
DecodedStreamData(int64_t aInitialTime, SourceMediaStream* aStream);
|
||||
explicit DecodedStreamData(SourceMediaStream* aStream);
|
||||
~DecodedStreamData();
|
||||
bool IsFinished() const;
|
||||
int64_t GetClock() const;
|
||||
int64_t GetPosition() const;
|
||||
|
||||
/* The following group of fields are protected by the decoder's monitor
|
||||
* and can be read or written on any thread.
|
||||
*/
|
||||
// Count of audio frames written to the stream
|
||||
int64_t mAudioFramesWritten;
|
||||
// Saved value of aInitialTime. Timestamp of the first audio and/or
|
||||
// video packet written.
|
||||
const int64_t mInitialTime; // microseconds
|
||||
// mNextVideoTime is the end timestamp for the last packet sent to the stream.
|
||||
// Therefore video packets starting at or after this time need to be copied
|
||||
// to the output stream.
|
||||
@ -95,7 +92,7 @@ public:
|
||||
explicit DecodedStream(ReentrantMonitor& aMonitor);
|
||||
DecodedStreamData* GetData() const;
|
||||
void DestroyData();
|
||||
void RecreateData(int64_t aInitialTime, MediaStreamGraph* aGraph);
|
||||
void RecreateData(MediaStreamGraph* aGraph);
|
||||
nsTArray<OutputStreamData>& OutputStreams();
|
||||
ReentrantMonitor& GetReentrantMonitor() const;
|
||||
void Connect(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
|
||||
|
@ -32,8 +32,9 @@
|
||||
#include "WMFDecoder.h"
|
||||
#endif
|
||||
|
||||
using namespace mozilla::layers;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::layers;
|
||||
using namespace mozilla::media;
|
||||
|
||||
// Default timeout msecs until try to enter dormant state by heuristic.
|
||||
static const int DEFAULT_HEURISTIC_DORMANT_TIMEOUT_MSECS = 60000;
|
||||
@ -48,6 +49,12 @@ namespace mozilla {
|
||||
// fluctuating bitrates.
|
||||
static const int64_t CAN_PLAY_THROUGH_MARGIN = 1;
|
||||
|
||||
// The amount of instability we tollerate in calls to
|
||||
// MediaDecoder::UpdateEstimatedMediaDuration(); changes of duration
|
||||
// less than this are ignored, as they're assumed to be the result of
|
||||
// instability in the duration estimation.
|
||||
static const uint64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
|
||||
|
||||
// avoid redefined macro in unified build
|
||||
#undef DECODER_LOG
|
||||
|
||||
@ -343,6 +350,10 @@ MediaDecoder::MediaDecoder() :
|
||||
mMediaSeekable(true),
|
||||
mSameOriginMedia(false),
|
||||
mReentrantMonitor("media.decoder"),
|
||||
mEstimatedDuration(AbstractThread::MainThread(), NullableTimeUnit(),
|
||||
"MediaDecoder::mEstimatedDuration (Canonical)"),
|
||||
mExplicitDuration(AbstractThread::MainThread(), Maybe<double>(),
|
||||
"MediaDecoder::mExplicitDuration (Canonical)"),
|
||||
mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
|
||||
"MediaDecoder::mPlayState (Canonical)"),
|
||||
mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED,
|
||||
@ -488,7 +499,6 @@ nsresult MediaDecoder::InitializeStateMachine(MediaDecoder* aCloneDonor)
|
||||
void MediaDecoder::SetStateMachineParameters()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mDecoderStateMachine->SetDuration(mDuration);
|
||||
if (mMinimizePreroll) {
|
||||
mDecoderStateMachine->DispatchMinimizePrerollUntilPlaybackStarts();
|
||||
}
|
||||
@ -1065,12 +1075,12 @@ void MediaDecoder::UpdateLogicalPosition(MediaDecoderEventVisibility aEventVisib
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoder::DurationChanged()
|
||||
void MediaDecoder::DurationChanged(TimeUnit aNewDuration)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
int64_t oldDuration = mDuration;
|
||||
mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
|
||||
mDuration = aNewDuration.ToMicroseconds();
|
||||
// Duration has changed so we should recompute playback rate
|
||||
UpdatePlaybackRate();
|
||||
|
||||
@ -1080,33 +1090,10 @@ void MediaDecoder::DurationChanged()
|
||||
DECODER_LOG("Duration changed to %lld", mDuration);
|
||||
mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoder::SetDuration(double aDuration)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mozilla::IsInfinite(aDuration)) {
|
||||
SetInfinite(true);
|
||||
} else if (IsNaN(aDuration)) {
|
||||
mDuration = -1;
|
||||
SetInfinite(true);
|
||||
} else {
|
||||
mDuration = static_cast<int64_t>(NS_round(aDuration * static_cast<double>(USECS_PER_S)));
|
||||
if (CurrentPosition() > aNewDuration.ToMicroseconds()) {
|
||||
Seek(aNewDuration.ToSeconds(), SeekTarget::Accurate);
|
||||
}
|
||||
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
if (mDecoderStateMachine) {
|
||||
mDecoderStateMachine->SetDuration(mDuration);
|
||||
}
|
||||
|
||||
// Duration has changed so we should recompute playback rate
|
||||
UpdatePlaybackRate();
|
||||
}
|
||||
|
||||
void MediaDecoder::SetMediaDuration(int64_t aDuration)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(GetStateMachine());
|
||||
GetStateMachine()->SetDuration(aDuration);
|
||||
}
|
||||
|
||||
void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
|
||||
@ -1114,8 +1101,17 @@ void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
|
||||
if (mPlayState <= PLAY_STATE_LOADING) {
|
||||
return;
|
||||
}
|
||||
NS_ENSURE_TRUE_VOID(GetStateMachine());
|
||||
GetStateMachine()->UpdateEstimatedDuration(aDuration);
|
||||
|
||||
// The duration is only changed if its significantly different than the
|
||||
// the current estimate, as the incoming duration is an estimate and so
|
||||
// often is unstable as more data is read and the estimate is updated.
|
||||
// Can result in a durationchangeevent. aDuration is in microseconds.
|
||||
if (mEstimatedDuration.Ref().isSome() &&
|
||||
mozilla::Abs(mEstimatedDuration.Ref().ref().ToMicroseconds() - aDuration) < ESTIMATED_DURATION_FUZZ_FACTOR_USECS) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEstimatedDuration = Some(TimeUnit::FromMicroseconds(aDuration));
|
||||
}
|
||||
|
||||
void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) {
|
||||
@ -1164,12 +1160,6 @@ void MediaDecoder::SetFragmentEndTime(double aTime)
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoder::SetMediaEndTime(int64_t aTime)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(GetStateMachine());
|
||||
GetStateMachine()->SetMediaEndTime(aTime);
|
||||
}
|
||||
|
||||
void MediaDecoder::Suspend()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
@ -456,14 +456,6 @@ public:
|
||||
// Call on the main thread only.
|
||||
virtual bool IsEndedOrShutdown() const;
|
||||
|
||||
// Set the duration of the media resource in units of seconds.
|
||||
// This is called via a channel listener if it can pick up the duration
|
||||
// from a content header. Must be called from the main thread only.
|
||||
virtual void SetDuration(double aDuration);
|
||||
|
||||
// Sets the initial duration of the media. Called while the media metadata
|
||||
// is being read and the decode is being setup.
|
||||
void SetMediaDuration(int64_t aDuration) override;
|
||||
// Updates the media duration. This is called while the media is being
|
||||
// played, calls before the media has reached loaded metadata are ignored.
|
||||
// The duration is assumed to be an estimate, and so a degree of
|
||||
@ -491,9 +483,6 @@ public:
|
||||
// this point the media pauses. aTime is in seconds.
|
||||
virtual void SetFragmentEndTime(double aTime);
|
||||
|
||||
// Set the end time of the media. aTime is in microseconds.
|
||||
void SetMediaEndTime(int64_t aTime) final override;
|
||||
|
||||
// Invalidate the frame.
|
||||
void Invalidate();
|
||||
void InvalidateWithFlags(uint32_t aFlags);
|
||||
@ -525,7 +514,7 @@ public:
|
||||
|
||||
// Called by the state machine to notify the decoder that the duration
|
||||
// has changed.
|
||||
void DurationChanged();
|
||||
void DurationChanged(media::TimeUnit aNewDuration);
|
||||
|
||||
bool OnStateMachineTaskQueue() const override;
|
||||
|
||||
@ -993,7 +982,26 @@ private:
|
||||
nsRefPtr<CDMProxy> mProxy;
|
||||
#endif
|
||||
|
||||
// Media duration according to the demuxer's current estimate.
|
||||
//
|
||||
// Note that it's quite bizarre for this to live on the main thread - it would
|
||||
// make much more sense for this to be owned by the demuxer's task queue. But
|
||||
// currently this is only every changed in NotifyDataArrived, which runs on
|
||||
// the main thread. That will need to be cleaned up at some point.
|
||||
Canonical<media::NullableTimeUnit> mEstimatedDuration;
|
||||
public:
|
||||
AbstractCanonical<media::NullableTimeUnit>* CanonicalEstimatedDuration() { return &mEstimatedDuration; }
|
||||
protected:
|
||||
|
||||
// Media duration set explicitly by JS. At present, this is only ever present
|
||||
// for MSE.
|
||||
Canonical<Maybe<double>> mExplicitDuration;
|
||||
double ExplicitDuration() { return mExplicitDuration.Ref().ref(); }
|
||||
void SetExplicitDuration(double aValue) { mExplicitDuration.Set(Some(aValue)); }
|
||||
public:
|
||||
AbstractCanonical<Maybe<double>>* CanonicalExplicitDuration() { return &mExplicitDuration; }
|
||||
protected:
|
||||
|
||||
// Set to one of the valid play states.
|
||||
// This can only be changed on the main thread while holding the decoder
|
||||
// monitor. Thus, it can be safely read while holding the decoder monitor
|
||||
|
@ -44,9 +44,10 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace mozilla::layers;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::layers;
|
||||
using namespace mozilla::media;
|
||||
|
||||
#define NS_DispatchToMainThread(...) CompileError_UseAbstractThreadDispatchInstead
|
||||
|
||||
@ -162,12 +163,6 @@ static_assert(QUICK_BUFFERING_LOW_DATA_USECS <= AMPLE_AUDIO_USECS,
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// The amount of instability we tollerate in calls to
|
||||
// MediaDecoderStateMachine::UpdateEstimatedDuration(); changes of duration
|
||||
// less than this are ignored, as they're assumed to be the result of
|
||||
// instability in the duration estimation.
|
||||
static const uint64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
|
||||
|
||||
static TimeDuration UsecsToDuration(int64_t aUsecs) {
|
||||
return TimeDuration::FromMicroseconds(aUsecs);
|
||||
}
|
||||
@ -197,6 +192,11 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
||||
mStartTime(-1),
|
||||
mEndTime(-1),
|
||||
mDurationSet(false),
|
||||
mEstimatedDuration(mTaskQueue, NullableTimeUnit(),
|
||||
"MediaDecoderStateMachine::EstimatedDuration (Mirror)"),
|
||||
mExplicitDuration(mTaskQueue, Maybe<double>(),
|
||||
"MediaDecoderStateMachine::mExplicitDuration (Mirror)"),
|
||||
mObservedDuration(TimeUnit(), "MediaDecoderStateMachine::mObservedDuration"),
|
||||
mPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_LOADING,
|
||||
"MediaDecoderStateMachine::mPlayState (Mirror)"),
|
||||
mNextPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_PAUSED,
|
||||
@ -208,6 +208,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
||||
mFragmentEndTime(-1),
|
||||
mReader(aReader),
|
||||
mCurrentPosition(mTaskQueue, 0, "MediaDecoderStateMachine::mCurrentPosition (Canonical)"),
|
||||
mStreamStartTime(-1),
|
||||
mAudioStartTime(-1),
|
||||
mAudioEndTime(-1),
|
||||
mDecodedAudioEndTime(-1),
|
||||
@ -227,7 +228,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
||||
mAudioCompleted(false, "MediaDecoderStateMachine::mAudioCompleted"),
|
||||
mGotDurationFromMetaData(false),
|
||||
mDispatchedEventToDecode(false),
|
||||
mStopAudioThread(true),
|
||||
mQuickBuffering(false),
|
||||
mMinimizePreroll(false),
|
||||
mDecodeThreadWaiting(false),
|
||||
@ -294,6 +294,8 @@ MediaDecoderStateMachine::InitializationTask()
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
// Connect mirrors.
|
||||
mEstimatedDuration.Connect(mDecoder->CanonicalEstimatedDuration());
|
||||
mExplicitDuration.Connect(mDecoder->CanonicalExplicitDuration());
|
||||
mPlayState.Connect(mDecoder->CanonicalPlayState());
|
||||
mNextPlayState.Connect(mDecoder->CanonicalNextPlayState());
|
||||
mLogicallySeeking.Connect(mDecoder->CanonicalLogicallySeeking());
|
||||
@ -307,6 +309,9 @@ MediaDecoderStateMachine::InitializationTask()
|
||||
mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
|
||||
mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
|
||||
mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
|
||||
mWatchManager.Watch(mEstimatedDuration, &MediaDecoderStateMachine::RecomputeDuration);
|
||||
mWatchManager.Watch(mExplicitDuration, &MediaDecoderStateMachine::RecomputeDuration);
|
||||
mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration);
|
||||
mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
|
||||
mWatchManager.Watch(mLogicallySeeking, &MediaDecoderStateMachine::LogicallySeekingChanged);
|
||||
mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::UpdateStreamBlockingForPlayState);
|
||||
@ -358,7 +363,7 @@ void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio,
|
||||
// This logic has to mimic AudioSink closely to make sure we write
|
||||
// the exact same silences
|
||||
CheckedInt64 audioWrittenOffset = aStream->mAudioFramesWritten +
|
||||
UsecsToFrames(mInfo.mAudio.mRate, aStream->mInitialTime + mStartTime);
|
||||
UsecsToFrames(mInfo.mAudio.mRate, mStreamStartTime);
|
||||
CheckedInt64 frameOffset = UsecsToFrames(mInfo.mAudio.mRate, aAudio->mTime);
|
||||
|
||||
if (!audioWrittenOffset.isValid() ||
|
||||
@ -443,6 +448,7 @@ void MediaDecoderStateMachine::SendStreamData()
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()");
|
||||
MOZ_ASSERT(mStreamStartTime != -1);
|
||||
|
||||
DecodedStreamData* stream = GetDecodedStream();
|
||||
|
||||
@ -461,7 +467,7 @@ void MediaDecoderStateMachine::SendStreamData()
|
||||
SourceMediaStream::ADDTRACK_QUEUED);
|
||||
stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId,
|
||||
TaskQueue(), GetWakeDecoderRunnable());
|
||||
stream->mNextAudioTime = mStartTime + stream->mInitialTime;
|
||||
stream->mNextAudioTime = mStreamStartTime;
|
||||
}
|
||||
if (mInfo.HasVideo()) {
|
||||
TrackID videoTrackId = mInfo.mVideo.mTrackId;
|
||||
@ -471,11 +477,7 @@ void MediaDecoderStateMachine::SendStreamData()
|
||||
stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId,
|
||||
TaskQueue(), GetWakeDecoderRunnable());
|
||||
|
||||
// TODO: We can't initialize |mNextVideoTime| until |mStartTime|
|
||||
// is set. This is a good indication that DecodedStreamData is in
|
||||
// deep coupling with the state machine and we should move the class
|
||||
// into MediaDecoderStateMachine.
|
||||
stream->mNextVideoTime = mStartTime + stream->mInitialTime;
|
||||
stream->mNextVideoTime = mStreamStartTime;
|
||||
}
|
||||
mediaStream->FinishAddTracks();
|
||||
stream->mStreamInitialized = true;
|
||||
@ -578,7 +580,7 @@ void MediaDecoderStateMachine::SendStreamData()
|
||||
}
|
||||
endPosition = std::max(endPosition,
|
||||
mediaStream->MicrosecondsToStreamTimeRoundDown(
|
||||
stream->mNextVideoTime - stream->mInitialTime));
|
||||
stream->mNextVideoTime - mStreamStartTime));
|
||||
}
|
||||
|
||||
if (!stream->mHaveSentFinish) {
|
||||
@ -1344,15 +1346,8 @@ void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(int64_t aTime)
|
||||
NS_ASSERTION(mStartTime >= 0, "Should have positive mStartTime");
|
||||
mCurrentPosition = aTime - mStartTime;
|
||||
NS_ASSERTION(mCurrentPosition >= 0, "CurrentTime should be positive!");
|
||||
if (aTime > mEndTime) {
|
||||
NS_ASSERTION(mCurrentPosition > GetDuration(),
|
||||
"CurrentTime must be after duration if aTime > endTime!");
|
||||
DECODER_LOG("Setting new end time to %lld", aTime);
|
||||
mEndTime = aTime;
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(mDecoder, &MediaDecoder::DurationChanged);
|
||||
AbstractThread::MainThread()->Dispatch(event.forget());
|
||||
}
|
||||
mObservedDuration = std::max(mObservedDuration.Ref(),
|
||||
TimeUnit::FromMicroseconds(mCurrentPosition.Ref()));
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
|
||||
@ -1438,79 +1433,70 @@ int64_t MediaDecoderStateMachine::GetEndTime()
|
||||
return mEndTime;
|
||||
}
|
||||
|
||||
// Runnable which dispatches an event to the main thread to seek to the new
|
||||
// aSeekTarget.
|
||||
class SeekRunnable : public nsRunnable {
|
||||
public:
|
||||
SeekRunnable(MediaDecoder* aDecoder, double aSeekTarget)
|
||||
: mDecoder(aDecoder), mSeekTarget(aSeekTarget) {}
|
||||
NS_IMETHOD Run() {
|
||||
mDecoder->Seek(mSeekTarget, SeekTarget::Accurate);
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
nsRefPtr<MediaDecoder> mDecoder;
|
||||
double mSeekTarget;
|
||||
};
|
||||
|
||||
void MediaDecoderStateMachine::SetDuration(int64_t aDuration)
|
||||
void MediaDecoderStateMachine::RecomputeDuration()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread() || OnDecodeTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
|
||||
if (aDuration < 0) {
|
||||
mDurationSet = false;
|
||||
// We dispatch DurationChanged to the MediaDecoder when the duration changes
|
||||
// sometime after initialization, unless it has already been fired by the code
|
||||
// that set the new duration.
|
||||
bool fireDurationChanged = false;
|
||||
|
||||
TimeUnit duration;
|
||||
if (mExplicitDuration.Ref().isSome()) {
|
||||
double d = mExplicitDuration.Ref().ref();
|
||||
if (IsNaN(d)) {
|
||||
// We have an explicit duration (which means that we shouldn't look at
|
||||
// any other duration sources), but the duration isn't ready yet.
|
||||
return;
|
||||
}
|
||||
// We don't fire duration changed for this case because it should have
|
||||
// already been fired on the main thread when the explicit duration was set.
|
||||
duration = TimeUnit::FromSeconds(d);
|
||||
} else if (mEstimatedDuration.Ref().isSome()) {
|
||||
duration = mEstimatedDuration.Ref().ref();
|
||||
fireDurationChanged = true;
|
||||
} else if (mInfo.mMetadataDuration.isSome()) {
|
||||
duration = mInfo.mMetadataDuration.ref();
|
||||
} else if (mInfo.mMetadataEndTime.isSome() && mStartTime >= 0) {
|
||||
duration = mInfo.mMetadataEndTime.ref() - TimeUnit::FromMicroseconds(mStartTime);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (duration < mObservedDuration.Ref()) {
|
||||
duration = mObservedDuration;
|
||||
fireDurationChanged = true;
|
||||
}
|
||||
|
||||
fireDurationChanged = fireDurationChanged && duration.ToMicroseconds() != GetDuration();
|
||||
SetDuration(duration);
|
||||
|
||||
if (fireDurationChanged) {
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethodWithArg<TimeUnit>(mDecoder, &MediaDecoder::DurationChanged, duration);
|
||||
AbstractThread::MainThread()->Dispatch(event.forget());
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::SetDuration(TimeUnit aDuration)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
MOZ_ASSERT(aDuration.ToMicroseconds() >= 0);
|
||||
mDurationSet = true;
|
||||
|
||||
if (mStartTime == -1) {
|
||||
SetStartTime(0);
|
||||
}
|
||||
|
||||
if (aDuration == INT64_MAX) {
|
||||
if (aDuration.IsInfinite()) {
|
||||
mEndTime = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
mEndTime = mStartTime + aDuration;
|
||||
|
||||
if (mDecoder && mEndTime >= 0 && mEndTime < mCurrentPosition.ReadOnWrongThread()) {
|
||||
// The current playback position is now past the end of the element duration
|
||||
// the user agent must also seek to the time of the end of the media
|
||||
// resource.
|
||||
if (NS_IsMainThread()) {
|
||||
// Seek synchronously.
|
||||
mDecoder->Seek(double(mEndTime) / USECS_PER_S, SeekTarget::Accurate);
|
||||
} else {
|
||||
// Queue seek to new end position.
|
||||
nsCOMPtr<nsIRunnable> task =
|
||||
new SeekRunnable(mDecoder, double(mEndTime) / USECS_PER_S);
|
||||
AbstractThread::MainThread()->Dispatch(task.forget());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::UpdateEstimatedDuration(int64_t aDuration)
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
int64_t duration = GetDuration();
|
||||
if (aDuration != duration &&
|
||||
mozilla::Abs(aDuration - duration) > ESTIMATED_DURATION_FUZZ_FACTOR_USECS) {
|
||||
SetDuration(aDuration);
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(mDecoder, &MediaDecoder::DurationChanged);
|
||||
AbstractThread::MainThread()->Dispatch(event.forget());
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::SetMediaEndTime(int64_t aEndTime)
|
||||
{
|
||||
MOZ_ASSERT(OnDecodeTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
||||
mEndTime = aEndTime;
|
||||
mEndTime = mStartTime + aDuration.ToMicroseconds();
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::SetFragmentEndTime(int64_t aEndTime)
|
||||
@ -1799,17 +1785,6 @@ void MediaDecoderStateMachine::StopAudioThread()
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
||||
if (mStopAudioThread) {
|
||||
// Audio sink is being stopped in another thread. Wait until finished.
|
||||
while (mAudioSink) {
|
||||
mDecoder->GetReentrantMonitor().Wait();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mStopAudioThread = true;
|
||||
// Wake up audio sink so that it can reach the finish line.
|
||||
mDecoder->GetReentrantMonitor().NotifyAll();
|
||||
if (mAudioSink) {
|
||||
DECODER_LOG("Shutdown audio thread");
|
||||
mAudioSink->PrepareToShutdown();
|
||||
@ -1819,8 +1794,6 @@ void MediaDecoderStateMachine::StopAudioThread()
|
||||
}
|
||||
mAudioSink = nullptr;
|
||||
}
|
||||
// Wake up those waiting for audio sink to finish.
|
||||
mDecoder->GetReentrantMonitor().NotifyAll();
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -1922,16 +1895,9 @@ MediaDecoderStateMachine::InitiateSeek()
|
||||
mCurrentSeek.mTarget.mTime = seekTime;
|
||||
|
||||
if (mAudioCaptured) {
|
||||
// TODO: We should re-create the decoded stream after seek completed as we do
|
||||
// for audio thread since it is until then we know which position we seek to
|
||||
// as far as fast-seek is concerned. It also fix the problem where stream
|
||||
// clock seems to go backwards during seeking.
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethodWithArgs<int64_t, MediaStreamGraph*>(this,
|
||||
&MediaDecoderStateMachine::RecreateDecodedStream,
|
||||
seekTime - mStartTime,
|
||||
nullptr);
|
||||
AbstractThread::MainThread()->Dispatch(event.forget());
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArgs<MediaStreamGraph*>(
|
||||
this, &MediaDecoderStateMachine::RecreateDecodedStream, nullptr);
|
||||
AbstractThread::MainThread()->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
mDropAudioUntilNextDiscontinuity = HasAudio();
|
||||
@ -2092,11 +2058,10 @@ MediaDecoderStateMachine::StartAudioThread()
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
if (mAudioCaptured) {
|
||||
NS_ASSERTION(mStopAudioThread, "mStopAudioThread must always be true if audio is captured");
|
||||
MOZ_ASSERT(!mAudioSink);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mStopAudioThread = false;
|
||||
if (HasAudio() && !mAudioSink) {
|
||||
// The audio end time should always be at least the audio start time.
|
||||
mAudioEndTime = mAudioStartTime;
|
||||
@ -2241,6 +2206,10 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
|
||||
mInfo = aMetadata->mInfo;
|
||||
mMetadataTags = aMetadata->mTags.forget();
|
||||
|
||||
if (mInfo.mMetadataDuration.isSome() || mInfo.mMetadataEndTime.isSome()) {
|
||||
RecomputeDuration();
|
||||
}
|
||||
|
||||
if (HasVideo()) {
|
||||
DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
|
||||
mReader->IsAsync(),
|
||||
@ -2250,7 +2219,6 @@ MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
|
||||
|
||||
mDecoder->StartProgressUpdates();
|
||||
mGotDurationFromMetaData = (GetDuration() != -1) || mDurationSet;
|
||||
|
||||
if (mGotDurationFromMetaData) {
|
||||
// We have all the information required: duration and size
|
||||
// Inform the element that we've loaded the metadata.
|
||||
@ -2486,6 +2454,7 @@ MediaDecoderStateMachine::SeekCompleted()
|
||||
} else {
|
||||
newCurrentTime = video ? video->mTime : seekTime;
|
||||
}
|
||||
mStreamStartTime = newCurrentTime;
|
||||
mPlayDuration = newCurrentTime - mStartTime;
|
||||
|
||||
mDecoder->StartProgressUpdates();
|
||||
@ -2578,6 +2547,8 @@ MediaDecoderStateMachine::FinishShutdown()
|
||||
mPendingWakeDecoder = nullptr;
|
||||
|
||||
// Disconnect canonicals and mirrors before shutting down our task queue.
|
||||
mEstimatedDuration.DisconnectIfConnected();
|
||||
mExplicitDuration.DisconnectIfConnected();
|
||||
mPlayState.DisconnectIfConnected();
|
||||
mNextPlayState.DisconnectIfConnected();
|
||||
mLogicallySeeking.DisconnectIfConnected();
|
||||
@ -2621,11 +2592,6 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||
mDelayedScheduler.Reset(); // Must happen on state machine task queue.
|
||||
mDispatchedStateMachine = false;
|
||||
|
||||
// If audio is being captured, stop the audio sink if it's running
|
||||
if (mAudioCaptured) {
|
||||
StopAudioThread();
|
||||
}
|
||||
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
|
||||
|
||||
@ -2805,6 +2771,7 @@ MediaDecoderStateMachine::Reset()
|
||||
|
||||
mVideoFrameEndTime = -1;
|
||||
mDecodedVideoEndTime = -1;
|
||||
mStreamStartTime = -1;
|
||||
mAudioStartTime = -1;
|
||||
mAudioEndTime = -1;
|
||||
mDecodedAudioEndTime = -1;
|
||||
@ -2891,6 +2858,14 @@ MediaDecoderStateMachine::GetAudioClock() const
|
||||
(mAudioSink ? mAudioSink->GetPosition() : 0);
|
||||
}
|
||||
|
||||
int64_t MediaDecoderStateMachine::GetStreamClock() const
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
MOZ_ASSERT(mStreamStartTime != -1);
|
||||
return mStreamStartTime + GetDecodedStream()->GetPosition();
|
||||
}
|
||||
|
||||
int64_t MediaDecoderStateMachine::GetVideoStreamPosition() const
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
@ -2920,17 +2895,15 @@ int64_t MediaDecoderStateMachine::GetClock() const
|
||||
clock_time = mPlayDuration + mStartTime;
|
||||
} else {
|
||||
if (mAudioCaptured) {
|
||||
clock_time = mStartTime + GetDecodedStream()->GetClock();
|
||||
clock_time = GetStreamClock();
|
||||
} else if (HasAudio() && !mAudioCompleted) {
|
||||
clock_time = GetAudioClock();
|
||||
} else {
|
||||
// Audio is disabled on this system. Sync to the system clock.
|
||||
clock_time = GetVideoStreamPosition();
|
||||
}
|
||||
// Ensure the clock can never go backwards.
|
||||
// Note we allow clock going backwards in capture mode - we should fix this in bug 1162381.
|
||||
NS_ASSERTION(GetMediaTime() <= clock_time || mPlaybackRate <= 0 || mAudioCaptured,
|
||||
"Clock should go forwards.");
|
||||
NS_ASSERTION(GetMediaTime() <= clock_time || mPlaybackRate <= 0,
|
||||
"Clock should go forwards.");
|
||||
}
|
||||
|
||||
return clock_time;
|
||||
@ -3241,7 +3214,10 @@ void MediaDecoderStateMachine::SetStartTime(int64_t aStartTimeUsecs)
|
||||
// first actual audio frame we have, we'll inject silence during playback
|
||||
// to ensure the audio starts at the correct time.
|
||||
mAudioStartTime = mStartTime;
|
||||
mStreamStartTime = mStartTime;
|
||||
DECODER_LOG("Set media start time to %lld", mStartTime);
|
||||
|
||||
RecomputeDuration();
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::UpdateNextFrameStatus()
|
||||
@ -3523,6 +3499,12 @@ void MediaDecoderStateMachine::DispatchAudioCaptured()
|
||||
MOZ_ASSERT(self->OnTaskQueue());
|
||||
ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
|
||||
if (!self->mAudioCaptured) {
|
||||
// Stop the audio sink if it's running.
|
||||
self->StopAudioThread();
|
||||
// GetMediaTime() could return -1 because we haven't decoded
|
||||
// the 1st frame. But this is OK since we will update mStreamStartTime
|
||||
// again in SetStartTime().
|
||||
self->mStreamStartTime = self->GetMediaTime();
|
||||
self->mAudioCaptured = true;
|
||||
self->ScheduleStateMachine();
|
||||
}
|
||||
@ -3538,7 +3520,7 @@ void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
if (!GetDecodedStream()) {
|
||||
RecreateDecodedStream(mCurrentPosition.ReadOnWrongThread(), aStream->Graph());
|
||||
RecreateDecodedStream(aStream->Graph());
|
||||
}
|
||||
mDecodedStream.Connect(aStream, aFinishWhenEnded);
|
||||
DispatchAudioCaptured();
|
||||
@ -3577,13 +3559,11 @@ void MediaDecoderStateMachine::UpdateStreamBlockingForStateMachinePlaying()
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::RecreateDecodedStream(int64_t aInitialTime,
|
||||
MediaStreamGraph* aGraph)
|
||||
void MediaDecoderStateMachine::RecreateDecodedStream(MediaStreamGraph* aGraph)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
DECODER_LOG("RecreateDecodedStream aInitialTime=%lld!", aInitialTime);
|
||||
mDecodedStream.RecreateData(aInitialTime, aGraph);
|
||||
mDecodedStream.RecreateData(aGraph);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -171,9 +171,8 @@ private:
|
||||
|
||||
// Recreates mDecodedStream. Call this to create mDecodedStream at first,
|
||||
// and when seeking, to ensure a new stream is set up with fresh buffers.
|
||||
// aInitialTime is relative to mStartTime.
|
||||
// Decoder monitor must be held.
|
||||
void RecreateDecodedStream(int64_t aInitialTime, MediaStreamGraph* aGraph);
|
||||
void RecreateDecodedStream(MediaStreamGraph* aGraph);
|
||||
|
||||
void Shutdown();
|
||||
public:
|
||||
@ -205,19 +204,7 @@ public:
|
||||
// media metadata. The decoder monitor must be obtained before calling this.
|
||||
// aDuration is in microseconds.
|
||||
// A value of INT64_MAX will be treated as infinity.
|
||||
void SetDuration(int64_t aDuration);
|
||||
|
||||
// Called while decoding metadata to set the end time of the media
|
||||
// resource. The decoder monitor must be obtained before calling this.
|
||||
// aEndTime is in microseconds.
|
||||
void SetMediaEndTime(int64_t aEndTime);
|
||||
|
||||
// Called from main thread to update the duration with an estimated value.
|
||||
// The duration is only changed if its significantly different than the
|
||||
// the current duration, as the incoming duration is an estimate and so
|
||||
// often is unstable as more data is read and the estimate is updated.
|
||||
// Can result in a durationchangeevent. aDuration is in microseconds.
|
||||
void UpdateEstimatedDuration(int64_t aDuration);
|
||||
void SetDuration(media::TimeUnit aDuration);
|
||||
|
||||
// Functions used by assertions to ensure we're calling things
|
||||
// on the appropriate threads.
|
||||
@ -549,6 +536,8 @@ protected:
|
||||
// Called on the state machine thread.
|
||||
int64_t GetAudioClock() const;
|
||||
|
||||
int64_t GetStreamClock() const;
|
||||
|
||||
// Get the video stream position, taking the |playbackRate| change into
|
||||
// account. This is a position in the media, not the duration of the playback
|
||||
// so far.
|
||||
@ -899,11 +888,24 @@ public:
|
||||
// It will be set to -1 if the duration is infinite
|
||||
int64_t mEndTime;
|
||||
|
||||
// Recomputes the canonical duration from various sources.
|
||||
void RecomputeDuration();
|
||||
|
||||
// Will be set when SetDuration has been called with a value != -1
|
||||
// mDurationSet false doesn't indicate that we do not have a valid duration
|
||||
// as mStartTime and mEndTime could have been set separately.
|
||||
bool mDurationSet;
|
||||
|
||||
// The duration according to the demuxer's current estimate, mirrored from the main thread.
|
||||
Mirror<media::NullableTimeUnit> mEstimatedDuration;
|
||||
|
||||
// The duration explicitly set by JS, mirrored from the main thread.
|
||||
Mirror<Maybe<double>> mExplicitDuration;
|
||||
|
||||
// The highest timestamp that our position has reached. Monotonically
|
||||
// increasing.
|
||||
Watchable<media::TimeUnit> mObservedDuration;
|
||||
|
||||
// The current play state and next play state, mirrored from the main thread.
|
||||
Mirror<MediaDecoder::PlayState> mPlayState;
|
||||
Mirror<MediaDecoder::PlayState> mNextPlayState;
|
||||
@ -999,6 +1001,9 @@ protected:
|
||||
public:
|
||||
AbstractCanonical<int64_t>* CanonicalCurrentPosition() { return &mCurrentPosition; }
|
||||
protected:
|
||||
// The presentation time of the first audio/video frame that is sent to the
|
||||
// media stream.
|
||||
int64_t mStreamStartTime;
|
||||
|
||||
// The presentation time of the first audio frame that was played in
|
||||
// microseconds. We can add this to the audio stream position to determine
|
||||
@ -1186,8 +1191,8 @@ protected:
|
||||
}
|
||||
|
||||
// True if we shouldn't play our audio (but still write it to any capturing
|
||||
// streams). When this is true, mStopAudioThread is always true and
|
||||
// the audio thread will never start again after it has stopped.
|
||||
// streams). When this is true, the audio thread will never start again after
|
||||
// it has stopped.
|
||||
bool mAudioCaptured;
|
||||
|
||||
// True if an event to notify about a change in the playback
|
||||
@ -1215,10 +1220,6 @@ protected:
|
||||
// unneccessary runnables, since the decode thread runs in a loop.
|
||||
bool mDispatchedEventToDecode;
|
||||
|
||||
// False while audio thread should be running. Accessed state machine
|
||||
// and audio threads. Syncrhonised by decoder monitor.
|
||||
bool mStopAudioThread;
|
||||
|
||||
// If this is true while we're in buffering mode, we can exit early,
|
||||
// as it's likely we may be able to playback. This happens when we enter
|
||||
// buffering mode soon after the decode starts, because the decode-ahead
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "mozilla/CDMProxy.h"
|
||||
#endif
|
||||
|
||||
using namespace mozilla::media;
|
||||
|
||||
using mozilla::layers::Image;
|
||||
using mozilla::layers::LayerManager;
|
||||
using mozilla::layers::LayersBackend;
|
||||
@ -332,8 +334,7 @@ MediaFormatReader::OnDemuxerInitDone(nsresult)
|
||||
|
||||
int64_t duration = std::max(videoDuration, audioDuration);
|
||||
if (duration != -1) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(duration);
|
||||
mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
|
||||
}
|
||||
|
||||
mSeekable = mDemuxer->IsSeekable();
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "ImageTypes.h"
|
||||
#include "MediaData.h"
|
||||
#include "StreamBuffer.h" // for TrackID
|
||||
#include "TimeUnits.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -348,6 +349,14 @@ public:
|
||||
VideoInfo mVideo;
|
||||
AudioInfo mAudio;
|
||||
|
||||
// If the metadata includes a duration, we store it here.
|
||||
media::NullableTimeUnit mMetadataDuration;
|
||||
|
||||
// The Ogg reader tries to kinda-sorta compute the duration by seeking to the
|
||||
// end and determining the timestamp of the last frame. This isn't useful as
|
||||
// a duration until we know the start time, so we need to track it separately.
|
||||
media::NullableTimeUnit mMetadataEndTime;
|
||||
|
||||
EncryptionInfo mCrypto;
|
||||
};
|
||||
|
||||
|
@ -33,6 +33,8 @@
|
||||
#include "nsProxyRelease.h"
|
||||
#include "nsIContentPolicy.h"
|
||||
|
||||
using mozilla::media::TimeUnit;
|
||||
|
||||
PRLogModuleInfo* gMediaResourceLog;
|
||||
#define RESOURCE_LOG(msg, ...) MOZ_LOG(gMediaResourceLog, mozilla::LogLevel::Debug, \
|
||||
(msg, ##__VA_ARGS__))
|
||||
@ -236,37 +238,6 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
|
||||
dataIsBounded = true;
|
||||
}
|
||||
|
||||
if (mOffset == 0) {
|
||||
// Look for duration headers from known Ogg content systems.
|
||||
// In the case of multiple options for obtaining the duration
|
||||
// the order of precedence is:
|
||||
// 1) The Media resource metadata if possible (done by the decoder itself).
|
||||
// 2) Content-Duration message header.
|
||||
// 3) X-AMZ-Meta-Content-Duration.
|
||||
// 4) X-Content-Duration.
|
||||
// 5) Perform a seek in the decoder to find the value.
|
||||
nsAutoCString durationText;
|
||||
nsresult ec = NS_OK;
|
||||
rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("Content-Duration"), durationText);
|
||||
if (NS_FAILED(rv)) {
|
||||
rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText);
|
||||
}
|
||||
if (NS_FAILED(rv)) {
|
||||
rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText);
|
||||
}
|
||||
|
||||
// If there is a Content-Duration header with a valid value, record
|
||||
// the duration.
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
double duration = durationText.ToDouble(&ec);
|
||||
if (ec == NS_OK && duration >= 0) {
|
||||
mDecoder->SetDuration(duration);
|
||||
// We know the resource must be bounded.
|
||||
dataIsBounded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assume Range requests have a bounded upper limit unless the
|
||||
// Content-Range header tells us otherwise.
|
||||
bool boundedSeekLimit = true;
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "mozilla/net/RtspChannelChild.h"
|
||||
#endif
|
||||
using namespace mozilla::net;
|
||||
using namespace mozilla::media;
|
||||
|
||||
PRLogModuleInfo* gRtspMediaResourceLog;
|
||||
#define RTSP_LOG(msg, ...) MOZ_LOG(gRtspMediaResourceLog, mozilla::LogLevel::Debug, \
|
||||
@ -725,7 +726,6 @@ RtspMediaResource::OnConnected(uint8_t aTrackIdx,
|
||||
// Not live stream.
|
||||
mIsLiveStream = false;
|
||||
mDecoder->SetInfinite(false);
|
||||
mDecoder->SetDuration((double)(durationUs) / USECS_PER_S);
|
||||
} else {
|
||||
// Live stream.
|
||||
// Check the preference "media.realtime_decoder.enabled".
|
||||
|
@ -143,7 +143,8 @@ public:
|
||||
Watchable(const T& aInitialValue, const char* aName)
|
||||
: WatchTarget(aName), mValue(aInitialValue) {}
|
||||
|
||||
operator const T&() const { return mValue; }
|
||||
const T& Ref() const { return mValue; }
|
||||
operator const T&() const { return Ref(); }
|
||||
Watchable& operator=(const T& aNewValue)
|
||||
{
|
||||
if (aNewValue != mValue) {
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "Intervals.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/dom/TimeRanges.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -25,7 +26,6 @@ struct nsTArray_CopyChooser<mozilla::media::TimeIntervals>
|
||||
};
|
||||
|
||||
namespace mozilla {
|
||||
namespace media {
|
||||
|
||||
// Number of microseconds per second. 1e6.
|
||||
static const int64_t USECS_PER_S = 1000000;
|
||||
@ -33,6 +33,8 @@ static const int64_t USECS_PER_S = 1000000;
|
||||
// Number of microseconds per millisecond.
|
||||
static const int64_t USECS_PER_MS = 1000;
|
||||
|
||||
namespace media {
|
||||
|
||||
// Number of nanoseconds per second. 1e9.
|
||||
static const int64_t NSECS_PER_S = 1000000000;
|
||||
|
||||
@ -203,6 +205,8 @@ private:
|
||||
CheckedInt64 mValue;
|
||||
};
|
||||
|
||||
typedef Maybe<TimeUnit> NullableTimeUnit;
|
||||
|
||||
typedef Interval<TimeUnit> TimeInterval;
|
||||
|
||||
class TimeIntervals : public IntervalSet<TimeUnit>
|
||||
|
@ -137,12 +137,6 @@ CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate);
|
||||
// overflow while calulating the conversion.
|
||||
CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate);
|
||||
|
||||
// Number of microseconds per second. 1e6.
|
||||
static const int64_t USECS_PER_S = 1000000;
|
||||
|
||||
// Number of microseconds per millisecond.
|
||||
static const int64_t USECS_PER_MS = 1000;
|
||||
|
||||
// Converts milliseconds to seconds.
|
||||
#define MS_TO_SECONDS(ms) ((double)(ms) / (PR_MSEC_PER_SEC))
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
namespace mozilla {
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::media;
|
||||
|
||||
typedef mozilla::layers::Image Image;
|
||||
typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
|
||||
@ -55,8 +56,7 @@ nsresult AndroidMediaReader::ReadMetadata(MediaInfo* aInfo,
|
||||
int64_t durationUs;
|
||||
mPlugin->GetDuration(mPlugin, &durationUs);
|
||||
if (durationUs) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(durationUs);
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(durationUs));
|
||||
}
|
||||
|
||||
if (mPlugin->HasVideo(mPlugin)) {
|
||||
|
@ -19,6 +19,8 @@
|
||||
// 1152 is most memory efficient.
|
||||
#define MAX_AUDIO_FRAMES 128
|
||||
|
||||
using namespace mozilla::media;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
@ -413,11 +415,11 @@ AppleMP3Reader::ReadMetadata(MediaInfo* aInfo,
|
||||
aInfo->mAudio.mChannels = mAudioChannels;
|
||||
}
|
||||
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDuration = mMP3FrameParser.GetDuration();
|
||||
mDecoder->SetMediaDuration(mDuration);
|
||||
}
|
||||
// This special snowflake reader doesn't seem to set *aInfo = mInfo like all
|
||||
// the others. Yuck.
|
||||
mDuration = mMP3FrameParser.GetDuration();
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mDuration));
|
||||
aInfo->mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mDuration));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
#include "MediaResource.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
using namespace mozilla::media;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
||||
@ -194,10 +196,6 @@ DirectShowReader::ReadMetadata(MediaInfo* aInfo,
|
||||
mInfo.mAudio.mBitDepth = format.wBitsPerSample;
|
||||
mBytesPerSample = format.wBitsPerSample / 8;
|
||||
|
||||
*aInfo = mInfo;
|
||||
// Note: The SourceFilter strips ID3v2 tags out of the stream.
|
||||
*aTags = nullptr;
|
||||
|
||||
// Begin decoding!
|
||||
hr = mControl->Run();
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
|
||||
@ -207,8 +205,7 @@ DirectShowReader::ReadMetadata(MediaInfo* aInfo,
|
||||
|
||||
int64_t duration = mMP3FrameParser.GetDuration();
|
||||
if (SUCCEEDED(hr)) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(duration);
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
|
||||
}
|
||||
|
||||
LOG("Successfully initialized DirectShow MP3 decoder.");
|
||||
@ -218,6 +215,10 @@ DirectShowReader::ReadMetadata(MediaInfo* aInfo,
|
||||
RefTimeToUsecs(duration),
|
||||
mBytesPerSample);
|
||||
|
||||
*aInfo = mInfo;
|
||||
// Note: The SourceFilter strips ID3v2 tags out of the stream.
|
||||
*aTags = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ using mozilla::layers::Image;
|
||||
using mozilla::layers::LayerManager;
|
||||
using mozilla::layers::ImageContainer;
|
||||
using mozilla::layers::LayersBackend;
|
||||
using mozilla::media::TimeUnit;
|
||||
|
||||
PRLogModuleInfo* GetDemuxerLog() {
|
||||
static PRLogModuleInfo* log = nullptr;
|
||||
@ -431,8 +432,7 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo,
|
||||
duration = mDemuxer->Duration();
|
||||
}
|
||||
if (duration != -1) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(duration);
|
||||
mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
|
||||
}
|
||||
|
||||
*aInfo = mInfo;
|
||||
|
@ -25,6 +25,7 @@ namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
using namespace layers;
|
||||
using namespace media;
|
||||
|
||||
// Un-comment to enable logging of seek bisections.
|
||||
//#define SEEK_LOGGING
|
||||
@ -470,10 +471,9 @@ nsresult GStreamerReader::ReadMetadata(MediaInfo* aInfo,
|
||||
if (isMP3 && mMP3FrameParser.IsMP3()) {
|
||||
// The MP3FrameParser has reported a duration; use that over the gstreamer
|
||||
// reported duration for inter-platform consistency.
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mUseParserDuration = true;
|
||||
mLastParserDuration = mMP3FrameParser.GetDuration();
|
||||
mDecoder->SetMediaDuration(mLastParserDuration);
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mLastParserDuration));
|
||||
} else {
|
||||
LOG(LogLevel::Debug, "querying duration");
|
||||
// Otherwise use the gstreamer duration.
|
||||
@ -485,10 +485,9 @@ nsresult GStreamerReader::ReadMetadata(MediaInfo* aInfo,
|
||||
if (gst_element_query_duration(GST_ELEMENT(mPlayBin),
|
||||
&format, &duration) && format == GST_FORMAT_TIME) {
|
||||
#endif
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
LOG(LogLevel::Debug, "have duration %" GST_TIME_FORMAT, GST_TIME_ARGS(duration));
|
||||
duration = GST_TIME_AS_USECONDS (duration);
|
||||
mDecoder->SetMediaDuration(duration);
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,14 +20,16 @@ extern PRLogModuleInfo* GetMediaSourceLog();
|
||||
#define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("MediaSourceDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
|
||||
#define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("MediaSourceDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
|
||||
|
||||
using namespace mozilla::media;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class SourceBufferDecoder;
|
||||
|
||||
MediaSourceDecoder::MediaSourceDecoder(dom::HTMLMediaElement* aElement)
|
||||
: mMediaSource(nullptr)
|
||||
, mMediaSourceDuration(UnspecifiedNaN<double>())
|
||||
{
|
||||
SetExplicitDuration(UnspecifiedNaN<double>());
|
||||
Init(aElement);
|
||||
}
|
||||
|
||||
@ -169,46 +171,14 @@ MediaSourceDecoder::IsExpectingMoreData()
|
||||
return !mReader->IsEnded();
|
||||
}
|
||||
|
||||
class DurationChangedRunnable : public nsRunnable {
|
||||
public:
|
||||
DurationChangedRunnable(MediaSourceDecoder* aDecoder,
|
||||
double aOldDuration,
|
||||
double aNewDuration)
|
||||
: mDecoder(aDecoder)
|
||||
, mOldDuration(aOldDuration)
|
||||
, mNewDuration(aNewDuration)
|
||||
{ }
|
||||
|
||||
NS_IMETHOD Run() override final {
|
||||
mDecoder->DurationChanged(mOldDuration, mNewDuration);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<MediaSourceDecoder> mDecoder;
|
||||
double mOldDuration;
|
||||
double mNewDuration;
|
||||
};
|
||||
|
||||
void
|
||||
MediaSourceDecoder::DurationChanged(double aOldDuration, double aNewDuration)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// Run the MediaSource duration changed algorithm
|
||||
if (mMediaSource) {
|
||||
mMediaSource->DurationChange(aOldDuration, aNewDuration);
|
||||
}
|
||||
// Run the MediaElement duration changed algorithm
|
||||
MediaDecoder::DurationChanged();
|
||||
}
|
||||
|
||||
void
|
||||
MediaSourceDecoder::SetInitialDuration(int64_t aDuration)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// Only use the decoded duration if one wasn't already
|
||||
// set.
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
if (!mMediaSource || !IsNaN(mMediaSourceDuration)) {
|
||||
if (!mMediaSource || !IsNaN(ExplicitDuration())) {
|
||||
return;
|
||||
}
|
||||
double duration = aDuration;
|
||||
@ -222,8 +192,9 @@ MediaSourceDecoder::SetInitialDuration(int64_t aDuration)
|
||||
void
|
||||
MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
double oldDuration = mMediaSourceDuration;
|
||||
double oldDuration = ExplicitDuration();
|
||||
if (aDuration >= 0) {
|
||||
int64_t checkedDuration;
|
||||
if (NS_FAILED(SecondsToUsecs(aDuration, checkedDuration))) {
|
||||
@ -231,39 +202,17 @@ MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalActio
|
||||
// We want a very bigger number, but not infinity.
|
||||
checkedDuration = INT64_MAX - 1;
|
||||
}
|
||||
GetStateMachine()->SetDuration(checkedDuration);
|
||||
mMediaSourceDuration = aDuration;
|
||||
SetExplicitDuration(aDuration);
|
||||
} else {
|
||||
GetStateMachine()->SetDuration(INT64_MAX);
|
||||
mMediaSourceDuration = PositiveInfinity<double>();
|
||||
SetExplicitDuration(PositiveInfinity<double>());
|
||||
}
|
||||
if (mReader) {
|
||||
mReader->SetMediaSourceDuration(mMediaSourceDuration);
|
||||
mReader->SetMediaSourceDuration(ExplicitDuration());
|
||||
}
|
||||
ScheduleDurationChange(oldDuration, aDuration, aAction);
|
||||
}
|
||||
|
||||
void
|
||||
MediaSourceDecoder::ScheduleDurationChange(double aOldDuration,
|
||||
double aNewDuration,
|
||||
MSRangeRemovalAction aAction)
|
||||
{
|
||||
if (aAction == MSRangeRemovalAction::SKIP) {
|
||||
if (NS_IsMainThread()) {
|
||||
MediaDecoder::DurationChanged();
|
||||
} else {
|
||||
nsCOMPtr<nsIRunnable> task =
|
||||
NS_NewRunnableMethod(this, &MediaDecoder::DurationChanged);
|
||||
NS_DispatchToMainThread(task);
|
||||
}
|
||||
} else {
|
||||
if (NS_IsMainThread()) {
|
||||
DurationChanged(aOldDuration, aNewDuration);
|
||||
} else {
|
||||
nsCOMPtr<nsIRunnable> task =
|
||||
new DurationChangedRunnable(this, aOldDuration, aNewDuration);
|
||||
NS_DispatchToMainThread(task);
|
||||
}
|
||||
MediaDecoder::DurationChanged(TimeUnit::FromSeconds(ExplicitDuration()));
|
||||
if (mMediaSource && aAction != MSRangeRemovalAction::SKIP) {
|
||||
mMediaSource->DurationChange(oldDuration, aDuration);
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,7 +220,7 @@ double
|
||||
MediaSourceDecoder::GetMediaSourceDuration()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
return mMediaSourceDuration;
|
||||
return ExplicitDuration();
|
||||
}
|
||||
|
||||
void
|
||||
@ -329,7 +278,7 @@ double
|
||||
MediaSourceDecoder::GetDuration()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
return mMediaSourceDuration;
|
||||
return ExplicitDuration();
|
||||
}
|
||||
|
||||
already_AddRefed<SourceBufferDecoder>
|
||||
|
@ -62,7 +62,6 @@ public:
|
||||
void SetInitialDuration(int64_t aDuration);
|
||||
void SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction);
|
||||
double GetMediaSourceDuration();
|
||||
void DurationChanged(double aOldDuration, double aNewDuration);
|
||||
|
||||
// Called whenever a TrackBuffer has new data appended or a new decoder
|
||||
// initializes. Safe to call from any thread.
|
||||
@ -94,18 +93,12 @@ public:
|
||||
|
||||
private:
|
||||
void DoSetMediaSourceDuration(double aDuration);
|
||||
void ScheduleDurationChange(double aOldDuration,
|
||||
double aNewDuration,
|
||||
MSRangeRemovalAction aAction);
|
||||
|
||||
// The owning MediaSource holds a strong reference to this decoder, and
|
||||
// calls Attach/DetachMediaSource on this decoder to set and clear
|
||||
// mMediaSource.
|
||||
dom::MediaSource* mMediaSource;
|
||||
nsRefPtr<MediaSourceReader> mReader;
|
||||
|
||||
// Protected by GetReentrantMonitor()
|
||||
double mMediaSourceDuration;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -1137,7 +1137,7 @@ MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
|
||||
mInfo.mCrypto.AddInitData(info.mCrypto);
|
||||
MSE_DEBUG("audio reader=%p duration=%lld",
|
||||
mAudioSourceDecoder.get(),
|
||||
mAudioSourceDecoder->GetReader()->GetDecoder()->GetMediaDuration());
|
||||
mInfo.mMetadataDuration.isSome() ? mInfo.mMetadataDuration.ref().ToMicroseconds() : -1);
|
||||
}
|
||||
|
||||
if (mVideoTrack) {
|
||||
@ -1150,7 +1150,7 @@ MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
|
||||
mInfo.mCrypto.AddInitData(info.mCrypto);
|
||||
MSE_DEBUG("video reader=%p duration=%lld",
|
||||
GetVideoReader(),
|
||||
GetVideoReader()->GetDecoder()->GetMediaDuration());
|
||||
mInfo.mMetadataDuration.isSome() ? mInfo.mMetadataDuration.ref().ToMicroseconds() : -1);
|
||||
}
|
||||
|
||||
*aInfo = mInfo;
|
||||
|
@ -37,7 +37,6 @@ SourceBufferDecoder::SourceBufferDecoder(MediaResource* aResource,
|
||||
, mParentDecoder(aParentDecoder)
|
||||
, mReader(nullptr)
|
||||
, mTimestampOffset(aTimestampOffset)
|
||||
, mMediaDuration(-1)
|
||||
, mRealMediaDuration(0)
|
||||
, mTrimmedOffset(-1)
|
||||
{
|
||||
@ -64,12 +63,6 @@ SourceBufferDecoder::NotifyBytesConsumed(int64_t aBytes, int64_t aOffset)
|
||||
MSE_DEBUG("UNIMPLEMENTED");
|
||||
}
|
||||
|
||||
int64_t
|
||||
SourceBufferDecoder::GetMediaDuration()
|
||||
{
|
||||
return mMediaDuration;
|
||||
}
|
||||
|
||||
VideoFrameContainer*
|
||||
SourceBufferDecoder::GetVideoFrameContainer()
|
||||
{
|
||||
@ -120,12 +113,6 @@ SourceBufferDecoder::RemoveMediaTracks()
|
||||
MSE_DEBUG("UNIMPLEMENTED");
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferDecoder::SetMediaEndTime(int64_t aTime)
|
||||
{
|
||||
MSE_DEBUG("UNIMPLEMENTED");
|
||||
}
|
||||
|
||||
bool
|
||||
SourceBufferDecoder::HasInitializationData()
|
||||
{
|
||||
@ -179,12 +166,6 @@ SourceBufferDecoder::NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
|
||||
return mParentDecoder->NotifyDecodedFrames(aParsed, aDecoded, aDropped);
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferDecoder::SetMediaDuration(int64_t aDuration)
|
||||
{
|
||||
mMediaDuration = aDuration;
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferDecoder::SetRealMediaDuration(int64_t aDuration)
|
||||
{
|
||||
|
@ -36,7 +36,7 @@ public:
|
||||
virtual bool IsTransportSeekable() final override;
|
||||
virtual bool OnDecodeTaskQueue() const final override;
|
||||
virtual bool OnStateMachineTaskQueue() const final override;
|
||||
virtual int64_t GetMediaDuration() final override;
|
||||
virtual int64_t GetMediaDuration() final override { MOZ_ASSERT_UNREACHABLE(""); return -1; };
|
||||
virtual layers::ImageContainer* GetImageContainer() final override;
|
||||
virtual MediaDecoderOwner* GetOwner() final override;
|
||||
virtual SourceBufferResource* GetResource() const final override;
|
||||
@ -54,8 +54,6 @@ public:
|
||||
virtual void OnReadMetadataCompleted() final override;
|
||||
virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) final override;
|
||||
virtual void RemoveMediaTracks() final override;
|
||||
virtual void SetMediaDuration(int64_t aDuration) final override;
|
||||
virtual void SetMediaEndTime(int64_t aTime) final override;
|
||||
virtual void SetMediaSeekable(bool aMediaSeekable) final override;
|
||||
virtual void UpdateEstimatedMediaDuration(int64_t aDuration) final override;
|
||||
virtual bool HasInitializationData() final override;
|
||||
@ -150,8 +148,6 @@ private:
|
||||
nsRefPtr<MediaDecoderReader> mReader;
|
||||
// in microseconds
|
||||
int64_t mTimestampOffset;
|
||||
// mMediaDuration contains the apparent buffer duration, excluding trimmed data.
|
||||
int64_t mMediaDuration;
|
||||
// mRealMediaDuration contains the real buffer duration, including trimmed data.
|
||||
int64_t mRealMediaDuration;
|
||||
// in seconds
|
||||
|
@ -809,7 +809,9 @@ TrackBuffer::CompleteInitializeDecoder(SourceBufferDecoder* aDecoder)
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t duration = aDecoder->GetMediaDuration();
|
||||
|
||||
int64_t duration = mInfo.mMetadataDuration.isSome()
|
||||
? mInfo.mMetadataDuration.ref().ToMicroseconds() : -1;
|
||||
if (!duration) {
|
||||
// Treat a duration of 0 as infinity
|
||||
duration = -1;
|
||||
|
@ -24,6 +24,7 @@ extern "C" {
|
||||
#include "gfx2DGlue.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::media;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -289,9 +290,8 @@ void OggReader::SetupTargetSkeleton(SkeletonState* aSkeletonState)
|
||||
BuildSerialList(tracks);
|
||||
int64_t duration = 0;
|
||||
if (NS_SUCCEEDED(aSkeletonState->GetDuration(tracks, duration))) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(duration);
|
||||
LOG(LogLevel::Debug, ("Got duration from Skeleton index %lld", duration));
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -474,10 +474,8 @@ nsresult OggReader::ReadMetadata(MediaInfo* aInfo,
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
if (mDecoder->GetMediaDuration() == -1 &&
|
||||
!mDecoder->IsShutdown() &&
|
||||
resource->GetLength() >= 0 &&
|
||||
mDecoder->IsMediaSeekable())
|
||||
if (mInfo.mMetadataDuration.isNothing() && !mDecoder->IsShutdown() &&
|
||||
resource->GetLength() >= 0 && mDecoder->IsMediaSeekable())
|
||||
{
|
||||
// We didn't get a duration from the index or a Content-Duration header.
|
||||
// Seek to the end of file to find the end time.
|
||||
@ -491,7 +489,7 @@ nsresult OggReader::ReadMetadata(MediaInfo* aInfo,
|
||||
endTime = RangeEndTime(length);
|
||||
}
|
||||
if (endTime != -1) {
|
||||
mDecoder->SetMediaEndTime(endTime);
|
||||
mInfo.mMetadataEndTime.emplace(TimeUnit::FromMicroseconds(endTime));
|
||||
LOG(LogLevel::Debug, ("Got Ogg duration from seeking to end %lld", endTime));
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@
|
||||
|
||||
using namespace android;
|
||||
using namespace mozilla::layers;
|
||||
using namespace mozilla::media;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -712,8 +713,7 @@ MediaCodecReader::HandleResourceAllocated()
|
||||
}
|
||||
int64_t duration = audioDuration > videoDuration ? audioDuration : videoDuration;
|
||||
if (duration >= INT64_C(0)) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(duration);
|
||||
mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
|
||||
}
|
||||
|
||||
// Video track's frame sizes will not overflow. Activate the video track.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#define MAX_VIDEO_DECODE_SECONDS 0.1
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::media;
|
||||
using namespace android;
|
||||
|
||||
namespace mozilla {
|
||||
@ -281,16 +282,14 @@ void MediaOmxReader::HandleResourceAllocated()
|
||||
|
||||
if (mLastParserDuration >= 0) {
|
||||
// Prefer the parser duration if we have it.
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(mLastParserDuration);
|
||||
mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(mLastParserDuration));
|
||||
} else {
|
||||
// MP3 parser failed to find a duration.
|
||||
// Set the total duration (the max of the audio and video track).
|
||||
int64_t durationUs;
|
||||
mOmxDecoder->GetDuration(&durationUs);
|
||||
if (durationUs) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(durationUs);
|
||||
mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(durationUs));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "gfx2DGlue.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::media;
|
||||
|
||||
RawReader::RawReader(AbstractMediaDecoder* aDecoder)
|
||||
: MediaDecoderReader(aDecoder),
|
||||
@ -97,10 +98,8 @@ nsresult RawReader::ReadMetadata(MediaInfo* aInfo,
|
||||
|
||||
int64_t length = resource->GetLength();
|
||||
if (length != -1) {
|
||||
ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->SetMediaDuration(USECS_PER_S *
|
||||
(length - sizeof(RawVideoHeader)) /
|
||||
(mFrameSize * mFrameRate));
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromSeconds((length - sizeof(RawVideoHeader)) /
|
||||
(mFrameSize * mFrameRate)));
|
||||
}
|
||||
|
||||
*aInfo = mInfo;
|
||||
|
@ -1,2 +1 @@
|
||||
X-Content-Duration: 1.96
|
||||
Cache-Control: no-store
|
||||
|
@ -1,2 +1 @@
|
||||
X-Content-Duration: 9000
|
||||
Cache-Control: no-store
|
||||
|
@ -1,25 +0,0 @@
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
var file = Components.classes["@mozilla.org/file/directory_service;1"].
|
||||
getService(Components.interfaces.nsIProperties).
|
||||
get("CurWorkD", Components.interfaces.nsILocalFile);
|
||||
var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
|
||||
createInstance(Components.interfaces.nsIBinaryInputStream);
|
||||
var paths = "tests/dom/media/test/320x240.ogv";
|
||||
var split = paths.split("/");
|
||||
for(var i = 0; i < split.length; ++i) {
|
||||
file.append(split[i]);
|
||||
}
|
||||
fis.init(file, -1, -1, false);
|
||||
bis.setInputStream(fis);
|
||||
var bytes = bis.readBytes(bis.available());
|
||||
response.setHeader("Content-Duration", "0.233", false);
|
||||
response.setHeader("Content-Type", "video/ogg", false);
|
||||
response.write(bytes, bytes.length);
|
||||
// Make this request async to prevent a default Content-Length from being provided.
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
bis.close();
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
var file = Components.classes["@mozilla.org/file/directory_service;1"].
|
||||
getService(Components.interfaces.nsIProperties).
|
||||
get("CurWorkD", Components.interfaces.nsILocalFile);
|
||||
var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
|
||||
createInstance(Components.interfaces.nsIBinaryInputStream);
|
||||
var paths = "tests/dom/media/test/320x240.ogv";
|
||||
var split = paths.split("/");
|
||||
for(var i = 0; i < split.length; ++i) {
|
||||
file.append(split[i]);
|
||||
}
|
||||
fis.init(file, -1, -1, false);
|
||||
bis.setInputStream(fis);
|
||||
var bytes = bis.readBytes(bis.available());
|
||||
response.setHeader("x-amz-meta-content-duration", "0.233", false);
|
||||
response.setHeader("Content-Type", "video/ogg", false);
|
||||
response.write(bytes, bytes.length);
|
||||
// Make this request async to prevent a default Content-Length from being provided.
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
bis.close();
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
var file = Components.classes["@mozilla.org/file/directory_service;1"].
|
||||
getService(Components.interfaces.nsIProperties).
|
||||
get("CurWorkD", Components.interfaces.nsILocalFile);
|
||||
var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
|
||||
createInstance(Components.interfaces.nsIBinaryInputStream);
|
||||
var paths = "tests/dom/media/test/320x240.ogv";
|
||||
var split = paths.split("/");
|
||||
for(var i = 0; i < split.length; ++i) {
|
||||
file.append(split[i]);
|
||||
}
|
||||
fis.init(file, -1, -1, false);
|
||||
bis.setInputStream(fis);
|
||||
var bytes = bis.readBytes(bis.available());
|
||||
response.setStatusLine(request.httpVersion, 200, "Content Follows");
|
||||
response.setHeader("Content-Type", "video/ogg", false);
|
||||
response.write(bytes, bytes.length);
|
||||
// Make this request async to prevent a default Content-Length from being provided.
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
bis.close();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user