Merge fx-team to m-c. a=merge

--HG--
extra : amend_source : 35bb2e9e2b827884507d4fcf12c69150baa3c4b3
This commit is contained in:
Ryan VanderMeulen 2014-08-08 16:17:12 -04:00
commit 834e51bc80
38 changed files with 2004 additions and 284 deletions

View File

@ -299,8 +299,8 @@ skip-if = true # browser_drag.js is disabled, as it needs to be updated for the
[browser_findbarClose.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (tries to grab an iframe directly from content)
[browser_fullscreen-window-open.js]
[browser_fxa_oauth.js]
skip-if = buildapp == 'mulet' || e10s || os == "linux" # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly. Linux: Intermittent failures - bug 941575.
[browser_fxa_oauth.js]
[browser_gestureSupport.js]
skip-if = e10s # Bug 863514 - no gesture support.
[browser_getshortcutoruri.js]
@ -437,8 +437,8 @@ skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
[browser_visibleTabs_contextMenu.js]
skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
[browser_visibleTabs_tabPreview.js]
[browser_web_channel.js]
skip-if = (os == "win" && !debug) || e10s # Bug 1007418 / Bug 698371 - thumbnail captures need e10s love (tabPreviews_capture fails with Argument 1 of CanvasRenderingContext2D.drawWindow does not implement interface Window.)
[browser_web_channel.js]
[browser_windowopen_reflows.js]
skip-if = buildapp == 'mulet'
[browser_wyciwyg_urlbarCopying.js]

View File

@ -12,6 +12,11 @@ Cu.import("resource:///modules/loop/MozLoopService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "hookWindowCloseForPanelClose",
"resource://gre/modules/MozSocialAPI.jsm");
XPCOMUtils.defineLazyGetter(this, "appInfo", function() {
return Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo)
.QueryInterface(Ci.nsIXULRuntime);
});
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
@ -28,6 +33,7 @@ this.EXPORTED_SYMBOLS = ["injectLoopAPI"];
function injectLoopAPI(targetWindow) {
let ringer;
let ringerStopper;
let appVersionInfo;
let api = {
/**
@ -239,7 +245,31 @@ function injectLoopAPI(targetWindow) {
value: function(str) {
clipboardHelper.copyString(str);
}
}
},
/**
* Returns the app version information for use during feedback.
*
* @return {Object} An object containing:
* - channel: The update channel the application is on
* - version: The application version
* - OS: The operating system the application is running on
*/
appVersionInfo: {
enumerable: true,
get: function() {
if (!appVersionInfo) {
let defaults = Services.prefs.getDefaultBranch(null);
appVersionInfo = Cu.cloneInto({
channel: defaults.getCharPref("app.update.channel"),
version: appInfo.version,
OS: appInfo.OS
}, targetWindow);
}
return appVersionInfo;
}
},
};
let contentObj = Cu.createObjectIn(targetWindow);

View File

@ -248,11 +248,20 @@ loop.conversation = (function(OT, mozL10n) {
feedback: function() {
document.title = mozL10n.get("call_has_ended");
var feebackAPIBaseUrl = navigator.mozLoop.getLoopCharPref(
"feedback.baseUrl");
var appVersionInfo = navigator.mozLoop.appVersionInfo;
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
product: navigator.mozLoop.getLoopCharPref("feedback.product"),
platform: appVersionInfo.OS,
channel: appVersionInfo.channel,
version: appVersionInfo.version
});
this.loadReactComponent(sharedViews.FeedbackView({
feedbackApiClient: new loop.FeedbackAPIClient({
baseUrl: navigator.mozLoop.getLoopCharPref("feedback.baseUrl"),
product: navigator.mozLoop.getLoopCharPref("feedback.product")
})
feedbackApiClient: feedbackClient
}));
}
});

View File

@ -248,11 +248,20 @@ loop.conversation = (function(OT, mozL10n) {
feedback: function() {
document.title = mozL10n.get("call_has_ended");
var feebackAPIBaseUrl = navigator.mozLoop.getLoopCharPref(
"feedback.baseUrl");
var appVersionInfo = navigator.mozLoop.appVersionInfo;
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
product: navigator.mozLoop.getLoopCharPref("feedback.product"),
platform: appVersionInfo.OS,
channel: appVersionInfo.channel,
version: appVersionInfo.version
});
this.loadReactComponent(sharedViews.FeedbackView({
feedbackApiClient: new loop.FeedbackAPIClient({
baseUrl: navigator.mozLoop.getLoopCharPref("feedback.baseUrl"),
product: navigator.mozLoop.getLoopCharPref("feedback.product")
})
feedbackApiClient: feedbackClient
}));
}
});

View File

@ -5,58 +5,82 @@
/* global loop:true */
var loop = loop || {};
loop.FeedbackAPIClient = (function($) {
loop.FeedbackAPIClient = (function($, _) {
"use strict";
/**
* Feedback API client. Sends feedback data to an input.mozilla.com compatible
* API.
*
* Available settings:
* - {String} baseUrl Base API url (required)
* @param {String} baseUrl Base API url (required)
* @param {Object} defaults Defaults field values for that client.
*
* Required defaults:
* - {String} product Product name (required)
*
* @param {Object} settings Settings.
* Optional defaults:
* - {String} platform Platform name, eg. "Windows 8", "Android", "Linux"
* - {String} version Product version, eg. "22b2", "1.1"
* - {String} channel Product channel, eg. "stable", "beta"
* - {String} user_agent eg. Mozilla/5.0 (Mobile; rv:18.0) Gecko/18.0 Firefox/18.0
*
* @link http://fjord.readthedocs.org/en/latest/api.html
*/
function FeedbackAPIClient(settings) {
settings = settings || {};
if (!settings.hasOwnProperty("baseUrl")) {
throw new Error("Missing required baseUrl setting.");
function FeedbackAPIClient(baseUrl, defaults) {
this.baseUrl = baseUrl;
if (!this.baseUrl) {
throw new Error("Missing required 'baseUrl' argument.");
}
this._baseUrl = settings.baseUrl;
if (!settings.hasOwnProperty("product")) {
throw new Error("Missing required product setting.");
this.defaults = defaults || {};
// required defaults checks
if (!this.defaults.hasOwnProperty("product")) {
throw new Error("Missing required 'product' default.");
}
this._product = settings.product;
}
FeedbackAPIClient.prototype = {
/**
* Formats Feedback data to match the API spec.
*
* @param {Object} fields Feedback form data.
* @return {Object} Formatted data.
* Supported field names by the feedback API.
* @type {Array}
*/
_formatData: function(fields) {
var formatted = {};
_supportedFields: ["happy",
"category",
"description",
"product",
"platform",
"version",
"channel",
"user_agent"],
/**
* Creates a formatted payload object compliant with the Feedback API spec
* against validated field data.
*
* @param {Object} fields Feedback initial values.
* @return {Object} Formatted payload object.
* @throws {Error} If provided values are invalid
*/
_createPayload: function(fields) {
if (typeof fields !== "object") {
throw new Error("Invalid feedback data provided.");
}
formatted.product = this._product;
formatted.happy = fields.happy;
formatted.category = fields.category;
Object.keys(fields).forEach(function(name) {
if (this._supportedFields.indexOf(name) === -1) {
throw new Error("Unsupported field " + name);
}
}, this);
// Payload is basically defaults + fields merged in
var payload = _.extend({}, this.defaults, fields);
// Default description field value
if (!fields.description) {
formatted.description = (fields.happy ? "Happy" : "Sad") + " User";
} else {
formatted.description = fields.description;
payload.description = (fields.happy ? "Happy" : "Sad") + " User";
}
return formatted;
return payload;
},
/**
@ -67,11 +91,11 @@ loop.FeedbackAPIClient = (function($) {
*/
send: function(fields, cb) {
var req = $.ajax({
url: this._baseUrl,
url: this.baseUrl,
method: "POST",
contentType: "application/json",
dataType: "json",
data: JSON.stringify(this._formatData(fields))
data: JSON.stringify(this._createPayload(fields))
});
req.done(function(result) {
@ -89,4 +113,4 @@ loop.FeedbackAPIClient = (function($) {
};
return FeedbackAPIClient;
})(jQuery);
})(jQuery, _);

View File

@ -38,7 +38,14 @@ describe("loop.conversation", function() {
getLoopCharPref: sandbox.stub(),
startAlerting: function() {},
stopAlerting: function() {},
ensureRegistered: function() {}
ensureRegistered: function() {},
get appVersionInfo() {
return {
version: "42",
channel: "test",
platform: "test"
};
}
};
// XXX These stubs should be hoisted in a common file

View File

@ -2,6 +2,7 @@
support-files =
head.js
[browser_mozLoop_appVersionInfo.js]
[browser_mozLoop_charPref.js]
[browser_mozLoop_doNotDisturb.js]
skip-if = buildapp == 'mulet'

View File

@ -0,0 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This is an integration test from navigator.mozLoop through to the end
* effects - rather than just testing MozLoopAPI alone.
*/
add_task(loadLoopPanel);
add_task(function* test_mozLoop_appVersionInfo() {
Assert.ok(gMozLoopAPI, "mozLoop should exist");
let appVersionInfo = gMozLoopAPI.appVersionInfo;
Assert.ok(appVersionInfo, "should have appVersionInfo");
Assert.equal(appVersionInfo.channel,
Services.prefs.getCharPref("app.update.channel"),
"appVersionInfo.channel should match the application channel");
var appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo)
.QueryInterface(Ci.nsIXULRuntime);
Assert.equal(appVersionInfo.version,
appInfo.version,
"appVersionInfo.version should match the application version");
Assert.equal(appVersionInfo.OS,
appInfo.OS,
"appVersionInfo.os should match the running os");
});

View File

@ -31,13 +31,13 @@ describe("loop.FeedbackAPIClient", function() {
it("should require a baseUrl setting", function() {
expect(function() {
return new loop.FeedbackAPIClient();
}).to.Throw(/required baseUrl/);
}).to.Throw(/required 'baseUrl'/);
});
it("should require a product setting", function() {
expect(function() {
return new loop.FeedbackAPIClient({baseUrl: "http://fake"});
}).to.Throw(/required product/);
return new loop.FeedbackAPIClient("http://fake", {});
}).to.Throw(/required 'product'/);
});
});
@ -45,9 +45,9 @@ describe("loop.FeedbackAPIClient", function() {
var client;
beforeEach(function() {
client = new loop.FeedbackAPIClient({
baseUrl: "http://fake/feedback",
product: "Hello"
client = new loop.FeedbackAPIClient("http://fake/feedback", {
product: "Hello",
version: "42b1"
});
});
@ -103,16 +103,57 @@ describe("loop.FeedbackAPIClient", function() {
expect(parsed.description).eql("it's far too awesome!");
});
it("should send product information", function() {
client.send({product: "Hello"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.product).eql("Hello");
});
it("should send platform information when provided", function() {
client.send({platform: "Windows 8"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.platform).eql("Windows 8");
});
it("should send channel information when provided", function() {
client.send({channel: "beta"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.channel).eql("beta");
});
it("should send version information when provided", function() {
client.send({version: "42b1"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.version).eql("42b1");
});
it("should send user_agent information when provided", function() {
client.send({user_agent: "MOZAGENT"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.user_agent).eql("MOZAGENT");
});
it("should throw on invalid feedback data", function() {
expect(function() {
client.send("invalid data", function(){});
}).to.Throw(/Invalid/);
});
it("should throw on unsupported field name", function() {
expect(function() {
client.send({bleh: "bah"}, function(){});
}).to.Throw(/Unsupported/);
});
it("should call passed callback on success", function() {
var cb = sandbox.spy();
var fakeResponseData = {description: "confusing"};
client.send({reason: "confusing"}, cb);
client.send({category: "confusing"}, cb);
requests[0].respond(200, {"Content-Type": "application/json"},
JSON.stringify(fakeResponseData));
@ -124,7 +165,7 @@ describe("loop.FeedbackAPIClient", function() {
it("should call passed callback on error", function() {
var cb = sandbox.spy();
var fakeErrorData = {error: true};
client.send({reason: "confusing"}, cb);
client.send({category: "confusing"}, cb);
requests[0].respond(400, {"Content-Type": "application/json"},
JSON.stringify(fakeErrorData));

View File

@ -15,7 +15,7 @@
<div id="main"></div>
<script src="fake-mozLoop.js"></script>
<script src="fake-l10n.js"></script>
<script src="../content/libs/sdk.js"></script>
<script src="../content/shared/libs/sdk.js"></script>
<script src="../content/shared/libs/react-0.11.1.js"></script>
<script src="../content/shared/libs/jquery-2.1.0.js"></script>
<script src="../content/shared/libs/lodash-2.4.1.js"></script>

View File

@ -35,10 +35,10 @@
// Feedback API client configured to send data to the stage input server,
// which is available at https://input.allizom.org
var stageFeedbackApiClient = new loop.FeedbackAPIClient({
baseUrl: "https://input.allizom.org/api/v1/feedback",
product: "Loop"
});
var stageFeedbackApiClient = new loop.FeedbackAPIClient(
"https://input.allizom.org/api/v1/feedback", {
product: "Loop"
});
var Example = React.createClass({displayName: 'Example',
render: function() {

View File

@ -35,10 +35,10 @@
// Feedback API client configured to send data to the stage input server,
// which is available at https://input.allizom.org
var stageFeedbackApiClient = new loop.FeedbackAPIClient({
baseUrl: "https://input.allizom.org/api/v1/feedback",
product: "Loop"
});
var stageFeedbackApiClient = new loop.FeedbackAPIClient(
"https://input.allizom.org/api/v1/feedback", {
product: "Loop"
});
var Example = React.createClass({
render: function() {

View File

@ -29,6 +29,7 @@ support-files =
browser_formdata_format_sample.html
browser_pageStyle_sample.html
browser_pageStyle_sample_nested.html
browser_sessionHistory_slow.sjs
browser_scrollPositions_sample.html
browser_scrollPositions_sample_frameset.html
browser_sessionStorage.html
@ -42,6 +43,7 @@ support-files =
browser_463206_sample.html
browser_466937_sample.html
browser_485482_sample.html
browser_637020_slow.sjs
browser_662743_sample.html
browser_739531_sample.html
browser_911547_sample.html
@ -81,7 +83,6 @@ skip-if = buildapp == 'mulet'
[browser_privatetabs.js]
[browser_scrollPositions.js]
[browser_sessionHistory.js]
[browser_sessionHistory_slow.sjs]
[browser_sessionStorage.js]
[browser_swapDocShells.js]
[browser_telemetry.js]
@ -163,7 +164,6 @@ skip-if = true # Needs to be rewritten as Marionette test, bug 995916
[browser_635418.js]
[browser_636279.js]
[browser_637020.js]
[browser_637020_slow.sjs]
[browser_644409-scratchpads.js]
[browser_645428.js]
[browser_659591.js]

View File

@ -213,6 +213,7 @@ let DebuggerView = {
bindKey("_doGlobalSearch", "globalSearchKey", { alt: true });
bindKey("_doFunctionSearch", "functionSearchKey");
extraKeys[Editor.keyFor("jumpToLine")] = false;
extraKeys["Esc"] = false;
function bindKey(func, key, modifiers = {}) {
let key = document.getElementById(key).getAttribute("key");

View File

@ -213,6 +213,7 @@ var TextEditor = Class({
return;
}
this.editor.setText(resourceContents);
this.editor.clearHistory();
this.editor.setClean();
this.emit("load");
}, console.error);

View File

@ -38,6 +38,10 @@ function testEditFile(projecteditor, filePath, newData) {
info ("Setting text in the editor and doing checks before saving");
editor.editor.undo();
editor.editor.undo();
is (editor.editor.getText(), initialData, "Editor is still loaded with correct contents after undo");
editor.editor.setText(newData);
is (editor.editor.getText(), newData, "Editor has been filled with new data");
is (viewContainer.label.textContent, "*" + originalTreeLabel, "Label is marked as changed");

View File

@ -36,7 +36,7 @@
* about:telemetry.
*
* You can view telemetry stats for large groups of Firefox users at
* metrics.mozilla.com.
* telemetry.mozilla.org.
*/
const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version";
@ -170,6 +170,11 @@ Telemetry.prototype = {
userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS"
},
webide: {
histogram: "DEVTOOLS_WEBIDE_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_WEBIDE_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_WEBIDE_TIME_ACTIVE_SECONDS"
},
custom: {
histogram: "DEVTOOLS_CUSTOM_OPENED_BOOLEAN",
userHistogram: "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG",
@ -194,7 +199,7 @@ Telemetry.prototype = {
this.logOncePerBrowserVersion(charts.userHistogram, true);
}
if (charts.timerHistogram) {
this._timers.set(charts.timerHistogram, new Date());
this.startTimer(charts.timerHistogram);
}
},
@ -205,12 +210,31 @@ Telemetry.prototype = {
return;
}
let startTime = this._timers.get(charts.timerHistogram);
this.stopTimer(charts.timerHistogram);
},
/**
* Record the start time for a timing-based histogram entry.
*
* @param String histogramId
* Histogram in which the data is to be stored.
*/
startTimer: function(histogramId) {
this._timers.set(histogramId, new Date());
},
/**
* Stop the timer and log elasped time for a timing-based histogram entry.
*
* @param String histogramId
* Histogram in which the data is to be stored.
*/
stopTimer: function(histogramId) {
let startTime = this._timers.get(histogramId);
if (startTime) {
let time = (new Date() - startTime) / 1000;
this.log(charts.timerHistogram, time);
this._timers.delete(charts.timerHistogram);
this.log(histogramId, time);
this._timers.delete(histogramId);
}
},
@ -258,11 +282,8 @@ Telemetry.prototype = {
},
destroy: function() {
for (let [histogram, time] of this._timers) {
time = (new Date() - time) / 1000;
this.log(histogram, time);
this._timers.delete(histogram);
for (let histogramId of this._timers.keys()) {
this.stopTimer(histogramId);
}
}
};

View File

@ -626,6 +626,8 @@ StyleSheetEditor.prototype = {
this.saveToFile();
};
bindings["Esc"] = false;
return bindings;
},

View File

@ -89,7 +89,7 @@ function CheckLockState() {
// ADB check
if (AppManager.selectedRuntime instanceof USBRuntime) {
let device = Devices.getByName(AppManager.selectedRuntime.id);
if (device.summonRoot) {
if (device && device.summonRoot) {
device.isRoot().then(isRoot => {
if (isRoot) {
adbCheckResult.textContent = sYes;

View File

@ -21,6 +21,7 @@ const ProjectEditor = require("projecteditor/projecteditor");
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {GetAvailableAddons} = require("devtools/webide/addons");
const {GetTemplatesJSON, GetAddonsJSON} = require("devtools/webide/remote-resources");
const Telemetry = require("devtools/shared/telemetry");
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
@ -47,6 +48,9 @@ window.addEventListener("unload", function onUnload() {
let UI = {
init: function() {
this._telemetry = new Telemetry();
this._telemetry.toolOpened("webide");
AppManager.init();
this.onMessage = this.onMessage.bind(this);
@ -98,6 +102,8 @@ let UI = {
AppManager.off("app-manager-update", this.appManagerUpdate);
AppManager.uninit();
window.removeEventListener("message", this.onMessage);
this.updateConnectionTelemetry();
this._telemetry.toolClosed("webide");
},
onfocus: function() {
@ -121,6 +127,7 @@ let UI = {
case "connection":
this.updateRuntimeButton();
this.updateCommands();
this.updateConnectionTelemetry();
break;
case "project":
this.updateTitle();
@ -218,12 +225,13 @@ let UI = {
},
busyWithProgressUntil: function(promise, operationDescription) {
this.busyUntil(promise, operationDescription);
let busy = this.busyUntil(promise, operationDescription);
let win = document.querySelector("window");
let progress = document.querySelector("#action-busy-determined");
progress.mode = "undetermined";
win.classList.add("busy-determined");
win.classList.remove("busy-undetermined");
return busy;
},
busyUntil: function(promise, operationDescription) {
@ -333,6 +341,7 @@ let UI = {
connectToRuntime: function(runtime) {
let name = runtime.getName();
let promise = AppManager.connectToRuntime(runtime);
promise.then(() => this.initConnectionTelemetry());
return this.busyUntil(promise, "connecting to runtime");
},
@ -346,6 +355,47 @@ let UI = {
}
},
_actionsToLog: new Set(),
/**
* For each new connection, track whether play and debug were ever used. Only
* one value is collected for each button, even if they are used multiple
* times during a connection.
*/
initConnectionTelemetry: function() {
this._actionsToLog.add("play");
this._actionsToLog.add("debug");
},
/**
* Action occurred. Log that it happened, and remove it from the loggable
* set.
*/
onAction: function(action) {
if (!this._actionsToLog.has(action)) {
return;
}
this.logActionState(action, true);
this._actionsToLog.delete(action);
},
/**
* Connection status changed or we are shutting down. Record any loggable
* actions as having not occurred.
*/
updateConnectionTelemetry: function() {
for (let action of this._actionsToLog.values()) {
this.logActionState(action, false);
}
this._actionsToLog.clear();
},
logActionState: function(action, state) {
let histogramId = "DEVTOOLS_WEBIDE_CONNECTION_" +
action.toUpperCase() + "_USED";
this._telemetry.log(histogramId, state);
},
/********** PROJECTS **********/
// Panel & button
@ -660,8 +710,7 @@ let UI = {
splitter.setAttribute("hidden", "true");
document.querySelector("#action-button-debug").removeAttribute("active");
},
}
};
let Cmds = {
quit: function() {
@ -909,15 +958,25 @@ let Cmds = {
},
play: function() {
let busy;
switch(AppManager.selectedProject.type) {
case "packaged":
return UI.busyWithProgressUntil(AppManager.installAndRunProject(), "installing and running app");
busy = UI.busyWithProgressUntil(AppManager.installAndRunProject(),
"installing and running app");
break;
case "hosted":
return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
busy = UI.busyUntil(AppManager.installAndRunProject(),
"installing and running app");
break;
case "runtimeApp":
return UI.busyUntil(AppManager.runRuntimeApp(), "running app");
busy = UI.busyUntil(AppManager.runRuntimeApp(), "running app");
break;
}
return promise.reject();
if (!busy) {
return promise.reject();
}
UI.onAction("play");
return busy;
},
stop: function() {
@ -925,6 +984,7 @@ let Cmds = {
},
toggleToolbox: function() {
UI.onAction("debug");
if (UI.toolboxIframe) {
UI.closeToolbox();
return promise.resolve();
@ -961,4 +1021,4 @@ let Cmds = {
showPrefs: function() {
UI.selectDeckPanel("prefs");
},
}
};

View File

@ -25,6 +25,7 @@ const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {USBRuntime, WiFiRuntime, SimulatorRuntime,
gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
const discovery = require("devtools/toolkit/discovery/discovery");
const Telemetry = require("devtools/shared/telemetry");
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
@ -66,6 +67,8 @@ exports.AppManager = AppManager = {
this.observe = this.observe.bind(this);
Services.prefs.addObserver(WIFI_SCANNING_PREF, this, false);
this._telemetry = new Telemetry();
},
uninit: function() {
@ -345,6 +348,25 @@ exports.AppManager = AppManager = {
}
}, deferred.reject);
// Record connection result in telemetry
let logResult = result => {
this._telemetry.log("DEVTOOLS_WEBIDE_CONNECTION_RESULT", result);
if (runtime.type) {
this._telemetry.log("DEVTOOLS_WEBIDE_" + runtime.type +
"_CONNECTION_RESULT", result);
}
};
deferred.promise.then(() => logResult(true), () => logResult(false));
// If successful, record connection time in telemetry
deferred.promise.then(() => {
const timerId = "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS";
this._telemetry.startTimer(timerId);
this.connection.once(Connection.Events.STATUS_CHANGED, () => {
this._telemetry.stopTimer(timerId);
});
});
return deferred.promise;
},

View File

@ -13,11 +13,21 @@ const promise = require("promise");
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
// These type strings are used for logging events to Telemetry
let RuntimeTypes = {
usb: "USB",
wifi: "WIFI",
simulator: "SIMULATOR",
remote: "REMOTE",
local: "LOCAL"
};
function USBRuntime(id) {
this.id = id;
}
USBRuntime.prototype = {
type: RuntimeTypes.usb,
connect: function(connection) {
let device = Devices.getByName(this.id);
if (!device) {
@ -59,6 +69,7 @@ function WiFiRuntime(deviceName) {
}
WiFiRuntime.prototype = {
type: RuntimeTypes.wifi,
connect: function(connection) {
let service = discovery.getRemoteService("devtools", this.deviceName);
if (!service) {
@ -82,6 +93,7 @@ function SimulatorRuntime(version) {
}
SimulatorRuntime.prototype = {
type: RuntimeTypes.simulator,
connect: function(connection) {
let port = ConnectionManager.getFreeTCPPort();
let simulator = Simulator.getByVersion(this.version);
@ -104,6 +116,7 @@ SimulatorRuntime.prototype = {
}
let gLocalRuntime = {
type: RuntimeTypes.local,
connect: function(connection) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
@ -120,6 +133,7 @@ let gLocalRuntime = {
}
let gRemoteRuntime = {
type: RuntimeTypes.remote,
connect: function(connection) {
let win = Services.wm.getMostRecentWindow("devtools:webide");
if (!win) {

View File

@ -31,3 +31,4 @@ support-files =
[test_manifestUpdate.html]
[test_addons.html]
[test_deviceinfo.html]
[test_telemetry.html]

View File

@ -81,8 +81,9 @@ function removeAllProjects() {
return Task.spawn(function* () {
yield AppProjects.load();
let projects = AppProjects.store.object.projects;
for (let i = 0; i < projects.length; i++) {
yield AppProjects.remove(projects[i].location);
// AppProjects.remove mutates the projects array in-place
while (projects.length > 0) {
yield AppProjects.remove(projects[0].location);
}
});
}
@ -96,6 +97,14 @@ function nextTick() {
return deferred.promise;
}
function waitForTime(time) {
let deferred = promise.defer();
setTimeout(() => {
deferred.resolve();
}, time);
return deferred.promise;
}
function documentIsLoaded(doc) {
let deferred = promise.defer();
if (doc.readyState == "complete") {

View File

@ -0,0 +1,242 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title></title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
<script type="application/javascript;version=1.8" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<script type="application/javascript;version=1.8">
const Telemetry = require("devtools/shared/telemetry");
const { USBRuntime, WiFiRuntime, SimulatorRuntime, gRemoteRuntime,
gLocalRuntime } = require("devtools/webide/runtimes");
// 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;
function patchTelemetry() {
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);
}
}
}
function resetTelemetry() {
Telemetry.prototype.log = Telemetry.prototype._oldlog;
delete Telemetry.prototype._oldlog;
delete Telemetry.prototype.telemetryInfo;
}
function cycleWebIDE() {
return Task.spawn(function*() {
let win = yield openWebIDE();
// Wait a bit, so we're open for a non-zero time
yield waitForTime(TOOL_DELAY);
yield closeWebIDE(win);
});
}
function addFakeRuntimes(win) {
// We use the real runtimes here (and switch out some functionality)
// so we can ensure that logging happens as it would in real use.
let usb = new USBRuntime("fakeUSB");
// Use local pipe instead
usb.connect = function(connection) {
ok(connection, win.AppManager.connection, "connection is valid");
connection.host = null; // force connectPipe
connection.connect();
return promise.resolve();
};
win.AppManager.runtimeList.usb.push(usb);
let wifi = new WiFiRuntime("fakeWiFi");
// Use local pipe instead
wifi.connect = function(connection) {
ok(connection, win.AppManager.connection, "connection is valid");
connection.host = null; // force connectPipe
connection.connect();
return promise.resolve();
};
win.AppManager.runtimeList.wifi.push(wifi);
let sim = new SimulatorRuntime("fakeSimulator");
// Use local pipe instead
sim.connect = function(connection) {
ok(connection, win.AppManager.connection, "connection is valid");
connection.host = null; // force connectPipe
connection.connect();
return promise.resolve();
};
sim.getName = function() {
return this.version;
};
win.AppManager.runtimeList.simulator.push(sim);
let remote = gRemoteRuntime;
// Use local pipe instead
remote.connect = function(connection) {
ok(connection, win.AppManager.connection, "connection is valid");
connection.host = null; // force connectPipe
connection.connect();
return promise.resolve();
};
let local = gLocalRuntime;
win.AppManager.runtimeList.custom = [gRemoteRuntime, gLocalRuntime];
win.AppManager.update("runtimelist");
}
function addTestApp(win) {
return Task.spawn(function*() {
let packagedAppLocation = getTestFilePath("app");
yield win.Cmds.importPackagedApp(packagedAppLocation);
});
}
function startConnection(win, type, index) {
let panelNode = win.document.querySelector("#runtime-panel");
let items = panelNode.querySelectorAll(".runtime-panel-item-" + type);
if (index === undefined) {
is(items.length, 1, "Found one runtime button");
}
let deferred = promise.defer();
win.AppManager.connection.once(
win.Connection.Events.CONNECTED,
() => deferred.resolve());
items[index || 0].click();
return deferred.promise;
}
function waitUntilConnected(win) {
return Task.spawn(function*() {
ok(win.document.querySelector("window").className, "busy", "UI is busy");
yield win.UI._busyPromise;
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
});
}
function connectToRuntime(win, type, index) {
return Task.spawn(function*() {
yield startConnection(win, type, index);
yield waitUntilConnected(win);
});
}
function checkResults() {
let result = Telemetry.prototype.telemetryInfo;
for (let [histId, value] of Iterator(result)) {
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
ok(value.length === 1 && !!value[0],
"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;
});
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");
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTION_RESULT") {
ok(value.length === 5, histId + " has 5 connection results");
let okay = value.every(function(element) {
return !!element;
});
ok(okay, "All " + histId + " connections succeeded");
} else if (histId.endsWith("CONNECTION_RESULT")) {
ok(value.length === 1 && !!value[0],
histId + " has 1 successful connection");
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS") {
ok(value.length === 5, histId + " has 5 connection results");
let okay = value.every(function(element) {
return element > 0;
});
ok(okay, "All " + histId + " connections have time > 0");
} else if (histId.endsWith("USED")) {
info(value.length);
ok(value.length === 5, histId + " has 5 connection actions");
let okay = value.every(function(element) {
return !element;
});
ok(okay, "All " + histId + " actions were skipped");
} else {
ok(false, "Unexpected " + histId + " was logged");
}
}
}
window.onload = function() {
SimpleTest.waitForExplicitFinish();
Task.spawn(function* () {
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
patchTelemetry();
// Cycle once, so we can test for multiple opens
yield cycleWebIDE();
let win = yield openWebIDE();
// Wait a bit, so we're open for a non-zero time
yield waitForTime(TOOL_DELAY);
addFakeRuntimes(win);
yield addTestApp(win);
// Each one should log a connection result and non-zero connection
// time
yield connectToRuntime(win, "usb");
yield waitForTime(TOOL_DELAY);
yield connectToRuntime(win, "wifi");
yield waitForTime(TOOL_DELAY);
yield connectToRuntime(win, "simulator");
yield waitForTime(TOOL_DELAY);
yield connectToRuntime(win, "custom", 0 /* remote */);
yield waitForTime(TOOL_DELAY);
yield connectToRuntime(win, "custom", 1 /* local */);
yield waitForTime(TOOL_DELAY);
yield closeWebIDE(win);
checkResults();
resetTelemetry();
DebuggerServer.destroy();
SimpleTest.finish();
});
}
</script>
</body>
</html>

View File

@ -183,7 +183,19 @@ this.ContentSearch = {
_onMessageManageEngines: function (msg, data) {
let browserWin = msg.target.ownerDocument.defaultView;
browserWin.BrowserSearch.searchBar.openManager(null);
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
getService(Components.interfaces.nsIWindowMediator);
let window = wm.getMostRecentWindow("Browser:SearchManager");
if (window) {
window.focus()
}
else {
browserWin.setTimeout(function () {
browserWin.openDialog("chrome://browser/content/search/engineManager.xul",
"_blank", "chrome,dialog,modal,centerscreen,resizable");
}, 0);
}
return Promise.resolve();
},

17
mach
View File

@ -92,6 +92,7 @@ if __name__ == '__main__':
def fork_interpose():
import imp
import os
import sys
orig_find_module = imp.find_module
def my_find_module(name, dirs):
if name == 'mach':
@ -100,7 +101,23 @@ if __name__ == '__main__':
return (f, path, ('', 'r', imp.PY_SOURCE))
return orig_find_module(name, dirs)
# Don't allow writing bytecode file for mach module.
orig_load_module = imp.load_module
def my_load_module(name, file, path, description):
# multiprocess.forking invokes imp.load_module manually and
# hard-codes the name __parents_main__ as the module name.
if name == '__parents_main__':
old_bytecode = sys.dont_write_bytecode
sys.dont_write_bytecode = True
try:
return orig_load_module(name, file, path, description)
finally:
sys.dont_write_bytecode = old_bytecode
return orig_load_module(name, file, path, description)
imp.find_module = my_find_module
imp.load_module = my_load_module
from multiprocessing.forking import main; main()
def my_get_command_line():

View File

@ -7,21 +7,37 @@ Components.utils.import("resource://gre/modules/ctypes.jsm");
Components.utils.import("resource://gre/modules/JNI.jsm");
add_task(function test_JNI() {
let iconSize = -1;
let jni = null;
var jenv = null;
try {
jni = new JNI();
let cls = jni.findClass("org/mozilla/gecko/GeckoAppShell");
let method = jni.getStaticMethodID(cls, "getPreferredIconSize", "()I");
iconSize = jni.callStaticIntMethod(cls, method);
jenv = JNI.GetForThread();
// Test a simple static method.
var geckoAppShell = JNI.LoadClass(jenv, "org.mozilla.gecko.GeckoAppShell", {
static_methods: [
{ name: "getPreferredIconSize", sig: "()I" }
],
});
let iconSize = -1;
iconSize = geckoAppShell.getPreferredIconSize();
do_check_neq(iconSize, -1);
// Test GeckoNetworkManager methods that are accessed by PaymentsUI.js.
// The return values can vary, so we can't test for equivalence, but we
// can ensure that the method calls return values of the correct type.
let jGeckoNetworkManager = JNI.LoadClass(jenv, "org/mozilla/gecko/GeckoNetworkManager", {
static_methods: [
{ name: "getMNC", sig: "()I" },
{ name: "getMCC", sig: "()I" },
],
});
do_check_eq(typeof jGeckoNetworkManager.getMNC(), "number");
do_check_eq(typeof jGeckoNetworkManager.getMCC(), "number");
} finally {
if (jni != null) {
jni.close();
if (jenv) {
JNI.UnloadClasses(jenv);
}
}
do_check_neq(iconSize, -1);
});
run_next_test();

View File

@ -3172,11 +3172,14 @@ Tab.prototype = {
this.id = aParams.tabID;
stub = true;
} else {
let jni = new JNI();
let cls = jni.findClass("org/mozilla/gecko/Tabs");
let method = jni.getStaticMethodID(cls, "getNextTabId", "()I");
this.id = jni.callStaticIntMethod(cls, method);
jni.close();
let jenv = JNI.GetForThread();
let jTabs = JNI.LoadClass(jenv, "org.mozilla.gecko.Tabs", {
static_methods: [
{ name: "getNextTabId", sig: "()I" }
],
});
this.id = jTabs.getNextTabId();
JNI.UnloadClasses(jenv);
}
this.desktopMode = ("desktopMode" in aParams) ? aParams.desktopMode : false;

View File

@ -88,8 +88,6 @@
// We probably tried to reload a URI that caused an exception to
// occur; e.g. a nonexistent file.
}
buttonEl.disabled = true;
}
function initPage()

View File

@ -142,11 +142,15 @@ PaymentUI.prototype = {
},
_getNetworkInfo: function(type) {
let jni = new JNI();
let cls = jni.findClass("org/mozilla/gecko/GeckoNetworkManager");
let method = jni.getStaticMethodID(cls, "get" + type.toUpperCase(), "()I");
let val = jni.callStaticIntMethod(cls, method);
jni.close();
let jenv = JNI.GetForThread();
let jMethodName = "get" + type.toUpperCase();
let jGeckoNetworkManager = JNI.LoadClass(jenv, "org/mozilla/gecko/GeckoNetworkManager", {
static_methods: [
{ name: jMethodName, sig: "()I" },
],
});
let val = jGeckoNetworkManager[jMethodName]();
JNI.UnloadClasses(jenv);
if (val < 0)
return null;

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,7 @@ user_pref("security.warn_viewing_mixed", false);
user_pref("app.update.enabled", false);
user_pref("app.update.staging.enabled", false);
// Make sure GMPInstallManager won't hit the network.
user_pref("media.gmp-manager.url", "https://%(server)s/dummy.xml");
user_pref("media.gmp-manager.url.override", "http://%(server)s/dummy.xml");
user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
user_pref("dom.w3c_touch_events.enabled", 1);
user_pref("dom.undo_manager.enabled", true);

View File

@ -1342,3 +1342,13 @@ try {
prefs.setBoolPref("geo.provider.testing", true);
}
} catch (e) { }
// We need to avoid hitting the network with certain components.
try {
if (runningInParent) {
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
prefs.setCharPref("media.gmp-manager.url.override", "http://%(server)s/dummy.xml");
}
} catch (e) { }

View File

@ -5877,6 +5877,11 @@
"kind": "boolean",
"description": "How many times has the devtool's Developer Toolbar been opened via the toolbox button?"
},
"DEVTOOLS_WEBIDE_OPENED_BOOLEAN": {
"expires_in_version": "never",
"kind": "boolean",
"description": "How many times has the DevTools WebIDE been opened?"
},
"DEVTOOLS_CUSTOM_OPENED_BOOLEAN": {
"expires_in_version": "never",
"kind": "boolean",
@ -5992,6 +5997,11 @@
"kind": "flag",
"description": "How many users have opened the devtool's Developer Toolbar been opened via the toolbox button?"
},
"DEVTOOLS_WEBIDE_OPENED_PER_USER_FLAG": {
"expires_in_version": "never",
"kind": "flag",
"description": "How many users have opened the DevTools WebIDE?"
},
"DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG": {
"expires_in_version": "never",
"kind": "flag",
@ -6151,6 +6161,13 @@
"n_buckets": 100,
"description": "How long has the developer toolbar been active (seconds)"
},
"DEVTOOLS_WEBIDE_TIME_ACTIVE_SECONDS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "10000000",
"n_buckets": 100,
"description": "How long has WebIDE been active (seconds)"
},
"DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS": {
"expires_in_version": "never",
"kind": "exponential",
@ -6158,6 +6175,53 @@
"n_buckets": 100,
"description": "How long has a custom developer tool been active (seconds)"
},
"DEVTOOLS_WEBIDE_CONNECTION_RESULT": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Did WebIDE runtime connection succeed?"
},
"DEVTOOLS_WEBIDE_USB_CONNECTION_RESULT": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Did WebIDE USB runtime connection succeed?"
},
"DEVTOOLS_WEBIDE_WIFI_CONNECTION_RESULT": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Did WebIDE WiFi runtime connection succeed?"
},
"DEVTOOLS_WEBIDE_SIMULATOR_CONNECTION_RESULT": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Did WebIDE simulator runtime connection succeed?"
},
"DEVTOOLS_WEBIDE_REMOTE_CONNECTION_RESULT": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Did WebIDE remote runtime connection succeed?"
},
"DEVTOOLS_WEBIDE_LOCAL_CONNECTION_RESULT": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Did WebIDE local runtime connection succeed?"
},
"DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "10000000",
"n_buckets": 100,
"description": "How long was WebIDE connected to a runtime (seconds)?"
},
"DEVTOOLS_WEBIDE_CONNECTION_PLAY_USED": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Was WebIDE's play button used during this runtime connection?"
},
"DEVTOOLS_WEBIDE_CONNECTION_DEBUG_USED": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Was WebIDE's debug button used during this runtime connection?"
},
"BROWSER_IS_USER_DEFAULT": {
"expires_in_version": "never",
"kind": "boolean",

View File

@ -18,13 +18,14 @@ Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Task.jsm");
//Cu.import("resource://gre/modules/GMPInstallManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
"resource://gre/modules/GMPInstallManager.jsm");
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const SEC_IN_A_DAY = 24 * 60 * 60;
const OPENH264_PLUGIN_ID = "gmp-gmpopenh264";
const OPENH264_PREF_BRANCH = "media." + OPENH264_PLUGIN_ID + ".";
const OPENH264_PREF_ENABLED = "enabled";
@ -39,6 +40,8 @@ const OPENH264_PREF_LOGGING_DUMP = OPENH264_PREF_LOGGING + ".dump"; // media.gmp
const OPENH264_HOMEPAGE_URL = "http://www.openh264.org/";
const OPENH264_OPTIONS_URL = "chrome://mozapps/content/extensions/openH264Prefs.xul";
const GMP_PREF_LASTCHECK = "media.gmp-manager.lastCheck";
XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
() => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
XPCOMUtils.defineLazyGetter(this, "prefs",
@ -172,9 +175,25 @@ let OpenH264Wrapper = {
AddonManagerPrivate.callNoUpdateListeners(this, aListener);
if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED ||
this._updateTask !== null) {
return;
if (aReason === AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
if (!AddonManager.shouldAutoUpdate(this)) {
this._log.trace("findUpdates() - no autoupdate");
return Promise.resolve(false);
}
let secSinceLastCheck = Date.now() / 1000 - Preferences.get(GMP_PREF_LASTCHECK, 0);
if (secSinceLastCheck <= SEC_IN_A_DAY) {
this._log.trace("findUpdates() - last check was less then a day ago");
return Promise.resolve(false);
}
} else if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED) {
this._log.trace("findUpdates() - unsupported reason");
return Promise.resolve(false);
}
if (this._updateTask !== null) {
this._log.trace("findUpdates() - update task already running");
return this._updateTask;
}
this._updateTask = Task.spawn(function* OpenH264Provider_updateTask() {
@ -184,7 +203,10 @@ let OpenH264Wrapper = {
let addons = yield installManager.checkForAddons();
let openH264 = addons.find(addon => addon.isOpenH264);
if (openH264 && !openH264.isInstalled) {
this._log.trace("findUpdates() - found update, installing");
yield installManager.installAddon(openH264);
} else {
this._log.trace("findUpdates() - no updates");
}
this._log.info("findUpdates() - updateTask succeeded");
} catch (e) {
@ -192,8 +214,11 @@ let OpenH264Wrapper = {
throw e;
} finally {
this._updateTask = null;
return true;
}
}.bind(this));
return this._updateTask;
},
get pluginMimeTypes() { return []; },

View File

@ -18,6 +18,8 @@ const OPENH264_PREF_AUTOUPDATE = OPENH264_PREF_BRANCH + "autoupdate";
const PREF_LOGGING = OPENH264_PREF_BRANCH + "provider.logging";
const PREF_LOGGING_LEVEL = PREF_LOGGING + ".level";
const PREF_LOGGING_DUMP = PREF_LOGGING + ".dump";
const GMP_PREF_LASTCHECK = "media.gmp-manager.lastCheck";
const GMP_PREF_LOG = "media.gmp-manager.log";
const TEST_DATE = new Date(2013, 0, 1, 12);
@ -88,6 +90,7 @@ function openDetailsView(aId) {
add_task(function* initializeState() {
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(GMP_PREF_LOG, true);
gManagerWindow = yield open_manager();
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
@ -102,6 +105,8 @@ add_task(function* initializeState() {
Services.prefs.clearUserPref(OPENH264_PREF_AUTOUPDATE);
Services.prefs.clearUserPref(PREF_LOGGING_DUMP);
Services.prefs.clearUserPref(PREF_LOGGING_LEVEL);
Services.prefs.clearUserPref(GMP_PREF_LOG);
Services.prefs.clearUserPref(GMP_PREF_LASTCHECK);
});
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
@ -230,6 +235,8 @@ add_task(function* testPreferencesButton() {
});
add_task(function* testUpdateButton() {
Services.prefs.clearUserPref(GMP_PREF_LASTCHECK);
yield gCategoryUtilities.openType("plugin");
let doc = gManagerWindow.document;
let item = get_addon_element(gManagerWindow, OPENH264_PLUGIN_ID);
@ -243,9 +250,15 @@ add_task(function* testUpdateButton() {
gInstalledAddonId = "";
gInstallDeferred = Promise.defer();
let button = doc.getAnonymousElementByAttribute(item, "anonid", "preferences-btn");
EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
let deferred = Promise.defer();
wait_for_view_load(gManagerWindow, deferred.resolve);
yield deferred.promise;
let button = doc.getElementById("detail-findUpdates-btn");
Assert.ok(button != null, "Got detail-findUpdates-btn");
button.click();
EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
yield gInstallDeferred.promise;
Assert.equal(gInstalledAddonId, OPENH264_PLUGIN_ID);

View File

@ -15,23 +15,48 @@ const OPENH264_PREF_AUTOUPDATE = OPENH264_PREF_BRANCH + "autoupdate";
const PREF_LOGGING = OPENH264_PREF_BRANCH + "provider.logging";
const PREF_LOGGING_LEVEL = PREF_LOGGING + ".level";
const PREF_LOGGING_DUMP = PREF_LOGGING + ".dump";
const GMP_PREF_LASTCHECK = "media.gmp-manager.lastCheck";
const GMP_PREF_LOG = "media.gmp-manager.log";
const SEC_IN_A_DAY = 24 * 60 * 60;
XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
() => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
let gProfileDir = null;
let MockGMPAddon = Object.freeze({
id: OPENH264_PLUGIN_ID,
isOpenH264: true,
isInstalled: false,
});
let gInstalledAddonId = "";
function MockGMPInstallManager() {
}
MockGMPInstallManager.prototype = {
checkForAddons: () => Promise.resolve([MockGMPAddon]),
installAddon: addon => {
gInstalledAddonId = addon.id;
return Promise.resolve();
},
};
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
startupManager();
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(GMP_PREF_LOG, true);
run_next_test();
}
add_task(function* test_notInstalled() {
Services.prefs.setCharPref(OPENH264_PREF_PATH, "");
Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, false);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
Assert.equal(addons.length, 1);
@ -218,3 +243,35 @@ add_task(function* test_pluginRegistration() {
Assert.equal(addedPath, file.path);
Assert.equal(removedPath, null);
});
add_task(function* test_periodicUpdate() {
let OpenH264Scope = Cu.import("resource://gre/modules/addons/OpenH264Provider.jsm");
Object.defineProperty(OpenH264Scope, "GMPInstallManager", {
value: MockGMPInstallManager,
writable: true,
enumerable: true,
configurable: true
});
Services.prefs.clearUserPref(OPENH264_PREF_AUTOUPDATE);
let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
let prefs = Services.prefs;
Assert.equal(addons.length, 1);
let addon = addons[0];
addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
Services.prefs.setIntPref(GMP_PREF_LASTCHECK, 0);
let result = yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
Assert.strictEqual(result, false);
addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
Services.prefs.setIntPref(GMP_PREF_LASTCHECK, Date.now() / 1000 - 60);
result = yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
Assert.strictEqual(result, false);
Services.prefs.setIntPref(GMP_PREF_LASTCHECK, Date.now() / 1000 - 2 * SEC_IN_A_DAY);
gInstalledAddonId = "";
result = yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
Assert.strictEqual(result, true);
Assert.equal(gInstalledAddonId, OPENH264_PLUGIN_ID);
});