diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml
index ad2b79e819c..8493d422e67 100644
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml
index 2cb1ae7bc9d..51fcf13d8bb 100644
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml
index 68c02c7ad20..d09ba4debaf 100644
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -17,7 +17,7 @@
-
+
@@ -130,7 +130,7 @@
-
+
diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml
index 9c3a62ebd1c..e5995c023e9 100644
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -15,7 +15,7 @@
-
+
@@ -128,7 +128,7 @@
-
+
diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml
index 2cb1ae7bc9d..51fcf13d8bb 100644
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml
index 9a46797b627..e94af884616 100644
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
index 61099395eee..ba0c7c7bc85 100644
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
- "revision": "d4afc0a7f72fd7793359b9575ea7c90cd54e2348",
+ "revision": "6fa1c6e992e9d7169e8e6cd8c714d1983087a87c",
"repo_path": "/integration/gaia-central"
}
diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml
index e027a611e33..29c3110eed5 100644
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml
index 729be0afff9..1ee4bdd96e0 100644
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml
index 1b4c63e8bce..58f30bd4d2d 100644
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml
index 27379571ba6..4d468c5f554 100644
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/browser/base/content/test/social/browser_addons.js b/browser/base/content/test/social/browser_addons.js
index 116e9eef168..4f6965b7445 100644
--- a/browser/base/content/test/social/browser_addons.js
+++ b/browser/base/content/test/social/browser_addons.js
@@ -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();
diff --git a/browser/base/content/test/social/social_worker.js b/browser/base/content/test/social/social_worker.js
index 1bea0d0ccc6..9455e6c9577 100644
--- a/browser/base/content/test/social/social_worker.js
+++ b/browser/base/content/test/social/social_worker.js
@@ -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;
}
}
diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js
index ae708d82657..d45d9a5d078 100644
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -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.
diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js
index f01bb3ed793..fe6d3a133ec 100644
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -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);
diff --git a/browser/devtools/debugger/test/browser.ini b/browser/devtools/debugger/test/browser.ini
index fb4d772072f..91784710f90 100644
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -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]
diff --git a/browser/devtools/debugger/test/browser_dbg_hit-counts-01.js b/browser/devtools/debugger/test/browser_dbg_hit-counts-01.js
new file mode 100644
index 00000000000..5701e86c8bc
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_hit-counts-01.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Evaluating two functions on the same line and checking for correct hit count
+ * for both of them in CodeMirror's gutter.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_same-line-functions.html";
+const CODE_URL = "code_same-line-functions.js";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gEditor;
+
+function test() {
+ Task.async(function* () {
+ yield pushPrefs(["devtools.debugger.tracer", true]);
+
+ initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+
+ Task.async(function* () {
+ yield waitForSourceShown(gPanel, CODE_URL);
+ yield startTracing(gPanel);
+
+ clickButton();
+
+ yield waitForClientEvents(aPanel, "traces");
+
+ testHitCounts();
+
+ yield stopTracing(gPanel);
+ yield popPrefs();
+ yield closeDebuggerAndFinish(gPanel);
+ })();
+ });
+ })().catch(e => {
+ ok(false, "Got an error: " + e.message + "\n" + e.stack);
+ });
+}
+
+function clickButton() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebuggee.document.querySelector("button"),
+ gDebuggee);
+}
+
+function testHitCounts() {
+ let marker = gEditor.getMarker(0, 'hit-counts');
+
+ is(marker.innerHTML, "1\u00D7|1\u00D7",
+ "Both functions should be hit only once.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+});
\ No newline at end of file
diff --git a/browser/devtools/debugger/test/browser_dbg_hit-counts-02.js b/browser/devtools/debugger/test/browser_dbg_hit-counts-02.js
new file mode 100644
index 00000000000..00722dca99e
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_hit-counts-02.js
@@ -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;
+});
\ No newline at end of file
diff --git a/browser/devtools/debugger/test/code_same-line-functions.js b/browser/devtools/debugger/test/code_same-line-functions.js
new file mode 100644
index 00000000000..58643f59d46
--- /dev/null
+++ b/browser/devtools/debugger/test/code_same-line-functions.js
@@ -0,0 +1 @@
+function first() { var a = "first"; second(); function second() { var a = "second"; } }
\ No newline at end of file
diff --git a/browser/devtools/debugger/test/doc_same-line-functions.html b/browser/devtools/debugger/test/doc_same-line-functions.html
new file mode 100644
index 00000000000..dbdf2644d33
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_same-line-functions.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ Debugger Tracer test page
+
+
+
+
+
+
+
diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js
index dfc21bc0334..925865d7e74 100644
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -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;
+}
\ No newline at end of file
diff --git a/browser/devtools/shared/test/browser_tableWidget_basic.js b/browser/devtools/shared/test/browser_tableWidget_basic.js
index 4e5218a4fe1..9d9840ec5d2 100644
--- a/browser/devtools/shared/test/browser_tableWidget_basic.js
+++ b/browser/devtools/shared/test/browser_tableWidget_basic.js
@@ -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);
diff --git a/browser/devtools/shared/widgets/TableWidget.js b/browser/devtools/shared/widgets/TableWidget.js
index 8363ce68ed7..fc99401d1a0 100644
--- a/browser/devtools/shared/widgets/TableWidget.js
+++ b/browser/devtools/shared/widgets/TableWidget.js
@@ -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();
diff --git a/browser/devtools/sourceeditor/codemirror/mozilla.css b/browser/devtools/sourceeditor/codemirror/mozilla.css
index 31ae8e55d44..bd09bb23f42 100644
--- a/browser/devtools/sourceeditor/codemirror/mozilla.css
+++ b/browser/devtools/sourceeditor/codemirror/mozilla.css
@@ -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;
diff --git a/browser/devtools/sourceeditor/debugger.js b/browser/devtools/sourceeditor/debugger.js
index 607e591e324..35919fed745 100644
--- a/browser/devtools/sourceeditor/debugger.js
+++ b/browser/devtools/sourceeditor/debugger.js
@@ -115,6 +115,7 @@ function hasBreakpoint(ctx, line) {
let markers = cm.lineInfo(line).gutterMarkers;
return markers != null &&
+ markers.breakpoints &&
markers.breakpoints.classList.contains("breakpoint");
}
diff --git a/browser/devtools/sourceeditor/editor.js b/browser/devtools/sourceeditor/editor.js
index ddf7f31a0f1..23225b28099 100644
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -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);
diff --git a/browser/devtools/webconsole/console-output.js b/browser/devtools/webconsole/console-output.js
index 66a14150cfd..681783a06b9 100644
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -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++;
diff --git a/browser/devtools/webconsole/test/browser.ini b/browser/devtools/webconsole/test/browser.ini
index 9de33440942..68cc675d0d3 100644
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -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]
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_table.js b/browser/devtools/webconsole/test/browser_webconsole_output_table.js
new file mode 100644
index 00000000000..d1fbdd8dfcd
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_table.js
@@ -0,0 +1,158 @@
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that console.table() works as intended.
+
+ "use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-table.html";
+
+const TEST_DATA = [
+ {
+ command: "console.table(languages1)",
+ data: [
+ { _index: "0", name: "\"JavaScript\"", fileExtension: "Array[1]" },
+ { _index: "1", name: "Object", fileExtension: "\".ts\"" },
+ { _index: "2", name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
+ ],
+ columns: { _index: "(index)", name: "name", fileExtension: "fileExtension" }
+ },
+ {
+ command: "console.table(languages1, 'name')",
+ data: [
+ { _index: "0", name: "\"JavaScript\"", fileExtension: "Array[1]" },
+ { _index: "1", name: "Object", fileExtension: "\".ts\"" },
+ { _index: "2", name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
+ ],
+ columns: { _index: "(index)", name: "name" }
+ },
+ {
+ command: "console.table(languages1, ['name'])",
+ data: [
+ { _index: "0", name: "\"JavaScript\"", fileExtension: "Array[1]" },
+ { _index: "1", name: "Object", fileExtension: "\".ts\"" },
+ { _index: "2", name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
+ ],
+ columns: { _index: "(index)", name: "name" }
+ },
+ {
+ command: "console.table(languages2)",
+ data: [
+ { _index: "csharp", name: "\"C#\"", paradigm: "\"object-oriented\"" },
+ { _index: "fsharp", name: "\"F#\"", paradigm: "\"functional\"" }
+ ],
+ columns: { _index: "(index)", name: "name", paradigm: "paradigm" }
+ },
+ {
+ command: "console.table([[1, 2], [3, 4]])",
+ data: [
+ { _index: "0", 0: "1", 1: "2" },
+ { _index: "1", 0: "3", 1: "4" }
+ ],
+ columns: { _index: "(index)", 0: "0", 1: "1" }
+ },
+ {
+ command: "console.table({a: [1, 2], b: [3, 4]})",
+ data: [
+ { _index: "a", 0: "1", 1: "2" },
+ { _index: "b", 0: "3", 1: "4" }
+ ],
+ columns: { _index: "(index)", 0: "0", 1: "1" }
+ },
+ {
+ command: "console.table(family)",
+ data: [
+ { _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
+ { _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
+ { _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
+ { _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
+ ],
+ columns: { _index: "(index)", firstName: "firstName", lastName: "lastName", age: "age" }
+ },
+ {
+ command: "console.table(family, [])",
+ data: [
+ { _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
+ { _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
+ { _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
+ { _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
+ ],
+ columns: { _index: "(index)" }
+ },
+ {
+ command: "console.table(family, ['firstName', 'lastName'])",
+ data: [
+ { _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
+ { _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
+ { _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
+ { _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
+ ],
+ columns: { _index: "(index)", firstName: "firstName", lastName: "lastName" }
+ },
+ {
+ command: "console.table(mySet)",
+ data: [
+ { _index: "0", _value: "1" },
+ { _index: "1", _value: "5" },
+ { _index: "2", _value: "\"some text\"" },
+ { _index: "3", _value: "null" },
+ { _index: "4", _value: "undefined" }
+ ],
+ columns: { _index: "(iteration index)", _value: "Values" }
+ },
+ {
+ command: "console.table(myMap)",
+ data: [
+ { _index: "0", _key: "\"a string\"", _value: "\"value associated with 'a string'\"" },
+ { _index: "1", _key: "5", _value: "\"value associated with 5\"" },
+ ],
+ columns: { _index: "(iteration index)", _key: "Key", _value: "Values" }
+ }
+];
+
+let test = asyncTest(function*() {
+ const {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+
+ for (let testdata of TEST_DATA) {
+ hud.jsterm.clearOutput();
+
+ info("Executing " + testdata.command);
+
+ let onTableRender = once(hud.ui, "messages-table-rendered");
+ hud.jsterm.execute(testdata.command);
+ yield onTableRender;
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: testdata.command + " output",
+ consoleTable: true
+ }],
+ });
+
+ let node = [...result.matched][0];
+ ok(node, "found trace log node");
+
+ let obj = node._messageObject;
+ ok(obj, "console.trace message object");
+
+ ok(obj._data, "found table data object");
+
+ let data = obj._data.map(entries => {
+ let result = {};
+
+ for (let key of Object.keys(entries)) {
+ result[key] = entries[key] instanceof HTMLElement ?
+ entries[key].textContent : entries[key];
+ }
+
+ return result;
+ });
+
+ is(data.toSource(), testdata.data.toSource(), "table data is correct");
+ ok(obj._columns, "found table column object");
+ is(obj._columns.toSource(), testdata.columns.toSource(), "table column is correct");
+ }
+});
diff --git a/browser/devtools/webconsole/test/head.js b/browser/devtools/webconsole/test/head.js
index 352d7e88ad8..0a38806fb77 100644
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -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;
+}
+
diff --git a/browser/devtools/webconsole/test/test-console-extras.html b/browser/devtools/webconsole/test/test-console-extras.html
index ba6331242b4..ae0b521c5b7 100644
--- a/browser/devtools/webconsole/test/test-console-extras.html
+++ b/browser/devtools/webconsole/test/test-console-extras.html
@@ -7,9 +7,6 @@
console.log("start");
console.clear()
console.dirxml()
- console.profile()
- console.profileEnd()
- console.table()
console.log("end");
}
diff --git a/browser/devtools/webconsole/test/test-console-table.html b/browser/devtools/webconsole/test/test-console-table.html
new file mode 100644
index 00000000000..7a3f2333eda
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-table.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ Test for Bug 899753 - console.table support
+
+
+
+ Hello world!
+
+
diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js
index 611231d32b9..82ac1d5c395 100644
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -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;
diff --git a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
index a67eaf6e802..d74b96e3f8e 100644
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -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
diff --git a/browser/themes/shared/devtools/webconsole.inc.css b/browser/themes/shared/devtools/webconsole.inc.css
index f471a4e8746..6ce4ce24b9c 100644
--- a/browser/themes/shared/devtools/webconsole.inc.css
+++ b/browser/themes/shared/devtools/webconsole.inc.css
@@ -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);
}
diff --git a/dom/base/Console.cpp b/dom/base/Console.cpp
index e1dc28f5ae4..06d3ec09abf 100644
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -624,6 +624,7 @@ METHOD(Warn, "warn")
METHOD(Error, "error")
METHOD(Exception, "exception")
METHOD(Debug, "debug")
+METHOD(Table, "table")
void
Console::Trace(JSContext* aCx)
diff --git a/dom/base/Console.h b/dom/base/Console.h
index 6f4de097f2d..99e05120306 100644
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -66,6 +66,9 @@ public:
void
Debug(JSContext* aCx, const Sequence& aData);
+ void
+ Table(JSContext* aCx, const Sequence& aData);
+
void
Trace(JSContext* aCx);
@@ -111,6 +114,7 @@ private:
MethodError,
MethodException,
MethodDebug,
+ MethodTable,
MethodTrace,
MethodDir,
MethodGroup,
diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp
index bc21c8409b2..89d950ca0f0 100644
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -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
diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h
index 9ee2c1f6c74..c6de41eca5f 100644
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -55,7 +55,6 @@ DOMCI_CLASS(ModalContentWindow)
DOMCI_CLASS(MozSmsMessage)
DOMCI_CLASS(MozMmsMessage)
-DOMCI_CLASS(MozSmsFilter)
DOMCI_CLASS(MozMobileMessageThread)
// @font-face in CSS
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
index b875903d32f..060536d8819 100644
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -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;
}
diff --git a/dom/base/test/test_console.xul b/dom/base/test/test_console.xul
index 4dde1fe3db8..a2896eb3cfb 100644
--- a/dom/base/test/test_console.xul
+++ b/dom/base/test/test_console.xul
@@ -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");
diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf
index f5531fde16a..3e7b6dd0b28 100644
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -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)
diff --git a/dom/mobilemessage/interfaces/moz.build b/dom/mobilemessage/interfaces/moz.build
index 62952307a93..e35f078b6df 100644
--- a/dom/mobilemessage/interfaces/moz.build
+++ b/dom/mobilemessage/interfaces/moz.build
@@ -9,7 +9,6 @@ XPIDL_SOURCES += [
'nsIDOMMozMmsMessage.idl',
'nsIDOMMozMobileMessageThread.idl',
'nsIDOMMozSmsMessage.idl',
- 'nsIDOMSmsFilter.idl',
'nsIMmsService.idl',
'nsIMobileMessageCallback.idl',
'nsIMobileMessageCursorCallback.idl',
diff --git a/dom/mobilemessage/interfaces/nsIDOMSmsFilter.idl b/dom/mobilemessage/interfaces/nsIDOMSmsFilter.idl
deleted file mode 100644
index cdc67d1b119..00000000000
--- a/dom/mobilemessage/interfaces/nsIDOMSmsFilter.idl
+++ /dev/null
@@ -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;
-};
diff --git a/dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl b/dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl
index ac7b05c3365..831d145bd90 100644
--- a/dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl
+++ b/dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl
@@ -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);
diff --git a/dom/mobilemessage/src/MobileMessageManager.cpp b/dom/mobilemessage/src/MobileMessageManager.cpp
index 087f72df41b..c5f62a1a784 100644
--- a/dom/mobilemessage/src/MobileMessageManager.cpp
+++ b/dom/mobilemessage/src/MobileMessageManager.cpp
@@ -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
-MobileMessageManager::GetMessages(nsIDOMMozSmsFilter* aFilter,
+MobileMessageManager::GetMessages(const MobileMessageFilter& aFilter,
bool aReverse,
ErrorResult& aRv)
{
@@ -378,16 +377,62 @@ MobileMessageManager::GetMessages(nsIDOMMozSmsFilter* aFilter,
return nullptr;
}
- nsCOMPtr 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 ptrNumbers;
+ uint32_t numbersCount = 0;
+ if (!aFilter.mNumbers.IsNull() &&
+ aFilter.mNumbers.Value().Length()) {
+ const FallibleTArray& 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(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 cursorCallback =
new MobileMessageCursorCallback();
-
nsCOMPtr 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);
diff --git a/dom/mobilemessage/src/MobileMessageManager.h b/dom/mobilemessage/src/MobileMessageManager.h
index d99c224c492..58f64afd687 100644
--- a/dom/mobilemessage/src/MobileMessageManager.h
+++ b/dom/mobilemessage/src/MobileMessageManager.h
@@ -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
- GetMessages(nsIDOMMozSmsFilter* aFilter,
+ GetMessages(const MobileMessageFilter& aFilter,
bool aReverse,
ErrorResult& aRv);
diff --git a/dom/mobilemessage/src/SmsFilter.cpp b/dom/mobilemessage/src/SmsFilter.cpp
deleted file mode 100644
index 590de2d58fa..00000000000
--- a/dom/mobilemessage/src/SmsFilter.cpp
+++ /dev/null
@@ -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 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 aStartDate)
-{
- if (aStartDate.isNull()) {
- mData.startDate() = 0;
- return NS_OK;
- }
-
- if (!aStartDate.isObject()) {
- return NS_ERROR_INVALID_ARG;
- }
-
- JS::Rooted 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 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 aEndDate)
-{
- if (aEndDate.isNull()) {
- mData.endDate() = 0;
- return NS_OK;
- }
-
- if (!aEndDate.isObject()) {
- return NS_ERROR_INVALID_ARG;
- }
-
- JS::Rooted 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 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 aNumbers)
-{
- if (aNumbers.isNull()) {
- mData.numbers().Clear();
- return NS_OK;
- }
-
- if (!aNumbers.isObject()) {
- return NS_ERROR_INVALID_ARG;
- }
-
- JS::Rooted 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 numbers;
-
- for (uint32_t i=0; i 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 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 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 aThreadId)
-{
- if (!mData.threadId()) {
- aThreadId.setNull();
- return NS_OK;
- }
-
- aThreadId.setNumber(static_cast(mData.threadId()));
- return NS_OK;
-}
-
-NS_IMETHODIMP
-SmsFilter::SetThreadId(JSContext* aCx, JS::Handle 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(number);
- if (integer == 0 || integer != number) {
- return NS_ERROR_INVALID_ARG;
- }
- mData.threadId() = integer;
-
- return NS_OK;
-}
-
-} // namespace dom
-} // namespace mozilla
diff --git a/dom/mobilemessage/src/SmsFilter.h b/dom/mobilemessage/src/SmsFilter.h
deleted file mode 100644
index 13754ab2541..00000000000
--- a/dom/mobilemessage/src/SmsFilter.h
+++ /dev/null
@@ -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
diff --git a/dom/mobilemessage/src/Types.h b/dom/mobilemessage/src/Types.h
index 5e29d59a011..92781062dc0 100644
--- a/dom/mobilemessage/src/Types.h
+++ b/dom/mobilemessage/src/Types.h
@@ -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::eReadStatus_EndGuard>
{};
-/**
- * Read state serializer.
- */
-template <>
-struct ParamTraits
- : public ContiguousEnumSerializer<
- mozilla::dom::mobilemessage::ReadState,
- mozilla::dom::mobilemessage::eReadState_Unknown,
- mozilla::dom::mobilemessage::eReadState_EndGuard>
-{};
-
/**
* Message class serializer.
*/
diff --git a/dom/mobilemessage/src/android/MobileMessageDatabaseService.cpp b/dom/mobilemessage/src/android/MobileMessageDatabaseService.cpp
index 5c540a512f0..6bf31333b00 100644
--- a/dom/mobilemessage/src/android/MobileMessageDatabaseService.cpp
+++ b/dom/mobilemessage/src/android/MobileMessageDatabaseService.cpp
@@ -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)
diff --git a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
index 3ba8d2ebe53..0f853ac7340 100644
--- a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
+++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
@@ -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;
diff --git a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
index f31a16d9f05..0b10153aadb 100644
--- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
@@ -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) {
diff --git a/dom/mobilemessage/src/ipc/SmsIPCService.cpp b/dom/mobilemessage/src/ipc/SmsIPCService.cpp
index 83f2931947c..bc03cc14370 100644
--- a/dom/mobilemessage/src/ipc/SmsIPCService.cpp
+++ b/dom/mobilemessage/src/ipc/SmsIPCService.cpp
@@ -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(aFilter)->GetData());
+ SmsFilterData data;
+
+ data.hasStartDate() = aHasStartDate;
+ data.startDate() = aStartDate;
+ data.hasEndDate() = aHasEndDate;
+ data.startDate() = aEndDate;
+
+ if (aNumbersCount && aNumbers) {
+ nsTArray& 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);
diff --git a/dom/mobilemessage/src/ipc/SmsParent.cpp b/dom/mobilemessage/src/ipc/SmsParent.cpp
index 6f761786045..3762c196679 100644
--- a/dom/mobilemessage/src/ipc/SmsParent.cpp
+++ b/dom/mobilemessage/src/ipc/SmsParent.cpp
@@ -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 dbService =
do_GetService(MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
if (dbService) {
- nsCOMPtr filter = new SmsFilter(aRequest.filter());
- bool reverse = aRequest.reverse();
+ const SmsFilterData& filter = aRequest.filter();
- rv = dbService->CreateMessageCursor(filter, reverse, this,
+ const nsTArray& numbers = filter.numbers();
+ nsAutoArrayPtr 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));
}
diff --git a/dom/mobilemessage/src/ipc/SmsTypes.ipdlh b/dom/mobilemessage/src/ipc/SmsTypes.ipdlh
index 5cc1283310c..a3450a258a3 100644
--- a/dom/mobilemessage/src/ipc/SmsTypes.ipdlh
+++ b/dom/mobilemessage/src/ipc/SmsTypes.ipdlh
@@ -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;
};
diff --git a/dom/mobilemessage/src/moz.build b/dom/mobilemessage/src/moz.build
index d889facc77a..e7e9ea74169 100644
--- a/dom/mobilemessage/src/moz.build
+++ b/dom/mobilemessage/src/moz.build
@@ -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',
]
diff --git a/dom/mobilemessage/tests/marionette/head.js b/dom/mobilemessage/tests/marionette/head.js
index aab56904164..c7872041556 100644
--- a/dom/mobilemessage/tests/marionette/head.js
+++ b/dom/mobilemessage/tests/marionette/head.js
@@ -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) {
diff --git a/dom/mobilemessage/tests/marionette/mmdb_head.js b/dom/mobilemessage/tests/marionette/mmdb_head.js
index 11d120b203d..6537783cdd8 100644
--- a/dom/mobilemessage/tests/marionette/mmdb_head.js
+++ b/dom/mobilemessage/tests/marionette/mmdb_head.js
@@ -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);
}
/**
diff --git a/dom/mobilemessage/tests/marionette/test_filter_date.js b/dom/mobilemessage/tests/marionette/test_filter_date.js
index 12108dc4872..adb731e5c19 100644
--- a/dom/mobilemessage/tests/marionette/test_filter_date.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_date.js
@@ -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, []));
});
diff --git a/dom/mobilemessage/tests/marionette/test_filter_mixed.js b/dom/mobilemessage/tests/marionette/test_filter_mixed.js
index 5c1e8ce763b..cac2d6e4e6b 100644
--- a/dom/mobilemessage/tests/marionette/test_filter_mixed.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_mixed.js
@@ -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");
diff --git a/dom/mobilemessage/tests/marionette/test_filter_number.js b/dom/mobilemessage/tests/marionette/test_filter_number.js
index 9d37dc5536e..0688714f76a 100644
--- a/dom/mobilemessage/tests/marionette/test_filter_number.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_number.js
@@ -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.
diff --git a/dom/mobilemessage/tests/marionette/test_filter_read.js b/dom/mobilemessage/tests/marionette/test_filter_read.js
index 5ffb0d5aa71..cbe67c62413 100644
--- a/dom/mobilemessage/tests/marionette/test_filter_read.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_read.js
@@ -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);
diff --git a/dom/mobilemessage/tests/marionette/test_filter_received.js b/dom/mobilemessage/tests/marionette/test_filter_received.js
index f935ed5ed27..6e1d4da2e97 100644
--- a/dom/mobilemessage/tests/marionette/test_filter_received.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_received.js
@@ -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);
diff --git a/dom/mobilemessage/tests/marionette/test_filter_sent.js b/dom/mobilemessage/tests/marionette/test_filter_sent.js
index cd9655fe39b..e4d6f07d3be 100644
--- a/dom/mobilemessage/tests/marionette/test_filter_sent.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_sent.js
@@ -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);
diff --git a/dom/mobilemessage/tests/marionette/test_filter_unread.js b/dom/mobilemessage/tests/marionette/test_filter_unread.js
index 6d1b041cc62..1a4afad1a32 100644
--- a/dom/mobilemessage/tests/marionette/test_filter_unread.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_unread.js
@@ -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);
diff --git a/dom/mobilemessage/tests/marionette/test_getthreads.js b/dom/mobilemessage/tests/marionette/test_getthreads.js
index 1c80903c65c..75b363a57a5 100644
--- a/dom/mobilemessage/tests/marionette/test_getthreads.js
+++ b/dom/mobilemessage/tests/marionette/test_getthreads.js
@@ -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");
diff --git a/dom/mobilemessage/tests/marionette/test_invalid_address.js b/dom/mobilemessage/tests/marionette/test_invalid_address.js
index a1e1b4b8ecf..5464c34d77a 100644
--- a/dom/mobilemessage/tests/marionette/test_invalid_address.js
+++ b/dom/mobilemessage/tests/marionette/test_invalid_address.js
@@ -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) {
diff --git a/dom/mobilemessage/tests/marionette/test_message_classes.js b/dom/mobilemessage/tests/marionette/test_message_classes.js
index a8a332f153b..6c067ba82a5 100644
--- a/dom/mobilemessage/tests/marionette/test_message_classes.js
+++ b/dom/mobilemessage/tests/marionette/test_message_classes.js
@@ -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.
diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_full_storage.js b/dom/mobilemessage/tests/marionette/test_mmdb_full_storage.js
index a7fee738b60..ca3e4768c2b 100644
--- a/dom/mobilemessage/tests/marionette/test_mmdb_full_storage.js
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_full_storage.js
@@ -165,7 +165,7 @@ function testCreateMessageCursor(aMmdb) {
log("testCreateMessageCursor()");
setStorageFull(true);
- return createMessageCursor(aMmdb, {}, false)
+ return createMessageCursor(aMmdb)
.then(() => setStorageFull(false));
}
diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
index a2177842084..b315a4467ce 100644
--- a/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
@@ -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");
diff --git a/dom/mobilemessage/tests/marionette/test_mmsmessage_attachments.js b/dom/mobilemessage/tests/marionette/test_mmsmessage_attachments.js
index 77b2ee9e2db..8db959c98e8 100644
--- a/dom/mobilemessage/tests/marionette/test_mmsmessage_attachments.js
+++ b/dom/mobilemessage/tests/marionette/test_mmsmessage_attachments.js
@@ -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) {
diff --git a/dom/mobilemessage/tests/marionette/test_phone_number_normalization.js b/dom/mobilemessage/tests/marionette/test_phone_number_normalization.js
index 3bf940f3f36..b2785a1e7c9 100644
--- a/dom/mobilemessage/tests/marionette/test_phone_number_normalization.js
+++ b/dom/mobilemessage/tests/marionette/test_phone_number_normalization.js
@@ -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) {
diff --git a/dom/mobilemessage/tests/marionette/test_update_thread_record_in_delete.js b/dom/mobilemessage/tests/marionette/test_update_thread_record_in_delete.js
index e9610a636ae..a11710e91bc 100644
--- a/dom/mobilemessage/tests/marionette/test_update_thread_record_in_delete.js
+++ b/dom/mobilemessage/tests/marionette/test_update_thread_record_in_delete.js
@@ -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) {
diff --git a/dom/mobilemessage/tests/mochitest/mochitest.ini b/dom/mobilemessage/tests/mochitest/mochitest.ini
index c0edbf36c77..21a8fb33b32 100644
--- a/dom/mobilemessage/tests/mochitest/mochitest.ini
+++ b/dom/mobilemessage/tests/mochitest/mochitest.ini
@@ -3,4 +3,3 @@ skip-if = e10s
[test_sms_basics.html]
skip-if = toolkit == 'android' #Bug 909036
-[test_smsfilter.html]
diff --git a/dom/mobilemessage/tests/mochitest/test_sms_basics.html b/dom/mobilemessage/tests/mochitest/test_sms_basics.html
index e6a3a5c1b36..37a7ac0b42f 100644
--- a/dom/mobilemessage/tests/mochitest/test_sms_basics.html
+++ b/dom/mobilemessage/tests/mochitest/test_sms_basics.html
@@ -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() {
diff --git a/dom/mobilemessage/tests/mochitest/test_smsfilter.html b/dom/mobilemessage/tests/mochitest/test_smsfilter.html
deleted file mode 100644
index 922dc462f2d..00000000000
--- a/dom/mobilemessage/tests/mochitest/test_smsfilter.html
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
- Test SmsFilter
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/dom/tests/mochitest/general/test_consoleAPI.html b/dom/tests/mochitest/general/test_consoleAPI.html
index f594930c4cb..85038a3a94f 100644
--- a/dom/tests/mochitest/general/test_consoleAPI.html
+++ b/dom/tests/mochitest/general/test_consoleAPI.html
@@ -37,6 +37,7 @@ function doTest() {
"profileEnd": "function",
"assert": "function",
"count": "function",
+ "table": "function",
"__noSuchMethod__": "function"
};
diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html
index 61f83350e8d..f09bef561d5 100644
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -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!
diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
index 35587689cad..a4f7af32592 100644
--- a/dom/webidl/Console.webidl
+++ b/dom/webidl/Console.webidl
@@ -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);
diff --git a/dom/webidl/MozMobileMessageManager.webidl b/dom/webidl/MozMobileMessageManager.webidl
index 6f92d3de162..1111ae35f67 100644
--- a/dom/webidl/MozMobileMessageManager.webidl
+++ b/dom/webidl/MozMobileMessageManager.webidl
@@ -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? 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]
diff --git a/gfx/ipc/GfxMessageUtils.h b/gfx/ipc/GfxMessageUtils.h
index 64b644c846f..f672400013a 100644
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -761,6 +761,7 @@ struct ParamTraits
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
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) &&
diff --git a/gfx/layers/FrameMetrics.h b/gfx/layers/FrameMetrics.h
index c8b5e96a078..bcf5e43b73f 100644
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -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).
//
diff --git a/gfx/layers/LayerMetricsWrapper.h b/gfx/layers/LayerMetricsWrapper.h
new file mode 100644
index 00000000000..40b6d6dbbf4
--- /dev/null
+++ b/gfx/layers/LayerMetricsWrapper.h
@@ -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 */
diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp
index bbf7342da8a..e5d0f660c99 100644
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -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 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& aArray)
+{
+ if (!mRoot) {
+ return;
+ }
+
+ FrameMetrics::ViewID rootScrollableId = GetRootScrollableLayerId();
+ if (rootScrollableId == FrameMetrics::NULL_SCROLL_ID) {
+ aArray.AppendElement(mRoot);
+ return;
}
nsTArray 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& 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], "", "]");
+ }
}
}
diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h
index c4f522f8e23..305a30fd6f9 100644
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -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& 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&) 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& 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& 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 mMaskLayer;
gfx::UserData mUserData;
nsIntRegion mVisibleRegion;
- FrameMetrics mFrameMetrics;
- FrameMetrics::ViewID mScrollHandoffParentId;
+ nsTArray 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 mAPZC;
+ nsTArray > mApzcs;
uint32_t mContentFlags;
bool mUseClipRect;
bool mUseTileSourceRect;
diff --git a/gfx/layers/LayersLogging.cpp b/gfx/layers/LayersLogging.cpp
index c6b177fa412..546fa33aedb 100644
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -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();
}
diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp
index c315d7d56bb..2d8ca499161 100644
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -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);
diff --git a/gfx/layers/apz/src/APZCTreeManager.h b/gfx/layers/apz/src/APZCTreeManager.h
index 4a418ce7636..01d02410cd2 100644
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -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,
diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp
index 65ede5b296d..e03517feeaf 100644
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -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);
diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h
index 9ea87705141..1e02cae76fa 100644
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -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
diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp
index d18db48c54b..bc7362e994d 100644
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -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()
diff --git a/gfx/layers/client/ClientTiledThebesLayer.cpp b/gfx/layers/client/ClientTiledThebesLayer.cpp
index 901fa81be26..6eea8757a7f 100644
--- a/gfx/layers/client/ClientTiledThebesLayer.cpp
+++ b/gfx/layers/client/ClientTiledThebesLayer.cpp
@@ -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();
diff --git a/gfx/layers/client/ClientTiledThebesLayer.h b/gfx/layers/client/ClientTiledThebesLayer.h
index f4539ee3fc4..1cd6369b200 100644
--- a/gfx/layers/client/ClientTiledThebesLayer.h
+++ b/gfx/layers/client/ClientTiledThebesLayer.h
@@ -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()
diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp
index 74edb632824..9c7295c095c 100644
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -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(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);
diff --git a/gfx/layers/client/TiledContentClient.h b/gfx/layers/client/TiledContentClient.h
index 04c35cd523d..cc39ee40eec 100644
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -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);
diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp
index 24f52f6bc89..e8fd0d1320d 100644
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -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 scrollableLayers;
#ifdef MOZ_WIDGET_ANDROID
- scrollableLayers.AppendElement(mLayerManager->GetPrimaryScrollableLayer());
+ mLayerManager->GetRootScrollableLayers(scrollableLayers);
#else
mLayerManager->GetScrollableLayers(scrollableLayers);
#endif
diff --git a/gfx/layers/composite/AsyncCompositionManager.h b/gfx/layers/composite/AsyncCompositionManager.h
index 17af6acf27c..b07009cd51f 100644
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -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);
diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp
index 32e8a5685d2..e87ce2ac976 100644
--- a/gfx/layers/composite/ContainerLayerComposite.cpp
+++ b/gfx/layers/composite/ContainerLayerComposite.cpp
@@ -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);
diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp
index 1f922dc8e29..2ce8b0745e1 100644
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -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 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
diff --git a/gfx/layers/composite/ThebesLayerComposite.cpp b/gfx/layers/composite/ThebesLayerComposite.cpp
index f876bd5f224..8b1b7bc2803 100644
--- a/gfx/layers/composite/ThebesLayerComposite.cpp
+++ b/gfx/layers/composite/ThebesLayerComposite.cpp
@@ -172,19 +172,6 @@ ThebesLayerComposite::GenEffectChain(EffectChain& aEffect)
aEffect.mPrimaryEffect = mBuffer->GenEffect(GetEffectFilter());
}
-CSSToScreenScale
-ThebesLayerComposite::GetEffectiveResolution()
-{
- for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
- const FrameMetrics& metrics = parent->GetFrameMetrics();
- if (metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) {
- return metrics.GetZoom();
- }
- }
-
- return CSSToScreenScale(1.0);
-}
-
void
ThebesLayerComposite::PrintInfo(std::stringstream& aStream, const char* aPrefix)
{
diff --git a/gfx/layers/composite/ThebesLayerComposite.h b/gfx/layers/composite/ThebesLayerComposite.h
index 5d64fbc4c86..71defa3a573 100644
--- a/gfx/layers/composite/ThebesLayerComposite.h
+++ b/gfx/layers/composite/ThebesLayerComposite.h
@@ -86,8 +86,6 @@ protected:
private:
gfx::Filter GetEffectFilter() { return gfx::Filter::LINEAR; }
- CSSToScreenScale GetEffectiveResolution();
-
private:
RefPtr mBuffer;
};
diff --git a/gfx/layers/composite/TiledContentHost.cpp b/gfx/layers/composite/TiledContentHost.cpp
index 3d0058cd4a0..02b97205e7f 100644
--- a/gfx/layers/composite/TiledContentHost.cpp
+++ b/gfx/layers/composite/TiledContentHost.cpp
@@ -9,6 +9,7 @@
#include "mozilla/gfx/Matrix.h" // for Matrix4x4
#include "mozilla/layers/Compositor.h" // for Compositor
#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc
+#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL
#include "nsAString.h"
#include "nsDebug.h" // for NS_WARNING
@@ -367,7 +368,7 @@ TiledContentHost::Composite(EffectChain& aEffectChain,
// Background colors are only stored on scrollable layers. Grab
// the one from the nearest scrollable ancestor layer.
for (Layer* ancestor = GetLayer(); ancestor; ancestor = ancestor->GetParent()) {
- if (ancestor->GetFrameMetrics().IsScrollable()) {
+ if (ancestor->HasScrollableFrameMetrics()) {
backgroundColor = ancestor->GetBackgroundColor();
break;
}
diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp
index bee80bffb68..f748a3c321b 100644
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -319,7 +319,6 @@ LayerTransactionParent::RecvUpdate(const InfallibleTArray& cset,
layer->SetAnimations(common.animations());
layer->SetInvalidRegion(common.invalidRegion());
layer->SetFrameMetrics(common.metrics());
- layer->SetScrollHandoffParentId(common.scrollParentId());
layer->SetBackgroundColor(common.backgroundColor().value());
layer->SetContentDescription(common.contentDescription());
@@ -680,6 +679,7 @@ LayerTransactionParent::RecvGetAnimationTransform(PLayerParent* aParent,
bool
LayerTransactionParent::RecvSetAsyncScrollOffset(PLayerParent* aLayer,
+ const FrameMetrics::ViewID& aId,
const int32_t& aX, const int32_t& aY)
{
if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
@@ -690,7 +690,13 @@ LayerTransactionParent::RecvSetAsyncScrollOffset(PLayerParent* aLayer,
if (!layer) {
return false;
}
- AsyncPanZoomController* controller = layer->GetAsyncPanZoomController();
+ AsyncPanZoomController* controller = nullptr;
+ for (uint32_t i = 0; i < layer->GetFrameMetricsCount(); i++) {
+ if (layer->GetFrameMetrics(i).GetScrollId() == aId) {
+ controller = layer->GetAsyncPanZoomController(i);
+ break;
+ }
+ }
if (!controller) {
return false;
}
diff --git a/gfx/layers/ipc/LayerTransactionParent.h b/gfx/layers/ipc/LayerTransactionParent.h
index dd428127e5f..c3401d17ef6 100644
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -124,7 +124,7 @@ protected:
virtual bool RecvGetAnimationTransform(PLayerParent* aParent,
MaybeTransform* aTransform)
MOZ_OVERRIDE;
- virtual bool RecvSetAsyncScrollOffset(PLayerParent* aLayer,
+ virtual bool RecvSetAsyncScrollOffset(PLayerParent* aLayer, const FrameMetrics::ViewID& aId,
const int32_t& aX, const int32_t& aY) MOZ_OVERRIDE;
virtual bool RecvGetAPZTestData(APZTestData* aOutData);
diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh
index 10777cf6bdd..0ac7c4fa324 100644
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -215,8 +215,7 @@ struct CommonLayerAttributes {
// Animated colors will only honored for ColorLayers.
Animation[] animations;
nsIntRegion invalidRegion;
- FrameMetrics metrics;
- ViewID scrollParentId;
+ FrameMetrics[] metrics;
LayerColor backgroundColor;
string contentDescription;
};
diff --git a/gfx/layers/ipc/PLayerTransaction.ipdl b/gfx/layers/ipc/PLayerTransaction.ipdl
index 5f3a3debcd5..99faa20c90a 100644
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -19,6 +19,7 @@ using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h
using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h";
using class mozilla::layers::APZTestData from "mozilla/layers/APZTestData.h";
+using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
/**
* The layers protocol is spoken between thread contexts that manage
@@ -83,7 +84,7 @@ parent:
// The next time this layer is composited, add this async scroll offset in
// CSS pixels.
// Useful for testing rendering of async scrolling.
- async SetAsyncScrollOffset(PLayer layer, int32_t x, int32_t y);
+ async SetAsyncScrollOffset(PLayer layer, ViewID id, int32_t x, int32_t y);
// Drop any front buffers that might be retained on the compositor
// side.
diff --git a/gfx/layers/ipc/ShadowLayers.cpp b/gfx/layers/ipc/ShadowLayers.cpp
index 59b8a34fb2d..a83b6847fa9 100644
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -607,8 +607,7 @@ ShadowLayerForwarder::EndTransaction(InfallibleTArray* aReplies,
common.maskLayerParent() = nullptr;
common.animations() = mutant->GetAnimations();
common.invalidRegion() = mutant->GetInvalidRegion();
- common.metrics() = mutant->GetFrameMetrics();
- common.scrollParentId() = mutant->GetScrollHandoffParentId();
+ common.metrics() = mutant->GetAllFrameMetrics();
common.backgroundColor() = mutant->GetBackgroundColor();
common.contentDescription() = mutant->GetContentDescription();
attrs.specific() = null_t();
diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build
index 85b25b9a90f..285c0c050f0 100644
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -163,6 +163,7 @@ EXPORTS.mozilla.layers += [
'ipc/SharedBufferManagerParent.h',
'ipc/SharedPlanarYCbCrImage.h',
'ipc/SharedRGBImage.h',
+ 'LayerMetricsWrapper.h',
'LayersTypes.h',
'opengl/CompositingRenderTargetOGL.h',
'opengl/CompositorOGL.h',
diff --git a/gfx/tests/gtest/TestAsyncPanZoomController.cpp b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
index 35678c6c7ca..b5d563f0662 100644
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -12,6 +12,7 @@
#include "mozilla/layers/GeckoContentController.h"
#include "mozilla/layers/CompositorParent.h"
#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/LayerMetricsWrapper.h"
#include "mozilla/UniquePtr.h"
#include "base/task.h"
#include "Layers.h"
@@ -1495,7 +1496,7 @@ TEST_F(APZHitTestingTester, HitTesting1) {
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
hit = GetTargetAPZC(ScreenPoint(15, 15));
- EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+ EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
// expect hit point at LayerIntPoint(15, 15)
EXPECT_EQ(Point(15, 15), transformToApzc * Point(15, 15));
EXPECT_EQ(Point(15, 15), transformToGecko * Point(15, 15));
@@ -1503,9 +1504,9 @@ TEST_F(APZHitTestingTester, HitTesting1) {
// Now we have a sub APZC with a better fit
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
- EXPECT_NE(root->GetAsyncPanZoomController(), layers[3]->GetAsyncPanZoomController());
+ EXPECT_NE(root->GetAsyncPanZoomController(0), layers[3]->GetAsyncPanZoomController(0));
hit = GetTargetAPZC(ScreenPoint(25, 25));
- EXPECT_EQ(layers[3]->GetAsyncPanZoomController(), hit.get());
+ EXPECT_EQ(layers[3]->GetAsyncPanZoomController(0), hit.get());
// expect hit point at LayerIntPoint(25, 25)
EXPECT_EQ(Point(25, 25), transformToApzc * Point(25, 25));
EXPECT_EQ(Point(25, 25), transformToGecko * Point(25, 25));
@@ -1513,20 +1514,20 @@ TEST_F(APZHitTestingTester, HitTesting1) {
// At this point, layers[4] obscures layers[3] at the point (15, 15) so
// hitting there should hit the root APZC
hit = GetTargetAPZC(ScreenPoint(15, 15));
- EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+ EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
// Now test hit testing when we have two scrollable layers
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, paintSequenceNumber++);
hit = GetTargetAPZC(ScreenPoint(15, 15));
- EXPECT_EQ(layers[4]->GetAsyncPanZoomController(), hit.get());
+ EXPECT_EQ(layers[4]->GetAsyncPanZoomController(0), hit.get());
// expect hit point at LayerIntPoint(15, 15)
EXPECT_EQ(Point(15, 15), transformToApzc * Point(15, 15));
EXPECT_EQ(Point(15, 15), transformToGecko * Point(15, 15));
// Hit test ouside the reach of layer[3,4] but inside root
hit = GetTargetAPZC(ScreenPoint(90, 90));
- EXPECT_EQ(root->GetAsyncPanZoomController(), hit.get());
+ EXPECT_EQ(root->GetAsyncPanZoomController(0), hit.get());
// expect hit point at LayerIntPoint(90, 90)
EXPECT_EQ(Point(90, 90), transformToApzc * Point(90, 90));
EXPECT_EQ(Point(90, 90), transformToGecko * Point(90, 90));
@@ -1555,9 +1556,9 @@ TEST_F(APZHitTestingTester, HitTesting2) {
// layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
// layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
- AsyncPanZoomController* apzcroot = root->GetAsyncPanZoomController();
- AsyncPanZoomController* apzc1 = layers[1]->GetAsyncPanZoomController();
- AsyncPanZoomController* apzc3 = layers[3]->GetAsyncPanZoomController();
+ AsyncPanZoomController* apzcroot = root->GetAsyncPanZoomController(0);
+ AsyncPanZoomController* apzc1 = layers[1]->GetAsyncPanZoomController(0);
+ AsyncPanZoomController* apzc3 = layers[3]->GetAsyncPanZoomController(0);
// Hit an area that's clearly on the root layer but not any of the child layers.
nsRefPtr hit = GetTargetAPZC(ScreenPoint(75, 25));
@@ -1670,21 +1671,21 @@ TEST_F(APZCTreeManagerTester, ScrollableThebesLayers) {
AsyncPanZoomController* nullAPZC = nullptr;
// so they should have the same APZC
- EXPECT_EQ(nullAPZC, layers[0]->GetAsyncPanZoomController());
- EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController());
- EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController());
- EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+ EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
+ EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController(0));
+ EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController(0));
+ EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
// Change the scrollId of layers[1], and verify the APZC changes
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
- EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+ EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
// Change the scrollId of layers[2] to match that of layers[1], ensure we get the same
// APZC for both again
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
- EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
+ EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
}
TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
@@ -1694,32 +1695,32 @@ TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
AsyncPanZoomController* nullAPZC = nullptr;
// Ensure all the scrollable layers have an APZC
- EXPECT_EQ(nullAPZC, layers[0]->GetAsyncPanZoomController());
- EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController());
- EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController());
- EXPECT_EQ(nullAPZC, layers[3]->GetAsyncPanZoomController());
- EXPECT_NE(nullAPZC, layers[4]->GetAsyncPanZoomController());
- EXPECT_EQ(nullAPZC, layers[5]->GetAsyncPanZoomController());
- EXPECT_NE(nullAPZC, layers[6]->GetAsyncPanZoomController());
- EXPECT_NE(nullAPZC, layers[7]->GetAsyncPanZoomController());
- EXPECT_NE(nullAPZC, layers[8]->GetAsyncPanZoomController());
+ EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
+ EXPECT_NE(nullAPZC, layers[1]->GetAsyncPanZoomController(0));
+ EXPECT_NE(nullAPZC, layers[2]->GetAsyncPanZoomController(0));
+ EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
+ EXPECT_NE(nullAPZC, layers[4]->GetAsyncPanZoomController(0));
+ EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
+ EXPECT_NE(nullAPZC, layers[6]->GetAsyncPanZoomController(0));
+ EXPECT_NE(nullAPZC, layers[7]->GetAsyncPanZoomController(0));
+ EXPECT_NE(nullAPZC, layers[8]->GetAsyncPanZoomController(0));
// Ensure those that scroll together have the same APZCs
- EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), layers[2]->GetAsyncPanZoomController());
- EXPECT_EQ(layers[4]->GetAsyncPanZoomController(), layers[6]->GetAsyncPanZoomController());
+ EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), layers[2]->GetAsyncPanZoomController(0));
+ EXPECT_EQ(layers[4]->GetAsyncPanZoomController(0), layers[6]->GetAsyncPanZoomController(0));
// Ensure those that don't scroll together have different APZCs
- EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[4]->GetAsyncPanZoomController());
- EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[7]->GetAsyncPanZoomController());
- EXPECT_NE(layers[1]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
- EXPECT_NE(layers[4]->GetAsyncPanZoomController(), layers[7]->GetAsyncPanZoomController());
- EXPECT_NE(layers[4]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
- EXPECT_NE(layers[7]->GetAsyncPanZoomController(), layers[8]->GetAsyncPanZoomController());
+ EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[4]->GetAsyncPanZoomController(0));
+ EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[7]->GetAsyncPanZoomController(0));
+ EXPECT_NE(layers[1]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
+ EXPECT_NE(layers[4]->GetAsyncPanZoomController(0), layers[7]->GetAsyncPanZoomController(0));
+ EXPECT_NE(layers[4]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
+ EXPECT_NE(layers[7]->GetAsyncPanZoomController(0), layers[8]->GetAsyncPanZoomController(0));
nsRefPtr hit = GetTargetAPZC(ScreenPoint(25, 25));
- EXPECT_EQ(layers[1]->GetAsyncPanZoomController(), hit.get());
+ EXPECT_EQ(layers[1]->GetAsyncPanZoomController(0), hit.get());
hit = GetTargetAPZC(ScreenPoint(275, 375));
- EXPECT_EQ(layers[8]->GetAsyncPanZoomController(), hit.get());
+ EXPECT_EQ(layers[8]->GetAsyncPanZoomController(0), hit.get());
hit = GetTargetAPZC(ScreenPoint(250, 100));
- EXPECT_EQ(layers[7]->GetAsyncPanZoomController(), hit.get());
+ EXPECT_EQ(layers[7]->GetAsyncPanZoomController(0), hit.get());
}
class APZOverscrollHandoffTester : public APZCTreeManagerTester {
@@ -1728,7 +1729,9 @@ protected:
TestAsyncPanZoomController* rootApzc;
void SetScrollHandoff(Layer* aChild, Layer* aParent) {
- aChild->SetScrollHandoffParentId(aParent->GetFrameMetrics().GetScrollId());
+ FrameMetrics metrics = aChild->GetFrameMetrics(0);
+ metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
+ aChild->SetFrameMetrics(metrics);
}
void CreateOverscrollHandoffLayerTree1() {
@@ -1743,7 +1746,7 @@ protected:
SetScrollHandoff(layers[1], root);
registration = MakeUnique(0, root, mcc);
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
- rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+ rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
}
void CreateOverscrollHandoffLayerTree2() {
@@ -1763,7 +1766,7 @@ protected:
// and this is the second layer tree for a particular test.
MOZ_ASSERT(registration);
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
- rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+ rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
}
void CreateOverscrollHandoffLayerTree3() {
@@ -1798,7 +1801,7 @@ protected:
SetScrollHandoff(layers[1], root);
registration = MakeUnique(0, root, mcc);
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
- rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController();
+ rootApzc = (TestAsyncPanZoomController*)root->GetAsyncPanZoomController(0);
rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
}
};
@@ -1810,7 +1813,7 @@ TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) {
// Set up the APZC tree.
CreateOverscrollHandoffLayerTree1();
- TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+ TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
// Enable touch-listeners so that we can separate the queueing of input
// events from them being processed.
@@ -1837,7 +1840,7 @@ TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) {
// Set up an initial APZC tree.
CreateOverscrollHandoffLayerTree1();
- TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+ TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
// Enable touch-listeners so that we can separate the queueing of input
// events from them being processed.
@@ -1852,7 +1855,7 @@ TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) {
CreateOverscrollHandoffLayerTree2();
nsRefPtr middle = layers[1];
childApzc->GetFrameMetrics().mMayHaveTouchListeners = true;
- TestAsyncPanZoomController* middleApzc = (TestAsyncPanZoomController*)middle->GetAsyncPanZoomController();
+ TestAsyncPanZoomController* middleApzc = (TestAsyncPanZoomController*)middle->GetAsyncPanZoomController(0);
// Queue input events for another pan.
ApzcPanNoFling(childApzc, time, 30, 90);
@@ -1882,10 +1885,10 @@ TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) {
// Set up an initial APZC tree.
CreateOverscrollHandoffLayerTree3();
- TestAsyncPanZoomController* parent1 = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
- TestAsyncPanZoomController* child1 = (TestAsyncPanZoomController*)layers[2]->GetAsyncPanZoomController();
- TestAsyncPanZoomController* parent2 = (TestAsyncPanZoomController*)layers[3]->GetAsyncPanZoomController();
- TestAsyncPanZoomController* child2 = (TestAsyncPanZoomController*)layers[4]->GetAsyncPanZoomController();
+ TestAsyncPanZoomController* parent1 = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
+ TestAsyncPanZoomController* child1 = (TestAsyncPanZoomController*)layers[2]->GetAsyncPanZoomController(0);
+ TestAsyncPanZoomController* parent2 = (TestAsyncPanZoomController*)layers[3]->GetAsyncPanZoomController(0);
+ TestAsyncPanZoomController* child2 = (TestAsyncPanZoomController*)layers[4]->GetAsyncPanZoomController(0);
// Pan on the lower child.
int time = 0;
@@ -1914,7 +1917,7 @@ TEST_F(APZOverscrollHandoffTester, Scrollgrab) {
// Set up the layer tree
CreateScrollgrabLayerTree();
- TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+ TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
// Pan on the child, enough to fully scroll the scrollgrab parent (20 px)
// and leave some more (another 15 px) for the child.
@@ -1930,7 +1933,7 @@ TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) {
// Set up the layer tree
CreateScrollgrabLayerTree();
- TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController();
+ TestAsyncPanZoomController* childApzc = (TestAsyncPanZoomController*)layers[1]->GetAsyncPanZoomController(0);
// Pan on the child, not enough to fully scroll the scrollgrab parent.
int time = 0;
diff --git a/gfx/tests/gtest/TestLayers.cpp b/gfx/tests/gtest/TestLayers.cpp
index 65ceecddef7..8917cec8c23 100644
--- a/gfx/tests/gtest/TestLayers.cpp
+++ b/gfx/tests/gtest/TestLayers.cpp
@@ -6,6 +6,7 @@
#include "TestLayers.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
+#include "mozilla/layers/LayerMetricsWrapper.h"
using namespace mozilla;
using namespace mozilla::gfx;
@@ -322,3 +323,130 @@ TEST(Layers, RepositionChild) {
ASSERT_EQ(layers[1], layers[2]->GetNextSibling());
ASSERT_EQ(nullptr, layers[1]->GetNextSibling());
}
+
+TEST(LayerMetricsWrapper, SimpleTree) {
+ nsTArray > layers;
+ nsRefPtr lm;
+ nsRefPtr root = CreateLayerTree("c(c(c(tt)c(t)))", nullptr, nullptr, lm, layers);
+ LayerMetricsWrapper wrapper(root);
+
+ ASSERT_EQ(root.get(), wrapper.GetLayer());
+ wrapper = wrapper.GetFirstChild();
+ ASSERT_EQ(layers[1].get(), wrapper.GetLayer());
+ ASSERT_FALSE(wrapper.GetNextSibling().IsValid());
+ wrapper = wrapper.GetFirstChild();
+ ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+ wrapper = wrapper.GetFirstChild();
+ ASSERT_EQ(layers[3].get(), wrapper.GetLayer());
+ ASSERT_FALSE(wrapper.GetFirstChild().IsValid());
+ wrapper = wrapper.GetNextSibling();
+ ASSERT_EQ(layers[4].get(), wrapper.GetLayer());
+ ASSERT_FALSE(wrapper.GetNextSibling().IsValid());
+ wrapper = wrapper.GetParent();
+ ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+ wrapper = wrapper.GetNextSibling();
+ ASSERT_EQ(layers[5].get(), wrapper.GetLayer());
+ ASSERT_FALSE(wrapper.GetNextSibling().IsValid());
+ wrapper = wrapper.GetLastChild();
+ ASSERT_EQ(layers[6].get(), wrapper.GetLayer());
+ wrapper = wrapper.GetParent();
+ ASSERT_EQ(layers[5].get(), wrapper.GetLayer());
+ LayerMetricsWrapper layer5 = wrapper;
+ wrapper = wrapper.GetPrevSibling();
+ ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+ wrapper = wrapper.GetParent();
+ ASSERT_EQ(layers[1].get(), wrapper.GetLayer());
+ ASSERT_TRUE(layer5 == wrapper.GetLastChild());
+ LayerMetricsWrapper rootWrapper(root);
+ ASSERT_TRUE(rootWrapper == wrapper.GetParent());
+}
+
+static FrameMetrics
+MakeMetrics(FrameMetrics::ViewID aId) {
+ FrameMetrics metrics;
+ metrics.SetScrollId(aId);
+ return metrics;
+}
+
+TEST(LayerMetricsWrapper, MultiFramemetricsTree) {
+ nsTArray > layers;
+ nsRefPtr lm;
+ nsRefPtr root = CreateLayerTree("c(c(c(tt)c(t)))", nullptr, nullptr, lm, layers);
+
+ nsTArray metrics;
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 0)); // topmost of root layer
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 1));
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 2));
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID)); // bottom of root layer
+ root->SetFrameMetrics(metrics);
+
+ metrics.Clear();
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 3));
+ layers[1]->SetFrameMetrics(metrics);
+
+ metrics.Clear();
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID));
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 4));
+ layers[2]->SetFrameMetrics(metrics);
+
+ metrics.Clear();
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 5));
+ layers[4]->SetFrameMetrics(metrics);
+
+ metrics.Clear();
+ metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 6));
+ layers[5]->SetFrameMetrics(metrics);
+
+ LayerMetricsWrapper wrapper(root, LayerMetricsWrapper::StartAt::TOP);
+ nsTArray expectedLayers;
+ expectedLayers.AppendElement(layers[0].get());
+ expectedLayers.AppendElement(layers[0].get());
+ expectedLayers.AppendElement(layers[0].get());
+ expectedLayers.AppendElement(layers[0].get());
+ expectedLayers.AppendElement(layers[0].get());
+ expectedLayers.AppendElement(layers[0].get());
+ expectedLayers.AppendElement(layers[1].get());
+ expectedLayers.AppendElement(layers[2].get());
+ expectedLayers.AppendElement(layers[2].get());
+ expectedLayers.AppendElement(layers[3].get());
+ nsTArray expectedIds;
+ expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 0);
+ expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+ expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 1);
+ expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 2);
+ expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+ expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+ expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 3);
+ expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+ expectedIds.AppendElement(FrameMetrics::START_SCROLL_ID + 4);
+ expectedIds.AppendElement(FrameMetrics::NULL_SCROLL_ID);
+ for (int i = 0; i < 10; i++) {
+ ASSERT_EQ(expectedLayers[i], wrapper.GetLayer());
+ ASSERT_EQ(expectedIds[i], wrapper.Metrics().GetScrollId());
+ wrapper = wrapper.GetFirstChild();
+ }
+ ASSERT_FALSE(wrapper.IsValid());
+
+ wrapper = LayerMetricsWrapper(root, LayerMetricsWrapper::StartAt::BOTTOM);
+ for (int i = 5; i < 10; i++) {
+ ASSERT_EQ(expectedLayers[i], wrapper.GetLayer());
+ ASSERT_EQ(expectedIds[i], wrapper.Metrics().GetScrollId());
+ wrapper = wrapper.GetFirstChild();
+ }
+ ASSERT_FALSE(wrapper.IsValid());
+
+ wrapper = LayerMetricsWrapper(layers[4], LayerMetricsWrapper::StartAt::BOTTOM);
+ ASSERT_EQ(FrameMetrics::START_SCROLL_ID + 5, wrapper.Metrics().GetScrollId());
+ wrapper = wrapper.GetParent();
+ ASSERT_EQ(FrameMetrics::START_SCROLL_ID + 4, wrapper.Metrics().GetScrollId());
+ ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+ ASSERT_FALSE(wrapper.GetNextSibling().IsValid());
+ wrapper = wrapper.GetParent();
+ ASSERT_EQ(FrameMetrics::NULL_SCROLL_ID, wrapper.Metrics().GetScrollId());
+ ASSERT_EQ(layers[2].get(), wrapper.GetLayer());
+ wrapper = wrapper.GetNextSibling();
+ ASSERT_EQ(FrameMetrics::START_SCROLL_ID + 6, wrapper.Metrics().GetScrollId());
+ ASSERT_EQ(layers[5].get(), wrapper.GetLayer());
+}
diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp
index 49ff4ff04d3..a632b5d2a86 100644
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -650,6 +650,7 @@ static void RecordFrameMetrics(nsIFrame* aForFrame,
nsIFrame* aScrollFrame,
const nsIFrame* aReferenceFrame,
ContainerLayer* aRoot,
+ ViewID aScrollParentId,
const nsRect& aViewport,
bool aForceNullScrollId,
bool aIsRoot,
@@ -706,6 +707,7 @@ static void RecordFrameMetrics(nsIFrame* aForFrame,
metrics.SetScrollId(scrollId);
metrics.SetIsRoot(aIsRoot);
+ metrics.SetScrollParentId(aScrollParentId);
// Only the root scrollable frame for a given presShell should pick up
// the presShell's resolution. All the other frames are 1.0.
@@ -1291,7 +1293,7 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder,
RecordFrameMetrics(aForFrame, rootScrollFrame,
aBuilder->FindReferenceFrameFor(aForFrame),
- root, viewport,
+ root, FrameMetrics::NULL_SCROLL_ID, viewport,
!isRoot, isRoot, containerParameters);
// NS_WARNING is debug-only, so don't even bother checking the conditions in
@@ -3681,9 +3683,8 @@ nsDisplaySubDocument::BuildLayer(nsDisplayListBuilder* aBuilder,
mFrame->GetPosition() +
mFrame->GetOffsetToCrossDoc(ReferenceFrame());
- container->SetScrollHandoffParentId(mScrollParentId);
RecordFrameMetrics(mFrame, rootScrollFrame, ReferenceFrame(),
- container, viewport,
+ container, mScrollParentId, viewport,
false, isRootContentDocument, params);
}
@@ -3992,9 +3993,8 @@ nsDisplayScrollLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
mScrollFrame->GetPosition() +
mScrollFrame->GetOffsetToCrossDoc(ReferenceFrame());
- layer->SetScrollHandoffParentId(mScrollParentId);
RecordFrameMetrics(mScrolledFrame, mScrollFrame, ReferenceFrame(), layer,
- viewport, false, false, params);
+ mScrollParentId, viewport, false, false, params);
if (mList.IsOpaque()) {
nsRect displayport;
diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java
index 181ecc69847..b06547f8c81 100644
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2375,12 +2375,12 @@ public class GeckoAppShell
}
@WrapElementForJNI(stubName = "CreateMessageListWrapper")
- public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
+ public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
if (SmsManager.getInstance() == null) {
return;
}
- SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId);
+ SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId);
}
@WrapElementForJNI(stubName = "GetNextMessageInListWrapper")
diff --git a/mobile/android/base/GeckoSmsManager.java b/mobile/android/base/GeckoSmsManager.java
index a68286cbe74..fe9370298d2 100644
--- a/mobile/android/base/GeckoSmsManager.java
+++ b/mobile/android/base/GeckoSmsManager.java
@@ -737,22 +737,22 @@ public class GeckoSmsManager
}
@Override
- public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
+ public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
class CreateMessageListRunnable implements Runnable {
private long mStartDate;
private long mEndDate;
private String[] mNumbers;
private int mNumbersCount;
- private int mDeliveryState;
+ private String mDelivery;
private boolean mReverse;
private int mRequestId;
- CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
+ CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
mStartDate = aStartDate;
mEndDate = aEndDate;
mNumbers = aNumbers;
mNumbersCount = aNumbersCount;
- mDeliveryState = aDeliveryState;
+ mDelivery = aDelivery;
mReverse = aReverse;
mRequestId = aRequestId;
}
@@ -766,11 +766,11 @@ public class GeckoSmsManager
// TODO: should use the |selectionArgs| argument in |ContentResolver.query()|.
ArrayList restrictions = new ArrayList();
- if (mStartDate != 0) {
+ if (mStartDate >= 0) {
restrictions.add("date >= " + mStartDate);
}
- if (mEndDate != 0) {
+ if (mEndDate >= 0) {
restrictions.add("date <= " + mEndDate);
}
@@ -785,11 +785,11 @@ public class GeckoSmsManager
restrictions.add(numberRestriction);
}
- if (mDeliveryState == kDeliveryStateUnknown) {
+ if (mDelivery == null) {
restrictions.add("type IN ('" + kSmsTypeSentbox + "', '" + kSmsTypeInbox + "')");
- } else if (mDeliveryState == kDeliveryStateSent) {
+ } else if (mDelivery == "sent") {
restrictions.add("type = " + kSmsTypeSentbox);
- } else if (mDeliveryState == kDeliveryStateReceived) {
+ } else if (mDelivery == "received") {
restrictions.add("type = " + kSmsTypeInbox);
} else {
throw new UnexpectedDeliveryStateException();
@@ -853,7 +853,7 @@ public class GeckoSmsManager
}
}
- if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId))) {
+ if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId))) {
Log.e("GeckoSmsManager", "Failed to add CreateMessageListRunnable to the SmsIOThread");
notifyReadingMessageListFailed(kUnknownError, aRequestId);
}
diff --git a/mobile/android/base/SmsManager.java b/mobile/android/base/SmsManager.java
index 28930bda8b3..0bdf07244da 100644
--- a/mobile/android/base/SmsManager.java
+++ b/mobile/android/base/SmsManager.java
@@ -28,7 +28,7 @@ interface ISmsManager
public void send(String aNumber, String aMessage, int aRequestId);
public void getMessage(int aMessageId, int aRequestId);
public void deleteMessage(int aMessageId, int aRequestId);
- public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId);
+ public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId);
public void getNextMessageInList(int aListId, int aRequestId);
public void clearMessageList(int aListId);
}
diff --git a/mobile/android/base/sync/setup/activities/SendTabActivity.java b/mobile/android/base/sync/setup/activities/SendTabActivity.java
index 880019a8a72..a559c1937ab 100644
--- a/mobile/android/base/sync/setup/activities/SendTabActivity.java
+++ b/mobile/android/base/sync/setup/activities/SendTabActivity.java
@@ -146,8 +146,7 @@ public class SendTabActivity extends LocaleAwareActivity {
enableSend(false);
- // will enableSend if appropriate.
- updateClientList();
+ // Sending will be enabled in onResume, if appropriate.
}
protected static SendTabData getSendTabData(Intent intent) throws IllegalArgumentException {
@@ -184,14 +183,14 @@ public class SendTabActivity extends LocaleAwareActivity {
* Ensure that the view's list of clients is backed by a recently populated
* array adapter.
*/
- protected synchronized void updateClientList() {
+ protected synchronized void updateClientList(final TabSender sender, final ClientRecordArrayAdapter adapter) {
// Fetching the client list hits the clients database, so we spin this onto
// a background task.
new AsyncTask>() {
@Override
protected Collection doInBackground(Void... params) {
- return getOtherClients();
+ return getOtherClients(sender);
}
@Override
@@ -199,12 +198,12 @@ public class SendTabActivity extends LocaleAwareActivity {
// We're allowed to update the UI from here.
Logger.debug(LOG_TAG, "Got " + clientArray.size() + " clients.");
- arrayAdapter.setClientRecordList(clientArray);
+ adapter.setClientRecordList(clientArray);
if (clientArray.size() == 1) {
- arrayAdapter.checkItem(0, true);
+ adapter.checkItem(0, true);
}
- enableSend(arrayAdapter.getNumCheckedGUIDs() > 0);
+ enableSend(adapter.getNumCheckedGUIDs() > 0);
}
}.execute();
}
@@ -235,6 +234,9 @@ public class SendTabActivity extends LocaleAwareActivity {
this.tabSender = new FxAccountTabSender(applicationContext, fxAccount);
+ // will enableSend if appropriate.
+ updateClientList(tabSender, this.arrayAdapter);
+
Logger.info(LOG_TAG, "Allowing tab send for Firefox Account.");
registerDisplayURICommand();
return;
@@ -244,6 +246,9 @@ public class SendTabActivity extends LocaleAwareActivity {
if (syncAccounts.length > 0) {
this.tabSender = new Sync11TabSender(applicationContext, syncAccounts[0], accountManager);
+ // will enableSend if appropriate.
+ updateClientList(tabSender, this.arrayAdapter);
+
Logger.info(LOG_TAG, "Allowing tab send for Sync account.");
registerDisplayURICommand();
return;
@@ -360,18 +365,18 @@ public class SendTabActivity extends LocaleAwareActivity {
/**
* @return a collection of client records, excluding our own.
*/
- protected Collection getOtherClients() {
+ protected Collection getOtherClients(final TabSender sender) {
+ if (sender == null) {
+ Logger.warn(LOG_TAG, "No tab sender when fetching other client IDs.");
+ return new ArrayList(0);
+ }
+
final Map all = getAllClients();
if (all == null) {
return new ArrayList(0);
}
- if (this.tabSender == null) {
- Logger.warn(LOG_TAG, "No tab sender when fetching other client IDs.");
- return new ArrayList(0);
- }
-
- final String ourGUID = this.tabSender.getAccountGUID();
+ final String ourGUID = sender.getAccountGUID();
if (ourGUID == null) {
return all.values();
}
diff --git a/mobile/android/search/java/org/mozilla/search/MainActivity.java b/mobile/android/search/java/org/mozilla/search/MainActivity.java
index 37fc8f10ad1..debd32e46ef 100644
--- a/mobile/android/search/java/org/mozilla/search/MainActivity.java
+++ b/mobile/android/search/java/org/mozilla/search/MainActivity.java
@@ -59,6 +59,8 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
private View preSearch;
private View postSearch;
+ private View settingsButton;
+
private View suggestions;
private SuggestionsFragment suggestionsFragment;
@@ -119,6 +121,16 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
preSearch = findViewById(R.id.presearch);
postSearch = findViewById(R.id.postsearch);
+ settingsButton = findViewById(R.id.settings_button);
+
+ // Apply click handler to settings button.
+ settingsButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startActivity(new Intent(MainActivity.this, SearchPreferenceActivity.class));
+ }
+ });
+
suggestions = findViewById(R.id.suggestions);
suggestionsFragment = (SuggestionsFragment) getSupportFragmentManager().findFragmentById(R.id.suggestions);
@@ -155,6 +167,7 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
editText = null;
preSearch = null;
postSearch = null;
+ settingsButton = null;
suggestionsFragment = null;
suggestions = null;
animationText = null;
@@ -195,7 +208,7 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
@Override
public void onSuggest(String query) {
- editText.setText(query);
+ editText.setText(query);
}
@Override
@@ -223,8 +236,8 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
/**
* Animates search suggestion to search bar. This animation has 2 main parts:
*
- * 1) Vertically translate query text from suggestion card to search bar.
- * 2) Expand suggestion card to fill the results view area.
+ * 1) Vertically translate query text from suggestion card to search bar.
+ * 2) Expand suggestion card to fill the results view area.
*
* @param query
* @param suggestionAnimation
@@ -296,6 +309,8 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
}
this.editState = editState;
+ updateSettingsButtonVisibility();
+
editText.setActive(editState == EditState.EDITING);
suggestions.setVisibility(editState == EditState.EDITING ? View.VISIBLE : View.INVISIBLE);
}
@@ -306,10 +321,21 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
}
this.searchState = searchState;
+ updateSettingsButtonVisibility();
+
preSearch.setVisibility(searchState == SearchState.PRESEARCH ? View.VISIBLE : View.INVISIBLE);
postSearch.setVisibility(searchState == SearchState.POSTSEARCH ? View.VISIBLE : View.INVISIBLE);
}
+ private void updateSettingsButtonVisibility() {
+ // Show button on launch screen when keyboard is down.
+ if (searchState == SearchState.PRESEARCH && editState == EditState.WAITING) {
+ settingsButton.setVisibility(View.VISIBLE);
+ } else {
+ settingsButton.setVisibility(View.INVISIBLE);
+ }
+ }
+
@Override
public void onBackPressed() {
if (editState == EditState.EDITING) {
diff --git a/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java b/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
index 546ce161246..24a19e31615 100644
--- a/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
@@ -5,7 +5,6 @@
package org.mozilla.search;
import android.app.Activity;
-import android.content.Intent;
import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
@@ -115,13 +114,6 @@ public class PreSearchFragment extends Fragment {
}
});
- // Apply click handler to settings button.
- mainView.findViewById(R.id.settings_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startActivity(new Intent(getActivity(), SearchPreferenceActivity.class));
- }
- });
return mainView;
}
diff --git a/mobile/android/search/java/org/mozilla/search/ui/BackCaptureEditText.java b/mobile/android/search/java/org/mozilla/search/ui/BackCaptureEditText.java
new file mode 100644
index 00000000000..727ad810570
--- /dev/null
+++ b/mobile/android/search/java/org/mozilla/search/ui/BackCaptureEditText.java
@@ -0,0 +1,36 @@
+/* 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/. */
+
+package org.mozilla.search.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+/**
+ * An EditText subclass that loses focus when the keyboard
+ * is dismissed.
+ */
+public class BackCaptureEditText extends EditText {
+ public BackCaptureEditText(Context context) {
+ super(context);
+ }
+
+ public BackCaptureEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BackCaptureEditText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
+ clearFocus();
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+}
diff --git a/mobile/android/search/res/layout/clearable_edit_text.xml b/mobile/android/search/res/layout/clearable_edit_text.xml
index abbc5e91dbd..b0f81754943 100644
--- a/mobile/android/search/res/layout/clearable_edit_text.xml
+++ b/mobile/android/search/res/layout/clearable_edit_text.xml
@@ -4,7 +4,7 @@
-
+
+
+
-
-
diff --git a/mobile/android/search/search_activity_sources.mozbuild b/mobile/android/search/search_activity_sources.mozbuild
index 7fd5ee1787a..bf350854e90 100644
--- a/mobile/android/search/search_activity_sources.mozbuild
+++ b/mobile/android/search/search_activity_sources.mozbuild
@@ -17,4 +17,5 @@ search_activity_sources = [
'java/org/mozilla/search/providers/SearchEngineManager.java',
'java/org/mozilla/search/SearchPreferenceActivity.java',
'java/org/mozilla/search/SearchWidget.java',
+ 'java/org/mozilla/search/ui/BackCaptureEditText.java',
]
diff --git a/services/sync/modules/browserid_identity.js b/services/sync/modules/browserid_identity.js
index b489807cc8f..277fcb3120a 100644
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -188,7 +188,11 @@ this.BrowserIDManager.prototype = {
this._log.error("Could not authenticate: " + err);
});
- this._shouldHaveSyncKeyBundle = false;
+ // initializeWithCurrentIdentity() can be called after the
+ // identity module was first initialized, e.g., after the
+ // user completes a force authentication, so we should make
+ // sure all credentials are reset before proceeding.
+ this.resetCredentials();
this._authFailureReason = null;
return this._fxaService.getSignedInUser().then(accountData => {
@@ -579,9 +583,10 @@ this.BrowserIDManager.prototype = {
// for now assume it is just a transient network related problem.
this._authFailureReason = LOGIN_FAILED_NETWORK_ERROR;
}
- // Drop the sync key bundle, but still expect to have one.
- // This will arrange for us to be in the right 'currentAuthState'
- // such that UI will show the right error.
+ // this._authFailureReason being set to be non-null in the above if clause
+ // ensures we are in the correct currentAuthState, and
+ // this._shouldHaveSyncKeyBundle being true ensures everything that cares knows
+ // that there is no authentication dance still under way.
this._shouldHaveSyncKeyBundle = true;
Weave.Status.login = this._authFailureReason;
Services.obs.notifyObservers(null, "weave:service:login:error", null);
diff --git a/toolkit/components/places/ColorAnalyzer_worker.js b/toolkit/components/places/ColorAnalyzer_worker.js
index c1ab2a29f1b..01fce06375f 100644
--- a/toolkit/components/places/ColorAnalyzer_worker.js
+++ b/toolkit/components/places/ColorAnalyzer_worker.js
@@ -119,7 +119,7 @@ onmessage = function(event) {
// only send back the most desirable colors
mergedColors.sort(function(a, b) {
- return b.desirability - a.desirability;
+ return b.desirability != a.desirability ? b.desirability - a.desirability : b.color - a.color;
});
mergedColors = mergedColors.map(function(metadata) {
return metadata.color;
@@ -216,7 +216,7 @@ function mergeColors(colorFrequencies, numPixels, threshold) {
}
function descendingFreqSort(a, b) {
- return b.freq - a.freq;
+ return b.freq != a.freq ? b.freq - a.freq : b.color - a.color;
}
/**
diff --git a/toolkit/components/places/tests/browser/browser_colorAnalyzer.js b/toolkit/components/places/tests/browser/browser_colorAnalyzer.js
index 88bc4251580..c5cbbaf5f88 100644
--- a/toolkit/components/places/tests/browser/browser_colorAnalyzer.js
+++ b/toolkit/components/places/tests/browser/browser_colorAnalyzer.js
@@ -330,13 +330,13 @@ tests.push(function test_categoryDiscover() {
});
tests.push(function test_localeGeneric() {
- frcTest(filePrefix + "localeGeneric.png", 0x00A400,
- "localeGeneric analysis returns orange");
+ frcTest(filePrefix + "localeGeneric.png", 0x3EC23E,
+ "localeGeneric analysis returns green");
});
tests.push(function test_dictionaryGeneric() {
- frcTest(filePrefix + "dictionaryGeneric-16.png", 0x502E1E,
- "dictionaryGeneric-16 analysis returns blue");
+ frcTest(filePrefix + "dictionaryGeneric-16.png", 0x854C30,
+ "dictionaryGeneric-16 analysis returns brown");
});
tests.push(function test_extensionGeneric() {
diff --git a/toolkit/components/social/SocialService.jsm b/toolkit/components/social/SocialService.jsm
index 9dc6c623ee0..b28a86a9cf0 100644
--- a/toolkit/components/social/SocialService.jsm
+++ b/toolkit/components/social/SocialService.jsm
@@ -687,7 +687,11 @@ this.SocialService = {
// overwrite the existing provider then notify the front end so it can
// handle any reload that might be necessary.
if (ActiveProviders.has(manifest.origin)) {
- let provider = new SocialProvider(manifest);
+ // unload the worker prior to replacing the provider instance, also
+ // ensures the workerapi instance is terminated.
+ let provider = SocialServiceInternal.providers[manifest.origin];
+ provider.enabled = false;
+ provider = new SocialProvider(manifest);
SocialServiceInternal.providers[provider.origin] = provider;
// update the cache and ui, reload provider if necessary
this.getOrderedProviderList(providers => {
@@ -756,8 +760,10 @@ function SocialProvider(input) {
SocialProvider.prototype = {
reload: function() {
- this._terminate();
- this._activate();
+ // calling terminate/activate does not set the enabled state whereas setting
+ // enabled will call terminate/activate
+ this.enabled = false;
+ this.enabled = true;
Services.obs.notifyObservers(null, "social:provider-reload", this.origin);
},
diff --git a/toolkit/devtools/client/dbg-client.jsm b/toolkit/devtools/client/dbg-client.jsm
index bca633ca430..6812b472254 100644
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -1792,7 +1792,13 @@ ThreadClient.prototype = {
aOnResponse(aResponse);
return;
}
- doSetBreakpoint(this.resume.bind(this));
+
+ const { type, why } = aResponse;
+ const cleanUp = type == "paused" && why.type == "interrupted"
+ ? () => this.resume()
+ : noop;
+
+ doSetBreakpoint(cleanUp);
});
},
diff --git a/toolkit/devtools/server/actors/common.js b/toolkit/devtools/server/actors/common.js
index 8d3362c02ff..d8f60a2b2b1 100644
--- a/toolkit/devtools/server/actors/common.js
+++ b/toolkit/devtools/server/actors/common.js
@@ -166,3 +166,27 @@ ActorPool.prototype = {
exports.ActorPool = ActorPool;
+// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
+// implemented.
+exports.getOffsetColumn = function getOffsetColumn(aOffset, aScript) {
+ let bestOffsetMapping = null;
+ for (let offsetMapping of aScript.getAllColumnOffsets()) {
+ if (!bestOffsetMapping ||
+ (offsetMapping.offset <= aOffset &&
+ offsetMapping.offset > bestOffsetMapping.offset)) {
+ bestOffsetMapping = offsetMapping;
+ }
+ }
+
+ if (!bestOffsetMapping) {
+ // XXX: Try not to completely break the experience of using the debugger for
+ // the user by assuming column 0. Simultaneously, report the error so that
+ // there is a paper trail if the assumption is bad and the debugging
+ // experience becomes wonky.
+ reportError(new Error("Could not find a column for offset " + aOffset
+ + " in the script " + aScript));
+ return 0;
+ }
+
+ return bestOffsetMapping.columnNumber;
+}
diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js
index acce9a78dfc..58f79b3b6c8 100644
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -8,7 +8,7 @@
const Services = require("Services");
const { Cc, Ci, Cu, components, ChromeWorker } = require("chrome");
-const { ActorPool } = require("devtools/server/actors/common");
+const { ActorPool, getOffsetColumn } = require("devtools/server/actors/common");
const { DebuggerServer } = require("devtools/server/main");
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const { dbg_assert, dumpn, update } = DevToolsUtils;
@@ -5226,31 +5226,6 @@ exports.ThreadSources = ThreadSources;
// Utility functions.
-// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
-// implemented.
-function getOffsetColumn(aOffset, aScript) {
- let bestOffsetMapping = null;
- for (let offsetMapping of aScript.getAllColumnOffsets()) {
- if (!bestOffsetMapping ||
- (offsetMapping.offset <= aOffset &&
- offsetMapping.offset > bestOffsetMapping.offset)) {
- bestOffsetMapping = offsetMapping;
- }
- }
-
- if (!bestOffsetMapping) {
- // XXX: Try not to completely break the experience of using the debugger for
- // the user by assuming column 0. Simultaneously, report the error so that
- // there is a paper trail if the assumption is bad and the debugging
- // experience becomes wonky.
- reportError(new Error("Could not find a column for offset " + aOffset
- + " in the script " + aScript));
- return 0;
- }
-
- return bestOffsetMapping.columnNumber;
-}
-
/**
* Return the non-source-mapped location of the given Debugger.Frame. If the
* frame does not have a script, the location's properties are all null.
diff --git a/toolkit/devtools/server/actors/tracer.js b/toolkit/devtools/server/actors/tracer.js
index bc6f6c1cf9c..c11b75a206b 100644
--- a/toolkit/devtools/server/actors/tracer.js
+++ b/toolkit/devtools/server/actors/tracer.js
@@ -8,6 +8,7 @@ const { Cu } = require("chrome");
const { DebuggerServer } = require("devtools/server/main");
const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
const Debugger = require("Debugger");
+const { getOffsetColumn } = require("devtools/server/actors/common");
// TODO bug 943125: remove this polyfill and use Debugger.Frame.prototype.depth
// once it is implemented.
@@ -60,6 +61,7 @@ const TRACE_TYPES = new Set([
"yield",
"name",
"location",
+ "hitCount",
"callsite",
"parameterNames",
"arguments",
@@ -81,6 +83,7 @@ function TracerActor(aConn, aParent)
this._sequence = 0;
this._bufferSendTimer = null;
this._buffer = [];
+ this._hitCounts = new WeakMap();
// Keep track of how many different trace requests have requested what kind of
// tracing info. This way we can minimize the amount of data we are collecting
@@ -236,6 +239,11 @@ TracerActor.prototype = {
this._requestsForTraceType[traceType]--;
}
+ // Clear hit counts if no trace is requesting them.
+ if (!this._requestsForTraceType.hitCount) {
+ this._hitCounts.clear();
+ }
+
if (this.idle) {
this.dbg.enabled = false;
}
@@ -272,16 +280,26 @@ TracerActor.prototype = {
: "(" + aFrame.type + ")";
}
- if (this._requestsForTraceType.location && aFrame.script) {
- // We should return the location of the start of the script, but
- // Debugger.Script does not provide complete start locations (bug
- // 901138). Instead, return the current offset (the location of the first
- // statement in the function).
- packet.location = {
- url: aFrame.script.url,
- line: aFrame.script.startLine,
- column: getOffsetColumn(aFrame.offset, aFrame.script)
- };
+ if (aFrame.script) {
+ if (this._requestsForTraceType.hitCount) {
+ // Increment hit count.
+ let previousHitCount = this._hitCounts.get(aFrame.script) || 0;
+ this._hitCounts.set(aFrame.script, previousHitCount + 1);
+
+ packet.hitCount = this._hitCounts.get(aFrame.script);
+ }
+
+ if (this._requestsForTraceType.location) {
+ // We should return the location of the start of the script, but
+ // Debugger.Script does not provide complete start locations (bug
+ // 901138). Instead, return the current offset (the location of the first
+ // statement in the function).
+ packet.location = {
+ url: aFrame.script.url,
+ line: aFrame.script.startLine,
+ column: getOffsetColumn(aFrame.offset, aFrame.script)
+ };
+ }
}
if (this._parent.threadActor && aFrame.script) {
@@ -497,12 +515,6 @@ MapStack.prototype = {
}
};
-// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when
-// it is implemented.
-function getOffsetColumn(aOffset, aScript) {
- return 0;
-}
-
// Serialization helper functions. Largely copied from script.js and modified
// for use in serialization rather than object actor requests.
diff --git a/toolkit/devtools/server/tests/unit/test_trace_actor-05.js b/toolkit/devtools/server/tests/unit/test_trace_actor-05.js
index a46235b51a4..9ab3f54f716 100644
--- a/toolkit/devtools/server/tests/unit/test_trace_actor-05.js
+++ b/toolkit/devtools/server/tests/unit/test_trace_actor-05.js
@@ -88,9 +88,10 @@ function test_enter_exit_frame()
do_check_eq(traces[1].name, "foo");
// XXX: foo's definition is at tracerlocations.js:3:0, but Debugger.Script
- // does not provide complete definition locations (bug 901138). |column|
- // will always be 0 until we can get bug 863089 fixed.
- check_location(traces[1].location, { url: url, line: 3, column: 0 });
+ // does not provide complete definition locations (bug 901138). Therefore,
+ // we use the first statement in the function (tracerlocations.js:4:2) for
+ // a column approximation.
+ check_location(traces[1].location, { url: url, line: 3, column: 2 });
check_location(traces[1].callsite, { url: url, line: 8, column: 0 });
do_check_eq(typeof traces[1].parameterNames, "object");
diff --git a/toolkit/devtools/server/tests/unit/test_trace_actor-11.js b/toolkit/devtools/server/tests/unit/test_trace_actor-11.js
new file mode 100644
index 00000000000..a36b981e7b3
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_trace_actor-11.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that hit counts from tracer count function frames correctly, even after
+ * restarting the trace.
+ */
+
+var gDebuggee;
+var gClient;
+var gTraceClient;
+
+function run_test()
+{
+ initTestTracerServer();
+ gDebuggee = addTestGlobal("test-tracer-actor");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect(function() {
+ attachTestTab(gClient, "test-tracer-actor", function(aResponse, aTabClient) {
+ gClient.attachTracer(aResponse.traceActor, function(aResponse, aTraceClient) {
+ gTraceClient = aTraceClient;
+ test_hit_counts();
+ });
+ });
+ });
+ do_test_pending();
+}
+
+function test_hit_counts()
+{
+ start_trace()
+ .then(eval_code)
+ .then(listen_to_traces)
+ .then(stop_trace)
+ .then(start_trace) // Restart tracing.
+ .then(eval_code)
+ .then(listen_to_traces)
+ .then(stop_trace)
+ .then(function() {
+ finishClient(gClient);
+ }).then(null, error => {
+ do_check_true(false, "Should not get an error, got: " + DevToolsUtils.safeErrorString(error));
+ });
+}
+
+function listen_to_traces() {
+ const tracesStopped = promise.defer();
+ gClient.addListener("traces", (aEvent, { traces }) => {
+ for (let t of traces) {
+ check_trace(t);
+ }
+ tracesStopped.resolve();
+ });
+ return tracesStopped.promise;
+}
+
+function start_trace()
+{
+ let deferred = promise.defer();
+ gTraceClient.startTrace(["depth", "name", "location", "hitCount"], null, function() { deferred.resolve(); });
+ return deferred.promise;
+}
+
+function eval_code()
+{
+ gDebuggee.eval("(" + function iife() {
+ [1, 2, 3].forEach(function noop() {
+ for (let x of [1]) {}
+ });
+ } + ")()");
+}
+
+function stop_trace()
+{
+ let deferred = promise.defer();
+ gTraceClient.stopTrace(null, function() { deferred.resolve(); });
+ return deferred.promise;
+}
+
+function check_trace({ type, sequence, depth, name, location, hitCount })
+{
+ if (location) {
+ do_check_true(location.url !== "self-hosted");
+ }
+
+ switch(sequence) {
+ case 0:
+ do_check_eq(name, "(eval)");
+ do_check_eq(hitCount, 1);
+ break;
+
+ case 1:
+ do_check_eq(name, "iife");
+ do_check_eq(hitCount, 1);
+ break;
+
+ case 2:
+ do_check_eq(hitCount, 1);
+ do_check_eq(name, "noop");
+ break;
+
+ case 4:
+ do_check_eq(hitCount, 2);
+ do_check_eq(name, "noop");
+ break;
+
+ case 6:
+ do_check_eq(hitCount, 3);
+ do_check_eq(name, "noop");
+ break;
+
+ case 3:
+ case 5:
+ case 7:
+ case 8:
+ case 9:
+ do_check_eq(type, "exitedFrame");
+ break;
+
+ default:
+ // Should have covered all sequences.
+ do_check_true(false);
+ }
+}
diff --git a/toolkit/devtools/server/tests/unit/test_trace_actor-12.js b/toolkit/devtools/server/tests/unit/test_trace_actor-12.js
new file mode 100644
index 00000000000..3bcf22a7f0b
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_trace_actor-12.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that hit counts are correct even if we start tracing other things first
+ * and then start tracing hit counts.
+ */
+
+var gDebuggee;
+var gClient;
+var gTraceClient;
+
+function run_test()
+{
+ initTestTracerServer();
+ gDebuggee = addTestGlobal("test-tracer-actor");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect(function() {
+ attachTestTab(gClient, "test-tracer-actor", function(aResponse, aTabClient) {
+ gClient.attachTracer(aResponse.traceActor, function(aResponse, aTraceClient) {
+ gTraceClient = aTraceClient;
+ test_hit_counts();
+ });
+ });
+ });
+ do_test_pending();
+}
+
+function test_hit_counts()
+{
+ const tracesStopped = promise.defer();
+ gClient.addListener("traces", (aEvent, { traces }) => {
+ for (let t of traces) {
+ check_trace(t);
+ }
+ tracesStopped.resolve();
+ });
+
+ start_trace_without_hit_counts()
+ .then(eval_code)
+ .then(start_trace_hit_counts)
+ .then(eval_code)
+ .then(() => tracesStopped.promise)
+ .then(stop_trace)
+ .then(function() {
+ finishClient(gClient);
+ }).then(null, error => {
+ do_check_true(false,
+ "Should not get an error, got: " + DevToolsUtils.safeErrorString(error));
+ });
+}
+
+function listen_to_traces() {
+ const tracesStopped = promise.defer();
+ gClient.addListener("traces", (aEvent, { traces }) => {
+ for (let t of traces) {
+ check_trace(t);
+ }
+ tracesStopped.resolve();
+ });
+ return tracesStopped;
+}
+
+function start_trace_without_hit_counts()
+{
+ let deferred = promise.defer();
+ gTraceClient.startTrace(["depth", "name", "location"], null,
+ function() { deferred.resolve(); });
+ return deferred.promise;
+}
+
+function start_trace_hit_counts()
+{
+ let deferred = promise.defer();
+ gTraceClient.startTrace(["hitCount"], null,
+ function() { deferred.resolve(); });
+ return deferred.promise;
+}
+
+function eval_code()
+{
+ gDebuggee.eval("(" + function iife() {
+ [1, 2, 3].forEach(function noop() {
+ for (let x of [1]) {}
+ });
+ } + ")()");
+}
+
+function stop_trace()
+{
+ let deferred = promise.defer();
+ gTraceClient.stopTrace(null, function() { deferred.resolve(); });
+ return deferred.promise;
+}
+
+function check_trace({ type, sequence, depth, name, location, hitCount })
+{
+ if (location) {
+ do_check_true(location.url !== "self-hosted");
+ }
+
+ switch(sequence) {
+
+ // First evaluation (before tracing hit counts).
+ case 0:
+ case 1:
+ case 2:
+ case 4:
+ case 6:
+ do_check_eq(hitCount, undefined);
+ break;
+
+ // Second evaluation (after tracing hit counts).
+ case 10:
+ do_check_eq(name, "(eval)");
+ do_check_eq(hitCount, 1);
+ break;
+
+ case 11:
+ do_check_eq(name, "iife");
+ do_check_eq(hitCount, 1);
+ break;
+
+ case 12:
+ do_check_eq(hitCount, 1);
+ do_check_eq(name, "noop");
+ break;
+
+ case 14:
+ do_check_eq(hitCount, 2);
+ do_check_eq(name, "noop");
+ break;
+
+ case 16:
+ do_check_eq(hitCount, 3);
+ do_check_eq(name, "noop");
+ break;
+
+ case 3:
+ case 5:
+ case 7:
+ case 8:
+ case 9:
+ case 13:
+ case 15:
+ case 17:
+ case 18:
+ case 19:
+ do_check_eq(type, "exitedFrame");
+ break;
+
+ default:
+ // Should have covered all sequences.
+ do_check_true(false);
+ }
+}
diff --git a/toolkit/devtools/server/tests/unit/xpcshell.ini b/toolkit/devtools/server/tests/unit/xpcshell.ini
index b5d98195a89..efd1ffb278a 100644
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -197,6 +197,8 @@ reason = bug 820380
[test_trace_actor-08.js]
[test_trace_actor-09.js]
[test_trace_actor-10.js]
+[test_trace_actor-11.js]
+[test_trace_actor-12.js]
[test_ignore_caught_exceptions.js]
[test_requestTypes.js]
reason = bug 937197
diff --git a/toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js b/toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js
index 2eb8434e31a..b32d7433658 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js
@@ -57,10 +57,14 @@ function checkStateMenu(locked) {
Assert.equal(Services.prefs.prefIsLocked(getTestPluginPref()), locked,
"Preference lock state should be correct.");
let menuList = gManagerWindow.document.getAnonymousElementByAttribute(gPluginElement, "anonid", "state-menulist");
+ // State menu should always have a selected item which must be visible
+ let selectedMenuItem = menuList.querySelector(".addon-control[selected=\"true\"]");
is_element_visible(menuList, "State menu should be visible.");
Assert.equal(menuList.disabled, locked,
"State menu should" + (locked === true ? "" : " not") + " be disabled.");
+
+ is_element_visible(selectedMenuItem, "State menu's selected item should be visible.");
}
function checkStateMenuDetail(locked) {
diff --git a/toolkit/themes/linux/mozapps/extensions/extensions.css b/toolkit/themes/linux/mozapps/extensions/extensions.css
index a1f34416167..cf5f6baf006 100644
--- a/toolkit/themes/linux/mozapps/extensions/extensions.css
+++ b/toolkit/themes/linux/mozapps/extensions/extensions.css
@@ -880,12 +880,12 @@ setting[type="radio"] > radiogroup {
/*** buttons ***/
-.addon-control[disabled="true"] {
+.addon-control[disabled="true"]:not(.no-auto-hide) {
display: none;
}
-.addon-control.no-auto-hide {
- display: block;
+.no-auto-hide .addon-control {
+ display: block !important;
}
.addon-control.enable {
diff --git a/toolkit/themes/osx/mozapps/extensions/extensions.css b/toolkit/themes/osx/mozapps/extensions/extensions.css
index 1d8fe5936b6..89b18303cca 100644
--- a/toolkit/themes/osx/mozapps/extensions/extensions.css
+++ b/toolkit/themes/osx/mozapps/extensions/extensions.css
@@ -1104,12 +1104,16 @@ setting[type="radio"] > radiogroup {
/*** buttons ***/
-.addon-control[disabled="true"] {
+.addon-control[disabled="true"]:not(.no-auto-hide) {
display: none;
}
-.addon-control.no-auto-hide {
- display: block;
+.no-auto-hide .addon-control {
+ display: block !important;
+}
+
+.no-auto-hide > .menulist-dropmarker {
+ -moz-padding-start: 0px !important;
}
button.button-link {
diff --git a/toolkit/themes/windows/mozapps/extensions/extensions.css b/toolkit/themes/windows/mozapps/extensions/extensions.css
index 7dbaa055b79..d43df7966ca 100644
--- a/toolkit/themes/windows/mozapps/extensions/extensions.css
+++ b/toolkit/themes/windows/mozapps/extensions/extensions.css
@@ -1120,12 +1120,12 @@ menulist { /* Fixes some styling inconsistencies */
/*** buttons ***/
-.addon-control[disabled="true"] {
+.addon-control[disabled="true"]:not(.no-auto-hide) {
display: none;
}
-.addon-control.no-auto-hide {
- display: block;
+.no-auto-hide .addon-control {
+ display: block !important;
}
button.button-link {
diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp
index aabc0f988bf..f2a2647e72d 100644
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1161,9 +1161,14 @@ AndroidBridge::CreateMessageList(const dom::mobilemessage::SmsFilterData& aFilte
env->DeleteLocalRef(elem);
}
- mozilla::widget::android::GeckoAppShell::CreateMessageListWrapper(aFilter.startDate(),
- aFilter.endDate(), numbers, aFilter.numbers().Length(),
- aFilter.delivery(), aReverse, requestId);
+ int64_t startDate = aFilter.hasStartDate() ? aFilter.startDate() : -1;
+ int64_t endDate = aFilter.hasEndDate() ? aFilter.endDate() : -1;
+ GeckoAppShell::CreateMessageListWrapper(startDate, endDate,
+ numbers, aFilter.numbers().Length(),
+ aFilter.delivery(),
+ aFilter.hasRead(), aFilter.read(),
+ aFilter.threadId(),
+ aReverse, requestId);
}
void
diff --git a/widget/android/GeneratedJNIWrappers.cpp b/widget/android/GeneratedJNIWrappers.cpp
index 72919ab68a7..68d444fc735 100644
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -111,7 +111,7 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
jCloseNotification = getStaticMethod("closeNotification", "(Ljava/lang/String;)V");
jConnectionGetMimeType = getStaticMethod("connectionGetMimeType", "(Ljava/net/URLConnection;)Ljava/lang/String;");
jCreateInputStream = getStaticMethod("createInputStream", "(Ljava/net/URLConnection;)Ljava/io/InputStream;");
- jCreateMessageListWrapper = getStaticMethod("createMessageList", "(JJ[Ljava/lang/String;IIZI)V");
+ jCreateMessageListWrapper = getStaticMethod("createMessageList", "(JJ[Ljava/lang/String;ILjava/lang/String;ZZJZI)V");
jCreateShortcut = getStaticMethod("createShortcut", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jDeleteMessageWrapper = getStaticMethod("deleteMessage", "(II)V");
jDisableBatteryNotifications = getStaticMethod("disableBatteryNotifications", "()V");
@@ -335,21 +335,24 @@ jobject GeckoAppShell::CreateInputStream(jobject a0) {
return ret;
}
-void GeckoAppShell::CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, int32_t a4, bool a5, int32_t a6) {
+void GeckoAppShell::CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, const nsAString& a4, bool a5, bool a6, int64_t a7, bool a8, int32_t a9) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
- if (env->PushLocalFrame(1) != 0) {
+ if (env->PushLocalFrame(2) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_CRASH("Exception should have caused crash.");
}
- jvalue args[7];
+ jvalue args[10];
args[0].j = a0;
args[1].j = a1;
args[2].l = a2;
args[3].i = a3;
- args[4].i = a4;
+ args[4].l = AndroidBridge::NewJavaString(env, a4);
args[5].z = a5;
- args[6].i = a6;
+ args[6].z = a6;
+ args[7].j = a7;
+ args[8].z = a8;
+ args[9].i = a9;
env->CallStaticVoidMethodA(mGeckoAppShellClass, jCreateMessageListWrapper, args);
AndroidBridge::HandleUncaughtException(env);
diff --git a/widget/android/GeneratedJNIWrappers.h b/widget/android/GeneratedJNIWrappers.h
index 25ece85dbc1..643b1470922 100644
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -29,7 +29,7 @@ public:
static void CloseNotification(const nsAString& a0);
static jstring ConnectionGetMimeType(jobject a0);
static jobject CreateInputStream(jobject a0);
- static void CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, int32_t a4, bool a5, int32_t a6);
+ static void CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, const nsAString& a4, bool a5, bool a6, int64_t a7, bool a8, int32_t a9);
static void CreateShortcut(const nsAString& a0, const nsAString& a1, const nsAString& a2);
static void DeleteMessageWrapper(int32_t a0, int32_t a1);
static void DisableBatteryNotifications();