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