diff --git a/src/index.html b/src/index.html
index fc3e2295a7..68fcf9cf32 100644
--- a/src/index.html
+++ b/src/index.html
@@ -9,6 +9,7 @@
Firefox Marketplace
+
@@ -36,7 +37,6 @@
-
@@ -46,6 +46,7 @@
+
@@ -55,7 +56,6 @@
-
diff --git a/src/media/css/header.styl b/src/media/css/header.styl
index 09d3dc3360..6ff2b1bbe7 100644
--- a/src/media/css/header.styl
+++ b/src/media/css/header.styl
@@ -1,6 +1,12 @@
// styles for the global site header only.
@import 'lib';
+// Add the header's height back as padding. This is overriden when the header
+// is not `position: fixed`.
+body {
+ padding-top: $header-height;
+}
+
// ***************** Wide Mobile (600+ px)
// usable on any header
@@ -9,8 +15,8 @@
border-top: 1px solid $salt-flat-white;
border-bottom: 1px solid $seavan-salt-white;
display-flex();
- height: 48px;
- max-height: 48px;
+ height: $header-height;
+ max-height: $header-height;
position: relative;
width: 100%;
@@ -25,7 +31,7 @@
color: $castle-skull-gray;
font-size: 18px;
font-weight: 400;
- line-height: 48px;
+ line-height: $header-height;
z-index: 10;
}
}
@@ -45,7 +51,7 @@
display: block;
}
> nav {
- height: 48px;
+ height: $header-height;
display-flex();
position: relative;
text-align: left;
@@ -56,7 +62,7 @@
}
}
.site {
- height: 48px;
+ height: $header-height;
margin: 0 5px;
width: 200px;
@@ -197,10 +203,10 @@
.header-button {
color: $castle-skull-gray;
display: block;
- height: 48px;
- line-height: 48px;
+ height: $header-height;
+ line-height: $header-height;
text-align: center;
- width: 48px;
+ width: $header-height; // $header-height so it's square.
// header buttons with icons
&.icon {
@@ -425,15 +431,7 @@
flex(1);
}
}
-
-[data-page-type~=root] {
- main {
- padding-top: 48px;
- }
-}
main {
- padding-top: 38px;
-
&:before {
// This makes a sweet gradient below the header
// (which will appear when scrolling past the category drop-down menu).
@@ -441,7 +439,7 @@ main {
display: block;
gradientLinear(unquote('to top, rgba(15,15,15,0) 0%, rgba(15,15,15,.3) 100%'));
height: 2px;
- top: 48px;
+ top: $header-height;
position: fixed;
width: 100%;
z-index: 18;
@@ -465,7 +463,7 @@ main {
}
}
[data-page-type] main {
- padding: 0 0 48px;
+ padding: 0 0 $header-height;
&:before {
display: none;
@@ -477,6 +475,10 @@ main {
// This probably shouldn't be media query based.
@media $wider-desktop {
+ body {
+ padding-top: 0;
+ }
+
.header-button.settings {
right: 0;
top: 0;
@@ -514,7 +516,7 @@ main {
> nav {
height: 60px;
margin: 0 auto;
- max-width: 1024px;
+ max-width: $max-site-width;
}
.site {
margin: 0;
@@ -594,13 +596,12 @@ main {
}
// ***************** Wide Mobile (> 671px)
-
@media $at-least-desktop {
.secondary-header {
background: none;
border: 0;
- height: 48px;
- max-height: 48px;
+ height: $header-height;
+ max-height: $header-height;
position: relative;
&:before {
diff --git a/src/media/css/incompatible.styl b/src/media/css/incompatible.styl
deleted file mode 100644
index b63821f6b6..0000000000
--- a/src/media/css/incompatible.styl
+++ /dev/null
@@ -1,76 +0,0 @@
-@import 'lib';
-
-#incompatibility-banner {
- background: $grey-gardens-gray;
- color: $cement-gray;
- display: none;
- margin-bottom: -48px;
- margin-top: 48px;
- overflow: hidden;
- position: relative;
-
- &:before,
- &:after {
- content: "";
- display: block;
- glow(rgba(0,0,0,0.85), 3px);
- height: 1px;
- position: relative;
- top: -1px;
- width: 100%;
- }
- &:after {
- bottom: -1px;
- top: auto;
- }
- a {
- display: table-cell;
-
- &.close {
- margin-top: -17px;
- top: 50%;
- }
- }
- p {
- background: url(../img/logos/firefox-256.png) no-repeat;
- background-size: 102px 96px;
- margin: 0 auto;
- max-width: $desktop-content;
- min-height: 95px;
- padding: 28px 60px 28px 110px;
- position: relative;
- text-align: left;
- }
-}
-
-@media $wider-desktop {
- #incompatibility-banner {
- margin-top: 0;
- }
-}
-
-@media $narrower-than-desktop {
- #incompatibility-banner {
- p {
- background-size: 85px 80px;
- min-height: 80px;
- padding: 20px 60px 20px 90px;
- }
- }
-}
-
-.show-incompatibility-banner {
- #incompatibility-banner {
- display: block;
- }
- main {
- padding-top: 48px;
- }
-}
-
-@media $narrower-than-desktop {
- .show-incompatibility-banner main {
- padding-top: 0;
- }
-}
-
diff --git a/src/media/css/lib.styl b/src/media/css/lib.styl
index 0f358e69b3..fc134e8cd4 100644
--- a/src/media/css/lib.styl
+++ b/src/media/css/lib.styl
@@ -1,6 +1,8 @@
// Widths
$desktop-content = 690px;
$desktop-ftr = 732px;
+$header-height = 48px;
+$max-site-width = 1024px;
// Use these as @media $at-least-desktop {}
$at-least-desktop = unquote('(min-width: 710px)');
diff --git a/src/media/css/navbar.styl b/src/media/css/navbar.styl
index c1ee2d1a77..ab9b412488 100644
--- a/src/media/css/navbar.styl
+++ b/src/media/css/navbar.styl
@@ -28,9 +28,9 @@
body[data-page-type~=leaf],
body[data-page-type~=search] {
.site-nav {
- // Slide up and down.
+ // Slide out of view.
bottom: 50px;
- margin-bottom: -40px;
+ margin-bottom: -50px;
}
}
.site-nav {
@@ -182,7 +182,6 @@
}
.site-nav {
background-color: #E0E0E0;
- bottom: 38px;
height: 50px;
margin: 0 auto;
z-index: 25;
diff --git a/src/media/css/site-banner.styl b/src/media/css/site-banner.styl
new file mode 100644
index 0000000000..dc9ec70082
--- /dev/null
+++ b/src/media/css/site-banner.styl
@@ -0,0 +1,52 @@
+@import 'lib';
+
+.mkt-banner {
+ background: $grey-gardens-gray;
+ color: $cement-gray;
+ display: block;
+ overflow: hidden;
+ position: relative;
+ line-height: 1.3;
+
+ .mkt-banner-content {
+ align-items: center;
+ background: url("../img/logos/firefox-64.png") no-repeat;
+ background-position: 0 50%;
+ background-size: 64px 60px;
+ display-flex();
+ flex-line();
+ margin: 0 auto;
+ max-width: $max-site-width;
+ min-height: 60px;
+ padding: 10px 40px 10px 70px;
+ position: relative;
+ text-align: left;
+ }
+
+ .close {
+ margin-top: -17px;
+ top: 50%;
+ }
+}
+
+.mkt-banner.mkt-banner-success {
+ background: #64be3c;
+ color: #fff;
+
+ .mkt-banner-content {
+ font-size: 15px;
+ font-weight: 300;
+ }
+
+ small {
+ display: block;
+ font-size: 12px;
+ padding-top: 5px;
+ }
+
+ a {
+ color: #fff;
+ font-weight: 500;
+ text-decoration: underline;
+ }
+}
diff --git a/src/media/css/site.styl b/src/media/css/site.styl
index 028baf5f81..3eb8786bfd 100644
--- a/src/media/css/site.styl
+++ b/src/media/css/site.styl
@@ -98,13 +98,6 @@ body {
position: relative;
}
-@media $small-desktop {
- nav.site-nav {
- // Account for header height change.
- margin-bottom: -38px;
- }
-}
-
.row {
clear: both;
}
diff --git a/src/media/img/logos/firefox-64.png b/src/media/img/logos/firefox-64.png
new file mode 100644
index 0000000000..136171e1db
Binary files /dev/null and b/src/media/img/logos/firefox-64.png differ
diff --git a/src/media/js/components.js b/src/media/js/components.js
new file mode 100644
index 0000000000..598c12ae26
--- /dev/null
+++ b/src/media/js/components.js
@@ -0,0 +1,154 @@
+define('components', ['document-register-element'], function () {
+ // Abstract element with attribute -> class mappings.
+ var MktHTMLElement = function () {};
+ MktHTMLElement.prototype = Object.create(HTMLElement.prototype, {
+ attributeChangedCallback: {
+ value: function (name, previousValue, value) {
+ // Handle setting classes based on attributeClasses.
+ if (this.attributeClasses.hasOwnProperty(name)) {
+ var className = this.attributeClasses[name];
+ if (value === null) {
+ this.classList.remove(className);
+ } else {
+ this.classList.add(className);
+ }
+ }
+ },
+ },
+ attributeClasses: {
+ value: {},
+ },
+ createdCallback: {
+ value: function () {
+ var self = this;
+ Object.keys(this.attributeClasses).forEach(function (attr) {
+ var className = self.attributeClasses[attr];
+ if (self.hasAttribute(attr) && className) {
+ self.classList.add(className);
+ }
+ self.__defineGetter__(attr, function () {
+ // Treat `foo=""` as `foo=true`.
+ return self.getAttribute(attr) ||
+ self.hasAttribute(attr);
+ });
+ self.__defineSetter__(attr, function (value) {
+ if (value === null || value === false) {
+ self.removeAttribute(attr);
+ } else {
+ self.setAttribute(attr, value || true);
+ }
+ });
+ });
+ },
+ },
+ });
+
+ var MktBanner = document.registerElement('mkt-banner', {
+ prototype: Object.create(MktHTMLElement.prototype, {
+ attributeClasses: {
+ value: {
+ success: 'mkt-banner-success',
+ dismiss: null,
+ },
+ },
+ createdCallback: {
+ value: function () {
+ MktHTMLElement.prototype.createdCallback.call(this);
+ this.classList.add('mkt-banner');
+
+ if (this.rememberDismissal && this.dismissed) {
+ this.dismissBanner();
+ }
+
+ // Format the initial HTML.
+ this.html(this.innerHTML);
+ },
+ },
+ html: {
+ value: function (html) {
+ var self = this;
+
+ var content = document.createElement('div');
+ content.classList.add('mkt-banner-content');
+
+ // Wrap the provided content in a span so it gets flexed as
+ // one element.
+ var contentSpan = document.createElement('span');
+ contentSpan.innerHTML = html;
+ content.appendChild(contentSpan);
+
+ if (!this.undismissable) {
+ var closeButton = document.createElement('a');
+ closeButton.classList.add('close');
+ closeButton.href = '#';
+ closeButton.textContent = gettext('Close');
+ closeButton.title = gettext('Close');
+ closeButton.addEventListener('click', function (e) {
+ e.preventDefault();
+ self.dismissBanner();
+ });
+ content.appendChild(closeButton);
+ }
+
+ this.innerHTML = '';
+ this.appendChild(content);
+ },
+ },
+ dismissed: {
+ get: function () {
+ return this.storage.getItem(this.storageKey);
+ },
+ },
+ dismissBanner: {
+ value: function () {
+ if (this.rememberDismissal) {
+ this.storage.setItem(this.storageKey, true);
+ }
+ this.parentNode.removeChild(this);
+ },
+ },
+ rememberDismissal: {
+ get: function () {
+ return this.dismiss === 'remember';
+ },
+ },
+ storage: {
+ get: function () {
+ return require('storage');
+ },
+ },
+ storageKey: {
+ get: function () {
+ return 'hide_' + this.id.replace(/-/g, '_');
+ },
+ },
+ undismissable: {
+ get: function () {
+ return this.dismiss === 'off';
+ },
+ },
+ }),
+ });
+
+ var MktLogin = document.registerElement('mkt-login', {
+ prototype: Object.create(MktHTMLElement.prototype, {
+ createdCallback: {
+ value: function () {
+ if (this.isLink) {
+ var link = document.createElement('a');
+ link.href = '#';
+ link.classList.add('persona');
+ link.textContent = this.textContent;
+ this.innerHTML = '';
+ this.appendChild(link);
+ }
+ },
+ },
+ isLink: {
+ get: function () {
+ return this.hasAttribute('link');
+ },
+ },
+ }),
+ });
+});
diff --git a/src/media/js/lib/document-register-element.js b/src/media/js/lib/document-register-element.js
new file mode 100644
index 0000000000..26e9b5c1f8
--- /dev/null
+++ b/src/media/js/lib/document-register-element.js
@@ -0,0 +1,504 @@
+/*!
+Copyright (C) 2014 by WebReflection
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+(function(window, document, Object, REGISTER_ELEMENT){'use strict';
+
+// in case it's there or already patched
+if (REGISTER_ELEMENT in document) return;
+
+// DO NOT USE THIS FILE DIRECTLY, IT WON'T WORK
+// THIS IS A PROJECT BASED ON A BUILD SYSTEM
+// THIS FILE IS JUST WRAPPED UP RESULTING IN
+// build/document-register-element.js
+// and its .max.js counter part
+
+var
+ // IE < 11 only + old WebKit for attributes + feature detection
+ EXPANDO_UID = '__' + REGISTER_ELEMENT + (Math.random() * 10e4 >> 0),
+
+ // shortcuts and costants
+ EXTENDS = 'extends',
+ DOM_ATTR_MODIFIED = 'DOMAttrModified',
+ DOM_SUBTREE_MODIFIED = 'DOMSubtreeModified',
+
+ // valid and invalid node names
+ validName = /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,
+ invalidNames = [
+ 'ANNOTATION-XML',
+ 'COLOR-PROFILE',
+ 'FONT-FACE',
+ 'FONT-FACE-SRC',
+ 'FONT-FACE-URI',
+ 'FONT-FACE-FORMAT',
+ 'FONT-FACE-NAME',
+ 'MISSING-GLYPH'
+ ],
+
+ // registered types and their prototypes
+ types = [],
+ protos = [],
+
+ // to query subnodes
+ query = '',
+
+ // html shortcut used to feature detect
+ documentElement = document.documentElement,
+
+ // ES5 inline helpers || basic patches
+ indexOf = types.indexOf || function (v) {
+ for(var i = this.length; i-- && this[i] !== v;){}
+ return i;
+ },
+
+ // other helpers / shortcuts
+ OP = Object.prototype,
+ hOP = OP.hasOwnProperty,
+ iPO = OP.isPrototypeOf,
+
+ defineProperty = Object.defineProperty,
+ gOPD = Object.getOwnPropertyDescriptor,
+ gOPN = Object.getOwnPropertyNames,
+ gPO = Object.getPrototypeOf,
+ sPO = Object.setPrototypeOf,
+
+ hasProto = !!Object.__proto__,
+
+ // used to create unique instances
+ create = Object.create || function Bridge(proto) {
+ // silly broken polyfill probably ever used but short enough to work
+ return proto ? ((Bridge.prototype = proto), new Bridge) : this;
+ },
+
+ // will set the prototype if possible
+ // or copy over all properties
+ setPrototype = sPO || (
+ hasProto ?
+ function (o, p) {
+ o.__proto__ = p;
+ return o;
+ } : (
+ (gOPN && gOPD) ?
+ (function(){
+ function setProperties(o, p) {
+ for (var
+ key,
+ names = gOPN(p),
+ i = 0, length = names.length;
+ i < length; i++
+ ) {
+ key = names[i];
+ if (!hOP.call(o, key)) {
+ defineProperty(o, key, gOPD(p, key));
+ }
+ }
+ }
+ return function (o, p) {
+ do {
+ setProperties(o, p);
+ } while (p = gPO(p));
+ return o;
+ };
+ }()) :
+ function (o, p) {
+ for (var key in p) {
+ o[key] = p[key];
+ }
+ return o;
+ }
+ )),
+
+ // based on setting prototype capability
+ // will check proto or the expando attribute
+ // in order to setup the node once
+ patchIfNotAlready = sPO || hasProto ?
+ function (node, proto) {
+ if (!iPO.call(proto, node)) {
+ setupNode(node, proto);
+ }
+ } :
+ function (node, proto) {
+ if (!node[EXPANDO_UID]) {
+ node[EXPANDO_UID] = Object(true);
+ setupNode(node, proto);
+ }
+ }
+ ,
+
+ // DOM shortcuts and helpers, if any
+
+ MutationObserver = window.MutationObserver ||
+ window.WebKitMutationObserver,
+
+ HTMLElementPrototype = (
+ window.HTMLElement ||
+ window.Element ||
+ window.Node
+ ).prototype,
+
+ cloneNode = HTMLElementPrototype.cloneNode,
+ setAttribute = HTMLElementPrototype.setAttribute,
+
+ // replaced later on
+ createElement = document.createElement,
+
+ // shared observer for all attributes
+ attributesObserver = MutationObserver && {
+ attributes: true,
+ characterData: true,
+ attributeOldValue: true
+ },
+
+ // useful to detect only if there's no MutationObserver
+ DOMAttrModified = MutationObserver || function(e) {
+ doesNotSupportDOMAttrModified = false;
+ documentElement.removeEventListener(
+ DOM_ATTR_MODIFIED,
+ DOMAttrModified
+ );
+ },
+
+ // internal flags
+ setListener = false,
+ doesNotSupportDOMAttrModified = true,
+
+ // optionally defined later on
+ onSubtreeModified,
+ callDOMAttrModified,
+ getAttributesMirror,
+ observer
+;
+
+if (!MutationObserver) {
+ documentElement.addEventListener(DOM_ATTR_MODIFIED, DOMAttrModified);
+ documentElement.setAttribute(EXPANDO_UID, 1);
+ documentElement.removeAttribute(EXPANDO_UID);
+ if (doesNotSupportDOMAttrModified) {
+ onSubtreeModified = function (e) {
+ var
+ node = this,
+ oldAttributes,
+ newAttributes,
+ key
+ ;
+ if (node === e.target) {
+ oldAttributes = node[EXPANDO_UID];
+ node[EXPANDO_UID] = (newAttributes = getAttributesMirror(node));
+ for (key in newAttributes) {
+ if (!(key in oldAttributes)) {
+ // attribute was added
+ return callDOMAttrModified(
+ 0,
+ node,
+ key,
+ oldAttributes[key],
+ newAttributes[key],
+ 'ADDITION'
+ );
+ } else if (newAttributes[key] !== oldAttributes[key]) {
+ // attribute was changed
+ return callDOMAttrModified(
+ 1,
+ node,
+ key,
+ oldAttributes[key],
+ newAttributes[key],
+ 'MODIFICATION'
+ );
+ }
+ }
+ // checking if it has been removed
+ for (key in oldAttributes) {
+ if (!(key in newAttributes)) {
+ // attribute removed
+ return callDOMAttrModified(
+ 2,
+ node,
+ key,
+ oldAttributes[key],
+ newAttributes[key],
+ 'REMOVAL'
+ );
+ }
+ }
+ }
+ };
+ callDOMAttrModified = function (
+ attrChange,
+ currentTarget,
+ attrName,
+ prevValue,
+ newValue,
+ action
+ ) {
+ var e = {
+ attrChange: attrChange,
+ currentTarget: currentTarget,
+ attrName: attrName,
+ prevValue: prevValue,
+ newValue: newValue
+ };
+ e[action] = attrChange;
+ onDOMAttrModified(e);
+ };
+ getAttributesMirror = function (node) {
+ for (var
+ attr, name,
+ result = {},
+ attributes = node.attributes,
+ i = 0, length = attributes.length;
+ i < length; i++
+ ) {
+ attr = attributes[i];
+ name = attr.name;
+ if (name !== 'setAttribute') {
+ result[name] = attr.value;
+ }
+ }
+ return result;
+ };
+ }
+}
+
+function loopAndVerify(list, action) {
+ for (var i = 0, length = list.length; i < length; i++) {
+ verifyAndSetupAndAction(list[i], action);
+ }
+}
+
+function loopAndSetup(list) {
+ for (var i = 0, length = list.length, node; i < length; i++) {
+ node = list[i];
+ setupNode(node, protos[getTypeIndex(node)]);
+ }
+}
+
+function executeAction(action) {
+ return function (node) {
+ if (iPO.call(HTMLElementPrototype, node)) {
+ verifyAndSetupAndAction(node, action);
+ loopAndVerify(
+ node.querySelectorAll(query),
+ action
+ );
+ }
+ };
+}
+
+function getTypeIndex(target) {
+ var is = target.getAttribute('is');
+ return indexOf.call(
+ types,
+ is ?
+ is.toUpperCase() :
+ target.nodeName
+ );
+}
+
+function onDOMAttrModified(e) {
+ var
+ node = e.currentTarget,
+ attrChange = e.attrChange,
+ prevValue = e.prevValue,
+ newValue = e.newValue
+ ;
+ if (node.attributeChangedCallback &&
+ e.attrName !== 'style') {
+ node.attributeChangedCallback(
+ e.attrName,
+ attrChange === e.ADDITION ? null : prevValue,
+ attrChange === e.REMOVAL ? null : newValue
+ );
+ }
+}
+
+function onDOMNode(action) {
+ var executor = executeAction(action);
+ return function (e) {
+ executor(e.target);
+ };
+}
+
+function patchedSetAttribute(name, value) {
+ var self = this;
+ setAttribute.call(self, name, value);
+ onSubtreeModified.call(self, {target: self});
+}
+
+function setupNode(node, proto) {
+ setPrototype(node, proto);
+ if (observer) {
+ observer.observe(node, attributesObserver);
+ } else {
+ if (doesNotSupportDOMAttrModified) {
+ node.setAttribute = patchedSetAttribute;
+ node[EXPANDO_UID] = getAttributesMirror(node);
+ node.addEventListener(DOM_SUBTREE_MODIFIED, onSubtreeModified);
+ }
+ node.addEventListener(DOM_ATTR_MODIFIED, onDOMAttrModified);
+ }
+ if (node.createdCallback) {
+ node.created = true;
+ node.createdCallback();
+ node.created = false;
+ }
+}
+
+function verifyAndSetupAndAction(node, action) {
+ var
+ fn,
+ i = getTypeIndex(node),
+ attached = 'attached',
+ detached = 'detached'
+ ;
+ if (-1 < i) {
+ patchIfNotAlready(node, protos[i]);
+ i = 0;
+ if (action === attached && !node[attached]) {
+ node[detached] = false;
+ node[attached] = true;
+ i = 1;
+ } else if (action === detached && !node[detached]) {
+ node[attached] = false;
+ node[detached] = true;
+ i = 1;
+ }
+ if (i && (fn = node[action + 'Callback'])) fn.call(node);
+ }
+}
+
+// set as enumerable, writable and configurable
+document[REGISTER_ELEMENT] = function registerElement(type, options) {
+ upperType = type.toUpperCase();
+ if (!setListener) {
+ // only first time document.registerElement is used
+ // we need to set this listener
+ // setting it by default might slow down for no reason
+ setListener = true;
+ if (MutationObserver) {
+ observer = (function(attached, detached){
+ function checkEmAll(list, callback) {
+ for (var i = 0, length = list.length; i < length; callback(list[i++])){}
+ }
+ return new MutationObserver(function (records) {
+ for (var
+ current, node,
+ i = 0, length = records.length; i < length; i++
+ ) {
+ current = records[i];
+ if (current.type === 'childList') {
+ checkEmAll(current.addedNodes, attached);
+ checkEmAll(current.removedNodes, detached);
+ } else {
+ node = current.target;
+ if (node.attributeChangedCallback &&
+ current.attributeName !== 'style') {
+ node.attributeChangedCallback(
+ current.attributeName,
+ current.oldValue,
+ node.getAttribute(current.attributeName)
+ );
+ }
+ }
+ }
+ });
+ }(executeAction('attached'), executeAction('detached')));
+ observer.observe(
+ document,
+ {
+ childList: true,
+ subtree: true
+ }
+ );
+ } else {
+ document.addEventListener('DOMNodeInserted', onDOMNode('attached'));
+ document.addEventListener('DOMNodeRemoved', onDOMNode('detached'));
+ }
+
+ document.addEventListener('readystatechange', function (e) {
+ loopAndVerify(
+ document.querySelectorAll(query),
+ 'attached'
+ );
+ });
+
+ document.createElement = function (localName, typeExtension) {
+ var i, node = createElement.apply(document, arguments);
+ if (typeExtension) {
+ node.setAttribute('is', localName = typeExtension.toLowerCase());
+ }
+ i = indexOf.call(types, localName.toUpperCase());
+ if (-1 < i) setupNode(node, protos[i]);
+ return node;
+ };
+
+ HTMLElementPrototype.cloneNode = function (deep) {
+ var
+ node = cloneNode.call(this, !!deep),
+ i = getTypeIndex(node)
+ ;
+ if (-1 < i) setupNode(node, protos[i]);
+ if (deep) loopAndSetup(node.querySelectorAll(query));
+ return node;
+ };
+ }
+
+ if (-1 < indexOf.call(types, upperType)) {
+ throw new Error('A ' + type + ' type is already registered');
+ }
+
+ if (!validName.test(upperType) || -1 < indexOf.call(invalidNames, upperType)) {
+ throw new Error('The type ' + type + ' is invalid');
+ }
+
+ var
+ constructor = function () {
+ return document.createElement(nodeName, extending && upperType);
+ },
+ opt = options || OP,
+ extending = hOP.call(opt, EXTENDS),
+ nodeName = extending ? options[EXTENDS] : upperType,
+ i = types.push(upperType) - 1,
+ upperType
+ ;
+
+ query = query.concat(
+ query.length ? ',' : '',
+ extending ? nodeName + '[is="' + type.toLowerCase() + '"]' : nodeName
+ );
+
+ constructor.prototype = (
+ protos[i] = hOP.call(opt, 'prototype') ?
+ opt.prototype :
+ create(HTMLElementPrototype)
+ );
+
+ loopAndVerify(
+ document.querySelectorAll(query),
+ 'attached'
+ );
+
+ return constructor;
+};
+
+
+}(window, document, Object, 'registerElement'));
+define('document-register-element', [], function () {});
diff --git a/src/media/js/main.js b/src/media/js/main.js
index 46743611e3..ea3dd18fc3 100644
--- a/src/media/js/main.js
+++ b/src/media/js/main.js
@@ -17,6 +17,7 @@ require.config({
'settings': ['settings_local', 'settings'],
'format': 'lib/format',
'hammerjs': 'hammer',
+ 'document-register-element': 'lib/document-register-element',
},
});
@@ -30,6 +31,7 @@ define(
'apps_buttons',
'cache',
'capabilities',
+ 'components',
'consumer_info',
'mobilenetwork',
'content-ratings',
@@ -67,6 +69,7 @@ function(_) {
var apps = require('apps');
var buttons = require('apps_buttons');
var capabilities = require('capabilities');
+ var consumer_info = require('consumer_info');
var format = require('format');
var $ = require('jquery');
var settings = require('settings');
@@ -170,7 +173,7 @@ function(_) {
// Refresh list of installed apps in case user uninstalled apps
// and switched back.
if (require('user').logged_in()) {
- require('consumer_info').fetch(true);
+ consumer_info.fetch(true);
}
apps.getInstalled().done(buttons.mark_btns_as_uninstalled);
}
@@ -186,16 +189,28 @@ function(_) {
$('#site-footer').html(
nunjucks.env.render('footer.html', context));
- if (!navigator.mozApps &&
- !navigator.userAgent.match(/googlebot/i) &&
- !require('storage').getItem('hide_incompatibility_banner')) {
+ if (!window['incompatibility-banner'] &&
+ !navigator.mozApps &&
+ !navigator.userAgent.match(/googlebot/i)) {
console.log('Adding incompatibility banner');
- $('#incompatibility-banner').html(
- nunjucks.env.render('incompatible.html'));
- z.body.addClass('show-incompatibility-banner');
+ $('#site-nav').after(nunjucks.env.render('incompatible.html'));
}
- z.body.toggleClass('logged-in', require('user').logged_in());
+ var logged_in = require('user').logged_in();
+
+ // Wait for the switches to be pulled down.
+ consumer_info.promise.then(function () {
+ var fxAccountsMigration = settings.switches.indexOf(
+ 'fx-accounts-migration') !== -1;
+
+ if (fxAccountsMigration && !window['fx-accounts-banner']) {
+ $('#site-nav').after(
+ nunjucks.env.render('fx-accounts-banner.html',
+ {logged_in: logged_in}));
+ }
+ });
+
+ z.body.toggleClass('logged-in', logged_in);
z.page.trigger('reloaded_chrome');
}).trigger('reload_chrome');
@@ -209,13 +224,6 @@ function(_) {
require('navigation').back();
});
- z.body.on('click', '#incompatibility-banner .close', function(e) {
- e.preventDefault();
- console.log('Hiding incompatibility banner');
- z.body.removeClass('show-incompatibility-banner');
- require('storage').setItem('hide_incompatibility_banner', true);
- });
-
var ImageDeferrer = require('image-deferrer');
var iconDeferrer = ImageDeferrer.Deferrer(100, null);
var screenshotDeferrer = ImageDeferrer.Deferrer(null, 200);
@@ -246,7 +254,7 @@ function(_) {
false
);
- require('consumer_info').promise.done(function() {
+ consumer_info.promise.done(function() {
console.log('Triggering initial navigation');
if (!z.spaceheater) {
z.page.trigger('navigate', [window.location.pathname + window.location.search]);
diff --git a/src/server.html b/src/server.html
index e523661a25..6ac3ff21b0 100644
--- a/src/server.html
+++ b/src/server.html
@@ -30,7 +30,6 @@
-
diff --git a/src/templates/fx-accounts-banner.html b/src/templates/fx-accounts-banner.html
new file mode 100644
index 0000000000..cb1c3131a0
--- /dev/null
+++ b/src/templates/fx-accounts-banner.html
@@ -0,0 +1,15 @@
+
+ {{ _('Firefox Accounts has arrived!') }}
+
+ {% if logged_in %}
+ {{ _("Transfer your account now. It's easy.") }}
+ {% else %}
+ {{ _("Simply sign in to transfer your account.") }}
+ {% endif %}
+
+
+
+ {{ _('Learn more about Firefox Accounts.') }}
+
+
+
diff --git a/src/templates/incompatible.html b/src/templates/incompatible.html
index 25e68971b1..8f4465c95d 100644
--- a/src/templates/incompatible.html
+++ b/src/templates/incompatible.html
@@ -1,5 +1,4 @@
-
- {{ _('You must use Firefox to install Firefox apps.') }}
+
+ {{ _('You must use Firefox to install Firefox apps.') }}{{ _('Download.') }}
- {{ _('Close') }}
-