Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-08-28 16:13:13 +02:00
commit 6486c70d50
142 changed files with 3056 additions and 1224 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="53a59364ce4f14068034c8d6fe01f4f6b9f78f23">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>
@ -130,7 +130,7 @@
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="197cd9492b9fadaa915c5daf36ff557f8f4a8d1c"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="30d0dfa566651fea8031551e86cec6018b7bbb12"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="57b16fcb790bdf0b53b3c6435a37ccc8ca90ed36"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="aad3e80dea67774aa51ed4e6c054856168dd180b"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="53a59364ce4f14068034c8d6fe01f4f6b9f78f23">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
@ -128,7 +128,7 @@
<!-- Emulator specific things -->
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="72ffdf71c68a96309212eb13d63560d66db14c9e"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="c0e0019a6ec1a6199a9c7bc4ace041259f3b8512"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="5f184e4aa6ad784e20b4c5e6be24db4b9a848087"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="e4b7cd053711ece3cd5616cd4fb7f75c43bce9c0"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="694cecf256122d0cb3b6a1a4efb4b5c7401db223"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="97d63c256a047b491565d624aea1dd5f1f8593ea"/>
<project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "d4afc0a7f72fd7793359b9575ea7c90cd54e2348",
"revision": "6fa1c6e992e9d7169e8e6cd8c714d1983087a87c",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="320650844ec7cba40a70317b761b88b47a8dca0e"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3a838afca295c9db32e1a3ec76d49fb7fe7fd2d2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="39cad6c82122b964f12a66771bfbcc14fb342d9e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3bb61a27cd2941b2ba9b616a11aaa44269210396"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
function first() { var a = "first"; second(); function second() { var a = "second"; } }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -115,6 +115,7 @@ function hasBreakpoint(ctx, line) {
let markers = cm.lineInfo(line).gutterMarkers;
return markers != null &&
markers.breakpoints &&
markers.breakpoints.classList.contains("breakpoint");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,9 +7,6 @@
console.log("start");
console.clear()
console.dirxml()
console.profile()
console.profileEnd()
console.table()
console.log("end");
}
</script>

View File

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

View File

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

View File

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

View File

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

View File

@ -624,6 +624,7 @@ METHOD(Warn, "warn")
METHOD(Error, "error")
METHOD(Exception, "exception")
METHOD(Debug, "debug")
METHOD(Table, "table")
void
Console::Trace(JSContext* aCx)

View File

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

View File

@ -5,8 +5,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ArrayUtils.h"
// On top because they include basictypes.h:
#include "mozilla/dom/SmsFilter.h"
#ifdef XP_WIN
#undef GetClassName
@ -125,7 +123,6 @@
#include "nsIDOMMozSmsMessage.h"
#include "nsIDOMMozMmsMessage.h"
#include "nsIDOMSmsFilter.h"
#include "nsIDOMMozMobileMessageThread.h"
#ifdef MOZ_B2G_FM
@ -348,9 +345,6 @@ static nsDOMClassInfoData sClassInfoData[] = {
NS_DEFINE_CLASSINFO_DATA(MozMmsMessage, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(MozSmsFilter, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(MozMobileMessageThread, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
@ -422,7 +416,6 @@ static const nsConstructorFuncMapData kConstructorFuncMap[] =
{
NS_DEFINE_CONSTRUCTOR_FUNC_DATA(Blob, DOMMultipartFileImpl::NewBlob)
NS_DEFINE_CONSTRUCTOR_FUNC_DATA(File, DOMMultipartFileImpl::NewFile)
NS_DEFINE_CONSTRUCTOR_FUNC_DATA(MozSmsFilter, SmsFilter::NewSmsFilter)
NS_DEFINE_CONSTRUCTOR_FUNC_DATA(XSLTProcessor, XSLTProcessorCtor)
};
#undef NS_DEFINE_CONSTRUCTOR_FUNC_DATA
@ -913,10 +906,6 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozMmsMessage)
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(MozSmsFilter, nsIDOMMozSmsFilter)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsFilter)
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(MozMobileMessageThread, nsIDOMMozMobileMessageThread)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozMobileMessageThread)
DOM_CLASSINFO_MAP_END

View File

@ -55,7 +55,6 @@ DOMCI_CLASS(ModalContentWindow)
DOMCI_CLASS(MozSmsMessage)
DOMCI_CLASS(MozMmsMessage)
DOMCI_CLASS(MozSmsFilter)
DOMCI_CLASS(MozMobileMessageThread)
// @font-face in CSS

View File

@ -2702,12 +2702,16 @@ nsDOMWindowUtils::SetAsyncScrollOffset(nsIDOMNode* aNode,
return NS_ERROR_UNEXPECTED;
}
}
FrameMetrics::ViewID viewId;
if (!nsLayoutUtils::FindIDFor(element, &viewId)) {
return NS_ERROR_UNEXPECTED;
}
ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
if (!forwarder || !forwarder->HasShadowManager()) {
return NS_ERROR_UNEXPECTED;
}
forwarder->GetShadowManager()->SendSetAsyncScrollOffset(
layer->AsShadowableLayer()->GetShadow(), aX, aY);
layer->AsShadowableLayer()->GetShadow(), viewId, aX, aY);
return NS_OK;
}

View File

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

View File

@ -1990,7 +1990,6 @@ addExternalIface('MozObserver', nativeType='nsIObserver', notflattened=True)
addExternalIface('MozRDFCompositeDataSource', nativeType='nsIRDFCompositeDataSource',
notflattened=True)
addExternalIface('MozRDFResource', nativeType='nsIRDFResource', notflattened=True)
addExternalIface('MozSmsFilter', headerFile='nsIDOMSmsFilter.h')
addExternalIface('MozSmsMessage')
addExternalIface('MozTreeBoxObject', nativeType='nsITreeBoxObject',
notflattened=True)

View File

