From f5c979d319202562e9eaefe6afbf7edbf6b67674 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Thu, 30 Apr 2015 07:40:49 +0100 Subject: [PATCH 01/88] Bug 1158112 - Move the Loop modules into a sub-directory and prepare eslint for enabling more rules for Loop. r=dmose --- browser/components/loop/.eslintignore | 10 ++- browser/components/loop/.eslintrc | 90 +++++++++---------- browser/components/loop/content/js/.eslintrc | 15 ++++ .../components/loop/content/shared/.eslintrc | 14 --- browser/components/loop/modules/.eslintrc | 17 ++++ .../loop/{ => modules}/CardDavImporter.jsm | 0 .../loop/{ => modules}/GoogleImporter.jsm | 0 .../loop/{ => modules}/LoopCalls.jsm | 0 .../loop/{ => modules}/LoopContacts.jsm | 0 .../loop/{ => modules}/LoopRooms.jsm | 0 .../loop/{ => modules}/LoopStorage.jsm | 0 .../loop/{ => modules}/MozLoopAPI.jsm | 0 .../loop/{ => modules}/MozLoopPushHandler.jsm | 0 .../loop/{ => modules}/MozLoopService.jsm | 0 .../loop/{ => modules}/MozLoopWorker.js | 0 browser/components/loop/moz.build | 20 ++--- browser/components/loop/standalone/.eslintrc | 14 --- .../components/loop/standalone/package.json | 4 +- browser/components/loop/test/.eslintrc | 7 ++ 19 files changed, 98 insertions(+), 93 deletions(-) create mode 100644 browser/components/loop/content/js/.eslintrc delete mode 100644 browser/components/loop/content/shared/.eslintrc create mode 100644 browser/components/loop/modules/.eslintrc rename browser/components/loop/{ => modules}/CardDavImporter.jsm (100%) rename browser/components/loop/{ => modules}/GoogleImporter.jsm (100%) rename browser/components/loop/{ => modules}/LoopCalls.jsm (100%) rename browser/components/loop/{ => modules}/LoopContacts.jsm (100%) rename browser/components/loop/{ => modules}/LoopRooms.jsm (100%) rename browser/components/loop/{ => modules}/LoopStorage.jsm (100%) rename browser/components/loop/{ => modules}/MozLoopAPI.jsm (100%) rename browser/components/loop/{ => modules}/MozLoopPushHandler.jsm (100%) rename browser/components/loop/{ => modules}/MozLoopService.jsm (100%) rename browser/components/loop/{ => modules}/MozLoopWorker.js (100%) delete mode 100644 browser/components/loop/standalone/.eslintrc create mode 100644 browser/components/loop/test/.eslintrc diff --git a/browser/components/loop/.eslintignore b/browser/components/loop/.eslintignore index 33359955167..c3a825f5e59 100644 --- a/browser/components/loop/.eslintignore +++ b/browser/components/loop/.eslintignore @@ -1,7 +1,9 @@ -# We still need to fix warnings in this file. -MozLoopWorker.js -# This file currently uses es7 features -MozLoopAPI.jsm +# This file currently uses a non-standard (and not on a standards track) +# if statement within catch. +modules/MozLoopWorker.js +# This file currently uses es7 features eslint issue: +# https://github.com/eslint/espree/issues/125 +modules/MozLoopAPI.jsm # Libs we don't need to check content/libs content/shared/libs diff --git a/browser/components/loop/.eslintrc b/browser/components/loop/.eslintrc index 607babeaa46..783d58657e7 100644 --- a/browser/components/loop/.eslintrc +++ b/browser/components/loop/.eslintrc @@ -1,3 +1,5 @@ +// Note: there are extra allowances for files used solely in Firefox desktop, +// see content/js/.eslintrc and modules/.eslintrc { "plugins": [ "react" @@ -5,18 +7,6 @@ "ecmaFeatures": { "forOf": true, "jsx": true, - // These are on for this directory for .jsm and content/js files. - // If adding more items here, consider turning them off for the following - // files if they aren't supported by all browsers. - // content/shared/.eslintrc - // content/standalone/.eslintrc - "blockBindings": true, - "arrowFunctions": true, - "destructuring": true, - "generators": true, - "spread": true, - "restParams": true, - "objectLiteralShorthandMethods": true }, "env": { "browser": true, @@ -43,49 +33,50 @@ // problems they find, one at a time. // Eslint built-in rules are documented at - "camelcase": 0, - "comma-dangle": 0, - "comma-spacing": 0, - "consistent-return": 0, - "curly": 0, - "dot-notation": 0, - "eol-last": 0, - "eqeqeq": 0, - "global-strict": 0, - "key-spacing": 0, - "new-cap": 0, - "no-catch-shadow": 0, - "no-console": 0, - "no-empty": 0, - "no-extra-bind": 0, - "no-extra-boolean-cast": 0, - "no-extra-semi": 0, - "no-multi-spaces": 0, - "no-new": 0, - "no-redeclare": 0, - "no-return-assign": 0, - "no-shadow": 0, - "no-spaced-func": 0, - "no-trailing-spaces": 0, - "no-undef": 0, - "no-underscore-dangle": 0, - "no-unused-expressions": 0, - "no-unused-vars": 0, - "no-use-before-define": 0, - "no-wrap-func": 0, - "quotes": 0, - "semi": 0, - "semi-spacing": 0, - "space-infix-ops": 0, - "space-return-throw-case": 0, - "strict": 0, - "yoda": 0, + "camelcase": 0, // TODO: Remove (use default) + "comma-dangle": 0, // TODO: Remove (use default) + "comma-spacing": 0, // TODO: Remove (use default) + "consistent-return": 0, // TODO: Remove (use default) + "curly": 0, // TODO: Remove (use default) + "dot-notation": 0, // TODO: Remove (use default) + "eol-last": 0, // TODO: Remove (use default) + "eqeqeq": 0, // TBD. Might need to be separate for content & chrome + "global-strict": 0, // Leave as zero (this will be unsupported in eslint 1.0.0) + "key-spacing": 0, // TODO: Remove (use default) + "new-cap": 0, // TODO: Remove (use default) + "no-catch-shadow": 0, // TODO: Remove (use default) + "no-console": 0, // Leave as 0. We use console logging in content code. + "no-empty": 0, // TODO: Remove (use default) + "no-extra-bind": 0, // Leave as 0 + "no-extra-boolean-cast": 0, // TODO: Remove (use default) + "no-extra-semi": 0, // TODO: Remove (use default) + "no-multi-spaces": 0, // TBD. + "no-new": 0, // TODO: Remove (use default) + "no-redeclare": 0, // TODO: Remove (use default) + "no-return-assign": 0, // TODO: Remove (use default) + "no-shadow": 0, // TODO: Remove (use default) + "no-spaced-func": 0, // TODO: Remove (use default) + "no-trailing-spaces": 0, // TODO: Remove (use default) + "no-undef": 0, // TODO: Remove (use default) + "no-underscore-dangle": 0, // Leave as 0. Commonly used for private variables. + "no-unused-expressions": 0, // TODO: Remove (use default) + "no-unused-vars": 0, // TODO: Remove (use default) + "no-use-before-define": 0, // TODO: Remove (use default) + "no-wrap-func": 0, // TODO: Remove (use default) + "quotes": 0, // [2, "double", "avoid-escape"], + "semi": 0, // TODO: Remove (use default) + "semi-spacing": 0, // TODO: Remove (use default) + "space-infix-ops": 0, // TODO: Remove (use default) + "space-return-throw-case": 0, // TODO: Remove (use default) + "strict": 0, // [2, "function"], + "yoda": 0, // [2, "never"], // eslint-plugin-react rules. These are documented at // "react/jsx-quotes": [2, "double", "avoid-escape"], "react/jsx-no-undef": 2, // Need to fix instances where this is failing. "react/jsx-sort-props": 0, + "react/jsx-sort-prop-types": 0, "react/jsx-uses-vars": 2, // Need to fix the couple of instances which don't // currently pass this rule. @@ -101,6 +92,7 @@ "react/react-in-jsx-scope": 0, // These ones we don't want to ever enable "react/display-name": 0, + "react/jsx-boolean-value": 0, "react/no-multi-comp": 0 } } diff --git a/browser/components/loop/content/js/.eslintrc b/browser/components/loop/content/js/.eslintrc new file mode 100644 index 00000000000..bcce1c501c1 --- /dev/null +++ b/browser/components/loop/content/js/.eslintrc @@ -0,0 +1,15 @@ +{ + "ecmaFeatures": { + // These are on for this directory for .jsm and content/js files. + "blockBindings": true, + "arrowFunctions": true, + "destructuring": true, + "generators": true, + "spread": true, + "restParams": true, + "objectLiteralShorthandMethods": true + }, + "rules": { + "generator-star-spacing": [2, "after"] + } +} diff --git a/browser/components/loop/content/shared/.eslintrc b/browser/components/loop/content/shared/.eslintrc deleted file mode 100644 index 19cce829002..00000000000 --- a/browser/components/loop/content/shared/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "ecmaFeatures": { - // Turn off top-level items needed for the jsm files, but not wanted - // for shared code as we can't support them. - "blockBindings": false, - "arrowFunctions": false, - "destructuring": false, - "forOf": true, - "generators": false, - "spread": false, - "restParams": false, - "objectLiteralShorthandMethods": false - } -} diff --git a/browser/components/loop/modules/.eslintrc b/browser/components/loop/modules/.eslintrc new file mode 100644 index 00000000000..bd58bc5876d --- /dev/null +++ b/browser/components/loop/modules/.eslintrc @@ -0,0 +1,17 @@ +{ + "ecmaFeatures": { + "arrowFunctions": true, + "blockBindings": true, + "destructuring": true, + "generators": true, + "restParams": true, + "spread": true, + "objectLiteralShorthandMethods": true, + }, + "rules": { + "generator-star-spacing": [2, "after"], + // We should fix the errors and enable this (set to 2) + "no-var": 0, + "strict": [2, "global"] + } +} diff --git a/browser/components/loop/CardDavImporter.jsm b/browser/components/loop/modules/CardDavImporter.jsm similarity index 100% rename from browser/components/loop/CardDavImporter.jsm rename to browser/components/loop/modules/CardDavImporter.jsm diff --git a/browser/components/loop/GoogleImporter.jsm b/browser/components/loop/modules/GoogleImporter.jsm similarity index 100% rename from browser/components/loop/GoogleImporter.jsm rename to browser/components/loop/modules/GoogleImporter.jsm diff --git a/browser/components/loop/LoopCalls.jsm b/browser/components/loop/modules/LoopCalls.jsm similarity index 100% rename from browser/components/loop/LoopCalls.jsm rename to browser/components/loop/modules/LoopCalls.jsm diff --git a/browser/components/loop/LoopContacts.jsm b/browser/components/loop/modules/LoopContacts.jsm similarity index 100% rename from browser/components/loop/LoopContacts.jsm rename to browser/components/loop/modules/LoopContacts.jsm diff --git a/browser/components/loop/LoopRooms.jsm b/browser/components/loop/modules/LoopRooms.jsm similarity index 100% rename from browser/components/loop/LoopRooms.jsm rename to browser/components/loop/modules/LoopRooms.jsm diff --git a/browser/components/loop/LoopStorage.jsm b/browser/components/loop/modules/LoopStorage.jsm similarity index 100% rename from browser/components/loop/LoopStorage.jsm rename to browser/components/loop/modules/LoopStorage.jsm diff --git a/browser/components/loop/MozLoopAPI.jsm b/browser/components/loop/modules/MozLoopAPI.jsm similarity index 100% rename from browser/components/loop/MozLoopAPI.jsm rename to browser/components/loop/modules/MozLoopAPI.jsm diff --git a/browser/components/loop/MozLoopPushHandler.jsm b/browser/components/loop/modules/MozLoopPushHandler.jsm similarity index 100% rename from browser/components/loop/MozLoopPushHandler.jsm rename to browser/components/loop/modules/MozLoopPushHandler.jsm diff --git a/browser/components/loop/MozLoopService.jsm b/browser/components/loop/modules/MozLoopService.jsm similarity index 100% rename from browser/components/loop/MozLoopService.jsm rename to browser/components/loop/modules/MozLoopService.jsm diff --git a/browser/components/loop/MozLoopWorker.js b/browser/components/loop/modules/MozLoopWorker.js similarity index 100% rename from browser/components/loop/MozLoopWorker.js rename to browser/components/loop/modules/MozLoopWorker.js diff --git a/browser/components/loop/moz.build b/browser/components/loop/moz.build index 5ed65838c6b..a2cbc2469d8 100644 --- a/browser/components/loop/moz.build +++ b/browser/components/loop/moz.build @@ -13,18 +13,18 @@ BROWSER_CHROME_MANIFESTS += [ ] EXTRA_JS_MODULES.loop += [ - 'CardDavImporter.jsm', 'content/shared/js/crypto.js', 'content/shared/js/utils.js', - 'GoogleImporter.jsm', - 'LoopCalls.jsm', - 'LoopContacts.jsm', - 'LoopRooms.jsm', - 'LoopStorage.jsm', - 'MozLoopAPI.jsm', - 'MozLoopPushHandler.jsm', - 'MozLoopService.jsm', - 'MozLoopWorker.js', + 'modules/CardDavImporter.jsm', + 'modules/GoogleImporter.jsm', + 'modules/LoopCalls.jsm', + 'modules/LoopContacts.jsm', + 'modules/LoopRooms.jsm', + 'modules/LoopStorage.jsm', + 'modules/MozLoopAPI.jsm', + 'modules/MozLoopPushHandler.jsm', + 'modules/MozLoopService.jsm', + 'modules/MozLoopWorker.js', ] with Files('**'): diff --git a/browser/components/loop/standalone/.eslintrc b/browser/components/loop/standalone/.eslintrc deleted file mode 100644 index 19cce829002..00000000000 --- a/browser/components/loop/standalone/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "ecmaFeatures": { - // Turn off top-level items needed for the jsm files, but not wanted - // for shared code as we can't support them. - "blockBindings": false, - "arrowFunctions": false, - "destructuring": false, - "forOf": true, - "generators": false, - "spread": false, - "restParams": false, - "objectLiteralShorthandMethods": false - } -} diff --git a/browser/components/loop/standalone/package.json b/browser/components/loop/standalone/package.json index d3e3c23e0ee..b7214f8c5a3 100644 --- a/browser/components/loop/standalone/package.json +++ b/browser/components/loop/standalone/package.json @@ -12,8 +12,8 @@ }, "dependencies": {}, "devDependencies": { - "eslint": "0.18.x", - "eslint-plugin-react": "2.0.x", + "eslint": "0.20.x", + "eslint-plugin-react": "2.2.x", "express": "3.x" }, "scripts": { diff --git a/browser/components/loop/test/.eslintrc b/browser/components/loop/test/.eslintrc new file mode 100644 index 00000000000..7de540fc5d4 --- /dev/null +++ b/browser/components/loop/test/.eslintrc @@ -0,0 +1,7 @@ +{ + "rules": { + // This is useful for some of the tests, e.g. + // expect(new Foo()).to.Throw(/error/) + "no-new": 0 + } +} From 22d1ca1c4ece45cf58bce30aaea23eaca6a03514 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Thu, 30 Apr 2015 07:40:49 +0100 Subject: [PATCH 02/88] Bug 1146305 - IDN links should be properly displayed in Hello rooms for context in conversations. r=mikedeboer --- .../components/loop/content/js/roomViews.js | 19 +++++++++++-- .../components/loop/content/js/roomViews.jsx | 19 +++++++++++-- .../loop/content/shared/js/utils.js | 28 +++++++++++++++++++ .../content/js/standaloneRoomViews.js | 11 ++++++-- .../content/js/standaloneRoomViews.jsx | 11 ++++++-- .../loop/test/desktop-local/roomViews_test.js | 17 +++++++++++ .../components/loop/test/shared/utils_test.js | 25 +++++++++++++++++ .../standalone/standaloneRoomViews_test.js | 21 ++++++++++++++ 8 files changed, 139 insertions(+), 12 deletions(-) diff --git a/browser/components/loop/content/js/roomViews.js b/browser/components/loop/content/js/roomViews.js index f39a5b1f39a..7416ac62c24 100644 --- a/browser/components/loop/content/js/roomViews.js +++ b/browser/components/loop/content/js/roomViews.js @@ -11,10 +11,11 @@ var loop = loop || {}; loop.roomViews = (function(mozL10n) { "use strict"; - var sharedActions = loop.shared.actions; - var sharedMixins = loop.shared.mixins; var ROOM_STATES = loop.store.ROOM_STATES; var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES; + var sharedActions = loop.shared.actions; + var sharedMixins = loop.shared.mixins; + var sharedUtils = loop.shared.utils; var sharedViews = loop.shared.views; /** @@ -312,13 +313,25 @@ loop.roomViews = (function(mozL10n) { var thumbnail = URL && URL.thumbnail || ""; var URLDescription = URL && URL.description || ""; var location = URL && URL.location || ""; + var locationData = null; + if (location) { + locationData = sharedUtils.formatURL(location); + } + + if (!locationData) { + return null; + } + return ( React.createElement("div", {className: "room-context"}, React.createElement("img", {className: "room-context-thumbnail", src: thumbnail}), React.createElement("div", {className: "room-context-content"}, React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")), React.createElement("div", {className: "room-context-description"}, URLDescription), - React.createElement("a", {className: "room-context-url", href: location, target: "_blank"}, location), + React.createElement("a", {className: "room-context-url", + href: location, + target: "_blank", + title: locationData.location}, locationData.hostname), this.props.roomData.roomDescription ? React.createElement("div", {className: "room-context-comment"}, this.props.roomData.roomDescription) : null, diff --git a/browser/components/loop/content/js/roomViews.jsx b/browser/components/loop/content/js/roomViews.jsx index 294ff6eef57..67552ff62f6 100644 --- a/browser/components/loop/content/js/roomViews.jsx +++ b/browser/components/loop/content/js/roomViews.jsx @@ -11,10 +11,11 @@ var loop = loop || {}; loop.roomViews = (function(mozL10n) { "use strict"; - var sharedActions = loop.shared.actions; - var sharedMixins = loop.shared.mixins; var ROOM_STATES = loop.store.ROOM_STATES; var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES; + var sharedActions = loop.shared.actions; + var sharedMixins = loop.shared.mixins; + var sharedUtils = loop.shared.utils; var sharedViews = loop.shared.views; /** @@ -312,13 +313,25 @@ loop.roomViews = (function(mozL10n) { var thumbnail = URL && URL.thumbnail || ""; var URLDescription = URL && URL.description || ""; var location = URL && URL.location || ""; + var locationData = null; + if (location) { + locationData = sharedUtils.formatURL(location); + } + + if (!locationData) { + return null; + } + return (
{mozL10n.get("context_inroom_label")}
{URLDescription}
- {location} + {locationData.hostname} {this.props.roomData.roomDescription ?
{this.props.roomData.roomDescription}
: null} diff --git a/browser/components/loop/content/shared/js/utils.js b/browser/components/loop/content/shared/js/utils.js index 3c0bbd0343c..e0a1d904966 100644 --- a/browser/components/loop/content/shared/js/utils.js +++ b/browser/components/loop/content/shared/js/utils.js @@ -271,6 +271,33 @@ var inChrome = typeof Components != "undefined" && "utils" in Components; }; } + /** + * Formats a url for display purposes. This includes converting the + * domain to punycode, and then decoding the url. + * + * @param {String} url The url to format. + * @return {Object} An object containing the hostname and full location. + */ + function formatURL(url) { + // We're using new URL to pass this through the browser's ACE/punycode + // processing system. If the browser considers a url to need to be + // punycode encoded for it to be displayed, then new URL will do that for + // us. This saves us needing our own punycode library. + var urlObject; + try { + urlObject = new URL(url); + } catch (ex) { + console.error("Error occurred whilst parsing URL:", ex); + return null; + } + + // Finally, ensure we look good. + return { + hostname: urlObject.hostname, + location: decodeURI(urlObject.href) + }; + } + /** * Generates and opens a mailto: url with call URL information prefilled. * Note: This only works for Desktop. @@ -536,6 +563,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components; ROOM_INFO_FAILURES: ROOM_INFO_FAILURES, composeCallUrlEmail: composeCallUrlEmail, formatDate: formatDate, + formatURL: formatURL, getBoolPreference: getBoolPreference, getOS: getOS, getOSVersion: getOSVersion, diff --git a/browser/components/loop/standalone/content/js/standaloneRoomViews.js b/browser/components/loop/standalone/content/js/standaloneRoomViews.js index 56dd6b59752..de4e7522407 100644 --- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js +++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js @@ -16,6 +16,7 @@ loop.standaloneRoomViews = (function(mozL10n) { var ROOM_STATES = loop.store.ROOM_STATES; var sharedActions = loop.shared.actions; var sharedMixins = loop.shared.mixins; + var sharedUtils = loop.shared.utils; var sharedViews = loop.shared.views; var StandaloneRoomInfoArea = React.createClass({displayName: "StandaloneRoomInfoArea", @@ -248,7 +249,10 @@ loop.standaloneRoomViews = (function(mozL10n) { return null; } - var location = this.props.roomContextUrl.location; + var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location); + if (!locationInfo) { + return null; + } var cx = React.addons.classSet; @@ -262,9 +266,10 @@ loop.standaloneRoomViews = (function(mozL10n) { React.createElement("img", {src: this.props.roomContextUrl.thumbnail}), React.createElement("div", {className: "standalone-context-url-description-wrapper"}, this.props.roomContextUrl.description, - React.createElement("br", null), React.createElement("a", {href: location, + React.createElement("br", null), React.createElement("a", {href: locationInfo.location, onClick: this.recordClick, - target: "_blank"}, location) + target: "_blank", + title: locationInfo.location}, locationInfo.hostname) ) ) ); diff --git a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx index 70169a2187f..cf2c779af28 100644 --- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx +++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx @@ -16,6 +16,7 @@ loop.standaloneRoomViews = (function(mozL10n) { var ROOM_STATES = loop.store.ROOM_STATES; var sharedActions = loop.shared.actions; var sharedMixins = loop.shared.mixins; + var sharedUtils = loop.shared.utils; var sharedViews = loop.shared.views; var StandaloneRoomInfoArea = React.createClass({ @@ -248,7 +249,10 @@ loop.standaloneRoomViews = (function(mozL10n) { return null; } - var location = this.props.roomContextUrl.location; + var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location); + if (!locationInfo) { + return null; + } var cx = React.addons.classSet; @@ -262,9 +266,10 @@ loop.standaloneRoomViews = (function(mozL10n) {
{this.props.roomContextUrl.description} -
{location} + target="_blank" + title={locationInfo.location}>{locationInfo.hostname}
); diff --git a/browser/components/loop/test/desktop-local/roomViews_test.js b/browser/components/loop/test/desktop-local/roomViews_test.js index a3f9b1dd725..5de6359ba94 100644 --- a/browser/components/loop/test/desktop-local/roomViews_test.js +++ b/browser/components/loop/test/desktop-local/roomViews_test.js @@ -242,6 +242,23 @@ describe("loop.roomViews", function () { expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null); }); + + it("should format the context url for display", function() { + sandbox.stub(sharedUtils, "formatURL").returns({ + location: "location", + hostname: "hostname" + }); + + view = mountTestComponent({ + showContext: true, + roomData: { + roomContextUrls: [fakeContextURL] + } + }); + + expect(view.getDOMNode().querySelector(".room-context-url").textContent) + .eql("hostname"); + }); }); }); diff --git a/browser/components/loop/test/shared/utils_test.js b/browser/components/loop/test/shared/utils_test.js index 3412e117582..b40abccd1f1 100644 --- a/browser/components/loop/test/shared/utils_test.js +++ b/browser/components/loop/test/shared/utils_test.js @@ -145,6 +145,31 @@ describe("loop.shared.utils", function() { }); }); + describe("#formatURL", function() { + it("should decode encoded URIs", function() { + expect(sharedUtils.formatURL("http://invalid.com/?a=Foo%20Bar")) + .eql({ + location: "http://invalid.com/?a=Foo Bar", + hostname: "invalid.com" + }); + }); + + it("should change some idn urls to ascii encoded", function() { + // Note, this is based on the browser's list of what does/doesn't get + // altered for punycode, so if the list changes this could change in the + // future. + expect(sharedUtils.formatURL("http://\u0261oogle.com/")) + .eql({ + location: "http://xn--oogle-qmc.com/", + hostname: "xn--oogle-qmc.com" + }); + }); + + it("should return null if it the url is not valid", function() { + expect(sharedUtils.formatURL("hinvalid//url")).eql(null); + }); + }); + describe("#composeCallUrlEmail", function() { var composeEmail; diff --git a/browser/components/loop/test/standalone/standaloneRoomViews_test.js b/browser/components/loop/test/standalone/standaloneRoomViews_test.js index 37019b9a267..5222452387b 100644 --- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js +++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js @@ -13,6 +13,7 @@ describe("loop.standaloneRoomViews", function() { var FEEDBACK_STATES = loop.store.FEEDBACK_STATES; var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES; var sharedActions = loop.shared.actions; + var sharedUtils = loop.shared.utils; var sandbox, dispatcher, activeRoomStore, feedbackStore, dispatch; @@ -96,6 +97,26 @@ describe("loop.standaloneRoomViews", function() { expect(view.getDOMNode().querySelector(".standalone-context-url")).not.eql(null); }); + it("should format the url for display", function() { + sandbox.stub(sharedUtils, "formatURL").returns({ + location: "location", + hostname: "hostname" + }); + + var view = mountTestComponent({ + roomName: "Mike's room", + roomContextUrls: [{ + description: "Mark's super page", + location: "http://invalid.com", + thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + }] + }); + + expect(view.getDOMNode() + .querySelector(".standalone-context-url-description-wrapper > a").textContent) + .eql("hostname"); + }); + it("should not display context information if no urls are supplied", function() { var view = mountTestComponent({ roomName: "Mike's room" From 4cdd5904adb19f5dde78243d34990c2fb257fd9c Mon Sep 17 00:00:00 2001 From: Martin Tomes Date: Thu, 30 Apr 2015 07:40:50 +0100 Subject: [PATCH 03/88] Bug 1084296 - Add an ellipsis to Loop's "Remove Contact" menu item. r=Standard8 --- browser/components/loop/content/js/contacts.js | 2 +- browser/components/loop/content/js/contacts.jsx | 2 +- browser/locales/en-US/chrome/browser/loop/loop.properties | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/browser/components/loop/content/js/contacts.js b/browser/components/loop/content/js/contacts.js index 6fecc1808a8..bb29539ce5f 100644 --- a/browser/components/loop/content/js/contacts.js +++ b/browser/components/loop/content/js/contacts.js @@ -222,7 +222,7 @@ loop.contacts = (function(_, mozL10n) { "disabled": !this.props.canEdit }), onClick: this.onItemClick, "data-action": "remove"}, React.createElement("i", {className: "icon icon-remove"}), - mozL10n.get("remove_contact_menu_button") + mozL10n.get("remove_contact_menu_button2") ) ) ); diff --git a/browser/components/loop/content/js/contacts.jsx b/browser/components/loop/content/js/contacts.jsx index b78e60e6a99..b77f34eaa03 100644 --- a/browser/components/loop/content/js/contacts.jsx +++ b/browser/components/loop/content/js/contacts.jsx @@ -222,7 +222,7 @@ loop.contacts = (function(_, mozL10n) { "disabled": !this.props.canEdit })} onClick={this.onItemClick} data-action="remove"> - {mozL10n.get("remove_contact_menu_button")} + {mozL10n.get("remove_contact_menu_button2")} ); diff --git a/browser/locales/en-US/chrome/browser/loop/loop.properties b/browser/locales/en-US/chrome/browser/loop/loop.properties index e6fa1dd8d99..4815148dc5b 100644 --- a/browser/locales/en-US/chrome/browser/loop/loop.properties +++ b/browser/locales/en-US/chrome/browser/loop/loop.properties @@ -127,9 +127,9 @@ import_failed_description_simple=Sorry, contact import failed import_failed_description_some=Some contacts could not be imported import_failed_support_button=Help -## LOCALIZATION NOTE(remove_contact_menu_button): Displayed in the contact list in +## LOCALIZATION NOTE(remove_contact_menu_button2): Displayed in the contact list in ## a pop-up menu next to the contact's name. -remove_contact_menu_button=Remove Contact +remove_contact_menu_button2=Remove Contact… ## LOCALIZATION NOTE(confirm_delete_contact_alert): This is an alert that is displayed ## to confirm deletion of a contact. confirm_delete_contact_alert=Are you sure you want to delete this contact? From edb8cdbd2d19fdc471cdb2d46f4d15e773ede638 Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Thu, 30 Apr 2015 17:02:02 +1000 Subject: [PATCH 04/88] Bug 1151666 - avoid multiple verified email timers from running and avoid 'pending' calls in the test. r=zaach --- services/fxaccounts/FxAccounts.jsm | 6 +++ .../tests/xpcshell/test_accounts.js | 46 ++++--------------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm index 3836b7e76b1..deb362f139e 100644 --- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -1068,6 +1068,12 @@ FxAccountsInternal.prototype = { pollEmailStatus: function pollEmailStatus(currentState, sessionToken, why) { log.debug("entering pollEmailStatus: " + why); if (why == "start") { + if (this.currentTimer) { + log.debug("pollEmailStatus starting while existing timer is running"); + clearTimeout(this.currentTimer); + this.currentTimer = null; + } + // If we were already polling, stop and start again. This could happen // if the user requested the verification email to be resent while we // were already polling for receipt of an earlier email. diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js index 97d831d5953..656ec51b75f 100644 --- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -39,11 +39,6 @@ const CONTENT_URL = "http://accounts.example.com/"; Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", PROFILE_SERVER_URL); Services.prefs.setCharPref("identity.fxaccounts.settings.uri", CONTENT_URL); - -function run_test() { - run_next_test(); -} - /* * The FxAccountsClient communicates with the remote Firefox * Accounts auth server. Mock the server calls, with a little @@ -264,8 +259,6 @@ add_task(function* test_getCertificate() { // Sanity-check that our mocked client is working correctly add_test(function test_client_mock() { - do_test_pending(); - let fxa = new MockFxAccounts(); let client = fxa.internal.fxAccountsClient; do_check_eq(client._verified, false); @@ -275,7 +268,6 @@ add_test(function test_client_mock() { client.recoveryEmailStatus() .then(response => { do_check_eq(response.verified, false); - do_test_finished(); run_next_test(); }); }); @@ -285,8 +277,6 @@ add_test(function test_client_mock() { // Polling should detect that the email is verified, and eventually // 'onverified' should be observed add_test(function test_verification_poll() { - do_test_pending(); - let fxa = new MockFxAccounts(); let test_user = getTestUser("francine"); let login_notification_received = false; @@ -299,7 +289,6 @@ add_test(function test_verification_poll() { do_check_eq(user.verified, true); do_check_eq(user.email, test_user.email); do_check_true(login_notification_received); - do_test_finished(); run_next_test(); }); }); @@ -326,8 +315,6 @@ add_test(function test_verification_poll() { // poll should time out. No verifiedlogin event should be observed, and the // internal whenVerified promise should be rejected add_test(function test_polling_timeout() { - do_test_pending(); - // This test could be better - the onverified observer might fire on // somebody else's stack, and we're not making sure that we're not receiving // such a message. In other words, this tests either failure, or success, but @@ -351,15 +338,13 @@ add_test(function test_polling_timeout() { }, (fail) => { removeObserver(); - do_test_finished(); - run_next_test(); + fxa.signOut().then(run_next_test); } ); }); }); add_test(function test_getKeys() { - do_test_pending(); let fxa = new MockFxAccounts(); let user = getTestUser("eusebius"); @@ -384,7 +369,6 @@ add_test(function test_getKeys() { do_check_eq(user.kB, expandHex("66")); do_check_eq(user.keyFetchToken, undefined); do_check_eq(user.unwrapBKey, undefined); - do_test_finished(); run_next_test(); }); }); @@ -394,8 +378,6 @@ add_test(function test_getKeys() { // fetchAndUnwrapKeys with no keyFetchToken should trigger signOut add_test(function test_fetchAndUnwrapKeys_no_token() { - do_test_pending(); - let fxa = new MockFxAccounts(); let user = getTestUser("lettuce.protheroe"); delete user.keyFetchToken @@ -403,7 +385,6 @@ add_test(function test_fetchAndUnwrapKeys_no_token() { makeObserver(ONLOGOUT_NOTIFICATION, function() { log.debug("test_fetchAndUnwrapKeys_no_token observed logout"); fxa.internal.getUserAccountData().then(user => { - do_test_finished(); run_next_test(); }); }); @@ -424,8 +405,6 @@ add_test(function test_fetchAndUnwrapKeys_no_token() { // signs in with a verified email. Ensure that no sign-in events are triggered // on Alice's behalf. In the end, Bob should be the signed-in user. add_test(function test_overlapping_signins() { - do_test_pending(); - let fxa = new MockFxAccounts(); let alice = getTestUser("alice"); let bob = getTestUser("bob"); @@ -436,7 +415,6 @@ add_test(function test_overlapping_signins() { fxa.internal.getUserAccountData().then(user => { do_check_eq(user.email, bob.email); do_check_eq(user.verified, true); - do_test_finished(); run_next_test(); }); }); @@ -614,7 +592,7 @@ add_test(function test_accountStatus() { (result) => { do_check_false(result); fxa.internal.fxAccountsClient._deletedOnServer = false; - run_next_test(); + fxa.signOut().then(run_next_test); } ); } @@ -667,7 +645,6 @@ add_test(function test_resend_email() { }); add_test(function test_sign_out() { - do_test_pending(); let fxa = new MockFxAccounts(); let remoteSignOutCalled = false; let client = fxa.internal.fxAccountsClient; @@ -678,7 +655,6 @@ add_test(function test_sign_out() { fxa.internal.getUserAccountData().then(user => { do_check_eq(user, null); do_check_true(remoteSignOutCalled); - do_test_finished(); run_next_test(); }); }); @@ -686,7 +662,6 @@ add_test(function test_sign_out() { }); add_test(function test_sign_out_with_remote_error() { - do_test_pending(); let fxa = new MockFxAccounts(); let client = fxa.internal.fxAccountsClient; let remoteSignOutCalled = false; @@ -698,7 +673,6 @@ add_test(function test_sign_out_with_remote_error() { fxa.internal.getUserAccountData().then(user => { do_check_eq(user, null); do_check_true(remoteSignOutCalled); - do_test_finished(); run_next_test(); }); }); @@ -848,7 +822,7 @@ add_test(function test_getOAuthToken_invalid_param() { fxa.getOAuthToken() .then(null, err => { do_check_eq(err.message, "INVALID_PARAMETER"); - run_next_test(); + fxa.signOut().then(run_next_test); }); }); @@ -858,7 +832,7 @@ add_test(function test_getOAuthToken_invalid_scope_array() { fxa.getOAuthToken({scope: []}) .then(null, err => { do_check_eq(err.message, "INVALID_PARAMETER"); - run_next_test(); + fxa.signOut().then(run_next_test); }); }); @@ -872,7 +846,7 @@ add_test(function test_getOAuthToken_misconfigure_oauth_uri() { do_check_eq(err.message, "INVALID_PARAMETER"); // revert the pref Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1"); - run_next_test(); + fxa.signOut().then(run_next_test); }); }); @@ -886,7 +860,7 @@ add_test(function test_getOAuthToken_no_account() { fxa.getOAuthToken({ scope: "profile" }) .then(null, err => { do_check_eq(err.message, "NO_ACCOUNT"); - run_next_test(); + fxa.signOut().then(run_next_test); }); }); @@ -898,7 +872,7 @@ add_test(function test_getOAuthToken_unverified() { fxa.getOAuthToken({ scope: "profile" }) .then(null, err => { do_check_eq(err.message, "UNVERIFIED_ACCOUNT"); - run_next_test(); + fxa.signOut().then(run_next_test); }); }); }); @@ -1071,7 +1045,7 @@ add_test(function test_getSignedInUserProfile_error_uses_account_data() { fxa.getSignedInUserProfile() .catch(error => { do_check_eq(error.message, "UNKNOWN_ERROR"); - run_next_test(); + fxa.signOut().then(run_next_test); }); }); @@ -1087,7 +1061,7 @@ add_test(function test_getSignedInUserProfile_unverified_account() { fxa.getSignedInUserProfile() .catch(error => { do_check_eq(error.message, "UNVERIFIED_ACCOUNT"); - run_next_test(); + fxa.signOut().then(run_next_test); }); }); @@ -1103,7 +1077,7 @@ add_test(function test_getSignedInUserProfile_no_account_data() { fxa.getSignedInUserProfile() .catch(error => { do_check_eq(error.message, "NO_ACCOUNT"); - run_next_test(); + fxa.signOut().then(run_next_test); }); }); From 88aa0f4fd2e11c66e4bdb64b7d9c83031c58365d Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Thu, 30 Apr 2015 17:13:40 +1000 Subject: [PATCH 05/88] Bug 1098343 (part 1) - support 'sticky' preferences, meaning a user value is retained even when it matches the default. r=bsmedberg --- modules/libpref/prefapi.cpp | 21 ++- modules/libpref/prefapi.h | 5 +- modules/libpref/prefread.cpp | 12 +- modules/libpref/prefread.h | 6 +- .../libpref/test/unit/data/testPrefSticky.js | 2 + .../test/unit/data/testPrefStickyUser.js | 5 + modules/libpref/test/unit/test_stickyprefs.js | 170 ++++++++++++++++++ modules/libpref/test/unit/xpcshell.ini | 2 + 8 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 modules/libpref/test/unit/data/testPrefSticky.js create mode 100644 modules/libpref/test/unit/data/testPrefStickyUser.js create mode 100644 modules/libpref/test/unit/test_stickyprefs.js diff --git a/modules/libpref/prefapi.cpp b/modules/libpref/prefapi.cpp index 5e82ba9b971..70b0e3a8dd5 100644 --- a/modules/libpref/prefapi.cpp +++ b/modules/libpref/prefapi.cpp @@ -140,7 +140,8 @@ static nsresult pref_DoCallback(const char* changed_pref); enum { kPrefSetDefault = 1, - kPrefForceSet = 2 + kPrefForceSet = 2, + kPrefStickyDefault = 4, }; static nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags); @@ -340,7 +341,8 @@ pref_savePref(PLDHashTable *table, PLDHashEntryHdr *heh, uint32_t i, void *arg) (pref_ValueChanged(pref->defaultPref, pref->userPref, (PrefType) PREF_TYPE(pref)) || - !(pref->flags & PREF_HAS_DEFAULT))) { + !(pref->flags & PREF_HAS_DEFAULT) || + pref->flags & PREF_STICKY_DEFAULT)) { sourcePref = &pref->userPref; } else { if (argData->saveTypes == SAVE_ALL_AND_DEFAULTS) { @@ -778,6 +780,8 @@ nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t { pref_SetValue(&pref->defaultPref, &pref->flags, value, type); pref->flags |= PREF_HAS_DEFAULT; + if (flags & kPrefStickyDefault) + pref->flags |= PREF_STICKY_DEFAULT; if (!PREF_HAS_USER_VALUE(pref)) valueChanged = true; } @@ -787,9 +791,11 @@ nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t } else { - /* If new value is same as the default value, then un-set the user value. + /* If new value is same as the default value and it's not a "sticky" + pref, then un-set the user value. Otherwise, set the user value only if it has changed */ if ((pref->flags & PREF_HAS_DEFAULT) && + !(pref->flags & PREF_STICKY_DEFAULT) && !pref_ValueChanged(pref->defaultPref, value, type) && !(flags & kPrefForceSet)) { @@ -1002,7 +1008,12 @@ void PREF_ReaderCallback(void *closure, const char *pref, PrefValue value, PrefType type, - bool isDefault) + bool isDefault, + bool isStickyDefault) { - pref_HashPref(pref, value, type, isDefault ? kPrefSetDefault : kPrefForceSet); + uint32_t flags = isDefault ? kPrefSetDefault : kPrefForceSet; + if (isDefault && isStickyDefault) { + flags |= kPrefStickyDefault; + } + pref_HashPref(pref, value, type, flags); } diff --git a/modules/libpref/prefapi.h b/modules/libpref/prefapi.h index 675b384c475..f1e412a6781 100644 --- a/modules/libpref/prefapi.h +++ b/modules/libpref/prefapi.h @@ -61,6 +61,8 @@ typedef enum { PREF_INVALID = 0, PREF_LOCKED = 1, PREF_USERSET = 2, PREF_CONFIG = 4, PREF_REMOTE = 8, PREF_LILOCAL = 16, PREF_STRING = 32, PREF_INT = 64, PREF_BOOL = 128, PREF_HAS_DEFAULT = 256, + // pref is default pref with "sticky" semantics + PREF_STICKY_DEFAULT = 512, PREF_VALUETYPE_MASK = (PREF_STRING | PREF_INT | PREF_BOOL) } PrefType; @@ -183,7 +185,8 @@ void PREF_ReaderCallback( void *closure, const char *pref, PrefValue value, PrefType type, - bool isDefault); + bool isDefault, + bool isStickyDefault); #ifdef __cplusplus } diff --git a/modules/libpref/prefread.cpp b/modules/libpref/prefread.cpp index 01612ba9402..6c4d339894f 100644 --- a/modules/libpref/prefread.cpp +++ b/modules/libpref/prefread.cpp @@ -44,6 +44,7 @@ enum { static const char kUserPref[] = "user_pref"; static const char kPref[] = "pref"; +static const char kPrefSticky[] = "sticky_pref"; static const char kTrue[] = "true"; static const char kFalse[] = "false"; @@ -129,7 +130,8 @@ pref_DoCallback(PrefParseState *ps) default: break; } - (*ps->reader)(ps->closure, ps->lb, value, ps->vtype, ps->fdefault); + (*ps->reader)(ps->closure, ps->lb, value, ps->vtype, ps->fdefault, + ps->fstickydefault); return true; } @@ -188,6 +190,7 @@ PREF_ParseBuf(PrefParseState *ps, const char *buf, int bufLen) ps->vb = nullptr; ps->vtype = PREF_INVALID; ps->fdefault = false; + ps->fstickydefault = false; } switch (c) { case '/': /* begin comment block or line? */ @@ -198,7 +201,9 @@ PREF_ParseBuf(PrefParseState *ps, const char *buf, int bufLen) break; case 'u': /* indicating user_pref */ case 'p': /* indicating pref */ - ps->smatch = (c == 'u' ? kUserPref : kPref); + case 's': /* indicating sticky_pref */ + ps->smatch = (c == 'u' ? kUserPref : + (c == 's' ? kPrefSticky : kPref)); ps->sindex = 1; ps->nextstate = PREF_PARSE_UNTIL_OPEN_PAREN; state = PREF_PARSE_MATCH_STRING; @@ -242,7 +247,8 @@ PREF_ParseBuf(PrefParseState *ps, const char *buf, int bufLen) /* name parsing */ case PREF_PARSE_UNTIL_NAME: if (c == '\"' || c == '\'') { - ps->fdefault = (ps->smatch == kPref); + ps->fdefault = (ps->smatch == kPref || ps->smatch == kPrefSticky); + ps->fstickydefault = (ps->smatch == kPrefSticky); ps->quotechar = c; ps->nextstate = PREF_PARSE_UNTIL_COMMA; /* return here when done */ state = PREF_PARSE_QUOTED_STRING; diff --git a/modules/libpref/prefread.h b/modules/libpref/prefread.h index 4d5ce1e958f..3c317ff044d 100644 --- a/modules/libpref/prefread.h +++ b/modules/libpref/prefread.h @@ -26,12 +26,15 @@ extern "C" { * preference type (PREF_STRING, PREF_INT, or PREF_BOOL) * @param defPref * preference type (true: default, false: user preference) + * @param stickyPref + * default preference marked as a "sticky" pref */ typedef void (*PrefReader)(void *closure, const char *pref, PrefValue val, PrefType type, - bool defPref); + bool defPref, + bool stickyPref); /* structure fields are private */ typedef struct PrefParseState { @@ -52,6 +55,7 @@ typedef struct PrefParseState { char *vb; /* value buffer (ptr into lb) */ PrefType vtype; /* PREF_STRING,INT,BOOL */ bool fdefault; /* true if (default) pref */ + bool fstickydefault; /* true if (sticky) pref */ } PrefParseState; /** diff --git a/modules/libpref/test/unit/data/testPrefSticky.js b/modules/libpref/test/unit/data/testPrefSticky.js new file mode 100644 index 00000000000..69b3165fb48 --- /dev/null +++ b/modules/libpref/test/unit/data/testPrefSticky.js @@ -0,0 +1,2 @@ +pref("testPref.unsticky.bool", true); +sticky_pref("testPref.sticky.bool", false); diff --git a/modules/libpref/test/unit/data/testPrefStickyUser.js b/modules/libpref/test/unit/data/testPrefStickyUser.js new file mode 100644 index 00000000000..0ea0906813e --- /dev/null +++ b/modules/libpref/test/unit/data/testPrefStickyUser.js @@ -0,0 +1,5 @@ +// testPrefSticky.js defined this pref as a sticky_pref(). Once a sticky +// pref has been changed, it's written as a user_pref(). +// So this test file reflects that scenario. +// Note the default in testPrefSticky.js is also false. +user_pref("testPref.sticky.bool", false); diff --git a/modules/libpref/test/unit/test_stickyprefs.js b/modules/libpref/test/unit/test_stickyprefs.js new file mode 100644 index 00000000000..c2c5a7c4b74 --- /dev/null +++ b/modules/libpref/test/unit/test_stickyprefs.js @@ -0,0 +1,170 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +const ps = Services.prefs; + +// Once we fetch the profile directory the xpcshell test harness will send +// a profile-before-change notification at shutdown. This causes the prefs +// service to flush the prefs file - and the prefs file it uses ends up being +// testPrefSticky*.js in the test dir. This upsets things in confusing ways :) +// We avoid this by ensuring our "temp" prefs.js is the current prefs file. +do_get_profile(); +do_register_cleanup(saveAndReload); + +// A little helper to reset the service and load some pref files +function resetAndLoad(filenames) { + ps.resetPrefs(); + for (let filename of filenames) { + ps.readUserPrefs(do_get_file(filename)); + } +} + +// A little helper that saves the current state to a file in the profile +// dir, then resets the service and re-reads the file it just saved. +// Used to test what gets actually written - things the pref service decided +// not to write don't exist at all after this call. +function saveAndReload() { + let file = do_get_profile(); + file.append("prefs.js"); + ps.savePrefFile(file); + + // Now reset the pref service and re-read what we saved. + ps.resetPrefs(); + ps.readUserPrefs(file); +} + +function run_test() { + run_next_test(); +} + +// A sticky pref should not be written if the value is unchanged. +add_test(function notWrittenWhenUnchanged() { + resetAndLoad(["data/testPrefSticky.js"]); + Assert.strictEqual(ps.getBoolPref("testPref.unsticky.bool"), true); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), false); + + // write prefs - but we haven't changed the sticky one, so it shouldn't be written. + saveAndReload(); + // sticky should not have been written to the new file. + try { + ps.getBoolPref("testPref.sticky.bool"); + Assert.ok(false, "expected failure reading this pref"); + } catch (ex) { + Assert.ok(ex, "exception reading regular pref"); + } + run_next_test(); +}); + +// Loading a sticky_pref then a user_pref for the same pref means it should +// always be written. +add_test(function writtenOnceLoadedWithoutChange() { + // Load the same pref file *as well as* a pref file that has a user_pref for + // our sticky with the default value. It should be re-written without us + // touching it. + resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]); + // reset and re-read what we just wrote - it should be written. + saveAndReload(); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), false, + "user_pref was written with default value"); + run_next_test(); +}); + +// If a sticky pref is explicicitly changed, even to the default, it is written. +add_test(function writtenOnceLoadedWithChangeNonDefault() { + // Load the same pref file *as well as* a pref file that has a user_pref for + // our sticky - then change the pref. It should be written. + resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]); + // Set a new val and check we wrote it. + ps.setBoolPref("testPref.sticky.bool", false); + saveAndReload(); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), false, + "user_pref was written with custom value"); + run_next_test(); +}); + +// If a sticky pref is changed to the non-default value, it is written. +add_test(function writtenOnceLoadedWithChangeNonDefault() { + // Load the same pref file *as well as* a pref file that has a user_pref for + // our sticky - then change the pref. It should be written. + resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]); + // Set a new val and check we wrote it. + ps.setBoolPref("testPref.sticky.bool", true); + saveAndReload(); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), true, + "user_pref was written with custom value"); + run_next_test(); +}); + +// Test that prefHasUserValue always returns true whenever there is a sticky +// value, even when that value matches the default. This is mainly for +// about:config semantics - prefs with a sticky value always remain bold and +// always offer "reset" (which fully resets and drops the sticky value as if +// the pref had never changed.) +add_test(function hasUserValue() { + // sticky pref without user value. + resetAndLoad(["data/testPrefSticky.js"]); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), false); + Assert.ok(!ps.prefHasUserValue("testPref.sticky.bool"), + "should not initially reflect a user value"); + + ps.setBoolPref("testPref.sticky.bool", false); + Assert.ok(ps.prefHasUserValue("testPref.sticky.bool"), + "should reflect a user value after set to default"); + + ps.setBoolPref("testPref.sticky.bool", true); + Assert.ok(ps.prefHasUserValue("testPref.sticky.bool"), + "should reflect a user value after change to non-default"); + + ps.clearUserPref("testPref.sticky.bool"); + Assert.ok(!ps.prefHasUserValue("testPref.sticky.bool"), + "should reset to no user value"); + ps.setBoolPref("testPref.sticky.bool", false, "expected default"); + + // And make sure the pref immediately reflects a user value after load. + resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), false); + Assert.ok(ps.prefHasUserValue("testPref.sticky.bool"), + "should have a user value when loaded value is the default"); + run_next_test(); +}); + +// Test that clearUserPref removes the "sticky" value. +add_test(function clearUserPref() { + // load things such that we have a sticky value which is the same as the + // default. + resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]); + ps.clearUserPref("testPref.sticky.bool"); + + // Once we save prefs the sticky pref should no longer be written. + saveAndReload(); + try { + ps.getBoolPref("testPref.sticky.bool"); + Assert.ok(false, "expected failure reading this pref"); + } catch (ex) { + Assert.ok(ex, "pref doesn't have a sticky value"); + } + run_next_test(); +}); + +// Test that a pref observer gets a notification fired when a sticky pref +// has it's value changed to the same value as the default. The reason for +// this behaviour is that later we might have other code that cares about a +// pref being sticky (IOW, we notify due to the "state" of the pref changing +// even if the value has not) +add_test(function observerFires() { + // load things so there's no sticky value. + resetAndLoad(["data/testPrefSticky.js"]); + + function observe(subject, topic, data) { + Assert.equal(data, "testPref.sticky.bool"); + ps.removeObserver("testPref.sticky.bool", observe); + run_next_test(); + } + ps.addObserver("testPref.sticky.bool", observe, false); + + ps.setBoolPref("testPref.sticky.bool", ps.getBoolPref("testPref.sticky.bool")); + // and the observer will fire triggering the next text. +}); diff --git a/modules/libpref/test/unit/xpcshell.ini b/modules/libpref/test/unit/xpcshell.ini index 443d96e3bb4..2ee36ed26f9 100644 --- a/modules/libpref/test/unit/xpcshell.ini +++ b/modules/libpref/test/unit/xpcshell.ini @@ -11,6 +11,8 @@ support-files = [test_bug506224.js] [test_bug577950.js] [test_bug790374.js] +[test_stickyprefs.js] +support-files = data/testPrefSticky.js data/testPrefStickyUser.js [test_changeType.js] [test_dirtyPrefs.js] [test_extprefs.js] From 7b11b3f2841b05054448b7bbb15e0c511ad2b11d Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Thu, 30 Apr 2015 17:13:40 +1000 Subject: [PATCH 06/88] Bug 1098343 (part 2) - use sticky_pref to define devtools preferences that use different defaults on different channels. r=bgrins --- browser/app/profile/firefox.js | 8 ++++---- modules/libpref/init/all.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index f8989ed38ab..b3d120efd26 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1327,8 +1327,8 @@ pref("services.sync.prefs.sync.xpinstall.whitelist.required", true); // Developer edition preferences #ifdef MOZ_DEV_EDITION -pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org"); -pref("browser.devedition.theme.enabled", true); +sticky_pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org"); +sticky_pref("browser.devedition.theme.enabled", true); #endif // Developer edition promo preferences @@ -1507,9 +1507,9 @@ pref("devtools.webaudioeditor.inspectorWidth", 300); // Default theme ("dark" or "light") #ifdef MOZ_DEV_EDITION -pref("devtools.theme", "dark"); +sticky_pref("devtools.theme", "dark"); #else -pref("devtools.theme", "light"); +sticky_pref("devtools.theme", "light"); #endif // Display the introductory text diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index c8f4105743a..acc29098f8f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -786,9 +786,9 @@ pref("devtools.errorconsole.deprecation_warnings", true); // Disable debugging chrome #ifdef MOZ_DEV_EDITION -pref("devtools.chrome.enabled", true); +sticky_pref("devtools.chrome.enabled", true); #else -pref("devtools.chrome.enabled", false); +sticky_pref("devtools.chrome.enabled", false); #endif // Disable remote debugging protocol logging @@ -796,9 +796,9 @@ pref("devtools.debugger.log", false); pref("devtools.debugger.log.verbose", false); // Disable remote debugging connections #ifdef MOZ_DEV_EDITION -pref("devtools.debugger.remote-enabled", true); +sticky_pref("devtools.debugger.remote-enabled", true); #else -pref("devtools.debugger.remote-enabled", false); +sticky_pref("devtools.debugger.remote-enabled", false); #endif pref("devtools.debugger.remote-port", 6000); // Force debugger server binding on the loopback interface From b0d2ecc055fb615e5e18413271970b25c00f5d67 Mon Sep 17 00:00:00 2001 From: Ed Lee Date: Tue, 7 Apr 2015 19:13:46 -0700 Subject: [PATCH 07/88] Bug 1152145 - Filter for specific suggested tiles adgroups/buckets/frecent_sites lists with display name [r=adw] Use a set of predefined lists of allowed sites with explicit names. --- browser/base/content/newtab/sites.js | 2 +- .../test/newtab/browser_newtab_block.js | 3 ++ .../test/newtab/browser_newtab_enhanced.js | 3 ++ browser/modules/DirectoryLinksProvider.jsm | 43 +++++++++++++++++++ .../xpcshell/test_DirectoryLinksProvider.js | 20 ++++++++- 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/browser/base/content/newtab/sites.js b/browser/base/content/newtab/sites.js index eb56fddcb34..87e0bc47f1f 100644 --- a/browser/base/content/newtab/sites.js +++ b/browser/base/content/newtab/sites.js @@ -133,7 +133,7 @@ Site.prototype = { if (this.link.targetedSite) { this.node.setAttribute("suggested", true); - let targetedSite = ` ${this.link.targetedSite} `; + let targetedSite = ` ${this.link.targetedName} `; this._querySelector(".newtab-suggested").innerHTML = `
${newTabString("suggested.button", [targetedSite])}
`; } diff --git a/browser/base/content/test/newtab/browser_newtab_block.js b/browser/base/content/test/newtab/browser_newtab_block.js index 9509786feec..47c645baac3 100644 --- a/browser/base/content/test/newtab/browser_newtab_block.js +++ b/browser/base/content/test/newtab/browser_newtab_block.js @@ -18,6 +18,8 @@ gDirectorySource = "data:application/json," + JSON.stringify({ }); function runTests() { + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = (site) => false; @@ -84,4 +86,5 @@ function runTests() { yield blockCell(1); yield addNewTabPageTab(); checkGrid("1,2,3,4,5,6,7,8,9"); + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; } diff --git a/browser/base/content/test/newtab/browser_newtab_enhanced.js b/browser/base/content/test/newtab/browser_newtab_enhanced.js index a2ff6cd551d..0750017923a 100644 --- a/browser/base/content/test/newtab/browser_newtab_enhanced.js +++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js @@ -26,8 +26,11 @@ gDirectorySource = "data:application/json," + JSON.stringify({ }); function runTests() { + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; let origEnhanced = NewTabUtils.allPages.enhanced; registerCleanupFunction(() => { + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; Services.prefs.clearUserPref(PRELOAD_PREF); NewTabUtils.allPages.enhanced = origEnhanced; }); diff --git a/browser/modules/DirectoryLinksProvider.jsm b/browser/modules/DirectoryLinksProvider.jsm index ff4c491c95f..20c2d99e1b6 100644 --- a/browser/modules/DirectoryLinksProvider.jsm +++ b/browser/modules/DirectoryLinksProvider.jsm @@ -50,6 +50,34 @@ const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping"; // The preference that tells if newtab is enhanced const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced"; +// Only allow explicitly approved frecent sites with display name +const ALLOWED_FRECENT_SITES = new Map([ + [ 'airdroid.com,android-developers.blogspot.com,android.com,androidandme.com,androidapplications.com,androidapps.com,androidauthority.com,androidcentral.com,androidcommunity.com,androidfilehost.com,androidforums.com,androidguys.com,androidheadlines.com,androidpit.com,androidpolice.com,androidspin.com,androidtapp.com,androinica.com,droid-life.com,droidforums.net,droidviews.com,droidxforums.com,forum.xda-developers.com,phandroid.com,play.google.com,shopandroid.com,talkandroid.com,theandroidsoul.com,thedroidguy.com,videodroid.org', + 'Technology' ], + [ 'assurancewireless.com,att.com,attsavings.com,boostmobile.com,budgetmobile.com,consumercellular.com,credomobile.com,gosmartmobile.com,h2owirelessnow.com,lycamobile.com,lycamobile.us,metropcs.com,myfamilymobile.com,polarmobile.com,qlinkwireless.com,republicwireless.com,sprint.com,straighttalk.com,t-mobile.com,tracfonewireless.com,verizonwireless.com,virginmobile.com,virginmobile.com.au,virginmobileusa.com,vodafone.co.uk,vodafone.com,vzwshop.com', + 'Mobile Phone' ], + [ 'addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org', + 'Mozilla' ], + [ '3dprint.com,4sysops.com,access-programmers.co.uk,accountingweb.com,addictivetips.com,adweek.com,afterdawn.com,akihabaranews.com,anandtech.com,appsrumors.com,arstechnica.com,belkin.com,besttechinfo.com,betanews.com,bgr.com,botcrawl.com,breakingmuscle.com,canonrumors.com,cheap-phones.com,chip.de,chip.eu,cio.com,citeworld.com,cleanpcremove.com,cnet.com,commentcamarche.net,computer.org,computerhope.com,computershopper.com,computerweekly.com,contextures.com,coolest-gadgets.com,crn.com,csoonline.com,daniweb.com,data.com,datacenterknowledge.com,ddj.com,devicemag.com,digitaltrends.com,dottech.org,dpreview.com,dslreports.com,edugeek.net,eetimes.com,engadget.com,epic.com,eurekalert.org,eweek.com,experts-exchange.com,extremetech.com,fosshub.com,freesoftwaremagazine.com,funkyspacemonkey.com,futuremark.com,gadgetreview.com,ghacks.net,gizmodo.co.uk,gizmodo.com,globalsecurity.org,greenbot.com,gunup.com,guru3d.com,head-fi.org,hexus.net,hothardware.com,howtoforge.com,idg.com.au,idigitaltimes.com,idownloadblog.com,ihackmyi.com,ilounge.com,infomine.com,informationweek.com,intellireview.com,intomobile.com,iphonehacks.com,ismashphone.com,isource.com,it168.com,itechpost.com,itpro.co.uk,itworld.com,jailbreaknation.com,kioskea.net,laptoping.com,laptopmag.com,lightreading.com,livescience.com,malwaretips.com,mediaroom.com,mobilemag.com,modmyi.com,modmymobile.com,mophie.com,mozillazine.org,neoseeker.com,neowin.net,newscientist.com,newsoxy.com,nextadvisor.com,notebookcheck.com,notebookreview.com,nvidia.com,nwc.com,orafaq.com,osdir.com,osxdaily.com,our-hometown.com,pcadvisor.co.uk,pchome.net,pcmag.com,pconline.com.cn,pcpop.com,pcpro.co.uk,pcreview.co.uk,pcrisk.com,pcwelt.de,phonerebel.com,phonescoop.com,physorg.com,pocket-lint.com,post-theory.com,prnewswire.co.uk,prnewswire.com,programming4.us,quickpwn.com,readwrite.com,redmondpie.com,redorbit.com,reviewed.com,safer-networking.org,sciencedaily.com,sciencenews.org,scientificamerican.com,scientificblogging.com,sciverse.com,servicerow.com,sinfuliphone.com,singularityhub.com,slashdot.org,slashgear.com,softonic.com,softonic.com.br,softonic.fr,sophos.com,space.com,sparkfun.com,speedguide.net,stuff.tv,techdailynews.net,techdirt.com,techeblog.com,techhive.com,techie-buzz.com,technewsworld.com,techniqueworld.com,technobuffalo.com,technologyreview.com,technologytell.com,techpowerup.com,techpp.com,techrepublic.com,techshout.com,techweb.com,techworld.com,techworld.com.au,techworldtweets.com,telecomfile.com,tgdaily.com,theinquirer.net,thenextweb.com,theregister.co.uk,thermofisher.com,theverge.com,thewindowsclub.com,tomsguide.com,tomshardware.com,tomsitpro.com,toptenreviews.com,trustedreviews.com,tuaw.com,tweaktown.com,ubergizmo.com,unwiredview.com,venturebeat.com,wccftech.com,webmonkey.com,webpronews.com,windows7codecs.com,windowscentral.com,windowsitpro.com,windowstechies.com,winsupersite.com,wired.co.uk,wired.com,wp-themes.com,xda-developers.com,xml.com,zdnet.com,zmescience.com,zol.com.cn', + 'Technology' ], + [ '9to5mac.com,appadvice.com,apple.com,appleinsider.com,appleturns.com,appsafari.com,cultofmac.com,everymac.com,insanelymac.com,iphoneunlockspot.com,isource.com,itunes.apple.com,lowendmac.com,mac-forums.com,macdailynews.com,macenstein.com,macgasm.net,macintouch.com,maclife.com,macnews.com,macnn.com,macobserver.com,macosx.com,macpaw.com,macrumors.com,macsales.com,macstories.net,macupdate.com,macuser.co.uk,macworld.co.uk,macworld.com,maxiapple.com,spymac.com,theapplelounge.com', + 'Technology' ], + [ 'alistapart.com,answers.microsoft.com,backpack.openbadges.org,blog.chromium.org,caniuse.com,codefirefox.com,codepen.io,css-tricks.com,css3generator.com,cssdeck.com,csswizardry.com,devdocs.io,docs.angularjs.org,ghacks.net,github.com,html5demos.com,html5rocks.com,html5test.com,iojs.org,khanacademy.org,l10n.mozilla.org,learn.jquery.com,marketplace.firefox.com,mozilla-hispano.org,mozillians.org,news.ycombinator.com,npmjs.com,packagecontrol.io,quirksmode.org,readwrite.com,reps.mozilla.org,smashingmagazine.com,stackoverflow.com,status.modern.ie,teamtreehouse.com,tutorialspoint.com,udacity.com,validator.w3.org,w3.org,w3cschool.cc,w3schools.com,whatcanidoformozilla.org', + 'Web Development' ], + [ 'classroom.google.com,codecademy.com,elearning.ut.ac.id,khanacademy.org,learn.jquery.com,teamtreehouse.com,tutorialspoint.com,udacity.com,w3cschool.cc,w3schools.com', + 'Web Education' ], + [ 'abebooks.co.uk,abebooks.com,alibris.com,allaboutcircuits.com,allyoucanbooks.com,answersingenesis.org,artnet.com,audiobooks.com,barnesandnoble.com,barnesandnobleinc.com,bartleby.com,betterworldbooks.com,biggerbooks.com,bncollege.com,bookbyte.com,bookdepository.com,bookfinder.com,bookrenter.com,booksamillion.com,booksite.com,boundless.com,brookstone.com,btol.com,calibre-ebook.com,campusbookrentals.com,casadellibro.com,cbomc.com,cengagebrain.com,chapters.indigo.ca,christianbook.com,ciscopress.com,coursesmart.com,cqpress.com,crafterschoice.com,crossings.com,cshlp.org,deseretbook.com,directtextbook.com,discountmags.com,doubledaybookclub.com,doubledaylargeprint.com,doverpublications.com,ebooks.com,ecampus.com,fellabooks.net,fictionwise.com,flatworldknowledge.com,grolier.com,harpercollins.com,hayhouse.com,historybookclub.com,hpb.com,hpbmarketplace.com,interweave.com,iseeme.com,katiekazoo.com,knetbooks.com,learnoutloud.com,librarything.com,literaryguild.com,lulu.com,lww.com,macmillan.com,magazines.com,mbsdirect.net,militarybookclub.com,mypearsonstore.com,mysteryguild.com,netplaces.com,noble.com,novelguide.com,onespirit.com,oxfordjournals.org,paperbackswap.com,papy.co.jp,peachpit.com,penguin.com,penguingroup.com,pimsleur.com,powells.com,qpb.com,quepublishing.com,reviews.com,rhapsodybookclub.com,rodalestore.com,royalsocietypublishing.org,sagepub.com,scrubsmag.com,sfbc.com,simonandschuster.com,simonandschuster.net,simpletruths.com,teach12.net,textbooks.com,textbookx.com,thegoodcook.com,thriftbooks.com,tlsbooks.com,toshibabookplace.com,tumblebooks.com,urbookdownload.com,valorebooks.com,valuemags.com,wwnorton.com,zoobooks.com', + 'Literature' ], + [ 'aceshowbiz.com,aintitcoolnews.com,askkissy.com,askmen.com,atraf.co.il,audioboom.com,beamly.com,blippitt.com,bollywoodlife.com,bossip.com,buzzlamp.com,celebdirtylaundry.com,celebfocus.com,celebitchy.com,celebrity-gossip.net,celebrityabout.com,celebwild.com,chisms.net,concertboom.com,crushable.com,cultbox.co.uk,dailyentertainmentnews.com,dayscafe.com,deadline.com,deathandtaxesmag.com,diaryofahollywoodstreetking.com,digitalspy.com,egotastic.com,empirenews.net,enelbrasero.com,everydaycelebs.com,ew.com,extratv.com,facade.com,fanaru.com,fhm.com,geektyrant.com,glamourpage.com,heatworld.com,hlntv.com,hollyscoop.com,hollywoodreporter.com,hollywoodtuna.com,hypable.com,infotransfer.net,insideedition.com,interaksyon.com,jezebel.com,justjared.com,justjaredjr.com,komando.com,koreaboo.com,maxgo.com,maxim.com,maxviral.com,mediatakeout.com,mosthappy.com,moviestalk.com,my.ology.com,ngoisao.net,nofilmschool.com,nolocreo.com,octane.tv,ouchpress.com,people.com,peopleenespanol.com,perezhilton.com,pinkisthenewblog.com,platotv.tv,playbill.com,playbillvault.com,playgroundmag.net,popeater.com,popnhop.com,popsugar.co.uk,popsugar.com,purepeople.com,radaronline.com,rantchic.com,reshareworthy.com,rinkworks.com,ripbird.com,sara-freder.com,screenjunkies.com,soapcentral.com,soapoperadigest.com,sobadsogood.com,splitsider.com,starcasm.net,starpulse.com,straightfromthea.com,stupidcelebrities.net,stupiddope.com,tbn.org,theawesomedaily.com,theawl.com,thefrisky.com,thefw.com,theresacaputo.com,thezooom.com,tvnotas.com.mx,twanatells.com,vanswarpedtour.com,vietgiaitri.com,viral.buzz,vulture.com,wakavision.com,worthytales.net,wwtdd.com,younghollywood.com', + 'Entertainment News' ], + [ '247wallst.com,4-traders.com,advfn.com,agweb.com,allbusiness.com,barchart.com,barrons.com,beckershospitalreview.com,benzinga.com,bizjournals.com,bizsugar.com,bloomberg.com,bloomberglaw.com,business-standard.com,businessinsider.com,businessinsider.com.au,businesspundit.com,businessweek.com,businesswire.com,cboe.com,cheatsheet.com,chicagobusiness.com,cjonline.com,cnbc.com,cnnmoney.com,cqrcengage.com,dailyfinance.com,dailyfx.com,dealbreaker.com,djindexes.com,dowjones.com,easierstreetdaily.com,economist.com,economyandmarkets.com,economywatch.com,edweek.org,eleconomista.es,entrepreneur.com,etfdailynews.com,etfdb.com,ewallstreeter.com,fastcolabs.com,fastcompany.com,financeformulas.net,financialpost.com,flife.de,forbes.com,forexpros.com,fortune.com,foxbusiness.com,ft.com,ftpress.com,fx-exchange.com,hbr.org,howdofinance.com,ibtimes.com,inc.com,investopedia.com,investors.com,investorwords.com,journalofaccountancy.com,kiplinger.com,lendingandcredit.net,lfb.org,mainstreet.com,markettraders.com,marketwatch.com,maxkeiser.com,minyanville.com,ml.com,moneycontrol.com,moneymappress.com,moneynews.com,moneysavingexpert.com,morningstar.com,mortgagenewsdaily.com,motleyfool.com,mt.co.kr,nber.org,nyse.com,oilprice.com,pewsocialtrends.org,principal.com,qz.com,rantfinance.com,realclearmarkets.com,recode.net,reuters.ca,reuters.co.in,reuters.co.uk,reuters.com,rttnews.com,seekingalpha.com,smallbiztrends.com,streetinsider.com,thecheapinvestor.com,theeconomiccollapseblog.com,themoneyconverter.com,thestreet.com,tickertech.com,tradingeconomics.com,updown.com,valuewalk.com,wikinvest.com,wsj.com,zacks.com', + 'Financial News' ], + [ '10tv.com,8newsnow.com,9news.com,abc.net.au,abc7.com,abc7chicago.com,abcnews.go.com,aclu.org,activistpost.com,ajc.com,al.com,alan.com,alarab.net,aljazeera.com,americanthinker.com,app.com,aristeguinoticias.com,azcentral.com,baltimoresun.com,becomingminimalist.com,beforeitsnews.com,bigstory.ap.org,blackamericaweb.com,bloomberg.com,bloombergview.com,boston.com,bostonherald.com,breitbart.com,buffalonews.com,c-span.org,canada.com,cbs46.com,cbsnews.com,chicagotribune.com,chron.com,citizensvoice.com,citylab.com,cleveland.com,cnn.com,coed.com,countercurrentnews.com,courant.com,ctvnews.ca,dailyherald.com,dailynews.com,dallasnews.com,delawareonline.com,democratandchronicle.com,democraticunderground.com,democrats.org,denverpost.com,desmoinesregister.com,dispatch.com,elcomercio.pe,english.aljazeera.net,examiner.com,farsnews.com,firstcoastnews.com,firstpost.com,firsttoknow.com,foreignpolicy.com,foxnews.com,freebeacon.com,freep.com,fresnobee.com,gazette.com,global.nytimes.com,heraldtribune.com,hindustantimes.com,hngn.com,humanevents.com,huzlers.com,indiatimes.com,indystar.com,irishtimes.com,jacksonville.com,jpost.com,jsonline.com,kansascity.com,kctv5.com,kentucky.com,kickerdaily.com,king5.com,kmov.com,knoxnews.com,kpho.com,kvue.com,kwqc.com,kxan.com,lainformacion.com,latimes.com,ldnews.com,lex18.com,linternaute.com,livemint.com,lostateminor.com,m24.ru,macleans.ca,manchestereveningnews.co.uk,marinecorpstimes.com,masslive.com,mavikocaeli.com.tr,mcall.com,medium.com,mentalfloss.com,mercurynews.com,metro.us,miamiherald.com,militarytimes.com,mk.ru,mlive.com,mondotimes.com,montrealgazette.com,msnbc.com,msnewsnow.com,mynews13.com,mysanantonio.com,mysuncoast.com,nbclosangeles.com,nbcnewyork.com,nbcphiladelphia.com,ndtv.com,newindianexpress.com,news.cincinnati.com,news.google.com,news.msn.com,news.yahoo.com,news10.net,news8000.com,newsday.com,newsdaymarketing.net,newsen.com,newsmax.com,newsobserver.com,newsok.com,newsru.ua,newstatesman.com,newszoom.com,nj.com,nola.com,northjersey.com,nouvelobs.com,npr.org,nwfdailynews.com,nwitimes.com,nydailynews.com,nytimes.com,observer.com,ocregister.com,okcfox.com,omaha.com,onenewspage.com,ontheissues.org,oregonlive.com,orlandosentinel.com,palmbeachpost.com,pe.com,pennlive.com,philly.com,pilotonline.com,polar.com,post-gazette.com,postandcourier.com,presstelegram.com,presstv.ir,propublica.org,providencejournal.com,realclearpolitics.com,recorderonline.com,reporterdock.com,reporterherald.com,respublica.al,reuters.com,rg.ru,roanoke.com,sacbee.com,scmp.com,scnow.com,sdpnoticias.com,seattletimes.com,semana.com,sfgate.com,sharepowered.com,sinembargo.mx,slate.com,sltrib.com,sotomayortv.com,sourcewatch.org,spectator.co.uk,squaremirror.com,star-telegram.com,staradvertiser.com,startribune.com,statesman.com,stltoday.com,streetwise.co,stuff.co.nz,success.com,suffolknewsherald.com,sun-sentinel.com,sunnewsnetwork.ca,suntimes.com,supernewschannel.com,surenews.com,svoboda.org,syracuse.com,tampabay.com,tbd.com,telegram.com,telegraph.co.uk,tennessean.com,the-open-mind.com,theadvocate.com,theage.com.au,theatlantic.com,thebarefootwriter.com,theblaze.com,thecalifornian.com,thedailysheeple.com,thefix.com,theintelligencer.net,thelocal.com,thenational.ae,thenewstribune.com,theparisreview.org,thereporter.com,therepublic.com,thestar.com,thetelegram.com,thetimes.co.uk,theuspatriot.com,time.com,timescall.com,timesdispatch.com,timesleaderonline.com,timesofisrael.com,toledoblade.com,toprightnews.com,townhall.com,tpnn.com,trendolizer.com,triblive.com,tribune.com.pk,tricities.com,troymessenger.com,trueactivist.com,truthandaction.org,tsn.ua,tulsaworld.com,twincities.com,upi.com,usatoday.com,utsandiego.com,vagazette.com,viralwomen.com,vitalworldnews.com,voasomali.com,vox.com,washingtonexaminer.com,washingtonpost.com,watchdog.org,wave3.com,wavy.com,wbay.com,wbtw.com,wcpo.com,wctrib.com,wdtn.com,weeklystandard.com,westernjournalism.com,wfsb.com,wgrz.com,whas11.com,winonadailynews.com,wishtv.com,wistv.com,wkbn.com,wkow.com,wlfi.com,wmtw.com,wmur.com,wopular.com,world-top-news.com,worldnews.com,wplol.us,wpsdlocal6.com,wptz.com,wric.com,wsmv.com,wthitv.com,wthr.com,wtnh.com,wtol.com,wtsp.com,wvec.com,wwlp.com,wwltv.com,wyff4.com,yonhapnews.co.kr,yourbreakingnews.com', + 'News' ], + [ '2k.com,360game.vn,4399.com,a10.com,activision.com,addictinggames.com,alawar.com,alienwarearena.com,anagrammer.com,andkon.com,aq.com,arcadeprehacks.com,arcadeyum.com,arcgames.com,archeagegame.com,armorgames.com,askmrrobot.com,battle.net,battlefieldheroes.com,bigfishgames.com,bigpoint.com,bioware.com,bluesnews.com,boardgamegeek.com,bollyheaven.com,bubblebox.com,bukkit.org,bungie.net,buycraft.net,callofduty.com,candystand.com,cda.pl,challonge.com,championselect.net,cheapassgamer.com,cheatcc.com,cheatengine.org,cheathappens.com,chess.com,civfanatics.com,clashofclans-tools.com,clashofclansbuilder.com,comdotgame.com,commonsensemedia.org,coolrom.com,crazygames.com,csgolounge.com,curse.com,d20pfsrd.com,destructoid.com,diablofans.com,diablowiki.net,didigames.com,dota2.com,dota2lounge.com,dressupgames.com,dulfy.net,ebog.com,elderscrollsonline.com,elitedangerous.com,elitepvpers.com,emuparadise.me,enjoydressup.com,escapegames24.com,escapistmagazine.com,eventhubs.com,eveonline.com,farming-simulator.com,feed-the-beast.com,flashgames247.com,flightrising.com,flipline.com,flonga.com,freegames.ws,freeonlinegames.com,fresh-hotel.org,friv.com,friv.today,fullypcgames.net,funny-games.biz,funtrivia.com,futhead.com,g2a.com,gamasutra.com,game-debate.com,game-oldies.com,game321.com,gamebaby.com,gamebaby.net,gamebanana.com,gamefaqs.com,gamefly.com,gamefront.com,gamegape.com,gamehouse.com,gameinformer.com,gamejolt.com,gamemazing.com,gamemeteor.com,gamerankings.com,gamersgate.com,games-msn.com,games-workshop.com,games.com,games2girls.com,gamesbox.com,gamesfreak.net,gametop.com,gametracker.com,gametrailers.com,gamezhero.com,gbatemp.net,geforce.com,gematsu.com,giantbomb.com,girl.me,girlsgames123.com,girlsplay.com,gog.com,gogames.me,gonintendo.com,goodgamestudios.com,gosugamers.net,greenmangaming.com,gtaforums.com,gtainside.com,guildwars2.com,hackedarcadegames.com,hearthpwn.com,hirezstudios.com,hitbox.tv,hltv.org,howrse.com,icy-veins.com,indiedb.com,jayisgames.com,jigzone.com,joystiq.com,juegosdechicas.com,kabam.com,kbhgames.com,kerbalspaceprogram.com,king.com,kixeye.com,kizi.com,kogama.com,kongregate.com,kotaku.com,lolcounter.com,lolking.net,lolnexus.com,lolpro.com,lolskill.net,lootcrate.com,lumosity.com,mafa.com,mangafox.me,mangapark.com,mariowiki.com,maxgames.com,megagames.com,metacritic.com,mindjolt.com,minecraft.net,minecraftforum.net,minecraftservers.org,minecraftskins.com,mineplex.com,miniclip.com,mmo-champion.com,mmobomb.com,mmohuts.com,mmorpg.com,mmosite.com,mobafire.com,moddb.com,modxvm.com,mojang.com,moshimonsters.com,mousebreaker.com,moviestarplanet.com,mtgsalvation.com,muchgames.com,myonlinearcade.com,myplaycity.com,myrealgames.com,mythicspoiler.com,n4g.com,newgrounds.com,nexon.net,nexusmods.com,ninjakiwi.com,nintendo.com,nintendoeverything.com,nintendolife.com,nitrome.com,nosteam.ro,notdoppler.com,noxxic.com,operationsports.com,origin.com,ownedcore.com,pacogames.com,pathofexile.com,pcgamer.com,pch.com,pcsx2.net,penny-arcade.com,planetminecraft.com,plarium.com,playdota.com,playpink.com,playsides.com,playstationlifestyle.net,playstationtrophies.org,pog.com,pokemon.com,polygon.com,popcap.com,primarygames.com,probuilds.net,ps3hax.net,psnprofiles.com,psu.com,qq.com,r2games.com,resourcepack.net,retrogamer.com,rewardtv.com,riotgames.com,robertsspaceindustries.com,roblox.com,robocraftgame.com,rockpapershotgun.com,rockstargames.com,roosterteeth.com,runescape.com,schoolofdragons.com,screwattack.com,scufgaming.com,segmentnext.com,shacknews.com,shockwave.com,shoryuken.com,siliconera.com,silvergames.com,skydaz.com,smashbros.com,solomid.net,starcitygames.com,starsue.net,steamcommunity.com,steamgifts.com,strategywiki.org,supercheats.com,surrenderat20.net,swtor.com,tankionline.com,tcgplayer.com,teamfortress.com,teamliquid.net,tetrisfriends.com,thesims3.com,thesimsresource.com,thetechgame.com,topg.org,totaljerkface.com,toucharcade.com,transformice.com,trueachievements.com,twcenter.net,twitch.tv,twoplayergames.org,unity3d.com,vg247.com,vgchartz.com,videogamesblogger.com,warframe.com,warlight.net,warthunder.com,watchcartoononline.com,websudoku.com,wildstar-online.com,wildtangent.com,wineverygame.com,wizards.com,worldofsolitaire.com,worldoftanks.com,wowhead.com,wowprogress.com,wowwiki.com,xbox.com,xbox360iso.com,xboxachievements.com,xfire.com,xtremetop100.com,y8.com,yoyogames.com,zybez.net,zynga.com', + 'Video Game' ], +]); + // Only allow link urls that are http(s) const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]); @@ -431,6 +459,14 @@ let DirectoryLinksProvider = { this._enhancedLinks.get(NewTabUtils.extractSite(link.url)); }, + /** + * Get the display name of an allowed frecent sites. Returns undefined for a + * unallowed frecent sites. + */ + getFrecentSitesName(sites) { + return ALLOWED_FRECENT_SITES.get(sites.join(",")); + }, + /** * Check if a url's scheme is in a Set of allowed schemes */ @@ -468,6 +504,13 @@ let DirectoryLinksProvider = { }.bind(this); rawLinks.suggested.filter(validityFilter).forEach((link, position) => { + // Only allow suggested links with approved frecent sites + let name = this.getFrecentSitesName(link.frecent_sites); + if (name == undefined) { + return; + } + + link.targetedName = name; link.lastVisitDate = rawLinks.suggested.length - position; // We cache suggested tiles here but do not push any of them in the links list yet. diff --git a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js index 7faa9d97ac4..1d5a1ac5249 100644 --- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js +++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js @@ -269,6 +269,9 @@ add_task(function test_updateSuggestedTile() { let testObserver = new TestFirstRun(); DirectoryLinksProvider.addObserver(testObserver); + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; + yield promiseSetupDirectoryLinksProvider({linksURL: dataURI}); let links = yield fetchData(); @@ -383,6 +386,7 @@ add_task(function test_updateSuggestedTile() { // Cleanup yield promiseCleanDirectoryLinksProvider(); + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; NewTabUtils.getProviderLinks = origGetProviderLinks; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; @@ -421,6 +425,9 @@ add_task(function test_suggestedLinksMap() { }); add_task(function test_topSitesWithSuggestedLinks() { + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; + let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"]; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = function(site) { @@ -468,6 +475,7 @@ add_task(function test_topSitesWithSuggestedLinks() { isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks); // Cleanup. + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; NewTabUtils.getProviderLinks = origGetProviderLinks; }); @@ -479,7 +487,7 @@ add_task(function test_suggestedAttributes() { let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount; DirectoryLinksProvider._getCurrentTopSiteCount = () => 8; - let frecent_sites = ["top.site.com"]; + let frecent_sites = "addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org".split(","); let imageURI = "https://image/"; let title = "the title"; let type = "affiliate"; @@ -514,6 +522,7 @@ add_task(function test_suggestedAttributes() { // Make sure we get the expected attributes on the suggested tile let link = gLinks.getLinks()[0]; do_check_eq(link.imageURI, imageURI); + do_check_eq(link.targetedName, "Mozilla"); do_check_eq(link.targetedSite, frecent_sites[0]); do_check_eq(link.title, title); do_check_eq(link.type, type); @@ -528,6 +537,8 @@ add_task(function test_suggestedAttributes() { add_task(function test_frequencyCappedSites_views() { Services.prefs.setCharPref(kPingUrlPref, ""); + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = () => true; @@ -594,6 +605,7 @@ add_task(function test_frequencyCappedSites_views() { checkFirstTypeAndLength("organic", 1); // Cleanup. + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; gLinks.removeProvider(DirectoryLinksProvider); @@ -603,6 +615,8 @@ add_task(function test_frequencyCappedSites_views() { add_task(function test_frequencyCappedSites_click() { Services.prefs.setCharPref(kPingUrlPref, ""); + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = () => true; @@ -663,6 +677,7 @@ add_task(function test_frequencyCappedSites_click() { checkFirstTypeAndLength("organic", 1); // Cleanup. + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; gLinks.removeProvider(DirectoryLinksProvider); @@ -1139,6 +1154,8 @@ add_task(function test_DirectoryLinksProvider_getEnhancedLink() { }); add_task(function test_DirectoryLinksProvider_enhancedURIs() { + let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName; + DirectoryLinksProvider.getFrecentSitesName = () => ""; let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite; NewTabUtils.isTopPlacesSite = () => true; let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount; @@ -1182,6 +1199,7 @@ add_task(function test_DirectoryLinksProvider_enhancedURIs() { do_check_eq(links[0].enhancedImageURI, "data:,net1"); // Cleanup. + DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName; NewTabUtils.isTopPlacesSite = origIsTopPlacesSite; DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount; gLinks.removeProvider(DirectoryLinksProvider); From 208eb270b585956fe4ddc55db701665269054593 Mon Sep 17 00:00:00 2001 From: Ed Lee Date: Wed, 1 Apr 2015 01:02:50 -0700 Subject: [PATCH 08/88] Bug 1148862 - Update pref to the new v3 endpoint [r=adw] --- browser/app/profile/firefox.js | 4 ++-- browser/modules/DirectoryLinksProvider.jsm | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index b3d120efd26..43d7c75f033 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1646,10 +1646,10 @@ pref("browser.newtabpage.rows", 3); pref("browser.newtabpage.columns", 5); // directory tiles download URL -pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v2/links/fetch/%LOCALE%"); +pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%"); // endpoint to send newtab click and view pings -pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v2/links/"); +pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/"); // Enable the DOM fullscreen API. pref("full-screen-api.enabled", true); diff --git a/browser/modules/DirectoryLinksProvider.jsm b/browser/modules/DirectoryLinksProvider.jsm index 20c2d99e1b6..5d2519d635a 100644 --- a/browser/modules/DirectoryLinksProvider.jsm +++ b/browser/modules/DirectoryLinksProvider.jsm @@ -150,11 +150,6 @@ let DirectoryLinksProvider = { if (!this.__linksURL) { try { this.__linksURL = Services.prefs.getCharPref(this._observedPrefs["linksURL"]); - - // Temporarily override the default for en-US until new endpoint is live - if (this.locale == "en-US" && !Services.prefs.prefHasUserValue(this._observedPrefs["linksURL"])) { - this.__linksURL = "data:text/plain;base64,ewogICAgImRpcmVjdG9yeSI6IFsKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiIsCiAgICAgICAgICAgICJkaXJlY3RvcnlJZCI6IDQ5OCwKICAgICAgICAgICAgImVuaGFuY2VkSW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy9kMTFiYTBiMzA5NWJiMTlkODA5MmNkMjliZTljYmI5ZTE5NzY3MWVhLjI4MDg4LnBuZyIsCiAgICAgICAgICAgICJpbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzLzEzMzJhNjhiYWRmMTFlM2Y3ZjY5YmY3MzY0ZTc5YzBhN2UyNzUzYmMuNTMxNi5wbmciLAogICAgICAgICAgICAidGl0bGUiOiAiTW96aWxsYSBDb21tdW5pdHkiLAogICAgICAgICAgICAidHlwZSI6ICJhZmZpbGlhdGUiLAogICAgICAgICAgICAidXJsIjogImh0dHA6Ly9jb250cmlidXRlLm1vemlsbGEub3JnLyIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImJnQ29sb3IiOiAiIiwKICAgICAgICAgICAgImRpcmVjdG9yeUlkIjogNTAwLAogICAgICAgICAgICAiZW5oYW5jZWRJbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzL2NjNjM3NzRiN2E5YWFlMDJmZTM2YmM1Y2FmOTBjMWUyNWU2NmEyYmMuMTM3OTEucG5nIiwKICAgICAgICAgICAgImltYWdlVVJJIjogImh0dHBzOi8vZHRleDRrdmJwcG92dC5jbG91ZGZyb250Lm5ldC9pbWFnZXMvZTgyMmNkNDYyOGM1MTYyMzEzZjQ5ZjVkNDU1NmY4YWFmZGYzODc1MC4xMTUxMy5wbmciLAogICAgICAgICAgICAidGl0bGUiOiAiTW96aWxsYSBNYW5pZmVzdG8iLAogICAgICAgICAgICAidHlwZSI6ICJhZmZpbGlhdGUiLAogICAgICAgICAgICAidXJsIjogImh0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2Fib3V0L21hbmlmZXN0by8iCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiIsCiAgICAgICAgICAgICJkaXJlY3RvcnlJZCI6IDUwMiwKICAgICAgICAgICAgImVuaGFuY2VkSW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy80MGU1NjMwNDA1ZDUwMzFjYTczMzkzYmQ3YmMwMDY0MTU2ZjJjYzgyLjEwOTg0LnBuZyIsCiAgICAgICAgICAgICJpbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzLzQ5MGQ0MmQxZjlhNzZjMDc3Mzk2MjZkMWI4YTU2OTE2OWFlYzhmYmUuMTEwMzkucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIkN1c3RvbWl6ZSBGaXJlZm94IiwKICAgICAgICAgICAgInR5cGUiOiAiYWZmaWxpYXRlIiwKICAgICAgICAgICAgInVybCI6ICJodHRwOi8vZmFzdGVzdGZpcmVmb3guY29tL2ZpcmVmb3gvZGVza3RvcC9jdXN0b21pemUvIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYmdDb2xvciI6ICIiLAogICAgICAgICAgICAiZGlyZWN0b3J5SWQiOiA1MDQsCiAgICAgICAgICAgICJlbmhhbmNlZEltYWdlVVJJIjogImh0dHBzOi8vZHRleDRrdmJwcG92dC5jbG91ZGZyb250Lm5ldC9pbWFnZXMvODc3ZjFjNTYxZTczNWY3YjlmNDE5ZmY5YWM3OWViOGM3NDgxMTE5ZC4xNjc0NC5wbmciLAogICAgICAgICAgICAiaW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy8yNWM5ZmJiMDczMDhiODRkMTYwZmMxYjc5NTkzNjRhMmMxOGY5M2I5LjY0MDQucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIkZpcmVmb3ggTWFya2V0cGxhY2UiLAogICAgICAgICAgICAidHlwZSI6ICJhZmZpbGlhdGUiLAogICAgICAgICAgICAidXJsIjogImh0dHBzOi8vbWFya2V0cGxhY2UuZmlyZWZveC5jb20vIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYmdDb2xvciI6ICIjM2ZiNThlIiwKICAgICAgICAgICAgImRpcmVjdG9yeUlkIjogNTA1LAogICAgICAgICAgICAiZW5oYW5jZWRJbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzLzcyMDEyMWU3NDYyZDhjNzg2M2I0ZGQ4ZmE3YjVjMTA4OWI1ZjVmYjIuMzM4NjIucG5nIiwKICAgICAgICAgICAgImltYWdlVVJJIjogImh0dHBzOi8vZHRleDRrdmJwcG92dC5jbG91ZGZyb250Lm5ldC9pbWFnZXMvMGU2MDMxNjc1YTljNDkxZGQwYzY1ZTljNjdjZmJmNTRhNTg4MGYxNy4yMjk1LnN2ZyIsCiAgICAgICAgICAgICJ0aXRsZSI6ICJNb3ppbGxhIFdlYm1ha2VyIiwKICAgICAgICAgICAgInR5cGUiOiAiYWZmaWxpYXRlIiwKICAgICAgICAgICAgInVybCI6ICJodHRwczovL3dlYm1ha2VyLm9yZy8%2FdXRtX3NvdXJjZT1kaXJlY3RvcnktdGlsZXMmdXRtX21lZGl1bT1maXJlZm94LWJyb3dzZXIiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiIsCiAgICAgICAgICAgICJkaXJlY3RvcnlJZCI6IDUwNiwKICAgICAgICAgICAgImVuaGFuY2VkSW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy9kOTcxY2JhZmEwMzA5YTIwMWU1MThhY2RhYzRmMWVlNGRhYmM3ZWFhLjE1MTA5LnBuZyIsCiAgICAgICAgICAgICJpbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzL2I0YWRjNThkZDNjMDJkYTM1NTEwNDk3N2I5MTAyNTUwNjBjZmQ2ZDguMTAzNTAucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIkZpcmVmb3ggU3luYyIsCiAgICAgICAgICAgICJ0eXBlIjogImFmZmlsaWF0ZSIsCiAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovL21vemlsbGEtZXVyb3BlLm9yZy9maXJlZm94L3N5bmMiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiIsCiAgICAgICAgICAgICJkaXJlY3RvcnlJZCI6IDUwNywKICAgICAgICAgICAgImVuaGFuY2VkSW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy8yMmZiODU2Y2Q1ODM2NTg1NWViNzI1YjE1NjVmMDhhNzI0NjRlMDM5LjE4NzE3LnBuZyIsCiAgICAgICAgICAgICJpbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzLzA2OGUwY2NiZDg3MDFhMjhlMmYwNzhjNjQwZWUwNzJiOWExNmUyZTEuMTI0OTAucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIlByaXZhY3kgUHJpbmNpcGxlcyIsCiAgICAgICAgICAgICJ0eXBlIjogImFmZmlsaWF0ZSIsCiAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovL2V1cm9wZS5tb3ppbGxhLm9yZy9wcml2YWN5L3lvdSIKICAgICAgICB9CiAgICBdLAogICAgInN1Z2dlc3RlZCI6IFsKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiNjYWUxZjQiLAogICAgICAgICAgICAiZGlyZWN0b3J5SWQiOiA3MDIsCiAgICAgICAgICAgICJmcmVjZW50X3NpdGVzIjogWwogICAgICAgICAgICAgICAgImFkZG9ucy5tb3ppbGxhLm9yZyIsCiAgICAgICAgICAgICAgICAiYWlyLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJibG9nLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJidWd6aWxsYS5tb3ppbGxhLm9yZyIsCiAgICAgICAgICAgICAgICAiZGV2ZWxvcGVyLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJldGhlcnBhZC5tb3ppbGxhLm9yZyIsCiAgICAgICAgICAgICAgICAiaGFja3MubW96aWxsYS5vcmciLAogICAgICAgICAgICAgICAgImhnLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJtb3ppbGxhLm9yZyIsCiAgICAgICAgICAgICAgICAicGxhbmV0Lm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJxdWFsaXR5Lm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJzdXBwb3J0Lm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJzdXBwb3J0Lm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJ0cmVlaGVyZGVyLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJ3aWtpLm1vemlsbGEub3JnIgogICAgICAgICAgICBdLAogICAgICAgICAgICAiaW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy85ZWUyYjI2NTY3OGYyNzc1ZGUyZTRiZjY4MGRmNjAwYjUwMmU2MDM4LjM4NzUucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIlRoYW5rcyBmb3IgdGVzdGluZyEiLAogICAgICAgICAgICAidHlwZSI6ICJhZmZpbGlhdGUiLAogICAgICAgICAgICAidXJsIjogImh0dHBzOi8vd3d3Lm1vemlsbGEuY29tL2ZpcmVmb3gvdGlsZXMiCiAgICAgICAgfQogICAgXQp9Cg%3D%3D"; - } } catch (e) { Cu.reportError("Error fetching directory links url from prefs: " + e); From 6569f5989cd36f59b63ad044b189e5748c92999e Mon Sep 17 00:00:00 2001 From: Ed Lee Date: Tue, 21 Apr 2015 23:32:04 -0700 Subject: [PATCH 09/88] Bug 1156876 - Convert wiki page technical doc into in-tree .rst [r=bsmedberg] --- browser/docs/DirectoryLinksProvider.rst | 248 ++++++++++++++++++++++++ browser/docs/index.rst | 1 + 2 files changed, 249 insertions(+) create mode 100644 browser/docs/DirectoryLinksProvider.rst diff --git a/browser/docs/DirectoryLinksProvider.rst b/browser/docs/DirectoryLinksProvider.rst new file mode 100644 index 00000000000..ec02779ad66 --- /dev/null +++ b/browser/docs/DirectoryLinksProvider.rst @@ -0,0 +1,248 @@ +============================================= +Directory Links Architecture and Data Formats +============================================= + +Directory links are enhancements to the new tab experience that combine content +Firefox already knows about from user browsing with external content. There are +3 kinds of links: + +- directory links fill in additional tiles on the new tab page if there would + have been empty tiles because the user has a clean profile or cleared history +- suggested links are shown if certain triggering criteria matches the user's + browsing behavior, i.e., if the user has a top site that matches one of + several possible sites. E.g., only show a sports suggestion if the user has a + sport site as a top site +- enhanced links replace a matching user's visible history tile from the same + site but only the visual aspects: title, image, and rollover image + +To power the above features, DirectoryLinksProvider module downloads, at most +once per 24 hours, the directory source links as JSON with enough data for +Firefox to determine what should be shown or not. This module also handles +reporting back data about the tiles via asynchronous pings that don't return +data from the server. + +For the directory source and ping endpoints, the default preference values point +to Mozilla key-pinned servers with encryption. No cookies are set by the servers +but not enforced by Firefox. + +- default directory source endpoint: + https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL% +- default directory ping endpoint: https://tiles.services.mozilla.com/v3/links/ + + +Preferences +=========== + +There are two main preferences that control downloading links and reporting +metrics. + +``browser.newtabpage.directory.source`` +--------------------------------------- + +This endpoint tells Firefox where to download directory source file as a GET +request. It should return JSON of the appropriate format containing the relevant +links data. The value can be a data URI, e.g., an empty JSON object effectively +turns off remote downloading: ``data:text/plain,{}`` + +The preference value will have %LOCALE% and %CHANNEL% replaced by the +appropriate values for the build of Firefox, e.g., + +- directory source endpoint: + https://tiles.services.mozilla.com/v3/links/fetch/en-US/release + +``browser.newtabpage.directory.ping`` +------------------------------------- + +This endpoint tells Firefox where to report Tiles metrics as a POST request. The +data is sent as a JSON blob. Setting it to empty effectively turns off reporting +of Tiles data. + +A path segment will be appended to the endpoint of "view" or "click" depending +on the type of ping, e.g., + +- ``view`` ping endpoint: https://tiles.services.mozilla.com/v3/links/view +- ``click`` ping endpoint: https://tiles.services.mozilla.com/v3/links/click + + +Data Flow +========= + +When Firefox starts, it checks for a cached directory source file. If one +exists, it checks for its timestamp to determine if a new file should be +downloaded. + +If a directory source file needs to be downloaded, a GET request is made then +cacheed and unpacked the JSON into the different types of links. Various checks +filter out invalid links, e.g., those with http-hosted images or those that +don't fit the allowed suggestions. + +When a new tab page is built, DirectoryLinksProvider module provides additional +link data that is combined with history link data to determine which links can +be displayed or not. + +When a new tab page is shown, a ``view`` ping is sent with relevant tiles data. +Similarly, when the user clicks on various parts of tiles (to load the page, +pin, block, etc.), a ``click`` ping is sent with similar data. Both of these can +trigger downloading of fresh directory source links if 24 hours have elapsed +since last download. + +Users can turn off the ping with in-new-tab-page controls. + +As the new tab page is rendered, any images for tiles are downloaded if not +already cached. The default servers hosting the images are Mozilla CDN that +don't use cookies: https://tiles.cdn.mozilla.net/ + + +Source JSON Format +================== + +Firefox expects links data in a JSON object with top level keys each providing +an array of tile objects. The keys correspond to the different types of links: +``directory``, ``suggested``, and ``enhanced``. + +Example +------- + +Below is an example directory source file:: + + { + "directory": [ + { + "bgColor": "", + "directoryId": 498, + "enhancedImageURI": "https://tiles.cdn.mozilla.net/images/d11ba0b3095bb19d8092cd29be9cbb9e197671ea.28088.png", + "imageURI": "https://tiles.cdn.mozilla.net/images/1332a68badf11e3f7f69bf7364e79c0a7e2753bc.5316.png", + "title": "Mozilla Community", + "type": "affiliate", + "url": "http://contribute.mozilla.org/" + } + ], + "enhanced": [ + { + "bgColor": "", + "directoryId": 776, + "enhancedImageURI": "https://tiles.cdn.mozilla.net/images/44a14fc405cebc299ead86514dff0e3735c8cf65.10814.png", + "imageURI": "https://tiles.cdn.mozilla.net/images/20e24aa2219ec7542cc8cf0fd79f0c81e16ebeac.11859.png", + "title": "TurboTax", + "type": "sponsored", + "url": "https://turbotax.intuit.com/" + } + ], + "suggested": [ + { + "bgColor": "#cae1f4", + "directoryId": 702, + "frecent_sites": [ + "addons.mozilla.org", + "air.mozilla.org", + "blog.mozilla.org", + "bugzilla.mozilla.org", + "developer.mozilla.org", + "etherpad.mozilla.org", + "hacks.mozilla.org", + "hg.mozilla.org", + "mozilla.org", + "planet.mozilla.org", + "quality.mozilla.org", + "support.mozilla.org", + "treeherder.mozilla.org", + "wiki.mozilla.org" + ], + "imageURI": "https://tiles.cdn.mozilla.net/images/9ee2b265678f2775de2e4bf680df600b502e6038.3875.png", + "title": "Thanks for testing!", + "type": "affiliate", + "url": "https://www.mozilla.com/firefox/tiles" + } + ] + } + +Link Object +----------- + +Each link object has various values that Firefox uses to display a tile: + +- ``url`` - string url for the page to be loaded when the tile is clicked. Only + https and http URLs are allowed. +- ``title`` - string that appears below the tile. +- ``type`` - string relationship of the link to Mozilla. Expected values: + affiliate, organic, sponsored. +- ``imageURI`` - string url for the tile image to show. Only https and data URIs + are allowed. +- ``enhancedImageURI`` - string url for the image to be shown before the user + hovers. Only https and data URIs are allowed. +- ``bgColor`` - string css color for additional fill background color. +- ``directoryId`` - id of the tile to be used during ping reporting + +Suggested Link Object Extras +---------------------------- + +A suggested link has additional values: + +- ``frecent_sites`` - array of strings of the sites that can trigger showing a + Suggested Tile if the user has the site in one of the top 100 most-frecent + pages. Only preapproved array of strings that are hardcoded into the + DirectoryLinksProvider module are allowed. + +The preapproved arrays follow a policy for determining what topic grouping is +allowed as well as the composition of a grouping. The topics are broad +uncontroversial categories, e.g., Mobile Phone, News, Technology, Video Game, +Web Development. There are at least 5 sites within a grouping, and as many +popular sites relevant to the topic are included to avoid having one site be +clearly dominant. These requirements provide some deniability of which site +actually triggered a suggestion during ping reporting, so it's more difficult to +determine if a user has gone to a specific site. + + +Ping JSON Format +================ + +Firefox reports back an action and the state of tiles on the new tab page based +on the user opening a new tab or clicking a tile. The top level keys of the +ping: + +- ``locale`` - string locale of the Firefox build +- ``tiles`` - array of tiles ping objects + +An additional key at the top level indicates which action triggered the ping. +The value associated to the action key is the 0-based index into the tiles array +of which tile triggered the action. Valid actions: block, click, pin, sponsored, +sponsored_link, unpin, view. E.g., if the second tile is being clicked, the ping +will have ``"click": 1`` + +Example +------- + +Below is an example ``click`` ping with 3 tiles: a pinned suggested tile +followed by a history tile and a directory tile. The first tile is being +blocked:: + + { + "locale": "en-US", + "tiles": [ + { + "id": 702, + "pin": 1, + }, + {}, + { + "id": 498, + } + ], + "block": 0 + } + +Tiles Ping Object +----------------- + +Each tile of the new tab page is reported back as part of the ping with some or +none of the following optional values: + +- ``id`` - id that was provided as part of the downloaded link object (for all + types of links: directory, suggested, enhanced); not present if the tile was + created from user behavior, e.g., visiting pages +- ``pinned`` - 1 if the tile is pinned; not present otherwise +- ``pos`` - integer position if the tile is not in the natural order, e.g., a + pinned tile after an empty slot; not present otherwise +- ``score`` - integer truncated score based on the tile's frecency; not present + if 0 +- ``url`` - empty string if it's an enhanced tile; not present otherwise diff --git a/browser/docs/index.rst b/browser/docs/index.rst index 67fc69c8dc3..ad745b8feab 100644 --- a/browser/docs/index.rst +++ b/browser/docs/index.rst @@ -7,4 +7,5 @@ This is the nascent documentation of the Firefox front-end code. .. toctree:: :maxdepth: 1 + DirectoryLinksProvider UITelemetry From 3a5b6fe7225e8acb1ee50bc216d1852bf6b83765 Mon Sep 17 00:00:00 2001 From: Marco Bonardo Date: Thu, 30 Apr 2015 10:40:10 +0200 Subject: [PATCH 10/88] Bug 1159462 - unreachable code in editBookmarkOverlay.js. r=mano --- browser/components/places/content/editBookmarkOverlay.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js index fbc31241c22..0dc8a69083a 100644 --- a/browser/components/places/content/editBookmarkOverlay.js +++ b/browser/components/places/content/editBookmarkOverlay.js @@ -1004,12 +1004,14 @@ let gEditItemOverlay = { aLastModified, aItemType) { if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) this._onTagsChange(aItemId); - else if (this._paneInfo.isItem && aProperty == "title") - this._onItemTitleChange(aItemId, aValue); - else (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) + else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) return; switch (aProperty) { + case "title": + if (this._paneInfo.isItem) + this._onItemTitleChange(aItemId, aValue); + break; case "uri": let newURI = NetUtil.newURI(aValue); if (!newURI.equals(this._paneInfo.uri)) { From 625bf6b2a2259744a1f361a5d791880433e4e9c6 Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Wed, 29 Apr 2015 09:08:24 +0200 Subject: [PATCH 11/88] Bug 1145049 - Clean inspector on detach/disconnect. r=pbrosset --- toolkit/devtools/server/actors/highlighter.js | 7 +++ toolkit/devtools/server/actors/inspector.js | 48 ++++++++++++++++++- toolkit/devtools/server/actors/styles.js | 36 +++++++++++++- 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/toolkit/devtools/server/actors/highlighter.js b/toolkit/devtools/server/actors/highlighter.js index 45a37c1c254..0262b747473 100644 --- a/toolkit/devtools/server/actors/highlighter.js +++ b/toolkit/devtools/server/actors/highlighter.js @@ -184,6 +184,9 @@ let HighlighterActor = exports.HighlighterActor = protocol.ActorClass({ }, destroy: function() { + if (!this._inspector) { + return; + } protocol.Actor.prototype.destroy.call(this); this._destroyHighlighter(); @@ -195,6 +198,10 @@ let HighlighterActor = exports.HighlighterActor = protocol.ActorClass({ this._layoutHelpers = null; }, + disconnect: function () { + this.destroy(); + }, + /** * Display the box model highlighting on a given NodeActor. * There is only one instance of the box model highlighter, so calling this diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js index 8e5bc10480b..cc6ea41167a 100644 --- a/toolkit/devtools/server/actors/inspector.js +++ b/toolkit/devtools/server/actors/inspector.js @@ -220,6 +220,12 @@ var NodeActor = exports.NodeActor = protocol.ActorClass({ this.rawNode.ownerDocument.documentElement === this.rawNode; }, + destroy: function () { + protocol.Actor.prototype.destroy.call(this); + this.rawNode = null; + this.walker = null; + }, + // Returns the JSON representation of this object over the wire. form: function(detail) { if (detail === "actorid") { @@ -1219,6 +1225,9 @@ var WalkerActor = protocol.ActorClass({ destroy: function() { try { + if (this._destroyed) { + return; + } this._destroyed = true; this.clearPseudoClassLocks(); @@ -1226,6 +1235,17 @@ var WalkerActor = protocol.ActorClass({ this._hoveredNode = null; this.rootDoc = null; + this.rootWin = null; + this.rootNode = null; + this.tabActor = null; + this.layoutHelpers = null; + this._orphaned = null; + this._retainedOrphans = null; + this._refMap.forEach(actor => { + this.unmanage(actor); + actor.destroy(); + }); + this._refMap = null; this.reflowObserver.off("reflows", this._onReflows); this.reflowObserver = null; @@ -3312,6 +3332,30 @@ var InspectorActor = exports.InspectorActor = protocol.ActorClass({ this.tabActor = tabActor; }, + destroy: function () { + protocol.Actor.prototype.destroy.call(this); + this._highlighterPromise = null; + this._pageStylePromise = null; + this._walkerPromise = null; + if (this.walker) { + this.walker.destroy(); + } + this.walker = null; + if (this.pageStyle) { + this.pageStyle.destroy(); + } + this.pageStyle = null; + if (this.highlighter) { + this.highlighter.destroy(); + } + this.highlighter = null; + this.tabActor = null; + }, + + disconnect: function () { + this.destroy(); + }, + get window() this.tabActor.window, getWalker: method(function(options={}) { @@ -3356,7 +3400,7 @@ var InspectorActor = exports.InspectorActor = protocol.ActorClass({ } this._pageStylePromise = this.getWalker().then(walker => { - return PageStyleActor(this); + return this.pageStyle = PageStyleActor(this); }); return this._pageStylePromise; }, { @@ -3385,7 +3429,7 @@ var InspectorActor = exports.InspectorActor = protocol.ActorClass({ } this._highlighterPromise = this.getWalker().then(walker => { - return HighlighterActor(this, autohide); + return this.highlighter = HighlighterActor(this, autohide); }); return this._highlighterPromise; }, { diff --git a/toolkit/devtools/server/actors/styles.js b/toolkit/devtools/server/actors/styles.js index 39a6da7aa2f..83bffc2b83f 100644 --- a/toolkit/devtools/server/actors/styles.js +++ b/toolkit/devtools/server/actors/styles.js @@ -124,10 +124,32 @@ var PageStyleActor = protocol.ActorClass({ // Stores the association of DOM objects -> actors this.refMap = new Map; + // Keep the list of StyleRuleActor created for font + // in order to clean them up when the PageStyleActor is collected + this.fontStyleActors = new Set; + this.onFrameUnload = this.onFrameUnload.bind(this); events.on(this.inspector.tabActor, "will-navigate", this.onFrameUnload); }, + destroy: function () { + if (!this.walker) + return; + protocol.Actor.prototype.destroy.call(this); + events.off(this.inspector.tabActor, "will-navigate", this.onFrameUnload); + this.inspector = null; + this.walker = null; + this.refMap.forEach(actor => { + this.unmanage(actor); + actor.destroy(); + }); + this.fontStyleActors.forEach(actor => actor.destroy()); + this.fontStyleActors = null; + this.refMap = null; + this.cssLogic = null; + this._styleElement = null; + }, + get conn() this.inspector.conn, form: function(detail) { @@ -307,7 +329,9 @@ var PageStyleActor = protocol.ActorClass({ // If this font comes from a @font-face rule if (font.rule) { - fontFace.rule = StyleRuleActor(this, font.rule); + let styleActor = StyleRuleActor(this, font.rule); + this.fontStyleActors.add(styleActor); + fontFace.rule = styleActor; fontFace.ruleText = font.rule.cssText; } @@ -963,6 +987,16 @@ var StyleRuleActor = protocol.ActorClass({ get conn() this.pageStyle.conn, + destroy: function () { + if (!this.rawStyle) + return; + protocol.Actor.prototype.destroy.call(this); + this.rawStyle = null; + this.pageStyle = null; + this.rawNode = null; + this.rawRule = null; + }, + // Objects returned by this actor are owned by the PageStyleActor // to which this rule belongs. get marshallPool() this.pageStyle, From 7be58a87a414c40231df7cd32b2310756280ec18 Mon Sep 17 00:00:00 2001 From: Mark Finkle Date: Wed, 29 Apr 2015 10:23:19 -0400 Subject: [PATCH 12/88] Bug 1159263 - Add support to nsCaret to stop blinking after a set of cycles r=roc r=snorp --- layout/base/nsCaret.cpp | 15 +++++++++++++++ layout/base/nsCaret.h | 6 ++++++ mobile/android/app/mobile.js | 3 +++ 3 files changed, 24 insertions(+) diff --git a/layout/base/nsCaret.cpp b/layout/base/nsCaret.cpp index 8df23588040..2cf018ddc47 100644 --- a/layout/base/nsCaret.cpp +++ b/layout/base/nsCaret.cpp @@ -120,6 +120,7 @@ IsBidiUI() nsCaret::nsCaret() : mOverrideOffset(0) , mIsBlinkOn(false) +, mBlinkCount(-1) , mVisible(false) , mReadOnly(false) , mShowDuringSelection(false) @@ -594,6 +595,7 @@ void nsCaret::ResetBlinking() uint32_t blinkRate = static_cast( LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, 500)); if (blinkRate > 0) { + mBlinkCount = Preferences::GetInt("ui.caretBlinkCount", -1); mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, blinkRate, nsITimer::TYPE_REPEATING_SLACK); } @@ -913,6 +915,19 @@ void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure) } theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn; theCaret->SchedulePaint(); + + // mBlinkCount of -1 means blink count is not enabled. + if (theCaret->mBlinkCount == -1) { + return; + } + + // Track the blink count, but only at end of a blink cycle. + if (!theCaret->mIsBlinkOn) { + // If we exceeded the blink count, stop the timer. + if (--theCaret->mBlinkCount <= 0) { + theCaret->StopBlinking(); + } + } } void diff --git a/layout/base/nsCaret.h b/layout/base/nsCaret.h index 814dd05798a..c5b8e4e2073 100644 --- a/layout/base/nsCaret.h +++ b/layout/base/nsCaret.h @@ -205,6 +205,12 @@ protected: * mIsBlinkOn is true when we're in a blink cycle where the caret is on. */ bool mIsBlinkOn; + /** + * mBlinkCount is used to control the number of times to blink the caret + * before stopping the blink. This is reset each time we reset the + * blinking. + */ + int32_t mBlinkCount; /** * mIsVisible is true when SetVisible was last called with 'true'. */ diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index 7845a685354..e3cdb9af26b 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -47,6 +47,9 @@ pref("browser.viewport.defaultZoom", -1); /* allow scrollbars to float above chrome ui */ pref("ui.scrollbarsCanOverlapContent", 1); +/* turn off the caret blink after 10 cycles */ +pref("ui.caretBlinkCount", 10); + /* cache prefs */ pref("browser.cache.disk.enable", true); pref("browser.cache.disk.capacity", 20480); // kilobytes From c4c72decb353a4af4a5e3a1f788c5ac0e41851e9 Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Thu, 30 Apr 2015 14:00:58 +0200 Subject: [PATCH 13/88] Bug 1149754 - Part 4: Fix duplicated ping saving. r=dexter --- toolkit/components/telemetry/TelemetryStorage.jsm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/toolkit/components/telemetry/TelemetryStorage.jsm b/toolkit/components/telemetry/TelemetryStorage.jsm index d3826396f51..639985ed24c 100644 --- a/toolkit/components/telemetry/TelemetryStorage.jsm +++ b/toolkit/components/telemetry/TelemetryStorage.jsm @@ -443,8 +443,7 @@ let TelemetryStorageImpl = { return this.loadPingFile(pingPath).then(ping => { // Since we read a ping successfully, update the related histogram. Telemetry.getHistogramById("READ_SAVED_PING_SUCCESS").add(1); - this.addPendingPing(ping); - return this.savePing(ping, false); + return this.addPendingPing(ping); }); }, From 0b95cfd27335a1f1afc74519a90c4c476b18726e Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Thu, 30 Apr 2015 14:00:58 +0200 Subject: [PATCH 14/88] Bug 1148500 - Part 1 (Fx 40): Introduce a toolkit.telemetry.unified pref. r=dexter --- mobile/android/app/mobile.js | 4 ++++ modules/libpref/init/all.js | 4 ++++ toolkit/components/telemetry/docs/index.rst | 1 + .../components/telemetry/docs/preferences.rst | 17 +++++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 toolkit/components/telemetry/docs/preferences.rst diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index e3cdb9af26b..b950c88503f 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -866,6 +866,10 @@ pref("reader.toolbar.vertical", false); // Whether or not to display buttons related to reading list in reader view. pref("browser.readinglist.enabled", true); +// Telemetry settings. +// Whether to use the unified telemetry behavior, requires a restart. +pref("toolkit.telemetry.unified", false); + // Selection carets never fall-back to internal LongTap detector. pref("selectioncaret.detects.longtap", false); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index acc29098f8f..472ef85fa5e 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -765,6 +765,8 @@ pref("toolkit.scrollbox.verticalScrollDistance", 3); pref("toolkit.scrollbox.horizontalScrollDistance", 5); pref("toolkit.scrollbox.clickToScroll.scrollDelay", 150); +// Telemetry settings. +// Server to submit telemetry pings to. pref("toolkit.telemetry.server", "https://incoming.telemetry.mozilla.org"); // Telemetry server owner. Please change if you set toolkit.telemetry.server to a different server pref("toolkit.telemetry.server_owner", "Mozilla"); @@ -773,6 +775,8 @@ pref("toolkit.telemetry.infoURL", "https://www.mozilla.org/legal/privacy/firefox // Determines whether full SQL strings are returned when they might contain sensitive info // i.e. dynamically constructed SQL strings or SQL executed by addons against addon DBs pref("toolkit.telemetry.debugSlowSql", false); +// Whether to use the unified telemetry behavior, requires a restart. +pref("toolkit.telemetry.unified", true); // Identity module pref("toolkit.identity.enabled", false); diff --git a/toolkit/components/telemetry/docs/index.rst b/toolkit/components/telemetry/docs/index.rst index 84a7a45bd66..2c49b3ff5ca 100644 --- a/toolkit/components/telemetry/docs/index.rst +++ b/toolkit/components/telemetry/docs/index.rst @@ -19,3 +19,4 @@ Client-side, this consists of: common-ping environment main-ping + preferences diff --git a/toolkit/components/telemetry/docs/preferences.rst b/toolkit/components/telemetry/docs/preferences.rst new file mode 100644 index 00000000000..422411e89e1 --- /dev/null +++ b/toolkit/components/telemetry/docs/preferences.rst @@ -0,0 +1,17 @@ +Preferences +=========== + +Telemetry behaviour is controlled through the following preferences: + +``toolkit.telemetry.unified`` + + This controls whether unified behavior is enabled. If true: + + * Telemetry is always enabled and recording *base* data. + * Telemetry will send additional ``main`` pings. + +``toolkit.telemetry.enabled`` + + If unified is off, this controls whether the Telemetry module is enabled. + If unified is on, this controls whether to record *extended* data. + This preference is controlled through the `Preferences` dialog. From f594e8e05a2e4b32dbfd73de1d87b0a04da3ca3e Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Thu, 30 Apr 2015 14:00:58 +0200 Subject: [PATCH 15/88] Bug 1148500 - Part 2 (Fx 40): Complete Telemetry pref documentation. r=dexter --- toolkit/components/telemetry/docs/pings.rst | 10 ------- .../components/telemetry/docs/preferences.rst | 30 +++++++++++++++++-- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/toolkit/components/telemetry/docs/pings.rst b/toolkit/components/telemetry/docs/pings.rst index 2eb3078450d..28b5af3b5e1 100644 --- a/toolkit/components/telemetry/docs/pings.rst +++ b/toolkit/components/telemetry/docs/pings.rst @@ -45,13 +45,3 @@ To allow for cheaper lookup of archived pings, storage follows a specific naming * ```` - Timestamp of the ping creation date. * ```` - The ping identifier. * ```` - The ping type. - -Preferences -=========== - -Telemetry behaviour is controlled through the following preferences: - -* ``datareporting.healthreport.service.enabled`` - If true, records base Telemetry data. Otherwise, completely disables telemetry recording. -* ``toolkit.telemetry.enabled`` - If true, record the extended Telemetry data. Please note that base Telemetry data needs to be enabled as well and we need to be in an official build or in test mode. This preference is controlled through the `Preferences` dialog. -* ``datareporting.healthreport.uploadEnabled`` - Send the data we record if user has consented to FHR. This preference is controlled through the `Preferences` dialog. -* ``toolkit.telemetry.archive.enabled`` - Allow pings to be archived locally. diff --git a/toolkit/components/telemetry/docs/preferences.rst b/toolkit/components/telemetry/docs/preferences.rst index 422411e89e1..50c0f74064b 100644 --- a/toolkit/components/telemetry/docs/preferences.rst +++ b/toolkit/components/telemetry/docs/preferences.rst @@ -1,7 +1,10 @@ Preferences =========== -Telemetry behaviour is controlled through the following preferences: +Telemetry behaviour is controlled through the preferences listed here. + +*Note:* On official builds (which define ``MOZILLA_OFFICIAL``), Telemetry is only initialized when ``MOZ_TELEMETRY_REPORTING`` is defined. +Sending only happens on official builds with ``MOZ_TELEMETRY_REPORTING`` defined. ``toolkit.telemetry.unified`` @@ -12,6 +15,27 @@ Telemetry behaviour is controlled through the following preferences: ``toolkit.telemetry.enabled`` - If unified is off, this controls whether the Telemetry module is enabled. - If unified is on, this controls whether to record *extended* data. + If ``unified`` is off, this controls whether the Telemetry module is enabled. + If ``unified`` is on, this controls whether to record *extended* data. This preference is controlled through the `Preferences` dialog. + +``datareporting.healthreport.uploadEnabled`` + + Send the data we record if user has consented to FHR. This preference is controlled through the `Preferences` dialog. + +``toolkit.telemetry.archive.enabled`` + + Allow pings to be archived locally. This can only be enabled if ``unified`` is on. + +``toolkit.telemetry.server`` + + The server Telemetry pings are sent to. + +``toolkit.telemetry.log.level`` + + This sets the Telemetry logging verbosity per ``Log.jsm``, with ``Trace`` or ``0`` being the most verbose and the default being ``Warn``. + By default logging goes only the console service. + +``toolkit.telemetry.log.dump`` + + Sets whether to dump Telemetry log messages to ``stdout`` too. From e3bc63a2a14cc593835b9a55452b17a100ea2660 Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Thu, 30 Apr 2015 14:00:58 +0200 Subject: [PATCH 16/88] Bug 1148500 - Part 3 (Fx 40): Honor toolkit.telemetry.unified pref in Telemetry modules. r=dexter --- .../telemetry/TelemetryController.jsm | 28 +++--- .../components/telemetry/TelemetrySession.jsm | 93 ++++++++++--------- 2 files changed, 64 insertions(+), 57 deletions(-) diff --git a/toolkit/components/telemetry/TelemetryController.jsm b/toolkit/components/telemetry/TelemetryController.jsm index 8f38f04587e..6f35746483f 100644 --- a/toolkit/components/telemetry/TelemetryController.jsm +++ b/toolkit/components/telemetry/TelemetryController.jsm @@ -35,6 +35,11 @@ const PREF_CACHED_CLIENTID = PREF_BRANCH + "cachedClientID"; const PREF_FHR_ENABLED = "datareporting.healthreport.service.enabled"; const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled"; const PREF_SESSIONS_BRANCH = "datareporting.sessions."; +const PREF_UNIFIED = PREF_BRANCH + "unified"; + +// Whether the FHR/Telemetry unification features are enabled. +// Changing this pref requires a restart. +const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false); const PING_FORMAT_VERSION = 4; @@ -816,13 +821,10 @@ let Impl = { * false otherwise. */ enableTelemetryRecording: function enableTelemetryRecording() { + const enabled = Preferences.get(PREF_ENABLED, false); + // Enable base Telemetry recording, if needed. -#if !defined(MOZ_WIDGET_ANDROID) - Telemetry.canRecordBase = Preferences.get(PREF_FHR_ENABLED, false); -#else - // FHR recording is always "enabled" on Android (data upload is not). - Telemetry.canRecordBase = true; -#endif + Telemetry.canRecordBase = enabled || IS_UNIFIED_TELEMETRY; #ifdef MOZILLA_OFFICIAL if (!Telemetry.isOfficialTelemetry && !this._testMode) { @@ -834,7 +836,6 @@ let Impl = { } #endif - let enabled = Preferences.get(PREF_ENABLED, false); this._server = Preferences.get(PREF_SERVER, undefined); if (!enabled || !Telemetry.canRecordBase) { // Turn off extended telemetry recording if disabled by preferences or if base/telemetry @@ -874,16 +875,13 @@ let Impl = { // Only initialize the session recorder if FHR is enabled. // TODO: move this after the |enableTelemetryRecording| block and drop the - // PREF_FHR_ENABLED check after bug 1137252 lands. - if (!this._sessionRecorder && Preferences.get(PREF_FHR_ENABLED, true)) { + // PREF_FHR_ENABLED check once we permanently switch over to unified Telemetry. + if (!this._sessionRecorder && + (Preferences.get(PREF_FHR_ENABLED, true) || IS_UNIFIED_TELEMETRY)) { this._sessionRecorder = new SessionRecorder(PREF_SESSIONS_BRANCH); this._sessionRecorder.onStartup(); } - // Initialize some probes that are kept in their own modules - this._thirdPartyCookies = new ThirdPartyCookieProbe(); - this._thirdPartyCookies.init(); - if (!this.enableTelemetryRecording()) { this._log.config("setupChromeProcess - Telemetry recording is disabled, skipping Chrome process setup."); return Promise.resolve(); @@ -1031,11 +1029,13 @@ let Impl = { /** * Check if pings can be sent to the server. If FHR is not allowed to upload, * pings are not sent to the server (Telemetry is a sub-feature of FHR). + * If unified telemetry is off, don't send pings if Telemetry is disabled. * @return {Boolean} True if pings can be send to the servers, false otherwise. */ _canSend: function() { return (Telemetry.isOfficialTelemetry || this._testMode) && - Preferences.get(PREF_FHR_UPLOAD_ENABLED, false); + Preferences.get(PREF_FHR_UPLOAD_ENABLED, false) && + (IS_UNIFIED_TELEMETRY || Preferences.get(PREF_ENABLED)); }, /** diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm index 294dbf74720..9fbef9be724 100644 --- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -60,6 +60,8 @@ const PREF_BRANCH = "toolkit.telemetry."; const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID"; const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled"; const PREF_ASYNC_PLUGIN_INIT = "dom.ipc.plugins.asyncInit"; +const PREF_UNIFIED = PREF_BRANCH + "unified"; + const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload"; const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload"; @@ -69,6 +71,10 @@ const ABORTED_SESSION_FILE_NAME = "aborted-session-ping"; const SESSION_STATE_FILE_NAME = "session-state.json"; +// Whether the FHR/Telemetry unification features are enabled. +// Changing this pref requires a restart. +const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false); + // Maximum number of content payloads that we are willing to store. const MAX_NUM_CONTENT_PAYLOADS = 10; @@ -1339,12 +1345,8 @@ let Impl = { * respectively. */ assemblePayloadWithMeasurements: function(simpleMeasurements, info, reason, clearSubsession) { -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - const isSubsession = !this._isClassicReason(reason); -#else - const isSubsession = false; - clearSubsession = false; -#endif + const isSubsession = IS_UNIFIED_TELEMETRY && !this._isClassicReason(reason); + clearSubsession = IS_UNIFIED_TELEMETRY && clearSubsession; this._log.trace("assemblePayloadWithMeasurements - reason: " + reason + ", submitting subsession data: " + isSubsession); @@ -1481,6 +1483,11 @@ let Impl = { return Promise.resolve(); } + if (!Telemetry.canRecordBase && !testing) { + this._log.config("setupChromeProcess - Telemetry recording is disabled, skipping Chrome process setup."); + return Promise.resolve(); + } + // Generate a unique id once per session so the server can cope with duplicate // submissions, orphaning and other oddities. The id is shared across subsessions. this._sessionId = Policy.generateSessionUUID(); @@ -1503,11 +1510,6 @@ let Impl = { Preferences.set(PREF_PREVIOUS_BUILDID, thisBuildID); } - if (!Telemetry.canRecordBase && !testing) { - this._log.config("setupChromeProcess - Telemetry recording is disabled, skipping Chrome process setup."); - return Promise.resolve(); - } - TelemetryController.shutdown.addBlocker("TelemetrySession: shutting down", () => this.shutdownChromeProcess(), () => this._getState()); @@ -1541,23 +1543,26 @@ let Impl = { Telemetry.asyncFetchTelemetryData(function () {}); -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - // Check for a previously written aborted session ping. - yield this._checkAbortedSessionPing(); + if (IS_UNIFIED_TELEMETRY) { + // Check for a previously written aborted session ping. + yield this._checkAbortedSessionPing(); - TelemetryEnvironment.registerChangeListener(ENVIRONMENT_CHANGE_LISTENER, - (reason, data) => this._onEnvironmentChange(reason, data)); - // Write the first aborted-session ping as early as possible. Just do that - // if we are not testing, since calling Telemetry.reset() will make a previous - // aborted ping a pending ping. - if (!testing) { - yield this._saveAbortedSessionPing(); + // Write the first aborted-session ping as early as possible. Just do that + // if we are not testing, since calling Telemetry.reset() will make a previous + // aborted ping a pending ping. + if (!testing) { + yield this._saveAbortedSessionPing(); + } + + TelemetryEnvironment.registerChangeListener(ENVIRONMENT_CHANGE_LISTENER, + (reason, data) => this._onEnvironmentChange(reason, data)); + + // Start the scheduler. + // We skip this if unified telemetry is off, so we don't + // trigger the new unified ping types. + TelemetryScheduler.init(); } - // Start the scheduler. - TelemetryScheduler.init(); -#endif - this._delayedInitTaskDeferred.resolve(); } catch (e) { this._delayedInitTaskDeferred.reject(e); @@ -1663,7 +1668,10 @@ let Impl = { savePendingPings: function savePendingPings() { this._log.trace("savePendingPings"); -#ifndef MOZ_WIDGET_ANDROID + if (!IS_UNIFIED_TELEMETRY) { + return this.savePendingPingsClassic(); + } + let options = { retentionDays: RETENTION_DAYS, addClientId: true, @@ -1677,9 +1685,6 @@ let Impl = { return TelemetryController.addPendingPing(getPingType(shutdownPayload), shutdownPayload, options) .then(() => this.savePendingPingsClassic(), () => this.savePendingPingsClassic()); -#else - return this.savePendingPingsClassic(); -#endif }, /** @@ -1869,10 +1874,10 @@ let Impl = { this._log.trace("shutdownChromeProcess - testing: " + testing); let cleanup = () => { -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - TelemetryEnvironment.unregisterChangeListener(ENVIRONMENT_CHANGE_LISTENER); - TelemetryScheduler.shutdown(); -#endif + if (IS_UNIFIED_TELEMETRY) { + TelemetryEnvironment.unregisterChangeListener(ENVIRONMENT_CHANGE_LISTENER); + TelemetryScheduler.shutdown(); + } this.uninstall(); let reset = () => { @@ -1881,13 +1886,17 @@ let Impl = { }; if (Telemetry.isOfficialTelemetry || testing) { - return this.savePendingPings() - .then(() => this._stateSaveSerializer.flushTasks()) -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - .then(() => this._abortedSessionSerializer - .enqueueTask(() => this._removeAbortedSessionPing())) -#endif - .then(reset); + return Task.spawn(function*() { + yield this.savePendingPings(); + yield this._stateSaveSerializer.flushTasks(); + + if (IS_UNIFIED_TELEMETRY) { + yield this._abortedSessionSerializer + .enqueueTask(() => this._removeAbortedSessionPing()); + } + + reset(); + }.bind(this)); } reset(); @@ -1933,13 +1942,11 @@ let Impl = { }; let promise = TelemetryController.submitExternalPing(getPingType(payload), payload, options); -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) // If required, also save the payload as an aborted session. - if (saveAsAborted) { + if (saveAsAborted && IS_UNIFIED_TELEMETRY) { let abortedPromise = this._saveAbortedSessionPing(payload); promise = promise.then(() => abortedPromise); } -#endif return promise; }, From 43e04883db740f0fab688d42560da912d689a809 Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Thu, 30 Apr 2015 14:00:58 +0200 Subject: [PATCH 17/88] Bug 1148500 - Part 4 (Fx 40): Disable Telemetry recording in reftests. r=dexter --- layout/tools/reftest/runreftest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/layout/tools/reftest/runreftest.py b/layout/tools/reftest/runreftest.py index 45bf9a8203f..fda6b857b25 100644 --- a/layout/tools/reftest/runreftest.py +++ b/layout/tools/reftest/runreftest.py @@ -206,9 +206,7 @@ class RefTest(object): # Ensure that telemetry is disabled, so we don't connect to the telemetry # server in the middle of the tests. prefs['toolkit.telemetry.enabled'] = False - # Don't send Telemetry reports to the production server. This is - # needed as Telemetry sends pings also if FHR upload is enabled. - prefs['toolkit.telemetry.server'] = 'http://%(server)s/telemetry-dummy/' + prefs['toolkit.telemetry.unified'] = False # Likewise for safebrowsing. prefs['browser.safebrowsing.enabled'] = False prefs['browser.safebrowsing.malware.enabled'] = False From f7d71d417c4f89230aacf74332f5f69e76305ef6 Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Thu, 30 Apr 2015 14:00:58 +0200 Subject: [PATCH 18/88] Bug 1140037 - (Fx 40) Fuzz daily telemetry ping submission times to avoid submission volume spikes around midnight. r=dexter --- .../telemetry/TelemetryController.jsm | 102 +++++++++++++++++- .../components/telemetry/tests/unit/head.js | 29 +++-- .../tests/unit/test_TelemetryController.js | 71 ++++++++++-- 3 files changed, 185 insertions(+), 17 deletions(-) diff --git a/toolkit/components/telemetry/TelemetryController.jsm b/toolkit/components/telemetry/TelemetryController.jsm index 6f35746483f..403b8adf717 100644 --- a/toolkit/components/telemetry/TelemetryController.jsm +++ b/toolkit/components/telemetry/TelemetryController.jsm @@ -21,6 +21,7 @@ Cu.import("resource://gre/modules/PromiseUtils.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); Cu.import("resource://gre/modules/DeferredTask.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/Timer.jsm"); const LOGGER_NAME = "Toolkit.Telemetry"; const LOGGER_PREFIX = "TelemetryController::"; @@ -52,6 +53,12 @@ const DEFAULT_RETENTION_DAYS = 14; // Timeout after which we consider a ping submission failed. const PING_SUBMIT_TIMEOUT_MS = 2 * 60 * 1000; +// We treat pings before midnight as happening "at midnight" with this tolerance. +const MIDNIGHT_TOLERANCE_MS = 15 * 60 * 1000; +// We try to spread "midnight" pings out over this interval. +const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * 60 * 1000; +const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS; + XPCOMUtils.defineLazyModuleGetter(this, "ClientID", "resource://gre/modules/ClientID.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", @@ -119,11 +126,30 @@ function isNewPingFormat(aPing) { ("version" in aPing) && (aPing.version >= 2); } +/** + * Takes a date and returns it trunctated to a date with daily precision. + */ +function truncateToDays(date) { + return new Date(date.getFullYear(), + date.getMonth(), + date.getDate(), + 0, 0, 0, 0); +} + +function tomorrow(date) { + let d = new Date(date); + d.setDate(d.getDate() + 1); + return d; +} + /** * This is a policy object used to override behavior for testing. */ let Policy = { now: () => new Date(), + midnightPingFuzzingDelay: () => MIDNIGHT_FUZZING_DELAY_MS, + setPingSendTimeout: (callback, delayMs) => setTimeout(callback, delayMs), + clearPingSendTimeout: (id) => clearTimeout(id), } this.EXPORTED_SYMBOLS = ["TelemetryController"]; @@ -345,6 +371,9 @@ let Impl = { _delayedInitTask: null, // The deferred promise resolved when the initialization task completes. _delayedInitTaskDeferred: null, + // Timer for scheduled ping sends. + _pingSendTimer: null, + // The session recorder, shared with FHR and the Data Reporting Service. _sessionRecorder: null, // This is a public barrier Telemetry clients can use to add blockers to the shutdown @@ -488,6 +517,31 @@ let Impl = { }, error => this._log.error("addPendingPingFromFile - Unable to add the pending ping", error)); }, + /** + * This helper calculates the next time that we can send pings at. + * Currently this mostly redistributes ping sends around midnight to avoid submission + * spikes around local midnight for daily pings. + * + * @param now Date The current time. + * @return Number The next time (ms from UNIX epoch) when we can send pings. + */ + _getNextPingSendTime: function(now) { + const todayDate = truncateToDays(now); + const tomorrowDate = tomorrow(todayDate); + const nextMidnightRangeStart = tomorrowDate.getTime() - MIDNIGHT_TOLERANCE_MS; + const currentMidnightRangeEnd = todayDate.getTime() - MIDNIGHT_TOLERANCE_MS + Policy.midnightPingFuzzingDelay(); + + if (now.getTime() < currentMidnightRangeEnd) { + return currentMidnightRangeEnd; + } + + if (now.getTime() >= nextMidnightRangeStart) { + return nextMidnightRangeStart + Policy.midnightPingFuzzingDelay(); + } + + return now.getTime(); + }, + /** * Submit ping payloads to Telemetry. This will assemble a complete ping, adding * environment data, client id and some general info. @@ -514,12 +568,23 @@ let Impl = { // Always persist the pings if we are allowed to. let archivePromise = TelemetryArchive.promiseArchivePing(pingData) .catch(e => this._log.error("submitExternalPing - Failed to archive ping " + pingData.id, e)); - let p = [ archivePromise ]; - if (!this._initialized) { - // We are still initializing and should not send yet, add this to the pending pings. - this._log.trace("submitExternalPing - still initializing, ping is pending"); + // Check if we can send pings now. + const now = Policy.now(); + const nextPingSendTime = this._getNextPingSendTime(now); + const throttled = (nextPingSendTime > now.getTime()); + + // We can't send pings now, schedule a later send. + if (throttled) { + this._log.trace("submitExternalPing - throttled, delaying ping send to " + new Date(nextPingSendTime)); + this._reschedulePingSendTimer(nextPingSendTime); + } + + if (!this._initialized || throttled) { + // We can't send because we are still initializing or throttled, add this to the pending pings. + this._log.trace("submitExternalPing - ping is pending, initialized: " + this._initialized + + ", throttled: " + throttled); p.push(TelemetryStorage.addPendingPing(pingData)); } else { // Try to send the ping, persist it if sending it fails. @@ -546,6 +611,16 @@ let Impl = { return Promise.resolve(); } + // Check if we can send pings now - otherwise schedule a later send. + const now = Policy.now(); + const nextPingSendTime = this._getNextPingSendTime(now); + if (nextPingSendTime > now.getTime()) { + this._log.trace("sendPersistedPings - delaying ping send to " + new Date(nextPingSendTime)); + this._reschedulePingSendTimer(nextPingSendTime); + return Promise.resolve(); + } + + // We can send now. let pingsIterator = Iterator(this.popPayloads()); let p = [for (data of pingsIterator) this.doPing(data, true).catch((e) => { this._log.error("sendPersistedPings - doPing rejected", e); @@ -959,7 +1034,11 @@ let Impl = { try { // First wait for clients processing shutdown. yield this._shutdownBarrier.wait(); - // Then wait for any outstanding async ping activity. + + // Then clear scheduled ping sends... + this._clearPingSendTimer(); + + // ... and wait for any outstanding async ping activity. yield this._connectionsBarrier.wait(); } finally { // Reset state. @@ -1051,6 +1130,19 @@ let Impl = { }; }, + _reschedulePingSendTimer: function(timestamp) { + this._clearPingSendTimer(); + const interval = timestamp - Policy.now(); + this._pingSendTimer = Policy.setPingSendTimeout(() => this.sendPersistedPings(), interval); + }, + + _clearPingSendTimer: function() { + if (this._pingSendTimer) { + Policy.clearPingSendTimeout(this._pingSendTimer); + this._pingSendTimer = null; + } + }, + /** * Allows waiting for TelemetryControllers delayed initialization to complete. * This will complete before TelemetryController is shutting down. diff --git a/toolkit/components/telemetry/tests/unit/head.js b/toolkit/components/telemetry/tests/unit/head.js index 9daaf430036..c3748df7dbc 100644 --- a/toolkit/components/telemetry/tests/unit/head.js +++ b/toolkit/components/telemetry/tests/unit/head.js @@ -144,17 +144,31 @@ function fakeSchedulerTimer(set, clear) { */ function fakeNow(...args) { const date = new Date(...args); + const modules = [ + Cu.import("resource://gre/modules/TelemetrySession.jsm"), + Cu.import("resource://gre/modules/TelemetryEnvironment.jsm"), + Cu.import("resource://gre/modules/TelemetryController.jsm"), + ]; - let ping = Cu.import("resource://gre/modules/TelemetryController.jsm"); - ping.Policy.now = () => date; - let session = Cu.import("resource://gre/modules/TelemetrySession.jsm"); - session.Policy.now = () => date; - let environment = Cu.import("resource://gre/modules/TelemetryEnvironment.jsm"); - environment.Policy.now = () => date; + for (let m of modules) { + m.Policy.now = () => date; + } return new Date(date); } +// Fake the timeout functions for TelemetryController sending. +function fakePingSendTimer(set, clear) { + let ping = Cu.import("resource://gre/modules/TelemetryController.jsm"); + ping.Policy.setPingSendTimeout = set; + ping.Policy.clearPingSendTimeout = clear; +} + +function fakeMidnightPingFuzzingDelay(delayMs) { + let ping = Cu.import("resource://gre/modules/TelemetryController.jsm"); + ping.Policy.midnightPingFuzzingDelay = () => delayMs; +} + // Return a date that is |offset| ms in the future from |date|. function futureDate(date, offset) { return new Date(date.getTime() + offset); @@ -179,3 +193,6 @@ Services.prefs.setBoolPref("toolkit.telemetry.archive.enabled", true); // Avoid timers interrupting test behavior. fakeSchedulerTimer(() => {}, () => {}); +fakePingSendTimer(() => {}, () => {}); +// Make pind sending predictable. +fakeMidnightPingFuzzingDelay(0); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js index 4d00a840219..f8ee8aee933 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js @@ -150,17 +150,12 @@ add_task(function* asyncSetup() { // Ensure that not overwriting an existing file fails silently add_task(function* test_overwritePing() { - let ping = {id: "foo"} + let ping = {id: "foo"}; yield TelemetryStorage.savePing(ping, true); yield TelemetryStorage.savePing(ping, false); yield TelemetryStorage.cleanupPingFile(ping); }); -// Sends a ping to a non existing server. -add_task(function* test_noServerPing() { - yield sendPing(false, false); -}); - // Checks that a sent ping is correctly received by a dummy http server. add_task(function* test_simplePing() { startWebserver(); @@ -265,6 +260,70 @@ add_task(function* test_archivePings() { Assert.equal(ping.id, pingId, "TelemetryController must archive pings if FHR is enabled."); }); +// Test that we fuzz the submission time around midnight properly +// to avoid overloading the telemetry servers. +add_task(function* test_midnightPingSendFuzzing() { + const fuzzingDelay = 60 * 60 * 1000; + fakeMidnightPingFuzzingDelay(fuzzingDelay); + let now = new Date(2030, 5, 1, 11, 00, 0); + fakeNow(now); + + let pingSendTimerCallback = null; + let pingSendTimeout = null; + fakePingSendTimer((callback, timeout) => { + pingSendTimerCallback = callback; + pingSendTimeout = timeout; + }, () => {}); + + gRequestIterator = Iterator(new Request()); + yield TelemetryController.reset(); + + // A ping submitted shortly before midnight should not get sent yet. + now = new Date(2030, 5, 1, 23, 55, 0); + fakeNow(now); + registerPingHandler((req, res) => { + Assert.ok(false, "No ping should be received yet."); + }); + yield sendPing(true, true); + + Assert.ok(!!pingSendTimerCallback); + Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 0, 45, 0)); + + // A ping after midnight within the fuzzing delay should also not get sent. + now = new Date(2030, 5, 2, 0, 40, 0); + fakeNow(now); + pingSendTimeout = null; + yield sendPing(true, true); + Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 0, 45, 0)); + + // The Request constructor restores the previous ping handler. + gRequestIterator = Iterator(new Request()); + + // Setting the clock to after the fuzzing delay, we should trigger the two ping sends + // with the timer callback. + now = futureDate(now, pingSendTimeout); + fakeNow(now); + yield pingSendTimerCallback(); + let requests = []; + requests.push(yield gRequestIterator.next()); + requests.push(yield gRequestIterator.next()); + for (let req of requests) { + let ping = decodeRequestPayload(req); + checkPingFormat(ping, TEST_PING_TYPE, true, true); + } + + // Moving the clock further we should still send pings immediately. + now = futureDate(now, 5 * 60 * 1000); + yield sendPing(true, true); + let request = yield gRequestIterator.next(); + let ping = decodeRequestPayload(request); + checkPingFormat(ping, TEST_PING_TYPE, true, true); + + // Clean-up. + fakeMidnightPingFuzzingDelay(0); + fakePingSendTimer(() => {}, () => {}); +}); + add_task(function* stopServer(){ gHttpServer.stop(do_test_finished); }); From a604dbbbe8cbc72481e617383e05c04c4de08f35 Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Thu, 30 Apr 2015 14:00:58 +0200 Subject: [PATCH 19/88] Bug 1156857 - Fix StatisticsRecorder not being initialized in content processes. r=jimm --- toolkit/xre/nsEmbedFunctions.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index 506553312f5..d7034424ba6 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -76,6 +76,8 @@ #include "GeckoProfiler.h" + #include "base/histogram.h" + #if defined(MOZ_SANDBOX) && defined(XP_WIN) #define TARGET_SANDBOX_EXPORTS #include "mozilla/sandboxing/loggingCallbacks.h" @@ -137,6 +139,7 @@ XRE_LockProfileDirectory(nsIFile* aDirectory, } static int32_t sInitCounter; +static UniquePtr gStatisticsRecorder; nsresult XRE_InitEmbedding2(nsIFile *aLibXULDirectory, @@ -153,6 +156,9 @@ XRE_InitEmbedding2(nsIFile *aLibXULDirectory, if (++sInitCounter > 1) // XXXbsmedberg is this really the right solution? return NS_OK; + // This is needed by Telemetry to initialize histogram collection. + gStatisticsRecorder = MakeUnique(); + if (!aAppDirectory) aAppDirectory = aLibXULDirectory; @@ -206,6 +212,7 @@ XRE_TermEmbedding() gDirServiceProvider->DoShutdown(); NS_ShutdownXPCOM(nullptr); delete gDirServiceProvider; + gStatisticsRecorder = nullptr; } const char* From 6cb917c2a075541e08afc57ac7e91b3735fcc2c8 Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Wed, 29 Apr 2015 11:43:38 +0200 Subject: [PATCH 20/88] Backed out changeset ef6fd70cccf5 (bug 1145049) --- toolkit/devtools/server/actors/highlighter.js | 7 --- toolkit/devtools/server/actors/inspector.js | 48 +------------------ toolkit/devtools/server/actors/styles.js | 36 +------------- 3 files changed, 3 insertions(+), 88 deletions(-) diff --git a/toolkit/devtools/server/actors/highlighter.js b/toolkit/devtools/server/actors/highlighter.js index 0262b747473..45a37c1c254 100644 --- a/toolkit/devtools/server/actors/highlighter.js +++ b/toolkit/devtools/server/actors/highlighter.js @@ -184,9 +184,6 @@ let HighlighterActor = exports.HighlighterActor = protocol.ActorClass({ }, destroy: function() { - if (!this._inspector) { - return; - } protocol.Actor.prototype.destroy.call(this); this._destroyHighlighter(); @@ -198,10 +195,6 @@ let HighlighterActor = exports.HighlighterActor = protocol.ActorClass({ this._layoutHelpers = null; }, - disconnect: function () { - this.destroy(); - }, - /** * Display the box model highlighting on a given NodeActor. * There is only one instance of the box model highlighter, so calling this diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js index cc6ea41167a..8e5bc10480b 100644 --- a/toolkit/devtools/server/actors/inspector.js +++ b/toolkit/devtools/server/actors/inspector.js @@ -220,12 +220,6 @@ var NodeActor = exports.NodeActor = protocol.ActorClass({ this.rawNode.ownerDocument.documentElement === this.rawNode; }, - destroy: function () { - protocol.Actor.prototype.destroy.call(this); - this.rawNode = null; - this.walker = null; - }, - // Returns the JSON representation of this object over the wire. form: function(detail) { if (detail === "actorid") { @@ -1225,9 +1219,6 @@ var WalkerActor = protocol.ActorClass({ destroy: function() { try { - if (this._destroyed) { - return; - } this._destroyed = true; this.clearPseudoClassLocks(); @@ -1235,17 +1226,6 @@ var WalkerActor = protocol.ActorClass({ this._hoveredNode = null; this.rootDoc = null; - this.rootWin = null; - this.rootNode = null; - this.tabActor = null; - this.layoutHelpers = null; - this._orphaned = null; - this._retainedOrphans = null; - this._refMap.forEach(actor => { - this.unmanage(actor); - actor.destroy(); - }); - this._refMap = null; this.reflowObserver.off("reflows", this._onReflows); this.reflowObserver = null; @@ -3332,30 +3312,6 @@ var InspectorActor = exports.InspectorActor = protocol.ActorClass({ this.tabActor = tabActor; }, - destroy: function () { - protocol.Actor.prototype.destroy.call(this); - this._highlighterPromise = null; - this._pageStylePromise = null; - this._walkerPromise = null; - if (this.walker) { - this.walker.destroy(); - } - this.walker = null; - if (this.pageStyle) { - this.pageStyle.destroy(); - } - this.pageStyle = null; - if (this.highlighter) { - this.highlighter.destroy(); - } - this.highlighter = null; - this.tabActor = null; - }, - - disconnect: function () { - this.destroy(); - }, - get window() this.tabActor.window, getWalker: method(function(options={}) { @@ -3400,7 +3356,7 @@ var InspectorActor = exports.InspectorActor = protocol.ActorClass({ } this._pageStylePromise = this.getWalker().then(walker => { - return this.pageStyle = PageStyleActor(this); + return PageStyleActor(this); }); return this._pageStylePromise; }, { @@ -3429,7 +3385,7 @@ var InspectorActor = exports.InspectorActor = protocol.ActorClass({ } this._highlighterPromise = this.getWalker().then(walker => { - return this.highlighter = HighlighterActor(this, autohide); + return HighlighterActor(this, autohide); }); return this._highlighterPromise; }, { diff --git a/toolkit/devtools/server/actors/styles.js b/toolkit/devtools/server/actors/styles.js index 83bffc2b83f..39a6da7aa2f 100644 --- a/toolkit/devtools/server/actors/styles.js +++ b/toolkit/devtools/server/actors/styles.js @@ -124,32 +124,10 @@ var PageStyleActor = protocol.ActorClass({ // Stores the association of DOM objects -> actors this.refMap = new Map; - // Keep the list of StyleRuleActor created for font - // in order to clean them up when the PageStyleActor is collected - this.fontStyleActors = new Set; - this.onFrameUnload = this.onFrameUnload.bind(this); events.on(this.inspector.tabActor, "will-navigate", this.onFrameUnload); }, - destroy: function () { - if (!this.walker) - return; - protocol.Actor.prototype.destroy.call(this); - events.off(this.inspector.tabActor, "will-navigate", this.onFrameUnload); - this.inspector = null; - this.walker = null; - this.refMap.forEach(actor => { - this.unmanage(actor); - actor.destroy(); - }); - this.fontStyleActors.forEach(actor => actor.destroy()); - this.fontStyleActors = null; - this.refMap = null; - this.cssLogic = null; - this._styleElement = null; - }, - get conn() this.inspector.conn, form: function(detail) { @@ -329,9 +307,7 @@ var PageStyleActor = protocol.ActorClass({ // If this font comes from a @font-face rule if (font.rule) { - let styleActor = StyleRuleActor(this, font.rule); - this.fontStyleActors.add(styleActor); - fontFace.rule = styleActor; + fontFace.rule = StyleRuleActor(this, font.rule); fontFace.ruleText = font.rule.cssText; } @@ -987,16 +963,6 @@ var StyleRuleActor = protocol.ActorClass({ get conn() this.pageStyle.conn, - destroy: function () { - if (!this.rawStyle) - return; - protocol.Actor.prototype.destroy.call(this); - this.rawStyle = null; - this.pageStyle = null; - this.rawNode = null; - this.rawRule = null; - }, - // Objects returned by this actor are owned by the PageStyleActor // to which this rule belongs. get marshallPool() this.pageStyle, From 8b3997f1fa3a339fcfa881a2aca64f34e4732f6b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 29 Apr 2015 10:36:50 -0400 Subject: [PATCH 21/88] Bug 1155238: use RelengAPI-based Tooltool server. r=coop --- js/src/devtools/automation/winbuildenv.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/devtools/automation/winbuildenv.sh b/js/src/devtools/automation/winbuildenv.sh index 78030a9b898..ef09727c96f 100644 --- a/js/src/devtools/automation/winbuildenv.sh +++ b/js/src/devtools/automation/winbuildenv.sh @@ -34,7 +34,7 @@ export PATH="$(perl -le 'print join ":", grep { -d $_ } split ":", $ENV{PATH}')" if ! which mozmake 2>/dev/null; then export PATH="$PATH:$SOURCE/.." if ! which mozmake 2>/dev/null; then - TT_SERVER=${TT_SERVER:-http://tooltool.pvt.build.mozilla.org/build} + TT_SERVER=${TT_SERVER:-https://api.pub.build.mozilla.org/tooltool/} ( cd $SOURCE/..; ./scripts/scripts/tooltool/tooltool_wrapper.sh $SOURCE/browser/config/tooltool-manifests/${platform:-win32}/releng.manifest $TT_SERVER setup.sh c:/mozilla-build/python27/python.exe C:/mozilla-build/tooltool.py ) fi fi From c7e0b742a2e082e528dd50eb9733e8de5630f7a4 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Thu, 30 Apr 2015 12:06:18 +0200 Subject: [PATCH 22/88] Bug 1160104 - Fix "chrome://browser/content/tab-content.js, line 558: TypeError: docShell is null" failures r=mconley --- browser/base/content/tab-content.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/browser/base/content/tab-content.js b/browser/base/content/tab-content.js index ed94a7dd7e7..db943faca90 100644 --- a/browser/base/content/tab-content.js +++ b/browser/base/content/tab-content.js @@ -555,7 +555,9 @@ addEventListener("unload", () => { }, false); addMessageListener("Browser:AppTab", function(message) { - docShell.isAppTab = message.data.isAppTab; + if (docShell) { + docShell.isAppTab = message.data.isAppTab; + } }); let WebBrowserChrome = { From 89e2259a7fb51f8562047f339636a5fc8efde0c6 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Thu, 30 Apr 2015 07:30:22 -0700 Subject: [PATCH 23/88] Bug 1159929 - Cache the pref value for devtools.dump.emit in event emitter;r=fitzgen --- toolkit/devtools/event-emitter.js | 116 ++++++++++++++++-------------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/toolkit/devtools/event-emitter.js b/toolkit/devtools/event-emitter.js index 3c40f674d23..a2bbb192a19 100644 --- a/toolkit/devtools/event-emitter.js +++ b/toolkit/devtools/event-emitter.js @@ -25,6 +25,16 @@ module.exports = EventEmitter; const { Cu, components } = require("chrome"); const Services = require("Services"); const promise = require("promise"); +let loggingEnabled = true; + +if (!isWorker) { + loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit"); + Services.prefs.addObserver("devtools.dump.emit", { + observe: () => { + loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit"); + } + }, false); +} /** * Decorate an object with event emitter functionality. @@ -147,60 +157,60 @@ EventEmitter.prototype = { }, logEvent: function(aEvent, args) { - let logging = isWorker ? true : Services.prefs.getBoolPref("devtools.dump.emit"); - - if (logging) { - let caller, func, path; - if (!isWorker) { - caller = components.stack.caller.caller; - func = caller.name; - let file = caller.filename; - if (file.includes(" -> ")) { - file = caller.filename.split(/ -> /)[1]; - } - path = file + ":" + caller.lineNumber; - } - - let argOut = "("; - if (args.length === 1) { - argOut += aEvent; - } - - let out = "EMITTING: "; - - // We need this try / catch to prevent any dead object errors. - try { - for (let i = 1; i < args.length; i++) { - if (i === 1) { - argOut = "(" + aEvent + ", "; - } else { - argOut += ", "; - } - - let arg = args[i]; - argOut += arg; - - if (arg && arg.nodeName) { - argOut += " (" + arg.nodeName; - if (arg.id) { - argOut += "#" + arg.id; - } - if (arg.className) { - argOut += "." + arg.className; - } - argOut += ")"; - } - } - } catch(e) { - // Object is dead so the toolbox is most likely shutting down, - // do nothing. - } - - argOut += ")"; - out += "emit" + argOut + " from " + func + "() -> " + path + "\n"; - - dump(out); + if (!loggingEnabled) { + return; } + + let caller, func, path; + if (!isWorker) { + caller = components.stack.caller.caller; + func = caller.name; + let file = caller.filename; + if (file.includes(" -> ")) { + file = caller.filename.split(/ -> /)[1]; + } + path = file + ":" + caller.lineNumber; + } + + let argOut = "("; + if (args.length === 1) { + argOut += aEvent; + } + + let out = "EMITTING: "; + + // We need this try / catch to prevent any dead object errors. + try { + for (let i = 1; i < args.length; i++) { + if (i === 1) { + argOut = "(" + aEvent + ", "; + } else { + argOut += ", "; + } + + let arg = args[i]; + argOut += arg; + + if (arg && arg.nodeName) { + argOut += " (" + arg.nodeName; + if (arg.id) { + argOut += "#" + arg.id; + } + if (arg.className) { + argOut += "." + arg.className; + } + argOut += ")"; + } + } + } catch(e) { + // Object is dead so the toolbox is most likely shutting down, + // do nothing. + } + + argOut += ")"; + out += "emit" + argOut + " from " + func + "() -> " + path + "\n"; + + dump(out); }, }; From c9a6b9ed5369ef0da7b8caed83ff56fb55b348c5 Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Wed, 29 Apr 2015 11:55:08 +0200 Subject: [PATCH 24/88] Bug 1152733: change the name of Loop/ Hello histograms that recently changed to be enumerated types. r=vladan --- .../loop/content/shared/js/otSdkDriver.js | 4 ++-- .../test/mochitest/browser_mozLoop_telemetry.js | 4 ++-- .../loop/test/shared/otSdkDriver_test.js | 16 ++++++++-------- toolkit/components/telemetry/Histograms.json | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/browser/components/loop/content/shared/js/otSdkDriver.js b/browser/components/loop/content/shared/js/otSdkDriver.js index f6c52a36551..67d10316db6 100644 --- a/browser/components/loop/content/shared/js/otSdkDriver.js +++ b/browser/components/loop/content/shared/js/otSdkDriver.js @@ -789,7 +789,7 @@ loop.OTSdkDriver = (function() { bucket = buckets.MORE_THAN_5M; } - this.mozLoop.telemetryAddValue("LOOP_TWO_WAY_MEDIA_CONN_LENGTH", bucket); + this.mozLoop.telemetryAddValue("LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", bucket); this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_ALREADY_NOTED); this._connectionLengthNotedCalls++; @@ -857,7 +857,7 @@ loop.OTSdkDriver = (function() { return; } - this.mozLoop.telemetryAddValue("LOOP_SHARING_STATE_CHANGE", bucket); + this.mozLoop.telemetryAddValue("LOOP_SHARING_STATE_CHANGE_1", bucket); } }; diff --git a/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js index 071cc11a32c..46cda6b4369 100644 --- a/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js +++ b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js @@ -23,7 +23,7 @@ add_task(function* test_initialize() { * Tests that enumerated bucket histograms exist and can be updated. */ add_task(function* test_mozLoop_telemetryAdd_buckets() { - let histogramId = "LOOP_TWO_WAY_MEDIA_CONN_LENGTH"; + let histogramId = "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1"; let histogram = Services.telemetry.getHistogramById(histogramId); let CONN_LENGTH = gMozLoopAPI.TWO_WAY_MEDIA_CONN_LENGTH; @@ -49,7 +49,7 @@ add_task(function* test_mozLoop_telemetryAdd_buckets() { }); add_task(function* test_mozLoop_telemetryAdd_sharing_buckets() { - let histogramId = "LOOP_SHARING_STATE_CHANGE"; + let histogramId = "LOOP_SHARING_STATE_CHANGE_1"; let histogram = Services.telemetry.getHistogramById(histogramId); const SHARING_STATES = gMozLoopAPI.SHARING_STATE_CHANGE; diff --git a/browser/components/loop/test/shared/otSdkDriver_test.js b/browser/components/loop/test/shared/otSdkDriver_test.js index 5e2392606e9..4cfd4a8e9f3 100644 --- a/browser/components/loop/test/shared/otSdkDriver_test.js +++ b/browser/components/loop/test/shared/otSdkDriver_test.js @@ -473,7 +473,7 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(mozLoop.telemetryAddValue); sinon.assert.calledWith(mozLoop.telemetryAddValue, - "LOOP_TWO_WAY_MEDIA_CONN_LENGTH", + "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S); }); @@ -485,7 +485,7 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(mozLoop.telemetryAddValue); sinon.assert.calledWith(mozLoop.telemetryAddValue, - "LOOP_TWO_WAY_MEDIA_CONN_LENGTH", + "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S); }); @@ -497,7 +497,7 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(mozLoop.telemetryAddValue); sinon.assert.calledWith(mozLoop.telemetryAddValue, - "LOOP_TWO_WAY_MEDIA_CONN_LENGTH", + "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M); }); @@ -508,7 +508,7 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(mozLoop.telemetryAddValue); sinon.assert.calledWith(mozLoop.telemetryAddValue, - "LOOP_TWO_WAY_MEDIA_CONN_LENGTH", + "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M); }); @@ -530,7 +530,7 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(mozLoop.telemetryAddValue); sinon.assert.calledWithExactly(mozLoop.telemetryAddValue, - "LOOP_SHARING_STATE_CHANGE", + "LOOP_SHARING_STATE_CHANGE_1", mozLoop.SHARING_STATE_CHANGE.WINDOW_ENABLED); }); @@ -539,7 +539,7 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(mozLoop.telemetryAddValue); sinon.assert.calledWithExactly(mozLoop.telemetryAddValue, - "LOOP_SHARING_STATE_CHANGE", + "LOOP_SHARING_STATE_CHANGE_1", mozLoop.SHARING_STATE_CHANGE.BROWSER_ENABLED); }); @@ -548,7 +548,7 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(mozLoop.telemetryAddValue); sinon.assert.calledWithExactly(mozLoop.telemetryAddValue, - "LOOP_SHARING_STATE_CHANGE", + "LOOP_SHARING_STATE_CHANGE_1", mozLoop.SHARING_STATE_CHANGE.WINDOW_DISABLED); }); @@ -557,7 +557,7 @@ describe("loop.OTSdkDriver", function () { sinon.assert.calledOnce(mozLoop.telemetryAddValue); sinon.assert.calledWithExactly(mozLoop.telemetryAddValue, - "LOOP_SHARING_STATE_CHANGE", + "LOOP_SHARING_STATE_CHANGE_1", mozLoop.SHARING_STATE_CHANGE.BROWSER_DISABLED); }); }); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 35337eabbe1..c76951b18bb 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -7490,7 +7490,7 @@ "kind": "boolean", "description": "Stores 1 every time the URL is copied or shared." }, - "LOOP_TWO_WAY_MEDIA_CONN_LENGTH": { + "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1": { "alert_emails": ["firefox-dev@mozilla.org", "dmose@mozilla.com"], "expires_in_version": "43", "kind": "enumerated", @@ -7498,7 +7498,7 @@ "releaseChannelCollection": "opt-out", "description": "Connection length for bi-directionally connected media (0=SHORTER_THAN_10S, 1=BETWEEN_10S_AND_30S, 2=BETWEEN_30S_AND_5M, 3=MORE_THAN_5M)" }, - "LOOP_SHARING_STATE_CHANGE": { + "LOOP_SHARING_STATE_CHANGE_1": { "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"], "expires_in_version": "43", "kind": "enumerated", From 94dd87004660ab76e84afc7be5f90aa643606fcf Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Thu, 30 Apr 2015 11:08:27 -0400 Subject: [PATCH 25/88] Backed out changeset 41b2612eba71 (bug 1156857) for regressing bug 1100501. --- toolkit/xre/nsEmbedFunctions.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index d7034424ba6..506553312f5 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -76,8 +76,6 @@ #include "GeckoProfiler.h" - #include "base/histogram.h" - #if defined(MOZ_SANDBOX) && defined(XP_WIN) #define TARGET_SANDBOX_EXPORTS #include "mozilla/sandboxing/loggingCallbacks.h" @@ -139,7 +137,6 @@ XRE_LockProfileDirectory(nsIFile* aDirectory, } static int32_t sInitCounter; -static UniquePtr gStatisticsRecorder; nsresult XRE_InitEmbedding2(nsIFile *aLibXULDirectory, @@ -156,9 +153,6 @@ XRE_InitEmbedding2(nsIFile *aLibXULDirectory, if (++sInitCounter > 1) // XXXbsmedberg is this really the right solution? return NS_OK; - // This is needed by Telemetry to initialize histogram collection. - gStatisticsRecorder = MakeUnique(); - if (!aAppDirectory) aAppDirectory = aLibXULDirectory; @@ -212,7 +206,6 @@ XRE_TermEmbedding() gDirServiceProvider->DoShutdown(); NS_ShutdownXPCOM(nullptr); delete gDirServiceProvider; - gStatisticsRecorder = nullptr; } const char* From 9a78f562f777cbee9cfbbf04493a32848503f1ba Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Thu, 30 Apr 2015 16:41:13 +0100 Subject: [PATCH 26/88] Bug 1152761 - Add local storage for Loop's room keys in case recovery is required, and handle the recovery. r=mikedeboer --- browser/components/loop/modules/LoopRooms.jsm | 63 ++++- .../loop/modules/LoopRoomsCache.jsm | 159 +++++++++++ browser/components/loop/moz.build | 1 + browser/components/loop/test/xpcshell/head.js | 12 + .../loop/test/xpcshell/test_looprooms.js | 7 - .../test_looprooms_encryption_in_fxa.js | 267 ++++++++++++++++++ .../loop/test/xpcshell/xpcshell.ini | 1 + 7 files changed, 496 insertions(+), 14 deletions(-) create mode 100644 browser/components/loop/modules/LoopRoomsCache.jsm create mode 100644 browser/components/loop/test/xpcshell/test_looprooms_encryption_in_fxa.js diff --git a/browser/components/loop/modules/LoopRooms.jsm b/browser/components/loop/modules/LoopRooms.jsm index d3374c2d615..a258eeebe94 100644 --- a/browser/components/loop/modules/LoopRooms.jsm +++ b/browser/components/loop/modules/LoopRooms.jsm @@ -7,11 +7,13 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + const {MozLoopService, LOOP_SESSION_TYPE} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", + "resource://services-common/utils.js"); XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() { const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {}); return new EventEmitter(); @@ -19,8 +21,11 @@ XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() { XPCOMUtils.defineLazyGetter(this, "gLoopBundle", function() { return Services.strings.createBundle('chrome://browser/locale/loop/loop.properties'); }); + +XPCOMUtils.defineLazyModuleGetter(this, "LoopRoomsCache", + "resource:///modules/loop/LoopRoomsCache.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "loopUtils", - "resource:///modules/loop/utils.js", "utils") + "resource:///modules/loop/utils.js", "utils"); XPCOMUtils.defineLazyModuleGetter(this, "loopCrypto", "resource:///modules/loop/crypto.js", "LoopCrypto"); @@ -41,6 +46,8 @@ const roomsPushNotification = function(version, channelID) { let gDirty = true; // Global variable that keeps track of the currently used account. let gCurrentUser = null; +// Global variable that keeps track of the room cache. +let gRoomsCache = null; /** * Extend a `target` object with the properties defined in `source`. @@ -123,6 +130,13 @@ let LoopRoomsInternal = { */ rooms: new Map(), + get roomsCache() { + if (!gRoomsCache) { + gRoomsCache = new LoopRoomsCache(); + } + return gRoomsCache; + }, + /** * @var {String} sessionType The type of user session. May be 'FXA' or 'GUEST'. */ @@ -280,12 +294,40 @@ let LoopRoomsInternal = { throw new Error("Missing wrappedKey"); } - // Bug 1152761 will cause us to additionally store keys locally. We'll - // need to add some code for recovery in case decryption fails. - let key = yield this.promiseDecryptRoomKey(roomData.context.wrappedKey); + let savedRoomKey = yield this.roomsCache.getKey(this.sessionType, roomData.roomToken); + let fallback = false; + let key; + + try { + key = yield this.promiseDecryptRoomKey(roomData.context.wrappedKey); + } catch (error) { + // If we don't have a key saved, then we can't do anything. + if (!savedRoomKey) { + throw error; + } + + // We failed to decrypt the room key, so has our FxA key changed? + // If so, we fall-back to the saved room key. + key = savedRoomKey; + fallback = true; + } let decryptedData = yield loopCrypto.decryptBytes(key, roomData.context.value); + if (fallback) { + // Fallback decryption succeeded, so we need to re-encrypt the room key and + // save the data back again. + // XXX Bug 1152764 will implement this or make it a separate bug. + } else if (!savedRoomKey || key != savedRoomKey) { + // Decryption succeeded, but we don't have the right key saved. + try { + yield this.roomsCache.setKey(this.sessionType, roomData.roomToken, key); + } + catch (error) { + MozLoopService.log.error("Failed to save room key:", error); + } + } + roomData.roomKey = key; roomData.decryptedContext = JSON.parse(decryptedData); @@ -342,7 +384,7 @@ let LoopRoomsInternal = { this.saveAndNotifyUpdate(roomData, isUpdate); } catch (error) { - MozLoopService.log.error("Failed to decrypt room data: " + error); + MozLoopService.log.error("Failed to decrypt room data: ", error); // Do what we can to save the room data. room.decryptedContext = {}; this.saveAndNotifyUpdate(room, isUpdate); @@ -495,6 +537,9 @@ let LoopRoomsInternal = { this.setGuestCreatedRoom(true); } + // Now we've got the room token, we can save the key to disk. + yield this.roomsCache.setKey(this.sessionType, room.roomToken, room.roomKey); + eventEmitter.emit("add", room); callback(null, room); }.bind(this)).catch(callback); @@ -704,6 +749,10 @@ let LoopRoomsInternal = { sendData = { roomName: newRoomName }; + } else { + // This might be an upgrade to encrypted rename, so store the key + // just in case. + yield this.roomsCache.setKey(this.sessionType, all.roomToken, all.roomKey); } let response = yield MozLoopService.hawkRequest(this.sessionType, diff --git a/browser/components/loop/modules/LoopRoomsCache.jsm b/browser/components/loop/modules/LoopRoomsCache.jsm new file mode 100644 index 00000000000..e83ff11eb8a --- /dev/null +++ b/browser/components/loop/modules/LoopRoomsCache.jsm @@ -0,0 +1,159 @@ +/* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); +const {MozLoopService, LOOP_SESSION_TYPE} = + Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); +XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", + "resource://services-common/utils.js"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); + +this.EXPORTED_SYMBOLS = ["LoopRoomsCache"]; + +const LOOP_ROOMS_CACHE_FILENAME = "loopRoomsCache.json"; + +/** + * RoomsCache is a cache for saving simple rooms data to the disk in case we + * need it for back-up purposes, e.g. recording room keys for FxA if the user + * changes their password. + * + * The format of the data is: + * + * { + * : { + * : { + * "key": + * } + * } + * } + * + * It is intended to try and keep the data forward and backwards compatible in + * a reasonable manner, hence why the structure is more complex than it needs + * to be to store tokens and keys. + * + * @param {Object} options The options for the RoomsCache, containing: + * - {String} baseDir The base directory in which to save the file. + * - {String} filename The filename for the cache file. + */ +function LoopRoomsCache(options) { + options = options || {}; + + this.baseDir = options.baseDir || OS.Constants.Path.profileDir; + this.path = OS.Path.join( + this.baseDir, + options.filename || LOOP_ROOMS_CACHE_FILENAME + ); + this._cache = null; +} + +LoopRoomsCache.prototype = { + /** + * Updates the local copy of the cache and saves it to disk. + * + * @param {Object} contents An object to be saved in json format. + * @return {Promise} A promise that is resolved once the save is complete. + */ + _setCache: function(contents) { + this._cache = contents; + + return OS.File.makeDir(this.baseDir, {ignoreExisting: true}).then(() => { + return CommonUtils.writeJSON(contents, this.path); + }); + }, + + /** + * Returns the local copy of the cache if there is one, otherwise it reads + * it from the disk. + * + * @return {Promise} A promise that is resolved once the read is complete. + */ + _getCache: Task.async(function* () { + if (this._cache) { + return this._cache; + } + + try { + return (this._cache = yield CommonUtils.readJSON(this.path)); + } catch(error) { + // This is really complex due to OSFile's error handling, see bug 1160109. + if ((OS.Constants.libc && error.unixErrno != OS.Constants.libc.ENOENT) || + (OS.Constants.Win && error.winLastError != OS.Constants.Win.ERROR_FILE_NOT_FOUND)) { + MozLoopService.log.debug("Error reading the cache:", error); + } + return (this._cache = {}); + } + }), + + /** + * Function for testability purposes. Clears the cache. + * + * @return {Promise} A promise that is resolved once the clear is complete. + */ + clear: function() { + this._cache = null; + return OS.File.remove(this.path) + }, + + /** + * Gets a room key from the cache. + * + * @param {LOOP_SESSION_TYPE} sessionType The session type for the room. + * @param {String} roomToken The token for the room. + * @return {Promise} A promise that is resolved when the data has been read + * with the value of the key, or null if it isn't present. + */ + getKey: Task.async(function* (sessionType, roomToken) { + if (sessionType != LOOP_SESSION_TYPE.FXA) { + return null; + } + + let sessionData = (yield this._getCache())[sessionType]; + + if (!sessionData || !sessionData[roomToken]) { + return null; + } + return sessionData[roomToken].key; + }), + + /** + * Stores a room key into the cache. Note, if the key has not changed, + * the store will not be re-written. + * + * @param {LOOP_SESSION_TYPE} sessionType The session type for the room. + * @param {String} roomToken The token for the room. + * @param {String} roomKey The encryption key for the room. + * @return {Promise} A promise that is resolved when the data has been stored. + */ + setKey: Task.async(function* (sessionType, roomToken, roomKey) { + if (sessionType != LOOP_SESSION_TYPE.FXA) { + return; + } + + let cache = yield this._getCache(); + + // Create these objects if they don't exist. + // We aim to do this creation and setting of the room key in a + // forwards-compatible way so that if new fields are added to rooms later + // then we don't mess them up (if there's no keys). + if (!cache[sessionType]) { + cache[sessionType] = {}; + } + + if (!cache[sessionType][roomToken]) { + cache[sessionType][roomToken] = {}; + } + + // Only save it if there's no key, or it is different. + if (!cache[sessionType][roomToken].key || + cache[sessionType][roomToken].key != roomKey) { + cache[sessionType][roomToken].key = roomKey; + return yield this._setCache(cache); + } + }) +}; diff --git a/browser/components/loop/moz.build b/browser/components/loop/moz.build index a2cbc2469d8..11cd3bcca98 100644 --- a/browser/components/loop/moz.build +++ b/browser/components/loop/moz.build @@ -20,6 +20,7 @@ EXTRA_JS_MODULES.loop += [ 'modules/LoopCalls.jsm', 'modules/LoopContacts.jsm', 'modules/LoopRooms.jsm', + 'modules/LoopRoomsCache.jsm', 'modules/LoopStorage.jsm', 'modules/MozLoopAPI.jsm', 'modules/MozLoopPushHandler.jsm', diff --git a/browser/components/loop/test/xpcshell/head.js b/browser/components/loop/test/xpcshell/head.js index 59e90bb29b3..412254bcf7c 100644 --- a/browser/components/loop/test/xpcshell/head.js +++ b/browser/components/loop/test/xpcshell/head.js @@ -3,6 +3,9 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +// Initialize this before the imports, as some of them need it. +do_get_profile(); + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Http.jsm"); @@ -11,7 +14,9 @@ Cu.import("resource:///modules/loop/MozLoopService.jsm"); Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource:///modules/loop/LoopCalls.jsm"); Cu.import("resource:///modules/loop/LoopRooms.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); const { MozLoopServiceInternal } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); +const { LoopRoomsInternal } = Cu.import("resource:///modules/loop/LoopRooms.jsm", {}); XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler", "resource:///modules/loop/MozLoopPushHandler.jsm"); @@ -209,3 +214,10 @@ MockWebSocketChannel.prototype = { this.listener.onServerClose(this.context, err || -1); }, }; + +const extend = function(target, source) { + for (let key of Object.getOwnPropertyNames(source)) { + target[key] = source[key]; + } + return target; +}; diff --git a/browser/components/loop/test/xpcshell/test_looprooms.js b/browser/components/loop/test/xpcshell/test_looprooms.js index 5ab2d6c69a9..018f1727023 100644 --- a/browser/components/loop/test/xpcshell/test_looprooms.js +++ b/browser/components/loop/test/xpcshell/test_looprooms.js @@ -182,13 +182,6 @@ const kCreateRoomData = { const kChannelGuest = MozLoopService.channelIDs.roomsGuest; const kChannelFxA = MozLoopService.channelIDs.roomsFxA; -const extend = function(target, source) { - for (let key of Object.getOwnPropertyNames(source)) { - target[key] = source[key]; - } - return target; -}; - const normalizeRoom = function(room) { let newRoom = extend({}, room); let name = newRoom.decryptedContext.roomName; diff --git a/browser/components/loop/test/xpcshell/test_looprooms_encryption_in_fxa.js b/browser/components/loop/test/xpcshell/test_looprooms_encryption_in_fxa.js new file mode 100644 index 00000000000..bdb882988ff --- /dev/null +++ b/browser/components/loop/test/xpcshell/test_looprooms_encryption_in_fxa.js @@ -0,0 +1,267 @@ +/* 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/. */ + +Cu.import("resource://services-common/utils.js"); +const { LOOP_ROOMS_CACHE_FILENAME } = Cu.import("resource:///modules/loop/LoopRoomsCache.jsm", {}); + +const kContextEnabledPref = "loop.contextInConverations.enabled"; + +const kFxAKey = "uGIs-kGbYt1hBBwjyW7MLQ"; + +// Rooms details as responded by the server. +const kRoomsResponses = new Map([ + ["_nxD4V4FflQ", { + // Encrypted with roomKey "FliIGLUolW-xkKZVWstqKw". + // roomKey is wrapped with kFxAKey. + context: { + wrappedKey: "F3V27oPB+FgjFbVPML2PupONYqoIZ53XRU4BqG46Lr3eyIGumgCEqgjSe/MXAXiQ//8=", + value: "df7B4SNxhOI44eJjQavCevADyCCxz6/DEZbkOkRUMVUxzS42FbzN6C2PqmCKDYUGyCJTwJ0jln8TLw==", + alg: "AES-GCM" + }, + roomToken: "_nxD4V4FflQ", + roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ" + }], + ["QzBbvGmIZWU", { + context: { + wrappedKey: "AFu7WwFNjhWR5J6L8ks7S6H/1ktYVEw3yt1eIIWVaMabZaB3vh5612/FNzua4oS2oCM=", + value: "sqj+xRNEty8K3Q1gSMd5bIUYKu34JfiO2+LIMlJrOetFIbJdBoQ+U8JZNaTFl6Qp3RULZ41x0zeSBSk=", + alg: "AES-GCM" + }, + roomToken: "QzBbvGmIZWU", + roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU" + }] +]); + +const kExpectedRooms = new Map([ + ["_nxD4V4FflQ", { + context: { + wrappedKey: "F3V27oPB+FgjFbVPML2PupONYqoIZ53XRU4BqG46Lr3eyIGumgCEqgjSe/MXAXiQ//8=", + value: "df7B4SNxhOI44eJjQavCevADyCCxz6/DEZbkOkRUMVUxzS42FbzN6C2PqmCKDYUGyCJTwJ0jln8TLw==", + alg: "AES-GCM" + }, + decryptedContext: { + roomName: "First Room Name" + }, + roomKey: "FliIGLUolW-xkKZVWstqKw", + roomToken: "_nxD4V4FflQ", + roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ#FliIGLUolW-xkKZVWstqKw" + }], + ["QzBbvGmIZWU", { + context: { + wrappedKey: "AFu7WwFNjhWR5J6L8ks7S6H/1ktYVEw3yt1eIIWVaMabZaB3vh5612/FNzua4oS2oCM=", + value: "sqj+xRNEty8K3Q1gSMd5bIUYKu34JfiO2+LIMlJrOetFIbJdBoQ+U8JZNaTFl6Qp3RULZ41x0zeSBSk=", + alg: "AES-GCM" + }, + decryptedContext: { + roomName: "Loopy Discussion", + }, + roomKey: "h2H8Sa9QxLCTTiXNmJVtRA", + roomToken: "QzBbvGmIZWU", + roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU" + }] +]); + +const kCreateRoomProps = { + decryptedContext: { + roomName: "Say Hello", + }, + roomOwner: "Gavin", + maxSize: 2 +}; + +const kCreateRoomData = { + roomToken: "Vo2BFQqIaAM", + roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ", + expiresAt: 1405534180 +}; + +function getCachePath() { + return OS.Path.join(OS.Constants.Path.profileDir, LOOP_ROOMS_CACHE_FILENAME); +} + +function readRoomsCache() { + return CommonUtils.readJSON(getCachePath()); +} + +function saveRoomsCache(contents) { + delete LoopRoomsInternal.roomsCache._cache; + return CommonUtils.writeJSON(contents, getCachePath()); +} + +function clearRoomsCache() { + return LoopRoomsInternal.roomsCache.clear(); +} + +// This is a cut-down version of the one in test_looprooms.js. +add_task(function* setup_server() { + loopServer.registerPathHandler("/registration", (req, res) => { + res.setStatusLine(null, 200, "OK"); + res.processAsync(); + res.finish(); + }); + + loopServer.registerPathHandler("/rooms", (req, res) => { + res.setStatusLine(null, 200, "OK"); + + if (req.method == "POST") { + Assert.ok(req.bodyInputStream, "POST request should have a payload"); + let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream); + let data = JSON.parse(body); + + Assert.ok(!("decryptedContext" in data), "should not have any decrypted data"); + Assert.ok("context" in data, "should have context"); + + res.write(JSON.stringify(kCreateRoomData)); + } else { + res.write(JSON.stringify([...kRoomsResponses.values()])); + } + + res.processAsync(); + res.finish(); + }); + + function returnRoomDetails(res, roomName) { + roomDetail.roomName = roomName; + res.setStatusLine(null, 200, "OK"); + res.write(JSON.stringify(roomDetail)); + res.processAsync(); + res.finish(); + } + + function getJSONData(body) { + return JSON.parse(CommonUtils.readBytesFromInputStream(body)); + } + + // Add a request handler for each room in the list. + [...kRoomsResponses.values()].forEach(function(room) { + loopServer.registerPathHandler("/rooms/" + encodeURIComponent(room.roomToken), (req, res) => { + if (req.method == "POST") { + let data = getJSONData(req.bodyInputStream); + res.setStatusLine(null, 200, "OK"); + res.write(JSON.stringify(data)); + res.processAsync(); + res.finish(); + } else if (req.method == "PATCH") { + let data = getJSONData(req.bodyInputStream); + Assert.ok("context" in data, "should have encrypted context"); + // We return a fake encrypted name here as the context is + // encrypted. + returnRoomDetails(res, "fakeEncrypted"); + } else { + res.setStatusLine(null, 200, "OK"); + res.write(JSON.stringify(room)); + res.processAsync(); + res.finish(); + } + }); + }); + + loopServer.registerPathHandler("/rooms/error401", (req, res) => { + res.setStatusLine(null, 401, "Not Found"); + res.processAsync(); + res.finish(); + }); + + loopServer.registerPathHandler("/rooms/errorMalformed", (req, res) => { + res.setStatusLine(null, 200, "OK"); + res.write("{\"some\": \"Syntax Error!\"}}}}}}"); + res.processAsync(); + res.finish(); + }); + + mockPushHandler.registrationPushURL = kEndPointUrl; + + yield MozLoopService.promiseRegisteredWithServers(); +}); + + +// Test if getting rooms saves unknown keys correctly. +add_task(function* test_get_rooms_saves_unknown_keys() { + let rooms = yield LoopRooms.promise("getAll"); + + // Check that we've saved the encryption keys correctly. + let roomsCache = yield readRoomsCache(); + for (let room of [...kExpectedRooms.values()]) { + if (room.context.wrappedKey) { + Assert.equal(roomsCache[LOOP_SESSION_TYPE.FXA][room.roomToken].key, room.roomKey); + } + } + + yield clearRoomsCache(); +}); + +// Test that when we get a room it updates the saved key if it is different. +add_task(function* test_get_rooms_saves_different_keys() { + let roomsCache = {}; + roomsCache[LOOP_SESSION_TYPE.FXA] = { + QzBbvGmIZWU: {key: "fakeKey"} + }; + yield saveRoomsCache(roomsCache); + + const kRoomToken = "QzBbvGmIZWU"; + + let room = yield LoopRooms.promise("get", kRoomToken); + + // Check that we've saved the encryption keys correctly. + roomsCache = yield readRoomsCache(); + + Assert.notEqual(roomsCache[LOOP_SESSION_TYPE.FXA][kRoomToken].key, "fakeKey"); + Assert.equal(roomsCache[LOOP_SESSION_TYPE.FXA][kRoomToken].key, room.roomKey); + + yield clearRoomsCache(); +}); + +// Test that if roomKey decryption fails, the saved key is used for decryption. +add_task(function* test_get_rooms_uses_saved_key() { + const kRoomToken = "_nxD4V4FflQ"; + const kExpected = kExpectedRooms.get(kRoomToken) + + let roomsCache = {}; + roomsCache[LOOP_SESSION_TYPE.FXA] = { + "_nxD4V4FflQ": {key: kExpected.roomKey} + }; + yield saveRoomsCache(roomsCache); + + // Change the encryption key for FxA, so that decoding the room key will break. + Services.prefs.setCharPref("loop.key.fxa", "invalidKey"); + + let room = yield LoopRooms.promise("get", kRoomToken); + + Assert.deepEqual(room, kExpected); + + Services.prefs.setCharPref("loop.key.fxa", kFxAKey); + yield clearRoomsCache(); +}); + +// Test that when a room is created the new key is saved. +add_task(function* test_create_room_saves_key() { + let room = yield LoopRooms.promise("create", kCreateRoomProps); + + let roomsCache = yield readRoomsCache(); + + Assert.equal(roomsCache[LOOP_SESSION_TYPE.FXA][room.roomToken].key, room.roomKey); + + yield clearRoomsCache(); +}); + +function run_test() { + setupFakeLoopServer(); + + Services.prefs.setCharPref("loop.key.fxa", kFxAKey); + Services.prefs.setBoolPref(kContextEnabledPref, true); + + // Pretend we're signed into FxA. + MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" }; + MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" }; + + do_register_cleanup(function () { + Services.prefs.clearUserPref(kContextEnabledPref); + Services.prefs.clearUserPref("loop.key.fxa"); + + MozLoopServiceInternal.fxAOAuthTokenData = null; + MozLoopServiceInternal.fxAOAuthProfile = null; + }); + + run_next_test(); +} diff --git a/browser/components/loop/test/xpcshell/xpcshell.ini b/browser/components/loop/test/xpcshell/xpcshell.ini index 40ba2ce8d76..201c6709a6d 100644 --- a/browser/components/loop/test/xpcshell/xpcshell.ini +++ b/browser/components/loop/test/xpcshell/xpcshell.ini @@ -7,6 +7,7 @@ skip-if = toolkit == 'gonk' [test_loopapi_hawk_request.js] [test_looppush_initialize.js] [test_looprooms.js] +[test_looprooms_encryption_in_fxa.js] [test_loopservice_directcall.js] [test_loopservice_dnd.js] [test_loopservice_encryptionkey.js] From 134e60e42131b398524705ff7f82c5e62ed6beae Mon Sep 17 00:00:00 2001 From: Cedric Raudin Date: Thu, 30 Apr 2015 16:41:13 +0100 Subject: [PATCH 27/88] Bug 1115365 - Update the import string in the Loop contact panel to explain it will be from Google. r=Standard8 --- browser/components/loop/content/js/contacts.js | 2 +- browser/components/loop/content/js/contacts.jsx | 2 +- browser/locales/en-US/chrome/browser/loop/loop.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/browser/components/loop/content/js/contacts.js b/browser/components/loop/content/js/contacts.js index bb29539ce5f..bc6fcbb652f 100644 --- a/browser/components/loop/content/js/contacts.js +++ b/browser/components/loop/content/js/contacts.js @@ -587,7 +587,7 @@ loop.contacts = (function(_, mozL10n) { React.createElement(ButtonGroup, null, React.createElement(Button, {caption: this.state.importBusy ? mozL10n.get("importing_contacts_progress_button") - : mozL10n.get("import_contacts_button"), + : mozL10n.get("import_contacts_button2"), disabled: this.state.importBusy, onClick: this.handleImportButtonClick}, React.createElement("div", {className: cx({"contact-import-spinner": true, diff --git a/browser/components/loop/content/js/contacts.jsx b/browser/components/loop/content/js/contacts.jsx index b77f34eaa03..ab53c1145ba 100644 --- a/browser/components/loop/content/js/contacts.jsx +++ b/browser/components/loop/content/js/contacts.jsx @@ -587,7 +587,7 @@ loop.contacts = (function(_, mozL10n) {