From 3d121a074341e2d5b4de76460c67e56ffb890604 Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Fri, 19 Sep 2014 17:01:58 +0200 Subject: [PATCH 01/19] Bug 1000766: add a contacts list to the Loop contacts tab. r=Niko,Standard8,paolo --- .../components/loop/content/js/contacts.js | 190 ++++++++++++++++++ .../components/loop/content/js/contacts.jsx | 190 ++++++++++++++++++ browser/components/loop/content/js/panel.js | 3 +- browser/components/loop/content/js/panel.jsx | 3 +- browser/components/loop/content/panel.html | 2 + .../loop/content/shared/css/common.css | 12 ++ .../loop/content/shared/css/contacts.css | 163 +++++++++++++++ .../loop/content/shared/css/panel.css | 3 +- .../loop/content/shared/img/icons-10x10.svg | 53 +++++ .../loop/content/shared/img/icons-14x14.svg | 134 ++++++++++++ .../loop/content/shared/img/icons-16x16.svg | 5 + browser/components/loop/jar.mn | 4 + .../loop/test/desktop-local/index.html | 1 + .../loop/test/desktop-local/panel_test.js | 8 +- 14 files changed, 767 insertions(+), 4 deletions(-) create mode 100644 browser/components/loop/content/js/contacts.js create mode 100644 browser/components/loop/content/js/contacts.jsx create mode 100644 browser/components/loop/content/shared/css/contacts.css create mode 100644 browser/components/loop/content/shared/img/icons-10x10.svg create mode 100644 browser/components/loop/content/shared/img/icons-14x14.svg diff --git a/browser/components/loop/content/js/contacts.js b/browser/components/loop/content/js/contacts.js new file mode 100644 index 00000000000..cb7807fd31a --- /dev/null +++ b/browser/components/loop/content/js/contacts.js @@ -0,0 +1,190 @@ +/** @jsx React.DOM */ + +/* 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/. */ + +/*jshint newcap:false*/ +/*global loop:true, React */ + +var loop = loop || {}; +loop.contacts = (function(_, mozL10n) { + "use strict"; + + // Number of contacts to add to the list at the same time. + const CONTACTS_CHUNK_SIZE = 100; + + const ContactDetail = React.createClass({displayName: 'ContactDetail', + propTypes: { + handleContactClick: React.PropTypes.func, + contact: React.PropTypes.object.isRequired + }, + + handleContactClick: function() { + if (this.props.handleContactClick) { + this.props.handleContactClick(this.props.key); + } + }, + + getContactNames: function() { + // The model currently does not enforce a name to be present, but we're + // going to assume it is awaiting more advanced validation of required fields + // by the model. (See bug 1069918) + // NOTE: this method of finding a firstname and lastname is not i18n-proof. + let names = this.props.contact.name[0].split(" "); + return { + firstName: names.shift(), + lastName: names.join(" ") + }; + }, + + getPreferredEmail: function() { + // The model currently does not enforce a name to be present, but we're + // going to assume it is awaiting more advanced validation of required fields + // by the model. (See bug 1069918) + let email = this.props.contact.email[0]; + this.props.contact.email.some(function(address) { + if (address.pref) { + email = address; + return true; + } + return false; + }); + return email; + }, + + render: function() { + let names = this.getContactNames(); + let email = this.getPreferredEmail(); + let cx = React.addons.classSet; + let contactCSSClass = cx({ + contact: true, + blocked: this.props.contact.blocked + }); + + return ( + React.DOM.li({onClick: this.handleContactClick, className: contactCSSClass}, + React.DOM.div({className: "avatar"}), + React.DOM.div({className: "details"}, + React.DOM.div({className: "username"}, React.DOM.strong(null, names.firstName), " ", names.lastName, + React.DOM.i({className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}), + React.DOM.i({className: cx({"icon icon-blocked": this.props.contact.blocked})}) + ), + React.DOM.div({className: "email"}, email.value) + ), + React.DOM.div({className: "icons"}, + React.DOM.i({className: "icon icon-video"}), + React.DOM.i({className: "icon icon-caret-down"}) + ) + ) + ); + } + }); + + const ContactsList = React.createClass({displayName: 'ContactsList', + getInitialState: function() { + return { + contacts: {} + }; + }, + + componentDidMount: function() { + let contactsAPI = navigator.mozLoop.contacts; + + contactsAPI.getAll((err, contacts) => { + if (err) { + throw err; + } + + // Add contacts already present in the DB. We do this in timed chunks to + // circumvent blocking the main event loop. + let addContactsInChunks = () => { + contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => { + this.handleContactAddOrUpdate(contact); + }); + if (contacts.length) { + setTimeout(addContactsInChunks, 0); + } + }; + + addContactsInChunks(contacts); + + // Listen for contact changes/ updates. + contactsAPI.on("add", (eventName, contact) => { + this.handleContactAddOrUpdate(contact); + }); + contactsAPI.on("remove", (eventName, contact) => { + this.handleContactRemove(contact); + }); + contactsAPI.on("removeAll", () => { + this.handleContactRemoveAll(); + }); + contactsAPI.on("update", (eventName, contact) => { + this.handleContactAddOrUpdate(contact); + }); + }); + }, + + handleContactAddOrUpdate: function(contact) { + let contacts = this.state.contacts; + let guid = String(contact._guid); + contacts[guid] = contact; + this.setState({}); + }, + + handleContactRemove: function(contact) { + let contacts = this.state.contacts; + let guid = String(contact._guid); + if (!contacts[guid]) { + return; + } + delete contacts[guid]; + this.setState({}); + }, + + handleContactRemoveAll: function() { + this.setState({contacts: {}}); + }, + + sortContacts: function(contact1, contact2) { + let comp = contact1.name[0].localeCompare(contact2.name[0]); + if (comp !== 0) { + return comp; + } + // If names are equal, compare against unique ids make sure we have + // consistent ordering. + return contact1._guid - contact2._guid; + }, + + render: function() { + let viewForItem = item => { + return ContactDetail({key: item._guid, contact: item}) + }; + + let shownContacts = _.groupBy(this.state.contacts, function(contact) { + return contact.blocked ? "blocked" : "available"; + }); + + return ( + React.DOM.div({className: "listWrapper"}, + React.DOM.div({ref: "listSlider", className: "listPanels"}, + React.DOM.div({className: "faded"}, + React.DOM.ul(null, + shownContacts.available ? + shownContacts.available.sort(this.sortContacts).map(viewForItem) : + null, + shownContacts.blocked ? + shownContacts.blocked.sort(this.sortContacts).map(viewForItem) : + null + ) + ) + ) + ) + ); + } + }); + + return { + ContactsList: ContactsList + }; +})(_, document.mozL10n); diff --git a/browser/components/loop/content/js/contacts.jsx b/browser/components/loop/content/js/contacts.jsx new file mode 100644 index 00000000000..3ba83f4b60b --- /dev/null +++ b/browser/components/loop/content/js/contacts.jsx @@ -0,0 +1,190 @@ +/** @jsx React.DOM */ + +/* 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/. */ + +/*jshint newcap:false*/ +/*global loop:true, React */ + +var loop = loop || {}; +loop.contacts = (function(_, mozL10n) { + "use strict"; + + // Number of contacts to add to the list at the same time. + const CONTACTS_CHUNK_SIZE = 100; + + const ContactDetail = React.createClass({ + propTypes: { + handleContactClick: React.PropTypes.func, + contact: React.PropTypes.object.isRequired + }, + + handleContactClick: function() { + if (this.props.handleContactClick) { + this.props.handleContactClick(this.props.key); + } + }, + + getContactNames: function() { + // The model currently does not enforce a name to be present, but we're + // going to assume it is awaiting more advanced validation of required fields + // by the model. (See bug 1069918) + // NOTE: this method of finding a firstname and lastname is not i18n-proof. + let names = this.props.contact.name[0].split(" "); + return { + firstName: names.shift(), + lastName: names.join(" ") + }; + }, + + getPreferredEmail: function() { + // The model currently does not enforce a name to be present, but we're + // going to assume it is awaiting more advanced validation of required fields + // by the model. (See bug 1069918) + let email = this.props.contact.email[0]; + this.props.contact.email.some(function(address) { + if (address.pref) { + email = address; + return true; + } + return false; + }); + return email; + }, + + render: function() { + let names = this.getContactNames(); + let email = this.getPreferredEmail(); + let cx = React.addons.classSet; + let contactCSSClass = cx({ + contact: true, + blocked: this.props.contact.blocked + }); + + return ( +
  • +
    +
    +
    {names.firstName} {names.lastName} + + +
    +
    {email.value}
    +
    +
    + + +
    +
  • + ); + } + }); + + const ContactsList = React.createClass({ + getInitialState: function() { + return { + contacts: {} + }; + }, + + componentDidMount: function() { + let contactsAPI = navigator.mozLoop.contacts; + + contactsAPI.getAll((err, contacts) => { + if (err) { + throw err; + } + + // Add contacts already present in the DB. We do this in timed chunks to + // circumvent blocking the main event loop. + let addContactsInChunks = () => { + contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => { + this.handleContactAddOrUpdate(contact); + }); + if (contacts.length) { + setTimeout(addContactsInChunks, 0); + } + }; + + addContactsInChunks(contacts); + + // Listen for contact changes/ updates. + contactsAPI.on("add", (eventName, contact) => { + this.handleContactAddOrUpdate(contact); + }); + contactsAPI.on("remove", (eventName, contact) => { + this.handleContactRemove(contact); + }); + contactsAPI.on("removeAll", () => { + this.handleContactRemoveAll(); + }); + contactsAPI.on("update", (eventName, contact) => { + this.handleContactAddOrUpdate(contact); + }); + }); + }, + + handleContactAddOrUpdate: function(contact) { + let contacts = this.state.contacts; + let guid = String(contact._guid); + contacts[guid] = contact; + this.setState({}); + }, + + handleContactRemove: function(contact) { + let contacts = this.state.contacts; + let guid = String(contact._guid); + if (!contacts[guid]) { + return; + } + delete contacts[guid]; + this.setState({}); + }, + + handleContactRemoveAll: function() { + this.setState({contacts: {}}); + }, + + sortContacts: function(contact1, contact2) { + let comp = contact1.name[0].localeCompare(contact2.name[0]); + if (comp !== 0) { + return comp; + } + // If names are equal, compare against unique ids make sure we have + // consistent ordering. + return contact1._guid - contact2._guid; + }, + + render: function() { + let viewForItem = item => { + return + }; + + let shownContacts = _.groupBy(this.state.contacts, function(contact) { + return contact.blocked ? "blocked" : "available"; + }); + + return ( +
    +
    +
    +
      + {shownContacts.available ? + shownContacts.available.sort(this.sortContacts).map(viewForItem) : + null} + {shownContacts.blocked ? + shownContacts.blocked.sort(this.sortContacts).map(viewForItem) : + null} +
    +
    +
    +
    + ); + } + }); + + return { + ContactsList: ContactsList + }; +})(_, document.mozL10n); diff --git a/browser/components/loop/content/js/panel.js b/browser/components/loop/content/js/panel.js index 41c26921fcf..77b25826d3b 100644 --- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -14,6 +14,7 @@ loop.panel = (function(_, mozL10n) { var sharedViews = loop.shared.views; var sharedModels = loop.shared.models; var sharedMixins = loop.shared.mixins; + var ContactsList = loop.contacts.ContactsList; var __ = mozL10n.get; // aliasing translation function as __ for concision /** @@ -498,7 +499,7 @@ loop.panel = (function(_, mozL10n) { ToSView(null) ), Tab({name: "contacts"}, - React.DOM.span(null, "contacts") + ContactsList(null) ) ), React.DOM.div({className: "footer"}, diff --git a/browser/components/loop/content/js/panel.jsx b/browser/components/loop/content/js/panel.jsx index 3cf97c27cca..dc6c514207f 100644 --- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -14,6 +14,7 @@ loop.panel = (function(_, mozL10n) { var sharedViews = loop.shared.views; var sharedModels = loop.shared.models; var sharedMixins = loop.shared.mixins; + var ContactsList = loop.contacts.ContactsList; var __ = mozL10n.get; // aliasing translation function as __ for concision /** @@ -498,7 +499,7 @@ loop.panel = (function(_, mozL10n) { - contacts +
    diff --git a/browser/components/loop/content/panel.html b/browser/components/loop/content/panel.html index 497913f69ca..b169ace1122 100644 --- a/browser/components/loop/content/panel.html +++ b/browser/components/loop/content/panel.html @@ -9,6 +9,7 @@ + @@ -25,6 +26,7 @@ + diff --git a/browser/components/loop/content/shared/css/common.css b/browser/components/loop/content/shared/css/common.css index 577693ae112..91ce48d9e46 100644 --- a/browser/components/loop/content/shared/css/common.css +++ b/browser/components/loop/content/shared/css/common.css @@ -395,3 +395,15 @@ p { background: transparent url(../img/firefox-logo.png) no-repeat center center; background-size: contain; } + +.header { + padding: 5px 10px; + color: #888; + margin: 0; + border-bottom: 1px solid #CCC; + background: #EEE; + display: flex; + align-items: center; + flex-direction: row; + height: 24px; +} diff --git a/browser/components/loop/content/shared/css/contacts.css b/browser/components/loop/content/shared/css/contacts.css new file mode 100644 index 00000000000..072a1ce6148 --- /dev/null +++ b/browser/components/loop/content/shared/css/contacts.css @@ -0,0 +1,163 @@ +/* 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/. */ + +.contact { + display: flex; + flex-direction: row; + position: relative; + padding: 5px 10px; + color: #666; + font-size: 13px; + align-items: center; +} + +.contact:not(:first-child) { + border-top: 1px solid #ddd; +} + +.contact.blocked > .details > .username { + color: #d74345; +} + +.contact:hover { + background: #eee; +} + +.contact.selected { + background: #ebebeb; +} + +.contact:hover > .icons { + display: block; + z-index: 1000; +} + +.contact > .avatar { + width: 40px; + height: 40px; + background: #ccc; + border-radius: 50%; + margin-right: 10px; + overflow: hidden; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3); + background-image: url("../img/audio-call-avatar.svg"); + background-repeat: no-repeat; + background-color: #4ba6e7; + background-size: contain; +} + +.contact > .avatar > img { + width: 100%; +} + +.contact > .details > .username { + font-size: 12px; + line-height: 20px; + color: #222; + font-weight: normal; +} + +.contact > .details > .username > strong { + font-weight: bold; +} + +.contact > .details > .username > i.icon-blocked { + display: inline-block; + width: 10px; + height: 20px; + -moz-margin-start: 3px; + background-image: url("../img/icons-16x16.svg#block-red"); + background-position: center; + background-size: 10px 10px; + background-repeat: no-repeat; +} + +.contact > .details > .username > i.icon-google { + position: absolute; + right: 10px; + top: 35%; + width: 14px; + height: 14px; + border-radius: 50%; + background-image: url("../img/icons-16x16.svg#google"); + background-position: center; + background-size: 16px 16px; + background-repeat: no-repeat; + background-color: fff; +} + +.contact > .details > .email { + color: #999; + font-size: 11px; + line-height: 16px; +} + +.listWrapper { + overflow-x: hidden; + overflow-y: auto; + /* Show six contacts and scroll for the rest */ + max-height: 305px; +} + +.listPanels { + display: flex; + width: 200%; + flex-direction: row; + transition: 200ms ease-in; + transition-property: transform; +} + +.listPanels > div { + flex: 0 0 50%; +} + +.list { + display: flex; + flex-direction: column; + transition: opacity 0.3s ease-in-out; +} + +.list.faded { + opacity: 0.3; +} + +.list h3 { + margin: 0; + border-bottom: none; + border-top: 1px solid #ccc; +} + +.icons { + cursor: pointer; + display: none; + margin-left: auto; + padding: 12px 10px; + border-radius: 30px; + background: #7ed321; +} + +.icons:hover { + background: #89e029; +} + +.icons i { + margin: 0 5px; + display: inline-block; + background-position: center; + background-repeat: no-repeat; +} + +.icons i.icon-video { + background-image: url("../img/icons-14x14.svg#video-white"); + background-size: 14px 14px; + width: 16px; + height: 16px; +} + +.icons i.icon-caret-down { + background-image: url("../img/icons-10x10.svg#dropdown-white"); + background-size: 10px 10px; + width: 10px; + height: 16px; +} diff --git a/browser/components/loop/content/shared/css/panel.css b/browser/components/loop/content/shared/css/panel.css index 69b94eea596..8c5b828bce6 100644 --- a/browser/components/loop/content/shared/css/panel.css +++ b/browser/components/loop/content/shared/css/panel.css @@ -39,6 +39,7 @@ padding: 0 10px; height: 16px; cursor: pointer; + overflow: hidden; background-repeat: no-repeat; background-size: 16px 16px; background-position: center; @@ -82,6 +83,7 @@ .share { background: #fbfbfb; + margin-bottom: 14px; } .share .description, @@ -313,5 +315,4 @@ body[dir=rtl] .dropdown-menu-item { background: #EAEAEA; color: #7F7F7F; padding: 14px; - margin-top: 14px; } diff --git a/browser/components/loop/content/shared/img/icons-10x10.svg b/browser/components/loop/content/shared/img/icons-10x10.svg new file mode 100644 index 00000000000..2dc7a27a597 --- /dev/null +++ b/browser/components/loop/content/shared/img/icons-10x10.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/components/loop/content/shared/img/icons-14x14.svg b/browser/components/loop/content/shared/img/icons-14x14.svg new file mode 100644 index 00000000000..ecb7b149670 --- /dev/null +++ b/browser/components/loop/content/shared/img/icons-14x14.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/components/loop/content/shared/img/icons-16x16.svg b/browser/components/loop/content/shared/img/icons-16x16.svg index 901570ce12e..d62e062dd01 100644 --- a/browser/components/loop/content/shared/img/icons-16x16.svg +++ b/browser/components/loop/content/shared/img/icons-16x16.svg @@ -24,6 +24,10 @@ use[id$="-hover"] { use[id$="-active"] { fill: #0095dd; } + +use[id$="-red"] { + fill: #d74345 +} + diff --git a/browser/components/loop/jar.mn b/browser/components/loop/jar.mn index 4cd797be372..464379e145e 100644 --- a/browser/components/loop/jar.mn +++ b/browser/components/loop/jar.mn @@ -16,12 +16,14 @@ browser.jar: content/browser/loop/js/conversation.js (content/js/conversation.js) content/browser/loop/js/otconfig.js (content/js/otconfig.js) content/browser/loop/js/panel.js (content/js/panel.js) + content/browser/loop/js/contacts.js (content/js/contacts.js) # Shared styles content/browser/loop/shared/css/reset.css (content/shared/css/reset.css) content/browser/loop/shared/css/common.css (content/shared/css/common.css) content/browser/loop/shared/css/panel.css (content/shared/css/panel.css) content/browser/loop/shared/css/conversation.css (content/shared/css/conversation.css) + content/browser/loop/shared/css/contacts.css (content/shared/css/contacts.css) # Shared images content/browser/loop/shared/img/happy.png (content/shared/img/happy.png) @@ -46,6 +48,8 @@ browser.jar: content/browser/loop/shared/img/svg/glyph-signin-16x16.svg (content/shared/img/svg/glyph-signin-16x16.svg) content/browser/loop/shared/img/svg/glyph-signout-16x16.svg (content/shared/img/svg/glyph-signout-16x16.svg) content/browser/loop/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg) + content/browser/loop/shared/img/icons-10x10.svg (content/shared/img/icons-10x10.svg) + content/browser/loop/shared/img/icons-14x14.svg (content/shared/img/icons-14x14.svg) content/browser/loop/shared/img/icons-16x16.svg (content/shared/img/icons-16x16.svg) # Shared scripts diff --git a/browser/components/loop/test/desktop-local/index.html b/browser/components/loop/test/desktop-local/index.html index f59468a64d3..ec7dc942e68 100644 --- a/browser/components/loop/test/desktop-local/index.html +++ b/browser/components/loop/test/desktop-local/index.html @@ -42,6 +42,7 @@ + diff --git a/browser/components/loop/test/desktop-local/panel_test.js b/browser/components/loop/test/desktop-local/panel_test.js index bd02be53f2e..cc840fb9760 100644 --- a/browser/components/loop/test/desktop-local/panel_test.js +++ b/browser/components/loop/test/desktop-local/panel_test.js @@ -42,7 +42,13 @@ describe("loop.panel", function() { getLoopCharPref: sandbox.stub().returns("unseen"), copyString: sandbox.stub(), noteCallUrlExpiry: sinon.spy(), - composeEmail: sinon.spy() + composeEmail: sinon.spy(), + contacts: { + getAll: function(callback) { + callback(null, []); + }, + on: sandbox.stub() + } }; document.mozL10n.initialize(navigator.mozLoop); From 085706c93f5a289438abbf10e5a0e8014884c596 Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Fri, 19 Sep 2014 17:02:00 +0200 Subject: [PATCH 02/19] Bug 1069962: add support for Gravatar avatars. r=paolo --- browser/components/loop/MozLoopAPI.jsm | 45 +++++++++++++++++++ .../components/loop/content/js/contacts.js | 6 ++- .../components/loop/content/js/contacts.jsx | 6 ++- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/browser/components/loop/MozLoopAPI.jsm b/browser/components/loop/MozLoopAPI.jsm index 8e4359fe04e..fb9da6cbdc2 100644 --- a/browser/components/loop/MozLoopAPI.jsm +++ b/browser/components/loop/MozLoopAPI.jsm @@ -97,6 +97,15 @@ const injectObjectAPI = function(api, targetWindow) { return contentObj; }; +/** + * Get the two-digit hexadecimal code for a byte + * + * @param {byte} charCode + */ +const toHexString = function(charCode) { + return ("0" + charCode.toString(16)).slice(-2); +}; + /** * Inject the loop API into the given window. The caller must be sure the * window is a loop content window (eg, a panel, chatwindow, or similar). @@ -510,6 +519,42 @@ function injectLoopAPI(targetWindow) { Services.telemetry.getHistogramById(histogramId).add(value); } }, + + /** + * Compose a URL pointing to the location of an avatar by email address. + * At the moment we use the Gravatar service to match email addresses with + * avatars. This might change in the future as avatars might come from another + * source. + * + * @param {String} emailAddress Users' email address + * @param {Number} size Size of the avatar image to return in pixels. + * Optional. Default value: 40. + * @return the URL pointing to an avatar matching the provided email address. + */ + getUserAvatar: { + enumerable: true, + writable: true, + value: function(emailAddress, size = 40) { + if (!emailAddress) { + return ""; + } + + // Do the MD5 dance. + let hasher = Cc["@mozilla.org/security/hash;1"] + .createInstance(Ci.nsICryptoHash); + hasher.init(Ci.nsICryptoHash.MD5); + let stringStream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stringStream.data = emailAddress.trim().toLowerCase(); + hasher.updateFromStream(stringStream, -1); + let hash = hasher.finish(false); + // Convert the binary hash data to a hex string. + let md5Email = [toHexString(hash.charCodeAt(i)) for (i in hash)].join(""); + + // Compose the Gravatar URL. + return "http://www.gravatar.com/avatar/" + md5Email + ".jpg?default=blank&s=" + size; + } + }, }; function onStatusChanged(aSubject, aTopic, aData) { diff --git a/browser/components/loop/content/js/contacts.js b/browser/components/loop/content/js/contacts.js index cb7807fd31a..953f8923ccd 100644 --- a/browser/components/loop/content/js/contacts.js +++ b/browser/components/loop/content/js/contacts.js @@ -64,7 +64,9 @@ loop.contacts = (function(_, mozL10n) { return ( React.DOM.li({onClick: this.handleContactClick, className: contactCSSClass}, - React.DOM.div({className: "avatar"}), + React.DOM.div({className: "avatar"}, + React.DOM.img({src: navigator.mozLoop.getUserAvatar(email.value)}) + ), React.DOM.div({className: "details"}, React.DOM.div({className: "username"}, React.DOM.strong(null, names.firstName), " ", names.lastName, React.DOM.i({className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}), @@ -151,7 +153,7 @@ loop.contacts = (function(_, mozL10n) { if (comp !== 0) { return comp; } - // If names are equal, compare against unique ids make sure we have + // If names are equal, compare against unique ids to make sure we have // consistent ordering. return contact1._guid - contact2._guid; }, diff --git a/browser/components/loop/content/js/contacts.jsx b/browser/components/loop/content/js/contacts.jsx index 3ba83f4b60b..771df390f58 100644 --- a/browser/components/loop/content/js/contacts.jsx +++ b/browser/components/loop/content/js/contacts.jsx @@ -64,7 +64,9 @@ loop.contacts = (function(_, mozL10n) { return (
  • -
    +
    + +
    {names.firstName} {names.lastName} @@ -151,7 +153,7 @@ loop.contacts = (function(_, mozL10n) { if (comp !== 0) { return comp; } - // If names are equal, compare against unique ids make sure we have + // If names are equal, compare against unique ids to make sure we have // consistent ordering. return contact1._guid - contact2._guid; }, From efd449bfb54d65fc2741192ed01b401a7179d257 Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Fri, 19 Sep 2014 17:02:03 +0200 Subject: [PATCH 03/19] Bug 1069965: add a visual separator between available and blocked contacts. r=paolo --- browser/components/loop/content/js/contacts.js | 3 +++ browser/components/loop/content/js/contacts.jsx | 3 +++ browser/components/loop/content/shared/css/common.css | 2 +- browser/locales/en-US/chrome/browser/loop/loop.properties | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/browser/components/loop/content/js/contacts.js b/browser/components/loop/content/js/contacts.js index 953f8923ccd..f7b7a901311 100644 --- a/browser/components/loop/content/js/contacts.js +++ b/browser/components/loop/content/js/contacts.js @@ -175,6 +175,9 @@ loop.contacts = (function(_, mozL10n) { shownContacts.available ? shownContacts.available.sort(this.sortContacts).map(viewForItem) : null, + shownContacts.blocked ? + React.DOM.h3({className: "header"}, mozL10n.get("contacts_blocked_contacts")) : + null, shownContacts.blocked ? shownContacts.blocked.sort(this.sortContacts).map(viewForItem) : null diff --git a/browser/components/loop/content/js/contacts.jsx b/browser/components/loop/content/js/contacts.jsx index 771df390f58..2e31f367178 100644 --- a/browser/components/loop/content/js/contacts.jsx +++ b/browser/components/loop/content/js/contacts.jsx @@ -175,6 +175,9 @@ loop.contacts = (function(_, mozL10n) { {shownContacts.available ? shownContacts.available.sort(this.sortContacts).map(viewForItem) : null} + {shownContacts.blocked ? +

    {mozL10n.get("contacts_blocked_contacts")}

    : + null} {shownContacts.blocked ? shownContacts.blocked.sort(this.sortContacts).map(viewForItem) : null} diff --git a/browser/components/loop/content/shared/css/common.css b/browser/components/loop/content/shared/css/common.css index 91ce48d9e46..eaecae7fd9b 100644 --- a/browser/components/loop/content/shared/css/common.css +++ b/browser/components/loop/content/shared/css/common.css @@ -400,7 +400,7 @@ p { padding: 5px 10px; color: #888; margin: 0; - border-bottom: 1px solid #CCC; + border-top: 1px solid #CCC; background: #EEE; display: flex; align-items: center; diff --git a/browser/locales/en-US/chrome/browser/loop/loop.properties b/browser/locales/en-US/chrome/browser/loop/loop.properties index e92c9179ecd..468cd537612 100644 --- a/browser/locales/en-US/chrome/browser/loop/loop.properties +++ b/browser/locales/en-US/chrome/browser/loop/loop.properties @@ -85,6 +85,9 @@ new_contact_button=New Contact ## and click the 'New Contact' button to see the fields. new_contact_name_placeholder=Name new_contact_email_placeholder=Email + +contacts_blocked_contacts=Blocked Contacts + ## LOCALIZATION NOTE (add_contact_button): ## This is the button to actually add the new contact ## See https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#contacts From 048068f102cfd3f7d6390d4511b6591847fc3b73 Mon Sep 17 00:00:00 2001 From: Manu Jain Date: Fri, 19 Sep 2014 14:04:49 -0400 Subject: [PATCH 04/19] Bug 1063561 - Convert Addon Manager telemetry timings to use Components.utils.now(); r=irvingreid --- toolkit/mozapps/extensions/AddonManager.jsm | 4 ++-- toolkit/mozapps/extensions/internal/XPIProvider.jsm | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 7247446b44f..a45831c5f6c 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2431,9 +2431,9 @@ this.AddonManagerPrivate = { // Start a timer, record a simple measure of the time interval when // timer.done() is called simpleTimer: function(aName) { - let startTime = Date.now(); + let startTime = Cu.now(); return { - done: () => this.recordSimpleMeasure(aName, Date.now() - startTime) + done: () => this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime)) }; }, diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 69c6fb09467..bfdcc1f868c 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1494,7 +1494,7 @@ XPIState.prototype = { */ getModTime(aFile, aId) { let changed = false; - let scanStarted = Date.now(); + let scanStarted = Cu.now(); // For an unknown or enabled add-on, we do a full recursive scan. if (!('scanTime' in this) || this.enabled) { logger.debug('getModTime: Recursive scan of ' + aId); @@ -1539,7 +1539,7 @@ XPIState.prototype = { } } // Record duration of file-modified check - XPIProvider.setTelemetry(aId, "scan_MS", Date.now() - scanStarted); + XPIProvider.setTelemetry(aId, "scan_MS", Math.round(Cu.now() - scanStarted)); return changed; }, @@ -3539,10 +3539,10 @@ this.XPIProvider = { } // Telemetry probe added around getInstallState() to check perf - let telemetryCaptureTime = Date.now(); + let telemetryCaptureTime = Cu.now(); let installChanged = XPIStates.getInstallState(); let telemetry = Services.telemetry; - telemetry.getHistogramById("CHECK_ADDONS_MODIFIED_MS").add(Date.now() - telemetryCaptureTime); + telemetry.getHistogramById("CHECK_ADDONS_MODIFIED_MS").add(Math.round(Cu.now() - telemetryCaptureTime)); if (installChanged) { updateReasons.push("directoryState"); } From 277672174d2e980fb6b1287f806b7b4712878ef2 Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Fri, 19 Sep 2014 11:17:53 -0700 Subject: [PATCH 05/19] Bug 1068336 - Part 1: Add a slug to avoid caching robocop_testharness.js. r=mcomella --- mobile/android/base/tests/JavascriptTest.java | 11 +++------- mobile/android/base/tests/StringHelper.java | 20 ++++++++++++++++++- .../base/tests/testEventDispatcher.java | 3 +-- .../android/base/tests/testGeckoRequest.java | 2 +- .../base/tests/testJavascriptBridge.java | 3 +-- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/mobile/android/base/tests/JavascriptTest.java b/mobile/android/base/tests/JavascriptTest.java index 77a98388414..13fab5507ae 100644 --- a/mobile/android/base/tests/JavascriptTest.java +++ b/mobile/android/base/tests/JavascriptTest.java @@ -1,15 +1,11 @@ package org.mozilla.gecko.tests; +import org.json.JSONObject; +import org.mozilla.gecko.Actions; import org.mozilla.gecko.tests.helpers.JavascriptBridge; import org.mozilla.gecko.tests.helpers.JavascriptMessageParser; import android.util.Log; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.json.JSONObject; -import org.mozilla.gecko.Actions; -import org.mozilla.gecko.Assert; public class JavascriptTest extends BaseTest { private static final String LOGTAG = "JavascriptTest"; @@ -32,8 +28,7 @@ public class JavascriptTest extends BaseTest { mActions.expectGeckoEvent(EVENT_TYPE); mAsserter.dumpLog("Registered listener for " + EVENT_TYPE); - final String url = getAbsoluteUrl(StringHelper.ROBOCOP_JS_HARNESS_URL + - "?path=" + javascriptUrl); + final String url = getAbsoluteUrl(StringHelper.getHarnessUrlForJavascript(javascriptUrl)); mAsserter.dumpLog("Loading JavaScript test from " + url); loadUrl(url); diff --git a/mobile/android/base/tests/StringHelper.java b/mobile/android/base/tests/StringHelper.java index 8d412ced138..b2740f84667 100644 --- a/mobile/android/base/tests/StringHelper.java +++ b/mobile/android/base/tests/StringHelper.java @@ -104,7 +104,25 @@ public class StringHelper { public static final String ROBOCOP_TEXT_PAGE_URL = "/robocop/robocop_text_page.html"; public static final String ROBOCOP_ADOBE_FLASH_URL = "/robocop/robocop_adobe_flash.html"; public static final String ROBOCOP_INPUT_URL = "/robocop/robocop_input.html"; - public static final String ROBOCOP_JS_HARNESS_URL = "/robocop/robocop_javascript.html"; + + private static final String ROBOCOP_JS_HARNESS_URL = "/robocop/robocop_javascript.html"; + + /** + * Build a URL for loading a Javascript file in the Robocop Javascript + * harness. + *

    + * We append a random slug to avoid caching: see + * https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache. + * + * @param javascriptUrl to load. + * @return URL with harness wrapper. + */ + public static String getHarnessUrlForJavascript(String javascriptUrl) { + // We include a slug to make sure we never cache the harness. + return ROBOCOP_JS_HARNESS_URL + + "?slug=" + System.currentTimeMillis() + + "&path=" + javascriptUrl; + } // Robocop page titles public static final String ROBOCOP_BIG_LINK_TITLE = "Big Link"; diff --git a/mobile/android/base/tests/testEventDispatcher.java b/mobile/android/base/tests/testEventDispatcher.java index c879c9d4f54..fc032e787ff 100644 --- a/mobile/android/base/tests/testEventDispatcher.java +++ b/mobile/android/base/tests/testEventDispatcher.java @@ -59,8 +59,7 @@ public class testEventDispatcher extends UITest public void testEventDispatcher() { GeckoHelper.blockForReady(); - NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_JS_HARNESS_URL + - "?path=" + TEST_JS); + NavigationHelper.enterAndLoadUrl(StringHelper.getHarnessUrlForJavascript(TEST_JS)); js.syncCall("send_test_message", GECKO_EVENT); js.syncCall("send_message_for_response", GECKO_RESPONSE_EVENT, "success"); diff --git a/mobile/android/base/tests/testGeckoRequest.java b/mobile/android/base/tests/testGeckoRequest.java index c4367e52829..99499df1d91 100644 --- a/mobile/android/base/tests/testGeckoRequest.java +++ b/mobile/android/base/tests/testGeckoRequest.java @@ -38,7 +38,7 @@ public class testGeckoRequest extends UITest { public void testGeckoRequest() { GeckoHelper.blockForReady(); - NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_JS_HARNESS_URL + "?path=" + TEST_JS); + NavigationHelper.enterAndLoadUrl(StringHelper.getHarnessUrlForJavascript(TEST_JS)); // Register a listener for this request. js.syncCall("add_request_listener", REQUEST_EVENT); diff --git a/mobile/android/base/tests/testJavascriptBridge.java b/mobile/android/base/tests/testJavascriptBridge.java index 32384ab51d2..4e262e141dc 100644 --- a/mobile/android/base/tests/testJavascriptBridge.java +++ b/mobile/android/base/tests/testJavascriptBridge.java @@ -32,8 +32,7 @@ public class testJavascriptBridge extends UITest { public void testJavascriptBridge() { GeckoHelper.blockForReady(); - NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_JS_HARNESS_URL + - "?path=" + TEST_JS); + NavigationHelper.enterAndLoadUrl(StringHelper.getHarnessUrlForJavascript(TEST_JS)); js.syncCall("check_js_int_arg", 1); } From 2554887d4d929b47a7ec7603d6042174c7439dea Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Fri, 19 Sep 2014 11:17:54 -0700 Subject: [PATCH 06/19] Bug 1068336 - Part 2: Add a slug and headers to avoid caching test JavaScript files. r=mcomella --- mobile/android/base/tests/robocop_testharness.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mobile/android/base/tests/robocop_testharness.js b/mobile/android/base/tests/robocop_testharness.js index 3e5459a2339..acc711b8b2e 100644 --- a/mobile/android/base/tests/robocop_testharness.js +++ b/mobile/android/base/tests/robocop_testharness.js @@ -18,7 +18,11 @@ function _evalURI(uri, sandbox) { let theURI = SpecialPowers.Services.io .newURI(uri, window.document.characterSet, baseURI); - req.open('GET', theURI.spec, false); + // We append a random slug to avoid caching: see + // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache. + req.open('GET', theURI.spec + ((/\?/).test(theURI.spec) ? "&slug=" : "?slug=") + (new Date()).getTime(), false); + req.setRequestHeader('Cache-Control', 'no-cache'); + req.setRequestHeader('Pragma', 'no-cache'); req.send(); return SpecialPowers.Cu.evalInSandbox(req.responseText, sandbox, "1.8", uri, 1); From e527d34eafb66b0367c02f27f4d8e16397669685 Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Fri, 19 Sep 2014 11:19:37 -0700 Subject: [PATCH 07/19] Bug 1069569 - Complain about missing test files for all suites. r=mshal --HG-- extra : rebase_source : 55d571b413d6080f8fdb1e56b419c078d66f31e7 --- python/mozbuild/mozbuild/frontend/emitter.py | 10 +++++----- .../data/test-manifests-written/dir1/test_bar.js | 0 .../backend/data/test-manifests-written/xpcshell.js | 0 .../moz.build | 4 ++++ .../xpcshell.ini | 4 ++++ python/mozbuild/mozbuild/test/frontend/test_emitter.py | 8 ++++++++ 6 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/test_bar.js create mode 100644 python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.js create mode 100644 python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/moz.build create mode 100644 python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/xpcshell.ini diff --git a/python/mozbuild/mozbuild/frontend/emitter.py b/python/mozbuild/mozbuild/frontend/emitter.py index b976b19b144..30bebe3f914 100644 --- a/python/mozbuild/mozbuild/frontend/emitter.py +++ b/python/mozbuild/mozbuild/frontend/emitter.py @@ -850,11 +850,11 @@ class TreeMetadataEmitter(LoggingMixin): filtered = m.active_tests(exists=False, disabled=True, **self.info) - missing = [t['name'] for t in filtered if not os.path.exists(t['path'])] - if missing: - raise SandboxValidationError('Test manifest (%s) lists ' - 'test that does not exist: %s' % ( - path, ', '.join(missing)), context) + missing = [t['name'] for t in filtered if not os.path.exists(t['path'])] + if missing: + raise SandboxValidationError('Test manifest (%s) lists ' + 'test that does not exist: %s' % ( + path, ', '.join(missing)), context) out_dir = mozpath.join(install_prefix, manifest_reldir) if 'install-to-subdir' in defaults: diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/test_bar.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/dir1/test_bar.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.js b/python/mozbuild/mozbuild/test/backend/data/test-manifests-written/xpcshell.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/moz.build b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/moz.build new file mode 100644 index 00000000000..09c51cbb801 --- /dev/null +++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/moz.build @@ -0,0 +1,4 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini'] diff --git a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/xpcshell.ini b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/xpcshell.ini new file mode 100644 index 00000000000..9ab85c0cef5 --- /dev/null +++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-missing-test-file-unfiltered/xpcshell.ini @@ -0,0 +1,4 @@ +[DEFAULT] +support-files = support/** + +[missing.js] diff --git a/python/mozbuild/mozbuild/test/frontend/test_emitter.py b/python/mozbuild/mozbuild/test/frontend/test_emitter.py index 904afe52695..743e54c4429 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py +++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py @@ -501,6 +501,14 @@ class TestEmitterBasic(unittest.TestCase): 'lists test that does not exist: test_missing.html'): self.read_topsrcdir(reader) + def test_test_manifest_missing_test_error_unfiltered(self): + """Missing test files should result in error, even when the test list is not filtered.""" + reader = self.reader('test-manifest-missing-test-file-unfiltered') + + with self.assertRaisesRegexp(SandboxValidationError, + 'lists test that does not exist: missing.js'): + self.read_topsrcdir(reader) + def test_ipdl_sources(self): reader = self.reader('ipdl_sources') objs = self.read_topsrcdir(reader) From 7a33dc951ea44dbc168c4e90697a44d103f1cc15 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Fri, 19 Sep 2014 11:28:55 -0700 Subject: [PATCH 08/19] Backing out bug 893276 for causing bug 1058840. --- addon-sdk/source/lib/sdk/simple-storage.js | 117 +++-------- addon-sdk/source/test/test-simple-storage.js | 210 ++----------------- 2 files changed, 40 insertions(+), 287 deletions(-) diff --git a/addon-sdk/source/lib/sdk/simple-storage.js b/addon-sdk/source/lib/sdk/simple-storage.js index 0011f3e65be..74ba460771f 100644 --- a/addon-sdk/source/lib/sdk/simple-storage.js +++ b/addon-sdk/source/lib/sdk/simple-storage.js @@ -8,16 +8,13 @@ module.metadata = { "stability": "stable" }; -const { Cc, Ci, Cu } = require("chrome"); +const { Cc, Ci } = require("chrome"); const file = require("./io/file"); const prefs = require("./preferences/service"); const jpSelf = require("./self"); const timer = require("./timers"); const unload = require("./system/unload"); const { emit, on, off } = require("./event/core"); -const { defer } = require('./core/promise'); - -const { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod"; const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes @@ -38,57 +35,6 @@ Object.defineProperties(exports, { } }); -function getHash(data) { - let { promise, resolve } = defer(); - - let crypto = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); - crypto.init(crypto.MD5); - - let listener = { - onStartRequest: function() { }, - - onDataAvailable: function(request, context, inputStream, offset, count) { - crypto.updateFromStream(inputStream, count); - }, - - onStopRequest: function(request, context, status) { - resolve(crypto.finish(false)); - } - }; - - let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. - createInstance(Ci.nsIScriptableUnicodeConverter); - converter.charset = "UTF-8"; - let stream = converter.convertToInputStream(data); - let pump = Cc["@mozilla.org/network/input-stream-pump;1"]. - createInstance(Ci.nsIInputStreamPump); - pump.init(stream, -1, -1, 0, 0, true); - pump.asyncRead(listener, null); - - return promise; -} - -function writeData(filename, data) { - let { promise, resolve, reject } = defer(); - - let stream = file.open(filename, "w"); - try { - stream.writeAsync(data, err => { - if (err) - reject(err); - else - resolve(); - }); - } - catch (err) { - // writeAsync closes the stream after it's done, so only close on error. - stream.close(); - reject(err); - } - - return promise; -} - // A generic JSON store backed by a file on disk. This should be isolated // enough to move to its own module if need be... function JsonStore(options) { @@ -97,9 +43,11 @@ function JsonStore(options) { this.writePeriod = options.writePeriod; this.onOverQuota = options.onOverQuota; this.onWrite = options.onWrite; - this.hash = null; + unload.ensure(this); - this.startTimer(); + + this.writeTimer = timer.setInterval(this.write.bind(this), + this.writePeriod); } JsonStore.prototype = { @@ -133,18 +81,11 @@ JsonStore.prototype = { undefined; }, - startTimer: function JsonStore_startTimer() { - timer.setTimeout(() => { - this.write().then(this.startTimer.bind(this)); - }, this.writePeriod); - }, - // Removes the backing file and all empty subdirectories. purge: function JsonStore_purge() { try { // This'll throw if the file doesn't exist. file.remove(this.filename); - this.hash = null; let parentPath = this.filename; do { parentPath = file.dirname(parentPath); @@ -164,25 +105,31 @@ JsonStore.prototype = { // errors cause tests to fail. Supporting "known" errors in the test // harness appears to be non-trivial. Maybe later. this.root = JSON.parse(str); - let self = this; - getHash(str).then(hash => this.hash = hash); } catch (err) { this.root = {}; - this.hash = null; } }, + // If the store is under quota, writes the root to the backing file. + // Otherwise quota observers are notified and nothing is written. + write: function JsonStore_write() { + if (this.quotaUsage > 1) + this.onOverQuota(this); + else + this._write(); + }, + // Cleans up on unload. If unloading because of uninstall, the store is // purged; otherwise it's written. unload: function JsonStore_unload(reason) { - timer.clearTimeout(this.writeTimer); + timer.clearInterval(this.writeTimer); this.writeTimer = null; if (reason === "uninstall") this.purge(); else - this.write(); + this._write(); }, // True if the root is an empty object. @@ -201,40 +148,32 @@ JsonStore.prototype = { // Writes the root to the backing file, notifying write observers when // complete. If the store is over quota or if it's empty and the store has // never been written, nothing is written and write observers aren't notified. - write: Task.async(function JsonStore_write() { + _write: function JsonStore__write() { // Don't write if the root is uninitialized or if the store is empty and the // backing file doesn't yet exist. if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename))) return; - let data = JSON.stringify(this.root); - // If the store is over quota, don't write. The current under-quota state // should persist. - if ((this.quota > 0) && (data.length > this.quota)) { - this.onOverQuota(this); - return; - } - - // Hash the data to compare it to any previously written data - let hash = yield getHash(data); - - if (hash == this.hash) + if (this.quotaUsage > 1) return; // Finally, write. + let stream = file.open(this.filename, "w"); try { - yield writeData(this.filename, data); - - this.hash = hash; - if (this.onWrite) - this.onWrite(this); + stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) { + if (err) + console.error("Error writing simple storage file: " + this.filename); + else if (this.onWrite) + this.onWrite(this); + }.bind(this)); } catch (err) { - console.error("Error writing simple storage file: " + this.filename); - console.error(err); + // writeAsync closes the stream after it's done, so only close on error. + stream.close(); } - }) + } }; diff --git a/addon-sdk/source/test/test-simple-storage.js b/addon-sdk/source/test/test-simple-storage.js index e87c2885b5c..6914e5771a4 100644 --- a/addon-sdk/source/test/test-simple-storage.js +++ b/addon-sdk/source/test/test-simple-storage.js @@ -6,7 +6,6 @@ const file = require("sdk/io/file"); const prefs = require("sdk/preferences/service"); const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota"; -const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod"; let {Cc,Ci} = require("chrome"); @@ -35,13 +34,12 @@ exports.testSetGet = function (assert, done) { // Load the module again and make sure the value stuck. loader = Loader(module); ss = loader.require("sdk/simple-storage"); - assert.equal(ss.storage.foo, val, "Value should persist"); manager(loader).jsonStore.onWrite = function (storage) { - assert.fail("Nothing should be written since `storage` was not changed."); + file.remove(storeFilename); + done(); }; + assert.equal(ss.storage.foo, val, "Value should persist"); loader.unload(); - file.remove(storeFilename); - done(); }; let val = "foo"; ss.storage.foo = val; @@ -162,11 +160,10 @@ exports.testQuotaExceededHandle = function (assert, done) { assert.equal(ss.storage.x, 4, "x value should be correct"); assert.equal(ss.storage.y, 5, "y value should be correct"); manager(loader).jsonStore.onWrite = function (storage) { - assert.fail("Nothing should be written since `storage` was not changed."); + prefs.reset(QUOTA_PREF); + done(); }; loader.unload(); - prefs.reset(QUOTA_PREF); - done(); }; loader.unload(); }); @@ -200,9 +197,6 @@ exports.testQuotaExceededNoHandle = function (assert, done) { assert.equal(ss.storage, val, "Over-quota value should not have been written, " + "old value should have persisted: " + ss.storage); - manager(loader).jsonStore.onWrite = function (storage) { - assert.fail("Nothing should be written since `storage` was not changed."); - }; loader.unload(); prefs.reset(QUOTA_PREF); done(); @@ -257,184 +251,6 @@ exports.testUninstall = function (assert, done) { loader.unload(); }; -exports.testChangeInnerArray = function(assert, done) { - prefs.set(WRITE_PERIOD_PREF, 10); - - let expected = { - x: [5, 7], - y: [7, 28], - z: [6, 2] - }; - - // Load the module, set a value. - let loader = Loader(module); - let ss = loader.require("sdk/simple-storage"); - manager(loader).jsonStore.onWrite = function (storage) { - assert.ok(file.exists(storeFilename), "Store file should exist"); - - // Load the module again and check the result - loader = Loader(module); - ss = loader.require("sdk/simple-storage"); - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - // Add a property - ss.storage.x.push(["bar"]); - expected.x.push(["bar"]); - manager(loader).jsonStore.onWrite = function (storage) { - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - // Modify a property - ss.storage.y[0] = 42; - expected.y[0] = 42; - manager(loader).jsonStore.onWrite = function (storage) { - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - // Delete a property - delete ss.storage.z[1]; - delete expected.z[1]; - manager(loader).jsonStore.onWrite = function (storage) { - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - // Modify the new inner-object - ss.storage.x[2][0] = "baz"; - expected.x[2][0] = "baz"; - manager(loader).jsonStore.onWrite = function (storage) { - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - manager(loader).jsonStore.onWrite = function (storage) { - assert.fail("Nothing should be written since `storage` was not changed."); - }; - loader.unload(); - - // Load the module again and check the result - loader = Loader(module); - ss = loader.require("sdk/simple-storage"); - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - loader.unload(); - file.remove(storeFilename); - prefs.reset(WRITE_PERIOD_PREF); - done(); - }; - }; - }; - }; - }; - - ss.storage = { - x: [5, 7], - y: [7, 28], - z: [6, 2] - }; - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - loader.unload(); -}; - -exports.testChangeInnerObject = function(assert, done) { - prefs.set(WRITE_PERIOD_PREF, 10); - - let expected = { - x: { - a: 5, - b: 7 - }, - y: { - c: 7, - d: 28 - }, - z: { - e: 6, - f: 2 - } - }; - - // Load the module, set a value. - let loader = Loader(module); - let ss = loader.require("sdk/simple-storage"); - manager(loader).jsonStore.onWrite = function (storage) { - assert.ok(file.exists(storeFilename), "Store file should exist"); - - // Load the module again and check the result - loader = Loader(module); - ss = loader.require("sdk/simple-storage"); - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - // Add a property - ss.storage.x.g = {foo: "bar"}; - expected.x.g = {foo: "bar"}; - manager(loader).jsonStore.onWrite = function (storage) { - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - // Modify a property - ss.storage.y.c = 42; - expected.y.c = 42; - manager(loader).jsonStore.onWrite = function (storage) { - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - // Delete a property - delete ss.storage.z.f; - delete expected.z.f; - manager(loader).jsonStore.onWrite = function (storage) { - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - // Modify the new inner-object - ss.storage.x.g.foo = "baz"; - expected.x.g.foo = "baz"; - manager(loader).jsonStore.onWrite = function (storage) { - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - manager(loader).jsonStore.onWrite = function (storage) { - assert.fail("Nothing should be written since `storage` was not changed."); - }; - loader.unload(); - - // Load the module again and check the result - loader = Loader(module); - ss = loader.require("sdk/simple-storage"); - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - loader.unload(); - file.remove(storeFilename); - prefs.reset(WRITE_PERIOD_PREF); - done(); - }; - }; - }; - }; - }; - - ss.storage = { - x: { - a: 5, - b: 7 - }, - y: { - c: 7, - d: 28 - }, - z: { - e: 6, - f: 2 - } - }; - assert.equal(JSON.stringify(ss.storage), - JSON.stringify(expected), "Should see the expected object"); - - loader.unload(); -}; - exports.testSetNoSetRead = function (assert, done) { // Load the module, set a value. let loader = Loader(module); @@ -453,13 +269,12 @@ exports.testSetNoSetRead = function (assert, done) { // Load the module a third time and make sure the value stuck. loader = Loader(module); ss = loader.require("sdk/simple-storage"); - assert.equal(ss.storage.foo, val, "Value should persist"); manager(loader).jsonStore.onWrite = function (storage) { - assert.fail("Nothing should be written since `storage` was not changed."); + file.remove(storeFilename); + done(); }; + assert.equal(ss.storage.foo, val, "Value should persist"); loader.unload(); - file.remove(storeFilename); - done(); }; let val = "foo"; ss.storage.foo = val; @@ -480,13 +295,12 @@ function setGetRoot(assert, done, val, compare) { // Load the module again and make sure the value stuck. loader = Loader(module); ss = loader.require("sdk/simple-storage"); - assert.ok(compare(ss.storage, val), "Value should persist"); - manager(loader).jsonStore.onWrite = function (storage) { - assert.fail("Nothing should be written since `storage` was not changed."); + manager(loader).jsonStore.onWrite = function () { + file.remove(storeFilename); + done(); }; + assert.ok(compare(ss.storage, val), "Value should persist"); loader.unload(); - file.remove(storeFilename); - done(); }; ss.storage = val; assert.ok(compare(ss.storage, val), "Value read should be value set"); From 85caf3c5c17b89213d709d168fe985c9154a9c0b Mon Sep 17 00:00:00 2001 From: Garvan Keeley Date: Fri, 15 Aug 2014 14:30:00 -0400 Subject: [PATCH 09/19] Bug 1054520 - Add ifdef to enable stumbler in nightly. r=nalexander --- mobile/android/confvars.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mobile/android/confvars.sh b/mobile/android/confvars.sh index 32421aa9270..0d4320eaaed 100644 --- a/mobile/android/confvars.sh +++ b/mobile/android/confvars.sh @@ -86,5 +86,9 @@ if test ! "$RELEASE_BUILD"; then MOZ_ANDROID_SHARE_OVERLAY=1 fi -# Don't enable the Mozilla Location Service stumbler. -# MOZ_ANDROID_MLS_STUMBLER=1 +# Enable the Mozilla Location Service stumbler in Nightly. +if test "$NIGHTLY_BUILD"; then + MOZ_ANDROID_MLS_STUMBLER=1 +else + MOZ_ANDROID_MLS_STUMBLER= +fi From 406152858d93476f930d069e42f97bd974d27acf Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 17 Sep 2014 12:31:47 -0700 Subject: [PATCH 10/19] Bug 1068388 - Part 1: Clarify that Stumbler pref is Android-only. r=nalexander, r=garvank --- .../base/preferences/GeckoPreferences.java | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/mobile/android/base/preferences/GeckoPreferences.java b/mobile/android/base/preferences/GeckoPreferences.java index b12b23bfdf8..652ed5c2196 100644 --- a/mobile/android/base/preferences/GeckoPreferences.java +++ b/mobile/android/base/preferences/GeckoPreferences.java @@ -110,14 +110,15 @@ OnSharedPreferenceChangeListener private static final String PREFS_MENU_CHAR_ENCODING = "browser.menu.showCharacterEncoding"; private static final String PREFS_MP_ENABLED = "privacy.masterpassword.enabled"; private static final String PREFS_UPDATER_AUTODOWNLOAD = "app.update.autodownload"; - private static final String PREFS_GEO_REPORTING = "app.geo.reportdata"; + private static final String PREFS_GEO_REPORTING = NON_PREF_PREFIX + "app.geo.reportdata"; private static final String PREFS_GEO_LEARN_MORE = NON_PREF_PREFIX + "geo.learn_more"; private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link"; private static final String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled"; private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom"; private static final String PREFS_DISPLAY_TITLEBAR_MODE = "browser.chrome.titlebarMode"; private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync"; - private static final String PREFS_STUMBLER_ENABLED = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF"; + + private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF"; // This isn't a Gecko pref, even if it looks like one. private static final String PREFS_BROWSER_LOCALE = "locale"; @@ -869,10 +870,10 @@ OnSharedPreferenceChangeListener /** * Broadcast the provided value as the value of the - * PREFS_STUMBLER_ENABLED pref. + * PREFS_GEO_REPORTING pref. */ public static void broadcastStumblerPref(final Context context, final boolean value) { - Intent intent = new Intent(PREFS_STUMBLER_ENABLED) + Intent intent = new Intent(ACTION_STUMBLER_UPLOAD_PREF) .putExtra("pref", PREFS_GEO_REPORTING) .putExtra("branch", GeckoSharedPrefs.APP_PREFS_NAME) .putExtra("enabled", value) @@ -888,7 +889,7 @@ OnSharedPreferenceChangeListener /** * Broadcast the current value of the - * PREFS_STUMBLER_ENABLED pref. + * PREFS_GEO_REPORTING pref. */ public static void broadcastStumblerPref(final Context context) { final boolean value = getBooleanPref(context, PREFS_GEO_REPORTING, false); @@ -1321,22 +1322,7 @@ OnSharedPreferenceChangeListener @Override public void prefValue(String prefName, final int value) { final Preference pref = getField(prefName); - final CheckBoxPrefSetter prefSetter; - if (PREFS_GEO_REPORTING.equals(prefName)) { - if (Versions.preICS) { - prefSetter = new CheckBoxPrefSetter(); - } else { - prefSetter = new TwoStatePrefSetter(); - } - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - prefSetter.setBooleanPref(pref, value == 1); - } - }); - } else { - Log.w(LOGTAG, "Unhandled int value for pref [" + pref + "]"); - } + Log.w(LOGTAG, "Unhandled int value for pref [" + pref + "]"); } @Override From 8f4de031bf80f5f7dda13f5957d8635546c3adeb Mon Sep 17 00:00:00 2001 From: Garvan Keeley Date: Wed, 17 Sep 2014 15:25:00 -0400 Subject: [PATCH 11/19] Bug 1068388 - Part 2: Remove the use of app.geo.reportdata from gecko. r=rnewman --- mobile/android/app/mobile.js | 1 - mobile/android/base/GeckoApp.java | 6 ------ 2 files changed, 7 deletions(-) diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index bfb580d696b..eb4d956c944 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -403,7 +403,6 @@ pref("privacy.item.syncAccount", true); // enable geo pref("geo.enabled", true); -pref("app.geo.reportdata", 0); // content sink control -- controls responsiveness during page load // see https://bugzilla.mozilla.org/show_bug.cgi?id=481566#c9 diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index a59a7053df0..b6b6988a609 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -1563,12 +1563,6 @@ public abstract class GeckoApp } }); - PrefsHelper.getPref("app.geo.reportdata", new PrefsHelper.PrefHandlerBase() { - @Override public void prefValue(String pref, int value) { - // Acting on this pref is Bug 1036508; for now, do nothing. - } - }); - // Trigger the completion of the telemetry timer that wraps activity startup, // then grab the duration to give to FHR. mJavaUiStartupTimer.stop(); From 5b8f5069efa68f56e0becf6dcbf31d817dec20e3 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 17 Sep 2014 12:35:41 -0700 Subject: [PATCH 12/19] Bug 1068388 - Part 3: Hide stumbler settings items if stumbler isn't built. r=garvank --- mobile/android/base/preferences/GeckoPreferences.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mobile/android/base/preferences/GeckoPreferences.java b/mobile/android/base/preferences/GeckoPreferences.java index 652ed5c2196..b67eb7194fc 100644 --- a/mobile/android/base/preferences/GeckoPreferences.java +++ b/mobile/android/base/preferences/GeckoPreferences.java @@ -684,10 +684,9 @@ OnSharedPreferenceChangeListener preferences.removePreference(pref); i--; continue; - } else if (AppConstants.RELEASE_BUILD && - (PREFS_GEO_REPORTING.equals(key) || - PREFS_GEO_LEARN_MORE.equals(key))) { - // We don't build wifi/cell tower collection in release builds, so hide the UI. + } else if (!AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED && + (PREFS_GEO_REPORTING.equals(key) || + PREFS_GEO_LEARN_MORE.equals(key))) { preferences.removePreference(pref); i--; continue; From aee01f3965593b6d4b2be89284237bdf60c1e9a9 Mon Sep 17 00:00:00 2001 From: Garvan Keeley Date: Wed, 17 Sep 2014 15:27:00 -0400 Subject: [PATCH 13/19] Bug 1068388 - Part 4: Update UI pref key value for app.geo.reportdata. r=rnewman --- mobile/android/base/resources/xml/preferences_vendor.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/android/base/resources/xml/preferences_vendor.xml b/mobile/android/base/resources/xml/preferences_vendor.xml index c200de3e3f3..55294ef0a91 100644 --- a/mobile/android/base/resources/xml/preferences_vendor.xml +++ b/mobile/android/base/resources/xml/preferences_vendor.xml @@ -35,7 +35,7 @@ android:summary="@string/datareporting_crashreporter_summary" android:defaultValue="false" /> - From e132000043706b59e91994bf4b47e11d59456039 Mon Sep 17 00:00:00 2001 From: Garvan Keeley Date: Thu, 18 Sep 2014 12:53:00 -0400 Subject: [PATCH 14/19] Bug 1068388 - Part 5: If stumbler built off, test should ignore stumbler prefs. r=nalexander --- mobile/android/base/tests/testSettingsMenuItems.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mobile/android/base/tests/testSettingsMenuItems.java b/mobile/android/base/tests/testSettingsMenuItems.java index f83e1e67085..27b8dd9be82 100644 --- a/mobile/android/base/tests/testSettingsMenuItems.java +++ b/mobile/android/base/tests/testSettingsMenuItems.java @@ -170,12 +170,14 @@ public class testSettingsMenuItems extends PixelTest { settingsMap.get(PATH_DISPLAY).remove(TITLE_BAR_LABEL_ARR); } - // Anonymous cell tower/wifi collection - only built if *not* release build - String[] networkReportingUi = { "Mozilla Location Service", "Receives Wi-Fi and cellular location data when running in the background and shares it with Mozilla to improve our geolocation service" }; - settingsMap.get(PATH_MOZILLA).add(networkReportingUi); + if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) { + // Anonymous cell tower/wifi collection + String[] networkReportingUi = { "Mozilla Location Service", "Receives Wi-Fi and cellular location data when running in the background and shares it with Mozilla to improve our geolocation service" }; + settingsMap.get(PATH_MOZILLA).add(networkReportingUi); - String[] learnMoreUi = { "Learn more" }; - settingsMap.get(PATH_MOZILLA).add(learnMoreUi); + String[] learnMoreUi = { "Learn more" }; + settingsMap.get(PATH_MOZILLA).add(learnMoreUi); + } } // Automatic updates From 05972a2494998d8443203cfd170e552df3bab6e8 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Fri, 19 Sep 2014 10:10:48 -0700 Subject: [PATCH 15/19] Bumping gaia.json for 3 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/ea495488fa96 Author: Kevin Grandon Desc: Merge pull request #24225 from KevinGrandon/bug_1069810_home_screen_auth_fix_with_test Bug 1069810 - Homescreen application does not handle basic authentication in icon urls ======== https://hg.mozilla.org/integration/gaia-central/rev/becefc2eddcc Author: Kevin Grandon Desc: Bug 1069810 - Add icon auth test r=kgrandon ======== https://hg.mozilla.org/integration/gaia-central/rev/cd1ae65c84b8 Author: Andreas Larsson Desc: Bug 1069810 - Homescreen application does not handle basic authentication in icon urls Use the url string instead of an anchor element when fetching icons --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 449f5c3d5ed..928254de7d5 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "02fabec9910191bf6f99cb9879a1e6603d5ed7c3", + "revision": "ea495488fa9692a4ccfd17cc714c336fc7acb8d9", "repo_path": "/integration/gaia-central" } From 38cb8b3d94a7f35ce239094c01fa35bbac4e8a72 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Fri, 19 Sep 2014 10:17:03 -0700 Subject: [PATCH 16/19] Bumping manifests a=b2g-bump --- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 97ed3356876..73a4764d470 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index b17d846b3c0..2d319751763 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 5006286bb66..2a1a974846c 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 2a2ffbd1eef..0e510d808ac 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index b17d846b3c0..2d319751763 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index e9fbe3c17a0..853c7bbdaa1 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 4609b7321c4..4cd6c468a30 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index ab210c2b352..8ba20b835e3 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 23a87e074a5..247e3ac73d5 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index c125a530eba..3ddc3cbf7cc 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index 45f633064c1..c21150e2800 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + From debf1c8aa2d2fc9234cf04e05f1fb1752b1bd8e4 Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Fri, 19 Sep 2014 12:19:19 -0700 Subject: [PATCH 17/19] backout 79bf6ebacb09 for causing 1070072 --- mobile/android/chrome/content/FindHelper.js | 2 +- mobile/android/chrome/content/ZoomHelper.js | 47 +++++++++++---------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/mobile/android/chrome/content/FindHelper.js b/mobile/android/chrome/content/FindHelper.js index 39b10eb6295..c3437961c27 100644 --- a/mobile/android/chrome/content/FindHelper.js +++ b/mobile/android/chrome/content/FindHelper.js @@ -78,7 +78,7 @@ var FindHelper = { } } else { // Disabled until bug 1014113 is fixed - // ZoomHelper.zoomToRect(aData.rect); + //ZoomHelper.zoomToRect(aData.rect, -1, false, true); this._viewportChanged = true; } } diff --git a/mobile/android/chrome/content/ZoomHelper.js b/mobile/android/chrome/content/ZoomHelper.js index dbbd31d6801..eee9a880044 100644 --- a/mobile/android/chrome/content/ZoomHelper.js +++ b/mobile/android/chrome/content/ZoomHelper.js @@ -81,16 +81,24 @@ var ZoomHelper = { */ zoomToElement: function(aElement, aClickY = -1, aCanZoomOut = true, aCanScrollHorizontally = true) { let rect = ElementTouchHelper.getBoundingContentRect(aElement); + ZoomHelper.zoomToRect(rect, aClickY, aCanZoomOut, aCanScrollHorizontally, aElement); + }, + zoomToRect: function(aRect, aClickY = -1, aCanZoomOut = true, aCanScrollHorizontally = true, aElement) { const margin = 15; + if(!aRect.h || !aRect.w) { + aRect.h = aRect.height; + aRect.w = aRect.width; + } + let viewport = BrowserApp.selectedTab.getViewport(); - rect = new Rect(aCanScrollHorizontally ? Math.max(viewport.cssPageLeft, rect.x - margin) : viewport.cssX, - rect.y, - aCanScrollHorizontally ? rect.w + 2 * margin : viewport.cssWidth, - rect.h); + let bRect = new Rect(aCanScrollHorizontally ? Math.max(viewport.cssPageLeft, aRect.x - margin) : viewport.cssX, + aRect.y, + aCanScrollHorizontally ? aRect.w + 2 * margin : viewport.cssWidth, + aRect.h); // constrict the rect to the screen's right edge - rect.width = Math.min(rect.width, viewport.cssPageRight - rect.x); + bRect.width = Math.min(bRect.width, viewport.cssPageRight - bRect.x); // if the rect is already taking up most of the visible area and is stretching the // width of the page, then we want to zoom out instead. @@ -98,44 +106,37 @@ var ZoomHelper = { if (BrowserEventHandler.mReflozPref) { let zoomFactor = BrowserApp.selectedTab.getZoomToMinFontSize(aElement); - rect.width = zoomFactor <= 1.0 ? rect.width : gScreenWidth / zoomFactor; - rect.height = zoomFactor <= 1.0 ? rect.height : rect.height / zoomFactor; - if (zoomFactor == 1.0 || ZoomHelper.isRectZoomedIn(rect, viewport)) { + bRect.width = zoomFactor <= 1.0 ? bRect.width : gScreenWidth / zoomFactor; + bRect.height = zoomFactor <= 1.0 ? bRect.height : bRect.height / zoomFactor; + if (zoomFactor == 1.0 || ZoomHelper.isRectZoomedIn(bRect, viewport)) { if (aCanZoomOut) { ZoomHelper.zoomOut(); } return; } - } else if (ZoomHelper.isRectZoomedIn(rect, viewport)) { + } else if (ZoomHelper.isRectZoomedIn(bRect, viewport)) { if (aCanZoomOut) { ZoomHelper.zoomOut(); } return; } - - ZoomHelper.zoomToRect(rect, aClickY); } - }, - /* Zoom to a specific part of the screen defined by a rect, - * optionally keeping a particular part of it in view - * if it is really tall. - */ - zoomToRect: function(aRect, aClickY = -1) { - let rect = new Rect(aRect.x, - aRect.y, - aRect.width, - Math.min(aRect.width * viewport.cssHeight / viewport.cssWidth, aRect.height)); + let rect = {}; rect.type = "Browser:ZoomToRect"; + rect.x = bRect.x; + rect.y = bRect.y; + rect.w = bRect.width; + rect.h = Math.min(bRect.width * viewport.cssHeight / viewport.cssWidth, bRect.height); if (aClickY >= 0) { // if the block we're zooming to is really tall, and we want to keep a particular // part of it in view, then adjust the y-coordinate of the target rect accordingly. - // the 1.2 multiplier is just a little fuzz to compensate for aRect including horizontal + // the 1.2 multiplier is just a little fuzz to compensate for bRect including horizontal // margins but not vertical ones. let cssTapY = viewport.cssY + aClickY; - if ((aRect.height > rect.h) && (cssTapY > rect.y + (rect.h * 1.2))) { + if ((bRect.height > rect.h) && (cssTapY > rect.y + (rect.h * 1.2))) { rect.y = cssTapY - (rect.h / 2); } } From bca2b0ed5eb3d210a3c41f96f23a32fb9570cf07 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Fri, 19 Sep 2014 12:22:30 -0700 Subject: [PATCH 18/19] Bumping manifests a=b2g-bump --- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/flame/sources.xml | 2 +- b2g/config/hamachi/sources.xml | 2 +- b2g/config/helix/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/wasabi/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 73a4764d470..0fa09301dc1 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 2d319751763..5c937ca3f1c 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -20,7 +20,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 2a1a974846c..438c106df08 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 0e510d808ac..7e0cac93335 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 2d319751763..5c937ca3f1c 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -20,7 +20,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 853c7bbdaa1..37262f8f6ec 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 4cd6c468a30..9b2d13ad4f5 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 8ba20b835e3..4444e22acd3 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 247e3ac73d5..47942959f44 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -16,7 +16,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 3ddc3cbf7cc..45d2ad2dc87 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index c21150e2800..8c78f0029c6 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -18,7 +18,7 @@ - + From d3dd736b407c653c766bd5d43e262c2d2460b190 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Fri, 19 Sep 2014 12:25:48 -0700 Subject: [PATCH 19/19] Bumping gaia.json for 4 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/b5f6c10e2583 Author: Wilson Page Desc: Merge pull request #24010 from wilsonpage/1066660 Bug 1066660 - [camera] Update drag for improved transitions ======== https://hg.mozilla.org/integration/gaia-central/rev/bb0487323a07 Author: Wilson Page Desc: Bug 1066660 - [camera] Update drag for improved transitions ======== https://hg.mozilla.org/integration/gaia-central/rev/dcdd1b716bcf Author: Douglas Sherk Desc: Merge pull request #24193 from DouglasSherk/1069439-ice-contacts-broken Bug 1069439 - Fix ICE contacts not showing up on emergency call screen. r=Rik ======== https://hg.mozilla.org/integration/gaia-central/rev/024360bfc8a9 Author: Doug Sherk Desc: Bug 1069439 - Fix ICE contacts not showing up on emergency call screen. r=Rik --- b2g/config/gaia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 928254de7d5..060b46c7748 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "ea495488fa9692a4ccfd17cc714c336fc7acb8d9", + "revision": "b5f6c10e258311d9f04ed759291bbe6af10a772b", "repo_path": "/integration/gaia-central" }