зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to b2g-inbound
This commit is contained in:
Коммит
49bc66d73d
|
@ -1440,8 +1440,15 @@ HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum,
|
|||
endOffset = tempOffset;
|
||||
}
|
||||
|
||||
*aStartOffset = DOMPointToOffset(startNode, startOffset);
|
||||
*aEndOffset = DOMPointToOffset(endNode, endOffset, true);
|
||||
if (!nsContentUtils::ContentIsDescendantOf(startNode, mContent))
|
||||
*aStartOffset = 0;
|
||||
else
|
||||
*aStartOffset = DOMPointToOffset(startNode, startOffset);
|
||||
|
||||
if (!nsContentUtils::ContentIsDescendantOf(endNode, mContent))
|
||||
*aEndOffset = CharacterCount();
|
||||
else
|
||||
*aEndOffset = DOMPointToOffset(endNode, endOffset, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ this.ContentControl.prototype = {
|
|||
|
||||
// Attempt to forward move to a potential child cursor in our
|
||||
// new position.
|
||||
this.sendToChild(vc, aMessage, { action: childAction});
|
||||
this.sendToChild(vc, aMessage, { action: childAction }, true);
|
||||
}
|
||||
} else if (!this._childMessageSenders.has(aMessage.target)) {
|
||||
// We failed to move, and the message is not from a child, so forward
|
||||
|
@ -136,6 +136,8 @@ this.ContentControl.prototype = {
|
|||
}
|
||||
if (!Utils.getMessageManager(aEvent.target)) {
|
||||
aEvent.preventDefault();
|
||||
} else {
|
||||
aEvent.target.focus();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -153,6 +155,7 @@ this.ContentControl.prototype = {
|
|||
if (!forwarded) {
|
||||
this._contentScope.get().sendAsyncMessage('AccessFu:CursorCleared');
|
||||
}
|
||||
this.document.activeElement.blur();
|
||||
},
|
||||
|
||||
handleAutoMove: function cc_handleAutoMove(aMessage) {
|
||||
|
@ -248,7 +251,7 @@ this.ContentControl.prototype = {
|
|||
};
|
||||
|
||||
let vc = this.vc;
|
||||
if (!this.sendToChild(vc, aMessage)) {
|
||||
if (!this.sendToChild(vc, aMessage, null, true)) {
|
||||
let position = vc.position;
|
||||
activateAccessible(getActivatableDescendant(position) || position);
|
||||
}
|
||||
|
@ -347,12 +350,18 @@ this.ContentControl.prototype = {
|
|||
return null;
|
||||
},
|
||||
|
||||
sendToChild: function cc_sendToChild(aVirtualCursor, aMessage, aReplacer) {
|
||||
let mm = this.getChildCursor(aVirtualCursor.position);
|
||||
sendToChild: function cc_sendToChild(aVirtualCursor, aMessage, aReplacer,
|
||||
aFocus) {
|
||||
let position = aVirtualCursor.position;
|
||||
let mm = this.getChildCursor(position);
|
||||
if (!mm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aFocus) {
|
||||
position.takeFocus();
|
||||
}
|
||||
|
||||
// XXX: This is a silly way to make a deep copy
|
||||
let newJSON = JSON.parse(JSON.stringify(aMessage.json));
|
||||
newJSON.origin = 'parent';
|
||||
|
@ -432,7 +441,7 @@ this.ContentControl.prototype = {
|
|||
noOpIfOnScreen: true,
|
||||
forcePresent: true
|
||||
}
|
||||
});
|
||||
}, null, true);
|
||||
|
||||
if (!moved && !sentToChild) {
|
||||
forcePresentFunc();
|
||||
|
|
|
@ -156,7 +156,8 @@ this.EventManager.prototype = {
|
|||
let reason = event.reason;
|
||||
let oldAccessible = event.oldAccessible;
|
||||
|
||||
if (this.editState.editing) {
|
||||
if (this.editState.editing &&
|
||||
!Utils.getState(position).contains(States.FOCUSED)) {
|
||||
aEvent.accessibleDocument.takeFocus();
|
||||
}
|
||||
this.present(
|
||||
|
|
|
@ -303,6 +303,12 @@ AccessFuContentTest.prototype = {
|
|||
this.lazyCompare(android, expected.android));
|
||||
}
|
||||
|
||||
if (expected.focused) {
|
||||
var doc = currentTabDocument();
|
||||
is(doc.activeElement, doc.querySelector(expected.focused),
|
||||
'Correct element is focused');
|
||||
}
|
||||
|
||||
this.pump();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,10 +30,12 @@
|
|||
[
|
||||
// Simple traversal forward
|
||||
[ContentMessages.simpleMoveNext, {
|
||||
speak: ['Phone status bar', 'Traversal Rule test document']
|
||||
speak: ['Phone status bar', 'Traversal Rule test document'],
|
||||
focused: 'body'
|
||||
}],
|
||||
[ContentMessages.simpleMoveNext, {
|
||||
speak: ['wow', {'string': 'headingLevel', 'args': [1]} ,'such app']
|
||||
speak: ['wow', {'string': 'headingLevel', 'args': [1]} ,'such app'],
|
||||
focused: 'iframe'
|
||||
}],
|
||||
[ContentMessages.simpleMoveNext, {
|
||||
speak: ['many option', {'string': 'stateNotChecked'},
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
|
||||
function changeDOMSelection(aID, aNodeID1, aNodeOffset1,
|
||||
aNodeID2, aNodeOffset2,
|
||||
aStartOffset, aEndOffset)
|
||||
aTests)
|
||||
{
|
||||
this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
|
||||
|
||||
|
@ -130,15 +130,18 @@
|
|||
|
||||
this.finalCheck = function changeDOMSelection_finalCheck()
|
||||
{
|
||||
is(this.hyperText.selectionCount, 1,
|
||||
"setSelectionBounds: Wrong selection count for " + aID);
|
||||
var startOffset = {}, endOffset = {};
|
||||
this.hyperText.getSelectionBounds(0, startOffset, endOffset);
|
||||
for (var i = 0; i < aTests.length; i++) {
|
||||
var text = getAccessible(aTests[i][0], nsIAccessibleText);
|
||||
is(text.selectionCount, 1,
|
||||
"setSelectionBounds: Wrong selection count for " + aID);
|
||||
var startOffset = {}, endOffset = {};
|
||||
text.getSelectionBounds(0, startOffset, endOffset);
|
||||
|
||||
is(startOffset.value, aStartOffset,
|
||||
"setSelectionBounds: Wrong start offset for " + aID);
|
||||
is(endOffset.value, aEndOffset,
|
||||
"setSelectionBounds: Wrong end offset for " + aID);
|
||||
is(startOffset.value, aTests[i][1],
|
||||
"setSelectionBounds: Wrong start offset for " + aID);
|
||||
is(endOffset.value, aTests[i][2],
|
||||
"setSelectionBounds: Wrong end offset for " + aID);
|
||||
}
|
||||
}
|
||||
|
||||
this.getID = function changeDOMSelection_getID()
|
||||
|
@ -179,7 +182,10 @@
|
|||
gQueue.push(new synthFocus("textarea", onfocusEventSeq("textarea")));
|
||||
gQueue.push(new changeSelection("textarea", 1, 3));
|
||||
|
||||
gQueue.push(new changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0, 2, 2));
|
||||
gQueue.push(new changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0,
|
||||
[["c1", 2, 2]]));
|
||||
gQueue.push(new changeDOMSelection("c2", "c2", 0, "c2_div2", 1,
|
||||
[["c2", 0, 3], ["c2_div2", 0, 2]]));
|
||||
gQueue.invoke(); // Will call SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
@ -209,6 +215,7 @@
|
|||
<input id="textbox" value="hello"/>
|
||||
<textarea id="textarea">hello</textarea>
|
||||
<div id="c1">hi<span id="c1_span1"></span><span id="c1_span2"></span>hi</div>
|
||||
<div id="c2">hi<div id="c2_div2">hi</div></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#ifdef XP_WIN
|
||||
// we want a wmain entry point
|
||||
#define XRE_DONT_SUPPORT_XPSP2 // See https://bugzil.la/1023941#c32
|
||||
#include "nsWindowsWMain.cpp"
|
||||
#define snprintf _snprintf
|
||||
#define strcasecmp _stricmp
|
||||
|
|
|
@ -30,6 +30,11 @@ LOCAL_INCLUDES += [
|
|||
'/xpcom/build',
|
||||
]
|
||||
|
||||
DELAYLOAD_DLLS += [
|
||||
'mozglue.dll',
|
||||
]
|
||||
USE_STATIC_LIBS = True
|
||||
|
||||
if CONFIG['_MSC_VER']:
|
||||
# Always enter a Windows program through wmain, whether or not we're
|
||||
# a console application.
|
||||
|
@ -50,9 +55,15 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
|||
if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
|
||||
LDFLAGS += ['/HEAP:0x40000']
|
||||
|
||||
USE_LIBS += [
|
||||
'xpcomglue',
|
||||
]
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
USE_LIBS += [
|
||||
'mozglue',
|
||||
'xpcomglue_staticruntime',
|
||||
]
|
||||
else:
|
||||
USE_LIBS += [
|
||||
'xpcomglue',
|
||||
]
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
|
||||
|
|
|
@ -249,15 +249,37 @@ var tests = {
|
|||
SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
|
||||
if (topic != "provider-update")
|
||||
return;
|
||||
is(origin, addonManifest.origin, "provider updated")
|
||||
// The worker will have reloaded and the current provider instance
|
||||
// disabled, removed from the provider list. We have a reference
|
||||
// here, check it is is disabled.
|
||||
is(provider.enabled, false, "old provider instance is disabled")
|
||||
is(origin, addonManifest.origin, "provider manifest updated")
|
||||
SocialService.unregisterProviderListener(providerListener);
|
||||
Services.prefs.clearUserPref("social.whitelist");
|
||||
let provider = Social._getProviderFromOrigin(origin);
|
||||
is(provider.manifest.version, 2, "manifest version is 2");
|
||||
Social.uninstallProvider(origin, function() {
|
||||
gBrowser.removeTab(tab);
|
||||
next();
|
||||
});
|
||||
|
||||
// Get the new provider instance, fetch the manifest via workerapi
|
||||
// and validate that data as well.
|
||||
let p = Social._getProviderFromOrigin(origin);
|
||||
is(p.manifest.version, 2, "manifest version is 2");
|
||||
let port = p.getWorkerPort();
|
||||
ok(port, "got a new port");
|
||||
port.onmessage = function (e) {
|
||||
let topic = e.data.topic;
|
||||
switch (topic) {
|
||||
case "social.manifest":
|
||||
let manifest = e.data.data;
|
||||
is(manifest.version, 2, "manifest version is 2");
|
||||
port.close();
|
||||
Social.uninstallProvider(origin, function() {
|
||||
Services.prefs.clearUserPref("social.whitelist");
|
||||
gBrowser.removeTab(tab);
|
||||
next();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
port.postMessage({topic: "test-init"});
|
||||
port.postMessage({topic: "manifest-get"});
|
||||
|
||||
});
|
||||
|
||||
let port = provider.getWorkerPort();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let testPort, sidebarPort, apiPort;
|
||||
let testPort, sidebarPort, apiPort, updatingManifest=false;
|
||||
|
||||
onconnect = function(e) {
|
||||
let port = e.ports[0];
|
||||
|
@ -116,12 +116,21 @@ onconnect = function(e) {
|
|||
if (testPort)
|
||||
testPort.postMessage({topic:"got-share-data-message", result: event.data.result});
|
||||
break;
|
||||
case "manifest-get":
|
||||
apiPort.postMessage({topic: 'social.manifest-get'});
|
||||
break;
|
||||
case "worker.update":
|
||||
updatingManifest = true;
|
||||
apiPort.postMessage({topic: 'social.manifest-get'});
|
||||
break;
|
||||
case "social.manifest":
|
||||
event.data.data.version = 2;
|
||||
apiPort.postMessage({topic: 'social.manifest-set', data: event.data.data});
|
||||
if (updatingManifest) {
|
||||
updatingManifest = false;
|
||||
event.data.data.version = 2;
|
||||
apiPort.postMessage({topic: 'social.manifest-set', data: event.data.data});
|
||||
} else if (testPort) {
|
||||
testPort.postMessage({topic:"social.manifest", data: event.data.data});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1668,6 +1668,12 @@
|
|||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</xul:vbox>
|
||||
<xul:vbox pack="start">
|
||||
<xul:toolbarbutton anonid="closebutton"
|
||||
class="messageCloseButton popup-notification-closebutton tabbable close-icon"
|
||||
xbl:inherits="oncommand=closebuttoncommand"
|
||||
tooltiptext="&closeNotification.tooltip;"/>
|
||||
</xul:vbox>
|
||||
</xul:hbox>
|
||||
</content>
|
||||
<resources>
|
||||
|
|
|
@ -1171,6 +1171,7 @@ SourceScripts.prototype = {
|
|||
// both in the editor and the breakpoints pane.
|
||||
DebuggerController.Breakpoints.updatePaneBreakpoints();
|
||||
DebuggerController.Breakpoints.updateEditorBreakpoints();
|
||||
DebuggerController.HitCounts.updateEditorHitCounts();
|
||||
|
||||
// Make sure the events listeners are up to date.
|
||||
if (DebuggerView.instrumentsPaneTab == "events-tab") {
|
||||
|
@ -1223,6 +1224,7 @@ SourceScripts.prototype = {
|
|||
// both in the editor and the breakpoints pane.
|
||||
DebuggerController.Breakpoints.updatePaneBreakpoints();
|
||||
DebuggerController.Breakpoints.updateEditorBreakpoints();
|
||||
DebuggerController.HitCounts.updateEditorHitCounts();
|
||||
|
||||
// Signal that sources have been added.
|
||||
window.emit(EVENTS.SOURCES_ADDED);
|
||||
|
@ -1488,6 +1490,7 @@ Tracer.prototype = {
|
|||
let fields = [
|
||||
"name",
|
||||
"location",
|
||||
"hitCount",
|
||||
"parameterNames",
|
||||
"depth",
|
||||
"arguments",
|
||||
|
@ -1521,6 +1524,7 @@ Tracer.prototype = {
|
|||
}
|
||||
|
||||
this._trace = null;
|
||||
DebuggerController.HitCounts.clear();
|
||||
aCallback(aResponse);
|
||||
});
|
||||
},
|
||||
|
@ -1529,6 +1533,15 @@ Tracer.prototype = {
|
|||
const tracesLength = traces.length;
|
||||
let tracesToShow;
|
||||
|
||||
// Update hit counts.
|
||||
for (let t of traces) {
|
||||
if (t.type == "enteredFrame") {
|
||||
DebuggerController.HitCounts.set(t.location, t.hitCount);
|
||||
}
|
||||
}
|
||||
DebuggerController.HitCounts.updateEditorHitCounts();
|
||||
|
||||
// Limit number of traces to be shown in the log.
|
||||
if (tracesLength > TracerView.MAX_TRACES) {
|
||||
tracesToShow = traces.slice(tracesLength - TracerView.MAX_TRACES, tracesLength);
|
||||
this._stack.splice(0, this._stack.length);
|
||||
|
@ -1537,6 +1550,7 @@ Tracer.prototype = {
|
|||
tracesToShow = traces;
|
||||
}
|
||||
|
||||
// Show traces in the log.
|
||||
for (let t of tracesToShow) {
|
||||
if (t.type == "enteredFrame") {
|
||||
this._onCall(t);
|
||||
|
@ -1544,7 +1558,6 @@ Tracer.prototype = {
|
|||
this._onReturn(t);
|
||||
}
|
||||
}
|
||||
|
||||
DebuggerView.Tracer.commit();
|
||||
},
|
||||
|
||||
|
@ -2224,6 +2237,84 @@ Object.defineProperty(Breakpoints.prototype, "_addedOrDisabled", {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles Tracer's hit counts.
|
||||
*/
|
||||
function HitCounts() {
|
||||
/**
|
||||
* Storage of hit counts for every location
|
||||
* hitCount = _locations[url][line][column]
|
||||
*/
|
||||
this._hitCounts = Object.create(null);
|
||||
}
|
||||
|
||||
HitCounts.prototype = {
|
||||
set: function({url, line, column}, aHitCount) {
|
||||
if (!this._hitCounts[url]) {
|
||||
this._hitCounts[url] = Object.create(null);
|
||||
}
|
||||
if (!this._hitCounts[url][line]) {
|
||||
this._hitCounts[url][line] = Object.create(null);
|
||||
}
|
||||
this._hitCounts[url][line][column] = aHitCount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update all the hit counts in the editor view. This is invoked when the
|
||||
* selected script is changed, or when new sources are received via the
|
||||
* _onNewSource and _onSourcesAdded event listeners.
|
||||
*/
|
||||
updateEditorHitCounts: function() {
|
||||
// First, remove all hit counters.
|
||||
DebuggerView.editor.removeAllMarkers("hit-counts");
|
||||
|
||||
// Then, add new hit counts, just for the current source.
|
||||
for (let url in this._hitCounts) {
|
||||
for (let line in this._hitCounts[url]) {
|
||||
for (let column in this._hitCounts[url][line]) {
|
||||
this._updateEditorHitCount({url, line, column});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a hit counter on a certain line.
|
||||
*/
|
||||
_updateEditorHitCount: function({url, line, column}) {
|
||||
// Editor must be initialized.
|
||||
if (!DebuggerView.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to do anything if the counter's source is not being shown in the
|
||||
// editor.
|
||||
if (DebuggerView.Sources.selectedValue != url) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There might be more counters on the same line. We need to combine them
|
||||
// into one.
|
||||
let content = Object.keys(this._hitCounts[url][line])
|
||||
.sort() // Sort by key (column).
|
||||
.map(a => this._hitCounts[url][line][a]) // Extract values.
|
||||
.map(a => a + "\u00D7") // Format hit count (e.g. 146×).
|
||||
.join("|");
|
||||
|
||||
// CodeMirror's lines are indexed from 0, while traces start from 1
|
||||
DebuggerView.editor.addContentMarker(line - 1, "hit-counts", "hit-count",
|
||||
content);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all hit couters and clear the storage
|
||||
*/
|
||||
clear: function() {
|
||||
DebuggerView.editor.removeAllMarkers("hit-counts");
|
||||
this._hitCounts = Object.create(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Localization convenience methods.
|
||||
*/
|
||||
|
@ -2265,6 +2356,7 @@ DebuggerController.SourceScripts = new SourceScripts();
|
|||
DebuggerController.Breakpoints = new Breakpoints();
|
||||
DebuggerController.Breakpoints.DOM = new EventListeners();
|
||||
DebuggerController.Tracer = new Tracer();
|
||||
DebuggerController.HitCounts = new HitCounts();
|
||||
|
||||
/**
|
||||
* Export some properties to the global scope for easier access.
|
||||
|
|
|
@ -221,12 +221,17 @@ let DebuggerView = {
|
|||
extraKeys[shortcut] = () => DebuggerView.Filtering[func]();
|
||||
}
|
||||
|
||||
let gutters = ["breakpoints"];
|
||||
if (Services.prefs.getBoolPref("devtools.debugger.tracer")) {
|
||||
gutters.unshift("hit-counts");
|
||||
}
|
||||
|
||||
this.editor = new Editor({
|
||||
mode: Editor.modes.text,
|
||||
readOnly: true,
|
||||
lineNumbers: true,
|
||||
showAnnotationRuler: true,
|
||||
gutters: [ "breakpoints" ],
|
||||
gutters: gutters,
|
||||
extraKeys: extraKeys,
|
||||
contextMenu: "sourceEditorContextMenu"
|
||||
});
|
||||
|
@ -410,6 +415,7 @@ let DebuggerView = {
|
|||
// Synchronize any other components with the currently displayed source.
|
||||
DebuggerView.Sources.selectedValue = aSource.url;
|
||||
DebuggerController.Breakpoints.updateEditorBreakpoints();
|
||||
DebuggerController.HitCounts.updateEditorHitCounts();
|
||||
|
||||
histogram.add(Date.now() - startTime);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ support-files =
|
|||
code_math.map
|
||||
code_math.min.js
|
||||
code_math_bogus_map.js
|
||||
code_same-line-functions.js
|
||||
code_script-switching-01.js
|
||||
code_script-switching-02.js
|
||||
code_test-editor-mode
|
||||
|
@ -74,6 +75,7 @@ support-files =
|
|||
doc_pretty-print-on-paused.html
|
||||
doc_random-javascript.html
|
||||
doc_recursion-stack.html
|
||||
doc_same-line-functions.html
|
||||
doc_scope-variable.html
|
||||
doc_scope-variable-2.html
|
||||
doc_scope-variable-3.html
|
||||
|
@ -161,6 +163,8 @@ skip-if = true # Bug 933950 (leaky test)
|
|||
[browser_dbg_function-display-name.js]
|
||||
[browser_dbg_global-method-override.js]
|
||||
[browser_dbg_globalactor.js]
|
||||
[browser_dbg_hit-counts-01.js]
|
||||
[browser_dbg_hit-counts-02.js]
|
||||
[browser_dbg_host-layout.js]
|
||||
[browser_dbg_iframes.js]
|
||||
[browser_dbg_instruments-pane-collapse.js]
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Evaluating two functions on the same line and checking for correct hit count
|
||||
* for both of them in CodeMirror's gutter.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_same-line-functions.html";
|
||||
const CODE_URL = "code_same-line-functions.js";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gEditor;
|
||||
|
||||
function test() {
|
||||
Task.async(function* () {
|
||||
yield pushPrefs(["devtools.debugger.tracer", true]);
|
||||
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gEditor = gDebugger.DebuggerView.editor;
|
||||
|
||||
Task.async(function* () {
|
||||
yield waitForSourceShown(gPanel, CODE_URL);
|
||||
yield startTracing(gPanel);
|
||||
|
||||
clickButton();
|
||||
|
||||
yield waitForClientEvents(aPanel, "traces");
|
||||
|
||||
testHitCounts();
|
||||
|
||||
yield stopTracing(gPanel);
|
||||
yield popPrefs();
|
||||
yield closeDebuggerAndFinish(gPanel);
|
||||
})();
|
||||
});
|
||||
})().catch(e => {
|
||||
ok(false, "Got an error: " + e.message + "\n" + e.stack);
|
||||
});
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
}
|
||||
|
||||
function testHitCounts() {
|
||||
let marker = gEditor.getMarker(0, 'hit-counts');
|
||||
|
||||
is(marker.innerHTML, "1\u00D7|1\u00D7",
|
||||
"Both functions should be hit only once.");
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
gEditor = null;
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* When tracing is stopped all hit counters should be cleared.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_same-line-functions.html";
|
||||
const CODE_URL = "code_same-line-functions.js";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gEditor;
|
||||
|
||||
function test() {
|
||||
Task.async(function* () {
|
||||
yield pushPrefs(["devtools.debugger.tracer", true]);
|
||||
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gEditor = gDebugger.DebuggerView.editor;
|
||||
|
||||
Task.async(function* () {
|
||||
yield waitForSourceShown(gPanel, CODE_URL);
|
||||
yield startTracing(gPanel);
|
||||
|
||||
clickButton();
|
||||
|
||||
yield waitForClientEvents(aPanel, "traces");
|
||||
|
||||
testHitCountsBeforeStopping();
|
||||
|
||||
yield stopTracing(gPanel);
|
||||
|
||||
testHitCountsAfterStopping();
|
||||
|
||||
yield popPrefs();
|
||||
yield closeDebuggerAndFinish(gPanel);
|
||||
})();
|
||||
});
|
||||
})().catch(e => {
|
||||
ok(false, "Got an error: " + e.message + "\n" + e.stack);
|
||||
});
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
}
|
||||
|
||||
function testHitCountsBeforeStopping() {
|
||||
let marker = gEditor.getMarker(0, 'hit-counts');
|
||||
ok(marker, "A counter should exists.");
|
||||
}
|
||||
|
||||
function testHitCountsAfterStopping() {
|
||||
let marker = gEditor.getMarker(0, 'hit-counts');
|
||||
is(marker, undefined, "A counter should be cleared.");
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
gEditor = null;
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
function first() { var a = "first"; second(); function second() { var a = "second"; } }
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Debugger Tracer test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="code_same-line-functions.js"></script>
|
||||
<button onclick="first()">Click me!</button>
|
||||
</body>
|
||||
</html>
|
|
@ -926,3 +926,14 @@ function doInterrupt(aPanel) {
|
|||
return rdpInvoke(threadClient, threadClient.interrupt);
|
||||
}
|
||||
|
||||
function pushPrefs(...aPrefs) {
|
||||
let deferred = promise.defer();
|
||||
SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function popPrefs() {
|
||||
let deferred = promise.defer();
|
||||
SpecialPowers.popPrefEnv(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
|
@ -36,6 +36,7 @@ function test() {
|
|||
emptyText: "This is dummy empty text",
|
||||
highlightUpdated: true,
|
||||
removableColumns: true,
|
||||
firstColumn: "col4"
|
||||
});
|
||||
startTests();
|
||||
});
|
||||
|
@ -126,12 +127,42 @@ function populateTable() {
|
|||
function testTreeItemInsertedCorrectly() {
|
||||
is(table.tbody.children.length, 4*2 /* double because splitters */,
|
||||
"4 columns exist");
|
||||
for (let i = 0; i < 4; i++) {
|
||||
is(table.tbody.children[i*2].firstChild.children.length, 9 + 1 /* header */,
|
||||
|
||||
// Test firstColumn option and check if the nodes are inserted correctly
|
||||
is(table.tbody.children[0].firstChild.children.length, 9 + 1 /* header */,
|
||||
"Correct rows in column 4");
|
||||
is(table.tbody.children[0].firstChild.firstChild.value, "Column 4",
|
||||
"Correct column header value");
|
||||
|
||||
for (let i = 1; i < 4; i++) {
|
||||
is(table.tbody.children[i * 2].firstChild.children.length, 9 + 1 /* header */,
|
||||
"Correct rows in column " + i);
|
||||
is(table.tbody.children[i*2].firstChild.firstChild.value, "Column " + (i + 1),
|
||||
is(table.tbody.children[i * 2].firstChild.firstChild.value, "Column " + i,
|
||||
"Correct column header value");
|
||||
}
|
||||
for (let i = 1; i < 10; i++) {
|
||||
is(table.tbody.children[2].firstChild.children[i].value, "id" + i,
|
||||
"Correct value in row " + i);
|
||||
}
|
||||
|
||||
// Remove firstColumn option and reset the table
|
||||
table.clear();
|
||||
table.firstColumn = "";
|
||||
table.setColumns({
|
||||
col1: "Column 1",
|
||||
col2: "Column 2",
|
||||
col3: "Column 3",
|
||||
col4: "Column 4"
|
||||
});
|
||||
populateTable();
|
||||
|
||||
// Check if the nodes are inserted correctly without firstColumn option
|
||||
for (let i = 0; i < 4; i++) {
|
||||
is(table.tbody.children[i * 2].firstChild.children.length, 9 + 1 /* header */,
|
||||
"Correct rows in column " + i);
|
||||
is(table.tbody.children[i * 2].firstChild.firstChild.value, "Column " + (i + 1),
|
||||
"Correct column header value");
|
||||
}
|
||||
for (let i = 1; i < 10; i++) {
|
||||
is(table.tbody.firstChild.firstChild.children[i].value, "id" + i,
|
||||
"Correct value in row " + i);
|
||||
|
|
|
@ -40,6 +40,7 @@ const MAX_VISIBLE_STRING_SIZE = 100;
|
|||
* - highlightUpdated: true to highlight the changed/added row.
|
||||
* - removableColumns: Whether columns are removeable. If set to true,
|
||||
* the context menu in the headers will not appear.
|
||||
* - firstColumn: key of the first column that should appear.
|
||||
*/
|
||||
function TableWidget(node, options={}) {
|
||||
EventEmitter.decorate(this);
|
||||
|
@ -48,10 +49,11 @@ function TableWidget(node, options={}) {
|
|||
this.window = this.document.defaultView;
|
||||
this._parent = node;
|
||||
|
||||
let {initialColumns, emptyText, uniqueId, highlightUpdated, removableColumns} =
|
||||
options;
|
||||
let {initialColumns, emptyText, uniqueId, highlightUpdated, removableColumns,
|
||||
firstColumn} = options;
|
||||
this.emptyText = emptyText || "";
|
||||
this.uniqueId = uniqueId || "name";
|
||||
this.firstColumn = firstColumn || "";
|
||||
this.highlightUpdated = highlightUpdated || false;
|
||||
this.removableColumns = removableColumns || false;
|
||||
|
||||
|
@ -237,10 +239,24 @@ TableWidget.prototype = {
|
|||
sortOn = null;
|
||||
}
|
||||
|
||||
if (!(this.firstColumn in columns)) {
|
||||
this.firstColumn = null;
|
||||
}
|
||||
|
||||
if (this.firstColumn) {
|
||||
this.columns.set(this.firstColumn,
|
||||
new Column(this, this.firstColumn, columns[this.firstColumn]));
|
||||
}
|
||||
|
||||
for (let id in columns) {
|
||||
if (!sortOn) {
|
||||
sortOn = id;
|
||||
}
|
||||
|
||||
if (this.firstColumn && id == this.firstColumn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.columns.set(id, new Column(this, id, columns[id]));
|
||||
if (hiddenColumns.indexOf(id) > -1) {
|
||||
this.columns.get(id).toggleColumn();
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
width: 16px;
|
||||
}
|
||||
|
||||
.hit-counts {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.error, .breakpoint, .debugLocation, .breakpoint-debugLocation {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
|
@ -17,6 +21,17 @@
|
|||
background-size: contain;
|
||||
}
|
||||
|
||||
.hit-count {
|
||||
display: inline-block;
|
||||
height: 12px;
|
||||
border: solid rgba(0,0,0,0.2);
|
||||
border-width: 1px 1px 1px 0;
|
||||
border-radius: 0 3px 3px 0;
|
||||
padding: 0 3px;
|
||||
font-size: 10px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-image: url("chrome://browser/skin/devtools/editor-error.png");
|
||||
opacity: 0.75;
|
||||
|
|
|
@ -115,6 +115,7 @@ function hasBreakpoint(ctx, line) {
|
|||
let markers = cm.lineInfo(line).gutterMarkers;
|
||||
|
||||
return markers != null &&
|
||||
markers.breakpoints &&
|
||||
markers.breakpoints.classList.contains("breakpoint");
|
||||
}
|
||||
|
||||
|
|
|
@ -618,6 +618,32 @@ Editor.prototype = {
|
|||
cm.lineInfo(line).gutterMarkers[gutterName].classList.remove(markerClass);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a marker with a specified class and an HTML content to a line's
|
||||
* gutter. If another marker exists on that line, it is overwritten by a new
|
||||
* marker.
|
||||
*/
|
||||
addContentMarker: function (line, gutterName, markerClass, content) {
|
||||
let cm = editors.get(this);
|
||||
let info = cm.lineInfo(line);
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
let marker = cm.getWrapperElement().ownerDocument.createElement("div");
|
||||
marker.className = markerClass;
|
||||
marker.innerHTML = content;
|
||||
cm.setGutterMarker(info.line, gutterName, marker);
|
||||
},
|
||||
|
||||
/**
|
||||
* The reverse of addContentMarker. Removes any line's markers in the
|
||||
* specified gutter.
|
||||
*/
|
||||
removeContentMarker: function (line, gutterName) {
|
||||
let cm = editors.get(this);
|
||||
cm.setGutterMarker(info.line, gutterName, null);
|
||||
},
|
||||
|
||||
getMarker: function(line, gutterName) {
|
||||
let cm = editors.get(this);
|
||||
let info = cm.lineInfo(line);
|
||||
|
|
|
@ -10,8 +10,12 @@ const {Cc, Ci, Cu} = require("chrome");
|
|||
loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
|
||||
loader.lazyImporter(this, "escapeHTML", "resource:///modules/devtools/VariablesView.jsm");
|
||||
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
|
||||
loader.lazyImporter(this, "Task","resource://gre/modules/Task.jsm");
|
||||
loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
|
||||
loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
|
||||
loader.lazyImporter(this, "ObjectClient", "resource://gre/modules/devtools/dbg-client.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "TableWidget", "devtools/shared/widgets/TableWidget", true);
|
||||
|
||||
const Heritage = require("sdk/core/heritage");
|
||||
const URI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
|
||||
|
@ -81,6 +85,7 @@ const CONSOLE_API_LEVELS_TO_SEVERITIES = {
|
|||
info: "info",
|
||||
log: "log",
|
||||
trace: "log",
|
||||
table: "log",
|
||||
debug: "log",
|
||||
dir: "log",
|
||||
group: "log",
|
||||
|
@ -111,6 +116,12 @@ const RE_CLEANUP_STYLES = [
|
|||
/['"(]*(?:chrome|resource|about|app|data|https?|ftp|file):+\/*/gi,
|
||||
];
|
||||
|
||||
// Maximum number of rows to display in console.table().
|
||||
const TABLE_ROW_MAX_ITEMS = 1000;
|
||||
|
||||
// Maximum number of columns to display in console.table().
|
||||
const TABLE_COLUMN_MAX_ITEMS = 10;
|
||||
|
||||
/**
|
||||
* The ConsoleOutput object is used to manage output of messages in the Web
|
||||
* Console.
|
||||
|
@ -1616,6 +1627,344 @@ Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype,
|
|||
_renderRepeatNode: function() { },
|
||||
}); // Messages.ConsoleTrace.prototype
|
||||
|
||||
/**
|
||||
* The ConsoleTable message is used for console.table() calls.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Messages.Extended
|
||||
* @param object packet
|
||||
* The Console API call packet received from the server.
|
||||
*/
|
||||
Messages.ConsoleTable = function(packet)
|
||||
{
|
||||
let options = {
|
||||
className: "cm-s-mozilla",
|
||||
timestamp: packet.timeStamp,
|
||||
category: "webdev",
|
||||
severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
|
||||
private: packet.private,
|
||||
filterDuplicates: false,
|
||||
location: {
|
||||
url: packet.filename,
|
||||
line: packet.lineNumber,
|
||||
},
|
||||
};
|
||||
|
||||
this._populateTableData = this._populateTableData.bind(this);
|
||||
this._renderTable = this._renderTable.bind(this);
|
||||
Messages.Extended.call(this, [this._renderTable], options);
|
||||
|
||||
this._repeatID.consoleApiLevel = packet.level;
|
||||
this._arguments = packet.arguments;
|
||||
};
|
||||
|
||||
Messages.ConsoleTable.prototype = Heritage.extend(Messages.Extended.prototype,
|
||||
{
|
||||
/**
|
||||
* Holds the arguments the content script passed to the console.table()
|
||||
* method.
|
||||
*
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_arguments: null,
|
||||
|
||||
/**
|
||||
* Array of objects that holds the data to log in the table.
|
||||
*
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_data: null,
|
||||
|
||||
/**
|
||||
* Key value pair of the id and display name for the columns in the table.
|
||||
* Refer to the TableWidget API.
|
||||
*
|
||||
* @private
|
||||
* @type object
|
||||
*/
|
||||
_columns: null,
|
||||
|
||||
/**
|
||||
* A promise that resolves when the table data is ready or null if invalid
|
||||
* arguments are provided.
|
||||
*
|
||||
* @private
|
||||
* @type promise|null
|
||||
*/
|
||||
_populatePromise: null,
|
||||
|
||||
init: function()
|
||||
{
|
||||
let result = Messages.Extended.prototype.init.apply(this, arguments);
|
||||
this._data = [];
|
||||
this._columns = {};
|
||||
|
||||
this._populatePromise = this._populateTableData();
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the key value pair of the id and display name for the columns in the
|
||||
* table.
|
||||
*
|
||||
* @private
|
||||
* @param array|string columns
|
||||
* Either a string or array containing the names for the columns in
|
||||
* the output table.
|
||||
*/
|
||||
_setColumns: function(columns)
|
||||
{
|
||||
if (columns.class == "Array") {
|
||||
let items = columns.preview.items;
|
||||
|
||||
for (let item of items) {
|
||||
if (typeof item == "string") {
|
||||
this._columns[item] = item;
|
||||
}
|
||||
}
|
||||
} else if (typeof columns == "string" && columns) {
|
||||
this._columns[columns] = columns;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the table data and columns from the arguments received from the
|
||||
* server.
|
||||
*
|
||||
* @return Promise|null
|
||||
* Returns a promise that resolves when the table data is ready or
|
||||
* null if the arguments are invalid.
|
||||
*/
|
||||
_populateTableData: function()
|
||||
{
|
||||
let deferred = promise.defer();
|
||||
|
||||
if (this._arguments.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = this._arguments[0];
|
||||
if (data.class != "Array" && data.class != "Object" &&
|
||||
data.class != "Map" && data.class != "Set") {
|
||||
return;
|
||||
}
|
||||
|
||||
let hasColumnsArg = false;
|
||||
if (this._arguments.length > 1) {
|
||||
if (data.class == "Object" || data.class == "Array") {
|
||||
this._columns["_index"] = l10n.getStr("table.index");
|
||||
} else {
|
||||
this._columns["_index"] = l10n.getStr("table.iterationIndex");
|
||||
}
|
||||
|
||||
this._setColumns(this._arguments[1]);
|
||||
hasColumnsArg = true;
|
||||
}
|
||||
|
||||
if (data.class == "Object" || data.class == "Array") {
|
||||
// Get the object properties, and parse the key and value properties into
|
||||
// the table data and columns.
|
||||
this.client = new ObjectClient(this.output.owner.jsterm.hud.proxy.client,
|
||||
data);
|
||||
this.client.getPrototypeAndProperties(aResponse => {
|
||||
let {ownProperties} = aResponse;
|
||||
let rowCount = 0;
|
||||
let columnCount = 0;
|
||||
|
||||
for (let index of Object.keys(ownProperties || {})) {
|
||||
// Avoid outputting the length property if the data argument provided
|
||||
// is an array
|
||||
if (data.class == "Array" && index == "length") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasColumnsArg) {
|
||||
this._columns["_index"] = l10n.getStr("table.index");
|
||||
}
|
||||
|
||||
let property = ownProperties[index].value;
|
||||
let item = { _index: index };
|
||||
|
||||
if (property.class == "Object" || property.class == "Array") {
|
||||
let {preview} = property;
|
||||
let entries = property.class == "Object" ?
|
||||
preview.ownProperties : preview.items;
|
||||
|
||||
for (let key of Object.keys(entries)) {
|
||||
let value = property.class == "Object" ?
|
||||
preview.ownProperties[key].value : preview.items[key];
|
||||
|
||||
item[key] = this._renderValueGrip(value, { concise: true });
|
||||
|
||||
if (!hasColumnsArg && !(key in this._columns) &&
|
||||
(++columnCount <= TABLE_COLUMN_MAX_ITEMS)) {
|
||||
this._columns[key] = key;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Display the value for any non-object data input.
|
||||
item["_value"] = this._renderValueGrip(property, { concise: true });
|
||||
|
||||
if (!hasColumnsArg && !("_value" in this._columns)) {
|
||||
this._columns["_value"] = l10n.getStr("table.value");
|
||||
}
|
||||
}
|
||||
|
||||
this._data.push(item);
|
||||
|
||||
if (++rowCount == TABLE_ROW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
} else if (data.class == "Map") {
|
||||
let entries = data.preview.entries;
|
||||
|
||||
if (!hasColumnsArg) {
|
||||
this._columns["_index"] = l10n.getStr("table.iterationIndex");
|
||||
this._columns["_key"] = l10n.getStr("table.key");
|
||||
this._columns["_value"] = l10n.getStr("table.value");
|
||||
}
|
||||
|
||||
let rowCount = 0;
|
||||
for (let index of Object.keys(entries || {})) {
|
||||
let [key, value] = entries[index];
|
||||
let item = {
|
||||
_index: index,
|
||||
_key: this._renderValueGrip(key, { concise: true }),
|
||||
_value: this._renderValueGrip(value, { concise: true })
|
||||
};
|
||||
|
||||
this._data.push(item);
|
||||
|
||||
if (++rowCount == TABLE_ROW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
} else if (data.class == "Set") {
|
||||
let entries = data.preview.items;
|
||||
|
||||
if (!hasColumnsArg) {
|
||||
this._columns["_index"] = l10n.getStr("table.iterationIndex");
|
||||
this._columns["_value"] = l10n.getStr("table.value");
|
||||
}
|
||||
|
||||
let rowCount = 0;
|
||||
for (let index of Object.keys(entries || {})) {
|
||||
let value = entries[index];
|
||||
let item = {
|
||||
_index : index,
|
||||
_value: this._renderValueGrip(value, { concise: true })
|
||||
};
|
||||
|
||||
this._data.push(item);
|
||||
|
||||
if (++rowCount == TABLE_ROW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
render: function()
|
||||
{
|
||||
Messages.Extended.prototype.render.apply(this, arguments);
|
||||
this.element.setAttribute("open", true);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the table.
|
||||
*
|
||||
* @private
|
||||
* @return DOMElement
|
||||
*/
|
||||
_renderTable: function()
|
||||
{
|
||||
let cmvar = this.document.createElementNS(XHTML_NS, "span");
|
||||
cmvar.className = "cm-variable";
|
||||
cmvar.textContent = "console";
|
||||
|
||||
let cmprop = this.document.createElementNS(XHTML_NS, "span");
|
||||
cmprop.className = "cm-property";
|
||||
cmprop.textContent = "table";
|
||||
|
||||
let title = this.document.createElementNS(XHTML_NS, "span");
|
||||
title.className = "message-body devtools-monospace";
|
||||
title.appendChild(cmvar);
|
||||
title.appendChild(this.document.createTextNode("."));
|
||||
title.appendChild(cmprop);
|
||||
title.appendChild(this.document.createTextNode("():"));
|
||||
|
||||
let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
|
||||
let location = Messages.Simple.prototype._renderLocation.call(this);
|
||||
if (location) {
|
||||
location.target = "jsdebugger";
|
||||
}
|
||||
|
||||
let body = this.document.createElementNS(XHTML_NS, "span");
|
||||
body.className = "message-flex-body";
|
||||
body.appendChild(title);
|
||||
if (repeatNode) {
|
||||
body.appendChild(repeatNode);
|
||||
}
|
||||
if (location) {
|
||||
body.appendChild(location);
|
||||
}
|
||||
body.appendChild(this.document.createTextNode("\n"));
|
||||
|
||||
let result = this.document.createElementNS(XHTML_NS, "div");
|
||||
result.appendChild(body);
|
||||
|
||||
if (this._populatePromise) {
|
||||
this._populatePromise.then(() => {
|
||||
if (this._data.length > 0) {
|
||||
let widget = new Widgets.Table(this, this._data, this._columns).render();
|
||||
result.appendChild(widget.element);
|
||||
}
|
||||
|
||||
result.scrollIntoView();
|
||||
this.output.owner.emit("messages-table-rendered");
|
||||
|
||||
// Release object actors
|
||||
if (Array.isArray(this._arguments)) {
|
||||
for (let arg of this._arguments) {
|
||||
if (WebConsoleUtils.isActorGrip(arg)) {
|
||||
this.output._releaseObject(arg.actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._arguments = null;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
_renderBody: function()
|
||||
{
|
||||
let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
|
||||
body.classList.remove("devtools-monospace", "message-body");
|
||||
return body;
|
||||
},
|
||||
|
||||
// no-op for the message location and .repeats elements.
|
||||
// |this._renderTable| handles customized message output.
|
||||
_renderLocation: function() { },
|
||||
_renderRepeatNode: function() { },
|
||||
}); // Messages.ConsoleTable.prototype
|
||||
|
||||
let Widgets = {};
|
||||
|
||||
/**
|
||||
|
@ -3012,6 +3361,63 @@ Widgets.Stacktrace.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
|
|||
}); // Widgets.Stacktrace.prototype
|
||||
|
||||
|
||||
/**
|
||||
* The table widget.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Widgets.BaseWidget
|
||||
* @param object message
|
||||
* The owning message.
|
||||
* @param array data
|
||||
* Array of objects that holds the data to log in the table.
|
||||
* @param object columns
|
||||
* Object containing the key value pair of the id and display name for
|
||||
* the columns in the table.
|
||||
*/
|
||||
Widgets.Table = function(message, data, columns)
|
||||
{
|
||||
Widgets.BaseWidget.call(this, message);
|
||||
this.data = data;
|
||||
this.columns = columns;
|
||||
};
|
||||
|
||||
Widgets.Table.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
|
||||
{
|
||||
/**
|
||||
* Array of objects that holds the data to output in the table.
|
||||
* @type array
|
||||
*/
|
||||
data: null,
|
||||
|
||||
/**
|
||||
* Object containing the key value pair of the id and display name for
|
||||
* the columns in the table.
|
||||
* @type object
|
||||
*/
|
||||
columns: null,
|
||||
|
||||
render: function() {
|
||||
if (this.element) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let result = this.element = this.document.createElementNS(XHTML_NS, "div");
|
||||
result.className = "consoletable devtools-monospace";
|
||||
|
||||
this.table = new TableWidget(result, {
|
||||
initialColumns: this.columns,
|
||||
uniqueId: "_index",
|
||||
firstColumn: "_index"
|
||||
});
|
||||
|
||||
for (let row of this.data) {
|
||||
this.table.push(row);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}); // Widgets.Table.prototype
|
||||
|
||||
function gSequenceId()
|
||||
{
|
||||
return gSequenceId.n++;
|
||||
|
|
|
@ -67,6 +67,7 @@ support-files =
|
|||
test-console-extras.html
|
||||
test-console-replaced-api.html
|
||||
test-console.html
|
||||
test-console-table.html
|
||||
test-console-output-02.html
|
||||
test-console-output-03.html
|
||||
test-console-output-04.html
|
||||
|
@ -305,6 +306,7 @@ skip-if = buildapp == 'mulet'
|
|||
[browser_webconsole_output_dom_elements_03.js]
|
||||
[browser_webconsole_output_dom_elements_04.js]
|
||||
[browser_webconsole_output_events.js]
|
||||
[browser_webconsole_output_table.js]
|
||||
[browser_console_variables_view_highlighter.js]
|
||||
[browser_webconsole_start_netmon_first.js]
|
||||
[browser_webconsole_console_trace_duplicates.js]
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
/* 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/ */
|
||||
|
||||
// Tests that console.table() works as intended.
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-table.html";
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
command: "console.table(languages1)",
|
||||
data: [
|
||||
{ _index: "0", name: "\"JavaScript\"", fileExtension: "Array[1]" },
|
||||
{ _index: "1", name: "Object", fileExtension: "\".ts\"" },
|
||||
{ _index: "2", name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
|
||||
],
|
||||
columns: { _index: "(index)", name: "name", fileExtension: "fileExtension" }
|
||||
},
|
||||
{
|
||||
command: "console.table(languages1, 'name')",
|
||||
data: [
|
||||
{ _index: "0", name: "\"JavaScript\"", fileExtension: "Array[1]" },
|
||||
{ _index: "1", name: "Object", fileExtension: "\".ts\"" },
|
||||
{ _index: "2", name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
|
||||
],
|
||||
columns: { _index: "(index)", name: "name" }
|
||||
},
|
||||
{
|
||||
command: "console.table(languages1, ['name'])",
|
||||
data: [
|
||||
{ _index: "0", name: "\"JavaScript\"", fileExtension: "Array[1]" },
|
||||
{ _index: "1", name: "Object", fileExtension: "\".ts\"" },
|
||||
{ _index: "2", name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
|
||||
],
|
||||
columns: { _index: "(index)", name: "name" }
|
||||
},
|
||||
{
|
||||
command: "console.table(languages2)",
|
||||
data: [
|
||||
{ _index: "csharp", name: "\"C#\"", paradigm: "\"object-oriented\"" },
|
||||
{ _index: "fsharp", name: "\"F#\"", paradigm: "\"functional\"" }
|
||||
],
|
||||
columns: { _index: "(index)", name: "name", paradigm: "paradigm" }
|
||||
},
|
||||
{
|
||||
command: "console.table([[1, 2], [3, 4]])",
|
||||
data: [
|
||||
{ _index: "0", 0: "1", 1: "2" },
|
||||
{ _index: "1", 0: "3", 1: "4" }
|
||||
],
|
||||
columns: { _index: "(index)", 0: "0", 1: "1" }
|
||||
},
|
||||
{
|
||||
command: "console.table({a: [1, 2], b: [3, 4]})",
|
||||
data: [
|
||||
{ _index: "a", 0: "1", 1: "2" },
|
||||
{ _index: "b", 0: "3", 1: "4" }
|
||||
],
|
||||
columns: { _index: "(index)", 0: "0", 1: "1" }
|
||||
},
|
||||
{
|
||||
command: "console.table(family)",
|
||||
data: [
|
||||
{ _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
|
||||
{ _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
|
||||
{ _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
|
||||
{ _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
|
||||
],
|
||||
columns: { _index: "(index)", firstName: "firstName", lastName: "lastName", age: "age" }
|
||||
},
|
||||
{
|
||||
command: "console.table(family, [])",
|
||||
data: [
|
||||
{ _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
|
||||
{ _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
|
||||
{ _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
|
||||
{ _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
|
||||
],
|
||||
columns: { _index: "(index)" }
|
||||
},
|
||||
{
|
||||
command: "console.table(family, ['firstName', 'lastName'])",
|
||||
data: [
|
||||
{ _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
|
||||
{ _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
|
||||
{ _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
|
||||
{ _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
|
||||
],
|
||||
columns: { _index: "(index)", firstName: "firstName", lastName: "lastName" }
|
||||
},
|
||||
{
|
||||
command: "console.table(mySet)",
|
||||
data: [
|
||||
{ _index: "0", _value: "1" },
|
||||
{ _index: "1", _value: "5" },
|
||||
{ _index: "2", _value: "\"some text\"" },
|
||||
{ _index: "3", _value: "null" },
|
||||
{ _index: "4", _value: "undefined" }
|
||||
],
|
||||
columns: { _index: "(iteration index)", _value: "Values" }
|
||||
},
|
||||
{
|
||||
command: "console.table(myMap)",
|
||||
data: [
|
||||
{ _index: "0", _key: "\"a string\"", _value: "\"value associated with 'a string'\"" },
|
||||
{ _index: "1", _key: "5", _value: "\"value associated with 5\"" },
|
||||
],
|
||||
columns: { _index: "(iteration index)", _key: "Key", _value: "Values" }
|
||||
}
|
||||
];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
const {tab} = yield loadTab(TEST_URI);
|
||||
let hud = yield openConsole(tab);
|
||||
|
||||
for (let testdata of TEST_DATA) {
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
info("Executing " + testdata.command);
|
||||
|
||||
let onTableRender = once(hud.ui, "messages-table-rendered");
|
||||
hud.jsterm.execute(testdata.command);
|
||||
yield onTableRender;
|
||||
|
||||
let [result] = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: testdata.command + " output",
|
||||
consoleTable: true
|
||||
}],
|
||||
});
|
||||
|
||||
let node = [...result.matched][0];
|
||||
ok(node, "found trace log node");
|
||||
|
||||
let obj = node._messageObject;
|
||||
ok(obj, "console.trace message object");
|
||||
|
||||
ok(obj._data, "found table data object");
|
||||
|
||||
let data = obj._data.map(entries => {
|
||||
let result = {};
|
||||
|
||||
for (let key of Object.keys(entries)) {
|
||||
result[key] = entries[key] instanceof HTMLElement ?
|
||||
entries[key].textContent : entries[key];
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
is(data.toSource(), testdata.data.toSource(), "table data is correct");
|
||||
ok(obj._columns, "found table column object");
|
||||
is(obj._columns.toSource(), testdata.columns.toSource(), "table column is correct");
|
||||
}
|
||||
});
|
|
@ -912,6 +912,8 @@ function openDebugger(aOptions = {})
|
|||
* message.
|
||||
* - consoleGroup: boolean, set to |true| to match a console.group()
|
||||
* message.
|
||||
* - consoleTable: boolean, set to |true| to match a console.table()
|
||||
* message.
|
||||
* - longString: boolean, set to |true} to match long strings in the
|
||||
* message.
|
||||
* - collapsible: boolean, set to |true| to match messages that can
|
||||
|
@ -970,6 +972,22 @@ function waitForMessages(aOptions)
|
|||
return result;
|
||||
}
|
||||
|
||||
function checkConsoleTable(aRule, aElement)
|
||||
{
|
||||
let elemText = aElement.textContent;
|
||||
let table = aRule.consoleTable;
|
||||
|
||||
if (!checkText("console.table():", elemText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aRule.category = CATEGORY_WEBDEV;
|
||||
aRule.severity = SEVERITY_LOG;
|
||||
aRule.type = Messages.ConsoleTable;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkConsoleTrace(aRule, aElement)
|
||||
{
|
||||
let elemText = aElement.textContent;
|
||||
|
@ -1146,6 +1164,10 @@ function waitForMessages(aOptions)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (aRule.consoleTable && !checkConsoleTable(aRule, aElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aRule.consoleTrace && !checkConsoleTrace(aRule, aElement)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1593,3 +1615,34 @@ function checkOutputForInputs(hud, inputTests)
|
|||
|
||||
return Task.spawn(runner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for eventName on target.
|
||||
* @param {Object} target An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function once(target, eventName, useCapture=false) {
|
||||
info("Waiting for event: '" + eventName + "' on " + target + ".");
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
||||
for (let [add, remove] of [
|
||||
["addEventListener", "removeEventListener"],
|
||||
["addListener", "removeListener"],
|
||||
["on", "off"]
|
||||
]) {
|
||||
if ((add in target) && (remove in target)) {
|
||||
target[add](eventName, function onEvent(...aArgs) {
|
||||
target[remove](eventName, onEvent, useCapture);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
}, useCapture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
console.log("start");
|
||||
console.clear()
|
||||
console.dirxml()
|
||||
console.profile()
|
||||
console.profileEnd()
|
||||
console.table()
|
||||
console.log("end");
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html dir="ltr" lang="en">
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<!--
|
||||
- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<title>Test for Bug 899753 - console.table support</title>
|
||||
<script>
|
||||
var languages1 = [
|
||||
{ name: "JavaScript", fileExtension: [".js"] },
|
||||
{ name: { a: "TypeScript" }, fileExtension: ".ts" },
|
||||
{ name: "CoffeeScript", fileExtension: ".coffee" }
|
||||
];
|
||||
|
||||
var languages2 = {
|
||||
csharp: { name: "C#", paradigm: "object-oriented" },
|
||||
fsharp: { name: "F#", paradigm: "functional" }
|
||||
};
|
||||
|
||||
function Person(firstName, lastName, age)
|
||||
{
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
var family = {};
|
||||
family.mother = new Person("Susan", "Doyle", 32);
|
||||
family.father = new Person("John", "Doyle", 33);
|
||||
family.daughter = new Person("Lily", "Doyle", 5);
|
||||
family.son = new Person("Mike", "Doyle", 8);
|
||||
|
||||
var myMap = new Map();
|
||||
|
||||
myMap.set("a string", "value associated with 'a string'");
|
||||
myMap.set(5, "value associated with 5");
|
||||
|
||||
var mySet = new Set();
|
||||
|
||||
mySet.add(1);
|
||||
mySet.add(5);
|
||||
mySet.add("some text");
|
||||
mySet.add(null);
|
||||
mySet.add(undefined);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Hello world!</p>
|
||||
</body>
|
||||
</html>
|
|
@ -123,6 +123,7 @@ const LEVELS = {
|
|||
info: SEVERITY_INFO,
|
||||
log: SEVERITY_LOG,
|
||||
trace: SEVERITY_LOG,
|
||||
table: SEVERITY_LOG,
|
||||
debug: SEVERITY_LOG,
|
||||
dir: SEVERITY_LOG,
|
||||
group: SEVERITY_LOG,
|
||||
|
@ -1212,6 +1213,11 @@ WebConsoleFrame.prototype = {
|
|||
node = msg.init(this.output).render().element;
|
||||
break;
|
||||
}
|
||||
case "table": {
|
||||
let msg = new Messages.ConsoleTable(aMessage);
|
||||
node = msg.init(this.output).render().element;
|
||||
break;
|
||||
}
|
||||
case "trace": {
|
||||
let msg = new Messages.ConsoleTrace(aMessage);
|
||||
node = msg.init(this.output).render().element;
|
||||
|
|
|
@ -250,3 +250,10 @@ messageToggleDetails=Show/hide message details.
|
|||
# example: 1 empty slot
|
||||
# example: 5 empty slots
|
||||
emptySlotLabel=#1 empty slot;#1 empty slots
|
||||
|
||||
# LOCALIZATION NOTE (table.index, table.iterationIndex, table.key, table.value):
|
||||
# the column header displayed in the console table widget.
|
||||
table.index=(index)
|
||||
table.iterationIndex=(iteration index)
|
||||
table.key=Key
|
||||
table.value=Values
|
||||
|
|
|
@ -2664,6 +2664,7 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
|
|||
|
||||
#sidebar,
|
||||
sidebarheader {
|
||||
-moz-appearance: -moz-mac-vibrancy-light;
|
||||
background-color: #e2e7ed;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,10 @@ a {
|
|||
margin: 3px;
|
||||
}
|
||||
|
||||
.message-body-wrapper .table-widget-body {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* The red bubble that shows the number of times a message is repeated */
|
||||
.message-repeats {
|
||||
-moz-user-select: none;
|
||||
|
@ -223,6 +227,13 @@ a {
|
|||
color: hsl(24,85%,39%);
|
||||
}
|
||||
|
||||
.theme-selected .console-string,
|
||||
.theme-selected .cm-number,
|
||||
.theme-selected .cm-variable,
|
||||
.theme-selected .kind-ArrayLike {
|
||||
color: #f5f7fa !important; /* Selection Text Color */
|
||||
}
|
||||
|
||||
.message[category=network] > .indent {
|
||||
-moz-border-end: solid #000 6px;
|
||||
}
|
||||
|
@ -429,6 +440,10 @@ a {
|
|||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.consoletable {
|
||||
margin: 5px 0 0 0;
|
||||
}
|
||||
|
||||
.theme-light .message[severity=error] .stacktrace {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,20 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
ExplicitChildIterator(const ExplicitChildIterator& aOther)
|
||||
: mParent(aOther.mParent), mChild(aOther.mChild),
|
||||
mDefaultChild(aOther.mDefaultChild),
|
||||
mShadowIterator(aOther.mShadowIterator ?
|
||||
new ExplicitChildIterator(*aOther.mShadowIterator) :
|
||||
nullptr),
|
||||
mIndexInInserted(aOther.mIndexInInserted), mIsFirst(aOther.mIsFirst) {}
|
||||
|
||||
ExplicitChildIterator(ExplicitChildIterator&& aOther)
|
||||
: mParent(aOther.mParent), mChild(aOther.mChild),
|
||||
mDefaultChild(aOther.mDefaultChild),
|
||||
mShadowIterator(Move(aOther.mShadowIterator)),
|
||||
mIndexInInserted(aOther.mIndexInInserted), mIsFirst(aOther.mIsFirst) {}
|
||||
|
||||
nsIContent* GetNextChild();
|
||||
|
||||
// Looks for aChildToFind respecting insertion points until aChildToFind
|
||||
|
@ -118,6 +132,12 @@ public:
|
|||
Init(false);
|
||||
}
|
||||
|
||||
FlattenedChildIterator(FlattenedChildIterator&& aOther)
|
||||
: ExplicitChildIterator(Move(aOther)), mXBLInvolved(aOther.mXBLInvolved) {}
|
||||
|
||||
FlattenedChildIterator(const FlattenedChildIterator& aOther)
|
||||
: ExplicitChildIterator(aOther), mXBLInvolved(aOther.mXBLInvolved) {}
|
||||
|
||||
bool XBLInvolved() { return mXBLInvolved; }
|
||||
|
||||
protected:
|
||||
|
@ -126,7 +146,7 @@ protected:
|
|||
* doesn't want to consider XBL.
|
||||
*/
|
||||
FlattenedChildIterator(nsIContent* aParent, bool aIgnoreXBL)
|
||||
: ExplicitChildIterator(aParent), mXBLInvolved(false)
|
||||
: ExplicitChildIterator(aParent), mXBLInvolved(false)
|
||||
{
|
||||
Init(aIgnoreXBL);
|
||||
}
|
||||
|
@ -152,6 +172,16 @@ public:
|
|||
mOriginalContent(aNode), mFlags(aFlags),
|
||||
mPhase(eNeedBeforeKid) {}
|
||||
|
||||
AllChildrenIterator(AllChildrenIterator&& aOther)
|
||||
: FlattenedChildIterator(Move(aOther)),
|
||||
mOriginalContent(aOther.mOriginalContent),
|
||||
mAnonKids(Move(aOther.mAnonKids)), mFlags(aOther.mFlags),
|
||||
mPhase(aOther.mPhase)
|
||||
#ifdef DEBUG
|
||||
, mMutationGuard(aOther.mMutationGuard)
|
||||
#endif
|
||||
{}
|
||||
|
||||
#ifdef DEBUG
|
||||
~AllChildrenIterator() { MOZ_ASSERT(!mMutationGuard.Mutated(0)); }
|
||||
#endif
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
@ -50,13 +51,17 @@ RemoveMapRef(nsAttrHashKey::KeyType aKey, nsRefPtr<Attr>& aData,
|
|||
|
||||
nsDOMAttributeMap::~nsDOMAttributeMap()
|
||||
{
|
||||
mAttributeCache.Enumerate(RemoveMapRef, nullptr);
|
||||
if (mAttributeCache) {
|
||||
mAttributeCache->Enumerate(RemoveMapRef, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDOMAttributeMap::DropReference()
|
||||
{
|
||||
mAttributeCache.Enumerate(RemoveMapRef, nullptr);
|
||||
if (mAttributeCache) {
|
||||
mAttributeCache->Enumerate(RemoveMapRef, nullptr);
|
||||
}
|
||||
mContent = nullptr;
|
||||
}
|
||||
|
||||
|
@ -82,7 +87,9 @@ TraverseMapEntry(nsAttrHashKey::KeyType aKey, nsRefPtr<Attr>& aData,
|
|||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap)
|
||||
tmp->mAttributeCache.Enumerate(TraverseMapEntry, &cb);
|
||||
if (tmp->mAttributeCache) {
|
||||
tmp->mAttributeCache->Enumerate(TraverseMapEntry, &cb);
|
||||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
@ -136,9 +143,10 @@ SetOwnerDocumentFunc(nsAttrHashKey::KeyType aKey,
|
|||
nsresult
|
||||
nsDOMAttributeMap::SetOwnerDocument(nsIDocument* aDocument)
|
||||
{
|
||||
uint32_t n = mAttributeCache.Enumerate(SetOwnerDocumentFunc, aDocument);
|
||||
NS_ENSURE_TRUE(n == mAttributeCache.Count(), NS_ERROR_FAILURE);
|
||||
|
||||
if (mAttributeCache) {
|
||||
uint32_t n = mAttributeCache->Enumerate(SetOwnerDocumentFunc, aDocument);
|
||||
NS_ENSURE_TRUE(n == mAttributeCache->Count(), NS_ERROR_FAILURE);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -146,13 +154,15 @@ void
|
|||
nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID, nsIAtom* aLocalName)
|
||||
{
|
||||
nsAttrKey attr(aNamespaceID, aLocalName);
|
||||
Attr *node = mAttributeCache.GetWeak(attr);
|
||||
if (node) {
|
||||
// Break link to map
|
||||
node->SetMap(nullptr);
|
||||
if (mAttributeCache) {
|
||||
Attr *node = mAttributeCache->GetWeak(attr);
|
||||
if (node) {
|
||||
// Break link to map
|
||||
node->SetMap(nullptr);
|
||||
|
||||
// Remove from cache
|
||||
mAttributeCache.Remove(attr);
|
||||
// Remove from cache
|
||||
mAttributeCache->Remove(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,7 +174,13 @@ nsDOMAttributeMap::RemoveAttribute(mozilla::dom::NodeInfo* aNodeInfo)
|
|||
nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
|
||||
|
||||
nsRefPtr<Attr> node;
|
||||
if (!mAttributeCache.Get(attr, getter_AddRefs(node))) {
|
||||
if (mAttributeCache && mAttributeCache->Get(attr, getter_AddRefs(node))) {
|
||||
// Break link to map
|
||||
node->SetMap(nullptr);
|
||||
|
||||
// Remove from cache
|
||||
mAttributeCache->Remove(attr);
|
||||
} else {
|
||||
nsAutoString value;
|
||||
// As we are removing the attribute we need to set the current value in
|
||||
// the attribute node.
|
||||
|
@ -172,13 +188,6 @@ nsDOMAttributeMap::RemoveAttribute(mozilla::dom::NodeInfo* aNodeInfo)
|
|||
nsRefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
|
||||
node = new Attr(nullptr, ni.forget(), value, true);
|
||||
}
|
||||
else {
|
||||
// Break link to map
|
||||
node->SetMap(nullptr);
|
||||
|
||||
// Remove from cache
|
||||
mAttributeCache.Remove(attr);
|
||||
}
|
||||
|
||||
return node.forget();
|
||||
}
|
||||
|
@ -190,12 +199,13 @@ nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo* aNodeInfo, bool aNsAware
|
|||
|
||||
nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
|
||||
|
||||
Attr* node = mAttributeCache.GetWeak(attr);
|
||||
EnsureAttributeCache();
|
||||
Attr* node = mAttributeCache->GetWeak(attr);
|
||||
if (!node) {
|
||||
nsRefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
|
||||
nsRefPtr<Attr> newAttr =
|
||||
new Attr(this, ni.forget(), EmptyString(), aNsAware);
|
||||
mAttributeCache.Put(attr, newAttr);
|
||||
mAttributeCache->Put(attr, newAttr);
|
||||
node = newAttr;
|
||||
}
|
||||
|
||||
|
@ -241,6 +251,14 @@ nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsDOMAttributeMap::EnsureAttributeCache()
|
||||
{
|
||||
if (!mAttributeCache) {
|
||||
mAttributeCache = MakeUnique<AttrCache>();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMAttributeMap::SetNamedItem(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn)
|
||||
{
|
||||
|
@ -342,7 +360,8 @@ nsDOMAttributeMap::SetNamedItemInternal(Attr& aAttr,
|
|||
// Add the new attribute to the attribute map before updating
|
||||
// its value in the element. @see bug 364413.
|
||||
nsAttrKey attrkey(ni->NamespaceID(), ni->NameAtom());
|
||||
mAttributeCache.Put(attrkey, &aAttr);
|
||||
EnsureAttributeCache();
|
||||
mAttributeCache->Put(attrkey, &aAttr);
|
||||
aAttr.SetMap(this);
|
||||
|
||||
rv = mContent->SetAttr(ni->NamespaceID(), ni->NameAtom(),
|
||||
|
@ -528,14 +547,14 @@ nsDOMAttributeMap::RemoveNamedItemNS(const nsAString& aNamespaceURI,
|
|||
uint32_t
|
||||
nsDOMAttributeMap::Count() const
|
||||
{
|
||||
return mAttributeCache.Count();
|
||||
return mAttributeCache ? mAttributeCache->Count() : 0;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
nsDOMAttributeMap::Enumerate(AttrCache::EnumReadFunction aFunc,
|
||||
void *aUserArg) const
|
||||
{
|
||||
return mAttributeCache.EnumerateRead(aFunc, aUserArg);
|
||||
return mAttributeCache ? mAttributeCache->EnumerateRead(aFunc, aUserArg) : 0;
|
||||
}
|
||||
|
||||
size_t
|
||||
|
@ -551,8 +570,10 @@ size_t
|
|||
nsDOMAttributeMap::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t n = aMallocSizeOf(this);
|
||||
n += mAttributeCache.SizeOfExcludingThis(AttrCacheSizeEnumerator,
|
||||
aMallocSizeOf);
|
||||
n += mAttributeCache
|
||||
? mAttributeCache->SizeOfExcludingThis(AttrCacheSizeEnumerator,
|
||||
aMallocSizeOf)
|
||||
: 0;
|
||||
|
||||
// NB: mContent is non-owning and thus not counted.
|
||||
return n;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#define nsDOMAttributeMap_h
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/dom/Attr.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
@ -183,9 +184,11 @@ private:
|
|||
nsCOMPtr<Element> mContent;
|
||||
|
||||
/**
|
||||
* Cache of Attrs.
|
||||
* Cache of Attrs. It's usually empty, and thus initialized lazily.
|
||||
*/
|
||||
AttrCache mAttributeCache;
|
||||
mozilla::UniquePtr<AttrCache> mAttributeCache;
|
||||
|
||||
void EnsureAttributeCache();
|
||||
|
||||
/**
|
||||
* SetNamedItem() (aWithNS = false) and SetNamedItemNS() (aWithNS =
|
||||
|
|
|
@ -323,7 +323,7 @@ static bool
|
|||
IsMP4SupportedType(const nsACString& aType)
|
||||
{
|
||||
return Preferences::GetBool("media.fragmented-mp4.exposed", false) &&
|
||||
MP4Decoder::GetSupportedCodecs(aType, nullptr);
|
||||
MP4Decoder::CanHandleMediaType(aType);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -406,8 +406,9 @@ DecoderTraits::CanHandleMediaType(const char* aMIMEType,
|
|||
}
|
||||
#endif
|
||||
#ifdef MOZ_FMP4
|
||||
if (IsMP4SupportedType(nsDependentCString(aMIMEType))) {
|
||||
result = aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
|
||||
if (MP4Decoder::CanHandleMediaType(nsDependentCString(aMIMEType),
|
||||
aRequestedCodecs)) {
|
||||
return aHaveRequestedCodecs ? CANPLAY_YES : CANPLAY_MAYBE;
|
||||
}
|
||||
#endif
|
||||
#ifdef MOZ_GSTREAMER
|
||||
|
@ -683,6 +684,14 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac
|
|||
/* static */
|
||||
bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
|
||||
{
|
||||
// Forbid playing media in video documents if the user has opted
|
||||
// not to, using either the legacy WMF specific pref, or the newer
|
||||
// catch-all pref.
|
||||
if (!Preferences::GetBool("media.windows-media-foundation.play-stand-alone", true) ||
|
||||
!Preferences::GetBool("media.play-stand-alone", true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
IsOggType(aType) ||
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
|
@ -703,8 +712,7 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
|
|||
IsMP4SupportedType(aType) ||
|
||||
#endif
|
||||
#ifdef MOZ_WMF
|
||||
(IsWMFSupportedType(aType) &&
|
||||
Preferences::GetBool("media.windows-media-foundation.play-stand-alone", true)) ||
|
||||
IsWMFSupportedType(aType) ||
|
||||
#endif
|
||||
#ifdef MOZ_DIRECTSHOW
|
||||
IsDirectShowSupportedType(aType) ||
|
||||
|
|
|
@ -198,4 +198,36 @@ TemporaryRef<SharedThreadPool> GetMediaDecodeThreadPool()
|
|||
Preferences::GetUint("media.num-decode-threads", 25));
|
||||
}
|
||||
|
||||
bool
|
||||
ExtractH264CodecDetails(const nsAString& aCodec,
|
||||
int16_t& aProfile,
|
||||
int16_t& aLevel)
|
||||
{
|
||||
// H.264 codecs parameters have a type defined as avc1.PPCCLL, where
|
||||
// PP = profile_idc, CC = constraint_set flags, LL = level_idc.
|
||||
// We ignore the constraint_set flags, as it's not clear from any
|
||||
// documentation what constraints the platform decoders support.
|
||||
// See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
|
||||
// for more details.
|
||||
if (aCodec.Length() != strlen("avc1.PPCCLL")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the codec starts with "avc1.".
|
||||
const nsAString& sample = Substring(aCodec, 0, 5);
|
||||
if (!sample.EqualsASCII("avc1.")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the profile_idc, constrains, and level_idc.
|
||||
nsresult rv = NS_OK;
|
||||
aProfile = PromiseFlatString(Substring(aCodec, 5, 2)).ToInteger(&rv, 16);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
aLevel = PromiseFlatString(Substring(aCodec, 9, 2)).ToInteger(&rv, 16);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end namespace mozilla
|
||||
|
|
|
@ -215,6 +215,45 @@ class SharedThreadPool;
|
|||
// for decoding streams.
|
||||
TemporaryRef<SharedThreadPool> GetMediaDecodeThreadPool();
|
||||
|
||||
enum H264_PROFILE {
|
||||
H264_PROFILE_UNKNOWN = 0,
|
||||
H264_PROFILE_BASE = 0x42,
|
||||
H264_PROFILE_MAIN = 0x4D,
|
||||
H264_PROFILE_EXTENDED = 0x58,
|
||||
H264_PROFILE_HIGH = 0x64,
|
||||
};
|
||||
|
||||
enum H264_LEVEL {
|
||||
H264_LEVEL_1 = 10,
|
||||
H264_LEVEL_1_b = 11,
|
||||
H264_LEVEL_1_1 = 11,
|
||||
H264_LEVEL_1_2 = 12,
|
||||
H264_LEVEL_1_3 = 13,
|
||||
H264_LEVEL_2 = 20,
|
||||
H264_LEVEL_2_1 = 21,
|
||||
H264_LEVEL_2_2 = 22,
|
||||
H264_LEVEL_3 = 30,
|
||||
H264_LEVEL_3_1 = 31,
|
||||
H264_LEVEL_3_2 = 32,
|
||||
H264_LEVEL_4 = 40,
|
||||
H264_LEVEL_4_1 = 41,
|
||||
H264_LEVEL_4_2 = 42,
|
||||
H264_LEVEL_5 = 50,
|
||||
H264_LEVEL_5_1 = 51,
|
||||
H264_LEVEL_5_2 = 52
|
||||
};
|
||||
|
||||
// Extracts the H.264/AVC profile and level from an H.264 codecs string.
|
||||
// H.264 codecs parameters have a type defined as avc1.PPCCLL, where
|
||||
// PP = profile_idc, CC = constraint_set flags, LL = level_idc.
|
||||
// See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
|
||||
// for more details.
|
||||
// Returns false on failure.
|
||||
bool
|
||||
ExtractH264CodecDetails(const nsAString& aCodecs,
|
||||
int16_t& aProfile,
|
||||
int16_t& aLevel);
|
||||
|
||||
} // end namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "MP4Reader.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsCharSeparatedTokenizer.h"
|
||||
#ifdef MOZ_EME
|
||||
#include "mozilla/CDMProxy.h"
|
||||
#endif
|
||||
|
@ -50,48 +51,77 @@ MP4Decoder::SetCDMProxy(CDMProxy* aProxy)
|
|||
}
|
||||
#endif
|
||||
|
||||
static bool
|
||||
IsSupportedAudioCodec(const nsAString& aCodec)
|
||||
{
|
||||
// AAC-LC, HE-AAC or MP3 in M4A.
|
||||
return aCodec.EqualsASCII("mp4a.40.2") ||
|
||||
#ifndef MOZ_GONK_MEDIACODEC // B2G doesn't support MP3 in MP4 yet.
|
||||
aCodec.EqualsASCII("mp3") ||
|
||||
#endif
|
||||
aCodec.EqualsASCII("mp4a.40.5");
|
||||
}
|
||||
|
||||
static bool
|
||||
IsSupportedH264Codec(const nsAString& aCodec)
|
||||
{
|
||||
int16_t profile = 0, level = 0;
|
||||
|
||||
if (!ExtractH264CodecDetails(aCodec, profile, level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Just assume what we can play on all platforms the codecs/formats that
|
||||
// WMF can play, since we don't have documentation about what other
|
||||
// platforms can play... According to the WMF documentation:
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd797815%28v=vs.85%29.aspx
|
||||
// "The Media Foundation H.264 video decoder is a Media Foundation Transform
|
||||
// that supports decoding of Baseline, Main, and High profiles, up to level
|
||||
// 5.1.". We also report that we can play Extended profile, as there are
|
||||
// bitstreams that are Extended compliant that are also Baseline compliant.
|
||||
return level >= H264_LEVEL_1 &&
|
||||
level <= H264_LEVEL_5_1 &&
|
||||
(profile == H264_PROFILE_BASE ||
|
||||
profile == H264_PROFILE_MAIN ||
|
||||
profile == H264_PROFILE_EXTENDED ||
|
||||
profile == H264_PROFILE_HIGH);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
MP4Decoder::GetSupportedCodecs(const nsACString& aType,
|
||||
char const *const ** aCodecList)
|
||||
MP4Decoder::CanHandleMediaType(const nsACString& aType,
|
||||
const nsAString& aCodecs)
|
||||
{
|
||||
if (!IsEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// AAC in M4A.
|
||||
static char const *const aacAudioCodecs[] = {
|
||||
"mp4a.40.2", // AAC-LC
|
||||
// TODO: AAC-HE ?
|
||||
nullptr
|
||||
};
|
||||
if (aType.EqualsASCII("audio/mp4") ||
|
||||
aType.EqualsASCII("audio/x-m4a")) {
|
||||
if (aCodecList) {
|
||||
*aCodecList = aacAudioCodecs;
|
||||
}
|
||||
return true;
|
||||
if (aType.EqualsASCII("audio/mp4") || aType.EqualsASCII("audio/x-m4a")) {
|
||||
return aCodecs.IsEmpty() || IsSupportedAudioCodec(aCodecs);
|
||||
}
|
||||
|
||||
// H.264 + AAC in MP4.
|
||||
static char const *const h264Codecs[] = {
|
||||
"avc1.42E01E", // H.264 Constrained Baseline Profile Level 3.0
|
||||
"avc1.42001E", // H.264 Baseline Profile Level 3.0
|
||||
"avc1.58A01E", // H.264 Extended Profile Level 3.0
|
||||
"avc1.4D401E", // H.264 Main Profile Level 3.0
|
||||
"avc1.64001E", // H.264 High Profile Level 3.0
|
||||
"avc1.64001F", // H.264 High Profile Level 3.1
|
||||
"mp4a.40.2", // AAC-LC
|
||||
// TODO: There must be more profiles here?
|
||||
nullptr
|
||||
};
|
||||
if (aType.EqualsASCII("video/mp4")) {
|
||||
if (aCodecList) {
|
||||
*aCodecList = h264Codecs;
|
||||
}
|
||||
return true;
|
||||
if (!aType.EqualsASCII("video/mp4")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Verify that all the codecs specifed are ones that we expect that
|
||||
// we can play.
|
||||
nsCharSeparatedTokenizer tokenizer(aCodecs, ',');
|
||||
bool expectMoreTokens = false;
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
const nsSubstring& token = tokenizer.nextToken();
|
||||
expectMoreTokens = tokenizer.separatorAfterCurrentToken();
|
||||
if (IsSupportedAudioCodec(token) || IsSupportedH264Codec(token)) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (expectMoreTokens) {
|
||||
// Last codec name was empty
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -151,7 +181,7 @@ HavePlatformMPEGDecoders()
|
|||
#endif
|
||||
IsFFmpegAvailable() ||
|
||||
IsAppleAvailable() ||
|
||||
IsGonkMP4DecoderAvailable() ||
|
||||
IsGonkMP4DecoderAvailable() ||
|
||||
// TODO: Other platforms...
|
||||
false;
|
||||
}
|
||||
|
@ -160,8 +190,8 @@ HavePlatformMPEGDecoders()
|
|||
bool
|
||||
MP4Decoder::IsEnabled()
|
||||
{
|
||||
return HavePlatformMPEGDecoders() &&
|
||||
Preferences::GetBool("media.fragmented-mp4.enabled");
|
||||
return Preferences::GetBool("media.fragmented-mp4.enabled") &&
|
||||
HavePlatformMPEGDecoders();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -28,12 +28,11 @@ public:
|
|||
virtual nsresult SetCDMProxy(CDMProxy* aProxy) MOZ_OVERRIDE;
|
||||
#endif
|
||||
|
||||
// Returns true if aType is a MIME type that we can render with the
|
||||
// a MP4 platform decoder backend. If aCodecList is non null,
|
||||
// it is filled with a (static const) null-terminated list of strings
|
||||
// denoting the codecs we'll playback.
|
||||
static bool GetSupportedCodecs(const nsACString& aType,
|
||||
char const *const ** aCodecList);
|
||||
// Returns true if aMIMEType is a type that we think we can render with the
|
||||
// a MP4 platform decoder backend. If aCodecs is non emtpy, it is filled
|
||||
// with a comma-delimited list of codecs to check support for.
|
||||
static bool CanHandleMediaType(const nsACString& aMIMEType,
|
||||
const nsAString& aCodecs = EmptyString());
|
||||
|
||||
// Returns true if the MP4 backend is preffed on, and we're running on a
|
||||
// platform that is likely to have decoders for the contained formats.
|
||||
|
|
|
@ -774,7 +774,10 @@ MP4Reader::NotifyDataArrived(const char* aBuffer, uint32_t aLength,
|
|||
int64_t aOffset)
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
GetTaskQueue()->Dispatch(NS_NewRunnableMethod(this, &MP4Reader::UpdateIndex));
|
||||
if (GetTaskQueue()) {
|
||||
GetTaskQueue()->Dispatch(
|
||||
NS_NewRunnableMethod(this, &MP4Reader::UpdateIndex));
|
||||
}
|
||||
} else {
|
||||
UpdateIndex();
|
||||
}
|
||||
|
|
|
@ -145,7 +145,8 @@ AppleVTDecoder::Drain()
|
|||
// Context object to hold a copy of sample metadata.
|
||||
class FrameRef {
|
||||
public:
|
||||
Microseconds timestamp;
|
||||
Microseconds decode_timestamp;
|
||||
Microseconds composition_timestamp;
|
||||
Microseconds duration;
|
||||
int64_t byte_offset;
|
||||
bool is_sync_point;
|
||||
|
@ -153,7 +154,8 @@ public:
|
|||
explicit FrameRef(mp4_demuxer::MP4Sample* aSample)
|
||||
{
|
||||
MOZ_ASSERT(aSample);
|
||||
timestamp = aSample->composition_timestamp;
|
||||
decode_timestamp = aSample->decode_timestamp;
|
||||
composition_timestamp = aSample->composition_timestamp;
|
||||
duration = aSample->duration;
|
||||
byte_offset = aSample->byte_offset;
|
||||
is_sync_point = aSample->is_sync_point;
|
||||
|
@ -180,9 +182,10 @@ PlatformCallback(void* decompressionOutputRefCon,
|
|||
nsAutoPtr<FrameRef> frameRef =
|
||||
nsAutoPtr<FrameRef>(static_cast<FrameRef*>(sourceFrameRefCon));
|
||||
|
||||
LOG("mp4 output frame %lld pts %lld duration %lld us%s",
|
||||
LOG("mp4 output frame %lld dts %lld pts %lld duration %lld us%s",
|
||||
frameRef->byte_offset,
|
||||
frameRef->timestamp,
|
||||
frameRef->decode_timestamp,
|
||||
frameRef->composition_timestamp,
|
||||
frameRef->duration,
|
||||
frameRef->is_sync_point ? " keyframe" : ""
|
||||
);
|
||||
|
@ -302,11 +305,11 @@ AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage,
|
|||
mImageContainer,
|
||||
nullptr,
|
||||
aFrameRef->byte_offset,
|
||||
aFrameRef->timestamp,
|
||||
aFrameRef->composition_timestamp,
|
||||
aFrameRef->duration,
|
||||
buffer,
|
||||
aFrameRef->is_sync_point,
|
||||
aFrameRef->timestamp,
|
||||
aFrameRef->decode_timestamp,
|
||||
visible);
|
||||
// Unlock the returned image data.
|
||||
CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
|
||||
|
@ -320,10 +323,21 @@ AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage,
|
|||
// Frames come out in DTS order but we need to output them
|
||||
// in composition order.
|
||||
mReorderQueue.Push(data.forget());
|
||||
if (mReorderQueue.Length() > 2) {
|
||||
// Assume a frame with a PTS <= current DTS is ready.
|
||||
while (mReorderQueue.Length() > 0) {
|
||||
VideoData* readyData = mReorderQueue.Pop();
|
||||
mCallback->Output(readyData);
|
||||
if (readyData->mTime <= aFrameRef->decode_timestamp) {
|
||||
LOG("returning queued frame with pts %lld", readyData->mTime);
|
||||
mCallback->Output(readyData);
|
||||
} else {
|
||||
LOG("requeued frame with pts %lld > %lld",
|
||||
readyData->mTime, aFrameRef->decode_timestamp);
|
||||
mReorderQueue.Push(readyData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG("%llu decoded frames queued",
|
||||
static_cast<unsigned long long>(mReorderQueue.Length()));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -337,8 +351,8 @@ TimingInfoFromSample(mp4_demuxer::MP4Sample* aSample)
|
|||
timestamp.duration = CMTimeMake(aSample->duration, USECS_PER_S);
|
||||
timestamp.presentationTimeStamp =
|
||||
CMTimeMake(aSample->composition_timestamp, USECS_PER_S);
|
||||
// No DTS value available from libstagefright.
|
||||
timestamp.decodeTimeStamp = CMTimeMake(0, USECS_PER_S);
|
||||
timestamp.decodeTimeStamp =
|
||||
CMTimeMake(aSample->decode_timestamp, USECS_PER_S);
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ FFmpegH264Decoder<LIBAV_VER>::DecodeFrame(mp4_demuxer::MP4Sample* aSample)
|
|||
aSample->Pad(FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
packet.data = aSample->data;
|
||||
packet.size = aSample->size;
|
||||
packet.dts = aSample->decode_timestamp;
|
||||
packet.pts = aSample->composition_timestamp;
|
||||
packet.flags = aSample->is_sync_point ? AV_PKT_FLAG_KEY : 0;
|
||||
packet.pos = aSample->byte_offset;
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
function check_mp4(v, enabled) {
|
||||
function check(type, expected) {
|
||||
var ex = enabled ? expected : "";
|
||||
is(v.canPlayType(type), ex, type + "='" + ex + "'");
|
||||
}
|
||||
|
||||
check("video/mp4", "maybe");
|
||||
check("audio/mp4", "maybe");
|
||||
check("audio/x-m4a", "maybe");
|
||||
|
||||
// Not the MIME type that other browsers respond to, so we won't either.
|
||||
check("audio/m4a", "");
|
||||
// Only Safari responds affirmatively to "audio/aac",
|
||||
// so we'll let x-m4a cover aac support.
|
||||
check("audio/aac", "");
|
||||
|
||||
check("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.58A01E, mp4a.40.2\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.4D401E, mp4a.40.2\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.64001E, mp4a.40.2\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"", "probably");
|
||||
|
||||
check("video/mp4; codecs=\"avc1.42E01E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.42001E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.58A01E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.4D401E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.64001E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.64001F\"", "probably");
|
||||
|
||||
check("audio/mp4; codecs=\"mp4a.40.2\"", "probably");
|
||||
check("audio/mp4; codecs=mp4a.40.2", "probably");
|
||||
check("audio/x-m4a; codecs=\"mp4a.40.2\"", "probably");
|
||||
check("audio/x-m4a; codecs=mp4a.40.2", "probably");
|
||||
}
|
||||
|
||||
function check_mp3(v, enabled) {
|
||||
function check(type, expected) {
|
||||
var ex = enabled ? expected : "";
|
||||
is(v.canPlayType(type), ex, type + "='" + ex + "'");
|
||||
}
|
||||
|
||||
check("audio/mpeg", "maybe");
|
||||
check("audio/mp3", "maybe");
|
||||
|
||||
check("audio/mpeg; codecs=\"mp3\"", "probably");
|
||||
check("audio/mpeg; codecs=mp3", "probably");
|
||||
|
||||
check("audio/mp3; codecs=\"mp3\"", "probably");
|
||||
check("audio/mp3; codecs=mp3", "probably");
|
||||
}
|
|
@ -108,7 +108,6 @@ support-files =
|
|||
bug604067.webm^headers^
|
||||
bug883173.vtt
|
||||
can_play_type_dash.js
|
||||
can_play_type_mpeg.js
|
||||
can_play_type_ogg.js
|
||||
can_play_type_wave.js
|
||||
can_play_type_webm.js
|
||||
|
|
|
@ -17,9 +17,82 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=799315
|
|||
<video id="v"></video>
|
||||
|
||||
<pre id="test">
|
||||
<script src="can_play_type_mpeg.js"></script>
|
||||
<script>
|
||||
|
||||
function check_mp4(v, enabled) {
|
||||
function check(type, expected) {
|
||||
var ex = enabled ? expected : "";
|
||||
is(v.canPlayType(type), ex, type + "='" + ex + "'");
|
||||
}
|
||||
|
||||
check("video/mp4", "maybe");
|
||||
check("audio/mp4", "maybe");
|
||||
check("audio/x-m4a", "maybe");
|
||||
|
||||
// Not the MIME type that other browsers respond to, so we won't either.
|
||||
check("audio/m4a", "");
|
||||
// Only Safari responds affirmatively to "audio/aac",
|
||||
// so we'll let x-m4a cover aac support.
|
||||
check("audio/aac", "");
|
||||
|
||||
// H.264 Constrained Baseline Profile Level 3.0, AAC-LC
|
||||
check("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"", "probably");
|
||||
|
||||
// H.264 Constrained Baseline Profile Level 3.0, mp3
|
||||
check("video/mp4; codecs=\"avc1.42E01E, mp3\"", "probably");
|
||||
|
||||
check("video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.58A01E, mp4a.40.2\"", "probably");
|
||||
|
||||
const ProbablyIfNotLinux = !IsLinuxGStreamer() ? "probably" : "";
|
||||
|
||||
// H.264 Main Profile Level 3.0, AAC-LC
|
||||
check("video/mp4; codecs=\"avc1.4D401E, mp4a.40.2\"", "probably");
|
||||
// H.264 Main Profile Level 3.1, AAC-LC
|
||||
check("video/mp4; codecs=\"avc1.4D401F, mp4a.40.2\"", ProbablyIfNotLinux);
|
||||
// H.264 Main Profile Level 4.0, AAC-LC
|
||||
check("video/mp4; codecs=\"avc1.4D4028, mp4a.40.2\"", ProbablyIfNotLinux);
|
||||
// H.264 High Profile Level 3.0, AAC-LC
|
||||
check("video/mp4; codecs=\"avc1.64001E, mp4a.40.2\"", "probably");
|
||||
// H.264 High Profile Level 3.1, AAC-LC
|
||||
check("video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"", "probably");
|
||||
|
||||
check("video/mp4; codecs=\"avc1.42E01E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.42001E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.58A01E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.4D401E\"", "probably");
|
||||
check("video/mp4; codecs=\"avc1.64001F\"", "probably");
|
||||
|
||||
// AAC-LC
|
||||
check("audio/mp4; codecs=\"mp4a.40.2\"", "probably");
|
||||
check("audio/mp4; codecs=mp4a.40.2", "probably");
|
||||
check("audio/x-m4a; codecs=\"mp4a.40.2\"", "probably");
|
||||
check("audio/x-m4a; codecs=mp4a.40.2", "probably");
|
||||
|
||||
// HE-AAC v1
|
||||
check("audio/mp4; codecs=\"mp4a.40.5\"", ProbablyIfNotLinux);
|
||||
check("audio/mp4; codecs=mp4a.40.5", ProbablyIfNotLinux);
|
||||
check("audio/x-m4a; codecs=\"mp4a.40.5\"", ProbablyIfNotLinux);
|
||||
check("audio/x-m4a; codecs=mp4a.40.5", ProbablyIfNotLinux);
|
||||
|
||||
}
|
||||
|
||||
function check_mp3(v, enabled) {
|
||||
function check(type, expected) {
|
||||
var ex = enabled ? expected : "";
|
||||
is(v.canPlayType(type), ex, type + "='" + ex + "'");
|
||||
}
|
||||
|
||||
check("audio/mpeg", "maybe");
|
||||
check("audio/mp3", "maybe");
|
||||
|
||||
check("audio/mpeg; codecs=\"mp3\"", "probably");
|
||||
check("audio/mpeg; codecs=mp3", "probably");
|
||||
|
||||
check("audio/mp3; codecs=\"mp3\"", "probably");
|
||||
check("audio/mp3; codecs=mp3", "probably");
|
||||
}
|
||||
|
||||
function IsWindowsVistaOrLater() {
|
||||
var re = /Windows NT (\d+\.\d)/;
|
||||
var winver = navigator.userAgent.match(re);
|
||||
|
@ -45,6 +118,11 @@ function getPref(name) {
|
|||
return pref;
|
||||
}
|
||||
|
||||
function IsLinuxGStreamer() {
|
||||
return /Linux/.test(navigator.userAgent) &&
|
||||
getPref("media.gstreamer.enabled");
|
||||
}
|
||||
|
||||
// Check whether we should expect the new MP4Reader-based support to work.
|
||||
function IsMP4ReaderAvailable() {
|
||||
var prefs = getPref("media.fragmented-mp4.enabled") &&
|
||||
|
|
|
@ -54,38 +54,17 @@ IsSupportedH264Codec(const nsAString& aCodec)
|
|||
// 5.1.". We also report that we can play Extended profile, as there are
|
||||
// bitstreams that are Extended compliant that are also Baseline compliant.
|
||||
|
||||
// H.264 codecs parameters have a type defined as avc1.PPCCLL, where
|
||||
// PP = profile_idc, CC = constraint_set flags, LL = level_idc.
|
||||
// We ignore the constraint_set flags, as it's not clear from the WMF
|
||||
// documentation what constraints the WMF H.264 decoder supports.
|
||||
// See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
|
||||
// for more details.
|
||||
if (aCodec.Length() != strlen("avc1.PPCCLL")) {
|
||||
int16_t profile = 0, level = 0;
|
||||
if (!ExtractH264CodecDetails(aCodec, profile, level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the codec starts with "avc1.".
|
||||
const nsAString& sample = Substring(aCodec, 0, 5);
|
||||
if (!sample.EqualsASCII("avc1.")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the profile_idc and level_idc. Note: the constraint_set flags
|
||||
// are ignored, it's not clear from the WMF documentation if they make a
|
||||
// difference.
|
||||
nsresult rv = NS_OK;
|
||||
const int32_t profile = PromiseFlatString(Substring(aCodec, 5, 2)).ToInteger(&rv, 16);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
const int32_t level = PromiseFlatString(Substring(aCodec, 9, 2)).ToInteger(&rv, 16);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
return level >= eAVEncH264VLevel1 &&
|
||||
level <= eAVEncH264VLevel5_1 &&
|
||||
(profile == eAVEncH264VProfile_Base ||
|
||||
profile == eAVEncH264VProfile_Main ||
|
||||
profile == eAVEncH264VProfile_Extended ||
|
||||
profile == eAVEncH264VProfile_High);
|
||||
return level >= H264_LEVEL_1 &&
|
||||
level <= H264_LEVEL_5_1 &&
|
||||
(profile == H264_PROFILE_BASE ||
|
||||
profile == H264_PROFILE_MAIN ||
|
||||
profile == H264_PROFILE_EXTENDED ||
|
||||
profile == H264_PROFILE_HIGH);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -624,6 +624,7 @@ METHOD(Warn, "warn")
|
|||
METHOD(Error, "error")
|
||||
METHOD(Exception, "exception")
|
||||
METHOD(Debug, "debug")
|
||||
METHOD(Table, "table")
|
||||
|
||||
void
|
||||
Console::Trace(JSContext* aCx)
|
||||
|
|
|
@ -66,6 +66,9 @@ public:
|
|||
void
|
||||
Debug(JSContext* aCx, const Sequence<JS::Value>& aData);
|
||||
|
||||
void
|
||||
Table(JSContext* aCx, const Sequence<JS::Value>& aData);
|
||||
|
||||
void
|
||||
Trace(JSContext* aCx);
|
||||
|
||||
|
@ -111,6 +114,7 @@ private:
|
|||
MethodError,
|
||||
MethodException,
|
||||
MethodDebug,
|
||||
MethodTable,
|
||||
MethodTrace,
|
||||
MethodDir,
|
||||
MethodGroup,
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
ok("console" in window, "Console exists");
|
||||
window.console.log(42);
|
||||
ok("table" in console, "Console has the 'table' method.");
|
||||
window.console = 42;
|
||||
is(window.console, 42, "Console is replacable");
|
||||
|
||||
|
|
|
@ -29,8 +29,7 @@ DefineStaticJSVals(JSContext* cx)
|
|||
return InternJSString(cx, s_length_id, "length");
|
||||
}
|
||||
|
||||
|
||||
const char HandlerFamily = 0;
|
||||
const char DOMProxyHandler::family = 0;
|
||||
|
||||
js::DOMProxyShadowsResult
|
||||
DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id)
|
||||
|
@ -60,7 +59,7 @@ DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id)
|
|||
struct SetDOMProxyInformation
|
||||
{
|
||||
SetDOMProxyInformation() {
|
||||
js::SetDOMProxyInformation((const void*) &HandlerFamily,
|
||||
js::SetDOMProxyInformation((const void*) &DOMProxyHandler::family,
|
||||
js::PROXY_EXTRA_SLOT + JSPROXYSLOT_EXPANDO, DOMProxyShadows);
|
||||
}
|
||||
};
|
||||
|
@ -363,5 +362,25 @@ DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handl
|
|||
return true;
|
||||
}
|
||||
|
||||
//static
|
||||
JSObject *
|
||||
DOMProxyHandler::GetExpandoObject(JSObject *obj)
|
||||
{
|
||||
MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
|
||||
JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
|
||||
if (v.isObject()) {
|
||||
return &v.toObject();
|
||||
}
|
||||
|
||||
if (v.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
js::ExpandoAndGeneration* expandoAndGeneration =
|
||||
static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
|
||||
v = expandoAndGeneration->expando;
|
||||
return v.isUndefined() ? nullptr : &v.toObject();
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -24,17 +24,6 @@ enum {
|
|||
|
||||
template<typename T> struct Prefable;
|
||||
|
||||
// This variable exists solely to provide a unique address for use as an identifier.
|
||||
extern const char HandlerFamily;
|
||||
inline const void* ProxyFamily() { return &HandlerFamily; }
|
||||
|
||||
inline bool IsDOMProxy(JSObject *obj)
|
||||
{
|
||||
const js::Class* clasp = js::GetObjectClass(obj);
|
||||
return clasp->isProxy() &&
|
||||
js::GetProxyHandler(obj)->family() == ProxyFamily();
|
||||
}
|
||||
|
||||
class BaseDOMProxyHandler : public js::BaseProxyHandler
|
||||
{
|
||||
public:
|
||||
|
@ -89,7 +78,7 @@ class DOMProxyHandler : public BaseDOMProxyHandler
|
|||
{
|
||||
public:
|
||||
DOMProxyHandler()
|
||||
: BaseDOMProxyHandler(ProxyFamily())
|
||||
: BaseDOMProxyHandler(&family)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -121,29 +110,23 @@ public:
|
|||
virtual bool setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
|
||||
JS::MutableHandle<JS::Value> vp, bool *done) const;
|
||||
|
||||
static JSObject* GetExpandoObject(JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
|
||||
JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
|
||||
if (v.isObject()) {
|
||||
return &v.toObject();
|
||||
}
|
||||
static JSObject* GetExpandoObject(JSObject* obj);
|
||||
|
||||
if (v.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
js::ExpandoAndGeneration* expandoAndGeneration =
|
||||
static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
|
||||
v = expandoAndGeneration->expando;
|
||||
return v.isUndefined() ? nullptr : &v.toObject();
|
||||
}
|
||||
/* GetAndClearExpandoObject does not DROP or clear the preserving wrapper flag. */
|
||||
static JSObject* GetAndClearExpandoObject(JSObject* obj);
|
||||
static JSObject* EnsureExpandoObject(JSContext* cx,
|
||||
JS::Handle<JSObject*> obj);
|
||||
|
||||
static const char family;
|
||||
};
|
||||
|
||||
inline bool IsDOMProxy(JSObject *obj)
|
||||
{
|
||||
const js::Class* clasp = js::GetObjectClass(obj);
|
||||
return clasp->isProxy() &&
|
||||
js::GetProxyHandler(obj)->family() == &DOMProxyHandler::family;
|
||||
}
|
||||
|
||||
inline const DOMProxyHandler*
|
||||
GetDOMProxyHandler(JSObject* obj)
|
||||
{
|
||||
|
|
|
@ -72,6 +72,8 @@
|
|||
#include "mozilla/layers/ShadowLayers.h"
|
||||
#endif
|
||||
|
||||
#include <queue>
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::gfx;
|
||||
|
@ -435,12 +437,10 @@ WebGLContext::SetContextOptions(JSContext* aCx, JS::Handle<JS::Value> aOptions)
|
|||
newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
|
||||
newOpts.antialias = attributes.mAntialias;
|
||||
newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
|
||||
if (attributes.mAlpha.WasPassed()) {
|
||||
newOpts.alpha = attributes.mAlpha.Value();
|
||||
}
|
||||
|
||||
// enforce that if stencil is specified, we also give back depth
|
||||
newOpts.depth |= newOpts.stencil;
|
||||
if (attributes.mAlpha.WasPassed()) {
|
||||
newOpts.alpha = attributes.mAlpha.Value();
|
||||
}
|
||||
|
||||
// Don't do antialiasing if we've disabled MSAA.
|
||||
if (!gfxPrefs::MSAALevel()) {
|
||||
|
@ -471,36 +471,333 @@ WebGLContext::SetContextOptions(JSContext* aCx, JS::Handle<JS::Value> aOptions)
|
|||
int32_t
|
||||
WebGLContext::GetWidth() const
|
||||
{
|
||||
return mWidth;
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
int32_t
|
||||
WebGLContext::GetHeight() const
|
||||
{
|
||||
return mHeight;
|
||||
return mHeight;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* So there are a number of points of failure here. We might fail based
|
||||
* on EGL vs. WGL, or we might fail to alloc a too-large size, or we
|
||||
* might not be able to create a context with a certain combo of context
|
||||
* creation attribs.
|
||||
*
|
||||
* We don't want to test the complete fallback matrix. (for now, at
|
||||
* least) Instead, attempt creation in this order:
|
||||
* 1. By platform API. (e.g. EGL vs. WGL)
|
||||
* 2. By context creation attribs.
|
||||
* 3. By size.
|
||||
*
|
||||
* That is, try to create headless contexts based on the platform API.
|
||||
* Next, create dummy-sized backbuffers for the contexts with the right
|
||||
* caps. Finally, resize the backbuffer to an acceptable size given the
|
||||
* requested size.
|
||||
*/
|
||||
|
||||
static bool
|
||||
IsFeatureInBlacklist(const nsCOMPtr<nsIGfxInfo>& gfxInfo, int32_t feature)
|
||||
{
|
||||
int32_t status;
|
||||
if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(feature, &status)))
|
||||
return false;
|
||||
|
||||
return status != nsIGfxInfo::FEATURE_STATUS_OK;
|
||||
}
|
||||
|
||||
static already_AddRefed<GLContext>
|
||||
CreateHeadlessNativeGL(bool forceEnabled,
|
||||
const nsCOMPtr<nsIGfxInfo>& gfxInfo,
|
||||
WebGLContext* webgl)
|
||||
{
|
||||
if (!forceEnabled &&
|
||||
IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_OPENGL))
|
||||
{
|
||||
webgl->GenerateWarning("Refused to create native OpenGL context"
|
||||
" because of blacklisting.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<GLContext> gl = gl::GLContextProvider::CreateHeadless();
|
||||
if (!gl) {
|
||||
webgl->GenerateWarning("Error during native OpenGL init.");
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(!gl->IsANGLE());
|
||||
|
||||
return gl.forget();
|
||||
}
|
||||
|
||||
// Note that we have a separate call for ANGLE and EGL, even though
|
||||
// right now, we get ANGLE implicitly by using EGL on Windows.
|
||||
// Eventually, we want to be able to pick ANGLE-EGL or native EGL.
|
||||
static already_AddRefed<GLContext>
|
||||
CreateHeadlessANGLE(bool forceEnabled,
|
||||
const nsCOMPtr<nsIGfxInfo>& gfxInfo,
|
||||
WebGLContext* webgl)
|
||||
{
|
||||
nsRefPtr<GLContext> gl;
|
||||
|
||||
#ifdef XP_WIN
|
||||
if (!forceEnabled &&
|
||||
IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_ANGLE))
|
||||
{
|
||||
webgl->GenerateWarning("Refused to create ANGLE OpenGL context"
|
||||
" because of blacklisting.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gl = gl::GLContextProviderEGL::CreateHeadless();
|
||||
if (!gl) {
|
||||
webgl->GenerateWarning("Error during ANGLE OpenGL init.");
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(gl->IsANGLE());
|
||||
#endif
|
||||
|
||||
return gl.forget();
|
||||
}
|
||||
|
||||
static already_AddRefed<GLContext>
|
||||
CreateHeadlessEGL(bool forceEnabled,
|
||||
const nsCOMPtr<nsIGfxInfo>& gfxInfo,
|
||||
WebGLContext* webgl)
|
||||
{
|
||||
nsRefPtr<GLContext> gl;
|
||||
|
||||
#ifdef ANDROID
|
||||
gl = gl::GLContextProviderEGL::CreateHeadless();
|
||||
if (!gl) {
|
||||
webgl->GenerateWarning("Error during EGL OpenGL init.");
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(!gl->IsANGLE());
|
||||
#endif
|
||||
|
||||
return gl.forget();
|
||||
}
|
||||
|
||||
|
||||
static already_AddRefed<GLContext>
|
||||
CreateHeadlessGL(bool forceEnabled,
|
||||
const nsCOMPtr<nsIGfxInfo>& gfxInfo,
|
||||
WebGLContext* webgl)
|
||||
{
|
||||
bool preferEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL");
|
||||
bool disableANGLE = Preferences::GetBool("webgl.disable-angle", false);
|
||||
|
||||
if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) {
|
||||
disableANGLE = true;
|
||||
}
|
||||
|
||||
nsRefPtr<GLContext> gl;
|
||||
|
||||
if (preferEGL)
|
||||
gl = CreateHeadlessEGL(forceEnabled, gfxInfo, webgl);
|
||||
|
||||
if (!gl && !disableANGLE)
|
||||
gl = CreateHeadlessANGLE(forceEnabled, gfxInfo, webgl);
|
||||
|
||||
if (!gl)
|
||||
gl = CreateHeadlessNativeGL(forceEnabled, gfxInfo, webgl);
|
||||
|
||||
return gl.forget();
|
||||
}
|
||||
|
||||
// Try to create a dummy offscreen with the given caps.
|
||||
static bool
|
||||
CreateOffscreenWithCaps(GLContext* gl, const SurfaceCaps& caps)
|
||||
{
|
||||
gfx::IntSize dummySize(16, 16);
|
||||
return gl->InitOffscreen(dummySize, caps);
|
||||
}
|
||||
|
||||
static void
|
||||
PopulateCapFallbackQueue(const SurfaceCaps& baseCaps,
|
||||
std::queue<SurfaceCaps>* fallbackCaps)
|
||||
{
|
||||
fallbackCaps->push(baseCaps);
|
||||
|
||||
// Dropping antialias drops our quality, but not our correctness.
|
||||
// The user basically doesn't have to handle if this fails, they
|
||||
// just get reduced quality.
|
||||
if (baseCaps.antialias) {
|
||||
SurfaceCaps nextCaps(baseCaps);
|
||||
nextCaps.antialias = false;
|
||||
PopulateCapFallbackQueue(nextCaps, fallbackCaps);
|
||||
}
|
||||
|
||||
// If we have to drop one of depth or stencil, we'd prefer to keep
|
||||
// depth. However, the client app will need to handle if this
|
||||
// doesn't work.
|
||||
if (baseCaps.stencil) {
|
||||
SurfaceCaps nextCaps(baseCaps);
|
||||
nextCaps.stencil = false;
|
||||
PopulateCapFallbackQueue(nextCaps, fallbackCaps);
|
||||
}
|
||||
|
||||
if (baseCaps.depth) {
|
||||
SurfaceCaps nextCaps(baseCaps);
|
||||
nextCaps.depth = false;
|
||||
PopulateCapFallbackQueue(nextCaps, fallbackCaps);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
CreateOffscreen(GLContext* gl,
|
||||
const WebGLContextOptions& options,
|
||||
const nsCOMPtr<nsIGfxInfo>& gfxInfo,
|
||||
WebGLContext* webgl,
|
||||
layers::ISurfaceAllocator* surfAllocator)
|
||||
{
|
||||
SurfaceCaps baseCaps;
|
||||
|
||||
baseCaps.color = true;
|
||||
baseCaps.alpha = options.alpha;
|
||||
baseCaps.antialias = options.antialias;
|
||||
baseCaps.depth = options.depth;
|
||||
baseCaps.preserve = options.preserveDrawingBuffer;
|
||||
baseCaps.stencil = options.stencil;
|
||||
|
||||
// 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 = Preferences::GetBool("webgl.prefer-16bpp", false);
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
baseCaps.surfaceAllocator = surfAllocator;
|
||||
#endif
|
||||
|
||||
// Done with baseCaps construction.
|
||||
|
||||
bool forceAllowAA = Preferences::GetBool("webgl.msaa-force", false);
|
||||
if (!forceAllowAA &&
|
||||
IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA))
|
||||
{
|
||||
webgl->GenerateWarning("Disallowing antialiased backbuffers due"
|
||||
" to blacklisting.");
|
||||
baseCaps.antialias = false;
|
||||
}
|
||||
|
||||
std::queue<SurfaceCaps> fallbackCaps;
|
||||
PopulateCapFallbackQueue(baseCaps, &fallbackCaps);
|
||||
|
||||
bool created = false;
|
||||
while (!fallbackCaps.empty()) {
|
||||
SurfaceCaps& caps = fallbackCaps.front();
|
||||
|
||||
created = CreateOffscreenWithCaps(gl, caps);
|
||||
if (created)
|
||||
break;
|
||||
|
||||
fallbackCaps.pop();
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
bool
|
||||
WebGLContext::CreateOffscreenGL(bool forceEnabled)
|
||||
{
|
||||
nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
|
||||
|
||||
layers::ISurfaceAllocator* surfAllocator = nullptr;
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
|
||||
if (docWidget) {
|
||||
layers::LayerManager* layerManager = docWidget->GetLayerManager();
|
||||
if (layerManager) {
|
||||
// XXX we really want "AsSurfaceAllocator" here for generality
|
||||
layers::ShadowLayerForwarder* forwarder = layerManager->AsShadowForwarder();
|
||||
if (forwarder) {
|
||||
surfAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
gl = CreateHeadlessGL(forceEnabled, gfxInfo, this);
|
||||
|
||||
do {
|
||||
if (!gl)
|
||||
break;
|
||||
|
||||
if (!CreateOffscreen(gl, mOptions, gfxInfo, this, surfAllocator))
|
||||
break;
|
||||
|
||||
if (!InitAndValidateGL())
|
||||
break;
|
||||
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
gl = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fallback for resizes:
|
||||
bool
|
||||
WebGLContext::ResizeBackbuffer(uint32_t requestedWidth, uint32_t requestedHeight)
|
||||
{
|
||||
uint32_t width = requestedWidth;
|
||||
uint32_t height = requestedHeight;
|
||||
|
||||
bool resized = false;
|
||||
while (width || height) {
|
||||
width = width ? width : 1;
|
||||
height = height ? height : 1;
|
||||
|
||||
gfx::IntSize curSize(width, height);
|
||||
if (gl->ResizeOffscreen(curSize)) {
|
||||
resized = true;
|
||||
break;
|
||||
}
|
||||
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
}
|
||||
|
||||
if (!resized)
|
||||
return false;
|
||||
|
||||
mWidth = gl->OffscreenSize().width;
|
||||
mHeight = gl->OffscreenSize().height;
|
||||
MOZ_ASSERT((uint32_t)mWidth == width);
|
||||
MOZ_ASSERT((uint32_t)mHeight == height);
|
||||
|
||||
if (width != requestedWidth ||
|
||||
height != requestedHeight)
|
||||
{
|
||||
GenerateWarning("Requested size %dx%d was too large, but resize"
|
||||
" to %dx%d succeeded.",
|
||||
requestedWidth, requestedHeight,
|
||||
width, height);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebGLContext::SetDimensions(int32_t width, int32_t height)
|
||||
WebGLContext::SetDimensions(int32_t sWidth, int32_t sHeight)
|
||||
{
|
||||
// Early error return cases
|
||||
if (!GetCanvas())
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
if (width < 0 || height < 0) {
|
||||
if (sWidth < 0 || sHeight < 0) {
|
||||
GenerateWarning("Canvas size is too large (seems like a negative value wrapped)");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (!GetCanvas())
|
||||
return NS_ERROR_FAILURE;
|
||||
uint32_t width = sWidth;
|
||||
uint32_t height = sHeight;
|
||||
|
||||
// Early success return cases
|
||||
|
||||
GetCanvas()->InvalidateCanvas();
|
||||
|
||||
if (gl && mWidth == width && mHeight == height)
|
||||
return NS_OK;
|
||||
|
||||
// Zero-sized surfaces can cause problems.
|
||||
if (width == 0) {
|
||||
width = 1;
|
||||
|
@ -511,20 +808,29 @@ WebGLContext::SetDimensions(int32_t width, int32_t height)
|
|||
|
||||
// If we already have a gl context, then we just need to resize it
|
||||
if (gl) {
|
||||
if ((uint32_t)mWidth == width &&
|
||||
(uint32_t)mHeight == height)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (IsContextLost())
|
||||
return NS_OK;
|
||||
|
||||
MakeContextCurrent();
|
||||
|
||||
// If we've already drawn, we should commit the current buffer.
|
||||
PresentScreenBuffer();
|
||||
|
||||
// ResizeOffscreen scraps the current prod buffer before making a new one.
|
||||
gl->ResizeOffscreen(gfx::IntSize(width, height)); // Doesn't matter if it succeeds (soft-fail)
|
||||
// It's unlikely that we'll get a proper-sized context if we recreate if we didn't on resize
|
||||
if (!ResizeBackbuffer(width, height)) {
|
||||
GenerateWarning("WebGL context failed to resize.");
|
||||
ForceLoseContext();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// everything's good, we're done here
|
||||
mWidth = gl->OffscreenSize().width;
|
||||
mHeight = gl->OffscreenSize().height;
|
||||
mResetLayer = true;
|
||||
|
||||
mBackbufferNeedsClear = true;
|
||||
|
||||
return NS_OK;
|
||||
|
@ -541,27 +847,6 @@ WebGLContext::SetDimensions(int32_t width, int32_t height)
|
|||
// and that is what can fail if we already have too many.
|
||||
LoseOldestWebGLContextIfLimitExceeded();
|
||||
|
||||
// Get some prefs for some preferred/overriden things
|
||||
NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
|
||||
|
||||
#ifdef XP_WIN
|
||||
bool preferEGL =
|
||||
Preferences::GetBool("webgl.prefer-egl", false);
|
||||
bool preferOpenGL =
|
||||
Preferences::GetBool("webgl.prefer-native-gl", false);
|
||||
#endif
|
||||
bool forceEnabled =
|
||||
Preferences::GetBool("webgl.force-enabled", false);
|
||||
bool disabled =
|
||||
Preferences::GetBool("webgl.disabled", false);
|
||||
bool prefer16bit =
|
||||
Preferences::GetBool("webgl.prefer-16bpp", false);
|
||||
|
||||
ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
|
||||
|
||||
if (disabled)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
// We're going to create an entirely new context. If our
|
||||
// generation is not 0 right now (that is, if this isn't the first
|
||||
// context we're creating), we may have to dispatch a context lost
|
||||
|
@ -570,111 +855,32 @@ WebGLContext::SetDimensions(int32_t width, int32_t height)
|
|||
// If incrementing the generation would cause overflow,
|
||||
// don't allow it. Allowing this would allow us to use
|
||||
// resource handles created from older context generations.
|
||||
if (!(mGeneration + 1).isValid())
|
||||
if (!(mGeneration + 1).isValid()) {
|
||||
GenerateWarning("Too many WebGL contexts created this run.");
|
||||
return NS_ERROR_FAILURE; // exit without changing the value of mGeneration
|
||||
|
||||
SurfaceCaps caps;
|
||||
|
||||
caps.color = true;
|
||||
caps.alpha = mOptions.alpha;
|
||||
caps.depth = mOptions.depth;
|
||||
caps.stencil = mOptions.stencil;
|
||||
|
||||
// we should really have this behind a
|
||||
// |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but
|
||||
// for now it's just behind a pref for testing/evaluation.
|
||||
caps.bpp16 = prefer16bit;
|
||||
|
||||
caps.preserve = mOptions.preserveDrawingBuffer;
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
nsIWidget *docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
|
||||
if (docWidget) {
|
||||
layers::LayerManager *layerManager = docWidget->GetLayerManager();
|
||||
if (layerManager) {
|
||||
// XXX we really want "AsSurfaceAllocator" here for generality
|
||||
layers::ShadowLayerForwarder *forwarder = layerManager->AsShadowForwarder();
|
||||
if (forwarder) {
|
||||
caps.surfaceAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool forceMSAA =
|
||||
Preferences::GetBool("webgl.msaa-force", false);
|
||||
|
||||
int32_t status;
|
||||
nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
|
||||
if (mOptions.antialias &&
|
||||
gfxInfo &&
|
||||
NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_MSAA, &status))) {
|
||||
if (status == nsIGfxInfo::FEATURE_STATUS_OK || forceMSAA) {
|
||||
caps.antialias = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
if (PR_GetEnv("MOZ_WEBGL_PREFER_EGL")) {
|
||||
preferEGL = true;
|
||||
}
|
||||
#endif
|
||||
// Get some prefs for some preferred/overriden things
|
||||
NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
|
||||
|
||||
// Ask GfxInfo about what we should use
|
||||
bool useOpenGL = true;
|
||||
|
||||
#ifdef XP_WIN
|
||||
bool useANGLE = true;
|
||||
#endif
|
||||
|
||||
if (gfxInfo && !forceEnabled) {
|
||||
if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_OPENGL, &status))) {
|
||||
if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
|
||||
useOpenGL = false;
|
||||
}
|
||||
}
|
||||
#ifdef XP_WIN
|
||||
if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_ANGLE, &status))) {
|
||||
if (status != nsIGfxInfo::FEATURE_STATUS_OK) {
|
||||
useANGLE = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bool disabled = Preferences::GetBool("webgl.disabled", false);
|
||||
if (disabled) {
|
||||
GenerateWarning("WebGL creation is disabled, and so disallowed here.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
// allow forcing GL and not EGL/ANGLE
|
||||
if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) {
|
||||
preferEGL = false;
|
||||
useANGLE = false;
|
||||
useOpenGL = true;
|
||||
// Alright, now let's start trying.
|
||||
bool forceEnabled = Preferences::GetBool("webgl.force-enabled", false);
|
||||
ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
|
||||
|
||||
if (!CreateOffscreenGL(forceEnabled)) {
|
||||
GenerateWarning("WebGL creation failed.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
#endif
|
||||
MOZ_ASSERT(gl);
|
||||
|
||||
gfxIntSize size(width, height);
|
||||
|
||||
#ifdef XP_WIN
|
||||
// if we want EGL, try it now
|
||||
if (!gl && (preferEGL || useANGLE) && !preferOpenGL) {
|
||||
gl = gl::GLContextProviderEGL::CreateOffscreen(size, caps);
|
||||
if (!gl || !InitAndValidateGL()) {
|
||||
GenerateWarning("Error during ANGLE OpenGL ES initialization");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// try the default provider, whatever that is
|
||||
if (!gl && useOpenGL) {
|
||||
gl = gl::GLContextProvider::CreateOffscreen(size, caps);
|
||||
if (gl && !InitAndValidateGL()) {
|
||||
GenerateWarning("Error during OpenGL initialization");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gl) {
|
||||
GenerateWarning("Can't get a usable WebGL context");
|
||||
if (!ResizeBackbuffer(width, height)) {
|
||||
GenerateWarning("Initializing WebGL backbuffer failed.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -684,23 +890,18 @@ WebGLContext::SetDimensions(int32_t width, int32_t height)
|
|||
}
|
||||
#endif
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mViewportWidth = width;
|
||||
mViewportHeight = height;
|
||||
mResetLayer = true;
|
||||
mOptionsFrozen = true;
|
||||
|
||||
// increment the generation number
|
||||
++mGeneration;
|
||||
#if 0
|
||||
if (mGeneration > 0) {
|
||||
// XXX dispatch context lost event
|
||||
}
|
||||
#endif
|
||||
|
||||
MakeContextCurrent();
|
||||
|
||||
gl->fViewport(0, 0, mWidth, mHeight);
|
||||
mViewportWidth = mWidth;
|
||||
mViewportHeight = mHeight;
|
||||
|
||||
// Make sure that we clear this out, otherwise
|
||||
// we'll end up displaying random memory
|
||||
gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
|
||||
|
@ -715,12 +916,12 @@ WebGLContext::SetDimensions(int32_t width, int32_t height)
|
|||
|
||||
mShouldPresent = true;
|
||||
|
||||
MOZ_ASSERT(gl->Caps().color == caps.color);
|
||||
MOZ_ASSERT(gl->Caps().alpha == caps.alpha);
|
||||
MOZ_ASSERT(gl->Caps().depth == caps.depth || !gl->Caps().depth);
|
||||
MOZ_ASSERT(gl->Caps().stencil == caps.stencil || !gl->Caps().stencil);
|
||||
MOZ_ASSERT(gl->Caps().antialias == caps.antialias || !gl->Caps().antialias);
|
||||
MOZ_ASSERT(gl->Caps().preserve == caps.preserve);
|
||||
MOZ_ASSERT(gl->Caps().color);
|
||||
MOZ_ASSERT(gl->Caps().alpha == mOptions.alpha);
|
||||
MOZ_ASSERT(gl->Caps().depth == mOptions.depth || !gl->Caps().depth);
|
||||
MOZ_ASSERT(gl->Caps().stencil == mOptions.stencil || !gl->Caps().stencil);
|
||||
MOZ_ASSERT(gl->Caps().antialias == mOptions.antialias || !gl->Caps().antialias);
|
||||
MOZ_ASSERT(gl->Caps().preserve == mOptions.preserveDrawingBuffer);
|
||||
|
||||
AssertCachedBindings();
|
||||
AssertCachedState();
|
||||
|
@ -1211,7 +1412,7 @@ WebGLContext::PresentScreenBuffer()
|
|||
gl->MakeCurrent();
|
||||
MOZ_ASSERT(!mBackbufferNeedsClear);
|
||||
if (!gl->PublishFrame()) {
|
||||
this->ForceLoseContext();
|
||||
ForceLoseContext();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1036,7 +1036,9 @@ protected:
|
|||
// Validation functions (implemented in WebGLContextValidate.cpp)
|
||||
GLenum BaseTexFormat(GLenum internalFormat) const;
|
||||
|
||||
bool CreateOffscreenGL(bool forceEnabled);
|
||||
bool InitAndValidateGL();
|
||||
bool ResizeBackbuffer(uint32_t width, uint32_t height);
|
||||
bool ValidateBlendEquationEnum(GLenum cap, const char *info);
|
||||
bool ValidateBlendFuncDstEnum(GLenum mode, const char *info);
|
||||
bool ValidateBlendFuncSrcEnum(GLenum mode, const char *info);
|
||||
|
|
|
@ -175,10 +175,10 @@ skip-if(winWidget) pref(webgl.prefer-16bpp,true)
|
|||
skip-if(winWidget) pref(webgl.prefer-16bpp,true) pref(webgl.force-layers-readback,true) random-if(Android&&AndroidVersion<15) == webgl-color-test.html?16bpp&readback wrapper.html?colors.png
|
||||
|
||||
# Force native GL (Windows):
|
||||
skip-if(!winWidget) pref(webgl.prefer-native-gl,true) == webgl-clear-test.html?native-gl wrapper.html?green.png
|
||||
skip-if(!winWidget) pref(webgl.prefer-native-gl,true) == webgl-orientation-test.html?native-gl wrapper.html?white-top-left.png
|
||||
skip-if(!winWidget) pref(webgl.prefer-native-gl,true) == webgl-color-test.html?native-gl wrapper.html?colors.png
|
||||
skip-if(!winWidget) pref(webgl.prefer-native-gl,true) pref(webgl.prefer-16bpp,true) == webgl-color-test.html?native-gl&16bpp wrapper.html?colors.png
|
||||
skip-if(!winWidget) pref(webgl.disable-angle,true) == webgl-clear-test.html?native-gl wrapper.html?green.png
|
||||
skip-if(!winWidget) pref(webgl.disable-angle,true) == webgl-orientation-test.html?native-gl wrapper.html?white-top-left.png
|
||||
skip-if(!winWidget) pref(webgl.disable-angle,true) == webgl-color-test.html?native-gl wrapper.html?colors.png
|
||||
skip-if(!winWidget) pref(webgl.disable-angle,true) pref(webgl.prefer-16bpp,true) == webgl-color-test.html?native-gl&16bpp wrapper.html?colors.png
|
||||
|
||||
|
||||
# Non-WebGL Reftests!
|
||||
|
|
|
@ -2280,6 +2280,34 @@ PeerConnectionWrapper.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Property-matching function for finding a certain stat in passed-in stats
|
||||
*
|
||||
* @param {object} stats
|
||||
* The stats to check from this PeerConnectionWrapper
|
||||
* @param {object} props
|
||||
* The properties to look for
|
||||
* @returns {boolean} Whether an entry containing all match-props was found.
|
||||
*/
|
||||
hasStat : function PCW_hasStat(stats, props) {
|
||||
for (var key in stats) {
|
||||
if (stats.hasOwnProperty(key)) {
|
||||
var res = stats[key];
|
||||
var match = true;
|
||||
for (var prop in props) {
|
||||
if (res[prop] !== props[prop]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the connection
|
||||
*/
|
||||
|
|
|
@ -413,6 +413,174 @@ var commandsPeerConnection = [
|
|||
test.next();
|
||||
});
|
||||
}
|
||||
],
|
||||
[
|
||||
'PC_LOCAL_CHECK_GETSTATS_AUDIOTRACK_OUTBOUND',
|
||||
function (test) {
|
||||
var pc = test.pcLocal;
|
||||
var stream = pc._pc.getLocalStreams()[0];
|
||||
var track = stream && stream.getAudioTracks()[0];
|
||||
if (track) {
|
||||
var msg = "pcLocal.HasStat outbound audio rtp ";
|
||||
pc.getStats(track, function(stats) {
|
||||
ok(pc.hasStat(stats,
|
||||
{ type:"outboundrtp", isRemote:false, mediaType:"audio" }),
|
||||
msg + "1");
|
||||
ok(!pc.hasStat(stats, { type:"inboundrtp", isRemote:false }), msg + "2");
|
||||
ok(!pc.hasStat(stats, { mediaType:"video" }), msg + "3");
|
||||
test.next();
|
||||
});
|
||||
} else {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'PC_LOCAL_CHECK_GETSTATS_VIDEOTRACK_OUTBOUND',
|
||||
function (test) {
|
||||
var pc = test.pcLocal;
|
||||
var stream = pc._pc.getLocalStreams()[0];
|
||||
var track = stream && stream.getVideoTracks()[0];
|
||||
if (track) {
|
||||
var msg = "pcLocal.HasStat outbound video rtp ";
|
||||
pc.getStats(track, function(stats) {
|
||||
ok(pc.hasStat(stats,
|
||||
{ type:"outboundrtp", isRemote:false, mediaType:"video" }),
|
||||
msg + "1");
|
||||
ok(!pc.hasStat(stats, { type:"inboundrtp", isRemote:false }), msg + "2");
|
||||
ok(!pc.hasStat(stats, { mediaType:"audio" }), msg + "3");
|
||||
test.next();
|
||||
});
|
||||
} else {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'PC_LOCAL_CHECK_GETSTATS_AUDIOTRACK_INBOUND',
|
||||
function (test) {
|
||||
var pc = test.pcLocal;
|
||||
var stream = pc._pc.getRemoteStreams()[0];
|
||||
var track = stream && stream.getAudioTracks()[0];
|
||||
if (track) {
|
||||
var msg = "pcLocal.HasStat inbound audio rtp ";
|
||||
pc.getStats(track, function(stats) {
|
||||
ok(pc.hasStat(stats,
|
||||
{ type:"inboundrtp", isRemote:false, mediaType:"audio" }),
|
||||
msg + "1");
|
||||
ok(!pc.hasStat(stats, { type:"outboundrtp", isRemote:false }), msg + "2");
|
||||
ok(!pc.hasStat(stats, { mediaType:"video" }), msg + "3");
|
||||
test.next();
|
||||
});
|
||||
} else {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'PC_LOCAL_CHECK_GETSTATS_VIDEOTRACK_INBOUND',
|
||||
function (test) {
|
||||
var pc = test.pcLocal;
|
||||
var stream = pc._pc.getRemoteStreams()[0];
|
||||
var track = stream && stream.getVideoTracks()[0];
|
||||
if (track) {
|
||||
var msg = "pcLocal.HasStat inbound video rtp ";
|
||||
pc.getStats(track, function(stats) {
|
||||
ok(pc.hasStat(stats,
|
||||
{ type:"inboundrtp", isRemote:false, mediaType:"video" }),
|
||||
msg + "1");
|
||||
ok(!pc.hasStat(stats, { type:"outboundrtp", isRemote:false }), msg + "2");
|
||||
ok(!pc.hasStat(stats, { mediaType:"audio" }), msg + "3");
|
||||
test.next();
|
||||
});
|
||||
} else {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'PC_REMOTE_CHECK_GETSTATS_AUDIOTRACK_OUTBOUND',
|
||||
function (test) {
|
||||
var pc = test.pcRemote;
|
||||
var stream = pc._pc.getLocalStreams()[0];
|
||||
var track = stream && stream.getAudioTracks()[0];
|
||||
if (track) {
|
||||
var msg = "pcRemote.HasStat outbound audio rtp ";
|
||||
pc.getStats(track, function(stats) {
|
||||
ok(pc.hasStat(stats,
|
||||
{ type:"outboundrtp", isRemote:false, mediaType:"audio" }),
|
||||
msg + "1");
|
||||
ok(!pc.hasStat(stats, { type:"inboundrtp", isRemote:false }), msg + "2");
|
||||
ok(!pc.hasStat(stats, { mediaType:"video" }), msg + "3");
|
||||
test.next();
|
||||
});
|
||||
} else {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'PC_REMOTE_CHECK_GETSTATS_VIDEOTRACK_OUTBOUND',
|
||||
function (test) {
|
||||
var pc = test.pcRemote;
|
||||
var stream = pc._pc.getLocalStreams()[0];
|
||||
var track = stream && stream.getVideoTracks()[0];
|
||||
if (track) {
|
||||
var msg = "pcRemote.HasStat outbound audio rtp ";
|
||||
pc.getStats(track, function(stats) {
|
||||
ok(pc.hasStat(stats,
|
||||
{ type:"outboundrtp", isRemote:false, mediaType:"video" }),
|
||||
msg + "1");
|
||||
ok(!pc.hasStat(stats, { type:"inboundrtp", isRemote:false }), msg + "2");
|
||||
ok(!pc.hasStat(stats, { mediaType:"audio" }), msg + "3");
|
||||
test.next();
|
||||
});
|
||||
} else {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'PC_REMOTE_CHECK_GETSTATS_AUDIOTRACK_INBOUND',
|
||||
function (test) {
|
||||
var pc = test.pcRemote;
|
||||
var stream = pc._pc.getRemoteStreams()[0];
|
||||
var track = stream && stream.getAudioTracks()[0];
|
||||
if (track) {
|
||||
var msg = "pcRemote.HasStat inbound audio rtp ";
|
||||
pc.getStats(track, function(stats) {
|
||||
ok(pc.hasStat(stats,
|
||||
{ type:"inboundrtp", isRemote:false, mediaType:"audio" }),
|
||||
msg + "1");
|
||||
ok(!pc.hasStat(stats, { type:"outboundrtp", isRemote:false }), msg + "2");
|
||||
ok(!pc.hasStat(stats, { mediaType:"video" }), msg + "3");
|
||||
test.next();
|
||||
});
|
||||
} else {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'PC_REMOTE_CHECK_GETSTATS_VIDEOTRACK_INBOUND',
|
||||
function (test) {
|
||||
var pc = test.pcRemote;
|
||||
var stream = pc._pc.getRemoteStreams()[0];
|
||||
var track = stream && stream.getVideoTracks()[0];
|
||||
if (track) {
|
||||
var msg = "pcRemote.HasStat inbound video rtp ";
|
||||
pc.getStats(track, function(stats) {
|
||||
ok(pc.hasStat(stats,
|
||||
{ type:"inboundrtp", isRemote:false, mediaType:"video" }),
|
||||
msg + "1");
|
||||
ok(!pc.hasStat(stats, { type:"outboundrtp", isRemote:false }), msg + "2");
|
||||
ok(!pc.hasStat(stats, { mediaType:"audio" }), msg + "3");
|
||||
test.next();
|
||||
});
|
||||
} else {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ DEFINES['NS_NO_XPCOM'] = True
|
|||
DEFINES['_HAS_EXCEPTIONS'] = 0
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
USE_STATIC_LIBS = True
|
||||
|
||||
if CONFIG['GNU_CC']:
|
||||
WIN32_EXE_LDFLAGS += ['-municode']
|
||||
|
|
|
@ -97,12 +97,22 @@ DialogWatcher.prototype.init = function() {
|
|||
ctypes.jschar.ptr,
|
||||
ctypes.int);
|
||||
}
|
||||
if (!this.messageBox) {
|
||||
// Handy for debugging this code
|
||||
this.messageBox = user32.declare("MessageBoxW",
|
||||
ctypes.winapi_abi,
|
||||
ctypes.int,
|
||||
ctypes.uintptr_t,
|
||||
ctypes.jschar.ptr,
|
||||
ctypes.jschar.ptr,
|
||||
ctypes.uint32_t);
|
||||
}
|
||||
};
|
||||
|
||||
DialogWatcher.prototype.getWindowText = function(hwnd) {
|
||||
var bufType = ctypes.ArrayType(ctypes.jschar);
|
||||
var buffer = new bufType(256);
|
||||
|
||||
|
||||
if (this.getWindowTextW(hwnd, buffer, buffer.length)) {
|
||||
return buffer.readString();
|
||||
}
|
||||
|
@ -154,13 +164,15 @@ DialogWatcher.prototype.processWindowEvents = function(timeout) {
|
|||
|
||||
var waitStatus = WAIT_OBJECT_0;
|
||||
var expectingStart = this.onDialogStart && this.hwnd === undefined;
|
||||
var startWaitTime = Date.now();
|
||||
while (this.hwnd === undefined || this.onDialogEnd && this.hwnd) {
|
||||
waitStatus = this.msgWaitForMultipleObjects(0, null, 0, expectingStart ?
|
||||
INFINITE : timeout, 0);
|
||||
if (waitStatus == WAIT_OBJECT_0) {
|
||||
var msg = new this.msgType;
|
||||
this.peekMessage(msg.address(), 0, 0, 0, PM_NOREMOVE);
|
||||
} else if (waitStatus == WAIT_TIMEOUT) {
|
||||
}
|
||||
if (waitStatus == WAIT_TIMEOUT || (Date.now() - startWaitTime) >= timeout) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
<title>Plugin Hang UI Test</title>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="utils.js" />
|
||||
<script type="application/javascript"
|
||||
src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hang_test.js" />
|
||||
<script type="application/javascript"
|
||||
|
@ -19,6 +21,7 @@
|
|||
<script class="testbody" type="application/javascript">
|
||||
<![CDATA[
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
|
@ -100,7 +103,8 @@ function runTests() {
|
|||
|
||||
resetVars();
|
||||
|
||||
hanguiExpect("Prime ChromeWorker", false, false, "test1");
|
||||
hanguiOperation("Prime ChromeWorker", 0, false, false, HANGUIOP_NOTHING, 0,
|
||||
false, "test1");
|
||||
}
|
||||
|
||||
window.frameLoaded = runTests;
|
||||
|
@ -244,7 +248,7 @@ function test3() {
|
|||
}
|
||||
|
||||
function test2() {
|
||||
// This test is identical to test1 because there were some bugs where the
|
||||
// This test is identical to test1 because there were some bugs where the
|
||||
// Hang UI would show on the first hang but not on subsequent hangs
|
||||
hanguiExpect("test2: Plugin Hang UI is showing", true, true, "test3");
|
||||
p.stall(STALL_DURATION);
|
||||
|
|
|
@ -496,6 +496,17 @@ nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition)
|
|||
return;
|
||||
}
|
||||
|
||||
if (mOptions && mOptions->mMaximumAge > 0) {
|
||||
DOMTimeStamp positionTime_ms;
|
||||
aPosition->GetTimestamp(&positionTime_ms);
|
||||
const uint32_t maximumAge_ms = mOptions->mMaximumAge;
|
||||
const bool isTooOld =
|
||||
DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) > positionTime_ms;
|
||||
if (isTooOld) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsRefPtr<Position> wrapped;
|
||||
|
||||
if (aPosition) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <pthread.h>
|
||||
#include <hardware/gps.h>
|
||||
|
||||
#include "mozilla/Constants.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
@ -81,8 +82,8 @@ GonkGPSGeolocationProvider::LocationCallback(GpsLocation* location)
|
|||
NS_IMETHOD Run() {
|
||||
nsRefPtr<GonkGPSGeolocationProvider> provider =
|
||||
GonkGPSGeolocationProvider::GetSingleton();
|
||||
provider->mLastGPSDerivedLocationTime = PR_Now();
|
||||
nsCOMPtr<nsIGeolocationUpdate> callback = provider->mLocationCallback;
|
||||
provider->mLastGPSPosition = mPosition;
|
||||
if (callback) {
|
||||
callback->Update(mPosition);
|
||||
}
|
||||
|
@ -101,7 +102,14 @@ GonkGPSGeolocationProvider::LocationCallback(GpsLocation* location)
|
|||
location->accuracy,
|
||||
location->bearing,
|
||||
location->speed,
|
||||
location->timestamp);
|
||||
PR_Now() / PR_USEC_PER_MSEC);
|
||||
// Note above: Can't use location->timestamp as the time from the satellite is a
|
||||
// minimum of 16 secs old (see http://leapsecond.com/java/gpsclock.htm).
|
||||
// All code from this point on expects the gps location to be timestamped with the
|
||||
// current time, most notably: the geolocation service which respects maximumAge
|
||||
// set in the DOM JS.
|
||||
|
||||
|
||||
NS_DispatchToMainThread(new UpdateLocationEvent(somewhere));
|
||||
}
|
||||
|
||||
|
@ -699,7 +707,7 @@ GonkGPSGeolocationProvider::NetworkLocationUpdate::Update(nsIDOMGeoPosition *pos
|
|||
coords->GetLongitude(&lon);
|
||||
coords->GetAccuracy(&acc);
|
||||
|
||||
double delta = MAXFLOAT;
|
||||
double delta = -1.0;
|
||||
|
||||
static double sLastMLSPosLat = 0;
|
||||
static double sLastMLSPosLon = 0;
|
||||
|
@ -708,15 +716,20 @@ GonkGPSGeolocationProvider::NetworkLocationUpdate::Update(nsIDOMGeoPosition *pos
|
|||
// Use spherical law of cosines to calculate difference
|
||||
// Not quite as correct as the Haversine but simpler and cheaper
|
||||
// Should the following be a utility function? Others might need this calc.
|
||||
const double radsInDeg = 3.14159265 / 180.0;
|
||||
const double radsInDeg = M_PI / 180.0;
|
||||
const double rNewLat = lat * radsInDeg;
|
||||
const double rNewLon = lon * radsInDeg;
|
||||
const double rOldLat = sLastMLSPosLat * radsInDeg;
|
||||
const double rOldLon = sLastMLSPosLon * radsInDeg;
|
||||
// WGS84 equatorial radius of earth = 6378137m
|
||||
delta = acos( (sin(rNewLat) * sin(rOldLat)) +
|
||||
(cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon)) )
|
||||
* 6378137;
|
||||
double cosDelta = (sin(rNewLat) * sin(rOldLat)) +
|
||||
(cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon));
|
||||
if (cosDelta > 1.0) {
|
||||
cosDelta = 1.0;
|
||||
} else if (cosDelta < -1.0) {
|
||||
cosDelta = -1.0;
|
||||
}
|
||||
delta = acos(cosDelta) * 6378137;
|
||||
}
|
||||
|
||||
sLastMLSPosLat = lat;
|
||||
|
@ -726,14 +739,40 @@ GonkGPSGeolocationProvider::NetworkLocationUpdate::Update(nsIDOMGeoPosition *pos
|
|||
// assume the MLS coord is unchanged, and stick with the GPS location
|
||||
const double kMinMLSCoordChangeInMeters = 10;
|
||||
|
||||
// if we haven't seen anything from the GPS device for 10s,
|
||||
// use this network derived location.
|
||||
const int kMaxGPSDelayBeforeConsideringMLS = 10000;
|
||||
int64_t diff = PR_Now() - provider->mLastGPSDerivedLocationTime;
|
||||
if (provider->mLocationCallback && diff > kMaxGPSDelayBeforeConsideringMLS
|
||||
&& delta > kMinMLSCoordChangeInMeters)
|
||||
{
|
||||
provider->mLocationCallback->Update(position);
|
||||
DOMTimeStamp time_ms = 0;
|
||||
if (provider->mLastGPSPosition) {
|
||||
provider->mLastGPSPosition->GetTimestamp(&time_ms);
|
||||
}
|
||||
const int64_t diff_ms = (PR_Now() / PR_USEC_PER_MSEC) - time_ms;
|
||||
|
||||
// We want to distinguish between the GPS being inactive completely
|
||||
// and temporarily inactive. In the former case, we would use a low
|
||||
// accuracy network location; in the latter, we only want a network
|
||||
// location that appears to updating with movement.
|
||||
|
||||
const bool isGPSFullyInactive = diff_ms > 1000 * 60 * 2; // two mins
|
||||
const bool isGPSTempInactive = diff_ms > 1000 * 10; // 10 secs
|
||||
|
||||
if (provider->mLocationCallback) {
|
||||
if (isGPSFullyInactive ||
|
||||
(isGPSTempInactive && delta > kMinMLSCoordChangeInMeters))
|
||||
{
|
||||
if (gGPSDebugging) {
|
||||
nsContentUtils::LogMessageToConsole("geo: Using MLS, GPS age:%fs, MLS Delta:%fm\n",
|
||||
diff_ms / 1000.0, delta);
|
||||
}
|
||||
provider->mLocationCallback->Update(position);
|
||||
} else if (provider->mLastGPSPosition) {
|
||||
if (gGPSDebugging) {
|
||||
nsContentUtils::LogMessageToConsole("geo: Using old GPS age:%fs\n",
|
||||
diff_ms / 1000.0);
|
||||
}
|
||||
|
||||
// This is a fallback case so that the GPS provider responds with its last
|
||||
// location rather than waiting for a more recent GPS or network location.
|
||||
// The service decides if the location is too old, not the provider.
|
||||
provider->mLocationCallback->Update(provider->mLastGPSPosition);
|
||||
}
|
||||
}
|
||||
|
||||
provider->InjectLocation(lat, lon, acc);
|
||||
|
@ -779,7 +818,6 @@ GonkGPSGeolocationProvider::Startup()
|
|||
}
|
||||
}
|
||||
|
||||
mLastGPSDerivedLocationTime = 0;
|
||||
mStarted = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "nsCOMPtr.h"
|
||||
#include "nsIGeolocationProvider.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIDOMGeoPosition.h"
|
||||
#ifdef MOZ_B2G_RIL
|
||||
#include "nsIRadioInterfaceLayer.h"
|
||||
#endif
|
||||
|
@ -111,9 +112,9 @@ private:
|
|||
nsCOMPtr<nsIRadioInterface> mRadioInterface;
|
||||
#endif
|
||||
nsCOMPtr<nsIGeolocationUpdate> mLocationCallback;
|
||||
PRTime mLastGPSDerivedLocationTime;
|
||||
nsCOMPtr<nsIThread> mInitThread;
|
||||
nsCOMPtr<nsIGeolocationProvider> mNetworkLocationProvider;
|
||||
nsCOMPtr<nsIDOMGeoPosition> mLastGPSPosition;
|
||||
|
||||
class NetworkLocationUpdate : public nsIGeolocationUpdate
|
||||
{
|
||||
|
|
|
@ -37,6 +37,7 @@ function doTest() {
|
|||
"profileEnd": "function",
|
||||
"assert": "function",
|
||||
"count": "function",
|
||||
"table": "function",
|
||||
"__noSuchMethod__": "function"
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ interface Console {
|
|||
void error(any... data);
|
||||
void _exception(any... data);
|
||||
void debug(any... data);
|
||||
void table(any... data);
|
||||
void trace();
|
||||
void dir(any... data);
|
||||
void group(any... data);
|
||||
|
|
|
@ -27,6 +27,7 @@ dictionary RTCStats {
|
|||
|
||||
dictionary RTCRTPStreamStats : RTCStats {
|
||||
DOMString ssrc;
|
||||
DOMString mediaType;
|
||||
DOMString remoteId;
|
||||
boolean isRemote = false;
|
||||
DOMString mediaTrackId;
|
||||
|
|
|
@ -80,7 +80,9 @@ RenderTarget9::RenderTarget9(Renderer *renderer, GLsizei width, GLsizei height,
|
|||
0, FALSE, &mRenderTarget, NULL);
|
||||
}
|
||||
|
||||
if (result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY)
|
||||
if (result == D3DERR_OUTOFVIDEOMEMORY ||
|
||||
result == E_INVALIDARG ||
|
||||
result == E_OUTOFMEMORY)
|
||||
{
|
||||
gl::error(GL_OUT_OF_MEMORY);
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ RenderbufferStorageBySamples(GLContext* aGL, GLsizei aSamples,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
GLuint
|
||||
CreateTexture(GLContext* aGL, GLenum aInternalFormat, GLenum aFormat,
|
||||
GLenum aType, const gfx::IntSize& aSize, bool linear)
|
||||
|
@ -47,10 +46,16 @@ CreateTexture(GLContext* aGL, GLenum aInternalFormat, GLenum aFormat,
|
|||
aGL->fGenTextures(1, &tex);
|
||||
ScopedBindTexture autoTex(aGL, tex);
|
||||
|
||||
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, linear ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST);
|
||||
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, linear ? LOCAL_GL_LINEAR : LOCAL_GL_NEAREST);
|
||||
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
|
||||
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
|
||||
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D,
|
||||
LOCAL_GL_TEXTURE_MIN_FILTER, linear ? LOCAL_GL_LINEAR
|
||||
: LOCAL_GL_NEAREST);
|
||||
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D,
|
||||
LOCAL_GL_TEXTURE_MAG_FILTER, linear ? LOCAL_GL_LINEAR
|
||||
: LOCAL_GL_NEAREST);
|
||||
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
|
||||
LOCAL_GL_CLAMP_TO_EDGE);
|
||||
aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
|
||||
LOCAL_GL_CLAMP_TO_EDGE);
|
||||
|
||||
aGL->fTexImage2D(LOCAL_GL_TEXTURE_2D,
|
||||
0,
|
||||
|
|
|
@ -277,7 +277,7 @@ GLContext::GLContext(const SurfaceCaps& caps,
|
|||
mRenderer(GLRenderer::Other),
|
||||
mHasRobustness(false),
|
||||
#ifdef DEBUG
|
||||
mGLError(LOCAL_GL_NO_ERROR),
|
||||
mIsInLocalErrorCheck(false),
|
||||
#endif
|
||||
mSharedContext(sharedContext),
|
||||
mCaps(caps),
|
||||
|
@ -1112,6 +1112,7 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl)
|
|||
raw_fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
|
||||
raw_fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mMaxCubeMapTextureSize);
|
||||
raw_fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mMaxRenderbufferSize);
|
||||
raw_fGetIntegerv(LOCAL_GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
if (mWorkAroundDriverBugs) {
|
||||
|
@ -1127,8 +1128,7 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl)
|
|||
// See bug 879656. 8192 fails, 8191 works.
|
||||
mMaxTextureSize = std::min(mMaxTextureSize, 8191);
|
||||
mMaxRenderbufferSize = std::min(mMaxRenderbufferSize, 8191);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// See bug 877949.
|
||||
mMaxTextureSize = std::min(mMaxTextureSize, 4096);
|
||||
mMaxRenderbufferSize = std::min(mMaxRenderbufferSize, 4096);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <ctype.h>
|
||||
#include <map>
|
||||
#include <bitset>
|
||||
#include <queue>
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <string.h>
|
||||
|
@ -505,7 +506,6 @@ private:
|
|||
// -----------------------------------------------------------------------------
|
||||
// Robustness handling
|
||||
public:
|
||||
|
||||
bool HasRobustness() const {
|
||||
return mHasRobustness;
|
||||
}
|
||||
|
@ -516,17 +516,13 @@ public:
|
|||
*/
|
||||
virtual bool SupportsRobustness() const = 0;
|
||||
|
||||
|
||||
private:
|
||||
bool mHasRobustness;
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Error handling
|
||||
public:
|
||||
|
||||
static const char* GLErrorToString(GLenum aError)
|
||||
{
|
||||
static const char* GLErrorToString(GLenum aError) {
|
||||
switch (aError) {
|
||||
case LOCAL_GL_INVALID_ENUM:
|
||||
return "GL_INVALID_ENUM";
|
||||
|
@ -549,12 +545,10 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/** \returns the first GL error, and guarantees that all GL error flags are cleared,
|
||||
* i.e. that a subsequent GetError call will return NO_ERROR
|
||||
*/
|
||||
GLenum GetAndClearError()
|
||||
{
|
||||
GLenum GetAndClearError() {
|
||||
// the first error is what we want to return
|
||||
GLenum error = fGetError();
|
||||
|
||||
|
@ -566,30 +560,99 @@ public:
|
|||
return error;
|
||||
}
|
||||
|
||||
|
||||
/*** In GL debug mode, we completely override glGetError ***/
|
||||
|
||||
GLenum fGetError()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
// debug mode ends up eating the error in AFTER_GL_CALL
|
||||
if (DebugMode()) {
|
||||
GLenum err = mGLError;
|
||||
mGLError = LOCAL_GL_NO_ERROR;
|
||||
return err;
|
||||
}
|
||||
#endif // DEBUG
|
||||
|
||||
private:
|
||||
GLenum raw_fGetError() {
|
||||
return mSymbols.fGetError();
|
||||
}
|
||||
|
||||
std::queue<GLenum> mGLErrorQueue;
|
||||
|
||||
public:
|
||||
GLenum fGetError() {
|
||||
if (!mGLErrorQueue.empty()) {
|
||||
GLenum err = mGLErrorQueue.front();
|
||||
mGLErrorQueue.pop();
|
||||
return err;
|
||||
}
|
||||
|
||||
return GetUnpushedError();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
private:
|
||||
GLenum GetUnpushedError() {
|
||||
return raw_fGetError();
|
||||
}
|
||||
|
||||
GLenum mGLError;
|
||||
#endif // DEBUG
|
||||
void ClearUnpushedErrors() {
|
||||
while (GetUnpushedError()) {
|
||||
// Discard errors.
|
||||
}
|
||||
}
|
||||
|
||||
GLenum GetAndClearUnpushedErrors() {
|
||||
GLenum err = GetUnpushedError();
|
||||
if (err) {
|
||||
ClearUnpushedErrors();
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void PushError(GLenum err) {
|
||||
mGLErrorQueue.push(err);
|
||||
}
|
||||
|
||||
void GetAndPushAllErrors() {
|
||||
while (true) {
|
||||
GLenum err = GetUnpushedError();
|
||||
if (!err)
|
||||
break;
|
||||
|
||||
PushError(err);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// Use this safer option.
|
||||
private:
|
||||
#ifdef DEBUG
|
||||
bool mIsInLocalErrorCheck;
|
||||
#endif
|
||||
|
||||
public:
|
||||
class ScopedLocalErrorCheck {
|
||||
GLContext* const mGL;
|
||||
bool mHasBeenChecked;
|
||||
|
||||
public:
|
||||
ScopedLocalErrorCheck(GLContext* gl)
|
||||
: mGL(gl)
|
||||
, mHasBeenChecked(false)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
MOZ_ASSERT(!mGL->mIsInLocalErrorCheck);
|
||||
mGL->mIsInLocalErrorCheck = true;
|
||||
#endif
|
||||
mGL->GetAndPushAllErrors();
|
||||
}
|
||||
|
||||
GLenum GetLocalError() {
|
||||
#ifdef DEBUG
|
||||
MOZ_ASSERT(mGL->mIsInLocalErrorCheck);
|
||||
mGL->mIsInLocalErrorCheck = false;
|
||||
#endif
|
||||
|
||||
MOZ_ASSERT(!mHasBeenChecked);
|
||||
mHasBeenChecked = true;
|
||||
|
||||
return mGL->GetAndClearUnpushedErrors();
|
||||
}
|
||||
|
||||
~ScopedLocalErrorCheck() {
|
||||
MOZ_ASSERT(mHasBeenChecked);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
static void GLAPIENTRY StaticDebugCallback(GLenum source,
|
||||
GLenum type,
|
||||
GLuint id,
|
||||
|
@ -624,8 +687,7 @@ private:
|
|||
# endif
|
||||
#endif
|
||||
|
||||
void BeforeGLCall(const char* glFunction)
|
||||
{
|
||||
void BeforeGLCall(const char* glFunction) {
|
||||
MOZ_ASSERT(IsCurrent());
|
||||
if (DebugMode()) {
|
||||
GLContext *currentGLContext = nullptr;
|
||||
|
@ -643,21 +705,23 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void AfterGLCall(const char* glFunction)
|
||||
{
|
||||
void AfterGLCall(const char* glFunction) {
|
||||
if (DebugMode()) {
|
||||
// calling fFinish() immediately after every GL call makes sure that if this GL command crashes,
|
||||
// the stack trace will actually point to it. Otherwise, OpenGL being an asynchronous API, stack traces
|
||||
// tend to be meaningless
|
||||
mSymbols.fFinish();
|
||||
mGLError = mSymbols.fGetError();
|
||||
GLenum err = GetUnpushedError();
|
||||
PushError(err);
|
||||
|
||||
if (DebugMode() & DebugTrace)
|
||||
printf_stderr("[gl:%p] < %s [0x%04x]\n", this, glFunction, mGLError);
|
||||
if (mGLError != LOCAL_GL_NO_ERROR) {
|
||||
printf_stderr("[gl:%p] < %s [0x%04x]\n", this, glFunction, err);
|
||||
|
||||
if (err != LOCAL_GL_NO_ERROR) {
|
||||
printf_stderr("GL ERROR: %s generated GL error %s(0x%04x)\n",
|
||||
glFunction,
|
||||
GLErrorToString(mGLError),
|
||||
mGLError);
|
||||
GLErrorToString(err),
|
||||
err);
|
||||
if (DebugMode() & DebugAbortOnError)
|
||||
NS_ABORT();
|
||||
}
|
||||
|
@ -2985,6 +3049,7 @@ protected:
|
|||
GLint mMaxCubeMapTextureSize;
|
||||
GLint mMaxTextureImageSize;
|
||||
GLint mMaxRenderbufferSize;
|
||||
GLint mMaxViewportDims[2];
|
||||
GLsizei mMaxSamples;
|
||||
bool mNeedsTextureSizeChecks;
|
||||
bool mWorkAroundDriverBugs;
|
||||
|
|
|
@ -247,25 +247,33 @@ CreateOffscreenFBOContext(bool aShare = true)
|
|||
return glContext.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderCGL::CreateHeadless()
|
||||
{
|
||||
nsRefPtr<GLContextCGL> glContext = CreateOffscreenFBOContext();
|
||||
if (!glContext)
|
||||
return nullptr;
|
||||
|
||||
if (!glContext->Init())
|
||||
return nullptr;
|
||||
|
||||
return glContext.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderCGL::CreateOffscreen(const gfxIntSize& size,
|
||||
const SurfaceCaps& caps)
|
||||
{
|
||||
nsRefPtr<GLContextCGL> glContext = CreateOffscreenFBOContext();
|
||||
if (glContext &&
|
||||
glContext->Init() &&
|
||||
glContext->InitOffscreen(ToIntSize(size), caps))
|
||||
{
|
||||
return glContext.forget();
|
||||
}
|
||||
nsRefPtr<GLContext> glContext = CreateHeadless();
|
||||
if (!glContext->InitOffscreen(ToIntSize(size), caps))
|
||||
return nullptr;
|
||||
|
||||
// everything failed
|
||||
return nullptr;
|
||||
return glContext.forget();
|
||||
}
|
||||
|
||||
static nsRefPtr<GLContext> gGlobalContext;
|
||||
|
||||
GLContext *
|
||||
GLContext*
|
||||
GLContextProviderCGL::GetGlobalContext()
|
||||
{
|
||||
if (!sCGLLibrary.EnsureInitialized()) {
|
||||
|
|
|
@ -878,20 +878,29 @@ GLContextEGL::CreateEGLPixmapOffscreenContext(const gfxIntSize& size)
|
|||
return glContext.forget();
|
||||
}
|
||||
|
||||
// Under EGL, on Android, pbuffers are supported fine, though
|
||||
// often without the ability to texture from them directly.
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderEGL::CreateOffscreen(const gfxIntSize& size,
|
||||
const SurfaceCaps& caps)
|
||||
GLContextProviderEGL::CreateHeadless()
|
||||
{
|
||||
if (!sEGLLibrary.EnsureInitialized()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gfxIntSize dummySize = gfxIntSize(16, 16);
|
||||
nsRefPtr<GLContextEGL> glContext;
|
||||
nsRefPtr<GLContext> glContext;
|
||||
glContext = GLContextEGL::CreateEGLPBufferOffscreenContext(dummySize);
|
||||
if (!glContext)
|
||||
return nullptr;
|
||||
|
||||
return glContext.forget();
|
||||
}
|
||||
|
||||
// Under EGL, on Android, pbuffers are supported fine, though
|
||||
// often without the ability to texture from them directly.
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderEGL::CreateOffscreen(const gfxIntSize& size,
|
||||
const SurfaceCaps& caps)
|
||||
{
|
||||
nsRefPtr<GLContext> glContext = CreateHeadless();
|
||||
if (!glContext)
|
||||
return nullptr;
|
||||
|
||||
|
@ -904,7 +913,7 @@ GLContextProviderEGL::CreateOffscreen(const gfxIntSize& size,
|
|||
// Don't want a global context on Android as 1) share groups across 2 threads fail on many Tegra drivers (bug 759225)
|
||||
// and 2) some mobile devices have a very strict limit on global number of GL contexts (bug 754257)
|
||||
// and 3) each EGL context eats 750k on B2G (bug 813783)
|
||||
GLContext *
|
||||
GLContext*
|
||||
GLContextProviderEGL::GetGlobalContext()
|
||||
{
|
||||
return nullptr;
|
||||
|
|
|
@ -1212,14 +1212,22 @@ DONE_CREATING_PIXMAP:
|
|||
return glContext.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderGLX::CreateHeadless()
|
||||
{
|
||||
gfxIntSize dummySize = gfxIntSize(16, 16);
|
||||
nsRefPtr<GLContext> glContext = CreateOffscreenPixmapContext(dummySize);
|
||||
if (!glContext)
|
||||
return nullptr;
|
||||
|
||||
return glContext.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderGLX::CreateOffscreen(const gfxIntSize& size,
|
||||
const SurfaceCaps& caps)
|
||||
{
|
||||
gfxIntSize dummySize = gfxIntSize(16, 16);
|
||||
nsRefPtr<GLContextGLX> glContext =
|
||||
CreateOffscreenPixmapContext(dummySize);
|
||||
|
||||
nsRefPtr<GLContext> glContext = CreateHeadless();
|
||||
if (!glContext)
|
||||
return nullptr;
|
||||
|
||||
|
|
|
@ -60,6 +60,10 @@ public:
|
|||
CreateOffscreen(const gfxIntSize& size,
|
||||
const SurfaceCaps& caps);
|
||||
|
||||
// Just create a context. We'll add offscreen stuff ourselves.
|
||||
static already_AddRefed<GLContext>
|
||||
CreateHeadless();
|
||||
|
||||
/**
|
||||
* Create wrapping Gecko GLContext for external gl context.
|
||||
*
|
||||
|
|
|
@ -22,8 +22,13 @@ GLContextProviderNull::CreateWrappingExisting(void*, void*)
|
|||
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderNull::CreateOffscreen(const gfxIntSize&,
|
||||
const SurfaceCaps&,
|
||||
ContextFlags)
|
||||
const SurfaceCaps&)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderNull::CreateHeadless()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -607,8 +607,7 @@ CreateWindowOffscreenContext()
|
|||
}
|
||||
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderWGL::CreateOffscreen(const gfxIntSize& size,
|
||||
const SurfaceCaps& caps)
|
||||
GLContextProviderWGL::CreateHeadless()
|
||||
{
|
||||
if (!sWGLLib.EnsureInitialized()) {
|
||||
return nullptr;
|
||||
|
@ -636,6 +635,18 @@ GLContextProviderWGL::CreateOffscreen(const gfxIntSize& size,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<GLContext> retGL = glContext;
|
||||
return retGL.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderWGL::CreateOffscreen(const gfxIntSize& size,
|
||||
const SurfaceCaps& caps)
|
||||
{
|
||||
nsRefPtr<GLContext> glContext = CreateHeadless();
|
||||
if (!glContext)
|
||||
return nullptr;
|
||||
|
||||
if (!glContext->InitOffscreen(ToIntSize(size), caps))
|
||||
return nullptr;
|
||||
|
||||
|
|
|
@ -568,6 +568,8 @@ DrawBuffer::Create(GLContext* const gl,
|
|||
pStencilRB = nullptr;
|
||||
}
|
||||
|
||||
GLContext::ScopedLocalErrorCheck localError(gl);
|
||||
|
||||
CreateRenderbuffersForOffscreen(gl, formats, size, caps.antialias,
|
||||
pColorMSRB, pDepthRB, pStencilRB);
|
||||
|
||||
|
@ -578,7 +580,8 @@ DrawBuffer::Create(GLContext* const gl,
|
|||
UniquePtr<DrawBuffer> ret( new DrawBuffer(gl, size, fb, colorMSRB,
|
||||
depthRB, stencilRB) );
|
||||
|
||||
if (!gl->IsFramebufferComplete(fb))
|
||||
GLenum err = localError.GetLocalError();
|
||||
if (err || !gl->IsFramebufferComplete(fb))
|
||||
return false;
|
||||
|
||||
*out_buffer = Move(ret);
|
||||
|
@ -624,6 +627,8 @@ ReadBuffer::Create(GLContext* gl,
|
|||
GLuint* pDepthRB = caps.depth ? &depthRB : nullptr;
|
||||
GLuint* pStencilRB = caps.stencil ? &stencilRB : nullptr;
|
||||
|
||||
GLContext::ScopedLocalErrorCheck localError(gl);
|
||||
|
||||
CreateRenderbuffersForOffscreen(gl, formats, surf->mSize, caps.antialias,
|
||||
nullptr, pDepthRB, pStencilRB);
|
||||
|
||||
|
@ -651,7 +656,9 @@ ReadBuffer::Create(GLContext* gl,
|
|||
|
||||
UniquePtr<ReadBuffer> ret( new ReadBuffer(gl, fb, depthRB,
|
||||
stencilRB, surf) );
|
||||
if (!gl->IsFramebufferComplete(fb)) {
|
||||
|
||||
GLenum err = localError.GetLocalError();
|
||||
if (err || !gl->IsFramebufferComplete(fb)) {
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -144,20 +144,28 @@ ChooseConfig(GLContext* gl,
|
|||
return config;
|
||||
}
|
||||
|
||||
// Returns EGL_NO_SURFACE on error.
|
||||
// Returns `EGL_NO_SURFACE` (`0`) on error.
|
||||
static EGLSurface
|
||||
CreatePBufferSurface(GLLibraryEGL* egl,
|
||||
EGLDisplay display,
|
||||
EGLConfig config,
|
||||
const gfx::IntSize& size)
|
||||
{
|
||||
auto width = size.width;
|
||||
auto height = size.height;
|
||||
|
||||
EGLint attribs[] = {
|
||||
LOCAL_EGL_WIDTH, size.width,
|
||||
LOCAL_EGL_HEIGHT, size.height,
|
||||
LOCAL_EGL_WIDTH, width,
|
||||
LOCAL_EGL_HEIGHT, height,
|
||||
LOCAL_EGL_NONE
|
||||
};
|
||||
|
||||
DebugOnly<EGLint> preCallErr = egl->fGetError();
|
||||
MOZ_ASSERT(preCallErr == LOCAL_EGL_SUCCESS);
|
||||
EGLSurface surface = egl->fCreatePbufferSurface(display, config, attribs);
|
||||
EGLint err = egl->fGetError();
|
||||
if (err != LOCAL_EGL_SUCCESS)
|
||||
return 0;
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
|
|
@ -23,11 +23,19 @@ SharedSurface_Basic::Create(GLContext* gl,
|
|||
const IntSize& size,
|
||||
bool hasAlpha)
|
||||
{
|
||||
UniquePtr<SharedSurface_Basic> ret;
|
||||
gl->MakeCurrent();
|
||||
|
||||
GLContext::ScopedLocalErrorCheck localError(gl);
|
||||
GLuint tex = CreateTexture(gl, formats.color_texInternalFormat,
|
||||
formats.color_texFormat,
|
||||
formats.color_texType,
|
||||
size);
|
||||
GLenum err = localError.GetLocalError();
|
||||
if (err) {
|
||||
gl->fDeleteTextures(1, &tex);
|
||||
return Move(ret);
|
||||
}
|
||||
|
||||
SurfaceFormat format = SurfaceFormat::B8G8R8X8;
|
||||
switch (formats.color_texInternalFormat) {
|
||||
|
@ -46,8 +54,7 @@ SharedSurface_Basic::Create(GLContext* gl,
|
|||
MOZ_CRASH("Unhandled Tex format.");
|
||||
}
|
||||
|
||||
typedef SharedSurface_Basic ptrT;
|
||||
UniquePtr<ptrT> ret( new ptrT(gl, size, hasAlpha, format, tex) );
|
||||
ret.reset( new SharedSurface_Basic(gl, size, hasAlpha, format, tex) );
|
||||
return Move(ret);
|
||||
}
|
||||
|
||||
|
@ -74,11 +81,8 @@ SharedSurface_Basic::SharedSurface_Basic(GLContext* gl,
|
|||
mTex,
|
||||
0);
|
||||
|
||||
GLenum status = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
|
||||
if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
|
||||
mGL->fDeleteFramebuffers(1, &mFB);
|
||||
mFB = 0;
|
||||
}
|
||||
DebugOnly<GLenum> status = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
|
||||
MOZ_ASSERT(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
|
||||
|
||||
int32_t stride = gfx::GetAlignedStride<4>(size.width * BytesPerPixel(format));
|
||||
mData = gfx::Factory::CreateDataSourceSurfaceWithStride(size, format, stride);
|
||||
|
@ -133,14 +137,24 @@ SharedSurface_GLTexture::Create(GLContext* prodGL,
|
|||
|
||||
bool ownsTex = false;
|
||||
|
||||
UniquePtr<SharedSurface_GLTexture> ret;
|
||||
|
||||
if (!tex) {
|
||||
GLContext::ScopedLocalErrorCheck localError(prodGL);
|
||||
|
||||
tex = CreateTextureForOffscreen(prodGL, formats, size);
|
||||
|
||||
GLenum err = localError.GetLocalError();
|
||||
if (err) {
|
||||
prodGL->fDeleteTextures(1, &tex);
|
||||
return Move(ret);
|
||||
}
|
||||
|
||||
ownsTex = true;
|
||||
}
|
||||
|
||||
typedef SharedSurface_GLTexture ptrT;
|
||||
UniquePtr<ptrT> ret( new ptrT(prodGL, consGL, size, hasAlpha, tex,
|
||||
ownsTex) );
|
||||
ret.reset( new SharedSurface_GLTexture(prodGL, consGL, size,
|
||||
hasAlpha, tex, ownsTex) );
|
||||
return Move(ret);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,42 +12,42 @@ namespace gl {
|
|||
|
||||
SurfaceCaps::SurfaceCaps()
|
||||
{
|
||||
Clear();
|
||||
Clear();
|
||||
}
|
||||
|
||||
SurfaceCaps::SurfaceCaps(const SurfaceCaps& other)
|
||||
{
|
||||
*this = other;
|
||||
*this = other;
|
||||
}
|
||||
|
||||
SurfaceCaps&
|
||||
SurfaceCaps::operator=(const SurfaceCaps& other)
|
||||
{
|
||||
any = other.any;
|
||||
color = other.color;
|
||||
alpha = other.alpha;
|
||||
bpp16 = other.bpp16;
|
||||
depth = other.depth;
|
||||
stencil = other.stencil;
|
||||
antialias = other.antialias;
|
||||
preserve = other.preserve;
|
||||
surfaceAllocator = other.surfaceAllocator;
|
||||
any = other.any;
|
||||
color = other.color;
|
||||
alpha = other.alpha;
|
||||
bpp16 = other.bpp16;
|
||||
depth = other.depth;
|
||||
stencil = other.stencil;
|
||||
antialias = other.antialias;
|
||||
preserve = other.preserve;
|
||||
surfaceAllocator = other.surfaceAllocator;
|
||||
|
||||
return *this;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
SurfaceCaps::Clear()
|
||||
{
|
||||
any = false;
|
||||
color = false;
|
||||
alpha = false;
|
||||
bpp16 = false;
|
||||
depth = false;
|
||||
stencil = false;
|
||||
antialias = false;
|
||||
preserve = false;
|
||||
surfaceAllocator = nullptr;
|
||||
any = false;
|
||||
color = false;
|
||||
alpha = false;
|
||||
bpp16 = false;
|
||||
depth = false;
|
||||
stencil = false;
|
||||
antialias = false;
|
||||
preserve = false;
|
||||
surfaceAllocator = nullptr;
|
||||
}
|
||||
|
||||
SurfaceCaps::~SurfaceCaps()
|
||||
|
|
|
@ -105,10 +105,17 @@ Compositor::DrawDiagnostics(DiagnosticFlags aFlags,
|
|||
}
|
||||
|
||||
RenderTargetRect
|
||||
Compositor::ClipRectInLayersCoordinates(RenderTargetIntRect aClip) const {
|
||||
Compositor::ClipRectInLayersCoordinates(Layer* aLayer, RenderTargetIntRect aClip) const {
|
||||
ContainerLayer* parent = aLayer->AsContainerLayer() ? aLayer->AsContainerLayer() : aLayer->GetParent();
|
||||
while (!parent->UseIntermediateSurface() && parent->GetParent()) {
|
||||
parent = parent->GetParent();
|
||||
}
|
||||
|
||||
RenderTargetIntPoint renderTargetOffset = RenderTargetIntRect::FromUntyped(
|
||||
parent->GetEffectiveVisibleRegion().GetBounds()).TopLeft();
|
||||
|
||||
RenderTargetRect result;
|
||||
aClip = aClip + RenderTargetIntPoint(GetCurrentRenderTarget()->GetOrigin().x,
|
||||
GetCurrentRenderTarget()->GetOrigin().y);
|
||||
aClip = aClip + renderTargetOffset;
|
||||
RenderTargetIntSize destSize = RenderTargetIntSize(GetWidgetSize().width,
|
||||
GetWidgetSize().height);
|
||||
|
||||
|
|
|
@ -498,7 +498,7 @@ public:
|
|||
// at the OS level rather than in Gecko.
|
||||
// In addition, the clip rect needs to be offset by the rendering origin.
|
||||
// This becomes important if intermediate surfaces are used.
|
||||
RenderTargetRect ClipRectInLayersCoordinates(RenderTargetIntRect aClip) const;
|
||||
RenderTargetRect ClipRectInLayersCoordinates(Layer* aLayer, RenderTargetIntRect aClip) const;
|
||||
|
||||
protected:
|
||||
void DrawDiagnosticsInternal(DiagnosticFlags aFlags,
|
||||
|
|
|
@ -345,6 +345,8 @@ ClientTiledThebesLayer::RenderLayer()
|
|||
TILING_LOG("TILING %p: Initial low-precision valid region %s\n", this, Stringify(mLowPrecisionValidRegion).c_str());
|
||||
|
||||
nsIntRegion neededRegion = mVisibleRegion;
|
||||
#ifndef MOZ_GFX_OPTIMIZE_MOBILE
|
||||
// This is handled by PadDrawTargetOutFromRegion in TiledContentClient for mobile
|
||||
if (MayResample()) {
|
||||
// If we're resampling then bilinear filtering can read up to 1 pixel
|
||||
// outside of our texture coords. Make the visible region a single rect,
|
||||
|
@ -358,6 +360,7 @@ ClientTiledThebesLayer::RenderLayer()
|
|||
padded.IntersectRect(padded, wholeTiles);
|
||||
neededRegion = padded;
|
||||
}
|
||||
#endif
|
||||
|
||||
nsIntRegion invalidRegion;
|
||||
invalidRegion.Sub(neededRegion, mValidRegion);
|
||||
|
|
|
@ -320,6 +320,7 @@ ClientTiledLayerBuffer::GetContentType(SurfaceMode* aMode) const
|
|||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
#if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK)
|
||||
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
||||
}
|
||||
#else
|
||||
if (!mThebesLayer->GetParent() ||
|
||||
!mThebesLayer->GetParent()->SupportsComponentAlphaChildren() ||
|
||||
|
@ -328,13 +329,13 @@ ClientTiledLayerBuffer::GetContentType(SurfaceMode* aMode) const
|
|||
} else {
|
||||
content = gfxContentType::COLOR;
|
||||
}
|
||||
#endif
|
||||
} else if (mode == SurfaceMode::SURFACE_OPAQUE) {
|
||||
if (mThebesLayer->MayResample()) {
|
||||
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
||||
content = gfxContentType::COLOR_ALPHA;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (aMode) {
|
||||
*aMode = mode;
|
||||
|
@ -1154,12 +1155,6 @@ ClientTiledLayerBuffer::ValidateTile(TileClient aTile,
|
|||
}
|
||||
}
|
||||
|
||||
if (backBuffer->HasInternalBuffer()) {
|
||||
// If our new buffer has an internal buffer, we don't want to keep another
|
||||
// TextureClient around unnecessarily, so discard the back-buffer.
|
||||
aTile.DiscardBackBuffer();
|
||||
}
|
||||
|
||||
// prepare an array of Moz2D tiles that will be painted into in PostValidate
|
||||
gfx::Tile moz2DTile;
|
||||
RefPtr<DrawTarget> dt = backBuffer->BorrowDrawTarget();
|
||||
|
@ -1191,8 +1186,8 @@ ClientTiledLayerBuffer::ValidateTile(TileClient aTile,
|
|||
drawRect.width,
|
||||
drawRect.height);
|
||||
gfx::IntPoint copyTarget(NS_roundf(drawRect.x), NS_roundf(drawRect.y));
|
||||
// Mark the newly updated area as invalid in the front buffer
|
||||
aTile.mInvalidFront.Or(aTile.mInvalidFront, nsIntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height));
|
||||
// Mark the newly updated area as invalid in the back buffer
|
||||
aTile.mInvalidBack.Or(aTile.mInvalidBack, nsIntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height));
|
||||
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
|
||||
|
|
|
@ -176,6 +176,16 @@ ContainerPrepare(ContainerT* aContainer,
|
|||
continue;
|
||||
}
|
||||
|
||||
RenderTargetRect quad = layerToRender->GetLayer()->
|
||||
TransformRectToRenderTarget(LayerPixel::FromUntyped(
|
||||
layerToRender->GetLayer()->GetEffectiveVisibleRegion().GetBounds()));
|
||||
|
||||
Compositor* compositor = aManager->GetCompositor();
|
||||
if (!layerToRender->GetLayer()->AsContainerLayer() &&
|
||||
!quad.Intersects(compositor->ClipRectInLayersCoordinates(layerToRender->GetLayer(), clipRect))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CULLING_LOG("Preparing sublayer %p\n", layerToRender->GetLayer());
|
||||
|
||||
nsIntRegion savedVisibleRegion;
|
||||
|
@ -269,7 +279,7 @@ RenderLayers(ContainerT* aContainer,
|
|||
gfx::Rect clipRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height);
|
||||
compositor->DrawQuad(
|
||||
RenderTargetPixel::ToUnknown(
|
||||
compositor->ClipRectInLayersCoordinates(aClipRect)),
|
||||
compositor->ClipRectInLayersCoordinates(aContainer, aClipRect)),
|
||||
clipRect, effectChain, opacity, Matrix4x4());
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +373,6 @@ RenderIntermediate(ContainerT* aContainer,
|
|||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
compositor->SetRenderTarget(surface);
|
||||
// pre-render all of the layers into our temporary
|
||||
RenderLayers(aContainer, aManager, RenderTargetPixel::FromUntyped(aClipRect));
|
||||
|
|
|
@ -379,16 +379,16 @@ TiledContentHost::Composite(EffectChain& aEffectChain,
|
|||
? gfxPrefs::LowPrecisionOpacity() : 1.0f;
|
||||
|
||||
nsIntRegion tmpRegion;
|
||||
const nsIntRegion* renderRegion;
|
||||
const nsIntRegion* renderRegion = aVisibleRegion;
|
||||
#ifndef MOZ_GFX_OPTIMIZE_MOBILE
|
||||
if (PaintWillResample()) {
|
||||
// If we're resampling, then the texture image will contain exactly the
|
||||
// entire visible region's bounds, and we should draw it all in one quad
|
||||
// to avoid unexpected aliasing.
|
||||
tmpRegion = aVisibleRegion->GetBounds();
|
||||
renderRegion = &tmpRegion;
|
||||
} else {
|
||||
renderRegion = aVisibleRegion;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Render the low and high precision buffers.
|
||||
RenderLayerBuffer(mLowPrecisionTiledBuffer,
|
||||
|
@ -435,8 +435,8 @@ TiledContentHost::RenderTile(const TileHost& aTile,
|
|||
Rect layerQuad(screenBounds.x, screenBounds.y, screenBounds.width, screenBounds.height);
|
||||
RenderTargetRect quad = RenderTargetRect::FromUnknown(aTransform.TransformBounds(layerQuad));
|
||||
|
||||
if (!quad.Intersects(mCompositor->ClipRectInLayersCoordinates(
|
||||
RenderTargetIntRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height)))) {
|
||||
if (!quad.Intersects(mCompositor->ClipRectInLayersCoordinates(mLayer,
|
||||
RenderTargetIntRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1113,23 +1113,25 @@ nsRect nsRegion::GetLargestRectangle (const nsRect& aContainingRect) const {
|
|||
return bestRect;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const nsRegion& m) {
|
||||
stream << "[";
|
||||
|
||||
int n;
|
||||
pixman_box32_t *boxes = pixman_region32_rectangles(const_cast<pixman_region32_t*>(&m.mImpl), &n);
|
||||
for (int i=0; i<n; i++) {
|
||||
if (i != 0) {
|
||||
stream << "; ";
|
||||
}
|
||||
stream << boxes[i].x1 << "," << boxes[i].y1 << "," << boxes[i].x2 << "," << boxes[i].y2;
|
||||
}
|
||||
|
||||
stream << "]";
|
||||
return stream;
|
||||
}
|
||||
|
||||
nsCString
|
||||
nsRegion::ToString() const {
|
||||
nsCString result;
|
||||
result.Append('[');
|
||||
|
||||
int n;
|
||||
pixman_box32_t *boxes = pixman_region32_rectangles(const_cast<pixman_region32_t*>(&mImpl), &n);
|
||||
for (int i=0; i<n; i++) {
|
||||
if (i != 0) {
|
||||
result.AppendLiteral("; ");
|
||||
}
|
||||
result.Append(nsPrintfCString("%d,%d,%d,%d", boxes[i].x1, boxes[i].y1, boxes[i].x2, boxes[i].y2));
|
||||
|
||||
}
|
||||
result.Append(']');
|
||||
|
||||
return result;
|
||||
return nsCString(mozilla::ToString(this).c_str());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <stdint.h> // for uint32_t, uint64_t
|
||||
#include <sys/types.h> // for int32_t
|
||||
#include "gfxCore.h" // for NS_GFX
|
||||
#include "mozilla/ToString.h" // for mozilla::ToString
|
||||
#include "nsCoord.h" // for nscoord
|
||||
#include "nsError.h" // for nsresult
|
||||
#include "nsPoint.h" // for nsIntPoint, nsPoint
|
||||
|
@ -68,6 +69,8 @@ public:
|
|||
return IsEqual(aRgn);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& stream, const nsRegion& m);
|
||||
|
||||
void Swap(nsRegion* aOther)
|
||||
{
|
||||
pixman_region32_t tmp = mImpl;
|
||||
|
@ -462,6 +465,10 @@ public:
|
|||
return IsEqual(aRgn);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& stream, const nsIntRegion& m) {
|
||||
return stream << m.mImpl;
|
||||
}
|
||||
|
||||
void Swap(nsIntRegion* aOther)
|
||||
{
|
||||
mImpl.Swap(&aOther->mImpl);
|
||||
|
|
|
@ -282,3 +282,5 @@
|
|||
// moz-apperance style used in setting proper glass margins
|
||||
#define NS_THEME_WIN_EXCLUDE_GLASS 242
|
||||
|
||||
#define NS_THEME_MAC_VIBRANCY_LIGHT 243
|
||||
#define NS_THEME_MAC_VIBRANCY_DARK 244
|
||||
|
|
|
@ -36,6 +36,27 @@ public:
|
|||
*/
|
||||
gfx3DMatrix(void);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& stream, const gfx3DMatrix& m) {
|
||||
if (m.IsIdentity()) {
|
||||
return stream << "[ I ]";
|
||||
}
|
||||
|
||||
if (m.Is2D()) {
|
||||
return stream << "["
|
||||
<< m._11 << " " << m._12 << "; "
|
||||
<< m._21 << " " << m._22 << "; "
|
||||
<< m._41 << " " << m._42
|
||||
<< "]";
|
||||
}
|
||||
|
||||
return stream << "["
|
||||
<< m._11 << " " << m._12 << " " << m._13 << " " << m._14 << "; "
|
||||
<< m._21 << " " << m._22 << " " << m._23 << " " << m._24 << "; "
|
||||
<< m._31 << " " << m._32 << " " << m._33 << " " << m._34 << "; "
|
||||
<< m._41 << " " << m._42 << " " << m._43 << " " << m._44
|
||||
<< "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Matrix multiplication.
|
||||
*/
|
||||
|
|
|
@ -50,6 +50,18 @@ public:
|
|||
_21(c), _22(d),
|
||||
_31(tx), _32(ty) { }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& stream, const gfxMatrix& m) {
|
||||
if (m.IsIdentity()) {
|
||||
return stream << "[identity]";
|
||||
}
|
||||
|
||||
return stream << "["
|
||||
<< m._11 << " " << m._12
|
||||
<< m._21 << " " << m._22
|
||||
<< m._31 << " " << m._32
|
||||
<< "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-multiplies m onto the matrix.
|
||||
*/
|
||||
|
|
|
@ -36,8 +36,14 @@ if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT':
|
|||
'/security/sandbox/chromium',
|
||||
]
|
||||
USE_LIBS += [
|
||||
'sandbox_s',
|
||||
'sandbox_staticruntime_s',
|
||||
]
|
||||
DELAYLOAD_DLLS += [
|
||||
'mozalloc.dll',
|
||||
'nss3.dll',
|
||||
'xul.dll'
|
||||
]
|
||||
USE_STATIC_LIBS = True
|
||||
|
||||
if CONFIG['_MSC_VER']:
|
||||
# Always enter a Windows program through wmain, whether or not we're
|
||||
|
@ -57,7 +63,9 @@ LDFLAGS += [CONFIG['MOZ_ALLOW_HEAP_EXECUTE_FLAGS']]
|
|||
if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
|
||||
LDFLAGS += ['/HEAP:0x40000']
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
# Windows builds have dll linkage warnings due to USE_STATIC_LIBS
|
||||
if CONFIG['OS_ARCH'] != 'WINNT':
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
|
||||
OS_LIBS += [
|
||||
|
|
|
@ -42,7 +42,7 @@ typedef mozilla::ipc::SharedMemoryBasic::Handle CrossProcessMutexHandle;
|
|||
typedef uintptr_t CrossProcessMutexHandle;
|
||||
#endif
|
||||
|
||||
class NS_COM_GLUE CrossProcessMutex
|
||||
class CrossProcessMutex
|
||||
{
|
||||
public:
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#if defined(XP_WIN)
|
||||
#include <windows.h>
|
||||
#define XRE_DONT_SUPPORT_XPSP2 // this app doesn't ship
|
||||
#include "nsWindowsWMain.cpp"
|
||||
#endif
|
||||
|
||||
|
|
|
@ -45,13 +45,11 @@ WrapperOwner::idOf(JSObject *obj)
|
|||
return objId;
|
||||
}
|
||||
|
||||
int sCPOWProxyHandler;
|
||||
|
||||
class CPOWProxyHandler : public BaseProxyHandler
|
||||
{
|
||||
public:
|
||||
CPOWProxyHandler()
|
||||
: BaseProxyHandler(&sCPOWProxyHandler) {}
|
||||
: BaseProxyHandler(&family) {}
|
||||
virtual ~CPOWProxyHandler() {}
|
||||
|
||||
virtual bool finalizeInBackground(Value priv) const MOZ_OVERRIDE {
|
||||
|
@ -86,9 +84,11 @@ class CPOWProxyHandler : public BaseProxyHandler
|
|||
virtual const char* className(JSContext *cx, HandleObject proxy) const MOZ_OVERRIDE;
|
||||
virtual void finalize(JSFreeOp *fop, JSObject *proxy) const MOZ_OVERRIDE;
|
||||
|
||||
static const char family;
|
||||
static const CPOWProxyHandler singleton;
|
||||
};
|
||||
|
||||
const char CPOWProxyHandler::family = 0;
|
||||
const CPOWProxyHandler CPOWProxyHandler::singleton;
|
||||
|
||||
#define FORWARD(call, args) \
|
||||
|
|
|
@ -601,7 +601,7 @@ JS_IsDeadWrapper(JSObject *obj)
|
|||
return false;
|
||||
}
|
||||
|
||||
return obj->as<ProxyObject>().handler()->family() == &DeadObjectProxy::sDeadObjectFamily;
|
||||
return obj->as<ProxyObject>().handler()->family() == &DeadObjectProxy::family;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -797,6 +797,7 @@ class ScriptedIndirectProxyHandler : public BaseProxyHandler
|
|||
virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) const MOZ_OVERRIDE;
|
||||
virtual bool isScripted() const MOZ_OVERRIDE { return true; }
|
||||
|
||||
static const char family;
|
||||
static const ScriptedIndirectProxyHandler singleton;
|
||||
};
|
||||
|
||||
|
@ -818,10 +819,10 @@ static const Class CallConstructHolder = {
|
|||
} /* anonymous namespace */
|
||||
|
||||
// This variable exists solely to provide a unique address for use as an identifier.
|
||||
static const char sScriptedIndirectProxyHandlerFamily = 0;
|
||||
const char ScriptedIndirectProxyHandler::family = 0;
|
||||
|
||||
ScriptedIndirectProxyHandler::ScriptedIndirectProxyHandler()
|
||||
: BaseProxyHandler(&sScriptedIndirectProxyHandlerFamily)
|
||||
: BaseProxyHandler(&family)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1118,6 +1119,7 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
|
|||
virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
|
||||
virtual bool isScripted() const MOZ_OVERRIDE { return true; }
|
||||
|
||||
static const char family;
|
||||
static const ScriptedDirectProxyHandler singleton;
|
||||
|
||||
// The "proxy extra" slot index in which the handler is stored. Revocable proxies need to set
|
||||
|
@ -1128,9 +1130,6 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
|
|||
static const int REVOKE_SLOT = 0;
|
||||
};
|
||||
|
||||
// This variable exists solely to provide a unique address for use as an identifier.
|
||||
static const char sScriptedDirectProxyHandlerFamily = 0;
|
||||
|
||||
static inline bool
|
||||
IsDataDescriptor(const PropertyDescriptor &desc)
|
||||
{
|
||||
|
@ -1389,7 +1388,7 @@ ArrayToIdVector(JSContext *cx, HandleObject proxy, HandleObject target, HandleVa
|
|||
}
|
||||
|
||||
ScriptedDirectProxyHandler::ScriptedDirectProxyHandler()
|
||||
: DirectProxyHandler(&sScriptedDirectProxyHandlerFamily)
|
||||
: DirectProxyHandler(&family)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -2208,6 +2207,7 @@ ScriptedDirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const C
|
|||
return true;
|
||||
}
|
||||
|
||||
const char ScriptedDirectProxyHandler::family = 0;
|
||||
const ScriptedDirectProxyHandler ScriptedDirectProxyHandler::singleton;
|
||||
|
||||
#define INVOKE_ON_PROTOTYPE(cx, handler, proxy, protoCall) \
|
||||
|
|
|
@ -87,6 +87,14 @@ class JS_FRIEND_API(Wrapper);
|
|||
*/
|
||||
class JS_FRIEND_API(BaseProxyHandler)
|
||||
{
|
||||
/*
|
||||
* Sometimes it's desirable to designate groups of proxy handlers as "similar".
|
||||
* For this, we use the notion of a "family": A consumer-provided opaque pointer
|
||||
* that designates the larger group to which this proxy belongs.
|
||||
*
|
||||
* If it will never be important to differentiate this proxy from others as
|
||||
* part of a distinct group, nullptr may be used instead.
|
||||
*/
|
||||
const void *mFamily;
|
||||
|
||||
/*
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче