merge mozilla-central zo mouilla-inbound. r=merge a=merge on a CLOSED TREE
|
@ -56,8 +56,10 @@ for (const type of [
|
|||
"SAVE_TO_POCKET",
|
||||
"SCREENSHOT_UPDATED",
|
||||
"SECTION_DEREGISTER",
|
||||
"SECTION_DISABLE",
|
||||
"SECTION_ENABLE",
|
||||
"SECTION_REGISTER",
|
||||
"SECTION_ROWS_UPDATE",
|
||||
"SECTION_UPDATE",
|
||||
"SET_PREF",
|
||||
"SNIPPETS_DATA",
|
||||
"SNIPPETS_RESET",
|
||||
|
|
|
@ -189,14 +189,27 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
}
|
||||
return section;
|
||||
});
|
||||
// If section doesn't exist in prevState, create a new section object and
|
||||
// append it to the sections state
|
||||
|
||||
// Invariant: Sections array sorted in increasing order of property `order`.
|
||||
// If section doesn't exist in prevState, create a new section object. If
|
||||
// the section has an order, insert it at the correct place in the array.
|
||||
// Otherwise, prepend it and set the order to be minimal.
|
||||
if (!hasMatch) {
|
||||
const initialized = action.data.rows && action.data.rows.length > 0;
|
||||
newState.push(Object.assign({title: "", initialized, rows: []}, action.data));
|
||||
let order;
|
||||
let index;
|
||||
if (prevState.length > 0) {
|
||||
order = action.data.order || prevState[0].order - 1;
|
||||
index = newState.findIndex(section => section.order >= order);
|
||||
} else {
|
||||
order = action.data.order || 1;
|
||||
index = 0;
|
||||
}
|
||||
const section = Object.assign({title: "", initialized, rows: [], order, enabled: false}, action.data);
|
||||
newState.splice(index, 0, section);
|
||||
}
|
||||
return newState;
|
||||
case at.SECTION_ROWS_UPDATE:
|
||||
case at.SECTION_UPDATE:
|
||||
return prevState.map(section => {
|
||||
if (section && section.id === action.data.id) {
|
||||
return Object.assign({}, section, action.data);
|
||||
|
|
|
@ -28,15 +28,17 @@ input {
|
|||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
vertical-align: middle; }
|
||||
vertical-align: middle;
|
||||
fill: rgba(12, 12, 13, 0.8);
|
||||
-moz-context-properties: fill; }
|
||||
.icon.icon-spacer {
|
||||
margin-inline-end: 8px; }
|
||||
.icon.icon-small-spacer {
|
||||
margin-inline-end: 6px; }
|
||||
.icon.icon-bookmark {
|
||||
background-image: url("assets/glyph-bookmark-16.svg"); }
|
||||
.icon.icon-bookmark-remove {
|
||||
background-image: url("assets/glyph-bookmark-remove-16.svg"); }
|
||||
.icon.icon-bookmark-added {
|
||||
background-image: url("chrome://browser/skin/bookmark.svg"); }
|
||||
.icon.icon-bookmark-hollow {
|
||||
background-image: url("chrome://browser/skin/bookmark-hollow.svg"); }
|
||||
.icon.icon-delete {
|
||||
background-image: url("assets/glyph-delete-16.svg"); }
|
||||
.icon.icon-dismiss {
|
||||
|
@ -46,7 +48,7 @@ input {
|
|||
.icon.icon-new-window {
|
||||
background-image: url("assets/glyph-newWindow-16.svg"); }
|
||||
.icon.icon-new-window-private {
|
||||
background-image: url("assets/glyph-newWindow-private-16.svg"); }
|
||||
background-image: url("chrome://browser/skin/privateBrowsing.svg"); }
|
||||
.icon.icon-settings {
|
||||
background-image: url("assets/glyph-settings-16.svg"); }
|
||||
.icon.icon-pin {
|
||||
|
@ -60,7 +62,7 @@ input {
|
|||
.icon.icon-trending {
|
||||
background-image: url("assets/glyph-trending-16.svg"); }
|
||||
.icon.icon-now {
|
||||
background-image: url("assets/glyph-now-16.svg"); }
|
||||
background-image: url("chrome://browser/skin/history.svg"); }
|
||||
.icon.icon-topsites {
|
||||
background-image: url("assets/glyph-topsites-16.svg"); }
|
||||
.icon.icon-pin-small {
|
||||
|
@ -79,7 +81,7 @@ body,
|
|||
height: 100%; }
|
||||
|
||||
body {
|
||||
background: #F6F6F8;
|
||||
background: #EDEDF0;
|
||||
color: #0C0C0D;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Ubuntu', 'Helvetica Neue', sans-serif;
|
||||
font-size: 16px; }
|
||||
|
@ -89,10 +91,10 @@ h2 {
|
|||
font-weight: normal; }
|
||||
|
||||
a {
|
||||
color: #00AFF7;
|
||||
color: #008EA4;
|
||||
text-decoration: none; }
|
||||
a:hover {
|
||||
color: #2bc1ff; }
|
||||
color: #00C8D7; }
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
|
@ -105,7 +107,7 @@ a {
|
|||
border: 0; }
|
||||
|
||||
.inner-border {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #D7D7DB;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -129,32 +131,32 @@ a {
|
|||
animation: fadeIn 0.2s; }
|
||||
|
||||
.actions {
|
||||
border-top: solid 1px rgba(0, 0, 0, 0.1);
|
||||
border-top: 1px solid #D7D7DB;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
padding: 15px 25px;
|
||||
justify-content: flex-start; }
|
||||
.actions button {
|
||||
background: #FBFBFB;
|
||||
border: solid 1px #BFBFBF;
|
||||
background: #F9F9FA;
|
||||
border: 1px solid #B1B1B3;
|
||||
border-radius: 5px;
|
||||
color: #0C0C0D;
|
||||
cursor: pointer;
|
||||
padding: 10px 30px; }
|
||||
.actions button:hover {
|
||||
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 0 5px #D7D7DB;
|
||||
transition: box-shadow 150ms; }
|
||||
.actions button.done {
|
||||
background: #0695F9;
|
||||
border: solid 1px #1677CF;
|
||||
background: #0A84FF;
|
||||
border: solid 1px #0060DF;
|
||||
color: #FFF;
|
||||
margin-inline-start: auto; }
|
||||
|
||||
.outer-wrapper {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding: 62px 32px 32px;
|
||||
padding: 40px 32px 32px;
|
||||
height: 100%; }
|
||||
|
||||
main {
|
||||
|
@ -171,10 +173,10 @@ main {
|
|||
main {
|
||||
width: 736px; } }
|
||||
main section {
|
||||
margin-bottom: 32px; }
|
||||
margin-bottom: 40px;
|
||||
position: relative; }
|
||||
|
||||
.section-title {
|
||||
color: #6E707E;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase; }
|
||||
|
@ -239,7 +241,7 @@ main {
|
|||
color: inherit;
|
||||
outline: none; }
|
||||
.top-sites-list .top-site-outer > a.active .tile, .top-sites-list .top-site-outer > a:focus .tile {
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
|
||||
transition: box-shadow 150ms; }
|
||||
.top-sites-list .top-site-outer .context-menu-button {
|
||||
cursor: pointer;
|
||||
|
@ -249,13 +251,13 @@ main {
|
|||
width: 27px;
|
||||
height: 27px;
|
||||
background-color: #FFF;
|
||||
background-image: url("assets/glyph-more-16.svg");
|
||||
background-position: 65%;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("chrome://browser/skin/page-action.svg");
|
||||
background-position: 55%;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid #B1B1B3;
|
||||
border-radius: 100%;
|
||||
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px rgba(12, 12, 13, 0.1);
|
||||
fill: rgba(12, 12, 13, 0.8);
|
||||
transform: scale(0.25);
|
||||
opacity: 0;
|
||||
transition-property: transform, opacity;
|
||||
|
@ -265,7 +267,7 @@ main {
|
|||
transform: scale(1);
|
||||
opacity: 1; }
|
||||
.top-sites-list .top-site-outer:hover .tile, .top-sites-list .top-site-outer:focus .tile, .top-sites-list .top-site-outer.active .tile {
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
|
||||
transition: box-shadow 150ms; }
|
||||
.top-sites-list .top-site-outer:hover .context-menu-button, .top-sites-list .top-site-outer:focus .context-menu-button, .top-sites-list .top-site-outer.active .context-menu-button {
|
||||
transform: scale(1);
|
||||
|
@ -275,8 +277,8 @@ main {
|
|||
height: 96px;
|
||||
width: 96px;
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
color: #A0A0A0;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 1px 4px 0 rgba(12, 12, 13, 0.1);
|
||||
color: #737373;
|
||||
font-weight: 200;
|
||||
font-size: 32px;
|
||||
text-transform: uppercase;
|
||||
|
@ -311,12 +313,13 @@ main {
|
|||
background-repeat: no-repeat; }
|
||||
.top-sites-list .top-site-outer .title {
|
||||
font: message-box;
|
||||
height: 30px;
|
||||
height: 20px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
width: 96px;
|
||||
position: relative; }
|
||||
.top-sites-list .top-site-outer .title .icon {
|
||||
fill: rgba(12, 12, 13, 0.6);
|
||||
offset-inline-start: 0;
|
||||
position: absolute;
|
||||
top: 10px; }
|
||||
|
@ -329,9 +332,9 @@ main {
|
|||
padding: 0 13px; }
|
||||
.top-sites-list .top-site-outer .edit-menu {
|
||||
background: #FFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid #B1B1B3;
|
||||
border-radius: 12.5px;
|
||||
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px rgba(12, 12, 13, 0.1);
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
offset-inline-end: -12.5px;
|
||||
|
@ -347,13 +350,13 @@ main {
|
|||
opacity: 1; }
|
||||
.top-sites-list .top-site-outer .edit-menu button {
|
||||
border: 0;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-right: 1px solid #B1B1B3;
|
||||
background-color: #FFF;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
width: 25px; }
|
||||
.top-sites-list .top-site-outer .edit-menu button:hover {
|
||||
background-color: #FBFBFB; }
|
||||
background-color: #F9F9FA; }
|
||||
.top-sites-list .top-site-outer .edit-menu button:last-child:dir(ltr) {
|
||||
border-right: 0; }
|
||||
.top-sites-list .top-site-outer .edit-menu button:first-child:dir(rtl) {
|
||||
|
@ -362,9 +365,6 @@ main {
|
|||
transform: scale(1);
|
||||
opacity: 1; }
|
||||
|
||||
.top-sites {
|
||||
position: relative; }
|
||||
|
||||
.edit-topsites-wrapper .edit-topsites-button {
|
||||
position: absolute;
|
||||
offset-inline-end: 0;
|
||||
|
@ -372,13 +372,13 @@ main {
|
|||
.edit-topsites-wrapper .edit-topsites-button button {
|
||||
background: none;
|
||||
border: 0;
|
||||
color: #A0A0A0;
|
||||
color: #737373;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
padding: 0; }
|
||||
.edit-topsites-wrapper .edit-topsites-button button:focus {
|
||||
background: #EBEBEB;
|
||||
border-bottom: dotted 1px #A0A0A0; }
|
||||
background: #F9F9FA;
|
||||
border-bottom: dotted 1px #737373; }
|
||||
|
||||
.edit-topsites-wrapper .modal {
|
||||
offset-inline-start: -31px;
|
||||
|
@ -394,16 +394,17 @@ main {
|
|||
position: relative;
|
||||
height: 16px;
|
||||
margin-bottom: 18px; }
|
||||
.sections-list .section-top-bar .section-title {
|
||||
float: left; }
|
||||
.sections-list .section-top-bar .section-info-option {
|
||||
float: right;
|
||||
margin-top: 14px; }
|
||||
offset-inline-end: 0;
|
||||
position: absolute;
|
||||
top: 0; }
|
||||
.sections-list .section-top-bar .info-option-icon {
|
||||
background-image: url("assets/glyph-info-option-12.svg");
|
||||
background-size: 12px 12px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
fill: rgba(12, 12, 13, 0.6);
|
||||
-moz-context-properties: fill;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
display: inline-block; }
|
||||
|
@ -420,10 +421,9 @@ main {
|
|||
z-index: 9999;
|
||||
position: absolute;
|
||||
background: #FFF;
|
||||
border: solid 1px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #D7D7DB;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
color: #0C0C0D;
|
||||
line-height: 120%;
|
||||
width: 320px;
|
||||
right: 0;
|
||||
|
@ -440,10 +440,9 @@ main {
|
|||
.sections-list .section-top-bar .info-option-link {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
color: #0A84FF; }
|
||||
color: #008EA4; }
|
||||
|
||||
.sections-list .section-list {
|
||||
clear: both;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, 224px);
|
||||
|
@ -471,7 +470,7 @@ main {
|
|||
width: 100%;
|
||||
height: 266px;
|
||||
display: flex;
|
||||
border: solid 1px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #D7D7DB;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 16px; }
|
||||
.sections-list .section-empty-state .empty-state {
|
||||
|
@ -481,7 +480,7 @@ main {
|
|||
background-size: 50px 50px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
fill: rgba(160, 160, 160, 0.4);
|
||||
fill: rgba(12, 12, 13, 0.6);
|
||||
-moz-context-properties: fill;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
|
@ -491,12 +490,12 @@ main {
|
|||
margin-bottom: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 300;
|
||||
color: #A0A0A0;
|
||||
color: #737373;
|
||||
text-align: center; }
|
||||
|
||||
.topic {
|
||||
font-size: 12px;
|
||||
color: #BFC0C7;
|
||||
color: #737373;
|
||||
margin-top: 12px;
|
||||
line-height: 1.6; }
|
||||
@media (min-width: 800px) {
|
||||
|
@ -534,6 +533,8 @@ main {
|
|||
margin-left: 5px;
|
||||
background-image: url("assets/topic-show-more-12.svg");
|
||||
background-repeat: no-repeat;
|
||||
fill: #008EA4;
|
||||
-moz-context-properties: fill;
|
||||
vertical-align: middle; }
|
||||
|
||||
.search-wrapper {
|
||||
|
@ -545,63 +546,55 @@ main {
|
|||
height: 36px; }
|
||||
.search-wrapper input {
|
||||
border: 0;
|
||||
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 0 12px 0 35px;
|
||||
height: 100%;
|
||||
border-radius: 4px 0 0 4px;
|
||||
padding-inline-start: 35px; }
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
padding-inline-end: 36px;
|
||||
padding-inline-start: 35px;
|
||||
width: 100%; }
|
||||
.search-wrapper input:focus {
|
||||
border-color: #0996F8;
|
||||
box-shadow: 0 0 0 2px #0996F8;
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 2px #0A84FF;
|
||||
z-index: 1; }
|
||||
.search-wrapper input:focus + .search-button {
|
||||
z-index: 1;
|
||||
box-shadow: 0 0 0 2px #0996F8;
|
||||
background-color: #0996F8;
|
||||
background-image: url("assets/glyph-forward-16-white.svg");
|
||||
color: #FFF; }
|
||||
background-color: #0A84FF;
|
||||
background-image: url("chrome://browser/skin/forward.svg");
|
||||
fill: #FFF;
|
||||
-moz-context-properties: fill; }
|
||||
.search-wrapper input[aria-expanded="true"] {
|
||||
border-radius: 4px 0 0 0; }
|
||||
.search-wrapper input:dir(rtl) {
|
||||
border-radius: 0 4px 4px 0; }
|
||||
.search-wrapper input:dir(rtl)[aria-expanded="true"] {
|
||||
border-radius: 0 4px 0 0; }
|
||||
border-radius: 4px 4px 0 0; }
|
||||
.search-wrapper .search-label {
|
||||
background: url("assets/glyph-search-16.svg") no-repeat center center/20px;
|
||||
fill: rgba(12, 12, 13, 0.6);
|
||||
-moz-context-properties: fill;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
offset-inline-start: 0;
|
||||
height: 100%;
|
||||
width: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2; }
|
||||
.search-wrapper .search-button {
|
||||
background: url("chrome://browser/skin/forward.svg") no-repeat center center;
|
||||
border-radius: 0 3px 3px 0;
|
||||
margin-inline-start: -1px;
|
||||
border: 0;
|
||||
width: 36px;
|
||||
padding: 0;
|
||||
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
background: #FFF url("assets/glyph-forward-16.svg") no-repeat center center;
|
||||
background-size: 16px 16px; }
|
||||
fill: rgba(12, 12, 13, 0.6);
|
||||
-moz-context-properties: fill;
|
||||
background-size: 16px 16px;
|
||||
height: 100%;
|
||||
offset-inline-end: 0;
|
||||
position: absolute; }
|
||||
.search-wrapper .search-button:hover {
|
||||
z-index: 1;
|
||||
box-shadow: 0 1px 0 0 rgba(0, 0, 1, 0.5);
|
||||
background-color: #0996F8;
|
||||
background-image: url("assets/glyph-forward-16-white.svg");
|
||||
color: #FFF;
|
||||
background-color: #0A84FF;
|
||||
fill: #FFF;
|
||||
cursor: pointer; }
|
||||
.search-wrapper .search-button:dir(rtl) {
|
||||
transform: scaleX(-1); }
|
||||
.search-wrapper .contentSearchSuggestionTable {
|
||||
transform: translate(-2px, 2px); }
|
||||
.search-wrapper .contentSearchSuggestionTable:dir(rtl) {
|
||||
transform: translate(2px, 2px); }
|
||||
border: 0;
|
||||
box-shadow: 0 0 0 2px #0A84FF;
|
||||
transform: translateY(2px); }
|
||||
|
||||
.context-menu {
|
||||
display: block;
|
||||
|
@ -612,7 +605,7 @@ main {
|
|||
offset-inline-start: 100%;
|
||||
margin-inline-start: 5px;
|
||||
z-index: 10000;
|
||||
background: #FBFBFB;
|
||||
background: #F9F9FA;
|
||||
border-radius: 5px; }
|
||||
.context-menu > ul {
|
||||
margin: 0;
|
||||
|
@ -634,7 +627,7 @@ main {
|
|||
display: flex;
|
||||
align-items: center; }
|
||||
.context-menu > ul > li > a:hover, .context-menu > ul > li > a:focus {
|
||||
background: #2B99FF;
|
||||
background: #0A84FF;
|
||||
color: #FFF; }
|
||||
.context-menu > ul > li > a:hover a, .context-menu > ul > li > a:focus a {
|
||||
color: #0C0C0D; }
|
||||
|
@ -645,21 +638,23 @@ main {
|
|||
font-size: 13px; }
|
||||
.prefs-pane .sidebar {
|
||||
background: #FFF;
|
||||
border-left: solid 1px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.08);
|
||||
border-left: 1px solid #D7D7DB;
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
|
||||
min-height: 100%;
|
||||
offset-inline-end: 0;
|
||||
padding: 40px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
transition: 0.1s cubic-bezier(0, 0, 0, 1);
|
||||
transition-property: left, right;
|
||||
transition-property: transform;
|
||||
width: 400px;
|
||||
z-index: 12000; }
|
||||
.prefs-pane .sidebar.hidden {
|
||||
offset-inline-end: -400px; }
|
||||
transform: translateX(100%); }
|
||||
.prefs-pane .sidebar.hidden:dir(rtl) {
|
||||
transform: translateX(-100%); }
|
||||
.prefs-pane .sidebar h1 {
|
||||
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid #D7D7DB;
|
||||
font-size: 24px;
|
||||
margin: 0;
|
||||
padding: 20px 0; }
|
||||
|
@ -677,8 +672,8 @@ main {
|
|||
font-size: 16px;
|
||||
font-weight: bold; }
|
||||
.prefs-pane .prefs-modal-inner-wrapper section .options {
|
||||
background: #FBFBFB;
|
||||
border: solid 1px rgba(0, 0, 0, 0.1);
|
||||
background: #F9F9FA;
|
||||
border: 1px solid #D7D7DB;
|
||||
border-radius: 3px;
|
||||
margin: 15px 0;
|
||||
margin-inline-start: 30px;
|
||||
|
@ -710,7 +705,7 @@ main {
|
|||
.prefs-pane [type='checkbox']:not(:checked) + label::before,
|
||||
.prefs-pane [type='checkbox']:checked + label::before {
|
||||
background: #FFF;
|
||||
border: 1px solid #C1C1C1;
|
||||
border: 1px solid #B1B1B3;
|
||||
border-radius: 3px;
|
||||
content: '';
|
||||
height: 21px;
|
||||
|
@ -728,30 +723,31 @@ main {
|
|||
top: 0;
|
||||
width: 21px;
|
||||
-moz-context-properties: fill, stroke;
|
||||
fill: #1691D2;
|
||||
fill: #0A84FF;
|
||||
stroke: none; }
|
||||
.prefs-pane [type='checkbox']:not(:checked) + label::after {
|
||||
opacity: 0; }
|
||||
.prefs-pane [type='checkbox']:checked + label::after {
|
||||
opacity: 1; }
|
||||
.prefs-pane [type='checkbox'] + label:hover::before {
|
||||
border: 1px solid #1691D2; }
|
||||
border: 1px solid #0A84FF; }
|
||||
.prefs-pane [type='checkbox']:checked:focus + label::before,
|
||||
.prefs-pane [type='checkbox']:not(:checked):focus + label::before {
|
||||
border: 1px dotted #1691D2; }
|
||||
border: 1px dotted #0A84FF; }
|
||||
|
||||
.prefs-pane-button button {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
padding: 15px;
|
||||
position: fixed;
|
||||
right: 15px;
|
||||
top: 15px;
|
||||
z-index: 12001; }
|
||||
.prefs-pane-button button:hover {
|
||||
background-color: #EBEBEB; }
|
||||
background-color: #F9F9FA; }
|
||||
.prefs-pane-button button:active {
|
||||
background-color: #EDEDF0; }
|
||||
.prefs-pane-button button:dir(rtl) {
|
||||
left: 5px;
|
||||
right: auto; }
|
||||
|
@ -779,7 +775,7 @@ main {
|
|||
margin-inline-end: 0; }
|
||||
|
||||
.modal-overlay {
|
||||
background: #FBFBFB;
|
||||
background: #F9F9FA;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
opacity: 0.8;
|
||||
|
@ -790,7 +786,7 @@ main {
|
|||
|
||||
.modal {
|
||||
background: #FFF;
|
||||
border: solid 1px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #D7D7DB;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
z-index: 11002; }
|
||||
|
@ -811,13 +807,13 @@ main {
|
|||
width: 27px;
|
||||
height: 27px;
|
||||
background-color: #FFF;
|
||||
background-image: url("assets/glyph-more-16.svg");
|
||||
background-position: 65%;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("chrome://browser/skin/page-action.svg");
|
||||
background-position: 55%;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid #B1B1B3;
|
||||
border-radius: 100%;
|
||||
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px rgba(12, 12, 13, 0.1);
|
||||
fill: rgba(12, 12, 13, 0.8);
|
||||
transform: scale(0.25);
|
||||
opacity: 0;
|
||||
transition-property: transform, opacity;
|
||||
|
@ -829,7 +825,7 @@ main {
|
|||
.card-outer .card {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 4px 0 rgba(9, 6, 13, 0.1); }
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); }
|
||||
.card-outer > a {
|
||||
display: block;
|
||||
color: inherit;
|
||||
|
@ -838,28 +834,26 @@ main {
|
|||
position: absolute;
|
||||
width: 224px; }
|
||||
.card-outer > a.active .card, .card-outer > a:focus .card {
|
||||
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 0 5px #D7D7DB;
|
||||
transition: box-shadow 150ms; }
|
||||
.card-outer > a.active .card-title, .card-outer > a:focus .card-title {
|
||||
color: #00AFF7; }
|
||||
color: #008EA4; }
|
||||
.card-outer:hover, .card-outer:focus, .card-outer.active {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 0 5px #D7D7DB;
|
||||
transition: box-shadow 150ms; }
|
||||
.card-outer:hover .context-menu-button, .card-outer:focus .context-menu-button, .card-outer.active .context-menu-button {
|
||||
transform: scale(1);
|
||||
opacity: 1; }
|
||||
.card-outer:hover .card-title, .card-outer:focus .card-title, .card-outer.active .card-title {
|
||||
color: #00AFF7; }
|
||||
color: #008EA4; }
|
||||
.card-outer .card-preview-image {
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
height: 122px;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.1);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom: 1px solid #D7D7DB;
|
||||
border-radius: 3px 3px 0 0; }
|
||||
.card-outer .card-details {
|
||||
padding: 15px 16px 12px; }
|
||||
|
@ -879,7 +873,7 @@ main {
|
|||
.card-outer .card-text.no-image.no-host-name.no-context {
|
||||
max-height: 230px; }
|
||||
.card-outer .card-host-name {
|
||||
color: #858585;
|
||||
color: #737373;
|
||||
font-size: 10px;
|
||||
padding-bottom: 4px;
|
||||
text-transform: uppercase; }
|
||||
|
@ -900,11 +894,11 @@ main {
|
|||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
color: #A0A0A0;
|
||||
color: #737373;
|
||||
font-size: 11px;
|
||||
display: flex; }
|
||||
.card-outer .card-context-icon {
|
||||
opacity: 0.5;
|
||||
fill: rgba(12, 12, 13, 0.6);
|
||||
font-size: 13px;
|
||||
margin-inline-end: 6px;
|
||||
display: block; }
|
||||
|
@ -915,7 +909,9 @@ main {
|
|||
white-space: nowrap; }
|
||||
|
||||
.manual-migration-container {
|
||||
background: rgba(215, 215, 219, 0.5);
|
||||
background: #F9F9FA;
|
||||
border: 1px solid #D7D7DB;
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
|
||||
font-size: 13px;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 40px;
|
||||
|
@ -945,6 +941,7 @@ main {
|
|||
@media (min-width: 544px) {
|
||||
.manual-migration-container .icon {
|
||||
display: block;
|
||||
fill: rgba(12, 12, 13, 0.6);
|
||||
margin: 0;
|
||||
margin-inline-end: 12px;
|
||||
align-self: center; } }
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill-rule: evenodd;
|
||||
fill:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<path d="M198.992,18a0.955,0.955,0,0,0-.772.651l-1.984,4.122-4.332.72a0.851,0.851,0,0,0-.53,1.563l3.112,3.262-0.69,4.589c-0.1.69,0.173,1.094,0.658,1.094a1.4,1.4,0,0,0,.635-0.181l3.9-2.075,3.9,2.075a1.4,1.4,0,0,0,.634.181c0.485,0,.761-0.4.659-1.094L203.5,28.317l3.108-3.259a0.853,0.853,0,0,0-.53-1.566l-4.3-.719-2.016-4.122A0.953,0.953,0,0,0,198.992,18h0Z" transform="translate(-191 -18)"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 844 B |
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill-rule: evenodd;
|
||||
fill:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<path d="M199.008,47.642l0.983,2.01,0.452,0.924,1.015,0.17,2.324,0.389-1.719,1.8-0.676.708,0.145,0.968,0.36,2.4-1.953-1.038-0.938-.5-0.939.5-1.951,1.037,0.36-2.4,0.146-.969-0.676-.709-1.718-1.8,2.349-.39,1.024-.17,0.45-.935,0.962-2M199,44a0.953,0.953,0,0,0-.772.651l-1.984,4.122-4.332.72a0.851,0.851,0,0,0-.53,1.563l3.112,3.262-0.69,4.589c-0.1.69,0.172,1.094,0.658,1.094a1.394,1.394,0,0,0,.634-0.181L199,57.744l3.9,2.075a1.4,1.4,0,0,0,.635.181c0.485,0,.761-0.4.658-1.094l-0.687-4.589,3.108-3.259a0.853,0.853,0,0,0-.53-1.566l-4.3-.72-2.016-4.122A0.953,0.953,0,0,0,199,44h0Z" transform="translate(-191 -44)"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 1.0 KiB |
|
@ -1,13 +1 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill-rule: evenodd;
|
||||
fill:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<path d="M426,22H416a1,1,0,0,1,0-2h3a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1h3A1,1,0,0,1,426,22Zm-0.9,10a1.132,1.132,0,0,1-1.1,1H418a1.125,1.125,0,0,1-1.1-1L416,23h10Z" transform="translate(-413 -18)"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g fill="context-fill"><path d="M6.5 12a.5.5 0 0 0 .5-.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 0 .5.5zm2 0a.5.5 0 0 0 .5-.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 0 .5.5zm2 0a.5.5 0 0 0 .5-.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 0 .5.5z"/><path d="M14 2h-3.05a2.5 2.5 0 0 0-4.9 0H3a1 1 0 0 0 0 2v9a3 3 0 0 0 3 3h5a3 3 0 0 0 3-3V4a1 1 0 0 0 0-2zM8.5 1a1.489 1.489 0 0 1 1.391 1H7.109A1.489 1.489 0 0 1 8.5 1zM12 13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4h7z"/></g></svg>
|
До Ширина: | Высота: | Размер: 647 B После Ширина: | Высота: | Размер: 517 B |
|
@ -1,13 +1 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill-rule: evenodd;
|
||||
fill:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<path d="M422.414,52l3.531-3.531a1,1,0,1,0-1.414-1.414L421,50.586l-3.531-3.531a1,1,0,1,0-1.414,1.414L419.586,52l-3.531,3.531a1,1,0,1,0,1.414,1.414L421,53.414l3.531,3.531a1,1,0,1,0,1.414-1.414Z" transform="translate(-413 -44)"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M9.414 8l3.531-3.531a1 1 0 1 0-1.414-1.414L8 6.586 4.469 3.055a1 1 0 1 0-1.414 1.414L6.586 8l-3.531 3.531a1 1 0 1 0 1.414 1.414L8 9.414l3.531 3.531a1 1 0 1 0 1.414-1.414z"/></svg>
|
До Ширина: | Высота: | Размер: 682 B После Ширина: | Высота: | Размер: 291 B |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>Forward - 16</title>
|
||||
<g>
|
||||
<polyline points="9 2 15 8 9 14" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="14" y1="8" x2="1" y2="8" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 394 B |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>Forward - 16</title>
|
||||
<g>
|
||||
<polyline points="9 2 15 8 9 14" fill="none" stroke="#a0a0a0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<line x1="14" y1="8" x2="1" y2="8" fill="none" stroke="#a0a0a0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 400 B |
|
@ -1,6 +1 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#4d4d4d" d="M365,190a4,4,0,1,1,4-4A4,4,0,0,1,365,190Zm0-6a2,2,0,1,0,2,2A2,2,0,0,0,365,184Z" transform="translate(-357 -178)"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M8 12a4 4 0 1 1 4-4 4 4 0 0 1-4 4zm0-6a2 2 0 1 0 2 2 2 2 0 0 0-2-2z"/></svg>
|
До Ширина: | Высота: | Размер: 450 B После Ширина: | Высота: | Размер: 188 B |
|
@ -1,6 +1 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="context-fill" d="M8 1a7 7 0 1 0 7 7 7.008 7.008 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zm0-7a1 1 0 0 0-1 1v3a1 1 0 1 0 2 0V8a1 1 0 0 0-1-1zm0-3.188A1.188 1.188 0 1 0 9.188 5 1.188 1.188 0 0 0 8 3.812z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M8 1a7 7 0 1 0 7 7 7.008 7.008 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zm0-7a1 1 0 0 0-1 1v3a1 1 0 1 0 2 0V8a1 1 0 0 0-1-1zm0-3.188A1.188 1.188 0 1 0 9.188 5 1.188 1.188 0 0 0 8 3.812z"/></svg>
|
До Ширина: | Высота: | Размер: 533 B После Ширина: | Высота: | Размер: 316 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="#999" d="M6 0a6 6 0 1 0 6 6 6 6 0 0 0-6-6zm.7 10.26a1.13 1.13 0 0 1-.78.28 1.13 1.13 0 0 1-.78-.28 1 1 0 0 1 0-1.42 1.13 1.13 0 0 1 .78-.28 1.13 1.13 0 0 1 .78.28 1 1 0 0 1 0 1.42zM8.55 5a3 3 0 0 1-.62.81l-.67.63a1.58 1.58 0 0 0-.4.57 2.24 2.24 0 0 0-.12.74H5.06a3.82 3.82 0 0 1 .19-1.35 2.11 2.11 0 0 1 .63-.86 4.17 4.17 0 0 0 .66-.67 1.09 1.09 0 0 0 .23-.67.73.73 0 0 0-.77-.86.71.71 0 0 0-.57.26 1.1 1.1 0 0 0-.23.7h-2A2.36 2.36 0 0 1 4 2.47a2.94 2.94 0 0 1 2-.65 3.06 3.06 0 0 1 2 .6 2.12 2.12 0 0 1 .72 1.72 2 2 0 0 1-.17.86z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="context-fill" d="M6 0a6 6 0 1 0 6 6 6 6 0 0 0-6-6zm.7 10.26a1.13 1.13 0 0 1-.78.28 1.13 1.13 0 0 1-.78-.28 1 1 0 0 1 0-1.42 1.13 1.13 0 0 1 .78-.28 1.13 1.13 0 0 1 .78.28 1 1 0 0 1 0 1.42zM8.55 5a3 3 0 0 1-.62.81l-.67.63a1.58 1.58 0 0 0-.4.57 2.24 2.24 0 0 0-.12.74H5.06a3.82 3.82 0 0 1 .19-1.35 2.11 2.11 0 0 1 .63-.86 4.17 4.17 0 0 0 .66-.67 1.09 1.09 0 0 0 .23-.67.73.73 0 0 0-.77-.86.71.71 0 0 0-.57.26 1.1 1.1 0 0 0-.23.7h-2A2.36 2.36 0 0 1 4 2.47a2.94 2.94 0 0 1 2-.65 3.06 3.06 0 0 1 2 .6 2.12 2.12 0 0 1 .72 1.72 2 2 0 0 1-.17.86z"/></svg>
|
До Ширина: | Высота: | Размер: 612 B После Ширина: | Высота: | Размер: 619 B |
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
circle {
|
||||
fill: #4d4d4d;
|
||||
}
|
||||
</style>
|
||||
|
||||
<g>
|
||||
<circle cx="2" cy="8" r="2"/>
|
||||
<circle cx="7" cy="8" r="2"/>
|
||||
<circle cx="12" cy="8" r="2"/>
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 352 B |
|
@ -1,13 +1 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill-rule: evenodd;
|
||||
fill:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<path d="M382,20.007A1,1,0,0,1,383,19h14a1,1,0,0,1,1,1.007V31.993A1,1,0,0,1,397,33H383a1,1,0,0,1-1-1.007V20.007ZM384,23h12v8H384V23Zm0.5-3a0.5,0.5,0,1,1-.5.5A0.5,0.5,0,0,1,384.5,20Zm2,0a0.5,0.5,0,1,1-.5.5A0.5,0.5,0,0,1,386.5,20Zm2,0a0.5,0.5,0,1,1-.5.5A0.5,0.5,0,0,1,388.5,20Z" transform="translate(-382 -18)"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g fill="context-fill"><path d="M14.923 1.618A1 1 0 0 0 14 1H9a1 1 0 0 0 0 2h2.586L8.293 6.293a1 1 0 1 0 1.414 1.414L13 4.414V7a1 1 0 0 0 2 0V2a1 1 0 0 0-.077-.382z"/><path d="M14 10a1 1 0 0 0-1 1v2H3V3h2a1 1 0 0 0 0-2H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1z"/></g></svg>
|
До Ширина: | Высота: | Размер: 765 B После Ширина: | Высота: | Размер: 382 B |
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill-rule: evenodd;
|
||||
fill:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<path d="M356.994,24.619c-1.954.47-1.714,1.625-1.714,1.625s2.264,0.849,3.368.258a8.76,8.76,0,0,0,1.167-.668s-1.493-1.534-2.821-1.215m-5.987,0c-1.328-.32-2.821,1.215-2.821,1.215a8.76,8.76,0,0,0,1.167.668c1.1,0.591,3.368-.258,3.368-0.258s0.24-1.155-1.714-1.625M362,24.667c0,2.006-.647,5.334-3.755,5.333-1.143,0-3.1-1.993-4.245-1.993S350.9,30,349.755,30C346.647,30,346,26.673,346,24.667c0-2.094.984-2.813,3.628-2.638,2.739,0.181,3.066,1.087,4.372,1.087s1.8-.906,4.373-1.087c2.713-.191,3.627.544,3.627,2.638" transform="translate(-346 -18)"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 993 B |
|
@ -1,6 +0,0 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#4d4d4d" d="M8 0a8 8 0 1 0 8 8 8.009 8.009 0 0 0-8-8zm0 14a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zm3.5-6H8V4.5a.5.5 0 0 0-1 0v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 0-1z"/>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 479 B |
|
@ -1,8 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
|
||||
<style>
|
||||
path {
|
||||
fill: #D7D7DB;
|
||||
}
|
||||
</style>
|
||||
<path d="M10.53,9.47,8.25,7.19,9.8,5.643a.694.694,0,0,0,0-.98,3.04,3.04,0,0,0-2.161-.894H7.517A1.673,1.673,0,0,1,5.846,2.1V1.692A.693.693,0,0,0,4.664,1.2L1.2,4.664a.693.693,0,0,0,.49,1.182H2.1A1.672,1.672,0,0,1,3.769,7.517v.117a2.8,2.8,0,0,0,.925,2.192A.693.693,0,0,0,5.643,9.8L7.19,8.251l2.28,2.28A.75.75,0,0,0,10.53,9.47Z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path fill="context-fill" d="M10.53 9.47L8.25 7.19 9.8 5.643a.694.694 0 0 0 0-.98 3.04 3.04 0 0 0-2.161-.894h-.122A1.673 1.673 0 0 1 5.846 2.1v-.408A.693.693 0 0 0 4.664 1.2L1.2 4.664a.693.693 0 0 0 .49 1.182h.41a1.672 1.672 0 0 1 1.669 1.671v.117a2.8 2.8 0 0 0 .925 2.192.693.693 0 0 0 .949-.026L7.19 8.251l2.28 2.28a.75.75 0 0 0 1.06-1.061z"/></svg>
|
До Ширина: | Высота: | Размер: 479 B После Ширина: | Высота: | Размер: 434 B |
|
@ -1,8 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill: #4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<path d="M14.707,13.293,11.414,10l2.293-2.293a1,1,0,0,0,0-1.414A4.384,4.384,0,0,0,10.586,5h-.172A2.415,2.415,0,0,1,8,2.586V2a1,1,0,0,0-1.707-.707l-5,5A1,1,0,0,0,2,8h.586A2.415,2.415,0,0,1,5,10.414v.169a4.036,4.036,0,0,0,1.337,3.166,1,1,0,0,0,1.37-.042L10,11.414l3.293,3.293a1,1,0,0,0,1.414-1.414ZM7.129,11.456A2.684,2.684,0,0,1,7,10.583v-.169A4.386,4.386,0,0,0,5.708,7.293,4.414,4.414,0,0,0,4.136,6.278L6.279,4.136A4.4,4.4,0,0,0,7.292,5.707,4.384,4.384,0,0,0,10.414,7h.172a2.4,2.4,0,0,1,.848.152Z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M14.707 13.293L11.414 10l2.293-2.293a1 1 0 0 0 0-1.414A4.384 4.384 0 0 0 10.586 5h-.172A2.415 2.415 0 0 1 8 2.586V2a1 1 0 0 0-1.707-.707l-5 5A1 1 0 0 0 2 8h.586A2.415 2.415 0 0 1 5 10.414v.169a4.036 4.036 0 0 0 1.337 3.166 1 1 0 0 0 1.37-.042L10 11.414l3.293 3.293a1 1 0 0 0 1.414-1.414zm-7.578-1.837A2.684 2.684 0 0 1 7 10.583v-.169a4.386 4.386 0 0 0-1.292-3.121 4.414 4.414 0 0 0-1.572-1.015l2.143-2.142a4.4 4.4 0 0 0 1.013 1.571A4.384 4.384 0 0 0 10.414 7h.172a2.4 2.4 0 0 1 .848.152z"/></svg>
|
До Ширина: | Высота: | Размер: 652 B После Ширина: | Высота: | Размер: 608 B |
|
@ -1,6 +1 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#4d4d4d" d="M8 15a8 8 0 0 1-8-8V3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4a8 8 0 0 1-8 8zm3.985-10.032a.99.99 0 0 0-.725.319L7.978 8.57 4.755 5.336A.984.984 0 0 0 4 4.968a1 1 0 0 0-.714 1.7l-.016.011 3.293 3.306.707.707a1 1 0 0 0 1.414 0l.707-.707L12.7 6.679a1 1 0 0 0-.715-1.711z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M8 15a8 8 0 0 1-8-8V3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4a8 8 0 0 1-8 8zm3.985-10.032a.99.99 0 0 0-.725.319L7.978 8.57 4.755 5.336A.984.984 0 0 0 4 4.968a1 1 0 0 0-.714 1.7l-.016.011 3.293 3.306.707.707a1 1 0 0 0 1.414 0l.707-.707L12.7 6.679a1 1 0 0 0-.715-1.711z"/></svg>
|
До Ширина: | Высота: | Размер: 593 B После Ширина: | Высота: | Размер: 381 B |
|
@ -1,13 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #a0a0a0;
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="glyph-search-16">
|
||||
<path id="Icon_-_Search_-_16" data-name="Icon - Search - 16" class="cls-1" d="M226.989,348.571l-2.2,2.2-9.533-9.534a11.436,11.436,0,1,1,2.2-2.2ZM208.37,323.745a8.407,8.407,0,1,0,8.406,8.406A8.406,8.406,0,0,0,208.37,323.745Z" transform="translate(-196 -320)"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M30.989 28.571l-2.2 2.2-9.533-9.534a11.436 11.436 0 1 1 2.2-2.2zM12.37 3.745a8.407 8.407 0 1 0 8.406 8.406 8.406 8.406 0 0 0-8.406-8.406z" fill="context-fill"/></svg>
|
До Ширина: | Высота: | Размер: 507 B После Ширина: | Высота: | Размер: 258 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g fill="none" stroke="#4d4d4d" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M8 1v3M8 12v3M4.5 11.5l-1.45 1.45M12.95 3.05L11 5M1 8h3M12 8h3"/><circle cx="8" cy="8" r="4"/><path d="M3.05 3.05L5 5M11 11l1.95 1.95"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g fill="none" stroke="context-fill" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M8 1v3m0 8v3m-3.5-3.5l-1.45 1.45m9.9-9.9L11 5M1 8h3m8 0h3"/><circle cx="8" cy="8" r="4"/><path d="M3.05 3.05L5 5m6 6l1.95 1.95"/></g></svg>
|
До Ширина: | Высота: | Размер: 334 B После Ширина: | Высота: | Размер: 332 B |
|
@ -1,11 +1 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="#4d4d4d">
|
||||
<rect x="1" y="1" width="6" height="6" rx="1" ry="1"/>
|
||||
<rect x="9" y="1" width="6" height="6" rx="1" ry="1"/>
|
||||
<rect x="1" y="9" width="6" height="6" rx="1" ry="1"/>
|
||||
<rect x="9" y="9" width="6" height="6" rx="1" ry="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g fill="context-fill"><rect x="1" y="1" width="6" height="6" rx="1" ry="1"/><rect x="9" y="1" width="6" height="6" rx="1" ry="1"/><rect x="1" y="9" width="6" height="6" rx="1" ry="1"/><rect x="9" y="9" width="6" height="6" rx="1" ry="1"/></g></svg>
|
До Ширина: | Высота: | Размер: 567 B После Ширина: | Высота: | Размер: 332 B |
|
@ -1,8 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Context-/-Pocket-Trending" fill="#999999">
|
||||
<path d="M12.164765,5.74981818 C12.4404792,5.74981818 12.5976221,6.06981818 12.4233364,6.28509091 C10.7404792,8.37236364 4.26619353,15.6829091 4.15905067,15.744 C5.70047924,12.3301818 7.1276221,8.976 7.1276221,8.976 L4.3276221,8.976 C4.09905067,8.976 3.9376221,8.74472727 4.02333638,8.52654545 C4.70047924,6.77672727 6.86190781,1.32945455 7.30476495,0.216727273 C7.35333638,0.0916363636 7.46190781,0.0174545455 7.59476495,0.016 C8.32476495,0.0130909091 10.7904792,0.00290909091 12.5790507,0 C12.844765,0 12.9976221,0.305454545 12.8433364,0.525090909 L9.17190781,5.74981818 L12.164765,5.74981818 Z" id="Fill-1"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M12.165 5.75a.33.33 0 0 1 .258.535c-1.683 2.087-8.157 9.398-8.264 9.459a385.997 385.997 0 0 0 2.969-6.768h-2.8a.328.328 0 0 1-.305-.45C4.7 6.777 6.862 1.33 7.305.217a.305.305 0 0 1 .29-.2C8.325.013 10.79.003 12.579 0c.266 0 .419.305.264.525L9.172 5.75h2.993z" fill="context-fill" fill-rule="evenodd"/></svg>
|
До Ширина: | Высота: | Размер: 985 B После Ширина: | Высота: | Размер: 399 B |
|
@ -1,11 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
path {
|
||||
fill: #4d4d4d;
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<path d="M11.414,10l2.293-2.293a1,1,0,0,0,0-1.414,4.418,4.418,0,0,0-.8-.622L11.425,7.15l.008,0-4.3,4.3,0-.017-1.48,1.476a3.865,3.865,0,0,0,.692.834,1,1,0,0,0,1.37-.042L10,11.414l3.293,3.293a1,1,0,0,0,1.414-1.414Z"/>
|
||||
<path d="M14.707,1.293a1,1,0,0,0-1.414,0L9.7,4.882A2.382,2.382,0,0,1,8,2.586V2a1,1,0,0,0-1.707-.707l-5,5A1,1,0,0,0,2,8h.586a2.382,2.382,0,0,1,2.3,1.7L1.293,13.293a1,1,0,1,0,1.414,1.414l12-12A1,1,0,0,0,14.707,1.293Zm-9,6A4.414,4.414,0,0,0,4.136,6.278L6.279,4.136A4.4,4.4,0,0,0,7.292,5.707a4.191,4.191,0,0,0,.9.684l-1.8,1.8A4.2,4.2,0,0,0,5.708,7.293Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M11.414 10l2.293-2.293a1 1 0 0 0 0-1.414 4.418 4.418 0 0 0-.8-.622L11.425 7.15h.008l-4.3 4.3v-.017l-1.48 1.476a3.865 3.865 0 0 0 .692.834 1 1 0 0 0 1.37-.042L10 11.414l3.293 3.293a1 1 0 0 0 1.414-1.414zm3.293-8.707a1 1 0 0 0-1.414 0L9.7 4.882A2.382 2.382 0 0 1 8 2.586V2a1 1 0 0 0-1.707-.707l-5 5A1 1 0 0 0 2 8h.586a2.382 2.382 0 0 1 2.3 1.7l-3.593 3.593a1 1 0 1 0 1.414 1.414l12-12a1 1 0 0 0 0-1.414zm-9 6a4.414 4.414 0 0 0-1.571-1.015l2.143-2.142a4.4 4.4 0 0 0 1.013 1.571 4.191 4.191 0 0 0 .9.684l-1.8 1.8a4.2 4.2 0 0 0-.684-.898z" fill="context-fill"/></svg>
|
До Ширина: | Высота: | Размер: 739 B После Ширина: | Высота: | Размер: 654 B |
|
@ -1,6 +1 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#4d4d4d" d="M14.5 8c-.971 0-1 1-1.75 1a.765.765 0 0 1-.75-.75V5a1 1 0 0 0-1-1H7.75A.765.765 0 0 1 7 3.25c0-.75 1-.779 1-1.75C8 .635 7.1 0 6 0S4 .635 4 1.5c0 .971 1 1 1 1.75a.765.765 0 0 1-.75.75H1a1 1 0 0 0-1 1v2.25A.765.765 0 0 0 .75 8c.75 0 .779-1 1.75-1C3.365 7 4 7.9 4 9s-.635 2-1.5 2c-.971 0-1-1-1.75-1a.765.765 0 0 0-.75.75V15a1 1 0 0 0 1 1h3.25a.765.765 0 0 0 .75-.75c0-.75-1-.779-1-1.75 0-.865.9-1.5 2-1.5s2 .635 2 1.5c0 .971-1 1-1 1.75a.765.765 0 0 0 .75.75H11a1 1 0 0 0 1-1v-3.25a.765.765 0 0 1 .75-.75c.75 0 .779 1 1.75 1 .865 0 1.5-.9 1.5-2s-.635-2-1.5-2z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M14.5 8c-.971 0-1 1-1.75 1a.765.765 0 0 1-.75-.75V5a1 1 0 0 0-1-1H7.75A.765.765 0 0 1 7 3.25c0-.75 1-.779 1-1.75C8 .635 7.1 0 6 0S4 .635 4 1.5c0 .971 1 1 1 1.75a.765.765 0 0 1-.75.75H1a1 1 0 0 0-1 1v2.25A.765.765 0 0 0 .75 8c.75 0 .779-1 1.75-1C3.365 7 4 7.9 4 9s-.635 2-1.5 2c-.971 0-1-1-1.75-1a.765.765 0 0 0-.75.75V15a1 1 0 0 0 1 1h3.25a.765.765 0 0 0 .75-.75c0-.75-1-.779-1-1.75 0-.865.9-1.5 2-1.5s2 .635 2 1.5c0 .971-1 1-1 1.75a.765.765 0 0 0 .75.75H11a1 1 0 0 0 1-1v-3.25a.765.765 0 0 1 .75-.75c.75 0 .779 1 1.75 1 .865 0 1.5-.9 1.5-2s-.635-2-1.5-2z"/></svg>
|
До Ширина: | Высота: | Размер: 888 B После Ширина: | Высота: | Размер: 676 B |
|
@ -1,12 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Icon / ></title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="Icon-/->" stroke-width="2" stroke="#008EA4">
|
||||
<polyline id="Path-2" points="4 2 8 6 4 10"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M4 2l4 4-4 4" stroke-width="2" stroke="context-fill" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
До Ширина: | Высота: | Размер: 644 B После Ширина: | Высота: | Размер: 211 B |
|
@ -3,12 +3,19 @@
|
|||
"newtab_page_title": "Dirica matidi manyen",
|
||||
"default_label_loading": "Tye ka cano…",
|
||||
"header_top_sites": "Kakube maloyo",
|
||||
"header_stories": "Lok madito",
|
||||
"header_visit_again": "Lim doki",
|
||||
"header_bookmarks": "Alamabuk ma cok coki",
|
||||
"header_recommended_by": "Lami tam obedo {provider}",
|
||||
"header_bookmarks_placeholder": "Pud i pee ki alamabuk.",
|
||||
"header_stories_from": "ki bot",
|
||||
"type_label_visited": "Kilimo",
|
||||
"type_label_bookmarked": "Kiketo alamabuk",
|
||||
"type_label_synced": "Kiribo ki i nyonyo mukene",
|
||||
"type_label_recommended": "Ma cuke lamal",
|
||||
"type_label_open": "Tye ayaba",
|
||||
"type_label_topic": "Lok",
|
||||
"type_label_now": "Kombedi",
|
||||
"menu_action_bookmark": "Alamabuk",
|
||||
"menu_action_remove_bookmark": "Kwany alamabuk",
|
||||
"menu_action_copy_address": "Lok kabedo",
|
||||
|
@ -18,12 +25,16 @@
|
|||
"menu_action_dismiss": "Kwer",
|
||||
"menu_action_delete": "Kwany ki ii gin mukato",
|
||||
"menu_action_pin": "Mwon",
|
||||
"menu_action_unpin": "War",
|
||||
"confirm_history_delete_p1": "Imoko ni imito kwanyo nyig jami weng me potbuk man ki i gin mukato mamegi?",
|
||||
"confirm_history_delete_notice_p2": "Pe ki twero gonyo tic man.",
|
||||
"menu_action_save_to_pocket": "Gwoki i jaba",
|
||||
"search_for_something_with": "Yeny pi {search_term} ki:",
|
||||
"search_button": "Yeny",
|
||||
"search_header": "Yeny me {search_engine_name}",
|
||||
"search_web_placeholder": "Yeny kakube",
|
||||
"search_settings": "Lok ter me yeny",
|
||||
"section_info_option": "Ngec",
|
||||
"welcome_title": "Wajoli i dirica matidi manyen",
|
||||
"welcome_body": "Firefox bi tic ki kabedo man me nyuto alamabukke mamegi, coc akwana, vidio, ki potbukke ma ilimo cokcoki ma pi gi tego loyo, wek i dok ii gi ma yot.",
|
||||
"welcome_label": "Tye ka kube ki wiye madito mamegi",
|
||||
|
@ -40,7 +51,11 @@
|
|||
"settings_pane_topsites_body": "Nong kakube ma ilimo loyo.",
|
||||
"settings_pane_topsites_options_showmore": "Nyut rek ariyo",
|
||||
"settings_pane_bookmarks_header": "Alamabuk ma cocoki",
|
||||
"settings_pane_bookmarks_body": "Alamabukke ni ma kicweyo manyen i kabedo acel macek.",
|
||||
"settings_pane_visit_again_header": "Lim Kidoco",
|
||||
"settings_pane_visit_again_body": "Firefox bi nyuti but gin mukato me yeny mamegi ma itwero mito me poo ikome onyo dok cen iyie.",
|
||||
"settings_pane_pocketstories_header": "Lok madito",
|
||||
"settings_pane_pocketstories_body": "Pocket, but jo me Mozilla, bi konyi me kube i jami mabeco loyo ma twero bedo ni pe i nongo.",
|
||||
"settings_pane_done_button": "Otum",
|
||||
"edit_topsites_button_text": "Yubi",
|
||||
"edit_topsites_button_label": "Yub bute pi kakubi ni ma giloyo",
|
||||
|
@ -48,13 +63,27 @@
|
|||
"edit_topsites_showless_button": "Nyut manok",
|
||||
"edit_topsites_done_button": "Otum",
|
||||
"edit_topsites_pin_button": "Mwon kakube man",
|
||||
"edit_topsites_unpin_button": "War kakube man",
|
||||
"edit_topsites_edit_button": "Yub kakube man",
|
||||
"edit_topsites_dismiss_button": "Kwer kakube man",
|
||||
"edit_topsites_add_button": "Medi",
|
||||
"topsites_form_add_header": "Kakube maloyo manyen",
|
||||
"topsites_form_edit_header": "Yub Kakube maloyo",
|
||||
"topsites_form_title_placeholder": "Ket wiye",
|
||||
"topsites_form_url_placeholder": "Coo onyo mwon URL",
|
||||
"topsites_form_add_button": "Medi",
|
||||
"topsites_form_save_button": "Gwoki",
|
||||
"topsites_form_cancel_button": "Kwer"
|
||||
"topsites_form_cancel_button": "Kwer",
|
||||
"topsites_form_url_validation": "URL ma tye atir mite",
|
||||
"pocket_read_more": "Lok macuk gi lamal:",
|
||||
"pocket_read_even_more": "Nen Lok mapol",
|
||||
"pocket_feedback_header": "Kakube maber loyo, dano makato milion 25 aye oyubo.",
|
||||
"pocket_feedback_body": "Pocket, but jo me Mozilla, bi konyi me kube i jami mabeco loyo ma twero bedo ni pe i nongo.",
|
||||
"pocket_send_feedback": "Cwal adwogi",
|
||||
"topstories_empty_state": "Ityeko weng. Rot doki lacen pi lok madito mapol ki bot {provider}. Pe itwero kuro? Yer lok macuke lamal me nongo lok mabeco mapol ki i but kakube.",
|
||||
"manual_migration_explanation": "Tem Firefox ki kakube ki alamabuk ni ma imaro loyo ki i layeny mukene.",
|
||||
"manual_migration_cancel_button": "Pe Apwoyo",
|
||||
"manual_migration_import_button": "Kel kombedi"
|
||||
},
|
||||
"af": {},
|
||||
"an": {},
|
||||
|
@ -227,7 +256,11 @@
|
|||
"pocket_read_even_more": "Daha çox hekayə gör",
|
||||
"pocket_feedback_header": "25 milyon nəfərin dəstəyi ilə internetin ən yaxşıları.",
|
||||
"pocket_feedback_body": "Pocket, Mozilla ailəsinin üzvü, yüksək keyfiyyətli məzmunları kəşf etməyinizə kömək edəcək.",
|
||||
"pocket_send_feedback": "Əks-əlaqə göndər"
|
||||
"pocket_send_feedback": "Əks-əlaqə göndər",
|
||||
"topstories_empty_state": "Hamısını oxudunuz. Yeni {provider} məqalələri üçün daha sonra təkrar yoxlayın. Gözləyə bilmirsiz? Məşhur mövzu seçərək internetdən daha çox gözəl məqalələr tapın.",
|
||||
"manual_migration_explanation": "Firefox səyyahını digər səyyahınızdan olan sevimli sayt və əlfəcinlərinizlə yoxlayın.",
|
||||
"manual_migration_cancel_button": "Xeyr, Təşəkkürlər",
|
||||
"manual_migration_import_button": "İndi idxal et"
|
||||
},
|
||||
"be": {
|
||||
"newtab_page_title": "Новая картка",
|
||||
|
@ -408,6 +441,7 @@
|
|||
"header_stories": "শীর্ষ গল্প",
|
||||
"header_visit_again": "পুনরায় ভিজিট করুন",
|
||||
"header_bookmarks": "সাম্প্রতিক বুকমার্ক",
|
||||
"header_recommended_by": "{provider} দ্বারা সুপারিশকৃত",
|
||||
"header_bookmarks_placeholder": "এখনও কোন বুকমার্ক নেই।",
|
||||
"header_stories_from": "থেকে",
|
||||
"type_label_visited": "পরিদর্শিত",
|
||||
|
@ -416,6 +450,7 @@
|
|||
"type_label_recommended": "ঝোঁক",
|
||||
"type_label_open": "খোলা",
|
||||
"type_label_topic": "টপিক",
|
||||
"type_label_now": "এখন",
|
||||
"menu_action_bookmark": "বুকমার্ক",
|
||||
"menu_action_remove_bookmark": "বুকমার্ক মুছে দিন",
|
||||
"menu_action_copy_address": "ঠিকানা কপি করুন",
|
||||
|
@ -434,6 +469,7 @@
|
|||
"search_header": "{search_engine_name} খুঁজুন",
|
||||
"search_web_placeholder": "ওয়েবে সন্ধান করুন",
|
||||
"search_settings": "সার্চ সেটিংস বদল করুন",
|
||||
"section_info_option": "তথ্য",
|
||||
"welcome_title": "নতুন ট্যাবে আপনাকে স্বাগতম",
|
||||
"welcome_body": "আপনার সাথে মিলে এমন বুর্কমার্ক, নিবন্ধ, ভিডিও এবং পাতা যেগুলো আপনি সম্প্রতি ভ্রমণ করেছে তা Firefox এই জায়গায় দেখাবে, যাতে আপনি সেগুলো দ্রুত খুঁজে পান।",
|
||||
"welcome_label": "আপনার হাইলাইট সমূহ চিহ্নিত করুন",
|
||||
|
@ -452,7 +488,9 @@
|
|||
"settings_pane_bookmarks_header": "সাম্প্রতিক বুকমার্ক",
|
||||
"settings_pane_bookmarks_body": "আপনার নতুন করা বুকমার্ক সহজ অবস্থানে রাখা হয়েছে।",
|
||||
"settings_pane_visit_again_header": "পুনরায় ভিজিট করুন",
|
||||
"settings_pane_visit_again_body": "Firefox আপনার ব্রাউজিং ইতিহাসের এমন একটি অংশ দেখাবে যা আপনি মনে রাখতে চান বা যাতে আবার ফিরে যেতে চান।",
|
||||
"settings_pane_pocketstories_header": "শীর্ষ গল্প",
|
||||
"settings_pane_pocketstories_body": "Pocket, হচ্ছে Mozilla পরিবারের একটি অংশ, যা আপনাকে ভালো-মানের কন্টেন্টের সাথে যুক্ত করবে যা আপনি অন্য কোথাও পাবেন না।",
|
||||
"settings_pane_done_button": "হয়েছে",
|
||||
"edit_topsites_button_text": "সম্পাদনা",
|
||||
"edit_topsites_button_label": "আপনার টপ সাইট সেকশন কাস্টমাইজ করুন",
|
||||
|
@ -467,13 +505,20 @@
|
|||
"topsites_form_add_header": "নতুন শীর্ষ সাইট",
|
||||
"topsites_form_edit_header": "শীর্ষ সাইট সম্পাদনা করুন",
|
||||
"topsites_form_title_placeholder": "নাম দিন",
|
||||
"topsites_form_url_placeholder": "টাইপ করুন অথবা পেস্ট করুন URL",
|
||||
"topsites_form_add_button": "যোগ",
|
||||
"topsites_form_save_button": "সংরক্ষণ",
|
||||
"topsites_form_cancel_button": "বাতিল",
|
||||
"topsites_form_url_validation": "কার্যকর URL প্রয়োজন",
|
||||
"pocket_read_more": "জনপ্রিয় বিষয়:",
|
||||
"pocket_read_even_more": "আরও গল্প দেখুন",
|
||||
"pocket_send_feedback": "প্রতিক্রিয়া জানান"
|
||||
"pocket_feedback_header": "ওয়েব জগতের সেরা, যা ২.৫ লক্ষ মানুষ রক্ষণাবেক্ষণ করে।",
|
||||
"pocket_feedback_body": "Pocket, হচ্ছে Mozilla পরিবারের একটি অংশ, যা আপনাকে ভালো-মানের কন্টেন্টের সাথে যুক্ত করবে যা আপনি অন্য কোথাও পাবেন না।",
|
||||
"pocket_send_feedback": "প্রতিক্রিয়া জানান",
|
||||
"topstories_empty_state": "কিছু একটা ঠিক নেই। {provider} এর শীর্ষ গল্পগুলো পেতে কিছুক্ষণ পর আবার দেখুন। অপেক্ষা করতে চান না? বিশ্বের সেরা গল্পগুলো পেতে কোন জনপ্রিয় বিষয় নির্বাচন করুন।",
|
||||
"manual_migration_explanation": "অন্য ব্রাউজার থেকে আপনার পছন্দের সাইট এবং বুকমার্কগুলো নিয়ে Firefox ব্যবহার করুন।",
|
||||
"manual_migration_cancel_button": "প্রয়োজন নেই",
|
||||
"manual_migration_import_button": "এখনই ইম্পোর্ট করুন"
|
||||
},
|
||||
"bn-IN": {},
|
||||
"br": {},
|
||||
|
@ -564,7 +609,65 @@
|
|||
"manual_migration_cancel_button": "No, gràcies",
|
||||
"manual_migration_import_button": "Importa-ho ara"
|
||||
},
|
||||
"cak": {},
|
||||
"cak": {
|
||||
"newtab_page_title": "K'ak'a' ruwi'",
|
||||
"default_label_loading": "Tajin nusamajij…",
|
||||
"header_top_sites": "Utziläj taq Ruxaq K'amaya'l",
|
||||
"header_stories": "Utziläj taq B'anob'äl",
|
||||
"header_visit_again": "Titz'et chik",
|
||||
"header_bookmarks": "K'ak'a' taq Yaketal",
|
||||
"header_recommended_by": "Chilab'en ruma {provider}",
|
||||
"header_bookmarks_placeholder": "K'a majani k'o jujun taq ayaketal.",
|
||||
"header_stories_from": "richin",
|
||||
"type_label_visited": "Tz'eton",
|
||||
"type_label_bookmarked": "Yakon retal",
|
||||
"type_label_synced": "Ximon rik'in jun chik okisaxel",
|
||||
"type_label_recommended": "Rujawaxik",
|
||||
"type_label_open": "Tijaq",
|
||||
"type_label_now": "Wakami",
|
||||
"menu_action_bookmark": "Yaketal",
|
||||
"menu_action_remove_bookmark": "Tiyuj el ri yaketal",
|
||||
"menu_action_copy_address": "Tiwachib'ëx Ochochib'äl",
|
||||
"menu_action_email_link": "Titaq Ximonel Tzij…",
|
||||
"menu_action_open_new_window": "Tijaq pa jun K'ak'a' Tzuwäch",
|
||||
"menu_action_open_private_window": "Tijaq pa jun K'ak'a' Ichinan Tzuwäch",
|
||||
"menu_action_dismiss": "Tichup ruwäch",
|
||||
"menu_action_delete": "Tiyuj el pa ri Natab'äl",
|
||||
"confirm_history_delete_p1": "¿La kan nawajo ye'ayüj el ronojel ri kib'eyal re taq ruxaq re' chi kikojol ri anatab'al?",
|
||||
"confirm_history_delete_notice_p2": "Man yatikïr ta najäl re b'anïk re'.",
|
||||
"menu_action_save_to_pocket": "Tiyak pa Pocket",
|
||||
"search_for_something_with": "Tikanoj {search_term} rik'in:",
|
||||
"search_button": "Tikanöx",
|
||||
"search_header": "{search_engine_name} Tikanöx",
|
||||
"search_web_placeholder": "Tikanöx pa Ajk'amaya'l",
|
||||
"search_settings": "Tijal Runuk'ulem Kanoxïk",
|
||||
"section_info_option": "Rutzijol",
|
||||
"welcome_title": "Ütz apetik pa ri k'ak'a' ruwi'",
|
||||
"welcome_body": "Firefox xtrokisaj re k'ojlib'äl re' richin xtuk'üt ri taq ruwi', rutzijol, tzuwäch chuqa' taq ruxaq yalan kejqalem ri k'a ja' xe'atz'ët, richin chanin yatikïr yatok jun mul chik.",
|
||||
"welcome_label": "Tiya' ketal ri Nïm taq K'ojlib'äl",
|
||||
"time_label_less_than_minute": "<1m",
|
||||
"time_label_minute": "{number}m",
|
||||
"time_label_hour": "{number}m",
|
||||
"time_label_day": "{ajilab'äl}m",
|
||||
"settings_pane_button_label": "Tawichinaj ri ruxaq richin K'ak'a' Ruwi'",
|
||||
"settings_pane_header": "K'ak'a' Ruwi' Taq Ajowab'äl",
|
||||
"settings_pane_body": "Tacha' ri natz'ët toq najäq jun k'ak'a' ruwi'.",
|
||||
"settings_pane_search_header": "Tikanöx",
|
||||
"settings_pane_search_body": "Tikanoj ri k'ak'a' taq ruwi' pa ri K'amaya'l.",
|
||||
"settings_pane_topsites_header": "Utziläj taq ruxaq K'amaya'l",
|
||||
"settings_pane_topsites_body": "Katok pa ri taq ajk'amaya'l yalan ye'atz'ët.",
|
||||
"settings_pane_topsites_options_showmore": "Kek'ut pe ka'i' cholaj",
|
||||
"settings_pane_bookmarks_header": "K'ak'a' taq Yaketal",
|
||||
"settings_pane_bookmarks_body": "Ri taq awajowab'äl k'a ri xenuk' pa jun utziläj k'ojlib'äl.",
|
||||
"settings_pane_visit_again_header": "Tab'etz'eta' chik",
|
||||
"settings_pane_visit_again_body": "Firefox xtuk'ut pe jalajoj taq rub'eyal ri b'anob'äl richin rukusaxik ri k'amaya'l rik'in jub'a' nawajo' nanataj chuqa' yatikir natzu' chik.",
|
||||
"settings_pane_pocketstories_header": "Utziläj taq B'anob'äl",
|
||||
"settings_pane_done_button": "Xk'is",
|
||||
"edit_topsites_button_text": "Tinuk'",
|
||||
"edit_topsites_button_label": "Tab'ana' runuk'ulem ri kitanaj Nimaläj taq Ruxaq K'amaya'l",
|
||||
"topsites_form_cancel_button": "Tiq'at",
|
||||
"manual_migration_import_button": "Tijik' pe"
|
||||
},
|
||||
"cs": {
|
||||
"newtab_page_title": "Nový panel",
|
||||
"default_label_loading": "Načítání…",
|
||||
|
@ -1163,7 +1266,6 @@
|
|||
"en-US": {
|
||||
"newtab_page_title": "New Tab",
|
||||
"default_label_loading": "Loading…",
|
||||
"home_page_title": "{build} Start Page",
|
||||
"header_top_sites": "Top Sites",
|
||||
"header_stories": "Top Stories",
|
||||
"header_visit_again": "Visit Again",
|
||||
|
@ -1749,7 +1851,77 @@
|
|||
"pocket_feedback_body": "Pocket, osana Mozilla perekonnast, aitab sul leida kvaliteetset sisu, mida sa muidu poleks ehk leidnud.",
|
||||
"pocket_send_feedback": "Saada tagasisidet"
|
||||
},
|
||||
"eu": {},
|
||||
"eu": {
|
||||
"newtab_page_title": "Fitxa berria",
|
||||
"default_label_loading": "Kargatzen…",
|
||||
"header_top_sites": "Gune erabilienak",
|
||||
"header_visit_again": "Bisitatu berriro",
|
||||
"header_bookmarks": "Azken laster-markak",
|
||||
"header_recommended_by": "{provider} hornitzaileak gomendatuta",
|
||||
"header_bookmarks_placeholder": "Ez daukazu laster-markarik oraindik.",
|
||||
"type_label_visited": "Bisitatuta",
|
||||
"type_label_bookmarked": "Laster-marka eginda",
|
||||
"type_label_synced": "Beste gailu batetik sinkronizatuta",
|
||||
"type_label_recommended": "Joerak",
|
||||
"type_label_open": "Ireki",
|
||||
"type_label_topic": "Gaia",
|
||||
"type_label_now": "Orain",
|
||||
"menu_action_bookmark": "Egin laster-marka",
|
||||
"menu_action_remove_bookmark": "Kendu laster-marka",
|
||||
"menu_action_copy_address": "Kopiatu helbidea",
|
||||
"menu_action_email_link": "Bidali lotura postaz…",
|
||||
"menu_action_open_new_window": "Ireki leiho berri batean",
|
||||
"menu_action_open_private_window": "Ireki leiho pribatu berrian",
|
||||
"menu_action_dismiss": "Baztertu",
|
||||
"menu_action_delete": "Ezabatu historiatik",
|
||||
"menu_action_pin": "Ainguratu",
|
||||
"menu_action_unpin": "Desainguratu",
|
||||
"confirm_history_delete_p1": "Ziur zaude orri honen agerpen guztiak ezabatu nahi dituzula historiatik?",
|
||||
"confirm_history_delete_notice_p2": "Ekintza hau ezin da desegin.",
|
||||
"menu_action_save_to_pocket": "Gorde Pocket-en",
|
||||
"search_for_something_with": "Bilatu {search_term} honekin:",
|
||||
"search_button": "Bilatu",
|
||||
"search_header": "{search_engine_name} bilaketa",
|
||||
"search_web_placeholder": "Bilatu webean",
|
||||
"search_settings": "Aldatu bilaketa-ezarpenak",
|
||||
"section_info_option": "Informazioa",
|
||||
"welcome_title": "Ongi etorri fitxa berrira",
|
||||
"time_label_less_than_minute": "<1m",
|
||||
"time_label_minute": "{number}m",
|
||||
"time_label_hour": "{number}h",
|
||||
"time_label_day": "{number}d",
|
||||
"settings_pane_button_label": "Pertsonalizatu fitxa berriaren orria",
|
||||
"settings_pane_header": "Fitxa berriaren hobespenak",
|
||||
"settings_pane_body": "Aukeratu fitxa berria irekitzean ikusten duzuna.",
|
||||
"settings_pane_search_header": "Bilatu",
|
||||
"settings_pane_search_body": "Bilatu webean zure fitxa berritik.",
|
||||
"settings_pane_topsites_header": "Gune erabilienak",
|
||||
"settings_pane_topsites_options_showmore": "Erakutsi bi errenkada",
|
||||
"settings_pane_bookmarks_header": "Azken laster-markak",
|
||||
"settings_pane_visit_again_header": "Bisitatu berriro",
|
||||
"settings_pane_visit_again_body": "Gogoratu edo itzuli nahiko duzun historiaren zatia erakutsiko dizu Firefoxek.",
|
||||
"settings_pane_pocketstories_body": "Pocket-ek, Mozilla familiakideak, bestela aurkituko ez zenukeen kalitate handiko edukiarekin konektatzen lagunduko dizu.",
|
||||
"settings_pane_done_button": "Eginda",
|
||||
"edit_topsites_button_text": "Editatu",
|
||||
"edit_topsites_button_label": "Pertsonalizatu gune erabilienen atala",
|
||||
"edit_topsites_showmore_button": "Erakutsi gehiago",
|
||||
"edit_topsites_showless_button": "Erakutsi gutxiago",
|
||||
"edit_topsites_done_button": "Eginda",
|
||||
"edit_topsites_pin_button": "Ainguratu gune hau",
|
||||
"edit_topsites_unpin_button": "Desainguratu gune hau",
|
||||
"edit_topsites_edit_button": "Editatu gune hau",
|
||||
"edit_topsites_dismiss_button": "Baztertu gune hau",
|
||||
"edit_topsites_add_button": "Gehitu",
|
||||
"topsites_form_title_placeholder": "Idatzi izenburua",
|
||||
"topsites_form_url_placeholder": "Idatzi edo itsatsi URLa",
|
||||
"topsites_form_add_button": "Gehitu",
|
||||
"topsites_form_save_button": "Gorde",
|
||||
"topsites_form_cancel_button": "Utzi",
|
||||
"topsites_form_url_validation": "Baliozko URLa behar da",
|
||||
"pocket_send_feedback": "Bidali iritzia",
|
||||
"manual_migration_cancel_button": "Ez, eskerrik asko",
|
||||
"manual_migration_import_button": "Inportatu orain"
|
||||
},
|
||||
"fa": {
|
||||
"newtab_page_title": "زبانه جدید",
|
||||
"default_label_loading": "در حال بارگیری…",
|
||||
|
@ -1831,6 +2003,7 @@
|
|||
"pocket_feedback_header": "بهترینهای وب، گزینش شده توسط بیش از ۲۵ میلیون نفر.",
|
||||
"pocket_feedback_body": "Pocket، بخشی از خانواده موزیلا، کمک خواهد کرد تا به محتوایی با کیفیت بالا مرتبط شوید که در غیر این صورت ممکن بود پیدا نکنید.",
|
||||
"pocket_send_feedback": "ارسال بازخورد",
|
||||
"topstories_empty_state": "فعلا تموم شد. بعدا دوباره سر بزن تا مطالب جدید از {provider} ببینی. نمیتونی صبر کنی؟ یک موضوع محبوب رو انتخاب کن تا مطالب جالب مرتبط از سراسر دنیا رو پیدا کنی.",
|
||||
"manual_migration_explanation": "فایرفاکس را با سایتهای مورد علاقه و نشانکهای خود در سایر مرورگرها امتحان کنید.",
|
||||
"manual_migration_cancel_button": "نه ممنون",
|
||||
"manual_migration_import_button": "هماکنون وارد شوند"
|
||||
|
@ -1843,13 +2016,16 @@
|
|||
"header_stories": "Ykkösjutut",
|
||||
"header_visit_again": "Käy toistekin",
|
||||
"header_bookmarks": "Uusimmat kirjanmerkit",
|
||||
"header_recommended_by": "Suositukset lähteestä {provider}",
|
||||
"header_bookmarks_placeholder": "Sinulla ei ole vielä kirjanmerkkejä.",
|
||||
"header_stories_from": "Lähde",
|
||||
"type_label_visited": "Vierailtu",
|
||||
"type_label_bookmarked": "Kirjanmerkki",
|
||||
"type_label_synced": "Synkronoitu toiselta laitteelta",
|
||||
"type_label_recommended": "Pinnalla",
|
||||
"type_label_open": "Avoin",
|
||||
"type_label_topic": "Aihe",
|
||||
"type_label_now": "Nyt",
|
||||
"menu_action_bookmark": "Lisää kirjanmerkki",
|
||||
"menu_action_remove_bookmark": "Poista kirjanmerkki",
|
||||
"menu_action_copy_address": "Kopioi osoite",
|
||||
|
@ -1868,6 +2044,7 @@
|
|||
"search_header": "{search_engine_name}-haku",
|
||||
"search_web_placeholder": "Verkkohaku",
|
||||
"search_settings": "Muuta hakuasetuksia",
|
||||
"section_info_option": "Tietoa",
|
||||
"welcome_title": "Tervetuloa uuteen välilehteen",
|
||||
"welcome_body": "Firefox käyttää tätä tilaa näyttämään olennaisimmat kirjanmerkit, artikkelit, videot ja sivut, joita olet katsellut, jotta pääset niihin takaisin nopeasti.",
|
||||
"welcome_label": "Tunnistetaan nostojasi",
|
||||
|
@ -1881,11 +2058,14 @@
|
|||
"settings_pane_search_header": "Haku",
|
||||
"settings_pane_search_body": "Tee verkkohakuja uudesta välilehdestä.",
|
||||
"settings_pane_topsites_header": "Ykkössivustot",
|
||||
"settings_pane_topsites_body": "Näe eniten vierailemasi sivustot.",
|
||||
"settings_pane_topsites_options_showmore": "Näytä kaksi riviä",
|
||||
"settings_pane_bookmarks_header": "Uusimmat kirjanmerkit",
|
||||
"settings_pane_bookmarks_body": "Uusimmat kirjanmerkkisi, yhdessä kätevässä paikassa.",
|
||||
"settings_pane_visit_again_header": "Käy toistekin",
|
||||
"settings_pane_visit_again_body": "Firefox näyttää selaushistoriastasi palasia, jotka saatat haluta muistaa tai joissa haluat ehkä käydä.",
|
||||
"settings_pane_pocketstories_header": "Ykkösjutut",
|
||||
"settings_pane_pocketstories_body": "Pocket, osa Mozilla-perhettä, auttaa löytämään laadukasta sisältöä, jota et ehkä olisi muuten löytänyt.",
|
||||
"settings_pane_done_button": "Valmis",
|
||||
"edit_topsites_button_text": "Muokkaa",
|
||||
"edit_topsites_button_label": "Muokkaa Ykkössivustot-osiota",
|
||||
|
@ -1908,7 +2088,12 @@
|
|||
"pocket_read_more": "Suositut aiheet:",
|
||||
"pocket_read_even_more": "Katso lisää juttuja",
|
||||
"pocket_feedback_header": "Netin parhaat palat, valikoitu yli 25 miljoonan ihmisen voimin.",
|
||||
"pocket_send_feedback": "Lähetä palautetta"
|
||||
"pocket_feedback_body": "Pocket, osa Mozilla-perhettä, auttaa löytämään laadukasta sisältöä, jota et ehkä olisi muuten löytänyt.",
|
||||
"pocket_send_feedback": "Lähetä palautetta",
|
||||
"topstories_empty_state": "Ei enempää suosituksia juuri nyt. Katso myöhemmin uudestaan lisää ykkösjuttuja lähteestä {provider}. Etkö malta odottaa? Valitse suosittu aihe ja löydä lisää hyviä juttuja ympäri verkkoa.",
|
||||
"manual_migration_explanation": "Kokeile Firefoxia toisesta selaimesta tuotujen suosikkisivustojesi ja kirjanmerkkiesi kanssa.",
|
||||
"manual_migration_cancel_button": "Ei kiitos",
|
||||
"manual_migration_import_button": "Tuo nyt"
|
||||
},
|
||||
"fr": {
|
||||
"newtab_page_title": "Nouvel onglet",
|
||||
|
@ -1917,7 +2102,7 @@
|
|||
"header_stories": "Articles populaires",
|
||||
"header_visit_again": "Visiter à nouveau",
|
||||
"header_bookmarks": "Marque-pages récents",
|
||||
"header_recommended_by": "Recommandé par {provider}",
|
||||
"header_recommended_by": "Recommandations par {provider}",
|
||||
"header_bookmarks_placeholder": "Vous ne possédez aucun marque-page pour l’instant.",
|
||||
"header_stories_from": "par",
|
||||
"type_label_visited": "Visité",
|
||||
|
@ -1940,7 +2125,7 @@
|
|||
"confirm_history_delete_p1": "Voulez-vous vraiment supprimer de l’historique toutes les occurrences de cette page ?",
|
||||
"confirm_history_delete_notice_p2": "Cette action est irréversible.",
|
||||
"menu_action_save_to_pocket": "Enregistrer dans Pocket",
|
||||
"search_for_something_with": "Recherche pour {search_term} avec :",
|
||||
"search_for_something_with": "Rechercher {search_term} avec :",
|
||||
"search_button": "Rechercher",
|
||||
"search_header": "Recherche {search_engine_name}",
|
||||
"search_web_placeholder": "Rechercher sur le Web",
|
||||
|
@ -2168,6 +2353,7 @@
|
|||
"header_stories": "Brod nan sgeul",
|
||||
"header_visit_again": "Tadhail a-rithist",
|
||||
"header_bookmarks": "Comharran-lìn o chionn goirid",
|
||||
"header_recommended_by": "’Ga mholadh le {provider}",
|
||||
"header_bookmarks_placeholder": "Chan eil comharra-lìn sam bith agad fhathast.",
|
||||
"header_stories_from": "o",
|
||||
"type_label_visited": "Na thadhail thu air",
|
||||
|
@ -2176,6 +2362,7 @@
|
|||
"type_label_recommended": "A’ treandadh",
|
||||
"type_label_open": "Fosgailte",
|
||||
"type_label_topic": "Cuspair",
|
||||
"type_label_now": "An-dràsta",
|
||||
"menu_action_bookmark": "Comharra-lìn",
|
||||
"menu_action_remove_bookmark": "Thoir an comharra-lìn air falbh",
|
||||
"menu_action_copy_address": "Dèan lethbhreac dhen t-seòladh",
|
||||
|
@ -2194,6 +2381,7 @@
|
|||
"search_header": "Lorg le {search_engine_name}",
|
||||
"search_web_placeholder": "Lorg air an lìon",
|
||||
"search_settings": "Atharraich roghainnean an luirg",
|
||||
"section_info_option": "Fiosrachadh",
|
||||
"welcome_title": "Fàilte gun taba ùr",
|
||||
"welcome_body": "Seallaidh Firefox na comharran-lìn, artaigealan, videothan is duilleagan as iomchaidhe dhut, an fheadhainn air an do thadhail thu o chionn goirid, ach an ruig thu iad gu luath.",
|
||||
"welcome_label": "Ag aithneachadh nan highlights agad",
|
||||
|
@ -2238,7 +2426,11 @@
|
|||
"pocket_read_even_more": "Seall barrachd sgeul",
|
||||
"pocket_feedback_header": "Brod an eadar-lìn, air a dheasachadh le barrachd air 25 millean duine.",
|
||||
"pocket_feedback_body": "Pocket, ball de theaghlach Mozilla, a cheanglas tu ri susbaint fhìor-mhath nach biodh tu air fhaicinn air dòigh eile.",
|
||||
"pocket_send_feedback": "Dè do bheachd air?"
|
||||
"pocket_send_feedback": "Dè do bheachd air?",
|
||||
"topstories_empty_state": "Sin na naidheachdan uile o {provider} an-dràsta ach bidh barrachd ann a dh’aithghearr. No thoir sùil air cuspair air a bheil fèill mhòr is leugh na tha a’ dol mun cuairt air an lìon an-dràsta.",
|
||||
"manual_migration_explanation": "Feuch Firefox leis na làraichean is comharran-lìn as fhearr leat o bhrabhsair eile.",
|
||||
"manual_migration_cancel_button": "Chan eil, tapadh leibh",
|
||||
"manual_migration_import_button": "Ion-phortaich an-dràsta"
|
||||
},
|
||||
"gl": {},
|
||||
"gn": {},
|
||||
|
@ -2279,6 +2471,7 @@
|
|||
"header_stories": "סיפורים מובילים",
|
||||
"header_visit_again": "ביקור חוזר",
|
||||
"header_bookmarks": "סימניות אחרונות",
|
||||
"header_recommended_by": "מומלץ על ידי {provider}",
|
||||
"header_bookmarks_placeholder": "אין לך סימניות עדיין.",
|
||||
"header_stories_from": "מאת",
|
||||
"type_label_visited": "ביקורים קודמים",
|
||||
|
@ -2287,6 +2480,7 @@
|
|||
"type_label_recommended": "פופולרי",
|
||||
"type_label_open": "פתיחה",
|
||||
"type_label_topic": "נושא",
|
||||
"type_label_now": "עכשיו",
|
||||
"menu_action_bookmark": "הוספת סימניה",
|
||||
"menu_action_remove_bookmark": "הסרת סימניה",
|
||||
"menu_action_copy_address": "העתקת כתובת",
|
||||
|
@ -2305,6 +2499,7 @@
|
|||
"search_header": "חיפוש ב־{search_engine_name}",
|
||||
"search_web_placeholder": "חיפוש ברשת",
|
||||
"search_settings": "שינוי הגדרות חיפוש",
|
||||
"section_info_option": "מידע",
|
||||
"welcome_title": "ברוכים הבאים לדף הלשונית החדשה",
|
||||
"welcome_body": "Firefox ישתמש באזור זה כדי להציג את הסימניות הרלוונטיות ביותר, מאמרים, סרטוני וידאו ודפים שביקרת בהם לאחרונה, כך שניתן יהיה לגשת אליהם שוב בקלות.",
|
||||
"welcome_label": "תחומי העניין שלך מזוהים",
|
||||
|
@ -2331,11 +2526,11 @@
|
|||
"edit_topsites_button_label": "התאמת אגף האתרים המובילים שלך",
|
||||
"edit_topsites_showmore_button": "להציג יותר",
|
||||
"edit_topsites_showless_button": "להציג פחות",
|
||||
"edit_topsites_done_button": "בוצע",
|
||||
"edit_topsites_done_button": "סיום",
|
||||
"edit_topsites_pin_button": "נעיצת אתר זה",
|
||||
"edit_topsites_unpin_button": "ביטול הצמדת אתר זה",
|
||||
"edit_topsites_edit_button": "עריכת אתר זה",
|
||||
"edit_topsites_dismiss_button": "התעלמות מאתר זה",
|
||||
"edit_topsites_dismiss_button": "הסרת אתר זה",
|
||||
"edit_topsites_add_button": "הוספה",
|
||||
"topsites_form_add_header": "אתר מוביל חדש",
|
||||
"topsites_form_edit_header": "עריכת אתר מוביל",
|
||||
|
@ -2349,7 +2544,10 @@
|
|||
"pocket_read_even_more": "צפייה בחדשות נוספות",
|
||||
"pocket_feedback_header": "המיטב מרחבי האינטרנט, נאסף על ידי 25 מיליון אנשים.",
|
||||
"pocket_feedback_body": "Pocket, חלק ממשפחת Mozilla, יסייע לך להתחבר לתוכן באיכות גבוהה שיתכן שלא היה מגיע אליך בדרך אחרת.",
|
||||
"pocket_send_feedback": "שליחת משוב"
|
||||
"pocket_send_feedback": "שליחת משוב",
|
||||
"manual_migration_explanation": "נסו את Firefox עם האתרים המועדפים עליך והסימניות שלך מדפדפן אחר.",
|
||||
"manual_migration_cancel_button": "לא תודה",
|
||||
"manual_migration_import_button": "ייבוא כעת"
|
||||
},
|
||||
"hi-IN": {
|
||||
"newtab_page_title": "नया टैब",
|
||||
|
@ -2408,12 +2606,19 @@
|
|||
"newtab_page_title": "Nova kartica",
|
||||
"default_label_loading": "Učitavanje…",
|
||||
"header_top_sites": "Najbolje stranice",
|
||||
"header_highlights": "Istaknuto",
|
||||
"header_stories": "Najbolje priče",
|
||||
"header_visit_again": "Posjetite ponovno",
|
||||
"header_bookmarks": "Nedavne zabilješke",
|
||||
"header_recommended_by": "Preporučeno od {provider}",
|
||||
"header_bookmarks_placeholder": "Još nemate niti jednu zabilješku.",
|
||||
"header_stories_from": "od",
|
||||
"type_label_visited": "Posjećeno",
|
||||
"type_label_bookmarked": "Zabilježeno",
|
||||
"type_label_synced": "Sinkronizirano s drugog uređaja",
|
||||
"type_label_recommended": "Popularno",
|
||||
"type_label_open": "Otvori",
|
||||
"type_label_topic": "Tema",
|
||||
"type_label_now": "Sada",
|
||||
"menu_action_bookmark": "Zabilježi stranicu",
|
||||
"menu_action_remove_bookmark": "Ukloni zabilješku",
|
||||
"menu_action_copy_address": "Kopiraj adresu",
|
||||
|
@ -2422,11 +2627,17 @@
|
|||
"menu_action_open_private_window": "Otvori u novom privatnom prozoru",
|
||||
"menu_action_dismiss": "Odbaci",
|
||||
"menu_action_delete": "Obriši iz povijesti",
|
||||
"menu_action_pin": "Zakači",
|
||||
"menu_action_unpin": "Otkači",
|
||||
"confirm_history_delete_p1": "Jeste li sigurni da želite obrisati sve primjere ove stranice iz vaše povijesti?",
|
||||
"confirm_history_delete_notice_p2": "Ova radnja je nepovratna.",
|
||||
"menu_action_save_to_pocket": "Spremi u Pocket",
|
||||
"search_for_something_with": "Traži {search_term} s:",
|
||||
"search_button": "Traži",
|
||||
"search_header": "{search_engine_name} pretraživanje",
|
||||
"search_web_placeholder": "Pretraži web",
|
||||
"search_settings": "Promijeni postavke pretraživanja",
|
||||
"section_info_option": "Info",
|
||||
"welcome_title": "Dobro došli u novu karticu",
|
||||
"welcome_body": "Firefox će koristiti ovaj prostor kako bi vam pokazao najbitnije zabilješke, članke, video uratke i stranice koje ste nedavno posjetili, tako da se možete lako vratiti na njih.",
|
||||
"welcome_label": "Identificiranje istaknutog",
|
||||
|
@ -2442,8 +2653,12 @@
|
|||
"settings_pane_topsites_header": "Najbolje stranice",
|
||||
"settings_pane_topsites_body": "Pristupite stranicama koje najčešće posjećujete.",
|
||||
"settings_pane_topsites_options_showmore": "Prikaži dva reda",
|
||||
"settings_pane_highlights_header": "Istaknuto",
|
||||
"settings_pane_highlights_body": "Osvrnite se na nedavno posjećene stranice i nove zabilješke.",
|
||||
"settings_pane_bookmarks_header": "Nedavne zabilješke",
|
||||
"settings_pane_bookmarks_body": "Vaše novo stvorene zabilješke na jednom praktičnom mjestu.",
|
||||
"settings_pane_visit_again_header": "Posjetite ponovno",
|
||||
"settings_pane_visit_again_body": "Firefox će vam prikazati dijelove vaše povijesti pretraživanja koje možda želite zapamtiti ili posjetiti ponovno.",
|
||||
"settings_pane_pocketstories_header": "Najbolje priče",
|
||||
"settings_pane_pocketstories_body": "Pocket, dio Mozilla obitelji, povezat će vas sa sadržajem visoke kvalitete koji možda inače ne biste pronašli.",
|
||||
"settings_pane_done_button": "Gotovo",
|
||||
"edit_topsites_button_text": "Uredi",
|
||||
"edit_topsites_button_label": "Prilagodite odjel s najboljim stranicama",
|
||||
|
@ -2451,8 +2666,27 @@
|
|||
"edit_topsites_showless_button": "Prikaži manje",
|
||||
"edit_topsites_done_button": "Gotovo",
|
||||
"edit_topsites_pin_button": "Zakači stranicu",
|
||||
"edit_topsites_unpin_button": "Otkači ovu stranicu",
|
||||
"edit_topsites_edit_button": "Uredi ovu stranicu",
|
||||
"edit_topsites_dismiss_button": "Odbaci stranicu"
|
||||
"edit_topsites_dismiss_button": "Odbaci stranicu",
|
||||
"edit_topsites_add_button": "Dodaj",
|
||||
"topsites_form_add_header": "Nova najbolja stranica",
|
||||
"topsites_form_edit_header": "Uredi najbolju stranicu",
|
||||
"topsites_form_title_placeholder": "Unesi naslov",
|
||||
"topsites_form_url_placeholder": "Utipkajte ili zalijepite URL",
|
||||
"topsites_form_add_button": "Dodaj",
|
||||
"topsites_form_save_button": "Spremi",
|
||||
"topsites_form_cancel_button": "Otkaži",
|
||||
"topsites_form_url_validation": "Potrebno je unijeti ispravan URL",
|
||||
"pocket_read_more": "Popularne teme:",
|
||||
"pocket_read_even_more": "Prikaži više priča",
|
||||
"pocket_feedback_header": "Najbolje od interneta, birano od preko 25 miliona ljudi.",
|
||||
"pocket_feedback_body": "Pocket, dio Mozilla obitelji, povezat će vas sa sadržajem visoke kvalitete koji možda inače ne biste pronašli.",
|
||||
"pocket_send_feedback": "Pošaljite povratnu informaciju",
|
||||
"topstories_empty_state": "Provjerite kasnije za više najpopularnijih priča od {provider}. Ne možete čekati? Odaberite popularne teme kako biste pronašli više kvalitetnih priča s cijelog weba.",
|
||||
"manual_migration_explanation": "Probajte Firefox s vašim omiljenim stranicama i zabilješkama iz drugog pretraživača.",
|
||||
"manual_migration_cancel_button": "Ne hvala",
|
||||
"manual_migration_import_button": "Uvezi sada"
|
||||
},
|
||||
"hsb": {
|
||||
"newtab_page_title": "Nowy rajtark",
|
||||
|
@ -3467,7 +3701,92 @@
|
|||
"newtab_page_title": "Jauna cilne"
|
||||
},
|
||||
"mai": {},
|
||||
"mk": {},
|
||||
"mk": {
|
||||
"newtab_page_title": "Ново јазиче",
|
||||
"default_label_loading": "Се вчитува…",
|
||||
"header_top_sites": "Врвни мрежни места",
|
||||
"header_stories": "Врвни написи",
|
||||
"header_visit_again": "Посети повторно",
|
||||
"header_bookmarks": "Скорешни обележувачи",
|
||||
"header_recommended_by": "Препорачано од {provider}",
|
||||
"header_bookmarks_placeholder": "Сѐ уште немате обележувачи.",
|
||||
"header_stories_from": "од",
|
||||
"type_label_visited": "Посетени",
|
||||
"type_label_bookmarked": "Обележани",
|
||||
"type_label_synced": "Синхронизирани од други уреди",
|
||||
"type_label_recommended": "Во тренд",
|
||||
"type_label_open": "Отворени",
|
||||
"type_label_topic": "Тема",
|
||||
"type_label_now": "Сега",
|
||||
"menu_action_bookmark": "Обележувач",
|
||||
"menu_action_remove_bookmark": "Отстрани обележувач",
|
||||
"menu_action_copy_address": "Копирај адреса",
|
||||
"menu_action_email_link": "Испрати врска…",
|
||||
"menu_action_open_new_window": "Отвори во нов прозорец",
|
||||
"menu_action_open_private_window": "Отвори во нов приватен прозорец",
|
||||
"menu_action_dismiss": "Откажи",
|
||||
"menu_action_delete": "Избриши од историја",
|
||||
"menu_action_pin": "Прикачи",
|
||||
"menu_action_unpin": "Откачи",
|
||||
"confirm_history_delete_p1": "Дали сте сигурни дека сакате да ја избришете оваа страница отсекаде во Вашата историја на прелистување?",
|
||||
"confirm_history_delete_notice_p2": "Ова дејство не може да се одврати.",
|
||||
"menu_action_save_to_pocket": "Зачувај во Pocket",
|
||||
"search_for_something_with": "Пребарај за {search_term} со:",
|
||||
"search_button": "Барај",
|
||||
"search_header": "Пребарување со {search_engine_name}",
|
||||
"search_web_placeholder": "Пребарајте на Интернет",
|
||||
"search_settings": "Промени поставувања за пребарување",
|
||||
"section_info_option": "Инфо",
|
||||
"welcome_title": "Добредојдовте во новото јазиче",
|
||||
"welcome_body": "Firefox ќе го искористи овој простор за да Ви ги прикаже најрелевантните обележувачи, написи, видеа и страници што сте ги посетиле, за да можете лесно да им се навратите.",
|
||||
"welcome_label": "Ги откривам Вашите интереси",
|
||||
"time_label_less_than_minute": "< 1 м",
|
||||
"time_label_minute": "{number} м",
|
||||
"time_label_hour": "{number} ч",
|
||||
"time_label_day": "{number} д",
|
||||
"settings_pane_button_label": "Прилагодете ја страницата на Вашето Ново јазиче",
|
||||
"settings_pane_header": "Преференци за Ново јазиче",
|
||||
"settings_pane_body": "Изберете што ќе гледате кога ќе отворите ново јазиче.",
|
||||
"settings_pane_search_header": "Пребарување",
|
||||
"settings_pane_search_body": "Пребарајте низ Интернет од Вашето ново јазиче.",
|
||||
"settings_pane_topsites_header": "Врвни мрежни места",
|
||||
"settings_pane_topsites_body": "Пристапете до мрежните места што ги посетувате најмногу.",
|
||||
"settings_pane_topsites_options_showmore": "Прикажи два реда",
|
||||
"settings_pane_bookmarks_header": "Скорешни обележувачи",
|
||||
"settings_pane_bookmarks_body": "Вашите нови обележувачи во едно згодно место.",
|
||||
"settings_pane_visit_again_header": "Посети повторно",
|
||||
"settings_pane_visit_again_body": "Firefox ќе прикаже делови од Вашата историја на прелистување кои можеби би сакале да ги запомните или пак да им се навратите.",
|
||||
"settings_pane_pocketstories_header": "Врвни написи",
|
||||
"settings_pane_pocketstories_body": "Pocket, дел од семејството на Mozilla, ќе Ви помогне да стигнете до високо-квалитетни содржини кои можеби не би ги откриле на друг начин.",
|
||||
"settings_pane_done_button": "Готово",
|
||||
"edit_topsites_button_text": "Уреди",
|
||||
"edit_topsites_button_label": "Прилагодете ги Вашите Врвни мрежни места",
|
||||
"edit_topsites_showmore_button": "Прикажи повеќе",
|
||||
"edit_topsites_showless_button": "Прикажи помалку",
|
||||
"edit_topsites_done_button": "Готово",
|
||||
"edit_topsites_pin_button": "Прикачи го ова мрежно место",
|
||||
"edit_topsites_unpin_button": "Откачи го ова мрежно место",
|
||||
"edit_topsites_edit_button": "Уреди го ова место",
|
||||
"edit_topsites_dismiss_button": "Отфрли го ова место",
|
||||
"edit_topsites_add_button": "Додај",
|
||||
"topsites_form_add_header": "Ново врвно мрежно место",
|
||||
"topsites_form_edit_header": "Уреди врвно мрежно место",
|
||||
"topsites_form_title_placeholder": "Внесете наслов",
|
||||
"topsites_form_url_placeholder": "Внесете или вметнете URL",
|
||||
"topsites_form_add_button": "Додај",
|
||||
"topsites_form_save_button": "Сними",
|
||||
"topsites_form_cancel_button": "Откажи",
|
||||
"topsites_form_url_validation": "Потребен е валиден URL",
|
||||
"pocket_read_more": "Популарни теми:",
|
||||
"pocket_read_even_more": "Види повеќе написи",
|
||||
"pocket_feedback_header": "Најдоброто од Интернет, одбрано од повеќе од 25 милиони луѓе.",
|
||||
"pocket_feedback_body": "Pocket, дел од семејството на Mozilla, ќе Ви помогне да стигнете до високо-квалитетни содржини кои можеби не би ги откриле на друг начин.",
|
||||
"pocket_send_feedback": "Остави коментар",
|
||||
"topstories_empty_state": "Имате видено сѐ! Навратете се подоцна за нови содржини од {provider}. Не можете да чекате? Изберете популарна тема и откријте уште одлични содржини ширум Интернет.",
|
||||
"manual_migration_explanation": "Пробајте го Firefox со Вашите омилени мрежни места и обележувачи од друг прелистувач.",
|
||||
"manual_migration_cancel_button": "Не, благодарам",
|
||||
"manual_migration_import_button": "Увези сега"
|
||||
},
|
||||
"ml": {
|
||||
"newtab_page_title": "പുതിയ ടാബ്",
|
||||
"default_label_loading": "ലോഡ്ചെയ്യുന്നു…",
|
||||
|
@ -3803,12 +4122,19 @@
|
|||
"newtab_page_title": "नयाँ ट्याब",
|
||||
"default_label_loading": "लोड हुदैँछ...",
|
||||
"header_top_sites": "शीर्ष साइटहरु",
|
||||
"header_highlights": "विशेषताहरू",
|
||||
"header_stories": "शीर्ष साइटहरु",
|
||||
"header_visit_again": "फेरि भ्रमण गर्नुहोस्",
|
||||
"header_bookmarks": "भर्खरैका पुस्तकचिनोहरु",
|
||||
"header_recommended_by": "{provider} द्वारा सिफारिस गरिएको",
|
||||
"header_bookmarks_placeholder": "तपाइँसँग अहिले सम्म कुनै पुस्तकचिनोहरु छैन ।",
|
||||
"header_stories_from": "बाट",
|
||||
"type_label_visited": "भ्रमण गरिएको",
|
||||
"type_label_bookmarked": "पुस्तकचिनो लागाइएको",
|
||||
"type_label_synced": "अर्को यण्त्रबाट समक्रमण गरिएको",
|
||||
"type_label_recommended": "प्रचलनमा",
|
||||
"type_label_open": "खोल्नुहोस्",
|
||||
"type_label_topic": "शीर्षक",
|
||||
"type_label_now": "अहिले",
|
||||
"menu_action_bookmark": "पुस्तकचिनो",
|
||||
"menu_action_remove_bookmark": "पुस्तकचिनो हटाउनुहोस्",
|
||||
"menu_action_copy_address": "ठेगाना प्रतिलिपि गर्नुहोस्",
|
||||
|
@ -3817,16 +4143,50 @@
|
|||
"menu_action_open_private_window": "नयाँ निजी सञ्झ्यालमा खोल्नुहोस्",
|
||||
"menu_action_dismiss": "खारेज गर्नुहोस्",
|
||||
"menu_action_delete": "इतिहासबाट मेट्नुहोस्",
|
||||
"menu_action_pin": "पिन गर्नुहोस्",
|
||||
"menu_action_unpin": "अन पिन गर्नुहोस्",
|
||||
"confirm_history_delete_p1": "के तपाईं पक्का हुनुहुन्छ कि तपाइँ यस पृष्ठको हरेक उदाहरण तपाइँको इतिहासबाट हटाउन चाहनुहुन्छ ?",
|
||||
"confirm_history_delete_notice_p2": "यो कार्य पूर्ववत गर्न सकिँदैन ।",
|
||||
"menu_action_save_to_pocket": "Pocketमा बचत गर्नुहोस्",
|
||||
"search_for_something_with": "{search_term} खोज्न प्रयोग गर्नुहोस्:",
|
||||
"search_button": "खोजी गर्नुहोस्",
|
||||
"search_header": "{search_engine_name} खोजी",
|
||||
"search_web_placeholder": "वेबमा खोज्नुहोस्",
|
||||
"search_settings": "खोजी सेटिङ परिवर्तन गर्नुहोस्",
|
||||
"section_info_option": "जानकारी",
|
||||
"welcome_title": "नयाँ ट्याबमा स्वागत छ",
|
||||
"welcome_body": "Firefoxले यस ठाउँको प्रयोग तपाईंको सबैभन्दा सान्दर्भिक पुस्तकचिनो, लेखहरू, भिडियोहरू, र तपाईंले हालै भ्रमण गर्नु भएको पृष्ठहरूलाई राख्न प्रयोग गर्दछ, जसले गर्दा तपाइँ तिनीहरूलाई सजिलै भेटाउन सक्नुहुनेछ ।",
|
||||
"welcome_label": "तपाईँका विशेषताहरु पत्ता लगाउँदै",
|
||||
"time_label_less_than_minute": "< १ मिनेट",
|
||||
"time_label_minute": "{number} मिनेट",
|
||||
"time_label_hour": "{number} घण्टा",
|
||||
"time_label_day": "{number} दिन"
|
||||
"time_label_day": "{number} दिन",
|
||||
"settings_pane_button_label": "तपाईंको नयाँ ट्याब पृष्ठ अनुकूलन गर्नुहोस्",
|
||||
"settings_pane_header": "नयाँ ट्याब प्राथमिकताहरू",
|
||||
"settings_pane_body": "तपाईंले नयाँ ट्याब खोल्ने बेलामा के देख्नु चाहनुहुन्छ भन्ने छनौट गर्नुहोस् ।",
|
||||
"settings_pane_search_header": "खोजी गर्नुहोस्",
|
||||
"settings_pane_search_body": "तपाईंको नयाँ ट्याबबाट वेबमा खोज्नुहोस् ।",
|
||||
"settings_pane_topsites_body": "तपाईले धेरै भ्रमण गर्नुभएका वेबसाइटहरूमा पहुँच गर्नुहोस् ।",
|
||||
"settings_pane_topsites_options_showmore": "दुई पङ्क्तिहरू देखाउनुहोस्",
|
||||
"settings_pane_bookmarks_header": "भर्खरैका पुस्तकचिनोहरु",
|
||||
"settings_pane_bookmarks_body": "तपाईंको नयाँ सिर्जना गरिएको पुस्तकचिनोहरुहरू एउटा सजिलो स्थानमा ।",
|
||||
"settings_pane_visit_again_header": "फेरि भ्रमण गर्नुहोस्",
|
||||
"settings_pane_pocketstories_header": "शीर्ष कथाहरू",
|
||||
"settings_pane_done_button": "सम्पन्न भयो",
|
||||
"edit_topsites_button_text": "सम्पादन गर्नुहोस्",
|
||||
"edit_topsites_button_label": "तपाईंको शीर्ष साइट खण्ड अनुकूलन गर्नुहोस्",
|
||||
"edit_topsites_showmore_button": "थप देखाउनुहोस्",
|
||||
"edit_topsites_showless_button": "थोरै देखाउनुहोस्",
|
||||
"edit_topsites_done_button": "सम्पन्न भयो",
|
||||
"edit_topsites_pin_button": "यस साइटलाई पिन गर्नुहोस्",
|
||||
"edit_topsites_unpin_button": "यस साइटलाई अनपिन गर्नुहोस्",
|
||||
"edit_topsites_edit_button": "यस साइटलाई सम्पादन गर्नुहोस्",
|
||||
"edit_topsites_dismiss_button": "यस साइटलाई खारेज गर्नुहोस्",
|
||||
"edit_topsites_add_button": "थप्नुहोस्",
|
||||
"topsites_form_add_header": "नयाँ शीर्ष साइट",
|
||||
"topsites_form_edit_header": "शीर्ष साइट सम्पादन गर्नुहोस्",
|
||||
"manual_migration_cancel_button": "पर्दैन, धन्यबाद",
|
||||
"manual_migration_import_button": "अहिले आयात गर्नुहोस्"
|
||||
},
|
||||
"nl": {
|
||||
"newtab_page_title": "Nieuw tabblad",
|
||||
|
@ -3937,7 +4297,7 @@
|
|||
"menu_action_email_link": "E-postlenke…",
|
||||
"menu_action_open_new_window": "Opne i nytt vindauge",
|
||||
"menu_action_open_private_window": "Opne i eit nytt privat vindauge",
|
||||
"menu_action_dismiss": "Avslå",
|
||||
"menu_action_dismiss": "Avvis",
|
||||
"menu_action_delete": "Slett frå historikk",
|
||||
"menu_action_pin": "Fest",
|
||||
"menu_action_unpin": "L:ys",
|
||||
|
@ -4193,7 +4553,7 @@
|
|||
"settings_pane_visit_again_header": "Visite novamente",
|
||||
"settings_pane_visit_again_body": "Firefox irá exibir a você partes do seu histórico de navegação que você pode querer relembrar ou acessar novamente.",
|
||||
"settings_pane_pocketstories_header": "Histórias populares",
|
||||
"settings_pane_pocketstories_body": "O Pocket, parte da família Mozilla, irá ajudar a conecta-se a conteúdo de alta qualidade que talvez não tenha encontrado de outra forma.",
|
||||
"settings_pane_pocketstories_body": "O Pocket, parte da família Mozilla, ajudará a conectá-lo a conteúdo de alta qualidade que talvez você não encontre de outra forma.",
|
||||
"settings_pane_done_button": "Concluído",
|
||||
"edit_topsites_button_text": "Editar",
|
||||
"edit_topsites_button_label": "Personalizar a sua seção de sites preferidos",
|
||||
|
@ -4216,7 +4576,7 @@
|
|||
"pocket_read_more": "Tópicos populares:",
|
||||
"pocket_read_even_more": "Ver mais histórias",
|
||||
"pocket_feedback_header": "O melhor da web, com curadoria de mais de 25 milhões de pessoas.",
|
||||
"pocket_feedback_body": "O Pocket, parte da família Mozilla, irá ajudar a conecta-se a conteúdo de alta qualidade que talvez não tenha encontrado de outra forma.",
|
||||
"pocket_feedback_body": "O Pocket, parte da família Mozilla, ajudará a conectá-lo a conteúdo de alta qualidade que talvez você não encontre de outra forma.",
|
||||
"pocket_send_feedback": "Enviar feedback",
|
||||
"topstories_empty_state": "Você já viu tudo. Volte mais tarde para mais histórias do {provider}. Não consegue esperar? Escolha um assunto popular para encontrar mais grandes histórias através da web.",
|
||||
"manual_migration_explanation": "Use o Firefox com seus sites favoritos de outro navegador.",
|
||||
|
@ -4399,13 +4759,14 @@
|
|||
"newtab_page_title": "Filă nouă",
|
||||
"default_label_loading": "Se încarcă…",
|
||||
"header_top_sites": "Site-uri de top",
|
||||
"header_highlights": "Evidențieri",
|
||||
"header_recommended_by": "Recomandat de {provider}",
|
||||
"header_stories_from": "de la",
|
||||
"type_label_visited": "Vizitate",
|
||||
"type_label_bookmarked": "Însemnat",
|
||||
"type_label_synced": "Sincronizat de pe alt dispozitiv",
|
||||
"type_label_open": "Deschise",
|
||||
"type_label_topic": "Subiect",
|
||||
"type_label_now": "Acum",
|
||||
"menu_action_bookmark": "Însemnează",
|
||||
"menu_action_remove_bookmark": "Elimină semnul de carte",
|
||||
"menu_action_copy_address": "Copiază adresa",
|
||||
|
@ -4414,11 +4775,14 @@
|
|||
"menu_action_open_private_window": "Deschide într-o fereastră privată nouă",
|
||||
"menu_action_dismiss": "Înlătură",
|
||||
"menu_action_delete": "Șterge din istoric",
|
||||
"confirm_history_delete_notice_p2": "Această acțiune este ireversibilă.",
|
||||
"menu_action_save_to_pocket": "Salvează în Pocket",
|
||||
"search_for_something_with": "Caută {search_term} cu: ",
|
||||
"search_button": "Caută",
|
||||
"search_header": "Căutare {search_engine_name}",
|
||||
"search_web_placeholder": "Caută pe web",
|
||||
"search_settings": "Schimbă setările de căutare",
|
||||
"section_info_option": "Informații",
|
||||
"welcome_title": "Bun venit în noua filă",
|
||||
"welcome_body": "Firefox va folosi acest spațiu pentru a arăta cele mai relevante semne de carte, articole, videouri și pagini vizitate recent pentru a reveni la acestea ușor.",
|
||||
"welcome_label": "Se identifică evidențierile tale",
|
||||
|
@ -4434,8 +4798,6 @@
|
|||
"settings_pane_topsites_header": "Site-uri de top",
|
||||
"settings_pane_topsites_body": "Accesează site-urile pe care le vizitezi mai des.",
|
||||
"settings_pane_topsites_options_showmore": "Arată două rânduri",
|
||||
"settings_pane_highlights_header": "Evidențieri",
|
||||
"settings_pane_highlights_body": "Privește înapoi la istoricul de navigare recent și noile semne de carte create.",
|
||||
"settings_pane_done_button": "Gata",
|
||||
"edit_topsites_button_text": "Editează",
|
||||
"edit_topsites_button_label": "Particularizează secțiunea site-urilor de top",
|
||||
|
@ -4455,7 +4817,8 @@
|
|||
"topsites_form_cancel_button": "Renunță",
|
||||
"topsites_form_url_validation": "URL valid necesar",
|
||||
"pocket_read_more": "Subiecte populare:",
|
||||
"pocket_send_feedback": "Trimite feedback"
|
||||
"pocket_send_feedback": "Trimite feedback",
|
||||
"manual_migration_cancel_button": "Nu, mulțumesc"
|
||||
},
|
||||
"ru": {
|
||||
"newtab_page_title": "Новая вкладка",
|
||||
|
@ -4676,8 +5039,8 @@
|
|||
"settings_pane_button_label": "Prilagodite stran novega zavihka",
|
||||
"settings_pane_header": "Nastavitve novega zavihka",
|
||||
"settings_pane_body": "Izberite, kaj naj se prikaže, ko odprete nov zavihek.",
|
||||
"settings_pane_search_header": "Išči",
|
||||
"settings_pane_search_body": "Iščite po spletu s strani novega zavihka.",
|
||||
"settings_pane_search_header": "Iskanje",
|
||||
"settings_pane_search_body": "Iščite po spletu z novega zavihka.",
|
||||
"settings_pane_topsites_header": "Glavne strani",
|
||||
"settings_pane_topsites_body": "Priročen dostop do najbolj obiskanih strani.",
|
||||
"settings_pane_topsites_options_showmore": "Prikaži dve vrsti",
|
||||
|
@ -4946,6 +5309,7 @@
|
|||
"header_stories": "முக்கிய கதைகள்",
|
||||
"header_visit_again": "மீண்டும் வருக",
|
||||
"header_bookmarks": "சமீபத்திய புத்தகக்குறிகள்",
|
||||
"header_recommended_by": "{provider} என்பவரால் பரிந்துரைக்கப்பட்டது",
|
||||
"header_bookmarks_placeholder": "நீங்கள் புத்தகக்குறிகளைக் கொண்டிருக்கவில்லை .",
|
||||
"header_stories_from": "அனுப்பியவர்",
|
||||
"type_label_visited": "பார்த்தவை",
|
||||
|
@ -4954,6 +5318,7 @@
|
|||
"type_label_recommended": "பிரபலமான",
|
||||
"type_label_open": "திற",
|
||||
"type_label_topic": "தலைப்பு",
|
||||
"type_label_now": "இப்போது",
|
||||
"menu_action_bookmark": "புத்தகக்குறி",
|
||||
"menu_action_remove_bookmark": "புத்தகக்குறியை நீக்கு",
|
||||
"menu_action_copy_address": "முகவரியை நகலெடு",
|
||||
|
@ -4972,6 +5337,7 @@
|
|||
"search_header": "{search_engine_name} தேடுபொறியில் தேடு",
|
||||
"search_web_placeholder": "இணையத்தில் தேடு",
|
||||
"search_settings": "தேடல் அமைவுகளை மாற்று",
|
||||
"section_info_option": "தகவல்",
|
||||
"welcome_title": "புதிய கீற்றுக்கு வருக",
|
||||
"welcome_body": "உங்களுக்கு மிகவும் பொருத்தமான புத்தகக்குறிகள், கட்டுரைகள், காணொளிகள் மற்றும் சமீபத்தில் பார்வையிட்ட பக்கங்களைக் காண்பிக்க பயர்பாக்ஸ் இந்த இடத்தைப் பயன்படுத்தும், எனவே நீங்கள் அவற்றை எளிதாகத் திரும்பப் பெறலாம்.",
|
||||
"welcome_label": "உங்களின் முக்கியம்சங்களை அடையாளம் காண்கிறோம்",
|
||||
|
@ -5016,7 +5382,9 @@
|
|||
"pocket_read_even_more": "இன்னும் கதைகளைப் பார்க்கவும்",
|
||||
"pocket_feedback_header": "இணையத்தின் சிறந்த செயலி, 250 இலட்ச மக்களால் தேர்ந்தெடுக்கப்பட்டது.",
|
||||
"pocket_feedback_body": "Pocket, ஒரு மொசில்லா குடும்ப உறுப்பினராக, உயர்தர உள்ளடக்கங்களுடன் இணைய உதவுகிறது, இது இல்லையேல் அது சாத்தியமாகது.",
|
||||
"pocket_send_feedback": "கருத்துகளைத் தெறிவிக்கவும்"
|
||||
"pocket_send_feedback": "கருத்துகளைத் தெறிவிக்கவும்",
|
||||
"manual_migration_cancel_button": "பரவாயில்லை",
|
||||
"manual_migration_import_button": "இப்போது இறக்கு"
|
||||
},
|
||||
"ta-LK": {},
|
||||
"te": {
|
||||
|
@ -5026,7 +5394,8 @@
|
|||
"header_stories": "ముఖ్య కథనాలు",
|
||||
"header_visit_again": "మళ్లీ సందర్శించండి",
|
||||
"header_bookmarks": "ఇటీవలి ఇష్టాంశములు",
|
||||
"header_bookmarks_placeholder": "మీకు ఇంకా బుక్మార్క్లు లేవు.",
|
||||
"header_recommended_by": "{provider}చే సిఫార్సు చేయబడినది",
|
||||
"header_bookmarks_placeholder": "మీకు ఇంకా ఎటువంటి ఇష్టాంశాలు లేవు.",
|
||||
"header_stories_from": "నుండి",
|
||||
"type_label_visited": "సందర్శించినవి",
|
||||
"type_label_bookmarked": "ఇష్టాంశము చేయబడినది",
|
||||
|
@ -5034,6 +5403,7 @@
|
|||
"type_label_recommended": "ట్రెండింగ్",
|
||||
"type_label_open": "తెరువు",
|
||||
"type_label_topic": "విషయం",
|
||||
"type_label_now": "ఇప్పుడు",
|
||||
"menu_action_bookmark": "ఇష్టాంశము",
|
||||
"menu_action_remove_bookmark": "ఇష్టాంశాన్ని తొలగించు",
|
||||
"menu_action_copy_address": "చిరునామా కాపీ చెయ్యండి",
|
||||
|
@ -5042,8 +5412,8 @@
|
|||
"menu_action_open_private_window": "కొత్త వ్యక్తిగత విండోలో తెరువు",
|
||||
"menu_action_dismiss": "విస్మరించు",
|
||||
"menu_action_delete": "చరిత్ర నుంచి తీసివేయి",
|
||||
"menu_action_pin": "పిన్",
|
||||
"menu_action_unpin": "పిన్ తీసివేయి",
|
||||
"menu_action_pin": "పిన్ను",
|
||||
"menu_action_unpin": "పిన్ను తీసివేయి",
|
||||
"confirm_history_delete_p1": "మీరు మీ చరిత్ర నుండి ఈ పేజీ యొక్క ప్రతి ఉదాహరణకు తొలగించాలనుకుంటున్నారా?",
|
||||
"confirm_history_delete_notice_p2": "ఈ చర్యను రద్దు చేయలేము.",
|
||||
"menu_action_save_to_pocket": "Pocket కి సేవ్ చేయండి",
|
||||
|
@ -5052,6 +5422,7 @@
|
|||
"search_header": "{search_engine_name} శోధన",
|
||||
"search_web_placeholder": "జాలంలో వెతకండి",
|
||||
"search_settings": "శోధన అమరికలు మార్చు",
|
||||
"section_info_option": "సమాచారం",
|
||||
"welcome_title": "కొత్త ట్యాబుకు స్వాగతం",
|
||||
"welcome_body": "సముచితమైన మీ ఇష్టాంశాలను, వ్యాసాలను, వీడియోలను, ఇంకా మీరు ఇటీవలే చూసిన పేజీలను మీకు తేలిగ్గా అందుబాటులో ఉంచేందుకు Firefox ఈ జాగాని వాడుకుంటుంది.",
|
||||
"welcome_label": "మీ ముఖ్యాంశాలను గుర్తిస్తున్నది",
|
||||
|
@ -5080,7 +5451,7 @@
|
|||
"edit_topsites_showless_button": "కొన్నే చూపించు",
|
||||
"edit_topsites_done_button": "పూర్తయింది",
|
||||
"edit_topsites_pin_button": "ఈ సైటును ఇక్కడ గుచ్చు",
|
||||
"edit_topsites_unpin_button": "ఈ సైట్ను అన్పిన్ చేయండి",
|
||||
"edit_topsites_unpin_button": "ఈ సైటుకి పిన్నుని తీసివేయండి",
|
||||
"edit_topsites_edit_button": "ఈ సైటును మార్చు",
|
||||
"edit_topsites_dismiss_button": "ఈ సైటుని తీసివేయి",
|
||||
"edit_topsites_add_button": "జోడించు",
|
||||
|
@ -5096,7 +5467,11 @@
|
|||
"pocket_read_even_more": "మరిన్ని కథలను వీక్షించండి",
|
||||
"pocket_feedback_header": "వెబ్లో అత్యుత్తమమైనది, 25 మిలియన్లకు పైగా ప్రజలు పర్యవేక్షించినవి.",
|
||||
"pocket_feedback_body": "Mozilla కుటుంబం యొక్క Pocket, మీరు కనుగొనలేకపోయే అధిక-నాణ్యత విషయముకి మిమ్మల్ని అనుసంధానించడానికి సహాయపడుతుంది.",
|
||||
"pocket_send_feedback": "అభిప్రాయాన్ని పంపండి"
|
||||
"pocket_send_feedback": "అభిప్రాయాన్ని పంపండి",
|
||||
"topstories_empty_state": "మీరు పట్టుబడ్డారు. {provider} నుండి మరింత అగ్ర కథనాల కోసం తరువాత తనిఖీ చేయండి. వేచి ఉండలేరా? జాలములోని అంతటి నుండి మరింత గొప్ప కథనాలను కనుగొనడానికి ప్రసిద్ధ అంశం ఎంచుకోండి.",
|
||||
"manual_migration_explanation": "మరొక విహరణి నుండి మీకు ఇష్టమైన సైట్లు మరియు ఇష్టంశాలతో Firefox ను ప్రయత్నించండి.",
|
||||
"manual_migration_cancel_button": "అడిగినందుకు ధన్యవాదాలు, వద్దు",
|
||||
"manual_migration_import_button": "ఇప్పుడే దిగుమతి చేయండి"
|
||||
},
|
||||
"th": {
|
||||
"newtab_page_title": "แท็บใหม่",
|
||||
|
@ -5124,7 +5499,7 @@
|
|||
"menu_action_dismiss": "ยกเลิก",
|
||||
"menu_action_delete": "ลบออกจากประวัติ",
|
||||
"menu_action_pin": "ปักหมุด",
|
||||
"menu_action_unpin": "ถอดหมุด",
|
||||
"menu_action_unpin": "ถอนหมุด",
|
||||
"confirm_history_delete_notice_p2": "การกระทำนี้ไม่สามารถเลิกทำได้",
|
||||
"menu_action_save_to_pocket": "บันทึกไปยัง Pocket",
|
||||
"search_for_something_with": "ค้นหาสำหรับ {search_term} ด้วย:",
|
||||
|
@ -5159,7 +5534,7 @@
|
|||
"edit_topsites_showless_button": "แสดงน้อยลง",
|
||||
"edit_topsites_done_button": "เสร็จสิ้น",
|
||||
"edit_topsites_pin_button": "ปักหมุดไซต์นี้",
|
||||
"edit_topsites_unpin_button": "ถอดหมุดไซต์นี้",
|
||||
"edit_topsites_unpin_button": "ถอนหมุดไซต์นี้",
|
||||
"edit_topsites_edit_button": "แก้ไขไซต์นี้",
|
||||
"edit_topsites_dismiss_button": "ไม่สนใจไซต์นี้",
|
||||
"edit_topsites_add_button": "เพิ่ม",
|
||||
|
@ -5344,7 +5719,7 @@
|
|||
"menu_action_pin": "Прикріпити",
|
||||
"menu_action_unpin": "Відкріпити",
|
||||
"confirm_history_delete_p1": "Ви справді хочете видалити всі записи про цю сторінку з історії?",
|
||||
"confirm_history_delete_notice_p2": "Цю дію неможливо відмінити.",
|
||||
"confirm_history_delete_notice_p2": "Цю дію неможливо скасувати.",
|
||||
"menu_action_save_to_pocket": "Зберегти в Pocket",
|
||||
"search_for_something_with": "Шукати {search_term} з:",
|
||||
"search_button": "Пошук",
|
||||
|
@ -5409,6 +5784,7 @@
|
|||
"header_stories": "بہترین کہانیاں",
|
||||
"header_visit_again": "دوبارہ دورہ کریں",
|
||||
"header_bookmarks": "حالیہ نشانیاں",
|
||||
"header_recommended_by": "{provider} کی جانب سے تجویز کردہ",
|
||||
"header_stories_from": "من جانب",
|
||||
"type_label_visited": "دورہ شدہ",
|
||||
"type_label_bookmarked": "نشان شدہ",
|
||||
|
@ -5416,6 +5792,7 @@
|
|||
"type_label_recommended": "رجحان سازی",
|
||||
"type_label_open": "کھولیں",
|
||||
"type_label_topic": "عنوان",
|
||||
"type_label_now": "ابھی",
|
||||
"menu_action_bookmark": "نشانی",
|
||||
"menu_action_remove_bookmark": "نشانى ہٹائيں",
|
||||
"menu_action_copy_address": "پتہ نقل کریں",
|
||||
|
@ -5425,6 +5802,7 @@
|
|||
"menu_action_dismiss": "برخاست کریں",
|
||||
"menu_action_delete": "تاریخ سے حذف کریں",
|
||||
"menu_action_pin": "پن",
|
||||
"menu_action_unpin": "ان پن",
|
||||
"confirm_history_delete_p1": "کیا آپ کو یقین ہے کہ آپ اس صفحہ کا ہر نمونہ اپنے سابقات سے حذف کرنا چاہتے ہیں؟",
|
||||
"confirm_history_delete_notice_p2": "یہ عمل کلعدم نہیں ہو سکتا۔",
|
||||
"menu_action_save_to_pocket": "Pocket میں محفوظ کریں",
|
||||
|
@ -5433,6 +5811,7 @@
|
|||
"search_header": "{search_engine_name} پر تلاش کریں",
|
||||
"search_web_placeholder": "ويب پر تلاش کريں",
|
||||
"search_settings": "تلاش کی سیٹکگیں تبدیل کریں",
|
||||
"section_info_option": "معلومات",
|
||||
"welcome_title": "نئے ٹیب میں خوش آمدید",
|
||||
"welcome_body": "اس جگہ کا استعمال کرنے ہوئے Firefox آپکی متعلقہ نشانیاں، عبارات، وڈیوز اور صفحات جن کا حال ہی میں ص آُپ نے دورہ کیا ہے دکھائے گا۔ تاکہ آپ ان تک واپس آسانی سے پہنچ سکیں۔",
|
||||
"welcome_label": "آپکی جھلکیوں کی نشاندہی کر رہا ہے",
|
||||
|
@ -5455,6 +5834,7 @@
|
|||
"edit_topsites_button_text": "تدوین",
|
||||
"edit_topsites_button_label": "اپنی بہترین سائٹس والے حصے کی تخصیص کریں",
|
||||
"edit_topsites_showmore_button": "مزید دکھائیں",
|
||||
"edit_topsites_showless_button": "کم دکھائیں",
|
||||
"edit_topsites_done_button": "ہوگیا",
|
||||
"edit_topsites_pin_button": "اس سائَٹ کو پن کریں",
|
||||
"edit_topsites_unpin_button": "اس سائٹ کو انپن کریں",
|
||||
|
@ -5472,9 +5852,14 @@
|
|||
"pocket_read_more": "مشہور مضامین:",
|
||||
"pocket_read_even_more": "مزید کہانیاں دیکھیں",
|
||||
"pocket_feedback_body": "Pocket ایک جصہ ہے Mozilla کے خاندان کا،آپ کو اعلی میعار کے مواد سے جڑنے میں مدد دے گا جو شاید آپ بصورت دیگر نہ ڈھونڈ سکتے۔",
|
||||
"pocket_send_feedback": "جواب الجواب ارسال کریں"
|
||||
"pocket_send_feedback": "جواب الجواب ارسال کریں",
|
||||
"manual_migration_cancel_button": "نہیں شکریہ",
|
||||
"manual_migration_import_button": "ابھی درآمد کری"
|
||||
},
|
||||
"uz": {
|
||||
"pocket_send_feedback": "Fikr-mulohaza joʻnatish",
|
||||
"manual_migration_cancel_button": "Yoʻq, kerak emas"
|
||||
},
|
||||
"uz": {},
|
||||
"vi": {
|
||||
"newtab_page_title": "Tab mới",
|
||||
"default_label_loading": "Đang tải…",
|
||||
|
@ -5491,6 +5876,7 @@
|
|||
"type_label_recommended": "Xu hướng",
|
||||
"type_label_open": "Mở",
|
||||
"type_label_topic": "Chủ đề",
|
||||
"type_label_now": "Bây giờ",
|
||||
"menu_action_bookmark": "Đánh dấu",
|
||||
"menu_action_remove_bookmark": "Xóa đánh dấu",
|
||||
"menu_action_copy_address": "Chép địa chỉ",
|
||||
|
@ -5514,9 +5900,12 @@
|
|||
"time_label_minute": "{number}phút",
|
||||
"time_label_hour": "{number}giờ",
|
||||
"time_label_day": "{number}ngày",
|
||||
"settings_pane_button_label": "Tùy biến trang Tab mới",
|
||||
"settings_pane_header": "Tùy chỉnh cho tab mới",
|
||||
"settings_pane_body": "Chọn cái bạn muốn tải khi một tab mới được mở ra.",
|
||||
"settings_pane_search_header": "Tìm kiếm",
|
||||
"settings_pane_topsites_body": "Truy cập vào các trang web mà bạn truy cập vào nhiều nhất.",
|
||||
"settings_pane_topsites_options_showmore": "Hiển thị hai hàng",
|
||||
"settings_pane_done_button": "Xong",
|
||||
"edit_topsites_button_text": "Chỉnh sửa",
|
||||
"edit_topsites_showmore_button": "Xem thêm",
|
||||
|
@ -5540,7 +5929,7 @@
|
|||
"newtab_page_title": "新标签页",
|
||||
"default_label_loading": "正在载入…",
|
||||
"header_top_sites": "常用网站",
|
||||
"header_stories": "热门报道",
|
||||
"header_stories": "热门文章",
|
||||
"header_visit_again": "再次造访",
|
||||
"header_bookmarks": "最近的书签",
|
||||
"header_recommended_by": "{provider} 推荐",
|
||||
|
@ -5558,8 +5947,8 @@
|
|||
"menu_action_copy_address": "复制地址",
|
||||
"menu_action_email_link": "用邮件发送链接…",
|
||||
"menu_action_open_new_window": "在新窗口中打开",
|
||||
"menu_action_open_private_window": "在新的隐私浏览窗口中打开",
|
||||
"menu_action_dismiss": "消除",
|
||||
"menu_action_open_private_window": "在新的隐私窗口中打开",
|
||||
"menu_action_dismiss": "隐藏",
|
||||
"menu_action_delete": "从历史记录中删除",
|
||||
"menu_action_pin": "固定",
|
||||
"menu_action_unpin": "取消固定",
|
||||
|
@ -5581,7 +5970,7 @@
|
|||
"time_label_day": "{number} 天前",
|
||||
"settings_pane_button_label": "定制您的新标签页",
|
||||
"settings_pane_header": "新标签页选项",
|
||||
"settings_pane_body": "选择您在新标签页上看到哪些组件。",
|
||||
"settings_pane_body": "选择打开新标签页时想看到什么。",
|
||||
"settings_pane_search_header": "搜索",
|
||||
"settings_pane_search_body": "从您的新标签页搜索网络。",
|
||||
"settings_pane_topsites_header": "常用网站",
|
||||
|
@ -5590,9 +5979,9 @@
|
|||
"settings_pane_bookmarks_header": "最近的书签",
|
||||
"settings_pane_bookmarks_body": "您最近创建的书签将在此显示。",
|
||||
"settings_pane_visit_again_header": "再次造访",
|
||||
"settings_pane_visit_again_body": "Firefox 在此显示您可能想记住或再次访问的浏览记录。",
|
||||
"settings_pane_visit_again_body": "Firefox 在此显示您可能想记住或将再次访问的浏览记录。",
|
||||
"settings_pane_pocketstories_header": "热门报道",
|
||||
"settings_pane_pocketstories_body": "Pocket,Mozilla 家族的一员,它可以帮助您找到更多不易发现的高品质内容。",
|
||||
"settings_pane_pocketstories_body": "Pocket 是 Mozilla 家族的一员,可以将您未发现的高品质内容带到眼前。",
|
||||
"settings_pane_done_button": "完成",
|
||||
"edit_topsites_button_text": "编辑",
|
||||
"edit_topsites_button_label": "定制您的“常用网站”区域",
|
||||
|
@ -5613,11 +6002,11 @@
|
|||
"topsites_form_cancel_button": "取消",
|
||||
"topsites_form_url_validation": "需要有效的网址",
|
||||
"pocket_read_more": "热门主题:",
|
||||
"pocket_read_even_more": "查看更多报道",
|
||||
"pocket_feedback_header": "超过2500万人构成的互联网。",
|
||||
"pocket_feedback_body": "Pocket,Mozilla 家族的一员,它可以帮助您找到更多不易发现的高品质内容。",
|
||||
"pocket_read_even_more": "查看更多文章",
|
||||
"pocket_feedback_header": "由超过 2500 万人挑选出来的网上精华内容。",
|
||||
"pocket_feedback_body": "Pocket 是 Mozilla 家族的一员,可以将您未发现的高品质内容带到眼前。",
|
||||
"pocket_send_feedback": "发送反馈",
|
||||
"topstories_empty_state": "已经都看过了。请过会再来查看 {provider} 提供的热门故事。不想等待?选择一个热门话题,找到网络上的更多好故事。",
|
||||
"topstories_empty_state": "所有文章都读完啦!晚点再来,{provider} 将推荐更多热门文章。等不及了?选择一个热门话题,找到更多网上的好文章。",
|
||||
"manual_migration_explanation": "将您在其他浏览器中常用的网站和书签导入 Firefox,体验别具一格的浏览器。",
|
||||
"manual_migration_cancel_button": "不用了",
|
||||
"manual_migration_import_button": "立即导入"
|
||||
|
|
|
@ -51,15 +51,15 @@ const PREFS_CONFIG = new Map([
|
|||
api_key_pref: "extensions.pocket.oAuthConsumerKey",
|
||||
// Use the opposite value as what default value the feed would have used
|
||||
hidden: !PREFS_CONFIG.get("feeds.section.topstories").getValue(args),
|
||||
learn_more_endpoint: "https://getpocket.com/firefox_learnmore?src=ff_newtab",
|
||||
learn_more_endpoint: "https://getpocket.cdn.mozilla.net/firefox_learnmore?src=ff_newtab",
|
||||
provider_description: "pocket_feedback_body",
|
||||
provider_icon: "pocket",
|
||||
provider_name: "Pocket",
|
||||
read_more_endpoint: "https://getpocket.com/explore/trending?src=ff_new_tab",
|
||||
stories_endpoint: `https://getpocket.com/v3/firefox/global-recs?consumer_key=$apiKey&locale_lang=${args.locale}`,
|
||||
read_more_endpoint: "https://getpocket.cdn.mozilla.net/explore/trending?src=ff_new_tab",
|
||||
stories_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=2&consumer_key=$apiKey&locale_lang=${args.locale}`,
|
||||
stories_referrer: "https://getpocket.com/recommendations",
|
||||
survey_link: "https://www.surveymonkey.com/r/newtabffx",
|
||||
topics_endpoint: `https://getpocket.com/v3/firefox/trending-topics?consumer_key=$apiKey&locale_lang=${args.locale}`
|
||||
topics_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/trending-topics?version=2&consumer_key=$apiKey&locale_lang=${args.locale}`
|
||||
})
|
||||
}],
|
||||
["migrationExpired", {
|
||||
|
|
|
@ -137,8 +137,10 @@ this.ActivityStreamMessageChannel = class ActivityStreamMessageChannel {
|
|||
|
||||
// Some pages might have already loaded, so we won't get the usual message
|
||||
for (const {loaded, portID} of this.channel.messagePorts) {
|
||||
const simulatedMsg = {target: {portID}};
|
||||
this.onNewTabInit(simulatedMsg);
|
||||
if (loaded) {
|
||||
this.onNewTabLoad({target: {portID}});
|
||||
this.onNewTabLoad(simulatedMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,21 +30,18 @@ this.LocalizationFeed = class LocalizationFeed {
|
|||
}
|
||||
|
||||
updateLocale() {
|
||||
// Just take the first element in the result array, as it should be
|
||||
// the best locale
|
||||
let locale = Services.locale.negotiateLanguages(
|
||||
// Order locales based on what we have available with fallback always first
|
||||
const locales = Services.locale.negotiateLanguages(
|
||||
Services.locale.getAppLocalesAsLangTags(), // desired locales
|
||||
Object.keys(this.allStrings), // available locales
|
||||
DEFAULT_LOCALE // fallback
|
||||
)[0];
|
||||
).reverse();
|
||||
|
||||
let strings = this.allStrings[locale];
|
||||
|
||||
// Use the default strings for any that are missing
|
||||
if (locale !== DEFAULT_LOCALE) {
|
||||
strings = Object.assign({}, this.allStrings[DEFAULT_LOCALE], strings || {});
|
||||
}
|
||||
// Start with default (first) locale then merge in strings of better locales
|
||||
const strings = Object.assign({}, ...locales.map(l => this.allStrings[l]));
|
||||
|
||||
// Use the best (last) locale as the primary locale
|
||||
const locale = locales.pop();
|
||||
this.store.dispatch(ac.BroadcastToContent({
|
||||
type: at.LOCALE_UPDATED,
|
||||
data: {
|
||||
|
|
|
@ -7,21 +7,85 @@ const {utils: Cu} = Components;
|
|||
const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
Cu.import("resource://gre/modules/EventEmitter.jsm");
|
||||
|
||||
/*
|
||||
* Generators for built in sections, keyed by the pref name for their feed.
|
||||
* Built in sections may depend on options stored as serialised JSON in the pref
|
||||
* `${feed_pref_name}.options`.
|
||||
*/
|
||||
const BUILT_IN_SECTIONS = {
|
||||
"feeds.section.topstories": options => ({
|
||||
id: "topstories",
|
||||
pref: {
|
||||
titleString: {id: "header_recommended_by", values: {provider: options.provider_name}},
|
||||
descString: {id: options.provider_description}
|
||||
},
|
||||
shouldHidePref: options.hidden,
|
||||
eventSource: "TOP_STORIES",
|
||||
icon: options.provider_icon,
|
||||
title: {id: "header_recommended_by", values: {provider: options.provider_name}},
|
||||
maxRows: 1,
|
||||
contextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
|
||||
infoOption: {
|
||||
header: {id: "pocket_feedback_header"},
|
||||
body: {id: options.provider_description},
|
||||
link: {href: options.survey_link, id: "pocket_send_feedback"}
|
||||
},
|
||||
emptyState: {
|
||||
message: {id: "topstories_empty_state", values: {provider: options.provider_name}},
|
||||
icon: "check"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const SectionsManager = {
|
||||
ACTIONS_TO_PROXY: ["SYSTEM_TICK", "NEW_TAB_LOAD"],
|
||||
initialized: false,
|
||||
sections: new Set(),
|
||||
sections: new Map(),
|
||||
init(prefs = {}) {
|
||||
for (const feedPrefName of Object.keys(BUILT_IN_SECTIONS)) {
|
||||
const optionsPrefName = `${feedPrefName}.options`;
|
||||
this.addBuiltInSection(feedPrefName, prefs[optionsPrefName]);
|
||||
}
|
||||
this.initialized = true;
|
||||
this.emit(this.INIT);
|
||||
},
|
||||
addBuiltInSection(feedPrefName, optionsPrefValue = "{}") {
|
||||
let options;
|
||||
try {
|
||||
options = JSON.parse(optionsPrefValue);
|
||||
} catch (e) {
|
||||
options = {};
|
||||
Cu.reportError("Problem parsing options pref", e);
|
||||
}
|
||||
const section = BUILT_IN_SECTIONS[feedPrefName](options);
|
||||
section.pref.feed = feedPrefName;
|
||||
this.addSection(section.id, Object.assign(section, {options}));
|
||||
},
|
||||
addSection(id, options) {
|
||||
this.sections.add(id);
|
||||
this.sections.set(id, options);
|
||||
this.emit(this.ADD_SECTION, id, options);
|
||||
},
|
||||
removeSection(id) {
|
||||
this.emit(this.REMOVE_SECTION, id);
|
||||
this.sections.delete(id);
|
||||
},
|
||||
updateRows(id, rows, shouldBroadcast) {
|
||||
enableSection(id) {
|
||||
this.updateSection(id, {enabled: true}, true);
|
||||
},
|
||||
disableSection(id) {
|
||||
this.updateSection(id, {enabled: false, rows: []}, true);
|
||||
},
|
||||
updateSection(id, options, shouldBroadcast) {
|
||||
if (this.sections.has(id)) {
|
||||
this.emit(this.UPDATE_ROWS, id, rows, shouldBroadcast);
|
||||
this.sections.set(id, Object.assign(this.sections.get(id), options));
|
||||
this.emit(this.UPDATE_SECTION, id, options, shouldBroadcast);
|
||||
}
|
||||
},
|
||||
onceInitialized(callback) {
|
||||
if (this.initialized) {
|
||||
callback();
|
||||
} else {
|
||||
this.once(this.INIT, callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -30,7 +94,7 @@ for (const action of [
|
|||
"ACTION_DISPATCHED",
|
||||
"ADD_SECTION",
|
||||
"REMOVE_SECTION",
|
||||
"UPDATE_ROWS",
|
||||
"UPDATE_SECTION",
|
||||
"INIT",
|
||||
"UNINIT"
|
||||
]) {
|
||||
|
@ -41,17 +105,19 @@ EventEmitter.decorate(SectionsManager);
|
|||
|
||||
class SectionsFeed {
|
||||
constructor() {
|
||||
this.init = this.init.bind(this);
|
||||
this.onAddSection = this.onAddSection.bind(this);
|
||||
this.onRemoveSection = this.onRemoveSection.bind(this);
|
||||
this.onUpdateRows = this.onUpdateRows.bind(this);
|
||||
this.onUpdateSection = this.onUpdateSection.bind(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
SectionsManager.on(SectionsManager.ADD_SECTION, this.onAddSection);
|
||||
SectionsManager.on(SectionsManager.REMOVE_SECTION, this.onRemoveSection);
|
||||
SectionsManager.on(SectionsManager.UPDATE_ROWS, this.onUpdateRows);
|
||||
SectionsManager.initialized = true;
|
||||
SectionsManager.emit(SectionsManager.INIT);
|
||||
SectionsManager.on(SectionsManager.UPDATE_SECTION, this.onUpdateSection);
|
||||
// Catch any sections that have already been added
|
||||
SectionsManager.sections.forEach((section, id) =>
|
||||
this.onAddSection(SectionsManager.ADD_SECTION, id, section));
|
||||
}
|
||||
|
||||
uninit() {
|
||||
|
@ -59,7 +125,7 @@ class SectionsFeed {
|
|||
SectionsManager.emit(SectionsManager.UNINIT);
|
||||
SectionsManager.off(SectionsManager.ADD_SECTION, this.onAddSection);
|
||||
SectionsManager.off(SectionsManager.REMOVE_SECTION, this.onRemoveSection);
|
||||
SectionsManager.off(SectionsManager.UPDATE_ROWS, this.onUpdateRows);
|
||||
SectionsManager.off(SectionsManager.UPDATE_SECTION, this.onUpdateSection);
|
||||
}
|
||||
|
||||
onAddSection(event, id, options) {
|
||||
|
@ -72,9 +138,9 @@ class SectionsFeed {
|
|||
this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_DEREGISTER, data: id}));
|
||||
}
|
||||
|
||||
onUpdateRows(event, id, rows, shouldBroadcast = false) {
|
||||
if (rows) {
|
||||
const action = {type: at.SECTION_ROWS_UPDATE, data: {id, rows}};
|
||||
onUpdateSection(event, id, options, shouldBroadcast = false) {
|
||||
if (options) {
|
||||
const action = {type: at.SECTION_UPDATE, data: Object.assign(options, {id})};
|
||||
this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : action);
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +148,22 @@ class SectionsFeed {
|
|||
onAction(action) {
|
||||
switch (action.type) {
|
||||
case at.INIT:
|
||||
this.init();
|
||||
SectionsManager.onceInitialized(this.init);
|
||||
break;
|
||||
// Wait for pref values, as some sections have options stored in prefs
|
||||
case at.PREFS_INITIAL_VALUES:
|
||||
SectionsManager.init(action.data);
|
||||
break;
|
||||
case at.PREF_CHANGED:
|
||||
if (action.data && action.data.name.match(/^feeds.section.(\S+).options$/i)) {
|
||||
SectionsManager.addBuiltInSection(action.data.name.slice(0, -8), action.data.value);
|
||||
}
|
||||
break;
|
||||
case at.SECTION_DISABLE:
|
||||
SectionsManager.disableSection(action.data);
|
||||
break;
|
||||
case at.SECTION_ENABLE:
|
||||
SectionsManager.enableSection(action.data);
|
||||
break;
|
||||
}
|
||||
if (SectionsManager.ACTIONS_TO_PROXY.includes(action.type) && SectionsManager.sections.size > 0) {
|
||||
|
|
|
@ -1,4 +1,24 @@
|
|||
Components.utils.importGlobalProperties(["URL"]);
|
||||
const {utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "IDNService", "@mozilla.org/network/idn-service;1", "nsIIDNService");
|
||||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
/**
|
||||
* Properly convert internationalized domain names.
|
||||
* @param {string} host Domain hostname.
|
||||
* @returns {string} Hostname suitable to be displayed.
|
||||
*/
|
||||
function handleIDNHost(hostname) {
|
||||
try {
|
||||
return IDNService.convertToDisplayIDN(hostname, {});
|
||||
} catch (e) {
|
||||
// If something goes wrong (e.g. host is an IP address) just fail back
|
||||
// to the full domain.
|
||||
return hostname;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shortURL - Creates a short version of a link's url, used for display purposes
|
||||
|
@ -20,7 +40,8 @@ this.shortURL = function shortURL(link) {
|
|||
return "";
|
||||
}
|
||||
const {eTLD} = link;
|
||||
const hostname = (link.hostname || new URL(link.url).hostname).replace(/^www\./i, "");
|
||||
const asciiHost = (link.hostname || new URL(link.url).hostname).replace(/^www\./i, "");
|
||||
const hostname = handleIDNHost(asciiHost);
|
||||
|
||||
// Remove the eTLD (e.g., com, net) and the preceding period from the hostname
|
||||
const eTLDLength = (eTLD || "").length || (hostname.match(/\.com$/) && 3);
|
|
@ -10,7 +10,7 @@ const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-str
|
|||
const {TippyTopProvider} = Cu.import("resource://activity-stream/lib/TippyTopProvider.jsm", {});
|
||||
const {insertPinned, TOP_SITES_SHOWMORE_LENGTH} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
|
||||
const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
|
||||
const {shortURL} = Cu.import("resource://activity-stream/common/ShortURL.jsm", {});
|
||||
const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
|
||||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
|
@ -20,6 +20,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Screenshots",
|
|||
const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
|
||||
const DEFAULT_SITES_PREF = "default.sites";
|
||||
const DEFAULT_TOP_SITES = [];
|
||||
const FRECENCY_THRESHOLD = 100; // 1 visit (skip first-run/one-time pages)
|
||||
|
||||
this.TopSitesFeed = class TopSitesFeed {
|
||||
constructor() {
|
||||
|
@ -62,7 +63,9 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
if (!frecent) {
|
||||
frecent = [];
|
||||
} else {
|
||||
frecent = frecent.filter(link => link && link.type !== "affiliate");
|
||||
// Get the best history links that pass the frecency threshold
|
||||
frecent = frecent.filter(link => link && link.type !== "affiliate" &&
|
||||
link.frecency > FRECENCY_THRESHOLD);
|
||||
}
|
||||
|
||||
// Group together websites that require deduping.
|
||||
|
@ -154,6 +157,9 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
case at.PLACES_HISTORY_CLEARED:
|
||||
this.refresh();
|
||||
break;
|
||||
case at.BLOCK_URL: // Topsite blocked, we want to get a new one in.
|
||||
this.refresh();
|
||||
break;
|
||||
case at.PREF_CHANGED:
|
||||
if (action.data.name === DEFAULT_SITES_PREF) {
|
||||
this.refreshDefaults(action.data.value);
|
||||
|
|
|
@ -9,26 +9,30 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/NewTabUtils.jsm");
|
||||
Cu.importGlobalProperties(["fetch"]);
|
||||
|
||||
const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
const {Prefs} = Cu.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
|
||||
const {shortURL} = Cu.import("resource://activity-stream/common/ShortURL.jsm", {});
|
||||
const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {});
|
||||
const {SectionsManager} = Cu.import("resource://activity-stream/lib/SectionsManager.jsm", {});
|
||||
|
||||
const STORIES_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
|
||||
const TOPICS_UPDATE_TIME = 3 * 60 * 60 * 1000; // 3 hours
|
||||
const STORIES_NOW_THRESHOLD = 24 * 60 * 60 * 1000; // 24 hours
|
||||
const SECTION_ID = "TopStories";
|
||||
const SECTION_ID = "topstories";
|
||||
const FEED_PREF = "feeds.section.topstories";
|
||||
const SECTION_OPTIONS_PREF = "feeds.section.topstories.options";
|
||||
|
||||
this.TopStoriesFeed = class TopStoriesFeed {
|
||||
|
||||
init() {
|
||||
try {
|
||||
this.storiesLastUpdated = 0;
|
||||
this.topicsLastUpdated = 0;
|
||||
this.storiesLastUpdated = 0;
|
||||
this.topicsLastUpdated = 0;
|
||||
|
||||
const prefs = new Prefs();
|
||||
const options = JSON.parse(prefs.get(SECTION_OPTIONS_PREF));
|
||||
SectionsManager.onceInitialized(this.parseOptions.bind(this));
|
||||
}
|
||||
|
||||
parseOptions() {
|
||||
SectionsManager.enableSection(SECTION_ID);
|
||||
const options = SectionsManager.sections.get(SECTION_ID).options;
|
||||
try {
|
||||
const apiKey = this._getApiKeyFromPref(options.api_key_pref);
|
||||
this.stories_endpoint = this._produceFinalEndpointUrl(options.stories_endpoint, apiKey);
|
||||
this.topics_endpoint = this._produceFinalEndpointUrl(options.topics_endpoint, apiKey);
|
||||
|
@ -36,30 +40,6 @@ this.TopStoriesFeed = class TopStoriesFeed {
|
|||
this.read_more_endpoint = options.read_more_endpoint;
|
||||
this.stories_referrer = options.stories_referrer;
|
||||
|
||||
// TODO https://github.com/mozilla/activity-stream/issues/2902
|
||||
const sectionOptions = {
|
||||
id: SECTION_ID,
|
||||
eventSource: "TOP_STORIES",
|
||||
icon: options.provider_icon,
|
||||
title: {id: "header_recommended_by", values: {provider: options.provider_name}},
|
||||
rows: [],
|
||||
maxRows: 1,
|
||||
contextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
|
||||
infoOption: {
|
||||
header: {id: "pocket_feedback_header"},
|
||||
body: {id: options.provider_description},
|
||||
link: {
|
||||
href: options.survey_link,
|
||||
id: "pocket_send_feedback"
|
||||
}
|
||||
},
|
||||
emptyState: {
|
||||
message: {id: "topstories_empty_state", values: {provider: options.provider_name}},
|
||||
icon: "check"
|
||||
}
|
||||
};
|
||||
this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_REGISTER, data: sectionOptions}));
|
||||
|
||||
this.fetchStories();
|
||||
this.fetchTopics();
|
||||
} catch (e) {
|
||||
|
@ -68,7 +48,7 @@ this.TopStoriesFeed = class TopStoriesFeed {
|
|||
}
|
||||
|
||||
uninit() {
|
||||
this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_DEREGISTER, data: SECTION_ID}));
|
||||
SectionsManager.disableSection(SECTION_ID);
|
||||
}
|
||||
|
||||
async fetchStories() {
|
||||
|
@ -81,27 +61,26 @@ this.TopStoriesFeed = class TopStoriesFeed {
|
|||
throw new Error(`Stories endpoint returned unexpected status: ${response.status}`);
|
||||
})
|
||||
.then(body => {
|
||||
let items = JSON.parse(body).list;
|
||||
let items = JSON.parse(body).recommendations;
|
||||
items = items
|
||||
.filter(s => !NewTabUtils.blockedLinks.isBlocked({"url": s.dedupe_url}))
|
||||
.filter(s => !NewTabUtils.blockedLinks.isBlocked({"url": s.url}))
|
||||
.map(s => ({
|
||||
"guid": s.id,
|
||||
"hostname": shortURL(Object.assign({}, s, {url: s.dedupe_url})),
|
||||
"hostname": shortURL(Object.assign({}, s, {url: s.url})),
|
||||
"type": (Date.now() - (s.published_timestamp * 1000)) <= STORIES_NOW_THRESHOLD ? "now" : "trending",
|
||||
"title": s.title,
|
||||
"description": s.excerpt,
|
||||
"image": this._normalizeUrl(s.image_src),
|
||||
"referrer": this.stories_referrer,
|
||||
"url": s.dedupe_url,
|
||||
"eTLD": this._addETLD(s.dedupe_url)
|
||||
"url": s.url,
|
||||
"eTLD": this._addETLD(s.url)
|
||||
}));
|
||||
return items;
|
||||
})
|
||||
.catch(error => Cu.reportError(`Failed to fetch content: ${error.message}`));
|
||||
|
||||
if (stories) {
|
||||
this.dispatchUpdateEvent(this.storiesLastUpdated,
|
||||
{"type": at.SECTION_ROWS_UPDATE, "data": {"id": SECTION_ID, "rows": stories}});
|
||||
this.dispatchUpdateEvent(this.storiesLastUpdated, {rows: stories});
|
||||
this.storiesLastUpdated = Date.now();
|
||||
}
|
||||
}
|
||||
|
@ -120,19 +99,14 @@ this.TopStoriesFeed = class TopStoriesFeed {
|
|||
.catch(error => Cu.reportError(`Failed to fetch topics: ${error.message}`));
|
||||
|
||||
if (topics) {
|
||||
this.dispatchUpdateEvent(this.topicsLastUpdated,
|
||||
{"type": at.SECTION_ROWS_UPDATE, "data": {"id": SECTION_ID, "topics": topics, "read_more_endpoint": this.read_more_endpoint}});
|
||||
this.dispatchUpdateEvent(this.topicsLastUpdated, {topics, read_more_endpoint: this.read_more_endpoint});
|
||||
this.topicsLastUpdated = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatchUpdateEvent(lastUpdated, evt) {
|
||||
if (lastUpdated === 0) {
|
||||
this.store.dispatch(ac.BroadcastToContent(evt));
|
||||
} else {
|
||||
this.store.dispatch(evt);
|
||||
}
|
||||
dispatchUpdateEvent(lastUpdated, data) {
|
||||
SectionsManager.updateSection(SECTION_ID, data, lastUpdated === 0);
|
||||
}
|
||||
|
||||
_getApiKeyFromPref(apiKeyPref) {
|
||||
|
@ -191,11 +165,6 @@ this.TopStoriesFeed = class TopStoriesFeed {
|
|||
this.init();
|
||||
}
|
||||
break;
|
||||
case at.PREF_CHANGED:
|
||||
if (action.data.name === SECTION_OPTIONS_PREF) {
|
||||
this.init();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -204,5 +173,4 @@ this.STORIES_UPDATE_TIME = STORIES_UPDATE_TIME;
|
|||
this.TOPICS_UPDATE_TIME = TOPICS_UPDATE_TIME;
|
||||
this.SECTION_ID = SECTION_ID;
|
||||
this.FEED_PREF = FEED_PREF;
|
||||
this.SECTION_OPTIONS_PREF = SECTION_OPTIONS_PREF;
|
||||
this.EXPORTED_SYMBOLS = ["TopStoriesFeed", "STORIES_UPDATE_TIME", "TOPICS_UPDATE_TIME", "SECTION_ID", "FEED_PREF", "SECTION_OPTIONS_PREF"];
|
||||
this.EXPORTED_SYMBOLS = ["TopStoriesFeed", "STORIES_UPDATE_TIME", "TOPICS_UPDATE_TIME", "SECTION_ID", "FEED_PREF"];
|
||||
|
|
|
@ -6,4 +6,3 @@ support-files =
|
|||
[browser_as_load_location.js]
|
||||
[browser_as_render.js]
|
||||
[browser_getScreenshots.js]
|
||||
skip-if=true # issue 2851
|
||||
|
|
|
@ -10,8 +10,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
const TEST_URL = "https://example.com/browser/browser/extensions/activity-stream/test/functional/mochitest/blue_page.html";
|
||||
const XHTMLNS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", false]]});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Screenshots", "resource://activity-stream/lib/Screenshots.jsm");
|
||||
|
||||
function get_pixels_for_data_uri(dataURI, width, height) {
|
||||
|
@ -32,6 +30,8 @@ function get_pixels_for_data_uri(dataURI, width, height) {
|
|||
}
|
||||
|
||||
add_task(async function test_screenshot() {
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", false]]});
|
||||
|
||||
// take a screenshot of a blue page and save it as a data URI
|
||||
const screenshotAsDataURI = await Screenshots.getScreenshotForURL(TEST_URL);
|
||||
let pixels = await get_pixels_for_data_uri(screenshotAsDataURI, 10, 10);
|
||||
|
|
|
@ -210,7 +210,8 @@ describe("Reducers", () => {
|
|||
id: `foo_bar_${i}`,
|
||||
title: `Foo Bar ${i}`,
|
||||
initialized: false,
|
||||
rows: [{url: "www.foo.bar"}, {url: "www.other.url"}]
|
||||
rows: [{url: "www.foo.bar"}, {url: "www.other.url"}],
|
||||
order: i
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -230,6 +231,29 @@ describe("Reducers", () => {
|
|||
const insertedSection = newState.find(section => section.id === "foo_bar_5");
|
||||
assert.propertyVal(insertedSection, "title", action.data.title);
|
||||
});
|
||||
it("should ensure sections are sorted by property `order` (increasing) on SECTION_REGISTER", () => {
|
||||
let newState = [];
|
||||
const state = Object.assign([], oldState);
|
||||
state.forEach(section => {
|
||||
Object.assign(section, {order: 5 - section.order});
|
||||
const action = {type: at.SECTION_REGISTER, data: section};
|
||||
newState = Sections(newState, action);
|
||||
});
|
||||
// Should have been inserted into newState in reverse order
|
||||
assert.deepEqual(newState.map(section => section.id), state.map(section => section.id).reverse());
|
||||
const newSection = {id: "new_section", order: 2.5};
|
||||
const action = {type: at.SECTION_REGISTER, data: newSection};
|
||||
newState = Sections(newState, action);
|
||||
// Should have been inserted at index 2, between second and third section
|
||||
assert.equal(newState[2].id, newSection.id);
|
||||
});
|
||||
it("should insert sections without an `order` at the top on SECTION_REGISTER", () => {
|
||||
const newSection = {id: "new_section"};
|
||||
const action = {type: at.SECTION_REGISTER, data: newSection};
|
||||
const newState = Sections(oldState, action);
|
||||
assert.equal(newState[0].id, newSection.id);
|
||||
assert.ok(newState[0].order < newState[1].order);
|
||||
});
|
||||
it("should set newSection.rows === [] if no rows are provided on SECTION_REGISTER", () => {
|
||||
const action = {type: at.SECTION_REGISTER, data: {id: "foo_bar_5", title: "Foo Bar 5"}};
|
||||
const newState = Sections(oldState, action);
|
||||
|
@ -244,17 +268,17 @@ describe("Reducers", () => {
|
|||
const updatedSection = newState.find(section => section.id === "foo_bar_2");
|
||||
assert.ok(updatedSection && updatedSection.title === NEW_TITLE);
|
||||
});
|
||||
it("should have no effect on SECTION_ROWS_UPDATE if the id doesn't exist", () => {
|
||||
const action = {type: at.SECTION_ROWS_UPDATE, data: {id: "fake_id", data: "fake_data"}};
|
||||
it("should have no effect on SECTION_UPDATE if the id doesn't exist", () => {
|
||||
const action = {type: at.SECTION_UPDATE, data: {id: "fake_id", data: "fake_data"}};
|
||||
const newState = Sections(oldState, action);
|
||||
assert.deepEqual(oldState, newState);
|
||||
});
|
||||
it("should update the section rows with the correct data on SECTION_ROWS_UPDATE", () => {
|
||||
const FAKE_DATA = ["some", "fake", "data"];
|
||||
const action = {type: at.SECTION_ROWS_UPDATE, data: {id: "foo_bar_2", rows: FAKE_DATA}};
|
||||
it("should update the section with the correct data on SECTION_UPDATE", () => {
|
||||
const FAKE_DATA = {rows: ["some", "fake", "data"], foo: "bar"};
|
||||
const action = {type: at.SECTION_UPDATE, data: Object.assign(FAKE_DATA, {id: "foo_bar_2"})};
|
||||
const newState = Sections(oldState, action);
|
||||
const updatedSection = newState.find(section => section.id === "foo_bar_2");
|
||||
assert.equal(updatedSection.rows, FAKE_DATA);
|
||||
assert.include(updatedSection, FAKE_DATA);
|
||||
});
|
||||
it("should remove blocked and deleted urls from all rows in all sections", () => {
|
||||
const blockAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "www.foo.bar"}};
|
||||
|
|
|
@ -80,6 +80,16 @@ describe("ActivityStreamMessageChannel", () => {
|
|||
mm.createChannel();
|
||||
assert.notCalled(global.AboutNewTab.override);
|
||||
});
|
||||
it("should simluate init for existing ports", () => {
|
||||
sinon.stub(mm, "onActionFromContent");
|
||||
RPmessagePorts.push({loaded: false, portID: "inited"});
|
||||
RPmessagePorts.push({loaded: true, portID: "loaded"});
|
||||
|
||||
mm.createChannel();
|
||||
|
||||
assert.calledWith(mm.onActionFromContent.firstCall, {type: at.NEW_TAB_INIT}, "inited");
|
||||
assert.calledWith(mm.onActionFromContent.secondCall, {type: at.NEW_TAB_INIT}, "loaded");
|
||||
});
|
||||
it("should simluate load for loaded ports", () => {
|
||||
sinon.stub(mm, "onActionFromContent");
|
||||
RPmessagePorts.push({loaded: true, portID: "foo"});
|
||||
|
|
|
@ -61,7 +61,7 @@ describe("Localization Feed", () => {
|
|||
it("should use strings for other locale", () => {
|
||||
const locale = "it";
|
||||
sandbox.stub(global.Services.locale, "negotiateLanguages")
|
||||
.returns([locale]);
|
||||
.returns([locale, DEFAULT_LOCALE]);
|
||||
|
||||
feed.updateLocale();
|
||||
|
||||
|
@ -74,7 +74,7 @@ describe("Localization Feed", () => {
|
|||
it("should use some fallback strings for partial locale", () => {
|
||||
const locale = "ru";
|
||||
sandbox.stub(global.Services.locale, "negotiateLanguages")
|
||||
.returns([locale]);
|
||||
.returns([locale, DEFAULT_LOCALE]);
|
||||
|
||||
feed.updateLocale();
|
||||
|
||||
|
@ -87,16 +87,33 @@ describe("Localization Feed", () => {
|
|||
too: TEST_STRINGS[DEFAULT_LOCALE].too
|
||||
});
|
||||
});
|
||||
it("should use all default strings for unknown locale", () => {
|
||||
const locale = "xyz";
|
||||
it("should use multiple fallback strings before default", () => {
|
||||
const primaryLocale = "ru";
|
||||
const secondaryLocale = "it";
|
||||
sandbox.stub(global.Services.locale, "negotiateLanguages")
|
||||
.returns([locale]);
|
||||
.returns([primaryLocale, secondaryLocale, DEFAULT_LOCALE]);
|
||||
|
||||
feed.updateLocale();
|
||||
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
const arg = feed.store.dispatch.firstCall.args[0];
|
||||
assert.propertyVal(arg, "type", at.LOCALE_UPDATED);
|
||||
assert.propertyVal(arg.data, "locale", locale);
|
||||
assert.propertyVal(arg.data, "locale", primaryLocale);
|
||||
assert.deepEqual(arg.data.strings, {
|
||||
foo: TEST_STRINGS[primaryLocale].foo,
|
||||
too: TEST_STRINGS[secondaryLocale].too
|
||||
});
|
||||
});
|
||||
it("should use all default strings for unknown locale", () => {
|
||||
sandbox.stub(global.Services.locale, "negotiateLanguages")
|
||||
.returns([DEFAULT_LOCALE]);
|
||||
|
||||
feed.updateLocale();
|
||||
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
const arg = feed.store.dispatch.firstCall.args[0];
|
||||
assert.propertyVal(arg, "type", at.LOCALE_UPDATED);
|
||||
assert.propertyVal(arg.data, "locale", DEFAULT_LOCALE);
|
||||
assert.deepEqual(arg.data.strings, TEST_STRINGS[DEFAULT_LOCALE]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,20 +1,52 @@
|
|||
"use strict";
|
||||
const {SectionsFeed, SectionsManager} = require("lib/SectionsManager.jsm");
|
||||
const {EventEmitter} = require("test/unit/utils");
|
||||
const {EventEmitter, GlobalOverrider} = require("test/unit/utils");
|
||||
const {MAIN_MESSAGE_TYPE, CONTENT_MESSAGE_TYPE} = require("common/Actions.jsm");
|
||||
|
||||
const FAKE_ID = "FAKE_ID";
|
||||
const FAKE_OPTIONS = {icon: "FAKE_ICON", title: "FAKE_TITLE"};
|
||||
const FAKE_ROWS = [{url: "1"}, {url: "2"}, {"url": "3"}];
|
||||
|
||||
let globals;
|
||||
|
||||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globals.restore();
|
||||
// Redecorate SectionsManager to remove any listeners that have been added
|
||||
EventEmitter.decorate(SectionsManager);
|
||||
SectionsManager.init();
|
||||
});
|
||||
|
||||
describe("SectionsManager", () => {
|
||||
it("should be initialised with .initialized == false", () => {
|
||||
assert.notOk(SectionsManager.initialized);
|
||||
describe("#init", () => {
|
||||
it("should initialise the sections map with the built in sections", () => {
|
||||
SectionsManager.sections.clear();
|
||||
SectionsManager.initialized = false;
|
||||
SectionsManager.init();
|
||||
assert.equal(SectionsManager.sections.size, 1);
|
||||
assert.ok(SectionsManager.sections.has("topstories"));
|
||||
});
|
||||
it("should set .initialized to true", () => {
|
||||
SectionsManager.sections.clear();
|
||||
SectionsManager.initialized = false;
|
||||
SectionsManager.init();
|
||||
assert.ok(SectionsManager.initialized);
|
||||
});
|
||||
});
|
||||
describe("#addBuiltInSection", () => {
|
||||
it("should not report an error if options is undefined", () => {
|
||||
globals.sandbox.spy(global.Components.utils, "reportError");
|
||||
SectionsManager.addBuiltInSection("feeds.section.topstories", undefined);
|
||||
assert.notCalled(Components.utils.reportError);
|
||||
});
|
||||
it("should report an error if options is malformed", () => {
|
||||
globals.sandbox.spy(global.Components.utils, "reportError");
|
||||
SectionsManager.addBuiltInSection("feeds.section.topstories", "invalid");
|
||||
assert.calledOnce(Components.utils.reportError);
|
||||
});
|
||||
});
|
||||
describe("#addSection", () => {
|
||||
it("should add the id to sections and emit an ADD_SECTION event", () => {
|
||||
|
@ -38,29 +70,67 @@ describe("SectionsManager", () => {
|
|||
assert.calledWith(spy, SectionsManager.REMOVE_SECTION, FAKE_ID);
|
||||
});
|
||||
});
|
||||
describe("#updateRows", () => {
|
||||
it("should emit an UPDATE_ROWS event with correct arguments", () => {
|
||||
describe("#enableSection", () => {
|
||||
it("should call updateSection with {enabled: true}", () => {
|
||||
sinon.spy(SectionsManager, "updateSection");
|
||||
SectionsManager.addSection(FAKE_ID, FAKE_OPTIONS);
|
||||
SectionsManager.enableSection(FAKE_ID);
|
||||
assert.calledOnce(SectionsManager.updateSection);
|
||||
assert.calledWith(SectionsManager.updateSection, FAKE_ID, {enabled: true}, true);
|
||||
SectionsManager.updateSection.restore();
|
||||
});
|
||||
});
|
||||
describe("#disableSection", () => {
|
||||
it("should call updateSection with {enabled: false, rows: []}", () => {
|
||||
sinon.spy(SectionsManager, "updateSection");
|
||||
SectionsManager.addSection(FAKE_ID, FAKE_OPTIONS);
|
||||
SectionsManager.disableSection(FAKE_ID);
|
||||
assert.calledOnce(SectionsManager.updateSection);
|
||||
assert.calledWith(SectionsManager.updateSection, FAKE_ID, {enabled: false, rows: []}, true);
|
||||
SectionsManager.updateSection.restore();
|
||||
});
|
||||
});
|
||||
describe("#updateSection", () => {
|
||||
it("should emit an UPDATE_SECTION event with correct arguments", () => {
|
||||
SectionsManager.addSection(FAKE_ID, FAKE_OPTIONS);
|
||||
const spy = sinon.spy();
|
||||
SectionsManager.on(SectionsManager.UPDATE_ROWS, spy);
|
||||
SectionsManager.updateRows(FAKE_ID, FAKE_ROWS, true);
|
||||
SectionsManager.on(SectionsManager.UPDATE_SECTION, spy);
|
||||
SectionsManager.updateSection(FAKE_ID, {rows: FAKE_ROWS}, true);
|
||||
assert.calledOnce(spy);
|
||||
assert.calledWith(spy, SectionsManager.UPDATE_ROWS, FAKE_ID, FAKE_ROWS, true);
|
||||
assert.calledWith(spy, SectionsManager.UPDATE_SECTION, FAKE_ID, {rows: FAKE_ROWS}, true);
|
||||
});
|
||||
it("should do nothing if the section doesn't exist", () => {
|
||||
SectionsManager.removeSection(FAKE_ID);
|
||||
const spy = sinon.spy();
|
||||
SectionsManager.on(SectionsManager.UPDATE_ROWS, spy);
|
||||
SectionsManager.updateRows(FAKE_ID, FAKE_ROWS, true);
|
||||
SectionsManager.on(SectionsManager.UPDATE_SECTION, spy);
|
||||
SectionsManager.updateSection(FAKE_ID, {rows: FAKE_ROWS}, true);
|
||||
assert.notCalled(spy);
|
||||
});
|
||||
});
|
||||
describe("#onceInitialized", () => {
|
||||
it("should call the callback immediately if SectionsManager is initialised", () => {
|
||||
SectionsManager.initialized = true;
|
||||
const callback = sinon.spy();
|
||||
SectionsManager.onceInitialized(callback);
|
||||
assert.calledOnce(callback);
|
||||
});
|
||||
it("should bind the callback to .once(INIT) if SectionsManager is not initialised", () => {
|
||||
SectionsManager.initialized = false;
|
||||
sinon.spy(SectionsManager, "once");
|
||||
const callback = () => {};
|
||||
SectionsManager.onceInitialized(callback);
|
||||
assert.calledOnce(SectionsManager.once);
|
||||
assert.calledWith(SectionsManager.once, SectionsManager.INIT, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("SectionsFeed", () => {
|
||||
let feed;
|
||||
|
||||
beforeEach(() => {
|
||||
SectionsManager.sections.clear();
|
||||
SectionsManager.initialized = false;
|
||||
feed = new SectionsFeed();
|
||||
feed.store = {dispatch: sinon.spy()};
|
||||
});
|
||||
|
@ -78,17 +148,19 @@ describe("SectionsFeed", () => {
|
|||
for (const [event, listener] of [
|
||||
[SectionsManager.ADD_SECTION, feed.onAddSection],
|
||||
[SectionsManager.REMOVE_SECTION, feed.onRemoveSection],
|
||||
[SectionsManager.UPDATE_ROWS, feed.onUpdateRows]
|
||||
[SectionsManager.UPDATE_SECTION, feed.onUpdateSection]
|
||||
]) {
|
||||
assert.calledWith(SectionsManager.on, event, listener);
|
||||
}
|
||||
});
|
||||
it("should emit an INIT event and set SectionsManager.initialized to true", () => {
|
||||
const spy = sinon.spy();
|
||||
SectionsManager.on(SectionsManager.INIT, spy);
|
||||
it("should call onAddSection for any already added sections in SectionsManager", () => {
|
||||
SectionsManager.init();
|
||||
assert.ok(SectionsManager.sections.has("topstories"));
|
||||
const topstories = SectionsManager.sections.get("topstories");
|
||||
sinon.spy(feed, "onAddSection");
|
||||
feed.init();
|
||||
assert.calledOnce(spy);
|
||||
assert.ok(SectionsManager.initialized);
|
||||
assert.calledOnce(feed.onAddSection);
|
||||
assert.calledWith(feed.onAddSection, SectionsManager.ADD_SECTION, "topstories", topstories);
|
||||
});
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
|
@ -100,7 +172,7 @@ describe("SectionsFeed", () => {
|
|||
for (const [event, listener] of [
|
||||
[SectionsManager.ADD_SECTION, feed.onAddSection],
|
||||
[SectionsManager.REMOVE_SECTION, feed.onRemoveSection],
|
||||
[SectionsManager.UPDATE_ROWS, feed.onUpdateRows]
|
||||
[SectionsManager.UPDATE_SECTION, feed.onUpdateSection]
|
||||
]) {
|
||||
assert.calledWith(SectionsManager.off, event, listener);
|
||||
}
|
||||
|
@ -135,21 +207,21 @@ describe("SectionsFeed", () => {
|
|||
assert.equal(action.meta.to, CONTENT_MESSAGE_TYPE);
|
||||
});
|
||||
});
|
||||
describe("#onUpdateRows", () => {
|
||||
describe("#onUpdateSection", () => {
|
||||
it("should do nothing if no rows are provided", () => {
|
||||
feed.onUpdateRows(null, FAKE_ID, null);
|
||||
feed.onUpdateSection(null, FAKE_ID, null);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
});
|
||||
it("should dispatch a SECTION_ROWS_UPDATE action with the correct data", () => {
|
||||
feed.onUpdateRows(null, FAKE_ID, FAKE_ROWS);
|
||||
it("should dispatch a SECTION_UPDATE action with the correct data", () => {
|
||||
feed.onUpdateSection(null, FAKE_ID, {rows: FAKE_ROWS});
|
||||
const action = feed.store.dispatch.firstCall.args[0];
|
||||
assert.equal(action.type, "SECTION_ROWS_UPDATE");
|
||||
assert.equal(action.type, "SECTION_UPDATE");
|
||||
assert.deepEqual(action.data, {id: FAKE_ID, rows: FAKE_ROWS});
|
||||
// Should be not broadcast by default, so meta should not exist
|
||||
assert.notOk(action.meta);
|
||||
});
|
||||
it("should broadcast the action only if shouldBroadcast is true", () => {
|
||||
feed.onUpdateRows(null, FAKE_ID, FAKE_ROWS, true);
|
||||
feed.onUpdateSection(null, FAKE_ID, {rows: FAKE_ROWS}, true);
|
||||
const action = feed.store.dispatch.firstCall.args[0];
|
||||
// Should be broadcast
|
||||
assert.equal(action.meta.from, MAIN_MESSAGE_TYPE);
|
||||
|
@ -157,18 +229,46 @@ describe("SectionsFeed", () => {
|
|||
});
|
||||
});
|
||||
describe("#onAction", () => {
|
||||
it("should call init() on action INIT", () => {
|
||||
sinon.spy(feed, "init");
|
||||
it("should bind this.init to SectionsManager.INIT on INIT", () => {
|
||||
sinon.spy(SectionsManager, "once");
|
||||
feed.onAction({type: "INIT"});
|
||||
assert.calledOnce(feed.init);
|
||||
assert.calledOnce(SectionsManager.once);
|
||||
assert.calledWith(SectionsManager.once, SectionsManager.INIT, feed.init);
|
||||
});
|
||||
it("should call SectionsManager.init on action PREFS_INITIAL_VALUES", () => {
|
||||
sinon.spy(SectionsManager, "init");
|
||||
feed.onAction({type: "PREFS_INITIAL_VALUES", data: {foo: "bar"}});
|
||||
assert.calledOnce(SectionsManager.init);
|
||||
assert.calledWith(SectionsManager.init, {foo: "bar"});
|
||||
});
|
||||
it("should call SectionsManager.addBuiltInSection on suitable PREF_CHANGED events", () => {
|
||||
sinon.spy(SectionsManager, "addBuiltInSection");
|
||||
feed.onAction({type: "PREF_CHANGED", data: {name: "feeds.section.topstories.options", value: "foo"}});
|
||||
assert.calledOnce(SectionsManager.addBuiltInSection);
|
||||
assert.calledWith(SectionsManager.addBuiltInSection, "feeds.section.topstories", "foo");
|
||||
});
|
||||
it("should call SectionsManager.disableSection on SECTION_DISABLE", () => {
|
||||
sinon.spy(SectionsManager, "disableSection");
|
||||
feed.onAction({type: "SECTION_DISABLE", data: 1234});
|
||||
assert.calledOnce(SectionsManager.disableSection);
|
||||
assert.calledWith(SectionsManager.disableSection, 1234);
|
||||
SectionsManager.disableSection.restore();
|
||||
});
|
||||
it("should call SectionsManager.enableSection on SECTION_ENABLE", () => {
|
||||
sinon.spy(SectionsManager, "enableSection");
|
||||
feed.onAction({type: "SECTION_ENABLE", data: 1234});
|
||||
assert.calledOnce(SectionsManager.enableSection);
|
||||
assert.calledWith(SectionsManager.enableSection, 1234);
|
||||
SectionsManager.enableSection.restore();
|
||||
});
|
||||
it("should emit a ACTION_DISPATCHED event and forward any action in ACTIONS_TO_PROXY if there are any sections", () => {
|
||||
const spy = sinon.spy();
|
||||
const allowedActions = SectionsManager.ACTIONS_TO_PROXY;
|
||||
const disallowedActions = ["PREF_CHANGED", "OPEN_PRIVATE_WINDOW"];
|
||||
feed.init();
|
||||
SectionsManager.on(SectionsManager.ACTION_DISPATCHED, spy);
|
||||
// Make sure we start with no sections - no event should be emitted
|
||||
assert.equal(SectionsManager.sections.size, 0);
|
||||
SectionsManager.sections.clear();
|
||||
feed.onAction({type: allowedActions[0]});
|
||||
assert.notCalled(spy);
|
||||
// Then add a section and check correct behaviour
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
const {shortURL} = require("common/ShortURL.jsm");
|
||||
const {shortURL} = require("lib/ShortURL.jsm");
|
||||
const {GlobalOverrider} = require("test/unit/utils");
|
||||
|
||||
describe("shortURL", () => {
|
||||
let globals;
|
||||
let IDNStub;
|
||||
|
||||
beforeEach(() => {
|
||||
IDNStub = sinon.stub().callsFake(id => id);
|
||||
|
||||
globals = new GlobalOverrider();
|
||||
globals.set("IDNService", {convertToDisplayIDN: IDNStub});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globals.restore();
|
||||
});
|
||||
|
||||
it("should return a blank string if url and hostname is falsey", () => {
|
||||
assert.equal(shortURL({url: ""}), "");
|
||||
assert.equal(shortURL({hostname: null}), "");
|
||||
|
@ -10,6 +25,13 @@ describe("shortURL", () => {
|
|||
assert.equal(shortURL({hostname: "com.blah.com", eTLD: "com"}), "com.blah");
|
||||
});
|
||||
|
||||
it("should call convertToDisplayIDN when calling shortURL", () => {
|
||||
shortURL({hostname: "com.blah.com", eTLD: "com"});
|
||||
|
||||
assert.calledOnce(IDNStub);
|
||||
assert.calledWithExactly(IDNStub, "com.blah.com", {});
|
||||
});
|
||||
|
||||
it("should use the hostname, if provided", () => {
|
||||
assert.equal(shortURL({hostname: "foo.com", url: "http://bar.com", eTLD: "com"}), "foo");
|
||||
});
|
|
@ -5,7 +5,12 @@ const {FakePrefs, GlobalOverrider} = require("test/unit/utils");
|
|||
const action = {meta: {fromTarget: {}}};
|
||||
const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm");
|
||||
const {insertPinned, TOP_SITES_SHOWMORE_LENGTH} = require("common/Reducers.jsm");
|
||||
const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({url: `http://www.site${i}.com`}));
|
||||
|
||||
const FAKE_FRECENCY = 200;
|
||||
const FAKE_LINKS = new Array(TOP_SITES_SHOWMORE_LENGTH).fill(null).map((v, i) => ({
|
||||
frecency: FAKE_FRECENCY,
|
||||
url: `http://www.site${i}.com`
|
||||
}));
|
||||
const FAKE_SCREENSHOT = "data123";
|
||||
|
||||
function FakeTippyTopProvider() {}
|
||||
|
@ -53,7 +58,7 @@ describe("Top Sites Feed", () => {
|
|||
"common/Reducers.jsm": {insertPinned, TOP_SITES_SHOWMORE_LENGTH},
|
||||
"lib/Screenshots.jsm": {Screenshots: fakeScreenshot},
|
||||
"lib/TippyTopProvider.jsm": {TippyTopProvider: FakeTippyTopProvider},
|
||||
"common/ShortURL.jsm": {shortURL: shortURLStub}
|
||||
"lib/ShortURL.jsm": {shortURL: shortURLStub}
|
||||
}));
|
||||
feed = new TopSitesFeed();
|
||||
feed.store = {dispatch: sinon.spy(), getState() { return {TopSites: {rows: Array(12).fill("site")}}; }};
|
||||
|
@ -106,10 +111,27 @@ describe("Top Sites Feed", () => {
|
|||
assert.deepEqual(result, reference);
|
||||
assert.calledOnce(global.NewTabUtils.activityStreamLinks.getTopSites);
|
||||
});
|
||||
it("should filter out low frecency links", async () => {
|
||||
links = [
|
||||
{frecency: FAKE_FRECENCY, url: "https://enough/visited"},
|
||||
{frecency: 100, url: "https://visited/once"},
|
||||
{frecency: 0, url: "https://unvisited/page"}
|
||||
];
|
||||
|
||||
const result = await feed.getLinksWithDefaults();
|
||||
|
||||
assert.equal(result[0].url, links[0].url);
|
||||
assert.notEqual(result[1].url, links[1].url);
|
||||
assert.notEqual(result[1].url, links[2].url);
|
||||
});
|
||||
it("should filter out the defaults that have been blocked", async () => {
|
||||
// make sure we only have one top site, and we block the only default site we have to show
|
||||
const url = "www.myonlytopsite.com";
|
||||
const topsite = {url, hostname: shortURLStub({url})};
|
||||
const topsite = {
|
||||
frecency: FAKE_FRECENCY,
|
||||
hostname: shortURLStub({url}),
|
||||
url
|
||||
};
|
||||
const blockedDefaultSite = {url: "https://foo.com"};
|
||||
fakeNewTabUtils.activityStreamLinks.getTopSites = () => [topsite];
|
||||
fakeNewTabUtils.blockedLinks.isBlocked = site => (site.url === blockedDefaultSite.url);
|
||||
|
@ -134,7 +156,7 @@ describe("Top Sites Feed", () => {
|
|||
assert.equal(result, site.hostname);
|
||||
});
|
||||
it("should add defaults if there are are not enough links", async () => {
|
||||
links = [{url: "foo.com"}];
|
||||
links = [{frecency: FAKE_FRECENCY, url: "foo.com"}];
|
||||
|
||||
const result = await feed.getLinksWithDefaults();
|
||||
const reference = [...links, ...DEFAULT_TOP_SITES].map(s => Object.assign({}, s, {hostname: shortURLStub(s)}));
|
||||
|
@ -144,7 +166,7 @@ describe("Top Sites Feed", () => {
|
|||
it("should only add defaults up to TOP_SITES_SHOWMORE_LENGTH", async () => {
|
||||
links = [];
|
||||
for (let i = 0; i < TOP_SITES_SHOWMORE_LENGTH - 1; i++) {
|
||||
links.push({url: `foo${i}.com`});
|
||||
links.push({frecency: FAKE_FRECENCY, url: `foo${i}.com`});
|
||||
}
|
||||
const result = await feed.getLinksWithDefaults();
|
||||
const reference = [...links, DEFAULT_TOP_SITES[0]].map(s => Object.assign({}, s, {hostname: shortURLStub(s)}));
|
||||
|
@ -358,5 +380,10 @@ describe("Top Sites Feed", () => {
|
|||
await feed.onAction({type: at.INIT});
|
||||
assert.calledOnce(feed.refresh);
|
||||
});
|
||||
it("should call refresh on BLOCK_URL action", async () => {
|
||||
sinon.stub(feed, "refresh");
|
||||
await feed.onAction({type: at.BLOCK_URL});
|
||||
assert.calledOnce(feed.refresh);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
const injector = require("inject!lib/TopStoriesFeed.jsm");
|
||||
const {FakePrefs} = require("test/unit/utils");
|
||||
const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm");
|
||||
const {actionTypes: at} = require("common/Actions.jsm");
|
||||
const {GlobalOverrider} = require("test/unit/utils");
|
||||
|
||||
describe("Top Stories Feed", () => {
|
||||
|
@ -10,34 +10,45 @@ describe("Top Stories Feed", () => {
|
|||
let TOPICS_UPDATE_TIME;
|
||||
let SECTION_ID;
|
||||
let FEED_PREF;
|
||||
let SECTION_OPTIONS_PREF;
|
||||
let instance;
|
||||
let clock;
|
||||
let globals;
|
||||
let sectionsManagerStub;
|
||||
let shortURLStub;
|
||||
|
||||
const FAKE_OPTIONS = {
|
||||
"stories_endpoint": "https://somedomain.org/stories?key=$apiKey",
|
||||
"stories_referrer": "https://somedomain.org/referrer",
|
||||
"topics_endpoint": "https://somedomain.org/topics?key=$apiKey",
|
||||
"survey_link": "https://www.surveymonkey.com/r/newtabffx",
|
||||
"api_key_pref": "apiKeyPref",
|
||||
"provider_name": "test-provider",
|
||||
"provider_icon": "provider-icon",
|
||||
"provider_description": "provider_desc"
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = `{
|
||||
"stories_endpoint": "https://somedomain.org/stories?key=$apiKey",
|
||||
"stories_referrer": "https://somedomain.org/referrer",
|
||||
"topics_endpoint": "https://somedomain.org/topics?key=$apiKey",
|
||||
"survey_link": "https://www.surveymonkey.com/r/newtabffx",
|
||||
"api_key_pref": "apiKeyPref",
|
||||
"provider_name": "test-provider",
|
||||
"provider_icon": "provider-icon",
|
||||
"provider_description": "provider_desc"
|
||||
}`;
|
||||
FakePrefs.prototype.prefs.apiKeyPref = "test-api-key";
|
||||
|
||||
globals = new GlobalOverrider();
|
||||
globals.set("Services", {locale: {getRequestedLocale: () => "en-CA"}});
|
||||
|
||||
sectionsManagerStub = {
|
||||
onceInitialized: sinon.stub().callsFake(callback => callback()),
|
||||
enableSection: sinon.spy(),
|
||||
disableSection: sinon.spy(),
|
||||
updateSection: sinon.spy(),
|
||||
sections: new Map([["topstories", {options: FAKE_OPTIONS}]])
|
||||
};
|
||||
|
||||
clock = sinon.useFakeTimers();
|
||||
|
||||
shortURLStub = sinon.stub().callsFake(site => site.url);
|
||||
|
||||
({TopStoriesFeed, STORIES_UPDATE_TIME, TOPICS_UPDATE_TIME, SECTION_ID, FEED_PREF, SECTION_OPTIONS_PREF} = injector({
|
||||
({TopStoriesFeed, STORIES_UPDATE_TIME, TOPICS_UPDATE_TIME, SECTION_ID, FEED_PREF} = injector({
|
||||
"lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs},
|
||||
"common/ShortURL.jsm": {shortURL: shortURLStub}
|
||||
"lib/ShortURL.jsm": {shortURL: shortURLStub},
|
||||
"lib/SectionsManager.jsm": {SectionsManager: sectionsManagerStub}
|
||||
}));
|
||||
instance = new TopStoriesFeed();
|
||||
instance.store = {getState() { return {}; }, dispatch: sinon.spy()};
|
||||
|
@ -52,42 +63,20 @@ describe("Top Stories Feed", () => {
|
|||
it("should create a TopStoriesFeed", () => {
|
||||
assert.instanceOf(instance, TopStoriesFeed);
|
||||
});
|
||||
it("should initialize endpoints based on prefs", () => {
|
||||
it("should bind parseOptions to SectionsManager.onceInitialized", () => {
|
||||
instance.onAction({type: at.INIT});
|
||||
assert.calledOnce(sectionsManagerStub.onceInitialized);
|
||||
});
|
||||
it("should initialize endpoints based on options", () => {
|
||||
instance.onAction({type: at.INIT});
|
||||
assert.equal("https://somedomain.org/stories?key=test-api-key", instance.stories_endpoint);
|
||||
assert.equal("https://somedomain.org/referrer", instance.stories_referrer);
|
||||
assert.equal("https://somedomain.org/topics?key=test-api-key", instance.topics_endpoint);
|
||||
});
|
||||
it("should register section", () => {
|
||||
const expectedSectionOptions = {
|
||||
id: SECTION_ID,
|
||||
eventSource: "TOP_STORIES",
|
||||
icon: "provider-icon",
|
||||
title: {id: "header_recommended_by", values: {provider: "test-provider"}},
|
||||
rows: [],
|
||||
maxRows: 1,
|
||||
contextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
|
||||
infoOption: {
|
||||
header: {id: "pocket_feedback_header"},
|
||||
body: {id: "provider_desc"},
|
||||
link: {
|
||||
href: "https://www.surveymonkey.com/r/newtabffx",
|
||||
id: "pocket_send_feedback"
|
||||
}
|
||||
},
|
||||
emptyState: {
|
||||
message: {id: "topstories_empty_state", values: {provider: "test-provider"}},
|
||||
icon: "check"
|
||||
}
|
||||
};
|
||||
|
||||
it("should enable its section", () => {
|
||||
instance.onAction({type: at.INIT});
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_REGISTER);
|
||||
assert.calledWith(instance.store.dispatch, ac.BroadcastToContent({
|
||||
type: at.SECTION_REGISTER,
|
||||
data: expectedSectionOptions
|
||||
}));
|
||||
assert.calledOnce(sectionsManagerStub.enableSection);
|
||||
assert.calledWith(sectionsManagerStub.enableSection, SECTION_ID);
|
||||
});
|
||||
it("should fetch stories on init", () => {
|
||||
instance.fetchStories = sinon.spy();
|
||||
|
@ -104,13 +93,13 @@ describe("Top Stories Feed", () => {
|
|||
it("should not fetch if endpoint not configured", () => {
|
||||
let fetchStub = globals.sandbox.stub();
|
||||
globals.set("fetch", fetchStub);
|
||||
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = "{}";
|
||||
sectionsManagerStub.sections.set("topstories", {options: {}});
|
||||
instance.init();
|
||||
assert.notCalled(fetchStub);
|
||||
});
|
||||
it("should report error for invalid configuration", () => {
|
||||
globals.sandbox.spy(global.Components.utils, "reportError");
|
||||
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = "invalid";
|
||||
sectionsManagerStub.sections.set("topstories", {options: {api_key_pref: "invalid"}});
|
||||
instance.init();
|
||||
|
||||
assert.called(Components.utils.reportError);
|
||||
|
@ -119,31 +108,27 @@ describe("Top Stories Feed", () => {
|
|||
let fakeServices = {prefs: {getCharPref: sinon.spy()}, locale: {getRequestedLocale: sinon.spy()}};
|
||||
globals.set("Services", fakeServices);
|
||||
globals.sandbox.spy(global.Components.utils, "reportError");
|
||||
FakePrefs.prototype.prefs["feeds.section.topstories.options"] = `{
|
||||
"stories_endpoint": "https://somedomain.org/stories?key=$apiKey",
|
||||
"topics_endpoint": "https://somedomain.org/topics?key=$apiKey"
|
||||
}`;
|
||||
sectionsManagerStub.sections.set("topstories", {
|
||||
options: {
|
||||
"stories_endpoint": "https://somedomain.org/stories?key=$apiKey",
|
||||
"topics_endpoint": "https://somedomain.org/topics?key=$apiKey"
|
||||
}
|
||||
});
|
||||
instance.init();
|
||||
|
||||
assert.called(Components.utils.reportError);
|
||||
});
|
||||
it("should deregister section", () => {
|
||||
instance.onAction({type: at.UNINIT});
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
assert.calledWith(instance.store.dispatch, ac.BroadcastToContent({
|
||||
type: at.SECTION_DEREGISTER,
|
||||
data: SECTION_ID
|
||||
}));
|
||||
});
|
||||
it("should initialize on FEED_INIT", () => {
|
||||
instance.init = sinon.spy();
|
||||
instance.onAction({type: at.FEED_INIT, data: FEED_PREF});
|
||||
assert.calledOnce(instance.init);
|
||||
});
|
||||
it("should initialize on PREF_CHANGED", () => {
|
||||
instance.init = sinon.spy();
|
||||
instance.onAction({type: at.PREF_CHANGED, data: {name: SECTION_OPTIONS_PREF}});
|
||||
assert.calledOnce(instance.init);
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
it("should disable its section", () => {
|
||||
instance.onAction({type: at.UNINIT});
|
||||
assert.calledOnce(sectionsManagerStub.disableSection);
|
||||
assert.calledWith(sectionsManagerStub.disableSection, SECTION_ID);
|
||||
});
|
||||
});
|
||||
describe("#fetch", () => {
|
||||
|
@ -152,11 +137,11 @@ describe("Top Stories Feed", () => {
|
|||
globals.set("fetch", fetchStub);
|
||||
globals.set("NewTabUtils", {blockedLinks: {isBlocked: globals.sandbox.spy()}});
|
||||
|
||||
const response = `{"list": [{"id" : "1",
|
||||
const response = `{"recommendations": [{"id" : "1",
|
||||
"title": "title",
|
||||
"excerpt": "description",
|
||||
"image_src": "image-url",
|
||||
"dedupe_url": "rec-url",
|
||||
"url": "rec-url",
|
||||
"published_timestamp" : "123"
|
||||
}]}`;
|
||||
const stories = [{
|
||||
|
@ -179,14 +164,12 @@ describe("Top Stories Feed", () => {
|
|||
assert.calledOnce(fetchStub);
|
||||
assert.calledOnce(shortURLStub);
|
||||
assert.calledWithExactly(fetchStub, instance.stories_endpoint);
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE);
|
||||
assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.id, SECTION_ID);
|
||||
assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.rows, stories);
|
||||
assert.calledOnce(sectionsManagerStub.updateSection);
|
||||
assert.calledWith(sectionsManagerStub.updateSection, SECTION_ID, {rows: stories});
|
||||
});
|
||||
it("should dispatch events", () => {
|
||||
it("should call SectionsManager.updateSection", () => {
|
||||
instance.dispatchUpdateEvent(123, {});
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
assert.calledOnce(sectionsManagerStub.updateSection);
|
||||
});
|
||||
it("should report error for unexpected stories response", async () => {
|
||||
let fetchStub = globals.sandbox.stub();
|
||||
|
@ -199,7 +182,7 @@ describe("Top Stories Feed", () => {
|
|||
|
||||
assert.calledOnce(fetchStub);
|
||||
assert.calledWithExactly(fetchStub, instance.stories_endpoint);
|
||||
assert.notCalled(instance.store.dispatch);
|
||||
assert.notCalled(sectionsManagerStub.updateSection);
|
||||
assert.called(Components.utils.reportError);
|
||||
});
|
||||
it("should exclude blocked (dismissed) URLs", async () => {
|
||||
|
@ -207,15 +190,14 @@ describe("Top Stories Feed", () => {
|
|||
globals.set("fetch", fetchStub);
|
||||
globals.set("NewTabUtils", {blockedLinks: {isBlocked: site => site.url === "blocked"}});
|
||||
|
||||
const response = `{"list": [{"dedupe_url" : "blocked"}, {"dedupe_url" : "not_blocked"}]}`;
|
||||
const response = `{"recommendations": [{"url" : "blocked"}, {"url" : "not_blocked"}]}`;
|
||||
instance.stories_endpoint = "stories-endpoint";
|
||||
fetchStub.resolves({ok: true, status: 200, text: () => response});
|
||||
await instance.fetchStories();
|
||||
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE);
|
||||
assert.equal(instance.store.dispatch.firstCall.args[0].data.rows.length, 1);
|
||||
assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[0].url, "not_blocked");
|
||||
assert.calledOnce(sectionsManagerStub.updateSection);
|
||||
assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows.length, 1);
|
||||
assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows[0].url, "not_blocked");
|
||||
});
|
||||
it("should mark stories as new", async () => {
|
||||
let fetchStub = globals.sandbox.stub();
|
||||
|
@ -223,7 +205,7 @@ describe("Top Stories Feed", () => {
|
|||
globals.set("NewTabUtils", {blockedLinks: {isBlocked: globals.sandbox.spy()}});
|
||||
clock.restore();
|
||||
const response = JSON.stringify({
|
||||
"list": [
|
||||
"recommendations": [
|
||||
{"published_timestamp": Date.now() / 1000},
|
||||
{"published_timestamp": "0"},
|
||||
{"published_timestamp": (Date.now() - 2 * 24 * 60 * 60 * 1000) / 1000}
|
||||
|
@ -234,12 +216,11 @@ describe("Top Stories Feed", () => {
|
|||
fetchStub.resolves({ok: true, status: 200, text: () => response});
|
||||
|
||||
await instance.fetchStories();
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE);
|
||||
assert.equal(instance.store.dispatch.firstCall.args[0].data.rows.length, 3);
|
||||
assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[0].type, "now");
|
||||
assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[1].type, "trending");
|
||||
assert.equal(instance.store.dispatch.firstCall.args[0].data.rows[2].type, "trending");
|
||||
assert.calledOnce(sectionsManagerStub.updateSection);
|
||||
assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows.length, 3);
|
||||
assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows[0].type, "now");
|
||||
assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows[1].type, "trending");
|
||||
assert.equal(sectionsManagerStub.updateSection.firstCall.args[1].rows[2].type, "trending");
|
||||
});
|
||||
it("should fetch topics and send event", async () => {
|
||||
let fetchStub = globals.sandbox.stub();
|
||||
|
@ -260,10 +241,8 @@ describe("Top Stories Feed", () => {
|
|||
|
||||
assert.calledOnce(fetchStub);
|
||||
assert.calledWithExactly(fetchStub, instance.topics_endpoint);
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
assert.propertyVal(instance.store.dispatch.firstCall.args[0], "type", at.SECTION_ROWS_UPDATE);
|
||||
assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.id, SECTION_ID);
|
||||
assert.deepEqual(instance.store.dispatch.firstCall.args[0].data.topics, topics);
|
||||
assert.calledOnce(sectionsManagerStub.updateSection);
|
||||
assert.calledWithMatch(sectionsManagerStub.updateSection, SECTION_ID, {topics});
|
||||
});
|
||||
it("should report error for unexpected topics response", async () => {
|
||||
let fetchStub = globals.sandbox.stub();
|
||||
|
@ -282,6 +261,7 @@ describe("Top Stories Feed", () => {
|
|||
});
|
||||
describe("#update", () => {
|
||||
it("should fetch stories after update interval", () => {
|
||||
instance.init();
|
||||
instance.fetchStories = sinon.spy();
|
||||
instance.onAction({type: at.SYSTEM_TICK});
|
||||
assert.notCalled(instance.fetchStories);
|
||||
|
@ -291,6 +271,7 @@ describe("Top Stories Feed", () => {
|
|||
assert.calledOnce(instance.fetchStories);
|
||||
});
|
||||
it("should fetch topics after update interval", () => {
|
||||
instance.init();
|
||||
instance.fetchTopics = sinon.spy();
|
||||
instance.onAction({type: at.SYSTEM_TICK});
|
||||
assert.notCalled(instance.fetchTopics);
|
||||
|
|
|
@ -127,6 +127,7 @@ EventEmitter.decorate = function(objectToDecorate) {
|
|||
let emitter = new EventEmitter();
|
||||
objectToDecorate.on = emitter.on.bind(emitter);
|
||||
objectToDecorate.off = emitter.off.bind(emitter);
|
||||
objectToDecorate.once = emitter.once.bind(emitter);
|
||||
objectToDecorate.emit = emitter.emit.bind(emitter);
|
||||
};
|
||||
EventEmitter.prototype = {
|
||||
|
@ -150,6 +151,20 @@ EventEmitter.prototype = {
|
|||
));
|
||||
}
|
||||
},
|
||||
once(event, listener) {
|
||||
return new Promise(resolve => {
|
||||
let handler = (_, first, ...rest) => {
|
||||
this.off(event, handler);
|
||||
if (listener) {
|
||||
listener(event, first, ...rest);
|
||||
}
|
||||
resolve(first);
|
||||
};
|
||||
|
||||
handler._originalListener = listener;
|
||||
this.on(event, handler);
|
||||
});
|
||||
},
|
||||
// All arguments to this method will be sent to listeners
|
||||
emit(event, ...args) {
|
||||
if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(event)) {
|
||||
|
|
|
@ -26,10 +26,12 @@ FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
|||
const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
|
||||
const GetStringFromName = Services.strings.createBundle(BUNDLE_URI).GetStringFromName;
|
||||
let changeAutofillOptsKey = "changeAutofillOptions";
|
||||
let viewAutofillOptsKey = "viewAutofillOptionsLink";
|
||||
if (AppConstants.platform != "macosx") {
|
||||
let autofillOptsKey = "autofillOptionsLink";
|
||||
let autofillSecurityOptionsKey = "autofillSecurityOptionsLink";
|
||||
if (AppConstants.platform == "macosx") {
|
||||
changeAutofillOptsKey += "OSX";
|
||||
viewAutofillOptsKey += "OSX";
|
||||
autofillOptsKey += "OSX";
|
||||
autofillSecurityOptionsKey += "OSX";
|
||||
}
|
||||
|
||||
const CONTENT = {
|
||||
|
@ -69,6 +71,7 @@ const CONTENT = {
|
|||
update: {
|
||||
notificationId: "autofill-address",
|
||||
message: GetStringFromName("updateAddressMessage"),
|
||||
linkMessage: GetStringFromName(autofillOptsKey),
|
||||
anchor: {
|
||||
id: "autofill-address-notification-icon",
|
||||
URL: "chrome://formautofill/content/formfill-anchor.svg",
|
||||
|
@ -89,6 +92,34 @@ const CONTENT = {
|
|||
popupIconURL: "chrome://formautofill/content/icon-address-update.svg",
|
||||
},
|
||||
},
|
||||
creditCard: {
|
||||
notificationId: "autofill-credit-card",
|
||||
message: GetStringFromName("saveCreditCardMessage"),
|
||||
linkMessage: GetStringFromName(autofillSecurityOptionsKey),
|
||||
anchor: {
|
||||
id: "autofill-credit-card-notification-icon",
|
||||
URL: "chrome://formautofill/content/formfill-anchor.svg",
|
||||
tooltiptext: GetStringFromName("openAutofillMessagePanel"),
|
||||
},
|
||||
mainAction: {
|
||||
label: GetStringFromName("saveCreditCardLabel"),
|
||||
accessKey: "S",
|
||||
callbackState: "save",
|
||||
},
|
||||
secondaryActions: [{
|
||||
label: GetStringFromName("cancelCreditCardLabel"),
|
||||
accessKey: "D",
|
||||
callbackState: "cancel",
|
||||
}, {
|
||||
label: GetStringFromName("disableCreditCardLabel"),
|
||||
accessKey: "N",
|
||||
callbackState: "disable",
|
||||
}],
|
||||
options: {
|
||||
persistWhileVisible: true,
|
||||
popupIconURL: "chrome://formautofill/content/icon-credit-card.svg",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let FormAutofillDoorhanger = {
|
||||
|
@ -136,8 +167,10 @@ let FormAutofillDoorhanger = {
|
|||
* Target browser element for showing doorhanger.
|
||||
* @param {string} id
|
||||
* The ID of the doorhanger.
|
||||
* @param {string} message
|
||||
* The localized string for link title.
|
||||
*/
|
||||
_appendPrivacyPanelLink(browser, id) {
|
||||
_appendPrivacyPanelLink(browser, id, message) {
|
||||
let notificationId = id + "-notification";
|
||||
let chromeDoc = browser.ownerDocument;
|
||||
let notification = chromeDoc.getElementById(notificationId);
|
||||
|
@ -148,7 +181,7 @@ let FormAutofillDoorhanger = {
|
|||
privacyLinkElement.className = "text-link";
|
||||
privacyLinkElement.setAttribute("useoriginprincipal", true);
|
||||
privacyLinkElement.setAttribute("href", "about:preferences#privacy");
|
||||
privacyLinkElement.setAttribute("value", GetStringFromName(viewAutofillOptsKey));
|
||||
privacyLinkElement.setAttribute("value", message);
|
||||
notificationcontent.appendChild(privacyLinkElement);
|
||||
notification.append(notificationcontent);
|
||||
}
|
||||
|
@ -221,13 +254,22 @@ let FormAutofillDoorhanger = {
|
|||
async show(browser, type) {
|
||||
log.debug("show doorhanger with type:", type);
|
||||
return new Promise((resolve) => {
|
||||
let content = CONTENT[type];
|
||||
let {
|
||||
notificationId,
|
||||
message,
|
||||
linkMessage,
|
||||
anchor,
|
||||
mainAction,
|
||||
secondaryActions,
|
||||
options,
|
||||
} = CONTENT[type];
|
||||
|
||||
let chromeWin = browser.ownerGlobal;
|
||||
content.options.eventCallback = (topic) => {
|
||||
options.eventCallback = (topic) => {
|
||||
log.debug("eventCallback:", topic);
|
||||
|
||||
if (topic == "removed" || topic == "dismissed") {
|
||||
this._removeCheckboxListener(browser, content);
|
||||
this._removeCheckboxListener(browser, {notificationId, options});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -235,23 +277,23 @@ let FormAutofillDoorhanger = {
|
|||
if (topic != "shown") {
|
||||
return;
|
||||
}
|
||||
this._addCheckboxListener(browser, content);
|
||||
this._addCheckboxListener(browser, {notificationId, options});
|
||||
|
||||
// There's no preferences link or other customization in first time use doorhanger.
|
||||
if (type == "firstTimeUse") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._appendPrivacyPanelLink(browser, content.notificationId);
|
||||
this._appendPrivacyPanelLink(browser, notificationId, linkMessage);
|
||||
};
|
||||
this._setAnchor(browser, content.anchor);
|
||||
this._setAnchor(browser, anchor);
|
||||
chromeWin.PopupNotifications.show(
|
||||
browser,
|
||||
content.notificationId,
|
||||
content.message,
|
||||
content.anchor.id,
|
||||
...this._createActions(content.mainAction, content.secondaryActions, resolve),
|
||||
content.options,
|
||||
notificationId,
|
||||
message,
|
||||
anchor.id,
|
||||
...this._createActions(mainAction, secondaryActions, resolve),
|
||||
options,
|
||||
);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -12,14 +12,13 @@ this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
|
|||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHeuristics",
|
||||
"resource://formautofill/FormAutofillHeuristics.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
|
||||
this.log = null;
|
||||
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
||||
|
@ -284,15 +283,14 @@ FormAutofillHandler.prototype = {
|
|||
// card number. Otherwise, the number can be decrypted with the default
|
||||
// password.
|
||||
if (profile["cc-number-encrypted"]) {
|
||||
try {
|
||||
profile["cc-number"] = await MasterPassword.decrypt(profile["cc-number-encrypted"], true);
|
||||
} catch (e) {
|
||||
if (e.result == Cr.NS_ERROR_ABORT) {
|
||||
log.warn("User canceled master password entry");
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
let decrypted = await this._decrypt(profile["cc-number-encrypted"], true);
|
||||
|
||||
if (!decrypted) {
|
||||
// Early return if the decrypted is empty or undefined
|
||||
return;
|
||||
}
|
||||
|
||||
profile["cc-number"] = decrypted;
|
||||
}
|
||||
targetSet = this.creditCard;
|
||||
} else if (FormAutofillUtils.isAddressField(focusedDetail.fieldName)) {
|
||||
|
@ -392,13 +390,13 @@ FormAutofillHandler.prototype = {
|
|||
* @param {Object} focusedInput
|
||||
* A focused input element for determining credit card or address fields.
|
||||
*/
|
||||
async previewFormFields(profile, focusedInput) {
|
||||
previewFormFields(profile, focusedInput) {
|
||||
log.debug("preview profile in autofillFormFields:", profile);
|
||||
|
||||
// Always show the decrypted credit card number when Master Password is
|
||||
// disabled.
|
||||
if (profile["cc-number-encrypted"] && !MasterPassword.isEnabled) {
|
||||
profile["cc-number"] = await MasterPassword.decrypt(profile["cc-number-encrypted"], true);
|
||||
if (profile["cc-number-decrypted"]) {
|
||||
profile["cc-number"] = profile["cc-number-decrypted"];
|
||||
}
|
||||
|
||||
let fieldDetails = this.getFieldDetailsByElement(focusedInput);
|
||||
|
@ -565,11 +563,23 @@ FormAutofillHandler.prototype = {
|
|||
delete data.address;
|
||||
}
|
||||
|
||||
if (data.creditCard && !data.creditCard.record["cc-number"]) {
|
||||
log.debug("No credit card record saving since card number is empty");
|
||||
if (data.creditCard && (!data.creditCard.record["cc-number"] ||
|
||||
!FormAutofillUtils.isCCNumber(data.creditCard.record["cc-number"]))) {
|
||||
log.debug("No credit card record saving since card number is invalid");
|
||||
delete data.creditCard;
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
async _decrypt(cipherText, reauth) {
|
||||
return new Promise((resolve) => {
|
||||
Services.cpmm.addMessageListener("FormAutofill:DecryptedString", function getResult(result) {
|
||||
Services.cpmm.removeMessageListener("FormAutofill:DecryptedString", getResult);
|
||||
resolve(result.data);
|
||||
});
|
||||
|
||||
Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -277,7 +277,7 @@ var FormAutofillNameUtils = {
|
|||
},
|
||||
|
||||
joinNameParts({given, middle, family}) {
|
||||
if (this._isCJKName(given) && this._isCJKName(family) && middle == "") {
|
||||
if (this._isCJKName(given) && this._isCJKName(family) && !middle) {
|
||||
return family + given;
|
||||
}
|
||||
return [given, middle, family].filter(part => part && part.length).join(" ");
|
||||
|
|
|
@ -50,7 +50,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
|||
this.log = null;
|
||||
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
||||
|
||||
const {ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF} = FormAutofillUtils;
|
||||
const {
|
||||
ENABLED_AUTOFILL_ADDRESSES_PREF,
|
||||
ENABLED_AUTOFILL_CREDITCARDS_PREF,
|
||||
CREDITCARDS_COLLECTION_NAME,
|
||||
} = FormAutofillUtils;
|
||||
|
||||
function FormAutofillParent() {
|
||||
// Lazily load the storage JSM to avoid disk I/O until absolutely needed.
|
||||
|
@ -88,6 +92,7 @@ FormAutofillParent.prototype = {
|
|||
Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
|
||||
Services.ppmm.addMessageListener("FormAutofill:RemoveCreditCards", this);
|
||||
Services.ppmm.addMessageListener("FormAutofill:OpenPreferences", this);
|
||||
Services.ppmm.addMessageListener("FormAutofill:GetDecryptedString", this);
|
||||
Services.mm.addMessageListener("FormAutofill:OnFormSubmit", this);
|
||||
|
||||
// Observing the pref and storage changes
|
||||
|
@ -219,6 +224,21 @@ FormAutofillParent.prototype = {
|
|||
case "FormAutofill:OpenPreferences": {
|
||||
const win = RecentWindow.getMostRecentBrowserWindow();
|
||||
win.openPreferences("panePrivacy", {origin: "autofillFooter"});
|
||||
break;
|
||||
}
|
||||
case "FormAutofill:GetDecryptedString": {
|
||||
let {cipherText, reauth} = data;
|
||||
let string;
|
||||
try {
|
||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_ABORT) {
|
||||
throw e;
|
||||
}
|
||||
log.warn("User canceled master password entry");
|
||||
}
|
||||
target.sendAsyncMessage("FormAutofill:DecryptedString", string);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -244,7 +264,8 @@ FormAutofillParent.prototype = {
|
|||
|
||||
/**
|
||||
* Get the records from profile store and return results back to content
|
||||
* process.
|
||||
* process. It will decrypt the credit card number and append
|
||||
* "cc-number-decrypted" to each record if MasterPassword isn't set.
|
||||
*
|
||||
* @private
|
||||
* @param {string} data.collectionName
|
||||
|
@ -263,35 +284,43 @@ FormAutofillParent.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let recordsInCollection = collection.getAll();
|
||||
if (!info || !info.fieldName || !recordsInCollection.length) {
|
||||
target.sendAsyncMessage("FormAutofill:Records", recordsInCollection);
|
||||
return;
|
||||
}
|
||||
|
||||
let isCCAndMPEnabled = collectionName == CREDITCARDS_COLLECTION_NAME && MasterPassword.isEnabled;
|
||||
// We don't filter "cc-number" when MasterPassword is set.
|
||||
if (isCCAndMPEnabled && info.fieldName == "cc-number") {
|
||||
recordsInCollection = recordsInCollection.filter(record => !!record["cc-number"]);
|
||||
target.sendAsyncMessage("FormAutofill:Records", recordsInCollection);
|
||||
return;
|
||||
}
|
||||
|
||||
let records = [];
|
||||
if (info && info.fieldName &&
|
||||
!(MasterPassword.isEnabled && info.fieldName == "cc-number")) {
|
||||
if (info.fieldName == "cc-number") {
|
||||
for (let record of collection.getAll()) {
|
||||
let number = await MasterPassword.decrypt(record["cc-number-encrypted"]);
|
||||
if (number.startsWith(searchString)) {
|
||||
records.push(record);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let lcSearchString = searchString.toLowerCase();
|
||||
let result = collection.getAll().filter(record => {
|
||||
// Return true if string is not provided and field exists.
|
||||
// TODO: We'll need to check if the address is for billing or shipping.
|
||||
// (Bug 1358941)
|
||||
let name = record[info.fieldName];
|
||||
let lcSearchString = searchString.toLowerCase();
|
||||
|
||||
if (!searchString) {
|
||||
return !!name;
|
||||
}
|
||||
|
||||
return name && name.toLowerCase().startsWith(lcSearchString);
|
||||
});
|
||||
|
||||
records = result;
|
||||
for (let record of recordsInCollection) {
|
||||
let fieldValue = record[info.fieldName];
|
||||
if (!fieldValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cache the decrypted "cc-number" in each record for content to preview
|
||||
// when MasterPassword isn't set.
|
||||
if (!isCCAndMPEnabled && record["cc-number-encrypted"]) {
|
||||
record["cc-number-decrypted"] = await MasterPassword.decrypt(record["cc-number-encrypted"]);
|
||||
}
|
||||
|
||||
// Filter "cc-number" based on the decrypted one.
|
||||
if (info.fieldName == "cc-number") {
|
||||
fieldValue = record["cc-number-decrypted"];
|
||||
}
|
||||
|
||||
if (!lcSearchString || String(fieldValue).toLowerCase().startsWith(lcSearchString)) {
|
||||
records.push(record);
|
||||
}
|
||||
} else {
|
||||
records = collection.getAll();
|
||||
}
|
||||
|
||||
target.sendAsyncMessage("FormAutofill:Records", records);
|
||||
|
@ -326,9 +355,7 @@ FormAutofillParent.prototype = {
|
|||
this._updateStatus();
|
||||
},
|
||||
|
||||
_onFormSubmit(data, target) {
|
||||
let {address} = data;
|
||||
|
||||
_onAddressSubmit(address, target) {
|
||||
if (address.guid) {
|
||||
// Avoid updating the fields that users don't modify.
|
||||
let originalAddress = this.profileStorage.addresses.get(address.guid);
|
||||
|
@ -389,4 +416,38 @@ FormAutofillParent.prototype = {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
async _onCreditCardSubmit(creditCard, target) {
|
||||
// We'll show the credit card doorhanger if:
|
||||
// - User applys autofill and changed
|
||||
// - User fills form manually
|
||||
if (creditCard.guid &&
|
||||
Object.keys(creditCard.record).every(key => creditCard.untouchedFields.includes(key))) {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = await FormAutofillDoorhanger.show(target, "creditCard");
|
||||
if (state == "cancel") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == "disable") {
|
||||
Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.profileStorage.creditCards.normalizeCCNumberFields(creditCard.record);
|
||||
this.profileStorage.creditCards.add(creditCard.record);
|
||||
},
|
||||
|
||||
_onFormSubmit(data, target) {
|
||||
let {address, creditCard} = data;
|
||||
|
||||
if (address) {
|
||||
this._onAddressSubmit(address, target);
|
||||
}
|
||||
if (creditCard) {
|
||||
this._onCreditCardSubmit(creditCard, target);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path fill="#4E4D4D" d="M9 22.2H6.4c-.6 0-1 .4-1 1s.4 1 1 1H9c.6 0 1-.4 1-1s-.4-1-1-1z"/>
|
||||
<path fill="#4E4D4D" d="M28 7.6v8H4v-4h10v-4H4c-2.2 0-4 1.8-4 4v16c0 2.2 1.8 4 4 4h24c2.2 0 4-1.8 4-4v-16c0-2.2-1.8-4-4-4zm-24 20V19h24v8.6H4z"/>
|
||||
<path fill="#4E4D4D" d="M19.2 22.2h-6.3c-.6 0-1 .4-1 1s.4 1 1 1h6.3c.6 0 1-.4 1-1s-.5-1-1-1zM16.3 7.9c-.4.4-.4 1 0 1.4l4 4c.4.4 1 .4 1.4 0l4-4c.4-.4.4-1 0-1.4s-1-.4-1.4 0L22 10.2v-9c0-.5-.4-1-1-1-.5 0-1 .4-1 1v9l-2.3-2.3c-.4-.4-1-.4-1.4 0z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 765 B |
|
@ -8,14 +8,20 @@ savedAddresses = Saved Addresses…
|
|||
enableCreditCardAutofill = Autofill credit cards
|
||||
savedCreditCards = Saved Credit Cards…
|
||||
saveAddressesMessage = Firefox now saves addresses so you can fill out forms faster.
|
||||
viewAutofillOptionsLink = View Form Autofill Options
|
||||
autofillOptionsLink = Form Autofill Options
|
||||
autofillSecurityOptionsLink = Form Autofill & Security Options
|
||||
changeAutofillOptions = Change Form Autofill Options
|
||||
viewAutofillOptionsLinkOSX = View Form Autofill Preferences
|
||||
autofillOptionsLinkOSX = Form Autofill Preferences
|
||||
autofillSecurityOptionsLinkOSX = Form Autofill & Security Preferences
|
||||
changeAutofillOptionsOSX = Change Form Autofill Preferences
|
||||
addressesSyncCheckbox = Share addresses with synced devices
|
||||
updateAddressMessage = Would you like to update your address with this new information?
|
||||
createAddressLabel = Create New Address
|
||||
updateAddressLabel = Update Address
|
||||
saveCreditCardMessage = Would you like Firefox to save this credit card? (Security code will not be saved)
|
||||
saveCreditCardLabel = Save Credit Card
|
||||
cancelCreditCardLabel = Don’t Save
|
||||
disableCreditCardLabel = Never Save credit Cards
|
||||
openAutofillMessagePanel = Open Form Autofill message panel
|
||||
autocompleteFooterOption = Form Autofill Options
|
||||
autocompleteFooterOptionShort = More Options
|
||||
|
|
|
@ -10,30 +10,31 @@ label > span {
|
|||
input,
|
||||
select {
|
||||
width: calc(50% - 9.5em);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#given-name-container,
|
||||
#additional-name-container,
|
||||
#address-level1-container,
|
||||
#postal-code-container,
|
||||
#country-container {
|
||||
#country-container,
|
||||
#family-name-container,
|
||||
#organization-container,
|
||||
#address-level2-container,
|
||||
#tel-container {
|
||||
flex: 0 1 50%;
|
||||
}
|
||||
|
||||
#family-name-container,
|
||||
#organization-container,
|
||||
#street-address-container,
|
||||
#address-level2-container,
|
||||
#email-container,
|
||||
#tel-container {
|
||||
flex: 0 1 100%;
|
||||
padding-inline-end: 50%;
|
||||
}
|
||||
|
||||
#family-name,
|
||||
#organization,
|
||||
#address-level2,
|
||||
#tel {
|
||||
flex: 0 0 auto;
|
||||
#street-address-container,
|
||||
#email-container {
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
#street-address,
|
||||
|
@ -42,7 +43,7 @@ select {
|
|||
}
|
||||
|
||||
#country-warning-message {
|
||||
flex: 1;
|
||||
flex: 1 0 auto;
|
||||
align-items: center;
|
||||
text-align: start;
|
||||
color: #737373;
|
||||
|
|
|
@ -17,9 +17,9 @@ form {
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
label,
|
||||
p {
|
||||
margin: 0 0 0.5em;
|
||||
form > label,
|
||||
form > p {
|
||||
margin: 0 0 0.5em !important;
|
||||
}
|
||||
|
||||
label > span,
|
||||
|
|
|
@ -9,6 +9,7 @@ support-files =
|
|||
[browser_autocomplete_marked_back_forward.js]
|
||||
[browser_autocomplete_marked_detached_tab.js]
|
||||
[browser_check_installed.js]
|
||||
[browser_creditCard_doorhanger.js]
|
||||
[browser_dropdown_layout.js]
|
||||
[browser_editAddressDialog.js]
|
||||
[browser_editCreditCardDialog.js]
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_submit_creditCard_cancel_saving() {
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
name.setUserInput("User 1");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("1111222233334444");
|
||||
|
||||
// Wait 1000ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON);
|
||||
}
|
||||
);
|
||||
|
||||
await sleep(1000);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 0, "No credit card saved");
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_saved() {
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
name.setUserInput("User 1");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("1111222233334444");
|
||||
|
||||
// Wait 1000ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await TestUtils.topicObserved("formautofill-storage-changed");
|
||||
}
|
||||
);
|
||||
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 address in storage");
|
||||
is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
|
||||
});
|
|
@ -32,7 +32,7 @@ add_task(async function test_first_time_save() {
|
|||
let cb = getDoorhangerCheckbox();
|
||||
ok(cb.hidden, "Sync checkbox should be hidden");
|
||||
// Open the panel via main button
|
||||
await clickDoorhangerButton(MAIN_BUTTON_INDEX);
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
let tab = await tabPromise;
|
||||
ok(tab, "Privacy panel opened");
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
|
@ -111,7 +111,7 @@ add_task(async function test_first_time_save_with_sync_account() {
|
|||
is(SpecialPowers.getBoolPref(SYNC_ADDRESSES_PREF), true,
|
||||
"addresses sync should be enabled after checked");
|
||||
// Open the panel via main button
|
||||
await clickDoorhangerButton(MAIN_BUTTON_INDEX);
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
let tab = await tabPromise;
|
||||
ok(tab, "Privacy panel opened");
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
|
|
|
@ -26,7 +26,7 @@ add_task(async function test_update_address() {
|
|||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON_INDEX);
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -59,7 +59,7 @@ add_task(async function test_create_new_address() {
|
|||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON_INDEX);
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -92,7 +92,7 @@ add_task(async function test_create_new_address_merge() {
|
|||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON_INDEX);
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -128,7 +128,7 @@ add_task(async function test_submit_untouched_fields() {
|
|||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON_INDEX);
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* exported MANAGE_ADDRESSES_DIALOG_URL, MANAGE_CREDIT_CARDS_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, EDIT_CREDIT_CARD_DIALOG_URL,
|
||||
BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5,
|
||||
TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL,
|
||||
TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, CREDITCARD_FORM_URL,
|
||||
FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF, SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF,
|
||||
sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
|
||||
getAddresses, saveAddress, removeAddresses, saveCreditCard,
|
||||
|
@ -14,6 +14,8 @@ const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml
|
|||
const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml";
|
||||
const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
|
||||
const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
|
||||
const CREDITCARD_FORM_URL =
|
||||
"http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
|
||||
const FTU_PREF = "extensions.formautofill.firstTimeUse";
|
||||
const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
|
||||
const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
|
||||
|
@ -60,7 +62,6 @@ const TEST_ADDRESS_5 = {
|
|||
const TEST_CREDIT_CARD_1 = {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1234567812345678",
|
||||
// "cc-number-encrypted": "",
|
||||
"cc-exp-month": 4,
|
||||
"cc-exp-year": 2017,
|
||||
};
|
||||
|
@ -78,8 +79,9 @@ const TEST_CREDIT_CARD_3 = {
|
|||
"cc-exp-year": 2000,
|
||||
};
|
||||
|
||||
const MAIN_BUTTON_INDEX = 0;
|
||||
const SECONDARY_BUTTON_INDEX = 1;
|
||||
const MAIN_BUTTON = "button";
|
||||
const SECONDARY_BUTTON = "secondaryButton";
|
||||
const MENU_BUTTON = "menubutton";
|
||||
|
||||
function getDisplayedPopupItems(browser, selector = ".autocomplete-richlistitem") {
|
||||
const {autoCompletePopup: {richlistbox: itemsBox}} = browser;
|
||||
|
@ -188,25 +190,28 @@ function removeCreditCards(guids) {
|
|||
/**
|
||||
* Clicks the popup notification button and wait for popup hidden.
|
||||
*
|
||||
* @param {number} buttonIndex Number indicating which button to click.
|
||||
* See the constants in this file.
|
||||
* @param {string} button The button type in popup notification.
|
||||
* @param {number} index The action's index in menu list.
|
||||
*/
|
||||
async function clickDoorhangerButton(buttonIndex) {
|
||||
async function clickDoorhangerButton(button, index) {
|
||||
let popuphidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
|
||||
let notifications = PopupNotifications.panel.childNodes;
|
||||
ok(notifications.length > 0, "at least one notification displayed");
|
||||
ok(true, notifications.length + " notification(s)");
|
||||
let notification = notifications[0];
|
||||
|
||||
if (buttonIndex == MAIN_BUTTON_INDEX) {
|
||||
ok(true, "Triggering main action");
|
||||
EventUtils.synthesizeMouseAtCenter(notification.button, {});
|
||||
} else if (buttonIndex == SECONDARY_BUTTON_INDEX) {
|
||||
ok(true, "Triggering secondary action");
|
||||
EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
|
||||
} else if (notification.childNodes[buttonIndex - 1]) {
|
||||
ok(true, "Triggering secondary action with index " + buttonIndex);
|
||||
EventUtils.synthesizeMouseAtCenter(notification.childNodes[buttonIndex - 1], {});
|
||||
if (button == MAIN_BUTTON || button == SECONDARY_BUTTON) {
|
||||
EventUtils.synthesizeMouseAtCenter(notification[button], {});
|
||||
} else if (button == MENU_BUTTON) {
|
||||
// Click the dropmarker arrow and wait for the menu to show up.
|
||||
let dropdownPromise =
|
||||
BrowserTestUtils.waitForEvent(notification.menupopup, "popupshown");
|
||||
await EventUtils.synthesizeMouseAtCenter(notification.menubutton, {});
|
||||
ok(true, "notification menupopup displayed");
|
||||
await dropdownPromise;
|
||||
|
||||
let actionMenuItem = notification.querySelectorAll("menuitem")[index];
|
||||
await EventUtils.synthesizeMouseAtCenter(actionMenuItem, {});
|
||||
}
|
||||
await popuphidden;
|
||||
}
|
||||
|
|
|
@ -503,6 +503,19 @@ function do_test(testcases, testFn) {
|
|||
let formLike = FormLikeFactory.createFromForm(form);
|
||||
let handler = new FormAutofillHandler(formLike);
|
||||
let promises = [];
|
||||
// Replace the interal decrypt method with MasterPassword API
|
||||
handler._decrypt = async (cipherText, reauth) => {
|
||||
let string;
|
||||
try {
|
||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_ABORT) {
|
||||
throw e;
|
||||
}
|
||||
do_print("User canceled master password entry");
|
||||
}
|
||||
return string;
|
||||
};
|
||||
|
||||
handler.collectFormFields();
|
||||
let handlerInfo = handler[testcase.expectedFillingForm];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Test for make sure getRecords can retrieve right collection from storag.
|
||||
* Test for make sure getRecords can retrieve right collection from storage.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
@ -166,11 +166,14 @@ add_task(async function test_getRecords_creditCards() {
|
|||
await formAutofillParent.init();
|
||||
await formAutofillParent.profileStorage.initialize();
|
||||
let collection = profileStorage.creditCards;
|
||||
let decryptedCCNumber = [TEST_CREDIT_CARD_1["cc-number"], TEST_CREDIT_CARD_2["cc-number"]];
|
||||
await collection.normalizeCCNumberFields(TEST_CREDIT_CARD_1);
|
||||
await collection.normalizeCCNumberFields(TEST_CREDIT_CARD_2);
|
||||
let mockCreditCards = [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2];
|
||||
sinon.stub(collection, "getAll");
|
||||
collection.getAll.returns(mockCreditCards);
|
||||
sinon.stub(collection, "getAll", () => [Object.assign({}, TEST_CREDIT_CARD_1), Object.assign({}, TEST_CREDIT_CARD_2)]);
|
||||
let CreditCardsWithDecryptedNumber = [
|
||||
Object.assign({}, TEST_CREDIT_CARD_1, {"cc-number-decrypted": decryptedCCNumber[0]}),
|
||||
Object.assign({}, TEST_CREDIT_CARD_2, {"cc-number-decrypted": decryptedCCNumber[1]}),
|
||||
];
|
||||
|
||||
let testCases = [
|
||||
{
|
||||
|
@ -180,7 +183,7 @@ add_task(async function test_getRecords_creditCards() {
|
|||
info: {fieldName: "cc-name"},
|
||||
searchString: "John Doe",
|
||||
},
|
||||
expectedResult: [TEST_CREDIT_CARD_1],
|
||||
expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "If the search string could match multiple creditCards (without masterpassword)",
|
||||
|
@ -189,7 +192,7 @@ add_task(async function test_getRecords_creditCards() {
|
|||
info: {fieldName: "cc-name"},
|
||||
searchString: "John",
|
||||
},
|
||||
expectedResult: [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2],
|
||||
expectedResult: CreditCardsWithDecryptedNumber,
|
||||
},
|
||||
{
|
||||
description: "If the search string could not match any creditCard (without masterpassword)",
|
||||
|
@ -207,7 +210,7 @@ add_task(async function test_getRecords_creditCards() {
|
|||
info: {fieldName: "cc-number"},
|
||||
searchString: "123",
|
||||
},
|
||||
expectedResult: [TEST_CREDIT_CARD_1],
|
||||
expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "If the search string could match multiple creditCards (without masterpassword)",
|
||||
|
@ -216,7 +219,7 @@ add_task(async function test_getRecords_creditCards() {
|
|||
info: {fieldName: "cc-number"},
|
||||
searchString: "1",
|
||||
},
|
||||
expectedResult: [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2],
|
||||
expectedResult: CreditCardsWithDecryptedNumber,
|
||||
},
|
||||
{
|
||||
description: "If the search string could match 1 creditCard (with masterpassword)",
|
||||
|
|
|
@ -33,6 +33,18 @@ const ADDRESS_COMPUTE_TESTCASES = [
|
|||
"name": "Timothy John Berners-Lee",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Has split CJK names",
|
||||
address: {
|
||||
"given-name": "德明",
|
||||
"family-name": "孫",
|
||||
},
|
||||
expectedResult: {
|
||||
"given-name": "德明",
|
||||
"family-name": "孫",
|
||||
"name": "孫德明",
|
||||
},
|
||||
},
|
||||
|
||||
// Address
|
||||
{
|
||||
|
|
|
@ -238,7 +238,7 @@ var onboardingTourset = {
|
|||
<img src="resource://onboarding/img/figure_library.svg" role="presentation"/>
|
||||
</section>
|
||||
<aside class="onboarding-tour-button-container">
|
||||
<button id="onboarding-tour-library-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-library.button"></button>
|
||||
<button id="onboarding-tour-library-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-library.button2"></button>
|
||||
</aside>
|
||||
`;
|
||||
return div;
|
||||
|
|
|
@ -96,7 +96,7 @@ onboarding.tour-library=Library
|
|||
onboarding.tour-library.title=Keep it together.
|
||||
# LOCALIZATION NOTE (onboarding.tour-library.description): This string will be used in the library tour description. %1$S is brandShortName
|
||||
onboarding.tour-library.description=Check out the new %1$S library in the redesigned toolbar. The library puts the things you’ve seen and saved to %1$S - your browsing history, bookmarks, Pocket lists, and synced tabs - in one convenient place.
|
||||
onboarding.tour-library.button=Show Library in Menu
|
||||
onboarding.tour-library.button2=Show Library Menu
|
||||
onboarding.notification.onboarding-tour-library.title=Keep it together.
|
||||
# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-library.message): This string will be used in the notification message for the library tour. %S is brandShortName
|
||||
onboarding.notification.onboarding-tour-library.message=The new %S library puts the great things you’ve discovered on the web in one convenient place.
|
||||
|
|
|
@ -31,7 +31,9 @@ AboutURL.prototype = {
|
|||
},
|
||||
|
||||
getURIFlags: function (aURI) {
|
||||
return nsIAboutModule.ALLOW_SCRIPT || nsIAboutModule.ENABLE_INDEXED_DB;
|
||||
return nsIAboutModule.ALLOW_SCRIPT |
|
||||
nsIAboutModule.ENABLE_INDEXED_DB |
|
||||
nsIAboutModule.HIDE_FROM_ABOUTABOUT;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -17,17 +17,11 @@ collapsePanes=Collapse panes
|
|||
# LOCALIZATION NOTE (copySourceUrl): This is the text that appears in the
|
||||
# context menu to copy the source URL of file open.
|
||||
copySourceUrl=Copy Source Url
|
||||
|
||||
# LOCALIZATION NOTE (copySourceUrl.accesskey): Access key to copy the source URL of a file from
|
||||
# the context menu.
|
||||
copySourceUrl.accesskey=u
|
||||
|
||||
# LOCALIZATION NOTE (copyStackTrace): This is the text that appears in the
|
||||
# context menu to copy the stack trace methods, file names and row number.
|
||||
copyStackTrace=Copy Stack Trace
|
||||
|
||||
# LOCALIZATION NOTE (copyStackTrace.accesskey): Access key to copy the stack trace data from
|
||||
# the context menu.
|
||||
copyStackTrace.accesskey=c
|
||||
|
||||
# LOCALIZATION NOTE (expandPanes): This is the tooltip for the button
|
||||
|
@ -212,50 +206,23 @@ searchPanelVariable=Filter variables (%S)
|
|||
# are displayed in the breakpoints menu item popup.
|
||||
breakpointMenuItem.setConditional=Configure conditional breakpoint
|
||||
breakpointMenuItem.enableSelf=Enable breakpoint
|
||||
breakpointMenuItem.enableSelf.accesskey=E
|
||||
breakpointMenuItem.disableSelf=Disable breakpoint
|
||||
breakpointMenuItem.disableSelf.accesskey=D
|
||||
breakpointMenuItem.deleteSelf=Remove breakpoint
|
||||
breakpointMenuItem.deleteSelf.accesskey=R
|
||||
breakpointMenuItem.enableOthers=Enable others
|
||||
breakpointMenuItem.enableOthers.accesskey=o
|
||||
breakpointMenuItem.disableOthers=Disable others
|
||||
breakpointMenuItem.deleteOthers=Remove others
|
||||
breakpointMenuItem.enableAll=Enable all breakpoints
|
||||
breakpointMenuItem.disableAll=Disable all breakpoints
|
||||
breakpointMenuItem.deleteAll=Remove all breakpoints
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.deleteSelf.accesskey): Access key to remove the
|
||||
# currently selected breakpoint from the context menu
|
||||
breakpointMenuItem.deleteSelf.accesskey=r
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.enableSelf.accesskey): Access key to enable the
|
||||
# currently selected breakpoint from the context menu
|
||||
breakpointMenuItem.enableSelf.accesskey=e
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.disableSelf.accesskey): Access key to disable the
|
||||
# currently selected breakpoint from the context menu
|
||||
breakpointMenuItem.disableSelf.accesskey=d
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.deleteAll.accesskey): Access key to remove all
|
||||
# the breakpoints from the context menu
|
||||
breakpointMenuItem.deleteAll.accesskey=a
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.enableAll.accesskey): Access key to enable all
|
||||
# the breakpoints from the context menu
|
||||
breakpointMenuItem.enableAll.accesskey=b
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.disableAll.accesskey): Access key to disable all
|
||||
# the breakpoints from the context menu
|
||||
breakpointMenuItem.disableAll.accesskey=c
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.deleteOthers.accesskey): Access key to remove
|
||||
# other breakpoints from the context menu
|
||||
breakpointMenuItem.deleteOthers.accesskey=p
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.enableOthers.accesskey): Access key to enable
|
||||
# other breakpoints from the context menu
|
||||
breakpointMenuItem.enableOthers.accesskey=q
|
||||
|
||||
# LOCALIZATION NOTE (breakpointMenuItem.disableOthers.accesskey): Access key to disable
|
||||
# other breakpoints from the context menu
|
||||
breakpointMenuItem.disableOthers.accesskey=s
|
||||
breakpointMenuItem.deleteOthers=Remove others
|
||||
breakpointMenuItem.deleteOthers.accesskey=h
|
||||
breakpointMenuItem.enableAll=Enable all breakpoints
|
||||
breakpointMenuItem.enableAll.accesskey=b
|
||||
breakpointMenuItem.disableAll=Disable all breakpoints
|
||||
breakpointMenuItem.disableAll.accesskey=k
|
||||
breakpointMenuItem.deleteAll=Remove all breakpoints
|
||||
breakpointMenuItem.deleteAll.accesskey=a
|
||||
|
||||
# LOCALIZATION NOTE (breakpoints.header): Breakpoints right sidebar pane header.
|
||||
breakpoints.header=Breakpoints
|
||||
|
@ -353,17 +320,11 @@ editor.jumpToMappedLocation1=Jump to %S location
|
|||
# LOCALIZATION NOTE (framework.disableGrouping): This is the text that appears in the
|
||||
# context menu to disable framework grouping.
|
||||
framework.disableGrouping=Disable Framework Grouping
|
||||
|
||||
# LOCALIZATION NOTE (framework.disableGrouping.accesskey): Access key to toggle
|
||||
# framework grouping from the context menu.
|
||||
framework.disableGrouping.accesskey=u
|
||||
|
||||
# LOCALIZATION NOTE (framework.enableGrouping): This is the text that appears in the
|
||||
# context menu to enable framework grouping.
|
||||
framework.enableGrouping=Enable Framework Grouping
|
||||
|
||||
# LOCALIZATION NOTE (framework.enableGrouping.accesskey): Access key to toggle
|
||||
# framework grouping from the context menu.
|
||||
framework.enableGrouping.accesskey=u
|
||||
|
||||
# LOCALIZATION NOTE (generated): Source Map term for a server source location
|
||||
|
@ -379,75 +340,48 @@ expressions.placeholder=Add Watch Expression
|
|||
# LOCALIZATION NOTE (sourceTabs.closeTab): Editor source tab context menu item
|
||||
# for closing the selected tab below the mouse.
|
||||
sourceTabs.closeTab=Close tab
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeTab.accesskey): Access key to close the currently select
|
||||
# source tab from the editor context menu item.
|
||||
sourceTabs.closeTab.accesskey=c
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeOtherTabs): Editor source tab context menu item
|
||||
# for closing the other tabs.
|
||||
sourceTabs.closeOtherTabs=Close others
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeOtherTabs.accesskey): Access key to close other source tabs
|
||||
# from the editor context menu.
|
||||
sourceTabs.closeOtherTabs.accesskey=o
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeTabsToEnd): Editor source tab context menu item
|
||||
# for closing the tabs to the end (the right for LTR languages) of the selected tab.
|
||||
sourceTabs.closeTabsToEnd=Close tabs to the right
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeTabsToEnd.accesskey): Access key to close source tabs
|
||||
# after the selected tab from the editor context menu.
|
||||
sourceTabs.closeTabsToEnd.accesskey=e
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeAllTabs): Editor source tab context menu item
|
||||
# for closing all tabs.
|
||||
sourceTabs.closeAllTabs=Close all tabs
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeAllTabs.accesskey): Access key to close all tabs from the
|
||||
# editor context menu.
|
||||
sourceTabs.closeAllTabs.accesskey=a
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.revealInTree): Editor source tab context menu item
|
||||
# for revealing source in tree.
|
||||
sourceTabs.revealInTree=Reveal in Tree
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.revealInTree.accesskey): Access key to reveal a source in the
|
||||
# tree from the context menu.
|
||||
sourceTabs.revealInTree.accesskey=r
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.copyLink): Editor source tab context menu item
|
||||
# for copying a link address.
|
||||
sourceTabs.copyLink=Copy Link Address
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.copyLink.accesskey): Access key to copy a link addresss from the
|
||||
# editor context menu.
|
||||
sourceTabs.copyLink.accesskey=l
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.prettyPrint): Editor source tab context menu item
|
||||
# for pretty printing the source.
|
||||
sourceTabs.prettyPrint=Pretty Print Source
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.prettyPrint.accesskey): Access key to pretty print a source from
|
||||
# the editor context menu.
|
||||
sourceTabs.prettyPrint.accesskey=p
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.blackbox): Tooltip text associated
|
||||
# with the blackbox button
|
||||
sourceFooter.blackbox=Blackbox Source
|
||||
sourceFooter.blackbox.accesskey=B
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.unblackbox): Tooltip text associated
|
||||
# with the blackbox button
|
||||
sourceFooter.unblackbox=Unblackbox Source
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.unblackbox.accesskey): Access key to blackbox
|
||||
# an associated source
|
||||
sourceFooter.unblackbox.accesskey=b
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.blackbox.accesskey): Access key to blackbox
|
||||
# an associated source
|
||||
sourceFooter.blackbox.accesskey=b
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.blackboxed): Text associated
|
||||
# with a blackboxed source
|
||||
sourceFooter.blackboxed=Blackboxed Source
|
||||
|
|
|
@ -81,7 +81,6 @@ support-files =
|
|||
[css-animations/test_event-dispatch.html]
|
||||
[css-animations/test_event-order.html]
|
||||
[css-animations/test_keyframeeffect-getkeyframes.html]
|
||||
skip-if = stylo
|
||||
[css-animations/test_pseudoElement-get-animations.html]
|
||||
[css-animations/test_setting-effect.html]
|
||||
[css-transitions/test_animation-cancel.html]
|
||||
|
|
|
@ -104,8 +104,15 @@ promise_test(function(t) {
|
|||
div.animate({ transform: [ 'translate(0px)', 'translate(100px)' ] },
|
||||
{ duration: 400 * MS_PER_SEC,
|
||||
delay: -200 * MS_PER_SEC });
|
||||
return waitForPaints();
|
||||
}).then(() => {
|
||||
|
||||
// TODO: Current waitForPaint() will not wait for MozAfterPaint in this
|
||||
// case(Bug 1341294), so this waiting code is workaround for it.
|
||||
// This waitForFrame() uses Promise, but bug 1193394 will be using same
|
||||
// handling of microtask, so if landed bug 1193394 this test might be
|
||||
// failure since this promise will resolve in same tick of Element.animate.
|
||||
return waitForFrame();
|
||||
}).then(() => waitForPaints())
|
||||
.then(() => {
|
||||
const transformStr =
|
||||
SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
|
||||
const translateX = getTranslateXFromTransform(transformStr);
|
||||
|
@ -146,8 +153,12 @@ promise_test(function(t) {
|
|||
200 * MS_PER_SEC);
|
||||
animation.currentTime = 100 * MS_PER_SEC;
|
||||
animation.playbackRate = 0.1;
|
||||
return waitForPaints();
|
||||
}).then(() => {
|
||||
|
||||
// As the above test case, we should fix bug 1341294 before bug 1193394
|
||||
// lands.
|
||||
return waitForFrame();
|
||||
}).then(() => waitForPaints())
|
||||
.then(() => {
|
||||
const transformStr =
|
||||
SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
|
||||
const translateX = getTranslateXFromTransform(transformStr);
|
||||
|
|
|
@ -786,6 +786,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Selection)
|
|||
// we don't want to notify the listeners during JS GC (they could be
|
||||
// in JS!).
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionListeners)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedRange)
|
||||
tmp->RemoveAllRanges();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameSelection)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
|
@ -798,6 +799,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Selection)
|
|||
}
|
||||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorFocusRange)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedRange)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameSelection)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListeners)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
@ -2290,6 +2292,32 @@ Selection::RemoveAllRanges(ErrorResult& aRv)
|
|||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
Selection::RemoveAllRangesTemporarily()
|
||||
{
|
||||
if (!mCachedRange) {
|
||||
// Look for a range which isn't referred by other than this instance.
|
||||
// If there is, it'll be released by calling Clear(). So, we can reuse it
|
||||
// when we need to create a range.
|
||||
for (auto& rangeData : mRanges) {
|
||||
auto& range = rangeData.mRange;
|
||||
if (range->GetRefCount() == 1 ||
|
||||
(range->GetRefCount() == 2 && range == mAnchorFocusRange)) {
|
||||
mCachedRange = range;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then, remove all ranges.
|
||||
ErrorResult result;
|
||||
RemoveAllRanges(result);
|
||||
if (result.Failed()) {
|
||||
mCachedRange = nullptr;
|
||||
}
|
||||
return result.StealNSResult();
|
||||
}
|
||||
|
||||
/** AddRange adds the specified range to the selection
|
||||
* @param aRange is the range to be added
|
||||
*/
|
||||
|
@ -2332,6 +2360,10 @@ Selection::AddRangeInternal(nsRange& aRange, nsIDocument* aDocument,
|
|||
return;
|
||||
}
|
||||
|
||||
// If a range is being added, we don't need cached range because Collapse()
|
||||
// won't use it.
|
||||
mCachedRange = nullptr;
|
||||
|
||||
// AddTableCellRange might flush frame.
|
||||
RefPtr<Selection> kungFuDeathGrip(this);
|
||||
|
||||
|
@ -2588,7 +2620,9 @@ Selection::Collapse(nsINode& aContainer, uint32_t aOffset, ErrorResult& aRv)
|
|||
// If the old range isn't referred by anybody other than this method,
|
||||
// we should reuse it for reducing the recreation cost.
|
||||
if (oldRange && oldRange->GetRefCount() == 1) {
|
||||
range = oldRange;
|
||||
range = Move(oldRange);
|
||||
} else if (mCachedRange) {
|
||||
range = Move(mCachedRange);
|
||||
} else {
|
||||
range = new nsRange(container);
|
||||
}
|
||||
|
@ -4061,18 +4095,28 @@ Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
|
|||
endOffset = aAnchorOffset;
|
||||
}
|
||||
|
||||
RefPtr<nsRange> newRange;
|
||||
nsresult rv = nsRange::CreateRange(start, startOffset, end, endOffset,
|
||||
getter_AddRefs(newRange));
|
||||
// CreateRange returns IndexSizeError if any offset is out of bounds.
|
||||
// If there is cached range, we should reuse it for saving the allocation
|
||||
// const (and some other cost in nsRange::DoSetRange().
|
||||
RefPtr<nsRange> newRange = Move(mCachedRange);
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
if (newRange) {
|
||||
rv = newRange->SetStartAndEnd(start, startOffset, end, endOffset);
|
||||
} else {
|
||||
rv = nsRange::CreateRange(start, startOffset, end, endOffset,
|
||||
getter_AddRefs(newRange));
|
||||
}
|
||||
|
||||
// nsRange::SetStartAndEnd() and nsRange::CreateRange() returns
|
||||
// IndexSizeError if any offset is out of bounds.
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
rv = RemoveAllRanges();
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
// Use non-virtual method instead of nsISelection::RemoveAllRanges().
|
||||
RemoveAllRanges(aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -215,6 +215,14 @@ public:
|
|||
void RemoveRange(nsRange& aRange, mozilla::ErrorResult& aRv);
|
||||
void RemoveAllRanges(mozilla::ErrorResult& aRv);
|
||||
|
||||
/**
|
||||
* RemoveAllRangesTemporarily() is useful if the caller will add one or more
|
||||
* ranges later. This tries to cache a removing range if it's possible.
|
||||
* If a range is not referred by anything else this selection, the range
|
||||
* can be reused later. Otherwise, this works as same as RemoveAllRanges().
|
||||
*/
|
||||
nsresult RemoveAllRangesTemporarily();
|
||||
|
||||
void Stringify(nsAString& aResult);
|
||||
|
||||
bool ContainsNode(nsINode& aNode, bool aPartlyContained, mozilla::ErrorResult& aRv);
|
||||
|
@ -473,6 +481,12 @@ private:
|
|||
AutoTArray<RangeData, 1> mRanges;
|
||||
|
||||
RefPtr<nsRange> mAnchorFocusRange;
|
||||
// mCachedRange is set by RemoveAllRangesTemporarily() and used by
|
||||
// Collapse() and SetBaseAndExtent(). If there is a range which will be
|
||||
// released by Clear(), RemoveAllRangesTemporarily() stores it with this.
|
||||
// If Collapse() is called without existing ranges, it'll reuse this range
|
||||
// for saving the creation cost.
|
||||
RefPtr<nsRange> mCachedRange;
|
||||
RefPtr<nsFrameSelection> mFrameSelection;
|
||||
RefPtr<nsAutoScrollTimer> mAutoScrollTimer;
|
||||
FallibleTArray<nsCOMPtr<nsISelectionListener>> mSelectionListeners;
|
||||
|
@ -552,30 +566,16 @@ public:
|
|||
} // namespace dom
|
||||
|
||||
inline bool
|
||||
IsValidSelectionType(RawSelectionType aRawSelectionType)
|
||||
IsValidRawSelectionType(RawSelectionType aRawSelectionType)
|
||||
{
|
||||
switch (static_cast<SelectionType>(aRawSelectionType)) {
|
||||
case SelectionType::eNone:
|
||||
case SelectionType::eNormal:
|
||||
case SelectionType::eSpellCheck:
|
||||
case SelectionType::eIMERawClause:
|
||||
case SelectionType::eIMESelectedRawClause:
|
||||
case SelectionType::eIMEConvertedClause:
|
||||
case SelectionType::eIMESelectedClause:
|
||||
case SelectionType::eAccessibility:
|
||||
case SelectionType::eFind:
|
||||
case SelectionType::eURLSecondary:
|
||||
case SelectionType::eURLStrikeout:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return aRawSelectionType >= nsISelectionController::SELECTION_NONE &&
|
||||
aRawSelectionType <= nsISelectionController::SELECTION_URLSTRIKEOUT;
|
||||
}
|
||||
|
||||
inline SelectionType
|
||||
ToSelectionType(RawSelectionType aRawSelectionType)
|
||||
{
|
||||
if (!IsValidSelectionType(aRawSelectionType)) {
|
||||
if (!IsValidRawSelectionType(aRawSelectionType)) {
|
||||
return SelectionType::eInvalid;
|
||||
}
|
||||
return static_cast<SelectionType>(aRawSelectionType);
|
||||
|
@ -584,18 +584,22 @@ ToSelectionType(RawSelectionType aRawSelectionType)
|
|||
inline RawSelectionType
|
||||
ToRawSelectionType(SelectionType aSelectionType)
|
||||
{
|
||||
MOZ_ASSERT(aSelectionType != SelectionType::eInvalid);
|
||||
return static_cast<RawSelectionType>(aSelectionType);
|
||||
}
|
||||
|
||||
inline RawSelectionType ToRawSelectionType(TextRangeType aTextRangeType)
|
||||
inline RawSelectionType
|
||||
ToRawSelectionType(TextRangeType aTextRangeType)
|
||||
{
|
||||
return ToRawSelectionType(ToSelectionType(aTextRangeType));
|
||||
}
|
||||
|
||||
inline bool operator &(SelectionType aSelectionType,
|
||||
RawSelectionType aRawSelectionTypes)
|
||||
inline SelectionTypeMask
|
||||
ToSelectionTypeMask(SelectionType aSelectionType)
|
||||
{
|
||||
return (ToRawSelectionType(aSelectionType) & aRawSelectionTypes) != 0;
|
||||
MOZ_ASSERT(aSelectionType != SelectionType::eInvalid);
|
||||
return aSelectionType == SelectionType::eNone ? 0 :
|
||||
(1 << (static_cast<uint8_t>(aSelectionType) - 1));
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -5412,52 +5412,6 @@ nsContentUtils::IsInSameAnonymousTree(const nsINode* aNode,
|
|||
return nodeAsContent->GetBindingParent() == aContent->GetBindingParent();
|
||||
}
|
||||
|
||||
class AnonymousContentDestroyer : public Runnable {
|
||||
public:
|
||||
explicit AnonymousContentDestroyer(nsCOMPtr<nsIContent>* aContent)
|
||||
: mozilla::Runnable("AnonymousContentDestroyer")
|
||||
{
|
||||
mContent.swap(*aContent);
|
||||
mParent = mContent->GetParent();
|
||||
mDoc = mContent->OwnerDoc();
|
||||
}
|
||||
explicit AnonymousContentDestroyer(nsCOMPtr<Element>* aElement)
|
||||
: mozilla::Runnable("AnonymousContentDestroyer")
|
||||
{
|
||||
mContent = aElement->forget();
|
||||
mParent = mContent->GetParent();
|
||||
mDoc = mContent->OwnerDoc();
|
||||
}
|
||||
NS_IMETHOD Run() override {
|
||||
mContent->UnbindFromTree();
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
nsCOMPtr<nsIContent> mContent;
|
||||
// Hold strong refs to the parent content and document so that they
|
||||
// don't die unexpectedly
|
||||
nsCOMPtr<nsIDocument> mDoc;
|
||||
nsCOMPtr<nsIContent> mParent;
|
||||
};
|
||||
|
||||
/* static */
|
||||
void
|
||||
nsContentUtils::DestroyAnonymousContent(nsCOMPtr<nsIContent>* aContent)
|
||||
{
|
||||
if (*aContent) {
|
||||
AddScriptRunner(new AnonymousContentDestroyer(aContent));
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
nsContentUtils::DestroyAnonymousContent(nsCOMPtr<Element>* aElement)
|
||||
{
|
||||
if (*aElement) {
|
||||
AddScriptRunner(new AnonymousContentDestroyer(aElement));
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
nsContentUtils::NotifyInstalledMenuKeyboardListener(bool aInstalling)
|
||||
|
|
|
@ -1672,12 +1672,6 @@ public:
|
|||
*/
|
||||
static void DestroyMatchString(void* aData);
|
||||
|
||||
/**
|
||||
* Unbinds the content from the tree and nulls it out if it's not null.
|
||||
*/
|
||||
static void DestroyAnonymousContent(nsCOMPtr<nsIContent>* aContent);
|
||||
static void DestroyAnonymousContent(nsCOMPtr<Element>* aElement);
|
||||
|
||||
/*
|
||||
* Notify when the first XUL menu is opened and when the all XUL menus are
|
||||
* closed. At opening, aInstalling should be TRUE, otherwise, it should be
|
||||
|
|
|
@ -26,18 +26,19 @@ interface nsISelectionDisplay;
|
|||
interface nsISelectionController : nsISelectionDisplay
|
||||
{
|
||||
// RawSelectionType values:
|
||||
const short SELECTION_NONE=0;
|
||||
const short SELECTION_NORMAL=1;
|
||||
const short SELECTION_SPELLCHECK=2;
|
||||
const short SELECTION_IME_RAWINPUT=4;
|
||||
const short SELECTION_IME_SELECTEDRAWTEXT=8;
|
||||
const short SELECTION_IME_CONVERTEDTEXT=16;
|
||||
const short SELECTION_IME_SELECTEDCONVERTEDTEXT=32;
|
||||
const short SELECTION_ACCESSIBILITY=64; // For accessibility API usage
|
||||
const short SELECTION_FIND=128;
|
||||
const short SELECTION_URLSECONDARY=256;
|
||||
const short SELECTION_URLSTRIKEOUT=512;
|
||||
const short NUM_SELECTIONTYPES=11;
|
||||
const short SELECTION_NONE = 0;
|
||||
const short SELECTION_NORMAL = 1;
|
||||
const short SELECTION_SPELLCHECK = 2;
|
||||
const short SELECTION_IME_RAWINPUT = 3;
|
||||
const short SELECTION_IME_SELECTEDRAWTEXT = 4;
|
||||
const short SELECTION_IME_CONVERTEDTEXT = 5;
|
||||
const short SELECTION_IME_SELECTEDCONVERTEDTEXT = 6;
|
||||
// For accessibility API usage
|
||||
const short SELECTION_ACCESSIBILITY = 7;
|
||||
const short SELECTION_FIND = 8;
|
||||
const short SELECTION_URLSECONDARY = 9;
|
||||
const short SELECTION_URLSTRIKEOUT = 10;
|
||||
const short NUM_SELECTIONTYPES = 11;
|
||||
|
||||
// SelectionRegion values:
|
||||
const short SELECTION_ANCHOR_REGION = 0;
|
||||
|
@ -293,7 +294,15 @@ interface nsISelectionController : nsISelectionDisplay
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
// RawSelectionType should be used to store nsISelectionController::SELECTION_*.
|
||||
typedef short RawSelectionType;
|
||||
|
||||
// SelectionTypeMask should be used to store bit-mask of selection types.
|
||||
// The value can be retrieved with ToSelectionTypeMask() and checking if
|
||||
// a selection type is in a mask with |SelectionType & SelectionTypeMask|.
|
||||
typedef uint16_t SelectionTypeMask;
|
||||
|
||||
// SelectionType should be used in internal handling because of type safe.
|
||||
enum class SelectionType : RawSelectionType
|
||||
{
|
||||
eInvalid = -1,
|
||||
|
@ -320,16 +329,29 @@ enum : size_t
|
|||
{
|
||||
// kSelectionTypeCount is number of SelectionType.
|
||||
kSelectionTypeCount = nsISelectionController::NUM_SELECTIONTYPES,
|
||||
// kPresentSelectionTypeCount is number of SelectionType except "none".
|
||||
kPresentSelectionTypeCount = kSelectionTypeCount - 1
|
||||
};
|
||||
|
||||
// kPresentSelectionTypes is selection types which may be displayed.
|
||||
// I.e., selection types except eNone.
|
||||
static const SelectionType kPresentSelectionTypes[] = {
|
||||
SelectionType::eNormal,
|
||||
SelectionType::eSpellCheck,
|
||||
SelectionType::eIMERawClause,
|
||||
SelectionType::eIMESelectedRawClause,
|
||||
SelectionType::eIMEConvertedClause,
|
||||
SelectionType::eIMESelectedClause,
|
||||
SelectionType::eAccessibility,
|
||||
SelectionType::eFind,
|
||||
SelectionType::eURLSecondary,
|
||||
SelectionType::eURLStrikeout,
|
||||
};
|
||||
|
||||
// Please include mozilla/dom/Selection.h for the following APIs.
|
||||
const char* ToChar(SelectionType aSelectionType);
|
||||
inline bool IsValidRawSelectionType(RawSelectionType aRawSelectionType);
|
||||
inline SelectionType ToSelectionType(RawSelectionType aRawSelectionType);
|
||||
inline RawSelectionType ToRawSelectionType(SelectionType aSelectionType);
|
||||
inline bool operator &(SelectionType aSelectionType,
|
||||
RawSelectionType aRawSelectionTypes);
|
||||
inline SelectionTypeMask ToSelectionTypeMask(SelectionType aSelectionType);
|
||||
|
||||
} // namespace mozilla
|
||||
%}
|
||||
|
|
|
@ -3232,7 +3232,7 @@ ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent)
|
|||
mSelection->StartBatchChanges();
|
||||
|
||||
// Clear selection first before setting
|
||||
rv = mSelection->RemoveAllRanges();
|
||||
rv = mSelection->RemoveAllRangesTemporarily();
|
||||
// Need to call EndBatchChanges at the end even if call failed
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if (aEvent->mReversed) {
|
||||
|
|
|
@ -2657,7 +2657,7 @@ HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv)
|
|||
* on success, and NS_ERROR_FAILURE on failure.
|
||||
*/
|
||||
static nsresult
|
||||
IsInRanges(dom::TimeRanges& aRanges,
|
||||
IsInRanges(TimeRanges& aRanges,
|
||||
double aValue,
|
||||
bool& aIsInRanges,
|
||||
int32_t& aIntervalIndex)
|
||||
|
@ -2740,13 +2740,13 @@ HTMLMediaElement::Seek(double aTime,
|
|||
}
|
||||
|
||||
// Clamp the seek target to inside the seekable ranges.
|
||||
RefPtr<dom::TimeRanges> seekable = new dom::TimeRanges(ToSupports(OwnerDoc()));
|
||||
media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
|
||||
if (seekableIntervals.IsInvalid()) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise.
|
||||
return promise.forget();
|
||||
}
|
||||
seekableIntervals.ToTimeRanges(seekable);
|
||||
RefPtr<TimeRanges> seekable =
|
||||
new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals);
|
||||
uint32_t length = 0;
|
||||
seekable->GetLength(&length);
|
||||
if (!length) {
|
||||
|
@ -2865,10 +2865,9 @@ NS_IMETHODIMP HTMLMediaElement::GetDuration(double* aDuration)
|
|||
already_AddRefed<TimeRanges>
|
||||
HTMLMediaElement::Seekable() const
|
||||
{
|
||||
RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()));
|
||||
if (mDecoder) {
|
||||
mDecoder->GetSeekable().ToTimeRanges(ranges);
|
||||
}
|
||||
media::TimeIntervals seekable =
|
||||
mDecoder ? mDecoder->GetSeekable() : media::TimeIntervals();
|
||||
RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()), seekable);
|
||||
return ranges.forget();
|
||||
}
|
||||
|
||||
|
@ -6549,13 +6548,9 @@ HTMLMediaElement::CopyInnerTo(Element* aDest, bool aPreallocateChildren)
|
|||
already_AddRefed<TimeRanges>
|
||||
HTMLMediaElement::Buffered() const
|
||||
{
|
||||
RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()));
|
||||
if (mDecoder) {
|
||||
media::TimeIntervals buffered = mDecoder->GetBuffered();
|
||||
if (!buffered.IsInvalid()) {
|
||||
buffered.ToTimeRanges(ranges);
|
||||
}
|
||||
}
|
||||
media::TimeIntervals buffered =
|
||||
mDecoder ? mDecoder->GetBuffered() : media::TimeIntervals();
|
||||
RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()), buffered);
|
||||
return ranges.forget();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "mozilla/dom/WakeLock.h"
|
||||
#include "mozilla/dom/power/PowerManagerService.h"
|
||||
#include "mozilla/dom/Performance.h"
|
||||
#include "mozilla/dom/TimeRanges.h"
|
||||
#include "mozilla/dom/VideoPlaybackQuality.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "mozilla/dom/TimeRanges.h"
|
||||
#include "mozilla/dom/TimeRangesBinding.h"
|
||||
#include "mozilla/dom/HTMLMediaElement.h"
|
||||
#include "TimeUnits.h"
|
||||
#include "nsError.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -31,6 +32,35 @@ TimeRanges::TimeRanges(nsISupports* aParent)
|
|||
{
|
||||
}
|
||||
|
||||
TimeRanges::TimeRanges(nsISupports* aParent,
|
||||
const media::TimeIntervals& aTimeIntervals)
|
||||
: TimeRanges(aParent)
|
||||
{
|
||||
if (aTimeIntervals.IsInvalid()) {
|
||||
return;
|
||||
}
|
||||
for (const media::TimeInterval& interval : aTimeIntervals) {
|
||||
Add(interval.mStart.ToSeconds(), interval.mEnd.ToSeconds());
|
||||
}
|
||||
}
|
||||
|
||||
TimeRanges::TimeRanges(const media::TimeIntervals& aTimeIntervals)
|
||||
: TimeRanges(nullptr, aTimeIntervals)
|
||||
{
|
||||
}
|
||||
|
||||
media::TimeIntervals
|
||||
TimeRanges::ToTimeIntervals() const
|
||||
{
|
||||
media::TimeIntervals t;
|
||||
for (uint32_t i = 0; i < Length(); i++) {
|
||||
ErrorResult rv;
|
||||
t += media::TimeInterval(media::TimeUnit::FromSeconds(Start(i, rv)),
|
||||
media::TimeUnit::FromSeconds(End(i, rv)));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
TimeRanges::~TimeRanges()
|
||||
{
|
||||
}
|
||||
|
@ -43,7 +73,7 @@ TimeRanges::GetLength(uint32_t* aLength)
|
|||
}
|
||||
|
||||
double
|
||||
TimeRanges::Start(uint32_t aIndex, ErrorResult& aRv)
|
||||
TimeRanges::Start(uint32_t aIndex, ErrorResult& aRv) const
|
||||
{
|
||||
if (aIndex >= mRanges.Length()) {
|
||||
aRv = NS_ERROR_DOM_INDEX_SIZE_ERR;
|
||||
|
@ -62,7 +92,7 @@ TimeRanges::Start(uint32_t aIndex, double* aTime)
|
|||
}
|
||||
|
||||
double
|
||||
TimeRanges::End(uint32_t aIndex, ErrorResult& aRv)
|
||||
TimeRanges::End(uint32_t aIndex, ErrorResult& aRv) const
|
||||
{
|
||||
if (aIndex >= mRanges.Length()) {
|
||||
aRv = NS_ERROR_DOM_INDEX_SIZE_ERR;
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
#include "nsTArray.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "TimeUnits.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace dom {
|
||||
|
||||
class TimeRanges;
|
||||
|
||||
} // namespace dom
|
||||
|
||||
namespace dom {
|
||||
|
@ -35,6 +35,10 @@ public:
|
|||
|
||||
TimeRanges();
|
||||
explicit TimeRanges(nsISupports* aParent);
|
||||
explicit TimeRanges(const media::TimeIntervals& aTimeIntervals);
|
||||
TimeRanges(nsISupports* aParent, const media::TimeIntervals& aTimeIntervals);
|
||||
|
||||
media::TimeIntervals ToTimeIntervals() const;
|
||||
|
||||
void Add(double aStart, double aEnd);
|
||||
|
||||
|
@ -53,7 +57,8 @@ public:
|
|||
// Mutate this TimeRange to be the intersection of this and aOtherRanges.
|
||||
void Intersection(const TimeRanges* aOtherRanges);
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
nsISupports* GetParentObject() const;
|
||||
|
||||
|
@ -62,9 +67,9 @@ public:
|
|||
return mRanges.Length();
|
||||
}
|
||||
|
||||
virtual double Start(uint32_t aIndex, ErrorResult& aRv);
|
||||
double Start(uint32_t aIndex, ErrorResult& aRv) const;
|
||||
|
||||
virtual double End(uint32_t aIndex, ErrorResult& aRv);
|
||||
double End(uint32_t aIndex, ErrorResult& aRv) const;
|
||||
|
||||
// Shift all values by aOffset seconds.
|
||||
void Shift(double aOffset);
|
||||
|
|
|
@ -504,10 +504,11 @@ nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly)
|
|||
{
|
||||
RefPtr<nsCaret> caret = shell->GetCaret();
|
||||
if (caret) {
|
||||
nsISelection* domSel =
|
||||
Selection* selection =
|
||||
mFrameSelection->GetSelection(SelectionType::eNormal);
|
||||
if (domSel)
|
||||
if (selection) {
|
||||
caret->SetCaretReadOnly(aReadOnly);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
@ -547,10 +548,11 @@ nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility)
|
|||
{
|
||||
RefPtr<nsCaret> caret = shell->GetCaret();
|
||||
if (caret) {
|
||||
nsISelection* domSel =
|
||||
Selection* selection =
|
||||
mFrameSelection->GetSelection(SelectionType::eNormal);
|
||||
if (domSel)
|
||||
if (selection) {
|
||||
caret->SetVisibilityDuringSelection(aVisibility);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
@ -1338,22 +1340,16 @@ nsTextEditorState::BindToFrame(nsTextControlFrame* aFrame)
|
|||
mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
|
||||
|
||||
// Get the caret and make it a selection listener.
|
||||
RefPtr<nsISelection> domSelection;
|
||||
if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(domSelection))) &&
|
||||
domSelection) {
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(domSelection));
|
||||
// FYI: It's safe to use raw pointer for calling
|
||||
// Selection::AddSelectionListner() because it only appends the listener
|
||||
// to its internal array.
|
||||
Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
|
||||
if (selection) {
|
||||
RefPtr<nsCaret> caret = shell->GetCaret();
|
||||
nsCOMPtr<nsISelectionListener> listener;
|
||||
if (caret) {
|
||||
listener = do_QueryInterface(caret);
|
||||
if (listener) {
|
||||
selPriv->AddSelectionListener(listener);
|
||||
}
|
||||
selection->AddSelectionListener(caret);
|
||||
}
|
||||
|
||||
selPriv->AddSelectionListener(static_cast<nsISelectionListener*>
|
||||
(mTextListener));
|
||||
selection->AddSelectionListener(mTextListener);
|
||||
}
|
||||
|
||||
// If an editor exists from before, prepare it for usage
|
||||
|
@ -2208,14 +2204,13 @@ nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame)
|
|||
|
||||
if (mSelCon) {
|
||||
if (mTextListener) {
|
||||
RefPtr<nsISelection> domSelection;
|
||||
if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(domSelection))) &&
|
||||
domSelection) {
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(domSelection));
|
||||
|
||||
selPriv->RemoveSelectionListener(static_cast<nsISelectionListener*>
|
||||
(mTextListener));
|
||||
// FYI: It's safe to use raw pointer for calling
|
||||
// Selection::RemoveSelectionListener() because it only removes the
|
||||
// listener from its array.
|
||||
Selection* selection =
|
||||
mSelCon->GetSelection(SelectionType::eNormal);
|
||||
if (selection) {
|
||||
selection->RemoveSelectionListener(mTextListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2264,9 +2259,9 @@ nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame)
|
|||
// Unbind the anonymous content from the tree.
|
||||
// We actually hold a reference to the content nodes so that
|
||||
// they're not actually destroyed.
|
||||
nsContentUtils::DestroyAnonymousContent(&rootNode);
|
||||
nsContentUtils::DestroyAnonymousContent(&mPlaceholderDiv);
|
||||
nsContentUtils::DestroyAnonymousContent(&mPreviewDiv);
|
||||
aFrame->DestroyAnonymousContent(rootNode.forget());
|
||||
aFrame->DestroyAnonymousContent(mPlaceholderDiv.forget());
|
||||
aFrame->DestroyAnonymousContent(mPreviewDiv.forget());
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -2647,11 +2642,11 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||
{
|
||||
AutoNoJSAPI nojsapi;
|
||||
|
||||
nsCOMPtr<nsISelection> domSel;
|
||||
mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(domSel));
|
||||
SelectionBatcher selectionBatcher(domSel ? domSel->AsSelection() :
|
||||
nullptr);
|
||||
// FYI: It's safe to use raw pointer for selection here because
|
||||
// SelectionBatcher will grab it with RefPtr.
|
||||
Selection* selection =
|
||||
mSelCon->GetSelection(SelectionType::eNormal);
|
||||
SelectionBatcher selectionBatcher(selection);
|
||||
|
||||
if (NS_WARN_IF(!weakFrame.IsAlive())) {
|
||||
return true;
|
||||
|
@ -2694,10 +2689,12 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||
}
|
||||
} else {
|
||||
AutoDisableUndo disableUndo(textEditor);
|
||||
if (domSel) {
|
||||
if (selection) {
|
||||
// Since we don't use undo transaction, we don't need to store
|
||||
// selection state. SetText will set selection to tail.
|
||||
domSel->RemoveAllRanges();
|
||||
// Note that textEditor will collapse selection to the end.
|
||||
// Therefore, it's safe to use RemoveAllRangesTemporarily() here.
|
||||
selection->RemoveAllRangesTemporarily();
|
||||
}
|
||||
|
||||
textEditor->SetText(newValue);
|
||||
|
|
|
@ -53,7 +53,7 @@ VP9Benchmark::IsVP9DecodeFast()
|
|||
sHasRunTest = true;
|
||||
|
||||
RefPtr<WebMDemuxer> demuxer = new WebMDemuxer(
|
||||
new BufferMediaResource(sWebMSample, sizeof(sWebMSample), nullptr));
|
||||
new BufferMediaResource(sWebMSample, sizeof(sWebMSample)));
|
||||
RefPtr<Benchmark> estimiser =
|
||||
new Benchmark(demuxer,
|
||||
{
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#include "MediaResource.h"
|
||||
#include "nsISeekableStream.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -20,13 +19,10 @@ namespace mozilla {
|
|||
class BufferMediaResource : public MediaResource
|
||||
{
|
||||
public:
|
||||
BufferMediaResource(const uint8_t* aBuffer,
|
||||
uint32_t aLength,
|
||||
nsIPrincipal* aPrincipal)
|
||||
BufferMediaResource(const uint8_t* aBuffer, uint32_t aLength)
|
||||
: mBuffer(aBuffer)
|
||||
, mLength(aLength)
|
||||
, mOffset(0)
|
||||
, mPrincipal(aPrincipal)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -36,12 +32,6 @@ protected:
|
|||
}
|
||||
|
||||
private:
|
||||
// Get the current principal for the channel
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override
|
||||
{
|
||||
nsCOMPtr<nsIPrincipal> principal = mPrincipal;
|
||||
return principal.forget();
|
||||
}
|
||||
// These methods are called off the main thread.
|
||||
nsresult ReadAt(int64_t aOffset, char* aBuffer,
|
||||
uint32_t aCount, uint32_t* aBytes) override
|
||||
|
@ -87,25 +77,10 @@ private:
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
{
|
||||
// Not owned:
|
||||
// - mBuffer
|
||||
// - mPrincipal
|
||||
size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
{
|
||||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t * mBuffer;
|
||||
uint32_t mLength;
|
||||
uint32_t mOffset;
|
||||
nsCOMPtr<nsIPrincipal> mPrincipal;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -487,6 +487,22 @@ ChannelMediaDecoder::ShouldThrottleDownload()
|
|||
return stats.mDownloadRate > factor * stats.mPlaybackRate;
|
||||
}
|
||||
|
||||
void
|
||||
ChannelMediaDecoder::AddSizeOfResources(ResourceSizes* aSizes)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mResource) {
|
||||
aSizes->mByteSize += mResource->SizeOfIncludingThis(aSizes->mMallocSizeOf);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<nsIPrincipal>
|
||||
ChannelMediaDecoder::GetCurrentPrincipal()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mResource ? mResource->GetCurrentPrincipal() : nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
ChannelMediaDecoder::IsTransportSeekable()
|
||||
{
|
||||
|
|
|
@ -78,6 +78,8 @@ public:
|
|||
bool aIsPrivateBrowsing,
|
||||
nsIStreamListener** aStreamListener);
|
||||
|
||||
void AddSizeOfResources(ResourceSizes* aSizes) override;
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override;
|
||||
bool IsTransportSeekable() override;
|
||||
void SetLoadInBackground(bool aLoadInBackground) override;
|
||||
void Suspend() override;
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
#include "mozilla/gfx/Rect.h"
|
||||
#include "nsIMemoryReporter.h"
|
||||
#include "nsRect.h"
|
||||
#include "nsSize.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
|
|
@ -700,15 +700,6 @@ MediaDecoder::GetCurrentTime()
|
|||
return mLogicalPosition;
|
||||
}
|
||||
|
||||
already_AddRefed<nsIPrincipal>
|
||||
MediaDecoder::GetCurrentPrincipal()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MediaResource* r = GetResource();
|
||||
AbstractThread::AutoEnter context(AbstractMainThread());
|
||||
return r ? r->GetCurrentPrincipal() : nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoder::OnMetadataUpdate(TimedMetadata&& aMetadata)
|
||||
{
|
||||
|
@ -1345,15 +1336,6 @@ MediaDecoder::SizeOfAudioQueue()
|
|||
return 0;
|
||||
}
|
||||
|
||||
void MediaDecoder::AddSizeOfResources(ResourceSizes* aSizes)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (GetResource()) {
|
||||
aSizes->mByteSize +=
|
||||
GetResource()->SizeOfIncludingThis(aSizes->mMallocSizeOf);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoder::NotifyDataArrivedInternal()
|
||||
{
|
||||
|
|
|
@ -121,7 +121,7 @@ public:
|
|||
void NetworkError();
|
||||
|
||||
// Return the principal of the current URI being played or downloaded.
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
|
||||
|
||||
// Return the time position in the video stream being
|
||||
// played measured in seconds.
|
||||
|
@ -275,7 +275,7 @@ private:
|
|||
MozPromiseHolder<SizeOfPromise> mCallback;
|
||||
};
|
||||
|
||||
virtual void AddSizeOfResources(ResourceSizes* aSizes);
|
||||
virtual void AddSizeOfResources(ResourceSizes* aSizes) = 0;
|
||||
|
||||
VideoFrameContainer* GetVideoFrameContainer()
|
||||
{
|
||||
|
|
|
@ -160,9 +160,6 @@ public:
|
|||
NS_METHOD_(MozExternalRefCountType) AddRef(void);
|
||||
NS_METHOD_(MozExternalRefCountType) Release(void);
|
||||
|
||||
// Get the current principal for the channel
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
|
||||
|
||||
// These methods are called off the main thread.
|
||||
// Read up to aCount bytes from the stream. The read starts at
|
||||
// aOffset in the stream, seeking to that location initially if
|
||||
|
@ -219,14 +216,6 @@ public:
|
|||
*/
|
||||
virtual nsresult GetCachedRanges(MediaByteRangeSet& aRanges) = 0;
|
||||
|
||||
virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
|
||||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~MediaResource() {};
|
||||
|
||||
|
@ -290,6 +279,9 @@ public:
|
|||
// requests are supported by the connection/server.
|
||||
virtual bool IsTransportSeekable() = 0;
|
||||
|
||||
// Get the current principal for the channel
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
|
||||
|
||||
/**
|
||||
* Open the stream. This creates a stream listener and returns it in
|
||||
* aStreamListener; this listener needs to be notified of incoming data.
|
||||
|
@ -315,18 +307,17 @@ public:
|
|||
// Returns true if the resource is a live stream.
|
||||
bool IsLiveStream() { return GetLength() == -1; }
|
||||
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
// Might be useful to track in the future:
|
||||
// - mChannel
|
||||
// - mURI (possibly owned, looks like just a ref from mChannel)
|
||||
// Not owned:
|
||||
// - mCallback
|
||||
size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
|
||||
return size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/dom/TimeRanges.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -19,7 +18,7 @@ namespace media {
|
|||
class TimeIntervals;
|
||||
} // namespace media
|
||||
} // namespace mozilla
|
||||
// CopyChooser specalization for nsTArray
|
||||
// CopyChooser specialization for nsTArray
|
||||
template<>
|
||||
struct nsTArray_CopyChooser<mozilla::media::TimeIntervals>
|
||||
{
|
||||
|
@ -241,34 +240,6 @@ public:
|
|||
}
|
||||
|
||||
TimeIntervals() = default;
|
||||
|
||||
// Make TimeIntervals interchangeable with dom::TimeRanges.
|
||||
explicit TimeIntervals(dom::TimeRanges* aRanges)
|
||||
{
|
||||
for (uint32_t i = 0; i < aRanges->Length(); i++) {
|
||||
ErrorResult rv;
|
||||
*this +=
|
||||
TimeInterval(TimeUnit::FromSeconds(aRanges->Start(i, rv)),
|
||||
TimeUnit::FromSeconds(aRanges->End(i, rv)));
|
||||
}
|
||||
}
|
||||
TimeIntervals& operator = (dom::TimeRanges* aRanges)
|
||||
{
|
||||
*this = TimeIntervals(aRanges);
|
||||
return *this;
|
||||
}
|
||||
|
||||
static TimeIntervals FromTimeRanges(dom::TimeRanges* aRanges)
|
||||
{
|
||||
return TimeIntervals(aRanges);
|
||||
}
|
||||
|
||||
void ToTimeRanges(dom::TimeRanges* aRanges) const
|
||||
{
|
||||
for (IndexType i = 0; i < Length(); i++) {
|
||||
aRanges->Add(Start(i).ToSeconds(), End(i).ToSeconds());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -16,10 +16,6 @@ class MockMediaResource : public MediaResource
|
|||
{
|
||||
public:
|
||||
explicit MockMediaResource(const char* aFileName);
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount,
|
||||
uint32_t* aBytes) override;
|
||||
// Data stored in file, caching recommended.
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gtest/gtest.h"
|
||||
#include "AudioCompactor.h"
|
||||
#include "nsIMemoryReporter.h"
|
||||
|
||||
using mozilla::AudioCompactor;
|
||||
using mozilla::AudioData;
|
||||
|
|
|
@ -536,8 +536,7 @@ TEST(IntervalSet, TimeRangesSeconds)
|
|||
i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(45), media::TimeUnit::FromSeconds(50)));
|
||||
|
||||
media::TimeIntervals i(i0 + i1);
|
||||
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges();
|
||||
i.ToTimeRanges(tr);
|
||||
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges(i);
|
||||
EXPECT_EQ(tr->Length(), i.Length());
|
||||
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
|
||||
ErrorResult rv;
|
||||
|
@ -572,22 +571,13 @@ TEST(IntervalSet, TimeRangesConversion)
|
|||
tr->Add(53, 57);
|
||||
tr->Add(45, 50);
|
||||
|
||||
// explicit copy constructor
|
||||
media::TimeIntervals i1(tr);
|
||||
// explicit copy constructor and ToTimeIntervals.
|
||||
media::TimeIntervals i1(tr->ToTimeIntervals());
|
||||
CheckTimeRanges(tr, i1);
|
||||
|
||||
// static FromTimeRanges
|
||||
media::TimeIntervals i2 = media::TimeIntervals::FromTimeRanges(tr);
|
||||
CheckTimeRanges(tr, i2);
|
||||
|
||||
media::TimeIntervals i3;
|
||||
// operator=(TimeRanges*)
|
||||
i3 = tr;
|
||||
CheckTimeRanges(tr, i3);
|
||||
|
||||
// operator= test
|
||||
i1 = tr.get();
|
||||
CheckTimeRanges(tr, i1);
|
||||
// ctor(const TimeIntervals&)
|
||||
RefPtr<dom::TimeRanges> tr2 = new dom::TimeRanges(tr->ToTimeIntervals());
|
||||
CheckTimeRanges(tr2, i1);
|
||||
}
|
||||
|
||||
TEST(IntervalSet, TimeRangesMicroseconds)
|
||||
|
@ -605,8 +595,7 @@ TEST(IntervalSet, TimeRangesMicroseconds)
|
|||
i1.Add(media::TimeInterval(media::TimeUnit::FromMicroseconds(45), media::TimeUnit::FromMicroseconds(50)));
|
||||
|
||||
media::TimeIntervals i(i0 + i1);
|
||||
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges();
|
||||
i.ToTimeRanges(tr);
|
||||
RefPtr<dom::TimeRanges> tr = new dom::TimeRanges(i);
|
||||
EXPECT_EQ(tr->Length(), i.Length());
|
||||
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
|
||||
ErrorResult rv;
|
||||
|
@ -630,9 +619,8 @@ TEST(IntervalSet, TimeRangesMicroseconds)
|
|||
tr = new dom::TimeRanges();
|
||||
tr->Add(0, 30);
|
||||
tr->Add(50, std::numeric_limits<double>::infinity());
|
||||
media::TimeIntervals i_oo{media::TimeIntervals::FromTimeRanges(tr)};
|
||||
RefPtr<dom::TimeRanges> tr2 = new dom::TimeRanges();
|
||||
i_oo.ToTimeRanges(tr2);
|
||||
media::TimeIntervals i_oo = tr->ToTimeIntervals();
|
||||
RefPtr<dom::TimeRanges> tr2 = new dom::TimeRanges(i_oo);
|
||||
EXPECT_EQ(tr->Length(), tr2->Length());
|
||||
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
|
||||
ErrorResult rv;
|
||||
|
|
|
@ -92,6 +92,22 @@ HLSDecoder::Load(nsIChannel* aChannel)
|
|||
return InitializeStateMachine();
|
||||
}
|
||||
|
||||
void
|
||||
HLSDecoder::AddSizeOfResources(ResourceSizes* aSizes)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mResource) {
|
||||
aSizes->mByteSize += mResource->SizeOfIncludingThis(aSizes->mMallocSizeOf);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<nsIPrincipal>
|
||||
HLSDecoder::GetCurrentPrincipal()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mResource ? mResource->GetCurrentPrincipal() : nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
HLSDecoder::Play()
|
||||
{
|
||||
|
|
|
@ -37,6 +37,8 @@ public:
|
|||
|
||||
void Pause() override;
|
||||
|
||||
void AddSizeOfResources(ResourceSizes* aSizes) override;
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override;
|
||||
bool IsTransportSeekable() override { return true; }
|
||||
void Suspend() override;
|
||||
void Resume() override;
|
||||
|
|
|
@ -59,7 +59,7 @@ public:
|
|||
bool IsDataCachedToEndOfResource(int64_t aOffset) override { UNIMPLEMENTED(); return false; }
|
||||
nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
|
||||
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
|
||||
|
@ -83,23 +83,23 @@ public:
|
|||
|
||||
void Detach() { mDecoder = nullptr; }
|
||||
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
// TODO: track JAVA wrappers.
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class HLSResourceCallbacksSupport;
|
||||
|
||||
void onDataAvailable();
|
||||
void onError(int aErrorCode);
|
||||
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
{
|
||||
size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
{
|
||||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
HLSDecoder* mDecoder;
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
|
|
|
@ -60,7 +60,8 @@ MediaSourceDecoder::Load(nsIPrincipal* aPrincipal)
|
|||
MOZ_ASSERT(!GetStateMachine());
|
||||
AbstractThread::AutoEnter context(AbstractMainThread());
|
||||
|
||||
mResource = new MediaSourceResource(aPrincipal);
|
||||
mPrincipal = aPrincipal;
|
||||
mResource = new MediaSourceResource();
|
||||
|
||||
nsresult rv = MediaShutdownManager::Instance().Register(this);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
|
@ -362,6 +363,13 @@ MediaSourceDecoder::NotifyInitDataArrived()
|
|||
}
|
||||
}
|
||||
|
||||
already_AddRefed<nsIPrincipal>
|
||||
MediaSourceDecoder::GetCurrentPrincipal()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return do_AddRef(mPrincipal);
|
||||
}
|
||||
|
||||
#undef MSE_DEBUG
|
||||
#undef MSE_DEBUGV
|
||||
|
||||
|
|
|
@ -49,6 +49,8 @@ public:
|
|||
return mDemuxer;
|
||||
}
|
||||
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override;
|
||||
|
||||
bool IsTransportSeekable() override { return true; }
|
||||
|
||||
// Returns a string describing the state of the MediaSource internal
|
||||
|
@ -72,6 +74,7 @@ private:
|
|||
bool IsLiveStream() override final { return !mEnded; }
|
||||
|
||||
RefPtr<MediaSourceResource> mResource;
|
||||
RefPtr<nsIPrincipal> mPrincipal;
|
||||
|
||||
// The owning MediaSource holds a strong reference to this decoder, and
|
||||
// calls Attach/DetachMediaSource on this decoder to set and clear
|
||||
|
|
|
@ -26,11 +26,10 @@ namespace mozilla {
|
|||
class MediaSourceResource final : public MediaResource
|
||||
{
|
||||
public:
|
||||
explicit MediaSourceResource(nsIPrincipal* aPrincipal = nullptr)
|
||||
: mPrincipal(aPrincipal)
|
||||
, mMonitor("MediaSourceResource")
|
||||
MediaSourceResource()
|
||||
: mMonitor("MediaSourceResource")
|
||||
, mEnded(false)
|
||||
{}
|
||||
{}
|
||||
|
||||
nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
|
||||
bool ShouldCacheReads() override { UNIMPLEMENTED(); return false; }
|
||||
|
@ -43,11 +42,6 @@ public:
|
|||
bool IsDataCachedToEndOfResource(int64_t aOffset) override { UNIMPLEMENTED(); return false; }
|
||||
nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
|
||||
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override
|
||||
{
|
||||
return RefPtr<nsIPrincipal>(mPrincipal).forget();
|
||||
}
|
||||
|
||||
nsresult GetCachedRanges(MediaByteRangeSet& aRanges) override
|
||||
{
|
||||
UNIMPLEMENTED();
|
||||
|
@ -62,18 +56,17 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
|
||||
return size;
|
||||
// TODO: track source buffers appended to MediaSource.
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
RefPtr<nsIPrincipal> mPrincipal;
|
||||
Monitor mMonitor;
|
||||
bool mEnded; // protected by mMonitor
|
||||
};
|
||||
|
|
|
@ -114,14 +114,13 @@ SourceBuffer::GetBuffered(ErrorResult& aRv)
|
|||
media::TimeIntervals intersection = mTrackBuffersManager->Buffered();
|
||||
MSE_DEBUGV("intersection=%s", DumpTimeRanges(intersection).get());
|
||||
if (mBuffered) {
|
||||
media::TimeIntervals currentValue(mBuffered);
|
||||
media::TimeIntervals currentValue(mBuffered->ToTimeIntervals());
|
||||
rangeChanged = (intersection != currentValue);
|
||||
MSE_DEBUGV("currentValue=%s", DumpTimeRanges(currentValue).get());
|
||||
}
|
||||
// 5. If intersection ranges does not contain the exact same range information as the current value of this attribute, then update the current value of this attribute to intersection ranges.
|
||||
if (rangeChanged) {
|
||||
mBuffered = new TimeRanges(ToSupports(this));
|
||||
intersection.ToTimeRanges(mBuffered);
|
||||
mBuffered = new TimeRanges(ToSupports(this), intersection);
|
||||
}
|
||||
// 6. Return the current value of this attribute.
|
||||
return mBuffered;
|
||||
|
|
|
@ -31,11 +31,6 @@ class SourceBufferResource final : public MediaResource
|
|||
public:
|
||||
SourceBufferResource();
|
||||
nsresult Close();
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override
|
||||
{
|
||||
UNIMPLEMENTED();
|
||||
return nullptr;
|
||||
}
|
||||
nsresult ReadAt(int64_t aOffset,
|
||||
char* aBuffer,
|
||||
uint32_t aCount,
|
||||
|
@ -83,17 +78,13 @@ public:
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
size_t size = MediaResource::SizeOfExcludingThis(aMallocSizeOf);
|
||||
size += mInputBuffer.SizeOfExcludingThis(aMallocSizeOf);
|
||||
|
||||
return size;
|
||||
return mInputBuffer.SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
|
|
@ -173,17 +173,8 @@ MediaDecodeTask::CreateReader()
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsPIDOMWindowInner* parent = mDecodeJob.mContext->GetParentObject();
|
||||
MOZ_ASSERT(parent);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(parent);
|
||||
if (sop) {
|
||||
principal = sop->GetPrincipal();
|
||||
}
|
||||
|
||||
RefPtr<BufferMediaResource> resource =
|
||||
new BufferMediaResource(static_cast<uint8_t*>(mBuffer), mLength, principal);
|
||||
new BufferMediaResource(static_cast<uint8_t*>(mBuffer), mLength);
|
||||
|
||||
mMainThread =
|
||||
mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other);
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const ADDON_ID = "test-plugin-from-xpi@tests.mozilla.org";
|
||||
const XRE_EXTENSIONS_DIR_LIST = "XREExtDL";
|
||||
const NS_APP_PLUGINS_DIR_LIST = "APluginsDL";
|
||||
|
||||
const gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
|
||||
var gProfileDir = null;
|
||||
|
||||
function getAddonRoot(profileDir, id) {
|
||||
let dir = profileDir.clone();
|
||||
dir.append("extensions");
|
||||
Assert.ok(dir.exists(), "Extensions dir should exist: " + dir.path);
|
||||
dir.append(id);
|
||||
return dir;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
allow_all_plugins();
|
||||
loadAddonManager();
|
||||
gProfileDir = do_get_profile();
|
||||
do_register_cleanup(() => shutdownManager());
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(async function test_state() {
|
||||
// Remove test so we will have only one "Test Plug-in" registered.
|
||||
// xpcshell tests have plugins in per-test profiles, so that's fine.
|
||||
let file = get_test_plugin();
|
||||
file.remove(true);
|
||||
file = get_test_plugin(true);
|
||||
file.remove(true);
|
||||
|
||||
Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_CLICKTOPLAY);
|
||||
Services.prefs.setIntPref("plugin.defaultXpi.state", Ci.nsIPluginTag.STATE_ENABLED);
|
||||
|
||||
let success = await installAddon("testaddon.xpi");
|
||||
Assert.ok(success, "Should have installed addon.");
|
||||
let addonDir = getAddonRoot(gProfileDir, ADDON_ID);
|
||||
|
||||
let provider = {
|
||||
classID: Components.ID("{0af6b2d7-a06c-49b7-babc-636d292b0dbb}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider,
|
||||
Ci.nsIDirectoryServiceProvider2]),
|
||||
|
||||
getFile: function (prop, persistant) {
|
||||
throw Cr.NS_ERROR_FAILURE;
|
||||
},
|
||||
|
||||
getFiles: function (prop) {
|
||||
let result = [];
|
||||
|
||||
switch (prop) {
|
||||
case XRE_EXTENSIONS_DIR_LIST:
|
||||
result.push(addonDir);
|
||||
break;
|
||||
case NS_APP_PLUGINS_DIR_LIST:
|
||||
let pluginDir = addonDir.clone();
|
||||
pluginDir.append("plugins");
|
||||
result.push(pluginDir);
|
||||
break;
|
||||
default:
|
||||
throw Cr.NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
|
||||
hasMoreElements: () => result.length > 0,
|
||||
getNext: () => result.shift(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
|
||||
dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
|
||||
dirSvc = null;
|
||||
|
||||
// We installed a non-restartless addon, need to restart the manager.
|
||||
restartManager();
|
||||
gPluginHost.reloadPlugins();
|
||||
|
||||
Assert.ok(addonDir.exists(), "Addon path should exist: " + addonDir.path);
|
||||
Assert.ok(addonDir.isDirectory(), "Addon path should be a directory: " + addonDir.path);
|
||||
let pluginDir = addonDir.clone();
|
||||
pluginDir.append("plugins");
|
||||
Assert.ok(pluginDir.exists(), "Addon plugins path should exist: " + pluginDir.path);
|
||||
Assert.ok(pluginDir.isDirectory(), "Addon plugins path should be a directory: " + pluginDir.path);
|
||||
|
||||
let addon = await getAddonByID(ADDON_ID);
|
||||
Assert.ok(!addon.appDisabled, "Addon should not be appDisabled");
|
||||
Assert.ok(addon.isActive, "Addon should be active");
|
||||
Assert.ok(addon.isCompatible, "Addon should be compatible");
|
||||
Assert.ok(!addon.userDisabled, "Addon should not be user disabled");
|
||||
|
||||
let testPlugin = get_test_plugintag();
|
||||
Assert.notEqual(testPlugin, null, "Test plugin should have been found");
|
||||
Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Test plugin from addon should have state enabled");
|
||||
|
||||
pluginDir.append(testPlugin.filename);
|
||||
Assert.ok(pluginDir.exists(), "Plugin file should exist in addon directory: " + pluginDir.path);
|
||||
|
||||
testPlugin = get_test_plugintag("Second Test Plug-in");
|
||||
Assert.notEqual(testPlugin, null, "Second test plugin should have been found");
|
||||
Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Second test plugin from addon should have state enabled");
|
||||
});
|