From 88effeb4670608662cb869c1294cbfedc664aa6a Mon Sep 17 00:00:00 2001 From: Nevin Chen Date: Fri, 2 Dec 2016 11:12:24 +0800 Subject: [PATCH 01/29] Bug 1320605 - The shrunk height just nee to cover the text itself. r=sebastian MozReview-Commit-ID: BdoLEOKjvxw --HG-- extra : rebase_source : 98f2be697c16d170d974ded868ad99dd1d2946c8 --- .../org/mozilla/gecko/widget/FadedMultiColorTextView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/FadedMultiColorTextView.java b/mobile/android/base/java/org/mozilla/gecko/widget/FadedMultiColorTextView.java index 319bd4778c45..3728f0c09ba8 100644 --- a/mobile/android/base/java/org/mozilla/gecko/widget/FadedMultiColorTextView.java +++ b/mobile/android/base/java/org/mozilla/gecko/widget/FadedMultiColorTextView.java @@ -57,8 +57,9 @@ public class FadedMultiColorTextView extends FadedTextView { final float center = getHeight() / 2; // Shrink height of gradient to prevent it overlaying parent view border. - final float top = center - getTextSize() + 2; - final float bottom = center + getTextSize() - 2; + // The shrunk size just nee to cover the text itself. + final float top = center - getTextSize() / 2; + final float bottom = center + getTextSize() / 2; canvas.drawRect(left, top, right, bottom, fadePaint); } From 6d092ba5ea0fdfa11ff0be94cd63ae7876622c0b Mon Sep 17 00:00:00 2001 From: Ting-Yu Chou Date: Thu, 8 Dec 2016 22:04:08 +0800 Subject: [PATCH 02/29] Bug 1322464 - Fix an invalid assignment in assertion expression. r=froydnj MozReview-Commit-ID: EFCSnfxnreT --HG-- extra : rebase_source : 92636764a12eda78315a9a62d81709913078fd41 --- js/src/threading/windows/ConditionVariable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/threading/windows/ConditionVariable.cpp b/js/src/threading/windows/ConditionVariable.cpp index 868c351416be..5bc80bfbb030 100644 --- a/js/src/threading/windows/ConditionVariable.cpp +++ b/js/src/threading/windows/ConditionVariable.cpp @@ -268,7 +268,7 @@ public: // A race condition similar to the case described above could // occur, so waitResult could be WAIT_TIMEOUT, but that doesn't // matter for the actions that need to be taken. - MOZ_RELEASE_ASSERT(waitResult = WAIT_OBJECT_0 + 1 || + MOZ_RELEASE_ASSERT(waitResult == WAIT_OBJECT_0 + 1 || waitResult == WAIT_TIMEOUT); BOOL success = ResetEvent(wakeAllEvent_); From 16809923e0ab20ec599166aeed90c87d78486e24 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Fri, 2 Dec 2016 10:32:04 -0700 Subject: [PATCH 03/29] Bug 1321839 - make devtools/shared eslint-clean; r=jryans MozReview-Commit-ID: AGSdhrT4Z3D --HG-- extra : rebase_source : b053c5a54d39e6669598714e5f572c0d3dc0ebb4 --- .eslintignore | 8 - devtools/shared/DevToolsUtils.js | 160 ++++++++++--------- devtools/shared/ThreadSafeDevToolsUtils.js | 12 +- devtools/shared/builtin-modules.js | 86 +++++----- devtools/shared/content-observer.js | 2 +- devtools/shared/deprecated-sync-thenables.js | 2 + devtools/shared/dom-node-constants.js | 2 + devtools/shared/flags.js | 9 +- devtools/shared/path.js | 5 +- devtools/shared/protocol.js | 88 ++++++---- devtools/shared/system.js | 31 ++-- 11 files changed, 223 insertions(+), 182 deletions(-) diff --git a/.eslintignore b/.eslintignore index e67eceb534da..c327eb4e0146 100644 --- a/.eslintignore +++ b/.eslintignore @@ -125,14 +125,6 @@ devtools/server/tests/browser/** !devtools/server/tests/browser/browser_webextension_inspected_window.js devtools/server/tests/mochitest/** devtools/server/tests/unit/** -devtools/shared/*.js -!devtools/shared/async-storage.js -!devtools/shared/async-utils.js -!devtools/shared/defer.js -!devtools/shared/event-emitter.js -!devtools/shared/indentation.js -!devtools/shared/loader-plugin-raw.jsm -!devtools/shared/task.js devtools/shared/apps/** devtools/shared/client/** devtools/shared/discovery/** diff --git a/devtools/shared/DevToolsUtils.js b/devtools/shared/DevToolsUtils.js index d44184fd6b8e..c59d790b6bc6 100644 --- a/devtools/shared/DevToolsUtils.js +++ b/devtools/shared/DevToolsUtils.js @@ -2,11 +2,13 @@ * 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/. */ +/* globals setImmediate, rpc */ + "use strict"; /* General utilities used throughout devtools. */ -var { Ci, Cu, Cc, components } = require("chrome"); +var { Ci, Cu, components } = require("chrome"); var Services = require("Services"); var promise = require("promise"); var defer = require("devtools/shared/defer"); @@ -16,6 +18,10 @@ var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/s loader.lazyRequireGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm", true); +// Using this name lets the eslint plugin know about lazy defines in +// this file. +var DevToolsUtils = exports; + // Re-export the thread-safe utils. const ThreadSafeDevToolsUtils = require("./ThreadSafeDevToolsUtils.js"); for (let key of Object.keys(ThreadSafeDevToolsUtils)) { @@ -25,9 +31,9 @@ for (let key of Object.keys(ThreadSafeDevToolsUtils)) { /** * Waits for the next tick in the event loop to execute a callback. */ -exports.executeSoon = function executeSoon(aFn) { +exports.executeSoon = function (fn) { if (isWorker) { - setImmediate(aFn); + setImmediate(fn); } else { let executor; // Only enable async stack reporting when DEBUG_JS_MODULES is set @@ -35,10 +41,10 @@ exports.executeSoon = function executeSoon(aFn) { if (AppConstants.DEBUG_JS_MODULES || flags.testing) { let stack = getStack(); executor = () => { - callFunctionWithAsyncStack(aFn, stack, "DevToolsUtils.executeSoon"); + callFunctionWithAsyncStack(fn, stack, "DevToolsUtils.executeSoon"); }; } else { - executor = aFn; + executor = fn; } Services.tm.mainThread.dispatch({ run: exports.makeInfallible(executor) @@ -52,7 +58,7 @@ exports.executeSoon = function executeSoon(aFn) { * @return Promise * A promise that is resolved after the next tick in the event loop. */ -exports.waitForTick = function waitForTick() { +exports.waitForTick = function () { let deferred = defer(); exports.executeSoon(deferred.resolve); return deferred.promise; @@ -61,14 +67,14 @@ exports.waitForTick = function waitForTick() { /** * Waits for the specified amount of time to pass. * - * @param number aDelay + * @param number delay * The amount of time to wait, in milliseconds. * @return Promise * A promise that is resolved after the specified amount of time passes. */ -exports.waitForTime = function waitForTime(aDelay) { +exports.waitForTime = function (delay) { let deferred = defer(); - setTimeout(deferred.resolve, aDelay); + setTimeout(deferred.resolve, delay); return deferred.promise; }; @@ -77,21 +83,21 @@ exports.waitForTime = function waitForTime(aDelay) { * very large arrays by yielding to the browser and continuing execution on the * next tick. * - * @param Array aArray + * @param Array array * The array being iterated over. - * @param Function aFn + * @param Function fn * The function called on each item in the array. If a promise is * returned by this function, iterating over the array will be paused * until the respective promise is resolved. * @returns Promise * A promise that is resolved once the whole array has been iterated - * over, and all promises returned by the aFn callback are resolved. + * over, and all promises returned by the fn callback are resolved. */ -exports.yieldingEach = function yieldingEach(aArray, aFn) { +exports.yieldingEach = function (array, fn) { const deferred = defer(); let i = 0; - let len = aArray.length; + let len = array.length; let outstanding = [deferred.promise]; (function loop() { @@ -108,7 +114,7 @@ exports.yieldingEach = function yieldingEach(aArray, aFn) { } try { - outstanding.push(aFn(aArray[i], i++)); + outstanding.push(fn(array[i], i++)); } catch (e) { deferred.reject(e); return; @@ -126,22 +132,21 @@ exports.yieldingEach = function yieldingEach(aArray, aFn) { * allows the lazy getter to be defined on a prototype and work correctly with * instances. * - * @param Object aObject + * @param Object object * The prototype object to define the lazy getter on. - * @param String aKey + * @param String key * The key to define the lazy getter on. - * @param Function aCallback + * @param Function callback * The callback that will be called to determine the value. Will be * called with the |this| value of the current instance. */ -exports.defineLazyPrototypeGetter = -function defineLazyPrototypeGetter(aObject, aKey, aCallback) { - Object.defineProperty(aObject, aKey, { +exports.defineLazyPrototypeGetter = function (object, key, callback) { + Object.defineProperty(object, key, { configurable: true, get: function () { - const value = aCallback.call(this); + const value = callback.call(this); - Object.defineProperty(this, aKey, { + Object.defineProperty(this, key, { configurable: true, writable: true, value: value @@ -156,17 +161,17 @@ function defineLazyPrototypeGetter(aObject, aKey, aCallback) { * Safely get the property value from a Debugger.Object for a given key. Walks * the prototype chain until the property is found. * - * @param Debugger.Object aObject + * @param Debugger.Object object * The Debugger.Object to get the value from. - * @param String aKey + * @param String key * The key to look for. * @return Any */ -exports.getProperty = function getProperty(aObj, aKey) { - let root = aObj; +exports.getProperty = function (object, key) { + let root = object; try { do { - const desc = aObj.getOwnPropertyDescriptor(aKey); + const desc = object.getOwnPropertyDescriptor(key); if (desc) { if ("value" in desc) { return desc.value; @@ -174,8 +179,8 @@ exports.getProperty = function getProperty(aObj, aKey) { // Call the getter if it's safe. return exports.hasSafeGetter(desc) ? desc.get.call(root).return : undefined; } - aObj = aObj.proto; - } while (aObj); + object = object.proto; + } while (object); } catch (e) { // If anything goes wrong report the error and return undefined. exports.reportException("getProperty", e); @@ -186,16 +191,16 @@ exports.getProperty = function getProperty(aObj, aKey) { /** * Determines if a descriptor has a getter which doesn't call into JavaScript. * - * @param Object aDesc + * @param Object desc * The descriptor to check for a safe getter. * @return Boolean * Whether a safe getter was found. */ -exports.hasSafeGetter = function hasSafeGetter(aDesc) { +exports.hasSafeGetter = function (desc) { // Scripted functions that are CCWs will not appear scripted until after // unwrapping. try { - let fn = aDesc.get.unwrap(); + let fn = desc.get.unwrap(); return fn && fn.callable && fn.class == "Function" && fn.script === undefined; } catch (e) { // Avoid exception 'Object in compartment marked as invisible to Debugger' @@ -210,32 +215,34 @@ exports.hasSafeGetter = function hasSafeGetter(aDesc) { * * See bugs 945920 and 946752 for discussion. * - * @type Object aObj + * @type Object obj * The object to check. * @return Boolean - * True if it is safe to read properties from aObj, or false otherwise. + * True if it is safe to read properties from obj, or false otherwise. */ -exports.isSafeJSObject = function isSafeJSObject(aObj) { +exports.isSafeJSObject = function (obj) { // If we are running on a worker thread, Cu is not available. In this case, // we always return false, just to be on the safe side. if (isWorker) { return false; } - if (Cu.getGlobalForObject(aObj) == + if (Cu.getGlobalForObject(obj) == Cu.getGlobalForObject(exports.isSafeJSObject)) { - return true; // aObj is not a cross-compartment wrapper. + // obj is not a cross-compartment wrapper. + return true; } - let principal = Cu.getObjectPrincipal(aObj); + let principal = Cu.getObjectPrincipal(obj); if (Services.scriptSecurityManager.isSystemPrincipal(principal)) { - return true; // allow chrome objects + // allow chrome objects + return true; } - return Cu.isXrayWrapper(aObj); + return Cu.isXrayWrapper(obj); }; -exports.dumpn = function dumpn(str) { +exports.dumpn = function (str) { if (flags.wantLogging) { dump("DBG-SERVER: " + str + "\n"); } @@ -253,26 +260,27 @@ exports.dumpv = function (msg) { /** * Defines a getter on a specified object that will be created upon first use. * - * @param aObject + * @param object * The object to define the lazy getter on. - * @param aName - * The name of the getter to define on aObject. - * @param aLambda + * @param name + * The name of the getter to define on object. + * @param lambda * A function that returns what the getter should return. This will * only ever be called once. */ -exports.defineLazyGetter = function defineLazyGetter(aObject, aName, aLambda) { - Object.defineProperty(aObject, aName, { +exports.defineLazyGetter = function (object, name, lambda) { + Object.defineProperty(object, name, { get: function () { - delete aObject[aName]; - return aObject[aName] = aLambda.apply(aObject); + delete object[name]; + object[name] = lambda.apply(object); + return object[name]; }, configurable: true, enumerable: true }); }; -exports.defineLazyGetter(this, "AppConstants", () => { +DevToolsUtils.defineLazyGetter(this, "AppConstants", () => { if (isWorker) { return {}; } @@ -329,47 +337,44 @@ Object.defineProperty(exports, "assert", { * Defines a getter on a specified object for a module. The module will not * be imported until first use. * - * @param aObject + * @param object * The object to define the lazy getter on. - * @param aName - * The name of the getter to define on aObject for the module. - * @param aResource + * @param name + * The name of the getter to define on object for the module. + * @param resource * The URL used to obtain the module. - * @param aSymbol + * @param symbol * The name of the symbol exported by the module. - * This parameter is optional and defaults to aName. + * This parameter is optional and defaults to name. */ -exports.defineLazyModuleGetter = function defineLazyModuleGetter(aObject, aName, - aResource, - aSymbol) -{ - this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() { - var temp = {}; - Cu.import(aResource, temp); - return temp[aSymbol || aName]; +exports.defineLazyModuleGetter = function (object, name, resource, symbol) { + this.defineLazyGetter(object, name, function () { + let temp = {}; + Cu.import(resource, temp); + return temp[symbol || name]; }); }; -exports.defineLazyGetter(this, "NetUtil", () => { +DevToolsUtils.defineLazyGetter(this, "NetUtil", () => { return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil; }); -exports.defineLazyGetter(this, "OS", () => { +DevToolsUtils.defineLazyGetter(this, "OS", () => { return Cu.import("resource://gre/modules/osfile.jsm", {}).OS; }); -exports.defineLazyGetter(this, "TextDecoder", () => { +DevToolsUtils.defineLazyGetter(this, "TextDecoder", () => { return Cu.import("resource://gre/modules/osfile.jsm", {}).TextDecoder; }); -exports.defineLazyGetter(this, "NetworkHelper", () => { +DevToolsUtils.defineLazyGetter(this, "NetworkHelper", () => { return require("devtools/shared/webconsole/network-helper"); }); /** * Performs a request to load the desired URL and returns a promise. * - * @param aURL String + * @param urlIn String * The URL we will request. * @param aOptions Object * An object with the following optional properties: @@ -396,14 +401,14 @@ exports.defineLazyGetter(this, "NetworkHelper", () => { * without relying on caching when we can (not for eval, etc.): * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/ */ -function mainThreadFetch(aURL, aOptions = { loadFromCache: true, +function mainThreadFetch(urlIn, aOptions = { loadFromCache: true, policy: Ci.nsIContentPolicy.TYPE_OTHER, window: null, charset: null, principal: null, cacheKey: null }) { // Create a channel. - let url = aURL.split(" -> ").pop(); + let url = urlIn.split(" -> ").pop(); let channel; try { channel = newChannelForURL(url, aOptions); @@ -526,7 +531,7 @@ function mainThreadFetch(aURL, aOptions = { loadFromCache: true, * @return {nsIChannel} - The newly created channel. Throws on failure. */ function newChannelForURL(url, { policy, window, principal }) { - var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL; + let securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL; let uri; try { @@ -570,15 +575,15 @@ function newChannelForURL(url, { policy, window, principal }) { // Fetch is defined differently depending on whether we are on the main thread // or a worker thread. -if (!this.isWorker) { - exports.fetch = mainThreadFetch; -} else { +if (this.isWorker) { // Services is not available in worker threads, nor is there any other way // to fetch a URL. We need to enlist the help from the main thread here, by // issuing an rpc request, to fetch the URL on our behalf. exports.fetch = function (url, options) { return rpc("fetch", url, options); }; +} else { + exports.fetch = mainThreadFetch; } /** @@ -633,7 +638,7 @@ errorOnFlag(exports, "wantVerbose"); // Calls the property with the given `name` on the given `object`, where // `name` is a string, and `object` a Debugger.Object instance. -/// +// // This function uses only the Debugger.Object API to call the property. It // avoids the use of unsafeDeference. This is useful for example in workers, // where unsafeDereference will return an opaque security wrapper to the @@ -668,5 +673,4 @@ function callPropertyOnObject(object, name) { return result.return; } - exports.callPropertyOnObject = callPropertyOnObject; diff --git a/devtools/shared/ThreadSafeDevToolsUtils.js b/devtools/shared/ThreadSafeDevToolsUtils.js index 55a98cf35e00..3255425dbba1 100644 --- a/devtools/shared/ThreadSafeDevToolsUtils.js +++ b/devtools/shared/ThreadSafeDevToolsUtils.js @@ -96,7 +96,7 @@ exports.reportException = function reportException(who, exception) { * don't have a way to get at them from JavaScript at the moment.) */ exports.makeInfallible = function (handler, name = handler.name) { - return function (/* arguments */) { + return function () { try { return handler.apply(this, arguments); } catch (ex) { @@ -128,7 +128,9 @@ exports.safeErrorString = function (error) { errorString += "\nStack: " + stack; } } - } catch (ee) { } + } catch (ee) { + // Ignore. + } // Append additional line and column number information to the output, // since it might not be part of the stringified error. @@ -138,7 +140,9 @@ exports.safeErrorString = function (error) { return errorString; } - } catch (ee) { } + } catch (ee) { + // Ignore. + } // We failed to find a good error description, so do the next best thing. return Object.prototype.toString.call(error); @@ -294,7 +298,7 @@ exports.settleAll = values => { if (!countdown) { resolve(resolutionValues); - return deferred.promise; + return; } function checkForCompletion() { diff --git a/devtools/shared/builtin-modules.js b/devtools/shared/builtin-modules.js index 0d5eae7c7bb5..2af9f0f8b36e 100644 --- a/devtools/shared/builtin-modules.js +++ b/devtools/shared/builtin-modules.js @@ -20,32 +20,31 @@ const jsmScope = Cu.import("resource://gre/modules/Services.jsm", {}); const { Services } = jsmScope; // Steal various globals only available in JSM scope (and not Sandbox one) const { PromiseDebugging, ChromeUtils, ThreadSafeChromeUtils, HeapSnapshot, - atob, btoa, Iterator } = jsmScope; + atob, btoa } = jsmScope; const { URL } = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(), {wantGlobalProperties: ["URL"]}); /** * Defines a getter on a specified object that will be created upon first use. * - * @param aObject + * @param object * The object to define the lazy getter on. - * @param aName - * The name of the getter to define on aObject. - * @param aLambda + * @param name + * The name of the getter to define on object. + * @param lambda * A function that returns what the getter should return. This will * only ever be called once. */ -function defineLazyGetter(aObject, aName, aLambda) -{ - Object.defineProperty(aObject, aName, { +function defineLazyGetter(object, name, lambda) { + Object.defineProperty(object, name, { get: function () { // Redefine this accessor property as a data property. - // Delete it first, to rule out "too much recursion" in case aObject is + // Delete it first, to rule out "too much recursion" in case object is // a proxy whose defineProperty handler might unwittingly trigger this // getter again. - delete aObject[aName]; - let value = aLambda.apply(aObject); - Object.defineProperty(aObject, aName, { + delete object[name]; + let value = lambda.apply(object); + Object.defineProperty(object, name, { value, writable: true, configurable: true, @@ -62,19 +61,18 @@ function defineLazyGetter(aObject, aName, aLambda) * Defines a getter on a specified object for a service. The service will not * be obtained until first use. * - * @param aObject + * @param object * The object to define the lazy getter on. - * @param aName - * The name of the getter to define on aObject for the service. - * @param aContract + * @param name + * The name of the getter to define on object for the service. + * @param contract * The contract used to obtain the service. - * @param aInterfaceName + * @param interfaceName * The name of the interface to query the service to. */ -function defineLazyServiceGetter(aObject, aName, aContract, aInterfaceName) -{ - defineLazyGetter(aObject, aName, function XPCU_serviceLambda() { - return Cc[aContract].getService(Ci[aInterfaceName]); +function defineLazyServiceGetter(object, name, contract, interfaceName) { + defineLazyGetter(object, name, function () { + return Cc[contract].getService(Ci[interfaceName]); }); } @@ -84,48 +82,47 @@ function defineLazyServiceGetter(aObject, aName, aContract, aInterfaceName) * teardown code (e.g. to register/unregister to services) and accepts * a proxy object which acts on behalf of the module until it is imported. * - * @param aObject + * @param object * The object to define the lazy getter on. - * @param aName - * The name of the getter to define on aObject for the module. - * @param aResource + * @param name + * The name of the getter to define on object for the module. + * @param resource * The URL used to obtain the module. - * @param aSymbol + * @param symbol * The name of the symbol exported by the module. - * This parameter is optional and defaults to aName. - * @param aPreLambda + * This parameter is optional and defaults to name. + * @param preLambda * A function that is executed when the proxy is set up. * This will only ever be called once. - * @param aPostLambda + * @param postLambda * A function that is executed when the module has been imported to * run optional teardown procedures on the proxy object. * This will only ever be called once. - * @param aProxy + * @param proxy * An object which acts on behalf of the module to be imported until * the module has been imported. */ -function defineLazyModuleGetter(aObject, aName, aResource, aSymbol, - aPreLambda, aPostLambda, aProxy) -{ - let proxy = aProxy || {}; +function defineLazyModuleGetter(object, name, resource, symbol, + preLambda, postLambda, proxy) { + proxy = proxy || {}; - if (typeof (aPreLambda) === "function") { - aPreLambda.apply(proxy); + if (typeof (preLambda) === "function") { + preLambda.apply(proxy); } - defineLazyGetter(aObject, aName, function XPCU_moduleLambda() { - var temp = {}; + defineLazyGetter(object, name, function () { + let temp = {}; try { - Cu.import(aResource, temp); + Cu.import(resource, temp); - if (typeof (aPostLambda) === "function") { - aPostLambda.apply(proxy); + if (typeof (postLambda) === "function") { + postLambda.apply(proxy); } } catch (ex) { - Cu.reportError("Failed to load module " + aResource + "."); + Cu.reportError("Failed to load module " + resource + "."); throw ex; } - return temp[aSymbol || aName]; + return temp[symbol || name]; }); } @@ -224,7 +221,8 @@ exports.globals = { lazyImporter: defineLazyModuleGetter, lazyServiceGetter: defineLazyServiceGetter, lazyRequireGetter: lazyRequireGetter, - id: null // Defined by Loader.jsm + // Defined by Loader.jsm + id: null }, // Let new XMLHttpRequest do the right thing. diff --git a/devtools/shared/content-observer.js b/devtools/shared/content-observer.js index 6bef2ebd56ab..e7d83da8ccca 100644 --- a/devtools/shared/content-observer.js +++ b/devtools/shared/content-observer.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -const {Cc, Ci, Cu, Cr} = require("chrome"); +const {Ci} = require("chrome"); const Services = require("Services"); const events = require("sdk/event/core"); diff --git a/devtools/shared/deprecated-sync-thenables.js b/devtools/shared/deprecated-sync-thenables.js index 52bf671cd8c4..4490e702d2e2 100644 --- a/devtools/shared/deprecated-sync-thenables.js +++ b/devtools/shared/deprecated-sync-thenables.js @@ -6,6 +6,8 @@ * THIS MODULE IS DEPRECATED. IMPORT "Promise.jsm" INSTEAD. */ +/* eslint-disable */ + "use strict"; this.Promise = {}; diff --git a/devtools/shared/dom-node-constants.js b/devtools/shared/dom-node-constants.js index 0b86ae49b588..9051c9dd6da5 100644 --- a/devtools/shared/dom-node-constants.js +++ b/devtools/shared/dom-node-constants.js @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +/* globals define */ + // Make this available to both AMD and CJS environments define(function (require, exports, module) { module.exports = { diff --git a/devtools/shared/flags.js b/devtools/shared/flags.js index 9903e6f50c29..4815723d0cbc 100644 --- a/devtools/shared/flags.js +++ b/devtools/shared/flags.js @@ -1,3 +1,4 @@ +"use strict"; /* * Create a writable property by tracking it with a private variable. @@ -7,8 +8,12 @@ function makeWritableFlag(exports, name) { let flag = false; Object.defineProperty(exports, name, { - get: function () { return flag; }, - set: function (state) { flag = state; } + get: function () { + return flag; + }, + set: function (state) { + flag = state; + } }); } diff --git a/devtools/shared/path.js b/devtools/shared/path.js index 94083679a4ab..f8aa0be13769 100644 --- a/devtools/shared/path.js +++ b/devtools/shared/path.js @@ -13,9 +13,8 @@ exports.joinURI = (initialPath, ...paths) => { try { url = new URL(initialPath); - } - catch (e) { - return; + } catch (e) { + return null; } for (let path of paths) { diff --git a/devtools/shared/protocol.js b/devtools/shared/protocol.js index 848c615f3daf..daf6674b1bd1 100644 --- a/devtools/shared/protocol.js +++ b/devtools/shared/protocol.js @@ -65,7 +65,9 @@ types.getType = function (type) { // If already registered, we're done here. let reg = registeredTypes.get(type); - if (reg) return reg; + if (reg) { + return reg; + } // New type, see if it's a collection/lifetime type: let sep = type.indexOf(":"); @@ -146,7 +148,9 @@ types.addType = function (name, typeObject = {}, options = {}) { } let type = object.merge({ - toString() { return "[protocol type:" + name + "]";}, + toString() { + return "[protocol type:" + name + "]"; + }, name: name, primitive: !(typeObject.read || typeObject.write), read: identityWrite, @@ -170,7 +174,9 @@ types.removeType = function (name) { type.name = "DEFUNCT:" + name; type.category = "defunct"; type.primitive = false; - type.read = type.write = function () { throw new Error("Using defunct type: " + name); }; + type.read = type.write = function () { + throw new Error("Using defunct type: " + name); + }; registeredTypes.delete(name); }; @@ -274,7 +280,7 @@ types.addActorType = function (name) { let actorID = typeof (v) === "string" ? v : v.actor; let front = ctx.conn.getActor(actorID); if (!front) { - front = new type.frontClass(ctx.conn); + front = new type.frontClass(ctx.conn); // eslint-disable-line new-cap front.actorID = actorID; ctx.marshallPool().manage(front); } @@ -353,7 +359,8 @@ types.addNullableType = function (subtype) { types.addActorDetail = function (name, actorType, detail) { actorType = types.getType(actorType); if (!actorType._actor) { - throw Error("Details only apply to actor types, tried to add detail '" + detail + "'' to " + actorType.name + "\n"); + throw Error(`Details only apply to actor types, tried to add detail '${detail}' ` + + `to ${actorType.name}`); } return types.addType(name, { _actor: true, @@ -402,7 +409,8 @@ types.removeLifetime = function (name) { types.addLifetimeType = function (lifetime, subtype) { subtype = types.getType(subtype); if (!subtype._actor) { - throw Error("Lifetimes only apply to actor types, tried to apply lifetime '" + lifetime + "'' to " + subtype.name); + throw Error(`Lifetimes only apply to actor types, tried to apply ` + + `lifetime '${lifetime}' to ${subtype.name}`); } let prop = registeredLifetimes.get(lifetime); return types.addType(lifetime + ":" + subtype.name, { @@ -580,7 +588,6 @@ function findPlaceholders(template, constructor, path = [], placeholders = []) { return placeholders; } - function describeTemplate(template) { return JSON.parse(JSON.stringify(template, (key, value) => { if (value.describe) { @@ -644,7 +651,9 @@ var Request = Class({ return fnArgs; }, - describe: function () { return describeTemplate(this.template); } + describe: function () { + return describeTemplate(this.template); + } }); /** @@ -701,7 +710,9 @@ var Response = Class({ return this.retVal.read(v, ctx); }, - describe: function () { return describeTemplate(this.template); } + describe: function () { + return describeTemplate(this.template); + } }); /** @@ -734,13 +745,17 @@ var Pool = Class({ /** * Return the parent pool for this client. */ - parent: function () { return this.conn.poolFor(this.actorID); }, + parent: function () { + return this.conn.poolFor(this.actorID); + }, /** * Override this if you want actors returned by this actor * to belong to a different actor by default. */ - marshallPool: function () { return this; }, + marshallPool: function () { + return this; + }, /** * Pool is the base class for all actors, even leaf nodes. @@ -749,7 +764,9 @@ var Pool = Class({ */ __poolMap: null, get _poolMap() { - if (this.__poolMap) return this.__poolMap; + if (this.__poolMap) { + return this.__poolMap; + } this.__poolMap = new Map(); this.conn.addActorPool(this); return this.__poolMap; @@ -883,7 +900,9 @@ var Actor = Class({ } }, - toString: function () { return "[Actor " + this.typeName + "/" + this.actorID + "]"; }, + toString: function () { + return "[Actor " + this.typeName + "/" + this.actorID + "]"; + }, _sendEvent: function (name, ...args) { if (!this._actorSpec.events.has(name)) { @@ -950,8 +969,12 @@ exports.Actor = Actor; */ exports.method = function (fn, spec = {}) { fn._methodSpec = Object.freeze(spec); - if (spec.request) Object.freeze(spec.request); - if (spec.response) Object.freeze(spec.response); + if (spec.request) { + Object.freeze(spec.request); + } + if (spec.response) { + Object.freeze(spec.response); + } return fn; }; @@ -986,7 +1009,8 @@ var generateActorSpec = function (actorDesc) { let methodSpec = desc.value._methodSpec; let spec = {}; spec.name = methodSpec.name || name; - spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined)); + spec.request = Request(object.merge({type: spec.name}, + methodSpec.request || undefined)); spec.response = Response(methodSpec.response || undefined); spec.release = methodSpec.release; spec.oneway = methodSpec.oneway; @@ -1002,7 +1026,8 @@ var generateActorSpec = function (actorDesc) { let spec = {}; spec.name = methodSpec.name || name; - spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined)); + spec.request = Request(object.merge({type: spec.name}, + methodSpec.request || undefined)); spec.response = Response(methodSpec.response || undefined); spec.release = methodSpec.release; spec.oneway = methodSpec.oneway; @@ -1056,7 +1081,7 @@ var generateRequestHandlers = function (actorSpec, actorProto) { let ret = this[spec.name].apply(this, args); - let sendReturn = (ret) => { + let sendReturn = (retToSend) => { if (spec.oneway) { // No need to send a response. return; @@ -1064,7 +1089,7 @@ var generateRequestHandlers = function (actorSpec, actorProto) { let response; try { - response = spec.response.write(ret, this); + response = spec.response.write(retToSend, this); } catch (ex) { console.error("Error writing response to: " + spec.name); throw ex; @@ -1202,9 +1227,13 @@ var Front = Class({ * @returns a promise that will resolve to the actorID this front * represents. */ - actor: function () { return promise.resolve(this.actorID); }, + actor: function () { + return promise.resolve(this.actorID); + }, - toString: function () { return "[Front for " + this.typeName + "/" + this.actorID + "]"; }, + toString: function () { + return "[Front for " + this.typeName + "/" + this.actorID + "]"; + }, /** * Update the actor from its representation. @@ -1265,7 +1294,9 @@ var Front = Class({ // Check to see if any of the preEvents returned a promise -- if so, // wait for their resolution before emitting. Otherwise, emit synchronously. if (results.some(result => result && typeof result.then === "function")) { - promise.all(results).then(() => events.emit.apply(null, [this, event.name].concat(args))); + promise.all(results).then(() => { + return events.emit.apply(null, [this, event.name].concat(args)); + }); return; } } @@ -1368,7 +1399,8 @@ var generateRequestMethods = function (actorSpec, frontProto) { if (name in frontProto) { let custom = frontProto[spec.name]._customFront; if (custom === undefined) { - throw Error("Existing method for " + spec.name + " not marked customFront while processing " + actorType.typeName + "."); + throw Error(`Existing method for ${spec.name} not marked customFront while ` + + ` processing ${actorSpec.typeName}.`); } // If the user doesn't need the impl don't generate it. if (!custom.impl) { @@ -1415,12 +1447,11 @@ var generateRequestMethods = function (actorSpec, frontProto) { } }); - // Process event specifications frontProto._clientSpec = {}; - let events = actorSpec.events; - if (events) { + let actorEvents = actorSpec.events; + if (actorEvents) { // This actor has events, scan the prototype for preEvent handlers... let preHandlers = new Map(); for (let name of Object.getOwnPropertyNames(frontProto)) { @@ -1430,7 +1461,7 @@ var generateRequestMethods = function (actorSpec, frontProto) { } if (desc.value._preEvent) { let preEvent = desc.value._preEvent; - if (!events.has(preEvent)) { + if (!actorEvents.has(preEvent)) { throw Error("preEvent for event that doesn't exist: " + preEvent); } let handlers = preHandlers.get(preEvent); @@ -1444,7 +1475,7 @@ var generateRequestMethods = function (actorSpec, frontProto) { frontProto._clientSpec.events = new Map(); - for (let [name, request] of events) { + for (let [name, request] of actorEvents) { frontProto._clientSpec.events.set(request.type, { name: name, request: request, @@ -1518,7 +1549,6 @@ exports.dumpActorSpec = function (type) { } } - JSON.stringify(ret); return ret; diff --git a/devtools/shared/system.js b/devtools/shared/system.js index ae46016e7e7c..22ad384e5c52 100644 --- a/devtools/shared/system.js +++ b/devtools/shared/system.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -const { Cc, Ci, Cu } = require("chrome"); +const { Cc, Ci } = require("chrome"); const { Task } = require("devtools/shared/task"); loader.lazyRequireGetter(this, "Services"); @@ -64,10 +64,10 @@ function* getSystemInfo() { hardware = yield exports.getSetting("deviceinfo.hardware"); version = yield exports.getSetting("deviceinfo.os"); } catch (e) { + // Ignore. } - } - // Not B2G - else { + } else { + // Not B2G os = appInfo.OS; version = appInfo.version; } @@ -80,7 +80,8 @@ function* getSystemInfo() { } if (win) { - let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + let utils = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); dpi = utils.displayDPI; useragent = win.navigator.userAgent; width = win.screen.width; @@ -131,7 +132,8 @@ function* getSystemInfo() { geckoversion: geckoVersion, // Locale used in this build - locale: Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"), + locale: Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"), /** * Information regarding the operating system. @@ -186,12 +188,13 @@ function getProfileLocation() { // In child processes, we cannot access the profile location. try { let profd = Services.dirsvc.get("ProfD", Ci.nsILocalFile); - let profservice = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService); - var profiles = profservice.profiles; + let profservice = Cc["@mozilla.org/toolkit/profile-service;1"] + .getService(Ci.nsIToolkitProfileService); + let profiles = profservice.profiles; while (profiles.hasMoreElements()) { let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile); if (profile.rootDir.path == profd.path) { - return profile = profile.name; + return profile.name; } } @@ -214,7 +217,8 @@ function getAppIniString(section, key) { return undefined; } - let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(Ci.nsIINIParserFactory).createINIParser(inifile); + let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"] + .getService(Ci.nsIINIParserFactory).createINIParser(inifile); try { return iniParser.getString(section, key); } catch (e) { @@ -316,13 +320,14 @@ function getSetting(name) { // settingsService fails in b2g child processes // TODO bug 1205797, make this work in child processes. try { - settingsService = Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService); + settingsService = Cc["@mozilla.org/settingsService;1"] + .getService(Ci.nsISettingsService); } catch (e) { return promise.reject(e); } - let req = settingsService.createLock().get(name, { - handle: (name, value) => deferred.resolve(value), + settingsService.createLock().get(name, { + handle: (_, value) => deferred.resolve(value), handleError: (error) => deferred.reject(error), }); } else { From c080ecbf37170076485ec8d59627d9c2bc8929e3 Mon Sep 17 00:00:00 2001 From: Mike Shal Date: Wed, 7 Dec 2016 15:45:39 -0500 Subject: [PATCH 04/29] Bug 1295375 - Drop universal Mac builds; r=coop,gps MozReview-Commit-ID: 1huYEjyxRA1 --HG-- rename : browser/config/mozconfigs/macosx-universal/beta => browser/config/mozconfigs/macosx64/beta rename : browser/config/mozconfigs/macosx-universal/common-opt => browser/config/mozconfigs/macosx64/common-opt rename : browser/config/mozconfigs/macosx-universal/release => browser/config/mozconfigs/macosx64/release extra : rebase_source : 5d0e4cdb54d605d8d8d5cf4e78142bd262682dd3 --- browser/config/mozconfigs/macosx64/beta | 15 ++++++++++++ browser/config/mozconfigs/macosx64/common-opt | 15 ++++++++++++ browser/config/mozconfigs/macosx64/nightly | 17 +++++++------ browser/config/mozconfigs/macosx64/release | 21 ++++++++++++++++ browser/config/mozconfigs/whitelist | 6 ++--- .../compare-mozconfigs-wrapper.py | 3 +-- taskcluster/ci/build/macosx.yml | 24 ------------------- .../configs/builds/branch_specifics.py | 4 ++-- .../builds/releng_base_mac_64_builds.py | 4 ++-- .../configs/merge_day/aurora_to_beta.py | 3 +-- .../configs/merge_day/central_to_aurora.py | 6 ++--- .../configs/single_locale/macosx64.py | 2 +- ...layback-temporary-encrypted-clear.html.ini | 8 +++---- .../clearkey-mp4-setmediakeys.html.ini | 2 -- .../relations/css-styling/color-1.html.ini | 4 ++-- 15 files changed, 78 insertions(+), 56 deletions(-) create mode 100644 browser/config/mozconfigs/macosx64/beta create mode 100644 browser/config/mozconfigs/macosx64/common-opt create mode 100644 browser/config/mozconfigs/macosx64/release diff --git a/browser/config/mozconfigs/macosx64/beta b/browser/config/mozconfigs/macosx64/beta new file mode 100644 index 000000000000..64cf69d866f7 --- /dev/null +++ b/browser/config/mozconfigs/macosx64/beta @@ -0,0 +1,15 @@ +MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1} + +if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then + MOZ_AUTOMATION_UPLOAD_SYMBOLS=1 + MOZ_AUTOMATION_UPDATE_PACKAGING=1 +fi + +. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt" + +ac_add_options --enable-official-branding +ac_add_options --enable-verify-mar + +. "$topsrcdir/build/mozconfig.rust" +. "$topsrcdir/build/mozconfig.common.override" +. "$topsrcdir/build/mozconfig.cache" diff --git a/browser/config/mozconfigs/macosx64/common-opt b/browser/config/mozconfigs/macosx64/common-opt new file mode 100644 index 000000000000..413fb6dcca6e --- /dev/null +++ b/browser/config/mozconfigs/macosx64/common-opt @@ -0,0 +1,15 @@ +# This file is sourced by the nightly, beta, and release mozconfigs. + +. $topsrcdir/build/macosx/mozconfig.common + +ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL} +ac_add_options --with-google-api-keyfile=/builds/gapi.data +ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key + +# Needed to enable breakpad in application.ini +export MOZILLA_OFFICIAL=1 + +export MOZ_TELEMETRY_REPORTING=1 + +# Package js shell. +export MOZ_PACKAGE_JSSHELL=1 diff --git a/browser/config/mozconfigs/macosx64/nightly b/browser/config/mozconfigs/macosx64/nightly index 12fec0474334..0a915da54660 100644 --- a/browser/config/mozconfigs/macosx64/nightly +++ b/browser/config/mozconfigs/macosx64/nightly @@ -1,20 +1,19 @@ -. $topsrcdir/build/macosx/mozconfig.common +. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt" +ac_add_options --disable-install-strip ac_add_options --enable-verify-mar +ac_add_options --enable-profiling +ac_add_options --enable-instruments -# Needed to enable breakpad in application.ini -export MOZILLA_OFFICIAL=1 - -# Enable Telemetry -export MOZ_TELEMETRY_REPORTING=1 +# Cross-compiled builds fail when dtrace is enabled +if test `uname -s` != Linux; then + ac_add_options --enable-dtrace +fi if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then ac_add_options --with-macbundlename-prefix=Firefox fi -# Package js shell. -export MOZ_PACKAGE_JSSHELL=1 - ac_add_options --with-branding=browser/branding/nightly . "$topsrcdir/build/mozconfig.rust" diff --git a/browser/config/mozconfigs/macosx64/release b/browser/config/mozconfigs/macosx64/release new file mode 100644 index 000000000000..562a16b50ebe --- /dev/null +++ b/browser/config/mozconfigs/macosx64/release @@ -0,0 +1,21 @@ +# This make file should be identical to the beta mozconfig, apart from the +# safeguard below +MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1} + +if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then + MOZ_AUTOMATION_UPLOAD_SYMBOLS=1 + MOZ_AUTOMATION_UPDATE_PACKAGING=1 +fi + +. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt" + +ac_add_options --enable-official-branding +ac_add_options --enable-verify-mar + +# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in +# defines.sh during the beta cycle +export BUILDING_RELEASE=1 + +. "$topsrcdir/build/mozconfig.rust" +. "$topsrcdir/build/mozconfig.common.override" +. "$topsrcdir/build/mozconfig.cache" diff --git a/browser/config/mozconfigs/whitelist b/browser/config/mozconfigs/whitelist index 445629ef57ef..b18c0d3e145d 100644 --- a/browser/config/mozconfigs/whitelist +++ b/browser/config/mozconfigs/whitelist @@ -5,7 +5,7 @@ whitelist = { 'nightly': {}, } -all_platforms = ['win64', 'win32', 'linux32', 'linux64', 'macosx-universal'] +all_platforms = ['win64', 'win32', 'linux32', 'linux64', 'macosx64'] for platform in all_platforms: whitelist['nightly'][platform] = [ @@ -15,7 +15,7 @@ for platform in all_platforms: 'mk_add_options CLIENT_PY_ARGS="--hg-options=\'--verbose --time\' --hgtool=../tools/buildfarm/utils/hgtool.py --skip-chatzilla --skip-comm --skip-inspector --tinderbox-print"' ] -for platform in ['linux32', 'linux64', 'macosx-universal']: +for platform in ['linux32', 'linux64', 'macosx64']: whitelist['nightly'][platform] += [ 'mk_add_options MOZ_MAKE_FLAGS="-j4"', ] @@ -42,7 +42,7 @@ whitelist['nightly']['linux64'] += [ '. "$topsrcdir/build/mozconfig.cache"', ] -whitelist['nightly']['macosx-universal'] += [ +whitelist['nightly']['macosx64'] += [ 'if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then', 'ac_add_options --with-macbundlename-prefix=Firefox', 'fi', diff --git a/build/compare-mozconfig/compare-mozconfigs-wrapper.py b/build/compare-mozconfig/compare-mozconfigs-wrapper.py index 1c281a93f9a6..f737afe15086 100644 --- a/build/compare-mozconfig/compare-mozconfigs-wrapper.py +++ b/build/compare-mozconfig/compare-mozconfigs-wrapper.py @@ -19,8 +19,7 @@ log = logging.getLogger(__name__) def determine_platform(): platform_mapping = {'WINNT': {'x86_64': 'win64', 'i686': 'win32'}, - 'Darwin': {'x86_64': 'macosx-universal', - 'i386':'macosx-universal'}, + 'Darwin': {'x86_64': 'macosx64'}, 'Linux': {'x86_64': 'linux64', 'i686': 'linux32'}} diff --git a/taskcluster/ci/build/macosx.yml b/taskcluster/ci/build/macosx.yml index 72e563cd0a5d..7e7fb79d420e 100644 --- a/taskcluster/ci/build/macosx.yml +++ b/taskcluster/ci/build/macosx.yml @@ -44,27 +44,3 @@ macosx64/opt: script: "mozharness/scripts/fx_desktop_build.py" secrets: true tooltool-downloads: internal - -macosx64-universal/opt: - description: "MacOS X Universal Cross-compile" - index: - product: firefox - job-name: macosx64-opt - treeherder: - platform: osx-10-7/opt - symbol: tc(Bu) - tier: 2 - worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64 - worker: - implementation: docker-worker - max-run-time: 36000 - run: - using: mozharness - actions: [get-secrets build generate-build-stats update] - config: - - builds/releng_base_mac_64_cross_builds.py - - balrog/production.py - script: "mozharness/scripts/fx_desktop_build.py" - secrets: true - custom-build-variant-cfg: cross-universal - tooltool-downloads: internal diff --git a/testing/mozharness/configs/builds/branch_specifics.py b/testing/mozharness/configs/builds/branch_specifics.py index e3a7f95224ad..6ee761ef98f1 100644 --- a/testing/mozharness/configs/builds/branch_specifics.py +++ b/testing/mozharness/configs/builds/branch_specifics.py @@ -52,7 +52,7 @@ config = { 'force_clobber': True, }, 'macosx64': { - 'src_mozconfig': 'browser/config/mozconfigs/macosx-universal/release', + 'src_mozconfig': 'browser/config/mozconfigs/macosx64/release', 'force_clobber': True, }, 'win32': { @@ -126,7 +126,7 @@ config = { 'force_clobber': True, }, 'macosx64': { - 'src_mozconfig': 'browser/config/mozconfigs/macosx-universal/beta', + 'src_mozconfig': 'browser/config/mozconfigs/macosx64/beta', 'force_clobber': True, }, 'win32': { diff --git a/testing/mozharness/configs/builds/releng_base_mac_64_builds.py b/testing/mozharness/configs/builds/releng_base_mac_64_builds.py index e6e338adae3c..aa0e761f157d 100644 --- a/testing/mozharness/configs/builds/releng_base_mac_64_builds.py +++ b/testing/mozharness/configs/builds/releng_base_mac_64_builds.py @@ -27,7 +27,7 @@ config = { 'enable_signing': True, 'enable_ccache': True, 'vcs_share_base': '/builds/hg-shared', - 'objdir': 'obj-firefox/x86_64', + 'objdir': 'obj-firefox', 'tooltool_script': ["/builds/tooltool.py"], 'tooltool_bootstrap': "setup.sh", 'enable_count_ctors': False, @@ -73,7 +73,7 @@ config = { 'MINIDUMP_STACKWALK': '%(abs_tools_dir)s/breakpad/osx64/minidump_stackwalk', 'MINIDUMP_SAVE_PATH': '%(base_work_dir)s/minidumps', }, - 'src_mozconfig': 'browser/config/mozconfigs/macosx-universal/nightly', + 'src_mozconfig': 'browser/config/mozconfigs/macosx64/nightly', 'tooltool_manifest_src': 'browser/config/tooltool-manifests/macosx64/releng.manifest', ######################################################################### } diff --git a/testing/mozharness/configs/merge_day/aurora_to_beta.py b/testing/mozharness/configs/merge_day/aurora_to_beta.py index dc1fc4c83e95..981fa0d09c58 100644 --- a/testing/mozharness/configs/merge_day/aurora_to_beta.py +++ b/testing/mozharness/configs/merge_day/aurora_to_beta.py @@ -36,10 +36,9 @@ config = { "browser/config/mozconfigs/linux64/l10n-mozconfig", "browser/config/mozconfigs/win32/l10n-mozconfig", "browser/config/mozconfigs/win64/l10n-mozconfig", - "browser/config/mozconfigs/macosx-universal/l10n-mozconfig", "browser/config/mozconfigs/macosx64/l10n-mozconfig"] ] + [ - ("browser/config/mozconfigs/macosx-universal/nightly", + ("browser/config/mozconfigs/macosx64/nightly", "ac_add_options --with-branding=browser/branding/aurora", "ac_add_options --with-branding=browser/branding/nightly"), ("browser/confvars.sh", diff --git a/testing/mozharness/configs/merge_day/central_to_aurora.py b/testing/mozharness/configs/merge_day/central_to_aurora.py index 36347f667f61..292015959766 100644 --- a/testing/mozharness/configs/merge_day/central_to_aurora.py +++ b/testing/mozharness/configs/merge_day/central_to_aurora.py @@ -41,7 +41,7 @@ config = { "mobile/android/config/mozconfigs/android-x86/nightly", "browser/config/mozconfigs/linux32/nightly", "browser/config/mozconfigs/linux64/nightly", - "browser/config/mozconfigs/macosx-universal/nightly", + "browser/config/mozconfigs/macosx64/nightly", "browser/config/mozconfigs/win32/nightly", "browser/config/mozconfigs/win64/nightly"] ] + [ @@ -52,10 +52,10 @@ config = { ("browser/confvars.sh", "MAR_CHANNEL_ID=firefox-mozilla-central", "MAR_CHANNEL_ID=firefox-mozilla-aurora"), - ("browser/config/mozconfigs/macosx-universal/nightly", + ("browser/config/mozconfigs/macosx64/nightly", "ac_add_options --with-branding=browser/branding/nightly", "ac_add_options --with-branding=browser/branding/aurora"), - ("browser/config/mozconfigs/macosx-universal/l10n-mozconfig", + ("browser/config/mozconfigs/macosx64/l10n-mozconfig", "ac_add_options --with-branding=browser/branding/nightly", "ac_add_options --with-branding=browser/branding/aurora"), ("browser/config/mozconfigs/whitelist", diff --git a/testing/mozharness/configs/single_locale/macosx64.py b/testing/mozharness/configs/single_locale/macosx64.py index c2ee4767469c..81e1684d3618 100644 --- a/testing/mozharness/configs/single_locale/macosx64.py +++ b/testing/mozharness/configs/single_locale/macosx64.py @@ -5,7 +5,7 @@ config = { "platform": "macosx64", "stage_product": "firefox", "update_platform": "Darwin_x86_64-gcc3", - "mozconfig": "%(branch)s/browser/config/mozconfigs/macosx-universal/l10n-mozconfig", + "mozconfig": "%(branch)s/browser/config/mozconfigs/macosx64/l10n-mozconfig", "bootstrap_env": { "SHELL": '/bin/bash', "MOZ_OBJDIR": "obj-l10n", diff --git a/testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-encrypted-clear.html.ini b/testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-encrypted-clear.html.ini index 47540e13cc37..82089b50f060 100644 --- a/testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-encrypted-clear.html.ini +++ b/testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-encrypted-clear.html.ini @@ -1,8 +1,8 @@ [clearkey-mp4-playback-temporary-encrypted-clear.html] type: testharness expected: - if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT - if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT + if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT + if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT [org.w3.clearkey, temporary, mp4, playback, single key, encrypted then clear content] @@ -11,8 +11,8 @@ if debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): FAIL if debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): FAIL if not debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): FAIL - if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT - if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT + if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT + if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT diff --git a/testing/web-platform/meta/encrypted-media/clearkey-mp4-setmediakeys.html.ini b/testing/web-platform/meta/encrypted-media/clearkey-mp4-setmediakeys.html.ini index ade28339cc33..fd1fa6114c00 100644 --- a/testing/web-platform/meta/encrypted-media/clearkey-mp4-setmediakeys.html.ini +++ b/testing/web-platform/meta/encrypted-media/clearkey-mp4-setmediakeys.html.ini @@ -1,4 +1,2 @@ [clearkey-mp4-setmediakeys.html] type: testharness - expected: - if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT diff --git a/testing/web-platform/meta/mathml/relations/css-styling/color-1.html.ini b/testing/web-platform/meta/mathml/relations/css-styling/color-1.html.ini index f10a6469f0b8..0f010d5e9330 100644 --- a/testing/web-platform/meta/mathml/relations/css-styling/color-1.html.ini +++ b/testing/web-platform/meta/mathml/relations/css-styling/color-1.html.ini @@ -1,8 +1,8 @@ [color-1.html] type: reftest expected: - if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): PASS - if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): PASS + if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS + if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS if not debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): PASS From 079365ac4129924b6d9d0c942f83f128d32aa414 Mon Sep 17 00:00:00 2001 From: Hammad Akhtar Date: Tue, 6 Dec 2016 12:03:36 +0530 Subject: [PATCH 05/29] Bug 1322193 - Verify taskgraph implementations against documentation, with proper regex. Updated doc verification for fake values of kinds, parameters etc., regex optimized r=Callek MozReview-Commit-ID: 56ZEJECbtK5 --HG-- extra : rebase_source : ad1efed52363e95040a9907c6387b4cd57bdaa49 --- taskcluster/taskgraph/decision.py | 11 ----- taskcluster/taskgraph/generator.py | 10 ++++ taskcluster/taskgraph/test/test_generator.py | 50 ++++++++++---------- taskcluster/taskgraph/util/verifydoc.py | 21 ++++++-- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/taskcluster/taskgraph/decision.py b/taskcluster/taskgraph/decision.py index 640378e77015..3d805dc16ac5 100644 --- a/taskcluster/taskgraph/decision.py +++ b/taskcluster/taskgraph/decision.py @@ -16,7 +16,6 @@ from .generator import TaskGraphGenerator from .create import create_tasks from .parameters import Parameters from .taskgraph import TaskGraph -from .util.verifydoc import verify_docs from taskgraph.util.templates import Templates from taskgraph.util.time import ( @@ -76,7 +75,6 @@ def taskgraph_decision(options): """ parameters = get_decision_parameters(options) - verify_parameters(parameters) # create a TaskGraphGenerator instance tgg = TaskGraphGenerator( root_dir=options['root'], @@ -187,12 +185,3 @@ def get_action_yml(parameters): "now": current_json_time() }) return templates.load('action.yml', action_parameters) - - -def verify_parameters(parameters): - parameters_dict = dict(**parameters) - verify_docs( - filename="parameters.rst", - identifiers=parameters_dict.keys(), - appearing_as="inline-literal" - ) diff --git a/taskcluster/taskgraph/generator.py b/taskcluster/taskgraph/generator.py index 3483a4f484a0..dcd35523ecdd 100644 --- a/taskcluster/taskgraph/generator.py +++ b/taskcluster/taskgraph/generator.py @@ -62,6 +62,8 @@ class TaskGraphGenerator(object): self.root_dir = root_dir self.parameters = parameters + self.verify_parameters(self.parameters) + filters = parameters.get('filters', []) # Always add legacy target tasks method until we deprecate that API. @@ -237,6 +239,14 @@ class TaskGraphGenerator(object): self._run_results[k] = v return self._run_results[name] + def verify_parameters(self, parameters): + parameters_dict = dict(**parameters) + verify_docs( + filename="parameters.rst", + identifiers=parameters_dict.keys(), + appearing_as="inline-literal" + ) + def verify_kinds(self, kinds): verify_docs( filename="kinds.rst", diff --git a/taskcluster/taskgraph/test/test_generator.py b/taskcluster/taskgraph/test/test_generator.py index bf1ecfdaeab6..4c86f57be4fe 100644 --- a/taskcluster/taskgraph/test/test_generator.py +++ b/taskcluster/taskgraph/test/test_generator.py @@ -22,7 +22,7 @@ class FakeTask(base.Task): def load_tasks(cls, kind, path, config, parameters, loaded_tasks): return [cls(kind=kind, label='{}-t-{}'.format(kind, i), - attributes={'tasknum': str(i)}, + attributes={'_tasknum': str(i)}, task={}, i=i) for i in range(3)] @@ -51,7 +51,7 @@ class FakeKind(Kind): class WithFakeKind(TaskGraphGenerator): def _load_kinds(self): - for kind_name, deps in self.parameters['kinds']: + for kind_name, deps in self.parameters['_kinds']: yield FakeKind( kind_name, '/fake', {'kind-dependencies': deps} if deps else {}) @@ -59,7 +59,7 @@ class WithFakeKind(TaskGraphGenerator): class TestGenerator(unittest.TestCase): - def maketgg(self, target_tasks=None, kinds=[('fake', [])]): + def maketgg(self, target_tasks=None, kinds=[('_fake', [])]): FakeKind.loaded_kinds = [] self.target_tasks = target_tasks or [] @@ -69,7 +69,7 @@ class TestGenerator(unittest.TestCase): target_tasks_mod._target_task_methods['test_method'] = target_tasks_method parameters = { - 'kinds': kinds, + '_kinds': kinds, 'target_tasks_method': 'test_method', } @@ -78,59 +78,59 @@ class TestGenerator(unittest.TestCase): def test_kind_ordering(self): "When task kinds depend on each other, they are loaded in postorder" self.tgg = self.maketgg(kinds=[ - ('fake3', ['fake2', 'fake1']), - ('fake2', ['fake1']), - ('fake1', []), + ('_fake3', ['_fake2', '_fake1']), + ('_fake2', ['_fake1']), + ('_fake1', []), ]) self.tgg._run_until('full_task_set') - self.assertEqual(FakeKind.loaded_kinds, ['fake1', 'fake2', 'fake3']) + self.assertEqual(FakeKind.loaded_kinds, ['_fake1', '_fake2', '_fake3']) def test_full_task_set(self): "The full_task_set property has all tasks" self.tgg = self.maketgg() self.assertEqual(self.tgg.full_task_set.graph, - graph.Graph({'fake-t-0', 'fake-t-1', 'fake-t-2'}, set())) + graph.Graph({'_fake-t-0', '_fake-t-1', '_fake-t-2'}, set())) self.assertEqual(sorted(self.tgg.full_task_set.tasks.keys()), - sorted(['fake-t-0', 'fake-t-1', 'fake-t-2'])) + sorted(['_fake-t-0', '_fake-t-1', '_fake-t-2'])) def test_full_task_graph(self): "The full_task_graph property has all tasks, and links" self.tgg = self.maketgg() self.assertEqual(self.tgg.full_task_graph.graph, - graph.Graph({'fake-t-0', 'fake-t-1', 'fake-t-2'}, + graph.Graph({'_fake-t-0', '_fake-t-1', '_fake-t-2'}, { - ('fake-t-1', 'fake-t-0', 'prev'), - ('fake-t-2', 'fake-t-1', 'prev'), + ('_fake-t-1', '_fake-t-0', 'prev'), + ('_fake-t-2', '_fake-t-1', 'prev'), })) self.assertEqual(sorted(self.tgg.full_task_graph.tasks.keys()), - sorted(['fake-t-0', 'fake-t-1', 'fake-t-2'])) + sorted(['_fake-t-0', '_fake-t-1', '_fake-t-2'])) def test_target_task_set(self): "The target_task_set property has the targeted tasks" - self.tgg = self.maketgg(['fake-t-1']) + self.tgg = self.maketgg(['_fake-t-1']) self.assertEqual(self.tgg.target_task_set.graph, - graph.Graph({'fake-t-1'}, set())) + graph.Graph({'_fake-t-1'}, set())) self.assertEqual(self.tgg.target_task_set.tasks.keys(), - ['fake-t-1']) + ['_fake-t-1']) def test_target_task_graph(self): "The target_task_graph property has the targeted tasks and deps" - self.tgg = self.maketgg(['fake-t-1']) + self.tgg = self.maketgg(['_fake-t-1']) self.assertEqual(self.tgg.target_task_graph.graph, - graph.Graph({'fake-t-0', 'fake-t-1'}, - {('fake-t-1', 'fake-t-0', 'prev')})) + graph.Graph({'_fake-t-0', '_fake-t-1'}, + {('_fake-t-1', '_fake-t-0', 'prev')})) self.assertEqual(sorted(self.tgg.target_task_graph.tasks.keys()), - sorted(['fake-t-0', 'fake-t-1'])) + sorted(['_fake-t-0', '_fake-t-1'])) def test_optimized_task_graph(self): "The optimized task graph contains task ids" - self.tgg = self.maketgg(['fake-t-2']) + self.tgg = self.maketgg(['_fake-t-2']) tid = self.tgg.label_to_taskid self.assertEqual( self.tgg.optimized_task_graph.graph, - graph.Graph({tid['fake-t-0'], tid['fake-t-1'], tid['fake-t-2']}, { - (tid['fake-t-1'], tid['fake-t-0'], 'prev'), - (tid['fake-t-2'], tid['fake-t-1'], 'prev'), + graph.Graph({tid['_fake-t-0'], tid['_fake-t-1'], tid['_fake-t-2']}, { + (tid['_fake-t-1'], tid['_fake-t-0'], 'prev'), + (tid['_fake-t-2'], tid['_fake-t-1'], 'prev'), })) if __name__ == '__main__': diff --git a/taskcluster/taskgraph/util/verifydoc.py b/taskcluster/taskgraph/util/verifydoc.py index 9a4c65ed4f12..17ca13eeb199 100644 --- a/taskcluster/taskgraph/util/verifydoc.py +++ b/taskcluster/taskgraph/util/verifydoc.py @@ -10,18 +10,31 @@ base_path = os.path.join(os.getcwd(), "taskcluster/docs/") def verify_docs(filename, identifiers, appearing_as): + + # We ignore identifiers starting with '_' for the sake of tests. + # Strings starting with "_" are ignored for doc verification + # hence they can be used for faking test values with open(os.path.join(base_path, filename)) as fileObject: doctext = "".join(fileObject.readlines()) if appearing_as == "inline-literal": - expression_list = ["``" + identifier + "``" for identifier in identifiers] + expression_list = [ + "``" + identifier + "``" + for identifier in identifiers + if not identifier.startswith("_") + ] elif appearing_as == "heading": - expression_list = [identifier + "\n[-+\n*]+|[.+\n*]+" for identifier in identifiers] + expression_list = [ + identifier + "\n(?:(?:(?:-+\n)+)|(?:(?:.+\n)+))" + for identifier in identifiers + if not identifier.startswith("_") + ] else: - raise Exception("appearing_as = {} not defined".format(appearing_as)) + raise Exception("appearing_as = `{}` not defined".format(appearing_as)) for expression, identifier in zip(expression_list, identifiers): match_group = re.search(expression, doctext) if not match_group: raise Exception( - "{}: {} missing from doc file: {}".format(appearing_as, identifier, filename) + "{}: `{}` missing from doc file: `{}`" + .format(appearing_as, identifier, filename) ) From 836d0a3e98b0de6462acf922767070281c928a4a Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Fri, 25 Nov 2016 23:00:11 +0100 Subject: [PATCH 06/29] Bug 1320101 - Default to 2Mbps when no max bitrate is set. r=bwc,jesup webrtc.org is in kbps and our constraints are in bps. 2000U is already in kbps however. MozReview-Commit-ID: 2Y4z2CIO6jE --HG-- extra : rebase_source : 5fac9331637a1c07ca2c897e444a6ca392e7b4f2 --- media/webrtc/signaling/src/media-conduit/VideoConduit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index be7a26e4ffee..aa3b98cd864e 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -1975,7 +1975,7 @@ WebrtcVideoConduit::CodecConfigToWebRTCCodec(const VideoCodecConfig* codecInfo, cinst.minBitrate = mMinBitrate ? mMinBitrate : 200; cinst.startBitrate = mStartBitrate ? mStartBitrate : 300; cinst.targetBitrate = cinst.startBitrate; - cinst.maxBitrate = MinIgnoreZero(2000U, codecInfo->mEncodingConstraints.maxBr)/1000; + cinst.maxBitrate = MinIgnoreZero(2000U, codecInfo->mEncodingConstraints.maxBr/1000); // not mNegotiatedMaxBitrate! cinst.maxBitrate is the max for the codec, which will be overridden cinst.maxBitrate = MinIgnoreZero(cinst.maxBitrate, mPrefMaxBitrate); From ccf57b4de16edd3951081a76f792dae64ca6258e Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Thu, 1 Dec 2016 16:52:41 +0100 Subject: [PATCH 07/29] Bug 1320101 - mNegotiatedMaxBitrate should be able to cap the max bitrate. r=bwc,jesup MozReview-Commit-ID: 9WRUa0SKuM2 --HG-- extra : rebase_source : c7da24a7a4a061eaadee62b23f98601b75d1fe2c --- media/webrtc/signaling/src/media-conduit/VideoConduit.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index aa3b98cd864e..68cf0137fab8 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -1187,9 +1187,8 @@ WebrtcVideoConduit::SelectBitrates(unsigned short width, // simulcast layers in this encoding! So sum(layers.maxBitrate) <= // mNegotiatedMaxBitrate // Note that out_max already has had mPrefMaxBitrate applied to it - if (mNegotiatedMaxBitrate != 0 && mNegotiatedMaxBitrate > out_max) { - out_max = mNegotiatedMaxBitrate; - } + out_max = MinIgnoreZero(mNegotiatedMaxBitrate, out_max); + MOZ_ASSERT(mPrefMaxBitrate == 0 || out_max <= mPrefMaxBitrate); } From 54dd33be824a58f69a687ff008eef85df1b07deb Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Sat, 26 Nov 2016 00:28:29 +0100 Subject: [PATCH 08/29] Bug 1320101 - Pass mLastFramerateTenths by copy. r=jesup Just for the cleanliness of not having to pass an atomic that we always read by reference. MozReview-Commit-ID: 60HzuTAENdQ --HG-- extra : rebase_source : 999b0847139107b16eb2daaf24e6195bd3ebcd7e --- media/webrtc/signaling/src/media-conduit/VideoConduit.cpp | 4 ++-- media/webrtc/signaling/src/media-conduit/VideoConduit.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index 68cf0137fab8..5d774281dc25 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -1133,7 +1133,7 @@ void WebrtcVideoConduit::SelectBitrates(unsigned short width, unsigned short height, unsigned int cap, - mozilla::Atomic& aLastFramerateTenths, + int32_t aLastFramerateTenths, unsigned int& out_min, unsigned int& out_start, unsigned int& out_max) @@ -1155,7 +1155,7 @@ WebrtcVideoConduit::SelectBitrates(unsigned short width, } } - // mLastFramerateTenths is an atomic, and scaled by *10 + // mLastFramerateTenths is scaled by *10 double framerate = std::min((aLastFramerateTenths/10.),60.0); MOZ_ASSERT(framerate > 0); // Now linear reduction/increase based on fps (max 60fps i.e. doubling) diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.h b/media/webrtc/signaling/src/media-conduit/VideoConduit.h index 668b4249df81..43a9b599862b 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h @@ -147,7 +147,7 @@ public: void SelectBitrates(unsigned short width, unsigned short height, unsigned int cap, - mozilla::Atomic& aLastFramerateTenths, + int32_t aLastFramerateTenths, unsigned int& out_min, unsigned int& out_start, unsigned int& out_max); From 25fe54f71c8008fe6e86f0d664e96ee9cbd7ddce Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Wed, 7 Dec 2016 08:38:13 -1000 Subject: [PATCH 09/29] Bug 1320101 - Differentiate between b=TIAS and simulcast stream max-br. r=bwc,jesup MozReview-Commit-ID: HPnOvwtawwV --HG-- extra : rebase_source : 9133afc61b48d25f466c0e4a65a743f26eba48c5 --- media/webrtc/signaling/src/jsep/JsepTrack.cpp | 5 +++-- media/webrtc/signaling/src/jsep/JsepTrack.h | 10 ++++++++++ media/webrtc/signaling/src/jsep/JsepTrackEncoding.h | 10 ---------- media/webrtc/signaling/src/media-conduit/CodecConfig.h | 2 ++ .../signaling/src/media-conduit/VideoConduit.cpp | 6 ++++-- .../src/peerconnection/MediaPipelineFactory.cpp | 3 +++ 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.cpp b/media/webrtc/signaling/src/jsep/JsepTrack.cpp index 1b045d8ec7e6..8f3fbebf28c2 100644 --- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp +++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp @@ -261,6 +261,9 @@ JsepTrack::CreateEncodings( const std::vector& negotiatedCodecs, JsepTrackNegotiatedDetails* negotiatedDetails) { + negotiatedDetails->mTias = remote.GetBandwidth("TIAS"); + // TODO add support for b=AS if TIAS is not set (bug 976521) + std::vector rids; GetRids(remote, sdp::kRecv, &rids); // Get rids we will send NegotiateRids(rids, &mJsEncodeConstraints); @@ -294,8 +297,6 @@ JsepTrack::CreateEncodings( encoding->mConstraints = jsConstraints.constraints; } } - - encoding->UpdateMaxBitrate(remote); } } diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.h b/media/webrtc/signaling/src/jsep/JsepTrack.h index 5aa37404fff3..692f313a005d 100644 --- a/media/webrtc/signaling/src/jsep/JsepTrack.h +++ b/media/webrtc/signaling/src/jsep/JsepTrack.h @@ -28,6 +28,10 @@ namespace mozilla { class JsepTrackNegotiatedDetails { public: + JsepTrackNegotiatedDetails() : + mTias(0) + {} + size_t GetEncodingCount() const { @@ -56,12 +60,18 @@ public: return mUniquePayloadTypes; } + uint32_t GetTias() const + { + return mTias; + } + private: friend class JsepTrack; std::map mExtmap; std::vector mUniquePayloadTypes; PtrVector mEncodings; + uint32_t mTias; // bits per second }; class JsepTrack diff --git a/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h index b59b672e4293..078bd796f3fc 100644 --- a/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h +++ b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h @@ -39,16 +39,6 @@ public: return false; } - void UpdateMaxBitrate(const SdpMediaSection& remote) - { - uint32_t tias = remote.GetBandwidth("TIAS"); - // select minimum of the two which is not zero - mConstraints.maxBr = std::min(tias ? tias : mConstraints.maxBr, - mConstraints.maxBr ? mConstraints.maxBr : - tias); - // TODO add support for b=AS if TIAS is not set (bug 976521) - } - EncodingConstraints mConstraints; std::string mRid; diff --git a/media/webrtc/signaling/src/media-conduit/CodecConfig.h b/media/webrtc/signaling/src/media-conduit/CodecConfig.h index 308c97948166..153229fa4dd0 100755 --- a/media/webrtc/signaling/src/media-conduit/CodecConfig.h +++ b/media/webrtc/signaling/src/media-conduit/CodecConfig.h @@ -90,6 +90,7 @@ public: bool mRembFbSet; bool mFECFbSet; + uint32_t mTias; EncodingConstraints mEncodingConstraints; struct SimulcastEncoding { std::string rid; @@ -110,6 +111,7 @@ public: mType(type), mName(name), mFECFbSet(false), + mTias(0), mEncodingConstraints(constraints), mProfile(0x42), mConstraints(0xE0), diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index 5d774281dc25..3d2e0d919d6c 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -732,6 +732,10 @@ WebrtcVideoConduit::ConfigureSendMediaCodec(const VideoCodecConfig* codecConfig) CSFLogError(logTag, "%s Codec Mismatch ", __FUNCTION__); return kMediaConduitInvalidSendCodec; } + + // So we can comply with b=TIAS/b=AS/maxbr=X when input resolution changes + mNegotiatedMaxBitrate = codecConfig->mTias / 1000; + // Note: only for overriding parameters from GetCodec()! CodecConfigToWebRTCCodec(codecConfig, video_codec); if (mSendingWidth != 0) { @@ -751,8 +755,6 @@ WebrtcVideoConduit::ConfigureSendMediaCodec(const VideoCodecConfig* codecConfig) mSendingHeight = 0; mSendingFramerate = video_codec.maxFramerate; } - // So we can comply with b=TIAS/b=AS/maxbr=X when input resolution changes - mNegotiatedMaxBitrate = MinIgnoreZero(mPrefMaxBitrate, video_codec.maxBitrate); video_codec.mode = mCodecMode; diff --git a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp index f4bdbefbaaa8..c651e2a2f77d 100644 --- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp +++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp @@ -167,6 +167,8 @@ NegotiatedDetailsToVideoCodecConfigs(const JsepTrackNegotiatedDetails& aDetails, return NS_ERROR_INVALID_ARG; } + config->mTias = aDetails.GetTias(); + for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) { const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i)); if (jsepEncoding.HasFormat(codec->mDefaultPt)) { @@ -176,6 +178,7 @@ NegotiatedDetailsToVideoCodecConfigs(const JsepTrackNegotiatedDetails& aDetails, config->mSimulcastEncodings.push_back(encoding); } } + aConfigs->values.push_back(config); } From ebeb98b7b8e344e5fd76e1d94dd5bd9e9a592c17 Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Thu, 8 Dec 2016 10:11:18 -1000 Subject: [PATCH 10/29] Bug 1320101 - Support renegotiations with changes to TIAS and simulcast params. r=bwc,jesup MozReview-Commit-ID: GNWRNnwX9pk --HG-- extra : rebase_source : 5f17c8a478934efd708dd3b9a5a7880f124e6861 --- .../src/common/EncodingConstraints.h | 12 +++ .../signaling/src/media-conduit/CodecConfig.h | 14 ++++ .../src/media-conduit/VideoConduit.cpp | 74 ++++++++++++++----- 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/media/webrtc/signaling/src/common/EncodingConstraints.h b/media/webrtc/signaling/src/common/EncodingConstraints.h index efba7c51c61a..9c6ee25c5e68 100644 --- a/media/webrtc/signaling/src/common/EncodingConstraints.h +++ b/media/webrtc/signaling/src/common/EncodingConstraints.h @@ -42,6 +42,18 @@ public: scaleDownBy == constraints.scaleDownBy; } + /** + * This returns true if the constraints affecting resolution are equal. + */ + bool ResolutionEquals(const EncodingConstraints& constraints) const + { + return + maxWidth == constraints.maxWidth && + maxHeight == constraints.maxHeight && + maxFs == constraints.maxFs && + scaleDownBy == constraints.scaleDownBy; + } + uint32_t maxWidth; uint32_t maxHeight; uint32_t maxFps; diff --git a/media/webrtc/signaling/src/media-conduit/CodecConfig.h b/media/webrtc/signaling/src/media-conduit/CodecConfig.h index 153229fa4dd0..53ce811ac8c6 100755 --- a/media/webrtc/signaling/src/media-conduit/CodecConfig.h +++ b/media/webrtc/signaling/src/media-conduit/CodecConfig.h @@ -127,6 +127,20 @@ public: } } + bool ResolutionEquals(const VideoCodecConfig& aConfig) const + { + if (mSimulcastEncodings.size() != aConfig.mSimulcastEncodings.size()) { + return false; + } + for (size_t i = 0; i < mSimulcastEncodings.size(); ++i) { + if (!mSimulcastEncodings[i].constraints.ResolutionEquals( + aConfig.mSimulcastEncodings[i].constraints)) { + return false; + } + } + return true; + } + // Nothing seems to use this right now. Do we intend to support this // someday? bool RtcpFbAckIsSet(const std::string& type) const diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index 3d2e0d919d6c..752c1e0dd246 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -303,7 +303,6 @@ WebrtcVideoConduit::InitMain() { if (temp >= 0) { mPrefMaxBitrate = temp; - mNegotiatedMaxBitrate = temp; // simplifies logic in SelectBitrate (don't have to do two limit tests) } } if (mMinBitrate != 0 && mMinBitrate < webrtc::kViEMinCodecBitrate) { @@ -727,7 +726,7 @@ WebrtcVideoConduit::ConfigureSendMediaCodec(const VideoCodecConfig* codecConfig) }//for } - if(codecFound == false) + if(!codecFound) { CSFLogError(logTag, "%s Codec Mismatch ", __FUNCTION__); return kMediaConduitInvalidSendCodec; @@ -738,26 +737,63 @@ WebrtcVideoConduit::ConfigureSendMediaCodec(const VideoCodecConfig* codecConfig) // Note: only for overriding parameters from GetCodec()! CodecConfigToWebRTCCodec(codecConfig, video_codec); - if (mSendingWidth != 0) { - // We're already in a call and are reconfiguring (perhaps due to - // ReplaceTrack). Set to match the last frame we sent. - - // We could also set mLastWidth to 0, to force immediate reconfig - - // more expensive, but perhaps less risk of missing something. Really - // on ReplaceTrack we should just call ConfigureCodecMode(), and if the - // mode changed, we re-configure. - // Do this after CodecConfigToWebRTCCodec() to avoid messing up simulcast - video_codec.width = mSendingWidth; - video_codec.height = mSendingHeight; - video_codec.maxFramerate = mSendingFramerate; - } else { - mSendingWidth = 0; - mSendingHeight = 0; - mSendingFramerate = video_codec.maxFramerate; - } video_codec.mode = mCodecMode; + if (mSendingWidth != 0) { + bool resolutionChanged; + { + MutexAutoLock lock(mCodecMutex); + resolutionChanged = !mCurSendCodecConfig->ResolutionEquals(*codecConfig); + } + + if (resolutionChanged) { + // We're already in a call and due to renegotiation an encoder parameter + // that requires reconfiguration has changed. Resetting these members + // triggers reconfig on the next frame. + mLastWidth = 0; + mLastHeight = 0; + mSendingWidth = 0; + mSendingHeight = 0; + } else { + // We're already in a call but changes don't require a reconfiguration. + // We update the resolutions in the send codec to match the current + // settings. + webrtc::VideoCodec oldSendCodec; + if ((error = mPtrViECodec->GetSendCodec(mChannel, oldSendCodec)) != 0) { + CSFLogError(logTag, "%s: GetSendCodec failed, err %d", __FUNCTION__, error); + return kMediaConduitInvalidSendCodec; + } + + if (video_codec.numberOfSimulcastStreams != + oldSendCodec.numberOfSimulcastStreams) { + MOZ_ASSERT(false); + return kMediaConduitInvalidSendCodec; + } + + video_codec.width = oldSendCodec.width; + video_codec.height = oldSendCodec.height; + SelectBitrates(video_codec.width, video_codec.height, + video_codec.maxBitrate, + mLastFramerateTenths, + video_codec.minBitrate, + video_codec.targetBitrate, + video_codec.maxBitrate); + for (size_t i = 0; i < video_codec.numberOfSimulcastStreams; ++i) { + webrtc::SimulcastStream& stream(video_codec.simulcastStream[i]); + stream.width = oldSendCodec.simulcastStream[i].width; + stream.height = oldSendCodec.simulcastStream[i].height; + SelectBitrates(stream.width, + stream.height, + MinIgnoreZero(stream.jsMaxBitrate, video_codec.maxBitrate), + mLastFramerateTenths, + stream.minBitrate, + stream.targetBitrate, + stream.maxBitrate); + } + } + } + if(mPtrViECodec->SetSendCodec(mChannel, video_codec) == -1) { error = mPtrViEBase->LastError(); From bcce5eb099013af34228a8deb8f8266a68b3010e Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Tue, 11 Oct 2016 15:13:28 +0200 Subject: [PATCH 11/29] Bug 1309866 - Migrate RequestsMenuView to a React component with Redux store r=Honza MozReview-Commit-ID: IMu1sJLxQYy --HG-- extra : rebase_source : aa5a6f1a67ad0d4d83bcb0308d3dd6fe0de0ee0f --- .../client/netmonitor/actions/batching.js | 42 + devtools/client/netmonitor/actions/filters.js | 6 +- devtools/client/netmonitor/actions/index.js | 14 +- devtools/client/netmonitor/actions/moz.build | 4 + .../client/netmonitor/actions/requests.js | 60 +- .../client/netmonitor/actions/selection.js | 67 + devtools/client/netmonitor/actions/sort.js | 18 + .../netmonitor/actions/timing-markers.js | 19 + devtools/client/netmonitor/actions/ui.js | 13 +- .../netmonitor/components/clear-button.js | 17 +- .../client/netmonitor/components/moz.build | 6 + .../components/request-list-content.js | 255 +++ .../components/request-list-empty.js | 65 + .../components/request-list-header.js | 197 ++ .../components/request-list-item.js | 346 ++++ .../components/request-list-tooltip.js | 107 ++ .../netmonitor/components/request-list.js | 34 + .../netmonitor/components/summary-button.js | 12 +- .../netmonitor/components/toggle-button.js | 28 +- devtools/client/netmonitor/constants.js | 15 +- .../client/netmonitor/custom-request-view.js | 33 +- devtools/client/netmonitor/details-view.js | 6 +- devtools/client/netmonitor/har/har-builder.js | 4 +- .../client/netmonitor/har/har-collector.js | 4 +- .../client/netmonitor/middleware/batching.js | 132 ++ .../client/netmonitor/middleware/moz.build | 7 + devtools/client/netmonitor/moz.build | 5 +- .../netmonitor/netmonitor-controller.js | 74 +- devtools/client/netmonitor/netmonitor-view.js | 57 +- devtools/client/netmonitor/netmonitor.xul | 186 +- .../netmonitor/performance-statistics-view.js | 9 +- devtools/client/netmonitor/prefs.js | 1 - .../client/netmonitor/reducers/batching.js | 25 + .../client/netmonitor/reducers/filters.js | 4 +- devtools/client/netmonitor/reducers/index.js | 19 +- devtools/client/netmonitor/reducers/moz.build | 3 + .../client/netmonitor/reducers/requests.js | 237 ++- devtools/client/netmonitor/reducers/sort.js | 33 + .../netmonitor/reducers/timing-markers.js | 54 + devtools/client/netmonitor/reducers/ui.js | 22 +- .../netmonitor/request-list-context-menu.js | 54 +- devtools/client/netmonitor/request-utils.js | 36 +- .../client/netmonitor/requests-menu-view.js | 1677 +++-------------- .../client/netmonitor/selectors/filters.js | 13 + devtools/client/netmonitor/selectors/index.js | 62 +- .../client/netmonitor/selectors/moz.build | 5 +- .../client/netmonitor/selectors/requests.js | 119 ++ devtools/client/netmonitor/selectors/ui.js | 32 + devtools/client/netmonitor/sidebar-view.js | 1 - devtools/client/netmonitor/sort-predicates.js | 5 +- devtools/client/netmonitor/store.js | 14 +- devtools/client/netmonitor/test/browser.ini | 7 +- .../test/browser_net_accessibility-01.js | 39 +- .../test/browser_net_accessibility-02.js | 4 +- .../netmonitor/test/browser_net_api-calls.js | 2 +- .../netmonitor/test/browser_net_autoscroll.js | 36 +- .../netmonitor/test/browser_net_brotli.js | 2 +- .../test/browser_net_cached-status.js | 3 +- .../netmonitor/test/browser_net_cause.js | 10 +- .../test/browser_net_cause_redirect.js | 6 +- .../test/browser_net_content-type.js | 14 +- .../test/browser_net_copy_headers.js | 2 +- .../netmonitor/test/browser_net_copy_url.js | 2 +- .../test/browser_net_cors_requests.js | 3 +- .../netmonitor/test/browser_net_curl-utils.js | 8 +- .../test/browser_net_cyrillic-01.js | 2 +- .../test/browser_net_cyrillic-02.js | 2 +- .../netmonitor/test/browser_net_filter-01.js | 193 +- .../netmonitor/test/browser_net_filter-02.js | 210 ++- .../netmonitor/test/browser_net_filter-03.js | 78 +- .../test/browser_net_footer-summary.js | 29 +- .../netmonitor/test/browser_net_frame.js | 11 +- .../test/browser_net_icon-preview.js | 8 +- .../test/browser_net_image-tooltip.js | 18 +- .../netmonitor/test/browser_net_json-long.js | 2 +- .../test/browser_net_json-malformed.js | 2 +- .../test/browser_net_json_custom_mime.js | 2 +- .../test/browser_net_json_text_mime.js | 2 +- .../netmonitor/test/browser_net_jsonp.js | 4 +- .../test/browser_net_large-response.js | 2 +- .../test/browser_net_post-data-01.js | 4 +- .../test/browser_net_prefs-reload.js | 6 +- .../test/browser_net_raw_headers.js | 6 +- .../test/browser_net_reload-button.js | 6 +- .../test/browser_net_reload-markers.js | 4 +- .../test/browser_net_req-resp-bodies.js | 2 +- .../netmonitor/test/browser_net_resend.js | 26 +- .../test/browser_net_resend_cors.js | 8 +- .../test/browser_net_resend_headers.js | 12 +- .../test/browser_net_security-icon-click.js | 10 +- .../test/browser_net_security-redirect.js | 10 +- .../test/browser_net_security-state.js | 5 +- .../browser_net_security-tab-visibility.js | 4 +- .../test/browser_net_send-beacon-other-tab.js | 4 +- .../test/browser_net_send-beacon.js | 6 +- .../test/browser_net_service-worker-status.js | 5 +- .../test/browser_net_simple-request-data.js | 195 +- .../browser_net_simple-request-details.js | 2 +- .../test/browser_net_simple-request.js | 8 +- .../netmonitor/test/browser_net_sort-01.js | 23 +- .../netmonitor/test/browser_net_sort-02.js | 41 +- .../netmonitor/test/browser_net_sort-03.js | 38 +- .../test/browser_net_statistics-03.js | 2 +- .../test/browser_net_status-codes.js | 8 +- .../test/browser_net_streaming-response.js | 2 +- .../netmonitor/test/browser_net_throttle.js | 2 +- .../test/browser_net_timeline_ticks.js | 29 +- .../test/browser_net_timing-division.js | 27 +- devtools/client/netmonitor/test/head.js | 85 +- .../client/netmonitor/utils/format-utils.js | 44 + devtools/client/netmonitor/utils/moz.build | 8 + .../client/netmonitor/waterfall-background.js | 135 ++ devtools/client/preferences/devtools.js | 1 - .../browser_styleeditor_fetch-from-cache.js | 12 +- devtools/client/themes/netmonitor.css | 267 ++- devtools/client/themes/toolbars.css | 1 - ...ser_netmonitor_shows_reqs_in_webconsole.js | 4 +- .../browser_webconsole_netlogging_panel.js | 4 +- ...wser_webconsole_netlogging_reset_filter.js | 8 +- ...ser_webconsole_shows_reqs_in_netmonitor.js | 4 +- 120 files changed, 3352 insertions(+), 2677 deletions(-) create mode 100644 devtools/client/netmonitor/actions/batching.js create mode 100644 devtools/client/netmonitor/actions/selection.js create mode 100644 devtools/client/netmonitor/actions/sort.js create mode 100644 devtools/client/netmonitor/actions/timing-markers.js create mode 100644 devtools/client/netmonitor/components/request-list-content.js create mode 100644 devtools/client/netmonitor/components/request-list-empty.js create mode 100644 devtools/client/netmonitor/components/request-list-header.js create mode 100644 devtools/client/netmonitor/components/request-list-item.js create mode 100644 devtools/client/netmonitor/components/request-list-tooltip.js create mode 100644 devtools/client/netmonitor/components/request-list.js create mode 100644 devtools/client/netmonitor/middleware/batching.js create mode 100644 devtools/client/netmonitor/middleware/moz.build create mode 100644 devtools/client/netmonitor/reducers/batching.js create mode 100644 devtools/client/netmonitor/reducers/sort.js create mode 100644 devtools/client/netmonitor/reducers/timing-markers.js create mode 100644 devtools/client/netmonitor/selectors/filters.js create mode 100644 devtools/client/netmonitor/selectors/requests.js create mode 100644 devtools/client/netmonitor/selectors/ui.js create mode 100644 devtools/client/netmonitor/utils/format-utils.js create mode 100644 devtools/client/netmonitor/utils/moz.build create mode 100644 devtools/client/netmonitor/waterfall-background.js diff --git a/devtools/client/netmonitor/actions/batching.js b/devtools/client/netmonitor/actions/batching.js new file mode 100644 index 000000000000..fd68db185a32 --- /dev/null +++ b/devtools/client/netmonitor/actions/batching.js @@ -0,0 +1,42 @@ +/* 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/. */ + +"use strict"; + +const { + BATCH_ACTIONS, + BATCH_ENABLE, + BATCH_RESET, +} = require("../constants"); + +/** + * Process multiple actions at once as part of one dispatch, and produce only one + * state update at the end. This action is not processed by any reducer, but by a + * special store enhancer. + */ +function batchActions(actions) { + return { + type: BATCH_ACTIONS, + actions + }; +} + +function batchEnable(enabled) { + return { + type: BATCH_ENABLE, + enabled + }; +} + +function batchReset() { + return { + type: BATCH_RESET, + }; +} + +module.exports = { + batchActions, + batchEnable, + batchReset, +}; diff --git a/devtools/client/netmonitor/actions/filters.js b/devtools/client/netmonitor/actions/filters.js index 0082c64dfdd9..b8d8726afd39 100644 --- a/devtools/client/netmonitor/actions/filters.js +++ b/devtools/client/netmonitor/actions/filters.js @@ -42,12 +42,12 @@ function enableFilterTypeOnly(filter) { /** * Set filter text. * - * @param {string} url - A filter text is going to be set + * @param {string} text - A filter text is going to be set */ -function setFilterText(url) { +function setFilterText(text) { return { type: SET_FILTER_TEXT, - url, + text, }; } diff --git a/devtools/client/netmonitor/actions/index.js b/devtools/client/netmonitor/actions/index.js index 29ce28fd5d50..110f73ce76d8 100644 --- a/devtools/client/netmonitor/actions/index.js +++ b/devtools/client/netmonitor/actions/index.js @@ -4,8 +4,20 @@ "use strict"; +const batching = require("./batching"); const filters = require("./filters"); const requests = require("./requests"); +const selection = require("./selection"); +const sort = require("./sort"); +const timingMarkers = require("./timing-markers"); const ui = require("./ui"); -module.exports = Object.assign({}, filters, requests, ui); +Object.assign(exports, + batching, + filters, + requests, + selection, + sort, + timingMarkers, + ui +); diff --git a/devtools/client/netmonitor/actions/moz.build b/devtools/client/netmonitor/actions/moz.build index 9082b8bd1b88..e81d2714edfa 100644 --- a/devtools/client/netmonitor/actions/moz.build +++ b/devtools/client/netmonitor/actions/moz.build @@ -3,8 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + 'batching.js', 'filters.js', 'index.js', 'requests.js', + 'selection.js', + 'sort.js', + 'timing-markers.js', 'ui.js', ) diff --git a/devtools/client/netmonitor/actions/requests.js b/devtools/client/netmonitor/actions/requests.js index ae794a437496..77871a0ff293 100644 --- a/devtools/client/netmonitor/actions/requests.js +++ b/devtools/client/netmonitor/actions/requests.js @@ -5,21 +5,61 @@ "use strict"; const { - UPDATE_REQUESTS, + ADD_REQUEST, + UPDATE_REQUEST, + CLONE_SELECTED_REQUEST, + REMOVE_SELECTED_CUSTOM_REQUEST, + CLEAR_REQUESTS, } = require("../constants"); -/** - * Update request items - * - * @param {array} requests - visible request items - */ -function updateRequests(items) { +function addRequest(id, data, batch) { return { - type: UPDATE_REQUESTS, - items, + type: ADD_REQUEST, + id, + data, + meta: { batch }, + }; +} + +function updateRequest(id, data, batch) { + return { + type: UPDATE_REQUEST, + id, + data, + meta: { batch }, + }; +} + +/** + * Clone the currently selected request, set the "isCustom" attribute. + * Used by the "Edit and Resend" feature. + */ +function cloneSelectedRequest() { + return { + type: CLONE_SELECTED_REQUEST + }; +} + +/** + * Remove a request from the list. Supports removing only cloned requests with a + * "isCustom" attribute. Other requests never need to be removed. + */ +function removeSelectedCustomRequest() { + return { + type: REMOVE_SELECTED_CUSTOM_REQUEST + }; +} + +function clearRequests() { + return { + type: CLEAR_REQUESTS }; } module.exports = { - updateRequests, + addRequest, + updateRequest, + cloneSelectedRequest, + removeSelectedCustomRequest, + clearRequests, }; diff --git a/devtools/client/netmonitor/actions/selection.js b/devtools/client/netmonitor/actions/selection.js new file mode 100644 index 000000000000..dffc6d8cc000 --- /dev/null +++ b/devtools/client/netmonitor/actions/selection.js @@ -0,0 +1,67 @@ +/* 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/. */ + +"use strict"; + +const { getDisplayedRequests } = require("../selectors/index"); +const { SELECT_REQUEST, PRESELECT_REQUEST } = require("../constants"); + +/** + * When a new request with a given id is added in future, select it immediately. + * Used by the "Edit and Resend" feature, where we know in advance the ID of the + * request, at a time when it wasn't sent yet. + */ +function preselectRequest(id) { + return { + type: PRESELECT_REQUEST, + id + }; +} + +/** + * Select request with a given id. + */ +function selectRequest(id) { + return { + type: SELECT_REQUEST, + id + }; +} + +const PAGE_SIZE_ITEM_COUNT_RATIO = 5; + +/** + * Move the selection up to down according to the "delta" parameter. Possible values: + * - Number: positive or negative, move up or down by specified distance + * - "PAGE_UP" | "PAGE_DOWN" (String): page up or page down + * - +Infinity | -Infinity: move to the start or end of the list + */ +function selectDelta(delta) { + return (dispatch, getState) => { + const state = getState(); + const requests = getDisplayedRequests(state); + + if (requests.isEmpty()) { + return; + } + + const selIndex = requests.findIndex(r => r.id === state.requests.selectedId); + + if (delta === "PAGE_DOWN") { + delta = Math.ceil(requests.size / PAGE_SIZE_ITEM_COUNT_RATIO); + } else if (delta === "PAGE_UP") { + delta = -Math.ceil(requests.size / PAGE_SIZE_ITEM_COUNT_RATIO); + } + + const newIndex = Math.min(Math.max(0, selIndex + delta), requests.size - 1); + const newItem = requests.get(newIndex); + dispatch(selectRequest(newItem.id)); + }; +} + +module.exports = { + preselectRequest, + selectRequest, + selectDelta, +}; diff --git a/devtools/client/netmonitor/actions/sort.js b/devtools/client/netmonitor/actions/sort.js new file mode 100644 index 000000000000..2dd02373ec50 --- /dev/null +++ b/devtools/client/netmonitor/actions/sort.js @@ -0,0 +1,18 @@ +/* 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/. */ + +"use strict"; + +const { SORT_BY } = require("../constants"); + +function sortBy(sortType) { + return { + type: SORT_BY, + sortType + }; +} + +module.exports = { + sortBy +}; diff --git a/devtools/client/netmonitor/actions/timing-markers.js b/devtools/client/netmonitor/actions/timing-markers.js new file mode 100644 index 000000000000..4f1363a70637 --- /dev/null +++ b/devtools/client/netmonitor/actions/timing-markers.js @@ -0,0 +1,19 @@ +/* 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/. */ +"use strict"; + +const { ADD_TIMING_MARKER, CLEAR_TIMING_MARKERS } = require("../constants"); + +exports.addTimingMarker = (marker) => { + return { + type: ADD_TIMING_MARKER, + marker + }; +}; + +exports.clearTimingMarkers = () => { + return { + type: CLEAR_TIMING_MARKERS + }; +}; diff --git a/devtools/client/netmonitor/actions/ui.js b/devtools/client/netmonitor/actions/ui.js index 554921bc2781..31539518fb72 100644 --- a/devtools/client/netmonitor/actions/ui.js +++ b/devtools/client/netmonitor/actions/ui.js @@ -6,7 +6,7 @@ const { OPEN_SIDEBAR, - TOGGLE_SIDEBAR, + WATERFALL_RESIZE, } = require("../constants"); /** @@ -25,12 +25,21 @@ function openSidebar(open) { * Toggle sidebar open state. */ function toggleSidebar() { + return (dispatch, getState) => dispatch(openSidebar(!getState().ui.sidebarOpen)); +} + +/** + * Waterfall width has changed (likely on window resize). Update the UI. + */ +function resizeWaterfall(width) { return { - type: TOGGLE_SIDEBAR, + type: WATERFALL_RESIZE, + width }; } module.exports = { openSidebar, toggleSidebar, + resizeWaterfall, }; diff --git a/devtools/client/netmonitor/components/clear-button.js b/devtools/client/netmonitor/components/clear-button.js index cccff81fb9dd..5ef1a73211fd 100644 --- a/devtools/client/netmonitor/components/clear-button.js +++ b/devtools/client/netmonitor/components/clear-button.js @@ -2,12 +2,12 @@ * 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/. */ -/* globals NetMonitorView */ - "use strict"; const { DOM } = require("devtools/client/shared/vendor/react"); +const { connect } = require("devtools/client/shared/vendor/react-redux"); const { L10N } = require("../l10n"); +const Actions = require("../actions/index"); const { button } = DOM; @@ -15,15 +15,18 @@ const { button } = DOM; * Clear button component * A type of tool button is responsible for cleaning network requests. */ -function ClearButton() { +function ClearButton({ onClick }) { return button({ id: "requests-menu-clear-button", className: "devtools-button devtools-clear-icon", title: L10N.getStr("netmonitor.toolbar.clear"), - onClick: () => { - NetMonitorView.RequestsMenu.clear(); - }, + onClick, }); } -module.exports = ClearButton; +module.exports = connect( + undefined, + dispatch => ({ + onClick: () => dispatch(Actions.clearRequests()) + }) +)(ClearButton); diff --git a/devtools/client/netmonitor/components/moz.build b/devtools/client/netmonitor/components/moz.build index 02655b710f23..bb5ad9b7dd9f 100644 --- a/devtools/client/netmonitor/components/moz.build +++ b/devtools/client/netmonitor/components/moz.build @@ -5,6 +5,12 @@ DevToolsModules( 'clear-button.js', 'filter-buttons.js', + 'request-list-content.js', + 'request-list-empty.js', + 'request-list-header.js', + 'request-list-item.js', + 'request-list-tooltip.js', + 'request-list.js', 'search-box.js', 'summary-button.js', 'toggle-button.js', diff --git a/devtools/client/netmonitor/components/request-list-content.js b/devtools/client/netmonitor/components/request-list-content.js new file mode 100644 index 000000000000..c5f029600d2b --- /dev/null +++ b/devtools/client/netmonitor/components/request-list-content.js @@ -0,0 +1,255 @@ +/* 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/. */ +/* globals NetMonitorView */ + +"use strict"; + +const { Task } = require("devtools/shared/task"); +const { createClass, createFactory, DOM } = require("devtools/client/shared/vendor/react"); +const { div } = DOM; +const Actions = require("../actions/index"); +const RequestListItem = createFactory(require("./request-list-item")); +const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { setTooltipImageContent, + setTooltipStackTraceContent } = require("./request-list-tooltip"); +const { getDisplayedRequests, + getWaterfallScale } = require("../selectors/index"); +const { KeyCodes } = require("devtools/client/shared/keycodes"); + +// tooltip show/hide delay in ms +const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500; + +/** + * Renders the actual contents of the request list. + */ +const RequestListContent = createClass({ + displayName: "RequestListContent", + + componentDidMount() { + // Set the CSS variables for waterfall scaling + this.setScalingStyles(); + + // Install event handler for displaying a tooltip + this.props.tooltip.startTogglingOnHover(this.refs.contentEl, this.onHover, { + toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY, + interactive: true + }); + + // Install event handler to hide the tooltip on scroll + this.refs.contentEl.addEventListener("scroll", this.onScroll, true); + }, + + componentWillUpdate() { + // Check if the list is scrolled to bottom, before UI update + this.shouldScrollBottom = this.isScrolledToBottom(); + }, + + componentDidUpdate(prevProps) { + // Update the CSS variables for waterfall scaling after props change + this.setScalingStyles(); + + // Keep the list scrolled to bottom if a new row was added + if (this.shouldScrollBottom) { + let node = this.refs.contentEl; + node.scrollTop = node.scrollHeight; + } + }, + + componentWillUnmount() { + this.refs.contentEl.removeEventListener("scroll", this.onScroll, true); + + // Uninstall the tooltip event handler + this.props.tooltip.stopTogglingOnHover(); + }, + + /** + * Set the CSS variables for waterfall scaling. If React supported setting CSS + * variables as part of the "style" property of a DOM element, we would use that. + * + * However, React doesn't support this, so we need to use a hack and update the + * DOM element directly: https://github.com/facebook/react/issues/6411 + */ + setScalingStyles(prevProps) { + const { scale } = this.props; + if (scale == this.currentScale) { + return; + } + + this.currentScale = scale; + + const { style } = this.refs.contentEl; + style.removeProperty("--timings-scale"); + style.removeProperty("--timings-rev-scale"); + style.setProperty("--timings-scale", scale); + style.setProperty("--timings-rev-scale", 1 / scale); + }, + + isScrolledToBottom() { + const { contentEl } = this.refs; + const lastChildEl = contentEl.lastElementChild; + + if (!lastChildEl) { + return false; + } + + let lastChildRect = lastChildEl.getBoundingClientRect(); + let contentRect = contentEl.getBoundingClientRect(); + + return (lastChildRect.height + lastChildRect.top) <= contentRect.bottom; + }, + + /** + * The predicate used when deciding whether a popup should be shown + * over a request item or not. + * + * @param nsIDOMNode target + * The element node currently being hovered. + * @param object tooltip + * The current tooltip instance. + * @return {Promise} + */ + onHover: Task.async(function* (target, tooltip) { + let itemEl = target.closest(".request-list-item"); + if (!itemEl) { + return false; + } + let itemId = itemEl.dataset.id; + if (!itemId) { + return false; + } + let requestItem = this.props.displayedRequests.find(r => r.id == itemId); + if (!requestItem) { + return false; + } + + if (requestItem.responseContent && target.closest(".requests-menu-icon-and-file")) { + return setTooltipImageContent(tooltip, itemEl, requestItem); + } else if (requestItem.cause && target.closest(".requests-menu-cause-stack")) { + return setTooltipStackTraceContent(tooltip, requestItem); + } + + return false; + }), + + /** + * Scroll listener for the requests menu view. + */ + onScroll() { + this.props.tooltip.hide(); + }, + + /** + * Handler for keyboard events. For arrow up/down, page up/down, home/end, + * move the selection up or down. + */ + onKeyDown(e) { + let delta; + + switch (e.keyCode) { + case KeyCodes.DOM_VK_UP: + case KeyCodes.DOM_VK_LEFT: + delta = -1; + break; + case KeyCodes.DOM_VK_DOWN: + case KeyCodes.DOM_VK_RIGHT: + delta = +1; + break; + case KeyCodes.DOM_VK_PAGE_UP: + delta = "PAGE_UP"; + break; + case KeyCodes.DOM_VK_PAGE_DOWN: + delta = "PAGE_DOWN"; + break; + case KeyCodes.DOM_VK_HOME: + delta = -Infinity; + break; + case KeyCodes.DOM_VK_END: + delta = +Infinity; + break; + } + + if (delta) { + // Prevent scrolling when pressing navigation keys. + e.preventDefault(); + e.stopPropagation(); + this.props.onSelectDelta(delta); + } + }, + + /** + * If selection has just changed (by keyboard navigation), don't keep the list + * scrolled to bottom, but allow scrolling up with the selection. + */ + onFocusedNodeChange() { + this.shouldScrollBottom = false; + }, + + /** + * If a focused item was unmounted, transfer the focus to the container element. + */ + onFocusedNodeUnmount() { + if (this.refs.contentEl) { + this.refs.contentEl.focus(); + } + }, + + render() { + const { selectedRequestId, + displayedRequests, + firstRequestStartedMillis, + onItemMouseDown, + onItemContextMenu, + onSecurityIconClick } = this.props; + + return div( + { + ref: "contentEl", + className: "requests-menu-contents", + tabIndex: 0, + onKeyDown: this.onKeyDown, + }, + displayedRequests.map((item, index) => RequestListItem({ + key: item.id, + item, + index, + isSelected: item.id === selectedRequestId, + firstRequestStartedMillis, + onMouseDown: e => onItemMouseDown(e, item.id), + onContextMenu: e => onItemContextMenu(e, item.id), + onSecurityIconClick: e => onSecurityIconClick(e, item), + onFocusedNodeChange: this.onFocusedNodeChange, + onFocusedNodeUnmount: this.onFocusedNodeUnmount, + })) + ); + }, +}); + +module.exports = connect( + state => ({ + displayedRequests: getDisplayedRequests(state), + selectedRequestId: state.requests.selectedId, + scale: getWaterfallScale(state), + firstRequestStartedMillis: state.requests.firstStartedMillis, + tooltip: NetMonitorView.RequestsMenu.tooltip, + }), + dispatch => ({ + onItemMouseDown: (e, item) => dispatch(Actions.selectRequest(item)), + onItemContextMenu: (e, item) => { + e.preventDefault(); + NetMonitorView.RequestsMenu.contextMenu.open(e); + }, + onSelectDelta: (delta) => dispatch(Actions.selectDelta(delta)), + /** + * A handler that opens the security tab in the details view if secure or + * broken security indicator is clicked. + */ + onSecurityIconClick: (e, item) => { + const { securityState } = item; + if (securityState && securityState !== "insecure") { + // Choose the security tab. + NetMonitorView.NetworkDetails.widget.selectedIndex = 5; + } + }, + }) +)(RequestListContent); diff --git a/devtools/client/netmonitor/components/request-list-empty.js b/devtools/client/netmonitor/components/request-list-empty.js new file mode 100644 index 000000000000..f4fe56bc1c70 --- /dev/null +++ b/devtools/client/netmonitor/components/request-list-empty.js @@ -0,0 +1,65 @@ +/* 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/. */ +/* globals NetMonitorView */ + +"use strict"; + +const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react"); +const { L10N } = require("../l10n"); +const { div, span, button } = DOM; +const { connect } = require("devtools/client/shared/vendor/react-redux"); + +/** + * UI displayed when the request list is empty. Contains instructions on reloading + * the page and on triggering performance analysis of the page. + */ +const RequestListEmptyNotice = createClass({ + displayName: "RequestListEmptyNotice", + + propTypes: { + onReloadClick: PropTypes.func.isRequired, + onPerfClick: PropTypes.func.isRequired, + }, + + render() { + return div( + { + id: "requests-menu-empty-notice", + className: "request-list-empty-notice", + }, + div({ id: "notice-reload-message" }, + span(null, L10N.getStr("netmonitor.reloadNotice1")), + button( + { + id: "requests-menu-reload-notice-button", + className: "devtools-toolbarbutton", + "data-standalone": true, + onClick: this.props.onReloadClick, + }, + L10N.getStr("netmonitor.reloadNotice2") + ), + span(null, L10N.getStr("netmonitor.reloadNotice3")) + ), + div({ id: "notice-perf-message" }, + span(null, L10N.getStr("netmonitor.perfNotice1")), + button({ + id: "requests-menu-perf-notice-button", + title: L10N.getStr("netmonitor.perfNotice3"), + className: "devtools-button", + "data-standalone": true, + onClick: this.props.onPerfClick, + }), + span(null, L10N.getStr("netmonitor.perfNotice2")) + ) + ); + } +}); + +module.exports = connect( + undefined, + dispatch => ({ + onPerfClick: e => NetMonitorView.toggleFrontendMode(), + onReloadClick: e => NetMonitorView.reloadPage(), + }) +)(RequestListEmptyNotice); diff --git a/devtools/client/netmonitor/components/request-list-header.js b/devtools/client/netmonitor/components/request-list-header.js new file mode 100644 index 000000000000..d28226eccf2b --- /dev/null +++ b/devtools/client/netmonitor/components/request-list-header.js @@ -0,0 +1,197 @@ +/* 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/. */ + +/* globals document */ + +"use strict"; + +const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react"); +const { div, button } = DOM; +const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { L10N } = require("../l10n"); +const { getWaterfallScale } = require("../selectors/index"); +const Actions = require("../actions/index"); +const WaterfallBackground = require("../waterfall-background"); + +// ms +const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; +// px +const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; + +const REQUEST_TIME_DECIMALS = 2; + +const HEADERS = [ + { name: "status", label: "status3" }, + { name: "method" }, + { name: "file", boxName: "icon-and-file" }, + { name: "domain", boxName: "security-and-domain" }, + { name: "cause" }, + { name: "type" }, + { name: "transferred" }, + { name: "size" }, + { name: "waterfall" } +]; + +/** + * Render the request list header with sorting arrows for columns. + * Displays tick marks in the waterfall column header. + * Also draws the waterfall background canvas and updates it when needed. + */ +const RequestListHeader = createClass({ + displayName: "RequestListHeader", + + propTypes: { + sort: PropTypes.object, + scale: PropTypes.number, + waterfallWidth: PropTypes.number, + onHeaderClick: PropTypes.func.isRequired, + }, + + componentDidMount() { + this.background = new WaterfallBackground(document); + this.background.draw(this.props); + }, + + componentDidUpdate() { + this.background.draw(this.props); + }, + + componentWillUnmount() { + this.background.destroy(); + this.background = null; + }, + + render() { + const { sort, scale, waterfallWidth, onHeaderClick } = this.props; + + return div( + { id: "requests-menu-toolbar", className: "devtools-toolbar" }, + div({ id: "toolbar-labels" }, + HEADERS.map(header => { + const name = header.name; + const boxName = header.boxName || name; + const label = L10N.getStr(`netmonitor.toolbar.${header.label || name}`); + + let sorted, sortedTitle; + const active = sort.type == name ? true : undefined; + if (active) { + sorted = sort.ascending ? "ascending" : "descending"; + sortedTitle = L10N.getStr(sort.ascending + ? "networkMenu.sortedAsc" + : "networkMenu.sortedDesc"); + } + + return div( + { + id: `requests-menu-${boxName}-header-box`, + key: name, + className: `requests-menu-header requests-menu-${boxName}`, + // Used to style the next column. + "data-active": active, + }, + button( + { + id: `requests-menu-${name}-button`, + className: `requests-menu-header-button requests-menu-${name}`, + "data-sorted": sorted, + title: sortedTitle, + onClick: () => onHeaderClick(name), + }, + name == "waterfall" ? WaterfallLabel(waterfallWidth, scale, label) + : div({ className: "button-text" }, label), + div({ className: "button-icon" }) + ) + ); + }) + ) + ); + } +}); + +/** + * Build the waterfall header - timing tick marks with the right spacing + */ +function waterfallDivisionLabels(waterfallWidth, scale) { + let labels = []; + + // Build new millisecond tick labels... + let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE; + let scaledStep = scale * timingStep; + + // Ignore any divisions that would end up being too close to each other. + while (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) { + scaledStep *= 2; + } + + // Insert one label for each division on the current scale. + for (let x = 0; x < waterfallWidth; x += scaledStep) { + let millisecondTime = x / scale; + + let normalizedTime = millisecondTime; + let divisionScale = "millisecond"; + + // If the division is greater than 1 minute. + if (normalizedTime > 60000) { + normalizedTime /= 60000; + divisionScale = "minute"; + } else if (normalizedTime > 1000) { + // If the division is greater than 1 second. + normalizedTime /= 1000; + divisionScale = "second"; + } + + // Showing too many decimals is bad UX. + if (divisionScale == "millisecond") { + normalizedTime |= 0; + } else { + normalizedTime = L10N.numberWithDecimals(normalizedTime, REQUEST_TIME_DECIMALS); + } + + let width = (x + scaledStep | 0) - (x | 0); + // Adjust the first marker for the borders + if (x == 0) { + width -= 2; + } + // Last marker doesn't need a width specified at all + if (x + scaledStep >= waterfallWidth) { + width = undefined; + } + + labels.push(div( + { + key: labels.length, + className: "requests-menu-timings-division", + "data-division-scale": divisionScale, + style: { width } + }, + L10N.getFormatStr("networkMenu." + divisionScale, normalizedTime) + )); + } + + return labels; +} + +function WaterfallLabel(waterfallWidth, scale, label) { + let className = "button-text requests-menu-waterfall-label-wrapper"; + + if (scale != null) { + label = waterfallDivisionLabels(waterfallWidth, scale); + className += " requests-menu-waterfall-visible"; + } + + return div({ className }, label); +} + +module.exports = connect( + state => ({ + sort: state.sort, + scale: getWaterfallScale(state), + waterfallWidth: state.ui.waterfallWidth, + firstRequestStartedMillis: state.requests.firstStartedMillis, + timingMarkers: state.timingMarkers, + }), + dispatch => ({ + onHeaderClick: type => dispatch(Actions.sortBy(type)), + }) +)(RequestListHeader); diff --git a/devtools/client/netmonitor/components/request-list-item.js b/devtools/client/netmonitor/components/request-list-item.js new file mode 100644 index 000000000000..a7976ea2ae29 --- /dev/null +++ b/devtools/client/netmonitor/components/request-list-item.js @@ -0,0 +1,346 @@ +/* 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/. */ + +"use strict"; + +const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react"); +const { div, span, img } = DOM; +const { L10N } = require("../l10n"); +const { getFormattedSize } = require("../utils/format-utils"); +const { getAbbreviatedMimeType } = require("../request-utils"); + +/** + * Render one row in the request list. + */ +const RequestListItem = createClass({ + displayName: "RequestListItem", + + propTypes: { + item: PropTypes.object.isRequired, + index: PropTypes.number.isRequired, + isSelected: PropTypes.bool.isRequired, + firstRequestStartedMillis: PropTypes.number.isRequired, + onContextMenu: PropTypes.func.isRequired, + onMouseDown: PropTypes.func.isRequired, + onSecurityIconClick: PropTypes.func.isRequired, + }, + + componentDidMount() { + if (this.props.isSelected) { + this.refs.el.focus(); + } + }, + + shouldComponentUpdate(nextProps) { + return !relevantPropsEqual(this.props.item, nextProps.item) + || this.props.index !== nextProps.index + || this.props.isSelected !== nextProps.isSelected + || this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis; + }, + + componentDidUpdate(prevProps) { + if (!prevProps.isSelected && this.props.isSelected) { + this.refs.el.focus(); + if (this.props.onFocusedNodeChange) { + this.props.onFocusedNodeChange(); + } + } + }, + + componentWillUnmount() { + // If this node is being destroyed and has focus, transfer the focus manually + // to the parent tree component. Otherwise, the focus will get lost and keyboard + // navigation in the tree will stop working. This is a workaround for a XUL bug. + // See bugs 1259228 and 1152441 for details. + // DE-XUL: Remove this hack once all usages are only in HTML documents. + if (this.props.isSelected) { + this.refs.el.blur(); + if (this.props.onFocusedNodeUnmount) { + this.props.onFocusedNodeUnmount(); + } + } + }, + + render() { + const { + item, + index, + isSelected, + firstRequestStartedMillis, + onContextMenu, + onMouseDown, + onSecurityIconClick + } = this.props; + + let classList = [ "request-list-item" ]; + if (isSelected) { + classList.push("selected"); + } + classList.push(index % 2 ? "odd" : "even"); + + return div( + { + ref: "el", + className: classList.join(" "), + "data-id": item.id, + tabIndex: 0, + onContextMenu, + onMouseDown, + }, + StatusColumn(item), + MethodColumn(item), + FileColumn(item), + DomainColumn(item, onSecurityIconClick), + CauseColumn(item), + TypeColumn(item), + TransferredSizeColumn(item), + ContentSizeColumn(item), + WaterfallColumn(item, firstRequestStartedMillis) + ); + } +}); + +/** + * Used by shouldComponentUpdate: compare two items, and compare only properties + * relevant for rendering the RequestListItem. Other properties (like request and + * response headers, cookies, bodies) are ignored. These are very useful for the + * sidebar details, but not here. + */ +const RELEVANT_ITEM_PROPS = [ + "status", + "statusText", + "fromCache", + "fromServiceWorker", + "method", + "url", + "responseContentDataUri", + "remoteAddress", + "securityState", + "cause", + "mimeType", + "contentSize", + "transferredSize", + "startedMillis", + "totalTime", + "eventTimings", +]; + +function relevantPropsEqual(item1, item2) { + return item1 === item2 || RELEVANT_ITEM_PROPS.every(p => item1[p] === item2[p]); +} + +function StatusColumn(item) { + const { status, statusText, fromCache, fromServiceWorker } = item; + + let code, title; + + if (status) { + if (fromCache) { + code = "cached"; + } else if (fromServiceWorker) { + code = "service worker"; + } else { + code = status; + } + + if (statusText) { + title = `${status} ${statusText}`; + if (fromCache) { + title += " (cached)"; + } + if (fromServiceWorker) { + title += " (service worker)"; + } + } + } + + return div({ className: "requests-menu-subitem requests-menu-status", title }, + div({ className: "requests-menu-status-icon", "data-code": code }), + span({ className: "subitem-label requests-menu-status-code" }, status) + ); +} + +function MethodColumn(item) { + const { method } = item; + return div({ className: "requests-menu-subitem requests-menu-method-box" }, + span({ className: "subitem-label requests-menu-method" }, method) + ); +} + +function FileColumn(item) { + const { urlDetails, responseContentDataUri } = item; + + return div({ className: "requests-menu-subitem requests-menu-icon-and-file" }, + img({ + className: "requests-menu-icon", + src: responseContentDataUri, + hidden: !responseContentDataUri, + "data-type": responseContentDataUri ? "thumbnail" : undefined + }), + div( + { + className: "subitem-label requests-menu-file", + title: urlDetails.unicodeUrl + }, + urlDetails.baseNameWithQuery + ) + ); +} + +function DomainColumn(item, onSecurityIconClick) { + const { urlDetails, remoteAddress, securityState } = item; + + let iconClassList = [ "requests-security-state-icon" ]; + let iconTitle; + if (urlDetails.isLocal) { + iconClassList.push("security-state-local"); + iconTitle = L10N.getStr("netmonitor.security.state.secure"); + } else if (securityState) { + iconClassList.push(`security-state-${securityState}`); + iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`); + } + + let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : ""); + + return div( + { className: "requests-menu-subitem requests-menu-security-and-domain" }, + div({ + className: iconClassList.join(" "), + title: iconTitle, + onClick: onSecurityIconClick, + }), + span({ className: "subitem-label requests-menu-domain", title }, urlDetails.host) + ); +} + +function CauseColumn(item) { + const { cause } = item; + + let causeType = ""; + let causeUri = undefined; + let causeHasStack = false; + + if (cause) { + causeType = cause.type; + causeUri = cause.loadingDocumentUri; + causeHasStack = cause.stacktrace && cause.stacktrace.length > 0; + } + + return div( + { className: "requests-menu-subitem requests-menu-cause", title: causeUri }, + span({ className: "requests-menu-cause-stack", hidden: !causeHasStack }, "JS"), + span({ className: "subitem-label" }, causeType) + ); +} + +const CONTENT_MIME_TYPE_ABBREVIATIONS = { + "ecmascript": "js", + "javascript": "js", + "x-javascript": "js" +}; + +function TypeColumn(item) { + const { mimeType } = item; + let abbrevType; + if (mimeType) { + abbrevType = getAbbreviatedMimeType(mimeType); + abbrevType = CONTENT_MIME_TYPE_ABBREVIATIONS[abbrevType] || abbrevType; + } + + return div( + { className: "requests-menu-subitem requests-menu-type", title: mimeType }, + span({ className: "subitem-label" }, abbrevType) + ); +} + +function TransferredSizeColumn(item) { + const { transferredSize, fromCache, fromServiceWorker } = item; + + let text; + let className = "subitem-label"; + if (fromCache) { + text = L10N.getStr("networkMenu.sizeCached"); + className += " theme-comment"; + } else if (fromServiceWorker) { + text = L10N.getStr("networkMenu.sizeServiceWorker"); + className += " theme-comment"; + } else if (typeof transferredSize == "number") { + text = getFormattedSize(transferredSize); + } else if (transferredSize === null) { + text = L10N.getStr("networkMenu.sizeUnavailable"); + } + + return div( + { className: "requests-menu-subitem requests-menu-transferred", title: text }, + span({ className }, text) + ); +} + +function ContentSizeColumn(item) { + const { contentSize } = item; + + let text; + if (typeof contentSize == "number") { + text = getFormattedSize(contentSize); + } + + return div( + { className: "requests-menu-subitem subitem-label requests-menu-size", title: text }, + span({ className: "subitem-label" }, text) + ); +} + +// List of properties of the timing info we want to create boxes for +const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"]; + +function timingBoxes(item) { + const { eventTimings, totalTime, fromCache, fromServiceWorker } = item; + let boxes = []; + + if (fromCache || fromServiceWorker) { + return boxes; + } + + if (eventTimings) { + // Add a set of boxes representing timing information. + for (let key of TIMING_KEYS) { + let width = eventTimings.timings[key]; + + // Don't render anything if it surely won't be visible. + // One millisecond == one unscaled pixel. + if (width > 0) { + boxes.push(div({ + key, + className: "requests-menu-timings-box " + key, + style: { width } + })); + } + } + } + + if (typeof totalTime == "number") { + let text = L10N.getFormatStr("networkMenu.totalMS", totalTime); + boxes.push(div({ + key: "total", + className: "requests-menu-timings-total", + title: text + }, text)); + } + + return boxes; +} + +function WaterfallColumn(item, firstRequestStartedMillis) { + const startedDeltaMillis = item.startedMillis - firstRequestStartedMillis; + const paddingInlineStart = `${startedDeltaMillis}px`; + + return div({ className: "requests-menu-subitem requests-menu-waterfall" }, + div( + { className: "requests-menu-timings", style: { paddingInlineStart } }, + timingBoxes(item) + ) + ); +} + +module.exports = RequestListItem; diff --git a/devtools/client/netmonitor/components/request-list-tooltip.js b/devtools/client/netmonitor/components/request-list-tooltip.js new file mode 100644 index 000000000000..2bea502cf9e3 --- /dev/null +++ b/devtools/client/netmonitor/components/request-list-tooltip.js @@ -0,0 +1,107 @@ +/* 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/. */ + +/* globals gNetwork, NetMonitorController */ + +"use strict"; + +const { Task } = require("devtools/shared/task"); +const { formDataURI } = require("../request-utils"); +const { WEBCONSOLE_L10N } = require("../l10n"); +const { setImageTooltip, + getImageDimensions } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper"); + +// px +const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400; +// px +const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600; + +const HTML_NS = "http://www.w3.org/1999/xhtml"; + +const setTooltipImageContent = Task.async(function* (tooltip, itemEl, requestItem) { + let { mimeType, text, encoding } = requestItem.responseContent.content; + + if (!mimeType || !mimeType.includes("image/")) { + return false; + } + + let string = yield gNetwork.getString(text); + let src = formDataURI(mimeType, encoding, string); + let maxDim = REQUESTS_TOOLTIP_IMAGE_MAX_DIM; + let { naturalWidth, naturalHeight } = yield getImageDimensions(tooltip.doc, src); + let options = { maxDim, naturalWidth, naturalHeight }; + setImageTooltip(tooltip, tooltip.doc, src, options); + + return itemEl.querySelector(".requests-menu-icon"); +}); + +const setTooltipStackTraceContent = Task.async(function* (tooltip, requestItem) { + let {stacktrace} = requestItem.cause; + + if (!stacktrace || stacktrace.length == 0) { + return false; + } + + let doc = tooltip.doc; + let el = doc.createElementNS(HTML_NS, "div"); + el.className = "stack-trace-tooltip devtools-monospace"; + + for (let f of stacktrace) { + let { functionName, filename, lineNumber, columnNumber, asyncCause } = f; + + if (asyncCause) { + // if there is asyncCause, append a "divider" row into the trace + let asyncFrameEl = doc.createElementNS(HTML_NS, "div"); + asyncFrameEl.className = "stack-frame stack-frame-async"; + asyncFrameEl.textContent = + WEBCONSOLE_L10N.getFormatStr("stacktrace.asyncStack", asyncCause); + el.appendChild(asyncFrameEl); + } + + // Parse a source name in format "url -> url" + let sourceUrl = filename.split(" -> ").pop(); + + let frameEl = doc.createElementNS(HTML_NS, "div"); + frameEl.className = "stack-frame stack-frame-call"; + + let funcEl = doc.createElementNS(HTML_NS, "span"); + funcEl.className = "stack-frame-function-name"; + funcEl.textContent = + functionName || WEBCONSOLE_L10N.getStr("stacktrace.anonymousFunction"); + frameEl.appendChild(funcEl); + + let sourceEl = doc.createElementNS(HTML_NS, "span"); + sourceEl.className = "stack-frame-source-name"; + frameEl.appendChild(sourceEl); + + let sourceInnerEl = doc.createElementNS(HTML_NS, "span"); + sourceInnerEl.className = "stack-frame-source-name-inner"; + sourceEl.appendChild(sourceInnerEl); + + sourceInnerEl.textContent = sourceUrl; + sourceInnerEl.title = sourceUrl; + + let lineEl = doc.createElementNS(HTML_NS, "span"); + lineEl.className = "stack-frame-line"; + lineEl.textContent = `:${lineNumber}:${columnNumber}`; + sourceInnerEl.appendChild(lineEl); + + frameEl.addEventListener("click", () => { + // hide the tooltip immediately, not after delay + tooltip.hide(); + NetMonitorController.viewSourceInDebugger(filename, lineNumber); + }, false); + + el.appendChild(frameEl); + } + + tooltip.setContent(el, {width: REQUESTS_TOOLTIP_STACK_TRACE_WIDTH}); + + return true; +}); + +module.exports = { + setTooltipImageContent, + setTooltipStackTraceContent, +}; diff --git a/devtools/client/netmonitor/components/request-list.js b/devtools/client/netmonitor/components/request-list.js new file mode 100644 index 000000000000..7048fdb4b629 --- /dev/null +++ b/devtools/client/netmonitor/components/request-list.js @@ -0,0 +1,34 @@ +/* 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/. */ + +"use strict"; + +const { createFactory, PropTypes, DOM } = require("devtools/client/shared/vendor/react"); +const { div } = DOM; +const { connect } = require("devtools/client/shared/vendor/react-redux"); +const RequestListHeader = createFactory(require("./request-list-header")); +const RequestListEmptyNotice = createFactory(require("./request-list-empty")); +const RequestListContent = createFactory(require("./request-list-content")); + +/** + * Renders the request list - header, empty text, the actual content with rows + */ +const RequestList = function ({ isEmpty }) { + return div({ className: "request-list-container" }, + RequestListHeader(), + isEmpty ? RequestListEmptyNotice() : RequestListContent() + ); +}; + +RequestList.displayName = "RequestList"; + +RequestList.propTypes = { + isEmpty: PropTypes.bool.isRequired, +}; + +module.exports = connect( + state => ({ + isEmpty: state.requests.requests.isEmpty() + }) +)(RequestList); diff --git a/devtools/client/netmonitor/components/summary-button.js b/devtools/client/netmonitor/components/summary-button.js index 9465d3147b6c..7b8980d4ec5c 100644 --- a/devtools/client/netmonitor/components/summary-button.js +++ b/devtools/client/netmonitor/components/summary-button.js @@ -14,7 +14,7 @@ const { DOM, PropTypes } = require("devtools/client/shared/vendor/react"); const { connect } = require("devtools/client/shared/vendor/react-redux"); const { PluralForm } = require("devtools/shared/plural-form"); const { L10N } = require("../l10n"); -const { getSummary } = require("../selectors/index"); +const { getDisplayedRequestsSummary } = require("../selectors/index"); const { button, span } = DOM; @@ -22,14 +22,12 @@ function SummaryButton({ summary, triggerSummary, }) { - let { count, totalBytes, totalMillis } = summary; + let { count, bytes, millis } = summary; const text = (count === 0) ? L10N.getStr("networkMenu.empty") : PluralForm.get(count, L10N.getStr("networkMenu.summary")) .replace("#1", count) - .replace("#2", L10N.numberWithDecimals(totalBytes / 1024, - CONTENT_SIZE_DECIMALS)) - .replace("#3", L10N.numberWithDecimals(totalMillis / 1000, - REQUEST_TIME_DECIMALS)); + .replace("#2", L10N.numberWithDecimals(bytes / 1024, CONTENT_SIZE_DECIMALS)) + .replace("#3", L10N.numberWithDecimals(millis / 1000, REQUEST_TIME_DECIMALS)); return button({ id: "requests-menu-network-summary-button", @@ -47,7 +45,7 @@ SummaryButton.propTypes = { module.exports = connect( (state) => ({ - summary: getSummary(state), + summary: getDisplayedRequestsSummary(state), }), (dispatch) => ({ triggerSummary: () => { diff --git a/devtools/client/netmonitor/components/toggle-button.js b/devtools/client/netmonitor/components/toggle-button.js index 2a3cea027529..57d7f3a924e1 100644 --- a/devtools/client/netmonitor/components/toggle-button.js +++ b/devtools/client/netmonitor/components/toggle-button.js @@ -2,21 +2,20 @@ * 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/. */ -/* globals NetMonitorView */ - "use strict"; const { DOM, PropTypes } = require("devtools/client/shared/vendor/react"); const { connect } = require("devtools/client/shared/vendor/react-redux"); const { L10N } = require("../l10n"); const Actions = require("../actions/index"); +const { isSidebarToggleButtonDisabled } = require("../selectors/index"); const { button } = DOM; function ToggleButton({ disabled, open, - triggerSidebar, + onToggle, }) { let className = ["devtools-button"]; if (!open) { @@ -32,34 +31,21 @@ function ToggleButton({ title, disabled, tabIndex: "0", - onMouseDown: triggerSidebar, + onMouseDown: onToggle, }); } ToggleButton.propTypes = { disabled: PropTypes.bool.isRequired, - triggerSidebar: PropTypes.func.isRequired, + onToggle: PropTypes.func.isRequired, }; module.exports = connect( (state) => ({ - disabled: state.requests.items.length === 0, - open: state.ui.sidebar.open, + disabled: isSidebarToggleButtonDisabled(state), + open: state.ui.sidebarOpen, }), (dispatch) => ({ - triggerSidebar: () => { - dispatch(Actions.toggleSidebar()); - - let requestsMenu = NetMonitorView.RequestsMenu; - let selectedIndex = requestsMenu.selectedIndex; - - // Make sure there's a selection if the button is pressed, to avoid - // showing an empty network details pane. - if (selectedIndex == -1 && requestsMenu.itemCount) { - requestsMenu.selectedIndex = 0; - } else { - requestsMenu.selectedIndex = -1; - } - }, + onToggle: () => dispatch(Actions.toggleSidebar()) }) )(ToggleButton); diff --git a/devtools/client/netmonitor/constants.js b/devtools/client/netmonitor/constants.js index 48f520e64401..9ee9894791b8 100644 --- a/devtools/client/netmonitor/constants.js +++ b/devtools/client/netmonitor/constants.js @@ -11,12 +11,23 @@ const general = { }; const actionTypes = { + BATCH_ACTIONS: "BATCH_ACTIONS", + BATCH_ENABLE: "BATCH_ENABLE", + ADD_TIMING_MARKER: "ADD_TIMING_MARKER", + CLEAR_TIMING_MARKERS: "CLEAR_TIMING_MARKERS", + ADD_REQUEST: "ADD_REQUEST", + UPDATE_REQUEST: "UPDATE_REQUEST", + CLEAR_REQUESTS: "CLEAR_REQUESTS", + CLONE_SELECTED_REQUEST: "CLONE_SELECTED_REQUEST", + REMOVE_SELECTED_CUSTOM_REQUEST: "REMOVE_SELECTED_CUSTOM_REQUEST", + SELECT_REQUEST: "SELECT_REQUEST", + PRESELECT_REQUEST: "PRESELECT_REQUEST", + SORT_BY: "SORT_BY", TOGGLE_FILTER_TYPE: "TOGGLE_FILTER_TYPE", ENABLE_FILTER_TYPE_ONLY: "ENABLE_FILTER_TYPE_ONLY", SET_FILTER_TEXT: "SET_FILTER_TEXT", OPEN_SIDEBAR: "OPEN_SIDEBAR", - TOGGLE_SIDEBAR: "TOGGLE_SIDEBAR", - UPDATE_REQUESTS: "UPDATE_REQUESTS", + WATERFALL_RESIZE: "WATERFALL_RESIZE", }; module.exports = Object.assign({}, general, actionTypes); diff --git a/devtools/client/netmonitor/custom-request-view.js b/devtools/client/netmonitor/custom-request-view.js index 06871a61f483..f7ebf27e3d13 100644 --- a/devtools/client/netmonitor/custom-request-view.js +++ b/devtools/client/netmonitor/custom-request-view.js @@ -7,12 +7,11 @@ "use strict"; const { Task } = require("devtools/shared/task"); -const { - writeHeaderText, - getKeyWithEvent, - getUrlQuery, - parseQueryString, -} = require("./request-utils"); +const { writeHeaderText, + getKeyWithEvent, + getUrlQuery, + parseQueryString } = require("./request-utils"); +const Actions = require("./actions/index"); /** * Functions handling the custom request view. @@ -76,37 +75,41 @@ CustomRequestView.prototype = { */ onUpdate: function (field) { let selectedItem = NetMonitorView.RequestsMenu.selectedItem; + let store = NetMonitorView.RequestsMenu.store; let value; switch (field) { case "method": value = $("#custom-method-value").value.trim(); - selectedItem.attachment.method = value; + store.dispatch(Actions.updateRequest(selectedItem.id, { method: value })); break; case "url": value = $("#custom-url-value").value; this.updateCustomQuery(value); - selectedItem.attachment.url = value; + store.dispatch(Actions.updateRequest(selectedItem.id, { url: value })); break; case "query": let query = $("#custom-query-value").value; this.updateCustomUrl(query); - field = "url"; value = $("#custom-url-value").value; - selectedItem.attachment.url = value; + store.dispatch(Actions.updateRequest(selectedItem.id, { url: value })); break; case "body": value = $("#custom-postdata-value").value; - selectedItem.attachment.requestPostData = { postData: { text: value } }; + store.dispatch(Actions.updateRequest(selectedItem.id, { + requestPostData: { + postData: { text: value } + } + })); break; case "headers": let headersText = $("#custom-headers-value").value; value = parseHeadersText(headersText); - selectedItem.attachment.requestHeaders = { headers: value }; + store.dispatch(Actions.updateRequest(selectedItem.id, { + requestHeaders: { headers: value } + })); break; } - - NetMonitorView.RequestsMenu.updateMenuView(selectedItem, field, value); }, /** @@ -161,7 +164,7 @@ function parseHeadersText(text) { * Parse readable text list of a query string. * * @param string text - * Text of query string represetation + * Text of query string representation * @return array * Array of query params {name, value} */ diff --git a/devtools/client/netmonitor/details-view.js b/devtools/client/netmonitor/details-view.js index 97687323a131..00303aebf2e4 100644 --- a/devtools/client/netmonitor/details-view.js +++ b/devtools/client/netmonitor/details-view.js @@ -267,10 +267,6 @@ DetailsView.prototype = { // Tab is selected but not dirty. We're done here. populated[tab] = true; window.emit(EVENTS.TAB_UPDATED); - - if (NetMonitorController.isConnected()) { - NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible(); - } } } else if (viewState.dirty[tab]) { // Tab is dirty but no longer selected. Don't refresh it now, it'll be @@ -328,7 +324,7 @@ DetailsView.prototype = { } else { code = data.status; } - $("#headers-summary-status-circle").setAttribute("code", code); + $("#headers-summary-status-circle").setAttribute("data-code", code); $("#headers-summary-status-value").setAttribute("value", data.status + " " + data.statusText); $("#headers-summary-status").removeAttribute("hidden"); diff --git a/devtools/client/netmonitor/har/har-builder.js b/devtools/client/netmonitor/har/har-builder.js index 565a4be0973c..698be8dcfd01 100644 --- a/devtools/client/netmonitor/har/har-builder.js +++ b/devtools/client/netmonitor/har/har-builder.js @@ -63,9 +63,7 @@ HarBuilder.prototype = { let log = this.buildLog(); // Build entries. - let items = this._options.items; - for (let i = 0; i < items.length; i++) { - let file = items[i].attachment; + for (let file of this._options.items) { log.entries.push(this.buildEntry(log, file)); } diff --git a/devtools/client/netmonitor/har/har-collector.js b/devtools/client/netmonitor/har/har-collector.js index a55326a51e16..315066bf7572 100644 --- a/devtools/client/netmonitor/har/har-collector.js +++ b/devtools/client/netmonitor/har/har-collector.js @@ -201,9 +201,7 @@ HarCollector.prototype = { this.files.set(actor, file); // Mimic the Net panel data structure - this.items.push({ - attachment: file - }); + this.items.push(file); }, onNetworkEventUpdate: function (type, packet) { diff --git a/devtools/client/netmonitor/middleware/batching.js b/devtools/client/netmonitor/middleware/batching.js new file mode 100644 index 000000000000..bf53d787bb0c --- /dev/null +++ b/devtools/client/netmonitor/middleware/batching.js @@ -0,0 +1,132 @@ +/* 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/. */ + +"use strict"; + +const { BATCH_ACTIONS, BATCH_ENABLE, BATCH_RESET } = require("../constants"); + +// ms +const REQUESTS_REFRESH_RATE = 50; + +/** + * Middleware that watches for actions with a "batch = true" value in their meta field. + * These actions are queued and dispatched as one batch after a timeout. + * Special actions that are handled by this middleware: + * - BATCH_ENABLE can be used to enable and disable the batching. + * - BATCH_RESET discards the actions that are currently in the queue. + */ +function batchingMiddleware(store) { + return next => { + let queuedActions = []; + let enabled = true; + let flushTask = null; + + return action => { + if (action.type === BATCH_ENABLE) { + return setEnabled(action.enabled); + } + + if (action.type === BATCH_RESET) { + return resetQueue(); + } + + if (action.meta && action.meta.batch) { + if (!enabled) { + next(action); + return Promise.resolve(); + } + + queuedActions.push(action); + + if (!flushTask) { + flushTask = new DelayedTask(flushActions, REQUESTS_REFRESH_RATE); + } + + return flushTask.promise; + } + + return next(action); + }; + + function setEnabled(value) { + enabled = value; + + // If disabling the batching, flush the actions that have been queued so far + if (!enabled && flushTask) { + flushTask.runNow(); + } + } + + function resetQueue() { + queuedActions = []; + + if (flushTask) { + flushTask.cancel(); + flushTask = null; + } + } + + function flushActions() { + const actions = queuedActions; + queuedActions = []; + + next({ + type: BATCH_ACTIONS, + actions, + }); + + flushTask = null; + } + }; +} + +/** + * Create a delayed task that calls the specified task function after a delay. + */ +function DelayedTask(taskFn, delay) { + this._promise = new Promise((resolve, reject) => { + this.runTask = (cancel) => { + if (cancel) { + reject("Task cancelled"); + } else { + taskFn(); + resolve(); + } + this.runTask = null; + }; + this.timeout = setTimeout(this.runTask, delay); + }).catch(console.error); +} + +DelayedTask.prototype = { + /** + * Return a promise that is resolved after the task is performed or canceled. + */ + get promise() { + return this._promise; + }, + + /** + * Cancel the execution of the task. + */ + cancel() { + clearTimeout(this.timeout); + if (this.runTask) { + this.runTask(true); + } + }, + + /** + * Execute the scheduled task immediately, without waiting for the timeout. + * Resolves the promise correctly. + */ + runNow() { + clearTimeout(this.timeout); + if (this.runTask) { + this.runTask(); + } + } +}; + +module.exports = batchingMiddleware; diff --git a/devtools/client/netmonitor/middleware/moz.build b/devtools/client/netmonitor/middleware/moz.build new file mode 100644 index 000000000000..19c8f8b69de3 --- /dev/null +++ b/devtools/client/netmonitor/middleware/moz.build @@ -0,0 +1,7 @@ +# 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/. + +DevToolsModules( + 'batching.js', +) diff --git a/devtools/client/netmonitor/moz.build b/devtools/client/netmonitor/moz.build index b80c8726a458..8d9387a81329 100644 --- a/devtools/client/netmonitor/moz.build +++ b/devtools/client/netmonitor/moz.build @@ -6,8 +6,10 @@ DIRS += [ 'actions', 'components', 'har', + 'middleware', 'reducers', - 'selectors' + 'selectors', + 'utils', ] DevToolsModules( @@ -27,6 +29,7 @@ DevToolsModules( 'sort-predicates.js', 'store.js', 'toolbar-view.js', + 'waterfall-background.js', ) BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] diff --git a/devtools/client/netmonitor/netmonitor-controller.js b/devtools/client/netmonitor/netmonitor-controller.js index 2cd595a30d9d..a6d48d121f7b 100644 --- a/devtools/client/netmonitor/netmonitor-controller.js +++ b/devtools/client/netmonitor/netmonitor-controller.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* eslint-disable mozilla/reject-some-requires */ -/* globals window, document, NetMonitorView, gStore, Actions */ +/* globals window, NetMonitorView, gStore, Actions */ /* exported loader */ "use strict"; @@ -41,21 +41,17 @@ const EventEmitter = require("devtools/shared/event-emitter"); const Editor = require("devtools/client/sourceeditor/editor"); const {TimelineFront} = require("devtools/shared/fronts/timeline"); const {Task} = require("devtools/shared/task"); -const {Prefs} = require("./prefs"); const {EVENTS} = require("./events"); const Actions = require("./actions/index"); +const { getDisplayedRequestById } = require("./selectors/index"); XPCOMUtils.defineConstant(this, "EVENTS", EVENTS); XPCOMUtils.defineConstant(this, "ACTIVITY_TYPE", ACTIVITY_TYPE); XPCOMUtils.defineConstant(this, "Editor", Editor); -XPCOMUtils.defineConstant(this, "Prefs", Prefs); XPCOMUtils.defineLazyModuleGetter(this, "Chart", "resource://devtools/client/shared/widgets/Chart.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper", - "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper"); - /** * Object defining the network monitor controller components. */ @@ -91,6 +87,7 @@ var NetMonitorController = { } this._shutdown = promise.defer(); { + gStore.dispatch(Actions.batchReset()); NetMonitorView.destroy(); this.TargetEventsHandler.disconnect(); this.NetworkEventsHandler.disconnect(); @@ -287,19 +284,18 @@ var NetMonitorController = { let deferred = promise.defer(); let request = null; let inspector = function () { - let predicate = i => i.value === requestId; - request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate); + request = getDisplayedRequestById(gStore.getState(), requestId); if (!request) { // Reset filters so that the request is visible. gStore.dispatch(Actions.toggleFilterType("all")); - request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate); + request = getDisplayedRequestById(gStore.getState(), requestId); } // If the request was found, select it. Otherwise this function will be // called again once new requests arrive. if (request) { window.off(EVENTS.REQUEST_ADDED, inspector); - NetMonitorView.RequestsMenu.selectedItem = request; + gStore.dispatch(Actions.selectRequest(request.id)); deferred.resolve(); } }; @@ -398,14 +394,14 @@ TargetEventsHandler.prototype = { // Reset UI. if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) { NetMonitorView.RequestsMenu.reset(); - NetMonitorView.Sidebar.toggle(false); + } else { + // If the log is persistent, just clear all accumulated timing markers. + gStore.dispatch(Actions.clearTimingMarkers()); } // Switch to the default network traffic inspector view. if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) { NetMonitorView.showNetworkInspectorView(); } - // Clear any accumulated markers. - NetMonitorController.NetworkEventsHandler.clearMarkers(); window.emit(EVENTS.TARGET_WILL_NAVIGATE); break; @@ -429,8 +425,6 @@ TargetEventsHandler.prototype = { * Functions handling target network events. */ function NetworkEventsHandler() { - this._markers = []; - this._onNetworkEvent = this._onNetworkEvent.bind(this); this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); this._onDocLoadingMarker = this._onDocLoadingMarker.bind(this); @@ -456,19 +450,6 @@ NetworkEventsHandler.prototype = { return NetMonitorController.timelineFront; }, - get firstDocumentDOMContentLoadedTimestamp() { - let marker = this._markers.filter(e => { - return e.name == "document::DOMContentLoaded"; - })[0]; - - return marker ? marker.unixTime / 1000 : -1; - }, - - get firstDocumentLoadTimestamp() { - let marker = this._markers.filter(e => e.name == "document::Load")[0]; - return marker ? marker.unixTime / 1000 : -1; - }, - /** * Connect to the current target client. */ @@ -525,7 +506,7 @@ NetworkEventsHandler.prototype = { */ _onDocLoadingMarker: function (marker) { window.emit(EVENTS.TIMELINE_EVENT, marker); - this._markers.push(marker); + gStore.dispatch(Actions.addTimingMarker(marker)); }, /** @@ -547,8 +528,7 @@ NetworkEventsHandler.prototype = { } = networkInfo; NetMonitorView.RequestsMenu.addRequest( - actor, startedDateTime, method, url, isXHR, cause, fromCache, - fromServiceWorker + actor, {startedDateTime, method, url, isXHR, cause, fromCache, fromServiceWorker} ); window.emit(EVENTS.NETWORK_EVENT, actor); }, @@ -637,7 +617,7 @@ NetworkEventsHandler.prototype = { _onRequestHeaders: function (response) { NetMonitorView.RequestsMenu.updateRequest(response.from, { requestHeaders: response - }, () => { + }).then(() => { window.emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from); }); }, @@ -651,7 +631,7 @@ NetworkEventsHandler.prototype = { _onRequestCookies: function (response) { NetMonitorView.RequestsMenu.updateRequest(response.from, { requestCookies: response - }, () => { + }).then(() => { window.emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from); }); }, @@ -665,7 +645,7 @@ NetworkEventsHandler.prototype = { _onRequestPostData: function (response) { NetMonitorView.RequestsMenu.updateRequest(response.from, { requestPostData: response - }, () => { + }).then(() => { window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, response.from); }); }, @@ -679,7 +659,7 @@ NetworkEventsHandler.prototype = { _onSecurityInfo: function (response) { NetMonitorView.RequestsMenu.updateRequest(response.from, { securityInfo: response.securityInfo - }, () => { + }).then(() => { window.emit(EVENTS.RECEIVED_SECURITY_INFO, response.from); }); }, @@ -693,7 +673,7 @@ NetworkEventsHandler.prototype = { _onResponseHeaders: function (response) { NetMonitorView.RequestsMenu.updateRequest(response.from, { responseHeaders: response - }, () => { + }).then(() => { window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from); }); }, @@ -707,7 +687,7 @@ NetworkEventsHandler.prototype = { _onResponseCookies: function (response) { NetMonitorView.RequestsMenu.updateRequest(response.from, { responseCookies: response - }, () => { + }).then(() => { window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from); }); }, @@ -721,7 +701,7 @@ NetworkEventsHandler.prototype = { _onResponseContent: function (response) { NetMonitorView.RequestsMenu.updateRequest(response.from, { responseContent: response - }, () => { + }).then(() => { window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from); }); }, @@ -735,18 +715,11 @@ NetworkEventsHandler.prototype = { _onEventTimings: function (response) { NetMonitorView.RequestsMenu.updateRequest(response.from, { eventTimings: response - }, () => { + }).then(() => { window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from); }); }, - /** - * Clears all accumulated markers. - */ - clearMarkers: function () { - this._markers.length = 0; - }, - /** * Fetches the full text of a LongString. * @@ -763,15 +736,6 @@ NetworkEventsHandler.prototype = { } }; -/** - * Returns true if this is document is in RTL mode. - * @return boolean - */ -XPCOMUtils.defineLazyGetter(window, "isRTL", function () { - return window.getComputedStyle(document.documentElement, null) - .direction == "rtl"; -}); - /** * Convenient way of emitting events from the panel window. */ diff --git a/devtools/client/netmonitor/netmonitor-view.js b/devtools/client/netmonitor/netmonitor-view.js index b13b3f91df14..38a33a874d98 100644 --- a/devtools/client/netmonitor/netmonitor-view.js +++ b/devtools/client/netmonitor/netmonitor-view.js @@ -18,6 +18,7 @@ const { ToolbarView } = require("./toolbar-view"); const { SidebarView } = require("./sidebar-view"); const { DetailsView } = require("./details-view"); const { PerformanceStatisticsView } = require("./performance-statistics-view"); +var {Prefs} = require("./prefs"); // Initialize the global redux variables var gStore = configureStore(); @@ -80,12 +81,6 @@ var NetMonitorView = { this._detailsPane.setAttribute("width", Prefs.networkDetailsWidth); this._detailsPane.setAttribute("height", Prefs.networkDetailsHeight); this.toggleDetailsPane({ visible: false }); - - // Disable the performance statistics mode. - if (!Prefs.statistics) { - $("#request-menu-context-perf").hidden = true; - $("#notice-perf-message").hidden = true; - } }, /** @@ -169,7 +164,6 @@ var NetMonitorView = { */ showNetworkInspectorView: function () { this._body.selectedPanel = $("#network-inspector-view"); - this.RequestsMenu._flushWaterfallViews(true); }, /** @@ -192,7 +186,7 @@ var NetMonitorView = { // • The response content size and request total time are necessary for // populating the statistics view. // • The response mime type is used for categorization. - yield whenDataAvailable(requestsView, [ + yield whenDataAvailable(requestsView.store, [ "responseHeaders", "status", "contentSize", "mimeType", "totalTime" ]); } catch (ex) { @@ -200,8 +194,9 @@ var NetMonitorView = { console.error(ex); } - statisticsView.createPrimedCacheChart(requestsView.items); - statisticsView.createEmptyCacheChart(requestsView.items); + const requests = requestsView.store.getState().requests.requests; + statisticsView.createPrimedCacheChart(requests); + statisticsView.createEmptyCacheChart(requests); }); }, @@ -251,34 +246,36 @@ var $all = (selector, target = document) => target.querySelectorAll(selector); /** * Makes sure certain properties are available on all objects in a data store. * - * @param array dataStore - * The request view object from which to fetch the item list. + * @param Store dataStore + * A Redux store for which to check the availability of properties. * @param array mandatoryFields * A list of strings representing properties of objects in dataStore. * @return object * A promise resolved when all objects in dataStore contain the * properties defined in mandatoryFields. */ -function whenDataAvailable(requestsView, mandatoryFields) { - let deferred = promise.defer(); +function whenDataAvailable(dataStore, mandatoryFields) { + return new Promise((resolve, reject) => { + let interval = setInterval(() => { + const { requests } = dataStore.getState().requests; + const allFieldsPresent = !requests.isEmpty() && requests.every( + item => mandatoryFields.every( + field => item.get(field) !== undefined + ) + ); - let interval = setInterval(() => { - const { attachments } = requestsView; - if (attachments.length > 0 && attachments.every(item => { - return mandatoryFields.every(field => field in item); - })) { + if (allFieldsPresent) { + clearInterval(interval); + clearTimeout(timer); + resolve(); + } + }, WDA_DEFAULT_VERIFY_INTERVAL); + + let timer = setTimeout(() => { clearInterval(interval); - clearTimeout(timer); - deferred.resolve(); - } - }, WDA_DEFAULT_VERIFY_INTERVAL); - - let timer = setTimeout(() => { - clearInterval(interval); - deferred.reject(new Error("Timed out while waiting for data")); - }, WDA_DEFAULT_GIVE_UP_TIMEOUT); - - return deferred.promise; + reject(new Error("Timed out while waiting for data")); + }, WDA_DEFAULT_GIVE_UP_TIMEOUT); + }); } /** diff --git a/devtools/client/netmonitor/netmonitor.xul b/devtools/client/netmonitor/netmonitor.xul index 69e2e1d3100d..d0d17b7cd1ec 100644 --- a/devtools/client/netmonitor/netmonitor.xul +++ b/devtools/client/netmonitor/netmonitor.xul @@ -26,188 +26,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -