mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 01:35:35 +00:00
Merge m-c to b2ginbound, a=merge CLOSED TREE
This commit is contained in:
commit
d584388e6c
@ -13,6 +13,7 @@
|
||||
#import "mozAccessible.h"
|
||||
#import "mozActionElements.h"
|
||||
#import "mozHTMLAccessible.h"
|
||||
#import "mozTableAccessible.h"
|
||||
#import "mozTextAccessible.h"
|
||||
|
||||
using namespace mozilla;
|
||||
@ -62,6 +63,15 @@ AccessibleWrap::GetNativeType ()
|
||||
if (IsXULTabpanels())
|
||||
return [mozPaneAccessible class];
|
||||
|
||||
if (IsTable())
|
||||
return [mozTableAccessible class];
|
||||
|
||||
if (IsTableRow())
|
||||
return [mozTableRowAccessible class];
|
||||
|
||||
if (IsTableCell())
|
||||
return [mozTableCellAccessible class];
|
||||
|
||||
return GetTypeFromRole(Role());
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
@ -100,12 +110,13 @@ AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
|
||||
|
||||
uint32_t eventType = aEvent->GetEventType();
|
||||
|
||||
// ignore everything but focus-changed, value-changed, caret and selection
|
||||
// events for now.
|
||||
// ignore everything but focus-changed, value-changed, caret, selection
|
||||
// and document load complete events for now.
|
||||
if (eventType != nsIAccessibleEvent::EVENT_FOCUS &&
|
||||
eventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
|
||||
eventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
|
||||
eventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED)
|
||||
eventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED &&
|
||||
eventType != nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE)
|
||||
return NS_OK;
|
||||
|
||||
Accessible* accessible = aEvent->GetAccessible();
|
||||
@ -243,6 +254,9 @@ a11y::FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType)
|
||||
case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
|
||||
[aNativeAcc selectedTextDidChange];
|
||||
break;
|
||||
case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
|
||||
[aNativeAcc documentLoadComplete];
|
||||
break;
|
||||
}
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "Platform.h"
|
||||
#include "ProxyAccessible.h"
|
||||
#include "mozTableAccessible.h"
|
||||
|
||||
#include "nsAppShell.h"
|
||||
|
||||
@ -38,7 +39,18 @@ void
|
||||
ProxyCreated(ProxyAccessible* aProxy, uint32_t)
|
||||
{
|
||||
// Pass in dummy state for now as retrieving proxy state requires IPC.
|
||||
Class type = GetTypeFromRole(aProxy->Role());
|
||||
// Note that we can use ProxyAccessible::IsTable* functions here because they
|
||||
// do not use IPC calls but that might change after bug 1210477.
|
||||
Class type;
|
||||
if (aProxy->IsTable())
|
||||
type = [mozTableAccessible class];
|
||||
else if (aProxy->IsTableRow())
|
||||
type = [mozTableRowAccessible class];
|
||||
else if (aProxy->IsTableCell())
|
||||
type = [mozTableCellAccessible class];
|
||||
else
|
||||
type = GetTypeFromRole(aProxy->Role());
|
||||
|
||||
uintptr_t accWrap = reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY;
|
||||
mozAccessible* mozWrapper = [[type alloc] initWithAccessible:accWrap];
|
||||
aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
|
||||
|
@ -21,6 +21,7 @@ UNIFIED_SOURCES += [
|
||||
'mozActionElements.mm',
|
||||
'mozDocAccessible.mm',
|
||||
'mozHTMLAccessible.mm',
|
||||
'mozTableAccessible.mm',
|
||||
'mozTextAccessible.mm',
|
||||
'Platform.mm',
|
||||
'RootAccessibleWrap.mm',
|
||||
|
@ -134,6 +134,7 @@ static const uintptr_t IS_PROXY = 1;
|
||||
- (void)didReceiveFocus;
|
||||
- (void)valueDidChange;
|
||||
- (void)selectedTextDidChange;
|
||||
- (void)documentLoadComplete;
|
||||
|
||||
// internal method to retrieve a child at a given index.
|
||||
- (id)childAt:(uint32_t)i;
|
||||
|
@ -280,10 +280,6 @@ ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray)
|
||||
return [NSArray array];
|
||||
|
||||
static NSArray* generalAttributes = nil;
|
||||
static NSArray* tableAttrs = nil;
|
||||
static NSArray* tableRowAttrs = nil;
|
||||
static NSArray* tableCellAttrs = nil;
|
||||
NSMutableArray* tempArray = nil;
|
||||
|
||||
if (!generalAttributes) {
|
||||
// standard attributes that are shared and supported by all generic elements.
|
||||
@ -308,39 +304,8 @@ ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray)
|
||||
nil];
|
||||
}
|
||||
|
||||
if (!tableAttrs) {
|
||||
tempArray = [[NSMutableArray alloc] initWithArray:generalAttributes];
|
||||
[tempArray addObject:NSAccessibilityRowCountAttribute];
|
||||
[tempArray addObject:NSAccessibilityColumnCountAttribute];
|
||||
[tempArray addObject:NSAccessibilityRowsAttribute];
|
||||
tableAttrs = [[NSArray alloc] initWithArray:tempArray];
|
||||
[tempArray release];
|
||||
}
|
||||
if (!tableRowAttrs) {
|
||||
tempArray = [[NSMutableArray alloc] initWithArray:generalAttributes];
|
||||
[tempArray addObject:NSAccessibilityIndexAttribute];
|
||||
tableRowAttrs = [[NSArray alloc] initWithArray:tempArray];
|
||||
[tempArray release];
|
||||
}
|
||||
if (!tableCellAttrs) {
|
||||
tempArray = [[NSMutableArray alloc] initWithArray:generalAttributes];
|
||||
[tempArray addObject:NSAccessibilityRowIndexRangeAttribute];
|
||||
[tempArray addObject:NSAccessibilityColumnIndexRangeAttribute];
|
||||
[tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute];
|
||||
[tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute];
|
||||
tableCellAttrs = [[NSArray alloc] initWithArray:tempArray];
|
||||
[tempArray release];
|
||||
}
|
||||
|
||||
NSArray* objectAttributes = generalAttributes;
|
||||
|
||||
if ((accWrap && accWrap->IsTable()) || (proxy && proxy->IsTable()))
|
||||
objectAttributes = tableAttrs;
|
||||
else if ((accWrap && accWrap->IsTableRow()) || (proxy && proxy->IsTableRow()))
|
||||
objectAttributes = tableRowAttrs;
|
||||
else if ((accWrap && accWrap->IsTableCell()) || (proxy && proxy->IsTableCell()))
|
||||
objectAttributes = tableCellAttrs;
|
||||
|
||||
NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames];
|
||||
if ([additionalAttributes count])
|
||||
objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes];
|
||||
@ -425,114 +390,6 @@ ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray)
|
||||
if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
|
||||
return [self help];
|
||||
|
||||
if (accWrap) {
|
||||
if (accWrap->IsTable()) {
|
||||
TableAccessible* table = accWrap->AsTable();
|
||||
if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
|
||||
return @(table->RowCount());
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
|
||||
return @(table->ColCount());
|
||||
if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
|
||||
// Create a new array with the list of table rows.
|
||||
NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
|
||||
uint32_t totalCount = accWrap->ChildCount();
|
||||
for (uint32_t i = 0; i < totalCount; i++) {
|
||||
if (accWrap->GetChildAt(i)->IsTableRow()) {
|
||||
mozAccessible* curNative =
|
||||
GetNativeFromGeckoAccessible(accWrap->GetChildAt(i));
|
||||
if (curNative)
|
||||
[nativeArray addObject:GetObjectOrRepresentedView(curNative)];
|
||||
}
|
||||
}
|
||||
return nativeArray;
|
||||
}
|
||||
} else if (accWrap->IsTableRow()) {
|
||||
if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
|
||||
// Count the number of rows before that one to obtain the row index.
|
||||
uint32_t index = 0;
|
||||
Accessible* parent = accWrap->Parent();
|
||||
if (parent) {
|
||||
for (int32_t i = accWrap->IndexInParent() - 1; i >= 0; i--) {
|
||||
if (parent->GetChildAt(i)->IsTableRow()) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [NSNumber numberWithUnsignedInteger:index];
|
||||
}
|
||||
} else if (accWrap->IsTableCell()) {
|
||||
TableCellAccessible* cell = accWrap->AsTableCell();
|
||||
if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
|
||||
return [NSValue valueWithRange:NSMakeRange(cell->RowIdx(),
|
||||
cell->RowExtent())];
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
|
||||
return [NSValue valueWithRange:NSMakeRange(cell->ColIdx(),
|
||||
cell->ColExtent())];
|
||||
if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
|
||||
nsAutoTArray<Accessible*, 10> headerCells;
|
||||
cell->RowHeaderCells(&headerCells);
|
||||
return ConvertToNSArray(headerCells);
|
||||
}
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
|
||||
nsAutoTArray<Accessible*, 10> headerCells;
|
||||
cell->ColHeaderCells(&headerCells);
|
||||
return ConvertToNSArray(headerCells);
|
||||
}
|
||||
}
|
||||
} else if (proxy) {
|
||||
if (proxy->IsTable()) {
|
||||
if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
|
||||
return @(proxy->TableRowCount());
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
|
||||
return @(proxy->TableColumnCount());
|
||||
if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
|
||||
// Create a new array with the list of table rows.
|
||||
NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
|
||||
uint32_t totalCount = proxy->ChildrenCount();
|
||||
for (uint32_t i = 0; i < totalCount; i++) {
|
||||
if (proxy->ChildAt(i)->IsTableRow()) {
|
||||
mozAccessible* curNative =
|
||||
GetNativeFromProxy(proxy->ChildAt(i));
|
||||
if (curNative)
|
||||
[nativeArray addObject:GetObjectOrRepresentedView(curNative)];
|
||||
}
|
||||
}
|
||||
return nativeArray;
|
||||
}
|
||||
} else if (proxy->IsTableRow()) {
|
||||
if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
|
||||
// Count the number of rows before that one to obtain the row index.
|
||||
uint32_t index = 0;
|
||||
ProxyAccessible* parent = proxy->Parent();
|
||||
if (parent) {
|
||||
for (int32_t i = proxy->IndexInParent() - 1; i >= 0; i--) {
|
||||
if (parent->ChildAt(i)->IsTableRow()) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [NSNumber numberWithUnsignedInteger:index];
|
||||
}
|
||||
} else if (proxy->IsTableCell()) {
|
||||
if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
|
||||
return [NSValue valueWithRange:NSMakeRange(proxy->RowIdx(),
|
||||
proxy->RowExtent())];
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
|
||||
return [NSValue valueWithRange:NSMakeRange(proxy->ColIdx(),
|
||||
proxy->ColExtent())];
|
||||
if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
|
||||
nsTArray<ProxyAccessible*> headerCells;
|
||||
proxy->RowHeaderCells(&headerCells);
|
||||
return ConvertToNSArray(headerCells);
|
||||
}
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
|
||||
nsTArray<ProxyAccessible*> headerCells;
|
||||
proxy->ColHeaderCells(&headerCells);
|
||||
return ConvertToNSArray(headerCells);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (mRole) {
|
||||
case roles::MATHML_ROOT:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
|
||||
@ -1202,6 +1059,14 @@ struct RoleDescrComparator
|
||||
// Do nothing. mozTextAccessible will.
|
||||
}
|
||||
|
||||
- (void)documentLoadComplete
|
||||
{
|
||||
id realSelf = GetObjectOrRepresentedView(self);
|
||||
NSAccessibilityPostNotification(realSelf, NSAccessibilityFocusedUIElementChangedNotification);
|
||||
NSAccessibilityPostNotification(realSelf, @"AXLoadComplete");
|
||||
NSAccessibilityPostNotification(realSelf, @"AXLayoutComplete");
|
||||
}
|
||||
|
||||
- (NSString*)help
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
28
accessible/mac/mozTableAccessible.h
Normal file
28
accessible/mac/mozTableAccessible.h
Normal file
@ -0,0 +1,28 @@
|
||||
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:expandtab:shiftwidth=2:tabstop=2:
|
||||
*/
|
||||
/* 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"
|
||||
|
||||
@interface mozTablePartAccessible : mozAccessible
|
||||
- (BOOL)isLayoutTablePart;
|
||||
- (NSString*)role;
|
||||
@end
|
||||
|
||||
@interface mozTableAccessible : mozTablePartAccessible
|
||||
- (NSArray*)additionalAccessibilityAttributeNames;
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute;
|
||||
@end
|
||||
|
||||
@interface mozTableRowAccessible : mozTablePartAccessible
|
||||
- (NSArray*)additionalAccessibilityAttributeNames;
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute;
|
||||
@end
|
||||
|
||||
@interface mozTableCellAccessible : mozTablePartAccessible
|
||||
- (NSArray*)additionalAccessibilityAttributeNames;
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute;
|
||||
@end
|
240
accessible/mac/mozTableAccessible.mm
Normal file
240
accessible/mac/mozTableAccessible.mm
Normal file
@ -0,0 +1,240 @@
|
||||
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:expandtab:shiftwidth=2:tabstop=2:
|
||||
*/
|
||||
/* 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 "mozTableAccessible.h"
|
||||
#import "nsCocoaUtils.h"
|
||||
|
||||
@implementation mozTablePartAccessible
|
||||
- (BOOL)isLayoutTablePart;
|
||||
{
|
||||
if (Accessible* accWrap = [self getGeckoAccessible]) {
|
||||
while (accWrap) {
|
||||
if (accWrap->IsTable()) {
|
||||
return accWrap->AsTable()->IsProbablyLayoutTable();
|
||||
}
|
||||
accWrap = accWrap->Parent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ProxyAccessible* proxy = [self getProxyAccessible]) {
|
||||
while (proxy) {
|
||||
if (proxy->IsTable()) {
|
||||
return proxy->TableIsProbablyForLayout();
|
||||
}
|
||||
proxy = proxy->Parent();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
- (NSString*)role
|
||||
{
|
||||
return [self isLayoutTablePart] ? NSAccessibilityGroupRole : [super role];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation mozTableAccessible
|
||||
- (NSArray*)additionalAccessibilityAttributeNames
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
|
||||
if ([self isLayoutTablePart]) {
|
||||
return additionalAttributes;
|
||||
}
|
||||
|
||||
static NSArray* tableAttrs = nil;
|
||||
if (!tableAttrs) {
|
||||
NSMutableArray* tempArray = [NSMutableArray new];
|
||||
[tempArray addObject:NSAccessibilityRowCountAttribute];
|
||||
[tempArray addObject:NSAccessibilityColumnCountAttribute];
|
||||
[tempArray addObject:NSAccessibilityRowsAttribute];
|
||||
tableAttrs = [[NSArray alloc] initWithArray:tempArray];
|
||||
[tempArray release];
|
||||
}
|
||||
|
||||
return [additionalAttributes arrayByAddingObjectsFromArray:tableAttrs];
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute
|
||||
{
|
||||
if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
|
||||
TableAccessible* table = accWrap->AsTable();
|
||||
if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
|
||||
return @(table->RowCount());
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
|
||||
return @(table->ColCount());
|
||||
if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
|
||||
// Create a new array with the list of table rows.
|
||||
NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
|
||||
uint32_t totalCount = accWrap->ChildCount();
|
||||
for (uint32_t i = 0; i < totalCount; i++) {
|
||||
if (accWrap->GetChildAt(i)->IsTableRow()) {
|
||||
mozAccessible* curNative =
|
||||
GetNativeFromGeckoAccessible(accWrap->GetChildAt(i));
|
||||
if (curNative)
|
||||
[nativeArray addObject:GetObjectOrRepresentedView(curNative)];
|
||||
}
|
||||
}
|
||||
return nativeArray;
|
||||
}
|
||||
} else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
|
||||
if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
|
||||
return @(proxy->TableRowCount());
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
|
||||
return @(proxy->TableColumnCount());
|
||||
if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
|
||||
// Create a new array with the list of table rows.
|
||||
NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
|
||||
uint32_t totalCount = proxy->ChildrenCount();
|
||||
for (uint32_t i = 0; i < totalCount; i++) {
|
||||
if (proxy->ChildAt(i)->IsTableRow()) {
|
||||
mozAccessible* curNative =
|
||||
GetNativeFromProxy(proxy->ChildAt(i));
|
||||
if (curNative)
|
||||
[nativeArray addObject:GetObjectOrRepresentedView(curNative)];
|
||||
}
|
||||
}
|
||||
return nativeArray;
|
||||
}
|
||||
}
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation mozTableRowAccessible
|
||||
- (NSArray*)additionalAccessibilityAttributeNames
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
|
||||
if ([self isLayoutTablePart]) {
|
||||
return additionalAttributes;
|
||||
}
|
||||
|
||||
static NSArray* tableRowAttrs = nil;
|
||||
if (!tableRowAttrs) {
|
||||
NSMutableArray* tempArray = [NSMutableArray new];
|
||||
[tempArray addObject:NSAccessibilityIndexAttribute];
|
||||
tableRowAttrs = [[NSArray alloc] initWithArray:tempArray];
|
||||
[tempArray release];
|
||||
}
|
||||
|
||||
return [additionalAttributes arrayByAddingObjectsFromArray:tableRowAttrs];
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute
|
||||
{
|
||||
if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
|
||||
if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
|
||||
// Count the number of rows before that one to obtain the row index.
|
||||
uint32_t index = 0;
|
||||
Accessible* parent = accWrap->Parent();
|
||||
if (parent) {
|
||||
for (int32_t i = accWrap->IndexInParent() - 1; i >= 0; i--) {
|
||||
if (parent->GetChildAt(i)->IsTableRow()) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [NSNumber numberWithUnsignedInteger:index];
|
||||
}
|
||||
} else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
|
||||
if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
|
||||
// Count the number of rows before that one to obtain the row index.
|
||||
uint32_t index = 0;
|
||||
ProxyAccessible* parent = proxy->Parent();
|
||||
if (parent) {
|
||||
for (int32_t i = proxy->IndexInParent() - 1; i >= 0; i--) {
|
||||
if (parent->ChildAt(i)->IsTableRow()) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [NSNumber numberWithUnsignedInteger:index];
|
||||
}
|
||||
}
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation mozTableCellAccessible
|
||||
- (NSArray*)additionalAccessibilityAttributeNames
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
|
||||
if ([self isLayoutTablePart]) {
|
||||
return additionalAttributes;
|
||||
}
|
||||
|
||||
static NSArray* tableCellAttrs = nil;
|
||||
if (!tableCellAttrs) {
|
||||
NSMutableArray* tempArray = [NSMutableArray new];
|
||||
[tempArray addObject:NSAccessibilityRowIndexRangeAttribute];
|
||||
[tempArray addObject:NSAccessibilityColumnIndexRangeAttribute];
|
||||
[tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute];
|
||||
[tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute];
|
||||
tableCellAttrs = [[NSArray alloc] initWithArray:tempArray];
|
||||
[tempArray release];
|
||||
}
|
||||
|
||||
return [additionalAttributes arrayByAddingObjectsFromArray:tableCellAttrs];
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute
|
||||
{
|
||||
if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
|
||||
TableCellAccessible* cell = accWrap->AsTableCell();
|
||||
if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
|
||||
return [NSValue valueWithRange:NSMakeRange(cell->RowIdx(),
|
||||
cell->RowExtent())];
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
|
||||
return [NSValue valueWithRange:NSMakeRange(cell->ColIdx(),
|
||||
cell->ColExtent())];
|
||||
if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
|
||||
nsAutoTArray<Accessible*, 10> headerCells;
|
||||
cell->RowHeaderCells(&headerCells);
|
||||
return ConvertToNSArray(headerCells);
|
||||
}
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
|
||||
nsAutoTArray<Accessible*, 10> headerCells;
|
||||
cell->ColHeaderCells(&headerCells);
|
||||
return ConvertToNSArray(headerCells);
|
||||
}
|
||||
} else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
|
||||
if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
|
||||
return [NSValue valueWithRange:NSMakeRange(proxy->RowIdx(),
|
||||
proxy->RowExtent())];
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
|
||||
return [NSValue valueWithRange:NSMakeRange(proxy->ColIdx(),
|
||||
proxy->ColExtent())];
|
||||
if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
|
||||
nsTArray<ProxyAccessible*> headerCells;
|
||||
proxy->RowHeaderCells(&headerCells);
|
||||
return ConvertToNSArray(headerCells);
|
||||
}
|
||||
if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
|
||||
nsTArray<ProxyAccessible*> headerCells;
|
||||
proxy->ColHeaderCells(&headerCells);
|
||||
return ConvertToNSArray(headerCells);
|
||||
}
|
||||
}
|
||||
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
}
|
||||
@end
|
@ -14,8 +14,8 @@
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>26.0</em:minVersion>
|
||||
<em:maxVersion>30.0</em:maxVersion>
|
||||
<em:minVersion>44.0a1</em:minVersion>
|
||||
<em:maxVersion>45.0</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
|
@ -1174,12 +1174,10 @@ pref("dom.ipc.plugins.sandbox-level.flash", 0);
|
||||
// This controls the strength of the Windows content process sandbox for testing
|
||||
// purposes. This will require a restart.
|
||||
// On windows these levels are:
|
||||
// 0 - sandbox with USER_NON_ADMIN access token level
|
||||
// 1 - level 0 plus low integrity
|
||||
// 2 - a policy that we can reasonably call an effective sandbox
|
||||
// 3 - an equivalent basic policy to the Chromium renderer processes
|
||||
// See - security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
|
||||
// SetSecurityLevelForContentProcess() for what the different settings mean.
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
pref("security.sandbox.content.level", 1);
|
||||
pref("security.sandbox.content.level", 2);
|
||||
#else
|
||||
pref("security.sandbox.content.level", 0);
|
||||
#endif
|
||||
|
@ -1471,9 +1471,11 @@ nsContextMenu.prototype = {
|
||||
},
|
||||
|
||||
copyLink: function() {
|
||||
// If we're in a view source tab, remove the view-source: prefix
|
||||
let linkURL = this.linkURL.replace(/^view-source:/, "");
|
||||
var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
|
||||
getService(Ci.nsIClipboardHelper);
|
||||
clipboard.copyString(this.linkURL);
|
||||
clipboard.copyString(linkURL);
|
||||
},
|
||||
|
||||
///////////////
|
||||
|
@ -103,6 +103,9 @@ static RedirEntry kRedirMap[] = {
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "customizing", "chrome://browser/content/customizableui/aboutCustomizing.xul",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{
|
||||
"debugging", "chrome://devtools/content/aboutdebugging/aboutdebugging.xhtml",
|
||||
nsIAboutModule::ALLOW_SCRIPT },
|
||||
{ "loopconversation", "chrome://browser/content/loop/conversation.html",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::ALLOW_SCRIPT |
|
||||
|
@ -116,6 +116,7 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
|
||||
#endif
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "app-manager", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "customizing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "debugging", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "looppanel", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "loopconversation", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
|
||||
|
@ -13,3 +13,4 @@ support-files =
|
||||
[browser_ext_tabs_update.js]
|
||||
[browser_ext_windows_update.js]
|
||||
[browser_ext_contentscript_connect.js]
|
||||
[browser_ext_tab_runtimeConnect.js]
|
||||
|
@ -0,0 +1,71 @@
|
||||
add_task(function* () {
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["tabs"]
|
||||
},
|
||||
|
||||
background: function() {
|
||||
var messages_received = [];
|
||||
|
||||
var tabId;
|
||||
|
||||
browser.runtime.onConnect.addListener((port) => {
|
||||
browser.test.assertTrue(!!port, "tab to background port received");
|
||||
browser.test.assertEq("tab-connection-name", port.name, "port name should be defined and equal to connectInfo.name")
|
||||
browser.test.assertTrue(!!port.sender.tab, "port.sender.tab should be defined");
|
||||
browser.test.assertEq(tabId, port.sender.tab.id, "port.sender.tab.id should be equal to the expected tabId");
|
||||
|
||||
port.onMessage.addListener((msg) => {
|
||||
messages_received.push(msg);
|
||||
|
||||
if (messages_received.length == 1) {
|
||||
browser.test.assertEq("tab to background port message", msg, "'tab to background' port message received");
|
||||
port.postMessage("background to tab port message");
|
||||
}
|
||||
|
||||
if (messages_received.length == 2) {
|
||||
browser.test.assertTrue(!!msg.tabReceived, "'background to tab' reply port message received");
|
||||
browser.test.assertEq("background to tab port message", msg.tabReceived, "reply port content contains the message received");
|
||||
|
||||
browser.test.notifyPass("tabRuntimeConnect.pass");
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
browser.tabs.create({
|
||||
url: "tab.html"
|
||||
}, (tab) => { tabId = tab.id });
|
||||
},
|
||||
|
||||
files: {
|
||||
"tab.js": function() {
|
||||
var port = browser.runtime.connect({ name: "tab-connection-name"});
|
||||
port.postMessage("tab to background port message");
|
||||
port.onMessage.addListener((msg) => {
|
||||
port.postMessage({ tabReceived: msg });
|
||||
});
|
||||
},
|
||||
"tab.html": `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>test tab extension page</title>
|
||||
<meta charset="utf-8">
|
||||
<script src="tab.js" async></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>test tab extension page</h1>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
}
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("tabRuntimeConnect.pass");
|
||||
yield extension.unload();
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
});
|
@ -0,0 +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/. -->
|
||||
|
||||
<!ENTITY aboutDebugging.title "about:debugging">
|
||||
<!ENTITY aboutDebugging.addons "Add-ons">
|
||||
<!ENTITY aboutDebugging.addonDebugging.label "Enable add-on debugging">
|
||||
<!ENTITY aboutDebugging.addonDebugging.tooltip "Turning this on will allow you to debug add-ons and various other parts of the browser chrome">
|
||||
<!ENTITY aboutDebugging.workers "Workers">
|
@ -0,0 +1,12 @@
|
||||
# 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/.
|
||||
|
||||
debug = Debug
|
||||
|
||||
extensions = Extensions
|
||||
serviceWorkers = Service Workers
|
||||
sharedWorkers = Shared Workers
|
||||
otherWorkers = Other Workers
|
||||
|
||||
nothing = Nothing yet.
|
@ -28,6 +28,8 @@
|
||||
locale/browser/browser.properties (%chrome/browser/browser.properties)
|
||||
locale/browser/browser-pocket.properties (%chrome/browser/browser-pocket.properties)
|
||||
locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
|
||||
locale/browser/devtools/aboutdebugging.dtd (%chrome/browser/devtools/aboutdebugging.dtd)
|
||||
locale/browser/devtools/aboutdebugging.properties (%chrome/browser/devtools/aboutdebugging.properties)
|
||||
locale/browser/devtools/animationinspector.dtd (%chrome/browser/devtools/animationinspector.dtd)
|
||||
locale/browser/devtools/animationinspector.properties (%chrome/browser/devtools/animationinspector.properties)
|
||||
locale/browser/devtools/appcacheutils.properties (%chrome/browser/devtools/appcacheutils.properties)
|
||||
|
@ -915,20 +915,24 @@ public class DoCommand {
|
||||
int icon = R.drawable.ateamlogo;
|
||||
long when = System.currentTimeMillis();
|
||||
|
||||
Notification notification = new Notification(icon, tickerText, when);
|
||||
|
||||
notification.flags |= (Notification.FLAG_INSISTENT | Notification.FLAG_AUTO_CANCEL);
|
||||
notification.defaults |= Notification.DEFAULT_SOUND;
|
||||
notification.defaults |= Notification.DEFAULT_VIBRATE;
|
||||
notification.defaults |= Notification.DEFAULT_LIGHTS;
|
||||
|
||||
Context context = contextWrapper.getApplicationContext();
|
||||
|
||||
// Intent to launch an activity when the extended text is clicked
|
||||
Intent intent2 = new Intent(contextWrapper, SUTAgentAndroid.class);
|
||||
PendingIntent launchIntent = PendingIntent.getActivity(context, 0, intent2, 0);
|
||||
|
||||
notification.setLatestEventInfo(context, tickerText, expandedText, launchIntent);
|
||||
Notification notification = new Notification.Builder(context)
|
||||
.setContentTitle(tickerText)
|
||||
.setContentText(expandedText)
|
||||
.setSmallIcon(icon)
|
||||
.setWhen(when)
|
||||
.setContentIntent(launchIntent)
|
||||
.build();
|
||||
|
||||
notification.flags |= (Notification.FLAG_INSISTENT | Notification.FLAG_AUTO_CANCEL);
|
||||
notification.defaults |= Notification.DEFAULT_SOUND;
|
||||
notification.defaults |= Notification.DEFAULT_VIBRATE;
|
||||
notification.defaults |= Notification.DEFAULT_LIGHTS;
|
||||
|
||||
notificationManager.notify(1959, notification);
|
||||
}
|
||||
|
@ -222,20 +222,25 @@ public class RunCmdThread extends Thread
|
||||
int icon = R.drawable.ateamlogo;
|
||||
long when = System.currentTimeMillis();
|
||||
|
||||
Notification notification = new Notification(icon, tickerText, when);
|
||||
|
||||
notification.flags |= (Notification.FLAG_INSISTENT | Notification.FLAG_AUTO_CANCEL);
|
||||
notification.defaults |= Notification.DEFAULT_SOUND;
|
||||
notification.defaults |= Notification.DEFAULT_VIBRATE;
|
||||
notification.defaults |= Notification.DEFAULT_LIGHTS;
|
||||
|
||||
Context context = svc.getApplicationContext();
|
||||
|
||||
// Intent to launch an activity when the extended text is clicked
|
||||
Intent intent2 = new Intent(svc, SUTAgentAndroid.class);
|
||||
PendingIntent launchIntent = PendingIntent.getActivity(context, 0, intent2, 0);
|
||||
|
||||
notification.setLatestEventInfo(context, tickerText, expandedText, launchIntent);
|
||||
|
||||
Notification notification = new Notification.Builder(context)
|
||||
.setSmallIcon(icon)
|
||||
.setContentTitle(tickerText)
|
||||
.setContentText(expandedText)
|
||||
.setContentIntent(launchIntent)
|
||||
.setWhen(when)
|
||||
.build();
|
||||
|
||||
notification.flags |= (Notification.FLAG_INSISTENT | Notification.FLAG_AUTO_CANCEL);
|
||||
notification.defaults |= Notification.DEFAULT_SOUND;
|
||||
notification.defaults |= Notification.DEFAULT_VIBRATE;
|
||||
notification.defaults |= Notification.DEFAULT_LIGHTS;
|
||||
|
||||
notificationManager.notify(1959, notification);
|
||||
}
|
||||
|
@ -15,8 +15,13 @@
|
||||
#include "nsMemory.h"
|
||||
#include "nsStringBuffer.h"
|
||||
|
||||
#include "mozilla/dom/StructuredCloneTags.h"
|
||||
// for mozilla::dom::workers::kJSPrincipalsDebugToken
|
||||
#include "mozilla/dom/workers/Workers.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::ipc;
|
||||
|
||||
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||
nsJSPrincipals::AddRef()
|
||||
@ -85,7 +90,7 @@ JSPrincipals::dump()
|
||||
nsAutoCString str;
|
||||
static_cast<nsJSPrincipals *>(this)->GetScriptLocation(str);
|
||||
fprintf(stderr, "nsIPrincipal (%p) = %s\n", static_cast<void*>(this), str.get());
|
||||
} else if (debugToken == mozilla::dom::workers::kJSPrincipalsDebugToken) {
|
||||
} else if (debugToken == dom::workers::kJSPrincipalsDebugToken) {
|
||||
fprintf(stderr, "Web Worker principal singleton (%p)\n", this);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
@ -95,4 +100,104 @@ JSPrincipals::dump()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* static */ bool
|
||||
nsJSPrincipals::ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader,
|
||||
JSPrincipals** aOutPrincipals)
|
||||
{
|
||||
uint32_t tag;
|
||||
uint32_t unused;
|
||||
if (!JS_ReadUint32Pair(aReader, &tag, &unused)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(tag == SCTAG_DOM_NULL_PRINCIPAL ||
|
||||
tag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
|
||||
tag == SCTAG_DOM_CONTENT_PRINCIPAL)) {
|
||||
xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
nsJSPrincipals::ReadKnownPrincipalType(JSContext* aCx,
|
||||
JSStructuredCloneReader* aReader,
|
||||
uint32_t aTag,
|
||||
JSPrincipals** aOutPrincipals)
|
||||
{
|
||||
MOZ_ASSERT(aTag == SCTAG_DOM_NULL_PRINCIPAL ||
|
||||
aTag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
|
||||
aTag == SCTAG_DOM_CONTENT_PRINCIPAL);
|
||||
|
||||
if (NS_WARN_IF(!NS_IsMainThread())) {
|
||||
xpc::Throw(aCx, NS_ERROR_UNCATCHABLE_EXCEPTION);
|
||||
return false;
|
||||
}
|
||||
|
||||
PrincipalInfo info;
|
||||
if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) {
|
||||
info = SystemPrincipalInfo();
|
||||
} else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) {
|
||||
info = NullPrincipalInfo();
|
||||
} else {
|
||||
uint32_t suffixLength, specLength;
|
||||
if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoCString suffix;
|
||||
suffix.SetLength(suffixLength);
|
||||
if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoCString spec;
|
||||
spec.SetLength(specLength);
|
||||
if (!JS_ReadBytes(aReader, spec.BeginWriting(), specLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OriginAttributes attrs;
|
||||
attrs.PopulateFromSuffix(suffix);
|
||||
info = ContentPrincipalInfo(attrs, spec);
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrincipal> prin = PrincipalInfoToPrincipal(info, &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return false;
|
||||
}
|
||||
|
||||
*aOutPrincipals = get(prin.forget().take());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter)
|
||||
{
|
||||
PrincipalInfo info;
|
||||
if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) {
|
||||
xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.type() == PrincipalInfo::TNullPrincipalInfo) {
|
||||
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0);
|
||||
}
|
||||
if (info.type() == PrincipalInfo::TSystemPrincipalInfo) {
|
||||
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(info.type() == PrincipalInfo::TContentPrincipalInfo);
|
||||
const ContentPrincipalInfo& cInfo = info;
|
||||
nsAutoCString suffix;
|
||||
cInfo.attrs().CreateSuffix(suffix);
|
||||
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) &&
|
||||
JS_WriteUint32Pair(aWriter, suffix.Length(), cInfo.spec().Length()) &&
|
||||
JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) &&
|
||||
JS_WriteBytes(aWriter, cInfo.spec().get(), cInfo.spec().Length());
|
||||
}
|
||||
|
@ -16,6 +16,17 @@ public:
|
||||
static bool Subsume(JSPrincipals *jsprin, JSPrincipals *other);
|
||||
static void Destroy(JSPrincipals *jsprin);
|
||||
|
||||
/* JSReadPrincipalsOp for nsJSPrincipals */
|
||||
static bool ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader,
|
||||
JSPrincipals** aOutPrincipals);
|
||||
|
||||
static bool ReadKnownPrincipalType(JSContext* aCx,
|
||||
JSStructuredCloneReader* aReader,
|
||||
uint32_t aTag,
|
||||
JSPrincipals** aOutPrincipals);
|
||||
|
||||
bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) final;
|
||||
|
||||
/*
|
||||
* Get a weak reference to nsIPrincipal associated with the given JS
|
||||
* principal, and vice-versa.
|
||||
|
78
devtools/client/aboutdebugging/aboutdebugging.css
Normal file
78
devtools/client/aboutdebugging/aboutdebugging.css
Normal file
@ -0,0 +1,78 @@
|
||||
/* 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/. */
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2, h3, h4 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* Category tabs */
|
||||
|
||||
.category {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tab {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.tab:not(.active) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Prefs */
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Targets */
|
||||
|
||||
.targets {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.target {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.target-logo {
|
||||
height: 24px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.target-logo:not([src]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.target-details {
|
||||
flex: 1;
|
||||
}
|
97
devtools/client/aboutdebugging/aboutdebugging.js
Normal file
97
devtools/client/aboutdebugging/aboutdebugging.js
Normal file
@ -0,0 +1,97 @@
|
||||
/* 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/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
/* global AddonsComponent, DebuggerClient, DebuggerServer, React,
|
||||
RuntimesComponent, WorkersComponent */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { loader } = Components.utils.import(
|
||||
"resource://gre/modules/devtools/shared/Loader.jsm", {});
|
||||
|
||||
loader.lazyRequireGetter(this, "AddonsComponent",
|
||||
"devtools/client/aboutdebugging/components/addons", true);
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/main", true);
|
||||
loader.lazyRequireGetter(this, "DebuggerServer",
|
||||
"devtools/server/main", true);
|
||||
loader.lazyRequireGetter(this, "WorkersComponent",
|
||||
"devtools/client/aboutdebugging/components/workers", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
|
||||
let AboutDebugging = {
|
||||
_categories: null,
|
||||
get categories() {
|
||||
// If needed, initialize the list of available categories.
|
||||
if (!this._categories) {
|
||||
let elements = document.querySelectorAll(".category");
|
||||
this._categories = Array.map(elements, element => {
|
||||
let value = element.getAttribute("value");
|
||||
element.addEventListener("click", this.showTab.bind(this, value));
|
||||
return value;
|
||||
});
|
||||
}
|
||||
return this._categories;
|
||||
},
|
||||
|
||||
showTab(category) {
|
||||
// If no category was specified, try the URL hash.
|
||||
if (!category) {
|
||||
category = location.hash.substr(1);
|
||||
}
|
||||
// If no corresponding category can be found, use the first available.
|
||||
let categories = this.categories;
|
||||
if (categories.indexOf(category) < 0) {
|
||||
category = categories[0];
|
||||
}
|
||||
// Show the corresponding tab and hide the others.
|
||||
document.querySelector(".tab.active").classList.remove("active");
|
||||
document.querySelector("#tab-" + category).classList.add("active");
|
||||
document.querySelector(".category[selected]").removeAttribute("selected");
|
||||
document.querySelector(".category[value=" + category + "]")
|
||||
.setAttribute("selected", "true");
|
||||
location.hash = "#" + category;
|
||||
},
|
||||
|
||||
init() {
|
||||
// Show the first available tab.
|
||||
this.showTab();
|
||||
window.addEventListener("hashchange", () => this.showTab());
|
||||
|
||||
// Link checkboxes to prefs.
|
||||
let elements = document.querySelectorAll("input[type=checkbox][data-pref]");
|
||||
Array.map(elements, element => {
|
||||
let pref = element.dataset.pref;
|
||||
let updatePref = () => {
|
||||
Services.prefs.setBoolPref(pref, element.checked);
|
||||
};
|
||||
let updateCheckbox = () => {
|
||||
element.checked = Services.prefs.getBoolPref(pref);
|
||||
};
|
||||
element.addEventListener("change", updatePref, false);
|
||||
Services.prefs.addObserver(pref, updateCheckbox, false);
|
||||
updateCheckbox();
|
||||
});
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
|
||||
client.connect(() => {
|
||||
React.render(React.createElement(AddonsComponent, { client }),
|
||||
document.querySelector("#addons"));
|
||||
React.render(React.createElement(WorkersComponent, { client }),
|
||||
document.querySelector("#workers"));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function load() {
|
||||
window.removeEventListener("DOMContentLoaded", load);
|
||||
AboutDebugging.init();
|
||||
});
|
51
devtools/client/aboutdebugging/aboutdebugging.xhtml
Normal file
51
devtools/client/aboutdebugging/aboutdebugging.xhtml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
|
||||
<!ENTITY % toolboxDTD SYSTEM "chrome://browser/locale/devtools/toolbox.dtd"> %toolboxDTD;
|
||||
<!ENTITY % aboutdebuggingDTD SYSTEM "chrome://browser/locale/devtools/aboutdebugging.dtd"> %aboutdebuggingDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&aboutDebugging.title;</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/global.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/aboutdebugging/aboutdebugging.css" type="text/css"/>
|
||||
<script type="application/javascript" src="resource:///modules/devtools/client/shared/vendor/react.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="chrome://devtools/content/aboutdebugging/aboutdebugging.js"></script>
|
||||
</head>
|
||||
<body id="body">
|
||||
<div id="categories">
|
||||
<div class="category" value="addons" selected="true">
|
||||
<img class="category-icon" src="chrome://mozapps/skin/extensions/category-extensions.png"/>
|
||||
<div class="category-name">&aboutDebugging.addons;</div>
|
||||
</div>
|
||||
<div class="category" value="workers">
|
||||
<img class="category-icon" src="chrome://browser/skin/preferences/in-content/icons.svg#applications"/>
|
||||
<div class="category-name">&aboutDebugging.workers;</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<div id="tab-addons" class="tab active">
|
||||
<div class="header">
|
||||
<h1 class="header-name">&aboutDebugging.addons;</h1>
|
||||
</div>
|
||||
<input id="enable-addon-debugging" type="checkbox" data-pref="devtools.chrome.enabled"/>
|
||||
<label for="enable-addon-debugging" title="&aboutDebugging.addonDebugging.tooltip;">&aboutDebugging.addonDebugging.label;</label>
|
||||
<div id="addons"></div>
|
||||
</div>
|
||||
<div id="tab-workers" class="tab">
|
||||
<div class="header">
|
||||
<h1 class="header-name">&aboutDebugging.workers;</h1>
|
||||
</div>
|
||||
<input id="enable-worker-debugging" type="checkbox" data-pref="devtools.debugger.workers"/>
|
||||
<label for="enable-worker-debugging" title="&options.enableWorkers.tooltip;">&options.enableWorkers.label;</label>
|
||||
<div id="workers"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
78
devtools/client/aboutdebugging/components/addons.js
Normal file
78
devtools/client/aboutdebugging/components/addons.js
Normal file
@ -0,0 +1,78 @@
|
||||
/* 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/. */
|
||||
|
||||
/* global AddonManager, React, TargetListComponent */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"resource:///modules/devtools/client/shared/vendor/react.js");
|
||||
loader.lazyRequireGetter(this, "TargetListComponent",
|
||||
"devtools/client/aboutdebugging/components/target-list", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
|
||||
loader.lazyImporter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.png";
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/aboutdebugging.properties");
|
||||
|
||||
exports.AddonsComponent = React.createClass({
|
||||
displayName: "AddonsComponent",
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
extensions: []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
AddonManager.addAddonListener(this);
|
||||
this.update();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
AddonManager.removeAddonListener(this);
|
||||
},
|
||||
|
||||
render() {
|
||||
let client = this.props.client;
|
||||
let targets = this.state.extensions;
|
||||
let name = Strings.GetStringFromName("extensions");
|
||||
return React.createElement("div", null,
|
||||
React.createElement(TargetListComponent, { name, targets, client })
|
||||
);
|
||||
},
|
||||
|
||||
update() {
|
||||
AddonManager.getAllAddons(addons => {
|
||||
let extensions = addons.filter(addon => addon.isDebuggable).map(addon => {
|
||||
return {
|
||||
name: addon.name,
|
||||
icon: addon.iconURL || ExtensionIcon,
|
||||
type: addon.type,
|
||||
addonID: addon.id
|
||||
};
|
||||
});
|
||||
this.setState({ extensions });
|
||||
});
|
||||
},
|
||||
|
||||
onInstalled() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
onUninstalled() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
onEnabled() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
onDisabled() {
|
||||
this.update();
|
||||
},
|
||||
});
|
10
devtools/client/aboutdebugging/components/moz.build
Normal file
10
devtools/client/aboutdebugging/components/moz.build
Normal file
@ -0,0 +1,10 @@
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'addons.js',
|
||||
'target-list.js',
|
||||
'target.js',
|
||||
'workers.js',
|
||||
)
|
37
devtools/client/aboutdebugging/components/target-list.js
Normal file
37
devtools/client/aboutdebugging/components/target-list.js
Normal file
@ -0,0 +1,37 @@
|
||||
/* 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/. */
|
||||
|
||||
/* global React, TargetComponent */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"resource:///modules/devtools/client/shared/vendor/react.js");
|
||||
loader.lazyRequireGetter(this, "TargetComponent",
|
||||
"devtools/client/aboutdebugging/components/target", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/aboutdebugging.properties");
|
||||
const LocaleCompare = (a, b) => {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
};
|
||||
|
||||
exports.TargetListComponent = React.createClass({
|
||||
displayName: "TargetListComponent",
|
||||
|
||||
render() {
|
||||
let client = this.props.client;
|
||||
let targets = this.props.targets.sort(LocaleCompare).map(target => {
|
||||
return React.createElement(TargetComponent, { client, target });
|
||||
});
|
||||
return (
|
||||
React.createElement("div", { className: "targets" },
|
||||
React.createElement("h4", null, this.props.name),
|
||||
targets.length > 0 ? targets :
|
||||
React.createElement("p", null, Strings.GetStringFromName("nothing"))
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
66
devtools/client/aboutdebugging/components/target.js
Normal file
66
devtools/client/aboutdebugging/components/target.js
Normal file
@ -0,0 +1,66 @@
|
||||
/* 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/. */
|
||||
|
||||
/* global alert, BrowserToolboxProcess, gDevTools, React, TargetFactory,
|
||||
Toolbox */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"resource:///modules/devtools/client/shared/vendor/react.js");
|
||||
loader.lazyRequireGetter(this, "TargetFactory",
|
||||
"devtools/client/framework/target", true);
|
||||
loader.lazyRequireGetter(this, "Toolbox",
|
||||
"devtools/client/framework/toolbox", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
|
||||
loader.lazyImporter(this, "BrowserToolboxProcess",
|
||||
"resource:///modules/devtools/client/framework/ToolboxProcess.jsm");
|
||||
loader.lazyImporter(this, "gDevTools",
|
||||
"resource:///modules/devtools/client/framework/gDevTools.jsm");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/aboutdebugging.properties");
|
||||
|
||||
exports.TargetComponent = React.createClass({
|
||||
displayName: "TargetComponent",
|
||||
|
||||
debug() {
|
||||
let client = this.props.client;
|
||||
let target = this.props.target;
|
||||
switch (target.type) {
|
||||
case "extension":
|
||||
BrowserToolboxProcess.init({ addonID: target.addonID });
|
||||
break;
|
||||
case "serviceworker":
|
||||
// Fall through.
|
||||
case "sharedworker":
|
||||
// Fall through.
|
||||
case "worker":
|
||||
let workerActor = this.props.target.actorID;
|
||||
client.attachWorker(workerActor, (response, workerClient) => {
|
||||
gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
|
||||
"jsdebugger", Toolbox.HostType.WINDOW);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
alert("Not implemented yet!");
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let target = this.props.target;
|
||||
return React.createElement("div", { className: "target" },
|
||||
React.createElement("img", {
|
||||
className: "target-logo",
|
||||
src: target.icon }),
|
||||
React.createElement("div", { className: "target-details" },
|
||||
React.createElement("div", { className: "target-name" }, target.name),
|
||||
React.createElement("div", { className: "target-url" }, target.url)
|
||||
),
|
||||
React.createElement("button", { onClick: this.debug },
|
||||
Strings.GetStringFromName("debug"))
|
||||
);
|
||||
},
|
||||
});
|
85
devtools/client/aboutdebugging/components/workers.js
Normal file
85
devtools/client/aboutdebugging/components/workers.js
Normal file
@ -0,0 +1,85 @@
|
||||
/* 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/. */
|
||||
|
||||
/* global React, TargetListComponent */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "Ci",
|
||||
"chrome", true);
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"resource:///modules/devtools/client/shared/vendor/react.js");
|
||||
loader.lazyRequireGetter(this, "TargetListComponent",
|
||||
"devtools/client/aboutdebugging/components/target-list", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/aboutdebugging.properties");
|
||||
|
||||
exports.WorkersComponent = React.createClass({
|
||||
displayName: "WorkersComponent",
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
workers: {
|
||||
service: [],
|
||||
shared: [],
|
||||
other: []
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.props.client.addListener("workerListChanged", this.update);
|
||||
this.update();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.client.removeListener("workerListChanged", this.update);
|
||||
},
|
||||
|
||||
render() {
|
||||
let client = this.props.client;
|
||||
let workers = this.state.workers;
|
||||
return React.createElement("div", null,
|
||||
React.createElement(TargetListComponent, {
|
||||
name: Strings.GetStringFromName("serviceWorkers"),
|
||||
targets: workers.service, client }),
|
||||
React.createElement(TargetListComponent, {
|
||||
name: Strings.GetStringFromName("sharedWorkers"),
|
||||
targets: workers.shared, client }),
|
||||
React.createElement(TargetListComponent, {
|
||||
name: Strings.GetStringFromName("otherWorkers"),
|
||||
targets: workers.other, client })
|
||||
);
|
||||
},
|
||||
|
||||
update() {
|
||||
let client = this.props.client;
|
||||
let workers = this.getInitialState().workers;
|
||||
client.mainRoot.listWorkers(response => {
|
||||
let forms = response.workers;
|
||||
forms.forEach(form => {
|
||||
let worker = {
|
||||
name: form.url,
|
||||
actorID: form.actor
|
||||
};
|
||||
switch (form.type) {
|
||||
case Ci.nsIWorkerDebugger.TYPE_SERVICE:
|
||||
worker.type = "serviceworker";
|
||||
workers.service.push(worker);
|
||||
break;
|
||||
case Ci.nsIWorkerDebugger.TYPE_SHARED:
|
||||
worker.type = "sharedworker";
|
||||
workers.shared.push(worker);
|
||||
break;
|
||||
default:
|
||||
worker.type = "worker";
|
||||
workers.other.push(worker);
|
||||
}
|
||||
});
|
||||
this.setState({ workers });
|
||||
});
|
||||
}
|
||||
});
|
@ -41,18 +41,22 @@ add_task(function*() {
|
||||
|
||||
function doKeyHover(args) {
|
||||
info("Key pressed. Waiting for element to be highlighted/hovered");
|
||||
let onHighlighterReady = toolbox.once("highlighter-ready");
|
||||
let onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered");
|
||||
testActor.synthesizeKey(args);
|
||||
return inspector.toolbox.once("picker-node-hovered");
|
||||
return promise.all([onHighlighterReady, onPickerNodeHovered]);
|
||||
}
|
||||
|
||||
function moveMouseOver(selector) {
|
||||
info("Waiting for element " + selector + " to be highlighted");
|
||||
let onHighlighterReady = toolbox.once("highlighter-ready");
|
||||
let onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered");
|
||||
testActor.synthesizeMouse({
|
||||
options: {type: "mousemove"},
|
||||
center: true,
|
||||
selector: selector
|
||||
});
|
||||
return inspector.toolbox.once("picker-node-hovered");
|
||||
return promise.all([onHighlighterReady, onPickerNodeHovered]);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -14,24 +14,24 @@ add_task(function*() {
|
||||
info("Starting element picker");
|
||||
yield toolbox.highlighterUtils.startPicker();
|
||||
|
||||
info("Selecting the simple-div1 DIV");
|
||||
yield moveMouseOver("#simple-div2");
|
||||
info("Selecting the #another DIV");
|
||||
yield moveMouseOver("#another");
|
||||
|
||||
// Testing pick-node shortcut
|
||||
info("Testing enter/return key as pick-node command");
|
||||
yield doKeyPick({key: "VK_RETURN", options: {}});
|
||||
is(inspector.selection.nodeFront.id, "simple-div2", "The #simple-div2 node was selected. Passed.");
|
||||
is(inspector.selection.nodeFront.id, "another", "The #another node was selected. Passed.");
|
||||
|
||||
// Testing cancel-picker command
|
||||
info("Starting element picker again");
|
||||
yield toolbox.highlighterUtils.startPicker();
|
||||
|
||||
info("Selecting the simple-div1 DIV");
|
||||
yield moveMouseOver("#simple-div1");
|
||||
info("Selecting the ahoy DIV");
|
||||
yield moveMouseOver("#ahoy");
|
||||
|
||||
info("Testing escape key as cancel-picker command");
|
||||
yield doKeyStop({key: "VK_ESCAPE", options: {}});
|
||||
is(inspector.selection.nodeFront.id, "simple-div2", "The simple-div2 DIV is still selected. Passed.");
|
||||
is(inspector.selection.nodeFront.id, "another", "The #another DIV is still selected. Passed.");
|
||||
|
||||
function doKeyPick(args) {
|
||||
info("Key pressed. Waiting for element to be picked");
|
||||
@ -50,12 +50,14 @@ add_task(function*() {
|
||||
|
||||
function moveMouseOver(selector) {
|
||||
info("Waiting for element " + selector + " to be highlighted");
|
||||
let onHighlighterReady = toolbox.once("highlighter-ready");
|
||||
let onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered");
|
||||
testActor.synthesizeMouse({
|
||||
options: {type: "mousemove"},
|
||||
center: true,
|
||||
selector: selector
|
||||
});
|
||||
return inspector.toolbox.once("picker-node-hovered");
|
||||
return promise.all([onHighlighterReady, onPickerNodeHovered]);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -11,7 +11,7 @@
|
||||
</div>
|
||||
|
||||
<div id="simple-div2">
|
||||
<p>This is another node. You won't reach this in my test.</p>
|
||||
<p id="another">This is another node. You won't reach this in my test.</p>
|
||||
<p id="ahoy">Ahoy! How you doin' Capn'? <em>#ahoy</em></p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -157,6 +157,9 @@ devtools.jar:
|
||||
content/eyedropper/eyedropper.xul (eyedropper/eyedropper.xul)
|
||||
content/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
|
||||
content/eyedropper/nocursor.css (eyedropper/nocursor.css)
|
||||
content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
|
||||
content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
|
||||
content/aboutdebugging/aboutdebugging.js (aboutdebugging/aboutdebugging.js)
|
||||
% skin devtools classic/1.0 %skin/
|
||||
* skin/themes/common.css (themes/common.css)
|
||||
* skin/themes/dark-theme.css (themes/dark-theme.css)
|
||||
|
@ -7,6 +7,7 @@
|
||||
include('../templates.mozbuild')
|
||||
|
||||
DIRS += [
|
||||
'aboutdebugging/components',
|
||||
'animationinspector',
|
||||
'app-manager',
|
||||
'canvasdebugger',
|
||||
|
@ -7,7 +7,7 @@
|
||||
// A helper actor for brower/devtools/inspector tests.
|
||||
|
||||
let { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const {getElementFromPoint, getAdjustedQuads} = require("devtools/shared/layout/utils");
|
||||
const {getRect, getElementFromPoint, getAdjustedQuads} = require("devtools/shared/layout/utils");
|
||||
const promise = require("promise");
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
var DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
@ -511,6 +511,18 @@ const TestActor = exports.TestActor = protocol.ActorClass({
|
||||
value: RetVal("json")
|
||||
}
|
||||
}),
|
||||
|
||||
getNodeRect: protocol.method(Task.async(function* (selector) {
|
||||
let node = this._querySelector(selector);
|
||||
return getRect(this.content, node, this.content);
|
||||
}), {
|
||||
request: {
|
||||
selector: Arg(0, "string")
|
||||
},
|
||||
response: {
|
||||
value: RetVal("json")
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
const TestActorFront = exports.TestActorFront = protocol.FrontClass(TestActor, {
|
||||
@ -561,19 +573,6 @@ const TestActorFront = exports.TestActorFront = protocol.FrontClass(TestActor, {
|
||||
.then(value => value === null);
|
||||
},
|
||||
|
||||
assertHighlightedNode: Task.async(function* (selector) {
|
||||
let {visible, content} = yield this._getBoxModelStatus();
|
||||
let points = content.points;
|
||||
if (visible) {
|
||||
let x = (points.p1.x + points.p2.x + points.p3.x + points.p4.x) / 4;
|
||||
let y = (points.p1.y + points.p2.y + points.p3.y + points.p4.y) / 4;
|
||||
|
||||
return this.assertElementAtPoint(x, y, selector);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Assert that the box-model highlighter's current position corresponds to the
|
||||
* given node boxquads.
|
||||
@ -639,6 +638,77 @@ const TestActorFront = exports.TestActorFront = protocol.FrontClass(TestActor, {
|
||||
return ret;
|
||||
}),
|
||||
|
||||
assertHighlightedNode: Task.async(function* (selector) {
|
||||
// Taken and tweaked from:
|
||||
// https://github.com/iominh/point-in-polygon-extended/blob/master/src/index.js#L30-L85
|
||||
function isLeft(p0, p1, p2) {
|
||||
let l = ( (p1[0] - p0[0]) * (p2[1] - p0[1]) ) -
|
||||
( (p2[0] - p0[0]) * (p1[1] - p0[1]) );
|
||||
return l;
|
||||
}
|
||||
function isInside(point, polygon) {
|
||||
if (polygon.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var n = polygon.length;
|
||||
var newPoints = polygon.slice(0);
|
||||
newPoints.push(polygon[0]);
|
||||
var wn = 0; // wn counter
|
||||
|
||||
// loop through all edges of the polygon
|
||||
for (var i = 0; i < n; i++) {
|
||||
// Accept points on the edges
|
||||
let r = isLeft(newPoints[i], newPoints[i + 1], point);
|
||||
if (r === 0) {
|
||||
return true;
|
||||
}
|
||||
if (newPoints[i][1] <= point[1]) {
|
||||
if (newPoints[i + 1][1] > point[1] && r > 0) {
|
||||
wn++;
|
||||
}
|
||||
} else {
|
||||
if (newPoints[i + 1][1] <= point[1] && r < 0) {
|
||||
wn--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wn === 0) {
|
||||
dumpn(JSON.stringify(point) + " is outside of " + JSON.stringify(polygon));
|
||||
}
|
||||
// the point is outside only when this winding number wn===0, otherwise it's inside
|
||||
return wn !== 0;
|
||||
}
|
||||
|
||||
let {visible, border} = yield this._getBoxModelStatus();
|
||||
let points = border.points;
|
||||
if (visible) {
|
||||
// Check that the node is within the box model
|
||||
let { left, top, width, height } = yield this.getNodeRect(selector);
|
||||
let right = left + width;
|
||||
let bottom = top + height;
|
||||
|
||||
// Converts points dictionnary into an array
|
||||
let list = [];
|
||||
for(var i = 1; i <= 4; i++) {
|
||||
let p = points["p" + i];
|
||||
list.push([p.x, p.y]);
|
||||
}
|
||||
points = list;
|
||||
|
||||
// Check that each point of the node is within the box model
|
||||
if (!isInside([left, top], points) ||
|
||||
!isInside([right, top], points) ||
|
||||
!isInside([right, bottom], points) ||
|
||||
!isInside([left, bottom], points)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get the coordinate (points attribute) from one of the polygon elements in the
|
||||
* box model highlighter.
|
||||
|
16
devtools/client/shared/vendor/REACT_UPGRADING
vendored
Normal file
16
devtools/client/shared/vendor/REACT_UPGRADING
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
React has a dev and prod version. The dev version includes additional
|
||||
sanity checks and better errors, but at a slight perf cost. The prod
|
||||
version available on the web is by default minified, but we don't want
|
||||
a minified version, so we need to build it ourselves.
|
||||
|
||||
The react.js and react-dev.js were generated with the following steps:
|
||||
|
||||
* git clone https://github.com/facebook/react.git && cd react
|
||||
* npm install
|
||||
* grunt build
|
||||
* cp build/react-with-addons.js <gecko-dev>/devtools/client/shared/vendor/react-dev.js
|
||||
* NODE_ENV=production grunt build
|
||||
* cp build/react-with-addons.js <gecko-dev>/devtools/client/shared/vendor/react.js
|
||||
|
||||
The second build produces a non-minified React file but with all the
|
||||
sanity checks that incur a perf hit removed.
|
15993
devtools/client/shared/vendor/react-dev.js
vendored
15993
devtools/client/shared/vendor/react-dev.js
vendored
File diff suppressed because it is too large
Load Diff
15991
devtools/client/shared/vendor/react.js
vendored
15991
devtools/client/shared/vendor/react.js
vendored
File diff suppressed because it is too large
Load Diff
@ -4,57 +4,281 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This file holds various CSS parsing and rewriting utilities.
|
||||
// Some entry points of note are:
|
||||
// parseDeclarations - parse a CSS rule into declarations
|
||||
// RuleRewriter - rewrite CSS rule text
|
||||
// parsePseudoClassesAndAttributes - parse selector and extract
|
||||
// pseudo-classes
|
||||
// parseSingleValue - parse a single CSS property value
|
||||
|
||||
"use strict";
|
||||
|
||||
const {cssTokenizer} = require("devtools/client/sourceeditor/css-tokenizer");
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
Cu.importGlobalProperties(["CSS"]);
|
||||
const promise = require("promise");
|
||||
Cu.import("resource://gre/modules/Task.jsm", this);
|
||||
loader.lazyGetter(this, "DOMUtils", () => {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
});
|
||||
|
||||
const SELECTOR_ATTRIBUTE = exports.SELECTOR_ATTRIBUTE = 1;
|
||||
const SELECTOR_ELEMENT = exports.SELECTOR_ELEMENT = 2;
|
||||
const SELECTOR_PSEUDO_CLASS = exports.SELECTOR_PSEUDO_CLASS = 3;
|
||||
|
||||
// Used to test whether a newline appears anywhere in some text.
|
||||
const NEWLINE_RX = /[\r\n]/;
|
||||
// Used to test whether a bit of text starts an empty comment, either
|
||||
// an "ordinary" /* ... */ comment, or a "heuristic bypass" comment
|
||||
// like /*! ... */.
|
||||
const EMPTY_COMMENT_START_RX = /^\/\*!?[ \r\n\t\f]*$/;
|
||||
// Used to test whether a bit of text ends an empty comment.
|
||||
const EMPTY_COMMENT_END_RX = /^[ \r\n\t\f]*\*\//;
|
||||
// Used to test whether a string starts with a blank line.
|
||||
const BLANK_LINE_RX = /^[ \t]*(?:\r\n|\n|\r|\f|$)/;
|
||||
|
||||
// When commenting out a declaration, we put this character into the
|
||||
// comment opener so that future parses of the commented text know to
|
||||
// bypass the property name validity heuristic.
|
||||
const COMMENT_PARSING_HEURISTIC_BYPASS_CHAR = "!";
|
||||
|
||||
/**
|
||||
* Returns an array of CSS declarations given an string.
|
||||
* For example, parseDeclarations("width: 1px; height: 1px") would return
|
||||
* [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
|
||||
*
|
||||
* The input string is assumed to only contain declarations so { and }
|
||||
* characters will be treated as part of either the property or value,
|
||||
* depending where it's found.
|
||||
* Escape a comment body. Find the comment start and end strings in a
|
||||
* string and inserts backslashes so that the resulting text can
|
||||
* itself be put inside a comment.
|
||||
*
|
||||
* @param {String} inputString
|
||||
* An input string of CSS
|
||||
* @return {Array} an array of objects with the following signature:
|
||||
* [{"name": string, "value": string, "priority": string}, ...]
|
||||
* input string
|
||||
* @return {String} the escaped result
|
||||
*/
|
||||
function parseDeclarations(inputString) {
|
||||
function escapeCSSComment(inputString) {
|
||||
let result = inputString.replace(/\/(\\*)\*/g, "/\\$1*");
|
||||
return result.replace(/\*(\\*)\//g, "*\\$1/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-escape a comment body. This undoes any comment escaping that
|
||||
* was done by escapeCSSComment. That is, given input like "/\*
|
||||
* comment *\/", it will strip the backslashes.
|
||||
*
|
||||
* @param {String} inputString
|
||||
* input string
|
||||
* @return {String} the un-escaped result
|
||||
*/
|
||||
function unescapeCSSComment(inputString) {
|
||||
let result = inputString.replace(/\/\\(\\*)\*/g, "/$1*");
|
||||
return result.replace(/\*\\(\\*)\//g, "*$1/");
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function for parseDeclarations that implements a heuristic
|
||||
* to decide whether a given bit of comment text should be parsed as a
|
||||
* declaration.
|
||||
*
|
||||
* @param {String} name the property name that has been parsed
|
||||
* @return {Boolean} true if the property should be parsed, false if
|
||||
* the remainder of the comment should be skipped
|
||||
*/
|
||||
function shouldParsePropertyInComment(name) {
|
||||
try {
|
||||
// If the property name is invalid, the cssPropertyIsShorthand
|
||||
// will throw an exception. But if it is valid, no exception will
|
||||
// be thrown; so we just ignore the return value.
|
||||
DOMUtils.cssPropertyIsShorthand(name);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function for @see parseDeclarations that handles parsing
|
||||
* of comment text. This wraps a recursive call to parseDeclarations
|
||||
* with the processing needed to ensure that offsets in the result
|
||||
* refer back to the original, unescaped, input string.
|
||||
*
|
||||
* @param {String} commentText The text of the comment, without the
|
||||
* delimiters.
|
||||
* @param {Number} startOffset The offset of the comment opener
|
||||
* in the original text.
|
||||
* @param {Number} endOffset The offset of the comment closer
|
||||
* in the original text.
|
||||
* @return {array} Array of declarations of the same form as returned
|
||||
* by parseDeclarations.
|
||||
*/
|
||||
function parseCommentDeclarations(commentText, startOffset, endOffset) {
|
||||
let commentOverride = false;
|
||||
if (commentText === "") {
|
||||
return [];
|
||||
} else if (commentText[0] === COMMENT_PARSING_HEURISTIC_BYPASS_CHAR) {
|
||||
// This is the special sign that the comment was written by
|
||||
// rewriteDeclarations and so we should bypass the usual
|
||||
// heuristic.
|
||||
commentOverride = true;
|
||||
commentText = commentText.substring(1);
|
||||
}
|
||||
|
||||
let rewrittenText = unescapeCSSComment(commentText);
|
||||
|
||||
// We might have rewritten an embedded comment. For example
|
||||
// /\* ... *\/ would turn into /* ... */.
|
||||
// This rewriting is necessary for proper lexing, but it means
|
||||
// that the offsets we get back can be off. So now we compute
|
||||
// a map so that we can rewrite offsets later. The map is the same
|
||||
// length as |rewrittenText| and tells us how to map an index
|
||||
// into |rewrittenText| to an index into |commentText|.
|
||||
//
|
||||
// First, we find the location of each comment starter or closer in
|
||||
// |rewrittenText|. At these spots we put a 1 into |rewrites|.
|
||||
// Then we walk the array again, using the elements to compute a
|
||||
// delta, which we use to make the final mapping.
|
||||
//
|
||||
// Note we allocate one extra entry because we can see an ending
|
||||
// offset that is equal to the length.
|
||||
let rewrites = new Array(rewrittenText.length + 1).fill(0);
|
||||
|
||||
let commentRe = /\/\\*\*|\*\\*\//g;
|
||||
while (true) {
|
||||
let matchData = commentRe.exec(rewrittenText);
|
||||
if (!matchData) {
|
||||
break;
|
||||
}
|
||||
rewrites[matchData.index] = 1;
|
||||
}
|
||||
|
||||
let delta = 0;
|
||||
for (let i = 0; i <= rewrittenText.length; ++i) {
|
||||
delta += rewrites[i];
|
||||
// |startOffset| to add the offset from the comment starter, |+2|
|
||||
// for the length of the "/*", then |i| and |delta| as described
|
||||
// above.
|
||||
rewrites[i] = startOffset + 2 + i + delta;
|
||||
if (commentOverride) {
|
||||
++rewrites[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we pass "false" for parseComments here. It doesn't
|
||||
// seem worthwhile to support declarations in comments-in-comments
|
||||
// here, as there's no way to generate those using the tools, and
|
||||
// users would be crazy to write such things.
|
||||
let newDecls = parseDeclarationsInternal(rewrittenText, false,
|
||||
true, commentOverride);
|
||||
for (let decl of newDecls) {
|
||||
decl.offsets[0] = rewrites[decl.offsets[0]];
|
||||
decl.offsets[1] = rewrites[decl.offsets[1]];
|
||||
decl.colonOffsets[0] = rewrites[decl.colonOffsets[0]];
|
||||
decl.colonOffsets[1] = rewrites[decl.colonOffsets[1]];
|
||||
decl.commentOffsets = [startOffset, endOffset];
|
||||
}
|
||||
return newDecls;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function for parseDeclarationsInternal that creates a new
|
||||
* empty declaration.
|
||||
*
|
||||
* @return {object} an empty declaration of the form returned by
|
||||
* parseDeclarations
|
||||
*/
|
||||
function getEmptyDeclaration() {
|
||||
return {name: "", value: "", priority: "",
|
||||
terminator: "",
|
||||
offsets: [undefined, undefined],
|
||||
colonOffsets: false};
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function that does all the parsing work for
|
||||
* parseDeclarations. This is separate because it has some arguments
|
||||
* that don't make sense in isolation.
|
||||
*
|
||||
* The return value and arguments are like parseDeclarations, with
|
||||
* these additional arguments.
|
||||
*
|
||||
* @param {Boolean} inComment
|
||||
* If true, assume that this call is parsing some text
|
||||
* which came from a comment in another declaration.
|
||||
* In this case some heuristics are used to avoid parsing
|
||||
* text which isn't obviously a series of declarations.
|
||||
* @param {Boolean} commentOverride
|
||||
* This only makes sense when inComment=true.
|
||||
* When true, assume that the comment was generated by
|
||||
* rewriteDeclarations, and skip the usual name-checking
|
||||
* heuristic.
|
||||
*/
|
||||
function parseDeclarationsInternal(inputString, parseComments,
|
||||
inComment, commentOverride) {
|
||||
if (inputString === null || inputString === undefined) {
|
||||
throw new Error("empty input string");
|
||||
}
|
||||
|
||||
let tokens = cssTokenizer(inputString);
|
||||
let lexer = DOMUtils.getCSSLexer(inputString);
|
||||
|
||||
let declarations = [{name: "", value: "", priority: ""}];
|
||||
let declarations = [getEmptyDeclaration()];
|
||||
let lastProp = declarations[0];
|
||||
|
||||
let current = "", hasBang = false, lastProp;
|
||||
for (let token of tokens) {
|
||||
lastProp = declarations[declarations.length - 1];
|
||||
let current = "", hasBang = false;
|
||||
while (true) {
|
||||
let token = lexer.nextToken();
|
||||
if (!token) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore HTML comment tokens (but parse anything they might
|
||||
// happen to surround).
|
||||
if (token.tokenType === "htmlcomment") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the start and end offsets of the declaration, but only
|
||||
// when we see a significant token.
|
||||
if (token.tokenType !== "whitespace" && token.tokenType !== "comment") {
|
||||
if (lastProp.offsets[0] === undefined) {
|
||||
lastProp.offsets[0] = token.startOffset;
|
||||
}
|
||||
lastProp.offsets[1] = token.endOffset;
|
||||
} else if (lastProp.name && !current && !hasBang &&
|
||||
!lastProp.priority && lastProp.colonOffsets[1]) {
|
||||
// Whitespace appearing after the ":" is attributed to it.
|
||||
lastProp.colonOffsets[1] = token.endOffset;
|
||||
}
|
||||
|
||||
if (token.tokenType === "symbol" && token.text === ":") {
|
||||
if (!lastProp.name) {
|
||||
// Set the current declaration name if there's no name yet
|
||||
lastProp.name = current.trim();
|
||||
lastProp.colonOffsets = [token.startOffset, token.endOffset];
|
||||
current = "";
|
||||
hasBang = false;
|
||||
|
||||
// When parsing a comment body, if the left-hand-side is not a
|
||||
// valid property name, then drop it and stop parsing.
|
||||
if (inComment && !commentOverride &&
|
||||
!shouldParsePropertyInComment(lastProp.name)) {
|
||||
lastProp.name = null;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, just append ':' to the current value (declaration value
|
||||
// with colons)
|
||||
current += ":";
|
||||
}
|
||||
} else if (token.tokenType === "symbol" && token.text === ";") {
|
||||
lastProp.terminator = "";
|
||||
// When parsing a comment, if the name hasn't been set, then we
|
||||
// have probably just seen an ordinary semicolon used in text,
|
||||
// so drop this and stop parsing.
|
||||
if (inComment && !lastProp.name) {
|
||||
current = "";
|
||||
break;
|
||||
}
|
||||
lastProp.value = current.trim();
|
||||
current = "";
|
||||
hasBang = false;
|
||||
declarations.push({name: "", value: "", priority: ""});
|
||||
declarations.push(getEmptyDeclaration());
|
||||
lastProp = declarations[declarations.length - 1];
|
||||
} else if (token.tokenType === "ident") {
|
||||
if (token.text === "important" && hasBang) {
|
||||
lastProp.priority = "important";
|
||||
@ -68,9 +292,20 @@ function parseDeclarations(inputString) {
|
||||
} else if (token.tokenType === "symbol" && token.text === "!") {
|
||||
hasBang = true;
|
||||
} else if (token.tokenType === "whitespace") {
|
||||
current += " ";
|
||||
if (current !== "") {
|
||||
current += " ";
|
||||
}
|
||||
} else if (token.tokenType === "comment") {
|
||||
// For now, just ignore.
|
||||
if (parseComments && !lastProp.name && !lastProp.value) {
|
||||
let commentText = inputString.substring(token.startOffset + 2,
|
||||
token.endOffset - 2);
|
||||
let newDecls = parseCommentDeclarations(commentText, token.startOffset,
|
||||
token.endOffset);
|
||||
|
||||
// Insert the new declarations just before the final element.
|
||||
let lastDecl = declarations.pop();
|
||||
declarations = [...declarations, ...newDecls, lastDecl];
|
||||
}
|
||||
} else {
|
||||
current += inputString.substring(token.startOffset, token.endOffset);
|
||||
}
|
||||
@ -79,11 +314,22 @@ function parseDeclarations(inputString) {
|
||||
// Handle whatever trailing properties or values might still be there
|
||||
if (current) {
|
||||
if (!lastProp.name) {
|
||||
// Trailing property found, e.g. p1:v1;p2:v2;p3
|
||||
lastProp.name = current.trim();
|
||||
// Ignore this case in comments.
|
||||
if (!inComment) {
|
||||
// Trailing property found, e.g. p1:v1;p2:v2;p3
|
||||
lastProp.name = current.trim();
|
||||
}
|
||||
} else {
|
||||
// Trailing value found, i.e. value without an ending ;
|
||||
lastProp.value += current.trim();
|
||||
lastProp.value = current.trim();
|
||||
let terminator = lexer.performEOFFixup("", true);
|
||||
lastProp.terminator = terminator + ";";
|
||||
// If the input was unterminated, attribute the remainder to
|
||||
// this property. This avoids some bad behavior when rewriting
|
||||
// an unterminated comment.
|
||||
if (terminator) {
|
||||
lastProp.offsets[1] = inputString.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,6 +339,539 @@ function parseDeclarations(inputString) {
|
||||
return declarations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of CSS declarations given a string.
|
||||
* For example, parseDeclarations("width: 1px; height: 1px") would return
|
||||
* [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
|
||||
*
|
||||
* The input string is assumed to only contain declarations so { and }
|
||||
* characters will be treated as part of either the property or value,
|
||||
* depending where it's found.
|
||||
*
|
||||
* @param {String} inputString
|
||||
* An input string of CSS
|
||||
* @param {Boolean} parseComments
|
||||
* If true, try to parse the contents of comments as well.
|
||||
* A comment will only be parsed if it occurs outside of
|
||||
* the body of some other declaration.
|
||||
* @return {Array} an array of objects with the following signature:
|
||||
* [{"name": string, "value": string, "priority": string,
|
||||
* "terminator": string,
|
||||
* "offsets": [start, end], "colonOffsets": [start, end]},
|
||||
* ...]
|
||||
* Here, "offsets" holds the offsets of the start and end
|
||||
* of the declaration text, in a form suitable for use with
|
||||
* String.substring.
|
||||
* "terminator" is a string to use to terminate the declaration,
|
||||
* usually "" to mean no additional termination is needed.
|
||||
* "colonOffsets" holds the start and end locations of the
|
||||
* ":" that separates the property name from the value.
|
||||
* If the declaration appears in a comment, then there will
|
||||
* be an additional {"commentOffsets": [start, end] property
|
||||
* on the object, which will hold the offsets of the start
|
||||
* and end of the enclosing comment.
|
||||
*/
|
||||
function parseDeclarations(inputString, parseComments = false) {
|
||||
return parseDeclarationsInternal(inputString, parseComments, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that can be used to rewrite declarations in some
|
||||
* source text. The source text and parsing are handled in the same
|
||||
* way as @see parseDeclarations, with |parseComments| being true.
|
||||
* Rewriting is done by calling one of the modification functions like
|
||||
* setPropertyEnabled. The returned object has the same interface
|
||||
* as @see RuleModificationList.
|
||||
*
|
||||
* An example showing how to disable the 3rd property in a rule:
|
||||
*
|
||||
* let rewriter = new RuleRewriter(ruleActor, ruleActor.authoredText);
|
||||
* rewriter.setPropertyEnabled(3, "color", false);
|
||||
* rewriter.apply().then(() => { ... the change is made ... });
|
||||
*
|
||||
* The exported rewriting methods are |renameProperty|, |setPropertyEnabled|,
|
||||
* |createProperty|, |setProperty|, and |removeProperty|. The |apply|
|
||||
* method can be used to send the edited text to the StyleRuleActor;
|
||||
* |getDefaultIndentation| is useful for the methods requiring a
|
||||
* default indentation value; and |getResult| is useful for testing.
|
||||
*
|
||||
* Additionally, editing will set the |changedDeclarations| property
|
||||
* on this object. This property has the same form as the |changed|
|
||||
* property of the object returned by |getResult|.
|
||||
*
|
||||
* @param {StyleRuleFront} rule The style rule to use. Note that this
|
||||
* is only needed by the |apply| and |getDefaultIndentation| methods;
|
||||
* and in particular for testing it can be |null|.
|
||||
* @param {String} inputString The CSS source text to parse and modify.
|
||||
* @return {Object} an object that can be used to rewrite the input text.
|
||||
*/
|
||||
function RuleRewriter(rule, inputString) {
|
||||
this.rule = rule;
|
||||
this.inputString = inputString;
|
||||
// Whether there are any newlines in the input text.
|
||||
this.hasNewLine = /[\r\n]/.test(this.inputString);
|
||||
// Keep track of which any declarations we had to rewrite while
|
||||
// performing the requested action.
|
||||
this.changedDeclarations = {};
|
||||
// The declarations.
|
||||
this.declarations = parseDeclarations(this.inputString, true);
|
||||
|
||||
this.decl = null;
|
||||
this.result = null;
|
||||
// If not null, a promise that must be wait upon before |apply| can
|
||||
// do its work.
|
||||
this.editPromise = null;
|
||||
|
||||
// If the |defaultIndentation| property is set, then it is used;
|
||||
// otherwise the RuleRewriter will try to compute the default
|
||||
// indentation based on the style sheet's text. This override
|
||||
// facility is for testing.
|
||||
this.defaultIndentation = null;
|
||||
}
|
||||
|
||||
RuleRewriter.prototype = {
|
||||
/**
|
||||
* An internal function to complete initialization and set some
|
||||
* properties for further processing.
|
||||
*
|
||||
* @param {Number} index The index of the property to modify
|
||||
*/
|
||||
completeInitialization: function(index) {
|
||||
if (index < 0) {
|
||||
throw new Error("Invalid index " + index + ". Expected positive integer");
|
||||
}
|
||||
// |decl| is the declaration to be rewritten, or null if there is no
|
||||
// declaration corresponding to |index|.
|
||||
// |result| is used to accumulate the result text.
|
||||
if (index < this.declarations.length) {
|
||||
this.decl = this.declarations[index];
|
||||
this.result = this.inputString.substring(0, this.decl.offsets[0]);
|
||||
} else {
|
||||
this.decl = null;
|
||||
this.result = this.inputString;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A helper function to compute the indentation of some text. This
|
||||
* examines the rule's existing text to guess the indentation to use;
|
||||
* unlike |getDefaultIndentation|, which examines the entire style
|
||||
* sheet.
|
||||
*
|
||||
* @param {String} string the input text
|
||||
* @param {Number} offset the offset at which to compute the indentation
|
||||
* @return {String} the indentation at the indicated position
|
||||
*/
|
||||
getIndentation: function(string, offset) {
|
||||
let originalOffset = offset;
|
||||
for (--offset; offset >= 0; --offset) {
|
||||
let c = string[offset];
|
||||
if (c === "\r" || c === "\n" || c === "\f") {
|
||||
return string.substring(offset + 1, originalOffset);
|
||||
}
|
||||
if (c !== " " && c !== "\t") {
|
||||
// Found some non-whitespace character before we found a newline
|
||||
// -- let's reset the starting point and keep going, as we saw
|
||||
// something on the line before the declaration.
|
||||
originalOffset = offset;
|
||||
}
|
||||
}
|
||||
// Ran off the end.
|
||||
return "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Modify a property value to ensure it is "lexically safe" for
|
||||
* insertion into a style sheet. This function doesn't attempt to
|
||||
* ensure that the resulting text is a valid value for the given
|
||||
* property; but rather just that inserting the text into the style
|
||||
* sheet will not cause unwanted changes to other rules or
|
||||
* declarations.
|
||||
*
|
||||
* @param {String} text The input text. This should include the trailing ";".
|
||||
* @return {String} Text that has been rewritten to be "lexically safe".
|
||||
*/
|
||||
sanitizePropertyValue: function(text) {
|
||||
let lexer = DOMUtils.getCSSLexer(text);
|
||||
|
||||
let result = "";
|
||||
let previousOffset = 0;
|
||||
let braceDepth = 0;
|
||||
while (true) {
|
||||
let token = lexer.nextToken();
|
||||
if (!token) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.tokenType === "symbol") {
|
||||
switch (token.text) {
|
||||
case ";":
|
||||
// We simply drop the ";" here. This lets us cope with
|
||||
// declarations that don't have a ";" and also other
|
||||
// termination. The caller handles adding the ";" again.
|
||||
result += text.substring(previousOffset, token.startOffset);
|
||||
previousOffset = token.endOffset;
|
||||
break;
|
||||
|
||||
case "{":
|
||||
++braceDepth;
|
||||
break;
|
||||
|
||||
case "}":
|
||||
--braceDepth;
|
||||
if (braceDepth < 0) {
|
||||
// Found an unmatched close bracket.
|
||||
braceDepth = 0;
|
||||
// Copy out text from |previousOffset|.
|
||||
result += text.substring(previousOffset, token.startOffset);
|
||||
// Quote the offending symbol.
|
||||
result += "\\" + token.text;
|
||||
previousOffset = token.endOffset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy out any remaining text, then any needed terminators.
|
||||
result += text.substring(previousOffset, text.length) +
|
||||
lexer.performEOFFixup("", true);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Start at |index| and skip whitespace
|
||||
* backward in |string|. Return the index of the first
|
||||
* non-whitespace character, or -1 if the entire string was
|
||||
* whitespace.
|
||||
* @param {String} string the input string
|
||||
* @param {Number} index the index at which to start
|
||||
* @return {Number} index of the first non-whitespace character, or -1
|
||||
*/
|
||||
skipWhitespaceBackward: function(string, index) {
|
||||
for (--index;
|
||||
index >= 0 && (string[index] === " " || string[index] === "\t");
|
||||
--index) {
|
||||
// Nothing.
|
||||
}
|
||||
return index;
|
||||
},
|
||||
|
||||
/**
|
||||
* Terminate a given declaration, if needed.
|
||||
*
|
||||
* @param {Number} index The index of the rule to possibly
|
||||
* terminate. It might be invalid, so this
|
||||
* function must check for that.
|
||||
*/
|
||||
maybeTerminateDecl: function(index) {
|
||||
if (index < 0 || index >= this.declarations.length
|
||||
// No need to rewrite declarations in comments.
|
||||
|| ("commentOffsets" in this.declarations[index])) {
|
||||
return;
|
||||
}
|
||||
|
||||
let termDecl = this.declarations[index];
|
||||
let endIndex = termDecl.offsets[1];
|
||||
// Due to an oddity of the lexer, we might have gotten a bit of
|
||||
// extra whitespace in a trailing bad_url token -- so be sure to
|
||||
// skip that as well.
|
||||
endIndex = this.skipWhitespaceBackward(this.result, endIndex) + 1;
|
||||
|
||||
let trailingText = this.result.substring(endIndex);
|
||||
if (termDecl.terminator) {
|
||||
// Insert the terminator just at the end of the declaration,
|
||||
// before any trailing whitespace.
|
||||
this.result = this.result.substring(0, endIndex) + termDecl.terminator +
|
||||
trailingText;
|
||||
// The terminator includes the ";", but we don't want it in
|
||||
// the changed value.
|
||||
this.changedDeclarations[index] =
|
||||
termDecl.value + termDecl.terminator.slice(0, -1);
|
||||
}
|
||||
// If the rule generally has newlines, but this particular
|
||||
// declaration doesn't have a trailing newline, insert one now.
|
||||
// Maybe this style is too weird to bother with.
|
||||
if (this.hasNewLine && !NEWLINE_RX.test(trailingText)) {
|
||||
this.result += "\n";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sanitize the given property value and return the sanitized form.
|
||||
* If the property is rewritten during sanitization, make a note in
|
||||
* |changedDeclarations|.
|
||||
*
|
||||
* @param {String} text The property text.
|
||||
* @param {Number} index The index of the property.
|
||||
* @return {String} The sanitized text.
|
||||
*/
|
||||
sanitizeText: function(text, index) {
|
||||
let sanitizedText = this.sanitizePropertyValue(text);
|
||||
if (sanitizedText !== text) {
|
||||
this.changedDeclarations[index] = sanitizedText;
|
||||
}
|
||||
return sanitizedText;
|
||||
},
|
||||
|
||||
/**
|
||||
* Rename a declaration.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* @param {String} name current name of the property
|
||||
* @param {String} newName new name of the property
|
||||
*/
|
||||
renameProperty: function(index, name, newName) {
|
||||
this.completeInitialization(index);
|
||||
this.result += CSS.escape(newName);
|
||||
// We could conceivably compute the name offsets instead so we
|
||||
// could preserve white space and comments on the LHS of the ":".
|
||||
this.completeCopying(this.decl.colonOffsets[0]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable or disable a declaration
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* @param {String} name current name of the property
|
||||
* @param {Boolean} isEnabled true if the property should be enabled;
|
||||
* false if it should be disabled
|
||||
*/
|
||||
setPropertyEnabled: function(index, name, isEnabled) {
|
||||
this.completeInitialization(index);
|
||||
const decl = this.decl;
|
||||
let copyOffset = decl.offsets[1];
|
||||
if (isEnabled) {
|
||||
// Enable it. First see if the comment start can be deleted.
|
||||
let commentStart = decl.commentOffsets[0];
|
||||
if (EMPTY_COMMENT_START_RX.test(this.result.substring(commentStart))) {
|
||||
this.result = this.result.substring(0, commentStart);
|
||||
} else {
|
||||
this.result += "*/ ";
|
||||
}
|
||||
|
||||
// Insert the name and value separately, so we can report
|
||||
// sanitization changes properly.
|
||||
let commentNamePart =
|
||||
this.inputString.substring(decl.offsets[0],
|
||||
decl.colonOffsets[1]);
|
||||
this.result += unescapeCSSComment(commentNamePart);
|
||||
|
||||
// When uncommenting, we must be sure to sanitize the text, to
|
||||
// avoid things like /* decl: }; */, which will be accepted as
|
||||
// a property but which would break the entire style sheet.
|
||||
let newText = this.inputString.substring(decl.colonOffsets[1],
|
||||
decl.offsets[1]);
|
||||
newText = unescapeCSSComment(newText).trimRight();
|
||||
this.result += this.sanitizeText(newText, index) + ";";
|
||||
|
||||
// See if the comment end can be deleted.
|
||||
let trailingText = this.inputString.substring(decl.offsets[1]);
|
||||
if (EMPTY_COMMENT_END_RX.test(trailingText)) {
|
||||
copyOffset = decl.commentOffsets[1];
|
||||
} else {
|
||||
this.result += " /*";
|
||||
}
|
||||
} else {
|
||||
// Disable it. Note that we use our special comment syntax
|
||||
// here.
|
||||
let declText = this.inputString.substring(decl.offsets[0],
|
||||
decl.offsets[1]);
|
||||
this.result += "/*" + COMMENT_PARSING_HEURISTIC_BYPASS_CHAR +
|
||||
" " + escapeCSSComment(declText) + " */";
|
||||
}
|
||||
this.completeCopying(copyOffset);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a promise that will be resolved to the default indentation
|
||||
* of the rule. This is a helper for internalCreateProperty.
|
||||
*
|
||||
* @return {Promise} a promise that will be resolved to a string
|
||||
* that holds the default indentation that should be used
|
||||
* for edits to the rule.
|
||||
*/
|
||||
getDefaultIndentation: function() {
|
||||
return this.rule.parentStyleSheet.guessIndentation();
|
||||
},
|
||||
|
||||
/**
|
||||
* An internal function to create a new declaration. This does all
|
||||
* the work of |createProperty|.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* @param {String} name name of the new property
|
||||
* @param {String} value value of the new property
|
||||
* @param {String} priority priority of the new property; either
|
||||
* the empty string or "important"
|
||||
* @return {Promise} a promise that is resolved when the edit has
|
||||
* completed
|
||||
*/
|
||||
internalCreateProperty: Task.async(function*(index, name, value, priority) {
|
||||
this.completeInitialization(index);
|
||||
let newIndentation = "";
|
||||
if (this.hasNewLine) {
|
||||
if (this.declarations.length > 0) {
|
||||
newIndentation = this.getIndentation(this.inputString,
|
||||
this.declarations[0].offsets[0]);
|
||||
} else if (this.defaultIndentation) {
|
||||
newIndentation = this.defaultIndentation;
|
||||
} else {
|
||||
newIndentation = yield this.getDefaultIndentation();
|
||||
}
|
||||
}
|
||||
|
||||
this.maybeTerminateDecl(index - 1);
|
||||
|
||||
// If we generally have newlines, and if skipping whitespace
|
||||
// backward stops at a newline, then insert our text before that
|
||||
// whitespace. This ensures the indentation we computed is what
|
||||
// is actually used.
|
||||
let savedWhitespace = "";
|
||||
if (this.hasNewLine) {
|
||||
let wsOffset = this.skipWhitespaceBackward(this.result,
|
||||
this.result.length);
|
||||
if (this.result[wsOffset] === "\r" || this.result[wsOffset] === "\n") {
|
||||
savedWhitespace = this.result.substring(wsOffset + 1);
|
||||
this.result = this.result.substring(0, wsOffset + 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.result += newIndentation + CSS.escape(name) + ": " +
|
||||
this.sanitizeText(value, index);
|
||||
|
||||
if (priority === "important") {
|
||||
this.result += " !important";
|
||||
}
|
||||
this.result += ";";
|
||||
if (this.hasNewLine) {
|
||||
this.result += "\n";
|
||||
}
|
||||
this.result += savedWhitespace;
|
||||
|
||||
if (this.decl) {
|
||||
// Still want to copy in the declaration previously at this
|
||||
// index.
|
||||
this.completeCopying(this.decl.offsets[0]);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create a new declaration.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* @param {String} name name of the new property
|
||||
* @param {String} value value of the new property
|
||||
* @param {String} priority priority of the new property; either
|
||||
* the empty string or "important"
|
||||
*/
|
||||
createProperty: function(index, name, value, priority) {
|
||||
this.editPromise = this.internalCreateProperty(index, name, value,
|
||||
priority);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a declaration's value.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* This can be -1 in the case where
|
||||
* the rule does not support setRuleText;
|
||||
* generally for setting properties
|
||||
* on an element's style.
|
||||
* @param {String} name the property's name
|
||||
* @param {String} value the property's value
|
||||
* @param {String} priority the property's priority, either the empty
|
||||
* string or "important"
|
||||
*/
|
||||
setProperty: function(index, name, value, priority) {
|
||||
this.completeInitialization(index);
|
||||
// We might see a "set" on a previously non-existent property; in
|
||||
// that case, act like "create".
|
||||
if (!this.decl) {
|
||||
return this.createProperty(index, name, value, priority);
|
||||
}
|
||||
|
||||
// Note that this assumes that "set" never operates on disabled
|
||||
// properties.
|
||||
this.result += this.inputString.substring(this.decl.offsets[0],
|
||||
this.decl.colonOffsets[1]) +
|
||||
this.sanitizeText(value, index);
|
||||
|
||||
if (priority === "important") {
|
||||
this.result += " !important";
|
||||
}
|
||||
this.result += ";";
|
||||
this.completeCopying(this.decl.offsets[1]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a declaration.
|
||||
*
|
||||
* @param {Number} index index of the property in the rule.
|
||||
* @param {String} name the name of the property to remove
|
||||
*/
|
||||
removeProperty: function(index, name) {
|
||||
this.completeInitialization(index);
|
||||
let copyOffset = this.decl.offsets[1];
|
||||
// Maybe removing this rule left us with a completely blank
|
||||
// line. In this case, we'll delete the whole thing. We only
|
||||
// bother with this if we're looking at sources that already
|
||||
// have a newline somewhere.
|
||||
if (this.hasNewLine) {
|
||||
let nlOffset = this.skipWhitespaceBackward(this.result,
|
||||
this.decl.offsets[0]);
|
||||
if (nlOffset < 0 || this.result[nlOffset] === "\r" ||
|
||||
this.result[nlOffset] === "\n") {
|
||||
let trailingText = this.inputString.substring(copyOffset);
|
||||
let match = BLANK_LINE_RX.exec(trailingText);
|
||||
if (match) {
|
||||
this.result = this.result.substring(0, nlOffset + 1);
|
||||
copyOffset += match[0].length;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.completeCopying(copyOffset);
|
||||
},
|
||||
|
||||
/**
|
||||
* An internal function to copy any trailing text to the output
|
||||
* string.
|
||||
*
|
||||
* @param {Number} copyOffset Offset into |inputString| of the
|
||||
* final text to copy to the output string.
|
||||
*/
|
||||
completeCopying: function(copyOffset) {
|
||||
// Add the trailing text.
|
||||
this.result += this.inputString.substring(copyOffset);
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply the modifications in this object to the associated rule.
|
||||
*
|
||||
* @return {Promise} A promise which will be resolved when the modifications
|
||||
* are complete.
|
||||
*/
|
||||
apply: function() {
|
||||
return promise.resolve(this.editPromise).then(() => {
|
||||
return this.rule.setRuleText(this.result);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the result of the rewriting. This is used for testing.
|
||||
*
|
||||
* @return {object} an object of the form {changed: object, text: string}
|
||||
* |changed| is an object where each key is
|
||||
* the index of a property whose value had to be
|
||||
* rewritten during the sanitization process, and
|
||||
* whose value is the new text of the property.
|
||||
* |text| is the rewritten text of the rule.
|
||||
*/
|
||||
getResult: function() {
|
||||
return {changed: this.changedDeclarations, text: this.result};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of the parsed CSS selector value and type given a string.
|
||||
*
|
||||
@ -213,6 +992,12 @@ function parseSingleValue(value) {
|
||||
};
|
||||
}
|
||||
|
||||
exports.escapeCSSComment = escapeCSSComment;
|
||||
// unescapeCSSComment is exported for testing.
|
||||
exports._unescapeCSSComment = unescapeCSSComment;
|
||||
exports.parseDeclarations = parseDeclarations;
|
||||
// parseCommentDeclarations is exported for testing.
|
||||
exports._parseCommentDeclarations = parseCommentDeclarations;
|
||||
exports.RuleRewriter = RuleRewriter;
|
||||
exports.parsePseudoClassesAndAttributes = parsePseudoClassesAndAttributes;
|
||||
exports.parseSingleValue = parseSingleValue;
|
||||
|
@ -374,7 +374,9 @@ ElementStyle.prototype = {
|
||||
let overridden;
|
||||
if (earlier &&
|
||||
computedProp.priority === "important" &&
|
||||
earlier.priority !== "important") {
|
||||
earlier.priority !== "important" &&
|
||||
(earlier.textProp.rule.inherited ||
|
||||
!computedProp.textProp.rule.inherited)) {
|
||||
// New property is higher priority. Mark the earlier property
|
||||
// overridden (which will reverse its dirty state).
|
||||
earlier._overriddenDirty = !earlier._overriddenDirty;
|
||||
|
@ -130,6 +130,7 @@ skip-if = (os == "win" && debug) || e10s # bug 963492: win. bug 1040653: e10s.
|
||||
[browser_ruleview_mark_overridden_03.js]
|
||||
[browser_ruleview_mark_overridden_04.js]
|
||||
[browser_ruleview_mark_overridden_05.js]
|
||||
[browser_ruleview_mark_overridden_07.js]
|
||||
[browser_ruleview_mathml-element.js]
|
||||
[browser_ruleview_media-queries.js]
|
||||
[browser_ruleview_multiple-properties-duplicates.js]
|
||||
|
@ -0,0 +1,71 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the rule view marks overridden rules correctly based on the
|
||||
// specificity of the rule.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
#testid {
|
||||
margin-left: 23px;
|
||||
}
|
||||
|
||||
div {
|
||||
margin-right: 23px;
|
||||
margin-left: 1px !important;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-right: 1px !important;
|
||||
font-size: 79px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<span>
|
||||
<div id='testid' class='testclass'>Styled Node</div>
|
||||
</span>
|
||||
</body>
|
||||
`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testMarkOverridden(inspector, view);
|
||||
});
|
||||
|
||||
function* testMarkOverridden(inspector, view) {
|
||||
let elementStyle = view._elementStyle;
|
||||
|
||||
let RESULTS = [
|
||||
// We skip the first element
|
||||
[],
|
||||
[{name: "margin-left", value: "23px", overridden: true}],
|
||||
[{name: "margin-right", value: "23px", overridden: false},
|
||||
{name: "margin-left", value: "1px", overridden: false}],
|
||||
[{name: "font-size", value: "12px", overridden: false}],
|
||||
[{name: "font-size", value: "79px", overridden: true}]
|
||||
];
|
||||
|
||||
for (let i = 1; i < RESULTS.length; ++i) {
|
||||
let idRule = elementStyle.rules[i];
|
||||
|
||||
for (let propIndex in RESULTS[i]) {
|
||||
let expected = RESULTS[i][propIndex];
|
||||
let prop = idRule.textProps[propIndex];
|
||||
|
||||
info("Checking rule " + i + ", property " + propIndex);
|
||||
|
||||
is(prop.name, expected.name, "check property name");
|
||||
is(prop.value, expected.value, "check property value");
|
||||
is(prop.overridden, expected.overridden, "check property overridden");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
const {escapeCSSComment, _unescapeCSSComment} =
|
||||
devtools.require("devtools/client/styleinspector/css-parsing-utils");
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
input: "simple",
|
||||
expected: "simple"
|
||||
},
|
||||
{
|
||||
input: "/* comment */",
|
||||
expected: "/\\* comment *\\/"
|
||||
},
|
||||
{
|
||||
input: "/* two *//* comments */",
|
||||
expected: "/\\* two *\\//\\* comments *\\/"
|
||||
},
|
||||
{
|
||||
input: "/* nested /\\* comment *\\/ */",
|
||||
expected: "/\\* nested /\\\\* comment *\\\\/ *\\/",
|
||||
}
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
let i = 0;
|
||||
for (let test of TEST_DATA) {
|
||||
++i;
|
||||
do_print("Test #" + i);
|
||||
|
||||
let escaped = escapeCSSComment(test.input);
|
||||
equal(escaped, test.expected);
|
||||
let unescaped = _unescapeCSSComment(escaped);
|
||||
equal(unescaped, test.input);
|
||||
}
|
||||
}
|
@ -7,72 +7,72 @@
|
||||
|
||||
const Cu = Components.utils;
|
||||
const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
|
||||
const {parseDeclarations} =
|
||||
const {parseDeclarations, _parseCommentDeclarations} =
|
||||
require("devtools/client/styleinspector/css-parsing-utils");
|
||||
|
||||
const TEST_DATA = [
|
||||
// Simple test
|
||||
{
|
||||
input: "p:v;",
|
||||
expected: [{name: "p", value: "v", priority: ""}]
|
||||
expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
|
||||
},
|
||||
// Simple test
|
||||
{
|
||||
input: "this:is;a:test;",
|
||||
expected: [
|
||||
{name: "this", value: "is", priority: ""},
|
||||
{name: "a", value: "test", priority: ""}
|
||||
{name: "this", value: "is", priority: "", offsets: [0, 8]},
|
||||
{name: "a", value: "test", priority: "", offsets: [8, 15]}
|
||||
]
|
||||
},
|
||||
// Test a single declaration with semi-colon
|
||||
{
|
||||
input: "name:value;",
|
||||
expected: [{name: "name", value: "value", priority: ""}]
|
||||
expected: [{name: "name", value: "value", priority: "", offsets: [0, 11]}]
|
||||
},
|
||||
// Test a single declaration without semi-colon
|
||||
{
|
||||
input: "name:value",
|
||||
expected: [{name: "name", value: "value", priority: ""}]
|
||||
expected: [{name: "name", value: "value", priority: "", offsets: [0, 10]}]
|
||||
},
|
||||
// Test multiple declarations separated by whitespaces and carriage
|
||||
// returns and tabs
|
||||
{
|
||||
input: "p1 : v1 ; \t\t \n p2:v2; \n\n\n\n\t p3 : v3;",
|
||||
expected: [
|
||||
{name: "p1", value: "v1", priority: ""},
|
||||
{name: "p2", value: "v2", priority: ""},
|
||||
{name: "p3", value: "v3", priority: ""},
|
||||
{name: "p1", value: "v1", priority: "", offsets: [0, 9]},
|
||||
{name: "p2", value: "v2", priority: "", offsets: [16, 22]},
|
||||
{name: "p3", value: "v3", priority: "", offsets: [32, 45]},
|
||||
]
|
||||
},
|
||||
// Test simple priority
|
||||
{
|
||||
input: "p1: v1; p2: v2 !important;",
|
||||
expected: [
|
||||
{name: "p1", value: "v1", priority: ""},
|
||||
{name: "p2", value: "v2", priority: "important"}
|
||||
{name: "p1", value: "v1", priority: "", offsets: [0, 7]},
|
||||
{name: "p2", value: "v2", priority: "important", offsets: [8, 26]}
|
||||
]
|
||||
},
|
||||
// Test simple priority
|
||||
{
|
||||
input: "p1: v1 !important; p2: v2",
|
||||
expected: [
|
||||
{name: "p1", value: "v1", priority: "important"},
|
||||
{name: "p2", value: "v2", priority: ""}
|
||||
{name: "p1", value: "v1", priority: "important", offsets: [0, 18]},
|
||||
{name: "p2", value: "v2", priority: "", offsets: [19, 25]}
|
||||
]
|
||||
},
|
||||
// Test simple priority
|
||||
{
|
||||
input: "p1: v1 ! important; p2: v2 ! important;",
|
||||
expected: [
|
||||
{name: "p1", value: "v1", priority: "important"},
|
||||
{name: "p2", value: "v2", priority: "important"}
|
||||
{name: "p1", value: "v1", priority: "important", offsets: [0, 20]},
|
||||
{name: "p2", value: "v2", priority: "important", offsets: [21, 40]}
|
||||
]
|
||||
},
|
||||
// Test invalid priority
|
||||
{
|
||||
input: "p1: v1 important;",
|
||||
expected: [
|
||||
{name: "p1", value: "v1 important", priority: ""}
|
||||
{name: "p1", value: "v1 important", priority: "", offsets: [0, 17]}
|
||||
]
|
||||
},
|
||||
// Test various types of background-image urls
|
||||
@ -81,7 +81,8 @@ const TEST_DATA = [
|
||||
expected: [{
|
||||
name: "background-image",
|
||||
value: "url(../../relative/image.png)",
|
||||
priority: ""
|
||||
priority: "",
|
||||
offsets: [0, 47]
|
||||
}]
|
||||
},
|
||||
{
|
||||
@ -89,7 +90,8 @@ const TEST_DATA = [
|
||||
expected: [{
|
||||
name: "background-image",
|
||||
value: "url(http://site.com/test.png)",
|
||||
priority: ""
|
||||
priority: "",
|
||||
offsets: [0, 47]
|
||||
}]
|
||||
},
|
||||
{
|
||||
@ -97,7 +99,8 @@ const TEST_DATA = [
|
||||
expected: [{
|
||||
name: "background-image",
|
||||
value: "url(wow.gif)",
|
||||
priority: ""
|
||||
priority: "",
|
||||
offsets: [0, 30]
|
||||
}]
|
||||
},
|
||||
// Test that urls with :;{} characters in them are parsed correctly
|
||||
@ -108,7 +111,8 @@ const TEST_DATA = [
|
||||
name: "background",
|
||||
value: "red url(\"http://site.com/image{}:;.png?id=4#wat\") " +
|
||||
"repeat top right",
|
||||
priority: ""
|
||||
priority: "",
|
||||
offsets: [0, 78]
|
||||
}]
|
||||
},
|
||||
// Test that an empty string results in an empty array
|
||||
@ -126,7 +130,8 @@ const TEST_DATA = [
|
||||
expected: [{
|
||||
name: "content",
|
||||
value: "\";color:red;}selector{color:yellow;\"",
|
||||
priority: ""
|
||||
priority: "",
|
||||
offsets: [0, 45]
|
||||
}]
|
||||
},
|
||||
// Test that rules aren't parsed, just declarations. So { and } found after a
|
||||
@ -134,78 +139,85 @@ const TEST_DATA = [
|
||||
{
|
||||
input: "body {color:red;} p {color: blue;}",
|
||||
expected: [
|
||||
{name: "body {color", value: "red", priority: ""},
|
||||
{name: "} p {color", value: "blue", priority: ""},
|
||||
{name: "}", value: "", priority: ""}
|
||||
{name: "body {color", value: "red", priority: "", offsets: [0, 16]},
|
||||
{name: "} p {color", value: "blue", priority: "", offsets: [16, 33]},
|
||||
{name: "}", value: "", priority: "", offsets: [33, 34]}
|
||||
]
|
||||
},
|
||||
// Test unbalanced : and ;
|
||||
{
|
||||
input: "color :red : font : arial;",
|
||||
expected: [
|
||||
{name: "color", value: "red : font : arial", priority: ""}
|
||||
{name: "color", value: "red : font : arial", priority: "",
|
||||
offsets: [0, 26]}
|
||||
]
|
||||
},
|
||||
{
|
||||
input: "background: red;;;;;",
|
||||
expected: [{name: "background", value: "red", priority: ""}]
|
||||
expected: [{name: "background", value: "red", priority: "",
|
||||
offsets: [0, 16]}]
|
||||
},
|
||||
{
|
||||
input: "background:;",
|
||||
expected: [{name: "background", value: "", priority: ""}]
|
||||
expected: [{name: "background", value: "", priority: "",
|
||||
offsets: [0, 12]}]
|
||||
},
|
||||
{input: ";;;;;", expected: []},
|
||||
{input: ":;:;", expected: []},
|
||||
// Test name only
|
||||
{input: "color", expected: [
|
||||
{name: "color", value: "", priority: ""}
|
||||
{name: "color", value: "", priority: "", offsets: [0, 5]}
|
||||
]},
|
||||
// Test trailing name without :
|
||||
{input: "color:blue;font", expected: [
|
||||
{name: "color", value: "blue", priority: ""},
|
||||
{name: "font", value: "", priority: ""}
|
||||
{name: "color", value: "blue", priority: "", offsets: [0, 11]},
|
||||
{name: "font", value: "", priority: "", offsets: [11, 15]}
|
||||
]},
|
||||
// Test trailing name with :
|
||||
{input: "color:blue;font:", expected: [
|
||||
{name: "color", value: "blue", priority: ""},
|
||||
{name: "font", value: "", priority: ""}
|
||||
{name: "color", value: "blue", priority: "", offsets: [0, 11]},
|
||||
{name: "font", value: "", priority: "", offsets: [11, 16]}
|
||||
]},
|
||||
// Test leading value
|
||||
{input: "Arial;color:blue;", expected: [
|
||||
{name: "", value: "Arial", priority: ""},
|
||||
{name: "color", value: "blue", priority: ""}
|
||||
{name: "", value: "Arial", priority: "", offsets: [0, 6]},
|
||||
{name: "color", value: "blue", priority: "", offsets: [6, 17]}
|
||||
]},
|
||||
// Test hex colors
|
||||
{
|
||||
input: "color: #333",
|
||||
expected: [{name: "color", value: "#333", priority: ""}]
|
||||
expected: [{name: "color", value: "#333", priority: "", offsets: [0, 11]}]
|
||||
},
|
||||
{
|
||||
input: "color: #456789",
|
||||
expected: [{name: "color", value: "#456789", priority: ""}]
|
||||
expected: [{name: "color", value: "#456789", priority: "",
|
||||
offsets: [0, 14]}]
|
||||
},
|
||||
{
|
||||
input: "wat: #XYZ",
|
||||
expected: [{name: "wat", value: "#XYZ", priority: ""}]
|
||||
expected: [{name: "wat", value: "#XYZ", priority: "", offsets: [0, 9]}]
|
||||
},
|
||||
// Test string/url quotes escaping
|
||||
{
|
||||
input: "content: \"this is a 'string'\"",
|
||||
expected: [{name: "content", value: "\"this is a 'string'\"", priority: ""}]
|
||||
expected: [{name: "content", value: "\"this is a 'string'\"", priority: "",
|
||||
offsets: [0, 29]}]
|
||||
},
|
||||
{
|
||||
input: 'content: "this is a \\"string\\""',
|
||||
expected: [{
|
||||
name: "content",
|
||||
value: '"this is a \\"string\\""',
|
||||
priority: ""}]
|
||||
priority: "",
|
||||
offsets: [0, 31]}]
|
||||
},
|
||||
{
|
||||
input: "content: 'this is a \"string\"'",
|
||||
expected: [{
|
||||
name: "content",
|
||||
value: '\'this is a "string"\'',
|
||||
priority: ""
|
||||
priority: "",
|
||||
offsets: [0, 29]
|
||||
}]
|
||||
},
|
||||
{
|
||||
@ -213,7 +225,8 @@ const TEST_DATA = [
|
||||
expected: [{
|
||||
name: "content",
|
||||
value: "'this is a \\'string\\''",
|
||||
priority: ""
|
||||
priority: "",
|
||||
offsets: [0, 31],
|
||||
}]
|
||||
},
|
||||
{
|
||||
@ -221,7 +234,8 @@ const TEST_DATA = [
|
||||
expected: [{
|
||||
name: "content",
|
||||
value: "'this \\' is a \" really strange string'",
|
||||
priority: ""
|
||||
priority: "",
|
||||
offsets: [0, 47]
|
||||
}]
|
||||
},
|
||||
{
|
||||
@ -229,22 +243,124 @@ const TEST_DATA = [
|
||||
o very long title\"",
|
||||
expected: [
|
||||
{name: "content", value: '"a not s\\\
|
||||
o very long title"', priority: ""}
|
||||
o very long title"', priority: "", offsets: [0, 46]}
|
||||
]
|
||||
},
|
||||
// Test calc with nested parentheses
|
||||
{
|
||||
input: "width: calc((100% - 3em) / 2)",
|
||||
expected: [{name: "width", value: "calc((100% - 3em) / 2)", priority: ""}]
|
||||
expected: [{name: "width", value: "calc((100% - 3em) / 2)", priority: "",
|
||||
offsets: [0, 29]}]
|
||||
},
|
||||
|
||||
// Simple embedded comment test.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "width: 5; /* background: green; */ background: red;",
|
||||
expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
|
||||
{name: "background", value: "green", priority: "",
|
||||
offsets: [13, 31], commentOffsets: [10, 34]},
|
||||
{name: "background", value: "red", priority: "",
|
||||
offsets: [35, 51]}]
|
||||
},
|
||||
|
||||
// Embedded comment where the parsing heuristic fails.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "width: 5; /* background something: green; */ background: red;",
|
||||
expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
|
||||
{name: "background", value: "red", priority: "",
|
||||
offsets: [45, 61]}]
|
||||
},
|
||||
|
||||
// Embedded comment where the parsing heuristic is a bit funny.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "width: 5; /* background: */ background: red;",
|
||||
expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
|
||||
{name: "background", value: "", priority: "",
|
||||
offsets: [13, 24], commentOffsets: [10, 27]},
|
||||
{name: "background", value: "red", priority: "",
|
||||
offsets: [28, 44]}]
|
||||
},
|
||||
|
||||
// Another case where the parsing heuristic says not to bother; note
|
||||
// that there is no ";" in the comment.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "width: 5; /* background: yellow */ background: red;",
|
||||
expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
|
||||
{name: "background", value: "yellow", priority: "",
|
||||
offsets: [13, 31], commentOffsets: [10, 34]},
|
||||
{name: "background", value: "red", priority: "",
|
||||
offsets: [35, 51]}]
|
||||
},
|
||||
|
||||
// Parsing a comment should yield text that has been unescaped, and
|
||||
// the offsets should refer to the original text.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "/* content: '*\\/'; */",
|
||||
expected: [{name: "content", value: "'*/'", priority: "",
|
||||
offsets: [3, 18], commentOffsets: [0, 21]}]
|
||||
},
|
||||
|
||||
// Parsing a comment should yield text that has been unescaped, and
|
||||
// the offsets should refer to the original text. This variant
|
||||
// tests the no-semicolon path.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "/* content: '*\\/' */",
|
||||
expected: [{name: "content", value: "'*/'", priority: "",
|
||||
offsets: [3, 17], commentOffsets: [0, 20]}]
|
||||
},
|
||||
|
||||
// A comment-in-a-comment should yield the correct offsets.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "/* color: /\\* comment *\\/ red; */",
|
||||
expected: [{name: "color", value: "red", priority: "",
|
||||
offsets: [3, 30], commentOffsets: [0, 33]}]
|
||||
},
|
||||
|
||||
// HTML comments are ignored.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "<!-- color: red; --> color: blue;",
|
||||
expected: [{name: "color", value: "red", priority: "",
|
||||
offsets: [5, 16]},
|
||||
{name: "color", value: "blue", priority: "",
|
||||
offsets: [21, 33]}]
|
||||
},
|
||||
|
||||
// Don't error on an empty comment.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "/**/",
|
||||
expected: []
|
||||
},
|
||||
|
||||
// Parsing our special comments skips the name-check heuristic.
|
||||
{
|
||||
parseComments: true,
|
||||
input: "/*! walrus: zebra; */",
|
||||
expected: [{name: "walrus", value: "zebra", priority: "",
|
||||
offsets: [4, 18], commentOffsets: [0, 21]}]
|
||||
}
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
run_basic_tests();
|
||||
run_comment_tests();
|
||||
}
|
||||
|
||||
// Test parseDeclarations.
|
||||
function run_basic_tests() {
|
||||
for (let test of TEST_DATA) {
|
||||
do_print("Test input string " + test.input);
|
||||
let output;
|
||||
try {
|
||||
output = parseDeclarations(test.input);
|
||||
output = parseDeclarations(test.input, test.parseComments);
|
||||
} catch (e) {
|
||||
do_print("parseDeclarations threw an exception with the given input " +
|
||||
"string");
|
||||
@ -262,6 +378,29 @@ function run_test() {
|
||||
}
|
||||
}
|
||||
|
||||
const COMMENT_DATA = [
|
||||
{
|
||||
input: "content: 'hi",
|
||||
expected: [{name: "content", value: "'hi", priority: "", terminator: "';",
|
||||
offsets: [2, 14], colonOffsets: [9, 11],
|
||||
commentOffsets: [0, 16]}],
|
||||
},
|
||||
{
|
||||
input: "text that once confounded the parser;",
|
||||
expected: []
|
||||
},
|
||||
];
|
||||
|
||||
// Test parseCommentDeclarations.
|
||||
function run_comment_tests() {
|
||||
for (let test of COMMENT_DATA) {
|
||||
do_print("Test input string " + test.input);
|
||||
let output = _parseCommentDeclarations(test.input, 0,
|
||||
test.input.length + 4);
|
||||
deepEqual(output, test.expected);
|
||||
}
|
||||
}
|
||||
|
||||
function assertOutput(actual, expected) {
|
||||
if (actual.length === expected.length) {
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
@ -271,6 +410,10 @@ function assertOutput(actual, expected) {
|
||||
do_check_eq(expected[i].name, actual[i].name);
|
||||
do_check_eq(expected[i].value, actual[i].value);
|
||||
do_check_eq(expected[i].priority, actual[i].priority);
|
||||
deepEqual(expected[i].offsets, actual[i].offsets);
|
||||
if ("commentOffsets" in expected[i]) {
|
||||
deepEqual(expected[i].commentOffsets, actual[i].commentOffsets);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let prop of actual) {
|
||||
|
@ -0,0 +1,478 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
const {parseDeclarations, RuleRewriter} =
|
||||
devtools.require("devtools/client/styleinspector/css-parsing-utils");
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
desc: "simple set",
|
||||
input: "p:v;",
|
||||
instruction: {type: "set", name: "p", value: "N", priority: "",
|
||||
index: 0},
|
||||
expected: "p:N;"
|
||||
},
|
||||
{
|
||||
desc: "simple set clearing !important",
|
||||
input: "p:v !important;",
|
||||
instruction: {type: "set", name: "p", value: "N", priority: "",
|
||||
index: 0},
|
||||
expected: "p:N;"
|
||||
},
|
||||
{
|
||||
desc: "simple set adding !important",
|
||||
input: "p:v;",
|
||||
instruction: {type: "set", name: "p", value: "N", priority: "important",
|
||||
index: 0},
|
||||
expected: "p:N !important;"
|
||||
},
|
||||
{
|
||||
desc: "simple set between comments",
|
||||
input: "/*color:red;*/ p:v; /*color:green;*/",
|
||||
instruction: {type: "set", name: "p", value: "N", priority: "",
|
||||
index: 1},
|
||||
expected: "/*color:red;*/ p:N; /*color:green;*/"
|
||||
},
|
||||
// The rule view can generate a "set" with a previously unknown
|
||||
// property index; which should work like "create".
|
||||
{
|
||||
desc: "set at unknown index",
|
||||
input: "a:b; e: f;",
|
||||
instruction: {type: "set", name: "c", value: "d", priority: "",
|
||||
index: 2},
|
||||
expected: "a:b; e: f;c: d;"
|
||||
},
|
||||
{
|
||||
desc: "simple rename",
|
||||
input: "p:v;",
|
||||
instruction: {type: "rename", name: "p", newName: "q", index: 0},
|
||||
expected: "q:v;"
|
||||
},
|
||||
// "rename" is passed the name that the user entered, and must do
|
||||
// any escaping necessary to ensure that this is an identifier.
|
||||
{
|
||||
desc: "rename requiring escape",
|
||||
input: "p:v;",
|
||||
instruction: {type: "rename", name: "p", newName: "a b", index: 0},
|
||||
expected: "a\\ b:v;"
|
||||
},
|
||||
{
|
||||
desc: "simple create",
|
||||
input: "",
|
||||
instruction: {type: "create", name: "p", value: "v", priority: "important",
|
||||
index: 0},
|
||||
expected: "p: v !important;"
|
||||
},
|
||||
{
|
||||
desc: "create between two properties",
|
||||
input: "a:b; e: f;",
|
||||
instruction: {type: "create", name: "c", value: "d", priority: "",
|
||||
index: 1},
|
||||
expected: "a:b; c: d;e: f;"
|
||||
},
|
||||
// "create" is passed the name that the user entered, and must do
|
||||
// any escaping necessary to ensure that this is an identifier.
|
||||
{
|
||||
desc: "create requiring escape",
|
||||
input: "",
|
||||
instruction: {type: "create", name: "a b", value: "d", priority: "",
|
||||
index: 1},
|
||||
expected: "a\\ b: d;"
|
||||
},
|
||||
{
|
||||
desc: "simple disable",
|
||||
input: "p:v;",
|
||||
instruction: {type: "enable", name: "p", value: false, index: 0},
|
||||
expected: "/*! p:v; */"
|
||||
},
|
||||
{
|
||||
desc: "simple enable",
|
||||
input: "/* color:v; */",
|
||||
instruction: {type: "enable", name: "color", value: true, index: 0},
|
||||
expected: "color:v;"
|
||||
},
|
||||
{
|
||||
desc: "enable with following property in comment",
|
||||
input: "/* color:red; color: blue; */",
|
||||
instruction: {type: "enable", name: "color", value: true, index: 0},
|
||||
expected: "color:red; /* color: blue; */"
|
||||
},
|
||||
{
|
||||
desc: "enable with preceding property in comment",
|
||||
input: "/* color:red; color: blue; */",
|
||||
instruction: {type: "enable", name: "color", value: true, index: 1},
|
||||
expected: "/* color:red; */ color: blue;"
|
||||
},
|
||||
{
|
||||
desc: "simple remove",
|
||||
input: "a:b;c:d;e:f;",
|
||||
instruction: {type: "remove", name: "c", index: 1},
|
||||
expected: "a:b;e:f;"
|
||||
},
|
||||
{
|
||||
desc: "disable with comment ender in string",
|
||||
input: "content: '*/';",
|
||||
instruction: {type: "enable", name: "content", value: false, index: 0},
|
||||
expected: "/*! content: '*\\/'; */"
|
||||
},
|
||||
{
|
||||
desc: "enable with comment ender in string",
|
||||
input: "/* content: '*\\/'; */",
|
||||
instruction: {type: "enable", name: "content", value: true, index: 0},
|
||||
expected: "content: '*/';"
|
||||
},
|
||||
{
|
||||
desc: "enable requiring semicolon insertion",
|
||||
// Note the lack of a trailing semicolon in the comment.
|
||||
input: "/* color:red */ color: blue;",
|
||||
instruction: {type: "enable", name: "color", value: true, index: 0},
|
||||
expected: "color:red; color: blue;"
|
||||
},
|
||||
{
|
||||
desc: "create requiring semicolon insertion",
|
||||
// Note the lack of a trailing semicolon.
|
||||
input: "color: red",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 1},
|
||||
expected: "color: red;a: b;"
|
||||
},
|
||||
|
||||
// Newline insertion.
|
||||
{
|
||||
desc: "simple newline insertion",
|
||||
input: "\ncolor: red;\n",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 1},
|
||||
expected: "\ncolor: red;\na: b;\n"
|
||||
},
|
||||
// Newline insertion.
|
||||
{
|
||||
desc: "semicolon insertion before newline",
|
||||
// Note the lack of a trailing semicolon.
|
||||
input: "\ncolor: red\n",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 1},
|
||||
expected: "\ncolor: red;\na: b;\n"
|
||||
},
|
||||
// Newline insertion.
|
||||
{
|
||||
desc: "newline and semicolon insertion",
|
||||
// Note the lack of a trailing semicolon and newline.
|
||||
input: "\ncolor: red",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 1},
|
||||
expected: "\ncolor: red;\na: b;\n"
|
||||
},
|
||||
|
||||
// Newline insertion and indentation.
|
||||
{
|
||||
desc: "indentation with create",
|
||||
input: "\n color: red;\n",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 1},
|
||||
expected: "\n color: red;\n a: b;\n"
|
||||
},
|
||||
// Newline insertion and indentation.
|
||||
{
|
||||
desc: "indentation plus semicolon insertion before newline",
|
||||
// Note the lack of a trailing semicolon.
|
||||
input: "\n color: red\n",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 1},
|
||||
expected: "\n color: red;\n a: b;\n"
|
||||
},
|
||||
{
|
||||
desc: "indentation inserted before trailing whitespace",
|
||||
// Note the trailing whitespace. This could come from a rule
|
||||
// like:
|
||||
// @supports (mumble) {
|
||||
// body {
|
||||
// color: red;
|
||||
// }
|
||||
// }
|
||||
// Here if we create a rule we don't want it to follow
|
||||
// the indentation of the "}".
|
||||
input: "\n color: red;\n ",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 1},
|
||||
expected: "\n color: red;\n a: b;\n "
|
||||
},
|
||||
// Newline insertion and indentation.
|
||||
{
|
||||
desc: "indentation comes from preceding comment",
|
||||
// Note how the comment comes before the declaration.
|
||||
input: "\n /* comment */ color: red\n",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 1},
|
||||
expected: "\n /* comment */ color: red;\n a: b;\n"
|
||||
},
|
||||
// Default indentation.
|
||||
{
|
||||
desc: "use of default indentation",
|
||||
input: "\n",
|
||||
instruction: {type: "create", name: "a", value: "b", priority: "",
|
||||
index: 0},
|
||||
expected: "\n\ta: b;\n"
|
||||
},
|
||||
|
||||
// Deletion handles newlines properly.
|
||||
{
|
||||
desc: "deletion removes newline",
|
||||
input: "a:b;\nc:d;\ne:f;",
|
||||
instruction: {type: "remove", name: "c", index: 1},
|
||||
expected: "a:b;\ne:f;"
|
||||
},
|
||||
// Deletion handles newlines properly.
|
||||
{
|
||||
desc: "deletion remove blank line",
|
||||
input: "\n a:b;\n c:d; \ne:f;",
|
||||
instruction: {type: "remove", name: "c", index: 1},
|
||||
expected: "\n a:b;\ne:f;"
|
||||
},
|
||||
// Deletion handles newlines properly.
|
||||
{
|
||||
desc: "deletion leaves comment",
|
||||
input: "\n a:b;\n /* something */ c:d; \ne:f;",
|
||||
instruction: {type: "remove", name: "c", index: 1},
|
||||
expected: "\n a:b;\n /* something */ \ne:f;"
|
||||
},
|
||||
// Deletion handles newlines properly.
|
||||
{
|
||||
desc: "deletion leaves previous newline",
|
||||
input: "\n a:b;\n c:d; \ne:f;",
|
||||
instruction: {type: "remove", name: "e", index: 2},
|
||||
expected: "\n a:b;\n c:d; \n"
|
||||
},
|
||||
// Deletion handles newlines properly.
|
||||
{
|
||||
desc: "deletion removes trailing whitespace",
|
||||
input: "\n a:b;\n c:d; \n e:f;",
|
||||
instruction: {type: "remove", name: "e", index: 2},
|
||||
expected: "\n a:b;\n c:d; \n"
|
||||
},
|
||||
// Deletion handles newlines properly.
|
||||
{
|
||||
desc: "deletion preserves indentation",
|
||||
input: " a:b;\n c:d; \n e:f;",
|
||||
instruction: {type: "remove", name: "a", index: 0},
|
||||
expected: " c:d; \n e:f;"
|
||||
},
|
||||
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "enable single quote termination",
|
||||
input: "/* content: 'hi */ color: red;",
|
||||
instruction: {type: "enable", name: "content", value: true, index: 0},
|
||||
expected: "content: 'hi'; color: red;"
|
||||
},
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "create single quote termination",
|
||||
input: "content: 'hi",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "content: 'hi';color: red;"
|
||||
},
|
||||
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "enable double quote termination",
|
||||
input: "/* content: \"hi */ color: red;",
|
||||
instruction: {type: "enable", name: "content", value: true, index: 0},
|
||||
expected: "content: \"hi\"; color: red;"
|
||||
},
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "create double quote termination",
|
||||
input: "content: \"hi",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "content: \"hi\";color: red;"
|
||||
},
|
||||
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "enable url termination",
|
||||
input: "/* background-image: url(something.jpg */ color: red;",
|
||||
instruction: {type: "enable", name: "background-image", value: true,
|
||||
index: 0},
|
||||
expected: "background-image: url(something.jpg); color: red;"
|
||||
},
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "create url termination",
|
||||
input: "background-image: url(something.jpg",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "background-image: url(something.jpg);color: red;"
|
||||
},
|
||||
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "enable url single quote termination",
|
||||
input: "/* background-image: url('something.jpg */ color: red;",
|
||||
instruction: {type: "enable", name: "background-image", value: true,
|
||||
index: 0},
|
||||
expected: "background-image: url('something.jpg'); color: red;"
|
||||
},
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "create url single quote termination",
|
||||
input: "background-image: url('something.jpg",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "background-image: url('something.jpg');color: red;"
|
||||
},
|
||||
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "create url double quote termination",
|
||||
input: "/* background-image: url(\"something.jpg */ color: red;",
|
||||
instruction: {type: "enable", name: "background-image", value: true,
|
||||
index: 0},
|
||||
expected: "background-image: url(\"something.jpg\"); color: red;"
|
||||
},
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "enable url double quote termination",
|
||||
input: "background-image: url(\"something.jpg",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "background-image: url(\"something.jpg\");color: red;"
|
||||
},
|
||||
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "create backslash termination",
|
||||
input: "something: \\",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "something: \\\\;color: red;"
|
||||
},
|
||||
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "enable backslash single quote termination",
|
||||
input: "something: '\\",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "something: '\\\\';color: red;"
|
||||
},
|
||||
{
|
||||
desc: "enable backslash double quote termination",
|
||||
input: "something: \"\\",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "something: \"\\\\\";color: red;"
|
||||
},
|
||||
|
||||
// Termination insertion corner case.
|
||||
{
|
||||
desc: "enable comment termination",
|
||||
input: "something: blah /* comment ",
|
||||
instruction: {type: "create", name: "color", value: "red", priority: "",
|
||||
index: 1},
|
||||
expected: "something: blah /* comment*/; color: red;"
|
||||
},
|
||||
|
||||
// Rewrite a "heuristic override" comment.
|
||||
{
|
||||
desc: "enable with heuristic override comment",
|
||||
input: "/*! walrus: zebra; */",
|
||||
instruction: {type: "enable", name: "walrus", value: true, index: 0},
|
||||
expected: "walrus: zebra;"
|
||||
},
|
||||
|
||||
// Sanitize a bad value.
|
||||
{
|
||||
desc: "create sanitize unpaired brace",
|
||||
input: "",
|
||||
instruction: {type: "create", name: "p", value: "}", priority: "",
|
||||
index: 0},
|
||||
expected: "p: \\};",
|
||||
changed: {0: "\\}"}
|
||||
},
|
||||
// Sanitize a bad value.
|
||||
{
|
||||
desc: "set sanitize unpaired brace",
|
||||
input: "walrus: zebra;",
|
||||
instruction: {type: "set", name: "walrus", value: "{{}}}", priority: "",
|
||||
index: 0},
|
||||
expected: "walrus: {{}}\\};",
|
||||
changed: {0: "{{}}\\}"}
|
||||
},
|
||||
// Sanitize a bad value.
|
||||
{
|
||||
desc: "enable sanitize unpaired brace",
|
||||
input: "/*! walrus: }*/",
|
||||
instruction: {type: "enable", name: "walrus", value: true, index: 0},
|
||||
expected: "walrus: \\};",
|
||||
changed: {0: "\\}"}
|
||||
},
|
||||
|
||||
// Creating a new declaration does not require an attempt to
|
||||
// terminate a previous commented declaration.
|
||||
{
|
||||
desc: "disabled declaration does not need semicolon insertion",
|
||||
input: "/*! no: semicolon */\n",
|
||||
instruction: {type: "create", name: "walrus", value: "zebra", priority: "",
|
||||
index: 1},
|
||||
expected: "/*! no: semicolon */\nwalrus: zebra;\n",
|
||||
changed: {}
|
||||
},
|
||||
];
|
||||
|
||||
function rewriteDeclarations(inputString, instruction, defaultIndentation) {
|
||||
let rewriter = new RuleRewriter(null, inputString);
|
||||
rewriter.defaultIndentation = defaultIndentation;
|
||||
|
||||
switch (instruction.type) {
|
||||
case "rename":
|
||||
rewriter.renameProperty(instruction.index, instruction.name,
|
||||
instruction.newName);
|
||||
break;
|
||||
|
||||
case "enable":
|
||||
rewriter.setPropertyEnabled(instruction.index, instruction.name,
|
||||
instruction.value);
|
||||
break;
|
||||
|
||||
case "create":
|
||||
rewriter.createProperty(instruction.index, instruction.name,
|
||||
instruction.value, instruction.priority);
|
||||
break;
|
||||
|
||||
case "set":
|
||||
rewriter.setProperty(instruction.index, instruction.name,
|
||||
instruction.value, instruction.priority);
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
rewriter.removeProperty(instruction.index, instruction.name);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("unrecognized instruction");
|
||||
}
|
||||
|
||||
return rewriter.getResult();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let i = 0;
|
||||
for (let test of TEST_DATA) {
|
||||
++i;
|
||||
let {changed, text} = rewriteDeclarations(test.input, test.instruction,
|
||||
"\t");
|
||||
equal(text, test.expected, "output for " + test.desc);
|
||||
if ("changed" in test) {
|
||||
deepEqual(changed, test.changed, "changed result for " + test.desc);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ tail =
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_escapeCSSComment.js]
|
||||
[test_parseDeclarations.js]
|
||||
[test_parsePseudoClassesAndAttributes.js]
|
||||
[test_parseSingleValue.js]
|
||||
[test_rewriteDeclarations.js]
|
||||
|
@ -154,6 +154,13 @@ SimulatorProcess.prototype = {
|
||||
// Ignore eventual zombie instances of b2g that are left over.
|
||||
args.push("-no-remote");
|
||||
|
||||
// If we are running a simulator based on Mulet,
|
||||
// we have to override the default chrome URL
|
||||
// in order to prevent the Browser UI to appear.
|
||||
if (this.b2gBinary.leafName.includes("firefox")) {
|
||||
args.push("-chrome", "chrome://b2g/content/shell.html");
|
||||
}
|
||||
|
||||
return args;
|
||||
},
|
||||
};
|
||||
@ -215,6 +222,19 @@ Object.defineProperty(ASPp, "b2gBinary", {
|
||||
};
|
||||
binaries[OS].split("/").forEach(node => file.append(node));
|
||||
}
|
||||
// If the binary doesn't exists, it may be because of a simulator
|
||||
// based on mulet, which has a different binary name.
|
||||
if (!file.exists()) {
|
||||
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
|
||||
file.append("firefox");
|
||||
let binaries = {
|
||||
win32: "firefox-bin.exe",
|
||||
mac64: "B2G.app/Contents/MacOS/firefox-bin",
|
||||
linux32: "firefox-bin",
|
||||
linux64: "firefox-bin",
|
||||
};
|
||||
binaries[OS].split("/").forEach(node => file.append(node));
|
||||
}
|
||||
return file;
|
||||
}
|
||||
});
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "mozilla/dom/CanvasRenderingContext2D.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/DataSurfaceHelpers.h"
|
||||
#include "mozilla/layers/AsyncCanvasRenderer.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/unused.h"
|
||||
@ -166,7 +165,6 @@ public:
|
||||
mSize,
|
||||
mImage,
|
||||
nullptr,
|
||||
nullptr,
|
||||
getter_AddRefs(stream),
|
||||
mEncoder);
|
||||
|
||||
@ -180,7 +178,6 @@ public:
|
||||
mSize,
|
||||
mImage,
|
||||
nullptr,
|
||||
nullptr,
|
||||
getter_AddRefs(stream),
|
||||
mEncoder);
|
||||
}
|
||||
@ -237,7 +234,6 @@ ImageEncoder::ExtractData(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream)
|
||||
{
|
||||
nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
|
||||
@ -246,9 +242,10 @@ ImageEncoder::ExtractData(nsAString& aType,
|
||||
}
|
||||
|
||||
return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, nullptr,
|
||||
aContext, aRenderer, aStream, encoder);
|
||||
aContext, aStream, encoder);
|
||||
}
|
||||
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
|
||||
@ -344,7 +341,6 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
||||
const nsIntSize aSize,
|
||||
layers::Image* aImage,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream,
|
||||
imgIEncoder* aEncoder)
|
||||
{
|
||||
@ -370,11 +366,6 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
||||
rv = aContext->GetInputStream(encoderType.get(),
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else if (aRenderer) {
|
||||
NS_ConvertUTF16toUTF8 encoderType(aType);
|
||||
rv = aRenderer->GetInputStream(encoderType.get(),
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else if (aImage) {
|
||||
// It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread.
|
||||
// Other image formats could have problem to convert format off-main-thread.
|
||||
|
@ -19,7 +19,6 @@ class nsICanvasRenderingContextInternal;
|
||||
namespace mozilla {
|
||||
|
||||
namespace layers {
|
||||
class AsyncCanvasRenderer;
|
||||
class Image;
|
||||
} // namespace layers
|
||||
|
||||
@ -41,7 +40,6 @@ public:
|
||||
const nsAString& aOptions,
|
||||
const nsIntSize aSize,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream);
|
||||
|
||||
// Extracts data asynchronously. aType may change to "image/png" if we had to
|
||||
@ -86,7 +84,7 @@ public:
|
||||
nsIInputStream** aStream);
|
||||
|
||||
private:
|
||||
// When called asynchronously, aContext and aRenderer are null.
|
||||
// When called asynchronously, aContext is null.
|
||||
static nsresult
|
||||
ExtractDataInternal(const nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
@ -95,7 +93,6 @@ private:
|
||||
const nsIntSize aSize,
|
||||
layers::Image* aImage,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream,
|
||||
imgIEncoder* aEncoder);
|
||||
|
||||
|
@ -21,8 +21,6 @@
|
||||
#include "mozilla/dom/StructuredClone.h"
|
||||
#include "mozilla/dom/MessagePort.h"
|
||||
#include "mozilla/dom/MessagePortBinding.h"
|
||||
#include "mozilla/dom/OffscreenCanvas.h"
|
||||
#include "mozilla/dom/OffscreenCanvasBinding.h"
|
||||
#include "mozilla/dom/PMessagePort.h"
|
||||
#include "mozilla/dom/StructuredCloneTags.h"
|
||||
#include "mozilla/dom/SubtleCryptoBinding.h"
|
||||
@ -418,49 +416,19 @@ StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx,
|
||||
if (aTag == SCTAG_DOM_NULL_PRINCIPAL ||
|
||||
aTag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
|
||||
aTag == SCTAG_DOM_CONTENT_PRINCIPAL) {
|
||||
if (!NS_IsMainThread()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mozilla::ipc::PrincipalInfo info;
|
||||
if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) {
|
||||
info = mozilla::ipc::SystemPrincipalInfo();
|
||||
} else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) {
|
||||
info = mozilla::ipc::NullPrincipalInfo();
|
||||
} else {
|
||||
|
||||
uint32_t suffixLength, specLength;
|
||||
if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsAutoCString suffix;
|
||||
suffix.SetLength(suffixLength);
|
||||
if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsAutoCString spec;
|
||||
spec.SetLength(specLength);
|
||||
if (!JS_ReadBytes(aReader, spec.BeginWriting(), specLength)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OriginAttributes attrs;
|
||||
attrs.PopulateFromSuffix(suffix);
|
||||
info = mozilla::ipc::ContentPrincipalInfo(attrs, spec);
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(info, &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
JSPrincipals* prin;
|
||||
if (!nsJSPrincipals::ReadKnownPrincipalType(aCx, aReader, aTag, &prin)) {
|
||||
return nullptr;
|
||||
}
|
||||
// nsJSPrincipals::ReadKnownPrincipalType addrefs for us, but because of the
|
||||
// casting between JSPrincipals* and nsIPrincipal* we can't use
|
||||
// getter_AddRefs above and have to already_AddRefed here.
|
||||
nsCOMPtr<nsIPrincipal> principal = already_AddRefed<nsIPrincipal>(nsJSPrincipals::get(prin));
|
||||
|
||||
JS::RootedValue result(aCx);
|
||||
rv = nsContentUtils::WrapNative(aCx, principal, &NS_GET_IID(nsIPrincipal),
|
||||
&result);
|
||||
nsresult rv = nsContentUtils::WrapNative(aCx, principal,
|
||||
&NS_GET_IID(nsIPrincipal),
|
||||
&result);
|
||||
if (NS_FAILED(rv)) {
|
||||
xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return nullptr;
|
||||
@ -560,27 +528,8 @@ StructuredCloneHolder::WriteFullySerializableObjects(JSContext* aCx,
|
||||
nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(aObj);
|
||||
nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base);
|
||||
if (principal) {
|
||||
mozilla::ipc::PrincipalInfo info;
|
||||
if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(principal, &info)))) {
|
||||
xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.type() == mozilla::ipc::PrincipalInfo::TNullPrincipalInfo) {
|
||||
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0);
|
||||
}
|
||||
if (info.type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) {
|
||||
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(info.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
|
||||
const mozilla::ipc::ContentPrincipalInfo& cInfo = info;
|
||||
nsAutoCString suffix;
|
||||
cInfo.attrs().CreateSuffix(suffix);
|
||||
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) &&
|
||||
JS_WriteUint32Pair(aWriter, suffix.Length(), cInfo.spec().Length()) &&
|
||||
JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) &&
|
||||
JS_WriteBytes(aWriter, cInfo.spec().get(), cInfo.spec().Length());
|
||||
auto nsjsprincipals = nsJSPrincipals::get(principal);
|
||||
return nsjsprincipals->write(aCx, aWriter);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1066,25 +1015,6 @@ StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aTag == SCTAG_DOM_CANVAS) {
|
||||
MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
|
||||
mSupportedContext == SameProcessDifferentThread);
|
||||
MOZ_ASSERT(aContent);
|
||||
OffscreenCanvasCloneData* data =
|
||||
static_cast<OffscreenCanvasCloneData*>(aContent);
|
||||
nsRefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(data);
|
||||
delete data;
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (!GetOrCreateDOMReflector(aCx, canvas, &value)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
return false;
|
||||
}
|
||||
|
||||
aReturnObject.set(&value.toObject());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1116,24 +1046,6 @@ StructuredCloneHolder::CustomWriteTransferHandler(JSContext* aCx,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mSupportedContext == SameProcessSameThread ||
|
||||
mSupportedContext == SameProcessDifferentThread) {
|
||||
OffscreenCanvas* canvas = nullptr;
|
||||
rv = UNWRAP_OBJECT(OffscreenCanvas, aObj, canvas);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
MOZ_ASSERT(canvas);
|
||||
|
||||
*aExtraData = 0;
|
||||
*aTag = SCTAG_DOM_CANVAS;
|
||||
*aOwnership = JS::SCTAG_TMO_CUSTOM;
|
||||
*aContent = canvas->ToCloneData();
|
||||
MOZ_ASSERT(*aContent);
|
||||
canvas->SetNeutered();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -1151,17 +1063,6 @@ StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag,
|
||||
MOZ_ASSERT(!aContent);
|
||||
MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
|
||||
MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aTag == SCTAG_DOM_CANVAS) {
|
||||
MOZ_ASSERT(mSupportedContext == SameProcessSameThread ||
|
||||
mSupportedContext == SameProcessDifferentThread);
|
||||
MOZ_ASSERT(aContent);
|
||||
OffscreenCanvasCloneData* data =
|
||||
static_cast<OffscreenCanvasCloneData*>(aContent);
|
||||
delete data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,9 +48,6 @@ enum StructuredCloneTags {
|
||||
|
||||
SCTAG_DOM_FORMDATA,
|
||||
|
||||
// This tag is for OffscreenCanvas.
|
||||
SCTAG_DOM_CANVAS,
|
||||
|
||||
SCTAG_DOM_MAX
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "js/HeapAPI.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "WorkerPrivate.h"
|
||||
#include "WorkerRunnable.h"
|
||||
|
||||
@ -922,6 +923,10 @@ PerformanceBase::Mark(const nsAString& aName, ErrorResult& aRv)
|
||||
nsRefPtr<PerformanceMark> performanceMark =
|
||||
new PerformanceMark(GetAsISupports(), aName, Now());
|
||||
InsertUserEntry(performanceMark);
|
||||
|
||||
if (profiler_is_active()) {
|
||||
PROFILER_MARKER(NS_ConvertUTF16toUTF8(aName).get());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1,265 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "CanvasRenderingContextHelper.h"
|
||||
#include "ImageEncoder.h"
|
||||
#include "mozilla/dom/CanvasRenderingContext2D.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDOMJSUtils.h"
|
||||
#include "nsIScriptContext.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "WebGL1Context.h"
|
||||
#include "WebGL2Context.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
void
|
||||
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
||||
nsIGlobalObject* aGlobal,
|
||||
FileCallback& aCallback,
|
||||
const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsAutoString type;
|
||||
nsContentUtils::ASCIIToLower(aType, type);
|
||||
|
||||
nsAutoString params;
|
||||
bool usingCustomParseOptions;
|
||||
aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
// We disallow canvases of width or height zero, and set them to 1, so
|
||||
// we will have a discrepancy with the sizes of the canvas and the context.
|
||||
// That discrepancy is OK, the rest are not.
|
||||
nsIntSize elementSize = GetWidthHeight();
|
||||
if ((elementSize.width != mCurrentContext->GetWidth() &&
|
||||
(elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
|
||||
(elementSize.height != mCurrentContext->GetHeight() &&
|
||||
(elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* imageBuffer = nullptr;
|
||||
int32_t format = 0;
|
||||
if (mCurrentContext) {
|
||||
mCurrentContext->GetImageBuffer(&imageBuffer, &format);
|
||||
}
|
||||
|
||||
// Encoder callback when encoding is complete.
|
||||
class EncodeCallback : public EncodeCompleteCallback
|
||||
{
|
||||
public:
|
||||
EncodeCallback(nsIGlobalObject* aGlobal, FileCallback* aCallback)
|
||||
: mGlobal(aGlobal)
|
||||
, mFileCallback(aCallback) {}
|
||||
|
||||
// This is called on main thread.
|
||||
nsresult ReceiveBlob(already_AddRefed<Blob> aBlob)
|
||||
{
|
||||
nsRefPtr<Blob> blob = aBlob;
|
||||
|
||||
ErrorResult rv;
|
||||
uint64_t size = blob->GetSize(rv);
|
||||
if (rv.Failed()) {
|
||||
rv.SuppressException();
|
||||
} else {
|
||||
AutoJSAPI jsapi;
|
||||
if (jsapi.Init(mGlobal)) {
|
||||
JS_updateMallocCounter(jsapi.cx(), size);
|
||||
}
|
||||
}
|
||||
|
||||
nsRefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
|
||||
|
||||
mFileCallback->Call(*newBlob, rv);
|
||||
|
||||
mGlobal = nullptr;
|
||||
mFileCallback = nullptr;
|
||||
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
nsRefPtr<FileCallback> mFileCallback;
|
||||
};
|
||||
|
||||
nsRefPtr<EncodeCompleteCallback> callback =
|
||||
new EncodeCallback(aGlobal, &aCallback);
|
||||
|
||||
aRv = ImageEncoder::ExtractDataAsync(type,
|
||||
params,
|
||||
usingCustomParseOptions,
|
||||
imageBuffer,
|
||||
format,
|
||||
GetWidthHeight(),
|
||||
callback);
|
||||
}
|
||||
|
||||
already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType)
|
||||
{
|
||||
MOZ_ASSERT(aContextType != CanvasContextType::NoContext);
|
||||
nsRefPtr<nsICanvasRenderingContextInternal> ret;
|
||||
|
||||
switch (aContextType) {
|
||||
case CanvasContextType::NoContext:
|
||||
break;
|
||||
|
||||
case CanvasContextType::Canvas2D:
|
||||
Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
|
||||
ret = new CanvasRenderingContext2D();
|
||||
break;
|
||||
|
||||
case CanvasContextType::WebGL1:
|
||||
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
|
||||
|
||||
ret = WebGL1Context::Create();
|
||||
if (!ret)
|
||||
return nullptr;
|
||||
|
||||
break;
|
||||
|
||||
case CanvasContextType::WebGL2:
|
||||
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
|
||||
|
||||
ret = WebGL2Context::Create();
|
||||
if (!ret)
|
||||
return nullptr;
|
||||
|
||||
break;
|
||||
}
|
||||
MOZ_ASSERT(ret);
|
||||
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<nsISupports>
|
||||
CanvasRenderingContextHelper::GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
CanvasContextType contextType;
|
||||
if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType))
|
||||
return nullptr;
|
||||
|
||||
if (!mCurrentContext) {
|
||||
// This canvas doesn't have a context yet.
|
||||
nsRefPtr<nsICanvasRenderingContextInternal> context;
|
||||
context = CreateContext(contextType);
|
||||
if (!context) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ensure that the context participates in CC. Note that returning a
|
||||
// CC participant from QI doesn't addref.
|
||||
nsXPCOMCycleCollectionParticipant* cp = nullptr;
|
||||
CallQueryInterface(context, &cp);
|
||||
if (!cp) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mCurrentContext = context.forget();
|
||||
mCurrentContextType = contextType;
|
||||
|
||||
aRv = UpdateContext(aCx, aContextOptions);
|
||||
if (aRv.Failed()) {
|
||||
aRv = NS_OK; // See bug 645792
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// We already have a context of some type.
|
||||
if (contextType != mCurrentContextType)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext;
|
||||
return context.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
CanvasRenderingContextHelper::UpdateContext(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aNewContextOptions)
|
||||
{
|
||||
if (!mCurrentContext)
|
||||
return NS_OK;
|
||||
|
||||
nsIntSize sz = GetWidthHeight();
|
||||
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
|
||||
|
||||
nsresult rv = currentContext->SetIsOpaque(GetOpaqueAttr());
|
||||
if (NS_FAILED(rv)) {
|
||||
mCurrentContext = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = currentContext->SetContextOptions(aCx, aNewContextOptions);
|
||||
if (NS_FAILED(rv)) {
|
||||
mCurrentContext = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = currentContext->SetDimensions(sz.width, sz.height);
|
||||
if (NS_FAILED(rv)) {
|
||||
mCurrentContext = nullptr;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
CanvasRenderingContextHelper::ParseParams(JSContext* aCx,
|
||||
const nsAString& aType,
|
||||
const JS::Value& aEncoderOptions,
|
||||
nsAString& outParams,
|
||||
bool* const outUsingCustomParseOptions)
|
||||
{
|
||||
// Quality parameter is only valid for the image/jpeg MIME type
|
||||
if (aType.EqualsLiteral("image/jpeg")) {
|
||||
if (aEncoderOptions.isNumber()) {
|
||||
double quality = aEncoderOptions.toNumber();
|
||||
// Quality must be between 0.0 and 1.0, inclusive
|
||||
if (quality >= 0.0 && quality <= 1.0) {
|
||||
outParams.AppendLiteral("quality=");
|
||||
outParams.AppendInt(NS_lround(quality * 100.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't parsed the aParams check for proprietary options.
|
||||
// The proprietary option -moz-parse-options will take a image lib encoder
|
||||
// parse options string as is and pass it to the encoder.
|
||||
*outUsingCustomParseOptions = false;
|
||||
if (outParams.Length() == 0 && aEncoderOptions.isString()) {
|
||||
NS_NAMED_LITERAL_STRING(mozParseOptions, "-moz-parse-options:");
|
||||
nsAutoJSString paramString;
|
||||
if (!paramString.init(aCx, aEncoderOptions.toString())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (StringBeginsWith(paramString, mozParseOptions)) {
|
||||
nsDependentSubstring parseOptions = Substring(paramString,
|
||||
mozParseOptions.Length(),
|
||||
paramString.Length() -
|
||||
mozParseOptions.Length());
|
||||
outParams.Append(parseOptions);
|
||||
*outUsingCustomParseOptions = true;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
@ -1,71 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_
|
||||
#define MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_
|
||||
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "nsSize.h"
|
||||
|
||||
class nsICanvasRenderingContextInternal;
|
||||
class nsIGlobalObject;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ErrorResult;
|
||||
|
||||
namespace dom {
|
||||
|
||||
class FileCallback;
|
||||
|
||||
enum class CanvasContextType : uint8_t {
|
||||
NoContext,
|
||||
Canvas2D,
|
||||
WebGL1,
|
||||
WebGL2
|
||||
};
|
||||
|
||||
/**
|
||||
* Povides common RenderingContext functionality used by both OffscreenCanvas
|
||||
* and HTMLCanvasElement.
|
||||
*/
|
||||
class CanvasRenderingContextHelper
|
||||
{
|
||||
public:
|
||||
virtual already_AddRefed<nsISupports>
|
||||
GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual bool GetOpaqueAttr() = 0;
|
||||
|
||||
protected:
|
||||
virtual nsresult UpdateContext(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aNewContextOptions);
|
||||
|
||||
virtual nsresult ParseParams(JSContext* aCx,
|
||||
const nsAString& aType,
|
||||
const JS::Value& aEncoderOptions,
|
||||
nsAString& outParams,
|
||||
bool* const outCustomParseOptions);
|
||||
|
||||
void ToBlob(JSContext* aCx, nsIGlobalObject* global, FileCallback& aCallback,
|
||||
const nsAString& aType, JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType);
|
||||
|
||||
virtual nsIntSize GetWidthHeight() = 0;
|
||||
|
||||
CanvasContextType mCurrentContextType;
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_
|
@ -23,47 +23,12 @@
|
||||
|
||||
#include "CanvasUtils.h"
|
||||
#include "mozilla/gfx/Matrix.h"
|
||||
#include "WebGL2Context.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
namespace CanvasUtils {
|
||||
|
||||
bool
|
||||
GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type)
|
||||
{
|
||||
if (str.EqualsLiteral("2d")) {
|
||||
*out_type = dom::CanvasContextType::Canvas2D;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str.EqualsLiteral("experimental-webgl")) {
|
||||
*out_type = dom::CanvasContextType::WebGL1;
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef MOZ_WEBGL_CONFORMANT
|
||||
if (str.EqualsLiteral("webgl")) {
|
||||
/* WebGL 1.0, $2.1 "Context Creation":
|
||||
* If the user agent supports both the webgl and experimental-webgl
|
||||
* canvas context types, they shall be treated as aliases.
|
||||
*/
|
||||
*out_type = dom::CanvasContextType::WebGL1;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (WebGL2Context::IsSupported()) {
|
||||
if (str.EqualsLiteral("webgl2")) {
|
||||
*out_type = dom::CanvasContextType::WebGL2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This security check utility might be called from an source that never taints
|
||||
* others. For example, while painting a CanvasPattern, which is created from an
|
||||
|
@ -21,7 +21,6 @@ class HTMLCanvasElement;
|
||||
|
||||
namespace CanvasUtils {
|
||||
|
||||
bool GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type);
|
||||
|
||||
// Check that the rectangle [x,y,w,h] is a subrectangle of [0,0,realWidth,realHeight]
|
||||
|
||||
|
@ -1,242 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "OffscreenCanvas.h"
|
||||
|
||||
#include "mozilla/dom/OffscreenCanvasBinding.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "mozilla/layers/AsyncCanvasRenderer.h"
|
||||
#include "mozilla/layers/CanvasClient.h"
|
||||
#include "mozilla/layers/ImageBridgeChild.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "CanvasRenderingContext2D.h"
|
||||
#include "CanvasUtils.h"
|
||||
#include "GLScreenBuffer.h"
|
||||
#include "WebGL1Context.h"
|
||||
#include "WebGL2Context.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
OffscreenCanvasCloneData::OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer,
|
||||
uint32_t aWidth, uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
bool aNeutered)
|
||||
: mRenderer(aRenderer)
|
||||
, mWidth(aWidth)
|
||||
, mHeight(aHeight)
|
||||
, mCompositorBackendType(aCompositorBackend)
|
||||
, mNeutered(aNeutered)
|
||||
{
|
||||
}
|
||||
|
||||
OffscreenCanvasCloneData::~OffscreenCanvasCloneData()
|
||||
{
|
||||
}
|
||||
|
||||
OffscreenCanvas::OffscreenCanvas(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
layers::AsyncCanvasRenderer* aRenderer)
|
||||
: mAttrDirty(false)
|
||||
, mNeutered(false)
|
||||
, mWidth(aWidth)
|
||||
, mHeight(aHeight)
|
||||
, mCompositorBackendType(aCompositorBackend)
|
||||
, mCanvasClient(nullptr)
|
||||
, mCanvasRenderer(aRenderer)
|
||||
{}
|
||||
|
||||
OffscreenCanvas::~OffscreenCanvas()
|
||||
{
|
||||
ClearResources();
|
||||
}
|
||||
|
||||
OffscreenCanvas*
|
||||
OffscreenCanvas::GetParentObject() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
OffscreenCanvas::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
return OffscreenCanvasBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void
|
||||
OffscreenCanvas::ClearResources()
|
||||
{
|
||||
if (mCanvasClient) {
|
||||
mCanvasClient->Clear();
|
||||
ImageBridgeChild::DispatchReleaseCanvasClient(mCanvasClient);
|
||||
mCanvasClient = nullptr;
|
||||
|
||||
if (mCanvasRenderer) {
|
||||
nsCOMPtr<nsIThread> activeThread = mCanvasRenderer->GetActiveThread();
|
||||
MOZ_RELEASE_ASSERT(activeThread);
|
||||
MOZ_RELEASE_ASSERT(activeThread == NS_GetCurrentThread());
|
||||
mCanvasRenderer->SetCanvasClient(nullptr);
|
||||
mCanvasRenderer->mContext = nullptr;
|
||||
mCanvasRenderer->mGLContext = nullptr;
|
||||
mCanvasRenderer->ResetActiveThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<nsISupports>
|
||||
OffscreenCanvas::GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (mNeutered) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We only support WebGL in workers for now
|
||||
CanvasContextType contextType;
|
||||
if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) {
|
||||
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!(contextType == CanvasContextType::WebGL1 ||
|
||||
contextType == CanvasContextType::WebGL2))
|
||||
{
|
||||
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
already_AddRefed<nsISupports> result =
|
||||
CanvasRenderingContextHelper::GetContext(aCx,
|
||||
aContextId,
|
||||
aContextOptions,
|
||||
aRv);
|
||||
|
||||
if (!mCurrentContext) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mCanvasRenderer) {
|
||||
WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
|
||||
gl::GLContext* gl = webGL->GL();
|
||||
mCanvasRenderer->mContext = mCurrentContext;
|
||||
mCanvasRenderer->SetActiveThread();
|
||||
mCanvasRenderer->mGLContext = gl;
|
||||
mCanvasRenderer->SetIsAlphaPremultiplied(webGL->IsPremultAlpha() || !gl->Caps().alpha);
|
||||
|
||||
if (ImageBridgeChild::IsCreated()) {
|
||||
TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT;
|
||||
mCanvasClient = ImageBridgeChild::GetSingleton()->
|
||||
CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags).take();
|
||||
mCanvasRenderer->SetCanvasClient(mCanvasClient);
|
||||
|
||||
gl::GLScreenBuffer* screen = gl->Screen();
|
||||
gl::SurfaceCaps caps = screen->mCaps;
|
||||
auto forwarder = mCanvasClient->GetForwarder();
|
||||
|
||||
UniquePtr<gl::SurfaceFactory> factory =
|
||||
gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags);
|
||||
|
||||
if (factory)
|
||||
screen->Morph(Move(factory));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
OffscreenCanvas::CreateContext(CanvasContextType aContextType)
|
||||
{
|
||||
nsRefPtr<nsICanvasRenderingContextInternal> ret =
|
||||
CanvasRenderingContextHelper::CreateContext(aContextType);
|
||||
|
||||
ret->SetOffscreenCanvas(this);
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
void
|
||||
OffscreenCanvas::CommitFrameToCompositor()
|
||||
{
|
||||
// The attributes has changed, we have to notify main
|
||||
// thread to change canvas size.
|
||||
if (mAttrDirty) {
|
||||
if (mCanvasRenderer) {
|
||||
mCanvasRenderer->SetWidth(mWidth);
|
||||
mCanvasRenderer->SetHeight(mHeight);
|
||||
mCanvasRenderer->NotifyElementAboutAttributesChanged();
|
||||
}
|
||||
mAttrDirty = false;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
static_cast<WebGLContext*>(mCurrentContext.get())->PresentScreenBuffer();
|
||||
}
|
||||
|
||||
if (mCanvasRenderer && mCanvasRenderer->mGLContext) {
|
||||
mCanvasRenderer->NotifyElementAboutInvalidation();
|
||||
ImageBridgeChild::GetSingleton()->
|
||||
UpdateAsyncCanvasRenderer(mCanvasRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
OffscreenCanvasCloneData*
|
||||
OffscreenCanvas::ToCloneData()
|
||||
{
|
||||
return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, mHeight,
|
||||
mCompositorBackendType, mNeutered);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<OffscreenCanvas>
|
||||
OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData)
|
||||
{
|
||||
MOZ_ASSERT(aData);
|
||||
nsRefPtr<OffscreenCanvas> wc =
|
||||
new OffscreenCanvas(aData->mWidth, aData->mHeight,
|
||||
aData->mCompositorBackendType, aData->mRenderer);
|
||||
if (aData->mNeutered) {
|
||||
wc->SetNeutered();
|
||||
}
|
||||
return wc.forget();
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
OffscreenCanvas::PrefEnabled(JSContext* aCx, JSObject* aObj)
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
return Preferences::GetBool("gfx.offscreencanvas.enabled");
|
||||
} else {
|
||||
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
return workerPrivate->OffscreenCanvasEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj)
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return PrefEnabled(aCx, aObj);
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper, mCurrentContext)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(OffscreenCanvas, DOMEventTargetHelper)
|
||||
NS_IMPL_RELEASE_INHERITED(OffscreenCanvas, DOMEventTargetHelper)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OffscreenCanvas)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
@ -1,179 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_DOM_OFFSCREENCANVAS_H_
|
||||
#define MOZILLA_DOM_OFFSCREENCANVAS_H_
|
||||
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "CanvasRenderingContextHelper.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
struct JSContext;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ErrorResult;
|
||||
|
||||
namespace layers {
|
||||
class AsyncCanvasRenderer;
|
||||
class CanvasClient;
|
||||
} // namespace layers
|
||||
|
||||
namespace dom {
|
||||
|
||||
// This is helper class for transferring OffscreenCanvas to worker thread.
|
||||
// Because OffscreenCanvas is not thread-safe. So we cannot pass Offscreen-
|
||||
// Canvas to worker thread directly. Thus, we create this helper class and
|
||||
// store necessary data in it then pass it to worker thread.
|
||||
struct OffscreenCanvasCloneData final
|
||||
{
|
||||
OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer,
|
||||
uint32_t aWidth, uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
bool aNeutered);
|
||||
~OffscreenCanvasCloneData();
|
||||
|
||||
RefPtr<layers::AsyncCanvasRenderer> mRenderer;
|
||||
uint32_t mWidth;
|
||||
uint32_t mHeight;
|
||||
layers::LayersBackend mCompositorBackendType;
|
||||
bool mNeutered;
|
||||
};
|
||||
|
||||
class OffscreenCanvas final : public DOMEventTargetHelper
|
||||
, public CanvasRenderingContextHelper
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OffscreenCanvas, DOMEventTargetHelper)
|
||||
|
||||
OffscreenCanvas(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
layers::LayersBackend aCompositorBackend,
|
||||
layers::AsyncCanvasRenderer* aRenderer);
|
||||
|
||||
OffscreenCanvas* GetParentObject() const;
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
void ClearResources();
|
||||
|
||||
uint32_t Width() const
|
||||
{
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
uint32_t Height() const
|
||||
{
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
void SetWidth(uint32_t aWidth, ErrorResult& aRv)
|
||||
{
|
||||
if (mNeutered) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mWidth != aWidth) {
|
||||
mWidth = aWidth;
|
||||
CanvasAttrChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SetHeight(uint32_t aHeight, ErrorResult& aRv)
|
||||
{
|
||||
if (mNeutered) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHeight != aHeight) {
|
||||
mHeight = aHeight;
|
||||
CanvasAttrChanged();
|
||||
}
|
||||
}
|
||||
|
||||
nsICanvasRenderingContextInternal* GetContext() const
|
||||
{
|
||||
return mCurrentContext;
|
||||
}
|
||||
|
||||
static already_AddRefed<OffscreenCanvas>
|
||||
CreateFromCloneData(OffscreenCanvasCloneData* aData);
|
||||
|
||||
static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
|
||||
|
||||
// Return true on main-thread, and return gfx.offscreencanvas.enabled
|
||||
// on worker thread.
|
||||
static bool PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj);
|
||||
|
||||
OffscreenCanvasCloneData* ToCloneData();
|
||||
|
||||
void CommitFrameToCompositor();
|
||||
|
||||
virtual bool GetOpaqueAttr() override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual nsIntSize GetWidthHeight() override
|
||||
{
|
||||
return nsIntSize(mWidth, mHeight);
|
||||
}
|
||||
|
||||
virtual already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType) override;
|
||||
|
||||
virtual already_AddRefed<nsISupports>
|
||||
GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv) override;
|
||||
|
||||
void SetNeutered()
|
||||
{
|
||||
mNeutered = true;
|
||||
}
|
||||
|
||||
bool IsNeutered() const
|
||||
{
|
||||
return mNeutered;
|
||||
}
|
||||
|
||||
layers::LayersBackend GetCompositorBackendType() const
|
||||
{
|
||||
return mCompositorBackendType;
|
||||
}
|
||||
|
||||
private:
|
||||
~OffscreenCanvas();
|
||||
|
||||
void CanvasAttrChanged()
|
||||
{
|
||||
mAttrDirty = true;
|
||||
UpdateContext(nullptr, JS::NullHandleValue);
|
||||
}
|
||||
|
||||
bool mAttrDirty;
|
||||
bool mNeutered;
|
||||
|
||||
uint32_t mWidth;
|
||||
uint32_t mHeight;
|
||||
|
||||
layers::LayersBackend mCompositorBackendType;
|
||||
|
||||
layers::CanvasClient* mCanvasClient;
|
||||
RefPtr<layers::AsyncCanvasRenderer> mCanvasRenderer;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MOZILLA_DOM_OFFSCREENCANVAS_H_
|
@ -22,7 +22,6 @@
|
||||
#include "ImageEncoder.h"
|
||||
#include "Layers.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/HTMLVideoElement.h"
|
||||
#include "mozilla/dom/ImageData.h"
|
||||
#include "mozilla/EnumeratedArrayCycleCollection.h"
|
||||
@ -80,6 +79,125 @@ using namespace mozilla::gfx;
|
||||
using namespace mozilla::gl;
|
||||
using namespace mozilla::layers;
|
||||
|
||||
WebGLObserver::WebGLObserver(WebGLContext* webgl)
|
||||
: mWebGL(webgl)
|
||||
{
|
||||
}
|
||||
|
||||
WebGLObserver::~WebGLObserver()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
WebGLObserver::Destroy()
|
||||
{
|
||||
UnregisterMemoryPressureEvent();
|
||||
UnregisterVisibilityChangeEvent();
|
||||
mWebGL = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
WebGLObserver::RegisterVisibilityChangeEvent()
|
||||
{
|
||||
if (!mWebGL)
|
||||
return;
|
||||
|
||||
HTMLCanvasElement* canvas = mWebGL->GetCanvas();
|
||||
MOZ_ASSERT(canvas);
|
||||
|
||||
if (canvas) {
|
||||
nsIDocument* document = canvas->OwnerDoc();
|
||||
|
||||
document->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
|
||||
this, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebGLObserver::UnregisterVisibilityChangeEvent()
|
||||
{
|
||||
if (!mWebGL)
|
||||
return;
|
||||
|
||||
HTMLCanvasElement* canvas = mWebGL->GetCanvas();
|
||||
|
||||
if (canvas) {
|
||||
nsIDocument* document = canvas->OwnerDoc();
|
||||
|
||||
document->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
|
||||
this, true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebGLObserver::RegisterMemoryPressureEvent()
|
||||
{
|
||||
if (!mWebGL)
|
||||
return;
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
|
||||
MOZ_ASSERT(observerService);
|
||||
|
||||
if (observerService)
|
||||
observerService->AddObserver(this, "memory-pressure", false);
|
||||
}
|
||||
|
||||
void
|
||||
WebGLObserver::UnregisterMemoryPressureEvent()
|
||||
{
|
||||
if (!mWebGL)
|
||||
return;
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
|
||||
// Do not assert on observerService here. This might be triggered by
|
||||
// the cycle collector at a late enough time, that XPCOM services are
|
||||
// no longer available. See bug 1029504.
|
||||
if (observerService)
|
||||
observerService->RemoveObserver(this, "memory-pressure");
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebGLObserver::Observe(nsISupports*, const char* topic, const char16_t*)
|
||||
{
|
||||
if (!mWebGL || strcmp(topic, "memory-pressure")) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool wantToLoseContext = mWebGL->mLoseContextOnMemoryPressure;
|
||||
|
||||
if (!mWebGL->mCanLoseContextInForeground &&
|
||||
ProcessPriorityManager::CurrentProcessIsForeground())
|
||||
{
|
||||
wantToLoseContext = false;
|
||||
}
|
||||
|
||||
if (wantToLoseContext)
|
||||
mWebGL->ForceLoseContext();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebGLObserver::HandleEvent(nsIDOMEvent* event)
|
||||
{
|
||||
nsAutoString type;
|
||||
event->GetType(type);
|
||||
if (!mWebGL || !type.EqualsLiteral("visibilitychange"))
|
||||
return NS_OK;
|
||||
|
||||
HTMLCanvasElement* canvas = mWebGL->GetCanvas();
|
||||
MOZ_ASSERT(canvas);
|
||||
|
||||
if (canvas && !canvas->OwnerDoc()->Hidden())
|
||||
mWebGL->ForceRestoreContext();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
WebGLContextOptions::WebGLContextOptions()
|
||||
: alpha(true)
|
||||
, depth(true)
|
||||
@ -90,7 +208,7 @@ WebGLContextOptions::WebGLContextOptions()
|
||||
, failIfMajorPerformanceCaveat(false)
|
||||
{
|
||||
// Set default alpha state based on preference.
|
||||
if (gfxPrefs::WebGLDefaultNoAlpha())
|
||||
if (Preferences::GetBool("webgl.default-no-alpha", false))
|
||||
alpha = false;
|
||||
}
|
||||
|
||||
@ -164,10 +282,7 @@ WebGLContext::WebGLContext()
|
||||
mPixelStorePackAlignment = 4;
|
||||
mPixelStoreUnpackAlignment = 4;
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
// XXX mtseng: bug 709490, not thread safe
|
||||
WebGLMemoryTracker::AddWebGLContext(this);
|
||||
}
|
||||
WebGLMemoryTracker::AddWebGLContext(this);
|
||||
|
||||
mAllowContextRestore = true;
|
||||
mLastLossWasSimulated = false;
|
||||
@ -181,12 +296,15 @@ WebGLContext::WebGLContext()
|
||||
mAlreadyWarnedAboutFakeVertexAttrib0 = false;
|
||||
mAlreadyWarnedAboutViewportLargerThanDest = false;
|
||||
|
||||
mMaxWarnings = gfxPrefs::WebGLMaxWarningsPerContext();
|
||||
mMaxWarnings = Preferences::GetInt("webgl.max-warnings-per-context", 32);
|
||||
if (mMaxWarnings < -1) {
|
||||
GenerateWarning("webgl.max-warnings-per-context size is too large (seems like a negative value wrapped)");
|
||||
mMaxWarnings = 0;
|
||||
}
|
||||
|
||||
mContextObserver = new WebGLObserver(this);
|
||||
MOZ_RELEASE_ASSERT(mContextObserver, "Can't alloc WebGLContextObserver");
|
||||
|
||||
mLastUseIndex = 0;
|
||||
|
||||
InvalidateBufferFetching();
|
||||
@ -201,12 +319,10 @@ WebGLContext::WebGLContext()
|
||||
WebGLContext::~WebGLContext()
|
||||
{
|
||||
RemovePostRefreshObserver();
|
||||
mContextObserver->Destroy();
|
||||
|
||||
DestroyResourcesAndContext();
|
||||
if (NS_IsMainThread()) {
|
||||
// XXX mtseng: bug 709490, not thread safe
|
||||
WebGLMemoryTracker::RemoveWebGLContext(this);
|
||||
}
|
||||
WebGLMemoryTracker::RemoveWebGLContext(this);
|
||||
|
||||
mContextLossHandler->DisableTimer();
|
||||
mContextLossHandler = nullptr;
|
||||
@ -215,6 +331,8 @@ WebGLContext::~WebGLContext()
|
||||
void
|
||||
WebGLContext::DestroyResourcesAndContext()
|
||||
{
|
||||
mContextObserver->UnregisterMemoryPressureEvent();
|
||||
|
||||
if (!gl)
|
||||
return;
|
||||
|
||||
@ -313,35 +431,6 @@ WebGLContext::Invalidate()
|
||||
mCanvasElement->InvalidateCanvasContent(nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::OnVisibilityChange()
|
||||
{
|
||||
if (!IsContextLost()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mRestoreWhenVisible || mLastLossWasSimulated) {
|
||||
return;
|
||||
}
|
||||
|
||||
ForceRestoreContext();
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::OnMemoryPressure()
|
||||
{
|
||||
bool shouldLoseContext = mLoseContextOnMemoryPressure;
|
||||
|
||||
if (!mCanLoseContextInForeground &&
|
||||
ProcessPriorityManager::CurrentProcessIsForeground())
|
||||
{
|
||||
shouldLoseContext = false;
|
||||
}
|
||||
|
||||
if (shouldLoseContext)
|
||||
ForceLoseContext();
|
||||
}
|
||||
|
||||
//
|
||||
// nsICanvasRenderingContextInternal
|
||||
//
|
||||
@ -424,7 +513,7 @@ static bool
|
||||
IsFeatureInBlacklist(const nsCOMPtr<nsIGfxInfo>& gfxInfo, int32_t feature)
|
||||
{
|
||||
int32_t status;
|
||||
if (!NS_SUCCEEDED(gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, feature, &status)))
|
||||
if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(feature, &status)))
|
||||
return false;
|
||||
|
||||
return status != nsIGfxInfo::FEATURE_STATUS_OK;
|
||||
@ -435,29 +524,19 @@ HasAcceleratedLayers(const nsCOMPtr<nsIGfxInfo>& gfxInfo)
|
||||
{
|
||||
int32_t status;
|
||||
|
||||
gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo,
|
||||
nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
|
||||
&status);
|
||||
gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status);
|
||||
if (status)
|
||||
return true;
|
||||
gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo,
|
||||
nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS,
|
||||
&status);
|
||||
gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS, &status);
|
||||
if (status)
|
||||
return true;
|
||||
gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo,
|
||||
nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS,
|
||||
&status);
|
||||
gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS, &status);
|
||||
if (status)
|
||||
return true;
|
||||
gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo,
|
||||
nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
|
||||
&status);
|
||||
gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status);
|
||||
if (status)
|
||||
return true;
|
||||
gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo,
|
||||
nsIGfxInfo::FEATURE_OPENGL_LAYERS,
|
||||
&status);
|
||||
gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &status);
|
||||
if (status)
|
||||
return true;
|
||||
|
||||
@ -514,14 +593,11 @@ BaseCaps(const WebGLContextOptions& options, WebGLContext* webgl)
|
||||
// we should really have this behind a
|
||||
// |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but
|
||||
// for now it's just behind a pref for testing/evaluation.
|
||||
baseCaps.bpp16 = gfxPrefs::WebGLPrefer16bpp();
|
||||
baseCaps.bpp16 = Preferences::GetBool("webgl.prefer-16bpp", false);
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
do {
|
||||
auto canvasElement = webgl->GetCanvas();
|
||||
if (!canvasElement)
|
||||
break;
|
||||
|
||||
auto ownerDoc = canvasElement->OwnerDoc();
|
||||
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(ownerDoc);
|
||||
if (!docWidget)
|
||||
@ -542,7 +618,7 @@ BaseCaps(const WebGLContextOptions& options, WebGLContext* webgl)
|
||||
|
||||
// Done with baseCaps construction.
|
||||
|
||||
bool forceAllowAA = gfxPrefs::WebGLForceMSAA();
|
||||
bool forceAllowAA = Preferences::GetBool("webgl.msaa-force", false);
|
||||
nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
|
||||
if (!forceAllowAA &&
|
||||
IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA))
|
||||
@ -662,7 +738,7 @@ bool
|
||||
WebGLContext::CreateAndInitGL(bool forceEnabled)
|
||||
{
|
||||
bool preferEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL");
|
||||
bool disableANGLE = gfxPrefs::WebGLDisableANGLE();
|
||||
bool disableANGLE = Preferences::GetBool("webgl.disable-angle", false);
|
||||
|
||||
if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL"))
|
||||
disableANGLE = true;
|
||||
@ -743,6 +819,10 @@ WebGLContext::ResizeBackbuffer(uint32_t requestedWidth,
|
||||
NS_IMETHODIMP
|
||||
WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight)
|
||||
{
|
||||
// Early error return cases
|
||||
if (!GetCanvas())
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
if (signedWidth < 0 || signedHeight < 0) {
|
||||
GenerateWarning("Canvas size is too large (seems like a negative value wrapped)");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
@ -752,10 +832,7 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight)
|
||||
uint32_t height = signedHeight;
|
||||
|
||||
// Early success return cases
|
||||
|
||||
// May have a OffscreenCanvas instead of an HTMLCanvasElement
|
||||
if (GetCanvas())
|
||||
GetCanvas()->InvalidateCanvas();
|
||||
GetCanvas()->InvalidateCanvas();
|
||||
|
||||
// Zero-sized surfaces can cause problems.
|
||||
if (width == 0)
|
||||
@ -828,7 +905,10 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight)
|
||||
// pick up the old generation.
|
||||
++mGeneration;
|
||||
|
||||
bool disabled = gfxPrefs::WebGLDisabled();
|
||||
// Get some prefs for some preferred/overriden things
|
||||
NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
|
||||
|
||||
bool disabled = Preferences::GetBool("webgl.disabled", false);
|
||||
|
||||
// TODO: When we have software webgl support we should use that instead.
|
||||
disabled |= gfxPlatform::InSafeMode();
|
||||
@ -851,7 +931,7 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight)
|
||||
}
|
||||
|
||||
// Alright, now let's start trying.
|
||||
bool forceEnabled = gfxPrefs::WebGLForceEnabled();
|
||||
bool forceEnabled = Preferences::GetBool("webgl.force-enabled", false);
|
||||
ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
|
||||
|
||||
MOZ_ASSERT(!gl);
|
||||
@ -972,11 +1052,6 @@ WebGLContext::LoseOldestWebGLContextIfLimitExceeded()
|
||||
#endif
|
||||
MOZ_ASSERT(kMaxWebGLContextsPerPrincipal < kMaxWebGLContexts);
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
// XXX mtseng: bug 709490, WebGLMemoryTracker is not thread safe.
|
||||
return;
|
||||
}
|
||||
|
||||
// it's important to update the index on a new context before losing old contexts,
|
||||
// otherwise new unused contexts would all have index 0 and we couldn't distinguish older ones
|
||||
// when choosing which one to lose first.
|
||||
@ -1064,8 +1139,32 @@ WebGLContext::GetImageBuffer(uint8_t** out_imageBuffer, int32_t* out_format)
|
||||
|
||||
RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
|
||||
|
||||
return gfxUtils::GetImageBuffer(dataSurface, mOptions.premultipliedAlpha,
|
||||
out_imageBuffer, out_format);
|
||||
DataSourceSurface::MappedSurface map;
|
||||
if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map))
|
||||
return;
|
||||
|
||||
uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4];
|
||||
if (!imageBuffer) {
|
||||
dataSurface->Unmap();
|
||||
return;
|
||||
}
|
||||
memcpy(imageBuffer, map.mData, mWidth * mHeight * 4);
|
||||
|
||||
dataSurface->Unmap();
|
||||
|
||||
int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
|
||||
if (!mOptions.premultipliedAlpha) {
|
||||
// We need to convert to INPUT_FORMAT_RGBA, otherwise
|
||||
// we are automatically considered premult, and unpremult'd.
|
||||
// Yes, it is THAT silly.
|
||||
// Except for different lossy conversions by color,
|
||||
// we could probably just change the label, and not change the data.
|
||||
gfxUtils::ConvertBGRAtoRGBA(imageBuffer, mWidth * mHeight * 4);
|
||||
format = imgIEncoder::INPUT_FORMAT_RGBA;
|
||||
}
|
||||
|
||||
*out_imageBuffer = imageBuffer;
|
||||
*out_format = format;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@ -1077,18 +1176,20 @@ WebGLContext::GetInputStream(const char* mimeType,
|
||||
if (!gl)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
// Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
|
||||
bool premult;
|
||||
RefPtr<SourceSurface> snapshot =
|
||||
GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult);
|
||||
if (!snapshot)
|
||||
nsCString enccid("@mozilla.org/image/encoder;2?type=");
|
||||
enccid += mimeType;
|
||||
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
|
||||
if (!encoder)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!");
|
||||
nsAutoArrayPtr<uint8_t> imageBuffer;
|
||||
int32_t format = 0;
|
||||
GetImageBuffer(getter_Transfers(imageBuffer), &format);
|
||||
if (!imageBuffer)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
|
||||
return gfxUtils::GetInputStream(dataSurface, mOptions.premultipliedAlpha, mimeType,
|
||||
encoderOptions, out_stream);
|
||||
return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
|
||||
encoder, encoderOptions, out_stream);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1167,26 +1268,25 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder,
|
||||
}
|
||||
|
||||
WebGLContextUserData* userData = nullptr;
|
||||
if (builder->IsPaintingToWindow() && mCanvasElement) {
|
||||
// Make the layer tell us whenever a transaction finishes (including
|
||||
// the current transaction), so we can clear our invalidation state and
|
||||
// start invalidating again. We need to do this for the layer that is
|
||||
// being painted to a window (there shouldn't be more than one at a time,
|
||||
// and if there is, flushing the invalidation state more often than
|
||||
// necessary is harmless).
|
||||
if (builder->IsPaintingToWindow()) {
|
||||
// Make the layer tell us whenever a transaction finishes (including
|
||||
// the current transaction), so we can clear our invalidation state and
|
||||
// start invalidating again. We need to do this for the layer that is
|
||||
// being painted to a window (there shouldn't be more than one at a time,
|
||||
// and if there is, flushing the invalidation state more often than
|
||||
// necessary is harmless).
|
||||
|
||||
// The layer will be destroyed when we tear down the presentation
|
||||
// (at the latest), at which time this userData will be destroyed,
|
||||
// releasing the reference to the element.
|
||||
// The userData will receive DidTransactionCallbacks, which flush the
|
||||
// the invalidation state to indicate that the canvas is up to date.
|
||||
userData = new WebGLContextUserData(mCanvasElement);
|
||||
canvasLayer->SetDidTransactionCallback(
|
||||
WebGLContextUserData::DidTransactionCallback, userData);
|
||||
canvasLayer->SetPreTransactionCallback(
|
||||
WebGLContextUserData::PreTransactionCallback, userData);
|
||||
// The layer will be destroyed when we tear down the presentation
|
||||
// (at the latest), at which time this userData will be destroyed,
|
||||
// releasing the reference to the element.
|
||||
// The userData will receive DidTransactionCallbacks, which flush the
|
||||
// the invalidation state to indicate that the canvas is up to date.
|
||||
userData = new WebGLContextUserData(mCanvasElement);
|
||||
canvasLayer->SetDidTransactionCallback(
|
||||
WebGLContextUserData::DidTransactionCallback, userData);
|
||||
canvasLayer->SetPreTransactionCallback(
|
||||
WebGLContextUserData::PreTransactionCallback, userData);
|
||||
}
|
||||
|
||||
canvasLayer->SetUserData(&gWebGLLayerUserData, userData);
|
||||
|
||||
CanvasLayer::Data data;
|
||||
@ -1208,36 +1308,14 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder,
|
||||
layers::LayersBackend
|
||||
WebGLContext::GetCompositorBackendType() const
|
||||
{
|
||||
if (mCanvasElement) {
|
||||
return mCanvasElement->GetCompositorBackendType();
|
||||
} else if (mOffscreenCanvas) {
|
||||
return mOffscreenCanvas->GetCompositorBackendType();
|
||||
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
|
||||
if (docWidget) {
|
||||
layers::LayerManager* layerManager = docWidget->GetLayerManager();
|
||||
return layerManager->GetCompositorBackendType();
|
||||
}
|
||||
|
||||
return LayersBackend::LAYERS_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::Commit()
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
mOffscreenCanvas->CommitFrameToCompositor();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::GetCanvas(Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval)
|
||||
{
|
||||
if (mCanvasElement) {
|
||||
MOZ_RELEASE_ASSERT(!mOffscreenCanvas);
|
||||
retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement;
|
||||
} else if (mOffscreenCanvas) {
|
||||
retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas;
|
||||
} else {
|
||||
retval.SetNull();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::GetContextAttributes(dom::Nullable<dom::WebGLContextAttributes>& retval)
|
||||
{
|
||||
@ -1546,7 +1624,7 @@ WebGLContext::RunContextLossTimer()
|
||||
mContextLossHandler->RunTimer();
|
||||
}
|
||||
|
||||
class UpdateContextLossStatusTask : public nsCancelableRunnable
|
||||
class UpdateContextLossStatusTask : public nsRunnable
|
||||
{
|
||||
nsRefPtr<WebGLContext> mWebGL;
|
||||
|
||||
@ -1557,16 +1635,10 @@ public:
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() {
|
||||
if (mWebGL)
|
||||
mWebGL->UpdateContextLossStatus();
|
||||
mWebGL->UpdateContextLossStatus();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD Cancel() {
|
||||
mWebGL = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
@ -1593,7 +1665,7 @@ WebGLContext::EnqueueUpdateContextLossStatus()
|
||||
void
|
||||
WebGLContext::UpdateContextLossStatus()
|
||||
{
|
||||
if (!mCanvasElement && !mOffscreenCanvas) {
|
||||
if (!mCanvasElement) {
|
||||
// the canvas is gone. That happens when the page was closed before we got
|
||||
// this timer event. In this case, there's nothing to do here, just don't crash.
|
||||
return;
|
||||
@ -1621,23 +1693,12 @@ WebGLContext::UpdateContextLossStatus()
|
||||
// callback, so do that now.
|
||||
|
||||
bool useDefaultHandler;
|
||||
|
||||
if (mCanvasElement) {
|
||||
nsContentUtils::DispatchTrustedEvent(
|
||||
mCanvasElement->OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
|
||||
NS_LITERAL_STRING("webglcontextlost"),
|
||||
true,
|
||||
true,
|
||||
&useDefaultHandler);
|
||||
} else {
|
||||
// OffscreenCanvas case
|
||||
nsRefPtr<Event> event = new Event(mOffscreenCanvas, nullptr, nullptr);
|
||||
event->InitEvent(NS_LITERAL_STRING("webglcontextlost"), true, true);
|
||||
event->SetTrusted(true);
|
||||
mOffscreenCanvas->DispatchEvent(event, &useDefaultHandler);
|
||||
}
|
||||
|
||||
nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
|
||||
NS_LITERAL_STRING("webglcontextlost"),
|
||||
true,
|
||||
true,
|
||||
&useDefaultHandler);
|
||||
// We sent the callback, so we're just 'regular lost' now.
|
||||
mContextStatus = ContextLost;
|
||||
// If we're told to use the default handler, it means the script
|
||||
@ -1689,22 +1750,11 @@ WebGLContext::UpdateContextLossStatus()
|
||||
|
||||
// Revival!
|
||||
mContextStatus = ContextNotLost;
|
||||
|
||||
if (mCanvasElement) {
|
||||
nsContentUtils::DispatchTrustedEvent(
|
||||
mCanvasElement->OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
|
||||
NS_LITERAL_STRING("webglcontextrestored"),
|
||||
true,
|
||||
true);
|
||||
} else {
|
||||
nsRefPtr<Event> event = new Event(mOffscreenCanvas, nullptr, nullptr);
|
||||
event->InitEvent(NS_LITERAL_STRING("webglcontextrestored"), true, true);
|
||||
event->SetTrusted(true);
|
||||
bool unused;
|
||||
mOffscreenCanvas->DispatchEvent(event, &unused);
|
||||
}
|
||||
|
||||
nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
|
||||
NS_LITERAL_STRING("webglcontextrestored"),
|
||||
true,
|
||||
true);
|
||||
mEmitContextLostErrorOnce = true;
|
||||
return;
|
||||
}
|
||||
@ -1722,6 +1772,12 @@ WebGLContext::ForceLoseContext(bool simulateLosing)
|
||||
DestroyResourcesAndContext();
|
||||
mLastLossWasSimulated = simulateLosing;
|
||||
|
||||
// Register visibility change observer to defer the context restoring.
|
||||
// Restore the context when the app is visible.
|
||||
if (mRestoreWhenVisible && !mLastLossWasSimulated) {
|
||||
mContextObserver->RegisterVisibilityChangeEvent();
|
||||
}
|
||||
|
||||
// Queue up a task, since we know the status changed.
|
||||
EnqueueUpdateContextLossStatus();
|
||||
}
|
||||
@ -1733,6 +1789,8 @@ WebGLContext::ForceRestoreContext()
|
||||
mContextStatus = ContextLostAwaitingRestore;
|
||||
mAllowContextRestore = true; // Hey, you did say 'force'.
|
||||
|
||||
mContextObserver->UnregisterVisibilityChangeEvent();
|
||||
|
||||
// Queue up a task, since we know the status changed.
|
||||
EnqueueUpdateContextLossStatus();
|
||||
}
|
||||
@ -1867,7 +1925,6 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLContext,
|
||||
mCanvasElement,
|
||||
mOffscreenCanvas,
|
||||
mExtensions,
|
||||
mBound2DTextures,
|
||||
mBoundCubeMapTextures,
|
||||
|
@ -40,11 +40,7 @@
|
||||
// Generated
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsIDOMWebGLRenderingContext.h"
|
||||
#include "nsICanvasRenderingContextInternal.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
|
||||
|
||||
class nsIDocShell;
|
||||
@ -84,6 +80,7 @@ class WebGLContextLossHandler;
|
||||
class WebGLBuffer;
|
||||
class WebGLExtensionBase;
|
||||
class WebGLFramebuffer;
|
||||
class WebGLObserver;
|
||||
class WebGLProgram;
|
||||
class WebGLQuery;
|
||||
class WebGLRenderbuffer;
|
||||
@ -98,7 +95,6 @@ class WebGLVertexArray;
|
||||
namespace dom {
|
||||
class Element;
|
||||
class ImageData;
|
||||
class OwningHTMLCanvasElementOrOffscreenCanvas;
|
||||
struct WebGLContextAttributes;
|
||||
template<typename> struct Nullable;
|
||||
} // namespace dom
|
||||
@ -188,6 +184,7 @@ class WebGLContext
|
||||
friend class WebGLExtensionLoseContext;
|
||||
friend class WebGLExtensionVertexArray;
|
||||
friend class WebGLMemoryTracker;
|
||||
friend class WebGLObserver;
|
||||
|
||||
enum {
|
||||
UNPACK_FLIP_Y_WEBGL = 0x9240,
|
||||
@ -217,9 +214,6 @@ public:
|
||||
|
||||
NS_DECL_NSIDOMWEBGLRENDERINGCONTEXT
|
||||
|
||||
virtual void OnVisibilityChange() override;
|
||||
virtual void OnMemoryPressure() override;
|
||||
|
||||
// nsICanvasRenderingContextInternal
|
||||
virtual int32_t GetWidth() const override;
|
||||
virtual int32_t GetHeight() const override;
|
||||
@ -367,11 +361,8 @@ public:
|
||||
void AssertCachedBindings();
|
||||
void AssertCachedState();
|
||||
|
||||
dom::HTMLCanvasElement* GetCanvas() const { return mCanvasElement; }
|
||||
|
||||
// WebIDL WebGLRenderingContext API
|
||||
void Commit();
|
||||
void GetCanvas(Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval);
|
||||
dom::HTMLCanvasElement* GetCanvas() const { return mCanvasElement; }
|
||||
GLsizei DrawingBufferWidth() const { return IsContextLost() ? 0 : mWidth; }
|
||||
GLsizei DrawingBufferHeight() const {
|
||||
return IsContextLost() ? 0 : mHeight;
|
||||
@ -1517,6 +1508,8 @@ protected:
|
||||
ForceDiscreteGPUHelperCGL mForceDiscreteGPUHelper;
|
||||
#endif
|
||||
|
||||
nsRefPtr<WebGLObserver> mContextObserver;
|
||||
|
||||
public:
|
||||
// console logging helpers
|
||||
void GenerateWarning(const char* fmt, ...);
|
||||
@ -1621,6 +1614,32 @@ WebGLContext::ValidateObject(const char* info, ObjectType* object)
|
||||
return ValidateObjectAssumeNonNull(info, object);
|
||||
}
|
||||
|
||||
// Listen visibilitychange and memory-pressure event for context lose/restore
|
||||
class WebGLObserver final
|
||||
: public nsIObserver
|
||||
, public nsIDOMEventListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
NS_DECL_NSIDOMEVENTLISTENER
|
||||
|
||||
explicit WebGLObserver(WebGLContext* webgl);
|
||||
|
||||
void Destroy();
|
||||
|
||||
void RegisterVisibilityChangeEvent();
|
||||
void UnregisterVisibilityChangeEvent();
|
||||
|
||||
void RegisterMemoryPressureEvent();
|
||||
void UnregisterMemoryPressureEvent();
|
||||
|
||||
private:
|
||||
~WebGLObserver();
|
||||
|
||||
WebGLContext* mWebGL;
|
||||
};
|
||||
|
||||
size_t RoundUpToMultipleOf(size_t value, size_t multiple);
|
||||
|
||||
bool
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "WebGLContext.h"
|
||||
#include "WebGLContextUtils.h"
|
||||
#include "WebGLExtensions.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "GLContext.h"
|
||||
|
||||
#include "nsString.h"
|
||||
@ -75,15 +74,12 @@ bool WebGLContext::IsExtensionSupported(JSContext* cx,
|
||||
|
||||
// Chrome contexts need access to debug information even when
|
||||
// webgl.disable-extensions is set. This is used in the graphics
|
||||
// section of about:support
|
||||
if (NS_IsMainThread() &&
|
||||
xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
|
||||
// section of about:support.
|
||||
if (xpc::AccessCheck::isChrome(js::GetContextCompartment(cx)))
|
||||
allowPrivilegedExts = true;
|
||||
}
|
||||
|
||||
if (gfxPrefs::WebGLPrivilegedExtensionsEnabled()) {
|
||||
if (Preferences::GetBool("webgl.enable-privileged-extensions", false))
|
||||
allowPrivilegedExts = true;
|
||||
}
|
||||
|
||||
if (allowPrivilegedExts) {
|
||||
switch (ext) {
|
||||
@ -185,7 +181,9 @@ WebGLContext::IsExtensionSupported(WebGLExtensionID ext) const
|
||||
break;
|
||||
}
|
||||
|
||||
if (gfxPrefs::WebGLDraftExtensionsEnabled() || IsWebGL2()) {
|
||||
if (Preferences::GetBool("webgl.enable-draft-extensions", false) ||
|
||||
IsWebGL2())
|
||||
{
|
||||
switch (ext) {
|
||||
case WebGLExtensionID::EXT_disjoint_timer_query:
|
||||
return WebGLExtensionDisjointTimerQuery::IsSupported(this);
|
||||
|
@ -1390,10 +1390,7 @@ WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
|
||||
if (IsContextLost())
|
||||
return;
|
||||
|
||||
if (mCanvasElement &&
|
||||
mCanvasElement->IsWriteOnly() &&
|
||||
!nsContentUtils::IsCallerChrome())
|
||||
{
|
||||
if (mCanvasElement->IsWriteOnly() && !nsContentUtils::IsCallerChrome()) {
|
||||
GenerateWarning("readPixels: Not allowed");
|
||||
return rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
}
|
||||
|
@ -8,103 +8,15 @@
|
||||
#include "nsITimer.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "WebGLContext.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Begin worker specific code
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// On workers we can only dispatch CancelableRunnables, so we have to wrap the
|
||||
// timer's EventTarget to use our own cancelable runnable
|
||||
|
||||
class ContextLossWorkerEventTarget final : public nsIEventTarget
|
||||
{
|
||||
public:
|
||||
explicit ContextLossWorkerEventTarget(nsIEventTarget* aEventTarget)
|
||||
: mEventTarget(aEventTarget)
|
||||
{
|
||||
MOZ_ASSERT(aEventTarget);
|
||||
}
|
||||
|
||||
NS_DECL_NSIEVENTTARGET
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
||||
protected:
|
||||
~ContextLossWorkerEventTarget() {}
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIEventTarget> mEventTarget;
|
||||
};
|
||||
|
||||
class ContextLossWorkerRunnable final : public nsICancelableRunnable
|
||||
{
|
||||
public:
|
||||
explicit ContextLossWorkerRunnable(nsIRunnable* aRunnable)
|
||||
: mRunnable(aRunnable)
|
||||
{
|
||||
}
|
||||
|
||||
NS_DECL_NSICANCELABLERUNNABLE
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
||||
NS_FORWARD_NSIRUNNABLE(mRunnable->)
|
||||
|
||||
protected:
|
||||
~ContextLossWorkerRunnable() {}
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIRunnable> mRunnable;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(ContextLossWorkerEventTarget, nsIEventTarget,
|
||||
nsISupports)
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContextLossWorkerEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event(aEvent);
|
||||
return Dispatch(event.forget(), aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContextLossWorkerEventTarget::Dispatch(already_AddRefed<nsIRunnable>&& aEvent,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> eventRef(aEvent);
|
||||
nsRefPtr<ContextLossWorkerRunnable> wrappedEvent =
|
||||
new ContextLossWorkerRunnable(eventRef);
|
||||
return mEventTarget->Dispatch(wrappedEvent, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContextLossWorkerEventTarget::IsOnCurrentThread(bool* aResult)
|
||||
{
|
||||
return mEventTarget->IsOnCurrentThread(aResult);
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(ContextLossWorkerRunnable, nsICancelableRunnable,
|
||||
nsIRunnable)
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContextLossWorkerRunnable::Cancel()
|
||||
{
|
||||
mRunnable = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// End worker-specific code
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
WebGLContextLossHandler::WebGLContextLossHandler(WebGLContext* webgl)
|
||||
: mWeakWebGL(webgl)
|
||||
, mTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
|
||||
, mIsTimerRunning(false)
|
||||
, mShouldRunTimerAgain(false)
|
||||
, mIsDisabled(false)
|
||||
, mFeatureAdded(false)
|
||||
#ifdef DEBUG
|
||||
, mThread(NS_GetCurrentThread())
|
||||
#endif
|
||||
@ -178,17 +90,6 @@ WebGLContextLossHandler::RunTimer()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
dom::workers::WorkerPrivate* workerPrivate =
|
||||
dom::workers::GetCurrentThreadWorkerPrivate();
|
||||
nsCOMPtr<nsIEventTarget> target = workerPrivate->GetEventTarget();
|
||||
mTimer->SetTarget(new ContextLossWorkerEventTarget(target));
|
||||
if (!mFeatureAdded) {
|
||||
workerPrivate->AddFeature(workerPrivate->GetJSContext(), this);
|
||||
mFeatureAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
StartTimer(1000);
|
||||
|
||||
mIsTimerRunning = true;
|
||||
@ -203,14 +104,6 @@ WebGLContextLossHandler::DisableTimer()
|
||||
|
||||
mIsDisabled = true;
|
||||
|
||||
if (mFeatureAdded) {
|
||||
dom::workers::WorkerPrivate* workerPrivate =
|
||||
dom::workers::GetCurrentThreadWorkerPrivate();
|
||||
MOZ_RELEASE_ASSERT(workerPrivate);
|
||||
workerPrivate->RemoveFeature(workerPrivate->GetJSContext(), this);
|
||||
mFeatureAdded = false;
|
||||
}
|
||||
|
||||
// We can't just Cancel() the timer, as sometimes we end up
|
||||
// receiving a callback after calling Cancel(). This could cause us
|
||||
// to receive the callback after object destruction.
|
||||
@ -223,16 +116,4 @@ WebGLContextLossHandler::DisableTimer()
|
||||
mTimer->SetDelay(0);
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLContextLossHandler::Notify(JSContext* aCx, dom::workers::Status aStatus)
|
||||
{
|
||||
bool isWorkerRunning = aStatus < dom::workers::Closing;
|
||||
if (!isWorkerRunning && mIsTimerRunning) {
|
||||
mIsTimerRunning = false;
|
||||
this->Release();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "mozilla/WeakPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "WorkerFeature.h"
|
||||
|
||||
class nsIThread;
|
||||
class nsITimer;
|
||||
@ -18,14 +17,13 @@ class nsITimer;
|
||||
namespace mozilla {
|
||||
class WebGLContext;
|
||||
|
||||
class WebGLContextLossHandler : public dom::workers::WorkerFeature
|
||||
class WebGLContextLossHandler
|
||||
{
|
||||
WeakPtr<WebGLContext> mWeakWebGL;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
bool mIsTimerRunning;
|
||||
bool mShouldRunTimerAgain;
|
||||
bool mIsDisabled;
|
||||
bool mFeatureAdded;
|
||||
DebugOnly<nsIThread*> mThread;
|
||||
|
||||
public:
|
||||
@ -35,7 +33,6 @@ public:
|
||||
|
||||
void RunTimer();
|
||||
void DisableTimer();
|
||||
bool Notify(JSContext* aCx, dom::workers::Status aStatus) override;
|
||||
|
||||
protected:
|
||||
~WebGLContextLossHandler();
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver)
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport,
|
||||
nsISupports* data, bool)
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include <algorithm>
|
||||
#include "angle/ShaderLang.h"
|
||||
#include "CanvasUtils.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "GLContext.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
@ -1666,11 +1665,11 @@ WebGLContext::InitAndValidateGL()
|
||||
return false;
|
||||
}
|
||||
|
||||
mMinCapability = gfxPrefs::WebGLMinCapabilityMode();
|
||||
mDisableExtensions = gfxPrefs::WebGLDisableExtensions();
|
||||
mLoseContextOnMemoryPressure = gfxPrefs::WebGLLoseContextOnMemoryPressure();
|
||||
mCanLoseContextInForeground = gfxPrefs::WebGLCanLoseContextInForeground();
|
||||
mRestoreWhenVisible = gfxPrefs::WebGLRestoreWhenVisible();
|
||||
mMinCapability = Preferences::GetBool("webgl.min_capability_mode", false);
|
||||
mDisableExtensions = Preferences::GetBool("webgl.disable-extensions", false);
|
||||
mLoseContextOnMemoryPressure = Preferences::GetBool("webgl.lose-context-on-memory-pressure", false);
|
||||
mCanLoseContextInForeground = Preferences::GetBool("webgl.can-lose-context-in-foreground", true);
|
||||
mRestoreWhenVisible = Preferences::GetBool("webgl.restore-context-when-visible", true);
|
||||
|
||||
if (MinCapabilityMode())
|
||||
mDisableFragHighP = true;
|
||||
@ -1879,7 +1878,10 @@ WebGLContext::InitAndValidateGL()
|
||||
#endif
|
||||
|
||||
// Check the shader validator pref
|
||||
mBypassShaderValidation = gfxPrefs::WebGLBypassShaderValidator();
|
||||
NS_ENSURE_TRUE(Preferences::GetRootBranch(), false);
|
||||
|
||||
mBypassShaderValidation = Preferences::GetBool("webgl.bypass-shader-validation",
|
||||
mBypassShaderValidation);
|
||||
|
||||
// initialize shader translator
|
||||
if (!ShInitialize()) {
|
||||
@ -1935,6 +1937,9 @@ WebGLContext::InitAndValidateGL()
|
||||
mDefaultVertexArray->BindVertexArray();
|
||||
}
|
||||
|
||||
if (mLoseContextOnMemoryPressure)
|
||||
mContextObserver->RegisterMemoryPressureEvent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver)
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport,
|
||||
nsISupports* data, bool)
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "WebGLShaderValidator.h"
|
||||
|
||||
#include "angle/ShaderLang.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "GLContext.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "MurmurHash3.h"
|
||||
@ -44,7 +43,7 @@ ChooseValidatorCompileOptions(const ShBuiltInResources& resources,
|
||||
options |= SH_LIMIT_EXPRESSION_COMPLEXITY;
|
||||
}
|
||||
|
||||
if (gfxPrefs::WebGLAllANGLEOptions()) {
|
||||
if (Preferences::GetBool("webgl.all-angle-options", false)) {
|
||||
return options |
|
||||
SH_VALIDATE_LOOP_INDEXING |
|
||||
SH_UNROLL_FOR_LOOP_WITH_INTEGER_INDEX |
|
||||
|
@ -28,12 +28,10 @@ EXPORTS.mozilla.dom += [
|
||||
'CanvasPath.h',
|
||||
'CanvasPattern.h',
|
||||
'CanvasRenderingContext2D.h',
|
||||
'CanvasRenderingContextHelper.h',
|
||||
'CanvasUtils.h',
|
||||
'ImageBitmap.h',
|
||||
'ImageBitmapSource.h',
|
||||
'ImageData.h',
|
||||
'OffscreenCanvas.h',
|
||||
'TextMetrics.h',
|
||||
'WebGLVertexArrayObject.h',
|
||||
]
|
||||
@ -42,13 +40,11 @@ EXPORTS.mozilla.dom += [
|
||||
UNIFIED_SOURCES += [
|
||||
'CanvasImageCache.cpp',
|
||||
'CanvasRenderingContext2D.cpp',
|
||||
'CanvasRenderingContextHelper.cpp',
|
||||
'CanvasUtils.cpp',
|
||||
'DocumentRendererChild.cpp',
|
||||
'DocumentRendererParent.cpp',
|
||||
'ImageBitmap.cpp',
|
||||
'ImageData.cpp',
|
||||
'OffscreenCanvas.cpp',
|
||||
]
|
||||
|
||||
# WebGL Sources
|
||||
@ -154,7 +150,6 @@ LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
'/dom/html',
|
||||
'/dom/svg',
|
||||
'/dom/workers',
|
||||
'/dom/xul',
|
||||
'/gfx/gl',
|
||||
'/image',
|
||||
|
@ -12,13 +12,12 @@
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "mozilla/dom/OffscreenCanvas.h"
|
||||
#include "GraphicsFilter.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \
|
||||
{ 0xb84f2fed, 0x9d4b, 0x430b, \
|
||||
{ 0xbd, 0xfb, 0x85, 0x57, 0x8a, 0xc2, 0xb4, 0x4b } }
|
||||
{ 0x3cc9e801, 0x1806, 0x4ff6, \
|
||||
{ 0x86, 0x14, 0xf9, 0xd0, 0xf4, 0xfb, 0x3b, 0x08 } }
|
||||
|
||||
class gfxASurface;
|
||||
class nsDisplayListBuilder;
|
||||
@ -81,11 +80,6 @@ public:
|
||||
return mCanvasElement;
|
||||
}
|
||||
|
||||
void SetOffscreenCanvas(mozilla::dom::OffscreenCanvas* aOffscreenCanvas)
|
||||
{
|
||||
mOffscreenCanvas = aOffscreenCanvas;
|
||||
}
|
||||
|
||||
// Dimensions of the canvas, in pixels.
|
||||
virtual int32_t GetWidth() const = 0;
|
||||
virtual int32_t GetHeight() const = 0;
|
||||
@ -158,10 +152,6 @@ public:
|
||||
// Given a point, return hit region ID if it exists or an empty string if it doesn't
|
||||
virtual nsString GetHitRegion(const mozilla::gfx::Point& point) { return nsString(); }
|
||||
|
||||
virtual void OnVisibilityChange() {}
|
||||
|
||||
virtual void OnMemoryPressure() {}
|
||||
|
||||
//
|
||||
// shmem support
|
||||
//
|
||||
@ -174,7 +164,6 @@ public:
|
||||
|
||||
protected:
|
||||
nsRefPtr<mozilla::dom::HTMLCanvasElement> mCanvasElement;
|
||||
nsRefPtr<mozilla::dom::OffscreenCanvas> mOffscreenCanvas;
|
||||
nsRefPtr<nsRefreshDriver> mRefreshDriver;
|
||||
};
|
||||
|
||||
|
@ -27,10 +27,6 @@ support-files =
|
||||
imagebitmap_on_worker.js
|
||||
imagebitmap_structuredclone.js
|
||||
imagebitmap_structuredclone_iframe.html
|
||||
offscreencanvas.js
|
||||
offscreencanvas_mask.svg
|
||||
offscreencanvas_neuter.js
|
||||
offscreencanvas_serviceworker_inner.html
|
||||
|
||||
[test_2d.clearRect.image.offscreen.html]
|
||||
[test_2d.clip.winding.html]
|
||||
@ -265,22 +261,3 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
|
||||
[test_createPattern_broken.html]
|
||||
[test_setlinedash.html]
|
||||
[test_filter.html]
|
||||
[test_offscreencanvas_basic_webgl.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_dynamic_fallback.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_sharedworker.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_serviceworker.html]
|
||||
tags = offscreencanvas
|
||||
skip-if = buildapp == 'b2g'
|
||||
[test_offscreencanvas_neuter.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_many.html]
|
||||
tags = offscreencanvas
|
||||
skip-if = (toolkit == 'android' || toolkit == 'gonk' || toolkit == 'windows' || toolkit == 'gtk2' || toolkit == 'gtk3')
|
||||
[test_offscreencanvas_sizechange.html]
|
||||
tags = offscreencanvas
|
||||
[test_offscreencanvas_subworker.html]
|
||||
tags = offscreencanvas
|
||||
skip-if = (toolkit == 'android' || toolkit == 'gonk' || toolkit == 'windows' || toolkit == 'gtk2' || toolkit == 'gtk3')
|
||||
|
@ -1,299 +0,0 @@
|
||||
/* WebWorker for test_offscreencanvas_*.html */
|
||||
var port = null;
|
||||
|
||||
function ok(expect, msg) {
|
||||
if (port) {
|
||||
port.postMessage({type: "test", result: !!expect, name: msg});
|
||||
} else {
|
||||
postMessage({type: "test", result: !!expect, name: msg});
|
||||
}
|
||||
}
|
||||
|
||||
function finish() {
|
||||
if (port) {
|
||||
port.postMessage({type: "finish"});
|
||||
} else {
|
||||
postMessage({type: "finish"});
|
||||
}
|
||||
}
|
||||
|
||||
function drawCount(count) {
|
||||
if (port) {
|
||||
port.postMessage({type: "draw", count: count});
|
||||
} else {
|
||||
postMessage({type: "draw", count: count});
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// WebGL Drawing Functions
|
||||
//--------------------------------------------------------------------
|
||||
function createDrawFunc(canvas) {
|
||||
var gl;
|
||||
|
||||
try {
|
||||
gl = canvas.getContext("experimental-webgl");
|
||||
} catch (e) {}
|
||||
|
||||
if (!gl) {
|
||||
ok(false, "WebGL is unavailable");
|
||||
return null;
|
||||
}
|
||||
|
||||
var vertSrc = "attribute vec2 position; \
|
||||
void main(void) { \
|
||||
gl_Position = vec4(position, 0.0, 1.0); \
|
||||
}";
|
||||
|
||||
var fragSrc = "precision mediump float; \
|
||||
void main(void) { \
|
||||
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); \
|
||||
}";
|
||||
|
||||
// Returns a valid shader, or null on errors.
|
||||
var createShader = function(src, t) {
|
||||
var shader = gl.createShader(t);
|
||||
|
||||
gl.shaderSource(shader, src);
|
||||
gl.compileShader(shader);
|
||||
|
||||
return shader;
|
||||
};
|
||||
|
||||
var createProgram = function(vsSrc, fsSrc) {
|
||||
var vs = createShader(vsSrc, gl.VERTEX_SHADER);
|
||||
var fs = createShader(fsSrc, gl.FRAGMENT_SHADER);
|
||||
|
||||
var prog = gl.createProgram();
|
||||
gl.attachShader(prog, vs);
|
||||
gl.attachShader(prog, fs);
|
||||
gl.linkProgram(prog);
|
||||
|
||||
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
||||
var str = "Shader program linking failed:";
|
||||
str += "\nShader program info log:\n" + gl.getProgramInfoLog(prog);
|
||||
str += "\n\nVert shader log:\n" + gl.getShaderInfoLog(vs);
|
||||
str += "\n\nFrag shader log:\n" + gl.getShaderInfoLog(fs);
|
||||
console.log(str);
|
||||
ok(false, "Shader program linking failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
return prog;
|
||||
};
|
||||
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
|
||||
var program = createProgram(vertSrc, fragSrc);
|
||||
ok(program, "Creating shader program");
|
||||
|
||||
program.positionAttr = gl.getAttribLocation(program, "position");
|
||||
ok(program.positionAttr >= 0, "position attribute should be valid");
|
||||
|
||||
var vertCoordArr = new Float32Array([
|
||||
-1, -1,
|
||||
1, -1,
|
||||
-1, 1,
|
||||
1, 1,
|
||||
]);
|
||||
var vertCoordBuff = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertCoordBuff);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, vertCoordArr, gl.STATIC_DRAW);
|
||||
|
||||
var checkGLError = function(prefix, refValue) {
|
||||
if (!refValue) {
|
||||
refValue = 0;
|
||||
}
|
||||
|
||||
var error = gl.getError();
|
||||
ok(error == refValue,
|
||||
prefix + 'gl.getError should be 0x' + refValue.toString(16) +
|
||||
', was 0x' + error.toString(16) + '.');
|
||||
};
|
||||
|
||||
var testPixel = function(x, y, refData, infoString) {
|
||||
var pixel = new Uint8Array(4);
|
||||
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
|
||||
|
||||
var pixelMatches = pixel[0] == refData[0] &&
|
||||
pixel[1] == refData[1] &&
|
||||
pixel[2] == refData[2] &&
|
||||
pixel[3] == refData[3];
|
||||
ok(pixelMatches, infoString);
|
||||
};
|
||||
|
||||
var preDraw = function(prefix) {
|
||||
gl.clearColor(1.0, 0.0, 0.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
testPixel(0, 0, [255, 0, 0, 255], prefix + 'Should be red before drawing.');
|
||||
};
|
||||
|
||||
var postDraw = function(prefix) {
|
||||
testPixel(0, 0, [0, 255, 0, 255], prefix + 'Should be green after drawing.');
|
||||
};
|
||||
|
||||
gl.useProgram(program);
|
||||
gl.enableVertexAttribArray(program.position);
|
||||
gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Start drawing
|
||||
checkGLError('after setup');
|
||||
|
||||
return function(prefix) {
|
||||
if (prefix) {
|
||||
prefix = "[" + prefix + "] ";
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
|
||||
preDraw(prefix);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
postDraw(prefix);
|
||||
gl.commit();
|
||||
checkGLError(prefix);
|
||||
};
|
||||
}
|
||||
|
||||
/* entry point */
|
||||
function entryFunction(testStr, subtests, offscreenCanvas) {
|
||||
var test = testStr;
|
||||
var canvas = offscreenCanvas;
|
||||
|
||||
if (test != "subworker") {
|
||||
ok(canvas, "Canvas successfully transfered to worker");
|
||||
ok(canvas.getContext, "Canvas has getContext");
|
||||
|
||||
ok(canvas.width == 64, "OffscreenCanvas width should be 64");
|
||||
ok(canvas.height == 64, "OffscreenCanvas height should be 64");
|
||||
}
|
||||
|
||||
var draw;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Basic WebGL test
|
||||
//------------------------------------------------------------------------
|
||||
if (test == "webgl") {
|
||||
draw = createDrawFunc(canvas);
|
||||
if (!draw) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var iid = setInterval(function() {
|
||||
if (count++ > 20) {
|
||||
clearInterval(iid);
|
||||
ok(true, "Worker is done");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
draw("loop " +count);
|
||||
}, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Test dynamic fallback
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "webgl_fallback") {
|
||||
draw = createDrawFunc(canvas);
|
||||
if (!draw) {
|
||||
return;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var iid = setInterval(function() {
|
||||
++count;
|
||||
draw("loop " + count);
|
||||
drawCount(count);
|
||||
}, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Canvas Size Change from Worker
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "webgl_changesize") {
|
||||
draw = createDrawFunc(canvas);
|
||||
if (!draw) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
draw("64x64");
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 128;
|
||||
canvas.height = 128;
|
||||
draw("Increased to 128x128");
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 32;
|
||||
canvas.width = 32;
|
||||
draw("Decreased to 32x32");
|
||||
|
||||
setTimeout(function() {
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
draw("Increased to 64x64");
|
||||
|
||||
ok(true, "Worker is done");
|
||||
finish();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Using OffscreenCanvas from sub workers
|
||||
//------------------------------------------------------------------------
|
||||
else if (test == "subworker") {
|
||||
/* subworker tests take a list of tests to run on children */
|
||||
var stillRunning = 0;
|
||||
subtests.forEach(function (subtest) {
|
||||
++stillRunning;
|
||||
var subworker = new Worker('offscreencanvas.js');
|
||||
subworker.onmessage = function(evt) {
|
||||
/* report finish to parent when all children are finished */
|
||||
if (evt.data.type == "finish") {
|
||||
subworker.terminate();
|
||||
if (--stillRunning == 0) {
|
||||
ok(true, "Worker is done");
|
||||
finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* relay all other messages to parent */
|
||||
postMessage(evt.data);
|
||||
};
|
||||
|
||||
var findTransferables = function(t) {
|
||||
if (t.test == "subworker") {
|
||||
var result = [];
|
||||
t.subtests.forEach(function(test) {
|
||||
result = result.concat(findTransferables(test));
|
||||
});
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return [t.canvas];
|
||||
}
|
||||
};
|
||||
|
||||
subworker.postMessage(subtest, findTransferables(subtest));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onmessage = function(evt) {
|
||||
port = evt.ports[0];
|
||||
entryFunction(evt.data.test, evt.data.subtests, evt.data.canvas);
|
||||
};
|
||||
|
||||
onconnect = function(evt) {
|
||||
port = evt.ports[0];
|
||||
|
||||
port.addEventListener('message', function(evt) {
|
||||
entryFunction(evt.data.test, evt.data.subtests, evt.data.canvas);
|
||||
});
|
||||
|
||||
port.start();
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<mask id="fade_mask_both" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
|
||||
<linearGradient id="fade_gradient_both" gradientUnits="objectBoundingBox" x2="0" y2="1">
|
||||
<stop stop-color="white" stop-opacity="0" offset="0"></stop>
|
||||
<stop stop-color="white" stop-opacity="1" offset="0.2"></stop>
|
||||
<stop stop-color="white" stop-opacity="1" offset="0.8"></stop>
|
||||
<stop stop-color="white" stop-opacity="0" offset="1"></stop>
|
||||
</linearGradient>
|
||||
<rect x="0" y="0" width="1" height="1" fill="url(#fade_gradient_both)"></rect>
|
||||
</mask>
|
||||
</svg>
|
Before Width: | Height: | Size: 638 B |
@ -1 +0,0 @@
|
||||
/* empty worker for test_offscreencanvas_disable.html */
|
@ -1,32 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<script>
|
||||
function ok(expect, msg) {
|
||||
parent.postMessage({type: "test", result: !!expect, name: msg}, "*");
|
||||
}
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
|
||||
var messageChannel = new MessageChannel();
|
||||
messageChannel.port1.onmessage = function(evt) {
|
||||
parent.postMessage(evt.data, "*");
|
||||
}
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
navigator.serviceWorker.ready.then(function() {
|
||||
navigator.serviceWorker.controller.postMessage({test: 'webgl', canvas: offscreenCanvas}, [offscreenCanvas, messageChannel.port2]);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,62 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<canvas id="c-ref" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function testToDataURL() {
|
||||
// testing toDataURL
|
||||
// Fill c-ref with green color.
|
||||
var c = document.getElementById("c-ref");
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.rect(0, 0, 64, 64);
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fill();
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
ok(c.toDataURL() == htmlCanvas.toDataURL(), "toDataURL should return a 64x64 green square");
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
ok(worker, "Web worker successfully created");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
testToDataURL();
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
worker.postMessage({test: 'webgl', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,80 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function createCanvas(initWithMask) {
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
document.body.appendChild(canvas);
|
||||
if (initWithMask) {
|
||||
canvas.style.mask = "url('offscreencanvas_mask.svg#fade_mask_both')";
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function getRefSnapshot(initWithMask) {
|
||||
var refCanvas = createCanvas(!initWithMask);
|
||||
var ctx = refCanvas.getContext("2d");
|
||||
ctx.rect(0, 0, 64, 64);
|
||||
ctx.fillStyle = "#00FF00";
|
||||
ctx.fill();
|
||||
var result = snapshotWindow(window);
|
||||
document.body.removeChild(refCanvas);
|
||||
return result;
|
||||
}
|
||||
|
||||
function runTest(initWithMask) {
|
||||
var htmlCanvas = createCanvas(initWithMask);
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "draw") {
|
||||
if (msg.count === 10) {
|
||||
// Change the fallback state dynamically when drawing count reaches 10.
|
||||
if (initWithMask) {
|
||||
htmlCanvas.style.mask = "";
|
||||
} else {
|
||||
htmlCanvas.style.mask = "url('offscreencanvas_mask.svg#fade_mask_both')";
|
||||
}
|
||||
} else if (msg.count === 20) {
|
||||
var snapshotFallback = snapshotWindow(window);
|
||||
worker.terminate();
|
||||
document.body.removeChild(htmlCanvas);
|
||||
|
||||
var results = compareSnapshots(snapshotFallback, getRefSnapshot(initWithMask), true);
|
||||
ok(results[0], "after dynamic fallback, screenshots should be the same");
|
||||
|
||||
if (initWithMask) {
|
||||
SimpleTest.finish();
|
||||
} else {
|
||||
runTest(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
|
||||
worker.postMessage({test: 'webgl_fallback', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest.bind(false));
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,67 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
This test needs several workers run offscreen canvas simultaneously.
|
||||
So we choose 8 workers, 4 of them run basic webgl drawing test and
|
||||
others run size changing test.
|
||||
-->
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function createCanvas() {
|
||||
var htmlCanvas = document.createElement('canvas');
|
||||
htmlCanvas.width = 64;
|
||||
htmlCanvas.height = 64;
|
||||
document.body.appendChild(htmlCanvas);
|
||||
return htmlCanvas;
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
var stillRunning = 0;
|
||||
|
||||
var startWorker = function(canvas, test) {
|
||||
stillRunning++;
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
worker.terminate();
|
||||
if (--stillRunning == 0)
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
var offscreenCanvas = canvas.transferControlToOffscreen();
|
||||
worker.postMessage({test: test, canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
/* create 4 workers that do the regular drawing test and 4 workers
|
||||
that do the size change test */
|
||||
for (var i = 0; i < 4; i++) {
|
||||
startWorker(createCanvas(), 'webgl');
|
||||
}
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
startWorker(createCanvas(), 'webgl_changesize');
|
||||
}
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true]
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,78 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>OffscreenCanvas: Test neutering</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var worker = new Worker("offscreencanvas_neuter.js");
|
||||
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
ok(worker, "Web worker successfully created");
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
/* check html canvas is neuterd */
|
||||
is(htmlCanvas.width, 64, "HTML canvas has correct width");
|
||||
SimpleTest.doesThrow(
|
||||
function() { htmlCanvas.width = 128; },
|
||||
"Can't change html canvas' width after transferControlToOffscreen");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { htmlCanvas.height = 128; },
|
||||
"Can't change html canvas' height after transferControlToOffscreen");
|
||||
|
||||
ok(!htmlCanvas.getContext("2d"), "Can't getContext after transferControlToOffscreen");
|
||||
ok(!htmlCanvas.getContext("webgl"), "Can't getContext after transferControlToOffscreen");
|
||||
ok(!htmlCanvas.getContext("webgl2"), "Can't getContext after transferControlToOffscreen");
|
||||
|
||||
worker.postMessage(offscreenCanvas, [offscreenCanvas]);
|
||||
|
||||
/* check parent offscreencanvas is neutered after being transfered */
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.width = 128; },
|
||||
"Can't change transfered worker canvas width");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.height = 128; },
|
||||
"Can't change transfered worker canvas height");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.getContext("2d") },
|
||||
"Can't getContext on transfered worker canvas");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.getContext("webgl") },
|
||||
"Can't getContext on transfered worker canvas");
|
||||
|
||||
SimpleTest.doesThrow(
|
||||
function() { offscreenCanvas.getContext("webgl2") },
|
||||
"Can't getContext on transfered worker canvas");
|
||||
|
||||
// Transfer a neutered offscreencanvas should be ok.
|
||||
worker.postMessage(offscreenCanvas, [offscreenCanvas]);
|
||||
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,46 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
window.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
navigator.serviceWorker.register('offscreencanvas.js', { scope: "."})
|
||||
// Wait until the service worker is active.
|
||||
.then(navigator.serviceWorker.ready)
|
||||
// ...and then show the interface for the commands once it's ready.
|
||||
.then(function() {
|
||||
iframe = document.createElement("iframe");
|
||||
iframe.setAttribute('src', "offscreencanvas_serviceworker_inner.html");
|
||||
document.body.appendChild(iframe);
|
||||
})
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
["dom.serviceWorkers.exemptFromPerDomainMax", true],
|
||||
["dom.serviceWorkers.interception.enabled", true],
|
||||
["dom.serviceWorkers.enabled", true],
|
||||
["dom.serviceWorkers.testing.enabled", true]
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,47 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var worker = new SharedWorker("offscreencanvas.js");
|
||||
|
||||
ok(htmlCanvas, "Should have HTML canvas element");
|
||||
ok(worker, "Web worker successfully created");
|
||||
|
||||
ok(htmlCanvas.transferControlToOffscreen, "HTMLCanvasElement has transferControlToOffscreen function");
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
ok(offscreenCanvas, "Expected transferControlToOffscreen to succeed");
|
||||
|
||||
worker.port.start();
|
||||
|
||||
// We don't support transferring OffscreenCanvas via shared worker.
|
||||
SimpleTest.doesThrow(
|
||||
function() {
|
||||
worker.port.postMessage({test: 'webgl', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
},
|
||||
"OffscreenCanvas cannot transfer to shared worker"
|
||||
);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,41 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebGL in OffscreenCanvas</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c" width="64" height="64"></canvas>
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runTest() {
|
||||
|
||||
var htmlCanvas = document.getElementById("c");
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
var offscreenCanvas = htmlCanvas.transferControlToOffscreen();
|
||||
worker.postMessage({test: 'webgl_changesize', canvas: offscreenCanvas}, [offscreenCanvas]);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,90 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>OffscreenCanvas: Test subworkers</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
We want to test offscreen canvas works well when it running on worker
|
||||
and nested worker simultaneously. So we create 10 canvas and dispatch
|
||||
it to different workers and sub-workers.
|
||||
-->
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function createCanvas() {
|
||||
var htmlCanvas = document.createElement('canvas');
|
||||
htmlCanvas.width = 64;
|
||||
htmlCanvas.height = 64;
|
||||
document.body.appendChild(htmlCanvas);
|
||||
return htmlCanvas.transferControlToOffscreen();
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
|
||||
var worker = new Worker("offscreencanvas.js");
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
var msg = evt.data || {};
|
||||
if (msg.type == "test") {
|
||||
ok(msg.result, msg.name);
|
||||
}
|
||||
if (msg.type == "finish") {
|
||||
worker.terminate();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
var findTransferables = function(t) {
|
||||
if (t.test == "subworker") {
|
||||
var result = [];
|
||||
t.subtests.forEach(function(test) {
|
||||
result = result.concat(findTransferables(test));
|
||||
});
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return [t.canvas];
|
||||
}
|
||||
};
|
||||
|
||||
var testData =
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl', canvas: createCanvas()},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl', canvas: createCanvas()},
|
||||
{test: 'webgl_changesize', canvas: createCanvas()},
|
||||
{test: 'webgl', canvas: createCanvas()}
|
||||
]},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl', canvas: createCanvas()},
|
||||
{test: 'webgl_changesize', canvas: createCanvas()},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl_changesize', canvas: createCanvas()},
|
||||
{test: 'webgl', canvas: createCanvas()}
|
||||
]},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl_changesize', canvas: createCanvas()},
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'subworker', subtests: [
|
||||
{test: 'webgl_changesize', canvas: createCanvas()}
|
||||
]}
|
||||
]}
|
||||
]},
|
||||
]}
|
||||
]};
|
||||
|
||||
worker.postMessage(testData, findTransferables(testData));
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['gfx.offscreencanvas.enabled', true],
|
||||
['webgl.force-enabled', true],
|
||||
]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -203,13 +203,13 @@ ContentEventHandler::QueryContentRect(nsIContent* aContent,
|
||||
|
||||
// get rect for first frame
|
||||
nsRect resultRect(nsPoint(0, 0), frame->GetRect().Size());
|
||||
nsresult rv = ConvertToRootViewRelativeOffset(frame, resultRect);
|
||||
nsresult rv = ConvertToRootRelativeOffset(frame, resultRect);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// account for any additional frames
|
||||
while ((frame = frame->GetNextContinuation()) != nullptr) {
|
||||
nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size());
|
||||
rv = ConvertToRootViewRelativeOffset(frame, frameRect);
|
||||
rv = ConvertToRootRelativeOffset(frame, frameRect);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
resultRect.UnionRect(resultRect, frameRect);
|
||||
}
|
||||
@ -1017,7 +1017,7 @@ ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent)
|
||||
|
||||
// get the starting frame rect
|
||||
nsRect rect(nsPoint(0, 0), firstFrame->GetRect().Size());
|
||||
rv = ConvertToRootViewRelativeOffset(firstFrame, rect);
|
||||
rv = ConvertToRootRelativeOffset(firstFrame, rect);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsRect frameRect = rect;
|
||||
nsPoint ptOffset;
|
||||
@ -1059,7 +1059,7 @@ ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent)
|
||||
}
|
||||
}
|
||||
frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
|
||||
rv = ConvertToRootViewRelativeOffset(frame, frameRect);
|
||||
rv = ConvertToRootRelativeOffset(frame, frameRect);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (frame != lastFrame) {
|
||||
// not last frame, so just add rect to previous result
|
||||
@ -1125,7 +1125,7 @@ ContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent)
|
||||
lineBreakType);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (offset == aEvent->mInput.mOffset) {
|
||||
rv = ConvertToRootViewRelativeOffset(caretFrame, caretRect);
|
||||
rv = ConvertToRootRelativeOffset(caretFrame, caretRect);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nscoord appUnitsPerDevPixel =
|
||||
caretFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
@ -1186,7 +1186,7 @@ ContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent)
|
||||
rect.height = fontHeight;
|
||||
}
|
||||
|
||||
rv = ConvertToRootViewRelativeOffset(frame, rect);
|
||||
rv = ConvertToRootRelativeOffset(frame, rect);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped(
|
||||
@ -1547,18 +1547,21 @@ ContentEventHandler::GetStartFrameAndOffset(const nsRange* aRange,
|
||||
}
|
||||
|
||||
nsresult
|
||||
ContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame* aFrame,
|
||||
nsRect& aRect)
|
||||
ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame* aFrame,
|
||||
nsRect& aRect)
|
||||
{
|
||||
NS_ASSERTION(aFrame, "aFrame must not be null");
|
||||
|
||||
nsView* view = nullptr;
|
||||
nsPoint posInView;
|
||||
aFrame->GetOffsetFromView(posInView, &view);
|
||||
if (!view) {
|
||||
nsPresContext* rootPresContext = aFrame->PresContext()->GetRootPresContext();
|
||||
if (NS_WARN_IF(!rootPresContext)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
aRect += posInView + view->GetOffsetTo(nullptr);
|
||||
nsIFrame* rootFrame = rootPresContext->PresShell()->GetRootFrame();
|
||||
if (NS_WARN_IF(!rootFrame)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, aRect, rootFrame);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -136,9 +136,10 @@ protected:
|
||||
nsresult GetStartFrameAndOffset(const nsRange* aRange,
|
||||
nsIFrame*& aFrame,
|
||||
int32_t& aOffsetInFrame);
|
||||
// Convert the frame relative offset to the root view relative offset.
|
||||
nsresult ConvertToRootViewRelativeOffset(nsIFrame* aFrame,
|
||||
nsRect& aRect);
|
||||
// Convert the frame relative offset to the root frame of the root presContext
|
||||
// relative offset.
|
||||
nsresult ConvertToRootRelativeOffset(nsIFrame* aFrame,
|
||||
nsRect& aRect);
|
||||
// Expand aXPOffset to the nearest offset in cluster boundary. aForward is
|
||||
// true, it is expanded to forward.
|
||||
nsresult ExpandToClusterBoundary(nsIContent* aContent, bool aForward,
|
||||
|
@ -19,10 +19,8 @@
|
||||
#include "mozilla/dom/File.h"
|
||||
#include "mozilla/dom/HTMLCanvasElementBinding.h"
|
||||
#include "mozilla/dom/MouseEvent.h"
|
||||
#include "mozilla/dom/OffscreenCanvas.h"
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/gfx/Rect.h"
|
||||
#include "mozilla/layers/AsyncCanvasRenderer.h"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
@ -241,135 +239,18 @@ HTMLCanvasPrintState::NotifyDone()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
HTMLCanvasElementObserver::HTMLCanvasElementObserver(HTMLCanvasElement* aElement)
|
||||
: mElement(aElement)
|
||||
{
|
||||
RegisterVisibilityChangeEvent();
|
||||
RegisterMemoryPressureEvent();
|
||||
}
|
||||
|
||||
HTMLCanvasElementObserver::~HTMLCanvasElementObserver()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElementObserver::Destroy()
|
||||
{
|
||||
UnregisterMemoryPressureEvent();
|
||||
UnregisterVisibilityChangeEvent();
|
||||
mElement = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElementObserver::RegisterVisibilityChangeEvent()
|
||||
{
|
||||
if (!mElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIDocument* document = mElement->OwnerDoc();
|
||||
document->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
|
||||
this, true, false);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElementObserver::UnregisterVisibilityChangeEvent()
|
||||
{
|
||||
if (!mElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIDocument* document = mElement->OwnerDoc();
|
||||
document->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
|
||||
this, true);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElementObserver::RegisterMemoryPressureEvent()
|
||||
{
|
||||
if (!mElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
|
||||
MOZ_ASSERT(observerService);
|
||||
|
||||
if (observerService)
|
||||
observerService->AddObserver(this, "memory-pressure", false);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElementObserver::UnregisterMemoryPressureEvent()
|
||||
{
|
||||
if (!mElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
|
||||
// Do not assert on observerService here. This might be triggered by
|
||||
// the cycle collector at a late enough time, that XPCOM services are
|
||||
// no longer available. See bug 1029504.
|
||||
if (observerService)
|
||||
observerService->RemoveObserver(this, "memory-pressure");
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLCanvasElementObserver::Observe(nsISupports*, const char* aTopic, const char16_t*)
|
||||
{
|
||||
if (!mElement || strcmp(aTopic, "memory-pressure")) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mElement->OnMemoryPressure();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLCanvasElementObserver::HandleEvent(nsIDOMEvent* aEvent)
|
||||
{
|
||||
nsAutoString type;
|
||||
aEvent->GetType(type);
|
||||
if (!mElement || !type.EqualsLiteral("visibilitychange")) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mElement->OnVisibilityChange();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
HTMLCanvasElement::HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
|
||||
: nsGenericHTMLElement(aNodeInfo),
|
||||
mResetLayer(true) ,
|
||||
mWriteOnly(false)
|
||||
{
|
||||
}
|
||||
|
||||
HTMLCanvasElement::~HTMLCanvasElement()
|
||||
{
|
||||
if (mContextObserver) {
|
||||
mContextObserver->Destroy();
|
||||
mContextObserver = nullptr;
|
||||
}
|
||||
|
||||
ResetPrintCallback();
|
||||
if (mRequestedFrameRefreshObserver) {
|
||||
mRequestedFrameRefreshObserver->DetachFromRefreshDriver();
|
||||
}
|
||||
|
||||
if (mAsyncCanvasRenderer) {
|
||||
mAsyncCanvasRenderer->mHTMLCanvasElement = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
|
||||
@ -391,22 +272,6 @@ HTMLCanvasElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
return HTMLCanvasElementBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
HTMLCanvasElement::CreateContext(CanvasContextType aContextType)
|
||||
{
|
||||
nsRefPtr<nsICanvasRenderingContextInternal> ret =
|
||||
CanvasRenderingContextHelper::CreateContext(aContextType);
|
||||
|
||||
// Add Observer for webgl canvas.
|
||||
if (aContextType == CanvasContextType::WebGL1 ||
|
||||
aContextType == CanvasContextType::WebGL2) {
|
||||
mContextObserver = new HTMLCanvasElementObserver(this);
|
||||
}
|
||||
|
||||
ret->SetCanvasElement(this);
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
nsIntSize
|
||||
HTMLCanvasElement::GetWidthHeight()
|
||||
{
|
||||
@ -691,10 +556,51 @@ HTMLCanvasElement::ExtractData(nsAString& aType,
|
||||
aOptions,
|
||||
GetSize(),
|
||||
mCurrentContext,
|
||||
mAsyncCanvasRenderer,
|
||||
aStream);
|
||||
}
|
||||
|
||||
nsresult
|
||||
HTMLCanvasElement::ParseParams(JSContext* aCx,
|
||||
const nsAString& aType,
|
||||
const JS::Value& aEncoderOptions,
|
||||
nsAString& aParams,
|
||||
bool* usingCustomParseOptions)
|
||||
{
|
||||
// Quality parameter is only valid for the image/jpeg MIME type
|
||||
if (aType.EqualsLiteral("image/jpeg")) {
|
||||
if (aEncoderOptions.isNumber()) {
|
||||
double quality = aEncoderOptions.toNumber();
|
||||
// Quality must be between 0.0 and 1.0, inclusive
|
||||
if (quality >= 0.0 && quality <= 1.0) {
|
||||
aParams.AppendLiteral("quality=");
|
||||
aParams.AppendInt(NS_lround(quality * 100.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't parsed the aParams check for proprietary options.
|
||||
// The proprietary option -moz-parse-options will take a image lib encoder
|
||||
// parse options string as is and pass it to the encoder.
|
||||
*usingCustomParseOptions = false;
|
||||
if (aParams.Length() == 0 && aEncoderOptions.isString()) {
|
||||
NS_NAMED_LITERAL_STRING(mozParseOptions, "-moz-parse-options:");
|
||||
nsAutoJSString paramString;
|
||||
if (!paramString.init(aCx, aEncoderOptions.toString())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (StringBeginsWith(paramString, mozParseOptions)) {
|
||||
nsDependentSubstring parseOptions = Substring(paramString,
|
||||
mozParseOptions.Length(),
|
||||
paramString.Length() -
|
||||
mozParseOptions.Length());
|
||||
aParams.Append(parseOptions);
|
||||
*usingCustomParseOptions = true;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
|
||||
const nsAString& aMimeType,
|
||||
@ -753,38 +659,84 @@ HTMLCanvasElement::ToBlob(JSContext* aCx,
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString type;
|
||||
nsContentUtils::ASCIIToLower(aType, type);
|
||||
|
||||
nsAutoString params;
|
||||
bool usingCustomParseOptions;
|
||||
aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
// We disallow canvases of width or height zero, and set them to 1, so
|
||||
// we will have a discrepancy with the sizes of the canvas and the context.
|
||||
// That discrepancy is OK, the rest are not.
|
||||
nsIntSize elementSize = GetWidthHeight();
|
||||
if ((elementSize.width != mCurrentContext->GetWidth() &&
|
||||
(elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
|
||||
(elementSize.height != mCurrentContext->GetHeight() &&
|
||||
(elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* imageBuffer = nullptr;
|
||||
int32_t format = 0;
|
||||
if (mCurrentContext) {
|
||||
mCurrentContext->GetImageBuffer(&imageBuffer, &format);
|
||||
}
|
||||
|
||||
// Encoder callback when encoding is complete.
|
||||
class EncodeCallback : public EncodeCompleteCallback
|
||||
{
|
||||
public:
|
||||
EncodeCallback(nsIGlobalObject* aGlobal, FileCallback* aCallback)
|
||||
: mGlobal(aGlobal)
|
||||
, mFileCallback(aCallback) {}
|
||||
|
||||
// This is called on main thread.
|
||||
nsresult ReceiveBlob(already_AddRefed<Blob> aBlob)
|
||||
{
|
||||
nsRefPtr<Blob> blob = aBlob;
|
||||
|
||||
ErrorResult rv;
|
||||
uint64_t size = blob->GetSize(rv);
|
||||
if (rv.Failed()) {
|
||||
rv.SuppressException();
|
||||
} else {
|
||||
AutoJSAPI jsapi;
|
||||
if (jsapi.Init(mGlobal)) {
|
||||
JS_updateMallocCounter(jsapi.cx(), size);
|
||||
}
|
||||
}
|
||||
|
||||
nsRefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
|
||||
|
||||
mFileCallback->Call(*newBlob, rv);
|
||||
|
||||
mGlobal = nullptr;
|
||||
mFileCallback = nullptr;
|
||||
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
nsRefPtr<FileCallback> mFileCallback;
|
||||
};
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
|
||||
MOZ_ASSERT(global);
|
||||
|
||||
CanvasRenderingContextHelper::ToBlob(aCx, global, aCallback, aType,
|
||||
aParams, aRv);
|
||||
|
||||
}
|
||||
|
||||
OffscreenCanvas*
|
||||
HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
|
||||
{
|
||||
if (mCurrentContext) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!mOffscreenCanvas) {
|
||||
nsIntSize sz = GetWidthHeight();
|
||||
nsRefPtr<AsyncCanvasRenderer> renderer = GetAsyncCanvasRenderer();
|
||||
renderer->SetWidth(sz.width);
|
||||
renderer->SetHeight(sz.height);
|
||||
|
||||
mOffscreenCanvas = new OffscreenCanvas(sz.width,
|
||||
sz.height,
|
||||
GetCompositorBackendType(),
|
||||
renderer);
|
||||
mContextObserver = new HTMLCanvasElementObserver(this);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
return mOffscreenCanvas;
|
||||
nsRefPtr<EncodeCompleteCallback> callback = new EncodeCallback(global, &aCallback);
|
||||
aRv = ImageEncoder::ExtractDataAsync(type,
|
||||
params,
|
||||
usingCustomParseOptions,
|
||||
imageBuffer,
|
||||
format,
|
||||
GetSize(),
|
||||
callback);
|
||||
}
|
||||
|
||||
already_AddRefed<File>
|
||||
@ -855,6 +807,76 @@ HTMLCanvasElement::MozGetAsBlobImpl(const nsAString& aName,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetCanvasContextType(const nsAString& str, CanvasContextType* const out_type)
|
||||
{
|
||||
if (str.EqualsLiteral("2d")) {
|
||||
*out_type = CanvasContextType::Canvas2D;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str.EqualsLiteral("experimental-webgl")) {
|
||||
*out_type = CanvasContextType::WebGL1;
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef MOZ_WEBGL_CONFORMANT
|
||||
if (str.EqualsLiteral("webgl")) {
|
||||
/* WebGL 1.0, $2.1 "Context Creation":
|
||||
* If the user agent supports both the webgl and experimental-webgl
|
||||
* canvas context types, they shall be treated as aliases.
|
||||
*/
|
||||
*out_type = CanvasContextType::WebGL1;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (WebGL2Context::IsSupported()) {
|
||||
if (str.EqualsLiteral("webgl2")) {
|
||||
*out_type = CanvasContextType::WebGL2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContextForCanvas(CanvasContextType contextType, HTMLCanvasElement* canvas)
|
||||
{
|
||||
MOZ_ASSERT(contextType != CanvasContextType::NoContext);
|
||||
nsRefPtr<nsICanvasRenderingContextInternal> ret;
|
||||
|
||||
switch (contextType) {
|
||||
case CanvasContextType::NoContext:
|
||||
break;
|
||||
case CanvasContextType::Canvas2D:
|
||||
Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
|
||||
ret = new CanvasRenderingContext2D();
|
||||
break;
|
||||
|
||||
case CanvasContextType::WebGL1:
|
||||
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
|
||||
|
||||
ret = WebGL1Context::Create();
|
||||
if (!ret)
|
||||
return nullptr;
|
||||
break;
|
||||
|
||||
case CanvasContextType::WebGL2:
|
||||
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
|
||||
|
||||
ret = WebGL2Context::Create();
|
||||
if (!ret)
|
||||
return nullptr;
|
||||
break;
|
||||
}
|
||||
MOZ_ASSERT(ret);
|
||||
|
||||
ret->SetCanvasElement(canvas);
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
HTMLCanvasElement::GetContext(const nsAString& aContextId,
|
||||
nsISupports** aContext)
|
||||
@ -868,14 +890,45 @@ already_AddRefed<nsISupports>
|
||||
HTMLCanvasElement::GetContext(JSContext* aCx,
|
||||
const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv)
|
||||
ErrorResult& rv)
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
CanvasContextType contextType;
|
||||
if (!GetCanvasContextType(aContextId, &contextType))
|
||||
return nullptr;
|
||||
|
||||
if (!mCurrentContext) {
|
||||
// This canvas doesn't have a context yet.
|
||||
|
||||
nsRefPtr<nsICanvasRenderingContextInternal> context;
|
||||
context = CreateContextForCanvas(contextType, this);
|
||||
if (!context)
|
||||
return nullptr;
|
||||
|
||||
// Ensure that the context participates in CC. Note that returning a
|
||||
// CC participant from QI doesn't addref.
|
||||
nsXPCOMCycleCollectionParticipant* cp = nullptr;
|
||||
CallQueryInterface(context, &cp);
|
||||
if (!cp) {
|
||||
rv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mCurrentContext = context.forget();
|
||||
mCurrentContextType = contextType;
|
||||
|
||||
rv = UpdateContext(aCx, aContextOptions);
|
||||
if (rv.Failed()) {
|
||||
rv = NS_OK; // See bug 645792
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// We already have a context of some type.
|
||||
if (contextType != mCurrentContextType)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return CanvasRenderingContextHelper::GetContext(aCx, aContextId,
|
||||
aContextOptions, aRv);
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext;
|
||||
return context.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@ -897,7 +950,7 @@ HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
|
||||
// This canvas doesn't have a context yet.
|
||||
|
||||
nsRefPtr<nsICanvasRenderingContextInternal> context;
|
||||
context = CreateContext(contextType);
|
||||
context = CreateContextForCanvas(contextType, this);
|
||||
if (!context) {
|
||||
*aContext = nullptr;
|
||||
return NS_OK;
|
||||
@ -919,6 +972,36 @@ HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
HTMLCanvasElement::UpdateContext(JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions)
|
||||
{
|
||||
if (!mCurrentContext)
|
||||
return NS_OK;
|
||||
|
||||
nsIntSize sz = GetWidthHeight();
|
||||
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
|
||||
|
||||
nsresult rv = currentContext->SetIsOpaque(HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque));
|
||||
if (NS_FAILED(rv)) {
|
||||
mCurrentContext = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = currentContext->SetContextOptions(aCx, aNewContextOptions);
|
||||
if (NS_FAILED(rv)) {
|
||||
mCurrentContext = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = currentContext->SetDimensions(sz.width, sz.height);
|
||||
if (NS_FAILED(rv)) {
|
||||
mCurrentContext = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsIntSize
|
||||
HTMLCanvasElement::GetSize()
|
||||
@ -1022,12 +1105,6 @@ HTMLCanvasElement::GetIsOpaque()
|
||||
return mCurrentContext->GetIsOpaque();
|
||||
}
|
||||
|
||||
return GetOpaqueAttr();
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLCanvasElement::GetOpaqueAttr()
|
||||
{
|
||||
return HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque);
|
||||
}
|
||||
|
||||
@ -1036,57 +1113,16 @@ HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
|
||||
CanvasLayer *aOldLayer,
|
||||
LayerManager *aManager)
|
||||
{
|
||||
// The address of sOffscreenCanvasLayerUserDataDummy is used as the user
|
||||
// data key for retained LayerManagers managed by FrameLayerBuilder.
|
||||
// We don't much care about what value in it, so just assign a dummy
|
||||
// value for it.
|
||||
static uint8_t sOffscreenCanvasLayerUserDataDummy = 0;
|
||||
if (!mCurrentContext)
|
||||
return nullptr;
|
||||
|
||||
if (mCurrentContext) {
|
||||
return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager);
|
||||
}
|
||||
|
||||
if (mOffscreenCanvas) {
|
||||
if (!mResetLayer &&
|
||||
aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) {
|
||||
nsRefPtr<CanvasLayer> ret = aOldLayer;
|
||||
return ret.forget();
|
||||
}
|
||||
|
||||
nsRefPtr<CanvasLayer> layer = aManager->CreateCanvasLayer();
|
||||
if (!layer) {
|
||||
NS_WARNING("CreateCanvasLayer failed!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LayerUserData* userData = nullptr;
|
||||
layer->SetUserData(&sOffscreenCanvasLayerUserDataDummy, userData);
|
||||
|
||||
CanvasLayer::Data data;
|
||||
data.mRenderer = GetAsyncCanvasRenderer();
|
||||
data.mSize = GetWidthHeight();
|
||||
layer->Initialize(data);
|
||||
|
||||
layer->Updated();
|
||||
return layer.forget();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager);
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLCanvasElement::ShouldForceInactiveLayer(LayerManager* aManager)
|
||||
HTMLCanvasElement::ShouldForceInactiveLayer(LayerManager *aManager)
|
||||
{
|
||||
if (mCurrentContext) {
|
||||
return mCurrentContext->ShouldForceInactiveLayer(aManager);
|
||||
}
|
||||
|
||||
if (mOffscreenCanvas) {
|
||||
// TODO: We should handle offscreen canvas case.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !mCurrentContext || mCurrentContext->ShouldForceInactiveLayer(aManager);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1201,155 +1237,5 @@ HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
|
||||
return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
|
||||
}
|
||||
|
||||
AsyncCanvasRenderer*
|
||||
HTMLCanvasElement::GetAsyncCanvasRenderer()
|
||||
{
|
||||
if (!mAsyncCanvasRenderer) {
|
||||
mAsyncCanvasRenderer = new AsyncCanvasRenderer();
|
||||
mAsyncCanvasRenderer->mHTMLCanvasElement = this;
|
||||
}
|
||||
|
||||
return mAsyncCanvasRenderer;
|
||||
}
|
||||
|
||||
layers::LayersBackend
|
||||
HTMLCanvasElement::GetCompositorBackendType() const
|
||||
{
|
||||
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc());
|
||||
if (docWidget) {
|
||||
layers::LayerManager* layerManager = docWidget->GetLayerManager();
|
||||
return layerManager->GetCompositorBackendType();
|
||||
}
|
||||
|
||||
return LayersBackend::LAYERS_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::OnVisibilityChange()
|
||||
{
|
||||
if (OwnerDoc()->Hidden()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOffscreenCanvas) {
|
||||
class Runnable final : public nsCancelableRunnable
|
||||
{
|
||||
public:
|
||||
explicit Runnable(AsyncCanvasRenderer* aRenderer)
|
||||
: mRenderer(aRenderer)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
if (mRenderer && mRenderer->mContext) {
|
||||
mRenderer->mContext->OnVisibilityChange();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Revoke()
|
||||
{
|
||||
mRenderer = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<AsyncCanvasRenderer> mRenderer;
|
||||
};
|
||||
|
||||
nsRefPtr<nsIRunnable> runnable = new Runnable(mAsyncCanvasRenderer);
|
||||
nsCOMPtr<nsIThread> activeThread = mAsyncCanvasRenderer->GetActiveThread();
|
||||
if (activeThread) {
|
||||
activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
mCurrentContext->OnVisibilityChange();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLCanvasElement::OnMemoryPressure()
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
class Runnable final : public nsCancelableRunnable
|
||||
{
|
||||
public:
|
||||
explicit Runnable(AsyncCanvasRenderer* aRenderer)
|
||||
: mRenderer(aRenderer)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
if (mRenderer && mRenderer->mContext) {
|
||||
mRenderer->mContext->OnMemoryPressure();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Revoke()
|
||||
{
|
||||
mRenderer = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<AsyncCanvasRenderer> mRenderer;
|
||||
};
|
||||
|
||||
nsRefPtr<nsIRunnable> runnable = new Runnable(mAsyncCanvasRenderer);
|
||||
nsCOMPtr<nsIThread> activeThread = mAsyncCanvasRenderer->GetActiveThread();
|
||||
if (activeThread) {
|
||||
activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentContext) {
|
||||
mCurrentContext->OnMemoryPressure();
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer)
|
||||
{
|
||||
HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (element->GetWidthHeight() == aRenderer->GetSize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
gfx::IntSize asyncCanvasSize = aRenderer->GetSize();
|
||||
|
||||
ErrorResult rv;
|
||||
element->SetUnsignedIntAttr(nsGkAtoms::width, asyncCanvasSize.width, rv);
|
||||
if (rv.Failed()) {
|
||||
NS_WARNING("Failed to set width attribute to a canvas element asynchronously.");
|
||||
}
|
||||
|
||||
element->SetUnsignedIntAttr(nsGkAtoms::height, asyncCanvasSize.height, rv);
|
||||
if (rv.Failed()) {
|
||||
NS_WARNING("Failed to set height attribute to a canvas element asynchronously.");
|
||||
}
|
||||
|
||||
element->mResetLayer = true;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer)
|
||||
{
|
||||
HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
element->InvalidateCanvasContent(nullptr);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
@ -8,27 +8,20 @@
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsIDOMHTMLCanvasElement.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsSize.h"
|
||||
#include "nsError.h"
|
||||
|
||||
#include "mozilla/dom/CanvasRenderingContextHelper.h"
|
||||
#include "mozilla/gfx/Rect.h"
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
|
||||
class nsICanvasRenderingContextInternal;
|
||||
class nsITimerCallback;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class WebGLContext;
|
||||
|
||||
namespace layers {
|
||||
class AsyncCanvasRenderer;
|
||||
class CanvasLayer;
|
||||
class Image;
|
||||
class LayerManager;
|
||||
@ -42,33 +35,14 @@ class CanvasCaptureMediaStream;
|
||||
class File;
|
||||
class FileCallback;
|
||||
class HTMLCanvasPrintState;
|
||||
class OffscreenCanvas;
|
||||
class PrintCallback;
|
||||
class RequestedFrameRefreshObserver;
|
||||
|
||||
// Listen visibilitychange and memory-pressure event and inform
|
||||
// context when event is fired.
|
||||
class HTMLCanvasElementObserver final : public nsIObserver
|
||||
, public nsIDOMEventListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
NS_DECL_NSIDOMEVENTLISTENER
|
||||
|
||||
explicit HTMLCanvasElementObserver(HTMLCanvasElement* aElement);
|
||||
void Destroy();
|
||||
|
||||
void RegisterVisibilityChangeEvent();
|
||||
void UnregisterVisibilityChangeEvent();
|
||||
|
||||
void RegisterMemoryPressureEvent();
|
||||
void UnregisterMemoryPressureEvent();
|
||||
|
||||
private:
|
||||
~HTMLCanvasElementObserver();
|
||||
|
||||
HTMLCanvasElement* mElement;
|
||||
enum class CanvasContextType : uint8_t {
|
||||
NoContext,
|
||||
Canvas2D,
|
||||
WebGL1,
|
||||
WebGL2
|
||||
};
|
||||
|
||||
/*
|
||||
@ -110,15 +84,13 @@ protected:
|
||||
};
|
||||
|
||||
class HTMLCanvasElement final : public nsGenericHTMLElement,
|
||||
public nsIDOMHTMLCanvasElement,
|
||||
public CanvasRenderingContextHelper
|
||||
public nsIDOMHTMLCanvasElement
|
||||
{
|
||||
enum {
|
||||
DEFAULT_CANVAS_WIDTH = 300,
|
||||
DEFAULT_CANVAS_HEIGHT = 150
|
||||
};
|
||||
|
||||
typedef layers::AsyncCanvasRenderer AsyncCanvasRenderer;
|
||||
typedef layers::CanvasLayer CanvasLayer;
|
||||
typedef layers::LayerManager LayerManager;
|
||||
|
||||
@ -144,11 +116,6 @@ public:
|
||||
}
|
||||
void SetHeight(uint32_t aHeight, ErrorResult& aRv)
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
SetUnsignedIntAttr(nsGkAtoms::height, aHeight, aRv);
|
||||
}
|
||||
uint32_t Width()
|
||||
@ -157,45 +124,30 @@ public:
|
||||
}
|
||||
void SetWidth(uint32_t aWidth, ErrorResult& aRv)
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
SetUnsignedIntAttr(nsGkAtoms::width, aWidth, aRv);
|
||||
}
|
||||
|
||||
virtual already_AddRefed<nsISupports>
|
||||
already_AddRefed<nsISupports>
|
||||
GetContext(JSContext* aCx, const nsAString& aContextId,
|
||||
JS::Handle<JS::Value> aContextOptions,
|
||||
ErrorResult& aRv) override;
|
||||
|
||||
ErrorResult& aRv);
|
||||
void ToDataURL(JSContext* aCx, const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
nsAString& aDataURL, ErrorResult& aRv)
|
||||
{
|
||||
aRv = ToDataURL(aType, aParams, aCx, aDataURL);
|
||||
}
|
||||
|
||||
void ToBlob(JSContext* aCx,
|
||||
FileCallback& aCallback,
|
||||
const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv);
|
||||
|
||||
OffscreenCanvas* TransferControlToOffscreen(ErrorResult& aRv);
|
||||
|
||||
bool MozOpaque() const
|
||||
{
|
||||
return GetBoolAttr(nsGkAtoms::moz_opaque);
|
||||
}
|
||||
void SetMozOpaque(bool aValue, ErrorResult& aRv)
|
||||
{
|
||||
if (mOffscreenCanvas) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
SetHTMLBoolAttr(nsGkAtoms::moz_opaque, aValue, aRv);
|
||||
}
|
||||
already_AddRefed<File> MozGetAsFile(const nsAString& aName,
|
||||
@ -252,7 +204,6 @@ public:
|
||||
* across its entire area.
|
||||
*/
|
||||
bool GetIsOpaque();
|
||||
virtual bool GetOpaqueAttr() override;
|
||||
|
||||
virtual already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
|
||||
|
||||
@ -331,25 +282,19 @@ public:
|
||||
|
||||
nsresult GetContext(const nsAString& aContextId, nsISupports** aContext);
|
||||
|
||||
layers::LayersBackend GetCompositorBackendType() const;
|
||||
|
||||
void OnVisibilityChange();
|
||||
|
||||
void OnMemoryPressure();
|
||||
|
||||
static void SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
|
||||
static void InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
|
||||
|
||||
protected:
|
||||
virtual ~HTMLCanvasElement();
|
||||
|
||||
virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
virtual nsIntSize GetWidthHeight() override;
|
||||
|
||||
virtual already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType) override;
|
||||
nsIntSize GetWidthHeight();
|
||||
|
||||
nsresult UpdateContext(JSContext* aCx, JS::Handle<JS::Value> options);
|
||||
nsresult ParseParams(JSContext* aCx,
|
||||
const nsAString& aType,
|
||||
const JS::Value& aEncoderOptions,
|
||||
nsAString& aParams,
|
||||
bool* usingCustomParseOptions);
|
||||
nsresult ExtractData(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
nsIInputStream** aStream);
|
||||
@ -362,17 +307,13 @@ protected:
|
||||
nsISupports** aResult);
|
||||
void CallPrintCallback();
|
||||
|
||||
AsyncCanvasRenderer* GetAsyncCanvasRenderer();
|
||||
|
||||
bool mResetLayer;
|
||||
CanvasContextType mCurrentContextType;
|
||||
nsRefPtr<HTMLCanvasElement> mOriginalCanvas;
|
||||
nsRefPtr<PrintCallback> mPrintCallback;
|
||||
nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
|
||||
nsRefPtr<HTMLCanvasPrintState> mPrintState;
|
||||
nsTArray<WeakPtr<FrameCaptureListener>> mRequestedFrameListeners;
|
||||
nsRefPtr<RequestedFrameRefreshObserver> mRequestedFrameRefreshObserver;
|
||||
nsRefPtr<AsyncCanvasRenderer> mAsyncCanvasRenderer;
|
||||
nsRefPtr<OffscreenCanvas> mOffscreenCanvas;
|
||||
nsRefPtr<HTMLCanvasElementObserver> mContextObserver;
|
||||
|
||||
public:
|
||||
// Record whether this canvas should be write-only or not.
|
||||
|
@ -28,9 +28,9 @@ SetUpSandboxEnvironment()
|
||||
"SetUpSandboxEnvironment relies on nsDirectoryService being initialized");
|
||||
|
||||
// A low integrity temp only currently makes sense for Vista or Later and
|
||||
// sandbox pref level 1.
|
||||
// sandbox pref level >= 1.
|
||||
if (!IsVistaOrLater() ||
|
||||
Preferences::GetInt("security.sandbox.content.level") != 1) {
|
||||
Preferences::GetInt("security.sandbox.content.level") < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -125,9 +125,6 @@ public:
|
||||
// Set by Reader if the current audio track can be offloaded
|
||||
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) {}
|
||||
|
||||
// Called by Decoder/State machine to check audio offload condtions are met
|
||||
virtual bool CheckDecoderCanOffloadAudio() { return false; }
|
||||
|
||||
// Called from HTMLMediaElement when owner document activity changes
|
||||
virtual void SetElementVisibility(bool aIsVisible) {}
|
||||
|
||||
|
@ -256,7 +256,6 @@ MediaDecoderReader::AsyncReadMetadata()
|
||||
typedef ReadMetadataFailureReason Reason;
|
||||
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
|
||||
DECODER_LOG("MediaDecoderReader::AsyncReadMetadata");
|
||||
|
||||
// Attempt to read the metadata.
|
||||
|
@ -223,6 +223,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
||||
mSentPlaybackEndedEvent(false),
|
||||
mStreamSink(new DecodedStream(mTaskQueue, mAudioQueue, mVideoQueue)),
|
||||
mResource(aDecoder->GetResource()),
|
||||
mAudioOffloading(false),
|
||||
mBuffered(mTaskQueue, TimeIntervals(),
|
||||
"MediaDecoderStateMachine::mBuffered (Mirror)"),
|
||||
mEstimatedDuration(mTaskQueue, NullableTimeUnit(),
|
||||
@ -1088,15 +1089,13 @@ void MediaDecoderStateMachine::MaybeStartPlayback()
|
||||
}
|
||||
|
||||
bool playStatePermits = mPlayState == MediaDecoder::PLAY_STATE_PLAYING;
|
||||
if (!playStatePermits || mIsAudioPrerolling || mIsVideoPrerolling) {
|
||||
if (!playStatePermits || mIsAudioPrerolling ||
|
||||
mIsVideoPrerolling || mAudioOffloading) {
|
||||
DECODER_LOG("Not starting playback [playStatePermits: %d, "
|
||||
"mIsAudioPrerolling: %d, mIsVideoPrerolling: %d]",
|
||||
(int) playStatePermits, (int) mIsAudioPrerolling, (int) mIsVideoPrerolling);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDecoder->CheckDecoderCanOffloadAudio()) {
|
||||
DECODER_LOG("Offloading playback");
|
||||
"mIsAudioPrerolling: %d, mIsVideoPrerolling: %d, "
|
||||
"mAudioOffloading: %d]",
|
||||
(int)playStatePermits, (int)mIsAudioPrerolling,
|
||||
(int)mIsVideoPrerolling, (int)mAudioOffloading);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -205,6 +205,19 @@ public:
|
||||
OwnerThread()->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
void DispatchAudioOffloading(bool aAudioOffloading)
|
||||
{
|
||||
nsRefPtr<MediaDecoderStateMachine> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
|
||||
if (self->mAudioOffloading != aAudioOffloading) {
|
||||
self->mAudioOffloading = aAudioOffloading;
|
||||
self->ScheduleStateMachine();
|
||||
}
|
||||
});
|
||||
OwnerThread()->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
// Drop reference to decoder. Only called during shutdown dance.
|
||||
void BreakCycles() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
@ -1242,6 +1255,10 @@ private:
|
||||
MediaEventListener mAudioQueueListener;
|
||||
MediaEventListener mVideoQueueListener;
|
||||
|
||||
// True if audio is offloading.
|
||||
// Playback will not start when audio is offloading.
|
||||
bool mAudioOffloading;
|
||||
|
||||
#ifdef MOZ_EME
|
||||
void OnCDMProxyReady(nsRefPtr<CDMProxy> aProxy);
|
||||
void OnCDMProxyNotReady();
|
||||
|
@ -1497,7 +1497,6 @@ MediaStreamGraphImpl::RunInStableState(bool aSourceIsMSG)
|
||||
mLifecycleState >= LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN;
|
||||
#endif
|
||||
|
||||
TaskDispatcher& tailDispatcher = AbstractThread::MainThread()->TailDispatcher();
|
||||
for (uint32_t i = 0; i < runnables.Length(); ++i) {
|
||||
runnables[i]->Run();
|
||||
// "Direct" tail dispatcher are supposed to run immediately following the
|
||||
@ -1509,7 +1508,7 @@ MediaStreamGraphImpl::RunInStableState(bool aSourceIsMSG)
|
||||
// and we need to make sure that the watcher responding to "stream available"
|
||||
// has a chance to run before the second notification starts tearing things
|
||||
// down.
|
||||
tailDispatcher.DrainDirectTasks();
|
||||
AbstractThread::MainThread()->TailDispatcher().DrainDirectTasks();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,14 +4,16 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "VP8TrackEncoder.h"
|
||||
#include "vpx/vp8cx.h"
|
||||
#include "vpx/vpx_encoder.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "LayersLogging.h"
|
||||
#include "libyuv.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "prsystem.h"
|
||||
#include "VideoSegment.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "prsystem.h"
|
||||
#include "vpx/vp8cx.h"
|
||||
#include "vpx/vpx_encoder.h"
|
||||
#include "WebMWriter.h"
|
||||
#include "libyuv.h"
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -23,6 +25,7 @@ PRLogModuleInfo* gVP8TrackEncoderLog;
|
||||
#define DEFAULT_BITRATE_BPS 2500000
|
||||
#define DEFAULT_ENCODE_FRAMERATE 30
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::layers;
|
||||
|
||||
VP8TrackEncoder::VP8TrackEncoder()
|
||||
@ -259,91 +262,179 @@ nsresult VP8TrackEncoder::PrepareRawFrame(VideoChunk &aChunk)
|
||||
img = aChunk.mFrame.GetImage();
|
||||
}
|
||||
|
||||
if (img->GetSize() != IntSize(mFrameWidth, mFrameHeight)) {
|
||||
VP8LOG("Dynamic resolution changes (was %dx%d, now %dx%d) are unsupported\n",
|
||||
mFrameWidth, mFrameHeight, img->GetSize().width, img->GetSize().height);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
ImageFormat format = img->GetFormat();
|
||||
if (format != ImageFormat::PLANAR_YCBCR) {
|
||||
VP8LOG("Unsupported video format\n");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (format == ImageFormat::PLANAR_YCBCR) {
|
||||
PlanarYCbCrImage* yuv = static_cast<PlanarYCbCrImage *>(img.get());
|
||||
|
||||
// Cast away constness b/c some of the accessors are non-const
|
||||
PlanarYCbCrImage* yuv =
|
||||
const_cast<PlanarYCbCrImage *>(static_cast<const PlanarYCbCrImage *>(img.get()));
|
||||
// Big-time assumption here that this is all contiguous data coming
|
||||
// from getUserMedia or other sources.
|
||||
MOZ_ASSERT(yuv);
|
||||
if (!yuv->IsValid()) {
|
||||
NS_WARNING("PlanarYCbCrImage is not valid");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
const PlanarYCbCrImage::Data *data = yuv->GetData();
|
||||
|
||||
if (isYUV420(data) && !data->mCbSkip) { // 420 planar
|
||||
mVPXImageWrapper->planes[VPX_PLANE_Y] = data->mYChannel;
|
||||
mVPXImageWrapper->planes[VPX_PLANE_U] = data->mCbChannel;
|
||||
mVPXImageWrapper->planes[VPX_PLANE_V] = data->mCrChannel;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_Y] = data->mYStride;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_U] = data->mCbCrStride;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_V] = data->mCbCrStride;
|
||||
} else {
|
||||
uint32_t yPlaneSize = mFrameWidth * mFrameHeight;
|
||||
uint32_t halfWidth = (mFrameWidth + 1) / 2;
|
||||
uint32_t halfHeight = (mFrameHeight + 1) / 2;
|
||||
uint32_t uvPlaneSize = halfWidth * halfHeight;
|
||||
if (mI420Frame.IsEmpty()) {
|
||||
mI420Frame.SetLength(yPlaneSize + uvPlaneSize * 2);
|
||||
MOZ_RELEASE_ASSERT(yuv);
|
||||
if (!yuv->IsValid()) {
|
||||
NS_WARNING("PlanarYCbCrImage is not valid");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
const PlanarYCbCrImage::Data *data = yuv->GetData();
|
||||
|
||||
MOZ_ASSERT(mI420Frame.Length() >= (yPlaneSize + uvPlaneSize * 2));
|
||||
uint8_t *y = mI420Frame.Elements();
|
||||
uint8_t *cb = mI420Frame.Elements() + yPlaneSize;
|
||||
uint8_t *cr = mI420Frame.Elements() + yPlaneSize + uvPlaneSize;
|
||||
if (isYUV420(data) && !data->mCbSkip) {
|
||||
// 420 planar, no need for conversions
|
||||
mVPXImageWrapper->planes[VPX_PLANE_Y] = data->mYChannel;
|
||||
mVPXImageWrapper->planes[VPX_PLANE_U] = data->mCbChannel;
|
||||
mVPXImageWrapper->planes[VPX_PLANE_V] = data->mCrChannel;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_Y] = data->mYStride;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_U] = data->mCbCrStride;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_V] = data->mCbCrStride;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Not 420 planar, have to convert
|
||||
uint32_t yPlaneSize = mFrameWidth * mFrameHeight;
|
||||
uint32_t halfWidth = (mFrameWidth + 1) / 2;
|
||||
uint32_t halfHeight = (mFrameHeight + 1) / 2;
|
||||
uint32_t uvPlaneSize = halfWidth * halfHeight;
|
||||
|
||||
if (mI420Frame.IsEmpty()) {
|
||||
mI420Frame.SetLength(yPlaneSize + uvPlaneSize * 2);
|
||||
}
|
||||
|
||||
uint8_t *y = mI420Frame.Elements();
|
||||
uint8_t *cb = mI420Frame.Elements() + yPlaneSize;
|
||||
uint8_t *cr = mI420Frame.Elements() + yPlaneSize + uvPlaneSize;
|
||||
|
||||
if (format == ImageFormat::PLANAR_YCBCR) {
|
||||
PlanarYCbCrImage* yuv = static_cast<PlanarYCbCrImage *>(img.get());
|
||||
|
||||
MOZ_RELEASE_ASSERT(yuv);
|
||||
if (!yuv->IsValid()) {
|
||||
NS_WARNING("PlanarYCbCrImage is not valid");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
const PlanarYCbCrImage::Data *data = yuv->GetData();
|
||||
|
||||
int rv;
|
||||
std::string yuvFormat;
|
||||
if (isYUV420(data) && data->mCbSkip) {
|
||||
// If mCbSkip is set, we assume it's nv12 or nv21.
|
||||
if (data->mCbChannel < data->mCrChannel) { // nv12
|
||||
libyuv::NV12ToI420(data->mYChannel, data->mYStride,
|
||||
data->mCbChannel, data->mCbCrStride,
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
rv = libyuv::NV12ToI420(data->mYChannel, data->mYStride,
|
||||
data->mCbChannel, data->mCbCrStride,
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
yuvFormat = "NV12";
|
||||
} else { // nv21
|
||||
libyuv::NV21ToI420(data->mYChannel, data->mYStride,
|
||||
data->mCrChannel, data->mCbCrStride,
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
rv = libyuv::NV21ToI420(data->mYChannel, data->mYStride,
|
||||
data->mCrChannel, data->mCbCrStride,
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
yuvFormat = "NV21";
|
||||
}
|
||||
} else if (isYUV444(data) && !data->mCbSkip) {
|
||||
libyuv::I444ToI420(data->mYChannel, data->mYStride,
|
||||
data->mCbChannel, data->mCbCrStride,
|
||||
data->mCrChannel, data->mCbCrStride,
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
rv = libyuv::I444ToI420(data->mYChannel, data->mYStride,
|
||||
data->mCbChannel, data->mCbCrStride,
|
||||
data->mCrChannel, data->mCbCrStride,
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
yuvFormat = "I444";
|
||||
} else if (isYUV422(data) && !data->mCbSkip) {
|
||||
libyuv::I422ToI420(data->mYChannel, data->mYStride,
|
||||
data->mCbChannel, data->mCbCrStride,
|
||||
data->mCrChannel, data->mCbCrStride,
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
rv = libyuv::I422ToI420(data->mYChannel, data->mYStride,
|
||||
data->mCbChannel, data->mCbCrStride,
|
||||
data->mCrChannel, data->mCbCrStride,
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
yuvFormat = "I422";
|
||||
} else {
|
||||
VP8LOG("Unsupported planar format\n");
|
||||
NS_ASSERTION(false, "Unsupported planar format");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
mVPXImageWrapper->planes[VPX_PLANE_Y] = y;
|
||||
mVPXImageWrapper->planes[VPX_PLANE_U] = cb;
|
||||
mVPXImageWrapper->planes[VPX_PLANE_V] = cr;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_Y] = mFrameWidth;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_U] = halfWidth;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_V] = halfWidth;
|
||||
if (rv != 0) {
|
||||
VP8LOG("Converting an %s frame to I420 failed\n", yuvFormat.c_str());
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
VP8LOG("Converted an %s frame to I420\n");
|
||||
} else {
|
||||
// Not YCbCr at all. Try to get access to the raw data and convert.
|
||||
|
||||
RefPtr<SourceSurface> surf = img->GetAsSourceSurface();
|
||||
if (!surf) {
|
||||
VP8LOG("Getting surface from %s image failed\n", Stringify(format).c_str());
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<DataSourceSurface> data = surf->GetDataSurface();
|
||||
if (!data) {
|
||||
VP8LOG("Getting data surface from %s image with %s (%s) surface failed\n",
|
||||
Stringify(format).c_str(), Stringify(surf->GetType()).c_str(),
|
||||
Stringify(surf->GetFormat()).c_str());
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ);
|
||||
if (!map.IsMapped()) {
|
||||
VP8LOG("Reading DataSourceSurface from %s image with %s (%s) surface failed\n",
|
||||
Stringify(format).c_str(), Stringify(surf->GetType()).c_str(),
|
||||
Stringify(surf->GetFormat()).c_str());
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
int rv;
|
||||
switch (surf->GetFormat()) {
|
||||
case SurfaceFormat::B8G8R8A8:
|
||||
case SurfaceFormat::B8G8R8X8:
|
||||
rv = libyuv::ARGBToI420(static_cast<uint8*>(map.GetData()),
|
||||
map.GetStride(),
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
break;
|
||||
case SurfaceFormat::R5G6B5:
|
||||
rv = libyuv::RGB565ToI420(static_cast<uint8*>(map.GetData()),
|
||||
map.GetStride(),
|
||||
y, mFrameWidth,
|
||||
cb, halfWidth,
|
||||
cr, halfWidth,
|
||||
mFrameWidth, mFrameHeight);
|
||||
break;
|
||||
default:
|
||||
VP8LOG("Unsupported SourceSurface format %s\n",
|
||||
Stringify(surf->GetFormat()).c_str());
|
||||
NS_ASSERTION(false, "Unsupported SourceSurface format");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
if (rv != 0) {
|
||||
VP8LOG("%s to I420 conversion failed\n",
|
||||
Stringify(surf->GetFormat()).c_str());
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
VP8LOG("Converted a %s frame to I420\n",
|
||||
Stringify(surf->GetFormat()).c_str());
|
||||
}
|
||||
|
||||
mVPXImageWrapper->planes[VPX_PLANE_Y] = y;
|
||||
mVPXImageWrapper->planes[VPX_PLANE_U] = cb;
|
||||
mVPXImageWrapper->planes[VPX_PLANE_V] = cr;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_Y] = mFrameWidth;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_U] = halfWidth;
|
||||
mVPXImageWrapper->stride[VPX_PLANE_V] = halfWidth;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ private:
|
||||
// Muted frame, we only create it once.
|
||||
nsRefPtr<layers::Image> mMuteFrame;
|
||||
|
||||
// I420 frame, convert the 4:4:4, 4:2:2 to I420.
|
||||
// I420 frame, for converting to I420.
|
||||
nsTArray<uint8_t> mI420Frame;
|
||||
|
||||
/**
|
||||
|
@ -1200,12 +1200,6 @@ void GStreamerReader::Eos(GstAppSink* aSink)
|
||||
}
|
||||
mon.NotifyAll();
|
||||
}
|
||||
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
/* Potentially unblock the decode thread in ::DecodeLoop */
|
||||
mon.NotifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,6 +57,7 @@ private:
|
||||
{
|
||||
PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin());
|
||||
PlanarYCbCrData data;
|
||||
data.mPicSize = mImageSize;
|
||||
|
||||
const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
|
||||
const uint32_t halfWidth = (mImageSize.width + 1) / 2;
|
||||
@ -94,6 +95,7 @@ private:
|
||||
{
|
||||
PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin());
|
||||
PlanarYCbCrData data;
|
||||
data.mPicSize = mImageSize;
|
||||
|
||||
const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
|
||||
const uint32_t halfWidth = (mImageSize.width + 1) / 2;
|
||||
@ -130,6 +132,7 @@ private:
|
||||
{
|
||||
PlanarYCbCrImage *image = new PlanarYCbCrImage(new BufferRecycleBin());
|
||||
PlanarYCbCrData data;
|
||||
data.mPicSize = mImageSize;
|
||||
|
||||
const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
|
||||
const uint32_t halfWidth = (mImageSize.width + 1) / 2;
|
||||
|
@ -41,13 +41,38 @@ MediaOmxCommonDecoder::~MediaOmxCommonDecoder() {}
|
||||
void
|
||||
MediaOmxCommonDecoder::SetPlatformCanOffloadAudio(bool aCanOffloadAudio)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mCanOffloadAudio = aCanOffloadAudio;
|
||||
if (!aCanOffloadAudio) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop MDSM from playing to avoid startup glitch (bug 1053186).
|
||||
GetStateMachine()->DispatchAudioOffloading(true);
|
||||
|
||||
// Modify mCanOffloadAudio in the main thread.
|
||||
nsRefPtr<MediaOmxCommonDecoder> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
self->mCanOffloadAudio = true;
|
||||
});
|
||||
AbstractThread::MainThread()->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::DisableStateMachineAudioOffloading()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mCanOffloadAudio) {
|
||||
// mCanOffloadAudio is true implies we've called
|
||||
// |GetStateMachine()->DispatchAudioOffloading(true)| in
|
||||
// SetPlatformCanOffloadAudio(). We need to turn off audio offloading
|
||||
// for MDSM so it can start playback.
|
||||
GetStateMachine()->DispatchAudioOffloading(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MediaOmxCommonDecoder::CheckDecoderCanOffloadAudio()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return (mCanOffloadAudio && !mFallbackToStateMachine &&
|
||||
!mIsCaptured && mPlaybackRate == 1.0);
|
||||
}
|
||||
@ -64,10 +89,10 @@ MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
|
||||
MediaDecoder::FirstFrameLoaded(aInfo, aEventVisibility);
|
||||
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
if (!CheckDecoderCanOffloadAudio()) {
|
||||
DECODER_LOG(LogLevel::Debug, ("In %s Offload Audio check failed",
|
||||
__PRETTY_FUNCTION__));
|
||||
DisableStateMachineAudioOffloading();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -75,6 +100,7 @@ MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
mAudioOffloadPlayer = new AudioOffloadPlayer(this);
|
||||
#endif
|
||||
if (!mAudioOffloadPlayer) {
|
||||
DisableStateMachineAudioOffloading();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -85,6 +111,7 @@ MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
mFallbackToStateMachine = true;
|
||||
DECODER_LOG(LogLevel::Debug, ("In %s Unable to start offload audio %d."
|
||||
"Switching to normal mode", __PRETTY_FUNCTION__, err));
|
||||
DisableStateMachineAudioOffloading();
|
||||
return;
|
||||
}
|
||||
PauseStateMachine();
|
||||
@ -105,7 +132,6 @@ void
|
||||
MediaOmxCommonDecoder::PauseStateMachine()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
DECODER_LOG(LogLevel::Debug, ("%s", __PRETTY_FUNCTION__));
|
||||
|
||||
if (mShuttingDown) {
|
||||
@ -123,7 +149,6 @@ void
|
||||
MediaOmxCommonDecoder::ResumeStateMachine()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
DECODER_LOG(LogLevel::Debug, ("%s current time %f", __PRETTY_FUNCTION__, mLogicalPosition));
|
||||
|
||||
if (mShuttingDown) {
|
||||
@ -134,6 +159,8 @@ MediaOmxCommonDecoder::ResumeStateMachine()
|
||||
return;
|
||||
}
|
||||
|
||||
GetStateMachine()->DispatchAudioOffloading(false);
|
||||
|
||||
mFallbackToStateMachine = true;
|
||||
mAudioOffloadPlayer = nullptr;
|
||||
SeekTarget target = SeekTarget(mLogicalPosition,
|
||||
@ -231,8 +258,6 @@ MediaOmxCommonDecoder::CurrentPosition()
|
||||
if (!mAudioOffloadPlayer) {
|
||||
return MediaDecoder::CurrentPosition();
|
||||
}
|
||||
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
return mAudioOffloadPlayer->GetMediaTimeUs();
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,6 @@ public:
|
||||
virtual MediaDecoderOwner::NextFrameStatus NextFrameStatus() override;
|
||||
virtual void SetElementVisibility(bool aIsVisible) override;
|
||||
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) override;
|
||||
virtual bool CheckDecoderCanOffloadAudio() override;
|
||||
virtual void AddOutputStream(ProcessedMediaStream* aStream,
|
||||
bool aFinishWhenEnded) override;
|
||||
virtual void SetPlaybackRate(double aPlaybackRate) override;
|
||||
@ -50,6 +49,8 @@ protected:
|
||||
virtual ~MediaOmxCommonDecoder();
|
||||
void PauseStateMachine();
|
||||
void ResumeStateMachine();
|
||||
bool CheckDecoderCanOffloadAudio();
|
||||
void DisableStateMachineAudioOffloading();
|
||||
|
||||
MediaOmxCommonReader* mReader;
|
||||
|
||||
|
@ -61,7 +61,12 @@ GonkMediaDataDecoder::Init()
|
||||
nsresult
|
||||
GonkMediaDataDecoder::Shutdown()
|
||||
{
|
||||
return mManager->Shutdown();
|
||||
nsresult rv = mManager->Shutdown();
|
||||
|
||||
// Because codec allocated runnable and init promise is at reader TaskQueue,
|
||||
// so manager needs to be destroyed at reader TaskQueue to prevent racing.
|
||||
mManager = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Inserts data into the decoder's pipeline.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user