@ -9,7 +9,6 @@ XPIDL_SOURCES += [
'nsIDOMMozMmsMessage.idl',
'nsIDOMMozMobileMessageThread.idl',
'nsIDOMMozSmsMessage.idl',
'nsIDOMSmsFilter.idl',
'nsIMmsService.idl',
'nsIMobileMessageCallback.idl',
'nsIMobileMessageCursorCallback.idl',

View File

@ -1,33 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
[scriptable, builtinclass, uuid(17890b60-0367-45c6-9729-62e5bf349b2b)]
interface nsIDOMMozSmsFilter : nsISupports
{
// A date that can return null.
[implicit_jscontext]
attribute jsval startDate;
// A date that can return null.
[implicit_jscontext]
attribute jsval endDate;
// An array of DOMString that can return null.
[implicit_jscontext]
attribute jsval numbers;
// A DOMString that can return and be set to "sent", "received" or null.
[Null(Empty)]
attribute DOMString delivery;
// A read flag that can return and be set to a boolean or null.
[implicit_jscontext]
attribute jsval read;
// A thread id that can return and be set to a numeric value or null.
[implicit_jscontext]
attribute jsval threadId;
};

View File

@ -12,11 +12,10 @@
%}
interface nsICursorContinueCallback;
interface nsIDOMMozSmsFilter;
interface nsIMobileMessageCallback;
interface nsIMobileMessageCursorCallback;
[scriptable, uuid(8439916f-abc1-4c67-aa45-8a276a0a7855)]
[scriptable, uuid(ead626bc-f5b4-47e1-921c-0b956c9298e0)]
interface nsIMobileMessageDatabaseService : nsISupports
{
[binaryname(GetMessageMoz)]
@ -27,7 +26,16 @@ interface nsIMobileMessageDatabaseService : nsISupports
in uint32_t count,
in nsIMobileMessageCallback request);
nsICursorContinueCallback createMessageCursor(in nsIDOMMozSmsFilter filter,
nsICursorContinueCallback createMessageCursor(in boolean hasStartDate,
in unsigned long long startDate,
in boolean hasEndDate,
in unsigned long long endDate,
[array, size_is(numbersCount)] in wstring numbers,
in uint32_t numbersCount,
[Null(Null), Undefined(Null)] in DOMString delivery,
in boolean hasRead,
in boolean read,
in unsigned long long threadId,
in boolean reverse,
in nsIMobileMessageCursorCallback callback);

View File

@ -26,7 +26,6 @@
#include "nsIObserverService.h"
#include "nsISmsService.h"
#include "nsServiceManagerUtils.h" // For do_GetService()
#include "SmsFilter.h"
#define RECEIVED_EVENT_NAME NS_LITERAL_STRING("received")
#define RETRIEVING_EVENT_NAME NS_LITERAL_STRING("retrieving")
@ -367,7 +366,7 @@ MobileMessageManager::Delete(const Sequence<OwningLongOrMozSmsMessageOrMozMmsMes
}
already_AddRefed<DOMCursor>
MobileMessageManager::GetMessages(nsIDOMMozSmsFilter* aFilter,
MobileMessageManager::GetMessages(const MobileMessageFilter& aFilter,
bool aReverse,
ErrorResult& aRv)
{
@ -378,16 +377,62 @@ MobileMessageManager::GetMessages(nsIDOMMozSmsFilter* aFilter,
return nullptr;
}
nsCOMPtr<nsIDOMMozSmsFilter> filter = aFilter;
if (!filter) {
filter = new SmsFilter();
bool hasStartDate = !aFilter.mStartDate.IsNull();
uint64_t startDate = 0;
if (hasStartDate) {
startDate = aFilter.mStartDate.Value();
}
bool hasEndDate = !aFilter.mEndDate.IsNull();
uint64_t endDate = 0;
if (hasEndDate) {
endDate = aFilter.mEndDate.Value();
}
nsAutoArrayPtr<const char16_t*> ptrNumbers;
uint32_t numbersCount = 0;
if (!aFilter.mNumbers.IsNull() &&
aFilter.mNumbers.Value().Length()) {
const FallibleTArray<nsString>& numbers = aFilter.mNumbers.Value();
uint32_t index;
numbersCount = numbers.Length();
ptrNumbers = new const char16_t* [numbersCount];
for (index = 0; index < numbersCount; index++) {
ptrNumbers[index] = numbers[index].get();
}
}
nsString delivery;
delivery.SetIsVoid(true);
if (!aFilter.mDelivery.IsNull()) {
const uint32_t index = static_cast<uint32_t>(aFilter.mDelivery.Value());
const EnumEntry& entry =
MobileMessageFilterDeliveryValues::strings[index];
delivery.AssignASCII(entry.value, entry.length);
}
bool hasRead = !aFilter.mRead.IsNull();
bool read = false;
if (hasRead) {
read = aFilter.mRead.Value();
}
uint64_t threadId = 0;
if (!aFilter.mThreadId.IsNull()) {
threadId = aFilter.mThreadId.Value();
}
nsRefPtr<MobileMessageCursorCallback> cursorCallback =
new MobileMessageCursorCallback();
nsCOMPtr<nsICursorContinueCallback> continueCallback;
nsresult rv = dbService->CreateMessageCursor(filter, aReverse, cursorCallback,
nsresult rv = dbService->CreateMessageCursor(hasStartDate, startDate,
hasEndDate, endDate,
ptrNumbers, numbersCount,
delivery,
hasRead, read,
threadId,
aReverse, cursorCallback,
getter_AddRefs(continueCallback));
if (NS_FAILED(rv)) {
aRv.Throw(rv);

View File

@ -14,7 +14,6 @@
class nsISmsService;
class nsIDOMMozSmsMessage;
class nsIDOMMozMmsMessage;
class nsIDOMMozSmsFilter;
namespace mozilla {
namespace dom {
@ -23,6 +22,7 @@ class DOMRequest;
class DOMCursor;
struct MmsParameters;
struct MmsSendParameters;
struct MobileMessageFilter;
struct SmsSendParameters;
class MobileMessageManager MOZ_FINAL : public DOMEventTargetHelper
@ -90,7 +90,7 @@ public:
ErrorResult& aRv);
already_AddRefed<DOMCursor>
GetMessages(nsIDOMMozSmsFilter* aFilter,
GetMessages(const MobileMessageFilter& aFilter,
bool aReverse,
ErrorResult& aRv);

View File

@ -1,292 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SmsFilter.h"
#include "jsapi.h"
#include "jsfriendapi.h" // For js_DateGetMsecSinceEpoch.
#include "js/Utility.h"
#include "mozilla/dom/mobilemessage/Constants.h" // For MessageType
#include "mozilla/dom/ToJSValue.h"
#include "nsDOMString.h"
#include "nsError.h"
#include "nsIDOMClassInfo.h"
#include "nsJSUtils.h"
using namespace mozilla::dom::mobilemessage;
DOMCI_DATA(MozSmsFilter, mozilla::dom::SmsFilter)
namespace mozilla {
namespace dom {
NS_INTERFACE_MAP_BEGIN(SmsFilter)
NS_INTERFACE_MAP_ENTRY(nsIDOMMozSmsFilter)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozSmsFilter)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(SmsFilter)
NS_IMPL_RELEASE(SmsFilter)
SmsFilter::SmsFilter()
{
mData.startDate() = 0;
mData.endDate() = 0;
mData.delivery() = eDeliveryState_Unknown;
mData.read() = eReadState_Unknown;
mData.threadId() = 0;
}
SmsFilter::SmsFilter(const SmsFilterData& aData)
: mData(aData)
{
}
/* static */ nsresult
SmsFilter::NewSmsFilter(nsISupports** aSmsFilter)
{
NS_ADDREF(*aSmsFilter = new SmsFilter());
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::GetStartDate(JSContext* aCx, JS::MutableHandle<JS::Value> aStartDate)
{
if (mData.startDate() == 0) {
aStartDate.setNull();
return NS_OK;
}
aStartDate.setObjectOrNull(JS_NewDateObjectMsec(aCx, mData.startDate()));
NS_ENSURE_TRUE(aStartDate.isObject(), NS_ERROR_FAILURE);
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::SetStartDate(JSContext* aCx, JS::Handle<JS::Value> aStartDate)
{
if (aStartDate.isNull()) {
mData.startDate() = 0;
return NS_OK;
}
if (!aStartDate.isObject()) {
return NS_ERROR_INVALID_ARG;
}
JS::Rooted<JSObject*> obj(aCx, &aStartDate.toObject());
if (!JS_ObjectIsDate(aCx, obj)) {
return NS_ERROR_INVALID_ARG;
}
mData.startDate() = js_DateGetMsecSinceEpoch(obj);
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::GetEndDate(JSContext* aCx, JS::MutableHandle<JS::Value> aEndDate)
{
if (mData.endDate() == 0) {
aEndDate.setNull();
return NS_OK;
}
aEndDate.setObjectOrNull(JS_NewDateObjectMsec(aCx, mData.endDate()));
NS_ENSURE_TRUE(aEndDate.isObject(), NS_ERROR_FAILURE);
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::SetEndDate(JSContext* aCx, JS::Handle<JS::Value> aEndDate)
{
if (aEndDate.isNull()) {
mData.endDate() = 0;
return NS_OK;
}
if (!aEndDate.isObject()) {
return NS_ERROR_INVALID_ARG;
}
JS::Rooted<JSObject*> obj(aCx, &aEndDate.toObject());
if (!JS_ObjectIsDate(aCx, obj)) {
return NS_ERROR_INVALID_ARG;
}
mData.endDate() = js_DateGetMsecSinceEpoch(obj);
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::GetNumbers(JSContext* aCx, JS::MutableHandle<JS::Value> aNumbers)
{
uint32_t length = mData.numbers().Length();
if (length == 0) {
aNumbers.setNull();
return NS_OK;
}
if (!ToJSValue(aCx, mData.numbers(), aNumbers)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::SetNumbers(JSContext* aCx, JS::Handle<JS::Value> aNumbers)
{
if (aNumbers.isNull()) {
mData.numbers().Clear();
return NS_OK;
}
if (!aNumbers.isObject()) {
return NS_ERROR_INVALID_ARG;
}
JS::Rooted<JSObject*> obj(aCx, &aNumbers.toObject());
if (!JS_IsArrayObject(aCx, obj)) {
return NS_ERROR_INVALID_ARG;
}
uint32_t size;
MOZ_ALWAYS_TRUE(JS_GetArrayLength(aCx, obj, &size));
nsTArray<nsString> numbers;
for (uint32_t i=0; i<size; ++i) {
JS::Rooted<JS::Value> jsNumber(aCx);
if (!JS_GetElement(aCx, obj, i, &jsNumber)) {
return NS_ERROR_INVALID_ARG;
}
if (!jsNumber.isString()) {
return NS_ERROR_INVALID_ARG;
}
nsAutoJSString number;
if (!number.init(aCx, jsNumber.toString())) {
return NS_ERROR_FAILURE;
}
numbers.AppendElement(number);
}
mData.numbers().Clear();
mData.numbers().AppendElements(numbers);
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::GetDelivery(nsAString& aDelivery)
{
switch (mData.delivery()) {
case eDeliveryState_Received:
aDelivery = DELIVERY_RECEIVED;
break;
case eDeliveryState_Sent:
aDelivery = DELIVERY_SENT;
break;
case eDeliveryState_Unknown:
SetDOMStringToNull(aDelivery);
break;
default:
NS_ASSERTION(false, "We shouldn't get another delivery state!");
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::SetDelivery(const nsAString& aDelivery)
{
if (aDelivery.IsEmpty()) {
mData.delivery() = eDeliveryState_Unknown;
return NS_OK;
}
if (aDelivery.Equals(DELIVERY_RECEIVED)) {
mData.delivery() = eDeliveryState_Received;
return NS_OK;
}
if (aDelivery.Equals(DELIVERY_SENT)) {
mData.delivery() = eDeliveryState_Sent;
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP
SmsFilter::GetRead(JSContext* aCx, JS::MutableHandle<JS::Value> aRead)
{
if (mData.read() == eReadState_Unknown) {
aRead.setNull();
return NS_OK;
}
aRead.setBoolean(mData.read());
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::SetRead(JSContext* aCx, JS::Handle<JS::Value> aRead)
{
if (aRead.isNull()) {
mData.read() = eReadState_Unknown;
return NS_OK;
}
if (!aRead.isBoolean()) {
return NS_ERROR_INVALID_ARG;
}
mData.read() = aRead.toBoolean() ? eReadState_Read : eReadState_Unread;
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::GetThreadId(JSContext* aCx, JS::MutableHandle<JS::Value> aThreadId)
{
if (!mData.threadId()) {
aThreadId.setNull();
return NS_OK;
}
aThreadId.setNumber(static_cast<double>(mData.threadId()));
return NS_OK;
}
NS_IMETHODIMP
SmsFilter::SetThreadId(JSContext* aCx, JS::Handle<JS::Value> aThreadId)
{
if (aThreadId.isNull()) {
mData.threadId() = 0;
return NS_OK;
}
if (!aThreadId.isNumber()) {
return NS_ERROR_INVALID_ARG;
}
double number = aThreadId.toNumber();
uint64_t integer = static_cast<uint64_t>(number);
if (integer == 0 || integer != number) {
return NS_ERROR_INVALID_ARG;
}
mData.threadId() = integer;
return NS_OK;
}
} // namespace dom
} // namespace mozilla

View File

@ -1,43 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_mobilemessage_SmsFilter_h
#define mozilla_dom_mobilemessage_SmsFilter_h
#include "mozilla/dom/mobilemessage/SmsTypes.h"
#include "nsIDOMSmsFilter.h"
#include "mozilla/Attributes.h"
namespace mozilla {
namespace dom {
class SmsFilter MOZ_FINAL : public nsIDOMMozSmsFilter
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMMOZSMSFILTER
SmsFilter();
SmsFilter(const mobilemessage::SmsFilterData& aData);
const mobilemessage::SmsFilterData& GetData() const;
static nsresult NewSmsFilter(nsISupports** aSmsFilter);
private:
~SmsFilter() {}
mobilemessage::SmsFilterData mData;
};
inline const mobilemessage::SmsFilterData&
SmsFilter::GetData() const {
return mData;
}
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_mobilemessage_SmsFilter_h

View File

@ -48,16 +48,7 @@ enum ReadStatus {
eReadStatus_EndGuard
};
// For {Mms,Sms}FilterData.read.
enum ReadState {
eReadState_Unknown = -1,
eReadState_Unread,
eReadState_Read,
// This state should stay at the end.
eReadState_EndGuard
};
// For {Mms,Sms}FilterData.messageClass.
// For {Mms,Sms}MessageData.messageClass.
enum MessageClass {
eMessageClass_Normal = 0,
eMessageClass_Class0,
@ -115,17 +106,6 @@ struct ParamTraits<mozilla::dom::mobilemessage::ReadStatus>
mozilla::dom::mobilemessage::eReadStatus_EndGuard>
{};
/**
* Read state serializer.
*/
template <>
struct ParamTraits<mozilla::dom::mobilemessage::ReadState>
: public ContiguousEnumSerializer<
mozilla::dom::mobilemessage::ReadState,
mozilla::dom::mobilemessage::eReadState_Unknown,
mozilla::dom::mobilemessage::eReadState_EndGuard>
{};
/**
* Message class serializer.
*/

View File

@ -3,7 +3,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SmsFilter.h"
#include "MobileMessageDatabaseService.h"
#include "AndroidBridge.h"
@ -47,7 +46,16 @@ MobileMessageDatabaseService::DeleteMessage(int32_t *aMessageIds,
}
NS_IMETHODIMP
MobileMessageDatabaseService::CreateMessageCursor(nsIDOMMozSmsFilter* aFilter,
MobileMessageDatabaseService::CreateMessageCursor(bool aHasStartDate,
uint64_t aStartDate,
bool aHasEndDate,
uint64_t aEndDate,
const char16_t** aNumbers,
uint32_t aNumbersCount,
const nsAString& aDelivery,
bool aHasRead,
bool aRead,
uint64_t aThreadId,
bool aReverse,
nsIMobileMessageCursorCallback* aCallback,
nsICursorContinueCallback** aResult)

View File

@ -3105,25 +3105,47 @@ MobileMessageDB.prototype = {
}, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]);
},
createMessageCursor: function(filter, reverse, callback) {
createMessageCursor: function(aHasStartDate, aStartDate, aHasEndDate,
aEndDate, aNumbers, aNumbersCount, aDelivery,
aHasRead, aRead, aThreadId, aReverse, aCallback) {
if (DEBUG) {
debug("Creating a message cursor. Filters:" +
" startDate: " + filter.startDate +
" endDate: " + filter.endDate +
" delivery: " + filter.delivery +
" numbers: " + filter.numbers +
" read: " + filter.read +
" threadId: " + filter.threadId +
" reverse: " + reverse);
" startDate: " + (aHasStartDate ? aStartDate : "(null)") +
" endDate: " + (aHasEndDate ? aEndDate : "(null)") +
" delivery: " + aDelivery +
" numbers: " + (aNumbersCount ? aNumbers : "(null)") +
" read: " + (aHasRead ? aRead : "(null)") +
" threadId: " + aThreadId +
" reverse: " + aReverse);
}
let cursor = new GetMessagesCursor(this, callback);
let filter = {};
if (aHasStartDate) {
filter.startDate = aStartDate;
}
if (aHasEndDate) {
filter.endDate = aEndDate;
}
if (aNumbersCount) {
filter.numbers = aNumbers.slice();
}
if (aDelivery !== null) {
filter.delivery = aDelivery;
}
if (aHasRead) {
filter.read = aRead;
}
if (aThreadId) {
filter.threadId = aThreadId;
}
let cursor = new GetMessagesCursor(this, aCallback);
let self = this;
self.newTxn(READ_ONLY, function(error, txn, stores) {
let collector = cursor.collector;
let collect = collector.collect.bind(collector);
FilterSearcherHelper.transact(self, txn, error, filter, reverse, collect);
FilterSearcherHelper.transact(self, txn, error, filter, aReverse, collect);
}, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME]);
return cursor;
@ -3311,11 +3333,11 @@ let FilterSearcherHelper = {
filterTimestamp: function(startDate, endDate, direction, txn, collect) {
let range = null;
if (startDate != null && endDate != null) {
range = IDBKeyRange.bound(startDate.getTime(), endDate.getTime());
range = IDBKeyRange.bound(startDate, endDate);
} else if (startDate != null) {
range = IDBKeyRange.lowerBound(startDate.getTime());
range = IDBKeyRange.lowerBound(startDate);
} else if (endDate != null) {
range = IDBKeyRange.upperBound(endDate.getTime());
range = IDBKeyRange.upperBound(endDate);
}
this.filterIndex("timestamp", range, direction, txn, collect);
},
@ -3330,7 +3352,7 @@ let FilterSearcherHelper = {
* @param error
* Previous error while creating the transaction.
* @param filter
* A SmsFilter object.
* A MobileMessageFilter dictionary.
* @param reverse
* A boolean value indicating whether we should filter message in
* reversed order.
@ -3368,10 +3390,10 @@ let FilterSearcherHelper = {
// than all numeric values.
let startDate = 0, endDate = "";
if (filter.startDate != null) {
startDate = filter.startDate.getTime();
startDate = filter.startDate;
}
if (filter.endDate != null) {
endDate = filter.endDate.getTime();
endDate = filter.endDate;
}
let single, intersectionCollector;

View File

@ -108,8 +108,13 @@ MobileMessageDatabaseService.prototype = {
this.mmdb.deleteMessage(aMessageIds, aLength, aRequest);
},
createMessageCursor: function(aFilter, aReverse, aCallback) {
return this.mmdb.createMessageCursor(aFilter, aReverse, aCallback);
createMessageCursor: function(aHasStartDate, aStartDate, aHasEndDate,
aEndDate, aNumbers, aNumbersCount, aDelivery,
aHasRead, aRead, aThreadId, aReverse, aCallback) {
return this.mmdb.createMessageCursor(aHasStartDate, aStartDate, aHasEndDate,
aEndDate, aNumbers, aNumbersCount,
aDelivery, aHasRead, aRead, aThreadId,
aReverse, aCallback);
},
markMessageRead: function(aMessageId, aValue, aSendReadReport, aRequest) {

View File

@ -8,7 +8,6 @@
#include "nsXULAppAPI.h"
#include "mozilla/dom/mobilemessage/SmsChild.h"
#include "SmsMessage.h"
#include "SmsFilter.h"
#include "nsJSUtils.h"
#include "mozilla/dom/MozMobileMessageManagerBinding.h"
#include "mozilla/dom/BindingUtils.h"
@ -220,13 +219,40 @@ SmsIPCService::DeleteMessage(int32_t *aMessageIds, uint32_t aSize,
}
NS_IMETHODIMP
SmsIPCService::CreateMessageCursor(nsIDOMMozSmsFilter* aFilter,
SmsIPCService::CreateMessageCursor(bool aHasStartDate,
uint64_t aStartDate,
bool aHasEndDate,
uint64_t aEndDate,
const char16_t** aNumbers,
uint32_t aNumbersCount,
const nsAString& aDelivery,
bool aHasRead,
bool aRead,
uint64_t aThreadId,
bool aReverse,
nsIMobileMessageCursorCallback* aCursorCallback,
nsICursorContinueCallback** aResult)
{
const SmsFilterData& data =
SmsFilterData(static_cast<SmsFilter*>(aFilter)->GetData());
SmsFilterData data;
data.hasStartDate() = aHasStartDate;
data.startDate() = aStartDate;
data.hasEndDate() = aHasEndDate;
data.startDate() = aEndDate;
if (aNumbersCount && aNumbers) {
nsTArray<nsString>& numbers = data.numbers();
uint32_t index;
for (index = 0; index < aNumbersCount; index++) {
numbers.AppendElement(aNumbers[index]);
}
}
data.delivery() = aDelivery;
data.hasRead() = aHasRead;
data.read() = aRead;
data.threadId() = aThreadId;
return SendCursorRequest(CreateMessageCursorRequest(data, aReverse),
aCursorCallback, aResult);

View File

@ -14,7 +14,6 @@
#include "SmsMessage.h"
#include "MmsMessage.h"
#include "nsIMobileMessageDatabaseService.h"
#include "SmsFilter.h"
#include "MobileMessageThread.h"
#include "nsIDOMFile.h"
#include "mozilla/dom/ipc/Blob.h"
@ -774,10 +773,31 @@ MobileMessageCursorParent::DoRequest(const CreateMessageCursorRequest& aRequest)
nsCOMPtr<nsIMobileMessageDatabaseService> dbService =
do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
if (dbService) {
nsCOMPtr<nsIDOMMozSmsFilter> filter = new SmsFilter(aRequest.filter());
bool reverse = aRequest.reverse();
const SmsFilterData& filter = aRequest.filter();
rv = dbService->CreateMessageCursor(filter, reverse, this,
const nsTArray<nsString>& numbers = filter.numbers();
nsAutoArrayPtr<const char16_t*> ptrNumbers;
uint32_t numbersCount = numbers.Length();
if (numbersCount) {
uint32_t index;
ptrNumbers = new const char16_t* [numbersCount];
for (index = 0; index < numbersCount; index++) {
ptrNumbers[index] = numbers[index].get();
}
}
rv = dbService->CreateMessageCursor(filter.hasStartDate(),
filter.startDate(),
filter.hasEndDate(),
filter.endDate(),
ptrNumbers, numbersCount,
filter.delivery(),
filter.hasRead(),
filter.read(),
filter.threadId(),
aRequest.reverse(),
this,
getter_AddRefs(mContinueCallback));
}

View File

@ -77,11 +77,14 @@ union MobileMessageData
struct SmsFilterData
{
bool hasStartDate;
uint64_t startDate;
bool hasEndDate;
uint64_t endDate;
nsString[] numbers;
DeliveryState delivery;
ReadState read;
nsString delivery;
bool hasRead;
bool read;
uint64_t threadId;
};

View File

@ -40,7 +40,6 @@ EXPORTS.mozilla.dom += [
'DOMMobileMessageError.h',
'MmsMessage.h',
'MobileMessageManager.h',
'SmsFilter.h',
'SmsMessage.h',
]
@ -57,7 +56,6 @@ UNIFIED_SOURCES += [
'MobileMessageManager.cpp',
'MobileMessageService.cpp',
'MobileMessageThread.cpp',
'SmsFilter.cpp',
'SmsMessage.cpp',
'SmsServicesFactory.cpp',
]

View File

@ -243,18 +243,17 @@ function getMessage(aId) {
* Reject params:
* event -- a DOMEvent
*
* @param aFilter an optional MozSmsFilter instance.
* @param aReverse a boolean value indicating whether the order of the messages
* should be reversed.
* @param aFilter [optional]
* A MobileMessageFilter object.
* @param aReverse [optional]
* A boolean value indicating whether the order of the message should be
* reversed. Default: false.
*
* @return A deferred promise.
*/
function getMessages(aFilter, aReverse) {
let deferred = Promise.defer();
if (!aFilter) {
aFilter = new MozSmsFilter;
}
let messages = [];
let cursor = manager.getMessages(aFilter, aReverse || false);
cursor.onsuccess = function(aEvent) {

View File

@ -328,8 +328,21 @@ function createMmdbCursor(aMmdb, aMethodName) {
*
* @return A deferred promise.
*/
function createMessageCursor(aMmdb, aFilter, aReverse) {
return createMmdbCursor(aMmdb, "createMessageCursor", aFilter, aReverse);
function createMessageCursor(aMmdb, aStartDate = null, aEndDate = null,
aNumbers = null, aDelivery = null, aRead = null,
aThreadId = null, aReverse = false) {
return createMmdbCursor(aMmdb, "createMessageCursor",
aStartDate !== null,
aStartDate || 0,
aEndDate !== null,
aEndDate || 0,
aNumbers || null,
aNumbers && aNumbers.length || 0,
aDelivery || null,
aRead !== null,
aRead || false,
aThreadId || 0,
aReverse || false);
}
/**

View File

@ -24,11 +24,11 @@ function simulateIncomingSms() {
}
function test(aStartDate, aEndDate, aExpectedMessages) {
let filter = new MozSmsFilter();
if (aStartDate) {
let filter = {};
if (aStartDate !== null) {
filter.startDate = aStartDate;
}
if (aEndDate) {
if (aEndDate !== null) {
filter.endDate = aEndDate;
}
@ -64,16 +64,16 @@ startTestCommon(function testCaseMain() {
// Should return all messages.
//
.then(() => log("Testing [startTime, )"))
.then(() => test(new Date(startTime), null, allMessages))
.then(() => test(startTime, null, allMessages))
.then(() => log("Testing (, endTime]"))
.then(() => test(null, new Date(endTime), allMessages))
.then(() => test(null, endTime, allMessages))
.then(() => log("Testing [startTime, endTime]"))
.then(() => test(new Date(startTime), new Date(endTime), allMessages))
.then(() => test(startTime, endTime, allMessages))
// Should return only messages with timestamp <= startTime.
//
.then(() => log("Testing [, startTime)"))
.then(() => test(null, new Date(startTime),
.then(() => test(null, startTime,
reduceMessages(allMessages,
(function(a, b) {
return b <= a;
@ -82,7 +82,7 @@ startTestCommon(function testCaseMain() {
// Should return only messages with timestamp <= startTime + 1.
//
.then(() => log("Testing [, startTime + 1)"))
.then(() => test(null, new Date(startTime + 1),
.then(() => test(null, startTime + 1,
reduceMessages(allMessages,
(function(a, b) {
return b <= a;
@ -91,7 +91,7 @@ startTestCommon(function testCaseMain() {
// Should return only messages with timestamp >= endTime.
//
.then(() => log("Testing [endTime, )"))
.then(() => test(new Date(endTime), null,
.then(() => test(endTime, null,
reduceMessages(allMessages,
(function(a, b) {
return b >= a;
@ -100,7 +100,7 @@ startTestCommon(function testCaseMain() {
// Should return only messages with timestamp >= endTime - 1.
//
.then(() => log("Testing [endTime - 1, )"))
.then(() => test(new Date(endTime - 1), null,
.then(() => test(endTime - 1, null,
reduceMessages(allMessages,
(function(a, b) {
return b >= a;
@ -109,11 +109,11 @@ startTestCommon(function testCaseMain() {
// Should return none.
//
.then(() => log("Testing [endTime + 1, )"))
.then(() => test(new Date(endTime + 1), null, []))
.then(() => test(endTime + 1, null, []))
.then(() => log("Testing [endTime + 1, endTime + 86400000]"))
.then(() => test(new Date(endTime + 1), new Date(endTime + 86400000), []))
.then(() => test(endTime + 1, endTime + 86400000, []))
.then(() => log("Testing (, startTime - 1]"))
.then(() => test(null, new Date(startTime - 1), []))
.then(() => test(null, startTime - 1, []))
.then(() => log("Testing [startTime - 86400000, startTime - 1]"))
.then(() => test(new Date(startTime - 86400000), new Date(startTime - 1), []));
.then(() => test(startTime - 86400000, startTime - 1, []));
});

View File

@ -59,9 +59,6 @@ let tasks = {
};
function getAllMessages(callback, filter, reverse) {
if (!filter) {
filter = new MozSmsFilter;
}
let messages = [];
let request = manager.getMessages(filter, reverse || false);
request.onsuccess = function(event) {
@ -182,9 +179,10 @@ tasks.push(function assignInvalidThreadID() {
tasks.push(function testDeliveryAndNumber() {
log("Checking delivery == sent && number == 5555315550");
let filter = new MozSmsFilter();
filter.delivery = "sent";
filter.numbers = ["5555315550"];
let filter = {
delivery: "sent",
numbers: ["5555315550"],
};
getAllMessages(function(messages) {
// Only { delivery: "sent", receiver: "+15555315550", read: true }
is(messages.length, 1, "message count");
@ -209,9 +207,10 @@ tasks.push(function testDeliveryAndNumber() {
tasks.push(function testDeliveryAndNumberNotFound() {
log("Checking delivery == sent && number == INVALID_NUMBER");
let filter = new MozSmsFilter();
filter.delivery = "sent";
filter.numbers = [INVALID_NUMBER];
let filter = {
delivery: "sent",
numbers: [INVALID_NUMBER],
};
getAllMessages(function(messages) {
is(messages.length, 0, "message count");
@ -221,9 +220,10 @@ tasks.push(function testDeliveryAndNumberNotFound() {
tasks.push(function testDeliveryAndRead() {
log("Checking delivery == received && read == true");
let filter = new MozSmsFilter();
filter.delivery = "received";
filter.read = true;
let filter = {
delivery: "received",
read: true,
}
getAllMessages(function(messages) {
// { delivery: "received", sender: "5555315550", read: true },
// { delivery: "received", sender: "5555315552", read: true },
@ -250,9 +250,10 @@ tasks.push(function testDeliveryAndRead() {
tasks.push(function testDeliveryAndReadNotFound() {
log("Checking delivery == sent && read == false");
let filter = new MozSmsFilter();
filter.delivery = "sent";
filter.read = false;
let filter = {
delivery: "sent",
read: false,
};
getAllMessages(function(messages) {
is(messages.length, 0, "message count");
@ -262,9 +263,10 @@ tasks.push(function testDeliveryAndReadNotFound() {
tasks.push(function testDeliveryAndThreadId() {
log("Checking delivery == received && threadId == " + threadIds[0]);
let filter = new MozSmsFilter();
filter.delivery = "sent";
filter.threadId = threadIds[0];
let filter = {
delivery: "sent",
threadId: threadIds[0],
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", threadId: threadIds[0]}
is(messages.length, 1, "message count");
@ -287,9 +289,10 @@ tasks.push(function testDeliveryAndThreadId() {
tasks.push(function testDeliveryAndThreadIdNotFound() {
log("Checking delivery == sent && threadId == INVALID_THREAD_ID");
let filter = new MozSmsFilter();
filter.delivery = "sent";
filter.threadId = INVALID_THREAD_ID;
let filter = {
delivery: "sent",
threadId: INVALID_THREAD_ID,
};
getAllMessages(function(messages) {
is(messages.length, 0, "message count");
@ -299,9 +302,10 @@ tasks.push(function testDeliveryAndThreadIdNotFound() {
tasks.push(function testNumberAndRead() {
log("Checking number == 5555315550 && read == true");
let filter = new MozSmsFilter();
filter.numbers = ["5555315550"];
filter.read = true;
let filter = {
numbers: ["5555315550"],
read: true,
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "received", sender: "5555315550", read: true }
@ -327,9 +331,10 @@ tasks.push(function testNumberAndRead() {
tasks.push(function testNumberAndReadNotFound() {
log("Checking number == INVALID_NUMBER && read == true");
let filter = new MozSmsFilter();
filter.numbers = [INVALID_NUMBER];
filter.read = true;
let filter = {
numbers: [INVALID_NUMBER],
read: true,
};
getAllMessages(function(messages) {
is(messages.length, 0, "message count");
@ -339,9 +344,10 @@ tasks.push(function testNumberAndReadNotFound() {
tasks.push(function testNumberAndThreadId() {
log("Checking number == 5555315550 && threadId == " + threadIds[0]);
let filter = new MozSmsFilter();
filter.numbers = ["5555315550"];
filter.threadId = threadIds[0];
let filter = {
numbers: ["5555315550"],
threadId: threadIds[0],
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "received", sender: "5555315550", read: true }
@ -367,9 +373,10 @@ tasks.push(function testNumberAndThreadId() {
tasks.push(function testNumberAndThreadIdNotFound() {
log("Checking number == INVALID_NUMBER && threadId == INVALID_THREAD_ID");
let filter = new MozSmsFilter();
filter.numbers = [INVALID_NUMBER];
filter.threadId = INVALID_THREAD_ID;
let filter = {
numbers: [INVALID_NUMBER],
threadId: INVALID_THREAD_ID,
};
getAllMessages(function(messages) {
is(messages.length, 0, "message count");
@ -379,8 +386,9 @@ tasks.push(function testNumberAndThreadIdNotFound() {
tasks.push(function testMultipleNumbers() {
log("Checking number == 5555315550 || number == 5555315551");
let filter = new MozSmsFilter();
filter.numbers = ["5555315550", "5555315551"];
let filter = {
numbers: ["5555315550", "5555315551"],
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "received", sender: "5555315550", read: true }
@ -401,8 +409,9 @@ tasks.push(function testMultipleNumbers() {
tasks.push(function testMultipleNumbersNotFound() {
log("Checking number == INVALID_NUMBER || number == INVALID_NUMBER2");
let filter = new MozSmsFilter();
filter.numbers = [INVALID_NUMBER, INVALID_NUMBER2];
let filter = {
numbers: [INVALID_NUMBER, INVALID_NUMBER2],
};
getAllMessages(function(messages) {
is(messages.length, 0, "message count");
@ -412,9 +421,10 @@ tasks.push(function testMultipleNumbersNotFound() {
tasks.push(function testDeliveryAndMultipleNumbers() {
log("Checking delivery == sent && (number == 5555315550 || number == 5555315551)");
let filter = new MozSmsFilter();
filter.delivery = "sent";
filter.numbers = ["5555315550", "5555315551"];
let filter = {
delivery: "sent",
numbers: ["5555315550", "5555315551"],
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "sent", receiver: "+15555315551", read: true }
@ -434,9 +444,10 @@ tasks.push(function testDeliveryAndMultipleNumbers() {
tasks.push(function testMultipleNumbersAndRead() {
log("Checking (number == 5555315550 || number == 5555315551) && read == true");
let filter = new MozSmsFilter();
filter.numbers = ["5555315550", "5555315551"];
filter.read = true;
let filter = {
numbers: ["5555315550", "5555315551"],
read: true,
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "received", sender: "5555315550", read: true }
@ -457,9 +468,10 @@ tasks.push(function testMultipleNumbersAndRead() {
tasks.push(function testMultipleNumbersAndThreadId() {
log("Checking (number == 5555315550 || number == 5555315551) && threadId == " + threadIds[0]);
let filter = new MozSmsFilter();
filter.numbers = ["5555315550", "5555315551"];
filter.threadId = threadIds[0];
let filter = {
numbers: ["5555315550", "5555315551"],
threadId: threadIds[0],
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "received", sender: "5555315550", read: true }
@ -479,8 +491,9 @@ tasks.push(function testMultipleNumbersAndThreadId() {
tasks.push(function testNationalNumber() {
log("Checking number = 5555315550");
let filter = new MozSmsFilter();
filter.numbers = ["5555315550"];
let filter = {
numbers: ["5555315550"],
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "received", sender: "5555315550", read: true }
@ -498,8 +511,9 @@ tasks.push(function testNationalNumber() {
tasks.push(function testInternationalNumber() {
log("Checking number = +15555315550");
let filter = new MozSmsFilter();
filter.numbers = ["+15555315550"];
let filter = {
numbers: ["+15555315550"],
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "received", sender: "5555315550", read: true }
@ -517,9 +531,10 @@ tasks.push(function testInternationalNumber() {
tasks.push(function testReadAndThreadId() {
log("Checking read == true && threadId == " + threadIds[0]);
let filter = new MozSmsFilter();
filter.read = true;
filter.threadId = threadIds[0];
let filter = {
read: true,
threadId: threadIds[0],
};
getAllMessages(function(messages) {
// { delivery: "sent", receiver: "+15555315550", read: true }
// { delivery: "received", sender: "5555315550", read: true }
@ -543,9 +558,10 @@ tasks.push(function testReadAndThreadId() {
tasks.push(function testReadAndThreadIdNotFound() {
log("Checking read == true && threadId == INVALID_THREAD_ID");
let filter = new MozSmsFilter();
filter.read = true;
filter.threadId = INVALID_THREAD_ID;
let filter = {
read: true,
threadId: INVALID_THREAD_ID,
};
getAllMessages(function(messages) {
is(messages.length, 0, "message count");

View File

@ -28,8 +28,7 @@ function genFailingMms(aReceivers) {
function checkMessage(aNeedle, aValidNumbers) {
log(" Verifying " + aNeedle);
let filter = new MozSmsFilter();
filter.numbers = [aNeedle];
let filter = { numbers: [aNeedle] };
return getMessages(filter)
.then(function(messages) {
// Check the messages are sent to/received from aValidNumbers.

View File

@ -20,9 +20,8 @@ function verifyInitialState() {
function deleteAllMsgs(nextFunction) {
let msgList = new Array();
let filter = new MozSmsFilter;
let cursor = manager.getMessages(filter, false);
let cursor = manager.getMessages();
ok(cursor instanceof DOMCursor,
"cursor is instanceof " + cursor.constructor);
@ -162,11 +161,10 @@ function markMsgRead(smsMsgs) {
}
function getMsgs() {
var filter = new MozSmsFilter();
let foundSmsList = new Array();
// Set filter for read messages
filter.read = true;
let filter = { read: true };
log("Getting the read SMS messages.");
let cursor = manager.getMessages(filter, false);

View File

@ -20,9 +20,8 @@ function verifyInitialState() {
function deleteAllMsgs(nextFunction) {
let msgList = new Array();
let filter = new MozSmsFilter;
let cursor = manager.getMessages(filter, false);
let cursor = manager.getMessages();
ok(cursor instanceof DOMCursor,
"cursor is instanceof " + cursor.constructor);
@ -174,11 +173,10 @@ function sendSms() {
}
function getMsgs() {
var filter = new MozSmsFilter();
let foundSmsList = new Array();
// Set filter for received messages
filter.delivery = "received";
let filter = { delivery: "received" };
log("Getting the received SMS messages.");
let cursor = manager.getMessages(filter, false);

View File

@ -20,9 +20,8 @@ function verifyInitialState() {
function deleteAllMsgs(nextFunction) {
let msgList = new Array();
let filter = new MozSmsFilter;
let cursor = manager.getMessages(filter, false);
let cursor = manager.getMessages();
ok(cursor instanceof DOMCursor,
"cursor is instanceof " + cursor.constructor);
@ -171,11 +170,10 @@ manager.onreceived = function onreceived(event) {
};
function getMsgs() {
var filter = new MozSmsFilter();
let foundSmsList = new Array();
// Set filter for sent messages
filter.delivery = "sent";
let filter = { delivery: "sent" };
log("Getting the sent SMS messages.");
let cursor = manager.getMessages(filter, false);

View File

@ -20,9 +20,8 @@ function verifyInitialState() {
function deleteAllMsgs(nextFunction) {
let msgList = new Array();
let filter = new MozSmsFilter;
let cursor = manager.getMessages(filter, false);
let cursor = manager.getMessages();
ok(cursor instanceof DOMCursor,
"cursor is instanceof " + cursor.constructor);
@ -156,11 +155,10 @@ function markMsgRead() {
}
function getMsgs() {
var filter = new MozSmsFilter();
let foundSmsList = new Array();
// Set filter for read messages
filter.read = false;
let filter = { read: false };
log("Getting the unread SMS messages.");
let cursor = manager.getMessages(filter, false);

View File

@ -56,9 +56,6 @@ let tasks = {
};
function getAllMessages(callback, filter, reverse) {
if (!filter) {
filter = new MozSmsFilter;
}
let messages = [];
let request = manager.getMessages(filter, reverse || false);
request.onsuccess = function(event) {
@ -154,8 +151,7 @@ function checkThread(bodies, lastBody, unreadCount, participants,
}
// Check whether the thread does contain all the messages it supposed to have.
let filter = new MozSmsFilter;
filter.threadId = thread.id;
let filter = { threadId: thread.id };
getAllMessages(function(messages) {
is(messages.length, bodies.length, "messages.length and bodies.length");

View File

@ -44,9 +44,6 @@ let tasks = {
let manager;
function getAllMessages(callback, filter, reverse) {
if (!filter) {
filter = new MozSmsFilter;
}
let messages = [];
let request = manager.getMessages(filter, reverse || false);
request.onsuccess = function(event) {

View File

@ -76,7 +76,7 @@ function test_message_class_0() {
"Message's sentTimestamp should be equal to SENT_TIMESTAMP");
// Make sure the message is not stored.
let cursor = manager.getMessages(null, false);
let cursor = manager.getMessages();
cursor.onsuccess = function onsuccess() {
if (cursor.result) {
// Here we check whether there is any message of the same sender.

View File

@ -165,7 +165,7 @@ function testCreateMessageCursor(aMmdb) {
log("testCreateMessageCursor()");
setStorageFull(true);
return createMessageCursor(aMmdb, {}, false)
return createMessageCursor(aMmdb)
.then(() => setStorageFull(false));
}

View File

@ -630,7 +630,7 @@ function doVerifyDatabase(aMmdb, aExpected) {
is(aExpected.length, 0, "remaining unmatched threads");
// 5) retrieve all messages.
return createMessageCursor(aMmdb, {})
return createMessageCursor(aMmdb)
.then(function(aValues) {
let [errorCode, domMessages] = aValues;
is(errorCode, 0, "errorCode");

View File

@ -44,9 +44,6 @@ let tasks = {
let manager;
function getAllMessages(callback, filter, reverse) {
if (!filter) {
filter = new MozSmsFilter;
}
let messages = [];
let request = manager.getMessages(filter, reverse || false);
request.onsuccess = function(event) {

View File

@ -52,9 +52,6 @@ let tasks = {
};
function getAllMessages(callback, filter, reverse) {
if (!filter) {
filter = new MozSmsFilter;
}
let messages = [];
let request = manager.getMessages(filter, reverse || false);
request.onsuccess = function(event) {

View File

@ -60,9 +60,6 @@ let tasks = {
};
function getAllMessages(callback, filter, reverse) {
if (!filter) {
filter = new MozSmsFilter;
}
let messages = [];
let cursor = manager.getMessages(filter, reverse || false);
cursor.onsuccess = function(event) {

View File

@ -3,4 +3,3 @@ skip-if = e10s
[test_sms_basics.html]
skip-if = toolkit == 'android' #Bug 909036
[test_smsfilter.html]

View File

@ -50,7 +50,6 @@ function checkInterface(aInterface) {
function test() {
checkInterface("SmsMessage");
checkInterface("SmsEvent");
checkInterface("SmsFilter");
// If sms is disabled and permission is removed, sms is disabled.
SpecialPowers.pushPrefEnv({"set": [["dom.sms.enabled", false]]}, function() {

View File

@ -1,90 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test SmsFilter</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
<iframe></iframe>
</div>
<pre id="test">
<script type="application/javascript">
/** Test SmsFilter **/
function throwingCheck(aFilter, aProperty, disallowedValue)
{
disallowedValue.forEach(function(v) {
var exception = false;
try {
aFilter[aProperty] = v;
} catch (e) {
exception = true;
}
ok(exception, "An exception should have been thrown");
});
}
var filter = new MozSmsFilter();
isnot(filter, null, "filter should be set");
is(filter.startDate, null, "Parameters shouldn't be initialized");
is(filter.endDate, null, "Parameters shouldn't be initialized");
is(filter.numbers, null, "Parameters shouldn't be initialized");
is(filter.delivery, null, "Parameters shouldn't be initialized");
is(filter.read, null, "Parameters shouldn't be initialized");
is(filter.threadId, null, "Parameters shouldn't be initialized");
var date = new Date();
filter.startDate = date;
is(filter.startDate, "" + date, "Setters and getters should work!");
filter.startDate = null;
is(filter.startDate, null, "null should revert the value to default");
throwingCheck(filter, 'startDate', [ "foo", 42, [1, 2], function () {}, undefined ]);
filter.endDate = date;
is(filter.endDate, "" + date, "Setters and getters should work!");
filter.endDate = null;
is(filter.endDate, null, "null should revert the value to default");
throwingCheck(filter, 'endDate', [ "foo", 42, [1, 2], function () {}, undefined ]);
var numbers = [ "foo", "bar" ];
filter.numbers = numbers;
is(filter.numbers, "" + numbers, "Setters and getters should work!");
filter.numbers = null;
is(filter.numbers, null, "null should revert the value to default");
throwingCheck(filter, 'numbers', [ "foo", 42, function () {}, new Date(), undefined ]);
filter.delivery = "sent";
is(filter.delivery, "sent", "Setters and getters should work!");
filter.delivery = "received";
is(filter.delivery, "received", "Setters and getters should work!");
filter.delivery = "";
is(filter.delivery, null, "The empty string should revert the value to default.");
filter.delivery = null;
is(filter.delivery, null, "'null' should revert the value to default.");
throwingCheck(filter, 'delivery', [ "foo", 42, [1, 2], function () {}, new Date(), undefined ]);
filter.read = true;
is(filter.read, true, "Setters and getters should work!");
filter.read = false;
is(filter.read, false, "Setters and getters should work!");
filter.read = null;
is(filter.read, null, "'null' should revert the value to default");
throwingCheck(filter, 'read', [ "foo", 0, [1, 2], function () {}, new Date(), undefined ]);
filter.threadId = 1;
is(filter.threadId, 1, "Setters and getters should work!");
filter.threadId = null;
is(filter.threadId, null, "'null' should revert the value to default");
throwingCheck(filter, 'threadId', [ "foo", 0, 1.5, [1, 2], function () {}, new Date(), undefined ]);
</script>
</pre>
</body>
</html>

View File

@ -37,6 +37,7 @@ function doTest() {
"profileEnd": "function",
"assert": "function",
"count": "function",
"table": "function",
"__noSuchMethod__": "function"
};

View File

@ -702,8 +702,6 @@ var interfaceNamesInGlobalScope =
"MozSettingsEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
"MozSmsEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
"MozSmsFilter",
// IMPORTANT: Do not change this list without review from a DOM peer!
"MozSmsMessage",
// IMPORTANT: Do not change this list without review from a DOM peer!

View File

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

View File

@ -5,7 +5,6 @@
*/
interface MozMmsMessage;
interface MozSmsFilter;
interface MozSmsMessage;
dictionary SmsSegmentInfo {
@ -51,6 +50,31 @@ dictionary MmsSendParameters {
// specified under the multi-sim scenario.
};
enum MobileMessageFilterDelivery { "sent", "received" };
dictionary MobileMessageFilter
{
// Close lower bound range for filtering by the message timestamp.
// Time in milliseconds since Epoch.
[EnforceRange] DOMTimeStamp? startDate = null;
// Close upper bound range for filtering by the message timestamp.
// Time in milliseconds since Epoch.
[EnforceRange] DOMTimeStamp? endDate = null;
// An array of string message participant addresses that any of which
// appears or matches a message's sendor or recipients addresses.
sequence<DOMString>? numbers = null;
MobileMessageFilterDelivery? delivery = null;
// Filtering by whether a message has been read or not.
boolean? read = null;
// Filtering by a message's threadId attribute.
[EnforceRange] unsigned long long? threadId = 0;
};
[Pref="dom.sms.enabled"]
interface MozMobileMessageManager : EventTarget
{
@ -111,7 +135,7 @@ interface MozMobileMessageManager : EventTarget
// Iterates through Moz{Mms,Sms}Message.
[Throws]
DOMCursor getMessages(optional MozSmsFilter? filter = null,
DOMCursor getMessages(optional MobileMessageFilter filter,
optional boolean reverse = false);
[Throws]

View File

@ -761,6 +761,7 @@ struct ParamTraits<mozilla::layers::FrameMetrics>
WriteParam(aMsg, aParam.mCompositionBounds);
WriteParam(aMsg, aParam.mRootCompositionSize);
WriteParam(aMsg, aParam.mScrollId);
WriteParam(aMsg, aParam.mScrollParentId);
WriteParam(aMsg, aParam.mResolution);
WriteParam(aMsg, aParam.mCumulativeResolution);
WriteParam(aMsg, aParam.mZoom);
@ -787,6 +788,7 @@ struct ParamTraits<mozilla::layers::FrameMetrics>
ReadParam(aMsg, aIter, &aResult->mCompositionBounds) &&
ReadParam(aMsg, aIter, &aResult->mRootCompositionSize) &&
ReadParam(aMsg, aIter, &aResult->mScrollId) &&
ReadParam(aMsg, aIter, &aResult->mScrollParentId) &&
ReadParam(aMsg, aIter, &aResult->mResolution) &&
ReadParam(aMsg, aIter, &aResult->mCumulativeResolution) &&
ReadParam(aMsg, aIter, &aResult->mZoom) &&

View File

@ -71,6 +71,7 @@ public:
static const ViewID NULL_SCROLL_ID; // This container layer does not scroll.
static const ViewID START_SCROLL_ID = 2; // This is the ID that scrolling subframes
// will begin at.
static const FrameMetrics sNullMetrics; // We often need an empty metrics
FrameMetrics()
: mCompositionBounds(0, 0, 0, 0)
@ -86,6 +87,7 @@ public:
, mIsRoot(false)
, mHasScrollgrab(false)
, mScrollId(NULL_SCROLL_ID)
, mScrollParentId(NULL_SCROLL_ID)
, mScrollOffset(0, 0)
, mZoom(1)
, mUpdateScrollOffset(false)
@ -117,6 +119,7 @@ public:
mPresShellId == aOther.mPresShellId &&
mIsRoot == aOther.mIsRoot &&
mScrollId == aOther.mScrollId &&
mScrollParentId == aOther.mScrollParentId &&
mScrollOffset == aOther.mScrollOffset &&
mHasScrollgrab == aOther.mHasScrollgrab &&
mUpdateScrollOffset == aOther.mUpdateScrollOffset;
@ -404,6 +407,16 @@ public:
mScrollId = scrollId;
}
ViewID GetScrollParentId() const
{
return mScrollParentId;
}
void SetScrollParentId(ViewID aParentId)
{
mScrollParentId = aParentId;
}
void SetRootCompositionSize(const CSSSize& aRootCompositionSize)
{
mRootCompositionSize = aRootCompositionSize;
@ -467,6 +480,9 @@ private:
// A unique ID assigned to each scrollable frame.
ViewID mScrollId;
// The ViewID of the scrollable frame to which overscroll should be handed off.
ViewID mScrollParentId;
// The position of the top-left of the CSS viewport, relative to the document
// (or the document relative to the viewport, if that helps understand it).
//

View File

@ -0,0 +1,406 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef GFX_LAYERMETRICSWRAPPER_H
#define GFX_LAYERMETRICSWRAPPER_H
#include "Layers.h"
namespace mozilla {
namespace layers {
/**
* A wrapper class around a target Layer with that allows user code to
* walk through the FrameMetrics objects on the layer the same way it
* would walk through a ContainerLayer hierarchy. Consider the following
* layer tree:
*
* +---+
* | A |
* +---+
* / | \
* / | \
* / | \
* +---+ +-----+ +---+
* | B | | C | | D |
* +---+ +-----+ +---+
* | FMn |
* | . |
* | . |
* | . |
* | FM1 |
* | FM0 |
* +-----+
* / \
* / \
* +---+ +---+
* | E | | F |
* +---+ +---+
*
* In this layer tree, there are six layers with A being the root and B,D,E,F
* being leaf nodes. Layer C is in the middle and has n+1 FrameMetrics, labelled
* FM0...FMn. FM0 is the FrameMetrics you get by calling c->GetFrameMetrics(0)
* and FMn is the FrameMetrics you can obtain by calling
* c->GetFrameMetrics(c->GetFrameMetricsCount() - 1). This layer tree is
* conceptually equivalent to this one below:
*
* +---+
* | A |
* +---+
* / | \
* / | \
* / | \
* +---+ +-----+ +---+
* | B | | Cn | | D |
* +---+ +-----+ +---+
* |
* .
* .
* .
* |
* +-----+
* | C1 |
* +-----+
* |
* +-----+
* | C0 |
* +-----+
* / \
* / \
* +---+ +---+
* | E | | F |
* +---+ +---+
*
* In this layer tree, the layer C has been expanded into a stack of container
* layers C1...Cn, where C1 has FrameMetrics FM1 and Cn has FrameMetrics Fn.
* Although in this example C (in the first layer tree) and C0 (in the second
* layer tree) are both ContainerLayers (because they have children), they
* do not have to be. They may just be ThebesLayers or ColorLayers, for example,
* which do not have any children. However, the type of C will always be the
* same as the type of C0.
*
* The LayerMetricsWrapper class allows client code to treat the first layer
* tree as though it were the second. That is, instead of client code having
* to iterate through the FrameMetrics objects directly, it can use a
* LayerMetricsWrapper to encapsulate that aspect of the layer tree and just
* walk the tree as if it were a stack of ContainerLayers.
*
* The functions on this class do different things depending on which
* simulated ContainerLayer is being wrapped. For example, if the
* LayerMetricsWrapper is pretending to be C0, the GetNextSibling() function
* will return null even though the underlying layer C does actually have
* a next sibling. The LayerMetricsWrapper pretending to be Cn will return
* D as the next sibling.
*
* Implementation notes:
*
* The AtTopLayer() and AtBottomLayer() functions in this class refer to
* Cn and C0 in the second layer tree above; that is, they are predicates
* to test if the LayerMetricsWrapper is simulating the topmost or bottommost
* layer, as those will have special behaviour.
*
* It is possible to wrap a nullptr in a LayerMetricsWrapper, in which case
* the IsValid() function will return false. This is required to allow
* LayerMetricsWrapper to be a MOZ_STACK_CLASS (desirable because it is used
* in loops and recursion).
*
* This class purposely does not expose the wrapped layer directly to avoid
* user code from accidentally calling functions directly on it. Instead
* any necessary functions should be wrapped in this class. It does expose
* the wrapped layer as a void* for printf purposes.
*
* The implementation may look like it special-cases mIndex == 0 and/or
* GetFrameMetricsCount() == 0. This is an artifact of the fact that both
* mIndex and GetFrameMetricsCount() are uint32_t and GetFrameMetricsCount()
* can return 0 but mIndex cannot store -1. This seems better than the
* alternative of making mIndex a int32_t that can store -1, but then having
* to cast to uint32_t all over the place.
*/
class MOZ_STACK_CLASS LayerMetricsWrapper {
public:
enum StartAt {
TOP,
BOTTOM,
};
LayerMetricsWrapper()
: mLayer(nullptr)
, mIndex(0)
{
}
explicit LayerMetricsWrapper(Layer* aRoot, StartAt aStart = StartAt::TOP)
: mLayer(aRoot)
, mIndex(0)
{
if (!mLayer) {
return;
}
switch (aStart) {
case StartAt::TOP:
mIndex = mLayer->GetFrameMetricsCount();
if (mIndex > 0) {
mIndex--;
}
break;
case StartAt::BOTTOM:
mIndex = 0;
break;
default:
MOZ_ASSERT_UNREACHABLE("Unknown startAt value");
break;
}
}
explicit LayerMetricsWrapper(Layer* aLayer, uint32_t aMetricsIndex)
: mLayer(aLayer)
, mIndex(aMetricsIndex)
{
MOZ_ASSERT(mLayer);
MOZ_ASSERT(mIndex == 0 || mIndex < mLayer->GetFrameMetricsCount());
}
bool IsValid() const
{
return mLayer != nullptr;
}
MOZ_EXPLICIT_CONVERSION operator bool() const
{
return IsValid();
}
bool IsScrollInfoLayer() const
{
MOZ_ASSERT(IsValid());
// If we are not at the bottommost layer then it's
// a stack of container layers all the way down to
// mLayer, which we can ignore. We only care about
// non-container descendants.
return Metrics().IsScrollable()
&& mLayer->AsContainerLayer()
&& !mLayer->GetFirstChild();
}
LayerMetricsWrapper GetParent() const
{
MOZ_ASSERT(IsValid());
if (!AtTopLayer()) {
return LayerMetricsWrapper(mLayer, mIndex + 1);
}
if (mLayer->GetParent()) {
return LayerMetricsWrapper(mLayer->GetParent(), StartAt::BOTTOM);
}
return LayerMetricsWrapper(nullptr);
}
LayerMetricsWrapper GetFirstChild() const
{
MOZ_ASSERT(IsValid());
if (!AtBottomLayer()) {
return LayerMetricsWrapper(mLayer, mIndex - 1);
}
return LayerMetricsWrapper(mLayer->GetFirstChild());
}
LayerMetricsWrapper GetLastChild() const
{
MOZ_ASSERT(IsValid());
if (!AtBottomLayer()) {
return LayerMetricsWrapper(mLayer, mIndex - 1);
}
return LayerMetricsWrapper(mLayer->GetLastChild());
}
LayerMetricsWrapper GetPrevSibling() const
{
MOZ_ASSERT(IsValid());
if (AtTopLayer()) {
return LayerMetricsWrapper(mLayer->GetPrevSibling());
}
return LayerMetricsWrapper(nullptr);
}
LayerMetricsWrapper GetNextSibling() const
{
MOZ_ASSERT(IsValid());
if (AtTopLayer()) {
return LayerMetricsWrapper(mLayer->GetNextSibling());
}
return LayerMetricsWrapper(nullptr);
}
const FrameMetrics& Metrics() const
{
MOZ_ASSERT(IsValid());
if (mIndex >= mLayer->GetFrameMetricsCount()) {
return FrameMetrics::sNullMetrics;
}
return mLayer->GetFrameMetrics(mIndex);
}
AsyncPanZoomController* GetApzc() const
{
MOZ_ASSERT(IsValid());
if (mIndex >= mLayer->GetFrameMetricsCount()) {
return nullptr;
}
return mLayer->GetAsyncPanZoomController(mIndex);
}
void SetApzc(AsyncPanZoomController* aApzc) const
{
MOZ_ASSERT(IsValid());
if (mLayer->GetFrameMetricsCount() == 0) {
MOZ_ASSERT(mIndex == 0);
MOZ_ASSERT(aApzc == nullptr);
return;
}
MOZ_ASSERT(mIndex < mLayer->GetFrameMetricsCount());
mLayer->SetAsyncPanZoomController(mIndex, aApzc);
}
const char* Name() const
{
MOZ_ASSERT(IsValid());
if (AtBottomLayer()) {
return mLayer->Name();
}
return "DummyContainerLayer";
}
LayerManager* Manager() const
{
MOZ_ASSERT(IsValid());
return mLayer->Manager();
}
gfx::Matrix4x4 GetTransform() const
{
MOZ_ASSERT(IsValid());
if (AtBottomLayer()) {
return mLayer->GetTransform();
}
return gfx::Matrix4x4();
}
RefLayer* AsRefLayer() const
{
MOZ_ASSERT(IsValid());
if (AtBottomLayer()) {
return mLayer->AsRefLayer();
}
return nullptr;
}
nsIntRegion GetVisibleRegion() const
{
MOZ_ASSERT(IsValid());
if (AtBottomLayer()) {
return mLayer->GetVisibleRegion();
}
nsIntRegion region = mLayer->GetVisibleRegion();
region.Transform(gfx::To3DMatrix(mLayer->GetTransform()));
return region;
}
const nsIntRect* GetClipRect() const
{
MOZ_ASSERT(IsValid());
return mLayer->GetClipRect();
}
const std::string& GetContentDescription() const
{
MOZ_ASSERT(IsValid());
return mLayer->GetContentDescription();
}
// Expose an opaque pointer to the layer. Mostly used for printf
// purposes. This is not intended to be a general-purpose accessor
// for the underlying layer.
const void* GetLayer() const
{
MOZ_ASSERT(IsValid());
return (void*)mLayer;
}
bool operator==(const LayerMetricsWrapper& aOther) const
{
return mLayer == aOther.mLayer
&& mIndex == aOther.mIndex;
}
bool operator!=(const LayerMetricsWrapper& aOther) const
{
return !(*this == aOther);
}
static const FrameMetrics& TopmostScrollableMetrics(Layer* aLayer)
{
for (uint32_t i = aLayer->GetFrameMetricsCount(); i > 0; i--) {
if (aLayer->GetFrameMetrics(i - 1).IsScrollable()) {
return aLayer->GetFrameMetrics(i - 1);
}
}
return FrameMetrics::sNullMetrics;
}
static const FrameMetrics& BottommostScrollableMetrics(Layer* aLayer)
{
for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
if (aLayer->GetFrameMetrics(i).IsScrollable()) {
return aLayer->GetFrameMetrics(i);
}
}
return FrameMetrics::sNullMetrics;
}
static const FrameMetrics& BottommostMetrics(Layer* aLayer)
{
if (aLayer->GetFrameMetricsCount() > 0) {
return aLayer->GetFrameMetrics(0);
}
return FrameMetrics::sNullMetrics;
}
private:
bool AtBottomLayer() const
{
return mIndex == 0;
}
bool AtTopLayer() const
{
return mLayer->GetFrameMetricsCount() == 0 || mIndex == mLayer->GetFrameMetricsCount() - 1;
}
private:
Layer* mLayer;
uint32_t mIndex;
};
}
}
#endif /* GFX_LAYERMETRICSWRAPPER_H */

View File

@ -27,6 +27,7 @@
#include "mozilla/layers/Compositor.h" // for Compositor
#include "mozilla/layers/CompositorTypes.h"
#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite
#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
#include "mozilla/layers/LayersMessages.h" // for TransformFunction, etc
#include "nsAString.h"
#include "nsCSSValue.h" // for nsCSSValue::Array, etc
@ -47,16 +48,51 @@ FILEOrDefault(FILE* aFile)
typedef FrameMetrics::ViewID ViewID;
const ViewID FrameMetrics::NULL_SCROLL_ID = 0;
const FrameMetrics FrameMetrics::sNullMetrics;
using namespace mozilla::gfx;
//--------------------------------------------------
// LayerManager
Layer*
LayerManager::GetPrimaryScrollableLayer()
FrameMetrics::ViewID
LayerManager::GetRootScrollableLayerId()
{
if (!mRoot) {
return nullptr;
return FrameMetrics::NULL_SCROLL_ID;
}
nsTArray<LayerMetricsWrapper> queue;
queue.AppendElement(LayerMetricsWrapper(mRoot));
while (queue.Length()) {
LayerMetricsWrapper layer = queue[0];
queue.RemoveElementAt(0);
const FrameMetrics& frameMetrics = layer.Metrics();
if (frameMetrics.IsScrollable()) {
return frameMetrics.GetScrollId();
}
LayerMetricsWrapper child = layer.GetFirstChild();
while (child) {
queue.AppendElement(child);
child = child.GetNextSibling();
}
}
return FrameMetrics::NULL_SCROLL_ID;
}
void
LayerManager::GetRootScrollableLayers(nsTArray<Layer*>& aArray)
{
if (!mRoot) {
return;
}
FrameMetrics::ViewID rootScrollableId = GetRootScrollableLayerId();
if (rootScrollableId == FrameMetrics::NULL_SCROLL_ID) {
aArray.AppendElement(mRoot);
return;
}
nsTArray<Layer*> queue;
@ -65,21 +101,15 @@ LayerManager::GetPrimaryScrollableLayer()
Layer* layer = queue[0];
queue.RemoveElementAt(0);
const FrameMetrics& frameMetrics = layer->GetFrameMetrics();
if (frameMetrics.IsScrollable()) {
return layer;
if (LayerMetricsWrapper::TopmostScrollableMetrics(layer).GetScrollId() == rootScrollableId) {
aArray.AppendElement(layer);
continue;
}
if (ContainerLayer* containerLayer = layer->AsContainerLayer()) {
Layer* child = containerLayer->GetFirstChild();
while (child) {
queue.AppendElement(child);
child = child->GetNextSibling();
}
for (Layer* child = layer->GetFirstChild(); child; child = child->GetNextSibling()) {
queue.AppendElement(child);
}
}
return mRoot;
}
void
@ -95,18 +125,13 @@ LayerManager::GetScrollableLayers(nsTArray<Layer*>& aArray)
Layer* layer = queue.LastElement();
queue.RemoveElementAt(queue.Length() - 1);
const FrameMetrics& frameMetrics = layer->GetFrameMetrics();
if (frameMetrics.IsScrollable()) {
if (layer->HasScrollableFrameMetrics()) {
aArray.AppendElement(layer);
continue;
}
if (ContainerLayer* containerLayer = layer->AsContainerLayer()) {
Layer* child = containerLayer->GetFirstChild();
while (child) {
queue.AppendElement(child);
child = child->GetNextSibling();
}
for (Layer* child = layer->GetFirstChild(); child; child = child->GetNextSibling()) {
queue.AppendElement(child);
}
}
}
@ -170,7 +195,6 @@ Layer::Layer(LayerManager* aManager, void* aImplData) :
mPrevSibling(nullptr),
mImplData(aImplData),
mMaskLayer(nullptr),
mScrollHandoffParentId(FrameMetrics::NULL_SCROLL_ID),
mPostXScale(1.0f),
mPostYScale(1.0f),
mOpacity(1.0),
@ -449,20 +473,28 @@ Layer::SetAnimations(const AnimationArray& aAnimations)
}
void
Layer::SetAsyncPanZoomController(AsyncPanZoomController *controller)
Layer::SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller)
{
mAPZC = controller;
MOZ_ASSERT(aIndex < GetFrameMetricsCount());
mApzcs[aIndex] = controller;
}
AsyncPanZoomController*
Layer::GetAsyncPanZoomController() const
Layer::GetAsyncPanZoomController(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < GetFrameMetricsCount());
#ifdef DEBUG
if (mAPZC) {
MOZ_ASSERT(GetFrameMetrics().IsScrollable());
if (mApzcs[aIndex]) {
MOZ_ASSERT(GetFrameMetrics(aIndex).IsScrollable());
}
#endif
return mAPZC;
return mApzcs[aIndex];
}
void
Layer::FrameMetricsChanged()
{
mApzcs.SetLength(GetFrameMetricsCount());
}
void
@ -663,6 +695,33 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect,
return currentClip.Intersect(scissor);
}
const FrameMetrics&
Layer::GetFrameMetrics(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < GetFrameMetricsCount());
return mFrameMetrics[aIndex];
}
bool
Layer::HasScrollableFrameMetrics() const
{
for (uint32_t i = 0; i < GetFrameMetricsCount(); i++) {
if (GetFrameMetrics(i).IsScrollable()) {
return true;
}
}
return false;
}
bool
Layer::IsScrollInfoLayer() const
{
// A scrollable container layer with no children
return AsContainerLayer()
&& HasScrollableFrameMetrics()
&& !GetFirstChild();
}
const Matrix4x4
Layer::GetTransform() const
{
@ -1461,11 +1520,11 @@ Layer::PrintInfo(std::stringstream& aStream, const char* aPrefix)
if (mMaskLayer) {
aStream << nsPrintfCString(" [mMaskLayer=%p]", mMaskLayer.get()).get();
}
if (!mFrameMetrics.IsDefault()) {
AppendToString(aStream, mFrameMetrics, " [metrics=", "]");
}
if (mScrollHandoffParentId != FrameMetrics::NULL_SCROLL_ID) {
aStream << nsPrintfCString(" [scrollParent=%llu]", mScrollHandoffParentId).get();
for (uint32_t i = 0; i < mFrameMetrics.Length(); i++) {
if (!mFrameMetrics[i].IsDefault()) {
aStream << nsPrintfCString(" [metrics%d=", i).get();
AppendToString(aStream, mFrameMetrics[i], "", "]");
}
}
}

View File

@ -78,6 +78,7 @@ class AsyncPanZoomController;
class ClientLayerManager;
class CommonLayerAttributes;
class Layer;
class LayerMetricsWrapper;
class ThebesLayer;
class ContainerLayer;
class ImageLayer;
@ -336,10 +337,20 @@ public:
/**
* Does a breadth-first search from the root layer to find the first
* scrollable layer.
* scrollable layer, and returns its ViewID. Note that there may be
* other layers in the tree which share the same ViewID.
* Can be called any time.
*/
Layer* GetPrimaryScrollableLayer();
FrameMetrics::ViewID GetRootScrollableLayerId();
/**
* Does a breadth-first search from the root layer to find the first
* scrollable layer, and returns all the layers that have that ViewID
* as the first scrollable metrics in their ancestor chain. If no
* scrollable layers are found it just returns the root of the tree if
* there is one.
*/
void GetRootScrollableLayers(nsTArray<Layer*>& aArray);
/**
* Returns a list of all descendant layers for which
@ -818,29 +829,44 @@ public:
/**
* CONSTRUCTION PHASE ONLY
* Set the (sub)document metrics used to render the Layer subtree
* rooted at this.
* rooted at this. Note that a layer may have multiple FrameMetrics
* objects; calling this function will remove all of them and replace
* them with the provided FrameMetrics. See the documentation for
* SetFrameMetrics(const nsTArray<FrameMetrics>&) for more details.
*/
void SetFrameMetrics(const FrameMetrics& aFrameMetrics)
{
if (mFrameMetrics != aFrameMetrics) {
if (mFrameMetrics.Length() != 1 || mFrameMetrics[0] != aFrameMetrics) {
MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FrameMetrics", this));
mFrameMetrics = aFrameMetrics;
mFrameMetrics.ReplaceElementsAt(0, mFrameMetrics.Length(), aFrameMetrics);
FrameMetricsChanged();
Mutated();
}
}
/**
* CONSTRUCTION PHASE ONLY
* Set the ViewID of the ContainerLayer to which overscroll should be handed
* off. A value of NULL_SCROLL_ID means that the default handoff-parent-finding
* behaviour should be used (i.e. walk up the layer tree to find the next
* scrollable ancestor layer).
* Set the (sub)document metrics used to render the Layer subtree
* rooted at this. There might be multiple metrics on this layer
* because the layer may, for example, be contained inside multiple
* nested scrolling subdocuments. In general a Layer having multiple
* FrameMetrics objects is conceptually equivalent to having a stack
* of ContainerLayers that have been flattened into this Layer.
* See the documentation in LayerMetricsWrapper.h for a more detailed
* explanation of this conceptual equivalence.
*
* Note also that there is actually a many-to-many relationship between
* Layers and FrameMetrics, because multiple Layers may have identical
* FrameMetrics objects. This happens when those layers belong to the
* same scrolling subdocument and therefore end up with the same async
* transform when they are scrolled by the APZ code.
*/
void SetScrollHandoffParentId(FrameMetrics::ViewID aScrollParentId)
void SetFrameMetrics(const nsTArray<FrameMetrics>& aMetricsArray)
{
if (mScrollHandoffParentId != aScrollParentId) {
MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ScrollHandoffParentId", this));
mScrollHandoffParentId = aScrollParentId;
if (mFrameMetrics != aMetricsArray) {
MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FrameMetrics", this));
mFrameMetrics = aMetricsArray;
FrameMetricsChanged();
Mutated();
}
}
@ -1173,8 +1199,11 @@ public:
const nsIntRect* GetClipRect() { return mUseClipRect ? &mClipRect : nullptr; }
uint32_t GetContentFlags() { return mContentFlags; }
const nsIntRegion& GetVisibleRegion() const { return mVisibleRegion; }
const FrameMetrics& GetFrameMetrics() const { return mFrameMetrics; }
FrameMetrics::ViewID GetScrollHandoffParentId() const { return mScrollHandoffParentId; }
const FrameMetrics& GetFrameMetrics(uint32_t aIndex) const;
uint32_t GetFrameMetricsCount() const { return mFrameMetrics.Length(); }
const nsTArray<FrameMetrics>& GetAllFrameMetrics() { return mFrameMetrics; }
bool HasScrollableFrameMetrics() const;
bool IsScrollInfoLayer() const;
const EventRegions& GetEventRegions() const { return mEventRegions; }
ContainerLayer* GetParent() { return mParent; }
Layer* GetNextSibling() { return mNextSibling; }
@ -1474,9 +1503,16 @@ public:
// These functions allow attaching an AsyncPanZoomController to this layer,
// and can be used anytime.
// A layer has an APZC only-if GetFrameMetrics().IsScrollable()
void SetAsyncPanZoomController(AsyncPanZoomController *controller);
AsyncPanZoomController* GetAsyncPanZoomController() const;
// A layer has an APZC at index aIndex only-if GetFrameMetrics(aIndex).IsScrollable();
// attempting to get an APZC for a non-scrollable metrics will return null.
// The aIndex for these functions must be less than GetFrameMetricsCount().
void SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller);
AsyncPanZoomController* GetAsyncPanZoomController(uint32_t aIndex) const;
// The FrameMetricsChanged function is used internally to ensure the APZC array length
// matches the frame metrics array length.
private:
void FrameMetricsChanged();
public:
void ApplyPendingUpdatesForThisTransaction();
@ -1580,8 +1616,7 @@ protected:
nsRefPtr<Layer> mMaskLayer;
gfx::UserData mUserData;
nsIntRegion mVisibleRegion;
FrameMetrics mFrameMetrics;
FrameMetrics::ViewID mScrollHandoffParentId;
nsTArray<FrameMetrics> mFrameMetrics;
EventRegions mEventRegions;
gfx::Matrix4x4 mTransform;
// A mutation of |mTransform| that we've queued to be applied at the
@ -1601,7 +1636,7 @@ protected:
nsIntRect mClipRect;
nsIntRect mTileSourceRect;
nsIntRegion mInvalidRegion;
nsRefPtr<AsyncPanZoomController> mAPZC;
nsTArray<nsRefPtr<AsyncPanZoomController> > mApzcs;
uint32_t mContentFlags;
bool mUseClipRect;
bool mUseTileSourceRect;

View File

@ -125,6 +125,9 @@ AppendToString(std::stringstream& aStream, const FrameMetrics& m,
AppendToString(aStream, m.mCriticalDisplayPort, " cdp=");
if (!detailed) {
AppendToString(aStream, m.GetScrollId(), " scrollId=");
if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) {
AppendToString(aStream, m.GetScrollParentId(), " scrollParent=");
}
aStream << nsPrintfCString(" z=%.3f }", m.GetZoom().scale).get();
} else {
AppendToString(aStream, m.GetDisplayPortMargins(), " dpm=");
@ -137,6 +140,7 @@ AppendToString(std::stringstream& aStream, const FrameMetrics& m,
m.mTransformScale.scale).get();
aStream << nsPrintfCString(" u=(%d %lu)",
m.GetScrollOffsetUpdated(), m.GetScrollGeneration()).get();
AppendToString(aStream, m.GetScrollParentId(), " p=");
aStream << nsPrintfCString(" i=(%ld %lld) }",
m.GetPresShellId(), m.GetScrollId()).get();
}

View File

@ -12,6 +12,7 @@
#include "mozilla/gfx/Point.h" // for Point
#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
#include "mozilla/layers/AsyncPanZoomController.h"
#include "mozilla/layers/LayerMetricsWrapper.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/mozalloc.h" // for operator new
#include "mozilla/TouchEvents.h"
@ -160,7 +161,8 @@ APZCTreeManager::UpdatePanZoomControllerTree(CompositorParent* aCompositor,
if (aRoot) {
mApzcTreeLog << "[start]\n";
UpdatePanZoomControllerTree(state, aRoot,
LayerMetricsWrapper root(aRoot);
UpdatePanZoomControllerTree(state, root,
// aCompositor is null in gtest scenarios
aCompositor ? aCompositor->RootLayerTreeId() : 0,
Matrix4x4(), nullptr, nullptr, nsIntRegion());
@ -205,13 +207,13 @@ ComputeTouchSensitiveRegion(GeckoContentController* aController,
}
AsyncPanZoomController*
APZCTreeManager::PrepareAPZCForLayer(const Layer* aLayer,
APZCTreeManager::PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
const FrameMetrics& aMetrics,
uint64_t aLayersId,
const gfx::Matrix4x4& aAncestorTransform,
const nsIntRegion& aObscured,
AsyncPanZoomController*& aOutParent,
AsyncPanZoomController*& aOutNextSibling,
AsyncPanZoomController* aNextSibling,
TreeBuildingState& aState)
{
if (!aMetrics.IsScrollable()) {
@ -237,13 +239,13 @@ APZCTreeManager::PrepareAPZCForLayer(const Layer* aLayer,
if (!insertResult.second) {
apzc = insertResult.first->second;
}
APZCTM_LOG("Found APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer, guid.mLayersId, guid.mScrollId);
APZCTM_LOG("Found APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer.GetLayer(), guid.mLayersId, guid.mScrollId);
// If we haven't encountered a layer already with the same metrics, then we need to
// do the full reuse-or-make-an-APZC algorithm, which is contained inside the block
// below.
if (apzc == nullptr) {
apzc = aLayer->GetAsyncPanZoomController();
apzc = aLayer.GetApzc();
// If the content represented by the scrollable layer has changed (which may
// be possible because of DLBI heuristics) then we don't want to keep using
@ -290,11 +292,10 @@ APZCTreeManager::PrepareAPZCForLayer(const Layer* aLayer,
apzc->SetPrevSibling(nullptr);
apzc->SetLastChild(nullptr);
}
APZCTM_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer, aLayersId, aMetrics.GetScrollId());
APZCTM_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer.GetLayer(), aLayersId, aMetrics.GetScrollId());
apzc->NotifyLayersUpdated(aMetrics,
aState.mIsFirstPaint && (aLayersId == aState.mOriginatingLayersId));
apzc->SetScrollHandoffParentId(aLayer->GetScrollHandoffParentId());
nsIntRegion unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
apzc->SetLayerHitTestData(unobscured, aAncestorTransform);
@ -304,13 +305,13 @@ APZCTreeManager::PrepareAPZCForLayer(const Layer* aLayer,
mApzcTreeLog << "APZC " << guid
<< "\tcb=" << aMetrics.mCompositionBounds
<< "\tsr=" << aMetrics.mScrollableRect
<< (aLayer->GetVisibleRegion().IsEmpty() ? "\tscrollinfo" : "")
<< (aLayer.GetVisibleRegion().IsEmpty() ? "\tscrollinfo" : "")
<< (apzc->HasScrollgrab() ? "\tscrollgrab" : "")
<< "\t" << aLayer->GetContentDescription();
<< "\t" << aLayer.GetContentDescription();
// Bind the APZC instance into the tree of APZCs
if (aOutNextSibling) {
aOutNextSibling->SetPrevSibling(apzc);
if (aNextSibling) {
aNextSibling->SetPrevSibling(apzc);
} else if (aOutParent) {
aOutParent->SetLastChild(apzc);
} else {
@ -377,7 +378,8 @@ APZCTreeManager::PrepareAPZCForLayer(const Layer* aLayer,
AsyncPanZoomController*
APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
Layer* aLayer, uint64_t aLayersId,
const LayerMetricsWrapper& aLayer,
uint64_t aLayersId,
const gfx::Matrix4x4& aAncestorTransform,
AsyncPanZoomController* aParent,
AsyncPanZoomController* aNextSibling,
@ -385,12 +387,12 @@ APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
{
mTreeLock.AssertCurrentThreadOwns();
mApzcTreeLog << aLayer->Name() << '\t';
mApzcTreeLog << aLayer.Name() << '\t';
AsyncPanZoomController* apzc = PrepareAPZCForLayer(aLayer,
aLayer->GetFrameMetrics(), aLayersId, aAncestorTransform,
aLayer.Metrics(), aLayersId, aAncestorTransform,
aObscured, aParent, aNextSibling, aState);
aLayer->SetAsyncPanZoomController(apzc);
aLayer.SetApzc(apzc);
mApzcTreeLog << '\n';
@ -401,13 +403,13 @@ APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
// transform to layer L when we recurse into the children below. If we are at a layer
// with an APZC, such as P, then we reset the ancestorTransform to just PC, to start
// the new accumulation as we go down.
Matrix4x4 transform = aLayer->GetTransform();
Matrix4x4 transform = aLayer.GetTransform();
Matrix4x4 ancestorTransform = transform;
if (!apzc) {
ancestorTransform = ancestorTransform * aAncestorTransform;
}
uint64_t childLayersId = (aLayer->AsRefLayer() ? aLayer->AsRefLayer()->GetReferentId() : aLayersId);
uint64_t childLayersId = (aLayer.AsRefLayer() ? aLayer.AsRefLayer()->GetReferentId() : aLayersId);
nsIntRegion obscured;
if (aLayersId == childLayersId) {
@ -429,7 +431,7 @@ APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
// If there's no APZC at this level, any APZCs for our child layers will
// have our siblings as siblings.
AsyncPanZoomController* next = apzc ? nullptr : aNextSibling;
for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) {
for (LayerMetricsWrapper child = aLayer.GetLastChild(); child; child = child.GetPrevSibling()) {
gfx::TreeAutoIndent indent(mApzcTreeLog);
next = UpdatePanZoomControllerTree(aState, child, childLayersId,
ancestorTransform, aParent, next,
@ -437,10 +439,10 @@ APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
// Each layer obscures its previous siblings, so we augment the obscured
// region as we loop backwards through the children.
nsIntRegion childRegion = child->GetVisibleRegion();
childRegion.Transform(gfx::To3DMatrix(child->GetTransform()));
if (child->GetClipRect()) {
childRegion.AndWith(*child->GetClipRect());
nsIntRegion childRegion = child.GetVisibleRegion();
childRegion.Transform(gfx::To3DMatrix(child.GetTransform()));
if (child.GetClipRect()) {
childRegion.AndWith(*child.GetClipRect());
}
obscured.OrWith(childRegion);

View File

@ -44,6 +44,7 @@ class AsyncPanZoomController;
class CompositorParent;
class APZPaintLogHelper;
class OverscrollHandoffChain;
class LayerMetricsWrapper;
/**
* ****************** NOTE ON LOCK ORDERING IN APZ **************************
@ -378,13 +379,13 @@ private:
void UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc,
const ZoomConstraints& aConstraints);
AsyncPanZoomController* PrepareAPZCForLayer(const Layer* aLayer,
AsyncPanZoomController* PrepareAPZCForLayer(const LayerMetricsWrapper& aLayer,
const FrameMetrics& aMetrics,
uint64_t aLayersId,
const gfx::Matrix4x4& aAncestorTransform,
const nsIntRegion& aObscured,
AsyncPanZoomController*& aOutParent,
AsyncPanZoomController*& aOutNextSibling,
AsyncPanZoomController* aNextSibling,
TreeBuildingState& aState);
/**
@ -397,7 +398,8 @@ private:
* code.
*/
AsyncPanZoomController* UpdatePanZoomControllerTree(TreeBuildingState& aState,
Layer* aLayer, uint64_t aLayersId,
const LayerMetricsWrapper& aLayer,
uint64_t aLayersId,
const gfx::Matrix4x4& aAncestorTransform,
AsyncPanZoomController* aParent,
AsyncPanZoomController* aNextSibling,

View File

@ -739,7 +739,6 @@ AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
mAsyncScrollTimeoutTask(nullptr),
mTouchBlockBalance(0),
mTreeManager(aTreeManager),
mScrollParentId(FrameMetrics::NULL_SCROLL_ID),
mAPZCId(sAsyncPanZoomControllerCount++),
mSharedLock(nullptr)
{
@ -2414,6 +2413,7 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri
mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners;
mFrameMetrics.mMayHaveTouchCaret = aLayerMetrics.mMayHaveTouchCaret;
mFrameMetrics.SetScrollParentId(aLayerMetrics.GetScrollParentId());
APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint);
LogRendertraceRect(GetGuid(), "page", "brown", aLayerMetrics.mScrollableRect);

View File

@ -896,12 +896,8 @@ private:
* including handing off scroll to another APZC, and overscrolling.
*/
public:
void SetScrollHandoffParentId(FrameMetrics::ViewID aScrollParentId) {
mScrollParentId = aScrollParentId;
}
FrameMetrics::ViewID GetScrollHandoffParentId() const {
return mScrollParentId;
return mFrameMetrics.GetScrollParentId();
}
/**
@ -935,8 +931,6 @@ public:
bool SnapBackIfOverscrolled();
private:
FrameMetrics::ViewID mScrollParentId;
/**
* A helper function for calling APZCTreeManager::DispatchScroll().
* Guards against the case where the APZC is being concurrently destroyed

View File

@ -32,6 +32,7 @@
#include "gfxPrefs.h"
#ifdef MOZ_WIDGET_ANDROID
#include "AndroidBridge.h"
#include "LayerMetricsWrapper.h"
#endif
namespace mozilla {
@ -641,30 +642,26 @@ ClientLayerManager::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent,
bool aDrawingCritical)
{
#ifdef MOZ_WIDGET_ANDROID
Layer* primaryScrollable = GetPrimaryScrollableLayer();
if (primaryScrollable) {
const FrameMetrics& metrics = primaryScrollable->GetFrameMetrics();
// This is derived from the code in
// gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree.
CSSToLayerScale paintScale = metrics.LayersPixelsPerCSSPixel();
const CSSRect& metricsDisplayPort =
(aDrawingCritical && !metrics.mCriticalDisplayPort.IsEmpty()) ?
metrics.mCriticalDisplayPort : metrics.mDisplayPort;
LayerRect displayPort = (metricsDisplayPort + metrics.GetScrollOffset()) * paintScale;
ScreenPoint scrollOffset;
CSSToScreenScale zoom;
bool ret = AndroidBridge::Bridge()->ProgressiveUpdateCallback(
aHasPendingNewThebesContent, displayPort, paintScale.scale, aDrawingCritical,
scrollOffset, zoom);
aMetrics.SetScrollOffset(scrollOffset / zoom);
aMetrics.SetZoom(zoom);
return ret;
}
#endif
MOZ_ASSERT(aMetrics.IsScrollable());
// This is derived from the code in
// gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree.
CSSToLayerScale paintScale = aMetrics.LayersPixelsPerCSSPixel();
const CSSRect& metricsDisplayPort =
(aDrawingCritical && !aMetrics.mCriticalDisplayPort.IsEmpty()) ?
aMetrics.mCriticalDisplayPort : aMetrics.mDisplayPort;
LayerRect displayPort = (metricsDisplayPort + aMetrics.GetScrollOffset()) * paintScale;
ScreenPoint scrollOffset;
CSSToScreenScale zoom;
bool ret = AndroidBridge::Bridge()->ProgressiveUpdateCallback(
aHasPendingNewThebesContent, displayPort, paintScale.scale, aDrawingCritical,
scrollOffset, zoom);
aMetrics.SetScrollOffset(scrollOffset / zoom);
aMetrics.SetZoom(zoom);
return ret;
#else
return false;
#endif
}
ClientLayer::~ClientLayer()

View File

@ -13,6 +13,7 @@
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
#include "mozilla/gfx/BaseSize.h" // for BaseSize
#include "mozilla/gfx/Rect.h" // for Rect, RectTyped
#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
#include "mozilla/layers/LayersMessages.h"
#include "mozilla/mozalloc.h" // for operator delete, etc
#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc
@ -63,28 +64,30 @@ ApplyParentLayerToLayerTransform(const gfx::Matrix4x4& aTransform, const ParentL
}
static gfx::Matrix4x4
GetTransformToAncestorsParentLayer(Layer* aStart, Layer* aAncestor)
GetTransformToAncestorsParentLayer(Layer* aStart, const LayerMetricsWrapper& aAncestor)
{
gfx::Matrix4x4 transform;
Layer* ancestorParent = aAncestor->GetParent();
for (Layer* iter = aStart; iter != ancestorParent; iter = iter->GetParent()) {
transform = transform * iter->GetTransform();
const LayerMetricsWrapper& ancestorParent = aAncestor.GetParent();
for (LayerMetricsWrapper iter(aStart, LayerMetricsWrapper::StartAt::BOTTOM);
ancestorParent ? iter != ancestorParent : iter.IsValid();
iter = iter.GetParent()) {
transform = transform * iter.GetTransform();
// If the layer has a non-transient async transform then we need to apply it here
// because it will get applied by the APZ in the compositor as well
const FrameMetrics& metrics = iter->GetFrameMetrics();
const FrameMetrics& metrics = iter.Metrics();
transform = transform * gfx::Matrix4x4().Scale(metrics.mResolution.scale, metrics.mResolution.scale, 1.f);
}
return transform;
}
void
ClientTiledThebesLayer::GetAncestorLayers(Layer** aOutScrollAncestor,
Layer** aOutDisplayPortAncestor)
ClientTiledThebesLayer::GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor,
LayerMetricsWrapper* aOutDisplayPortAncestor)
{
Layer* scrollAncestor = nullptr;
Layer* displayPortAncestor = nullptr;
for (Layer* ancestor = this; ancestor; ancestor = ancestor->GetParent()) {
const FrameMetrics& metrics = ancestor->GetFrameMetrics();
LayerMetricsWrapper scrollAncestor;
LayerMetricsWrapper displayPortAncestor;
for (LayerMetricsWrapper ancestor(this, LayerMetricsWrapper::StartAt::BOTTOM); ancestor; ancestor = ancestor.GetParent()) {
const FrameMetrics& metrics = ancestor.Metrics();
if (!scrollAncestor && metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) {
scrollAncestor = ancestor;
}
@ -120,8 +123,8 @@ ClientTiledThebesLayer::BeginPaint()
// Get the metrics of the nearest scrollable layer and the nearest layer
// with a displayport.
Layer* scrollAncestor = nullptr;
Layer* displayPortAncestor = nullptr;
LayerMetricsWrapper scrollAncestor;
LayerMetricsWrapper displayPortAncestor;
GetAncestorLayers(&scrollAncestor, &displayPortAncestor);
if (!displayPortAncestor || !scrollAncestor) {
@ -135,10 +138,10 @@ ClientTiledThebesLayer::BeginPaint()
}
TILING_LOG("TILING %p: Found scrollAncestor %p and displayPortAncestor %p\n", this,
scrollAncestor, displayPortAncestor);
scrollAncestor.GetLayer(), displayPortAncestor.GetLayer());
const FrameMetrics& scrollMetrics = scrollAncestor->GetFrameMetrics();
const FrameMetrics& displayportMetrics = displayPortAncestor->GetFrameMetrics();
const FrameMetrics& scrollMetrics = scrollAncestor.Metrics();
const FrameMetrics& displayportMetrics = displayPortAncestor.Metrics();
// Calculate the transform required to convert ParentLayer space of our
// display port ancestor to the Layer space of this layer.
@ -183,7 +186,13 @@ ClientTiledThebesLayer::BeginPaint()
bool
ClientTiledThebesLayer::UseFastPath()
{
const FrameMetrics& parentMetrics = GetParent()->GetFrameMetrics();
LayerMetricsWrapper scrollAncestor;
GetAncestorLayers(&scrollAncestor, nullptr);
if (!scrollAncestor) {
return true;
}
const FrameMetrics& parentMetrics = scrollAncestor.Metrics();
bool multipleTransactionsNeeded = gfxPrefs::UseProgressiveTilePainting()
|| gfxPrefs::UseLowPrecisionBuffer()
|| !parentMetrics.mCriticalDisplayPort.IsEmpty();

View File

@ -77,8 +77,8 @@ public:
* scroll and have a displayport. The parameters are out-params
* which hold the return values; the values passed in may be null.
*/
void GetAncestorLayers(Layer** aOutScrollAncestor,
Layer** aOutDisplayPortAncestor);
void GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor,
LayerMetricsWrapper* aOutDisplayPortAncestor);
private:
ClientLayerManager* ClientManager()

View File

@ -19,6 +19,7 @@
#include "mozilla/gfx/Rect.h" // for Rect
#include "mozilla/gfx/Tools.h" // for BytesPerPixel
#include "mozilla/layers/CompositableForwarder.h"
#include "mozilla/layers/LayerMetricsWrapper.h"
#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder
#include "TextureClientPool.h"
#include "nsDebug.h" // for NS_ASSERTION
@ -159,7 +160,7 @@ ComputeViewTransform(const FrameMetrics& aContentMetrics, const FrameMetrics& aC
bool
SharedFrameMetricsHelper::UpdateFromCompositorFrameMetrics(
Layer* aLayer,
const LayerMetricsWrapper& aLayer,
bool aHasPendingNewThebesContent,
bool aLowPrecision,
ViewTransform& aViewTransform)
@ -167,16 +168,16 @@ SharedFrameMetricsHelper::UpdateFromCompositorFrameMetrics(
MOZ_ASSERT(aLayer);
CompositorChild* compositor = nullptr;
if (aLayer->Manager() &&
aLayer->Manager()->AsClientLayerManager()) {
compositor = aLayer->Manager()->AsClientLayerManager()->GetCompositorChild();
if (aLayer.Manager() &&
aLayer.Manager()->AsClientLayerManager()) {
compositor = aLayer.Manager()->AsClientLayerManager()->GetCompositorChild();
}
if (!compositor) {
return false;
}
const FrameMetrics& contentMetrics = aLayer->GetFrameMetrics();
const FrameMetrics& contentMetrics = aLayer.Metrics();
FrameMetrics compositorMetrics;
if (!compositor->LookupCompositorFrameMetrics(contentMetrics.GetScrollId(),
@ -1361,13 +1362,13 @@ ClientTiledLayerBuffer::ValidateTile(TileClient aTile,
* for the compositor state.
*/
static LayerRect
GetCompositorSideCompositionBounds(Layer* aScrollAncestor,
GetCompositorSideCompositionBounds(const LayerMetricsWrapper& aScrollAncestor,
const Matrix4x4& aTransformToCompBounds,
const ViewTransform& aAPZTransform)
{
Matrix4x4 nonTransientAPZUntransform = Matrix4x4().Scale(
aScrollAncestor->GetFrameMetrics().mResolution.scale,
aScrollAncestor->GetFrameMetrics().mResolution.scale,
aScrollAncestor.Metrics().mResolution.scale,
aScrollAncestor.Metrics().mResolution.scale,
1.f);
nonTransientAPZUntransform.Invert();
@ -1381,7 +1382,7 @@ GetCompositorSideCompositionBounds(Layer* aScrollAncestor,
transform.Invert();
return TransformTo<LayerPixel>(transform,
aScrollAncestor->GetFrameMetrics().mCompositionBounds);
aScrollAncestor.Metrics().mCompositionBounds);
}
bool
@ -1412,7 +1413,7 @@ ClientTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInval
TILING_LOG("TILING %p: Progressive update stale region %s\n", mThebesLayer, Stringify(staleRegion).c_str());
Layer* scrollAncestor = nullptr;
LayerMetricsWrapper scrollAncestor;
mThebesLayer->GetAncestorLayers(&scrollAncestor, nullptr);
// Find out the current view transform to determine which tiles to draw
@ -1420,16 +1421,18 @@ ClientTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInval
// caused by there being an incoming, more relevant paint.
ViewTransform viewTransform;
#if defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_ANDROID_APZ)
FrameMetrics compositorMetrics = scrollAncestor->GetFrameMetrics();
FrameMetrics contentMetrics = scrollAncestor.Metrics();
bool abortPaint = false;
// On Android, only the primary scrollable layer is async-scrolled, and the only one
// that the Java-side code can provide details about. If we're tiling some other layer
// then we already have all the information we need about it.
if (scrollAncestor == mManager->GetPrimaryScrollableLayer()) {
if (contentMetrics.GetScrollId() == mManager->GetRootScrollableLayerId()) {
FrameMetrics compositorMetrics = contentMetrics;
// The ProgressiveUpdateCallback updates the compositorMetrics
abortPaint = mManager->ProgressiveUpdateCallback(!staleRegion.Contains(aInvalidRegion),
compositorMetrics,
!drawingLowPrecision);
viewTransform = ComputeViewTransform(scrollAncestor->GetFrameMetrics(), compositorMetrics);
viewTransform = ComputeViewTransform(contentMetrics, compositorMetrics);
}
#else
MOZ_ASSERT(mSharedFrameMetricsHelper);

View File

@ -354,7 +354,7 @@ public:
* is useful for slow-to-render pages when the display-port starts lagging
* behind enough that continuing to draw it is wasted effort.
*/
bool UpdateFromCompositorFrameMetrics(Layer* aLayer,
bool UpdateFromCompositorFrameMetrics(const LayerMetricsWrapper& aLayer,
bool aHasPendingNewThebesContent,
bool aLowPrecision,
ViewTransform& aViewTransform);

View File

@ -21,6 +21,7 @@
#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor
#include "mozilla/layers/AsyncPanZoomController.h"
#include "mozilla/layers/Compositor.h" // for Compositor
#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
#include "nsCSSPropList.h"
#include "nsCoord.h" // for NSAppUnitsToFloatPixels, etc
#include "nsDebug.h" // for NS_ASSERTION, etc
@ -242,6 +243,7 @@ IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, gfxFloat aMax)
void
AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer,
Layer* aTransformedSubtreeRoot,
FrameMetrics::ViewID aTransformScrollId,
const Matrix4x4& aPreviousTransformForRoot,
const Matrix4x4& aCurrentTransformForRoot,
const LayerMargin& aFixedLayerMargins)
@ -249,110 +251,107 @@ AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer,
bool isRootFixed = aLayer->GetIsFixedPosition() &&
!aLayer->GetParent()->GetIsFixedPosition();
bool isStickyForSubtree = aLayer->GetIsStickyPosition() &&
aLayer->GetStickyScrollContainerId() ==
aTransformedSubtreeRoot->GetFrameMetrics().GetScrollId();
if (aLayer != aTransformedSubtreeRoot && (isRootFixed || isStickyForSubtree)) {
// Insert a translation so that the position of the anchor point is the same
// before and after the change to the transform of aTransformedSubtreeRoot.
// This currently only works for fixed layers with 2D transforms.
aLayer->GetStickyScrollContainerId() == aTransformScrollId;
bool isFixedOrSticky = (isRootFixed || isStickyForSubtree);
// Accumulate the transforms between this layer and the subtree root layer.
Matrix ancestorTransform;
if (!AccumulateLayerTransforms2D(aLayer->GetParent(), aTransformedSubtreeRoot,
ancestorTransform)) {
return;
// We want to process all the fixed and sticky children of
// aTransformedSubtreeRoot. Also, once we do encounter such a child, we don't
// need to recurse any deeper because the fixed layers are relative to their
// nearest scrollable layer.
if (aLayer == aTransformedSubtreeRoot || !isFixedOrSticky) {
// ApplyAsyncContentTransformToTree will call this function again for
// nested scrollable layers, so we don't need to recurse if the layer is
// scrollable.
if (aLayer == aTransformedSubtreeRoot || !aLayer->HasScrollableFrameMetrics()) {
for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) {
AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot, aTransformScrollId,
aPreviousTransformForRoot,
aCurrentTransformForRoot, aFixedLayerMargins);
}
}
Matrix oldRootTransform;
Matrix newRootTransform;
if (!aPreviousTransformForRoot.Is2D(&oldRootTransform) ||
!aCurrentTransformForRoot.Is2D(&newRootTransform)) {
return;
}
// Calculate the cumulative transforms between the subtree root with the
// old transform and the current transform.
Matrix oldCumulativeTransform = ancestorTransform * oldRootTransform;
Matrix newCumulativeTransform = ancestorTransform * newRootTransform;
if (newCumulativeTransform.IsSingular()) {
return;
}
Matrix newCumulativeTransformInverse = newCumulativeTransform;
newCumulativeTransformInverse.Invert();
// Now work out the translation necessary to make sure the layer doesn't
// move given the new sub-tree root transform.
Matrix layerTransform;
if (!GetBaseTransform2D(aLayer, &layerTransform)) {
return;
}
// Calculate any offset necessary, in previous transform sub-tree root
// space. This is used to make sure fixed position content respects
// content document fixed position margins.
LayerPoint offsetInOldSubtreeLayerSpace = GetLayerFixedMarginsOffset(aLayer, aFixedLayerMargins);
// Add the above offset to the anchor point so we can offset the layer by
// and amount that's specified in old subtree layer space.
const LayerPoint& anchorInOldSubtreeLayerSpace = aLayer->GetFixedPositionAnchor();
LayerPoint offsetAnchorInOldSubtreeLayerSpace = anchorInOldSubtreeLayerSpace + offsetInOldSubtreeLayerSpace;
// Add the local layer transform to the two points to make the equation
// below this section more convenient.
Point anchor(anchorInOldSubtreeLayerSpace.x, anchorInOldSubtreeLayerSpace.y);
Point offsetAnchor(offsetAnchorInOldSubtreeLayerSpace.x, offsetAnchorInOldSubtreeLayerSpace.y);
Point locallyTransformedAnchor = layerTransform * anchor;
Point locallyTransformedOffsetAnchor = layerTransform * offsetAnchor;
// Transforming the locallyTransformedAnchor by oldCumulativeTransform
// returns the layer's anchor point relative to the parent of
// aTransformedSubtreeRoot, before the new transform was applied.
// Then, applying newCumulativeTransformInverse maps that point relative
// to the layer's parent, which is the same coordinate space as
// locallyTransformedAnchor again, allowing us to subtract them and find
// out the offset necessary to make sure the layer stays stationary.
Point oldAnchorPositionInNewSpace =
newCumulativeTransformInverse * (oldCumulativeTransform * locallyTransformedOffsetAnchor);
Point translation = oldAnchorPositionInNewSpace - locallyTransformedAnchor;
if (aLayer->GetIsStickyPosition()) {
// For sticky positioned layers, the difference between the two rectangles
// defines a pair of translation intervals in each dimension through which
// the layer should not move relative to the scroll container. To
// accomplish this, we limit each dimension of the |translation| to that
// part of it which overlaps those intervals.
const LayerRect& stickyOuter = aLayer->GetStickyScrollRangeOuter();
const LayerRect& stickyInner = aLayer->GetStickyScrollRangeInner();
translation.y = IntervalOverlap(translation.y, stickyOuter.y, stickyOuter.YMost()) -
IntervalOverlap(translation.y, stickyInner.y, stickyInner.YMost());
translation.x = IntervalOverlap(translation.x, stickyOuter.x, stickyOuter.XMost()) -
IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost());
}
// Finally, apply the 2D translation to the layer transform.
TranslateShadowLayer2D(aLayer, ThebesPoint(translation));
// The transform has now been applied, so there's no need to iterate over
// child layers.
return;
}
// Fixed layers are relative to their nearest scrollable layer, so when we
// encounter a scrollable layer, bail. ApplyAsyncContentTransformToTree will
// have already recursed on this layer and called AlignFixedAndStickyLayers
// on it with its own transforms.
if (aLayer->GetFrameMetrics().IsScrollable() &&
aLayer != aTransformedSubtreeRoot) {
// Insert a translation so that the position of the anchor point is the same
// before and after the change to the transform of aTransformedSubtreeRoot.
// This currently only works for fixed layers with 2D transforms.
// Accumulate the transforms between this layer and the subtree root layer.
Matrix ancestorTransform;
if (!AccumulateLayerTransforms2D(aLayer->GetParent(), aTransformedSubtreeRoot,
ancestorTransform)) {
return;
}
for (Layer* child = aLayer->GetFirstChild();
child; child = child->GetNextSibling()) {
AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot,
aPreviousTransformForRoot,
aCurrentTransformForRoot, aFixedLayerMargins);
Matrix oldRootTransform;
Matrix newRootTransform;
if (!aPreviousTransformForRoot.Is2D(&oldRootTransform) ||
!aCurrentTransformForRoot.Is2D(&newRootTransform)) {
return;
}
// Calculate the cumulative transforms between the subtree root with the
// old transform and the current transform.
Matrix oldCumulativeTransform = ancestorTransform * oldRootTransform;
Matrix newCumulativeTransform = ancestorTransform * newRootTransform;
if (newCumulativeTransform.IsSingular()) {
return;
}
Matrix newCumulativeTransformInverse = newCumulativeTransform;
newCumulativeTransformInverse.Invert();
// Now work out the translation necessary to make sure the layer doesn't
// move given the new sub-tree root transform.
Matrix layerTransform;
if (!GetBaseTransform2D(aLayer, &layerTransform)) {
return;
}
// Calculate any offset necessary, in previous transform sub-tree root
// space. This is used to make sure fixed position content respects
// content document fixed position margins.
LayerPoint offsetInOldSubtreeLayerSpace = GetLayerFixedMarginsOffset(aLayer, aFixedLayerMargins);
// Add the above offset to the anchor point so we can offset the layer by
// and amount that's specified in old subtree layer space.
const LayerPoint& anchorInOldSubtreeLayerSpace = aLayer->GetFixedPositionAnchor();
LayerPoint offsetAnchorInOldSubtreeLayerSpace = anchorInOldSubtreeLayerSpace + offsetInOldSubtreeLayerSpace;
// Add the local layer transform to the two points to make the equation
// below this section more convenient.
Point anchor(anchorInOldSubtreeLayerSpace.x, anchorInOldSubtreeLayerSpace.y);
Point offsetAnchor(offsetAnchorInOldSubtreeLayerSpace.x, offsetAnchorInOldSubtreeLayerSpace.y);
Point locallyTransformedAnchor = layerTransform * anchor;
Point locallyTransformedOffsetAnchor = layerTransform * offsetAnchor;
// Transforming the locallyTransformedAnchor by oldCumulativeTransform
// returns the layer's anchor point relative to the parent of
// aTransformedSubtreeRoot, before the new transform was applied.
// Then, applying newCumulativeTransformInverse maps that point relative
// to the layer's parent, which is the same coordinate space as
// locallyTransformedAnchor again, allowing us to subtract them and find
// out the offset necessary to make sure the layer stays stationary.
Point oldAnchorPositionInNewSpace =
newCumulativeTransformInverse * (oldCumulativeTransform * locallyTransformedOffsetAnchor);
Point translation = oldAnchorPositionInNewSpace - locallyTransformedAnchor;
if (aLayer->GetIsStickyPosition()) {
// For sticky positioned layers, the difference between the two rectangles
// defines a pair of translation intervals in each dimension through which
// the layer should not move relative to the scroll container. To
// accomplish this, we limit each dimension of the |translation| to that
// part of it which overlaps those intervals.
const LayerRect& stickyOuter = aLayer->GetStickyScrollRangeOuter();
const LayerRect& stickyInner = aLayer->GetStickyScrollRangeInner();
translation.y = IntervalOverlap(translation.y, stickyOuter.y, stickyOuter.YMost()) -
IntervalOverlap(translation.y, stickyInner.y, stickyInner.YMost());
translation.x = IntervalOverlap(translation.x, stickyOuter.x, stickyOuter.XMost()) -
IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost());
}
// Finally, apply the 2D translation to the layer transform.
TranslateShadowLayer2D(aLayer, ThebesPoint(translation));
}
static void
@ -506,15 +505,15 @@ SampleAnimations(Layer* aLayer, TimeStamp aPoint)
}
static bool
SampleAPZAnimations(Layer* aLayer, TimeStamp aPoint)
SampleAPZAnimations(const LayerMetricsWrapper& aLayer, TimeStamp aPoint)
{
bool activeAnimations = false;
for (Layer* child = aLayer->GetFirstChild(); child;
child = child->GetNextSibling()) {
for (LayerMetricsWrapper child = aLayer.GetFirstChild(); child;
child = child.GetNextSibling()) {
activeAnimations |= SampleAPZAnimations(child, aPoint);
}
if (AsyncPanZoomController* apzc = aLayer->GetAsyncPanZoomController()) {
if (AsyncPanZoomController* apzc = aLayer.GetApzc()) {
activeAnimations |= apzc->AdvanceAnimations(aPoint);
}
@ -553,9 +552,21 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer)
ApplyAsyncContentTransformToTree(child);
}
if (AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController()) {
LayerComposite* layerComposite = aLayer->AsLayerComposite();
Matrix4x4 oldTransform = aLayer->GetTransform();
LayerComposite* layerComposite = aLayer->AsLayerComposite();
Matrix4x4 oldTransform = aLayer->GetTransform();
Matrix4x4 combinedAsyncTransformWithoutOverscroll;
Matrix4x4 combinedAsyncTransform;
bool hasAsyncTransform = false;
LayerMargin fixedLayerMargins(0, 0, 0, 0);
for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) {
AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(i);
if (!controller) {
continue;
}
hasAsyncTransform = true;
ViewTransform asyncTransformWithoutOverscroll, overscrollTransform;
ScreenPoint scrollOffset;
@ -563,12 +574,13 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer)
scrollOffset,
&overscrollTransform);
const FrameMetrics& metrics = aLayer->GetFrameMetrics();
const FrameMetrics& metrics = aLayer->GetFrameMetrics(i);
CSSToLayerScale paintScale = metrics.LayersPixelsPerCSSPixel();
CSSRect displayPort(metrics.mCriticalDisplayPort.IsEmpty() ?
metrics.mDisplayPort : metrics.mCriticalDisplayPort);
LayerMargin fixedLayerMargins(0, 0, 0, 0);
ScreenPoint offset(0, 0);
// XXX this call to SyncFrameMetrics is not currently being used. It will be cleaned
// up as part of bug 776030 or one of its dependencies.
SyncFrameMetrics(scrollOffset, asyncTransformWithoutOverscroll.mScale.scale,
metrics.mScrollableRect, mLayersUpdated, displayPort,
paintScale, mIsFirstPaint, fixedLayerMargins, offset);
@ -579,8 +591,12 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer)
// Apply the render offset
mLayerManager->GetCompositor()->SetScreenRenderOffset(offset);
Matrix4x4 transform = AdjustAndCombineWithCSSTransform(
asyncTransformWithoutOverscroll * overscrollTransform, aLayer);
combinedAsyncTransformWithoutOverscroll *= asyncTransformWithoutOverscroll;
combinedAsyncTransform *= (asyncTransformWithoutOverscroll * overscrollTransform);
}
if (hasAsyncTransform) {
Matrix4x4 transform = AdjustAndCombineWithCSSTransform(combinedAsyncTransform, aLayer);
// GetTransform already takes the pre- and post-scale into account. Since we
// will apply the pre- and post-scale again when computing the effective
@ -597,9 +613,14 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer)
NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(),
"overwriting animated transform!");
const FrameMetrics& bottom = LayerMetricsWrapper::BottommostScrollableMetrics(aLayer);
MOZ_ASSERT(bottom.IsScrollable()); // must be true because hasAsyncTransform is true
// Apply resolution scaling to the old transform - the layer tree as it is
// doesn't have the necessary transform to display correctly.
LayoutDeviceToLayerScale resolution = metrics.mCumulativeResolution;
// doesn't have the necessary transform to display correctly. We use the
// bottom-most scrollable metrics because that should have the most accurate
// cumulative resolution for aLayer.
LayoutDeviceToLayerScale resolution = bottom.mCumulativeResolution;
oldTransform.Scale(resolution.scale, resolution.scale, 1);
// For the purpose of aligning fixed and sticky layers, we disregard
@ -607,41 +628,29 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer)
// parameter. This ensures that the overscroll transform is not unapplied,
// and therefore that the visual effect applies to fixed and sticky layers.
Matrix4x4 transformWithoutOverscroll = AdjustAndCombineWithCSSTransform(
asyncTransformWithoutOverscroll, aLayer);
AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform,
combinedAsyncTransformWithoutOverscroll, aLayer);
// Since fixed/sticky layers are relative to their nearest scrolling ancestor,
// we use the ViewID from the bottommost scrollable metrics here.
AlignFixedAndStickyLayers(aLayer, aLayer, bottom.GetScrollId(), oldTransform,
transformWithoutOverscroll, fixedLayerMargins);
appliedTransform = true;
}
if (aLayer->AsContainerLayer() && aLayer->GetScrollbarDirection() != Layer::NONE) {
ApplyAsyncTransformToScrollbar(aLayer->AsContainerLayer());
if (aLayer->GetScrollbarDirection() != Layer::NONE) {
ApplyAsyncTransformToScrollbar(aLayer);
}
return appliedTransform;
}
static bool
LayerHasNonContainerDescendants(ContainerLayer* aContainer)
LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, Layer* aScrollbar)
{
for (Layer* child = aContainer->GetFirstChild();
child; child = child->GetNextSibling()) {
ContainerLayer* container = child->AsContainerLayer();
if (!container || LayerHasNonContainerDescendants(container)) {
return true;
}
}
return false;
}
static bool
LayerIsScrollbarTarget(Layer* aTarget, ContainerLayer* aScrollbar)
{
AsyncPanZoomController* apzc = aTarget->GetAsyncPanZoomController();
AsyncPanZoomController* apzc = aTarget.GetApzc();
if (!apzc) {
return false;
}
const FrameMetrics& metrics = aTarget->GetFrameMetrics();
const FrameMetrics& metrics = aTarget.Metrics();
if (metrics.GetScrollId() != aScrollbar->GetScrollbarTargetContainerId()) {
return false;
}
@ -649,21 +658,21 @@ LayerIsScrollbarTarget(Layer* aTarget, ContainerLayer* aScrollbar)
}
static void
ApplyAsyncTransformToScrollbarForContent(ContainerLayer* aScrollbar,
Layer* aContent, bool aScrollbarIsChild)
ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar,
const LayerMetricsWrapper& aContent,
bool aScrollbarIsDescendant)
{
// We only apply the transform if the scroll-target layer has non-container
// children (i.e. when it has some possibly-visible content). This is to
// avoid moving scroll-bars in the situation that only a scroll information
// layer has been built for a scroll frame, as this would result in a
// disparity between scrollbars and visible content.
if (aContent->AsContainerLayer() &&
!LayerHasNonContainerDescendants(aContent->AsContainerLayer())) {
if (aContent.IsScrollInfoLayer()) {
return;
}
const FrameMetrics& metrics = aContent->GetFrameMetrics();
AsyncPanZoomController* apzc = aContent->GetAsyncPanZoomController();
const FrameMetrics& metrics = aContent.Metrics();
AsyncPanZoomController* apzc = aContent.GetApzc();
Matrix4x4 asyncTransform = apzc->GetCurrentAsyncTransform();
Matrix4x4 nontransientTransform = apzc->GetNontransientAsyncTransform();
@ -698,7 +707,7 @@ ApplyAsyncTransformToScrollbarForContent(ContainerLayer* aScrollbar,
Matrix4x4 transform = scrollbarTransform * aScrollbar->GetTransform();
if (aScrollbarIsChild) {
if (aScrollbarIsDescendant) {
// If the scrollbar layer is a child of the content it is a scrollbar for, then we
// need to do an extra untransform to cancel out the transient async transform on
// the content. This is needed because otherwise that transient async transform is
@ -711,47 +720,50 @@ ApplyAsyncTransformToScrollbarForContent(ContainerLayer* aScrollbar,
// GetTransform already takes the pre- and post-scale into account. Since we
// will apply the pre- and post-scale again when computing the effective
// transform, we must apply the inverses here.
transform.Scale(1.0f/aScrollbar->GetPreXScale(),
1.0f/aScrollbar->GetPreYScale(),
1);
if (ContainerLayer* container = aScrollbar->AsContainerLayer()) {
transform.Scale(1.0f/container->GetPreXScale(),
1.0f/container->GetPreYScale(),
1);
}
transform = transform * Matrix4x4().Scale(1.0f/aScrollbar->GetPostXScale(),
1.0f/aScrollbar->GetPostYScale(),
1);
aScrollbar->AsLayerComposite()->SetShadowTransform(transform);
}
static Layer*
FindScrolledLayerForScrollbar(ContainerLayer* aLayer, bool* aOutIsAncestor)
static LayerMetricsWrapper
FindScrolledLayerForScrollbar(Layer* aScrollbar, bool* aOutIsAncestor)
{
// XXX: once bug 967844 is implemented there might be multiple scrolled layers
// that correspond to the scrollbar's scrollId. Verify that we deal with those
// cases correctly.
// Search all siblings of aLayer and of its ancestors.
for (Layer* ancestor = aLayer; ancestor; ancestor = ancestor->GetParent()) {
for (Layer* scrollTarget = ancestor;
// Search all siblings of aScrollbar and of its ancestors.
LayerMetricsWrapper scrollbar(aScrollbar, LayerMetricsWrapper::StartAt::BOTTOM);
for (LayerMetricsWrapper ancestor = scrollbar; ancestor; ancestor = ancestor.GetParent()) {
for (LayerMetricsWrapper scrollTarget = ancestor;
scrollTarget;
scrollTarget = scrollTarget->GetPrevSibling()) {
if (scrollTarget != aLayer &&
LayerIsScrollbarTarget(scrollTarget, aLayer)) {
scrollTarget = scrollTarget.GetPrevSibling()) {
if (scrollTarget != scrollbar &&
LayerIsScrollbarTarget(scrollTarget, aScrollbar)) {
*aOutIsAncestor = (scrollTarget == ancestor);
return scrollTarget;
}
}
for (Layer* scrollTarget = ancestor->GetNextSibling();
for (LayerMetricsWrapper scrollTarget = ancestor.GetNextSibling();
scrollTarget;
scrollTarget = scrollTarget->GetNextSibling()) {
if (LayerIsScrollbarTarget(scrollTarget, aLayer)) {
scrollTarget = scrollTarget.GetNextSibling()) {
if (LayerIsScrollbarTarget(scrollTarget, aScrollbar)) {
*aOutIsAncestor = false;
return scrollTarget;
}
}
}
return nullptr;
return LayerMetricsWrapper();
}
void
AsyncCompositionManager::ApplyAsyncTransformToScrollbar(ContainerLayer* aLayer)
AsyncCompositionManager::ApplyAsyncTransformToScrollbar(Layer* aLayer)
{
// If this layer corresponds to a scrollbar, then there should be a layer that
// is a previous sibling or a parent that has a matching ViewID on its FrameMetrics.
@ -761,7 +773,7 @@ AsyncCompositionManager::ApplyAsyncTransformToScrollbar(ContainerLayer* aLayer)
// this case we don't need to do anything because there can't be an async
// transform on the content.
bool isAncestor = false;
Layer* scrollTarget = FindScrolledLayerForScrollbar(aLayer, &isAncestor);
const LayerMetricsWrapper& scrollTarget = FindScrolledLayerForScrollbar(aLayer, &isAncestor);
if (scrollTarget) {
ApplyAsyncTransformToScrollbarForContent(aLayer, scrollTarget, isAncestor);
}
@ -772,7 +784,15 @@ AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer)
{
LayerComposite* layerComposite = aLayer->AsLayerComposite();
const FrameMetrics& metrics = aLayer->GetFrameMetrics();
FrameMetrics metrics = LayerMetricsWrapper::TopmostScrollableMetrics(aLayer);
if (!metrics.IsScrollable()) {
// On Fennec it's possible that the there is no scrollable layer in the
// tree, and this function just gets called with the root layer. In that
// case TopmostScrollableMetrics will return an empty FrameMetrics but we
// still want to use the actual non-scrollable metrics from the layer.
metrics = LayerMetricsWrapper::BottommostMetrics(aLayer);
}
// We must apply the resolution scale before a pan/zoom transform, so we call
// GetTransform here.
Matrix4x4 oldTransform = aLayer->GetTransform();
@ -892,7 +912,7 @@ AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer)
// Make sure fixed position layers don't move away from their anchor points
// when we're asynchronously panning or zooming
AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform,
AlignFixedAndStickyLayers(aLayer, aLayer, metrics.GetScrollId(), oldTransform,
aLayer->GetLocalTransform(), fixedLayerMargins);
}
@ -940,11 +960,11 @@ AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame)
// code also includes Fennec which is rendered async. Fennec uses
// its own platform-specific async rendering that is done partially
// in Gecko and partially in Java.
wantNextFrame |= SampleAPZAnimations(root, aCurrentFrame);
wantNextFrame |= SampleAPZAnimations(LayerMetricsWrapper(root), aCurrentFrame);
if (!ApplyAsyncContentTransformToTree(root)) {
nsAutoTArray<Layer*,1> scrollableLayers;
#ifdef MOZ_WIDGET_ANDROID
scrollableLayers.AppendElement(mLayerManager->GetPrimaryScrollableLayer());
mLayerManager->GetRootScrollableLayers(scrollableLayers);
#else
mLayerManager->GetScrollableLayers(scrollableLayers);
#endif

View File

@ -130,7 +130,7 @@ private:
* Update the shadow transform for aLayer assuming that is a scrollbar,
* so that it stays in sync with the content that is being scrolled by APZ.
*/
void ApplyAsyncTransformToScrollbar(ContainerLayer* aLayer);
void ApplyAsyncTransformToScrollbar(Layer* aLayer);
void SetFirstPaintViewport(const LayerIntPoint& aOffset,
const CSSToLayerScale& aZoom,
@ -169,6 +169,7 @@ private:
* zooming.
*/
void AlignFixedAndStickyLayers(Layer* aLayer, Layer* aTransformedSubtreeRoot,
FrameMetrics::ViewID aTransformScrollId,
const gfx::Matrix4x4& aPreviousTransformForRoot,
const gfx::Matrix4x4& aCurrentTransformForRoot,
const LayerMargin& aFixedLayerMargins);

View File

@ -23,6 +23,7 @@
#include "mozilla/layers/TextureHost.h" // for CompositingRenderTarget
#include "mozilla/layers/AsyncPanZoomController.h" // for AsyncPanZoomController
#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
#include "mozilla/mozalloc.h" // for operator delete, etc
#include "nsAutoPtr.h" // for nsRefPtr
#include "nsDebug.h" // for NS_ASSERTION
@ -120,21 +121,13 @@ static void PrintUniformityInfo(Layer* aLayer)
return;
}
FrameMetrics frameMetrics = aLayer->GetFrameMetrics();
if (!frameMetrics.IsScrollable()) {
Matrix4x4 transform = aLayer->AsLayerComposite()->GetShadowTransform();
if (!transform.Is2D()) {
return;
}
AsyncPanZoomController* apzc = aLayer->GetAsyncPanZoomController();
if (apzc) {
ViewTransform asyncTransform, overscrollTransform;
ScreenPoint scrollOffset;
apzc->SampleContentTransformForFrame(&asyncTransform,
scrollOffset,
&overscrollTransform);
printf_stderr("UniformityInfo Layer_Move %llu %p %f, %f\n",
TimeStamp::Now(), aLayer, scrollOffset.x.value, scrollOffset.y.value);
}
Point translation = transform.As2D().GetTranslation();
printf_stderr("UniformityInfo Layer_Move %llu %p %f, %f\n",
TimeStamp::Now(), aLayer, translation.x.value, translation.y.value);
}
/* all of the per-layer prepared data we need to maintain */
@ -264,10 +257,18 @@ RenderLayers(ContainerT* aContainer,
// composited area. If the layer has a background color, fill these areas
// with the background color by drawing a rectangle of the background color
// over the entire composited area before drawing the container contents.
if (AsyncPanZoomController* apzc = aContainer->GetAsyncPanZoomController()) {
// Make sure not to do this on a "scrollinfo" layer (one with an empty visible
// region) because it's just a placeholder for APZ purposes.
if (apzc->IsOverscrolled() && !aContainer->GetVisibleRegion().IsEmpty()) {
// Make sure not to do this on a "scrollinfo" layer because it's just a
// placeholder for APZ purposes.
if (aContainer->HasScrollableFrameMetrics() && !aContainer->IsScrollInfoLayer()) {
bool overscrolled = false;
for (uint32_t i = 0; i < aContainer->GetFrameMetricsCount(); i++) {
AsyncPanZoomController* apzc = aContainer->GetAsyncPanZoomController(i);
if (apzc && apzc->IsOverscrolled()) {
overscrolled = true;
break;
}
}
if (overscrolled) {
gfxRGBA color = aContainer->GetBackgroundColor();
// If the background is completely transparent, there's no point in
// drawing anything for it. Hopefully the layers behind, if any, will
@ -430,8 +431,11 @@ ContainerRender(ContainerT* aContainer,
}
aContainer->mPrepared = nullptr;
if (aContainer->GetFrameMetrics().IsScrollable()) {
const FrameMetrics& frame = aContainer->GetFrameMetrics();
for (uint32_t i = 0; i < aContainer->GetFrameMetricsCount(); i++) {
if (!aContainer->GetFrameMetrics(i).IsScrollable()) {
continue;
}
const FrameMetrics& frame = aContainer->GetFrameMetrics(i);
LayerRect layerBounds = frame.mCompositionBounds * ParentLayerToLayerScale(1.0);
gfx::Rect rect(layerBounds.x, layerBounds.y, layerBounds.width, layerBounds.height);
gfx::Rect clipRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height);

View File

@ -38,6 +38,7 @@
#include "mozilla/layers/Compositor.h" // for Compositor
#include "mozilla/layers/CompositorTypes.h"
#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc
#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
#include "mozilla/layers/LayersTypes.h" // for etc
#include "ipc/CompositorBench.h" // for CompositorBench
#include "ipc/ShadowLayerUtils.h"
@ -796,7 +797,13 @@ LayerManagerComposite::ComputeRenderIntegrity()
return 1.f;
}
const FrameMetrics& rootMetrics = root->GetFrameMetrics();
FrameMetrics rootMetrics = LayerMetricsWrapper::TopmostScrollableMetrics(root);
if (!rootMetrics.IsScrollable()) {
// The root may not have any scrollable metrics, in which case rootMetrics
// will just be an empty FrameMetrics. Instead use the actual metrics from
// the root layer.
rootMetrics = LayerMetricsWrapper(root).Metrics();
}
ParentLayerIntRect bounds = RoundedToInt(rootMetrics.mCompositionBounds);
nsIntRect screenRect(bounds.x,
bounds.y,
@ -809,12 +816,14 @@ LayerManagerComposite::ComputeRenderIntegrity()
#ifdef MOZ_WIDGET_ANDROID
// Use the transform on the primary scrollable layer and its FrameMetrics
// to find out how much of the viewport the current displayport covers
Layer* primaryScrollable = GetPrimaryScrollableLayer();
if (primaryScrollable) {
nsTArray<Layer*> rootScrollableLayers;
GetRootScrollableLayers(rootScrollableLayers);
if (rootScrollableLayers.Length() > 0) {
// This is derived from the code in
// AsyncCompositionManager::TransformScrollableLayer
const FrameMetrics& metrics = primaryScrollable->GetFrameMetrics();
Matrix4x4 transform = primaryScrollable->GetEffectiveTransform();
Layer* rootScrollable = rootScrollableLayers[0];
const FrameMetrics& metrics = LayerMetricsWrapper::TopmostScrollableMetrics(rootScrollable);
Matrix4x4 transform = rootScrollable->GetEffectiveTransform();
transform.ScalePost(metrics.mResolution.scale, metrics.mResolution.scale, 1);
// Clip the screen rect to the document bounds

Some files were not shown because too many files have changed in this diff Show More