Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,23 @@
{
"name": "app-deck",
"version": "0.0.0",
"homepage": "https://github.com/mozilla-b2g/gaia/tree/master/tv_apps/app-deck",
"description": "Smart Screen Apps Deck",
"main": "index.html",
"license": "MPL",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"evt": "fxos-components/evt#~0.4.0",
"smart-button": "fxos-components/smart-button",
"smart-bubbles": "smart-components/smart-bubbles",
"smart-dialog": "smart-components/smart-dialog",
"smart-modal-dialog": "smart-components/smart-modal-dialog"
}
}

View file

@ -0,0 +1,28 @@
{
"name": "evt",
"main": "index.js",
"version": "0.4.0",
"homepage": "https://github.com/wilsonpage/evt",
"authors": [
"Wilson Page <wilsonpage@me.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"package.json",
"test",
"tests",
"README.md"
],
"_release": "0.4.0",
"_resolution": {
"type": "version",
"tag": "v0.4.0",
"commit": "3593dd6b4dd19f683d00fd0eaf2581c11a128da6"
},
"_source": "git://github.com/fxos-components/evt.git",
"_target": "~0.4.0",
"_originalSource": "fxos-components/evt"
}

View file

@ -0,0 +1,19 @@
{
"name": "evt",
"main": "index.js",
"version": "0.4.0",
"homepage": "https://github.com/wilsonpage/evt",
"authors": [
"Wilson Page <wilsonpage@me.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"package.json",
"test",
"tests",
"README.md"
]
}

View file

@ -0,0 +1,154 @@
/**
* Evt
*
* A super lightweight
* event emitter library.
*
* @version 0.3.3
* @author Wilson Page <wilson.page@me.com>
*/
;(function() {
/**
* Locals
*/
var proto = Events.prototype;
var slice = [].slice;
/**
* Creates a new event emitter
* instance, or if passed an
* object, mixes the event logic
* into it.
*
* @param {Object} obj
* @return {Object}
*/
function Events(obj) {
if (!(this instanceof Events)) return new Events(obj);
if (obj) return mixin(obj, proto);
}
/**
* Registers a callback
* with an event name.
*
* @param {String} name
* @param {Function} cb
* @return {Event}
*/
proto.on = function(name, cb) {
this._cbs = this._cbs || {};
(this._cbs[name] || (this._cbs[name] = [])).push(cb);
return this;
};
/**
* Attach a callback that once
* called, detaches itself.
*
* TODO: Implement `.off()` to work
* with `once()` callbacks.
*
* @param {String} name
* @param {Function} cb
* @public
*/
proto.once = function(name, cb) {
this.on(name, one);
function one() {
cb.apply(this, arguments);
this.off(name, one);
}
};
/**
* Removes a single callback,
* or all callbacks associated
* with the passed event name.
*
* @param {String} name
* @param {Function} cb
* @return {Event}
*/
proto.off = function(name, cb) {
this._cbs = this._cbs || {};
if (!name) { this._cbs = {}; return; }
if (!cb) { return delete this._cbs[name]; }
var cbs = this._cbs[name] || [];
var i;
while (cbs && ~(i = cbs.indexOf(cb))) { cbs.splice(i, 1); }
return this;
};
/**
* Fires an event, triggering
* all callbacks registered on this
* event name.
*
* @param {String} name
* @return {Event}
*/
proto.fire = proto.emit = function(options) {
var cbs = this._cbs = this._cbs || {};
var name = options.name || options;
var batch = (cbs[name] || []).concat(cbs['*'] || []);
var ctx = options.ctx || this;
if (batch.length) {
this._fireArgs = arguments;
var args = slice.call(arguments, 1);
while (batch.length) {
batch.shift().apply(ctx, args);
}
}
return this;
};
proto.firer = function(name) {
var self = this;
return function() {
var args = slice.call(arguments);
args.unshift(name);
self.fire.apply(self, args);
};
};
/**
* Util
*/
/**
* Mixes in the properties
* of the second object into
* the first.
*
* @param {Object} a
* @param {Object} b
* @return {Object}
*/
function mixin(a, b) {
for (var key in b) a[key] = b[key];
return a;
}
/**
* Expose 'Event' (UMD)
*/
if (typeof exports === 'object') {
module.exports = Events;
} else if (typeof define === 'function' && define.amd) {
define(function(){ return Events; });
} else {
window.evt = Events;
}
})();

View file

@ -0,0 +1,30 @@
{
"name": "smart-bubbles",
"homepage": "https://github.com/smart-components/smart-bubbles",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md",
"gulpfile.js",
"package.json"
],
"devDependencies": {
"smart-button": "smart-components/smart-button"
},
"_release": "5c08ef5d84",
"_resolution": {
"type": "branch",
"branch": "master",
"commit": "5c08ef5d84b0bd1d8252600cfc744aa7869e9f92"
},
"_source": "git://github.com/smart-components/smart-bubbles.git",
"_target": "*",
"_originalSource": "smart-components/smart-bubbles"
}

View file

@ -0,0 +1,22 @@
{
"name": "smart-bubbles",
"version": "0.0.0",
"homepage": "https://github.com/smart-components/smart-bubbles",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md",
"gulpfile.js",
"package.json"
],
"devDependencies": {
"smart-button": "smart-components/smart-button"
}
}

View file

@ -0,0 +1,83 @@
window.SmartBubbles = (function(win) {
'use strict';
var DEFAULT_INTERVAL = 100;
// Extend from the HTMLElement prototype
var proto = Object.create(HTMLElement.prototype);
proto.fireEvent = function(event, detail) {
var evtObject = new CustomEvent(event, {
bubbles: false,
detail: detail || this
});
this.dispatchEvent(evtObject);
};
proto.handleEvent = function(evt) {
switch(evt.type) {
case 'animationend':
this.fireEvent('item-bubbled', ++this._finishedAnimationCount);
if (this._targetAnimationCount === this._finishedAnimationCount) {
this.playing = false;
[].forEach.call(this._elements, function(elem) {
elem.classList.remove('smart-bubble-playing');
// XXX:Usually we use <smart-button> within container of
// smart-bubbles. If we remove style of smart-bubble-playing,
// Gecko would apply original style of smart-button instead and it
// has transition property too. This results in another transition
// happens on <smart-button> while we thought we'd just stop
// animation. That is why we need to add 'smart-bubble-stopped' here
// to prevent unexpected animation from happening.
elem.classList.add('smart-bubble-stopped');
// use getComputedStyle to force flushing re-style
window.getComputedStyle(elem).animationName;
elem.classList.remove('smart-bubble-stopped');
window.getComputedStyle(elem).animationName;
elem.style.animationDelay = '';
}.bind(this));
this._elements = null;
this.fireEvent('all-items-bubbled');
}
evt.currentTarget.removeEventListener('animationend', this);
break;
}
};
proto.play = function(elements) {
this.stopImmediately();
this._elements = elements;
this._finishedAnimationCount = 0;
this._targetAnimationCount = 0;
var interval = parseInt(this.getAttribute('interval'), 10) ||
DEFAULT_INTERVAL;
[].forEach.call(elements, function(elem) {
this._targetAnimationCount++;
var delay = this._targetAnimationCount * interval / 1000;
elem.classList.add('smart-bubble-playing');
elem.style.animationDelay = delay + 's';
elem.addEventListener('animationend', this);
}.bind(this));
this.playing = true;
};
proto.stopImmediately = function() {
if (!this.playing || !this._elements) {
return;
}
[].forEach.call(this._elements, function(elem) {
elem.classList.remove('smart-bubble-playing');
elem.classList.add('smart-bubble-stopped');
elem.removeEventListener('animationend', this);
// use getComputedStyle to force flushing re-style
window.getComputedStyle(elem).animationName;
elem.classList.remove('smart-bubble-stopped');
}.bind(this));
this._elements = null;
this.playing = false;
this.fireEvent('all-items-stopped');
};
// Register and return the constructor
return document.registerElement('smart-bubbles', { prototype: proto });
})(window);

View file

@ -0,0 +1 @@
window.SmartBubbles=function(t){"use strict";var e=100,i=Object.create(HTMLElement.prototype);return i.fireEvent=function(t,e){var i=new CustomEvent(t,{bubbles:!1,detail:e||this});this.dispatchEvent(i)},i.handleEvent=function(t){switch(t.type){case"animationend":this.fireEvent("item-bubbled",++this._finishedAnimationCount),this._targetAnimationCount===this._finishedAnimationCount&&(this.playing=!1,[].forEach.call(this._elements,function(t){t.classList.remove("smart-bubble-playing"),t.classList.add("smart-bubble-stopped"),window.getComputedStyle(t).animationName,t.classList.remove("smart-bubble-stopped"),window.getComputedStyle(t).animationName,t.style.animationDelay=""}.bind(this)),this._elements=null,this.fireEvent("all-items-bubbled")),t.currentTarget.removeEventListener("animationend",this)}},i.play=function(t){this.stopImmediately(),this._elements=t,this._finishedAnimationCount=0,this._targetAnimationCount=0;var i=parseInt(this.getAttribute("interval"),10)||e;[].forEach.call(t,function(t){this._targetAnimationCount++;var e=this._targetAnimationCount*i/1e3;t.classList.add("smart-bubble-playing"),t.style.animationDelay=e+"s",t.addEventListener("animationend",this)}.bind(this)),this.playing=!0},i.stopImmediately=function(){this.playing&&this._elements&&([].forEach.call(this._elements,function(t){t.classList.remove("smart-bubble-playing"),t.classList.add("smart-bubble-stopped"),t.removeEventListener("animationend",this),window.getComputedStyle(t).animationName,t.classList.remove("smart-bubble-stopped")}.bind(this)),this._elements=null,this.playing=!1,this.fireEvent("all-items-stopped"))},document.registerElement("smart-bubbles",{prototype:i})}(window);

View file

@ -0,0 +1,83 @@
window.SmartBubbles = (function(win) {
'use strict';
var DEFAULT_INTERVAL = 100;
// Extend from the HTMLElement prototype
var proto = Object.create(HTMLElement.prototype);
proto.fireEvent = function(event, detail) {
var evtObject = new CustomEvent(event, {
bubbles: false,
detail: detail || this
});
this.dispatchEvent(evtObject);
};
proto.handleEvent = function(evt) {
switch(evt.type) {
case 'animationend':
this.fireEvent('item-bubbled', ++this._finishedAnimationCount);
if (this._targetAnimationCount === this._finishedAnimationCount) {
this.playing = false;
[].forEach.call(this._elements, function(elem) {
elem.classList.remove('smart-bubble-playing');
// XXX:Usually we use <smart-button> within container of
// smart-bubbles. If we remove style of smart-bubble-playing,
// Gecko would apply original style of smart-button instead and it
// has transition property too. This results in another transition
// happens on <smart-button> while we thought we'd just stop
// animation. That is why we need to add 'smart-bubble-stopped' here
// to prevent unexpected animation from happening.
elem.classList.add('smart-bubble-stopped');
// use getComputedStyle to force flushing re-style
window.getComputedStyle(elem).animationName;
elem.classList.remove('smart-bubble-stopped');
window.getComputedStyle(elem).animationName;
elem.style.animationDelay = '';
}.bind(this));
this._elements = null;
this.fireEvent('all-items-bubbled');
}
evt.currentTarget.removeEventListener('animationend', this);
break;
}
};
proto.play = function(elements) {
this.stopImmediately();
this._elements = elements;
this._finishedAnimationCount = 0;
this._targetAnimationCount = 0;
var interval = parseInt(this.getAttribute('interval'), 10) ||
DEFAULT_INTERVAL;
[].forEach.call(elements, function(elem) {
this._targetAnimationCount++;
var delay = this._targetAnimationCount * interval / 1000;
elem.classList.add('smart-bubble-playing');
elem.style.animationDelay = delay + 's';
elem.addEventListener('animationend', this);
}.bind(this));
this.playing = true;
};
proto.stopImmediately = function() {
if (!this.playing || !this._elements) {
return;
}
[].forEach.call(this._elements, function(elem) {
elem.classList.remove('smart-bubble-playing');
elem.classList.add('smart-bubble-stopped');
elem.removeEventListener('animationend', this);
// use getComputedStyle to force flushing re-style
window.getComputedStyle(elem).animationName;
elem.classList.remove('smart-bubble-stopped');
}.bind(this));
this._elements = null;
this.playing = false;
this.fireEvent('all-items-stopped');
};
// Register and return the constructor
return document.registerElement('smart-bubbles', { prototype: proto });
})(window);

View file

@ -0,0 +1,26 @@
*[smart-bubbles="true"] .smart-bubble-playing {
transition-property: none;
transform: none;
animation: bubble-animation 0.5s cubic-bezier(0.25, 0, 0, 1) both;
}
*[smart-bubbles="true"][smart-bubbles-direction="down"] .smart-bubble-playing {
animation: sinking-animation 0.5s cubic-bezier(0.25, 0, 0, 1) both;
}
*[smart-bubbles="true"] .smart-bubble-stopped {
transition-property: none;
transform: none;
animation: none;
}
@keyframes bubble-animation {
0% {opacity: 0; transform: translateY(8rem);}
50% {opacity: 1;}
100% {transform: translateY(0);}
}
@keyframes sinking-animation {
0% {opacity: 1; transform: translateY(0);}
100% {opacity: 0; transform: translateY(8rem);}
}

View file

@ -0,0 +1,26 @@
*[smart-bubbles="true"] .smart-bubble-playing {
transition-property: none;
transform: none;
animation: bubble-animation 0.5s cubic-bezier(0.25, 0, 0, 1) both;
}
*[smart-bubbles="true"][smart-bubbles-direction="down"] .smart-bubble-playing {
animation: sinking-animation 0.5s cubic-bezier(0.25, 0, 0, 1) both;
}
*[smart-bubbles="true"] .smart-bubble-stopped {
transition-property: none;
transform: none;
animation: none;
}
@keyframes bubble-animation {
0% {opacity: 0; transform: translateY(8rem);}
50% {opacity: 1;}
100% {transform: translateY(0);}
}
@keyframes sinking-animation {
0% {opacity: 1; transform: translateY(0);}
100% {opacity: 0; transform: translateY(8rem);}
}

View file

@ -0,0 +1,26 @@
{
"name": "smart-button",
"version": "0.0.3",
"homepage": "https://github.com/gaia-components/smart-button",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md"
],
"_release": "0.0.3",
"_resolution": {
"type": "version",
"tag": "v0.0.3",
"commit": "9994b26e2acc5a2f3a505fe4ae04795fbe96d072"
},
"_source": "git://github.com/fxos-components/smart-button.git",
"_target": "*",
"_originalSource": "fxos-components/smart-button"
}

View file

@ -0,0 +1,17 @@
{
"name": "smart-button",
"version": "0.0.3",
"homepage": "https://github.com/gaia-components/smart-button",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md"
]
}

View file

@ -0,0 +1,62 @@
'use strict';
/* global KeyEvent */
window.SmartButton = (function(win) {
// Extend from the HTMLButtonElement prototype
var proto = Object.create(HTMLButtonElement.prototype);
proto.createdCallback = function() {
this.addEventListener('mousedown', this);
this.addEventListener('mouseup', this);
this.addEventListener('touchstart', this);
this.addEventListener('touchend', this);
this.addEventListener('keydown', this);
this.addEventListener('keyup', this);
this.addEventListener('focus', this);
this.addEventListener('blur', this);
this.addEventListener('transitionend', this);
this.tabIndex = 0;
};
proto.handleEvent = function(evt) {
switch(evt.type) {
case 'mousedown':
case 'touchstart':
this.classList.add('pressed');
break;
case 'keydown':
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
this.classList.add('pressed');
}
break;
case 'mouseup':
case 'touchend':
this.classList.remove('pressed');
this.classList.add('released');
break;
case 'keyup':
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
this.classList.remove('pressed');
this.classList.add('released');
this.click();
}
break;
case 'transitionend':
if (this.classList.contains('released')) {
this.classList.remove('released');
}
break;
case 'focus':
this.classList.add('focused');
break;
case 'blur':
this.classList.remove('pressed');
this.classList.remove('released');
this.classList.remove('focused');
break;
}
};
// Register and return the constructor
return document.registerElement('smart-button', { prototype: proto });
})(window);

View file

@ -0,0 +1,498 @@
/**
* Button state:
* 1. normal
* In this state, we don't add any class.
* 2. focused (class name: focused)
* The focused state may be triggered by keyboard or touch/mouse.
* 3. pressed (class name: pressed)
* This state is triggered by user pressed confirm button, mousedown,
* and touchdown.
* 4. released (class name: released)
* This state is only for changing duration of transition. We enter this
* state when user release the button. After transitionend, we goes back
* to focused state.
**/
smart-button {
font-family: "Fira Sans";
display: inline-block;
width: 8rem;
height: 8rem;
/* we need to set border to none to remove the button border */
border: none;
/* the radius is still make button as round even if we don't have border */
border-radius: 50%;
/* 50% is not a real circle because of rounding, we have to use a value larger
than 50% */
-moz-outline-radius: 60%;
outline: transparent 0 solid;
background-color: rgba(0, 0, 0, 0.5);
background-repeat: no-repeat;
background-size: 5.4rem auto;
background-position: center center;
font-style: italic;
transition-property: all;
transition-timing-function: cubic-bezier(0.25, 0, 0, 1.0);
transition-duration: 0.42s;
}
smart-button.focused {
outline: 0;
background-color: #ffffff;
transform: scale(1.2);
transition-duration: 0.42s;
}
smart-button.released {
transition-duration: 0.16s;
}
smart-button.toggled {
background-color: #00caf2;
}
smart-button.pressed {
background-color: #00caf2;
transform: scale(0.8);
transition-duration: 0.06s;
}
smart-button.disabled {
background-color: rgba(0, 0, 0, 0.5);
}
smart-button[data-icon]:before {
width: 5.4rem;
left: 1.3rem;
font-size: 5.4rem;
line-height: 8rem;
text-align: center;
color: #ffffff;
white-space: normal;
top: 0;
position: absolute;
}
smart-button[data-icon].focused:before {
color: #2d2d2d;
}
smart-button[data-icon].toggled:before {
color: #00caf2;
}
smart-button[data-icon].pressed:before {
color: #ffffff;
}
smart-button[data-icon].disabled:before {
color: #ffffff;
opacity: 0.3;
}
/**
* Simple Text Button
**/
smart-button[type="simple-text"] {
color: #dddddd;
background: none;
border: none;
border-radius: 0;
width: auto;
height: 8rem;
line-height: 8rem;
font-size: 2.4rem;
-moz-user-select: none;
padding-left: 2.6rem;
padding-right: 2.6rem;
}
smart-button[type="simple-text"].focused {
color: #ffffff;
background: none;
}
smart-button[type="simple-text"].toggled {
color: #00caf2;
}
smart-button[type="simple-text"].pressed {
color: #00caf2;
background: none;
}
/**
* End of Simple Text Button
**/
/**
* underlined text button
*/
smart-button[type="underlined-text"] {
position: relative;
color: white;
font-size: 2.4rem;
padding-left: 3.8rem;
padding-right: 3.8rem;
border-radius: 4rem;
width: auto;
line-height: 8rem;
margin-right: 1.4rem;
overflow: hidden;
}
smart-button[type="underlined-text"]:after {
position: absolute;
width: 100%;
height: 100%;
left: 0;
content: ' ';
border-bottom: 0.6rem #61acd7 solid;
box-sizing: border-box;
}
smart-button[type="underlined-text"].focused {
transform: scale(1.2);
color: #2d2d2d;
}
smart-button[type="underlined-text"].pressed {
transform: scale(1);
color: white;
background-color: #00caf2;
}
/**
* End of underlined text button
*/
/**
* App Icons
**/
smart-button[type="app-button"] {
/* To use 33.4rem instead of to use 33.6rem is to work around the anti-alias
pixel. So, the circle is filled by the background almost totally. */
width: 33.4rem;
height: 33.4rem;
position: relative;
overflow: hidden;
background-color: transparent;
}
smart-button[type="app-button"].focused {
transform: scale(1.4);
/* To use outline will not change the size of button and make the whole button
thrilling. */
outline: 0.8rem solid #ffffff;
}
smart-button[type="app-button"].pressed {
/* While scaling, we may have a gap between outline and icon which may be
anti-alias pixel. So, we use scale(1.22) to prevent such kind of case */
transform: scale(1.22);
/* To use outline will not change the size of button and make the whole button
thrilling. */
outline: 1.0rem solid #ffffff;
}
smart-button[type="app-button"].fullsized {
background-size: 33.6rem auto;
}
smart-button[type="app-button"].fitted {
background-size: 22rem auto;
}
/** we use :after as label */
smart-button[type="app-button"]:after {
content: attr(label);
position: absolute;
bottom: 0;
left: auto;
right: auto;
width: 33.6rem;
height: 8rem;
padding-top: 1rem;
line-height: 3rem;
text-align: center;
font-size: 2.8rem;
font-style: italic;
color: #ffffff;
}
/**
* text backgorund color
**/
smart-button[type="app-button"][app-type="deck"]:after {
background-color: transparent;
}
smart-button[type="app-button"][app-type="tv"]:after {
background-color: #d90c83;
}
smart-button[type="app-button"][app-type="app"]:after,
smart-button[type="app-button"][app-type="bookmark"]:after {
background-color: rgba(0, 0, 0, 0.4);
}
smart-button[type="app-button"][app-type="device"]:after {
background-color: #4a82ea;
}
smart-button[type="app-button"][app-type="folder"] {
background-color: #595959;
}
smart-button[type="app-button"]:before {
left: 0;
line-height: 30rem;
font-size: 16rem;
width: 100%;
}
smart-button[type="app-button"][app-type="folder"]:before {
color: #CCCCCC;
}
/**
* End of App Icons
**/
/**
* text and icon button
*/
smart-button[type="text-and-icon"] {
position: relative;
color: white;
font-size: 2.4rem;
padding-left: 4.6rem;
padding-right: 4.6rem;
border-radius: 4rem;
width: auto;
line-height: 8rem;
margin-right: 1.4rem;
overflow: hidden;
}
smart-button[type="text-and-icon"][data-icon] {
padding-left: 9.6rem;
}
smart-button[type="text-and-icon"][data-icon]:before {
left: 3.8rem;
}
smart-button[type="text-and-icon"].focused {
transform: scale(1.2);
color: #2d2d2d;
}
smart-button[type="text-and-icon"].pressed {
transform: scale(1);
color: white;
background-color: #00caf2;
}
/**
* End of text and icon button
*/
/**
* banner button
**/
smart-button[type="banner-button"] {
position: relative;
color: #ffffff;
font-size: 2rem;
width: 36rem;
height: 20rem;
overflow: hidden;
border-radius: 0;
border: 0 solid #ffffff;
background-color: #5f5f5f;
background-repeat: no-repeat;
background-size: 120%;
box-sizing: border-box;
transition-property: all;
transition-timing-function: cubic-bezier(0.25, 0, 0.5, 1.0);
transition-duration: 0.1s;
}
smart-button[type="banner-button"][label]:before {
content: attr(label);
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 5rem;
line-height: 5rem;
text-align: center;
background-color: rgba(0, 0, 0, 0.4);
}
smart-button[type="banner-button"]:after {
content: " ";
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
transition-property: all;
transition-timing-function: cubic-bezier(0.25, 0, 0, 1.0);
transition-duration: 0.06s;
background-color: rgba(255, 255, 255, 0);
}
smart-button[type="banner-button"].focused {
transform: none;
border-width: 1.2rem;
background-size: 100%;
transition-duration: 0.24s;
}
smart-button[type="banner-button"].pressed {
transform: scale(0.945, 0.9);
}
smart-button[type="banner-button"].pressed:after {
background-color: rgba(255, 255, 255, 0.5);
}
/**
* banner button
**/
/**
* circle text button
*/
smart-button[type="circle-text"] {
width: 21rem;
height: 21rem;
color: white;
background-color: #5f6368;
line-height: 21rem;
padding: 2.5rem 2rem 1.5rem 2rem;
text-align: center;
font-size: 3.2rem;
font-weight: bolder;
overflow: hidden;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].focused {
transform: scale(1.2);
color: #2d2d2d;
background-color: white;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].pressed {
transform: scale(1);
color: white;
background-color: #00caf2;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].primary {
background-color: #00caf2;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].primary.focused {
color: #ffffff;
background-color: #00caf2;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].primary.pressed {
background-color: #0097b5;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].danger {
background-color: #e63d2f;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].danger.focused {
color: #ffffff;
background-color: #e63d2f;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].danger.pressed {
background-color: #ac2e23;
transition-property: transform, color, background-color;
}
/**
* End of circle text button
*/
/**
* rounded-corner text button
*/
smart-button[type="rounded-corner-text"] {
-moz-user-select: none;
width: auto;
height: 8rem;
border-radius: 4rem;
background-color: #5f6368;
padding: 0 3.8rem;
line-height: 8rem;
color: white;
text-align: center;
font-size: 2.4rem;
overflow: hidden;
transition-property: transform, color, background-color;
}
smart-button[type="rounded-corner-text"].focused,
smart-button[type="rounded-corner-text"]:hover {
transform: scale(1.2);
color: #2d2d2d;
background-color: white;
}
smart-button[type="rounded-corner-text"].pressed {
transform: scale(1);
/*color: white;*/
background-color: #00caf2;
}
smart-button[type="rounded-corner-text"].primary {
background-color: #00caf2;
}
smart-button[type="rounded-corner-text"].primary:hover,
smart-button[type="rounded-corner-text"].primary.focused {
color: #ffffff;
background-color: #00caf2;
}
smart-button[type="rounded-corner-text"].primary.pressed {
background-color: #0097b5;
}
/**
* End of rounded-corner text button
*/

View file

@ -0,0 +1,25 @@
{
"name": "smart-dialog",
"homepage": "https://github.com/smart-components/smart-dialog",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md"
],
"_release": "8910d99b8f",
"_resolution": {
"type": "branch",
"branch": "master",
"commit": "8910d99b8fb7c624b32ac494fea63c0898e886df"
},
"_source": "git://github.com/smart-components/smart-dialog.git",
"_target": "*",
"_originalSource": "smart-components/smart-dialog"
}

View file

@ -0,0 +1,17 @@
{
"name": "smart-dialog",
"version": "0.0.0",
"homepage": "https://github.com/smart-components/smart-dialog",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md"
]
}

View file

@ -0,0 +1,123 @@
/**
* Smart-Dialog
* - Event:
* 1. will-open
* 2. opened
* 3. will-close
* 4. closed
* - API:
* 1. open - open a smart-dialog: closed -> opening -> opened
* 2. close - close a smart-dialog: opened -> closing -> closed
* - Attribute:
* 1. title - title of the smart-dialog
* 2. esc-close - close dialog when ESC key is clicked (default true).
* Default scale: 1920x1080 (2*sqrt(960*960 + 1080*1080) / 20 = 144.5)
*/
window.SmartDialog = (function(win) {
'use strict';
// The value of openDuration and closeDuration should be the same as
// transitionDuration in opening and closing css class.
const openDuration = 1000;
const closeDuration = 650;
// Extend from the HTMLElement prototype
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function sd_createdCallback() {
this.addEventListener('transitionend', this);
document.addEventListener('keyup', this);
this.transitionTimer = null;
this.classList.add('closed');
if (this.hasAttribute('background-color')) {
this.style.backgroundColor = this.getAttribute('background-color');
}
};
proto._onOpened = function sd_onOpened() {
this.classList.remove('opening');
this.classList.add('opened');
this.fireEvent('opened');
};
proto._onClosed = function sd_onClosed() {
this.classList.remove('closing');
this.classList.add('closed');
this.fireEvent('closed');
};
proto.open = function sd_open() {
if (this.classList.contains('opened') ||
this.classList.contains('opening')) {
return;
}
this.fireEvent('will-open');
this.classList.remove('closing');
this.classList.remove('closed');
this.classList.add('opening');
// handle the case when transitionend event is not fired
clearTimeout(this.transitionTimer);
this.transitionTimer = setTimeout(function() {
this._onOpened();
this.transitionTimer = null;
}.bind(this), openDuration);
};
proto.close = function sd_close() {
if (this.classList.contains('closed') ||
this.classList.contains('closing')) {
return;
}
this.fireEvent('will-close');
this.classList.remove('opening');
this.classList.remove('opened');
this.classList.add('closing');
// handle the case when transitionend event is not fired
clearTimeout(this.transitionTimer);
this.transitionTimer = setTimeout(function() {
this._onClosed();
this.transitionTimer = null;
}.bind(this), closeDuration);
};
proto.handleEvent = function sd_handleEvent(evt) {
switch(evt.type) {
case 'transitionend':
if (evt.target === this && this.transitionTimer) {
if (this.classList.contains('opening')) {
this._onOpened();
} else if (this.classList.contains('closing')) {
this._onClosed();
}
clearTimeout(this.transitionTimer);
this.transitionTimer = null;
}
break;
case 'keyup':
// close dialog when Back is triggered
if (this.getAttribute('esc-close') != 'false' &&
(evt.keyCode === window.KeyEvent.DOM_VK_BACK_SPACE ||
evt.key === 'Backspace') &&
this.classList.contains('opened')) {
evt.preventDefault();
evt.stopPropagation();
this.close();
}
break;
}
};
proto.fireEvent = function sd_fireEvent(event, detail) {
var evtObject = new CustomEvent(event, {
bubbles: false,
detail: detail || this
});
this.dispatchEvent(evtObject);
};
// Register and return the constructor
return document.registerElement('smart-dialog', { prototype: proto });
})(window);

View file

@ -0,0 +1,103 @@
/**
* Smart-Dialog
* - State:
* 1. closed
* 2. opening
* 3. opened - content visible
* 4. closing
* Default scale: 1920x1080 (2*sqrt(960*960 + 1080*1080) / 20 = 144.5)
*/
smart-dialog {
position: fixed;
z-index: 2147483647;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
smart-dialog:before {
content: attr(title);
display: block;
margin-top: 5rem;
margin-left: 7.6rem;
font-family: "Fira Sans";
font-size: 5.4rem;
font-style: italic;
color: #ffffff;
}
smart-dialog:after {
content: "";
display: block;
position: fixed;
height: 2rem;
width: 2rem;
top: calc(100% - 1rem);
left: calc(50% - 1rem);
background-color: rgba(0, 0, 0, 0.9);
transition: none;
transform: scale(20);
}
smart-dialog.closed > *,
smart-dialog.closing > *,
smart-dialog.opening > *,
smart-dialog.closed:before,
smart-dialog.closing:before,
smart-dialog.opening:before {
display: none;
}
smart-dialog.closed {
visibility: hidden;
}
smart-dialog.opened {
background-color: rgba(0, 0, 0, 0.9);
}
smart-dialog.opened:after {
visibility: hidden;
border-radius: 50%;
transform: scale(144.5);
}
smart-dialog.opening:after {
border-radius: 50%;
transform: scale(144.5);
/* transitionDuration should be the same as openDuration variable in script */
transition: transform 1s cubic-bezier(0, 0, 0 ,1);
}
smart-dialog.closing:after {
border-radius: 50%;
transform: scale(20);
/* transitionDuration should be the same as closeDuration variable in script */
transition: transform 0.65s cubic-bezier(0.6, 0, 1 ,1);
}
/* 1280x800 for flatfish */
@media (width: 1280px) and (height: 800px) {
smart-dialog:after,
smart-dialog.closing:after {
transform: scale(7.5);
}
smart-dialog.opening:after {
transform: scale(102.5);
}
}
/* 800x1280 for flatfish vertical */
@media (width: 800px) and (height: 1280px) {
smart-dialog:after,
smart-dialog.closing:after {
transform: scale(8.3);
}
smart-dialog.opening:after {
transform: scale(134);
}
}

View file

@ -0,0 +1,31 @@
{
"name": "smart-modal-dialog",
"homepage": "https://github.com/smart-components/smart-modal-dialog",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md"
],
"dependencies": {
"smart-dialog": "smart-components/smart-dialog",
"smart-bubbles": "smart-components/smart-bubbles",
"smart-button": "smart-components/smart-button",
"smart-icons": "smart-components/smart-icons"
},
"_release": "3b62d9317c",
"_resolution": {
"type": "branch",
"branch": "master",
"commit": "3b62d9317cdd42e3e9604a5e6a59ce1edbff81cf"
},
"_source": "git://github.com/smart-components/smart-modal-dialog.git",
"_target": "*",
"_originalSource": "smart-components/smart-modal-dialog"
}

View file

@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
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/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

View file

@ -0,0 +1,122 @@
/**
* BaseModalDialog creates a dialog and handles esc key to close it. It also
* includes open/close animation.
* The template is shown below:
* <smart-dialog class="modal-dialog">
* <div class="outer-container">
* <div class="container"></div>
* </div>
* </smart-dialog>
*/
(function(exports) {
'use strict';
function BaseModalDialog(container) {
// Determine whether this dialog is opened or not
this.isOpened = false;
// smart-dialog helps us to handle ESC key to close the dialog.
this.element = document.createElement('smart-dialog');
this.element.classList.add('modal-dialog');
this.element.setAttribute('role', 'dialog');
// make dialog focusable in order to catch focus from mouse click or touch
// event.
this.element.setAttribute('tabIndex', '-1');
// in order to make vertical align, we need additional container
this.outerContainer = document.createElement('div');
this.outerContainer.classList.add('outer-container');
this.innerContainer = document.createElement('div');
this.innerContainer.classList.add('container');
this.outerContainer.appendChild(this.innerContainer);
this.element.appendChild(this.outerContainer);
this.container = container || document.body;
this.container.appendChild(this.element);
this._init();
}
var proto = Object.create(SmartDialog.prototype);
proto._render = function() {
// implement rendering here.
};
proto._init = function(container, options) {
// Put your custom init code here.
};
proto._setL10n = function(element, l10n) {
if ((typeof l10n) === 'string') {
element.setAttribute('data-l10n-id', l10n);
} else if (navigator.mozL10n) {
navigator.mozL10n.setAttributes(element, l10n.id, l10n.args);
}
};
proto._open = function(options) {
// If you want to override this, please call this at function at the end of
// yours.
// onCancel is triggered when ESC key is pressed.
this.onCancel = options.onCancel || function() {};
this.element.classList.add('visible');
this.element.open();
this.element.focus();
};
proto.open = function(options) {
if (this.isOpened) {
return;
}
this.isOpened = true;
// We should wait two frames for reflow.
window.requestAnimationFrame(function() {
window.requestAnimationFrame(this._open.bind(this, options));
}.bind(this));
};
proto.close = function() {
this.element.close();
this.element.focus();
};
proto.remove = function() {
this.container.removeChild(this.element);
};
proto._focusContent = function() {};
proto._blurContent = function() {};
proto.focus = function() {
if (this.element.classList.contains('opening') ||
this.element.classList.contains('closing')) {
this.element.focus();
} else {
this._focusContent();
}
};
proto.blur = function() {
this._blurContent();
};
proto.fireEvent = function smd_fireEvent(event, detail) {
var evtObject = new CustomEvent(event, {
bubbles: true,
detail: detail || this
});
this.container.dispatchEvent(evtObject);
};
BaseModalDialog.prototype = proto;
exports.BaseModalDialog = BaseModalDialog;
})(window);

View file

@ -0,0 +1,23 @@
{
"name": "smart-modal-dialog",
"version": "0.0.0",
"homepage": "https://github.com/smart-components/smart-modal-dialog",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md"
],
"dependencies": {
"smart-dialog": "smart-components/smart-dialog",
"smart-bubbles": "smart-components/smart-bubbles",
"smart-button": "smart-components/smart-button",
"smart-icons": "smart-components/smart-icons"
}
}

View file

@ -0,0 +1,60 @@
.multiple-value-selector .selector-list-container {
display: inline-block;
margin-left: 23.8rem;
margin-right: 15rem;
max-height: 70.8rem;
overflow-y: hidden;
/* 67.2rem - 23.8rem - 15rem */
width: calc(100% - 106rem);
padding: 0;
vertical-align: middle;
scroll-behavior: smooth;
outline: none;
}
.multiple-value-selector .modal-dialog-button-group {
margin-right: 20.8rem;
width: 46.4rem;
vertical-align: middle;
}
.multiple-value-selector .selector-list-item {
display: block;
/* substract 9.4rem for tick icon */
width: calc(100% - 9.4rem);
height: 13.6rem;
font-family: "Fira Sans";
font-size: 4.4rem;
font-style: italic;
color: #ffffff;
text-align: center;
line-height: 13.6rem;
transition: color 0.2s ease-in-out, transform 0.2s ease-in-out;
position: relative;
/* refer https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */
font-weight: 100;
}
.multiple-value-selector .selector-list-item.checked:after {
position: absolute;
right: 0;
font-family: 'smart-icons';
content: 'check';
display: inline-block;
font-weight: 500;
font-style: normal;
text-decoration: inherit;
text-transform: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
.multiple-value-selector .selector-list-item.focused {
color: #00caf2;
transform: scale(1.2);
}
.multiple-value-selector .selector-list-item.focused.checked:after {
color: #00caf2;
transform: scale(1);
}

View file

@ -0,0 +1,343 @@
/**
* MultipleValueSelector creates a list of options, and a dialog with ok/cancel
* buttons.
* The template is shown below:
* <smart-dialog class="modal-dialog multiple-value-selector">
* <div class="outer-container">
* <div class="container">
* <ul class="selector-list-container">
* <li class="selector-list-item checked">Item 1</li>
* <li class="selector-list-item focused">Item 2</li>
* <li class="selector-list-item">Item 3</li>
* </ul>
* <div class="modal-dialog-button-group">
* <smart-button></smart-button>
* ...
* <smart-button></smart-button>
* </div>
* </div>
* </div>
* </smart-dialog>
*
* options for open method:
* {
* list: [... array of items ...]
* buttonSettings: { // if we don't have button settings, we use Ok and
* l10n: { // Cancel as text content.
* ok: 'l10n-ok',
* cancel: 'l10n-cancel'
* }
* },
* onSelected: function() {} // called while user press ok
* onCancel: function() {} // called while user press esc or cancel
* }
*
* list item:
* {
* checked: false, // if checked by default, default value is false.
* textRaw: 'raw text',
* textL10nId: 'text or object for l10n',
* defaultFocus: true // the default focus item
* }
*
*/
(function(exports) {
'use strict';
var NORMAL_LIST_ITEM_SIZE = 136;
var SELECTED_LIST_ITEM_SIZE = 164;
function MultipleValueSelector(container) {
// call constructor of smart modal dialog.
BaseModalDialog.call(this, container);
}
// extend from smart modal dialog
var proto = Object.create(BaseModalDialog.prototype);
var NAVIGATION_INDEX = Object.freeze({
'LIST': 0,
'CANCEL': 1,
'OK': 2
});
proto._init = function() {
this._selectedItems = [];
this._clickedButton = null;
// listContainer has scroll bar, value selector will calculate the
// scroll top while focus it.
this.listContainer = document.createElement('ul');
this.listContainer.setAttribute('role', 'list');
this.listContainer.tabIndex = 0;
this.listContainer.classList.add('selector-list-container');
this.innerContainer.appendChild(this.listContainer);
// render button group
this.buttonGroup = document.createElement('div');
this.buttonGroup.classList.add('modal-dialog-button-group');
this.buttonGroup.setAttribute('smart-bubbles', 'true');
this.innerContainer.appendChild(this.buttonGroup);
this.smartBubble = document.createElement('smart-bubbles');
this.element.classList.add('multiple-value-selector');
this._initEventListeners();
this._listFocus = 0;
this._moduleFocus = NAVIGATION_INDEX.LIST;
};
proto._initEventListeners = function() {
this.element.addEventListener('will-open', this);
this.element.addEventListener('will-close', this);
this.element.addEventListener('opened', this);
this.element.addEventListener('closed', this);
this.smartBubble.addEventListener('all-items-bubbled', this);
};
proto._render = function() {
this._renderList();
this._renderButtons();
};
proto._renderList = function() {
this.listContainer.innerHTML = '';
this._options.list.forEach(function(item, index) {
var li = document.createElement('li');
li.setAttribute('role', 'listitem');
li.classList.add('selector-list-item');
if (item.checked) {
li.classList.add('checked');
}
if (item.textRaw) {
li.textContent = item.textRaw;
} else if (item.textL10nId) {
this._setL10n(li, item.textL10nId);
}
if (item.defaultFocus) {
this._defaultListFocus = index;
}
this.listContainer.appendChild(li);
}.bind(this));
};
proto._renderButtons = function() {
this.buttonGroup.innerHTML = '';
var buttonSettings = this._options.buttonSettings;
var okButton = document.createElement('smart-button');
okButton.setAttribute('type', 'circle-text');
if (buttonSettings && buttonSettings.l10n && buttonSettings.l10n.ok) {
this._setL10n(okButton, buttonSettings.l10n.ok);
} else {
okButton.textContent = 'Ok';
}
okButton.classList.add('primary');
okButton.addEventListener('click', this);
var cancelButton = document.createElement('smart-button');
cancelButton.setAttribute('type', 'circle-text');
if (buttonSettings && buttonSettings.l10n && buttonSettings.l10n.cancel) {
this._setL10n(cancelButton, buttonSettings.l10n.cancel);
} else {
cancelButton.textContent = 'cancel';
}
cancelButton.addEventListener('click', this);
this.buttonGroup.appendChild(cancelButton);
this.buttonGroup.appendChild(okButton);
this.okButton = okButton;
this.cancelButton = cancelButton;
};
proto.handleEvent = function (e) {
if (e.type === 'keydown') {
this._handleKeyDownEvent(e);
} else if (e.type === 'keyup') {
this._handleKeyUpEvent(e);
} else if (e.type === 'click') {
this._clickedButton = e.target;
this.element.close();
this.element.focus();
} else {
switch(e.target) {
case this.element:
if (e.type === 'will-open' || e.type === 'will-close') {
this.fireEvent('modal-dialog-' + e.type);
} else if (e.type === 'opened') {
this.scrollToIndex(this._listFocus);
// Play bubble animation when the smart-dialog is opened
this.smartBubble.play([this.cancelButton, this.okButton]);
} else if (e.type === 'closed') {
// Do click actions when the smart-dialog is closed
this.element.classList.remove('visible');
if (this._clickedButton === this.okButton && this._onSelected) {
this._onSelected(this._selectedItems);
} else if (this.onCancel) {
// ESC key enter
this.onCancel();
}
this.isOpened = false;
this.stopKeyNavigation();
this.element.classList.remove('modal-dialog-opened');
this.fireEvent('modal-dialog-closed');
}
break;
case this.smartBubble:
// all items bubbled
this.element.classList.add('modal-dialog-opened');
this.startKeyNavigation();
this.focus();
this.fireEvent('modal-dialog-opened');
break;
}
}
};
proto.startKeyNavigation = function() {
this.element.addEventListener('keydown', this);
this.element.addEventListener('keyup', this);
this.focus();
};
proto.stopKeyNavigation = function() {
this.element.removeEventListener('keyup', this);
};
proto._handleKeyDownEvent = function(e) {
if (e.keyCode === KeyEvent.DOM_VK_UP) {
this.moveUp(e);
} else if (e.keyCode === KeyEvent.DOM_VK_DOWN) {
this.moveDown(e);
} else if (e.keyCode === KeyEvent.DOM_VK_LEFT) {
this.moveLeft(e);
} else if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
this.moveRight(e);
} else if (e.keyCode == KeyEvent.DOM_VK_TAB) {
e.preventDefault();
if (this._moduleFocus === 2) {
this._moduleFocus = 0;
} else {
this._moduleFocus = this._moduleFocus === 2 ? 0 : this._moduleFocus + 1;
}
this.focus();
}
};
proto._handleKeyUpEvent = function(e) {
if (e.keyCode === KeyEvent.DOM_VK_RETURN && this._focusedListItem) {
this._focusedListItem.classList.toggle('checked');
if (this._focusedListItem.classList.contains('checked')) {
this._selectedItems.push(this._itemList[this._listFocus]);
} else {
var idx = this._selectedItems.indexOf(this._itemList[this._listFocus]);
if (idx > -1) {
this._selectedItems.splice(idx, 1);
}
}
}
};
// handle list focus
proto.moveUp = function(e) {
if (this._moduleFocus !== NAVIGATION_INDEX.LIST ||
this._listFocus < 1) {
return;
}
this._listFocus--;
this.focus();
e.preventDefault();
};
proto.moveDown = function(e) {
if (this._moduleFocus !== NAVIGATION_INDEX.LIST ||
this._listFocus > this._itemList.length - 2) {
return;
}
this._listFocus++;
this.focus();
e.preventDefault();
};
// handle module focus
proto.moveLeft = function(e) {
if (this._moduleFocus < 1) {
return;
}
this._moduleFocus--;
this.focus();
};
proto.moveRight = function(e) {
// we only have three modules: list, cancel, and ok
if (this._moduleFocus > 1) {
// Do nothing when focus index is at the last element
return;
}
this._moduleFocus++;
this.focus();
};
proto.scrollToIndex = function(index) {
var top = this.listContainer.scrollTop;
var bottom = top + this.listContainer.clientHeight;
var itemTop = NORMAL_LIST_ITEM_SIZE * index;
var itemBottom = itemTop + SELECTED_LIST_ITEM_SIZE;
if (top > itemTop) {
// item is at top
this.listContainer.scrollTop = itemTop;
} else if (bottom < itemBottom) {
// item is at bottom
this.listContainer.scrollTop += (itemBottom - bottom);
}
};
proto._focusContent = function() {
if (this._focusedListItem) {
this._focusedListItem.classList.remove('focused');
this._focusedListItem = null;
this.listContainer.classList.remove('focused');
}
switch(this._moduleFocus) {
case NAVIGATION_INDEX.LIST:
this._focusedListItem = this.listContainer.children[this._listFocus];
this._focusedListItem.classList.add('focused');
this.listContainer.classList.add('focused');
this.scrollToIndex(this._listFocus);
this.listContainer.focus();
break;
case NAVIGATION_INDEX.CANCEL:
this.cancelButton.focus();
break;
case NAVIGATION_INDEX.OK:
this.okButton.focus();
break;
}
};
proto._open = function(options) {
this._options = options;
this._itemList = options.list;
this._render();
this._listFocus = this._defaultListFocus || 0;
this._moduleFocus = NAVIGATION_INDEX.LIST;
this._onSelected = this._options.onSelected;
BaseModalDialog.prototype._open.call(this, options);
};
MultipleValueSelector.prototype = proto;
exports.MultipleValueSelector = MultipleValueSelector;
})(window);

View file

@ -0,0 +1,276 @@
/**
* SmartInputDialog creates an input text box with a clear button to clear text,
* and a dialog with a group of buttons as select options, Text box includes
* focus/blur animation, buttons includes open/close and bubbling animation.
* The template is shown below:
* <smart-dialog class="modal-dialog">
* <div class="outer-container">
* <div class="container">
* <div class="modal-dialog-message-container">
* <div class="modal-dialog-message"></div>
* <div class="modal-dialog-custom-group"></div>
* <div class="modal-dialog-input-group">
* <input></input>
* <button class="clear"></button>
* <div class="focus"></div>
* </div>
* </div>
* <div class="modal-dialog-button-group">
* <smart-button></smart-button>
* ...
* <smart-button></smart-button>
* </div>
* </div>
* </div>
* </smart-dialog>
*/
(function(exports) {
'use strict';
var DEFAULT_MARGIN = 44;
var INPUT_GROUP_INDEX = 0;
var BUTTON_GROUP_INDEX = 1;
function SmartInputDialog(container, options) {
SmartModalDialog.call(this, container, options);
}
var proto = Object.create(SmartModalDialog.prototype);
proto._render = function() {
SmartModalDialog.prototype._render.call(this);
this.textInput = document.createElement('input');
this.textInput.addEventListener('keydown', function(e) {
if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
if (this.selectionEnd < this.value.length) {
// We are going to move cursor in the text input, calling
// stopPropagation to prevent focus being transfer to clear button.
e.stopPropagation();
}
}
});
this.clearButton = document.createElement('button');
this.clearButton.setAttribute('data-icon', 'close');
this.clearButton.classList.add('clear');
this.clearButton.addEventListener('click', function() {
this.textInput.value = '';
this.textInput.focus();
}.bind(this));
this.focusBar = document.createElement('div');
this.focusBar.classList.add('focus');
this.smartInputGroup = document.createElement('div');
this.smartInputGroup.classList.add('modal-dialog-input-group');
this.smartInputGroup.appendChild(this.textInput);
this.smartInputGroup.appendChild(this.clearButton);
this.smartInputGroup.appendChild(this.focusBar);
this.messageContainer.appendChild(this.smartInputGroup);
};
proto._init = function() {
this.defaultFocusIndex = [-1, -1];
SmartModalDialog.prototype._init.call(this);
};
proto._createMessageGroup = function(options) {
this.message = options.message || {};
if (!this.message.textRaw && !this.message.textL10nId) {
this.messageElement.classList.add('hidden');
} else {
this.messageElement.classList.remove('hidden');
this.messageElement.textContent = this.message.textRaw || '';
if (this.message.textL10nId) {
this.messageElement.setAttribute('data-l10n-id',
this.message.textL10nId);
}
}
};
proto._createCustomGroup = function(options) {
this.customElements = [];
this.customElementOffset = 0;
this.customSettings = options.customElementSettings || null;
if (this.customSettings) {
while (this.customElementGroup.firstChild) {
this.customElementGroup.removeChild(this.customElementGroup.firstChild);
}
this.customElementGroup.appendChild(this.customSettings.element);
this.customElementGroup.classList.remove('hidden');
} else {
this.customElementGroup.classList.add('hidden');
}
};
proto._createInputGroup = function(options) {
this.inputElements = [this.textInput, this.clearButton];
if (options.initialInputValue) {
this.textInput.value = options.initialInputValue;
}
var retrunedCallback = options.onReturned;
if (retrunedCallback) {
this.textInput.addEventListener('keydown', function(evt) {
if (evt.keyCode === KeyEvent.DOM_VK_RETURN &&
this._getFocusedElement() !== this.clearButton) {
retrunedCallback();
}
}.bind(this));
}
};
proto._createButtonGroup = function(options) {
this.buttonElements = [];
// onCancel is triggered when ESC key is pressed.
this.onCancel = options.onCancel || function() {};
this.buttonElements = [];
this.buttonSettings = options.buttonSettings || [];
this.buttonGroup.innerHTML = '';
this._focusedIndex[BUTTON_GROUP_INDEX] = 0;
// Set up every button
this.buttonSettings.forEach(function buildButton(buttonSetting, index) {
var button = document.createElement('smart-button');
button.setAttribute('type', buttonSetting.type || 'circle-text');
if (buttonSetting.textL10nId) {
button.setAttribute('data-l10n-id', buttonSetting.textL10nId);
}
button.textContent = buttonSetting.textRaw || 'OK';
button.classList.add(buttonSetting.class || 'confirm');
if (buttonSetting.icon) {
button.style.backgroundImage = 'url(' + buttonSetting.icon + ')';
} else if (buttonSetting.iconFont) {
button.dataset.icon = buttonSetting.iconFont;
}
button.addEventListener('click', function() {
// Click action will be handled in closed event
this._clickedIndex = index;
this.element.close();
this.element.focus();
}.bind(this));
this.buttonElements.push(button);
this.buttonGroup.appendChild(button);
var customFocus = this.customSettings && this.customSettings.defaultFocus;
if (buttonSetting.defaultFocus && !customFocus) {
this._focusedIndex[BUTTON_GROUP_INDEX] = index;
}
var renderedCallback = options.onButtonRendered;
if (renderedCallback) {
// We add render callback to let user to do more complex visual
// modifications.
renderedCallback(button, buttonSetting);
}
}.bind(this));
};
proto._open = function(options) {
this._focusedIndex = this.defaultFocusIndex;
this._createMessageGroup(options);
this._createCustomGroup(options);
this._createInputGroup(options);
this._createButtonGroup(options);
this._focusedGroupIndex = 0;
this._focusedGroupIndex[INPUT_GROUP_INDEX] = 0;
// Put focusable element groups into verticalGroup,
// so we can simply navigate up/down among these groups.
this.verticalGroup = [this.inputElements, this.buttonElements];
this.element.classList.add('visible');
this.element.open();
this.element.focus();
};
proto.startKeyNavigation = function() {
this.element.addEventListener('keydown', this);
if (this._focusedIndex[0] === -1) {
this._focusedIndex[0] = 0;
}
this.focus();
};
proto._getFocusedElement = function() {
return this.verticalGroup[this._focusedGroupIndex]
[this._focusedIndex[this._focusedGroupIndex]];
};
proto.moveUp = function(e) {
if (this._focusedGroupIndex < 1) {
return;
}
this._focusedGroupIndex--;
this.focus();
};
proto.moveDown = function(e) {
if (this._focusedGroupIndex > this.verticalGroup.length - 1) {
return;
}
// Move input-group's focus back to input box when focus on button-group
this._focusedIndex[INPUT_GROUP_INDEX] = 0;
this._focusedGroupIndex++;
this.focus();
};
proto.movePrevious = function(e) {
if (this._focusedIndex[this._focusedGroupIndex] < 1) {
// Do nothing when focus index is at the first element.
return;
}
this._focusedIndex[this._focusedGroupIndex]--;
if (this._focusedGroupIndex === INPUT_GROUP_INDEX) {
// Moving from clean button to input text will move input caret position
// back one character, preventDefault so we don't change the position.
e.preventDefault();
}
this.focus();
};
proto.moveNext = function(e) {
if (this._focusedIndex[this._focusedGroupIndex] >
this.verticalGroup[this._focusedGroupIndex].length - 2) {
// Do nothing when focus index is at the last element
return;
}
this._focusedIndex[this._focusedGroupIndex]++;
this.focus();
};
proto._handleKeyEvent = function(e) {
if (e.keyCode === KeyEvent.DOM_VK_UP) {
this.moveUp(e);
} else if (e.keyCode === KeyEvent.DOM_VK_DOWN) {
this.moveDown(e);
} else if (e.keyCode === KeyEvent.DOM_VK_LEFT) {
this.movePrevious(e);
} else if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
this.moveNext(e);
} else if (e.keyCode == KeyEvent.DOM_VK_TAB) {
e.preventDefault();
if (this._focusedGroupIndex === INPUT_GROUP_INDEX) {
this.moveDown(e);
} else {
this.moveUp(e);
}
}
};
SmartInputDialog.prototype = proto;
exports.SmartInputDialog = SmartInputDialog;
})(window);

View file

@ -0,0 +1,293 @@
/**
* SmartModalDialog creates a dialog with a group of buttons as select
* options. It also includes open/close animation and bubbling animation.
* The template is shown below:
* <smart-dialog class="modal-dialog">
* <div class="outer-container">
* <div class="container">
* <div class="modal-dialog-message-container">
* <div class="modal-dialog-message"></div>
* <div class="modal-dialog-custom-group"></div>
* </div>
* <div class="modal-dialog-button-group">
* <smart-button></smart-button>
* ...
* <smart-button></smart-button>
* </div>
* </div>
* </div>
* </smart-dialog>
*/
(function(exports) {
'use strict';
var DEFAULT_MARGIN = 44;
function SmartModalDialog(container, options) {
// Determine whether this dialog is opened or not
this._clickedIndex = null;
this.defaultFocusIndex = -1;
this._margin = options && options.margin ? options.margin : DEFAULT_MARGIN;
this._translateX = 0;
BaseModalDialog.call(this, container);
}
var proto = Object.create(BaseModalDialog.prototype);
proto._init = function() {
this._render();
this.smartBubble = document.createElement('smart-bubbles');
this.element.addEventListener('will-open', this);
this.element.addEventListener('will-close', this);
this.element.addEventListener('opened', this);
this.element.addEventListener('closed', this);
this.smartBubble.addEventListener('all-items-bubbled', this);
};
proto._render = function() {
this.messageElement = document.createElement('div');
this.messageElement.classList.add('modal-dialog-message');
this.customElementGroup = document.createElement('div');
this.customElementGroup.classList.add('modal-dialog-custom-group');
this.messageContainer = document.createElement('div');
this.messageContainer.classList.add('modal-dialog-message-container');
this.messageContainer.appendChild(this.messageElement);
this.messageContainer.appendChild(this.customElementGroup);
this.buttonGroup = document.createElement('div');
this.buttonGroup.classList.add('modal-dialog-button-group');
this.buttonGroup.setAttribute('smart-bubbles', 'true');
this.innerContainer.appendChild(this.messageContainer);
this.innerContainer.appendChild(this.buttonGroup);
};
proto._renderMessage = function(options) {
this.message = options.message || {};
this.customSettings = options.customElementSettings || null;
if (this.customSettings) {
while (this.customElementGroup.firstChild) {
this.customElementGroup.removeChild(this.customElementGroup.firstChild);
}
this.customElementGroup.appendChild(this.customSettings.element);
this.customElementGroup.classList.remove('hidden');
} else {
this.customElementGroup.classList.add('hidden');
}
if (!this.message.textRaw && !this.message.textL10nId) {
this.messageElement.classList.add('hidden');
} else {
this.messageElement.classList.remove('hidden');
this.messageElement.textContent = this.message.textRaw || '';
if (this.message.textL10nId) {
this._setL10n(this.messageElement, this.message.textL10nId);
}
}
};
proto._renderButtons = function(options) {
var renderedCallback = options.onButtonRendered;
this.buttonElements = [];
this.buttonSettings = options.buttonSettings || [];
this.buttonGroup.innerHTML = '';
// Set up every button
this.buttonSettings.forEach(function buildButton(buttonSetting, index) {
var button = document.createElement('smart-button');
button.setAttribute('type', buttonSetting.type || 'circle-text');
button.textContent = buttonSetting.textRaw || 'OK';
if (buttonSetting.textL10nId) {
this._setL10n(button, buttonSetting.textL10nId);
}
button.classList.add(buttonSetting.class || 'confirm');
if (buttonSetting.icon) {
button.style.backgroundImage = 'url(' + buttonSetting.icon + ')';
} else if (buttonSetting.iconFont) {
button.dataset.icon = buttonSetting.iconFont;
}
button.addEventListener('click', function() {
// Click action will be handled in closed event
this._clickedIndex = index;
this.element.close();
this.element.focus();
}.bind(this));
this.buttonElements.push(button);
this.buttonGroup.appendChild(button);
this._focusedIndex =
(buttonSetting.defaultFocus) ? index : this._focusedIndex;
if (renderedCallback) {
// We add render callback to let user to do more complex visual
// modifications.
renderedCallback(button, buttonSetting);
}
}.bind(this));
};
proto._open = function(options) {
this._focusedIndex = this.defaultFocusIndex;
this._renderMessage(options);
this._renderButtons(options);
BaseModalDialog.prototype._open.call(this, options);
};
proto.startKeyNavigation = function() {
this.element.addEventListener('keydown', this);
if (this.buttonElements.length > 0) {
if (this._focusedIndex === -1) {
this._focusedIndex = 0;
}
this.focus();
}
};
/**
* Scroll the list to the input element
*/
proto._scrollTo = function(element) {
var newTransition = this._getScrollOffset(element);
this._translateX = newTransition;
this.buttonGroup.style.transform = 'translateX(' + this._translateX +
'px)';
};
/**
* Get the offset of the node element
*/
proto._getScrollOffset = function(nodeElem) {
if (!this._viewWidth) {
this._viewWidth = this.innerContainer.clientWidth - 2 * this._margin;
}
var nodeLeft = nodeElem.offsetLeft;
var nodeWidth = nodeElem.offsetWidth;
var listWidth = this.buttonGroup.offsetWidth;
var newTranslate = this._translateX;
var lastElement = this.buttonElements[this.buttonElements.length - 1];
var firstElement = this.buttonElements[0];
if (listWidth < this._viewWidth) {
// align to horizontal center if list width is smaller than the container
return (this._viewWidth - listWidth) / 2 + this._margin;
} else if (nodeLeft + nodeWidth >
-this._translateX + this._viewWidth + this._margin) {
// scroll left if the node falls beyond the right edge of container
newTranslate = this._viewWidth - nodeLeft - nodeWidth + this._margin;
} else if (nodeLeft < -this._translateX + this._margin) {
// scroll right if the node falls beyond the left edge of container
newTranslate = -nodeLeft + this._margin;
}
// If the new scroll offset contains first/last node, we have to align the
// list to begin/end.
if (lastElement.offsetLeft + lastElement.offsetWidth <=
-newTranslate + this._viewWidth + this._margin) {
return this._viewWidth + this._margin - lastElement.offsetLeft -
lastElement.offsetWidth;
} else if (firstElement.offsetLeft >= -newTranslate + this._margin) {
return -firstElement.offsetLeft + this._margin;
}
return newTranslate;
};
proto._getFocusedElement = function() {
return this.buttonElements[this._focusedIndex];
};
proto._focusContent = function() {
var elem = this._getFocusedElement();
this._scrollTo(elem);
// move focus to smart dialog while transition running
if (elem.focus && (typeof elem.focus) === 'function') {
elem.focus();
}
};
proto._blurContent = function() {
var elem = this._getFocusedElement();
if (elem.blur && (typeof elem.blur === 'function')) {
elem.blur();
}
};
proto.stopKeyNavigation = function() {
this.element.removeEventListener('keydown', this);
};
proto.movePrevious = function() {
if (this._focusedIndex < 1) {
// Do nothing when focus index is at the first button element.
return;
}
this._focusedIndex--;
this.focus();
};
proto.moveNext = function() {
if (this._focusedIndex > this.buttonElements.length - 2) {
// Do nothing when focus index is at the last button element
return;
}
this._focusedIndex++;
this.focus();
};
proto._handleKeyEvent = function(e) {
if (e.keyCode === KeyEvent.DOM_VK_LEFT) {
this.movePrevious();
} else if (e.keyCode === KeyEvent.DOM_VK_RIGHT) {
this.moveNext();
}
};
proto.handleEvent = function(e) {
this._handleKeyEvent(e);
switch(e.target) {
case this.element:
if (e.type === 'will-open' || e.type === 'will-close') {
this.fireEvent('modal-dialog-' + e.type);
} else if (e.type === 'opened') {
this._scrollTo(this.buttonElements[0]);
// Play bubble animation when the smart-dialog is opened
this.smartBubble.play(this.buttonElements);
} else if (e.type === 'closed') {
// Do click actions when the smart-dialog is closed
this.element.classList.remove('visible');
if ((this._clickedIndex || this._clickedIndex === 0) &&
this.buttonSettings[this._clickedIndex].onClick) {
this.buttonSettings[this._clickedIndex].onClick(
this.buttonSettings[this._clickedIndex]);
this._clickedIndex = null;
} else if (this.onCancel) {
// ESC key enter
this.onCancel();
}
this.isOpened = false;
this.stopKeyNavigation();
this.element.classList.remove('modal-dialog-opened');
this.fireEvent('modal-dialog-closed');
}
break;
case this.smartBubble:
this.element.classList.add('modal-dialog-opened');
this.startKeyNavigation();
this.focus();
this.fireEvent('modal-dialog-opened');
break;
}
};
SmartModalDialog.prototype = proto;
exports.SmartModalDialog = SmartModalDialog;
})(window);

View file

@ -0,0 +1,124 @@
.modal-dialog > .outer-container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
}
.modal-dialog > .outer-container:before {
content: "";
display: inline-block;
height: 100%;
width: 0.1rem;
margin-left: -0.1rem;
vertical-align: middle;
}
.modal-dialog .container {
width: 100%;
vertical-align: middle;
display: inline-block;
}
.modal-dialog .modal-dialog-message-container {
width: 96rem;
position: relative;
margin: 0 auto 13rem auto;
word-wrap: break-word;
}
.modal-dialog .modal-dialog-custom-group {
color: #ffffff;
text-align: center;
font-family: Fira Sans;
}
.modal-dialog .modal-dialog-custom-group.hidden {
display: none;
}
.modal-dialog-input-group {
text-align: center;
position: relative;
margin: 0 auto;
width: 70rem;
}
.modal-dialog-input-group input {
font-size: 3.8rem;
font-size: 3.8rem;
color: #ffffff;
text-align: center;
background: none;
border: none;
}
.modal-dialog-input-group .focus {
bottom: 0px;
width: 100%;
height: 0.4rem;
margin: 0.4rem auto 0 auto;
visibility: hidden;
background: #00caf2;
}
.modal-dialog-input-group .clear:before {
vertical-align: top;
line-height: 3rem;
}
.modal-dialog-input-group .clear {
position: absolute;
left: calc(100% - 4rem);
border: 0;
margin: 0;
padding: 0;
width: 3rem;
height: 3rem;
border-radius: 50%;
opacity: 0;
color: #ffffff;
cursor: pointer;
background: #999999;
}
.modal-dialog-input-group :focus ~ .focus {
visibility: visible;
}
.modal-dialog-input-group :focus ~ .clear,
.modal-dialog-input-group :focus.clear {
opacity: 1;
}
.modal-dialog .modal-dialog-message {
font-size: 3.8rem;
font-style: italic;
color: #ffffff;
text-align: center;
font-family: Fira Sans;
font-weight: 100;
margin-bottom: 3.4rem;
}
.modal-dialog .modal-dialog-message.hidden {
display: none;
}
.modal-dialog .modal-dialog-button-group {
white-space: nowrap;
/* use inline-block to wrap content */
display: inline-block;
}
.modal-dialog.modal-dialog-opened .modal-dialog-button-group {
transition: transform 0.2s ease;
}
.modal-dialog .modal-dialog-button-group > * {
margin-right: 4.4rem;
}
.modal-dialog .modal-dialog-button-group > *:last-child {
margin-right: 0;
}

View file

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<title>Apps</title>
<!-- stylesheets of building blocks -->
<link rel="stylesheet" type="text/css" href="bower_components/smart-button/style.css">
<link rel="stylesheet" type="text/css" href="bower_components/smart-dialog/style.css">
<link rel="stylesheet" type="text/css" href="bower_components/smart-bubbles/style.css">
<link rel="stylesheet" type="text/css" href="bower_components/smart-modal-dialog/style.css">
<link rel="stylesheet" type="text/css" href="style/app_deck.css">
<link rel="stylesheet" type="text/css" href="style/app_deck_list.css">
<link rel="stylesheet" type="text/css" href="shared/style/smart-screen/navigation.css">
<link rel="stylesheet" type="text/css" href="shared/style/smart-screen/icons.css">
<!-- default cards for first time launch -->
<link rel="resource" type="application/json" href="shared/resources/default-cards.json">
<!-- For test purpose on phone only -->
<script defer src="shared/js/gesture_detector.js"></script>
<script defer src="shared/js/smart-screen/init_gesture.js"></script>
<!-- Shared libraries -->
<script src="shared/js/l10n.js"></script>
<script src="shared/js/manifest_helper.js"></script>
<script defer src="bower_components/evt/index.js"></script>
<script defer src="shared/js/async_semaphore.js"></script>
<script defer src="shared/js/smart-screen/scrollable.js"></script>
<script defer src="shared/js/smart-screen/spatial_navigator.js"></script>
<script defer src="shared/js/smart-screen/key_navigation_adapter.js"></script>
<script defer src="shared/js/smart-screen/shared_utils.js"></script>
<script defer src="shared/js/smart-screen/applications.js"></script>
<script defer src="shared/js/smart-screen/piped_promise.js"></script>
<script defer src="shared/js/smart-screen/card_store.js"></script>
<script defer src="shared/js/smart-screen/cards/card.js"></script>
<script defer src="shared/js/smart-screen/cards/application.js"></script>
<script defer src="shared/js/smart-screen/cards/deck.js"></script>
<script defer src="shared/js/smart-screen/cards/folder.js"></script>
<script defer src="shared/js/smart-screen/cards/app_bookmark.js"></script>
<script defer src="shared/js/smart-screen/card_manager.js"></script>
<script defer src="shared/js/smart-screen/bookmark_manager.js"></script>
<!-- building blocks -->
<script defer src="bower_components/smart-button/script.js"></script>
<script defer src="bower_components/smart-dialog/script.js"></script>
<script defer src="bower_components/smart-bubbles/script.js"></script>
<script defer src="bower_components/smart-modal-dialog/base-modal-dialog.js"></script>
<script defer src="bower_components/smart-modal-dialog/smart-modal-dialog.js"></script>
<!-- Specific code -->
<script defer src="js/promotion_list.js"></script>
<script defer src="js/context_menu.js"></script>
<script defer src="js/app_deck.js"></script>
<!-- L10n properties -->
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="en-US">
<link rel="localization" href="locales/app-deck.{locale}.properties"/>
<link rel="localization" href="shared/locales/date/date.{locale}.properties"/>
</head>
<body>
<article id="main-section" contextmenu="context-menu">
<section role="region" class="skin-dark" id="jumbotron">
<!-- do not add hidden here because we need it visible to probe its size -->
<div id="app-deck-animation-banner" class="app-deck-animation-banner">
<div class="banner-background"></div>
<div class="banner-text-container">
<h1 class="banner-title"></h1>
<div class="banner-description"></div>
</div>
</div>
<div id="app-deck-main-banner" class="app-deck-main-banner">
<div class="banner-background"></div>
<div class="banner-text-container">
<h1 class="banner-title"></h1>
<span class="banner-description"></span>
</div>
</div>
<div id="app-deck-list-frame">
<div id="app-deck-list"></div>
</div>
</section>
<section role="region" class="skin-dark" id="app-deck-grid-view-container">
<div id="app-deck-grid-view"></div>
</section>
<menu type="context" id="context-menu">
<menuitem icon="style/icons/ic_pin.png" data-l10n-id="pin-to-home" id="pin-to-home"></menuitem>
<menuitem icon="style/icons/ic_pin.png" data-l10n-id="unpin-from-home" id="unpin-from-home"></menuitem>
<menuitem icon="style/icons/ic_delete.png" data-l10n-id="remove-card" id="remove-card"></menuitem>
</menu>
</article>
</body>
</html>

View file

@ -0,0 +1,462 @@
/* global SpatialNavigator, SharedUtils, Applications, URL, evt, XScrollable,
KeyNavigationAdapter, ContextMenu, CardManager, PromotionList,
BookmarkManager */
(function(exports) {
'use strict';
const ICON_SIZE = 180 * (window.devicePixelRatio || 1);
const DEFAULT_ICON_PATH = 'style/icons/appic_developer.png';
/**
* @class AppDeck
*
* @requires SpatialNavigator
* @requires SharedUtils
* @requires Applications
* @requires {@link http://bit.ly/1DWJJmF|evt}
* @requires XScrollable
* @requires KeyNavigationAdapter
* @requires {@link ContextMenu}
* @requires CardManager
* @requires {@link PromotionList}
*
* @fires AppDeck#focus
*/
var AppDeck = function() {
};
AppDeck.prototype = evt({
_navigableElements: [],
_spatialNavigator: undefined,
_keyNavigationAdapter: undefined,
_contextMenu: undefined,
_focusElem: undefined,
_appDeckGridViewElem: document.getElementById('app-deck-grid-view'),
_appDeckListScrollable: undefined,
_cardManager: undefined,
_bookmarkManager: undefined,
_outOfControlArea: true,
_disableNavigation: false,
/**
* Initialize AppDeck. This is the main entry of the whole app.
*
* @public
* @method AppDeck#init
*/
init: function ad_init() {
var that = this;
this._keyNavigationAdapter = new KeyNavigationAdapter();
this._keyNavigationAdapter.init();
this._cardManager = new CardManager();
// initialize CardManager in 'readonly' mode. Notice that only smart-home
// could use 'readwrite' mode.
this._cardManager.init('readonly').then(function() {
that._cardManager.on('cardlist-changed',
that.onCardListChanged.bind(that));
});
this._bookmarkManager = BookmarkManager;
this._bookmarkManager.init(null, 'readwrite');
this._bookmarkManager.on('change', that.onBookmarkChanged.bind(that));
// Because module Applications use manifest helper to get localized app
// name. We cannot initialize Applications until l10n is ready.
// See bug 1170083.
var afterApplicationsInit = function() {
var apps = Applications.getAllAppEntries();
var appGridElements = apps.map(that._createAppGridElement.bind(that));
appGridElements.forEach(function(appGridElem) {
that._appDeckGridViewElem.appendChild(appGridElem);
});
// Add Bookmarks
var bookmarkArr = [];
that._bookmarkManager.iterate(function(bookmark) {
bookmarkArr.push(bookmark);
}).then(function() {
bookmarkArr.sort((a, b) => a.date - b.date);
bookmarkArr.forEach(bookmark => {
var elem = that._createBookmarkGridElement(bookmark);
that._appDeckGridViewElem.appendChild(elem);
that._spatialNavigator.add(elem);
});
});
// promotion list must be created before XScrollable because it creates
// elements for XScrollable.
that._promotionList = new PromotionList();
that._promotionList.init();
that._appDeckListScrollable = new XScrollable({
frameElem: 'app-deck-list-frame',
listElem: 'app-deck-list',
itemClassName: 'app-deck-list-item',
margin: 1.4
});
that._navigableElements =
SharedUtils.nodeListToArray(
document.querySelectorAll('.navigable:not(.app-banner)'))
.concat(appGridElements);
that._spatialNavigator = new SpatialNavigator(that._navigableElements);
that._keyNavigationAdapter.on('move', that.onMove.bind(that));
// All behaviors which no need to have multple events while holding the
// key should use keyup.
that._keyNavigationAdapter.on('enter-keyup', that.onEnter.bind(that));
that._spatialNavigator.on('focus', that.onFocus.bind(that));
that._spatialNavigator.on('unfocus', that.onUnfocus.bind(that));
that._appDeckListScrollable.on('focus',
that.onFocusOnAppDeckListScrollable.bind(that));
// start focusing on promotion list
that._appDeckListScrollable.focus();
that._contextMenu = new ContextMenu();
that._contextMenu.init(that);
Applications.on('install', that.onAppInstalled.bind(that));
Applications.on('update', that.onAppUpdated.bind(that));
Applications.on('uninstall', that.onAppUninstalled.bind(that));
};
navigator.mozL10n.once(function() {
Applications.init(afterApplicationsInit);
});
},
/**
* Fills element with icon which is fetched from
* [app](http://mzl.la/1DJP6oZ)
*
* @private
* @method AppDeck#_fillAppButtonIcon
* @param {DOMApplication} app - app instance where we fetch icon from,
* see [here](http://mzl.la/1DJP6oZ)
* @param {SmartButton} elem - [SmartButton](http://bit.ly/1Ld0WYX)
* instance to be filled
*/
_fillAppButtonIcon: function ad_fillAppButtonIcon(app, elem) {
Applications.getIconBlob(app.manifestURL, app.entryPoint, ICON_SIZE,
function(blob) {
var iconURL = blob ? URL.createObjectURL(blob) : DEFAULT_ICON_PATH;
if (elem.dataset.revokableURL) {
// make sure to revoke iconURL once it is no longer needed.
// For example, icon is changed or app is uninstalled
URL.revokeObjectURL(elem.dataset.revokableURL);
}
elem.dataset.revokableURL = blob ? iconURL : undefined;
elem.style.backgroundImage = 'url("' + iconURL + '")';
});
},
/**
* Create SmartButton element based on input [app](http://mzl.la/1DJP6oZ)
* instance
*
* @private
* @method AppDeck#_createAppGridElement
* @param {DOMApplication} app - [app](http://mzl.la/1DJP6oZ|)
* instance
* @return {SmartButton} - see [here](http://bit.ly/1Ld0WYX)
*/
_createAppGridElement: function ad_createAppGridElement(app) {
var appButton = document.createElement('smart-button');
appButton.dataset.manifestURL = app.manifestURL;
appButton.dataset.entryPoint = app.entryPoint;
appButton.dataset.name = app.name;
appButton.dataset.removable = app.removable;
appButton.setAttribute('type', 'app-button');
appButton.setAttribute('app-type', 'app');
appButton.classList.add('app-button');
appButton.classList.add('navigable');
appButton.setAttribute('label', app.name);
this._fillAppButtonIcon(app, appButton);
return appButton;
},
_createBookmarkGridElement: function ad_createBookmarkElement(bookmark) {
var bookmarkButton = document.createElement('smart-button');
bookmarkButton.dataset.url = bookmark.url;
bookmarkButton.dataset.name = bookmark.name;
bookmarkButton.dataset.removable = true;
bookmarkButton.setAttribute('type', 'app-button');
bookmarkButton.setAttribute('app-type', 'bookmark');
bookmarkButton.classList.add('app-button');
bookmarkButton.classList.add('navigable');
bookmarkButton.setAttribute('label', bookmark.name);
if(bookmark.icon) {
var iconURL = URL.createObjectURL(bookmark.icon);
bookmarkButton.dataset.revokableURL = iconURL;
bookmarkButton.style.backgroundImage = 'url("' + iconURL + '")';
}
return bookmarkButton;
},
onCardListChanged: function ad_onCardListChanged() {
if (this._focusElem) {
this.fireFocusEvent(this._focusElem);
}
},
onBookmarkChanged: function ad_onBookmarkChanged(evt) {
var targetElem;
switch (evt.operation) {
case 'added':
this._bookmarkManager.get(evt.id).then(bookmark => {
targetElem = this._createBookmarkGridElement(bookmark);
this._appDeckGridViewElem.appendChild(targetElem);
this._spatialNavigator.add(targetElem);
});
break;
case 'removed':
targetElem = this._appDeckGridViewElem.querySelector(
'smart-button[data-url="' + evt.id +'"]');
// Move focus to next or previous element of `elem`, because
// we are going to remove `elem` from DOM tree
var nextFocus = targetElem.nextElementSibling ||
targetElem.previousElementSibling;
this._spatialNavigator.focus(nextFocus);
this._appDeckGridViewElem.removeChild(targetElem);
this._spatialNavigator.remove(targetElem);
URL.revokeObjectURL(targetElem.dataset.revokableURL);
break;
}
},
/**
* Notify other modules about details of currently focused element.
*
* @public
* @method AppDeck#fireFocusEvent
* @param {HTMLElement} elem - currently focused element
*/
/**
* This event is fired whenever the focus in AppDeck moves to a pinnable
* element (For now it's an app or a bookmark).
* @event AppDeck#focus
* @type {Object}
* @property {HTMLElement} elem - currently focused element
* @property {Boolean} pinned - Has it been pinned to Home
*/
fireFocusEvent: function ad_fireFocusEvent(elem) {
var that = this;
var type = elem && elem.getAttribute('app-type');
var query;
if (type === 'app') {
query = {
manifestURL: elem.dataset.manifestURL,
entryPoint: elem.dataset.entryPoint
};
} else if (type === 'bookmark') {
query = {
url: elem.dataset.url
};
} else {
// We have no other types for now.
return;
}
this._cardManager.isPinned(query).then(pinned => {
that.fire('focus', {
elem: elem,
pinned: pinned
});
});
},
onFocus: function ad_onFocus(elem) {
// The _focusElement may be XScrollable. It doesn't have blur method. If
// it has blur method, it should be DOM element.
if (this._focusElem && this._focusElem.blur) {
this._focusElem.blur();
}
if (elem instanceof XScrollable) {
// When we move focus from smart-button to scrollable,
// we have to remember the last focused smart-button in app list. So
// that, we can move focus back to app-deck-grid-view area afterwards.
if (this._focusElem) {
if (this._focusElem.nodeName === 'SMART-BUTTON') {
this._lastFocusedSmartButton = this._focusElem;
}
}
this._focusElem = elem;
elem.focus();
} else if (elem.nodeName) {
// When we move focus back to app-deck-grid-view area
// (e.g. area contains smart-button), we should always focus on
// last focused smart-button if there is any
if (elem.nodeName === 'SMART-BUTTON' && this._lastFocusedSmartButton) {
elem = this._lastFocusedSmartButton;
this._spatialNavigator.focus(elem);
}
elem.focus();
// Locate focused button to vertical middle of grid view. This way
// buttons one row above would always slightly be on bottom of the
// topmost scroll list (accroding to our layout). Thus we can prevent
// jumping from second row to the scroll list when pressing up key.
this._scrollTo(elem);
this._focusElem = elem;
}
this.fireFocusEvent(elem);
},
_scrollTo: function ad_scrollTo(elem) {
var scrollY = (elem.offsetTop - this._appDeckGridViewElem.offsetTop) -
(this._appDeckGridViewElem.offsetHeight - elem.offsetHeight) / 2;
this._appDeckGridViewElem.scrollTo(0, scrollY);
},
onUnfocus: function ad_onUnfocus(elem) {
// When we move focus from smart-button to another smart-button,
// we don't have to remember last focused smart-button
if (elem.nodeName === 'SMART-BUTTON') {
this._lastFocusedSmartButton = undefined;
}
},
onFocusOnAppDeckListScrollable:
function ad_onFocusOnAppDeckListScrollable(scrollable, itemElem) {
itemElem.focus();
},
onEnter: function ad_onEnter() {
if (this._disableNavigation) {
return;
}
var focused = this._spatialNavigator.getFocusedElement();
if (focused && focused.dataset && focused.dataset.manifestURL) {
Applications.launch(
focused.dataset.manifestURL, focused.dataset.entryPoint);
} else if (focused && focused.dataset && focused.dataset.url) {
window.open(focused.dataset.url, '_blank', 'remote=true,applike=true');
}
},
onMove: function ad_onMove(key) {
if (this._disableNavigation) {
return;
}
// When we are not in the SpatialNavigator's control area,
// use XScrollable object's spatial navigator to move.
// If it fails to move, and it's a 'down' operation, then we
// move into the last focused element within SpatialNavigator's
// control area.
if (this._outOfControlArea) {
if (!this._appDeckListScrollable.spatialNavigator.move(key) &&
key == 'down') {
this._spatialNavigator.focus(this._lastFocusedSmartButton);
this._outOfControlArea = false;
}
} else {
// Vice versa.
if (!this._spatialNavigator.move(key) && key == 'up') {
this._appDeckListScrollable.focus();
this._outOfControlArea = true;
}
}
},
onAppInstalled: function ad_onAppInstalled(apps) {
var that = this;
var appGridElements = apps.map(this._createAppGridElement.bind(this));
var firstBookmarkElem = this._appDeckGridViewElem.querySelector(
'smart-button[app-type="bookmark"]');
appGridElements.forEach(function(elem) {
that._appDeckGridViewElem.insertBefore(elem, firstBookmarkElem);
that._spatialNavigator.add(elem);
});
},
onAppUpdated: function ad_onAppUpdated(apps) {
var that = this;
var appGridElements = apps.map(this._findAppGridElement.bind(this));
appGridElements.forEach(function(elem, index) {
var app = apps[index];
elem.dataset.name = app.name;
that._fillAppButtonIcon(app, elem);
});
},
onAppUninstalled: function ad_onAppUninstalled(apps) {
var that = this;
var appGridElements = apps.map(this._findAppGridElement.bind(this));
appGridElements.forEach(function(elem) {
if (elem.dataset.revokableURL) {
URL.revokeObjectURL(elem.dataset.revokableURL);
}
// Move focus to next or previous element of `elem`, because
// we are going to remove `elem` from DOM tree
var nextFocus = elem.nextElementSibling || elem.previousElementSibling;
that._spatialNavigator.focus(nextFocus);
that._spatialNavigator.remove(elem);
that._appDeckGridViewElem.removeChild(elem);
});
},
/**
* Shorthand function to get all SmartButtons of apps
*
* @private
* @method AppDeck#_findAllAppGridElements
* @return {Array} array of SmartButton
*/
_findAllAppGridElements: function ad_findallAppGridElements() {
return SharedUtils.nodeListToArray(
document.getElementsByClassName('app-button'));
},
/**
* Shorthand function to find specific SmartButton representing the app
*
* @private
* @method AppDeck#_findAppGridElement
* @param {DOMApplication} app the [app](http://mzl.la/1DJP6oZ) instance
* which we want to find SmartButton is
* represented for
* @return {SmartButton}
*/
_findAppGridElement: function ad_findAppGridElement(app) {
var elements = this._findAllAppGridElements();
var found;
elements.some(function(element) {
if (element.dataset.manifestURL === app.manifestURL) {
found = element;
return true;
}
return false;
});
return found;
},
disableNavigation: function ad_stopNavigation() {
this._disableNavigation = true;
},
enableNavigation: function ad_stopNavigation() {
this._disableNavigation = false;
this._spatialNavigator.focus();
}
});
exports.appDeck = new AppDeck();
exports.appDeck.init();
}(window));

View file

@ -0,0 +1,252 @@
'use strict';
/* global MozActivity, Applications, BookmarkManager, SmartModalDialog */
/* jshint nonew: false */
(function(exports) {
/**
* It handles everything related to contextmenu in app-deck.
*
* @class ContextMenu
* @requires Applications
* @requires SharedUtils
*/
var ContextMenu = function() {
};
ContextMenu.prototype = {
pinToHomeElem: document.getElementById('pin-to-home'),
unpinFromHomeElem: document.getElementById('unpin-from-home'),
mainSection: document.getElementById('main-section'),
removeElem: document.getElementById('remove-card'),
contextMenuElem: document.getElementById('context-menu'),
_appDeck: undefined,
_target: {},
/**
* Initialize ContextMenu
*
* @public
* @method ContextMenu#init
* @param {AppDeck} appDeck - instance of {@link AppDeck}
*/
init: function cm_init(appDeck) {
this._appDeck = appDeck;
this._appDeck.on('focus', this.onFocus.bind(this));
this._bookmarkManager = BookmarkManager;
this._modalDialog = new SmartModalDialog();
this.pinToHomeElem.addEventListener('click', this.pinOrUnpin.bind(this));
this.unpinFromHomeElem.addEventListener(
'click', this.pinOrUnpin.bind(this));
this.removeElem.addEventListener('click', this.onRemove.bind(this));
this._modalDialog.container.addEventListener('modal-dialog-closed',
this._appDeck.enableNavigation.bind(this._appDeck));
this._modalDialog.container.addEventListener('modal-dialog-will-open',
this._appDeck.disableNavigation.bind(this._appDeck));
},
_selfApp: undefined,
/**
* Underlying function which actual send string message on 'appdeck-channel'
*
* @private
* @method ContextMenu#_connectAndSend
* @param {String} message - the message which will be sent on
* 'appdeck-channel'
*/
_connectAndSend: function cm_sendMessage(message) {
this._selfApp.connect('appdeck-channel').then(function (ports) {
ports.forEach(function(port) {
port.postMessage(message);
});
});
},
/**
* Send message on 'appdeck-channel' using Inter-App Communication.
* smart-home will monitor this channel. Currently we use this channel to
* tell smart-home to what app we would like to unpin.
*
* @public
* @method ContextMenu#sendMessage
* @param {String} type - message type, which must be 'unpin' for now.
* @param {Object} data - message data in JSON object format
*/
sendMessage: function cm_sendMessage(type, data) {
var that = this;
var message = {
type: type,
data: data
};
if (this._selfApp) {
this._connectAndSend(message);
} else {
navigator.mozApps.getSelf().onsuccess = function(evt) {
that._selfApp = evt.target.result;
that._connectAndSend(message);
};
}
},
/**
* To tell smart-home what app we would like to unpin
*
* @private
* @method ContextMetnu#_sendUnpinMessage
* @param {HTMLElement} cardElem - the card we'd like to unpin from Home
*/
_sendUnpinMessage: function cm_sendUnpinMessage(cardElem) {
var type = cardElem.getAttribute('app-type');
if (type === 'app') {
// Notice that here we didn't specify launchURL because we assume all
// 'Application' in app-deck are launched by calling app.launch().
// However, in the case of other deck-like app, if we are going to pin
// application which is launched by different launchURL, we must specify
// 'launchURL' here.
this.sendMessage('unpin', {
manifestURL: cardElem.dataset.manifestURL
});
} else if (type === 'bookmark') {
this.sendMessage('unpin', {
url: cardElem.dataset.url
});
}
},
/**
* Based on currently focused SmartButton, we'll decide we are going to
* pin or unpin the app. This function is invoked when we get 'click' event
* on pinToHomeElem. Notice that we use `MozActivity` to pin app because we
* want to bring users to Home when they finish pinning. However we unpin
* app via IAC because we want users stay in AppDeck after unpinning.
*
* @public
* @method ContextMenu#pinOrUnpin
*/
pinOrUnpin: function cm_pinOrUnpin() {
var cardElem = this._target.elem;
var type = this._getTargetType();
var dataset = cardElem.dataset;
if (this._target.pinned) {
this._sendUnpinMessage(this._target.elem);
} else if (type === 'app') {
// XXX: preferredSize should be determined by
// real offsetWidth of cardThumbnailElem in smart-home instead of
// hard-coded value
Applications.getIconBlob(
dataset.manifestURL, dataset.entryPoint, 354, blob => {
// Notice that here we didn't specify launchURL because we assume
// all 'Application' in app-deck are launched by calling
// app.launch().
// However, in the case of other deck-like app, if we are going to
// pin application which is launched by different launchURL, we
// must specify 'launchURL' here.
new MozActivity({
name: 'pin',
data: {
type: 'Application',
group: 'application',
manifestURL: dataset.manifestURL,
// We use app's original icon instead of screenshot here
// because we are in app deck. For the case of getting
// screenshot, please refer to bug 1100238.
thumbnail: blob
}
});
});
} else if (type === 'bookmark') {
this._bookmarkManager.get(dataset.url).then(card => {
new MozActivity({
name: 'pin',
data: {
name: {raw: card.name},
type: 'AppBookmark',
group: 'application',
url: card.url,
thumbnail: card.icon
}
});
});
}
},
onRemove: function cm_onRemove() {
var type = this._getTargetType();
var elem = this._target.elem;
if (type === 'app') {
this.uninstall(elem);
} else if (type === 'bookmark') {
this._appDeck.disableNavigation();
this._modalDialog.open({
message: {
textL10nId: {
id: 'delete-bookmark-alert',
args: {
name: elem.getAttribute('label')
}
}
},
buttonSettings: [
{
textL10nId: 'cancel',
defaultFocus: true,
},
{
textL10nId: 'delete',
class: 'danger',
onClick: () => {
this._bookmarkManager.remove(elem.dataset.url);
this._sendUnpinMessage(elem);
}
}
]
});
}
},
_getTargetType: function cm_getType() {
return this._target.elem && this._target.elem.getAttribute('app-type');
},
/**
* Uninstall app represented by current focused SmartButton
* @public
* @method ContextMenu#uninstall
* @param {HTMLElement} cardElem - the card we'd like to unpin from Home
*/
uninstall: function cm_uninstall(cardElem) {
var appInstance =
Applications.installedApps[cardElem.dataset.manifestURL];
if (appInstance) {
navigator.mozApps.mgmt.uninstall(appInstance);
}
},
onFocus: function cm_onFocus(target) {
this._target = target;
this.contextMenuElem.innerHTML = '';
if (target.pinned) {
this.contextMenuElem.appendChild(this.unpinFromHomeElem);
} else {
this.contextMenuElem.appendChild(this.pinToHomeElem);
}
this.contextMenuElem.appendChild(this.removeElem);
}
};
exports.ContextMenu = ContextMenu;
})(window);

View file

@ -0,0 +1,213 @@
/* global MozActivity, KeyEvent */
(function(exports) {
'use strict';
const ANIMATION_TIMER = 400;
var TEST_LIST = [
{
'imageUrl': 'style/banners/firefox-os.png',
'title': 'Firefox OS',
'description': 'Learn more about Firefox OS.',
'url': 'https://www.mozilla.org/en-US/firefox/os/'
}, {
'imageUrl': 'style/banners/participation.png',
'title': 'Firefox OS Smart TV Participation',
'description': 'Learn how you can participate!',
'url': 'https://firefoxos.mozilla.community/fxos-tv/'
}, {
'imageUrl': 'style/banners/mdn.png',
'title': 'MDN',
'description': 'Learn about TV apps developement!',
'url': 'http://mzl.la/1NFslRZ'
}
];
/**
* PromotionList controls everything on the upper half of AppDeck
* @class PromotionList
*/
function PromotionList() {}
var proto = PromotionList.prototype;
proto.init = function() {
this.bannerContainer = document.getElementById('app-deck-list');
this.container = document.getElementById('app-deck-main-banner');
this.animator = document.getElementById('app-deck-animation-banner');
this.animator.addEventListener('transitionend', this);
this._initBannerSize(this.animator);
this._initBannerSize(this.container);
this.animator.classList.add('hidden');
// load test content.
TEST_LIST.forEach(this._createBanner.bind(this));
// set first one as context item.
this.setContextItem(0);
};
proto._createBanner = function(item, index) {
var div = document.createElement('div');
div.classList.add('app-banner');
var btn = document.createElement('smart-button');
btn.setAttribute('type', 'banner-button');
btn.classList.add('app-deck-list-item');
btn.style.backgroundImage = 'url(\'' + item.imageUrl + '\'), ' +
'url(\'style/icons/ic_default_app.png\')';
btn.dataset.index = index;
btn.addEventListener('focus', this);
btn.addEventListener('keyup', this);
div.appendChild(btn);
this.bannerContainer.appendChild(div);
};
proto._initBannerSize = function pl_initBannerSize(container) {
var bg = container.querySelector('.banner-background');
var text = container.querySelector('.banner-text-container');
text.style.width = bg.style.width = container.clientWidth + 'px';
};
proto._fillMainBanner = function pl_fillMainBanner(container, item) {
var bg = container.querySelector('.banner-background');
var title = container.querySelector('.banner-title');
var desc = container.querySelector('.banner-description');
bg.style.backgroundImage = 'url(\'' + item.imageUrl + '\')';
title.textContent = item.title;
desc.textContent = item.description;
};
proto._slideItemIn = function pl_slideItemIn(item) {
// fill animation and start to slide
this._fillMainBanner(this.animator, item);
// cancel previous one if we already have it.
this._cancelSliding();
// remove hidden and add standby to prepare for the startup of animation.
this.animator.classList.remove('hidden');
this.animator.classList.add('standby');
this._animationTimeout = setTimeout(function() {
this._contextItem = item;
this.animationTimeout = 0;
this.animator.classList.remove('standby');
this.animator.classList.add('sliding');
this.container.classList.add('sliding');
}.bind(this), ANIMATION_TIMER);
};
proto._cancelSliding = function pl_cancelSliding() {
if (this._animationTimeout) {
clearTimeout(this._animationTimeout);
this._animationTimeout = 0;
// If _animationTimeout is not 0, it means the animation is prepared but
// is not triggered. So, we need to rollback the state while clear the
// timer.
this.animator.classList.remove('standby');
this.animator.classList.add('hidden');
}
};
proto.setContextItem = function pl_setContextItem(idx) {
var item = TEST_LIST[idx];
if (!this._contextItem) {
// If context item is undefined or null, it's first context item, we just
// set it without animation.
this._contextItem = item;
this._fillMainBanner(this.container, item);
return;
}
if (this._contextItem === item) {
// If the context item is self, we don't need to slide anything and should
// clear all pending or sliding timer.
if (this.animator.classList.contains('sliding')) {
// If _contextItem is sliding now, we should clear the pending animation
// which means user presses key back and forward quickly.
this._pendingAnimationItem = null;
} else {
// We still need to clear the sliding timer because the waiting item may
// not be current item.
this._cancelSliding();
}
return;
}
if (this.animator.classList.contains('sliding')) {
this._pendingAnimationItem = item;
return;
}
this._slideItemIn(item);
};
proto._handleFocusChange = function(evt) {
if (evt.target.dataset.index === null ||
typeof(evt.target.dataset.index) === 'undefined') {
return;
}
this.setContextItem(evt.target.dataset.index);
};
proto._openLink = function(evt) {
var idx = evt.target.dataset.index;
var url = TEST_LIST[idx].url;
// Ask browser app to open this link.
var req = new MozActivity({
name: 'view',
data: {type: 'url', url: url}
});
req.onerror = function() {
console.log(req.error.name);
};
};
proto._handleTransitionEnd = function(evt) {
if (evt.propertyName !== 'transform' ||
evt.currentTarget !== this.animator) {
return;
}
// banner-background is the last one to move. Once it is moved, we should
// set all data to main container.
if (evt.target.classList.contains('banner-background')) {
this.animator.classList.add('hidden');
this.animator.classList.remove('sliding');
this.container.classList.remove('sliding');
this._fillMainBanner(this.container, this._contextItem);
if (this._pendingAnimationItem) {
this._slideItemIn(this._pendingAnimationItem);
this._pendingAnimationItem = null;
}
}
};
proto.handleEvent = function(evt) {
switch(evt.type) {
case 'focus':
this._handleFocusChange(evt);
break;
case 'transitionend':
this._handleTransitionEnd(evt);
break;
case 'keyup':
if (evt.keyCode != KeyEvent.DOM_VK_RETURN) {
return;
}
this._openLink(evt);
break;
}
};
exports.PromotionList = PromotionList;
})(window);

View file

@ -0,0 +1,18 @@
{
"name": "app-deck",
"tags": {
"dictionaries": ["jsdoc"]
},
"source": {
"include": ["tv_apps/app-deck/js/**/*.js"]
},
"plugins": [
"plugins/markdown",
"plugins/summarize"
],
"opts": {
"private": true,
"recurse": true,
"destination": "docs/app-deck"
}
}

View file

@ -0,0 +1,10 @@
pin-to-home=Pin to Home
pin-to-home.label=Pin to Home
unpin-from-home=Unpin From Home
unpin-from-home.label=Unpin From Home
remove-card=Remove
remove-card.label=Remove
delete-bookmark-alert={{name}} and all of its data will be deleted.
delete=Delete
cancel=Cancel

View file

@ -0,0 +1,41 @@
{
"name": "Apps",
"description": "Smart Screen Apps Deck",
"launch_path": "/index.html",
"type": "certified",
"role": "deck",
"developer": {
"name": "The Gaia Team",
"url": "https://github.com/mozilla-b2g/gaia"
},
"locales": {
"en-US": {
"name": "Apps",
"description": "Smart Screen Apps Deck"
}
},
"default_locale": "en-US",
"icons": {
"354": "/style/icons/app-deck_354.png",
"708": "/style/icons/app-deck_708.png"
},
"orientation": "landscape",
"permissions": {
"webapps-manage": {},
"systemXHR": {},
"open-remote-window": {},
"moz-extremely-unstable-and-will-change-webcomponents": {}
},
"datastores-access": {
"home_cards": {
"access": "readonly",
"description": "Read card list"
}
},
"datastores-owned": {
"appdeck_bookmarks": {
"access": "readwrite",
"description": "Saves bookmarks from marketplace"
}
}
}

View file

@ -0,0 +1,76 @@
html {
font-size: 10px;
/* We have Fira Sans as default font in real device. But it isn't default in
* browser. That's the main reason this line is here.*/
font-family: "Fira Sans";
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
background-color: white;
}
a {
outline: 0;
}
a:hover, a:active, a:focus {
outline: 0;
}
section {
font-size: 1.6rem;
}
#main-section {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
#app-deck-grid-view-container {
width: 100%;
height: calc(100% - 46.6rem);
position: absolute;
left: 0;
bottom: 0;
background-color: #e0e0e0;
}
#app-deck-grid-view {
/* top = (6 - 5.8)rem */
padding: 0.2rem 14.2rem 0;
height: 100%;
overflow: hidden;
scroll-behavior: smooth;
}
#app-deck-grid-view:after {
content: "";
display: block;
/* app-deck-grid-view has padding-top 0.2rem, and the buttons in the first row
have margin-top 5.8rem, so app-deck-grid-view bottom should have height
5.8 + 0.2 - 2.9(button margin-bottom) = 3.1rem */
height: 3.1rem;
}
smart-button[type="app-button"] {
width: 18rem;
height: 18rem;
background-color: transparent;
background-repeat: no-repeat;
margin: 5.8rem 2.6rem 2.9rem 2.6rem;
background-size: 100% auto;
}
/** we use :after as label */
smart-button[type="app-button"]:after {
width: 18rem;
height: 5rem;
font-size: 1.6rem;
}

View file

@ -0,0 +1,152 @@
#jumbotron {
width: 100%;
height: 46.6rem;
overflow: hidden;
text-align: center;
vertical-align: middle;
position: relative;
background-color: #323232;
}
.app-deck-main-banner, .app-deck-animation-banner {
position: absolute;
left: -1rem;
top: -1rem;
width: calc(100% + 2rem);
height: calc(100% + 2rem);
}
.app-deck-animation-banner.hidden {
display: none;
}
.app-deck-animation-banner.standby .banner-background,
.app-deck-animation-banner.standby .banner-title,
.app-deck-animation-banner.standby .banner-description {
transform: translate(12rem, 0);
}
.app-deck-animation-banner.sliding .banner-background,
.app-deck-animation-banner.sliding .banner-title,
.app-deck-animation-banner.sliding .banner-description {
transform: translate(0, 0);
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1.0);
transition-duration: 0.66s;
}
.app-deck-animation-banner.sliding .banner-description {
transition-delay: 0.0s;
}
.app-deck-animation-banner.sliding .banner-title {
transition-delay: 0.06s;
}
.app-deck-animation-banner.sliding .banner-background {
transition-delay: 0.12s;
}
.app-deck-main-banner.sliding {
width: 0;
transition-property: width;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1.0);
transition-duration: 0.66s;
overflow: hidden;
background-position: left center;
}
.banner-background {
position: absolute;
/* to have slide-in transition work, we need to write a width to banner,
like => width: 194rem;. This task will be done at JavaScript in runtime. */
height: 100%;
background-repeat: no-repeat;
background-position: center center;
background-size: 100% auto;
}
.banner-background:before {
content: ' ';
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.4);
}
.banner-text-container {
position: absolute;
top: 0;
/* 24rem = 21.8rem banner list + 2.2rem spacing */
bottom: 24rem;
margin-top: auto;
margin-bottom: auto;
/* to have slide-in transition work, we need to write a width to banner,
like => width: 194rem;. This task will be done at JavaScript in runtime. */
/* 15rem = 7.2 header + 7.2 desc + 0.6 spacing */
max-height: 15rem;
text-align: center;
font-style: italic;
color: #ffffff;
}
.banner-title {
width: 120rem;
height: 7.2rem;
line-height: 7.2rem;
font-size: 5.2rem;
margin: 0;
padding: 0;
display: inline-block;
}
.banner-description {
display: inline-block;
color: #ffffff;
font-size: 2.4rem;
font-weight: bold;
line-height: 3.6rem;
width: 120rem;
max-height: 7.2rem;
margin-top: 0.6rem;
}
#app-deck-list-frame {
position: absolute;
top: 22.6rem;
width: 100%;
height: 21.8rem;
overflow: hidden;
}
#app-deck-list {
position: relative;
transition: transform 0.2s ease;
transform-origin: 0 50%;
}
.app-banner {
display: inline-block;
width: 38.4rem;
height: 21.8rem;
left: 0;
position: absolute;
}
smart-button.app-deck-list-item {
width: 100%;
height: 100%;
background-position: center center, center center;
background-size: 120% auto, 12rem auto;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

21
gaia/tv_apps/browser/bower.json Executable file
View file

@ -0,0 +1,21 @@
{
"name": "browser",
"version": "0.0.0",
"homepage": "https://github.com/mozilla-b2g/gaia/tree/master/tv_apps/browser",
"description": "Smart Screen Browser App",
"main": "index.html",
"license": "MPL",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"evt": "fxos-components/evt#~0.4.0",
"smart-button": "fxos-components/smart-button",
"smart-icons": "smart-components/smart-icons#~0.0.3"
}
}

View file

@ -0,0 +1,28 @@
{
"name": "evt",
"main": "index.js",
"version": "0.4.0",
"homepage": "https://github.com/wilsonpage/evt",
"authors": [
"Wilson Page <wilsonpage@me.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"package.json",
"test",
"tests",
"README.md"
],
"_release": "0.4.0",
"_resolution": {
"type": "version",
"tag": "v0.4.0",
"commit": "3593dd6b4dd19f683d00fd0eaf2581c11a128da6"
},
"_source": "git://github.com/gaia-components/evt.git",
"_target": "~0.4.0",
"_originalSource": "gaia-components/evt"
}

View file

@ -0,0 +1,19 @@
{
"name": "evt",
"main": "index.js",
"version": "0.4.0",
"homepage": "https://github.com/wilsonpage/evt",
"authors": [
"Wilson Page <wilsonpage@me.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"package.json",
"test",
"tests",
"README.md"
]
}

View file

@ -0,0 +1,154 @@
/**
* Evt
*
* A super lightweight
* event emitter library.
*
* @version 0.3.3
* @author Wilson Page <wilson.page@me.com>
*/
;(function() {
/**
* Locals
*/
var proto = Events.prototype;
var slice = [].slice;
/**
* Creates a new event emitter
* instance, or if passed an
* object, mixes the event logic
* into it.
*
* @param {Object} obj
* @return {Object}
*/
function Events(obj) {
if (!(this instanceof Events)) return new Events(obj);
if (obj) return mixin(obj, proto);
}
/**
* Registers a callback
* with an event name.
*
* @param {String} name
* @param {Function} cb
* @return {Event}
*/
proto.on = function(name, cb) {
this._cbs = this._cbs || {};
(this._cbs[name] || (this._cbs[name] = [])).push(cb);
return this;
};
/**
* Attach a callback that once
* called, detaches itself.
*
* TODO: Implement `.off()` to work
* with `once()` callbacks.
*
* @param {String} name
* @param {Function} cb
* @public
*/
proto.once = function(name, cb) {
this.on(name, one);
function one() {
cb.apply(this, arguments);
this.off(name, one);
}
};
/**
* Removes a single callback,
* or all callbacks associated
* with the passed event name.
*
* @param {String} name
* @param {Function} cb
* @return {Event}
*/
proto.off = function(name, cb) {
this._cbs = this._cbs || {};
if (!name) { this._cbs = {}; return; }
if (!cb) { return delete this._cbs[name]; }
var cbs = this._cbs[name] || [];
var i;
while (cbs && ~(i = cbs.indexOf(cb))) { cbs.splice(i, 1); }
return this;
};
/**
* Fires an event, triggering
* all callbacks registered on this
* event name.
*
* @param {String} name
* @return {Event}
*/
proto.fire = proto.emit = function(options) {
var cbs = this._cbs = this._cbs || {};
var name = options.name || options;
var batch = (cbs[name] || []).concat(cbs['*'] || []);
var ctx = options.ctx || this;
if (batch.length) {
this._fireArgs = arguments;
var args = slice.call(arguments, 1);
while (batch.length) {
batch.shift().apply(ctx, args);
}
}
return this;
};
proto.firer = function(name) {
var self = this;
return function() {
var args = slice.call(arguments);
args.unshift(name);
self.fire.apply(self, args);
};
};
/**
* Util
*/
/**
* Mixes in the properties
* of the second object into
* the first.
*
* @param {Object} a
* @param {Object} b
* @return {Object}
*/
function mixin(a, b) {
for (var key in b) a[key] = b[key];
return a;
}
/**
* Expose 'Event' (UMD)
*/
if (typeof exports === 'object') {
module.exports = Events;
} else if (typeof define === 'function' && define.amd) {
define(function(){ return Events; });
} else {
window.evt = Events;
}
})();

View file

@ -0,0 +1,26 @@
{
"name": "smart-button",
"version": "0.0.2",
"homepage": "https://github.com/gaia-components/smart-button",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md"
],
"_release": "0.0.2",
"_resolution": {
"type": "version",
"tag": "v0.0.2",
"commit": "04fdae935e2f2ba99d1c4f3e156d17f12eb2d142"
},
"_source": "git://github.com/gaia-components/smart-button.git",
"_target": "*",
"_originalSource": "gaia-components/smart-button"
}

View file

@ -0,0 +1,17 @@
{
"name": "smart-button",
"version": "0.0.2",
"homepage": "https://github.com/gaia-components/smart-button",
"main": "script.js",
"license": "MPL",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"examples",
"LICENSE",
"README.md"
]
}

View file

@ -0,0 +1,62 @@
'use strict';
/* global KeyEvent */
window.SmartButton = (function(win) {
// Extend from the HTMLButtonElement prototype
var proto = Object.create(HTMLButtonElement.prototype);
proto.createdCallback = function() {
this.addEventListener('mousedown', this);
this.addEventListener('mouseup', this);
this.addEventListener('touchstart', this);
this.addEventListener('touchend', this);
this.addEventListener('keydown', this);
this.addEventListener('keyup', this);
this.addEventListener('focus', this);
this.addEventListener('blur', this);
this.addEventListener('transitionend', this);
this.tabIndex = 0;
};
proto.handleEvent = function(evt) {
switch(evt.type) {
case 'mousedown':
case 'touchstart':
this.classList.add('pressed');
break;
case 'keydown':
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
this.classList.add('pressed');
}
break;
case 'mouseup':
case 'touchend':
this.classList.remove('pressed');
this.classList.add('released');
break;
case 'keyup':
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
this.classList.remove('pressed');
this.classList.add('released');
this.click();
}
break;
case 'transitionend':
if (this.classList.contains('released')) {
this.classList.remove('released');
}
break;
case 'focus':
this.classList.add('focused');
break;
case 'blur':
this.classList.remove('pressed');
this.classList.remove('released');
this.classList.remove('focused');
break;
}
};
// Register and return the constructor
return document.registerElement('smart-button', { prototype: proto });
})(window);

View file

@ -0,0 +1,497 @@
/**
* Button state:
* 1. normal
* In this state, we don't add any class.
* 2. focused (class name: focused)
* The focused state may be triggered by keyboard or touch/mouse.
* 3. pressed (class name: pressed)
* This state is triggered by user pressed confirm button, mousedown,
* and touchdown.
* 4. released (class name: released)
* This state is only for changing duration of transition. We enter this
* state when user release the button. After transitionend, we goes back
* to focused state.
**/
smart-button {
font-family: "Fira Sans";
display: inline-block;
width: 8rem;
height: 8rem;
/* we need to set border to none to remove the button border */
border: none;
/* the radius is still make button as round even if we don't have border */
border-radius: 50%;
/* 50% is not a real circle because of rounding, we have to use a value larger
than 50% */
-moz-outline-radius: 60%;
outline: transparent 0 solid;
background-color: rgba(0, 0, 0, 0.5);
background-repeat: no-repeat;
background-size: 5.4rem auto;
background-position: center center;
font-style: italic;
transition-property: all;
transition-timing-function: cubic-bezier(0.25, 0, 0, 1.0);
transition-duration: 0.42s;
}
smart-button.focused {
outline: 0;
background-color: #ffffff;
transform: scale(1.2);
transition-duration: 0.42s;
}
smart-button.released {
transition-duration: 0.16s;
}
smart-button.toggled {
background-color: #00caf2;
}
smart-button.pressed {
background-color: #00caf2;
transform: scale(0.8);
transition-duration: 0.06s;
}
smart-button.disabled {
background-color: rgba(0, 0, 0, 0.5);
}
smart-button[data-icon]:before {
width: 5.4rem;
left: 1.3rem;
font-size: 5.4rem;
line-height: 8rem;
text-align: center;
color: #ffffff;
white-space: normal;
top: 0;
position: absolute;
}
smart-button[data-icon].focused:before {
color: #2d2d2d;
}
smart-button[data-icon].toggled:before {
color: #00caf2;
}
smart-button[data-icon].pressed:before {
color: #ffffff;
}
smart-button[data-icon].disabled:before {
color: #ffffff;
opacity: 0.3;
}
/**
* Simple Text Button
**/
smart-button[type="simple-text"] {
color: #dddddd;
background: none;
border: none;
border-radius: 0;
width: auto;
height: 8rem;
line-height: 8rem;
font-size: 2.4rem;
-moz-user-select: none;
padding-left: 2.6rem;
padding-right: 2.6rem;
}
smart-button[type="simple-text"].focused {
color: #ffffff;
background: none;
}
smart-button[type="simple-text"].toggled {
color: #00caf2;
}
smart-button[type="simple-text"].pressed {
color: #00caf2;
background: none;
}
/**
* End of Simple Text Button
**/
/**
* underlined text button
*/
smart-button[type="underlined-text"] {
position: relative;
color: white;
font-size: 2.4rem;
padding-left: 3.8rem;
padding-right: 3.8rem;
border-radius: 4rem;
width: auto;
line-height: 8rem;
margin-right: 1.4rem;
overflow: hidden;
}
smart-button[type="underlined-text"]:after {
position: absolute;
width: 100%;
height: 100%;
left: 0;
content: ' ';
border-bottom: 0.6rem #61acd7 solid;
box-sizing: border-box;
}
smart-button[type="underlined-text"].focused {
transform: scale(1.2);
color: #2d2d2d;
}
smart-button[type="underlined-text"].pressed {
transform: scale(1);
color: white;
background-color: #00caf2;
}
/**
* End of underlined text button
*/
/**
* App Icons
**/
smart-button[type="app-button"] {
/* To use 33.4rem instead of to use 33.6rem is to work around the anti-alias
pixel. So, the circle is filled by the background almost totally. */
width: 33.4rem;
height: 33.4rem;
position: relative;
overflow: hidden;
background-color: transparent;
}
smart-button[type="app-button"].focused {
transform: scale(1.4);
/* To use outline will not change the size of button and make the whole button
thrilling. */
outline: 0.8rem solid #ffffff;
}
smart-button[type="app-button"].pressed {
/* While scaling, we may have a gap between outline and icon which may be
anti-alias pixel. So, we use scale(1.22) to prevent such kind of case */
transform: scale(1.22);
/* To use outline will not change the size of button and make the whole button
thrilling. */
outline: 1.0rem solid #ffffff;
}
smart-button[type="app-button"].fullsized {
background-size: 33.6rem auto;
}
smart-button[type="app-button"].fitted {
background-size: 22rem auto;
}
/** we use :after as label */
smart-button[type="app-button"]:after {
content: attr(label);
position: absolute;
bottom: 0;
left: auto;
right: auto;
width: 33.6rem;
height: 8rem;
padding-top: 1rem;
line-height: 3rem;
text-align: center;
font-size: 2.8rem;
font-style: italic;
color: #ffffff;
}
/**
* text backgorund color
**/
smart-button[type="app-button"][app-type="deck"]:after {
background-color: transparent;
}
smart-button[type="app-button"][app-type="tv"]:after {
background-color: #d90c83;
}
smart-button[type="app-button"][app-type="app"]:after {
background-color: rgba(0, 0, 0, 0.4);
}
smart-button[type="app-button"][app-type="device"]:after {
background-color: #4a82ea;
}
smart-button[type="app-button"][app-type="folder"] {
background-color: #595959;
}
smart-button[type="app-button"]:before {
left: 0;
line-height: 30rem;
font-size: 16rem;
width: 100%;
}
smart-button[type="app-button"][app-type="folder"]:before {
color: #CCCCCC;
}
/**
* End of App Icons
**/
/**
* text and icon button
*/
smart-button[type="text-and-icon"] {
position: relative;
color: white;
font-size: 2.4rem;
padding-left: 4.6rem;
padding-right: 4.6rem;
border-radius: 4rem;
width: auto;
line-height: 8rem;
margin-right: 1.4rem;
overflow: hidden;
}
smart-button[type="text-and-icon"][data-icon] {
padding-left: 9.6rem;
}
smart-button[type="text-and-icon"][data-icon]:before {
left: 3.8rem;
}
smart-button[type="text-and-icon"].focused {
transform: scale(1.2);
color: #2d2d2d;
}
smart-button[type="text-and-icon"].pressed {
transform: scale(1);
color: white;
background-color: #00caf2;
}
/**
* End of text and icon button
*/
/**
* banner button
**/
smart-button[type="banner-button"] {
position: relative;
color: #ffffff;
font-size: 2rem;
width: 36rem;
height: 20rem;
overflow: hidden;
border-radius: 0;
border: 0 solid #ffffff;
background-color: #5f5f5f;
background-repeat: no-repeat;
background-size: 120%;
box-sizing: border-box;
transition-property: all;
transition-timing-function: cubic-bezier(0.25, 0, 0.5, 1.0);
transition-duration: 0.1s;
}
smart-button[type="banner-button"][label]:before {
content: attr(label);
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 5rem;
line-height: 5rem;
text-align: center;
background-color: rgba(0, 0, 0, 0.4);
}
smart-button[type="banner-button"]:after {
content: " ";
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
transition-property: all;
transition-timing-function: cubic-bezier(0.25, 0, 0, 1.0);
transition-duration: 0.06s;
background-color: rgba(255, 255, 255, 0);
}
smart-button[type="banner-button"].focused {
transform: none;
border-width: 1.2rem;
background-size: 100%;
transition-duration: 0.24s;
}
smart-button[type="banner-button"].pressed {
transform: scale(0.945, 0.9);
}
smart-button[type="banner-button"].pressed:after {
background-color: rgba(255, 255, 255, 0.5);
}
/**
* banner button
**/
/**
* circle text button
*/
smart-button[type="circle-text"] {
width: 21rem;
height: 21rem;
color: white;
background-color: #5f6368;
line-height: 21rem;
padding: 2.5rem 2rem 1.5rem 2rem;
text-align: center;
font-size: 3.2rem;
font-weight: bolder;
overflow: hidden;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].focused {
transform: scale(1.2);
color: #2d2d2d;
background-color: white;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].pressed {
transform: scale(1);
color: white;
background-color: #00caf2;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].primary {
background-color: #00caf2;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].primary.focused {
color: #ffffff;
background-color: #00caf2;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].primary.pressed {
background-color: #0097b5;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].danger {
background-color: #e63d2f;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].danger.focused {
color: #ffffff;
background-color: #e63d2f;
transition-property: transform, color, background-color;
}
smart-button[type="circle-text"].danger.pressed {
background-color: #ac2e23;
transition-property: transform, color, background-color;
}
/**
* End of circle text button
*/
/**
* rounded-corner text button
*/
smart-button[type="rounded-corner-text"] {
-moz-user-select: none;
width: auto;
height: 8rem;
border-radius: 4rem;
background-color: #5f6368;
padding: 0 3.8rem;
line-height: 8rem;
color: white;
text-align: center;
font-size: 2.4rem;
overflow: hidden;
transition-property: transform, color, background-color;
}
smart-button[type="rounded-corner-text"].focused,
smart-button[type="rounded-corner-text"]:hover {
transform: scale(1.2);
color: #2d2d2d;
background-color: white;
}
smart-button[type="rounded-corner-text"].pressed {
transform: scale(1);
/*color: white;*/
background-color: #00caf2;
}
smart-button[type="rounded-corner-text"].primary {
background-color: #00caf2;
}
smart-button[type="rounded-corner-text"].primary:hover,
smart-button[type="rounded-corner-text"].primary.focused {
color: #ffffff;
background-color: #00caf2;
}
smart-button[type="rounded-corner-text"].primary.pressed {
background-color: #0097b5;
}
/**
* End of rounded-corner text button
*/

View file

@ -0,0 +1,30 @@
{
"name": "smart-icons",
"version": "0.0.3",
"homepage": "https://github.com/smart-components/smart-icons",
"description": "Icon font for smart-screen",
"license": "MPL",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"templates",
"images",
"test",
"tests",
"index.html",
"Gruntfile.js",
"README.md",
"package.json"
],
"_release": "0.0.3",
"_resolution": {
"type": "version",
"tag": "v0.0.3",
"commit": "0c335ac23f484069b4fafa7b7e5afd9f3c08d66e"
},
"_source": "git://github.com/smart-components/smart-icons.git",
"_target": "~0.0.3",
"_originalSource": "smart-components/smart-icons"
}

View file

@ -0,0 +1,21 @@
{
"name": "smart-icons",
"version": "0.0.3",
"homepage": "https://github.com/smart-components/smart-icons",
"description": "Icon font for smart-screen",
"license": "MPL",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"templates",
"images",
"test",
"tests",
"index.html",
"Gruntfile.js",
"README.md",
"package.json"
]
}

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,25 @@
/* Generated by grunt-webfont */
/* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
@font-face {
font-family: "smart-icons";
src: url("fonts/smart-icons.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
[data-icon]:before,
.ligature-icons {
font-family: "smart-icons";
content: attr(data-icon);
display: inline-block;
font-weight: 500;
font-style: normal;
text-decoration: inherit;
text-transform: none;
text-rendering: optimizeLegibility;
font-size: 30px;
-webkit-font-smoothing: antialiased;
}

View file

@ -0,0 +1,88 @@
'use strict';
/* jshint node: true */
var utils = require('utils');
var preprocessor = require('preprocessor');
var BrowserAppBuilder = function() {
};
BrowserAppBuilder.prototype.setOptions = function(options) {
this.stageDir = utils.getFile(options.STAGE_APP_DIR);
this.appDir = utils.getFile(options.APP_DIR);
this.distDirPath = options.GAIA_DISTRIBUTION_DIR;
};
BrowserAppBuilder.prototype.initJSON = function() {
var defaultJSONpath =
utils.joinPath(this.appDir.path, 'build', 'default.json');
var defaultJson = utils.getJSON(utils.getFile(defaultJSONpath));
var file =
utils.getFile(this.stageDir.path, 'js', 'init.json');
utils.writeContent(file,
utils.getDistributionFileContent('browser', defaultJson, this.distDirPath));
};
BrowserAppBuilder.prototype.initTopsitesJSON = function() {
var defaultJSONpath =
utils.joinPath(this.appDir.path, 'build', 'topsites.json');
var defaultJson = utils.getJSON(utils.getFile(defaultJSONpath));
defaultJson.topSites.forEach(function(site) {
if (site.iconPath) {
var file = utils.getFile(this.appDir.path, 'build', site.iconPath);
var icon = utils.getFileAsDataURI(file);
site.iconUri = icon;
delete site.iconPath;
}
}.bind(this));
var file =
utils.getFile(this.stageDir.path, 'js', 'inittopsites.json');
utils.writeContent(file,
utils.getDistributionFileContent('topsites', defaultJson,
this.distDirPath));
};
BrowserAppBuilder.prototype.enableFirefoxSync = function(options) {
var fileList = {
process: [
['index.html'],
['js', 'awesomescreen.js'],
['js', 'bookmarkStore.js'],
['js', 'historyStore.js'],
['js', 'browser_dialog.js'],
['js', 'index.js'],
['js', 'settings.js'],
['js', 'toolbar.js'],
['style', 'settings.css'],
['style', 'browser_dialog.css']
],
remove: [
['js', 'sync', 'bookmarks.js'],
['js', 'sync', 'db.js'],
['js', 'sync', 'ds_helper.js'],
['js', 'sync', 'history.js'],
['js', 'sync', 'manager_bridge.js'],
['js', 'sync', 'settings.js'],
['js', 'sync', 'toolbar.js'],
['test', 'unit', 'sync', 'manager_bridge_test.js'],
['test', 'unit', 'sync', 'settings_test.js'],
['test', 'unit', 'sync', 'toolbar_test.js']
]
};
preprocessor.execute(options, 'FIREFOX_SYNC', fileList);
};
BrowserAppBuilder.prototype.execute = function(options) {
this.setOptions(options);
this.initJSON();
this.initTopsitesJSON();
this.enableFirefoxSync(options);
};
exports.execute = function(options) {
utils.copyToStage(options);
(new BrowserAppBuilder()).execute(options);
};

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

View file

@ -0,0 +1,14 @@
{
"topSites": [
{
"title": "Mozilla",
"uri": "http://mozilla.org",
"iconPath": "mozilla.png"
},
{
"title": "Firefox OS",
"uri": "http://mozilla.org/firefoxos",
"iconPath": "firefox.png"
}
]
}

View file

@ -0,0 +1,58 @@
.fte-container {
position: fixed;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.75);
text-align: center;
z-index: 1000;
color: #fff;
opacity: 1;
transition: opacity 0.5s linear;
}
.fte-container.finish {
opacity: 0;
}
.fte-page {
width: 100%;
height: 100%;
}
.fte-logo {
margin: 7.5rem 0 1.5rem 0;
}
.fte-greeting {
margin: 1.5rem 0 8rem 0;
line-height: 7rem;
font-weight: lighter;
font-style: italic;
font-size: 7rem;
}
.fte-description {
width: 110rem;
margin: 8rem auto 3.5rem auto;
font-weight: lighter;
font-style: italic;
font-size: 3.8rem;
}
.fte-button-container {
position: absolute;
bottom: 10.5rem;
display: flex;
justify-content: center;
width: 100%;
}
.fte-button-container > smart-button {
width: 23.7rem;
font-size: 2.4rem;
padding: 0;
}
.fte-button-container > smart-button:first-child {
margin-right: 1.8rem;
}

View file

@ -0,0 +1,14 @@
<template>
<div id="fte-container" class="fte-container">
<div class="fte-page">
<img class="fte-logo" src="../style/images/firefox_logo.png"></img>
<h1 class="fte-greeting" data-l10n-id="fte-welcome"></h1>
<p class="fte-description" data-l10n-id="fte-description"></p>
<img class="fte-diagram" src="../style/images/account_image.png"></img>
<div class="fte-button-container">
<smart-button id="fte-skip" class="fte-button" type="rounded-corner-text" data-l10n-id="skip" data-behavior="skip"></smart-button>
<smart-button id="fte-sign-in" class="fte-button fte-finish primary" type="rounded-corner-text" class="primary" data-l10n-id="login" data-behavior="finish"></smart-button>
</div>
</div>
</div>
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

828
gaia/tv_apps/browser/index.html Executable file
View file

@ -0,0 +1,828 @@
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<meta charset="utf-8">
<title>web browser</title>
<!-- building blocks -->
<link rel="stylesheet" type="text/css" href="bower_components/smart-button/style.css">
<link rel="stylesheet" type="text/css" href="bower_components/smart-icons/smart-icons.css">
<!-- Specific style -->
<link rel="stylesheet" type="text/css" href="style/components.css">
<link rel="stylesheet" type="text/css" href="style/index.css">
<link rel="stylesheet" type="text/css" href="style/awesomescreen.css">
<link rel="stylesheet" type="text/css" href="style/authentication_dialog.css">
<link rel="stylesheet" type="text/css" href="style/browser_dialog.css">
<link rel="stylesheet" type="text/css" href="style/toolbar.css">
<link rel="stylesheet" type="text/css" href="style/tooltip.css">
<link rel="stylesheet" type="text/css" href="style/search_result.css">
<link rel="stylesheet" type="text/css" href="style/settings.css">
<link rel="stylesheet" type="text/css" href="style/smartList.css">
<!-- Resources -->
<link rel="resource" type="image/png" href="shared/resources/branding/browser_336.png">
<!-- Localization -->
<script defer="" src="/shared/js/l10n.js"></script>
<!-- L10n properties -->
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="en-US">
<!--IFDEF_FIREFOX_SYNC
<link rel="localization" href="shared/locales/firefox_sync/firefox_sync.{locale}.properties">
ENDIF_FIREFOX_SYNC-->
<link rel="localization" href="/locales/browser.{locale}.properties">
<!-- building blocks -->
<script defer src="bower_components/evt/index.js"></script>
<script defer src="bower_components/smart-button/script.js"></script>
<!-- Shared code -->
<script defer src="shared/js/url_helper.js"></script>
<script defer src="shared/js/utilities.js"></script>
<script defer src="shared/js/smart-screen/connection_manager.js"></script>
<script defer src="shared/js/lazy_loader.js"></script>
<script defer src="shared/js/smart-screen/simple_key_navigation.js"></script>
<script defer src="shared/js/smart-screen/f_t_e_wizard.js"></script>
<!-- Specific code -->
<script defer src="js/resource_loader.js"></script>
<script defer src="js/browser_db.js"></script>
<!--IFDEF_FIREFOX_SYNC
<script defer src="js/sync/db.js"></script>
ENDIF_FIREFOX_SYNC-->
<script defer src="js/index.js"></script>
<script defer src="js/awesomescreen.js"></script>
<script defer src="js/search_util.js"></script>
<script defer src="js/authentication_dialog.js"></script>
<script defer src="js/browser_dialog.js"></script>
<script defer src="js/toolbar.js"></script>
<script defer src="js/tooltip.js"></script>
<script defer src="js/search_result.js"></script>
<script defer src="js/settings.js"></script>
<script defer src="js/connection_handler.js"></script>
<script defer src="js/smartList.js"></script>
<script defer src="js/bookmarkStore.js"></script>
<script defer src="js/historyStore.js"></script>
<script defer src="js/fte.js"></script>
<!-- IAC Handler (put here because it starts sending event immediately) -->
<script defer src="shared/js/iac_handler.js"></script>
<!-- Lazy loaded code -->
<!-- <script src="shared/js/settings_listener.js"><script> -->
<!-- <script src="shared/js/sync/errors.js"></script> -->
<!-- <script src="js/sync/manager_bridge.js"></script> -->
<!-- <script src="js/sync/settings.js"></script> -->
<!-- <script src="js/sync/toolbar.js"></script> -->
<!-- <script src="js/sync/ds_helper.js"></script> -->
<!-- <script src="js/sync/bookmarks.js"></script> -->
<!-- <script src="js/sync/history.js"></script> -->
<!-- Lazy loaded Templates -->
<!-- <link rel="import" href="elements/fte.html"> -->
<!-- <link rel="stylesheet" type="text/css" href="elements/fte.css"> -->
</head>
<body class="page_screen start_page" role="application">
<!-- mask for menu -->
<div id="fade-base"></div>
<!-- side -->
<div id="side-block" class="side-window">
<!-- put widget here -->
</div>
<!-- main -->
<div id="main-block" data-mode="cursor" class="main-window">
<!-- toolbar -->
<ul id="toolbar-panel" data-pvt="false">
<div id="private-bar"></div>
<li id="sidebar-button-block" data-tips="WB_LT_TIPS_CHANGE_SCREEN" data-fade="false">
<div id="sidebar-button" data-sidebar="true">
<!-- XXX: Replace the SVG to icon font -->
<svg id="sidebar-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid slice">
<g id="sidebar-close-svg" visibility="hidden">
<g>
<path class="sidebar-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231F20" d="M36.899,15.805H18.214c-1.968,0-3.716,2.093-3.716,4.061v14.997
c0,1.968,1.747,3.332,3.716,3.332h18.686c1.968,0,3.502-1.364,3.502-3.332V19.866C40.401,17.898,38.867,15.805,36.899,15.805z
M37.578,32.739c0,1.583-1.234,2.68-2.817,2.68H20.265c-1.583,0-2.989-1.097-2.989-2.68V21.849c0-1.583,1.406-3.267,2.989-3.267
h14.497c1.583,0,2.817,1.684,2.817,3.267V32.739z"></path>
<path class="sidebar-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231F20" d="M16.589,22.805h22.4v2.1h-22.4V22.805z"></path>
</g>
</g>
<g id="sidebar-open-svg" visibility="hidden">
<path class="sidebar-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231F20" d="M36.45,15.805H17.764c-1.968,0-3.716,2.093-3.716,4.061v14.997
c0,1.968,1.747,3.332,3.716,3.332H36.45c1.968,0,3.502-1.364,3.502-3.332V19.866C39.952,17.898,38.418,15.805,36.45,15.805z
M37.129,21.849v0.955H23.839v-4.222h10.472C35.895,18.583,37.129,20.266,37.129,21.849z M34.312,35.419H23.839V24.905h13.289
v7.834C37.129,34.322,35.895,35.419,34.312,35.419z"></path>
</g>
</svg>
</div>
</li>
<li id="back-button-block" data-tips="WB_LT_TIPS_HISTORY_BACK">
<div id="back-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="back-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="back-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M39.325,24.619H21.352l6.701-6.701
c-0.38-1.648-0.861-3.185-1.428-4.584L15.46,24.5c-1.385,1.384-1.385,3.628,0,5.012l11.153,11.154
c0.567-1.396,1.048-2.928,1.431-4.57L21.349,29.4h17.976C39.662,27.764,39.661,26.256,39.325,24.619z"></path>
</svg>
</div>
</li>
<li id="forward-button-block" class="disable" data-tips="WB_LT_TIPS_HISTORY_FORWARD">
<div id="forward-button-border">
<div id="forward-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="forward-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="forward-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M38.54,24.5L27.375,13.334
c-0.567,1.399-1.048,2.936-1.429,4.584l6.702,6.701H14.675c-0.338,1.637-0.338,3.145,0,4.781h17.976l-6.695,6.695
c0.383,1.643,0.863,3.174,1.431,4.57L38.54,29.512C39.925,28.128,39.925,25.883,38.54,24.5z"></path>
</svg>
</div>
</div>
</li>
<li id="space-block">
<div></div>
</li>
<li id="addressbar-block">
<form id="address-form">
<input id="private-icon" value="" type="image">
<input id="ssl-indicator" value="" type="image">
<input id="url-input" dir="auto" type="text">
<div class="input-line"></div>
<div id="address-button" data-mode="load" data-tips="WB_LT_TIPS_RELOAD">
<!-- XXX: Replace the SVG to icon font -->
<svg id="address-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<g id="address-reload-svg" visibility="hidden">
<g>
<g>
<path class="address-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M39.701,24.33c-0.953-7.119-7.035-12.616-14.416-12.616
c-8.041,0-14.56,6.52-14.56,14.561c0,8.042,6.519,14.56,14.56,14.56c4.022,0,7.663-1.63,10.298-4.266l-2.231-2.231
c-2.064,2.065-4.916,3.343-8.067,3.343c-6.299,0-11.405-5.107-11.405-11.405c0-6.299,5.106-11.405,11.405-11.405
c5.622,0,10.282,4.071,11.222,9.423c-1.445,0.126-2.765,0.437-3.866,0.883l4.343,4.343c0.539,0.539,1.413,0.539,1.951,0
l4.339-4.338C42.251,24.763,41.03,24.472,39.701,24.33z"></path>
</g>
</g>
</g>
<g id="address-stop-svg" visibility="hidden">
<g>
<g>
<polygon class="address-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" points="37.553,19.225 34.776,16.448 26.983,24.241
19.207,16.465 16.43,19.242 24.206,27.018 16.448,34.776 19.225,37.553 26.983,29.795 34.758,37.57 37.536,34.793 29.76,27.018
"></polygon>
</g>
</g>
</g>
</svg>
</div>
</form>
</li>
<li id="searchbar-block">
<form id="search-form">
<input id="search-favicon" value="" type="image">
<input id="search-input" data-tips="WB_LT_TIPS_SEARCH_BAR" dir="auto" type="text">
<div class="input-line"></div>
<div id="search-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="search-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="search-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M39.774,36.538l-5.462-5.461
c1.128-1.731,1.789-3.793,1.789-6.012c0-6.096-4.943-11.036-11.037-11.036c-6.096,0-11.035,4.94-11.035,11.036
c0,6.093,4.939,11.036,11.035,11.036c2.218,0,4.278-0.661,6.008-1.787l5.462,5.462c0.455,0.454,1.547,0.098,2.442-0.796
C39.87,38.085,40.227,36.992,39.774,36.538z M25.065,32.271c-3.98,0-7.206-3.227-7.206-7.207c0-3.981,3.227-7.208,7.206-7.208
c3.981,0,7.207,3.227,7.207,7.208C32.272,29.045,29.046,32.271,25.065,32.271z"></path>
</svg>
</div>
<div id="search-clear-button"></div>
</form>
<button id="search-close-button" data-l10n-id="LT_NETWORK_STATUS_BTN_CLOSE">close</button>
</li>
<li id="iconbutton-block">
<ul>
<li id="bookmark-button-block" data-tips="WB_LT_TIPS_BOOKMARK_THIS_PAGE">
<div id="bookmark-button" data-isbookmark="false">
<!-- XXX: Replace the SVG to icon font -->
<svg id="bookmark-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<g id="bookmark-off-svg" visibility="hidden">
<g>
<g>
<path class="bookmark-button-path" fill="#1D1D1B" d="M43.116,22.382l-11.238-1.477L27,10.673l-4.878,10.231l-11.238,1.477l8.224,7.801L17.04,41.327l9.96-5.41
l9.96,5.41l-2.068-11.145L43.116,22.382z M27,32.824l-6.404,3.479l1.33-7.165l-5.287-5.016l7.225-0.949L27,16.594l3.136,6.579
l7.226,0.949l-5.287,5.016l1.33,7.165L27,32.824z"></path>
</g>
</g>
</g>
<g id="bookmark-on-svg" visibility="hidden">
<g>
<g>
<polygon class="bookmark-button-path" fill="#00caf2" points="43.116,22.382 31.878,20.904 27,10.673 22.122,20.904 10.884,22.382 19.107,30.183 17.04,41.327
27,35.917 36.96,41.327 34.893,30.183 "></polygon>
</g>
</g>
</g>
</svg>
</div>
</li>
<li id="show-bookmarks-button-block" data-tips="WB_LT_TIPS_SHOW_YOUR_BOOKMARK">
<div id="show-bookmarks-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="show-bookmarks-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="show-bookmarks-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M38.551,18.436h-4.794l-5.571-5.406l-5.581,5.406h-5.156
c-1.205,0-2.181,0.977-2.181,2.181v18.172c0,1.204,0.976,2.181,2.181,2.181h21.102c1.204,0,2.181-0.977,2.181-2.181V20.617
C40.731,19.413,39.755,18.436,38.551,18.436z M21.83,38.06h-4.363v-4.363h4.363V38.06z M21.833,31.521h-4.361v-4.361h4.361
V31.521z M21.833,24.978h-4.361v-4.361h4.361V24.978z M23.991,27.159h14.56v4.361h-14.56V27.159z M38.552,38.06H23.999v-4.363
h14.553V38.06z M38.542,24.968l-14.538,0.007v-4.356h14.548L38.542,24.968z"></path>
</svg>
</div>
</li>
<li id="home-button-block" data-tips="WB_LT_TIPS_HOMEPAGE">
<div id="home-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="home-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="home-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M27.557,19.535l-9.028,8.469
c-0.001,0.026-0.007,0.05-0.007,0.076v10.34c0,1.119,0.935,2.071,2.088,2.071h4.028c-0.188-0.238-0.305-0.534-0.305-0.86v-7.936
c0-0.769,0.623-1.393,1.392-1.393h3.756c0.769,0,1.392,0.624,1.392,1.393v7.936c0,0.326-0.117,0.623-0.305,0.86h4.044
c1.153,0,2.088-0.952,2.088-2.071v-10.34c0-0.014-0.004-0.027-0.004-0.041L27.557,19.535z M40.929,25.98L29.117,14.352
c-0.815-0.791-2.137-0.791-2.953,0L14.352,25.98c-0.418,0.406-0.618,0.94-0.608,1.472c0.013,0,0.024-0.003,0.037-0.003h2.553
l9.863-9.18l-0.035-0.032l0.651-0.541l0.895-0.833l1.373,1.243l-0.033,0.031l9.832,9.312h2.658
C41.547,26.919,41.347,26.385,40.929,25.98z"></path>
</svg>
</div>
</li>
<li id="zoom-button-block" data-tips="WB_LT_TIPS_ZOOM_IN" data-colorbar="red">
<div id="zoom-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="zoom-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="zoom-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M39.982,36.892l-6.505-6.505
c1.148-1.698,1.818-3.746,1.818-5.95c0-5.876-4.764-10.64-10.64-10.64c-5.876,0-10.639,4.764-10.639,10.64
c0,5.876,4.763,10.64,10.639,10.64c2.074,0,4.003-0.603,5.639-1.629l6.063,6.063c0.005,0.203,0.06,0.379,0.184,0.502
c0.438,0.438,1.492,0.095,2.353-0.767c0.755-0.755,1.112-1.657,0.895-2.164L39.982,36.892z M24.656,32.544
c-4.478,0-8.107-3.63-8.107-8.107c0-4.477,3.63-8.108,8.107-8.108c4.477,0,8.108,3.631,8.108,8.108
C32.765,28.914,29.134,32.544,24.656,32.544z M25.766,19.229h-2.09v4.21h-4.212v2.089h4.212v4.192h2.09v-4.192h4.189V23.44
h-4.189V19.229z"></path>
</svg>
</div>
</li>
<li id="tabs-button-block" data-tips="WB_LT_TIPS_TABS" data-tabs="tab01" data-colorbar="yellow">
<div id="tabs-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="tabs-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<g id="tabs-00-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.456,24.899c-1.332,0-1.962,1.188-1.962,4.537
c0,3.385,0.63,4.573,1.962,4.573c1.333,0,1.963-1.152,1.963-4.573C31.419,26.033,30.789,24.899,29.456,24.899z"></path>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M29.456,35.757c-2.845,0-4.339-2.27-4.339-6.32c0-4.033,1.495-6.284,4.339-6.284c2.863,0,4.339,2.251,4.339,6.284
C33.795,33.487,32.319,35.757,29.456,35.757z"></path>
</g>
</g>
<g id="tabs-01-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M31.806,35.486h-2.269v-9.795l-2.701,1.639l-1.008-1.549l3.943-2.412h2.035V35.486z"></path>
</g>
<g id="tabs-02-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M33.057,35.486h-7.544v-1.692c4.141-4.214,5.149-5.348,5.149-7.059c0-1.116-0.702-1.765-1.747-1.765
c-0.954,0-1.584,0.396-2.323,1.279l-1.422-1.117c0.972-1.26,2.25-1.98,3.925-1.98c2.503,0,3.943,1.513,3.943,3.403
c0,2.053-1.17,3.547-4.916,7.112h5.186L33.057,35.486z"></path>
</g>
<g id="tabs-03-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M28.889,35.757c-1.656,0-2.989-0.612-3.925-1.747l1.314-1.242c0.738,0.828,1.531,1.188,2.485,1.188
c1.296,0,2.143-0.774,2.143-2.035c0-1.44-0.81-1.944-2.179-1.944h-0.954l0.27-1.639h0.648c1.135,0,1.945-0.647,1.945-1.836
c0-0.99-0.702-1.585-1.783-1.585c-0.936,0-1.674,0.343-2.431,1.062l-1.17-1.278c1.081-1.026,2.341-1.549,3.799-1.549
c2.503,0,3.835,1.404,3.835,3.079c0,1.513-0.936,2.467-2.358,2.845c1.512,0.145,2.755,1.08,2.755,2.953
C33.282,34.1,31.662,35.757,28.889,35.757z"></path>
</g>
<g id="tabs-04-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.062,1.264-3.062,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M33.967,32.714h-1.459v2.772h-2.25v-2.772h-5.149v-1.585l3.528-7.994l1.963,0.738l-3.097,7.076h2.772l0.234-3.043h1.998v3.043
h1.459V32.714z"></path>
</g>
<g id="tabs-05-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.062,1.264-3.062,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M28.943,35.757c-1.692,0-2.881-0.685-3.817-1.711l1.332-1.242c0.648,0.756,1.423,1.17,2.431,1.17c1.351,0,2.197-0.864,2.197-2.431
c0-1.674-0.828-2.25-1.944-2.25c-0.559,0-1.009,0.126-1.566,0.414h-1.639v-6.338h7.021l-0.27,1.71h-4.537v3.007
c0.611-0.306,1.242-0.45,1.836-0.45c2.017,0,3.476,1.44,3.476,3.907C33.463,34.028,31.68,35.757,28.943,35.757z"></path>
</g>
<g id="tabs-06-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.397-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.861,29.095c-0.828,0-1.584,0.485-2.143,1.242
c0.036,2.593,0.595,3.709,1.98,3.709c1.225,0,1.818-1.062,1.818-2.646C31.518,29.671,30.888,29.095,29.861,29.095z"></path>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M29.699,35.757c-3.097,0-4.375-2.396-4.375-5.87c0-4.051,1.891-6.734,4.97-6.734c1.062,0,1.944,0.307,2.646,0.774l-0.864,1.423
c-0.522-0.307-1.099-0.486-1.746-0.486c-1.531,0-2.503,1.458-2.629,3.889c0.773-0.936,1.692-1.332,2.719-1.332
c1.891,0,3.421,1.368,3.421,3.943C33.841,33.902,32.148,35.757,29.699,35.757z"></path>
</g>
</g>
<g id="tabs-07-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.062,1.264-3.062,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M33.165,24.989l-4.483,10.731l-2.106-0.685l4.213-9.885h-5.113v-1.782h7.49V24.989z"></path>
</g>
<g id="tabs-08-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.366,29.995l-0.558-0.198
c-0.972,0.521-1.458,1.296-1.458,2.395c0,1.152,0.829,1.854,2.125,1.854c1.351,0,2.107-0.774,2.107-1.818
C31.581,31.093,30.986,30.589,29.366,29.995z"></path>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.564,28.284l0.342,0.126c0.954-0.594,1.296-1.188,1.296-2.053
c0-0.954-0.594-1.584-1.729-1.584c-1.044,0-1.729,0.54-1.729,1.603C27.745,27.402,28.394,27.852,29.564,28.284z"></path>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M29.42,35.757c-2.719,0-4.394-1.531-4.394-3.493c0-1.477,0.864-2.449,2.305-3.062c-1.296-0.702-1.818-1.674-1.818-2.862
c0-1.98,1.801-3.188,3.961-3.188c2.143,0,3.961,1.081,3.961,3.098c0,1.116-0.63,2.034-1.999,2.736
c1.656,0.738,2.485,1.801,2.485,3.241C33.921,34.208,32.175,35.757,29.42,35.757z"></path>
</g>
</g>
<g id="tabs-09-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M29.411,24.863c-1.17,0-1.908,0.811-1.908,2.358
c0,1.656,0.756,2.305,1.729,2.305c0.828,0,1.53-0.45,2.07-1.242C31.337,25.781,30.761,24.863,29.411,24.863z"></path>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M26.602,35.937l-0.486-1.639c3.025-0.792,4.753-1.963,5.078-4.357c-0.576,0.738-1.422,1.261-2.593,1.261
c-1.873,0-3.403-1.494-3.403-3.943c0-2.539,1.873-4.105,4.213-4.105c2.809,0,4.25,1.909,4.25,4.699
C33.66,32.84,31.463,34.784,26.602,35.937z"></path>
</g>
</g>
<g id="tabs-10-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M33.506,24.899c-1.332,0-1.962,1.188-1.962,4.537
c0,3.385,0.63,4.573,1.962,4.573c1.333,0,1.963-1.152,1.963-4.573C35.469,26.033,34.838,24.899,33.506,24.899z"></path>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M26.648,35.486h-2.269v-9.795l-2.701,1.639l-1.008-1.549l3.943-2.412h2.035V35.486z M33.506,35.757
c-2.845,0-4.339-2.27-4.339-6.32c0-4.033,1.495-6.284,4.339-6.284c2.863,0,4.339,2.251,4.339,6.284
C37.845,33.487,36.369,35.757,33.506,35.757z"></path>
</g>
</g>
<g id="tabs-11-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M27.755,35.486h-2.269v-9.795l-2.701,1.639l-1.008-1.549l3.943-2.412h2.035V35.486z M35.855,35.486h-2.269v-9.795l-2.701,1.639
l-1.008-1.549l3.943-2.412h2.035V35.486z"></path>
</g>
<g id="tabs-12-svg" visibility="hidden">
<g>
<g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M16.515,20.219c0-1.864,1.533-3.718,3.398-3.718h13.679
c-0.176-1.671-1.596-2.786-3.341-2.786H16.746c-1.864,0-3.062,1.264-3.062,3.128v13.504c0,1.739,1.162,3.154,2.83,3.339V20.219z"></path>
</g>
</g>
<path class="tabs-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#231815" d="M36.813,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297
c0,1.968,1.748,3.332,3.716,3.332h14.485c1.969,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.813,18.595z
M27.243,35.486h-2.27v-9.795l-2.7,1.639l-1.009-1.549l3.943-2.412h2.035V35.486z M37.107,35.486h-7.544v-1.692
c4.141-4.214,5.149-5.348,5.149-7.059c0-1.116-0.702-1.765-1.747-1.765c-0.954,0-1.584,0.396-2.322,1.279l-1.423-1.117
c0.973-1.26,2.251-1.98,3.926-1.98c2.502,0,3.942,1.513,3.942,3.403c0,2.053-1.17,3.547-4.915,7.112h5.186L37.107,35.486z"></path>
</g>
</svg>
</div>
</li>
<li id="new-tab-button-block" data-tips="WB_LT_TIPS_OPEN_A_NEW_TAB" data-colorbar="blue">
<div id="new-tab-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="new-tab-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="new-tab-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M33.59,16.501c-0.176-1.671-1.595-2.786-3.341-2.786H16.746
c-1.864,0-3.061,1.264-3.061,3.128v13.504c0,1.738,1.162,3.154,2.83,3.339V20.219c0-1.864,1.534-3.718,3.398-3.718H33.59z
M36.814,18.595H22.328c-1.968,0-3.716,2.094-3.716,4.062v14.297c0,1.968,1.748,3.332,3.716,3.332h14.485
c1.968,0,3.502-1.364,3.502-3.332V22.656C40.315,20.688,38.782,18.595,36.814,18.595z M35.394,30.061
c0,0.499-0.405,1.127-0.904,1.127h-3.981v4.006c0,0.499-0.602,0.904-1.102,0.904h-0.524c-0.5,0-1.189-0.405-1.189-0.904v-4.006
h-4c-0.5,0-0.904-0.628-0.904-1.127v-0.452c0-0.5,0.405-1.232,0.904-1.232h4v-3.982c0-0.5,0.689-0.904,1.189-0.904h0.524
c0.5,0,1.102,0.404,1.102,0.904v3.982h3.981c0.5,0,0.904,0.732,0.904,1.232V30.061z"></path>
</svg>
</div>
</li>
<li id="menu-button-block" data-tips="WB_LT_TIPS_MENU">
<div id="menu-button">
<!-- XXX: Replace the SVG to icon font -->
<svg id="menu-button-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="3 3 47 47" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="menu-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M36.783,16.15H17.217c-0.773,0-1.4,0.626-1.4,1.399v1.422
c0,0.773,0.627,1.4,1.4,1.4h19.566c0.773,0,1.4-0.627,1.4-1.4V17.55C38.183,16.776,37.557,16.15,36.783,16.15z M36.783,24.594
H17.217c-0.773,0-1.4,0.627-1.4,1.4v1.378c0,0.773,0.627,1.399,1.4,1.399h19.566c0.773,0,1.4-0.626,1.4-1.399v-1.378
C38.183,25.221,37.557,24.594,36.783,24.594z M36.783,33.672H17.217c-0.773,0-1.4,0.627-1.4,1.4v1.378
c0,0.773,0.627,1.399,1.4,1.399h19.566c0.773,0,1.4-0.626,1.4-1.399v-1.378C38.183,34.299,37.557,33.672,36.783,33.672z"></path>
</svg>
</div>
<ul id="menu-block">
<div>
<li id="history-block"><a id="history-tab" href="#" data-l10n-id="LT_SE_HISTORY"></a></li>
<li id="pocket-block"><a id="pocket-tab" href="#" data-l10n-id="pocket"></a></li>
<li id="private-window-block"><a id="private-window-tab" href="#" data-l10n-id="WB_LT_TIPS_NEW_PRIVATE_WINDOW"></a></li>
<!--IFDEF_FIREFOX_SYNC
<li id="fxsync-block"><a id="fxsync-tab" href="#" data-l10n-id="fxsync-sign-in-to-sync"></a></li>
ENDIF_FIREFOX_SYNC-->
<li id="settings-block"><a id="settings-tab" href="#" data-l10n-id="LT_APPS_SETTINGS"></a></li>
</div>
</ul>
</li>
</ul>
</li>
</ul>
<!-- bookmark animation -->
<div id="bookmark-button-anime">
<!-- XXX: Replace the SVG to icon font -->
<svg id="bookmark-button-anime-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 54 54" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<polygon class="bookmark-button-path" fill="#00CAF2" points="43.116,22.382 31.878,20.904 27,10.673 22.122,20.904 10.884,22.382 19.107,30.183 17.04,41.327
27,35.917 36.96,41.327 34.893,30.183"></polygon>
</svg>
</div>
<div id="show-bookmarks-button-anime">
<!-- XXX: Replace the SVG to icon font -->
<svg id="show-bookmarks-button-anime-svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 54 54" x="0" y="0" preserveAspectRatio="xMidyMid meet">
<path class="show-bookmarks-button-path" fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M38.551,18.436h-4.794l-5.571-5.406l-5.581,5.406h-5.156
c-1.205,0-2.181,0.977-2.181,2.181v18.172c0,1.204,0.976,2.181,2.181,2.181h21.102c1.204,0,2.181-0.977,2.181-2.181V20.617
C40.731,19.413,39.755,18.436,38.551,18.436z M21.83,38.06h-4.363v-4.363h4.363V38.06z M21.833,31.521h-4.361v-4.361h4.361
V31.521z M21.833,24.978h-4.361v-4.361h4.361V24.978z M23.991,27.159h14.56v4.361h-14.56V27.159z M38.552,38.06H23.999v-4.363
h14.553V38.06z M38.542,24.968l-14.538,0.007v-4.356h14.548L38.542,24.968z"></path>
</svg>
</div>
<!-- web -->
<div id="web-block">
<!--
<iframe mozbrowser remote mozasyncpanzoom mozallowfullscreen id="browser_iframe" scrolling="yes"></iframe>
-->
</div>
<!-- scroll up down -->
<div id="scroll-u"></div>
<div id="scroll-d"></div>
<div id="awesomescreen">
<!-- awesomescreen tabview -->
<section id="tab-view">
<div id="tabview-panels">
<ul id="tab-site-list" role="listbox">
</ul>
</div>
</section>
<section id="top-sites">
<div id="top-default-panels">
<ul id="top-default-list">
<li id="default-top-list-0" class="top-default-item"></li>
<li id="default-top-list-1" class="top-default-item"></li>
<li id="default-top-list-2" class="top-default-item"></li>
<li id="default-top-list-3" class="top-default-item"></li>
<li id="default-top-list-4" class="top-default-item"></li>
<li id="default-top-list-5" class="top-default-item"></li>
<li id="default-top-list-6" class="top-default-item"></li>
<li id="default-top-list-7" class="top-default-item"></li>
<li id="default-top-list-8" class="top-default-item"></li>
</ul>
</div>
<div id="top-panels">
<ul id="top-site-list" role="listbox"></ul>
</div>
</section>
<!-- awesomescreen bookamrk,history-->
<section id="bookmark" tabindex="0"></section>
<section id="history" tabindex="0"></section>
<!-- private window -->
<div id="private-browsing-block">
<div id="private-browsing-panel">
<!-- image logo -->
<div id="private-browsing-img"></div>
<!-- title -->
<h1 id="private-browsing-title" data-l10n-id="WB_LT_SETTINGS_PRIVATE_BROWSING"></h1>
<!-- sum title -->
<h2 id="private-browsing-subtitle" data-l10n-id="WB_LT_PRIVATE_BROWSING_1"></h2>
<!-- info -->
<p class="private-browsing-text-block private-browsing-text" data-l10n-id="WB_LT_PRIVATE_BROWSING_2"></p>
<p class="private-browsing-text-block private-browsing-text" data-l10n-id="WB_LT_PRIVATE_BROWSING_3"></p>
<!-- more info -->
<div id="private-browsing-more-info">
<p class="private-browsing-text" data-l10n-id="WB_LT_PRIVATE_BROWSING_4"></p>
<a id="private-browsing-more-info-link" class="private-browsing-text" target="_blank" data-l10n-id="WB_LT_LEARN_MORE"></a>
</div>
</div>
</div>
<!-- awesomescreen alert dialog -->
<div id="dialog-area">
<div id="bmd-dialog">
<div id="message-area">
<div id="message-title" class="title-area"></div>
<div id="bmtitle-area">
<input id="input-area" value="" size="30" type="text" required pattern="\s*\S.*">
<div class="input-line"></div>
<div id="circle-img"></div>
</div>
<div id="check-area">
<div id="cbox-area">
<div id="check-box">
<div id="check-box-background"></div>
<img id="check-img" src="style/images/check-on.svg">
</div>
<div id="text-area" data-l10n-id="WB_LT_ALSO_PIN_TO_HOME">Also pin to home</div>
</div>
</div>
</div>
<div id="button-area">
<div id="bt-position">
<button class="awesomescreen-primary-button none" id="ok-button" data-l10n-id="LT_WB_OK">OK</button>
<button class="awesomescreen-default-button none" id="pinhome-button" data-l10n-id="LT_PIN_TO_HOME">Pin to HOME</button>
<button class="awesomescreen-default-button none" id="clhistory-button" data-l10n-id="WB_LT_CLEAR_HISTORY">Clear History</button>
<button class="awesomescreen-default-button none" id="remove-button" data-l10n-id="WB_LT_CLEAR_BROWSING_HISTORY">Clear All History</button>
<button class="awesomescreen-default-button none" id="remove-bookmark-button" data-l10n-id="WB_LT_REMOVE">Remove</button>
<button class="awesomescreen-default-button none" id="edit-button" data-l10n-id="LT_CL_EDIT">Edit</button>
<button class="awesomescreen-default-button none" id="sethome-button" data-l10n-id="WB_LT_SET_AS_HOMEPAGE">Set as Homepage</button>
<button class="awesomescreen-danger-button none" id="remove-topsite-button" data-l10n-id="WB_LT_REMOVE_FROM_TOP_SITES">Remove from Top Sites</button>
<button class="awesomescreen-primary-button none" id="rename-confirm-button" data-l10n-id="LT_WB_OK">OK</button>
<button class="awesomescreen-danger-button none" id="remove-confirm-button" data-l10n-id="WB_LT_REMOVE">Remove</button>
<button class="awesomescreen-danger-button none" id="clear-button" data-l10n-id="WB_LT_CLEAR">Clear</button>
<button class="awesomescreen-default-button none" id="cancel-button" data-l10n-id="LT_CANCEL">Cancel</button>
</div>
</div>
</div>
</div>
<div id="dialog-banner-message"></div>
<div id="pointer-img"><img src="style/images/img_cursor_arrow2x.png" width="64" height="64"></div>
<!-- awesomescreen loading -->
<div id="awesome-loading-icon">
<div class="awesomecircle awesomecircle-1"></div>
<div class="awesomecircle awesomecircle-2"></div>
</div>
</div>
<!-- authentication -->
<section id="http-authentication-dialog" role="region" class="hidden skin-organic">
<div id="http-authentication-dialog-wrap">
<div id="http-authentication-header">
<div id="http-authentication-key"></div>
<div id="http-authentication-message"></div>
</div>
<table id="http-authentication-body">
<tbody><tr>
<td><div class="http-authentication-title" data-l10n-id="LT_FILE_SHARING_DEVICE_USER">User</div></td>
<td>
<form class="http-authentication-input" id="http-authentication-username">
<input class="http-authentication-input-area" id="http-authentication-username-area" dir="auto" type="text">
<div class="input-line"></div>
<div id="http-authentication-username-clear" class="special-pointer"></div>
</form>
</td>
</tr>
<tr>
<td><div class="http-authentication-title" data-l10n-id="LT_FILE_SHARING_DEVICE_PASSWORD">Password</div></td>
<td>
<form class="http-authentication-input" id="http-authentication-password">
<input class="http-authentication-input-area" id="http-authentication-password-area" type="password">
<div class="input-line"></div>
<div id="http-authentication-password-clear" class="special-pointer"></div>
</form>
</td>
</tr>
</tbody></table>
<div id="http-authentication-button">
<button class="http-authentication-button" id="http-authentication-ok">
<span data-l10n-id="LT_WB_OK">OK</span>
</button>
<button class="http-authentication-button" id="http-authentication-cancel">
<span data-l10n-id="LT_CANCEL">Cancel</span>
</button>
</div>
</div>
</section>
<!-- dialog -->
<div id="browser-dialog-base" class="hide">
<div id="browser-dialog">
<div id="browser-dialog-title">
<p id="browser-dialog-title-text"></p>
</div>
<div id="browser-dialog-msg">
<p id="browser-dialog-msg-text"></p>
</div>
<form id="browser-dialog-input">
<input id="browser-dialog-input-area">
<div class="input-line"></div>
<div id="browser-dialog-input-clear"></div>
</form>
<div id="browser-dialog-margin"></div>
<div id="browser-dialog-button">
<button class="browser-dialog-button" id="browser-dialog-button2"></button>
<button class="browser-dialog-button" id="browser-dialog-button1"></button>
</div>
</div>
</div>
<!-- settings -->
<div role="region" id="settings-list" class="skin-organic">
<div id="settings-list-area">
<div id="settings-header">
<p id="settings-header-title" data-l10n-id="WB_LT_BROWSER_SETTINGS">Browser Settings</p>
</div>
<div id="settings-list-item">
<div id="settings-homepage">
<p id="settings-homepage-title" data-l10n-id="WB_LT_HOMEPAGE">Homepage</p>
<p id="settings-homepage-name"></p>
</div>
<div id="settings-search-engine">
<p id="settings-search-engine-title" data-l10n-id="WB_LT_SETTINGS_SEARCH_ENGINE">Search Engine</p>
<p id="settings-search-engine-name"></p>
<p id="settings-search-engine-icon"></p>
</div>
<div id="settings-clear-history">
<p id="settings-clear-history-title" data-l10n-id="WB_LT_CLEAR_BROWSING_HISTORY">Clear All History</p>
</div>
<div id="settings-clear-cookie">
<p id="settings-clear-cookie-title" data-l10n-id="WB_LT_CLEAR_STORED_DATA">Clear Cookies and Stored Data</p>
</div>
</div>
<!--IFDEF_FIREFOX_SYNC
<div id="fxsync-area">
<div id="fxsync-header">
<p data-l10n-id="fxsync">Firefox Sync</p>
</div>
<div id="fxsync-disabled">
<smart-button id="fxsync-sign-in-button" type="rounded-corner-text" data-l10n-id="fxsync-sign-in"></smart-button>
<p data-l10n-id="fxsync-sign-in-to-sync-description"></p>
<p data-l10n-id="fxsync-sign-in-to-sync-description-plus"></p>
</div>
<div id="fxsync-enabled" hidden>
<span id="fxsync-signed-in-as" data-l10n-id="fxsync-signed-in-as"></span>
<smart-button id="fxsync-sync-now-button" type="rounded-corner-text" data-l10n-id="fxsync-sync-now"></smart-button>
<smart-button id="fxsync-sign-out-button" type="rounded-corner-text" data-l10n-id="fxsync-sign-out"></smart-button>
<ul class="settings-horizontal-list">
<li class="settings-horizontal-list-item">
<input type="checkbox" id="fxsync-collection-bookmarks" class="settings-list-checkbox"></input>
<label for="fxsync-collection-bookmarks"></label>
<span data-l10n-id="fxsync-collections-bookmarks"></span>
</li>
<li class="settings-horizontal-list-item">
<input type="checkbox" id="fxsync-collection-history" class="settings-list-checkbox"></input>
<label for="fxsync-collection-history"></label>
<span data-l10n-id="fxsync-collections-history"></span>
</li>
</ul>
<p class="fine-print"><a id="fxsync-tos" data-l10n-id="fxsync-tos"></a></p>
<p class="fine-print"><a id="fxsync-privacy" data-l10n-id="fxsync-privacy"></a></p>
</div>
</div>
</div>
ENDIF_FIREFOX_SYNC-->
<div id="settings-dialog" class="hidden">
<div id="settings-dialog-homepage" class="hidden">
<div id="settings-dialog-homepage-header">
<p id="settings-dialog-homepage-header-title" data-l10n-id="WB_LT_SET_AS_HOMEPAGE">Set as Homepage</p>
</div>
<form id="settings-dialog-homepage-input">
<input id="settings-dialog-homepage-input-area" value="" type="url">
<div class="input-line"></div>
<div id="settings-dialog-homepage-clear"></div>
</form>
<button id="settings-dialog-homepage-default" data-l10n-id="WB_LT_RESTORE_TO_DEFAULT">Restore to default</button>
<div id="settings-dialog-homepage-button">
<button id="settings-dialog-homepage-cancel" data-l10n-id="LT_CANCEL">Cancel</button>
<button id="settings-dialog-homepage-ok" data-l10n-id="LT_WB_OK">OK</button>
</div>
</div>
<div id="settings-dialog-search" class="hidden">
<div id="settings-dialog-search-header">
<p id="settings-dialog-search-header-title" data-l10n-id="WB_LT_SETTINGS_SEARCH_ENGINE">Search Engine</p>
</div>
<div id="settings-dialog-search-select">
<div id="settings-dialog-search-select-list"></div>
</div>
<div id="settings-dialog-search-button">
<button id="settings-dialog-search-cancel" data-l10n-id="LT_CANCEL">Cancel</button>
<button id="settings-dialog-search-ok" data-l10n-id="LT_WB_OK">OK</button>
</div>
</div>
</div>
<div id="settings-banner-message"></div>
</div>
<!-- loading -->
<gaia-loading id="loading-icon">
<div class="circle circle-1"></div>
<div class="circle circle-2"></div>
</gaia-loading>
<!-- zoom banner -->
<div id="zoom-banner-message-block">
<div id="zoom-banner-message"></div>
</div>
<!-- pan cursor mode button -->
<div id="mode-button-block" data-mode="cursor" data-tips="WB_LT_PRESS_COLOR_KEY">
<div id="mode-button-panel">
<button id="pan-cursor-button"></button>
<h2 id="mode-button-title" data-l10n-id="WB_LT_CURSOR_MODE">Cursor Mode</h2>
<div id="pan-cursor-button-block" data-colorbar=""></div>
</div>
</div>
<div id="pan-cursor-banner-message" data-l10n-id="WB_LT_SWITCH_PAN_MODE">You have switched to Pan Mode</div>
<!-- search result -->
<div id="search-result">
<div id="search-result-list"></div>
</div>
</div>
<div id="block-r"></div>
<div id="block-l"></div>
<div id="block-d"></div>
<!-- scroll left right -->
<div id="scroll-l"></div>
<div id="scroll-r"></div>
<!-- Tooltip -->
<div id="tooltip-block"></div>
</body>
</html>

View file

@ -0,0 +1,412 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
/* exported AuthenticationDialog */
/**
* Handles mozbrowserusernameandpasswordrequired event and show
* authentication dialog.
* @namespace AuthenticationDialog
*/
var AuthenticationDialog = {
_confirmed: {},
focusElement: [],
focusIndex: {x:0, y:0},
/** Get all DOM elements when inited. */
getAllElements: function ad_getAllElements() {
var toCamelCase = function toCamelCase(str) {
return str.replace(/\-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
};
var elementIDs = [
'http-authentication-dialog',
'http-authentication-username', 'http-authentication-username-clear',
'http-authentication-password', 'http-authentication-password-clear',
'http-authentication-username-area', 'http-authentication-password-area',
'http-authentication-message',
'http-authentication-ok', 'http-authentication-cancel'];
// Loop and add element with camel style name to Modal Dialog attribute.
elementIDs.forEach(function createElementRef(name) {
this[toCamelCase(name)] = document.getElementById(name);
}, this);
},
currentEvents: {},
/** Initialization. Get elements and add listeners. */
init: function ad_init(bindToWindow) {
// Get all elements initially.
this.getAllElements();
this.boundToWindow = bindToWindow || false;
this.httpAuthenticationUsername.addEventListener('submit',
this.dialogInputSubmit.bind(this));
this.httpAuthenticationPassword.addEventListener('submit',
this.dialogInputSubmit.bind(this));
this.httpAuthenticationUsernameArea.addEventListener('blur',
this.dialogInputAreaBlur.bind(this));
this.httpAuthenticationPasswordArea.addEventListener('blur',
this.dialogInputAreaBlur.bind(this));
this.httpAuthenticationUsernameArea.addEventListener('focus',
this.dialogUsernameAreaFocus.bind(this));
this.httpAuthenticationPasswordArea.addEventListener('focus',
this.dialogPasswordAreaFocus.bind(this));
this.httpAuthenticationUsernameClear.addEventListener('mouseup',
this.clearUsername.bind(this));
this.httpAuthenticationUsernameClear.addEventListener('keyup',
this.dialogButtonKeyup.bind(this));
this.httpAuthenticationUsernameClear.tabIndex = 0;
this.httpAuthenticationPasswordClear.addEventListener('mouseup',
this.clearPassword.bind(this));
this.httpAuthenticationPasswordClear.addEventListener('keyup',
this.dialogButtonKeyup.bind(this));
this.httpAuthenticationPasswordClear.tabIndex = 0;
this.httpAuthenticationOk.addEventListener('mouseup', this);
this.httpAuthenticationOk.addEventListener('keyup',
this.dialogButtonKeyup.bind(this));
this.httpAuthenticationOk.tabIndex = 0;
this.httpAuthenticationCancel.addEventListener('mouseup', this);
this.httpAuthenticationCancel.addEventListener('keyup',
this.dialogButtonKeyup.bind(this));
this.httpAuthenticationCancel.tabIndex = 0;
},
/**
* Default event handler for mozbrowser event and click event.
* @param {Event} evt
* @param {String} origin Tab ID
*/
handleEvent: function ad_handleEvent(evt, origin) {
switch (evt.type) {
case 'mozbrowserusernameandpasswordrequired':
evt.preventDefault();
this.currentEvents[origin] = evt;
// Show authentication dialog only if
// the frame is currently displayed.
this.show(origin);
break;
case 'mouseup':
if (evt.currentTarget === this.httpAuthenticationCancel) {
this.focusIndex.x = 2;
this.focusIndex.y = 1;
this.cancelHandler();
} else {
this.focusIndex.x = 2;
this.focusIndex.y = 0;
this.confirmHandler();
}
break;
}
},
/**
* Show dialog and set message.
* @param {String} origin Tab ID
*/
show: function ad_show(origin) {
this.currentOrigin = origin;
var evt = this.currentEvents[origin];
this.httpAuthenticationDialog.classList.remove('hidden');
// XXX: We don't have a better way to detect login failed.
if (this._confirmed[origin]) {
//this.httpAuthenticationMessage.setAttribute('data-l10n-id',
//'the-username-or-password-is-incorrect');
//this.httpAuthenticationMessage.classList.add('error');
} else {
navigator.mozL10n.setAttributes(
this.httpAuthenticationMessage,
'WB_LT_AUTH_MESSAGE',
{
host: evt.detail.host,
msg: evt.detail.realm
}
);
/*
navigator.mozL10n.setAttributes(this.httpAuthenticationMessage,
'http-authentication-message',
{ host: evt.detail.host });
*/
this.httpAuthenticationMessage.classList.remove('error');
}
Browser.switchCursorMode(false);
this.httpAuthenticationUsername.classList.remove('exfocus');
this.httpAuthenticationPassword.classList.remove('exfocus');
this.httpAuthenticationUsername.classList.remove('input');
this.httpAuthenticationPassword.classList.remove('input');
this.httpAuthenticationUsernameArea.value = '';
this.httpAuthenticationPasswordArea.value = '';
this.httpAuthenticationUsernameArea.focus();
this.focusElement[0] = [];
this.focusElement[0][0] = this.httpAuthenticationUsernameArea;
this.focusElement[0][1] = this.httpAuthenticationUsernameClear;
this.focusElement[1] = [];
this.focusElement[1][0] = this.httpAuthenticationPasswordArea;
this.focusElement[1][1] = this.httpAuthenticationPasswordClear;
this.focusElement[2] = [];
this.focusElement[2][0] = this.httpAuthenticationOk;
this.focusElement[2][1] = this.httpAuthenticationCancel;
this.focusIndex.x = 0;
this.focusIndex.y = 0;
},
/** Hide authentication dialog. */
hide: function ad_hide() {
var evt = this.currentEvents[this.currentOrigin];
if (!evt) {
return;
}
this.httpAuthenticationDialog.classList.add('hidden');
this.currentOrigin = null;
},
clearUsername: function ad_clearUsername() {
this.focusIndex.x = 0;
this.focusIndex.y = 1;
this.httpAuthenticationUsernameArea.value = '';
},
clearPassword: function ad_clearPassword() {
this.focusIndex.x = 1;
this.focusIndex.y = 1;
this.httpAuthenticationPasswordArea.value = '';
},
dialogButtonKeyup: function ad_dialogButtonKeyup(evt) {
if( evt ) evt.preventDefault();
switch( evt.keyCode ) {
case KeyEvent.DOM_VK_RETURN :
var elm = document.activeElement;
elm.classList.remove('active');
var dEvt = document.createEvent("MouseEvents");
dEvt.initMouseEvent("mouseup", true, true, window, 0, 0, 0, 0, 0,
false, false, false, false, 0, elm);
elm.dispatchEvent( dEvt );
break;
}
},
dialogInputSubmit: function ad_dialogInputSubmit(evt) {
if( evt ) evt.preventDefault();
this.dialogInputAreaBlur(evt);
},
dialogInputAreaBlur: function ad_dialogInputBlur(evt) {
if(Awesomescreen.pointerImg.style.display == 'none') {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
}
if (evt.currentTarget === this.httpAuthenticationUsernameArea) {
this.focusIndex.x = 0;
this.focusIndex.y = 0;
this.httpAuthenticationUsername.classList.remove('input');
this.httpAuthenticationUsername.classList.add('exfocus');
} else if(evt.currentTarget === this.httpAuthenticationPasswordArea) {
this.focusIndex.x = 1;
this.focusIndex.y = 0;
this.httpAuthenticationPassword.classList.remove('input');
this.httpAuthenticationPassword.classList.add('exfocus');
}
Awesomescreen.focusImgFunc(this.focusElement[this.focusIndex.x][this.focusIndex.y]);
},
dialogUsernameAreaFocus: function ad_dialogUsernameAreaFocus(evt) {
if( evt ) evt.preventDefault();
this.httpAuthenticationUsername.classList.add('input');
this.httpAuthenticationUsername.classList.remove('exfocus');
},
dialogPasswordAreaFocus: function ad_dialogPasswordAreaFocus(evt) {
if( evt ) evt.preventDefault();
this.httpAuthenticationPassword.classList.add('input');
this.httpAuthenticationPassword.classList.remove('exfocus');
},
/**
* When user clicks OK button on authentication dialog,
* authenticate user.
*/
confirmHandler: function ad_confirmHandler() {
var evt = this.currentEvents[this.currentOrigin];
evt.detail.authenticate(this.httpAuthenticationUsernameArea.value,
this.httpAuthenticationPasswordArea.value);
this.httpAuthenticationUsername.classList.remove('exfocus');
this.httpAuthenticationPassword.classList.remove('exfocus');
this.httpAuthenticationDialog.classList.add('hidden');
// To remember we had ever logged in.
this._confirmed[this.currentOrigin] = true;
delete this.currentEvents[this.currentOrigin];
Browser.switchCursorMode(true);
Awesomescreen.pointerImg.style.display = 'none';
},
/**
* When user clicks cancel button on authentication dialog or
* when the user try to escape the dialog with the escape key,
* cancel authentication.
*/
cancelHandler: function ad_cancelHandler() {
var evt = this.currentEvents[this.currentOrigin];
evt.detail.cancel();
if (this._confirmed[this.currentOrigin]) {
delete this._confirmed[this.currentOrigin];
}
delete this.currentEvents[this.currentOrigin];
this.httpAuthenticationUsername.classList.remove('exfocus');
this.httpAuthenticationPassword.classList.remove('exfocus');
this.httpAuthenticationDialog.classList.add('hidden');
Browser.switchCursorMode(true);
Awesomescreen.pointerImg.style.display = 'none';
},
/**
* Check if the specified tab currently has any event.
* @param {String} origin Tab ID
*/
originHasEvent: function(origin) {
return origin in this.currentEvents;
},
/**
* Clear events of the specified tab ID.
* @param {String} origin Tab ID
*/
clear: function ad_clear(origin) {
if (this.currentEvents[origin]) {
delete this.currentEvents[origin];
}
},
/**
* Authentication dialog displays a confirmation.
*/
isDisplayed: function is_displayed() {
return !this.httpAuthenticationDialog.classList.contains('hidden');
},
moveFocus: function ad_moveFocus(xd, yd) {
if(xd < 0 || yd < 0) {
return;
}
if( (!this.focusElement[xd]) || (!this.focusElement[xd][yd]) ) {
return;
}
var opt = null;
var flg = false;
this.focusIndex.x = xd;
this.focusIndex.y = yd;
if(Awesomescreen.pointerImg.style.display == 'none') {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
flg = true;
}
document.activeElement.blur();
if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id == 'http-authentication-username-area') {
this.httpAuthenticationPassword.classList.remove('exfocus');
if( !this.httpAuthenticationUsername.classList.contains('exfocus') ) {
this.httpAuthenticationUsername.classList.add('exfocus');
}
} else if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id == 'http-authentication-username-clear') {
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
this.httpAuthenticationPassword.classList.remove('exfocus');
if( !this.httpAuthenticationUsername.classList.contains('exfocus') ) {
this.httpAuthenticationUsername.classList.add('exfocus');
}
if( flg ) opt = Awesomescreen.DEFAULT_EXCLEAR;
} else if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id == 'http-authentication-password-area') {
this.httpAuthenticationUsername.classList.remove('exfocus');
if( !this.httpAuthenticationPassword.classList.contains('exfocus') ) {
this.httpAuthenticationPassword.classList.add('exfocus');
}
} else if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id == 'http-authentication-password-clear') {
this.httpAuthenticationUsername.classList.remove('exfocus');
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
if( !this.httpAuthenticationPassword.classList.contains('exfocus') ) {
this.httpAuthenticationPassword.classList.add('exfocus');
}
if( flg ) opt = Awesomescreen.DEFAULT_EXCLEAR;
} else {
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
this.httpAuthenticationUsername.classList.remove('exfocus');
this.httpAuthenticationPassword.classList.remove('exfocus');
}
Awesomescreen.focusImgFunc(this.focusElement[this.focusIndex.x][this.focusIndex.y], opt);
},
handleKeyEvent: function ad_handleKeyEvent(evt) {
if( !this.isDisplayed() ) return false;
if( document.activeElement.nodeName == 'INPUT' ) {
return true;
}
switch( evt.keyCode ) {
case KeyEvent.DOM_VK_LEFT :
evt.preventDefault();
this.moveFocus(this.focusIndex.x, this.focusIndex.y-1);
break;
case KeyEvent.DOM_VK_RIGHT :
evt.preventDefault();
this.moveFocus(this.focusIndex.x, this.focusIndex.y+1);
break;
case KeyEvent.DOM_VK_UP :
evt.preventDefault();
if( this.focusIndex.x == 2 ) {
this.moveFocus(this.focusIndex.x-1, 0);
} else {
this.moveFocus(this.focusIndex.x-1, this.focusIndex.y);
}
break;
case KeyEvent.DOM_VK_DOWN :
evt.preventDefault();
if( this.focusIndex.x == 1 ) {
this.moveFocus(this.focusIndex.x+1, 0);
} else {
this.moveFocus(this.focusIndex.x+1, this.focusIndex.y);
}
break;
case KeyEvent.DOM_VK_RETURN :
evt.preventDefault();
if((this.focusIndex.x == 0 && this.focusIndex.y == 0) ||
(this.focusIndex.x == 1 && this.focusIndex.y == 0)) {
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
Awesomescreen.pointerImg.style.display = 'none';
} else {
document.activeElement.classList.remove('active');
document.activeElement.classList.add('active');
}
break;
case KeyEvent.DOM_VK_BACK_SPACE :
this.cancelHandler();
return true;
default :
return false;
}
return true;
}
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,239 @@
/* globals BrowserDB, SyncBrowserDB, SyncManagerBridge */
'use strict';
(function(exports){
//IFDEF_FIREFOX_SYNC
/**
* The root folder id of synced bookmark data.
* @type {String}
*/
const SYNC_BOOKMARK_ROOT_FOLDER_ID = 'places';
/**
* The mobile folder id of synced bookmark data.
* @type {String}
*/
const SYNC_BOOKMARK_MOBILE_FOLDER_ID = 'mobile';
//ENDIF_FIREFOX_SYNC
// TODO: for the live time bookmark data loading task.
var BookmarkStore = {
isSynced: false,
cache: [],
currentFolder : null,
//IFDEF_FIREFOX_SYNC
syncBookmarkRootFolderCache: {
id: SYNC_BOOKMARK_ROOT_FOLDER_ID,
title: '',
type: 'folder',
readOnly: true
},
syncBookmarkMobileFolderCache: {
id: SYNC_BOOKMARK_MOBILE_FOLDER_ID,
title: '',
type: 'folder',
readOnly: true
},
//ENDIF_FIREFOX_SYNC
init: function () {
//IFDEF_FIREFOX_SYNC
var l10nPromises = [];
l10nPromises
.push(navigator.mozL10n.formatValue('fxsync-desktop-bookmarks'));
l10nPromises
.push(navigator.mozL10n.formatValue('fxsync-mobile-bookmarks'));
Promise.all(l10nPromises).then(
([l10nFxsyncDesktopBookmarks, l10nFxsyncMobileBookmarks]) => {
this.syncBookmarkRootFolderCache.title = l10nFxsyncDesktopBookmarks;
this.syncBookmarkMobileFolderCache.title = l10nFxsyncMobileBookmarks;
}
);
//ENDIF_FIREFOX_SYNC
},
reset: function(cb) {
this.cache = [];
this.currentFolder = null;
return new Promise(resolve => {
//IFNDEF_FIREFOX_SYNC
resolve();
//ENDIF_FIREFOX_SYNC
//IFDEF_FIREFOX_SYNC
SyncManagerBridge.getInfo().then(message => {
if (message.state === 'enabled' || message.state === 'syncing') {
this.isSynced = true;
} else {
this.isSynced = false;
}
resolve();
});
//ENDIF_FIREFOX_SYNC
});
},
getByRange: function(start, num, folderId, cb) {
var self = this;
function getCaches(){
var length = (start + num) > self.cache.length ?
(self.cache.length - start) : (start + num);
var result = [];
for(var i = start; i < length; i++) {
result.push(self.cache[i]);
}
return result;
}
if(folderId !== this.currentFolder) {
this.currentFolder = folderId;
this.updateCache().then(() => {
cb(getCaches());
});
} else {
cb(getCaches());
}
},
getByIndex: function(index, folderId, cb) {
var self = this;
function getCache(){
var result = null;
if(index >= 0 && index < self.cache.length) {
result = self.cache[index];
}
return result;
}
if(folderId !== this.currentFolder) {
this.currentFolder = folderId;
this.updateCache().then(() => {
cb(getCache());
});
} else {
cb(getCache());
}
},
//IFDEF_FIREFOX_SYNC
_createRootFolderCache: function () {
var self = this;
// We query remote bookmarks by parentid with
// SYNC_BOOKMARK_ROOT_FOLDER_ID for checking if the indexedDB has
// remote bookmark data. If there is any remote data, we create a
// folder item to show the synced bookmark.
function getPlacesBookmarks() {
return new Promise(resolve => {
SyncBrowserDB.getBookmark({
parentid: SYNC_BOOKMARK_ROOT_FOLDER_ID
}, syncPlacesBookmarks => {
if(syncPlacesBookmarks.length > 0) {
self.cache.push(self.syncBookmarkRootFolderCache);
}
resolve();
});
});
}
// If there is mobile folder and also has children in it,
// create a folder item to show the synced mobile bookmark
function getMobileBookmarks() {
return new Promise(resolve => {
SyncBrowserDB.getBookmark({
parentid: SYNC_BOOKMARK_MOBILE_FOLDER_ID
}, syncMobileBookmarks => {
if(syncMobileBookmarks.length > 0) {
self.cache.push(self.syncBookmarkMobileFolderCache);
}
resolve();
});
});
}
// get bookmark data from origin indexdDB
function getLocalBookmarks() {
return new Promise(resolve => {
BrowserDB.getBookmarks(localBookmarks => {
if(localBookmarks.length > 0) {
self.cache = self.cache.concat(localBookmarks);
}
resolve();
});
});
}
return getPlacesBookmarks()
.then(getMobileBookmarks)
.then(getLocalBookmarks);
},
_updateFolderCache: function () {
return new Promise(resolve => {
SyncBrowserDB.getBookmark({
parentid: this.currentFolder
}, syncBookmark => {
var length = syncBookmark.length,
bookmark = null;
for(var i = 0; i < length; i++) {
bookmark = syncBookmark[i];
//hide mobile folder in sync bookmark folder
if(this.currentFolder === SYNC_BOOKMARK_ROOT_FOLDER_ID &&
bookmark.id === SYNC_BOOKMARK_MOBILE_FOLDER_ID) {
continue;
}
// XXX: Now we only support two types: folder and bookmark.
// And the data from firefox sync can't be modified.
if(bookmark.type === 'folder' && bookmark.title) {
bookmark.readOnly = true;
this.cache.push(bookmark);
} else if (bookmark.type === 'bookmark') {
bookmark.uri = bookmark.bmkUri;
bookmark.readOnly = true;
this.cache.push(bookmark);
}
}
resolve();
});
});
},
//ENDIF_FIREFOX_SYNC
updateCache: function(){
this.cache = [];
return new Promise(resolve => {
if(!this.isSynced) {
// Get bookmark data from origin indexedDB
BrowserDB.getBookmarks(bookmarks => {
this.cache = bookmarks;
resolve();
});
} else {
//IFNDEF_FIREFOX_SYNC
resolve();
//ENDIF_FIREFOX_SYNC
//IFDEF_FIREFOX_SYNC
if(!this.currentFolder) {
this._createRootFolderCache().then(resolve);
} else {
this._updateFolderCache().then(resolve);
}
//ENDIF_FIREFOX_SYNC
}
});
}
};
exports.BookmarkStore = BookmarkStore;
})(window);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,607 @@
/* global Awesomescreen */
/* global Browser */
/* global BrowserDB */
/* global KeyEvent */
/* global Settings */
'use strict';
/**
* The dialog listen to mozbrowsershowmodalprompt event.
* (alert/confirm/prompt)
*
* @namespace Dialog
*/
var BrowserDialog = {
// Dialog Event
dialogEvt: null,
browserDialogInputArea: null,
PORT_MIN: 0,
PORT_MAX: 65535,
focusElement: [],
focusIndex: {x:0, y:0},
defaultFocusIndex: {x:0, y:0},
deferredActions: new Map(),
/** Get all elements when inited. */
getAllElements: function dialog_getAllElements() {
var toCamelCase = function toCamelCase(str) {
return str.replace(/\-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
};
var elementIDs = [
'browser-dialog-base', 'browser-dialog',
'browser-dialog-title', 'browser-dialog-msg',
'browser-dialog-input',
'browser-dialog-input-area',
'browser-dialog-input-clear',
'browser-dialog-button',
'browser-dialog-button1', 'browser-dialog-button2',
];
// Loop and add element with camel style name to Dialog attribute.
elementIDs.forEach(function createElementRef(name) {
this[toCamelCase(name)] = document.getElementById(name);
}, this);
},
/** Initialization. Get DOM elements and add listeners. */
init: function dialog_init() {
// Get all elements initially.
this.getAllElements();
this.browserDialogBase.addEventListener('mouseup',
this.dialogBaseClick.bind(this));
// Add event listener(dialog)
this.browserDialogButton1.addEventListener('mouseup',
this.dialogButton1.bind(this));
this.browserDialogButton1.addEventListener('keyup',
this.dialogButtonKeyup.bind(this));
this.browserDialogButton2.addEventListener('mouseup',
this.dialogButton2.bind(this));
this.browserDialogButton2.addEventListener('keyup',
this.dialogButtonKeyup.bind(this));
this.browserDialogInput.addEventListener('submit',
this.dialogInput.bind(this));
this.browserDialogInputClear.addEventListener('mouseup',
this.dialogInputClear.bind(this));
this.browserDialogInputClear.addEventListener('keyup',
this.dialogButtonKeyup.bind(this));
this.browserDialogInputArea.addEventListener('blur',
this.dialogInputAreaBlur.bind(this));
this.browserDialogInputArea.addEventListener('focus',
this.dialogInputAreaFocus.bind(this));
this.browserDialogInputArea.addEventListener('mousedown',
this.dialogInputAreaClick.bind(this));
},
/**
* create Dialog
*/
createDialog: function dialog_createDialog(type, evt) {
var opt;
this.cancelDialog();
if( evt != null ) {
evt.preventDefault();
}
this.dialogEvt = evt;
this.browserDialogBase.classList.remove('hide');
this.browserDialogBase.dataset.type = type;
var promise;
switch(type) {
case 'del_cookie':
opt = {
title: null,
msg: 'WB_LT_CONFIRM_DELETE_COOKIES',
bt1: 'LT_CANCEL',
bt2: 'WB_LT_CLEAR'
};
break;
case 'clear_history':
opt = {
title: null,
msg: 'WB_LT_CLEAR_ALL_HISTORY',
bt1: 'LT_CANCEL',
bt2: 'WB_LT_CLEAR'
};
break;
case 'max_bookmark':
opt = {
title: null,
msg: 'WB_LT_BOOKMARK_ERROR_1',
bt1: 'LT_CANCEL',
bt2: null
};
break;
case 'alert':
opt = {
title: null,
msg: {raw: evt.detail.message},
bt1: 'LT_WB_OK',
bt2: null
};
break;
case 'prompt':
opt = {
title: null,
msg: {raw: evt.detail.message},
bt1: 'LT_CANCEL',
bt2: 'LT_WB_OK'
};
break;
case 'confirm':
opt = {
title: null,
msg: {raw: evt.detail.message},
bt1: 'LT_CANCEL',
bt2: 'LT_WB_OK'
};
break;
//IFDEF_FIREFOX_SYNC
case 'signout_confirm':
opt = {
title: 'fxsync-confirm-sign-out-title',
msg: 'fxsync-confirm-sign-out-message',
bt1: 'LT_CANCEL',
bt2: 'fxsync-sign-out'
};
var deferred = {};
deferred.promise = new Promise(function(resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
promise = deferred.promise;
this.deferredActions.set('signout_confirm', deferred);
break;
//ENDIF_FIREFOX_SYNC
default:
return;
}
Browser.switchCursorMode(false);
// title
if (opt.title) {
this.browserDialogTitle.setAttribute('data-l10n-id', opt.title);
} else {
this.browserDialogTitle.removeAttribute('data-l10n-id');
this.browserDialogTitle.textContent = '';
}
// msg
if(opt.msg) {
if (typeof opt.msg === 'string') {
this.browserDialogMsg.setAttribute('data-l10n-id', opt.msg);
} else if (opt.msg.hasOwnProperty('raw')) {
this.browserDialogMsg.innerHTML = opt.msg.raw.replace(/\\n/g, '<br>');
}
} else {
this.browserDialogMsg.removeAttribute('data-l10n-id');
this.browserDialogMsg.textContent = '';
}
var countIndex = 0;
this.focusElement = [];
this.focusElement[0] = [];
this.focusElement[1] = [];
this.defaultFocusIndex = {x:0, y:0};
// button1
if(opt.bt1) {
this.browserDialogButton1.blur();
this.browserDialogButton1.setAttribute('data-l10n-id', opt.bt1);
this.browserDialogButton1.dataset.type = type;
this.browserDialogButton1.classList.add('visible');
}
// button2
if(opt.bt2) {
this.browserDialogButton2.blur();
this.browserDialogButton2.setAttribute('data-l10n-id', opt.bt2);
this.browserDialogButton2.dataset.type = type;
this.browserDialogButton2.classList.add('visible');
}
var width = Browser.SCREEN_WIDTH;
if( Browser.sideBlock.dataset.sidebar == 'true' ) {
width -= Browser.SIDE_WINDOW_WIDTH;
}
if(type == 'prompt') {
this.browserDialogInput.style.display = 'block';
this.browserDialogInput.classList.remove('exfocus');
this.browserDialogInput.classList.remove('input');
this.browserDialogInputClear.tabIndex = 0;
countIndex = 0;
this.focusElement[1][countIndex++] = this.browserDialogInputArea;
this.focusElement[1][countIndex++] = this.browserDialogInputClear;
//this.browserDialogInput.insertBefore(this.browserDialogInputArea,
// this.browserDialogInputClear);
this.browserDialogInputArea.type = 'text';
this.browserDialogInputArea.value = '';
} else {
this.browserDialogInput.style.display = 'none';
}
// initialize position
countIndex = 0;
if(opt.bt1 && opt.bt2) {
this.defaultFocusIndex.x = 0;
this.defaultFocusIndex.y = 0;
this.focusIndex.x = 0;
//IFDEF_FIREFOX_SYNC
if(type === 'signout_confirm') {
this.focusIndex.y = 0;
this.focusElement[0][countIndex++] = this.browserDialogButton1;
this.focusElement[0][countIndex++] = this.browserDialogButton2;
} else {
this.focusIndex.y = 1;
this.focusElement[0][countIndex++] = this.browserDialogButton2;
this.focusElement[0][countIndex++] = this.browserDialogButton1;
}
//ENDIF_FIREFOX_SYNC
//IFNDEF_FIREFOX_SYNC
this.focusIndex.y = 1;
this.focusElement[0][countIndex++] = this.browserDialogButton2;
this.focusElement[0][countIndex++] = this.browserDialogButton1;
//ENDIF_FIREFOX_SYNC
} else if(opt.bt1) {
this.defaultFocusIndex.x = 0;
this.defaultFocusIndex.y = 0;
this.focusIndex.x = 0;
this.focusIndex.y = 0;
this.focusElement[0][countIndex++] = this.browserDialogButton1;
}
Awesomescreen.focusImgFunc(
this.focusElement[this.focusIndex.x][this.focusIndex.y]);
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
return promise;
},
isDisplayed: function dialog_isDisplayed() {
return !this.browserDialogBase.classList.contains('hide');
},
dialogBaseClick: function dialog_dialogBaseClick(evt) {
if( evt ) {
evt.stopPropagation();
}
},
dialogButtonKeyup: function dialog_dialogButton1Keyup(evt) {
if( evt ) {
evt.preventDefault();
}
switch( evt.keyCode ) {
case KeyEvent.DOM_VK_RETURN :
var elm = document.activeElement;
elm.classList.remove('active');
var dEvt = document.createEvent('MouseEvents');
dEvt.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0,
false, false, false, false, 0, elm);
elm.dispatchEvent( dEvt );
break;
}
},
completeDeferredAction: function(type, resolution) {
if (!this.deferredActions.has(type)) {
return;
}
this.deferredActions.get(type)[resolution]();
this.deferredActions.delete(type);
},
dialogButton1: function dialog_dialogButton1(evt) {
if (evt) {
evt.preventDefault();
}
var type = this.browserDialogButton1.dataset.type;
switch(type) {
case 'del_cookie':
case 'clear_history':
case 'max_bookmark':
case 'alert':
case 'prompt':
case 'confirm':
case 'signout_confirm':
this.dialogButton2End(evt.currentTarget);
break;
default:
return;
}
this.completeDeferredAction(type, 'reject');
},
dialogButton2End: function dialog_dialogButton2End(target) {
// Animation end event
var end_event = (function() {
target.removeEventListener('transitionend', end_event, false);
BrowserDialog.cancelDialog();
});
target.addEventListener('transitionend', end_event, false);
},
dialogButton2: function dialog_dialogButton2(evt) {
if (evt) {
evt.preventDefault();
}
this.argEvt = evt.currentTarget;
var type = this.browserDialogButton2.dataset.type;
switch (type) {
case 'del_cookie':
var request = navigator.mozApps.getSelf();
request.onsuccess = function() {
var rtn = request.result.clearBrowserData();
rtn.onsuccess = function() {
//BrowserDialog.cancelDialog();
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
};
rtn.onerror = function() {
//BrowserDialog.cancelDialog();
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
Settings.clearCookieFailed();
};
};
request.onerror = function() {
//BrowserDialog.cancelDialog();
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
Settings.clearCookieFailed();
};
break;
case 'clear_history':
//BrowserDB.clearHistory(this.cancelDialog.bind(this));
BrowserDB.clearHistory(
function(){
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
}.bind(this));
Awesomescreen.selectTopSites();
break;
case 'prompt':
if (this.dialogEvt != null) {
this.dialogEvt.detail.returnValue = this.browserDialogInputArea.value;
if (this.dialogEvt.detail.unblock) {
this.dialogEvt.detail.unblock();
}
this.dialogEvt = null;
}
//this.cancelDialog();
this.dialogButton2End(this.argEvt);
break;
case 'confirm':
if (this.dialogEvt != null) {
this.dialogEvt.detail.returnValue = true;
if (this.dialogEvt.detail.unblock) {
this.dialogEvt.detail.unblock();
}
this.dialogEvt = null;
}
//this.cancelDialog();
this.dialogButton2End(this.argEvt);
break;
case 'signout_confirm':
BrowserDialog.dialogButton2End(BrowserDialog.argEvt);
break;
default:
break;
}
this.completeDeferredAction(type, 'resolve');
},
dialogInput: function dialog_dialogInput(evt) {
if( evt ) {
evt.preventDefault();
}
this.browserDialogInputArea.blur();
},
dialogInputAreaFocus: function dialog_dialogInputAreaFocus(evt) {
if( evt ) {
evt.preventDefault();
}
this.browserDialogInput.classList.add('input');
this.browserDialogInput.classList.remove('exfocus');
},
dialogInputClear: function dialog_dialogInputClear() {
this.browserDialogInputArea.value = '';
},
dialogInputAreaKeydown: function dialog_dialogInputKeydown(evt) {
if( evt ) {
evt.preventDefault();
}
switch( evt.keyCode ) {
case KeyEvent.DOM_VK_RETURN :
this.moveFocus(this.focusIndex.x+1, 0);
break;
}
},
dialogInputAreaClick: function dialog_dialogInputAreaClick(evt) {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
this.dialogInputAreaFocus();
this.focusIndex.x = 1;
this.focusIndex.y = 0;
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
Awesomescreen.pointerImg.style.display = 'none';
},
dialogInputAreaBlur: function dialog_dialogInputBlur() {
if(Awesomescreen.pointerImg.style.display == 'none') {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
}
BrowserDialog.browserDialogInput.classList.remove('input');
BrowserDialog.browserDialogInput.classList.add('exfocus');
var self = this;
setTimeout( function() {
self.focusIndex.x = 1;
self.focusIndex.y = 0;
Awesomescreen.focusImgFunc(self.browserDialogInputArea);
}, 1000);
},
/**
* cancel confirm dialog
*/
cancelDialog: function dialog_cancelDialog() {
if( !BrowserDialog.isDisplayed() ) {
return;
}
switch( BrowserDialog.browserDialogButton1.dataset.type ) {
case 'alert':
case 'prompt':
if( BrowserDialog.dialogEvt != null ) {
BrowserDialog.dialogEvt.detail.returnValue = null;
if( BrowserDialog.dialogEvt.detail.unblock ) {
BrowserDialog.dialogEvt.detail.unblock();
}
}
break;
case 'confirm':
if( BrowserDialog.dialogEvt != null ) {
BrowserDialog.dialogEvt.detail.returnValue = false;
if( BrowserDialog.dialogEvt.detail.unblock ) {
BrowserDialog.dialogEvt.detail.unblock();
}
}
break;
}
if( BrowserDialog.dialogEvt != null ) {
BrowserDialog.dialogEvt = null;
}
// hide dialog
if( BrowserDialog.browserDialogInputArea ) {
BrowserDialog.browserDialogInput.classList.remove('exfocus');
BrowserDialog.browserDialogInput.classList.remove('input');
}
BrowserDialog.browserDialogBase.classList.add('hide');
BrowserDialog.browserDialogButton1.classList.remove('visible');
BrowserDialog.browserDialogButton2.classList.remove('visible');
Browser.switchCursorMode(true);
Awesomescreen.pointerImg.style.display = 'none';
},
moveFocus: function dialog_moveFocus(xd, yd) {
if(!this.focusElement[xd]) {
return;
}
if(( xd < 0 || yd < 0 ) || ( !this.focusElement[xd][yd] )) {
if(( !this.browserDialogButton2.classList.contains('visible') ) &&
( Awesomescreen.pointerImg.style.display !== 'block' )) {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
var x = this.defaultFocusIndex.x;
var y = this.defaultFocusIndex.y;
this.focusIndex.x = x;
this.focusIndex.y = y;
Awesomescreen.focusImgFunc(this.focusElement[x][y]);
this.focusElement[x][y].focus();
}
return;
}
this.focusIndex.x = xd;
this.focusIndex.y = yd;
var opt = null;
var flg = false;
if(Awesomescreen.pointerImg.style.display == 'none') {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
flg = true;
}
document.activeElement.blur();
Awesomescreen.focusImgFunc(
this.focusElement[this.focusIndex.x][this.focusIndex.y], opt);
if(this.focusElement[this.focusIndex.x][this.focusIndex.y].nodeName ==
'INPUT') {
if( !this.browserDialogInput.classList.contains('exfocus') ) {
this.browserDialogInput.classList.add('exfocus');
}
} else if(this.focusElement[this.focusIndex.x][this.focusIndex.y].id ==
'browser-dialog-input-clear') {
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
if( !this.browserDialogInput.classList.contains('exfocus') ) {
this.browserDialogInput.classList.add('exfocus');
}
if( flg ) {
opt = Awesomescreen.DEFAULT_EXCLEAR;
}
} else {
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
this.browserDialogInput.classList.remove('exfocus');
}
},
handleKeyEvent: function dialog_handleKeyEvent(evt) {
if( !BrowserDialog.isDisplayed() ) {
return false;
}
if( document.activeElement.nodeName == 'INPUT' ) {
return true;
}
switch( evt.keyCode ) {
case KeyEvent.DOM_VK_LEFT :
evt.preventDefault();
this.moveFocus(this.focusIndex.x, this.focusIndex.y-1);
break;
case KeyEvent.DOM_VK_RIGHT :
evt.preventDefault();
this.moveFocus(this.focusIndex.x, this.focusIndex.y+1);
break;
case KeyEvent.DOM_VK_UP :
evt.preventDefault();
this.moveFocus(this.focusIndex.x+1, 0);
break;
case KeyEvent.DOM_VK_DOWN :
evt.preventDefault();
this.moveFocus(this.focusIndex.x-1, this.defaultFocusIndex.y);
break;
case KeyEvent.DOM_VK_RETURN :
evt.preventDefault();
if(this.focusIndex.x === 1 && this.focusIndex.y === 0) {
this.focusElement[this.focusIndex.x][this.focusIndex.y].focus();
Awesomescreen.pointerImg.style.display = 'none';
} else {
document.activeElement.classList.remove('active');
document.activeElement.classList.add('active');
}
break;
case KeyEvent.DOM_VK_BACK_SPACE :
BrowserDialog.cancelDialog();
return true;
default :
return false;
}
return true;
}
};

View file

@ -0,0 +1,77 @@
/* global ConnectionManager, ConnectionHandler, Browser,
UrlHelper, Awesomescreen */
'use strict';
(function(exports) {
var ConnectionHandler = function() {};
ConnectionHandler.prototype = {
_active: false,
_messageQueue: [],
init: function() {
this._connectionManager = new ConnectionManager();
this._connectionManager.init(['iac-webpage-open']);
this._connectionManager.on('view', this.preHandleMessage.bind(this));
},
preHandleMessage: function(detail) {
if (this._active) {
this._digestMessage();
this.openPage(detail.url);
} else {
this._messageQueue.push(detail);
}
},
_digestMessage: function() {
if (this._messageQueue.length !== 0) {
this._messageQueue.forEach(function(detail){
this.openPage(detail.url);
}.bind(this));
this._messageQueue = [];
}
},
openPage: function(url) {
url = Browser.getUrlFromInput(url);
if (Browser.currentInfo && Browser.currentInfo.url &&
UrlHelper.isURL(Browser.currentInfo.url)) {
var ev = {
detail: {
url: url,
frameElement: null
}
};
Awesomescreen.openNewTab(ev);
} else {
Browser.navigate(url);
}
this.bringToFront();
},
bringToFront: function() {
if (document.visibilityState === 'hidden') {
navigator.mozApps.getSelf().onsuccess = function(evt) {
var app = evt.target.result;
if (app) {
app.launch();
}
};
}
},
activate: function() {
this._active = true;
this._digestMessage();
},
pause: function() {
this._active = false;
},
};
exports.ConnectionHandler = ConnectionHandler;
}(window));
var connectionHandler = new ConnectionHandler();
connectionHandler.init();

76
gaia/tv_apps/browser/js/fte.js Executable file
View file

@ -0,0 +1,76 @@
/* 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/. */
/* global Browser */
/* global FTEWizard */
/* global ResourceLoader */
/* global SyncManagerBridge */
// XXX: If Firefox sign in is available outside of the browser app, we'll have
// to remove or redesign FTE.
'use strict';
(function (exports) {
if (localStorage.getItem('browserFTE.fteskip')) {
return;
}
var FTE = {
fteContainer: null,
fteWizard: null,
onuninit: function () {},
init(options) {
this.onuninit = options.onuninit;
ResourceLoader.loadByName('fte')
.then(node => {
document.body.appendChild(node);
this.fteContainer = document.getElementById('fte-container');
this.fteWizard = new FTEWizard('browserFTE');
this.fteWizard.init({
container: this.fteContainer,
propagateKeyEvent: true,
onfinish: () => {
SyncManagerBridge.enable();
this.uninit();
},
onskip: () => {
this.uninit();
}
});
document.addEventListener('visibilitychange', this);
});
},
handleEvent() {
if (!document.hidden) {
this.fteWizard.focus();
}
},
uninit() {
document.body.removeChild(this.fteContainer);
document.removeEventListener('visibilitychange', this);
ResourceLoader.unloadByName('fte');
this.onuninit();
}
};
exports.FTE = FTE;
exports.FTE.init({onuninit: () => {
Browser.switchCursorMode(true);
delete exports.FTE;
}});
})(window);

View file

@ -0,0 +1,338 @@
/* globals BrowserDB, SyncBrowserDB, SyncManagerBridge */
'use strict';
(function(exports){
const INITIAL_HISTORY_COUNT = 21;
const DAY_IN_MILLI_SECOND = 86400000;
// TODO: for the live time history data loading task.
var HistoryStore = {
isSynced: false,
cache: new Map(),
cacheIndexStartAt: 0,
cacheIndexEndAt: 0,
lastDataIndex: null,
currentFolder: null,
isHandlingCache: false,
cacheEventQueue: [],
reset: function (cb) {
return new Promise(resolve => {
this.cache.clear();
this.cacheIndexStartAt = 0;
this.cacheIndexEndAt = 0;
this.lastDataIndex = null;
this.currentFolder = null;
this.isHandlingCache = false;
this.cacheEventQueue = [];
//IFNDEF_FIREFOX_SYNC
resolve();
//ENDIF_FIREFOX_SYNC
//IFDEF_FIREFOX_SYNC
SyncManagerBridge.getInfo().then(message => {
if (message.state === 'enabled' || message.state === 'syncing') {
this.isSynced = true;
} else {
this.isSynced = false;
}
resolve();
});
//ENDIF_FIREFOX_SYNC
});
},
getByRange: function (start, num, folderId, cb) {
var self = this;
function getCaches(){
var length = (start + num) > self.cache.size ?
(self.cache.size - start) : (start + num);
var result = [];
for(var i = start; i < length; i++) {
result.push(self.cache.get(i));
}
return result;
}
if(folderId !== this.currentFolder) {
this.currentFolder = folderId;
this.fetchCache().then(() => {
cb(getCaches());
});
} else {
cb(getCaches());
}
},
//IFDEF_FIREFOX_SYNC
traverseNextCache: function(cacheIndex) {
return new Promise(resolve => {
var traverse = () =>{
var start = this.cache.get(this.cacheIndexStartAt).timestamp;
SyncBrowserDB.getHistoryTimestamp(start, 'next', false,
timestamp => {
if(timestamp) {
this.updateCacheByNextHistory(timestamp).then(() => {
var history = this.cache.get(cacheIndex);
if(!history) {
traverse();
} else {
resolve(history);
}
});
} else {
resolve(null);
}
}
);
};
traverse();
});
},
//ENDIF_FIREFOX_SYNC
//IFDEF_FIREFOX_SYNC
traversePreviousCache: function(cacheIndex) {
return new Promise(resolve => {
var traverse = () => {
var start = this.cache.get(this.cacheIndexEndAt).timestamp;
SyncBrowserDB.getHistoryTimestamp(start, 'prev', false,
timestamp => {
if(timestamp) {
this.updateCacheByPreviousHistory(timestamp).then(() => {
var history = this.cache.get(cacheIndex);
if(!history) {
traverse();
} else {
resolve(history);
}
});
} else {
this.lastDataIndex = this.cacheIndexEndAt;
resolve(null);
}
}
);
};
traverse();
});
},
//ENDIF_FIREFOX_SYNC
getCache: function(cacheIndex) {
return new Promise(resolve => {
var history = null;
//IFNDEF_FIREFOX_SYNC
if(this.cacheIndexStartAt <= cacheIndex <= this.cacheIndexEndAt) {
resolve(null);
} else {
history = this.cache.get(cacheIndex);
resolve(history);
}
//ENDIF_FIREFOX_SYNC
//IFDEF_FIREFOX_SYNC
if(cacheIndex < this.cacheIndexStartAt) {
this.traverseNextCache(cacheIndex).then(history => {
resolve(history);
});
} else if (cacheIndex > this.cacheIndexEndAt) {
if(this.lastDataIndex && cacheIndex > this.lastDataIndex) {
resolve(history);
} else {
this.traversePreviousCache(cacheIndex).then(history => {
resolve(history);
});
}
} else {
history = this.cache.get(cacheIndex);
resolve(history);
}
//ENDIF_FIREFOX_SYNC
});
},
handleCacheEvent: function() {
if(!this.isHandlingCache) {
this.isHandlingCache = true;
var handler = this.cacheEventQueue.shift();
handler().then(() => {
this.isHandlingCache = false;
if(this.cacheEventQueue.length > 0) {
this.handleCacheEvent();
}
});
}
},
getByIndex: function (index, folderId, cb) {
var handler = () => new Promise(resolve => {
if(folderId !== this.currentFolder) {
this.currentFolder = folderId;
this.fetchCache().then(() => {
this.getCache(index).then(history => {
cb(history);
resolve();
});
});
} else {
this.getCache(index).then(history => {
cb(history);
resolve();
});
}
});
this.cacheEventQueue.push(handler);
this.handleCacheEvent();
},
fetchCache: function () {
this.cache.clear();
this.cacheIndexStartAt = 0;
this.cacheIndexEndAt = -1;
this.lastDataIndex = null;
return new Promise( resolve => {
if(!this.isSynced) {
// Get bookmark data from origin indexdDB
BrowserDB.getHistory(localHistory => {
localHistory.forEach(history => {
this.cacheIndexEndAt++;
this.cache.set(this.cacheIndexEndAt, history);
});
this.lastDataIndex = this.cacheIndexEndAt;
resolve();
});
} else {
//IFNDEF_FIREFOX_SYNC
resolve();
//ENDIF_FIREFOX_SYNC
//IFDEF_FIREFOX_SYNC
if(!this.currentFolder) {
// XXX: Ideally we should use Promise.all for
// SyncBrowserDB.getBookmark and BrowserDB.getBookmarks.
// But now the implementation of these function are not
// return Promise.
SyncBrowserDB.getHistoryTimestamp(Date.now(), 'prev', true,
timestamp => {
// We query remote history by parentid for checking if
// the indexedDB has remote history data. If there is
// any remote data, we create a folder item to show the
// synced history.
if(timestamp) {
this.cacheIndexEndAt++;
this.cache.set(this.cacheIndexEndAt, {
id: 'sync_history',
title: 'Synced History',
type: 'folder',
readOnly: true
});
}
// get bookmark data from origin indexdDB
BrowserDB.getHistory(localHistory => {
localHistory.forEach(history => {
this.cacheIndexEndAt++;
this.cache.set(this.cacheIndexEndAt, history);
});
this.lastDataIndex = this.cacheIndexEndAt;
resolve();
});
}
);
} else {
SyncBrowserDB.getHistory(INITIAL_HISTORY_COUNT, syncHistory => {
syncHistory.forEach(history => {
// XXX: the data from firefox sync can't be modified.
history.readOnly = true;
this.cacheIndexEndAt++;
this.cache.set(this.cacheIndexEndAt, history);
});
if(syncHistory.length < INITIAL_HISTORY_COUNT) {
this.lastDataIndex = this.cacheIndexEndAt;
}
resolve();
});
}
//ENDIF_FIREFOX_SYNC
}
});
},
//IFDEF_FIREFOX_SYNC
updateCacheByNextHistory: function (timestamp) {
var promise = new Promise(resolve => {
var start = timestamp,
end = timestamp + DAY_IN_MILLI_SECOND;
SyncBrowserDB.getHistoryByTime(start, end, true, true, syncHistory => {
this.cache.clear();
this.cacheIndexEndAt = this.cacheIndexStartAt - 1;
var length = syncHistory.length;
while(length--) {
this.cacheIndexStartAt--;
this.cache.set(this.cacheIndexStartAt, syncHistory[length]);
}
resolve();
});
});
return promise;
},
//ENDIF_FIREFOX_SYNC
//IFDEF_FIREFOX_SYNC
updateCacheByPreviousHistory: function (timestamp) {
var promise = new Promise(resolve => {
var start = timestamp - DAY_IN_MILLI_SECOND,
end = timestamp;
SyncBrowserDB.getHistoryByTime(start, end, true, true, syncHistory => {
this.cache.clear();
this.cacheIndexStartAt = this.cacheIndexEndAt + 1;
syncHistory.forEach(history => {
// XXX: the data from firefox sync can't be modified.
history.readOnly = true;
this.cacheIndexEndAt++;
this.cache.set(this.cacheIndexEndAt, history);
});
resolve();
});
});
return promise;
},
//ENDIF_FIREFOX_SYNC
removeCache: function (index) {
if(this.cacheIndexStartAt <= index <= this.cacheIndexEndAt) {
var value = null;
for(var i = index; i<this.cacheIndexEndAt; i++) {
value = this.cache.get(i+1);
this.cache.set(i, value);
}
this.cache.delete(this.cacheIndexEndAt);
this.cacheIndexEndAt--;
}
}
};
exports.HistoryStore = HistoryStore;
})(window);

1221
gaia/tv_apps/browser/js/index.js Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,136 @@
/* 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/. */
/* HTML files to be loaded should be enclosed by a <template> element.
* <template>
* put HTML content here
* </template>
*/
'use strict';
(function (exports) {
/**
* ResourceLoader is a stand-alone module which provides methods for lazy
* loading HTML, CSS and javascript resources.
*
* TODO: Methods for lazy loading javascript files.
*
* @param {object} options [description]
*/
function ResourceLoader(options) {
this.path = '';
this.init(options);
}
ResourceLoader.prototype = {
constructor: ResourceLoader,
init(options) {
options = options || {};
this.path = options.path || 'elements';
},
/**
* Load HTML and CSS by name.
* @param {string} name [description]
* @return {Promise} The returned Promise is resolved with a Node.
*/
loadByName(name) {
var path = [this.path, name].join('/');
this.loadCss(path);
return this.loadHtml(path);
},
/**
* Load an HTML file from the specified path, the resulted DOM Node can be
* directly appended or modified with DOM API.
* @param {string} path The path of the HTML file, with or without
* html extension.
* @return {Promise} The returned Promise is resolved with a DOM Node.
*/
loadHtml(path) {
if (!path.endsWith('.html')) {
path = [path, 'html'].join('.');
}
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var template = xhr.responseXML.querySelector('template');
var node = document.importNode(template.content, true);
resolve(node);
};
xhr.open('GET', path);
xhr.responseType = 'document';
xhr.overrideMimeType('text/html');
xhr.send();
});
},
/**
* Load an CSS file from the specified path.
* @param {string} path The path of the CSS file, with or without
* css extension.
* @return {Promise}
*/
loadCss(path) {
if (!path.endsWith('.css')) {
path = [path, 'css'].join('.');
}
if (document.querySelector('link[rel=stylesheet][href="' + path + '"]')) {
return;
}
return new Promise((resolve, reject) => {
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = path;
link.addEventListener('load', () => {
resolve();
});
document.head.appendChild(link);
});
},
/**
* Unload a loaded template. We don't cache HTML templates, so we only need
* to remove the loaded link element from document.head.
* @param {string} name The name of the template.
*/
unloadByName(name) {
var path = [this.path, name].join('/');
this.unloadCss(path);
},
/**
* Remove a CSS link element from document.head.
* @param {string} path The path of the CSS file, with or without
* css extension.
*/
unloadCss(path) {
if (!path.endsWith('.css')) {
path = [path, 'css'].join('.');
}
var link =
document.querySelector('link[rel=stylesheet][href="' + path + '"]');
if (link) {
document.head.removeChild(link);
}
}
};
exports.ResourceLoader = new ResourceLoader();
})(window);

View file

@ -0,0 +1,242 @@
/* global _, Browser, BrowserDB, MozActivity */
'use strict';
/**
* Browser app search_result
* @namespace SearchResult
*/
var SearchResult = {
listTemplate: null,
searchResultTemplate: null,
allResult: null,
updateInProgress: false,
pendingUpdateFilter: null,
/**
* Intialise SearchResult.
*/
init: function result_init() {
/* get Element */
this.getAllElements();
/* Event Listener */
window.addEventListener('click', this);
// Create template elements
this.listTemplate = this.createList();
this.searchResultTemplate = this.createSearchTemplate();
},
/**
* CamelCase (xxx-yyy -> xxxYyy)
*/
toCamelCase: function toCamelCase(str) {
return str.replace(/-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
},
/**
* Get All Elements (from id)
*/
getAllElements: function result_getAllElements() {
var elementIDs = [
'search-result', 'search-result-list',
];
// Loop and add element with camel style name to Modal Dialog attribute.
elementIDs.forEach(function createElementRef(name) {
this[this.toCamelCase(name)] = document.getElementById(name);
}, this);
},
show: function result_show() {
this.searchResult.classList.add('visible');
this.handleSearchResultFormInput(null);
},
hide: function result_hide() {
var list = this.searchResultList.firstElementChild;
if( list ) this.searchResultList.removeChild(list);
this.searchResult.classList.remove('visible');
Toolbar.setSearchBarStyle('false');
},
isDisplayed: function result_isDisplayed() {
return this.searchResult.classList.contains('visible');
},
/**
* Submit search form
*/
handleSearchResultFormInput: function result_handleSearchResultFormInput(ev) {
if( ev ) ev.preventDefault();
this.resultUpdate(Toolbar.searchInput.value);
},
handleSearchResultFormSubmit: function result_handleSearchResultFormSubmit(ev) {
if( ev ) ev.preventDefault();
},
createList: function result_createList() {
var list = document.createElement('ul');
list.classList.add('result-box');
return list;
},
createSearchTemplate: function result_createSearchTemplate() {
var template = document.createElement('li');
template.classList.add('result-item');
var link = document.createElement('div');
link.classList.add('result-base');
var title = document.createElement('div');
title.classList.add('result-title');
var url = document.createElement('div');
url.classList.add('result-url');
link.appendChild(title);
link.appendChild(url);
template.appendChild(link);
return template;
},
resultUpdate: function result_resultUpdate(filter) {
// If an update is already in progress enqueue the following ones
if (this.updateInProgress) {
this.pendingUpdateFilter = filter;
return;
} else {
this.updateInProgress = true;
}
this.allResult = [];
BrowserDB.getBookmarks((function(bookmarks) {
this.resultFiltering(filter, bookmarks);
BrowserDB.getHistory((function(history) {
this.resultFiltering(filter, history);
this.populateResults(filter);
this.updateInProgress = false;
var pendingUpdateFilter = this.pendingUpdateFilter;
if (pendingUpdateFilter !== null) {
this.pendingUpdateFilter = null;
this.resultUpdate(pendingUpdateFilter);
}
}).bind(this));
}).bind(this));
},
resultFiltering: function result_resultFiltering(filter, data) {
if(( filter == '' ) || ( data == null )) return;
for( var i = 0 ; i < data.length ; i ++ ) {
var current = data[i];
var matched = false;
if( filter ) {
matched = this.matchesFilter(current.uri, filter) ||
this.matchesFilter(current.title, filter);
}
if( matched || !filter ) {
var j = 0;
for( ; j < this.allResult.length ; j ++ ) {
if( this.allResult[j].uri === current.uri ) {
break;
}
}
if( j == this.allResult.length ) {
this.allResult.push(current);
}
}
}
},
/**
* Check if the URI matches the regular expression filter.
* @param {String} uri
* @param {String} filter
* @returns {Boolean}
*/
matchesFilter: function result_matchesFilter(uri, filter) {
if( !uri ) return false;
return uri.match(new RegExp(filter, 'i')) !== null;
},
populateResults: function result_populateResults(filter) {
var list = this.createList();
var engine = SearchUtil.getCurrentEngineName();
if( engine ) {
var result = this.searchResultTemplate.cloneNode(true);
var title = result.childNodes[0].childNodes[0];
// title.innerHTML = 'Search by ' + engine;
navigator.mozL10n.setAttributes(title, 'WB_LT_TIPS_S_SEARCH', {
value0:engine
});
var uri = result.childNodes[0].childNodes[1];
uri.innerHTML = Browser.getSearchFromInput(filter);
list.appendChild(result);
result.addEventListener('mouseup',
this.handleResultSelect.bind(this));
}
for( var i = 0 ; i < this.allResult.length ; i ++ ) {
var result = this.searchResultTemplate.cloneNode(true);
var title = result.childNodes[0].childNodes[0];
title.innerHTML = this.allResult[i].title;
var uri = result.childNodes[0].childNodes[1];
uri.innerHTML = this.allResult[i].uri;
list.appendChild(result);
result.addEventListener('mouseup',
this.handleResultSelect.bind(this));
}
var oldList = this.searchResultList.firstElementChild;
if (oldList) {
this.searchResultList.replaceChild(list, oldList);
} else {
this.searchResultList.appendChild(list);
}
},
/**
* handle event
*/
handleEvent: function result_handleEvent(ev) {
switch(ev.type) {
case 'click':
break;
default:
break;
}
},
handleResultSelect: function result_handleResultSelect(ev) {
if( !ev ) return;
ev.preventDefault();
if(( ev.target.lastChild.lastChild.textContent != '' ) ||
( ev.target.lastChild.lastChild.textContent != undefined )) {
var url = ev.target.lastChild.lastChild.textContent;
Browser.navigate(url);
this.hide();
}
},
/**
* Handle Key Event.
*/
handleKeyEvent: function result_handleKeyEvent(ev) {
// in the input area focus (= display keyboard)
if(document.activeElement.nodeName == 'INPUT') {
return;
}
switch( ev.keyCode ) {
case KeyEvent.DOM_VK_BACK_SPACE :
this.hide();
break;
}
}
};

View file

@ -0,0 +1,283 @@
'use strict';
var SearchUtil = {
// country code
COUNTRY: 'US',
/* search engine list of the search bar */
ENGINE_LIST: {
'CN': [
{id: 2, name: 'Baidu'},
{id: 1, name: 'Bing'}
],
// default
'US': [
{id: 1, name: 'Bing'},
{id: 0, name: 'Google'}
]
},
// search engine
engineList: null,
searchId: null,
searchIdx: null,
DOMAIN_LIST: {
'US':'com' , 'CA':'ca' , 'DE':'de' , 'IE':'ie' ,
'NL':'nl' , 'HU':'hu' , 'PL':'pl' , 'CZ':'cz' ,
'SK':'sk' , 'IT':'it' , 'GB':'co.uk' , 'SI':'si' ,
'GR':'gr' , 'PT':'pt' , 'DK':'dk' , 'FI':'fi' ,
'HR':'hr' , 'NO':'no' , 'RO':'ro' , 'RU':'ru' ,
'BG':'bg' , 'LT':'lt' , 'YU':'rs' , 'SE':'se' ,
'LV':'lv' , 'EE':'ee' , 'TR':'com.tr' , 'BR':'com.br' ,
'ID':'co.id' , 'CN':'com.hk' , 'TW':'com.tw' , 'HK':'com.hk' ,
'VN':'com.vn' , 'TH':'co.th' , 'SA':'com.sa' , 'FR':'fr' ,
'ES':'es' , 'SG':'com.sg' , 'AU':'com.au' , 'NZ':'co.nz' ,
'IN':'co.in' , 'MY':'com.my' , 'AE':'ae' , 'KW':'com.kw' ,
'MX':'com.mx' , 'AT':'at' , 'BE':'be' , 'CH':'ch' ,
'MT':'com.mt' , 'AD':':' , 'LU':'lu' , 'IS':'is' ,
'UA':'com.ua' , 'KZ':'kz' , 'CIS':'by' , 'UK':'co.uk' ,
'CL':'cl' , 'PE':'com.pe' , 'CO':'com.co' , 'AR':'com.ar' ,
'PA':'com.pa' , 'JP':'co.jp' , 'MK':'mk' , 'MM':'com.mm'
},
// search domain
domainList: null,
/* URL of each search engine */
SEARCH_URL: {
//Google :
0 : [
"http://www.google.DOMAIN/search?ie=UTF-8&oe=UTF-8&q=",
"http://www.google.DOMAIN/search?ie=UTF-8&oe=UTF-8&tbm=isch&q=",
"http://www.google.DOMAIN/search?ie=UTF-8&oe=UTF-8&tbm=vid&q=",
"http://www.google.DOMAIN/search?ie=UTF-8&oe=UTF-8&tbm=nws&q=",
"http://maps.google.com/maps?q=",
"http://www.youtube.DOMAIN/results?search_query="
],
//bing :
1 : [
"http://www.bing.com/search?FORM=PANSSB&PC=PANS&q=",
"http://www.bing.com/images/search?FORM=PANSSB&PC=PANS&q=",
//"http://www.bing.com/videos/search?FORM=PANSSB&PC=PANS&q=",
"http://www.bing.com/?scope=video&FORM=PANSSB&PC=PANS&q=",
"http://www.bing.com/news/search?FORM=PANSSB&PC=PANS&q=",
"http://www.bing.com/maps/?FORM=PANSSB&PC=PANS&q=",
"http://www.bing.com/videos/search?FORM=PANSSB&PC=PANS&q="
],
//Baidu :
2 : [
"http://www.baidu.com/s?word=",
"http://image.baidu.com/i?word=",
"http://video.baidu.com/v?word=",
"http://news.baidu.com/ns?word=",
"http://map.baidu.com/m?word=",
"http://video.baidu.com/v?word="
]
},
searchUrl: null,
// keyword for auto category selection at voice search
SEARCH_CAT: {
//Google :
0 :
[
[ [ "www.google.", "/search", "" ] ],
[ [ "www.google.", "/search", "tbm=isch" ],
[ "www.google.", "/imghp", "" ] ],
[ [ "www.google.", "/search", "tbm=vid" ],
[ "www.google.", "/videohp","" ] ],
[ [ "www.google.", "/search", "tbm=nws" ],
[ "news.google.", "/nwshp", "" ] ],
[ [ "maps.google.", "", "" ],
[ "www.google.", "/maps", "" ] ],
[ [ "www.youtube.", "", "" ] ]
],
//Bing :
1 :
[
[ [ "www.bing.com", "/search", "" ] ],
[ [ "www.bing.com", "/images", "" ],
[ "www.bing.com", "", "scope=images" ] ],
[ [ "www.bing.com", "/videos", "" ],
[ "www.bing.com", "", "scope=video" ] ],
[ [ "www.bing.com", "/news", "" ],
[ "www.bing.com", "", "scope=news" ] ],
[ [ "www.bing.com", "/maps", "" ] ],
[ [ "www.bing.com", "/videos", "" ],
[ "www.bing.com", "", "scope=video" ] ]
],
//Baidu :
2 :
[
[ [ "www.baidu.com", "", "" ] ],
[ [ "image.baidu.com", "", "" ] ],
[ [ "video.baidu.com", "", "" ] ],
[ [ "news.baidu.com", "", "" ] ],
[ [ "map.baidu.com", "", "" ] ],
[ [ "video.baidu.com", "", "" ] ]
]
},
displaySearchEngineName: false,
/**
* init SearchUtil
*/
init: function searchUtil_init(country, cb) {
// set country code
this.COUNTRY = country;
// search engine (default US)
this.engineList = (country in this.ENGINE_LIST)?
this.ENGINE_LIST[country]: this.ENGINE_LIST['US'];
// search domain (default US)
this.domainList = (country in this.DOMAIN_LIST)?
this.DOMAIN_LIST[country]: this.DOMAIN_LIST['US'];
// search URL (default US)
this.searchUrl = this.SEARCH_URL;
var index;
for(index in this.searchUrl[0]){
this.searchUrl[0][index] =
this.searchUrl[0][index].replace(/DOMAIN/, this.domainList);
}
// get all database
this.getAllDatabase(cb);
},
/**
* get all database
*/
getAllDatabase: function searchUtil_getAllDatabase(cb){
// DB get all
this.getSearchEngineId((function(){
this.initDatabase((function(){
if(cb){
cb();
}
}).bind(this))
}).bind(this));
},
// init database
initDatabase: function search_initDatabase(cb){
// default search engine url
if(this.searchId != null){
this.convSearchIdToIdx(this.searchId);
}else{
this.searchIdx = 0;
this.displaySearchEngineName = true;
}
if(cb){
cb();
}
},
// get search engine id
getSearchEngineId: function searchUtil_getSearchEngineId(cb){
BrowserDB.getSetting('SEARCH_ENGINE', (function(result) {
Browser.debug("SEARCH_ENGINE = " + result);
if(result != undefined){
this.searchId = result;
this.convSearchIdToIdx(result);
}
if(cb){
cb(result);
}
}).bind(this));
},
// set search engine id
setSearchEngineId: function searchUtil_setSearchEngineId(id, cb){
this.searchId = id;
this.convSearchIdToIdx(id);
BrowserDB.updateSetting(id, 'SEARCH_ENGINE', cb);
},
// get search engine index
getSearchEngineIdx: function searchUtil_getSearchEngineIdx(cb){
if(this.searchId){
this.convSearchIdToIdx(this.searchId);
if(cb){
cb();
}
}else{
BrowserDB.getSetting('SEARCH_ENGINE', (function(result) {
if(result != undefined){
this.convSearchIdToIdx(result);
}
if(cb){
cb();
}
}).bind(this));
}
},
// Convert the search_id (DB) to search index.
convSearchIdToIdx: function searchUtil_convSearchIdToIdx(search_id) {
var index = null;
for(var i in this.engineList){
if(search_id == this.engineList[i].id){
index = i;
break;
}
}
if(index == null){
// terminated search service of Web. so select default.
this.searchIdx = 0;
this.searchId = this.engineList[0].id;
BrowserDB.updateSetting(undefined, 'SEARCH_ENGINE');
this.displaySearchEngineName = false;
}else{
this.searchIdx = index;
this.displaySearchEngineName = true;
}
},
// Get Search Engine List.
getEngineList: function searchUtil_getEngineList(){
return this.engineList;
},
// Get Current Search Engine List.
getCurrentEngineList: function searchUtil_getCurrentEngineList(){
if(this.searchIdx != null && this.searchIdx < this.engineList.length) {
return this.engineList[this.searchIdx];
}else{
return this.engineList[0];
}
},
// Get Current Search Engine Name.
getCurrentEngineName: function searchUtil_getCurrentEngineName(){
if(this.searchIdx != null && this.searchIdx < this.engineList.length) {
return this.engineList[this.searchIdx].name;
}else{
return this.engineList[0].name;
}
},
// Get Search Id.
getSearchId: function searchUtil_getSearchId(){
return this.searchId;
},
// Get Search Index.
getSearchIdx: function searchUtil_getSearchIdx(){
return this.searchIdx;
},
// Get Search Url.
getSearchUrl: function searchUtil_getSearchUrl(){
return this.searchUrl;
},
// Get Current Search Url.
getCurrentSearchUrl: function searchUtil_getCurrentSearchUrl(){
if(this.searchId != null && (this.searchId in this.searchUrl)){
return this.searchUrl[this.searchId];
}else{
return this.searchUrl[this.engineList[0].id];
}
},
// Get Display Search Engine Name flag.
getDispFlag: function searchUtil_getDispFlag(){
return this.displaySearchEngineName;
},
};

View file

@ -0,0 +1,719 @@
/* exported Settings */
/* global Awesomescreen */
/* global Browser */
/* global BrowserDB */
/* global BrowserDialog */
/* global KeyEvent */
/* global LazyLoader */
/* global SearchUtil */
/* global Toolbar */
/* global UrlHelper */
'use strict';
/**
* Browser app settings panel.
* @namespace Settings
*/
var Settings = {
DEFAULT_HOMEPAGE: 'http://www.mozilla.org',
currentSearchEngine: null,
selectSearchEngine: null,
searchEngineList: null,
focusList: null,
focusPos: 0,
/** Get all DOM elements when inited. */
getAllElements: function settings_getAllElements() {
var toCamelCase = function toCamelCase(str) {
return str.replace(/\-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
};
var elementIDs = [
'settings-list', 'settings-list-area',
'settings-homepage', 'settings-homepage-name',
'settings-search-engine', 'settings-search-engine-name',
'settings-clear-history',
'settings-clear-cookie',
'settings-dialog',
'settings-dialog-homepage',
'settings-dialog-homepage-input',
'settings-dialog-homepage-input-area',
'settings-dialog-homepage-clear',
'settings-dialog-homepage-default',
'settings-dialog-homepage-cancel',
'settings-dialog-homepage-ok',
'settings-dialog-search',
'settings-dialog-search-select',
'settings-dialog-search-select-list',
'settings-dialog-search-cancel',
'settings-dialog-search-ok',
'settings-banner-message'
];
// Loop and add element with camel style name to Modal Dialog attribute.
elementIDs.forEach(function createElementRef(name) {
this[toCamelCase(name)] = document.getElementById(name);
}, this);
},
/**
* Intialise settings panel.
*/
init: function settings_init() {
this.getAllElements();
this.getDefaultHomepage((function(result) {
/* default homepage nothing... */
Browser.debug('default homepage = ' + result);
if(!result) {
console.log('set default homepage...');
this.setDefaultHomepage();
this.setHomepage(this.DEFAULT_HOMEPAGE);
}
}).bind(this));
this.settingsList.addEventListener('mouseup',
this.handleListClick.bind(this));
this.settingsListArea.addEventListener('mouseup',
this.handleListAreaClick.bind(this));
this.settingsDialog.addEventListener('mouseup',
this.handleListAreaClick.bind(this));
this.settingsHomepage.addEventListener('mouseup',
this.handleHomepageClick.bind(this));
this.settingsSearchEngine.addEventListener('mouseup',
this.handleSearchEngineClick.bind(this));
this.settingsClearHistory.addEventListener('mouseup',
this.handleClearHistoryClick.bind(this));
this.settingsClearCookie.addEventListener('mouseup',
this.handleClearCookieClick.bind(this));
this.settingsDialogHomepageInput.addEventListener('submit',
this.handleDialogHomepageInputSubmit.bind(this));
this.settingsDialogHomepageClear.addEventListener('mouseup',
this.handleDialogHomepageClear.bind(this));
this.settingsDialogHomepageDefault.addEventListener('mouseup',
this.handleDialogHomepageDefault.bind(this));
this.settingsDialogHomepageCancel.addEventListener('mouseup',
this.handleDialogHomepageCancel.bind(this));
this.settingsDialogHomepageOk.addEventListener('mouseup',
this.handleDialogHomepageOk.bind(this));
this.settingsDialogSearchCancel.addEventListener('mouseup',
this.handleDialogSearchCancel.bind(this));
this.settingsDialogSearchOk.addEventListener('mouseup',
this.handleDialogSearchOk.bind(this));
this.settingsDialogHomepageInputArea.addEventListener('focus',
this.handleDialogHomepageInputAreaFocus.bind(this));
this.settingsDialogHomepageInputArea.addEventListener('mousedown',
this.handleDialogHomepageInputAreaClick.bind(this));
this.settingsDialogHomepageInputArea.addEventListener('blur',
this.handleDialogHomepageInputBlur.bind(this));
this.settingsDialogHomepageClear.addEventListener('keyup',
this.handleDialogHomepageClear.bind(this));
this.settingsDialogHomepageDefault.addEventListener('keyup',
this.handleDialogHomepageDefault.bind(this));
this.settingsDialogHomepageCancel.addEventListener('keyup',
this.handleDialogHomepageCancel.bind(this));
this.settingsDialogHomepageOk.addEventListener('keyup',
this.handleDialogHomepageOk.bind(this));
this.settingsDialogSearchCancel.addEventListener('keyup',
this.handleDialogSearchCancel.bind(this));
this.settingsDialogSearchOk.addEventListener('keyup',
this.handleDialogSearchOk.bind(this));
//IFDEF_FIREFOX_SYNC
LazyLoader.load('js/sync/settings.js');
//ENDIF_FIREFOX_SYNC
},
getDefaultHomepage: function settings_getDefaultHomepage(cb) {
BrowserDB.db.open((function() {
BrowserDB.getSetting( 'default_homepage', cb );
}).bind(this));
},
setDefaultHomepage: function settings_setDefaultHomepage() {
BrowserDB.db.open((function() {
BrowserDB.updateSetting( this.DEFAULT_HOMEPAGE, 'default_homepage' );
}).bind(this));
},
getHomepage: function settings_getHomepage(cb) {
BrowserDB.db.open((function() {
BrowserDB.getSetting( 'settings_homepage', cb );
}).bind(this));
},
setHomepage: function settings_setHomepage(url) {
BrowserDB.db.open((function() {
BrowserDB.updateSetting( url, 'settings_homepage' );
}).bind(this));
},
/**
* Show settings panel.
*/
show: function settings_show() {
document.body.classList.add('settings-screen');
this.getHomepage((function(result) {
this.settingsHomepageName.textContent = result;
}).bind(this));
this.settingsSearchEngineName.textContent =
SearchUtil.getCurrentEngineName();
this.currentSearchEngine = this.settingsSearchEngineName.textContent;
this.selectSearchEngine = this.currentSearchEngine;
// XXX: Get Firefox Account sign in status and display it
},
/**
* settings list displays a confirmation.
*/
isDisplayed: function settings_isDisplayed() {
return document.body.classList.contains('settings-screen');
},
/**
* Hide settings panel.
*/
hide: function settings_hide() {
// Browser.switchCursorMode( true );
BrowserDialog.cancelDialog();
this.hideDialogHomepage();
this.hideDialogSearch();
document.body.classList.remove('settings-screen');
},
handleListClick: function settings_handleListClick(evt) {
if( evt ) {
evt.stopPropagation();
}
this.hide();
},
handleListAreaClick: function settings_handleListAreaClick(evt) {
if( evt ) {
evt.stopPropagation();
}
},
focusChange: function settings_focusChange(pos) {
var opt = null;
var flg = false;
if( Awesomescreen.pointerImg.style.display == 'none' ) {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
flg = true;
}
for( var i = 0 ; i < Settings.focusList.length ; i ++ ) {
if( i == pos ) {
if(Settings.focusList[i].nodeName == 'INPUT') {
if( !Settings.settingsDialogHomepageInput
.classList.contains('exfocus') ) {
Settings.settingsDialogHomepageInput.classList.add('exfocus');
}
} else if(Settings.focusList[i].id == 'settings-dialog-homepage-clear'){
Settings.focusList[i].focus();
if( !Settings.settingsDialogHomepageInput
.classList.contains('exfocus') ) {
Settings.settingsDialogHomepageInput.classList.add('exfocus');
}
if( flg ) {
opt = Awesomescreen.DEFAULT_EXCLEAR;
}
} else {
Settings.focusList[i].focus();
Settings.settingsDialogHomepageInput.classList.remove('exfocus');
}
Awesomescreen.focusImgFunc(Settings.focusList[i], opt);
} else {
Settings.focusList[i].blur();
}
}
},
/**
* Handle homepage click.
*/
handleHomepageClick: function settings_handleHomepageClick() {
if( !this.isDialogHomepageDisplayed() ) {
this.focusList = [];
Browser.switchCursorMode(false);
this.settingsDialog.classList.remove('hidden');
this.settingsDialogHomepage.classList.remove('hidden');
this.settingsDialogHomepageInput.classList.remove('input');
this.settingsDialogHomepageInput.classList.remove('exfocus');
this.settingsDialogHomepageInputArea.value =
this.settingsHomepageName.textContent;
this.settingsDialogHomepageInputArea.tabIndex = '0';
this.settingsDialogHomepageClear.tabIndex = '0';
this.focusList.push(this.settingsDialogHomepageInputArea);
this.focusList.push(this.settingsDialogHomepageClear);
this.focusList.push(this.settingsDialogHomepageDefault);
this.focusList.push(this.settingsDialogHomepageOk);
this.focusList.push(this.settingsDialogHomepageCancel);
this.focusPos = this.focusList.length - 1;
this.focusChange(this.focusPos);
}
},
hideDialogHomepage: function settings_hideDialogHomepage() {
if( this.isDialogHomepageDisplayed() ) {
Awesomescreen.pointerImg.style.display = 'none';
this.settingsDialogHomepageInput.classList.remove('input');
this.settingsDialogHomepageInput.classList.remove('exfocus');
this.settingsDialogHomepage.classList.add('hidden');
this.settingsDialog.classList.add('hidden');
Browser.switchCursorMode(true);
}
},
isDialogHomepageDisplayed: function settings_isDialogHomepageDisplayed() {
return !this.settingsDialogHomepage.classList.contains('hidden');
},
handleDialogHomepageInputSubmit:
function settings_handleDialogHomepageInputSubmit(ev) {
if (ev) {
// Can be canceled
ev.preventDefault();
}
var hasScheme =
UrlHelper.hasScheme(this.settingsDialogHomepageInputArea.value);
// No scheme, prepend basic protocol and return
if (!hasScheme) {
this.settingsDialogHomepageInputArea.value =
'http://' + this.settingsDialogHomepageInputArea.value;
}
this.settingsDialogHomepageInputArea.blur();
},
handleDialogHomepageInputAreaFocus:
function settings_handleDialogHomepageInputAreaFocus(evt) {
if( evt ) {
evt.preventDefault();
}
Settings.settingsDialogHomepageInput.classList.add('input');
Settings.settingsDialogHomepageInput.classList.remove('exfocus');
},
handleDialogHomepageInputAreaClick:
function settings_handleDialogHomepageInputAreaClick(evt) {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
Settings.settingsDialogHomepageInput.classList.add('input');
Settings.settingsDialogHomepageInput.classList.remove('exfocus');
Settings.focusPos = 0;
Settings.focusList[Settings.focusPos].focus();
Awesomescreen.pointerImg.style.display = 'none';
},
handleDialogHomepageInputBlur:
function settings_handleDialogHomepageInputBlur(evt) {
if( evt ) {
evt.preventDefault();
}
if( Awesomescreen.pointerImg.style.display == 'none' ) {
Browser.switchCursorMode(true);
Browser.switchCursorMode(false);
}
// Pointer image to the input area(fucusPos=0)
Settings.focusPos = 0;
Settings.settingsDialogHomepageInput.classList.remove('input');
Settings.focusChange(Settings.focusPos);
Awesomescreen.pointerImg.style.display = 'block';
},
handleDialogHomepageClear:
function settings_handleDialogHomepageClear(evt) {
if( evt ) {
evt.preventDefault();
}
if(( evt.type == 'keyup' ) && ( evt.keyCode != KeyEvent.DOM_VK_RETURN )) {
return;
}
if( evt.keyCode != KeyEvent.DOM_VK_RETURN ) {
Awesomescreen.pointerImg.style.display = 'none';
document.activeElement.blur();
document.activeElement.classList.remove('exfocus');
}
document.activeElement.classList.remove('active');
this.settingsDialogHomepageInputArea.value = '';
},
handleDialogHomepageDefault:
function settings_handleDialogHomepageDefault(evt) {
if( evt ) {
evt.preventDefault();
}
if(( evt.type == 'keyup' ) && ( evt.keyCode != KeyEvent.DOM_VK_RETURN )) {
return;
}
if( evt.keyCode != KeyEvent.DOM_VK_RETURN ) {
Awesomescreen.pointerImg.style.display = 'none';
document.activeElement.blur();
document.activeElement.classList.remove('exfocus');
}
document.activeElement.classList.remove('active');
this.getDefaultHomepage((function(result) {
this.settingsDialogHomepageInputArea.value = result;
}).bind(this));
},
handleDialogHomepageCancel:
function settings_handleDialogHomepageCancel(evt) {
if( evt ) {
evt.preventDefault();
}
if(( evt.type == 'keyup' ) && ( evt.keyCode != KeyEvent.DOM_VK_RETURN )) {
return;
}
// animation end event handler
var end_event = (function() {
Settings.settingsDialogHomepageCancel.removeEventListener('transitionend',
end_event, false);
Settings.hideDialogHomepage();
});
// end animantion event
Settings.settingsDialogHomepageCancel.addEventListener('transitionend',
end_event, false);
document.activeElement.classList.remove('active');
},
handleDialogHomepageOk: function settings_handleDialogHomepageOk(evt) {
if( evt ) {
evt.preventDefault();
}
if(( evt.type == 'keyup' ) && ( evt.keyCode != KeyEvent.DOM_VK_RETURN )) {
return;
}
// animation end event handler
var end_event = (function() {
Settings.settingsDialogHomepageOk.removeEventListener('transitionend',
end_event, false);
Settings.hideDialogHomepage();
});
// end animantion event
Settings.settingsDialogHomepageOk.addEventListener('transitionend',
end_event, false);
document.activeElement.classList.remove('active');
this.setHomepage(this.settingsDialogHomepageInputArea.value);
this.settingsHomepageName.textContent =
this.settingsDialogHomepageInputArea.value;
},
/**
* Handle search engine click.
*/
handleSearchEngineClick: function settings_handleSearchEngineClick() {
if( !this.isDialogSearchDisplayed() ) {
this.focusList = [];
Browser.switchCursorMode(false);
this.settingsDialog.classList.remove('hidden');
this.settingsDialogSearch.classList.remove('hidden');
this.selectSearchEngine = this.currentSearchEngine;
this.searchEngineList = SearchUtil.getEngineList();
for(var i in this.searchEngineList) {
var title = document.createElement('div');
title.classList.add('title');
var name = document.createElement('p');
name.classList.add('name');
name.textContent = this.searchEngineList[i].name;
title.appendChild(name);
var check = document.createElement('div');
check.classList.add('check');
var onoff = document.createElement('p');
if( name.textContent == this.currentSearchEngine ) {
name.dataset.display = 'selected';
onoff.classList.add('on');
} else {
name.dataset.display = '';
onoff.classList.add('off');
}
check.appendChild(onoff);
var item = document.createElement('div');
item.classList.add('item');
item.appendChild(title);
item.appendChild(check);
item.tabIndex = '0';
this.settingsDialogSearchSelectList.appendChild(item);
item.addEventListener('mouseup',
this.handleDialogSearchSelected.bind(this));
item.addEventListener('keyup',
this.handleDialogSearchSelected.bind(this));
this.focusList.push(item);
}
this.focusList.push(this.settingsDialogSearchOk);
this.focusList.push(this.settingsDialogSearchCancel);
this.focusPos = this.focusList.length - 1;
this.focusChange(this.focusPos);
}
},
handleClearHistoryClick: function settings_handleClearHistoryClick() {
BrowserDialog.createDialog('clear_history', null);
},
handleClearCookieClick: function settings_handleClearCookieClick() {
BrowserDialog.createDialog('del_cookie', null);
},
clearCookieFailed: function settings_clearCookieFailed() {
// this.settingsBannerMessage.innerHTML =
// 'Failed to clear cookies and stored data';
this.settingsBannerMessage.setAttribute('data-l10n-id', 'LT_FAILED_');
this.showBannerMessage();
},
showBannerMessage: function settings_showBannerMessage() {
if( this.settingsBannerMessage.dataset.display != 'visible' ) {
this.settingsBannerMessage.dataset.display = 'visible';
}
clearTimeout( this.showBannerMessageTimeoutID );
this.showBannerMessageTimeoutID = setTimeout( function() {
Settings.settingsBannerMessage.dataset.display = '';
}, 3000);
},
hideDialogSearch: function settings_hideDialogSearch() {
if( this.isDialogSearchDisplayed() ) {
Awesomescreen.pointerImg.style.display = 'none';
var childs = this.settingsDialogSearchSelectList.childNodes;
var len = childs.length;
for(var i = 0 ; i < len ; i ++) {
this.settingsDialogSearchSelectList.removeChild(childs[0]);
}
this.settingsDialogSearch.classList.add('hidden');
this.settingsDialog.classList.add('hidden');
Browser.switchCursorMode(true);
}
},
isDialogSearchDisplayed: function settings_isDialogSearchDisplayed() {
return !this.settingsDialogSearch.classList.contains('hidden');
},
handleDialogSearchSelected:
function settings_handleDialogSearchSelected(ev) {
if( ev ) {
ev.preventDefault();
}
if(( ev.type == 'keyup' ) && ( ev.keyCode != KeyEvent.DOM_VK_RETURN )) {
return;
}
this.selectSearchEngine = ev.currentTarget.textContent;
var childs = this.settingsDialogSearchSelectList.childNodes;
var len = childs.length;
for(var i = 0 ; i < len ; i ++) {
if(childs[i].textContent == this.selectSearchEngine) {
childs[i].firstChild.firstChild.dataset.display = 'selected';
childs[i].lastChild.lastChild.classList.remove('off');
childs[i].lastChild.lastChild.classList.add('on');
} else {
childs[i].firstChild.firstChild.dataset.display = '';
childs[i].lastChild.lastChild.classList.remove('on');
childs[i].lastChild.lastChild.classList.add('off');
}
}
},
handleDialogSearchCancel: function settings_handleDialogSearchCancel(ev) {
if( ev ) {
ev.preventDefault();
}
if(( ev.type == 'keyup' ) && ( ev.keyCode != KeyEvent.DOM_VK_RETURN )) {
return;
}
// animation end event handler
var end_event = (function() {
Settings.settingsDialogSearchCancel.removeEventListener('transitionend',
end_event, false);
Settings.hideDialogSearch();
});
// end animantion event
Settings.settingsDialogSearchCancel.addEventListener('transitionend',
end_event, false);
document.activeElement.classList.remove('active');
},
handleDialogSearchOk: function settings_handleDialogSearchOk(ev) {
if( ev ) {
ev.preventDefault();
}
if(( ev.type == 'keyup' ) && ( ev.keyCode != KeyEvent.DOM_VK_RETURN )) {
return;
}
// animation end event handler
var end_event = (function() {
Settings.settingsDialogSearchOk.removeEventListener('transitionend',
end_event, false);
Settings.hideDialogSearch();
});
var setSearchEngine = function () {
this.currentSearchEngine = this.selectSearchEngine;
this.settingsSearchEngineName.textContent =
this.currentSearchEngine;
Toolbar.setSearchEngine();
};
// end animantion event
Settings.settingsDialogSearchOk.addEventListener('transitionend',
end_event, false);
if( this.currentSearchEngine != this.selectSearchEngine ) {
for(var i in this.searchEngineList) {
if( this.selectSearchEngine == this.searchEngineList[i].name ) {
SearchUtil.setSearchEngineId(this.searchEngineList[i].id,
setSearchEngine.bind(this));
break;
}
}
}
document.activeElement.classList.remove('active');
},
/**
* Handle Key Event.
*/
handleKeyEvent: function settings_handleKeyEvent(ev) {
if( !Settings.isDisplayed() ) {
return false;
}
// in the input area focus (= display keyboard)
if(document.activeElement.nodeName == 'INPUT') {
return true;
}
switch( ev.keyCode ) {
case KeyEvent.DOM_VK_LEFT :
ev.preventDefault();
if( Settings.isDialogHomepageDisplayed() ) {
if(( Settings.focusPos == 1 ) || ( Settings.focusPos == 4 )) {
Settings.focusPos --;
Settings.focusChange(Settings.focusPos);
}
return true;
}
if( Settings.isDialogSearchDisplayed() ) {
if( Settings.focusPos == Settings.focusList.length - 1 ) {
Settings.focusPos --;
Settings.focusChange(Settings.focusPos);
}
return true;
}
break;
case KeyEvent.DOM_VK_RIGHT :
ev.preventDefault();
if( Settings.isDialogHomepageDisplayed() ) {
if(( Settings.focusPos === 0 ) || ( Settings.focusPos === 3 )) {
Settings.focusPos ++;
Settings.focusChange(Settings.focusPos);
}
return true;
}
if( Settings.isDialogSearchDisplayed() ) {
if( Settings.focusPos == Settings.focusList.length - 2 ) {
Settings.focusPos ++;
Settings.focusChange(Settings.focusPos);
}
return true;
}
break;
case KeyEvent.DOM_VK_UP :
ev.preventDefault();
if( Settings.isDialogHomepageDisplayed() ) {
if(( Settings.focusPos === 2 ) || ( Settings.focusPos === 4 )) {
Settings.focusPos -= 2;
Settings.focusChange(Settings.focusPos);
} else if( Settings.focusPos === 3 ) {
Settings.focusPos --;
Settings.focusChange(Settings.focusPos);
}
return true;
}
if( Settings.isDialogSearchDisplayed() ) {
if( Settings.focusPos > 0 ) {
if( Settings.focusPos >= Settings.focusList.length - 1 ) {
Settings.focusPos = Settings.focusList.length - 3;
} else {
Settings.focusPos --;
}
Settings.focusChange(Settings.focusPos);
}
return true;
}
break;
case KeyEvent.DOM_VK_DOWN :
ev.preventDefault();
if( Settings.isDialogHomepageDisplayed() ) {
if(( Settings.focusPos == 1 ) || ( Settings.focusPos === 2 )) {
Settings.focusPos ++;
Settings.focusChange(Settings.focusPos);
} else if( Settings.focusPos === 0 ) {
Settings.focusPos += 2;
Settings.focusChange(Settings.focusPos);
}
return true;
}
if( Settings.isDialogSearchDisplayed() ) {
if( Settings.focusPos < Settings.focusList.length - 2 ) {
Settings.focusPos ++;
Settings.focusChange(Settings.focusPos);
}
return true;
}
break;
case KeyEvent.DOM_VK_RETURN :
ev.preventDefault();
if( Settings.focusPos === 0 ) {
Settings.focusList[Settings.focusPos].focus();
if( Settings.isDialogHomepageDisplayed() ) {
Awesomescreen.pointerImg.style.display = 'none';
}
}else{
document.activeElement.classList.remove('active');
document.activeElement.classList.add('active');
}
break;
case KeyEvent.DOM_VK_BACK_SPACE :
if( Settings.isDialogHomepageDisplayed() ) {
Settings.hideDialogHomepage();
return true;
}
if( Settings.isDialogSearchDisplayed() ) {
Settings.hideDialogSearch();
return true;
}
Settings.hide();
return true;
default :
return false;
}
return true;
}
};

View file

@ -0,0 +1,941 @@
/* globals KeyEvent */
'use strict';
(function(exports){
/**
* Max number of list item visible when scroll.
* @type {Number}
*/
const MAX_VISIBLE_ITEM = 7;
/**
* Initial list element query number when first render list view
* @type {Number}
*/
const INIT_LIST_ELEMENT_QUERY_NUMBER = 14;
/**
* Max number of list html element can be render.
* @type {Number}
*/
const MAX_LIST_ELEMENT = 21;
/**
* Height of list item element, unit is rem.
* @type {Number}
*/
const LIST_ITEM_HEIGHT = 12;
/**
* 1rem equals to 10px
* @type {Number}
*/
const PER_REM = 10;
/**
* SmartList constructor
* @param {Node} el - Target HTML element of SmartList.
* @param {String} l10nTitle - Smart list title string.
*/
function SmartList(el, l10nTitle){
this.el = el;
this.listEl = null;
this.listPointerEl = null;
this.l10nTitle = l10nTitle;
/**
* Number of list item rendered.
* @type {Number}
*/
this.listItemRenderNum = 0;
/**
* Index of focus element in list.
* @type {Number}
*/
this.focusIndex = 0;
/**
* Index of list item start at.
* @type {Number}
*/
this.listIndexStartAt = 0;
/**
* Index of list item end at.
* @type {Number}
*/
this.listIndexEndAt = 0;
/**
* Index of visible list item start at.
* @type {Number}
*/
this.listVisibleStartAt = 0;
/**
* Current navigation folder.
* @type {String}
*/
this.navState = null;
/**
* Save user browser history.
* @type {Array}
*/
this.navHistory = [];
/**
* list items which in dom tree and been initialized but render with data
* @type {Array}
*/
this.initializedItems = [];
/**
* List item element map
* @type {Object}
*/
this.listItemMap = {};
this.init();
}
SmartList.prototype = {
constructor: SmartList,
/**
* Create smart list dom tree architecture
*/
/**
* <div class="smart-list-container">
* <h2 class="smart-list-title" data-l10n-id={this.l10nTitle}></h2>
* <div class="smart-list-wrap">
* <ul class="smart-list-view"></ul>
* <div class="smart-list-mask"></div>
* <div class="smart-list-pointer"></div>
* </div>
* </div>
*/
init: function() {
var container = document.createElement('div'),
title = document.createElement('h2'),
listWrap = document.createElement('div'),
listView = document.createElement('ul'),
listMask = document.createElement('div'),
listPointer = document.createElement('div');
container.classList.add('smart-list-container');
title.classList.add('smart-list-title');
title.setAttribute('data-l10n-id', this.l10nTitle);
listWrap.classList.add('smart-list-wrap');
listView.classList.add('smart-list-view');
listMask.classList.add('smart-list-mask');
listPointer.classList.add('smart-list-pointer');
container.appendChild(title);
container.appendChild(listWrap);
listWrap.appendChild(listView);
listWrap.appendChild(listMask);
listWrap.appendChild(listPointer);
this.el.classList.add('smart-list');
this.el.addEventListener('keyup', this.handleListKeyUp.bind(this));
this.el.appendChild(container);
this.listEl = listView;
this.listPointerEl = listPointer;
},
/**
* Open smart list.
*/
open: function() {
this.reset();
this.dispatchLoadDataByRange(0, INIT_LIST_ELEMENT_QUERY_NUMBER, null,
(listData) => {
this.el.classList.add('show');
if (listData.length > 0) {
this.render(listData);
var focusEl = this.listItemMap[0];
this.pointerItem(focusEl);
this.focusItem(focusEl);
} else {
this.focusItem(this.el);
}
var event = new Event('open');
this.el.dispatchEvent(event);
}
);
},
/**
* Close smart list.
*/
close: function() {
var event = new Event('close');
this.navState = null;
this.navHistory = [];
this.el.classList.remove('show');
this.el.dispatchEvent(event);
if(this.isPointerDisplay()) {
this.hideListPointer();
}
},
/**
* Reset smart list
*/
reset: function(){
var key = 0;
this.listEl.style.transform = 'translateY(0rem)';
this.focusIndex = 0;
this.listVisibleStartAt = 0;
this.listIndexStartAt = 0;
this.listIndexEndAt = 0;
for (key in this.listItemMap) {
if (this.listItemMap.hasOwnProperty(key)) {
this.resetItem(this.listItemMap[key]);
this.initializedItems.push(this.listItemMap[key]);
delete this.listItemMap[key];
}
}
this.hideListPointer();
},
/**
* Go back to previous folder. If there is no previous folder, close list.
*/
goBack: function() {
if (!this.navState) {
this.close();
} else {
this.navHistory.pop();
this.navState = this.getCurNavHistory();
var folderId = this.navState ? this.navState.folderId : null;
this.reset();
this.switchListView(folderId);
}
},
/**
* Render smart list
* @param {Array} listData - Array of listData.
*/
render: function(listData){
var itemEl = null,
i = 0,
length = listData.length;
for (; i < length; i++) {
/*
* if there are reseted list item, use these to render data,
* else create new html element to render data
*/
if (this.initializedItems.length > 0) {
itemEl = this.initializedItems.pop();
this.updateItem(itemEl, listData[i]);
} else {
itemEl = this.createItem(listData[i]);
this.listEl.appendChild(itemEl);
this.listItemRenderNum++;
}
itemEl.setAttribute('data-index', i);
itemEl.style.transform =
'translateY(' + (LIST_ITEM_HEIGHT * i) + 'rem)';
this.listItemMap[i] = itemEl;
}
this.listIndexEndAt = length - 1;
},
/**
* Is smart list opened.
* @return {Boolean} - true when smart list opened.
*/
isDisplay: function() {
return this.el.classList.contains('show');
},
/**
* Add new list item.
* @param {Number} index - Index of new list item.
* @param {Object} data - The list item data.
*/
addItem: function(index, data) {
if (!data) {
return;
}
var itemEl = null,
transform = 0;
if (index < this.listIndexStartAt) {
// add item to the beginning of the list
if (this.initializedItems.length > 0) {
itemEl = this.initializedItems.pop();
this.updateItem(itemEl, data);
} else if (this.listItemRenderNum === MAX_LIST_ELEMENT){
itemEl = this.listItemMap[this.listIndexEndAt];
this.updateItem(itemEl, data);
delete this.listItemMap[this.listIndexEndAt];
this.listIndexEndAt--;
} else {
itemEl = this.createItem(data);
this.listEl.appendChild(itemEl);
this.listItemRenderNum++;
}
this.listItemMap[index] = itemEl;
itemEl.setAttribute('data-index', index);
transform = (index) * LIST_ITEM_HEIGHT;
itemEl.style.transform =
'translateY(' + transform + 'rem)';
this.listIndexStartAt = index;
} else if (index > this.listIndexEndAt) {
// add item to the end of the list
if (this.initializedItems.length > 0) {
itemEl = this.initializedItems.pop();
this.updateItem(itemEl, data);
} else if (this.listItemRenderNum === MAX_LIST_ELEMENT){
itemEl = this.listItemMap[this.listIndexStartAt];
this.updateItem(itemEl, data);
delete this.listItemMap[this.listIndexStartAt];
this.listIndexStartAt++;
} else {
itemEl = this.createItem(data);
this.listEl.appendChild(itemEl);
this.listItemRenderNum++;
}
this.listItemMap[index] = itemEl;
itemEl.setAttribute('data-index', index);
transform = (index) * LIST_ITEM_HEIGHT;
itemEl.style.transform =
'translateY(' + transform + 'rem)';
this.listIndexEndAt = index;
}
},
/**
* Create List Item.
* @param {Object} data - list item data object
*/
/**
* <li class="list-item">
* <div class="icon-box icon-box-left">
* <div class="icon"></div>
* </div>
* <div class="text-box">
* <span class="title"></span>
* <span class="uri"></span>
* </div>
* <div class="icon-box icon-box-right">
* <div class="icon" data-icon="arrow-right"></div>
* </div>
* </li>
*/
createItem: function(data) {
var itemEl = document.createElement('li'),
iconLeftEl = document.createElement('div'),
iconImgLeftEl = document.createElement('div'),
textEl = document.createElement('div'),
titleEl = document.createElement('span'),
uriEl = document.createElement('span'),
iconRightEl = document.createElement('div'),
iconImgRightEl = document.createElement('div');
itemEl.classList.add('list-item');
itemEl.setAttribute('tabindex', '0');
itemEl.setAttribute('data-type', data.type);
iconLeftEl.classList.add('icon-box', 'icon-box-left');
iconImgLeftEl.classList.add('icon');
iconLeftEl.appendChild(iconImgLeftEl);
textEl.classList.add('text-box');
titleEl.classList.add('title');
uriEl.classList.add('uri');
textEl.appendChild(titleEl);
textEl.appendChild(uriEl);
iconRightEl.classList.add('icon-box', 'icon-box-right');
iconImgRightEl.classList.add('icon');
iconRightEl.appendChild(iconImgRightEl);
itemEl.appendChild(iconLeftEl);
itemEl.appendChild(textEl);
itemEl.appendChild(iconRightEl);
this.updateItemContent({
item: itemEl,
iconImgLeft: iconImgLeftEl,
iconImgRight: iconImgRightEl,
title: titleEl,
uri: uriEl
}, data);
// XXX: delegate these events later
itemEl.
addEventListener('keydown', this.handleItemKeyDown.bind(this));
itemEl.
addEventListener('keyup', this.handleItemKeyUp.bind(this));
itemEl.
addEventListener('mouseup', this.handleItemMouseUp.bind(this));
itemEl.
addEventListener('mouseover', this.handleItemMouseOver.bind(this));
itemEl.
addEventListener('mouseout', this.handleItemMouseOut.bind(this));
return itemEl;
},
/**
* Update list item element content.
* @param {Node} el - HTML list item to update.
* @param {Object} data - list item data object.
*/
updateItem: function(el, data) {
var iconImgLeftEl = el.querySelector('.icon-box-left .icon'),
titleEl = el.querySelector('.title'),
uriEl = el.querySelector('.uri'),
iconImgRightEl = el.querySelector('.icon-box-right .icon');
this.updateItemContent({
item: el,
iconImgLeft: iconImgLeftEl,
iconImgRight: iconImgRightEl,
title: titleEl,
uri: uriEl
}, data);
},
/**
* Update the content of the elements in an given item.
* @param {Object} elems - all elements have to be updated.
* @param {Object} data - list item data object.
*/
updateItemContent: function(elems, data) {
var itemEl = elems.item,
iconImgLeftEl = elems.iconImgLeft,
iconImgRightEl = elems.iconImgRight,
titleEl = elems.title,
uriEl = elems.uri;
if (data.readOnly) {
itemEl.setAttribute('readOnly', data.readOnly);
} else {
itemEl.setAttribute('readOnly', false);
}
if (data.type) {
itemEl.setAttribute('data-type', data.type);
switch (data.type) {
case 'folder':
itemEl.setAttribute('data-folder', data.id);
iconImgLeftEl.style.backgroundImage = '';
iconImgLeftEl.setAttribute('data-icon', 'folder');
titleEl.textContent = data.title;
uriEl.textContent = '';
iconImgRightEl.setAttribute('data-icon', 'arrow-right');
break;
case 'bookmark':
itemEl.setAttribute('data-folder', '');
if (data.iconUri) {
iconImgLeftEl.style.backgroundImage = 'url(' + data.iconUri +')';
iconImgLeftEl.setAttribute('data-icon', '');
} else {
iconImgLeftEl.style.backgroundImage = '';
iconImgLeftEl.setAttribute('data-icon', 'default-fav');
}
titleEl.textContent = data.title ? data.title : data.uri;
uriEl.textContent = data.uri;
iconImgRightEl.setAttribute('data-icon', '');
break;
case 'button':
itemEl.setAttribute('data-folder', '');
iconImgLeftEl.style.backgroundImage = '';
iconImgLeftEl.setAttribute('data-icon', 'arrow-left');
titleEl.textContent = data.title;
uriEl.textContent = '';
iconImgRightEl.setAttribute('data-icon', '');
break;
default:
break;
}
} else {
// The records without "type" will be recognized as history records.
itemEl.setAttribute('data-type', 'bookmark');
itemEl.setAttribute('data-folder', '');
if (data.iconUri) {
iconImgLeftEl.setAttribute('data-icon', '');
iconImgLeftEl.style.backgroundImage = 'url(' + data.iconUri +')';
} else {
iconImgLeftEl.setAttribute('data-icon', 'default-fav');
iconImgLeftEl.style.backgroundImage = '';
}
titleEl.textContent = data.title ? data.title : data.uri;
uriEl.textContent = data.uri;
iconImgRightEl.setAttribute('data-icon', '');
}
},
/**
* Remove the focus list item.
*/
removeFocusItem: function() {
var targetEl = null,
i = 0;
targetEl = this.listItemMap[this.focusIndex];
if (targetEl) {
this.resetItem(targetEl);
this.initializedItems.push(targetEl);
//reindex and update transform of the element behind the focus index
for (i = this.focusIndex; i < this.listIndexEndAt; i++) {
this.listItemMap[i] = this.listItemMap[i+1];
targetEl = this.listItemMap[i];
targetEl.setAttribute('data-index', i);
this.shiftElement(targetEl, (-1 * LIST_ITEM_HEIGHT));
}
delete this.listItemMap[this.listIndexEndAt];
this.listIndexEndAt--;
// if the removed item is the last item of index,
// focus the new last element
if (this.listItemMap[this.focusIndex]) {
this.focusItem(this.listItemMap[this.focusIndex]);
} else if (this.listIndexEndAt !== -1) {
this.focusIndex = this.listIndexEndAt;
this.focusItem(this.listItemMap[this.focusIndex]);
}
var event = new Event('itemRemoved');
this.el.dispatchEvent(event);
}
},
/**
* Focus list item.
* @param {Node} el - HTML list item to focus.
*/
focusItem: function(el) {
el.focus();
},
pointerItem: function(el) {
var pos = el.getBoundingClientRect(),
transposX = (Math.ceil(pos.right) - 120 ) / PER_REM,
transposY = (Math.ceil(pos.bottom) - 40 ) / PER_REM;
this.listPointerEl.style.transform =
'translate(' + transposX + 'rem,' + transposY + 'rem)';
this.showListPointer();
},
/**
* Focus the First visible list item.
*/
focusFirstVisibleItem: function() {
var targetEl = null;
// if there is list item rendered and the current active element not in
// smart list, focus the first visible list item.
if (this.listIndexEndAt !== -1 &&
Array.prototype.indexOf.call(
this.listEl.children,
document.activeElement) === -1) {
this.focusIndex = this.listVisibleStartAt;
targetEl = this.listItemMap[this.focusIndex];
if (targetEl) {
this.pointerItem(targetEl);
this.focusItem(targetEl);
}
}
},
/**
* Reset list item.
* @param {Node} el - HTML list item to focus.
*/
resetItem: function(el) {
var iconImgEl = el.querySelector('.icon-box-left .icon'),
titleEl = el.querySelector('.title'),
uriEl = el.querySelector('.uri');
el.setAttribute('data-index', -1);
el.setAttribute('data-type', '');
el.setAttribute('data-folder', '');
el.setAttribute('readOnly', false);
el.style.transform = '';
iconImgEl.style.backgroundImage = '';
iconImgEl.setAttribute('data-icon', '');
titleEl.textContent = '';
uriEl.textContent = '';
},
/**
* Preload the item out of visible view.
* @param {Number} listIndex - preload list index.
*/
preloadItem: function(listIndex) {
var dataIndex = 0;
if (this.navState) {
if (listIndex === 0) {
var data = this.generateBackButtonData(this.navState.folderTitle);
this.addItem(0, data);
} else {
dataIndex = listIndex - 1;
this.dispatchLoadDataByIndex(
listIndex,
dataIndex,
this.navState.folderId
);
}
} else {
dataIndex = listIndex;
this.dispatchLoadDataByIndex(
listIndex,
dataIndex,
null
);
}
},
/**
* Shift list item transform
* @param {Node} el - list item element to transform
* @param {Nunber} shift - shift transform, unit is rem
*/
shiftElement: function(el, shift) {
var style = el.style.transform.split('('),
transform = parseInt(style[1], 10);
transform += shift;
el.style.transform =
'translateY(' + transform + 'rem) translateZ(0.01px)';
},
/**
* Get Focus list element's index
*/
getFocusItemIndex: function() {
return this.focusIndex;
},
/**
* Get Focus list element's uri content
*/
getFocusItemUri: function() {
var targetEl = this.listItemMap[this.focusIndex];
return targetEl ? targetEl.querySelector('.uri').textContent : null;
},
/**
* Get Focus list element's title content
*/
getFocusItemTitle: function() {
var targetEl = this.listItemMap[this.focusIndex];
return targetEl ? targetEl.querySelector('.title').textContent : null;
},
/**
* Update focus list element's title content
* @param {String} title - title to update
*/
updateFocusItemTitle: function(title) {
var targetEl = this.listItemMap[this.focusIndex];
if (targetEl) {
targetEl.querySelector('.title').textContent = title;
this.focusItem(targetEl);
var event = new Event('itemUpdated');
this.el.dispatchEvent(event);
}
},
isPointerDisplay: function() {
return this.listPointerEl.classList.contains('show');
},
showListPointer: function() {
this.listPointerEl.classList.add('show');
},
hideListPointer: function() {
this.listPointerEl.classList.remove('show');
},
/**
* Add folder id and folder title
* @param {[type]} folderId [description]
* @param {[type]} folderTitle [description]
*/
addNavHistory: function(folderId, folderTitle) {
this.navHistory.push({
folderId: folderId,
folderTitle: folderTitle
});
},
/**
* Get current user browse state.
* @return {String} current user browse folder.
*/
getCurNavHistory: function() {
var len = this.navHistory.length;
if (len > 0) {
return this.navHistory[len - 1];
} else {
return null;
}
},
/**
* Generate back button data.
* @return {Object} - back button data
*/
generateBackButtonData: function(title) {
return {
type: 'button',
title: 'Back to ' + title
};
},
switchListView: function(folderId) {
this.dispatchLoadDataByRange(0, INIT_LIST_ELEMENT_QUERY_NUMBER, folderId,
(listData) => {
// if user is browser list data in a folder,
// add back button data at the first place of listData
if (this.navState) {
listData.unshift(
this.generateBackButtonData(this.navState.folderTitle)
);
}
if (listData.length === 0) {
return;
}
this.render(listData);
// focus the first item in list. if the first item is button and list
// item length is bigger than 1, focus the second item
this.focusIndex = 0;
if (this.navState && this.listIndexEndAt > 0) {
this.focusIndex = 1;
}
var focusEl = this.listItemMap[this.focusIndex];
this.pointerItem(focusEl);
this.focusItem(focusEl);
}
);
},
/**
* Move focus to the previous element
*/
moveFocusIndexUp: function() {
this.focusIndex -= 1;
var preloadItemIndex = this.focusIndex - MAX_VISIBLE_ITEM;
if (preloadItemIndex >= 0 && preloadItemIndex < this.listIndexStartAt) {
this.preloadItem(preloadItemIndex);
}
// if the new focus element is before this.listVisibleStartAt,
// update list item's transform to shift down
var targetEl = this.listItemMap[this.focusIndex];
if (this.focusIndex < this.listVisibleStartAt) {
this.listVisibleStartAt--;
this.shiftElement(this.listEl, LIST_ITEM_HEIGHT);
this.focusItem(targetEl);
} else {
this.pointerItem(targetEl);
this.focusItem(targetEl);
}
},
/**
* Move focus to the next element
*/
moveFocusIndexDown: function() {
this.focusIndex += 1;
// pre-load list item later then listIndexEndAt
var preloadItemIndex = this.focusIndex + MAX_VISIBLE_ITEM;
if (preloadItemIndex > this.listIndexEndAt) {
this.preloadItem(preloadItemIndex);
}
// if the new focus element is after this.listIndexEndAt,
// update list item's transform to shift up
var targetEl = this.listItemMap[this.focusIndex];
if ((this.focusIndex - this.listVisibleStartAt) ===
MAX_VISIBLE_ITEM ) {
this.listVisibleStartAt++;
this.shiftElement(this.listEl, (-1 * LIST_ITEM_HEIGHT));
this.focusItem(targetEl);
} else {
this.pointerItem(targetEl);
this.focusItem(targetEl);
}
},
handleListKeyUp: function(e) {
switch(e.keyCode){
case KeyEvent.DOM_VK_ESCAPE:
case KeyEvent.DOM_VK_BACK_SPACE:
this.goBack();
break;
default:
return;
}
},
handleItemKeyDown: function(e) {
switch(e.keyCode){
case KeyEvent.DOM_VK_UP:
if(this.focusIndex > this.listIndexStartAt) {
this.moveFocusIndexUp();
}
break;
case KeyEvent.DOM_VK_DOWN:
if(this.focusIndex < this.listIndexEndAt) {
this.moveFocusIndexDown();
}
break;
default:
return;
}
},
handleItemKeyUp: function(e) {
switch(e.keyCode){
case KeyEvent.DOM_VK_SUBMENU:
this.handleKeySubmenu(e);
break;
case KeyEvent.DOM_VK_RETURN:
this.handleKeyReturn(e);
break;
default:
return;
}
},
handleItemMouseUp: function(e) {
switch(e.button){
case 0 :
// left click
this.handleKeyReturn(e);
break;
case 2:
//right click
this.handleKeySubmenu(e);
break;
default:
break;
}
},
handleItemMouseOver: function(e) {
var targetEl = e.currentTarget;
if (targetEl) {
var dataIndex = targetEl.getAttribute('data-index');
var event = new CustomEvent('mouseOverItem', {detail: targetEl});
targetEl.focus();
this.focusIndex = parseInt(dataIndex, 10);
this.el.dispatchEvent(event);
}
},
handleItemMouseOut: function(e) {
var targetEl = e.currentTarget;
if (targetEl) {
this.hideListPointer();
targetEl.blur();
}
},
handleKeyReturn: function(e) {
var targetEl = e.currentTarget,
type = targetEl.getAttribute('data-type'),
folderId = null;
switch(type) {
case 'folder':
var folderTitle = targetEl.querySelector('.title').textContent;
folderId = targetEl.getAttribute('data-folder');
this.addNavHistory(folderId, folderTitle);
this.navState = this.getCurNavHistory();
this.reset();
this.switchListView(folderId);
break;
case 'bookmark':
var uriEL = targetEl.querySelector('.uri');
if (uriEL) {
this.dispatchDisplayWebsite(uriEL.textContent);
this.close();
}
break;
case 'button':
this.navHistory.pop();
this.navState = this.getCurNavHistory();
folderId = this.navState ? this.navState.folderId : null;
this.reset();
this.switchListView(folderId);
break;
default:
break;
}
},
handleKeySubmenu: function(e) {
var targetEl = e.currentTarget;
if (targetEl.getAttribute('data-type') === 'bookmark') {
var event = new CustomEvent('showSubMenu', {
detail: {
readOnly: targetEl.getAttribute('readOnly')
}
});
this.el.dispatchEvent(event);
}
},
dispatchDisplayWebsite: function(uri) {
var event = new CustomEvent('displayWebsite', {detail: uri});
this.el.dispatchEvent(event);
},
dispatchLoadDataByRange: function(start, number, folderId, cb) {
var eventDetail = {
startAt: start,
number: number,
folderId: folderId,
callback: cb
};
var event = new CustomEvent('loadDataByRange', {detail: eventDetail});
this.el.dispatchEvent(event);
},
dispatchLoadDataByIndex: function(listIndex, dataIndex, navState) {
var event = new CustomEvent('loadDataByIndex', {
detail: {
listIndex: listIndex,
dataIndex: dataIndex,
folderId: navState
}});
this.el.dispatchEvent(event);
}
};
exports.SmartList = SmartList;
})(window);

View file

@ -0,0 +1,107 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
/* exported SyncBookmark */
/* global SyncBrowserDB */
/* global SyncDsHelper */
var SyncBookmark = (function () {
var syncDataStore;
function updateWrapper(data) {
return new Promise(resolve => {
if (data.deleted) {
SyncBrowserDB.removeBookmark(data.id, () => {
resolve();
});
} else {
SyncBrowserDB.updateRawBookmark(data, () => {
resolve();
});
}
});
}
function handleTask(task) {
return new Promise((resolve, reject) => {
var data = [];
if (task.data && task.data.fxsyncRecords) {
for (var key in task.data.fxsyncRecords) {
data.push(task.data.fxsyncRecords[key]);
}
}
switch(task.operation) {
case 'update':
case 'add':
Promise.all(data.map(updateWrapper)).then(resolve);
break;
case 'clear':
// Ignore clear operation.
resolve();
break;
case 'remove':
getBookmarkByDsId(task.id, bookmark => {
if (bookmark) {
SyncBrowserDB.removeBookmark(bookmark.id, () => {
resolve();
});
}
});
break;
case 'done':
resolve();
break;
}
});
}
function getBookmarkByDsId(dsid, callback) {
SyncBrowserDB.getBookmark({bmkUri: dsid}, bookmark => {
if (bookmark) {
callback(bookmark);
} else {
var specialType =
['query', 'microsummary', 'folder', 'livemark', 'separator'];
var isSpecialType = specialType.some(value => {
return dsid.indexOf(value + '|') === 0;
});
var tmp = dsid.split('|');
if (isSpecialType && tmp.length === 2) {
var fxsyncId = tmp[1];
SyncBrowserDB.getBookmark(fxsyncId, callback);
} else {
console.warn('trying to delete a unknown type?', dsid);
callback(null);
}
}
});
}
function start() {
if (syncDataStore) {
return Promise.resolve();
}
syncDataStore = new SyncDsHelper('bookmarks_store');
return syncDataStore.init().then(() => {
return syncDataStore.start(handleTask);
});
}
function stop() {
if (!syncDataStore) {
return Promise.reject('Uninitialized DataStore');
}
return syncDataStore.stop().then(() => {
syncDataStore = null;
return SyncBrowserDB.clearBookmarks();
});
}
return {
start: start,
stop: stop
};
})();

1958
gaia/tv_apps/browser/js/sync/db.js Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,113 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
/* exported SyncDsHelper */
/* global localStorage */
var SyncDsHelper = function (dataStoreName) {
if (typeof dataStoreName !== 'string') {
throw new Event('Incorrect DataStore name.');
}
this.dataStoreName = dataStoreName;
this.dataStoreRevId = dataStoreName + '::revId';
};
SyncDsHelper.prototype = {
store: null,
dataChangeHandler: null,
init() {
return this._ensureStore();
},
_ensureStore() {
if (this.store) {
return Promise.resolve(this.store);
}
return navigator.getDataStores(this.dataStoreName).then(stores => {
this.store = stores[0];
return this.store;
});
},
setSyncedRevId(revId) {
localStorage.setItem(this.dataStoreRevId, revId);
},
getSyncedRevId() {
return localStorage.getItem(this.dataStoreRevId);
},
registerStoreChangeEvent(handler) {
this._ensureStore().then(() => {
if (!this.dataChangeHandler) {
this.store.removeEventListener('change', this.dataChangeHandler);
}
this.dataChangeHandler = handler;
this.store.addEventListener('change', this.dataChangeHandler);
});
},
unregisterStoreChangeEvent() {
return this._ensureStore().then(() => {
this.store.removeEventListener('change', this.dataChangeHandler);
this.dataChangeHandler = null;
});
},
clear() {
localStorage.removeItem(this.dataStoreRevId);
return this._ensureStore().then(() => {
return this.store.clear();
});
},
start(handleTask) {
return this.init().then(() => {
this.registerStoreChangeEvent(() => {
this.dataStoreSync(handleTask);
});
return this.dataStoreSync(handleTask);
});
},
stop() {
return this.unregisterStoreChangeEvent().then(this.clear.bind(this));
},
dataStoreSync(handle) {
return this._ensureStore().then(() => {
var _revId = this.getSyncedRevId();
return new Promise((resolve, reject) => {
var runNextTask = (cursor) => {
cursor.next().then(task => {
manageTask(cursor, task);
});
};
var post = (cursor, task) => {
if (task.operation === 'done') {
this.setSyncedRevId(this.store.revisionId);
resolve();
return;
}
runNextTask(cursor);
};
var manageTask = (cursor, task) => {
if (handle) {
handle(task).then(() => post(cursor, task));
} else {
post(cursor, task);
}
};
runNextTask(this.store.sync(_revId));
});
});
}
};

View file

@ -0,0 +1,86 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
/* exported SyncHistory */
/* global SyncBrowserDB */
/* global SyncDsHelper */
/*
exampleHistroyInIndexedDB = {
"uri": "http://example.com/",
"title": "Hello World",
"frecency": 1,
"iconUri": "http://example.com/favicon.ico",
"screenshot": {}
}
*/
var SyncHistory = (function () {
var syncDataStore;
function handleTask(task) {
return new Promise(resolve => {
switch(task.operation) {
case 'update':
case 'add':
var data = {};
var syncedData = task.data;
if (syncedData && syncedData.url &&
Array.isArray(syncedData.visits) && syncedData.visits.length > 0) {
data.uri = syncedData.url;
data.title = syncedData.title;
data.fxsyncId = syncedData.fxsyncId;
data.timestamp = syncedData.visits[0];
} else {
console.warn('Invalid places record:', task);
resolve();
return;
}
SyncBrowserDB.updateRawHistory(data, () => {
resolve();
});
break;
case 'clear':
// Ignore clear operation.
resolve();
break;
case 'remove':
SyncBrowserDB.removeHistory(task.id, () => {
resolve();
});
break;
case 'done':
resolve();
break;
}
});
}
function start() {
if (syncDataStore) {
return Promise.resolve();
}
syncDataStore = new SyncDsHelper('places');
return syncDataStore.init().then(() => {
return syncDataStore.start(handleTask);
});
}
function stop() {
if (!syncDataStore) {
return Promise.reject('Uninitialized DataStore');
}
return syncDataStore.stop().then(() => {
syncDataStore = null;
return SyncBrowserDB.clearHistoryDeep();
});
}
return {
start: start,
stop: stop
};
})();

View file

@ -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/. */
/**
* This module acts as a bridge between the browser UI and the Sync manager
* that lives in the System application.
*
* It allows consumers to subscribe to updates done in the Sync state machine
* so they can update the UI accordingly.
*
* The simple high level picture of Sync in the browser is:
*
* settings.js toolbar.js
* | |
* | |
* \______ manager_bridge.js _______/
* |
* | IAC
* |
* smart_system/sync_manager.js
*
*/
/* global LazyLoader */
/* global SyncBookmark */
/* global SyncHistory */
'use strict';
(function(exports) {
const DEBUG = false;
function debug() {
if (DEBUG) {
console.log('[SyncManagerBridge] ' + Array.slice(arguments).concat());
}
}
var SyncManagerBridge = {
_port: null,
_requests: new Map(),
_listeners: new Map(),
addListener(listener) {
debug('addListener', listener);
if (!this._listeners.has(listener)) {
this._listeners.set(listener);
}
},
removeListener(listener) {
debug('removeListener', listener);
if (!this._listeners.has(listener)) {
return;
}
this._listeners.delete(listener);
},
connect() {
if (this._port) {
return Promise.resolve(this._port);
}
return new Promise((resolve, reject) => {
navigator.mozApps.getSelf().onsuccess = event => {
var app = event.target.result;
app.connect('gaia-sync-management').then(ports => {
if (!ports || !ports.length) {
return reject();
}
this._port = ports[0];
this._port.onmessage = this.onmessage.bind(this);
resolve(this._port);
}).catch(reject);
};
});
},
iacRequest(request) {
debug('iacRequest', JSON.stringify(request));
return new Promise(resolve => {
this.connect().then(port => {
if (request.id) {
this._requests.set(request.id, resolve);
}
port.postMessage(request);
});
});
},
onmessage(event) {
var self = this;
function notify() {
if (self._listeners.size) {
Array.from(self._listeners.keys()).forEach(listener => {
listener(message);
});
}
}
var message = event.data;
if (!message) {
return;
}
debug('message', JSON.stringify(message));
if (message.id && this._requests.has(message.id)) {
this._requests.get(message.id)(message);
this._requests.delete(message.id);
return;
}
if (message.name !== 'onsyncchange') {
return;
}
if (message.state == 'disabled' ||
message.state == 'enabled') {
LazyLoader.load([
'js/sync/ds_helper.js',
'js/sync/bookmarks.js',
'js/sync/history.js'
]).then(() => {
var method = message.state == 'enabled' ? 'start'
: 'stop';
Promise.all([
SyncBookmark[method](),
SyncHistory[method]()
]).catch(error => {
console.warn(error);
});
notify();
});
return;
}
notify();
},
getInfo() {
return this.iacRequest({
name: 'getInfo',
id: Date.now()
});
}
};
['enable',
'disable',
'sync'].forEach(requestName => {
SyncManagerBridge[requestName] = () => {
return SyncManagerBridge.iacRequest({
name: requestName
});
};
});
exports.SyncManagerBridge = SyncManagerBridge;
}(window));

View file

@ -0,0 +1,507 @@
/* 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/. */
/* global Awesomescreen */
/* global Browser */
/* global BrowserDialog */
/* global ERROR_DIALOG_CLOSED_BY_USER */
/* global ERROR_INVALID_SYNC_ACCOUNT */
/* global ERROR_OFFLINE */
/* global ERROR_SYNC_APP_KILLED */
/* global ERROR_UNKNOWN */
/* global LazyLoader */
/* global Settings */
/* global SettingsListener */
/* global SyncManagerBridge */
'use strict';
(function(exports) {
const DEBUG = false;
function debug() {
if (DEBUG) {
console.log('[FirefoxSyncSettings] ' + Array.slice(arguments).concat());
}
}
// Screens
const ENABLED = 'enabled';
const DISABLED = 'disabled';
const BOOKMARKS_SETTING = 'sync.collections.bookmarks.enabled';
const HISTORY_SETTING = 'sync.collections.history.enabled';
const ELEMENTS = [{
screen: DISABLED,
selector: '#fxsync-sign-in-button',
event: 'mouseup',
listener: 'enable'
}, {
screen: ENABLED,
selector: '#fxsync-signed-in-as'
}, {
screen: ENABLED,
selector: '#fxsync-sync-now-button',
event: 'mouseup',
listener: 'sync'
}, {
screen: ENABLED,
selector: '#fxsync-sign-out-button',
event: 'mouseup',
listener: 'disable'
}, {
screen: ENABLED,
selector: '#fxsync-collection-bookmarks',
event: 'click',
listener: 'onbookmarkschecked',
init: 'onbookmarkschange'
}, {
screen: ENABLED,
selector: '#fxsync-collection-history',
event: 'click',
listener: 'onhistorychecked',
init: 'onhistorychange'
}, {
screen: ENABLED,
selector: '#fxsync-tos',
event: 'click',
listener: 'openTos'
}, {
screen: ENABLED,
selector: '#fxsync-privacy',
event: 'click',
listener: 'openPrivacy'
}];
function getElementName(str) {
str = str.toLowerCase().replace('#fxsync-', '');
return str.replace(/-([a-z])/g, g => {
return g[1].toUpperCase();
});
}
var FirefoxSyncSettings = {
init() {
this.area = document.querySelector('#fxsync-area');
this.elements = {
enabled: this.area.querySelector('#fxsync-enabled'),
disabled: this.area.querySelector('#fxsync-disabled')
};
this.listeners = new Map();
this.collections = new Map();
this.screen = null;
this.state = 'disabled';
this.showScreen(DISABLED);
LazyLoader.load('shared/js/settings_listener.js').then(() => {
[{
name: BOOKMARKS_SETTING,
onchange: 'onbookmarkschange'
}, {
name: HISTORY_SETTING,
onchange: 'onhistorychange'
}].forEach(setting => {
SettingsListener.observe(setting.name, true, enabled => {
debug(setting.name + ' changed to ' + enabled);
enabled ? this.collections.set(setting.name, enabled)
: this.collections.delete(setting.name);
this[setting.onchange]();
this.maybeEnableSyncNow();
});
});
});
SyncManagerBridge.addListener(this.onsyncchange.bind(this));
document.addEventListener('visibilitychange', () => {
debug('Visibility change', document.hidden);
if (!document.hidden) {
this.refresh();
}
});
this.refresh();
},
refresh() {
debug('Refreshing');
SyncManagerBridge.getInfo().then(this.onsyncchange.bind(this));
},
/**
* This is where most part of the Sync settings logic lives.
*
* Here we handle Sync lifecycle events coming from the SyncManager
* through the SyncMangerBridge. We react to these events by
* changing the screen depending on the state.
*
* We show two possible different state:
* 1. Logged out screen. We simply show the user what Sync is and
* allow her to start using it or to keep walking.
* 2. Logged in screen. We show to the user several different options:
* + The user owning the Sync account. If the account belongs to a
* verified user that previosly used Sync, we are fine, if not, we
* show a warning asking the user to go to other Firefox client to
* create a FxA and login into Sync.
* + A button to disable sync. Clicking it will log the user out from
* FxA and will clear the synchronized data.
* + A button to start a sync on demand. Clicking it will disable the
* button and the collections switches while the synchronization is
* being performed. Once the sync is done successfully, we enable
* all the options back and refresh the last sync time.
* We disable this button if no collection is selected.
* + Collections switches. Allow the user to select which collections
* she wants to keep synchronized. If no collection is selected, the
* synchronization will be disabled.
* + ToS and Privacy links.
*/
onsyncchange(message) {
debug(JSON.stringify(message));
if (!message || !message.state) {
throw new Error('Missing sync state');
}
switch (message.state) {
case 'disabled':
// We want to show a dialog once Sync is disabled
// but we only want to do that if it's disabled via user action
// and the user has already logged in.
// (and not because it is already disabled from a previous run).
if (this.state === 'disabling' &&
(this.previousState === 'enabled' ||
this.previousState === 'syncing')) {
navigator.mozL10n.formatValue('fxsync-disabled').then(result => {
window.alert(result);
});
}
this.showScreen(DISABLED);
this.hideEnabling();
break;
case 'enabling':
this.showEnabling();
break;
case 'enabled':
// We want to show a dialog once Sync is enabled
// but we only want to do that if its enabled via user action
// (and not because it is already enabled from a previous run).
if (this.state === 'enabling') {
navigator.mozL10n.formatValue('fxsync-enabled').then(result => {
window.alert(result);
});
}
this.hideEnabling();
this.showScreen(ENABLED);
this.showUser(message.user);
this.showSyncNow();
break;
case 'syncing':
this.showScreen(ENABLED);
// In case that the user opens the browser app while we are on a
// syncing state the previous 'enabled' state is missed and so
// we need to make sure that we show the user here as well.
this.showUser(message.user);
this.showSyncing();
break;
case 'errored':
LazyLoader.load('shared/js/sync/errors.js', () => {
const IGNORED_ERRORS = [
ERROR_DIALOG_CLOSED_BY_USER,
ERROR_SYNC_APP_KILLED
];
if (IGNORED_ERRORS.indexOf(message.error) > -1) {
Settings.hide.bind(Settings)();
return;
}
var errorIds = [ERROR_UNKNOWN, ERROR_UNKNOWN + '-explanation'];
const KNOWN_ERRORS = [
ERROR_INVALID_SYNC_ACCOUNT,
ERROR_OFFLINE
];
if (KNOWN_ERRORS.indexOf(message.error) > -1) {
errorIds[0] = message.error;
errorIds[1] = message.error + '-explanation';
}
var l10n = navigator.mozL10n;
Promise.all(
errorIds.map(l10n.formatValue.bind(l10n))
).then(result => {
window.alert(result[0] + '\n' + result[1], () => {
this.showScreen(DISABLED);
});
});
});
break;
}
this.previousState = this.state;
this.state = message.state;
},
onbookmarkschange() {
debug('onbookmarkschange');
var needsCleanup;
if (!this.elements.collectionBookmarks) {
// We may observe a setting change once we already disabled Sync
// because the Sync Manager is reverting user preference changes to
// the default ones. In that case, we need to create the object
// briefly, so we can update the DOM accordingly.
this.elements.collectionBookmarks = this.area.querySelector(
'#fxsync-collection-bookmarks'
);
needsCleanup = true;
}
this.elements.collectionBookmarks.checked =
this.collections.has(BOOKMARKS_SETTING);
if (needsCleanup) {
this.elements.collectionBookmarks = null;
}
},
onbookmarkschecked() {
debug('onbookmarkschecked');
this.oncollectionchecked(this.elements.collectionBookmarks,
BOOKMARKS_SETTING);
},
onhistorychange() {
debug('onhistorychange');
var needsCleanup;
if (!this.elements.collectionHistory) {
// We may observe a setting change once we already disabled Sync
// because the Sync Manager is reverting user preference changes to
// the default ones. In that case, we need to create the object
// briefly, so we can update the DOM accordingly.
this.elements.collectionHistory = this.area.querySelector(
'#fxsync-collection-history'
);
needsCleanup = true;
}
this.elements.collectionHistory.checked =
this.collections.has(HISTORY_SETTING);
if (needsCleanup) {
this.elements.collectionHistory = null;
}
},
onhistorychecked() {
debug('onhistorychecked');
this.oncollectionchecked(this.elements.collectionHistory,
HISTORY_SETTING);
},
oncollectionchecked(element, setting) {
if (!element || !setting) {
return;
}
var checked = element.checked;
var settingObj = {};
settingObj[setting] = checked;
if (!checked) {
this.collections.delete(setting);
navigator.mozSettings.createLock().set(settingObj);
return;
}
// Yeah, mozId sucks...
// We cannot call watch twice to change the id lifetime
// callbacks. But we still need to do different stuff
// within the onlogin callback depending on who calls
// oncollectionchecked. So we need to save state.
// Thank you mozId.
this.setting = setting;
this.settingObj = settingObj;
try {
navigator.mozId.watch({
wantIssuer: 'firefox-accounts',
onlogin: () => {
// This callback is fired every time a new account logs in,
// but we only want to enable the history setting when this
// callback is called because the refresh auth flow succeeded.
// This can happen only if Sync is already enabled.
if (this.state !== 'enabled') {
return;
}
this.collections.set(this.setting, true);
navigator.mozSettings.createLock().set(this.settingObj);
},
onlogout: () => {},
onready: () => {},
onerror: error => {
element.checked = false;
console.error(error);
}
});
} catch(e) {}
navigator.mozId.request({
oncancel: () => {
element.checked = false;
},
// We keep authenticated sessions of 5 minutes, if the user clicks on
// the history collection switch after the 5 minutes are expired, we
// will be asking her to re-enter her fxa password.
refreshAuthentication: 5 * 60
});
},
addListener(element, event, listenerName) {
if (!element || !event || !listenerName) {
return;
}
// Because we bind this to the listener, we need to save
// the reference so we can remove it afterwards.
var listener = this[listenerName].bind(this);
element.addEventListener(event, listener);
return listener;
},
removeListener(element, event, listener) {
if (!element || !event || !listener) {
return;
}
element.removeEventListener(event, listener);
},
loadElements(screen) {
ELEMENTS.forEach(element => {
var name = getElementName(element.selector);
if (!Array.isArray(element.screen)) {
element.screen = [element.screen];
}
if (!this.elements[name]) {
this.elements[name] = this.area.querySelector(element.selector);
this.listeners.set(
name,
this.addListener(this.elements[name],
element.event, element.listener)
);
if (element.init) {
this[element.init]();
}
return;
}
if (element.init) {
this[element.init]();
return;
}
if (!this.listeners.has(name)) {
return;
}
if (this.elements[name].checked) {
this.elements[name].checked = false;
}
});
},
showScreen(screen) {
if (this.screen == screen) {
return;
}
this.screen = screen;
this.loadElements(screen);
this.elements.enabled.hidden = (screen != ENABLED);
this.elements.disabled.hidden = (screen != DISABLED);
},
showUser(user) {
navigator.mozL10n.setAttributes(this.elements.signedInAs,
'fxsync-signed-in-as', {
email: user
});
},
maybeEnableSyncNow() {
if (!this.elements.syncNowButton) {
return;
}
(this.collections.size <= 0) ?
this.elements.syncNowButton.classList.add('disabled') :
this.elements.syncNowButton.classList.remove('disabled');
},
disableSyncNowAndCollections(disabled) {
['collectionBookmarks',
'collectionHistory'].forEach(name => {
if (!this.elements[name]) {
return;
}
this.elements[name].disabled = disabled;
});
disabled ? this.elements.syncNowButton.classList.add('disabled')
: this.elements.syncNowButton.classList.remove('disabled');
},
showEnabling() {
this.elements.signInButton.classList.add('disabled');
this.elements.signInButton.dataset.l10nId = 'fxsync-signing';
},
hideEnabling() {
this.elements.signInButton.classList.remove('disabled');
this.elements.signInButton.dataset.l10nId = 'fxsync-sign-in';
},
showSyncNow() {
this.elements.syncNowButton.dataset.l10nId = 'fxsync-sync-now';
this.disableSyncNowAndCollections(false);
this.maybeEnableSyncNow();
},
showSyncing() {
this.elements.syncNowButton.dataset.l10nId = 'fxsync-syncing';
this.disableSyncNowAndCollections(true);
},
enable() {
SyncManagerBridge.enable();
},
disable() {
BrowserDialog.createDialog('signout_confirm').then(() => {
SyncManagerBridge.disable();
});
},
sync() {
SyncManagerBridge.sync();
},
openTos() {
this.showWebpage('https://accounts.firefox.com/legal/terms');
},
openPrivacy() {
this.showWebpage('https://accounts.firefox.com/legal/privacy');
},
showWebpage(url) {
Awesomescreen.createAddNewTab();
Browser.navigate(url);
Settings.hide();
}
};
exports.FirefoxSyncSettings = FirefoxSyncSettings;
LazyLoader.load('js/sync/manager_bridge.js', () => {
FirefoxSyncSettings.init();
});
}(window));

View file

@ -0,0 +1,113 @@
/* 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/. */
/* exports FirefoxSyncToolbar */
/* global LazyLoader */
/* global Settings */
/* global SyncManagerBridge */
'use strict';
/**
* This small module handles the logic of the Sync entry in the browser's
* top right toolbar menu.
*
* We show two different variations of the UI depending on the state of Sync:
*
* - When Sync is disabled, we show a 'Sign in to Sync' entry. Clicking it
* triggers the Firefox Accounts flow and signs the user into Sync if the
* FxA flow is completed successfuly.
* - When Sync is enabled, we show the 'Sign is as {{email}}' entry. Where
* {{email}} is the email used by the user to login into FxA.
*
* We also handle a third Sync state. When an error is received through the
* Sync Manager Bridge, we inform the user about them. The expected errors are:
*
* - ERROR_OFFLINE: The user is trying to log in Sync when the device is
* offline.
* - ERROR_INVALID_SYNC_ACCOUNT: The user is trying to log into Sync with a
* Firefox Account that has never been used before with Sync. On the first
* version we don't support the creation of new Sync accounts, so we ask the
* user to first login in Firefox Desktop or Android and try login again
* in the TV.
* - ERROR_TRY_LATER: The server is probably down. We inform the user about it
* and ask her to retry in a few minutes.
* - ERROR_UNKNOWN: We don't know what the h* was wrong, but we can't log the
* user in.
*
* Here we don't need to worry about keeping any kind of state or managing
* any state transistion apart from the UI changes. The Sync State Machine
* and Sync State Manager takes care of handling all that for us.
*/
(function(exports) {
const DEBUG = true;
function debug() {
if (DEBUG) {
console.log('[FirefoxSyncToolbar] ' + Array.slice(arguments).concat());
}
}
var FirefoxSyncToolbar = {
init() {
SyncManagerBridge.addListener(this.onsyncchange.bind(this));
this.syncBlock = document.getElementById('fxsync-block');
this.syncTab = this.syncBlock.querySelector('#fxsync-tab');
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
this.refresh();
}
});
this.refresh();
},
refresh() {
SyncManagerBridge.getInfo().then(this.onsyncchange.bind(this));
},
onsyncchange(message) {
debug(JSON.stringify(message));
if (!message || !message.state) {
throw new Error('Missing sync state');
}
switch (message.state) {
case 'disabled':
if (this.showListener) {
this.syncBlock.removeEventListener('mouseup', this.showListener);
this.showListener = null;
}
this.syncBlock.addEventListener('mouseup', SyncManagerBridge.enable);
this.syncTab.setAttribute('data-l10n-id', 'fxsync-sign-in-to-sync');
this.syncTab.removeAttribute('data-l10n-args');
break;
case 'enabling':
Settings.show();
this.syncTab.setAttribute('data-l10n-id', 'fxsync-signing');
break;
case 'syncing':
case 'enabled':
this.showListener = Settings.show.bind(Settings);
this.syncBlock.addEventListener('mouseup', this.showListener);
this.syncBlock.removeEventListener('mouseup',
SyncManagerBridge.enable);
navigator.mozL10n.setAttributes(this.syncTab, 'fxsync-signed-in-as', {
email: message.user
});
break;
}
}
};
// Exported only for testing purposes
exports.FirefoxSyncToolbar = FirefoxSyncToolbar;
LazyLoader.load('js/sync/manager_bridge.js', () => {
FirefoxSyncToolbar.init();
});
}(window));

View file

@ -0,0 +1,758 @@
/* global Awesomescreen */
/* global Browser */
/* global BrowserDB */
/* global LazyLoader */
/* global SearchResult */
/* global SearchUtil */
/* global Settings */
/* global Tooltip*/
/* global UrlHelper */
'use strict';
/**
* Browser app toolbar
* @namespace Toolbar
*/
var Toolbar = {
/* screen size */
SCREEN_HEIGHT: 1080,
SCREEN_WIDTH: 1920,
/* zoom */
availableZoomScale: [
{'scale': 1.25, 'disp': '100%'},
{'scale': 1.625, 'disp': '130%'},
{'scale': 2.0, 'disp': '160%'},
],
defaultZoomScale: 0,
zoomScale: 0,
availableZoomScaleYoutube: [
{'scale': 1.55, 'disp': '100%'},
{'scale': 1.775, 'disp': '130%'},
{'scale': 2.0, 'disp': '160%'},
],
urlButtonMode: null,
GO: 0,
REFRESH: 1,
STOP: 2,
SEARCH: 3,
initStart: true,
/**
* Intialise toolbar.
*/
init: function toolbar_init() {
/* get Element */
this.getAllElements();
/* Event Listener */
window.addEventListener('click', this);
this.sidebarButtonBlock.addEventListener('mouseup',
this.showHideSidebar.bind(this));
this.backButtonBlock.addEventListener('mouseup',
this.goBack.bind(this));
this.forwardButtonBlock.addEventListener('mouseup',
this.goForward.bind(this));
this.addressForm.addEventListener('submit',
this.handleUrlFormSubmit.bind(this));
this.urlInput.addEventListener('input',
this.handleUrlInputKeypress.bind(this));
this.addressButton.addEventListener('mouseup',
this.clickAddressButton.bind(this));
this.searchForm.addEventListener('submit',
this.handleSearchFormSubmit.bind(this));
this.searchInput.addEventListener('focus',
this.handleSearchInputFocus.bind(this));
// this.searchInput.addEventListener('keyup',
// this.handleSearchInputKeypress.bind(this));
this.searchInput.addEventListener('input',
this.handleSearchInputInput.bind(this));
this.searchButton.addEventListener('mouseup',
this.clickSearchButton.bind(this));
this.searchClearButton.addEventListener('mouseup',
this.clickSearchClearButton.bind(this));
this.searchCloseButton.addEventListener('mouseup',
this.clickSearchCloseButton.bind(this));
this.bookmarkButtonBlock.addEventListener('mouseup',
Browser.addBookmark.bind(Browser));
this.bookmarkButtonAnime.addEventListener('animationend',
Browser.bookmarkButtonAnimeEnd.bind(Browser));
this.showBookmarksButtonBlock.addEventListener('mouseup',
Awesomescreen.selectBookmarksTab.bind(Awesomescreen));
this.homeButtonBlock.addEventListener('mouseup',
this.clickHomeButtonBlock.bind(this));
this.zoomButtonBlock.addEventListener('mouseup',
this.clickZoomButtonBlock.bind(this));
this.tabsButtonBlock.addEventListener('click',
Awesomescreen.handleNewTab.bind(Awesomescreen));
this.newTabButtonBlock.addEventListener('click',
Awesomescreen.createAddNewTab.bind(Awesomescreen));
this.menuButtonBlock.addEventListener('mouseup',
this.clickMenuButtonBlock.bind(this));
this.historyBlock.addEventListener('mouseup',
Awesomescreen.selectHistoryTab.bind(Awesomescreen));
this.pocketBlock.addEventListener('mouseup',
this.clickPocketListBlock.bind(this));
this.privateWindowBlock.addEventListener('mouseup',
Browser.handlePrivateBrowsing.bind(Browser));
this.settingsBlock.addEventListener('mouseup',
Settings.show.bind(Settings));
this.modeButtonBlock.addEventListener('mouseover',
this.selectedModeButtonBlock.bind(this));
this.modeButtonBlock.addEventListener('mouseout',
this.unselectedModeButtonBlock.bind(this));
this.toolbarPanel.dataset.search = 'false';
var colorBar = Browser.getColorBarData();
this.panCursorButtonBlock.dataset.colorbar = colorBar[0].COLOR;
this.zoomButtonBlock.dataset.colorbar = colorBar[1].COLOR;
this.tabsButtonBlock.dataset.colorbar = colorBar[2].COLOR;
this.newTabButtonBlock.dataset.colorbar = colorBar[3].COLOR;
// li = 73px * 3(list) + 20px(hover) + 10px(padding)
this.menuBlock.style.height = 73 * 3 + 20 + 10 + 'px';
Tooltip.init();
//IFDEF_FIREFOX_SYNC
LazyLoader.load('js/sync/toolbar.js');
//ENDIF_FIREFOX_SYNC
},
/**
* toolbar bookmark registered check.
*/
isBookmarks: function toolbar_isBookmarks() {
if(Toolbar.bookmarkButton.dataset.isbookmark == 'true'){
return true;
}else{
return false;
}
},
/**
* Set search engine
*/
setSearchEngine: function toolbar_setSearchEngine() {
var engineName = SearchUtil.getCurrentEngineName().toLowerCase();
// favicon
this.searchFavicon.dataset.engine = engineName;
// placeholder
engineName = engineName.charAt(0).toUpperCase() + engineName.slice(1);
this.searchInput.placeholder = engineName;
// tooltip
navigator.mozL10n.formatValue('WB_LT_TIPS_SEARCH_BAR2', {
engine: engineName
}).then(val => {
this.searchInput.dataset.tips = val;
});
},
/**
* CamelCase (xxx-yyy -> xxxYyy)
*/
toCamelCase: function toCamelCase(str) {
return str.replace(/-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
},
/**
* Get All Elements (from id)
*/
getAllElements: function toolbar_getAllElements() {
var elementIDs = [
'toolbar-panel',
'sidebar-button-block', 'sidebar-button',
'back-button-block', 'back-button',
'forward-button-block', 'forward-button-border', 'forward-button',
'space-block',
'addressbar-block', 'address-form', 'ssl-indicator', 'url-input',
'address-button',
'searchbar-block', 'search-form', 'search-favicon', 'search-input',
'search-button', 'search-clear-button', 'search-close-button',
'iconbutton-block',
'bookmark-button-block', 'bookmark-button',
'show-bookmarks-button-block', 'show-bookmarks-button',
'home-button-block', 'home-button',
'zoom-button-block', 'zoom-button',
'tabs-button-block', 'tabs-button',
'new-tab-button-block', 'new-tab-button',
'menu-button-block', 'menu-button',
'menu-block',
'history-block', 'private-window-block',
'settings-block',
'history-tab', 'private-window-tab', 'settings-tab',
'mode-button-block',
'mode-button-title', 'pan-cursor-button', 'pan-cursor-button-block',
'pan-cursor-banner-message',
'zoom-banner-message-block', 'zoom-banner-message',
'loading-icon',
//'tooltip-block',
'bookmark-button-anime', 'show-bookmarks-button-anime',
'pocket-block', 'pocket-tab'
];
// Loop and add element with camel style name to Modal Dialog attribute.
elementIDs.forEach(function createElementRef(name) {
this[this.toCamelCase(name)] = document.getElementById(name);
}, this);
},
/**
* handle event
*/
handleEvent: function toolbar_handleEvent(ev) {
switch(ev.type) {
case 'click':
if(ev.target != this.menuButtonBlock) {
this.toolbarPanel.dataset.menu = 'hide';
}
// Settings in the display
if(Settings.isDisplayed()) {
switch(ev.target) {
case this.backButtonBlock:
case this.forwardButtonBlock:
case this.addressForm:
case this.urlInput:
case this.addressButton:
case this.searchForm:
case this.searchInput:
case this.searchButton:
case this.bookmarkButtonBlock:
case this.showBookmarksButtonBlock:
case this.homeButtonBlock:
case this.zoomButtonBlock:
case this.tabsButtonBlock:
case this.newTabButtonBlock:
case this.menuButtonBlock:
Settings.hide();
break;
}
}
break;
default:
break;
}
},
/**
* Specialized processing youtube (zoom)
*/
checkYoutube: function toolbar_checkYoutube() {
if(!Browser.currentInfo || !Browser.currentInfo.url) {
return false;
}
var end_pos1 =
Browser.currentInfo.url.indexOf('https://www.youtube.com', 0);
var end_pos2 = Browser.currentInfo.url.indexOf('http://www.youtube.com', 0);
if(end_pos1 < 0 && end_pos2 < 0) {
return false;
}
return true;
},
/**
* Full screen or Screen with submenu
*/
showHideSidebar: function toolbar_showHideSidebar() {
this.sidebarButtonBlock.blur();
// Hide Tooltip
Tooltip.hide();
// Fade animation end event handler
var fade_event = (function() {
Browser.fadeBase.classList.toggle('fade');
if(Browser.sideBlock.dataset.sidebar == 'true'){
// Full screen
Browser.sideBlock.dataset.sidebar = 'false';
Browser.mainBlock.dataset.sidebar = 'false';
// save screen mode
BrowserDB.updateSetting('full', 'screen_mode');
}else{
// Disp side screen
Browser.sideBlock.dataset.sidebar = 'true';
Browser.mainBlock.dataset.sidebar = 'true';
// save screen mode
BrowserDB.updateSetting('side', 'screen_mode');
}
Browser.fadeBase.removeEventListener('transitionend', fade_event, false);
// Specialized processing youtube (zoom)
Browser.currentInfo.dom.zoom(Toolbar.getZoomScale());
});
// Add fade animantion event
Browser.fadeBase.addEventListener('transitionend', fade_event, false);
Browser.fadeBase.classList.toggle('fade');
this.sidebarButtonBlock.dataset.fade='true';
},
/**
* Go back to the previous page
*/
goBack: function toolbar_goBack() {
if (Awesomescreen.isDisplayedTop()){
Awesomescreen.topsiteReturnFunc();
return;
}
if (!Browser.currentInfo.dom.getCanGoBack){
return;
}
Browser.currentInfo.dom.getCanGoBack().onsuccess = (function(ev) {
if(!ev.target.result){
// dialog
// XXX: refactor this and the same code in awesomescreen.js
navigator.mozL10n.formatValue('LT_BROWSER_CONFIRM_EXIT2')
.then(result => {
if (window.confirm(result)) {
window.close();
}
});
return;
}
}).bind(this);
Browser.currentInfo.dom.goBack();
},
/**
* Go forward to the next page
*/
goForward: function toolbar_goForward() {
Browser.currentInfo.dom.goForward();
},
// Set url form input
setUrlBar: function toolbar_setUrlBar(value) {
this.urlInput.value = value;
},
// Get url form input
getUrlFromInput: function toolbar_getUrlFromInput(input) {
var hasScheme = UrlHelper.hasScheme(input);
// No scheme, prepend basic protocol and return
if (!hasScheme) {
return 'http://' + input;
}
return input;
},
/**
* Url form submit
*/
handleUrlFormSubmit: function toolbar_handleUrlFormSubmit(ev) {
if (ev) {
// Can be canceled
ev.preventDefault();
}
if (this.urlButtonMode === null) {
return;
}
var url = this.getUrlFromInput(this.urlInput.value);
if (url !== Browser.currentInfo.url) {
this.setUrlBar(url);
Browser.currentInfo.url = url;
}
this.urlInput.blur();
Browser.navigate(url);
},
/**
* Input Url
*/
handleUrlInputKeypress: function toolbar_handleUrlInputKeypress(evt) {
var input = this.urlInput.value;
if (input === '') {
this.setUrlButtonMode(null);
this.addressButton.classList.add('disable');
return;
}
this.addressButton.classList.remove('disable');
this.setUrlButtonMode(
UrlHelper.isNotURL(input) ? this.SEARCH : this.GO
);
},
/**
* Address Bookmark Current url
*/
clickAddressButton: function toolbar_clickAddressButton(ev) {
//if(this.addressButton.classList.contains('disable')){
//return;
//}
if(this.addressButton.dataset.mode == 'load') {
if(Browser.currentInfo.url){
Browser.currentInfo.dom.reload(false);
}
this.stopAddressIcon();
} else {
Browser.currentInfo.dom.stop();
this.hiddenLoadingIcon();
this.loadAddressIcon();
}
},
loadAddressIcon: function toolbar_loadAddressIcon() {
this.addressButton.dataset.mode = 'load';
this.addressButton.dataset.tips = 'WB_LT_TIPS_RELOAD';
},
stopAddressIcon: function toolbar_stopAddressIcon() {
this.addressButton.dataset.mode = 'stop';
this.addressButton.dataset.tips = 'WB_LT_TIPS_STOP_LOADING';
},
showLoadingIcon: function toolbar_showLoadingIcon() {
if(Settings.isDisplayed()) {
return;
}
if(!this.loadingIcon.classList.contains('visible')){
this.loadingIcon.classList.add('visible');
this.stopAddressIcon();
}
},
hiddenLoadingIcon: function toolbar_hiddenLoadingIcon() {
if(Tooltip.isShow(this.addressButton)){
Tooltip.hide();
}
this.loadingIcon.classList.remove('visible');
this.loadAddressIcon();
},
/**
* Submit search form
*/
handleSearchFormSubmit: function toolbar_handleSearchFormSubmit(ev) {
if (ev) {
// Can be canceled
ev.preventDefault();
}
if( SearchResult.isDisplayed() ) {
this.searchInput.blur();
SearchResult.hide();
}
var url = Browser.getSearchFromInput(this.searchInput.value);
Browser.navigate(url);
},
/**
* Click search button
*/
clickSearchButton: function toolbar_clickSearchButton(ev) {
this.handleSearchFormSubmit(ev);
},
clickSearchClearButton: function toolbar_clickSearchClearButton(ev) {
if( ev ) {
ev.preventDefault();
}
this.searchInput.value = '';
SearchResult.resultUpdate(this.searchInput.value);
},
clickSearchCloseButton: function toolbar_clickSearchCloseButton(ev) {
if( ev ) {
ev.preventDefault();
}
SearchResult.hide();
},
/**
* Input Search
*/
getSearchBarStyle: function toolbar_getSearchBarStyle() {
return this.toolbarPanel.dataset.search;
},
setSearchBarStyle: function toolbar_setSearchBarStyle(value) {
this.toolbarPanel.dataset.search = value;
},
handleSearchInputFocus: function toolbar_handleSearchInputFocus(evt) {
this.handleSearchInputKeypress(evt);
},
handleSearchInputInput: function toolbar_handleSearchInputInput(evt) {
this.handleSearchInputKeypress(evt);
},
handleSearchInputKeypress: function toolbar_handleSearchInputKeypress(evt) {
if( this.getSearchBarStyle() == 'false' ) {
if( evt ) {
evt.preventDefault();
}
if(Toolbar.searchInput.value) {
Tooltip.hide();
this.setSearchBarStyle('true');
SearchResult.show();
}
} else {
SearchResult.handleSearchResultFormInput(evt);
}
},
/**
* Click home button
*/
clickHomeButtonBlock: function toolbar_clickHomeButtonBlock(ev) {
Settings.getHomepage((function(result) {
if(result) {
Browser.navigate(result);
}
}).bind(this));
},
/**
* Click zoom button
*/
clickZoomButtonBlock: function toolbar_clickZoomButtonBlock(ev) {
if(ev){
// stop event
ev.stopPropagation();
}
clearTimeout(this.showZoomBannerTimeoutID);
this.zoomScale = Browser.currentInfo.zoom;
this.zoomScale++;
if(this.zoomScale >= this.availableZoomScale.length) {
this.zoomScale = 0;
}
Browser.currentInfo.zoom = this.zoomScale;
Browser.currentInfo.dom.zoom(this.getZoomScale());
this.zoomBannerMessage.innerHTML =
this.availableZoomScale[this.zoomScale].disp;
if(!this.zoomBannerMessageBlock.classList.contains('visible')){
this.zoomBannerMessageBlock.classList.add('visible');
}
this.showZoomBannerTimeoutID = setTimeout(function(){
Toolbar.zoomBannerMessageBlock.classList.remove('visible');
}, 1500);
},
getZoomScale: function toolbar_getZoomScale() {
if(this.checkYoutube() && Browser.sideBlock.dataset.sidebar == 'false'){
return this.availableZoomScaleYoutube[this.zoomScale].scale;
} else {
return this.availableZoomScale[this.zoomScale].scale;
}
},
setZoomScale: function toolbar_setZoomScale(scale) {
this.zoomScale = scale;
},
getDefaultZoomScale: function toolbar_getDefaultZoomScale() {
if(this.checkYoutube() && Browser.sideBlock.dataset.sidebar == 'false'){
return this.availableZoomScaleYoutube[this.defaultZoomScale].scale;
} else {
return this.availableZoomScale[this.defaultZoomScale].scale;
}
},
/**
* Zoom out(for ars)
*/
zoomOut: function toolbar_zoomOut() {
clearTimeout(this.showZoomBannerTimeoutID);
this.zoomScale = Browser.currentInfo.zoom;
if(this.zoomScale === 0) {
return;
}
this.zoomScale--;
Browser.currentInfo.zoom = this.zoomScale;
Browser.currentInfo.dom.zoom(this.getZoomScale());
this.zoomBannerMessage.innerHTML =
this.availableZoomScale[this.zoomScale].disp;
if(!this.zoomBannerMessageBlock.classList.contains('visible')){
this.zoomBannerMessageBlock.classList.add('visible');
}
this.showZoomBannerTimeoutID = setTimeout(function(){
Toolbar.zoomBannerMessageBlock.classList.remove('visible');
}, 1500);
},
/**
* Click menu button
*/
clickMenuButtonBlock: function toolbar_clickMenuButtonBlock(ev) {
if(ev){
// stop event
ev.stopPropagation();
}
if(this.toolbarPanel.dataset.menu == 'show') {
this.toolbarPanel.dataset.menu = 'hide';
} else {
this.toolbarPanel.dataset.menu = 'show';
if(!this.menuBlock.style.minWidth) {
var ar = [];
// rate 1.25
ar.push(432); // default 432(540 / 1.25)
ar.push(this.historyTab.offsetWidth);
ar.push(this.privateWindowTab.offsetWidth);
ar.push(this.settingsTab.offsetWidth);
this.menuBlock.style.minWidth =
(Math.max.apply(null, ar) * 1.25) + 'px';
}
}
},
/**
* Show mode button
*/
showModeButtonTimer: function toolbar_showModeButtonTimer() {
this.showModeButtonTimeoutID = setTimeout(function(){
Toolbar.modeButtonBlock.classList.remove('visible');
}, 5000);
},
/**
* Show mode button
*/
showPanCursorButton: function toolbar_showPanCursorButton() {
if(!this.modeButtonBlock.classList.contains('visible')){
this.modeButtonBlock.classList.add('visible');
this.showModeButtonTimer();
}
},
/**
* Selected mode button
*/
selectedModeButtonBlock: function toolbar_selectedModeButtonBlock() {
clearTimeout(this.showModeButtonTimeoutID);
},
/**
* Unselected mode button
*/
unselectedModeButtonBlock: function toolbar_unselectedModeButtonBlock() {
this.showModeButtonTimer();
},
/**
* Show pan cursor banner
*/
showPanCursorBanner: function toolbar_showPanCursorBanner() {
if(!this.panCursorBannerMessage.classList.contains('visible')){
this.panCursorBannerMessage.classList.add('visible');
}
clearTimeout(this.showPanCursorBannerTimeoutID);
this.showPanCursorBannerTimeoutID = setTimeout(function(){
Toolbar.panCursorBannerMessage.classList.remove('visible');
}, 5000);
},
/**
* Click mode button
*/
clickModeButtonBlock: function toolbar_clickModeButtonBlock() {
if(Browser.getCursorPanMode() == 'cursor') {
Browser.setCursorPanMode('pan');
this.modeButtonTitle.setAttribute('data-l10n-id', 'WB_LT_PAN_MODE');
this.panCursorBannerMessage.setAttribute(
'data-l10n-id', 'WB_LT_SWITCH_PAN_MODE');
} else {
this.clearDragMode();
}
// Show mode button
this.showPanCursorButton();
// Show pan cursor banner
this.showPanCursorBanner();
},
// clear drag mode
clearDragMode: function toolbar_clearDragMode(){
if(Browser.getCursorPanMode() == 'pan') {
Browser.setCursorPanMode('cursor');
this.modeButtonTitle.setAttribute('data-l10n-id', 'WB_LT_CURSOR_MODE');
this.panCursorBannerMessage.setAttribute(
'data-l10n-id', 'WB_LT_SWITCH_CURSOR_MODE');
}
},
setUrlButtonMode: function toolbar_setUrlButtonMode(mode) {
this.urlButtonMode = mode;
},
setPrivateBrowsing: function toolbar_setPrivateBrowsing(bool) {
this.toolbarPanel.dataset.pvt = bool;
},
isPrivateBrowsing: function toolbar_getPrivateBrowsing() {
return (this.toolbarPanel.dataset.pvt == 'true');
},
initStartBrowsing: function toolbar_initStartBrowsing() {
if(this.initStart) {
this.initStart = false;
/* show pan cursor mode button */
this.showPanCursorButton();
}
},
setToolbarMode: function toolbar_setToolbarMode(mode) {
this.toolbarPanel.dataset.mode = mode;
},
getToolbarMode: function toolbar_getToolbarMode() {
return this.toolbarPanel.dataset.mode;
},
/**
* Open Pocket List
*/
clickPocketListBlock: function toolbar_clickPocketListBlock() {
var pocketHomePageUri = 'https://getpocket.com/fxostv';
if (Browser.currentInfo.url &&
UrlHelper.isURL(Browser.currentInfo.url)) {
var ev = {
detail: {
url: pocketHomePageUri,
frameElement: null
}
};
Awesomescreen.openNewTab(ev);
} else {
Browser.navigate(pocketHomePageUri);
}
}
};

View file

@ -0,0 +1,190 @@
/* global _ */
/* global Toolbar */
'use strict';
/**
* Browser App Tooltip.
* @namespace Tooltip
*/
var Tooltip = {
tooltipBlock: null,
showTimeoutID: null,
hideTimeoutID: null,
hideTimeoutData: 100,
overObj: null,
moveObj: null,
showTarget: null,
/**
* initialize
*/
init: function tooltip_init() {
this.tooltipBlock = document.getElementById('tooltip-block');
var elements = [
{target: Toolbar.sidebarButtonBlock},
{target: Toolbar.backButtonBlock},
{target: Toolbar.forwardButtonBlock},
{target: Toolbar.addressButton},
{target: Toolbar.searchInput},
{target: Toolbar.bookmarkButtonBlock},
{target: Toolbar.showBookmarksButtonBlock},
{target: Toolbar.homeButtonBlock},
{target: Toolbar.zoomButtonBlock},
{target: Toolbar.tabsButtonBlock},
{target: Toolbar.newTabButtonBlock},
{target: Toolbar.menuButtonBlock},
{target: Toolbar.modeButtonBlock},
];
this.overObj = this.mouseoverEvent.bind(this);
this.moveObj = this.mousemoveEvent.bind(this);
// toolbar icon
elements.forEach(function addEvent(elm) {
var target = elm.target;
// add event
target.addEventListener('mouseover', this.overObj);
target.addEventListener('mouseout', this.mouseoutEvent.bind(this));
target.addEventListener('mousedown', this.clickEvent.bind(this));
target.addEventListener('mouseup', this.clickEvent.bind(this));
}, this);
},
/**
* mousemove event
*/
mousemoveEvent: function tooltip_mousemoveEvent(ev) {
if(this.isShow()) {
// hide the tooltip in mouse movement at tooltip display.
this.stopMousemoveEvent(ev.target);
this.hideTimeoutID = setTimeout(function(){
Tooltip.hide();
}, this.hideTimeoutData);
return;
}
if(this.showTimeoutID) {
clearTimeout(this.showTimeoutID);
}
var bounds = ev.currentTarget.getBoundingClientRect();
var posY = Math.ceil(bounds.bottom);
var top = Math.ceil(bounds.top);
var tips = null;
if(ev.target.dataset.tips != null && ev.target.dataset.tips != undefined) {
if(ev.target == Toolbar.searchInput) {
tips = {raw: ev.target.dataset.tips};
} else {
tips = ev.target.dataset.tips;
}
}
this.showTimeoutID = setTimeout(function(){
Tooltip.showTarget = ev.target;
var disp = {
top: top,
text: tips,
x: ev.pageX,
//y: ev.pageY
y: posY,
};
Tooltip.displaySetting(disp);
}, 150);
},
startMousemoveEvent: function tooltip_startMousemoveEvent(target) {
target.addEventListener('mousemove', this.moveObj);
},
stopMousemoveEvent: function tooltip_stopMousemoveEvent(target) {
target.removeEventListener('mousemove', this.moveObj);
},
/**
* mouseover event
*/
mouseoverEvent: function tooltip_mouseoverEvent(ev) {
this.startMousemoveEvent(ev.target);
},
/**
* mouseout event
*/
mouseoutEvent: function tooltip_mouseoutEvent(ev) {
this.hide();
},
/**
* click event
*/
clickEvent: function tooltip_clickEvent(ev) {
this.hide();
},
/**
* show tooltip
*/
isShow: function tooltip_isShow(target) {
if(!target){
return this.tooltipBlock.classList.contains('visible');
} else {
if(target == this.showTarget){
return this.tooltipBlock.classList.contains('visible');
} else {
return false;
}
}
},
/**
* show tooltip
*/
show: function tooltip_show() {
this.tooltipBlock.classList.add('visible');
},
/**
* hide tooltip
*/
hide: function tooltip_hide() {
clearTimeout(this.showTimeoutID);
clearTimeout(this.hideTimeoutID);
this.tooltipBlock.classList.remove('visible');
},
/**
* display setting
*/
displaySetting: function tooltip_displaySetting(disp) {
if(disp.text == null || disp.text == undefined) {
return;
}
this.show();
this.tooltipBlock.style.left = '0px';
if (typeof disp.text === 'string') {
this.tooltipBlock.setAttribute('data-l10n-id', disp.text);
} else if (disp.text.hasOwnProperty('raw')) {
this.tooltipBlock.innerHTML = disp.text.replace(/\\n/g, '<br>');
}
// pos x adjustment
// 25 = scrollbar margin
var maxW = this.tooltipBlock.offsetWidth + disp.x + 25;
var posX = disp.x;
if(maxW >= Browser.SCREEN_WIDTH) {
// 58 = margin
posX = Browser.SCREEN_WIDTH - this.tooltipBlock.offsetWidth - 58;
}
// pos y adjustment
var bounds = this.tooltipBlock.getBoundingClientRect();
var hg = Math.ceil(bounds.height);
var posY = disp.y + 20;
var maxH = posY + hg;
if(maxH >= Browser.SCREEN_HEIGHT) {
// hover item
posY = disp.top - 20 - hg;
}
this.tooltipBlock.style.top = (posY + 0) + 'px';
this.tooltipBlock.style.left = (posX + 0) + 'px';
},
};

Some files were not shown because too many files have changed in this diff Show more