Merge m-c to b2ginbound, a=merge CLOSED TREE

This commit is contained in:
Wes Kocher 2015-10-05 13:12:03 -07:00
Родитель d5de779503 ab9156d415
Коммит d584388e6c
220 изменённых файлов: 20483 добавлений и 23248 удалений

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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 поставляемый 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 поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

15991
devtools/client/shared/vendor/react.js поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

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

До

Ширина:  |  Высота:  |  Размер: 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.

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше