author | Wes Kocher <wkocher@mozilla.com> |
Fri, 27 Sep 2013 20:32:24 -0700 | |
changeset 149111 | 2f4397db1830280edcd4e2f804a704756a9df67d |
parent 149088 | b3bb2171ff2b872427acc189bd8a2cf557cd0bfd (current diff) |
parent 149110 | 021068e308c1929c0b24fffa76759e717452010d (diff) |
child 149112 | caec8c0c4963b4d7f71633393feb9c3fb0cef126 |
child 149160 | eb3435771bef86009783434a18970e409dbe8f5e |
child 149162 | 6cb9c4e2777c2ad3c680b7979b1b1a6ddd402966 |
child 149173 | d6b8ac4e8da73bb929f50adb3985ed455e3becf3 |
child 155860 | cc1da159c8110b669a6034abbe1e4ee9b830fcc4 |
push id | 34439 |
push user | kwierso@gmail.com |
push date | Sat, 28 Sep 2013 03:39:12 +0000 |
treeherder | mozilla-inbound@caec8c0c4963 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 27.0a1 |
first release with | nightly linux32
2f4397db1830
/
27.0a1
/
20130928030206
/
files
nightly linux64
2f4397db1830
/
27.0a1
/
20130928030206
/
files
nightly mac
2f4397db1830
/
27.0a1
/
20130928030206
/
files
nightly win32
2f4397db1830
/
27.0a1
/
20130928030206
/
files
nightly win64
2f4397db1830
/
27.0a1
/
20130928030206
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
27.0a1
/
20130928030206
/
pushlog to previous
nightly linux64
27.0a1
/
20130928030206
/
pushlog to previous
nightly mac
27.0a1
/
20130928030206
/
pushlog to previous
nightly win32
27.0a1
/
20130928030206
/
pushlog to previous
nightly win64
27.0a1
/
20130928030206
/
pushlog to previous
|
--- a/browser/devtools/shared/test/Makefile.in +++ b/browser/devtools/shared/test/Makefile.in @@ -4,17 +4,21 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. MOCHITEST_BROWSER_FILES = \ browser_css_color.js \ browser_eventemitter_basic.js \ browser_observableobject.js \ browser_layoutHelpers.js \ browser_require_basic.js \ - browser_telemetry_buttonsandsidebar.js \ + browser_telemetry_sidebar.js \ + browser_telemetry_button_responsive.js \ + browser_telemetry_button_scratchpad.js \ + browser_telemetry_button_tilt.js \ + browser_telemetry_button_paintflashing.js \ browser_telemetry_toolboxtabs_inspector.js \ browser_telemetry_toolboxtabs_jsdebugger.js \ browser_telemetry_toolboxtabs_jsprofiler.js \ browser_telemetry_toolboxtabs_netmonitor.js \ browser_telemetry_toolboxtabs_options.js \ browser_telemetry_toolboxtabs_styleeditor.js \ browser_telemetry_toolboxtabs_webconsole.js \ browser_templater_basic.js \
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_telemetry_button_paintflashing.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_paintflashing.js</p>"; + +// Because we need to gather stats for the period of time that a tool has been +// opened we make use of setTimeout() to create tool active times. +const TOOL_DELAY = 200; + +let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +let Telemetry = require("devtools/shared/telemetry"); + +function init() { + Telemetry.prototype.telemetryInfo = {}; + Telemetry.prototype._oldlog = Telemetry.prototype.log; + Telemetry.prototype.log = function(histogramId, value) { + if (histogramId) { + if (!this.telemetryInfo[histogramId]) { + this.telemetryInfo[histogramId] = []; + } + + this.telemetryInfo[histogramId].push(value); + } + }; + + testButton("command-button-paintflashing"); +} + +function testButton(id) { + info("Testing " + id); + + let target = TargetFactory.forTab(gBrowser.selectedTab); + + gDevTools.showToolbox(target, "inspector").then(function(toolbox) { + info("inspector opened"); + + let button = toolbox.doc.querySelector("#" + id); + ok(button, "Captain, we have the button"); + + delayedClicks(button, 4).then(function() { + checkResults("_PAINTFLASHING_"); + }); + }).then(null, console.error); +} + +function delayedClicks(node, clicks) { + let deferred = promise.defer(); + let clicked = 0; + + // See TOOL_DELAY for why we need setTimeout here + setTimeout(function delayedClick() { + info("Clicking button " + node.id); + node.click(); + clicked++; + + if (clicked >= clicks) { + deferred.resolve(node); + } else { + setTimeout(delayedClick, TOOL_DELAY); + } + }, TOOL_DELAY); + + return deferred.promise; +} + +function checkResults(histIdFocus) { + let result = Telemetry.prototype.telemetryInfo; + + for (let [histId, value] of Iterator(result)) { + if (histId.startsWith("DEVTOOLS_INSPECTOR_") || + !histId.contains(histIdFocus)) { + // Inspector stats are tested in + // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here + // because we only open the inspector once for this test. + continue; + } + + if (histId.endsWith("OPENED_PER_USER_FLAG")) { + ok(value.length === 1 && value[0] === true, + "Per user value " + histId + " has a single value of true"); + } else if (histId.endsWith("OPENED_BOOLEAN")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element === true; + }); + + ok(okay, "All " + histId + " entries are === true"); + } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element > 0; + }); + + ok(okay, "All " + histId + " entries have time > 0"); + } + } + + finishUp(); +} + +function finishUp() { + gBrowser.removeCurrentTab(); + + Telemetry.prototype.log = Telemetry.prototype._oldlog; + delete Telemetry.prototype._oldlog; + delete Telemetry.prototype.telemetryInfo; + + TargetFactory = Services = promise = require = null; + + finish(); +} + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + waitForFocus(init, content); + }, true); + + content.location = TEST_URI; +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_telemetry_button_responsive.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_responsive.js</p>"; + +// Because we need to gather stats for the period of time that a tool has been +// opened we make use of setTimeout() to create tool active times. +const TOOL_DELAY = 200; + +let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +let Telemetry = require("devtools/shared/telemetry"); + +function init() { + Telemetry.prototype.telemetryInfo = {}; + Telemetry.prototype._oldlog = Telemetry.prototype.log; + Telemetry.prototype.log = function(histogramId, value) { + if (histogramId) { + if (!this.telemetryInfo[histogramId]) { + this.telemetryInfo[histogramId] = []; + } + + this.telemetryInfo[histogramId].push(value); + } + }; + + testButton("command-button-responsive"); +} + +function testButton(id) { + info("Testing " + id); + + let target = TargetFactory.forTab(gBrowser.selectedTab); + + gDevTools.showToolbox(target, "inspector").then(function(toolbox) { + info("inspector opened"); + + let button = toolbox.doc.querySelector("#" + id); + ok(button, "Captain, we have the button"); + + delayedClicks(button, 4).then(function() { + checkResults("_RESPONSIVE_"); + }); + }).then(null, console.error); +} + +function delayedClicks(node, clicks) { + let deferred = promise.defer(); + let clicked = 0; + + // See TOOL_DELAY for why we need setTimeout here + setTimeout(function delayedClick() { + info("Clicking button " + node.id); + node.click(); + clicked++; + + if (clicked >= clicks) { + deferred.resolve(node); + } else { + setTimeout(delayedClick, TOOL_DELAY); + } + }, TOOL_DELAY); + + return deferred.promise; +} + +function checkResults(histIdFocus) { + let result = Telemetry.prototype.telemetryInfo; + + for (let [histId, value] of Iterator(result)) { + if (histId.startsWith("DEVTOOLS_INSPECTOR_") || + !histId.contains(histIdFocus)) { + // Inspector stats are tested in + // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here + // because we only open the inspector once for this test. + continue; + } + + if (histId.endsWith("OPENED_PER_USER_FLAG")) { + ok(value.length === 1 && value[0] === true, + "Per user value " + histId + " has a single value of true"); + } else if (histId.endsWith("OPENED_BOOLEAN")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element === true; + }); + + ok(okay, "All " + histId + " entries are === true"); + } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element > 0; + }); + + ok(okay, "All " + histId + " entries have time > 0"); + } + } + + finishUp(); +} + +function finishUp() { + gBrowser.removeCurrentTab(); + + Telemetry.prototype.log = Telemetry.prototype._oldlog; + delete Telemetry.prototype._oldlog; + delete Telemetry.prototype.telemetryInfo; + + TargetFactory = Services = promise = require = null; + + finish(); +} + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + waitForFocus(init, content); + }, true); + + content.location = TEST_URI; +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_telemetry_button_scratchpad.js @@ -0,0 +1,155 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_scratchpad.js</p>"; + +// Because we need to gather stats for the period of time that a tool has been +// opened we make use of setTimeout() to create tool active times. +const TOOL_DELAY = 200; + +let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +let Telemetry = require("devtools/shared/telemetry"); + +let numScratchpads = 0; + +function init() { + Telemetry.prototype.telemetryInfo = {}; + Telemetry.prototype._oldlog = Telemetry.prototype.log; + Telemetry.prototype.log = function(histogramId, value) { + if (histogramId) { + if (!this.telemetryInfo[histogramId]) { + this.telemetryInfo[histogramId] = []; + } + + this.telemetryInfo[histogramId].push(value); + } + }; + + Services.ww.registerNotification(windowObserver); + testButton("command-button-scratchpad"); +} + +function testButton(id) { + info("Testing " + id); + + let target = TargetFactory.forTab(gBrowser.selectedTab); + + gDevTools.showToolbox(target, "inspector").then(function(toolbox) { + info("inspector opened"); + + let button = toolbox.doc.querySelector("#" + id); + ok(button, "Captain, we have the button"); + + delayedClicks(button, 4).then(null, console.error); + }).then(null, console.error); +} + +function windowObserver(aSubject, aTopic, aData) { + if (aTopic == "domwindowopened") { + let win = aSubject.QueryInterface(Ci.nsIDOMWindow); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + + if (win.Scratchpad) { + win.Scratchpad.addObserver({ + onReady: function() { + win.Scratchpad.removeObserver(this); + numScratchpads++; + win.close(); + + info("another scratchpad was opened and closed, count is now " + numScratchpads); + + if (numScratchpads === 4) { + Services.ww.unregisterNotification(windowObserver); + info("4 scratchpads have been opened and closed, checking results"); + checkResults("_SCRATCHPAD_"); + } + }, + }); + } + }, false); + } +} + +function delayedClicks(node, clicks) { + let deferred = promise.defer(); + let clicked = 0; + + // See TOOL_DELAY for why we need setTimeout here + setTimeout(function delayedClick() { + info("Clicking button " + node.id); + node.click(); + clicked++; + + if (clicked >= clicks) { + deferred.resolve(node); + } else { + setTimeout(delayedClick, TOOL_DELAY); + } + }, TOOL_DELAY); + + return deferred.promise; +} + +function checkResults(histIdFocus) { + let result = Telemetry.prototype.telemetryInfo; + + for (let [histId, value] of Iterator(result)) { + if (histId.startsWith("DEVTOOLS_INSPECTOR_") || + !histId.contains(histIdFocus)) { + // Inspector stats are tested in + // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here + // because we only open the inspector once for this test. + continue; + } + + if (histId.endsWith("OPENED_PER_USER_FLAG")) { + ok(value.length === 1 && value[0] === true, + "Per user value " + histId + " has a single value of true"); + } else if (histId.endsWith("OPENED_BOOLEAN")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element === true; + }); + + ok(okay, "All " + histId + " entries are === true"); + } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element > 0; + }); + + ok(okay, "All " + histId + " entries have time > 0"); + } + } + + finishUp(); +} + +function finishUp() { + gBrowser.removeCurrentTab(); + + Telemetry.prototype.log = Telemetry.prototype._oldlog; + delete Telemetry.prototype._oldlog; + delete Telemetry.prototype.telemetryInfo; + + TargetFactory = Services = promise = require = numScratchpads = null; + + finish(); +} + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + waitForFocus(init, content); + }, true); + + content.location = TEST_URI; +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_telemetry_button_tilt.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_tilt.js</p>"; + +// Because we need to gather stats for the period of time that a tool has been +// opened we make use of setTimeout() to create tool active times. +const TOOL_DELAY = 200; + +let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +let Telemetry = require("devtools/shared/telemetry"); + +function init() { + Telemetry.prototype.telemetryInfo = {}; + Telemetry.prototype._oldlog = Telemetry.prototype.log; + Telemetry.prototype.log = function(histogramId, value) { + if (histogramId) { + if (!this.telemetryInfo[histogramId]) { + this.telemetryInfo[histogramId] = []; + } + + this.telemetryInfo[histogramId].push(value); + } + }; + + testButton("command-button-tilt"); +} + +function testButton(id) { + info("Testing " + id); + + let target = TargetFactory.forTab(gBrowser.selectedTab); + + gDevTools.showToolbox(target, "inspector").then(function(toolbox) { + info("inspector opened"); + + let button = toolbox.doc.querySelector("#" + id); + ok(button, "Captain, we have the button"); + + delayedClicks(button, 4).then(function() { + checkResults("_TILT_"); + }); + }).then(null, console.error); +} + +function delayedClicks(node, clicks) { + let deferred = promise.defer(); + let clicked = 0; + + // See TOOL_DELAY for why we need setTimeout here + setTimeout(function delayedClick() { + info("Clicking button " + node.id); + node.click(); + clicked++; + + if (clicked >= clicks) { + deferred.resolve(node); + } else { + setTimeout(delayedClick, TOOL_DELAY); + } + }, TOOL_DELAY); + + return deferred.promise; +} + +function checkResults(histIdFocus) { + let result = Telemetry.prototype.telemetryInfo; + + for (let [histId, value] of Iterator(result)) { + if (histId.startsWith("DEVTOOLS_INSPECTOR_") || + !histId.contains(histIdFocus)) { + // Inspector stats are tested in + // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here + // because we only open the inspector once for this test. + continue; + } + + if (histId.endsWith("OPENED_PER_USER_FLAG")) { + ok(value.length === 1 && value[0] === true, + "Per user value " + histId + " has a single value of true"); + } else if (histId.endsWith("OPENED_BOOLEAN")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element === true; + }); + + ok(okay, "All " + histId + " entries are === true"); + } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element > 0; + }); + + ok(okay, "All " + histId + " entries have time > 0"); + } + } + + finishUp(); +} + +function finishUp() { + gBrowser.removeCurrentTab(); + + Telemetry.prototype.log = Telemetry.prototype._oldlog; + delete Telemetry.prototype._oldlog; + delete Telemetry.prototype.telemetryInfo; + + TargetFactory = Services = promise = require = null; + + finish(); +} + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + waitForFocus(init, content); + }, true); + + content.location = TEST_URI; +}
deleted file mode 100644 --- a/browser/devtools/shared/test/browser_telemetry_buttonsandsidebar.js +++ /dev/null @@ -1,179 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_buttonsandsidebar.js</p>"; - -// Because we need to gather stats for the period of time that a tool has been -// opened we make use of setTimeout() to create tool active times. -const TOOL_DELAY = 200; - -let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}); -let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); - -let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; -let Telemetry = require("devtools/shared/telemetry"); - -function init() { - Telemetry.prototype.telemetryInfo = {}; - Telemetry.prototype._oldlog = Telemetry.prototype.log; - Telemetry.prototype.log = function(histogramId, value) { - if (histogramId) { - if (!this.telemetryInfo[histogramId]) { - this.telemetryInfo[histogramId] = []; - } - - this.telemetryInfo[histogramId].push(value); - } - } - - testButtons(); -} - -function testButtons() { - info("Testing buttons"); - - let target = TargetFactory.forTab(gBrowser.selectedTab); - - gDevTools.showToolbox(target, "inspector").then(function(toolbox) { - let container = toolbox.doc.getElementById("toolbox-buttons"); - let buttons = container.getElementsByTagName("toolbarbutton"); - - // Copy HTMLCollection to array. - buttons = Array.prototype.slice.call(buttons); - - (function testButton() { - let button = buttons.pop(); - - if (button) { - info("Clicking button " + button.id); - button.click(); - delayedClicks(button, 3).then(function(button) { - if (buttons.length == 0) { - // Remove scratchpads - let wins = Services.wm.getEnumerator("devtools:scratchpad"); - while (wins.hasMoreElements()) { - let win = wins.getNext(); - info("Closing scratchpad window"); - win.close(); - } - - testSidebar(); - } else { - setTimeout(testButton, TOOL_DELAY); - } - }); - } - })(); - }).then(null, reportError); -} - -function delayedClicks(node, clicks) { - let deferred = promise.defer(); - let clicked = 0; - - setTimeout(function delayedClick() { - info("Clicking button " + node.id); - node.click(); - clicked++; - - if (clicked >= clicks) { - deferred.resolve(node); - } else { - setTimeout(delayedClick, TOOL_DELAY); - } - }, TOOL_DELAY); - - return deferred.promise; -} - -function testSidebar() { - info("Testing sidebar"); - - let target = TargetFactory.forTab(gBrowser.selectedTab); - - gDevTools.showToolbox(target, "inspector").then(function(toolbox) { - let inspector = toolbox.getCurrentPanel(); - let sidebarTools = ["ruleview", "computedview", "fontinspector", "layoutview"]; - - // Concatenate the array with itself so that we can open each tool twice. - sidebarTools.push.apply(sidebarTools, sidebarTools); - - setTimeout(function selectSidebarTab() { - let tool = sidebarTools.pop(); - if (tool) { - inspector.sidebar.select(tool); - setTimeout(function() { - setTimeout(selectSidebarTab, TOOL_DELAY); - }, TOOL_DELAY); - } else { - checkResults(); - } - }, TOOL_DELAY); - }); -} - -function checkResults() { - let result = Telemetry.prototype.telemetryInfo; - - for (let [histId, value] of Iterator(result)) { - if (histId.startsWith("DEVTOOLS_INSPECTOR_")) { - // Inspector stats are tested in browser_telemetry_toolboxtabs.js so we - // skip them here because we only open the inspector once for this test. - continue; - } - - if (histId.endsWith("OPENED_PER_USER_FLAG")) { - ok(value.length === 1 && value[0] === true, - "Per user value " + histId + " has a single value of true"); - } else if (histId.endsWith("OPENED_BOOLEAN")) { - ok(value.length > 1, histId + " has more than one entry"); - - let okay = value.every(function(element) { - return element === true; - }); - - ok(okay, "All " + histId + " entries are === true"); - } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { - ok(value.length > 1, histId + " has more than one entry"); - - let okay = value.every(function(element) { - return element > 0; - }); - - ok(okay, "All " + histId + " entries have time > 0"); - } - } - - finishUp(); -} - -function reportError(error) { - let stack = " " + error.stack.replace(/\n?.*?@/g, "\n JS frame :: "); - - ok(false, "ERROR: " + error + " at " + error.fileName + ":" + - error.lineNumber + "\n\nStack trace:" + stack); - finishUp(); -} - -function finishUp() { - gBrowser.removeCurrentTab(); - - Telemetry.prototype.log = Telemetry.prototype._oldlog; - delete Telemetry.prototype._oldlog; - delete Telemetry.prototype.telemetryInfo; - - TargetFactory = Services = promise = require = null; - - finish(); -} - -function test() { - waitForExplicitFinish(); - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function() { - gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); - waitForFocus(init, content); - }, true); - - content.location = TEST_URI; -}
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_telemetry_sidebar.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_sidebar.js</p>"; + +// Because we need to gather stats for the period of time that a tool has been +// opened we make use of setTimeout() to create tool active times. +const TOOL_DELAY = 200; + +let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}); +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); + +let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; +let Telemetry = require("devtools/shared/telemetry"); + +function init() { + Telemetry.prototype.telemetryInfo = {}; + Telemetry.prototype._oldlog = Telemetry.prototype.log; + Telemetry.prototype.log = function(histogramId, value) { + if (histogramId) { + if (!this.telemetryInfo[histogramId]) { + this.telemetryInfo[histogramId] = []; + } + + this.telemetryInfo[histogramId].push(value); + } + }; + + testSidebar(); +} + +function testSidebar() { + info("Testing sidebar"); + + let target = TargetFactory.forTab(gBrowser.selectedTab); + + gDevTools.showToolbox(target, "inspector").then(function(toolbox) { + let inspector = toolbox.getCurrentPanel(); + let sidebarTools = ["ruleview", "computedview", "fontinspector", "layoutview"]; + + // Concatenate the array with itself so that we can open each tool twice. + sidebarTools.push.apply(sidebarTools, sidebarTools); + + // See TOOL_DELAY for why we need setTimeout here + setTimeout(function selectSidebarTab() { + let tool = sidebarTools.pop(); + if (tool) { + inspector.sidebar.select(tool); + setTimeout(function() { + setTimeout(selectSidebarTab, TOOL_DELAY); + }, TOOL_DELAY); + } else { + checkResults(); + } + }, TOOL_DELAY); + }); +} + +function checkResults() { + let result = Telemetry.prototype.telemetryInfo; + + for (let [histId, value] of Iterator(result)) { + if (histId.startsWith("DEVTOOLS_INSPECTOR_")) { + // Inspector stats are tested in browser_telemetry_toolboxtabs.js so we + // skip them here because we only open the inspector once for this test. + continue; + } + + if (histId.endsWith("OPENED_PER_USER_FLAG")) { + ok(value.length === 1 && value[0] === true, + "Per user value " + histId + " has a single value of true"); + } else if (histId.endsWith("OPENED_BOOLEAN")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element === true; + }); + + ok(okay, "All " + histId + " entries are === true"); + } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return element > 0; + }); + + ok(okay, "All " + histId + " entries have time > 0"); + } + } + + finishUp(); +} + +function finishUp() { + gBrowser.removeCurrentTab(); + + Telemetry.prototype.log = Telemetry.prototype._oldlog; + delete Telemetry.prototype._oldlog; + delete Telemetry.prototype.telemetryInfo; + + TargetFactory = Services = promise = require = null; + + finish(); +} + +function test() { + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function() { + gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); + waitForFocus(init, content); + }, true); + + content.location = TEST_URI; +}
--- a/browser/devtools/shared/test/head.js +++ b/browser/devtools/shared/test/head.js @@ -1,14 +1,15 @@ /* 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/. */ let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); let TargetFactory = devtools.TargetFactory; +let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {}); /** * Open a new tab at a URL and call a callback on load */ function addTab(aURL, aCallback) { waitForExplicitFinish();
--- a/docshell/base/nsDefaultURIFixup.cpp +++ b/docshell/base/nsDefaultURIFixup.cpp @@ -398,17 +398,19 @@ NS_IMETHODIMP nsDefaultURIFixup::Keyword // can increment counts from the search engine. The assumption // here is that this keyword/submission will eventually result // in a search. Since we only generate a URI here, there is the // possibility we'll increment the counter without actually // incurring a search. A robust solution would involve currying // the search engine's name through various function calls. nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); if (obsSvc) { - obsSvc->NotifyObservers(defaultEngine, "keyword-search", NS_ConvertUTF8toUTF16(keyword).get()); + // Note that "keyword-search" refers to a search via the url + // bar, not a bookmarks keyword search. + obsSvc->NotifyObservers(defaultEngine, "keyword-search", NS_ConvertUTF8toUTF16(keyword).get()); } return submission->GetUri(aURI); } } } #endif
--- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -724,18 +724,18 @@ static void RecordFrameMetrics(nsIFrame* * metrics.mCumulativeResolution * layerToScreenScale); // For the root scroll frame of the root content document, clamp the // composition bounds to the widget bounds. This is necessary because, if // the page is zoomed in, the frame's size might be larger than the widget // bounds, but we don't want the composition bounds to be. bool useWidgetBounds = false; - bool isRootContentDocRootScrollFrame = aForFrame->GetParent() == nullptr - && presContext->IsRootContentDocument(); + bool isRootContentDocRootScrollFrame = presContext->IsRootContentDocument() + && aScrollFrame == presShell->GetRootScrollFrame(); if (isRootContentDocRootScrollFrame) { if (nsIWidget* widget = aForFrame->GetNearestWidget()) { nsIntRect bounds; widget->GetBounds(bounds); ScreenIntRect screenBounds = ScreenIntRect::FromUnknownRect(mozilla::gfx::IntRect( bounds.x, bounds.y, bounds.width, bounds.height)); AdjustForScrollBars(screenBounds, scrollableFrame); metrics.mCompositionBounds = screenBounds.ClampRect(metrics.mCompositionBounds);
--- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -1439,17 +1439,18 @@ abstract public class BrowserApp extends keywordSearch = ""; } else { keyword = url.substring(0, index); keywordSearch = url.substring(index + 1); } final String keywordUrl = BrowserDB.getUrlForKeyword(getContentResolver(), keyword); - // If there isn't a bookmark keyword, just load the URL. + // If there isn't a bookmark keyword, load the url. This may result in a query + // using the default search engine. if (TextUtils.isEmpty(keywordUrl)) { Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED); return; } recordSearch(null, "barkeyword"); // Otherwise, construct a search query from the bookmark keyword.
--- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -189,16 +189,17 @@ FENNEC_JAVA_FILES = \ gfx/IntSize.java \ gfx/JavaPanZoomController.java \ gfx/Layer.java \ gfx/LayerMarginsAnimator.java \ gfx/LayerRenderer.java \ gfx/LayerView.java \ gfx/NativePanZoomController.java \ gfx/NinePatchTileLayer.java \ + gfx/Overscroll.java \ gfx/PanningPerfAPI.java \ gfx/PanZoomController.java \ gfx/PanZoomTarget.java \ gfx/PluginLayer.java \ gfx/PointUtils.java \ gfx/ProgressiveUpdateData.java \ gfx/RectUtils.java \ gfx/RenderTask.java \
--- a/mobile/android/base/gfx/Axis.java +++ b/mobile/android/base/gfx/Axis.java @@ -149,16 +149,20 @@ abstract class Axis { protected abstract boolean marginsHidden(); Axis(SubdocumentScrollHelper subscroller) { mSubscroller = subscroller; mOverscrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS; mRecentVelocities = new float[FLING_VELOCITY_POINTS]; } + // Implementors can override these to show effects when the axis overscrolls + protected void overscrollFling(float velocity) { } + protected void overscrollPan(float displacement) { } + public void setOverScrollMode(int overscrollMode) { mOverscrollMode = overscrollMode; } public int getOverScrollMode() { return mOverscrollMode; } @@ -374,22 +378,32 @@ abstract class Axis { else mDisplacement += mVelocity * getEdgeResistance(false); // if overscroll is disabled and we're trying to overscroll, reset the displacement // to remove any excess. Using getExcess alone isn't enough here since it relies on // getOverscroll which doesn't take into account any new displacment being applied. // If we using a subscroller, we don't want to alter the scrolling being done if (getOverScrollMode() == View.OVER_SCROLL_NEVER && !mSubscroller.scrolling()) { + float originalDisplacement = mDisplacement; + if (mDisplacement + getOrigin() < getPageStart() - getMarginStart()) { mDisplacement = getPageStart() - getMarginStart() - getOrigin(); - stopFling(); } else if (mDisplacement + getViewportEnd() > getPageEnd() + getMarginEnd()) { mDisplacement = getPageEnd() - getMarginEnd() - getViewportEnd(); - stopFling(); + } + + // Return the amount of overscroll so that the overscroll controller can draw it for us + if (originalDisplacement != mDisplacement) { + if (mFlingState == FlingStates.FLINGING) { + overscrollFling(mVelocity / MS_PER_FRAME * 1000); + stopFling(); + } else if (mFlingState == FlingStates.PANNING) { + overscrollPan(originalDisplacement - mDisplacement); + } } } } float resetDisplacement() { float d = mDisplacement; mDisplacement = 0.0f; return d;
--- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -127,16 +127,20 @@ public class GeckoLayerClient implements mPanZoomController = PanZoomController.Factory.create(this, view, eventDispatcher); mMarginsAnimator = new LayerMarginsAnimator(this, view); mView = view; mView.setListener(this); mContentDocumentIsDisplayed = true; } + public void setOverscrollHandler(final Overscroll listener) { + mPanZoomController.setOverscrollHandler(listener); + } + /** Attaches to root layer so that Gecko appears. */ public void notifyGeckoReady() { mGeckoIsReady = true; mRootLayer = new VirtualLayer(new IntSize(mView.getWidth(), mView.getHeight())); mLayerRenderer = mView.getRenderer(); sendResizeEventIfNecessary(true);
--- a/mobile/android/base/gfx/JavaPanZoomController.java +++ b/mobile/android/base/gfx/JavaPanZoomController.java @@ -124,16 +124,19 @@ class JavaPanZoomController private float mAutonavZoomDelta; /* The user selected panning mode */ private AxisLockMode mMode; /* A medium-length tap/press is happening */ private boolean mMediumPress; /* Used to change the scrollY direction */ private boolean mNegateWheelScrollY; + // Handler to be notified when overscroll occurs + private Overscroll mOverscroll; + public JavaPanZoomController(PanZoomTarget target, View view, EventDispatcher eventDispatcher) { mTarget = target; mSubscroller = new SubdocumentScrollHelper(eventDispatcher); mX = new AxisX(mSubscroller); mY = new AxisY(mSubscroller); mTouchEventHandler = new TouchEventHandler(view.getContext(), view, this); checkMainThread(); @@ -1108,16 +1111,28 @@ class JavaPanZoomController @Override protected float getPageLength() { return getMetrics().getPageWidthWithMargins(); } @Override protected boolean marginsHidden() { ImmutableViewportMetrics metrics = getMetrics(); RectF maxMargins = mTarget.getMaxMargins(); return (metrics.marginLeft < maxMargins.left || metrics.marginRight < maxMargins.right); } + @Override + protected void overscrollFling(final float velocity) { + if (mOverscroll != null) { + mOverscroll.setVelocity(velocity, Overscroll.Axis.X); + } + } + @Override + protected void overscrollPan(final float distance) { + if (mOverscroll != null) { + mOverscroll.setDistance(distance, Overscroll.Axis.X); + } + } } private class AxisY extends Axis { AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); } @Override public float getOrigin() { return getMetrics().viewportRectTop; } @Override protected float getViewportLength() { return getMetrics().getHeight(); } @@ -1130,16 +1145,28 @@ class JavaPanZoomController @Override protected float getMarginEnd() { return mTarget.getMaxMargins().bottom - getMetrics().marginBottom; } @Override protected boolean marginsHidden() { ImmutableViewportMetrics metrics = getMetrics(); RectF maxMargins = mTarget.getMaxMargins(); return (metrics.marginTop < maxMargins.top || metrics.marginBottom < maxMargins.bottom); } + @Override + protected void overscrollFling(final float velocity) { + if (mOverscroll != null) { + mOverscroll.setVelocity(velocity, Overscroll.Axis.Y); + } + } + @Override + protected void overscrollPan(final float distance) { + if (mOverscroll != null) { + mOverscroll.setDistance(distance, Overscroll.Axis.Y); + } + } } /* * Zooming */ @Override public boolean onScaleBegin(SimpleScaleGestureDetector detector) { if (mState == PanZoomState.ANIMATED_ZOOM) @@ -1429,9 +1456,14 @@ class JavaPanZoomController public int getOverScrollMode() { return mX.getOverScrollMode(); } @Override public void updateScrollOffset(float cssX, float cssY) { // Nothing to update, this class doesn't store the scroll offset locally. } + + @Override + public void setOverscrollHandler(final Overscroll handler) { + mOverscroll = handler; + } }
--- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -13,16 +13,17 @@ import org.mozilla.gecko.R; import org.mozilla.gecko.TouchEventInterceptor; import org.mozilla.gecko.ZoomConstraints; import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget; import org.mozilla.gecko.util.EventDispatcher; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.Build; import android.os.Handler; import android.util.AttributeSet; @@ -62,16 +63,17 @@ public class LayerView extends FrameLayo private SurfaceView mSurfaceView; private TextureView mTextureView; private Listener mListener; /* This should only be modified on the Java UI thread. */ private final ArrayList<TouchEventInterceptor> mTouchInterceptors; + private final Overscroll mOverscroll; /* Flags used to determine when to show the painted surface. */ public static final int PAINT_START = 0; public static final int PAINT_BEFORE_FIRST = 1; public static final int PAINT_AFTER_FIRST = 2; public boolean shouldUseTextureView() { // Disable TextureView support for now as it causes panning/zooming @@ -99,20 +101,23 @@ public class LayerView extends FrameLayo public LayerView(Context context, AttributeSet attrs) { super(context, attrs); mGLController = GLController.getInstance(this); mPaintState = PAINT_START; mBackgroundColor = Color.WHITE; mTouchInterceptors = new ArrayList<TouchEventInterceptor>(); + mOverscroll = new Overscroll(this); } public void initializeView(EventDispatcher eventDispatcher) { mLayerClient = new GeckoLayerClient(getContext(), this, eventDispatcher); + mLayerClient.setOverscrollHandler(mOverscroll); + mPanZoomController = mLayerClient.getPanZoomController(); mMarginsAnimator = mLayerClient.getLayerMarginsAnimator(); mRenderer = new LayerRenderer(this); mInputConnectionHandler = null; setFocusable(true); setFocusableInTouchMode(true); @@ -214,16 +219,26 @@ public class LayerView extends FrameLayo result |= i.onInterceptTouchEvent(this, event); } } return result; } @Override + public void dispatchDraw(final Canvas canvas) { + super.dispatchDraw(canvas); + + // We must have a layer client to get valid viewport metrics + if (mLayerClient != null) { + mOverscroll.draw(canvas, getViewportMetrics()); + } + } + + @Override public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { requestFocus(); } if (runTouchInterceptors(event, false)) { return true; } @@ -482,24 +497,28 @@ public class LayerView extends FrameLayo if (!mGLController.hasValidSurface() || mSurfaceView == null) { surfaceChanged(width, height); return; } if (mListener != null) { mListener.sizeChanged(width, height); } + + mOverscroll.setSize(width, height); } private void surfaceChanged(int width, int height) { mGLController.surfaceChanged(width, height); if (mListener != null) { mListener.surfaceChanged(width, height); } + + mOverscroll.setSize(width, height); } private void onDestroyed() { mGLController.surfaceDestroyed(); } public Object getNativeWindow() { if (mSurfaceView != null)
--- a/mobile/android/base/gfx/NativePanZoomController.java +++ b/mobile/android/base/gfx/NativePanZoomController.java @@ -99,9 +99,12 @@ class NativePanZoomController implements long nextDelay = runDelayedCallback(); if (nextDelay >= 0) { mTarget.postDelayed(this, nextDelay); } } } public native void updateScrollOffset(float cssX, float cssY); + + public void setOverscrollHandler(final Overscroll listener) { + } }
new file mode 100644 --- /dev/null +++ b/mobile/android/base/gfx/Overscroll.java @@ -0,0 +1,127 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * 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.gecko.gfx; + +import android.content.Context; +import android.graphics.Canvas; +import android.support.v4.widget.EdgeEffectCompat; +import android.support.v4.view.ViewCompat; +import android.view.View; + +public class Overscroll { + // Used to index particular edges in the edges array + private static final int TOP = 0; + private static final int BOTTOM = 1; + private static final int LEFT = 2; + private static final int RIGHT = 3; + + // All four edges of the screen + private final EdgeEffectCompat[] mEdges = new EdgeEffectCompat[4]; + + // The view we're showing this overscroll on. + private final View mView; + + // The axis to show overscroll on. + public enum Axis { + X, + Y, + }; + + public Overscroll(final View v) { + mView = v; + Context context = v.getContext(); + for (int i = 0; i < 4; i++) { + mEdges[i] = new EdgeEffectCompat(context); + } + } + + public void setSize(final int width, final int height) { + mEdges[LEFT].setSize(height, width); + mEdges[RIGHT].setSize(height, width); + mEdges[TOP].setSize(width, height); + mEdges[BOTTOM].setSize(width, height); + } + + private EdgeEffectCompat getEdgeForAxisAndSide(final Axis axis, final float side) { + if (axis == Axis.Y) { + if (side < 0) { + return mEdges[TOP]; + } else { + return mEdges[BOTTOM]; + } + } else { + if (side < 0) { + return mEdges[LEFT]; + } else { + return mEdges[RIGHT]; + } + } + } + + public void setVelocity(final float velocity, final Axis axis) { + final EdgeEffectCompat edge = getEdgeForAxisAndSide(axis, velocity); + + // If we're showing overscroll already, start fading it out. + if (!edge.isFinished()) { + edge.onRelease(); + } else { + // Otherwise, show an absorb effect + edge.onAbsorb((int)velocity); + } + + ViewCompat.postInvalidateOnAnimation(mView); + } + + public void setDistance(final float distance, final Axis axis) { + // The first overscroll event often has zero distance. Throw it out + if (distance == 0.0f) { + return; + } + + final EdgeEffectCompat edge = getEdgeForAxisAndSide(axis, (int)distance); + edge.onPull(distance / (axis == Axis.X ? mView.getWidth() : mView.getHeight())); + ViewCompat.postInvalidateOnAnimation(mView); + } + + public void draw(final Canvas canvas, final ImmutableViewportMetrics metrics) { + if (metrics == null) { + return; + } + + // If we're pulling an edge, or fading it out, draw! + boolean invalidate = false; + if (!mEdges[TOP].isFinished()) { + invalidate |= draw(mEdges[TOP], canvas, metrics.marginLeft, metrics.marginTop, 0); + } + + if (!mEdges[BOTTOM].isFinished()) { + invalidate |= draw(mEdges[BOTTOM], canvas, mView.getWidth(), mView.getHeight(), 180); + } + + if (!mEdges[LEFT].isFinished()) { + invalidate |= draw(mEdges[LEFT], canvas, metrics.marginLeft, mView.getHeight(), 270); + } + + if (!mEdges[RIGHT].isFinished()) { + invalidate |= draw(mEdges[RIGHT], canvas, mView.getWidth(), metrics.marginTop, 90); + } + + // If the edge effect is animating off screen, invalidate. + if (invalidate) { + ViewCompat.postInvalidateOnAnimation(mView); + } + } + + public boolean draw(final EdgeEffectCompat edge, final Canvas canvas, final float translateX, final float translateY, final float rotation) { + final int state = canvas.save(); + canvas.translate(translateX, translateY); + canvas.rotate(rotation); + boolean invalidate = edge.draw(canvas); + canvas.restoreToCount(state); + + return invalidate; + } +}
--- a/mobile/android/base/gfx/PanZoomController.java +++ b/mobile/android/base/gfx/PanZoomController.java @@ -37,9 +37,11 @@ public interface PanZoomController { public void pageRectUpdated(); public void abortPanning(); public void abortAnimation(); public void setOverScrollMode(int overscrollMode); public int getOverScrollMode(); public void updateScrollOffset(float cssX, float cssY); + + public void setOverscrollHandler(final Overscroll controller); }
--- a/mobile/android/base/health/BrowserHealthRecorder.java +++ b/mobile/android/base/health/BrowserHealthRecorder.java @@ -60,19 +60,20 @@ import java.util.concurrent.atomic.Atomi */ public class BrowserHealthRecorder implements GeckoEventListener { private static final String LOG_TAG = "GeckoHealthRec"; private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; private static final String EVENT_ADDONS_ALL = "Addons:All"; private static final String EVENT_ADDONS_CHANGE = "Addons:Change"; private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling"; private static final String EVENT_PREF_CHANGE = "Pref:Change"; - - // This is raised from Gecko. It avoids browser.js having to know about the - // location that invoked it (the URL bar). + + // This is raised from Gecko and signifies a search via the URL bar (not a bookmarks keyword + // search). Using this event (rather than passing the invocation location as an arg) avoids + // browser.js having to know about the invocation location. public static final String EVENT_KEYWORD_SEARCH = "Search:Keyword"; // This is raised from Java. We include the location in the message. public static final String EVENT_SEARCH = "Search:Event"; public enum State { NOT_INITIALIZED, INITIALIZING, @@ -676,16 +677,19 @@ public class BrowserHealthRecorder imple Log.d(LOG_TAG, "Pref changed: " + pref); handlePrefValue(pref, message.getBoolean("value")); this.onEnvironmentChanged(); return; } // Searches. if (EVENT_KEYWORD_SEARCH.equals(event)) { + // A search via the URL bar. Since we eliminate all other search possibilities + // (e.g. bookmarks keyword, search suggestion) when we initially process the + // search URL, this is considered a default search. recordSearch(message.getString("identifier"), "bartext"); return; } if (EVENT_SEARCH.equals(event)) { if (!message.has("location")) { Log.d(LOG_TAG, "Ignoring search without location."); return; }
--- a/mobile/android/base/resources/layout/home_top_sites_page.xml +++ b/mobile/android/base/resources/layout/home_top_sites_page.xml @@ -1,29 +1,28 @@ <?xml version="1.0" encoding="utf-8"?> <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical"> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> <org.mozilla.gecko.home.HomeListView android:id="@+id/list" style="@style/Widget.TopSitesListView" android:layout_width="fill_parent" - android:layout_height="0dp" - android:layout_weight="1"/> + android:layout_height="fill_parent"/> <org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner" style="@style/Widget.HomeBanner" android:layout_width="fill_parent" android:layout_height="@dimen/home_banner_height" android:background="@drawable/home_banner" android:layout_gravity="bottom" android:gravity="center_vertical" android:visibility="gone" android:clickable="true" android:focusable="true"/> -</LinearLayout> +</FrameLayout>
--- a/mobile/android/base/tests/robocop.ini +++ b/mobile/android/base/tests/robocop.ini @@ -34,17 +34,17 @@ [testClearPrivateData] [testSettingsMenuItems] [testSystemPages] # [testPermissions] # see bug 757475 [testJarReader] [testDistribution] [testFindInPage] [testInputUrlBar] -# [testAddSearchEngine] # disabled on fig - bug 880060 +[testAddSearchEngine] [testImportFromAndroid] [testMasterPassword] [testDeviceSearchEngine] # Used for Talos, please don't use in mochitest #[testPan] #[testCheck] #[testCheck2]
--- a/mobile/android/base/tests/robocop_search.html +++ b/mobile/android/base/tests/robocop_search.html @@ -1,11 +1,11 @@ <html> <header> <title> Robocop Search Engine </title> </header> <body> <form method="get" action="http://www.google.com/search"> - <input type="text" name="q" size="50" maxlength="255" value="" /> + <input type="text" name="q" style="width:300px; height:500px;" maxlength="255" value="" /> <input type="submit" value="Google Search" /> </form> </body> </html>
--- a/mobile/android/base/tests/testAddSearchEngine.java.in +++ b/mobile/android/base/tests/testAddSearchEngine.java.in @@ -1,108 +1,112 @@ #filter substitution package @ANDROID_PACKAGE_NAME@.tests; import @ANDROID_PACKAGE_NAME@.*; import android.view.View; import android.widget.ListAdapter; import android.widget.ListView; -import android.util.Log; import java.util.ArrayList; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * Test adding a search engine from an input field context menu. - * 1. Get the number of existing search engines (As shown in the AwesomeScreen). + * 1. Get the number of existing search engines from the SearchEngine:Data event and as displayed in about:home. * 2. Load a page with a text field, open the context menu and add a search engine from the page. * 3. Get the number of search engines after adding the new one and verify it has increased by 1. */ -public class testAddSearchEngine extends PixelTest { +public class testAddSearchEngine extends AboutHomeTest { private final int MAX_WAIT_TEST_MS = 5000; + private final String SEARCH_TEXT = "Firefox for Android"; + private final String ADD_SEARCHENGINE_OPTION_TEXT = "Add Search Engine"; + @Override protected int getTestType() { return TEST_MOCHITEST; } public void testAddSearchEngine() { - String blankPageURL = getAbsoluteUrl("/robocop/robocop_blank_01.html"); - String searchEngineURL = getAbsoluteUrl("/robocop/robocop_search.html"); + String blankPageURL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL); + String searchEngineURL = getAbsoluteUrl(StringHelper.ROBOCOP_SEARCH_URL); blockForGeckoReady(); + int height = mDriver.getGeckoTop() + 150; + int width = mDriver.getGeckoLeft() + 150; + inputAndLoadUrl(blankPageURL); - waitForText("Browser Blank Page 01"); + waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE); // Get the searchengine data by clicking the awesomebar - this causes Gecko to send Java the list // of search engines. Actions.EventExpecter searchEngineDataEventExpector = mActions.expectGeckoEvent("SearchEngines:Data"); focusUrlBar(); + mActions.sendKeys(SEARCH_TEXT); String eventData = searchEngineDataEventExpector.blockForEventData(); searchEngineDataEventExpector.unregisterListener(); ArrayList<String> searchEngines; try { // Parse the data to get the number of searchengines. searchEngines = getSearchEnginesNames(eventData); } catch (JSONException e) { mAsserter.ok(false, "Fatal exception in testAddSearchEngine while decoding JSON search engine string from Gecko prior to addition of new engine.", e.toString()); return; } final int initialNumSearchEngines = searchEngines.size(); mAsserter.dumpLog("Search Engines list = " + searchEngines.toString()); // Verify that the number of displayed search engines is the same as the one received through the SearchEngines:Data event. - verifyDisplayedSearchEnginesCount("Browser Blank Page 01", initialNumSearchEngines); + verifyDisplayedSearchEnginesCount(initialNumSearchEngines); // Load the page for the search engine to add. inputAndLoadUrl(searchEngineURL); - waitForText("Robocop Search Engine"); + waitForText(StringHelper.ROBOCOP_SEARCH_TITLE); + verifyPageTitle(StringHelper.ROBOCOP_SEARCH_TITLE); // Used to long-tap on the search input box for the search engine to add. - int height = mDriver.getGeckoTop() + 10; - int width = mDriver.getGeckoLeft() + 20; + getInstrumentation().waitForIdleSync(); mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height)); mSolo.clickLongOnScreen(width,height); - if (!waitForText("Add Search Engine")) { - // TODO: clickLongOnScreen does not always work - known Robotium issue - . Clicking a second time seems to work. - mAsserter.dumpLog("Something went wrong and the context menu was not opened. Trying again"); - mSolo.clickLongOnScreen(width,height); - } - mAsserter.ok(waitForText("Add Search Engine"), "Waiting for the context menu to be opened", "The context menu was opened"); + + mAsserter.ok(waitForText(ADD_SEARCHENGINE_OPTION_TEXT), "Waiting for the context menu to be opened", "The context menu was opened"); // Add the search engine - mSolo.clickOnText("Add Search Engine"); + mSolo.clickOnText(ADD_SEARCHENGINE_OPTION_TEXT); waitForText("Cancel"); clickOnButton("OK"); - mAsserter.ok(!mSolo.searchText("Add Search Engine"), "Adding the Search Engine", "The add Search Engine pop-up has been closed"); + mAsserter.ok(!mSolo.searchText(ADD_SEARCHENGINE_OPTION_TEXT), "Adding the Search Engine", "The add Search Engine pop-up has been closed"); + waitForText(StringHelper.ROBOCOP_SEARCH_TITLE); // Make sure the pop-up is closed and we are back at the searchengine page // Load Robocop Blank 1 again to give the time for the searchengine to be added // TODO: This is a potential source of intermittent oranges - it's a race condition! - loadAndPaint(blankPageURL); - waitForText("Browser Blank Page 01"); + loadUrl(blankPageURL); + waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE); // Load search engines again and check that the quantity of engines has increased by 1. searchEngineDataEventExpector = mActions.expectGeckoEvent("SearchEngines:Data"); focusUrlBar(); + mActions.sendKeys(SEARCH_TEXT); eventData = searchEngineDataEventExpector.blockForEventData(); try { // Parse the data to get the number of searchengines searchEngines = getSearchEnginesNames(eventData); } catch (JSONException e) { mAsserter.ok(false, "Fatal exception in testAddSearchEngine while decoding JSON search engine string from Gecko after adding of new engine.", e.toString()); return; } mAsserter.dumpLog("Search Engines list = " + searchEngines.toString()); mAsserter.is(searchEngines.size(), initialNumSearchEngines + 1, "Checking the number of Search Engines has increased"); // Verify that the number of displayed searchengines is the same as the one received through the SearchEngines:Data event. - verifyDisplayedSearchEnginesCount("Browser Blank Page 01", initialNumSearchEngines + 1); + verifyDisplayedSearchEnginesCount(initialNumSearchEngines + 1); searchEngineDataEventExpector.unregisterListener(); } /** * Helper method to decode a list of search engine names from the provided search engine information * JSON string sent from Gecko. * @param searchEngineData The JSON string representing the search engine array to process * @return An ArrayList<String> containing the names of all the search engines represented in @@ -118,56 +122,26 @@ public class testAddSearchEngine extends JSONObject engineJSON = engines.getJSONObject(i); searchEngineNames.add(engineJSON.getString("name")); } return searchEngineNames; } /** * Method to verify that the displayed number of search engines matches the expected number. - * Uses a BooleanTest which counts how many SearchEngineRow instances are being displayed - * in the Awesomescreen. - * @param waitText Text from the loaded page to expect. Used to detect when the Awesomescreen - * close animation has completed. - * @param expectedCountParam The expected number of search engines. + * @param expectedCount The expected number of search engines. */ - public void verifyDisplayedSearchEnginesCount(String waitText, int expectedCountParam) { - final int expectedCount = expectedCountParam; - mActions.sendKeys("Firefox for Android"); + public void verifyDisplayedSearchEnginesCount(final int expectedCount) { + mSolo.clearEditText(0); + mActions.sendKeys(SEARCH_TEXT); boolean correctNumSearchEnginesDisplayed = waitForTest(new BooleanTest() { @Override public boolean test() { - ArrayList<ListView> views; - int searchEngineCount = 0; - views = mSolo.getCurrentViews(ListView.class); - for (ListView view : views) { - ListAdapter adapter = view.getAdapter(); - if (adapter != null) { - // Only count SearchEngineRow views - other views are not relavent to this test. - try { - ClassLoader classLoader = getActivity().getClassLoader(); - Class searchEngineRow = classLoader.loadClass("org.mozilla.gecko.SearchEngineRow"); - for (int i = 0; i < adapter.getCount(); i++ ) { - View item = view.getChildAt(i); - if (searchEngineRow.isInstance(item)) { - searchEngineCount++; - } - } - } catch (Exception e) { - mAsserter.dumpLog("Exception in verifyDisplayedSearchEnginesCount", e); - } - } - } - if (searchEngineCount == expectedCount) { - return true; - } else { - mAsserter.dumpLog("The wrong number of search engines was found. Found " + searchEngineCount + " search engines"); - return false; - } + return (findListViewWithTag("browser_search").getAdapter().getCount() == expectedCount); } }, MAX_WAIT_TEST_MS); - // Close the Awesomescreen + // Exit about:home mActions.sendSpecialKey(Actions.SpecialKey.BACK); - waitForText(waitText); + waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE); mAsserter.ok(correctNumSearchEnginesDisplayed, expectedCount + " Search Engines should be displayed" , "The correct number of Search Engines has been displayed"); } }
--- a/mobile/android/base/widget/FaviconView.java +++ b/mobile/android/base/widget/FaviconView.java @@ -57,27 +57,19 @@ public class FaviconView extends ImageVi } /** * Formats the image for display, if the prerequisite data are available. Upscales tiny Favicons to * normal sized ones, replaces null bitmaps with the default Favicon, and fills all remaining space * in this view with the coloured background. */ private void formatImage() { - // If we're called before bitmap is set, just show the default. - if (mIconBitmap == null) { - setImageResource(R.drawable.favicon); - hideBackground(); - return; - } - - // If we're called before size set, abort. - if (mActualWidth == 0 || mActualHeight == 0) { - hideBackground(); - setImageResource(R.drawable.favicon); + // If we're called before bitmap is set, or before size is set, show blank. + if (mIconBitmap == null || mActualWidth == 0 || mActualHeight == 0) { + clearImage(); return; } if (mScalingExpected && mActualWidth != mIconBitmap.getWidth()) { scaleBitmap(); // Don't scale the image every time something changes. mScalingExpected = false; } @@ -137,35 +129,49 @@ public class FaviconView extends ImageVi * * @param bitmap favicon image * @param key string used as a key to cache the dominant color of this image * @param allowScaling If true, allows the provided bitmap to be scaled by this FaviconView. * Typically, you should prefer using Favicons obtained via the caching system * (Favicons class), so as to exploit caching. */ private void updateImageInternal(Bitmap bitmap, String key, boolean allowScaling) { + if (bitmap == null) { + showDefaultFavicon(); + return; + } + // Reassigning the same bitmap? Don't bother. if (mUnscaledBitmap == bitmap) { return; } mUnscaledBitmap = bitmap; mIconBitmap = bitmap; mIconKey = key; mScalingExpected = allowScaling; // Possibly update the display. formatImage(); } + private void showDefaultFavicon() { + setImageResource(R.drawable.favicon); + hideBackground(); + } + /** * Clear image and background shown by this view. */ public void clearImage() { setImageResource(0); hideBackground(); + mUnscaledBitmap = null; + mIconBitmap = null; + mIconKey = null; + mScalingExpected = false; } /** * Update the displayed image and apply the scaling logic. * The scaling logic will attempt to resize the image to fit correctly inside the view in a way * that avoids unreasonable levels of loss of quality. * Scaling is necessary only when the icon being provided is not drawn from the Favicon cache * introduced in Bug 914296.
--- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -1397,17 +1397,19 @@ var BrowserApp = { this._handleTabSelected(this.getTabForId(parseInt(aData))); break; case "Tab:Closed": this._handleTabClosed(this.getTabForId(parseInt(aData))); break; case "keyword-search": - // This assumes that the user can only perform a keyword search on the selected tab. + // This event refers to a search via the URL bar, not a bookmarks + // keyword search. Note that this code assumes that the user can only + // perform a keyword search on the selected tab. this.selectedTab.userSearch = aData; let engine = aSubject.QueryInterface(Ci.nsISearchEngine); sendMessageToJava({ type: "Search:Keyword", identifier: engine.identifier, name: engine.name, });
--- a/mobile/android/components/HelperAppDialog.js +++ b/mobile/android/components/HelperAppDialog.js @@ -8,16 +8,21 @@ const Cu = Components.utils; const Cr = Components.results; const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir"; const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download"; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + // ----------------------------------------------------------------------- // HelperApp Launcher Dialog // ----------------------------------------------------------------------- function HelperAppLauncherDialog() { } HelperAppLauncherDialog.prototype = { classID: Components.ID("{e9d277a0-268a-4ec2-bb8c-10fdf3e44611}"), @@ -25,25 +30,26 @@ HelperAppLauncherDialog.prototype = { show: function hald_show(aLauncher, aContext, aReason) { // Save everything by default aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault; aLauncher.saveToDisk(null, false); }, promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) { - // Retrieve the user's default download directory - let dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); - let defaultFolder = dnldMgr.userDownloadsDirectory; + return Task.spawn(function() { + // Retrieve the user's default download directory + let defaultFolder = yield Downloads.getPreferredDownloadsDirectory(); - try { - file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt); - } catch (e) { } + try { + file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt); + } catch (e) { } - return file; + throw new Task.Result(file); + }.bind(this)); }, validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) { if (!(aLocalFile && this.isUsableDirectory(aLocalFile))) return null; // Remove any leading periods, since we don't want to save hidden files // automatically.
--- a/mobile/android/components/NSSDialogService.js +++ b/mobile/android/components/NSSDialogService.js @@ -177,17 +177,17 @@ NSSDialogs.prototype = { while (true) { let prompt = this.getPrompt(this.getString("clientAuthAsk.title"), this.getString("clientAuthAsk.message1"), [ this.getString("nssdialogs.ok.label"), this.getString("clientAuthAsk.viewCert.label"), this.getString("nssdialogs.cancel.label") ]) .addLabel({ id: "requestedDetails", label: serverRequestedDetails } ) - .addMenuList({ + .addMenulist({ id: "nicknames", label: this.getString("clientAuthAsk.message2"), values: certNickList, selected: selectedIndex }).addCheckbox({ id: "rememberBox", label: this.getString("clientAuthAsk.remember.label"), checked: rememberSetting });
--- a/storage/src/mozStorageConnection.cpp +++ b/storage/src/mozStorageConnection.cpp @@ -699,17 +699,19 @@ Connection::initializeInternal(nsIFile* nsresult Connection::databaseElementExists(enum DatabaseElementType aElementType, const nsACString &aElementName, bool *_exists) { if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; - nsAutoCString query("SELECT name FROM sqlite_master WHERE type = '"); + nsCString query("SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " + "SELECT * FROM sqlite_temp_master) " + "WHERE type = '"); switch (aElementType) { case INDEX: query.Append("index"); break; case TABLE: query.Append("table"); break; }
--- a/storage/test/unit/test_storage_connection.js +++ b/storage/test/unit/test_storage_connection.js @@ -103,16 +103,29 @@ add_task(function test_tableExists_not_c }); add_task(function test_indexExists_not_created() { var msc = getOpenedDatabase(); do_check_false(msc.indexExists("foo")); }); +add_task(function test_temp_tableExists_and_indexExists() +{ + var msc = getOpenedDatabase(); + msc.executeSimpleSQL("CREATE TEMP TABLE test_temp(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"); + do_check_true(msc.tableExists("test_temp")); + + msc.executeSimpleSQL("CREATE INDEX test_temp_ind ON test_temp (name)"); + do_check_true(msc.indexExists("test_temp_ind")); + + msc.executeSimpleSQL("DROP INDEX test_temp_ind"); + msc.executeSimpleSQL("DROP TABLE test_temp"); +}); + add_task(function test_createTable_not_created() { var msc = getOpenedDatabase(); msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT"); do_check_true(msc.tableExists("test")); }); add_task(function test_indexExists_created()
--- a/testing/mochitest/runtestsremote.py +++ b/testing/mochitest/runtestsremote.py @@ -459,25 +459,36 @@ class MochiRemote(Mochitest): print '\n'.join(logFile) with open(self.localLog, 'w') as localLog: localLog.write('\n'.join(logFile)) if failed > 0: return 1 return 0 - def printScreenshot(self): - try: - image = self._dm.pullFile("/mnt/sdcard/Robotium-Screenshots/robocop-screenshot.jpg") - encoded = base64.b64encode(image) - log.info("SCREENSHOT: data:image/jpg;base64,%s", encoded) - except: - # If the test passes, no screenshot will be generated and - # pullFile will fail -- continue silently. - pass + def printScreenshots(self, screenShotDir): + # TODO: This can be re-written after completion of bug 749421 + if not self._dm.dirExists(screenShotDir): + log.info("SCREENSHOT: No ScreenShots directory available: " + screenShotDir) + return + + printed = 0 + for name in self._dm.listFiles(screenShotDir): + fullName = screenShotDir + "/" + name + log.info("SCREENSHOT: FOUND: [%s]", fullName) + try: + image = self._dm.pullFile(fullName) + encoded = base64.b64encode(image) + log.info("SCREENSHOT: data:image/jpg;base64,%s", encoded) + printed += 1 + except: + log.info("SCREENSHOT: Could not be parsed") + pass + + log.info("SCREENSHOT: TOTAL PRINTED: [%s]", printed) def printDeviceInfo(self, printLogcat=False): try: if printLogcat: logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters) log.info('\n'+(''.join(logcat))) log.info("Device info: %s", self._dm.getInfo()) log.info("Test root: %s", self._dm.getDeviceRoot()) @@ -649,25 +660,26 @@ def main(): for i in range(20): if ("pandaboard" in devOS): cmd = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db 'insert or replace into bookmarks(_id,title,url,folder,parent,position) values (" + str(30 + i) + ",\"Bookmark"+ str(i) + "\",\"http://www.bookmark" + str(i) + ".com\",0,1," + str(100 + i) + ");'"] else: cmd = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db 'insert into bookmarks(title,url,bookmark) values (\"Bookmark"+ str(i) + "\",\"http://www.bookmark" + str(i) + ".com\",1);'"] if (options.dm_trans == "sut"): dm._runCmds([{"cmd": " ".join(cmd)}]) try: - dm.removeDir("/mnt/sdcard/Robotium-Screenshots") + screenShotDir = "/mnt/sdcard/Robotium-Screenshots" + dm.removeDir(screenShotDir) dm.recordLogcat() result = mochitest.runTests(options) if result != 0: log.error("runTests() exited with code %s", result) log_result = mochitest.addLogData() if result != 0 or log_result != 0: mochitest.printDeviceInfo(printLogcat=True) - mochitest.printScreenshot() + mochitest.printScreenshots(screenShotDir) # Ensure earlier failures aren't overwritten by success on this run if retVal is None or retVal == 0: retVal = result except: log.error("Automation Error: Exception caught while running tests") traceback.print_exc() mochitest.stopWebServer(options) mochitest.stopWebSocketServer(options)
--- a/toolkit/devtools/server/actors/styles.js +++ b/toolkit/devtools/server/actors/styles.js @@ -710,18 +710,25 @@ var StyleRuleActor = protocol.ActorClass * * @returns the rule with updated properties */ modifyProperties: method(function(modifications) { let validProps = new Map(); // Use a fresh element for each call to this function to prevent side effects // that pop up based on property values that were already set on the element. - let tempElement = Services.appShell.hiddenDOMWindow. - document.createElement("div"); + + let document; + if (this.rawNode) { + document = this.rawNode.ownerDocument; + } else { + document = this.rawRule.parentStyleSheet.ownerNode.ownerDocument; + } + + let tempElement = document.createElement("div"); for (let mod of modifications) { if (mod.type === "set") { tempElement.style.setProperty(mod.name, mod.value, mod.priority || ""); this.rawStyle.setProperty(mod.name, tempElement.style.getPropertyValue(mod.name), mod.priority || ""); } else if (mod.type === "remove") { this.rawStyle.removeProperty(mod.name);
--- a/toolkit/modules/Sqlite.jsm +++ b/toolkit/modules/Sqlite.jsm @@ -595,48 +595,48 @@ OpenedConnection.prototype = Object.free deferred.reject(error); }.bind(this) ); return deferred.promise; }, /** - * Whether a table exists in the database. - * - * IMPROVEMENT: Look for temporary tables. + * Whether a table exists in the database (both persistent and temporary tables). * * @param name * (string) Name of the table. * * @return Promise<bool> */ tableExists: function (name) { return this.execute( - "SELECT name FROM sqlite_master WHERE type='table' AND name=?", + "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " + + "SELECT * FROM sqlite_temp_master) " + + "WHERE type = 'table' AND name=?", [name]) .then(function onResult(rows) { return Promise.resolve(rows.length > 0); } ); }, /** - * Whether a named index exists. - * - * IMPROVEMENT: Look for indexes in temporary tables. + * Whether a named index exists (both persistent and temporary tables). * * @param name * (string) Name of the index. * * @return Promise<bool> */ indexExists: function (name) { return this.execute( - "SELECT name FROM sqlite_master WHERE type='index' AND name=?", + "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " + + "SELECT * FROM sqlite_temp_master) " + + "WHERE type = 'index' AND name=?", [name]) .then(function onResult(rows) { return Promise.resolve(rows.length > 0); } ); }, /**
--- a/toolkit/modules/tests/xpcshell/test_sqlite.js +++ b/toolkit/modules/tests/xpcshell/test_sqlite.js @@ -55,16 +55,32 @@ function getDummyDatabase(name, extraOpt for (let [k, v] in Iterator(TABLES)) { yield c.execute("CREATE TABLE " + k + "(" + v + ")"); c._initialStatementCount++; } throw new Task.Result(c); } +function getDummyTempDatabase(name, extraOptions={}) { + const TABLES = { + dirs: "id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT", + files: "id INTEGER PRIMARY KEY AUTOINCREMENT, dir_id INTEGER, path TEXT", + }; + + let c = yield getConnection(name, extraOptions); + c._initialStatementCount = 0; + + for (let [k, v] in Iterator(TABLES)) { + yield c.execute("CREATE TEMP TABLE " + k + "(" + v + ")"); + c._initialStatementCount++; + } + + throw new Task.Result(c); +} function run_test() { Cu.import("resource://testing-common/services-common/logging.js"); initTestLogging("Trace"); run_next_test(); } @@ -198,16 +214,37 @@ add_task(function test_index_exists() { do_check_false(yield c.indexExists("does_not_exist")); yield c.execute("CREATE INDEX my_index ON dirs (path)"); do_check_true(yield c.indexExists("my_index")); yield c.close(); }); +add_task(function test_temp_table_exists() { + let c = yield getDummyTempDatabase("temp_table_exists"); + + do_check_false(yield c.tableExists("temp_does_not_exist")); + do_check_true(yield c.tableExists("dirs")); + do_check_true(yield c.tableExists("files")); + + yield c.close(); +}); + +add_task(function test_temp_index_exists() { + let c = yield getDummyTempDatabase("temp_index_exists"); + + do_check_false(yield c.indexExists("temp_does_not_exist")); + + yield c.execute("CREATE INDEX my_index ON dirs (path)"); + do_check_true(yield c.indexExists("my_index")); + + yield c.close(); +}); + add_task(function test_close_cached() { let c = yield getDummyDatabase("close_cached"); yield c.executeCached("SELECT * FROM dirs"); yield c.executeCached("SELECT * FROM files"); yield c.close(); });
--- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -21,16 +21,17 @@ const PREF_APP_UPDATE_ENABLED = const PREF_APP_UPDATE_AUTO = "app.update.auto"; const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; const PREF_EM_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion"; const PREF_EM_HOTFIX_URL = "extensions.hotfix.url"; const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; +const UNKNOWN_XPCOM_ABI = "unknownABI"; const UPDATE_REQUEST_VERSION = 2; const CATEGORY_UPDATE_PARAMS = "extension-update-params"; const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi; const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility"; #ifdef MOZ_COMPATIBILITY_NIGHTLY var PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly";