Merge m-c to b2ginbound a=merge

This commit is contained in:
Wes Kocher 2015-02-23 17:02:28 -08:00
commit 058232bba2
126 changed files with 2861 additions and 1024 deletions

View File

@ -915,16 +915,46 @@ html, .fx-embedded, #main,
.standalone .room-conversation-wrapper .room-inner-info-area {
position: absolute;
top: calc(50% - 1em);
left: 0;
right: 25%;
/* `top` is chosen to vertically position the area near the center
of the media element. */
top: calc(50% - 140px);
left: 25%;
z-index: 1000;
margin: 0 auto;
padding: 0 1rem;
width: 50%;
/* `width` here is specified by the design spec. */
width: 250px;
color: #fff;
font-weight: bold;
font-size: 1.1em;
box-sizing: content-box;
}
.standalone .prompt-media-message {
padding-top: 136px; /* Fallback for browsers that don't support calc() */
/* 122px is 2x the intrinsic height of the background-image, and
1rem puts one line of margin between the background-image and
supporting text. */
padding-top: calc(122px + 1rem);
color: #000;
background-color: #fff;
background-image: url("../../img/gum-others.svg");
background-position: top center;
/* The background-image is scaled up at 2x the instrinsic size
(witdh & height) to make it easier to see. */
background-size: 202px 122px;
background-repeat: no-repeat;
border: 1rem #fff solid;
box-shadow: 0 0 5px #000;
margin: 0;
}
.standalone .prompt-media-message.chrome {
background-image: url("../../img/gum-chrome.svg");
}
.standalone .prompt-media-message.firefox {
background-image: url("../../img/gum-firefox.svg");
}
.standalone .prompt-media-message.opera {
background-image: url("../../img/gum-opera.svg");
}
.standalone .room-conversation-wrapper .room-inner-info-area button {

View File

@ -84,8 +84,13 @@ loop.shared.utils = (function(mozL10n) {
return !!localStorage.getItem(prefName);
}
function isChrome(platform) {
return platform.toLowerCase().indexOf('chrome') > -1 ||
platform.toLowerCase().indexOf('chromium') > -1;
}
function isFirefox(platform) {
return platform.indexOf("Firefox") !== -1;
return platform.toLowerCase().indexOf("firefox") !== -1;
}
function isFirefoxOS(platform) {
@ -97,6 +102,11 @@ loop.shared.utils = (function(mozL10n) {
return !!window.MozActivity && /mobi/i.test(platform);
}
function isOpera(platform) {
return platform.toLowerCase().indexOf('opera') > -1 ||
platform.toLowerCase().indexOf('opr') > -1;
}
/**
* Helper to get the platform if it is unsupported.
*
@ -167,8 +177,10 @@ loop.shared.utils = (function(mozL10n) {
composeCallUrlEmail: composeCallUrlEmail,
formatDate: formatDate,
getBoolPreference: getBoolPreference,
isChrome: isChrome,
isFirefox: isFirefox,
isFirefoxOS: isFirefoxOS,
isOpera: isOpera,
getUnsupportedPlatform: getUnsupportedPlatform,
locationData: locationData
};

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="102px" height="62px" viewBox="0 0 102 62" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(0.500000, 0.000000)">
<rect stroke="#ABE6F8" fill="#FFFFFF" x="0.5" y="1" width="100" height="60"/>
<path d="M36.4003,3 L39.02,9.66666667 L34.8325,9.66666667 L5.6875,9.66666667 L1.5,9.66666667 L4.1197,3 L36.4003,3 Z" fill="#AAE5F9"/>
<rect fill="#AAE5F9" x="0.5" y="9" width="100" height="10"/>
<path d="M21.004515,11.3333333 C19.6213095,11.3333333 18.5,12.4429954 18.5,13.8333333 L18.5,13.8333333 C18.5,15.2140452 19.6250227,16.3333333 21.0055946,16.3333333 L40.8816964,16.3333333 L90.002759,16.3333333 C91.3819471,16.3333333 92.5,15.2236713 92.5,13.8333333 L92.5,13.8333333 C92.5,12.4526215 91.3819191,11.3333333 89.995485,11.3333333 L21.004515,11.3333333 Z" fill="#FFFFFF"/>
<rect stroke="#ABE6F8" x="0" y="0" width="101" height="62"/>
<g transform="translate(0.000000, 18.000000)">
<rect stroke="#92CB3E" stroke-width="2" fill="#FFFFFF" x="0.5" y="0" width="100" height="10"/>
<path d="M89.5096131,3 C88.3997344,3 87.5,3.88772964 87.5,5 L87.5,5 C87.5,6.1045695 88.3933569,7 89.4971942,7 L88.5360952,7 C89.6426425,7 91.4357226,7 92.5331547,7 L95.5533191,7 C96.6560555,7 97.55,6.11227036 97.55,5 L97.55,5 C97.55,3.8954305 96.6581295,3 95.5403869,3 L89.5096131,3 Z" stroke="#92CB3E" fill="#FFFFFF"/>
<path d="M78.5096131,3 C77.3997344,3 76.5,3.88772964 76.5,5 L76.5,5 C76.5,6.1045695 77.3933569,7 78.4971942,7 L77.5360952,7 C78.6426425,7 80.4357226,7 81.5331547,7 L84.5533191,7 C85.6560555,7 86.55,6.11227036 86.55,5 L86.55,5 C86.55,3.8954305 85.6581295,3 84.5403869,3 L78.5096131,3 Z" stroke="#92CB3E" fill="#FFFFFF"/>
<path d="M8.5,3.69642857 L8.5,6.73214286 C8.5,6.81026825 8.46372804,6.86514121 8.39118304,6.89676339 C8.36700137,6.90606403 8.34375011,6.91071429 8.32142857,6.91071429 C8.27120511,6.91071429 8.22935285,6.89304333 8.19587054,6.85770089 L7.07142857,5.73325893 L7.07142857,6.19642857 C7.07142857,6.41778384 6.99283933,6.60704907 6.83565848,6.76422991 C6.67847764,6.92141076 6.48921242,7 6.26785714,7 L4.30357143,7 C4.08221616,7 3.89295093,6.92141076 3.73577009,6.76422991 C3.57858924,6.60704907 3.5,6.41778384 3.5,6.19642857 L3.5,4.23214286 C3.5,4.01078758 3.57858924,3.82152236 3.73577009,3.66434152 C3.89295093,3.50716067 4.08221616,3.42857143 4.30357143,3.42857143 L6.26785714,3.42857143 C6.48921242,3.42857143 6.67847764,3.50716067 6.83565848,3.66434152 C6.99283933,3.82152236 7.07142857,4.01078758 7.07142857,4.23214286 L7.07142857,4.69252232 L8.19587054,3.57087054 C8.22935285,3.5355281 8.27120511,3.51785714 8.32142857,3.51785714 C8.34375011,3.51785714 8.36700137,3.52250739 8.39118304,3.53180804 C8.46372804,3.56343022 8.5,3.61830318 8.5,3.69642857 L8.5,3.69642857 Z" fill="#00AF84"/>
</g>
<path d="M97.9285714,14.5714286 L97.9285714,14.8571429 C97.9285714,14.8958335 97.9144347,14.9293153 97.8861607,14.9575893 C97.8578868,14.9858632 97.824405,15 97.7857143,15 L94.6428571,15 C94.6041665,15 94.5706847,14.9858632 94.5424107,14.9575893 C94.5141368,14.9293153 94.5,14.8958335 94.5,14.8571429 L94.5,14.5714286 C94.5,14.5327379 94.5141368,14.4992561 94.5424107,14.4709821 C94.5706847,14.4427082 94.6041665,14.4285714 94.6428571,14.4285714 L97.7857143,14.4285714 C97.824405,14.4285714 97.8578868,14.4427082 97.8861607,14.4709821 C97.9144347,14.4992561 97.9285714,14.5327379 97.9285714,14.5714286 L97.9285714,14.5714286 Z M97.9285714,13.4285714 L97.9285714,13.7142857 C97.9285714,13.7529764 97.9144347,13.7864582 97.8861607,13.8147321 C97.8578868,13.8430061 97.824405,13.8571429 97.7857143,13.8571429 L94.6428571,13.8571429 C94.6041665,13.8571429 94.5706847,13.8430061 94.5424107,13.8147321 C94.5141368,13.7864582 94.5,13.7529764 94.5,13.7142857 L94.5,13.4285714 C94.5,13.3898808 94.5141368,13.356399 94.5424107,13.328125 C94.5706847,13.299851 94.6041665,13.2857143 94.6428571,13.2857143 L97.7857143,13.2857143 C97.824405,13.2857143 97.8578868,13.299851 97.8861607,13.328125 C97.9144347,13.356399 97.9285714,13.3898808 97.9285714,13.4285714 L97.9285714,13.4285714 Z M97.9285714,12.2857143 L97.9285714,12.5714286 C97.9285714,12.6101192 97.9144347,12.643601 97.8861607,12.671875 C97.8578868,12.700149 97.824405,12.7142857 97.7857143,12.7142857 L94.6428571,12.7142857 C94.6041665,12.7142857 94.5706847,12.700149 94.5424107,12.671875 C94.5141368,12.643601 94.5,12.6101192 94.5,12.5714286 L94.5,12.2857143 C94.5,12.2470236 94.5141368,12.2135418 94.5424107,12.1852679 C94.5706847,12.1569939 94.6041665,12.1428571 94.6428571,12.1428571 L97.7857143,12.1428571 C97.824405,12.1428571 97.8578868,12.1569939 97.8861607,12.1852679 C97.9144347,12.2135418 97.9285714,12.2470236 97.9285714,12.2857143 L97.9285714,12.2857143 Z" fill="#FFFFFF"/>
<path d="M5.92857143,13.5714286 L5.92857143,13.8571429 C5.92857143,13.9360123 5.90439012,14.0033479 5.85602679,14.0591518 C5.80766345,14.1149556 5.74479205,14.1428571 5.66741071,14.1428571 L4.09598214,14.1428571 L4.75,14.7991071 C4.8065479,14.8526788 4.83482143,14.9196425 4.83482143,15 C4.83482143,15.0803575 4.8065479,15.1473212 4.75,15.2008929 L4.58258929,15.3705357 C4.52752949,15.4255955 4.46056587,15.453125 4.38169643,15.453125 C4.30431509,15.453125 4.23660743,15.4255955 4.17857143,15.3705357 L2.72544643,13.9151786 C2.67038663,13.8601188 2.64285714,13.7931552 2.64285714,13.7142857 C2.64285714,13.6369044 2.67038663,13.5691967 2.72544643,13.5111607 L4.17857143,12.0602679 C4.23511933,12.00372 4.30282699,11.9754464 4.38169643,11.9754464 C4.45907777,11.9754464 4.52604138,12.00372 4.58258929,12.0602679 L4.75,12.2254464 C4.8065479,12.2819943 4.83482143,12.349702 4.83482143,12.4285714 C4.83482143,12.5074409 4.8065479,12.5751485 4.75,12.6316964 L4.09598214,13.2857143 L5.66741071,13.2857143 C5.74479205,13.2857143 5.80766345,13.3136158 5.85602679,13.3694196 C5.90439012,13.4252235 5.92857143,13.4925591 5.92857143,13.5714286 L5.92857143,13.5714286 Z" fill="#FFFFFF"/>
<path d="M10.9285714,13.5714286 L10.9285714,13.8571429 C10.9285714,13.9360123 10.9043901,14.0033479 10.8560268,14.0591518 C10.8076634,14.1149556 10.7447921,14.1428571 10.6674107,14.1428571 L9.09598214,14.1428571 L9.75,14.7991071 C9.8065479,14.8526788 9.83482143,14.9196425 9.83482143,15 C9.83482143,15.0803575 9.8065479,15.1473212 9.75,15.2008929 L9.58258929,15.3705357 C9.52752949,15.4255955 9.46056587,15.453125 9.38169643,15.453125 C9.30431509,15.453125 9.23660743,15.4255955 9.17857143,15.3705357 L7.72544643,13.9151786 C7.67038663,13.8601188 7.64285714,13.7931552 7.64285714,13.7142857 C7.64285714,13.6369044 7.67038663,13.5691967 7.72544643,13.5111607 L9.17857143,12.0602679 C9.23511933,12.00372 9.30282699,11.9754464 9.38169643,11.9754464 C9.45907777,11.9754464 9.52604138,12.00372 9.58258929,12.0602679 L9.75,12.2254464 C9.8065479,12.2819943 9.83482143,12.349702 9.83482143,12.4285714 C9.83482143,12.5074409 9.8065479,12.5751485 9.75,12.6316964 L9.09598214,13.2857143 L10.6674107,13.2857143 C10.7447921,13.2857143 10.8076634,13.3136158 10.8560268,13.3694196 C10.9043901,13.4252235 10.9285714,13.4925591 10.9285714,13.5714286 L10.9285714,13.5714286 Z" fill="#FFFFFF" transform="translate(9.285714, 13.714286) scale(-1, 1) translate(-9.285714, -13.714286) "/>
<path d="M16.9285714,12.1428571 L16.9285714,13.1428571 C16.9285714,13.1815478 16.9144347,13.2150296 16.8861607,13.2433036 C16.8578868,13.2715775 16.824405,13.2857143 16.7857143,13.2857143 L15.7857143,13.2857143 C15.723214,13.2857143 15.6793156,13.2559527 15.6540179,13.1964286 C15.6287201,13.1383926 15.6391367,13.0870538 15.6852679,13.0424107 L15.9933036,12.734375 C15.7730644,12.5305049 15.5133944,12.4285714 15.2142857,12.4285714 C15.059523,12.4285714 14.9118311,12.4587051 14.7712054,12.5189732 C14.6305797,12.5792414 14.5089291,12.6607138 14.40625,12.7633929 C14.3035709,12.8660719 14.2220985,12.9877225 14.1618304,13.1283482 C14.1015622,13.2689739 14.0714286,13.4166659 14.0714286,13.5714286 C14.0714286,13.7261912 14.1015622,13.8738832 14.1618304,14.0145089 C14.2220985,14.1551346 14.3035709,14.2767852 14.40625,14.3794643 C14.5089291,14.4821434 14.6305797,14.5636158 14.7712054,14.6238839 C14.9118311,14.6841521 15.059523,14.7142857 15.2142857,14.7142857 C15.3913699,14.7142857 15.558779,14.6755956 15.7165179,14.5982143 C15.8742567,14.5208329 16.0074399,14.411459 16.1160714,14.2700893 C16.1264881,14.2552083 16.1436011,14.2462798 16.1674107,14.2433036 C16.1882442,14.2433036 16.2068452,14.2499999 16.2232143,14.2633929 L16.5290179,14.5714286 C16.5424108,14.5833334 16.5494792,14.5985862 16.5502232,14.6171875 C16.5509673,14.6357888 16.545387,14.6525297 16.5334821,14.6674107 C16.371279,14.8638403 16.1748523,15.0159965 15.9441964,15.1238839 C15.7135405,15.2317714 15.4702394,15.2857143 15.2142857,15.2857143 C14.9821417,15.2857143 14.7604177,15.2403278 14.5491071,15.1495536 C14.3377966,15.0587793 14.1555067,14.9367567 14.0022321,14.7834821 C13.8489576,14.6302076 13.726935,14.4479177 13.6361607,14.2366071 C13.5453865,14.0252966 13.5,13.8035726 13.5,13.5714286 C13.5,13.3392846 13.5453865,13.1175606 13.6361607,12.90625 C13.726935,12.6949394 13.8489576,12.5126496 14.0022321,12.359375 C14.1555067,12.2061004 14.3377966,12.0840778 14.5491071,11.9933036 C14.7604177,11.9025293 14.9821417,11.8571429 15.2142857,11.8571429 C15.4330368,11.8571429 15.6447162,11.8984371 15.8493304,11.9810268 C16.0539445,12.0636165 16.2358623,12.1800588 16.3950893,12.3303571 L16.6852679,12.0424107 C16.7284228,11.9962795 16.7805056,11.985863 16.8415179,12.0111607 C16.8995539,12.0364585 16.9285714,12.0803568 16.9285714,12.1428571 L16.9285714,12.1428571 Z" fill="#FFFFFF"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="102px" height="62px" viewBox="0 0 102 62" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path d="M39.565,8.00739557 C39.4896406,8.00249919 39.4115419,8 39.3306224,8 C35.3657519,8 37.8823812,2 33.0112546,2 L7.18295591,2 C2.59740823,2 4.61972552,7.31710024 1.565,7.94055011 L1.565,7.94055011 L1.565,14 L15.5658248,14 L39.565,14 L39.565,8.00739557 Z" id="path-1"/>
</defs>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(0.500000, 0.000000)">
<g transform="translate(0.000000, 1.000000)">
<rect stroke="#ABE6F8" fill="#FFFFFF" x="0.565" y="0" width="100" height="60"/>
<g>
<use fill="#AAE5F9" fill-rule="evenodd" xlink:href="#path-1"/>
<use fill="none" xlink:href="#path-1"/>
<use fill="none" xlink:href="#path-1"/>
</g>
<rect fill="#ABE6F8" x="0.565" y="7" width="99.685" height="10"/>
<path d="M8.07491535,9.33333333 C6.68872738,9.33333333 5.565,10.4429954 5.565,11.8333333 L5.565,11.8333333 C5.565,13.2140452 6.68484687,14.3333333 8.05891597,14.3333333 L31.8786161,14.3333333 L90.0705787,14.3333333 C91.4482095,14.3333333 92.565,13.2236713 92.565,11.8333333 L92.565,11.8333333 C92.565,10.4526215 91.4491428,9.33333333 90.0550846,9.33333333 L8.07491535,9.33333333 Z" fill="#FFFFFF"/>
<ellipse stroke="#FFFFFF" fill="#AAE5F9" cx="5.565" cy="12" rx="3" ry="3"/>
<path d="M16.2240909,16.6938776 L44.5630004,16.6938776 C45.6668839,16.6938776 46.565,17.5893929 46.565,18.6940672 L46.565,44.9998104 C46.565,46.1094081 45.6686743,47 44.5630004,47 L5.56699958,47 C4.46311611,47 3.565,46.1044846 3.565,44.9998104 L3.565,18.6940672 C3.565,17.5844694 4.46132574,16.6938776 5.56699958,16.6938776 L8.40590909,16.6938776 L12.315,14 L16.2240909,16.6938776 Z" stroke="#92CB3E" stroke-width="2" fill="#FFFFFF"/>
<path d="M25.0575753,38 C23.680964,38 22.565,39.1096621 22.565,40.5 L22.565,40.5 C22.565,41.8807119 23.6941328,43 25.0722854,43 L26.115167,43 C27.4952935,43 29.7367265,43 31.1231981,43 L40.0643408,43 C41.4454167,43 42.565,41.8903379 42.565,40.5 L42.565,40.5 C42.565,39.1192881 41.4468578,38 40.0724247,38 L25.0575753,38 Z" stroke="#92CB3E" fill="#FFFFFF"/>
<path d="M17.565,22.3928571 L17.565,28.4642857 C17.565,28.6205365 17.4924561,28.7302824 17.3473661,28.7935268 C17.2990027,28.8121281 17.2525002,28.8214286 17.2078571,28.8214286 C17.1074102,28.8214286 17.0237057,28.7860867 16.9567411,28.7154018 L14.7078571,26.4665179 L14.7078571,27.3928571 C14.7078571,27.8355677 14.5506787,28.2140981 14.236317,28.5284598 C13.9219553,28.8428215 13.5434248,29 13.1007143,29 L9.17214286,29 C8.72943231,29 8.35090187,28.8428215 8.03654018,28.5284598 C7.72217849,28.2140981 7.565,27.8355677 7.565,27.3928571 L7.565,23.4642857 C7.565,23.0215752 7.72217849,22.6430447 8.03654018,22.328683 C8.35090187,22.0143213 8.72943231,21.8571429 9.17214286,21.8571429 L13.1007143,21.8571429 C13.5434248,21.8571429 13.9219553,22.0143213 14.236317,22.328683 C14.5506787,22.6430447 14.7078571,23.0215752 14.7078571,23.4642857 L14.7078571,24.3850446 L16.9567411,22.1417411 C17.0237057,22.0710562 17.1074102,22.0357143 17.2078571,22.0357143 C17.2525002,22.0357143 17.2990027,22.0450148 17.3473661,22.0636161 C17.4924561,22.1268604 17.565,22.2366064 17.565,22.3928571 L17.565,22.3928571 Z" fill="#00AF84"/>
<path d="M13.565,11.0178571 L13.565,12.8392857 C13.565,12.8861609 13.5432368,12.9190847 13.4997098,12.938058 C13.4852008,12.9436384 13.4712501,12.9464286 13.4578571,12.9464286 C13.4277231,12.9464286 13.4026117,12.935826 13.3825223,12.9146205 L12.7078571,12.2399554 L12.7078571,12.5178571 C12.7078571,12.6506703 12.6607036,12.7642294 12.5663951,12.8585379 C12.4720866,12.9528465 12.3585274,13 12.2257143,13 L11.0471429,13 C10.9143297,13 10.8007706,12.9528465 10.7064621,12.8585379 C10.6121535,12.7642294 10.565,12.6506703 10.565,12.5178571 L10.565,11.3392857 C10.565,11.2064726 10.6121535,11.0929134 10.7064621,10.9986049 C10.8007706,10.9042964 10.9143297,10.8571429 11.0471429,10.8571429 L12.2257143,10.8571429 C12.3585274,10.8571429 12.4720866,10.9042964 12.5663951,10.9986049 C12.6607036,11.0929134 12.7078571,11.2064726 12.7078571,11.3392857 L12.7078571,11.6155134 L13.3825223,10.9425223 C13.4026117,10.9213169 13.4277231,10.9107143 13.4578571,10.9107143 C13.4712501,10.9107143 13.4852008,10.9135044 13.4997098,10.9190848 C13.5432368,10.9380581 13.565,10.9709819 13.565,11.0178571 L13.565,11.0178571 Z" fill="#00AF84"/>
<path d="M6.86418806,12.1429618 C6.86418806,12.2021139 6.84605208,12.2526156 6.80977958,12.2944685 C6.77350707,12.3363214 6.72635353,12.3572475 6.66831752,12.3572475 L5.48974609,12.3572475 L5.98025949,12.849435 C6.02267041,12.8896138 6.04387556,12.9398365 6.04387556,13.0001046 C6.04387556,13.0603728 6.02267041,13.1105955 5.98025949,13.1507743 L5.85470145,13.2780064 C5.8134066,13.3193013 5.76318389,13.3399484 5.70403181,13.3399484 C5.6459958,13.3399484 5.59521506,13.3193013 5.55168806,13.2780064 L4.46184431,12.1864886 C4.42054946,12.1451937 4.39990234,12.094971 4.39990234,12.0358189 C4.39990234,11.9777829 4.42054946,11.9270022 4.46184431,11.8834752 L5.55168806,10.7953055 C5.59409898,10.7528946 5.64487973,10.7316895 5.70403181,10.7316895 C5.76206781,10.7316895 5.81229052,10.7528946 5.85470145,10.7953055 L5.98025949,10.9191895 C6.02267041,10.9616004 6.04387556,11.0123811 6.04387556,11.0715332 C6.04387556,11.1306853 6.02267041,11.181466 5.98025949,11.223877 L5.48974609,11.7143903 L6.66831752,11.7143903 C6.72635353,11.7143903 6.77350707,11.7353165 6.80977958,11.7771694 C6.84605208,11.8190223 6.86418806,11.869524 6.86418806,11.9286761 L6.86418806,12.1429618 Z" fill="#FFFFFF"/>
<path d="M97.1364286,12.6785714 L97.1364286,12.8928571 C97.1364286,12.9218751 97.125826,12.9469865 97.1046205,12.968192 C97.0834151,12.9893974 97.0583037,13 97.0292857,13 L94.6721429,13 C94.6431249,13 94.6180135,12.9893974 94.596808,12.968192 C94.5756026,12.9469865 94.565,12.9218751 94.565,12.8928571 L94.565,12.6785714 C94.565,12.6495534 94.5756026,12.6244421 94.596808,12.6032366 C94.6180135,12.5820311 94.6431249,12.5714286 94.6721429,12.5714286 L97.0292857,12.5714286 C97.0583037,12.5714286 97.0834151,12.5820311 97.1046205,12.6032366 C97.125826,12.6244421 97.1364286,12.6495534 97.1364286,12.6785714 L97.1364286,12.6785714 Z M97.1364286,11.8214286 L97.1364286,12.0357143 C97.1364286,12.0647323 97.125826,12.0898436 97.1046205,12.1110491 C97.0834151,12.1322546 97.0583037,12.1428571 97.0292857,12.1428571 L94.6721429,12.1428571 C94.6431249,12.1428571 94.6180135,12.1322546 94.596808,12.1110491 C94.5756026,12.0898436 94.565,12.0647323 94.565,12.0357143 L94.565,11.8214286 C94.565,11.7924106 94.5756026,11.7672992 94.596808,11.7460937 C94.6180135,11.7248883 94.6431249,11.7142857 94.6721429,11.7142857 L97.0292857,11.7142857 C97.0583037,11.7142857 97.0834151,11.7248883 97.1046205,11.7460937 C97.125826,11.7672992 97.1364286,11.7924106 97.1364286,11.8214286 L97.1364286,11.8214286 Z M97.1364286,10.9642857 L97.1364286,11.1785714 C97.1364286,11.2075894 97.125826,11.2327008 97.1046205,11.2539062 C97.0834151,11.2751117 97.0583037,11.2857143 97.0292857,11.2857143 L94.6721429,11.2857143 C94.6431249,11.2857143 94.6180135,11.2751117 94.596808,11.2539062 C94.5756026,11.2327008 94.565,11.2075894 94.565,11.1785714 L94.565,10.9642857 C94.565,10.9352677 94.5756026,10.9101564 94.596808,10.8889509 C94.6180135,10.8677454 94.6431249,10.8571429 94.6721429,10.8571429 L97.0292857,10.8571429 C97.0583037,10.8571429 97.0834151,10.8677454 97.1046205,10.8889509 C97.125826,10.9101564 97.1364286,10.9352677 97.1364286,10.9642857 L97.1364286,10.9642857 Z" fill="#FFFFFF"/>
</g>
<rect stroke="#ABE6F8" x="0" y="0" width="101" height="62"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="102px" height="62px" viewBox="0 0 102 62" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(0.500000, 0.000000)">
<rect stroke="#AAE5F9" fill="#FFFFFF" x="0.990196078" y="1" width="99.0196078" height="60"/>
<rect fill="#AAE5F9" x="2.97058824" y="3" width="33.6666667" height="13"/>
<rect fill="#AAE5F9" x="0.990196078" y="8" width="99.0196078" height="10"/>
<path d="M27.2559789,11 C25.8746722,11 24.754902,12.1096621 24.754902,13.5 L24.754902,13.5 C24.754902,14.8807119 25.8688951,16 27.2538514,16 L46.91717,16 L95.5280221,16 C96.9095015,16 98.0294118,14.8903379 98.0294118,13.5 L98.0294118,13.5 C98.0294118,12.1192881 96.9077658,11 95.5283349,11 L27.2559789,11 Z" fill="#FFFFFF"/>
<rect stroke="#92CB3E" stroke-width="2" fill="#FFFFFF" x="28.7156863" y="15" width="42.5784314" height="22" rx="2"/>
<path d="M51.5228862,29 C50.1376565,29 49.0147059,30.1096621 49.0147059,31.5 L49.0147059,31.5 C49.0147059,32.8807119 50.1327688,34 51.5124335,34 L55.0045081,34 L66.3156937,34 C67.6980258,34 68.8186275,32.8903379 68.8186275,31.5 L68.8186275,31.5 C68.8186275,30.1192881 67.6971405,29 66.3104471,29 L51.5228862,29 Z" stroke="#92CB3E" fill="#FFFFFF"/>
<rect stroke="#ABE6F8" x="0" y="0" width="101" height="62"/>
<path d="M40.6764706,19.7142857 L40.6764706,24.5714286 C40.6764706,24.6964292 40.6184355,24.7842259 40.5023634,24.8348214 C40.4636728,24.8497025 40.4264708,24.8571429 40.3907563,24.8571429 C40.3103988,24.8571429 40.2434351,24.8288693 40.1898634,24.7723214 L38.3907563,22.9732143 L38.3907563,23.7142857 C38.3907563,24.0684542 38.2650135,24.3712785 38.0135242,24.6227679 C37.7620348,24.8742572 37.4592105,25 37.105042,25 L33.9621849,25 C33.6080164,25 33.3051921,24.8742572 33.0537027,24.6227679 C32.8022134,24.3712785 32.6764706,24.0684542 32.6764706,23.7142857 L32.6764706,20.5714286 C32.6764706,20.2172601 32.8022134,19.9144358 33.0537027,19.6629464 C33.3051921,19.4114571 33.6080164,19.2857143 33.9621849,19.2857143 L37.105042,19.2857143 C37.4592105,19.2857143 37.7620348,19.4114571 38.0135242,19.6629464 C38.2650135,19.9144358 38.3907563,20.2172601 38.3907563,20.5714286 L38.3907563,21.3080357 L40.1898634,19.5133929 C40.2434351,19.456845 40.3103988,19.4285714 40.3907563,19.4285714 C40.4264708,19.4285714 40.4636728,19.4360118 40.5023634,19.4508929 C40.6184355,19.5014883 40.6764706,19.5892851 40.6764706,19.7142857 L40.6764706,19.7142857 Z" fill="#00AF84"/>
<path d="M22.2422969,14.5714286 L22.2422969,14.8571429 C22.2422969,14.8958335 22.2281602,14.9293153 22.1998862,14.9575893 C22.1716123,14.9858632 22.1381304,15 22.0994398,15 L18.9565826,15 C18.917892,15 18.8844102,14.9858632 18.8561362,14.9575893 C18.8278623,14.9293153 18.8137255,14.8958335 18.8137255,14.8571429 L18.8137255,14.5714286 C18.8137255,14.5327379 18.8278623,14.4992561 18.8561362,14.4709821 C18.8844102,14.4427082 18.917892,14.4285714 18.9565826,14.4285714 L22.0994398,14.4285714 C22.1381304,14.4285714 22.1716123,14.4427082 22.1998862,14.4709821 C22.2281602,14.4992561 22.2422969,14.5327379 22.2422969,14.5714286 L22.2422969,14.5714286 Z M22.2422969,13.4285714 L22.2422969,13.7142857 C22.2422969,13.7529764 22.2281602,13.7864582 22.1998862,13.8147321 C22.1716123,13.8430061 22.1381304,13.8571429 22.0994398,13.8571429 L18.9565826,13.8571429 C18.917892,13.8571429 18.8844102,13.8430061 18.8561362,13.8147321 C18.8278623,13.7864582 18.8137255,13.7529764 18.8137255,13.7142857 L18.8137255,13.4285714 C18.8137255,13.3898808 18.8278623,13.356399 18.8561362,13.328125 C18.8844102,13.299851 18.917892,13.2857143 18.9565826,13.2857143 L22.0994398,13.2857143 C22.1381304,13.2857143 22.1716123,13.299851 22.1998862,13.328125 C22.2281602,13.356399 22.2422969,13.3898808 22.2422969,13.4285714 L22.2422969,13.4285714 Z M22.2422969,12.2857143 L22.2422969,12.5714286 C22.2422969,12.6101192 22.2281602,12.643601 22.1998862,12.671875 C22.1716123,12.700149 22.1381304,12.7142857 22.0994398,12.7142857 L18.9565826,12.7142857 C18.917892,12.7142857 18.8844102,12.700149 18.8561362,12.671875 C18.8278623,12.643601 18.8137255,12.6101192 18.8137255,12.5714286 L18.8137255,12.2857143 C18.8137255,12.2470236 18.8278623,12.2135418 18.8561362,12.1852679 C18.8844102,12.1569939 18.917892,12.1428571 18.9565826,12.1428571 L22.0994398,12.1428571 C22.1381304,12.1428571 22.1716123,12.1569939 22.1998862,12.1852679 C22.2281602,12.2135418 22.2422969,12.2470236 22.2422969,12.2857143 L22.2422969,12.2857143 Z" fill="#FFFFFF"/>
<path d="M7.38935574,13.5714286 L7.38935574,13.8571429 C7.38935574,13.9360123 7.36517444,14.0033479 7.3168111,14.0591518 C7.26844776,14.1149556 7.20557637,14.1428571 7.12819503,14.1428571 L5.55676646,14.1428571 L6.21078431,14.7991071 C6.26733222,14.8526788 6.29560574,14.9196425 6.29560574,15 C6.29560574,15.0803575 6.26733222,15.1473212 6.21078431,15.2008929 L6.0433736,15.3705357 C5.9883138,15.4255955 5.92135018,15.453125 5.84248074,15.453125 C5.7650994,15.453125 5.69739175,15.4255955 5.63935574,15.3705357 L4.18623074,13.9151786 C4.13117094,13.8601188 4.10364146,13.7931552 4.10364146,13.7142857 C4.10364146,13.6369044 4.13117094,13.5691967 4.18623074,13.5111607 L5.63935574,12.0602679 C5.69590364,12.00372 5.7636113,11.9754464 5.84248074,11.9754464 C5.91986208,11.9754464 5.9868257,12.00372 6.0433736,12.0602679 L6.21078431,12.2254464 C6.26733222,12.2819943 6.29560574,12.349702 6.29560574,12.4285714 C6.29560574,12.5074409 6.26733222,12.5751485 6.21078431,12.6316964 L5.55676646,13.2857143 L7.12819503,13.2857143 C7.20557637,13.2857143 7.26844776,13.3136158 7.3168111,13.3694196 C7.36517444,13.4252235 7.38935574,13.4925591 7.38935574,13.5714286 L7.38935574,13.5714286 Z" fill="#FFFFFF"/>
<path d="M12.3403361,13.5714286 L12.3403361,13.8571429 C12.3403361,13.9360123 12.3161548,14.0033479 12.2677915,14.0591518 C12.2194282,14.1149556 12.1565568,14.1428571 12.0791754,14.1428571 L10.5077468,14.1428571 L11.1617647,14.7991071 C11.2183126,14.8526788 11.2465861,14.9196425 11.2465861,15 C11.2465861,15.0803575 11.2183126,15.1473212 11.1617647,15.2008929 L10.994354,15.3705357 C10.9392942,15.4255955 10.8723306,15.453125 10.7934611,15.453125 C10.7160798,15.453125 10.6483721,15.4255955 10.5903361,15.3705357 L9.13721113,13.9151786 C9.08215134,13.8601188 9.05462185,13.7931552 9.05462185,13.7142857 C9.05462185,13.6369044 9.08215134,13.5691967 9.13721113,13.5111607 L10.5903361,12.0602679 C10.646884,12.00372 10.7145917,11.9754464 10.7934611,11.9754464 C10.8708425,11.9754464 10.9378061,12.00372 10.994354,12.0602679 L11.1617647,12.2254464 C11.2183126,12.2819943 11.2465861,12.349702 11.2465861,12.4285714 C11.2465861,12.5074409 11.2183126,12.5751485 11.1617647,12.6316964 L10.5077468,13.2857143 L12.0791754,13.2857143 C12.1565568,13.2857143 12.2194282,13.3136158 12.2677915,13.3694196 C12.3161548,13.4252235 12.3403361,13.4925591 12.3403361,13.5714286 L12.3403361,13.5714286 Z" fill="#FFFFFF" transform="translate(10.697479, 13.714286) scale(-1, 1) translate(-10.697479, -13.714286) "/>
<path d="M17.2913165,12.1428571 L17.2913165,13.1428571 C17.2913165,13.1815478 17.2771798,13.2150296 17.2489058,13.2433036 C17.2206319,13.2715775 17.1871501,13.2857143 17.1484594,13.2857143 L16.1484594,13.2857143 C16.0859591,13.2857143 16.0420607,13.2559527 16.016763,13.1964286 C15.9914652,13.1383926 16.0018818,13.0870538 16.048013,13.0424107 L16.3560487,12.734375 C16.1358095,12.5305049 15.8761395,12.4285714 15.5770308,12.4285714 C15.4222681,12.4285714 15.2745762,12.4587051 15.1339505,12.5189732 C14.9933248,12.5792414 14.8716742,12.6607138 14.7689951,12.7633929 C14.666316,12.8660719 14.5848436,12.9877225 14.5245755,13.1283482 C14.4643073,13.2689739 14.4341737,13.4166659 14.4341737,13.5714286 C14.4341737,13.7261912 14.4643073,13.8738832 14.5245755,14.0145089 C14.5848436,14.1551346 14.666316,14.2767852 14.7689951,14.3794643 C14.8716742,14.4821434 14.9933248,14.5636158 15.1339505,14.6238839 C15.2745762,14.6841521 15.4222681,14.7142857 15.5770308,14.7142857 C15.754115,14.7142857 15.9215241,14.6755956 16.079263,14.5982143 C16.2370018,14.5208329 16.370185,14.411459 16.4788165,14.2700893 C16.4892332,14.2552083 16.5063462,14.2462798 16.5301558,14.2433036 C16.5509892,14.2433036 16.5695903,14.2499999 16.5859594,14.2633929 L16.891763,14.5714286 C16.9051559,14.5833334 16.9122243,14.5985862 16.9129683,14.6171875 C16.9137124,14.6357888 16.9081321,14.6525297 16.8962272,14.6674107 C16.734024,14.8638403 16.5375974,15.0159965 16.3069415,15.1238839 C16.0762856,15.2317714 15.8329845,15.2857143 15.5770308,15.2857143 C15.3448868,15.2857143 15.1231628,15.2403278 14.9118522,15.1495536 C14.7005417,15.0587793 14.5182518,14.9367567 14.3649772,14.7834821 C14.2117027,14.6302076 14.0896801,14.4479177 13.9989058,14.2366071 C13.9081315,14.0252966 13.8627451,13.8035726 13.8627451,13.5714286 C13.8627451,13.3392846 13.9081315,13.1175606 13.9989058,12.90625 C14.0896801,12.6949394 14.2117027,12.5126496 14.3649772,12.359375 C14.5182518,12.2061004 14.7005417,12.0840778 14.9118522,11.9933036 C15.1231628,11.9025293 15.3448868,11.8571429 15.5770308,11.8571429 C15.7957819,11.8571429 16.0074613,11.8984371 16.2120755,11.9810268 C16.4166896,12.0636165 16.5986074,12.1800588 16.7578344,12.3303571 L17.048013,12.0424107 C17.0911679,11.9962795 17.1432507,11.985863 17.204263,12.0111607 C17.262299,12.0364585 17.2913165,12.0803568 17.2913165,12.1428571 L17.2913165,12.1428571 Z" fill="#FFFFFF"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="51px" height="36px" viewBox="0 0 51 36" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(0.500000, 0.000000)">
<path d="M50,2.96428571 L50,33.3214286 C50,34.1026825 49.6372804,34.6514121 48.9118304,34.9676339 C48.6700137,35.0606403 48.4375011,35.1071429 48.2142857,35.1071429 C47.7120511,35.1071429 47.2935285,34.9304333 46.9587054,34.5770089 L35.7142857,23.3325893 L35.7142857,27.9642857 C35.7142857,30.1778384 34.9283933,32.0704907 33.3565848,33.6422991 C31.7847764,35.2141076 29.8921242,36 27.6785714,36 L8.03571429,36 C5.82216155,36 3.92950935,35.2141076 2.35770089,33.6422991 C0.785892439,32.0704907 0,30.1778384 0,27.9642857 L0,8.32142857 C0,6.10787584 0.785892439,4.21522363 2.35770089,2.64341518 C3.92950935,1.07160672 5.82216155,0.285714286 8.03571429,0.285714286 L27.6785714,0.285714286 C29.8921242,0.285714286 31.7847764,1.07160672 33.3565848,2.64341518 C34.9283933,4.21522363 35.7142857,6.10787584 35.7142857,8.32142857 L35.7142857,12.9252232 L46.9587054,1.70870536 C47.2935285,1.35528097 47.7120511,1.17857143 48.2142857,1.17857143 C48.4375011,1.17857143 48.6700137,1.22507394 48.9118304,1.31808036 C49.6372804,1.63430218 50,2.18303181 50,2.96428571 L50,2.96428571 Z" fill="#00AF84"/>
<path d="M19.8214286,22.09375 L19.8214286,25.4419643 C19.8214286,25.5907746 19.7656256,25.7209816 19.6540179,25.8325893 C19.5424102,25.944197 19.4122031,26 19.2633929,26 L15.9151786,26 C15.7663683,26 15.6361613,25.944197 15.5245536,25.8325893 C15.4129459,25.7209816 15.3571429,25.5907746 15.3571429,25.4419643 L15.3571429,22.09375 C15.3571429,21.9449397 15.4129459,21.8147327 15.5245536,21.703125 C15.6361613,21.5915173 15.7663683,21.5357143 15.9151786,21.5357143 L19.2633929,21.5357143 C19.4122031,21.5357143 19.5424102,21.5915173 19.6540179,21.703125 C19.7656256,21.8147327 19.8214286,21.9449397 19.8214286,22.09375 L19.8214286,22.09375 Z M24.2299107,13.7232143 C24.2299107,14.2254489 24.1578318,14.6951243 24.0136719,15.1322545 C23.8695119,15.5693846 23.7067531,15.9251288 23.5253906,16.1994978 C23.3440281,16.4738667 23.0882643,16.7505566 22.7580915,17.0295759 C22.4279187,17.3085951 22.1605293,17.5108811 21.9559152,17.6364397 C21.7513011,17.7619984 21.4676357,17.9270823 21.1049107,18.1316964 C20.7235844,18.3456112 20.4050422,18.6478775 20.1492746,19.0385045 C19.8935069,19.4291314 19.765625,19.7406982 19.765625,19.9732143 C19.765625,20.1313252 19.709822,20.2824584 19.5982143,20.4266183 C19.4866066,20.5707783 19.3563996,20.6428571 19.2075893,20.6428571 L15.859375,20.6428571 C15.7198654,20.6428571 15.601284,20.5568275 15.5036272,20.3847656 C15.4059705,20.2127038 15.3571429,20.0383193 15.3571429,19.8616071 L15.3571429,19.233817 C15.3571429,18.4618637 15.6594092,17.7340994 16.2639509,17.0505022 C16.8684926,16.3669051 17.5334785,15.8623528 18.2589286,15.5368304 C18.8076664,15.285713 19.1982875,15.025299 19.4308036,14.7555804 C19.6633196,14.4858617 19.7795759,14.1324427 19.7795759,13.6953125 C19.7795759,13.3046855 19.5633392,12.960567 19.1308594,12.6629464 C18.6983795,12.3653259 18.1984775,12.2165179 17.6311384,12.2165179 C17.0265967,12.2165179 16.5243696,12.3513751 16.124442,12.6210938 C15.7989195,12.8536098 15.3013426,13.3883887 14.6316964,14.2254464 C14.5107881,14.3742567 14.3666303,14.4486607 14.1992188,14.4486607 C14.087611,14.4486607 13.9713548,14.4114587 13.8504464,14.3370536 L11.5625,12.593192 C11.4415917,12.5001855 11.3695128,12.3839293 11.3462612,12.2444196 C11.3230096,12.10491 11.3485859,11.974703 11.4229911,11.8537946 C12.9110938,9.37982394 15.0688103,8.14285714 17.8962054,8.14285714 C18.6402567,8.14285714 19.3889471,8.28701493 20.1422991,8.57533482 C20.8956511,8.86365472 21.5745878,9.24962556 22.1791295,9.73325893 C22.7836712,10.2168923 23.2765978,10.8097993 23.6579241,11.5119978 C24.0392504,12.2141962 24.2299107,12.951261 24.2299107,13.7232143 L24.2299107,13.7232143 Z" fill="#FFFFFF"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -83,10 +83,20 @@ loop.standaloneRoomViews = (function(mozL10n) {
case ROOM_STATES.MEDIA_WAIT: {
var msg = mozL10n.get("call_progress_getting_media_description",
{clientShortname: mozL10n.get("clientShortname2")});
// XXX Bug 1047040 will add images to help prompt the user.
var utils = loop.shared.utils;
var isChrome = utils.isChrome(navigator.userAgent);
var isFirefox = utils.isFirefox(navigator.userAgent);
var isOpera = utils.isOpera(navigator.userAgent);
var promptMediaMessageClasses = React.addons.classSet({
"prompt-media-message": true,
"chrome": isChrome,
"firefox": isFirefox,
"opera": isOpera,
"other": !isChrome && !isFirefox && !isOpera
});
return (
React.createElement("div", {className: "room-inner-info-area"},
React.createElement("p", {className: "prompt-media-message"},
React.createElement("p", {className: promptMediaMessageClasses},
msg
)
)

View File

@ -83,10 +83,20 @@ loop.standaloneRoomViews = (function(mozL10n) {
case ROOM_STATES.MEDIA_WAIT: {
var msg = mozL10n.get("call_progress_getting_media_description",
{clientShortname: mozL10n.get("clientShortname2")});
// XXX Bug 1047040 will add images to help prompt the user.
var utils = loop.shared.utils;
var isChrome = utils.isChrome(navigator.userAgent);
var isFirefox = utils.isFirefox(navigator.userAgent);
var isOpera = utils.isOpera(navigator.userAgent);
var promptMediaMessageClasses = React.addons.classSet({
"prompt-media-message": true,
"chrome": isChrome,
"firefox": isFirefox,
"opera": isOpera,
"other": !isChrome && !isFirefox && !isOpera
});
return (
<div className="room-inner-info-area">
<p className="prompt-media-message">
<p className={promptMediaMessageClasses}>
{msg}
</p>
</div>

View File

@ -157,7 +157,7 @@ struct EventNameMapping
{
// This holds pointers to nsGkAtoms members, and is therefore safe as a
// non-owning reference.
nsIAtom* MOZ_OWNING_REF mAtom;
nsIAtom* MOZ_NON_OWNING_REF mAtom;
uint32_t mId;
int32_t mType;
mozilla::EventClassID mEventClassID;

View File

@ -120,27 +120,19 @@ nsStyleLinkElement::SetLineNumber(uint32_t aLineNumber)
}
/* static */ bool
nsStyleLinkElement::IsImportEnabled(nsIPrincipal* aPrincipal)
nsStyleLinkElement::IsImportEnabled()
{
static bool sAdded = false;
static bool sWebComponentsEnabled;
static bool sImportsEnabled;
if (!sAdded) {
// This part runs only once because of the static flag.
Preferences::AddBoolVarCache(&sWebComponentsEnabled,
"dom.webcomponents.enabled",
Preferences::AddBoolVarCache(&sImportsEnabled,
"dom.htmlimports.enabled",
false);
sAdded = true;
}
if (sWebComponentsEnabled) {
return true;
}
// If the web components pref is not enabled, check
// if we are in a certified app because imports is enabled
// for certified apps.
return aPrincipal &&
aPrincipal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED;
return sImportsEnabled;
}
static uint32_t ToLinkMask(const nsAString& aLink, nsIPrincipal* aPrincipal)
@ -155,8 +147,8 @@ static uint32_t ToLinkMask(const nsAString& aLink, nsIPrincipal* aPrincipal)
return nsStyleLinkElement::eNEXT;
else if (aLink.EqualsLiteral("alternate"))
return nsStyleLinkElement::eALTERNATE;
else if (aLink.EqualsLiteral("import") && aPrincipal &&
nsStyleLinkElement::IsImportEnabled(aPrincipal))
else if (aLink.EqualsLiteral("import") &&
nsStyleLinkElement::IsImportEnabled())
return nsStyleLinkElement::eHTMLIMPORT;
else
return 0;

View File

@ -68,7 +68,7 @@ public:
static uint32_t ParseLinkTypes(const nsAString& aTypes,
nsIPrincipal* aPrincipal);
static bool IsImportEnabled(nsIPrincipal* aPrincipal);
static bool IsImportEnabled();
void UpdateStyleSheetInternal()
{

View File

@ -28,6 +28,7 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "nsCycleCollector.h"
#include "nsIGlobalObject.h"
#include "nsIXPConnect.h"
#include "nsJSUtils.h"
#include "nsISupportsImpl.h"
@ -1577,6 +1578,15 @@ WrapNativeParent(JSContext* cx, const T& p)
return WrapNativeParent(cx, GetParentPointer(p), GetWrapperCache(p), GetUseXBLScope(p));
}
// Specialization for the case of nsIGlobalObject, since in that case
// we can just get the JSObject* directly.
template<>
inline JSObject*
WrapNativeParent(JSContext* cx, nsIGlobalObject* const& p)
{
return p ? p->GetGlobalJSObject() : JS::CurrentGlobalOrNull(cx);
}
template<typename T, bool WrapperCached=NativeHasMember<T>::GetParentObject>
struct GetParentObject
{

View File

@ -16,8 +16,9 @@ DataContainerEvent::DataContainerEvent(EventTarget* aOwner,
WidgetEvent* aEvent)
: Event(aOwner, aPresContext, aEvent)
{
if (mOwner) {
if (nsIDocument* doc = mOwner->GetExtantDoc()) {
nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mOwner);
if (win) {
if (nsIDocument* doc = win->GetExtantDoc()) {
doc->WarnOnceAbout(nsIDocument::eDataContainerEvent);
}
}

View File

@ -1026,7 +1026,11 @@ Event::TimeStamp() const
return 0.0;
}
nsPerformance* perf = mOwner->GetPerformance();
nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mOwner);
if (NS_WARN_IF(!win)) {
return 0.0;
}
nsPerformance* perf = win->GetPerformance();
if (NS_WARN_IF(!perf)) {
return 0.0;
}
@ -1049,8 +1053,9 @@ Event::TimeStamp() const
bool
Event::GetPreventDefault() const
{
if (mOwner) {
if (nsIDocument* doc = mOwner->GetExtantDoc()) {
nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(mOwner));
if (win) {
if (nsIDocument* doc = win->GetExtantDoc()) {
doc->WarnOnceAbout(nsIDocument::eGetPreventDefault);
}
}
@ -1130,23 +1135,23 @@ Event::SetOwner(mozilla::dom::EventTarget* aOwner)
nsCOMPtr<nsINode> n = do_QueryInterface(aOwner);
if (n) {
mOwner = do_QueryInterface(n->OwnerDoc()->GetScopeObject());
mOwner = n->OwnerDoc()->GetScopeObject();
return;
}
nsCOMPtr<nsPIDOMWindow> w = do_QueryInterface(aOwner);
if (w) {
if (w->IsOuterWindow()) {
mOwner = w->GetCurrentInnerWindow();
mOwner = do_QueryInterface(w->GetCurrentInnerWindow());
} else {
mOwner.swap(w);
mOwner = do_QueryInterface(w);
}
return;
}
nsCOMPtr<DOMEventTargetHelper> eth = do_QueryInterface(aOwner);
if (eth) {
mOwner = eth->GetOwner();
mOwner = eth->GetParentObject();
return;
}

View File

@ -19,6 +19,7 @@
#include "nsIScriptGlobalObject.h"
#include "Units.h"
#include "js/TypeDecls.h"
#include "nsIGlobalObject.h"
class nsIContent;
class nsIDOMEventTarget;
@ -63,15 +64,6 @@ private:
WidgetEvent* aEvent);
public:
void GetParentObject(nsIScriptGlobalObject** aParentObject)
{
if (mOwner) {
CallQueryInterface(mOwner, aParentObject);
} else {
*aParentObject = nullptr;
}
}
static Event* FromSupports(nsISupports* aSupports)
{
nsIDOMEvent* event =
@ -93,7 +85,7 @@ public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Event)
nsISupports* GetParentObject()
nsIGlobalObject* GetParentObject()
{
return mOwner;
}
@ -274,7 +266,7 @@ protected:
mozilla::WidgetEvent* mEvent;
nsRefPtr<nsPresContext> mPresContext;
nsCOMPtr<EventTarget> mExplicitOriginalTarget;
nsCOMPtr<nsPIDOMWindow> mOwner; // nsPIDOMWindow for now.
nsCOMPtr<nsIGlobalObject> mOwner;
bool mEventIsInternal;
bool mPrivateDataDuplicated;
bool mIsMainThreadEvent;

View File

@ -275,7 +275,7 @@ HTMLLinkElement::UpdateImport()
return;
}
if (!nsStyleLinkElement::IsImportEnabled(NodePrincipal())) {
if (!nsStyleLinkElement::IsImportEnabled()) {
// For now imports are hidden behind a pref...
return;
}

View File

@ -28,7 +28,6 @@
#include "nsIEffectiveTLDService.h"
#include "nsIIDNService.h"
#include "nsCRT.h"
#include "mozilla/plugins/PluginTypes.h"
#ifdef XP_WIN
#include "nsIWindowsRegKey.h"
@ -37,6 +36,7 @@
namespace mozilla {
namespace plugins {
class PluginAsyncSurrogate;
class PluginTag;
} // namespace mozilla
} // namespace plugins

View File

@ -2228,6 +2228,7 @@ XMLHttpRequest::Abort(ErrorResult& aRv)
if (mCanceled) {
aRv.ThrowUncatchableException();
return;
}
if (!mProxy) {

View File

@ -1,10 +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/.
include $(topsrcdir)/config/rules.mk
# Due to bug 796023, we can't have -DUNICODE and -D_UNICODE; defining those
# macros changes the type of LOGFONT to LOGFONTW instead of LOGFONTA. This
# changes the symbol names of exported C++ functions that use LOGFONT.
DEFINES := $(filter-out -DUNICODE -D_UNICODE,$(DEFINES))

View File

@ -170,3 +170,10 @@ CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk2', 'gtk3', 'gonk', 'qt'):
CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
if CONFIG['OS_ARCH'] == 'WINNT':
# Due to bug 796023, we can't have -DUNICODE and -D_UNICODE; defining those
# macros changes the type of LOGFONT to LOGFONTW instead of LOGFONTA. This
# changes the symbol names of exported C++ functions that use LOGFONT.
del DEFINES['UNICODE']
del DEFINES['_UNICODE']

View File

@ -1,7 +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/.
include $(topsrcdir)/config/rules.mk
DEFINES := $(filter-out -DUNICODE,$(DEFINES))

View File

@ -157,3 +157,6 @@ CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
CXXFLAGS += CONFIG['TK_CFLAGS']
CFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
CFLAGS += CONFIG['TK_CFLAGS']
if CONFIG['OS_ARCH'] == 'WINNT':
del DEFINES['UNICODE']

View File

@ -244,7 +244,7 @@ already_AddRefed<nsIDOMWindowUtils>
APZCCallbackHelper::GetDOMWindowUtils(const nsIContent* aContent)
{
nsCOMPtr<nsIDOMWindowUtils> utils;
nsIDocument* doc = aContent->GetCurrentDoc();
nsIDocument* doc = aContent->GetComposedDoc();
if (doc) {
utils = GetDOMWindowUtils(doc);
}

View File

@ -4,7 +4,5 @@
include $(topsrcdir)/config/rules.mk
DEFINES := $(filter-out -DUNICODE,$(DEFINES))
DeprecatedPremultiplyTables.h: $(srcdir)/genTables.py
$(PYTHON) $(srcdir)/genTables.py

View File

@ -1067,6 +1067,11 @@ gfxUserFontSet::UserFontCache::CacheFont(gfxFontEntry* aFontEntry,
NS_ASSERTION(aFontEntry->mFamilyName.Length() != 0,
"caching a font associated with no family yet");
// if caching is disabled, simply return
if (Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
return;
}
gfxUserFontData* data = aFontEntry->mUserFontData;
if (data->mIsBuffer) {
#ifdef DEBUG_USERFONT_CACHE
@ -1143,7 +1148,8 @@ gfxUserFontSet::UserFontCache::GetFont(nsIURI* aSrcURI,
gfxUserFontEntry* aUserFontEntry,
bool aPrivate)
{
if (!sUserFonts) {
if (!sUserFonts ||
Preferences::GetBool("gfx.downloadable_fonts.disable_cache")) {
return nullptr;
}

View File

@ -1973,13 +1973,6 @@ public:
mSoftwareVsyncRate = TimeDuration::FromMilliseconds(rate);
}
virtual ~D3DVsyncDisplay()
{
MOZ_ASSERT(NS_IsMainThread());
DisableVsync();
delete mVsyncThread;
}
virtual void EnableVsync() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
@ -2082,6 +2075,13 @@ public:
}
private:
virtual ~D3DVsyncDisplay()
{
MOZ_ASSERT(NS_IsMainThread());
DisableVsync();
delete mVsyncThread;
}
bool IsInVsyncThread()
{
return mVsyncThread->thread_id() == PlatformThread::CurrentId();

View File

@ -302,3 +302,6 @@ DEFINES['GRAPHITE2_STATIC'] = True
if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
DEFINES['OTS_DLL'] = True
if CONFIG['OS_ARCH'] == 'WINNT':
del DEFINES['UNICODE']

View File

@ -22,6 +22,8 @@ ${INCLUDES}
using namespace std;
using base::Thread;
namespace mozilla {
namespace _ipdltest {

View File

@ -20,6 +20,9 @@ struct RunnableMethodTraits<mozilla::_ipdltest2::TestOpensOpenedChild>
using namespace mozilla::ipc;
using base::ProcessHandle;
using base::Thread;
namespace mozilla {
// NB: this is generally bad style, but I am lazy.
using namespace _ipdltest;

View File

@ -311,6 +311,7 @@ selfhosting_srcs := \
$(srcdir)/builtin/Map.js \
$(srcdir)/builtin/Number.js \
$(srcdir)/builtin/Object.js \
$(srcdir)/builtin/RegExp.js \
$(srcdir)/builtin/String.js \
$(srcdir)/builtin/Set.js \
$(srcdir)/builtin/TypedArray.js \

View File

@ -689,7 +689,10 @@ js::obj_create(JSContext *cx, unsigned argc, Value *vp)
/* 15.2.3.5 step 4. */
if (args.hasDefined(1)) {
if (args[1].isPrimitive()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args[1], NullPtr());
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes);
return false;
}

View File

@ -328,67 +328,6 @@ regexp_toString(JSContext *cx, unsigned argc, Value *vp)
return CallNonGenericMethod<IsRegExp, regexp_toString_impl>(cx, args);
}
/* ES6 draft rev29 21.2.5.3 RegExp.prototype.flags */
bool
regexp_flags(JSContext *cx, unsigned argc, JS::Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
/* Steps 1-2. */
if (!args.thisv().isObject()) {
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.thisv(), NullPtr());
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
bytes, "not an object");
js_free(bytes);
return false;
}
RootedObject thisObj(cx, &args.thisv().toObject());
/* Step 3. */
StringBuffer sb(cx);
/* Steps 4-6. */
RootedValue global(cx);
if (!GetProperty(cx, thisObj, thisObj, cx->names().global, &global))
return false;
if (ToBoolean(global) && !sb.append('g'))
return false;
/* Steps 7-9. */
RootedValue ignoreCase(cx);
if (!GetProperty(cx, thisObj, thisObj, cx->names().ignoreCase, &ignoreCase))
return false;
if (ToBoolean(ignoreCase) && !sb.append('i'))
return false;
/* Steps 10-12. */
RootedValue multiline(cx);
if (!GetProperty(cx, thisObj, thisObj, cx->names().multiline, &multiline))
return false;
if (ToBoolean(multiline) && !sb.append('m'))
return false;
/* Steps 13-15. */
RootedValue unicode(cx);
if (!GetProperty(cx, thisObj, thisObj, cx->names().unicode, &unicode))
return false;
if (ToBoolean(unicode) && !sb.append('u'))
return false;
/* Steps 16-18. */
RootedValue sticky(cx);
if (!GetProperty(cx, thisObj, thisObj, cx->names().sticky, &sticky))
return false;
if (ToBoolean(sticky) && !sb.append('y'))
return false;
/* Step 19. */
args.rval().setString(sb.finishString());
return true;
}
/* ES6 draft rev32 21.2.5.4. */
MOZ_ALWAYS_INLINE bool
regexp_global_impl(JSContext *cx, CallArgs args)
@ -499,7 +438,7 @@ regexp_sticky(JSContext *cx, unsigned argc, JS::Value *vp)
}
static const JSPropertySpec regexp_properties[] = {
JS_PSG("flags", regexp_flags, 0),
JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0),
JS_PSG("global", regexp_global, 0),
JS_PSG("ignoreCase", regexp_ignoreCase, 0),
JS_PSG("multiline", regexp_multiline, 0),

38
js/src/builtin/RegExp.js Normal file
View File

@ -0,0 +1,38 @@
/* 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/. */
// ES6 draft rev34 (2015/02/20) 21.2.5.3 get RegExp.prototype.flags
function RegExpFlagsGetter() {
// Steps 1-2.
var R = this;
if (!IsObject(R))
ThrowError(JSMSG_NOT_NONNULL_OBJECT, R === null ? "null" : typeof R);
// Step 3.
var result = "";
// Steps 4-6.
if (R.global)
result += "g";
// Steps 7-9.
if (R.ignoreCase)
result += "i";
// Steps 10-12.
if (R.multiline)
result += "m";
// Steps 13-15.
// TODO: Uncomment these steps when bug 1135377 is fixed.
// if (R.unicode)
// result += "u";
// Steps 16-18.
if (R.sticky)
result += "y";
// Step 19.
return result;
}

View File

@ -16,7 +16,7 @@ function WeakSet_add(value) {
// Step 5.
if (!IsObject(value))
ThrowError(JSMSG_NOT_NONNULL_OBJECT);
ThrowError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, value));
// Steps 7-8.
callFunction(std_WeakMap_set, entries, value, true);

View File

@ -123,7 +123,10 @@ WeakSetObject::construct(JSContext *cx, unsigned argc, Value *vp)
if (isOriginalAdder) {
if (keyVal.isPrimitive()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, NullPtr());
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes);
return false;
}

View File

@ -748,6 +748,7 @@ class JitCode;
typedef PreBarriered<JSObject*> PreBarrieredObject;
typedef PreBarriered<JSScript*> PreBarrieredScript;
typedef PreBarriered<jit::JitCode*> PreBarrieredJitCode;
typedef PreBarriered<JSString*> PreBarrieredString;
typedef PreBarriered<JSAtom*> PreBarrieredAtom;
typedef RelocatablePtr<JSObject*> RelocatablePtrObject;

View File

@ -908,10 +908,10 @@ class GCRuntime
void sweepBackgroundThings(ZoneList &zones, LifoAlloc &freeBlocks, ThreadType threadType);
void assertBackgroundSweepingFinished();
bool shouldCompact();
IncrementalProgress compactPhase(bool lastGC);
IncrementalProgress compactPhase(bool lastGC, JS::gcreason::Reason reason);
void sweepTypesAfterCompacting(Zone *zone);
void sweepZoneAfterCompacting(Zone *zone);
ArenaHeader *relocateArenas();
ArenaHeader *relocateArenas(JS::gcreason::Reason reason);
void updateAllCellPointersParallel(MovingTracer *trc);
void updateAllCellPointersSerial(MovingTracer *trc);
void updatePointersToRelocatedCells();

View File

@ -422,7 +422,7 @@ GetObjectAllocKindForCopy(const Nursery &nursery, JSObject *obj)
// Unboxed plain objects are sized according to the data they store.
if (obj->is<UnboxedPlainObject>()) {
size_t nbytes = obj->as<UnboxedPlainObject>().layout().size();
size_t nbytes = obj->as<UnboxedPlainObject>().layoutDontCheckGeneration().size();
return GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + nbytes);
}

View File

@ -0,0 +1,19 @@
function binaryX4(op, v, w) {
var arr = [];
var [varr, warr] = [simdToArray(v), simdToArray(w)];
[varr, warr] = [varr.map(Math.fround), warr.map(Math.fround)];
for (var i = 0; i < 4; i++)
arr[i] = op(varr[i], warr[i]);
return arr.map(Math.fround);
}
function assertEqX4(vec, arr) {
assertEq(vec.x, arr[0]);
assertEq(vec.y, arr[1]);
assertEq(vec.z, arr[2]);
assertEq(vec.w, arr[3]);
}
function simdToArray(vec) {
return [vec.x, vec.y, vec.z, vec.w];
}

View File

@ -0,0 +1,33 @@
load(libdir + 'simd.js');
setJitCompilerOption("ion.warmup.trigger", 50);
function maxNum(x, y) {
if (x != x)
return y;
if (y != y)
return x;
return Math.max(x, y);
}
function minNum(x, y) {
if (x != x)
return y;
if (y != y)
return x;
return Math.min(x, y);
}
function f() {
var f1 = SIMD.float32x4(1, 2, 3, 4);
var f2 = SIMD.float32x4(4, 3, 2, 1);
for (var i = 0; i < 150; i++) {
assertEqX4(SIMD.float32x4.div(f1, f2), binaryX4((x, y) => x / y, f1, f2));
assertEqX4(SIMD.float32x4.min(f1, f2), binaryX4(Math.min, f1, f2));
assertEqX4(SIMD.float32x4.max(f1, f2), binaryX4(Math.max, f1, f2));
assertEqX4(SIMD.float32x4.minNum(f1, f2), binaryX4(minNum, f1, f2));
assertEqX4(SIMD.float32x4.maxNum(f1, f2), binaryX4(maxNum, f1, f2));
}
}
f();

View File

@ -0,0 +1,40 @@
load(libdir + "simd.js");
setJitCompilerOption("ion.warmup.trigger", 50);
var helpers = (function() {
var i32 = new Int32Array(2);
var f32 = new Float32Array(i32.buffer);
return {
and: function(x, y) {
f32[0] = x;
f32[1] = y;
i32[0] = i32[0] & i32[1];
return f32[0];
},
or: function(x, y) {
f32[0] = x;
f32[1] = y;
i32[0] = i32[0] | i32[1];
return f32[0];
},
xor: function(x, y) {
f32[0] = x;
f32[1] = y;
i32[0] = i32[0] ^ i32[1];
return f32[0];
},
}
})();
function f() {
var f1 = SIMD.float32x4(1, 2, 3, 4);
var f2 = SIMD.float32x4(4, 3, 2, 1);
for (var i = 0; i < 150; i++) {
assertEqX4(SIMD.float32x4.and(f1, f2), binaryX4(helpers.and, f1, f2));
assertEqX4(SIMD.float32x4.or(f1, f2), binaryX4(helpers.or, f1, f2));
assertEqX4(SIMD.float32x4.xor(f1, f2), binaryX4(helpers.xor, f1, f2));
}
}
f();

View File

@ -0,0 +1,18 @@
function f(obj) {
return typeof obj[15];
}
function test() {
var a = [1, 2];
a.__proto__ = {15: 1337};
var b = [1, 2, 3, 4];
for (var i = 0; i < 1000; i++) {
var r = f(i % 2 ? a : b);
assertEq(r, i % 2 ? "number" : "undefined");
}
}
test();
test();
test();

View File

@ -0,0 +1,10 @@
function Foo(a, b) {
b = {};
this.b = b;
};
var a = [];
for (var i = 0; i < 50; i++)
a.push(new Foo(i, i + 1));
i = 0;
a[i].c = i;

View File

@ -0,0 +1,28 @@
function f() {
var x = [1, 2, 3];
var y = {};
x.__proto__ = y;
for (var i = 0; i < 200; i++) {
if (i == 100)
y[100000] = 15;
else
assertEq(typeof x[100000], i > 100 ? "number" : "undefined");
}
}
function g() {
var x = [1, 2, 3];
var y = {};
x.__proto__ = y;
for (var i = 0; i < 200; i++) {
if (i == 100)
y[4] = 15;
else
assertEq(typeof x[4], i > 100 ? "number" : "undefined");
}
}
f();
g();

View File

@ -107,6 +107,7 @@ check("o[~(o)]");
check("o[+ (o)]");
check("o[- (o)]");
// A few one off tests
check_one("6", (function () { 6() }), " is not a function");
check_one("Array.prototype.reverse.call(...)", (function () { Array.prototype.reverse.call('123'); }), " is read-only");
@ -114,6 +115,8 @@ check_one(`(intermediate value)[Symbol.iterator](...).next(...).value`,
function () { var [{ x }] = [null, {}]; }, " is null");
check_one(`(intermediate value)[Symbol.iterator](...).next(...).value`,
function () { ieval("let (x) { var [a, b, [c0, c1]] = [x, x, x]; }") }, " is undefined");
check_one("void 1", function() { (void 1)(); }, " is not a function");
check_one("void o[1]", function() { var o = []; (void o[1])() }, " is not a function");
// Check fallback behavior
assertThrowsInstanceOf(function () { for (let x of undefined) {} }, TypeError);

View File

@ -9167,6 +9167,16 @@ GetTemplateObjectForNative(JSContext *cx, HandleScript script, jsbytecode *pc,
return !!res;
}
#undef ADD_INT32X4_SIMD_OP_NAME_
#define ADD_FLOAT32X4_SIMD_OP_NAME_(OP) || native == js::simd_float32x4_##OP
if (false
ARITH_FLOAT32X4_SIMD_OP(ADD_FLOAT32X4_SIMD_OP_NAME_)
BITWISE_COMMONX4_SIMD_OP(ADD_FLOAT32X4_SIMD_OP_NAME_))
{
Rooted<SimdTypeDescr *> descr(cx, &cx->global()->float32x4TypeDescr().as<SimdTypeDescr>());
res.set(cx->compartment()->jitCompartment()->getSimdTemplateObjectFor(cx, descr));
return !!res;
}
#undef ADD_FLOAT32X4_SIMD_OP_NAME_
}
return true;

View File

@ -805,10 +805,10 @@ class IonBuilder
// SIMD intrinsics and natives.
InliningStatus inlineConstructSimdObject(CallInfo &callInfo, SimdTypeDescr *target);
InliningStatus inlineSimdInt32x4BinaryArith(CallInfo &callInfo, JSNative native,
MSimdBinaryArith::Operation op);
InliningStatus inlineSimdInt32x4BinaryBitwise(CallInfo &callInfo, JSNative native,
MSimdBinaryBitwise::Operation op);
template <typename T>
InliningStatus inlineBinarySimd(CallInfo &callInfo, JSNative native,
typename T::Operation op, SimdTypeDescr::Type type);
// Utility intrinsics.
InliningStatus inlineIsCallable(CallInfo &callInfo);

View File

@ -3258,6 +3258,160 @@ GetElementIC::attachDenseElement(JSContext *cx, HandleScript outerScript, IonScr
return linkAndAttachStub(cx, masm, attacher, ion, "dense array");
}
/* static */ bool
GetElementIC::canAttachDenseElementHole(JSObject *obj, const Value &idval, TypedOrValueRegister output)
{
if (!idval.isInt32())
return false;
if (!output.hasValue())
return false;
if (!obj->isNative())
return false;
if (obj->as<NativeObject>().getDenseInitializedLength() == 0)
return false;
while (obj) {
if (obj->isIndexed())
return false;
if (ClassCanHaveExtraProperties(obj->getClass()))
return false;
JSObject *proto = obj->getProto();
if (!proto)
break;
if (!proto->isNative())
return false;
// Make sure objects on the prototype don't have dense elements.
if (proto->as<NativeObject>().getDenseInitializedLength() != 0)
return false;
obj = proto;
}
return true;
}
static bool
GenerateDenseElementHole(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
IonScript *ion, JSObject *obj, const Value &idval,
Register object, ConstantOrRegister index, TypedOrValueRegister output)
{
MOZ_ASSERT(GetElementIC::canAttachDenseElementHole(obj, idval, output));
MOZ_ASSERT(obj->lastProperty());
Register scratchReg = output.valueReg().scratchReg();
// Guard on the shape and group, to prevent non-dense elements from appearing.
Label failures;
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfShape()),
ImmGCPtr(obj->lastProperty()), &failures);
if (obj->hasUncacheableProto()) {
masm.loadPtr(Address(object, JSObject::offsetOfGroup()), scratchReg);
Address proto(scratchReg, ObjectGroup::offsetOfProto());
masm.branchPtr(Assembler::NotEqual, proto,
ImmMaybeNurseryPtr(obj->getProto()), &failures);
}
JSObject *pobj = obj->getProto();
while (pobj) {
MOZ_ASSERT(pobj->lastProperty());
masm.movePtr(ImmMaybeNurseryPtr(pobj), scratchReg);
if (pobj->hasUncacheableProto()) {
MOZ_ASSERT(!pobj->isSingleton());
Address groupAddr(scratchReg, JSObject::offsetOfGroup());
masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), &failures);
}
// Make sure the shape matches, to avoid non-dense elements.
masm.branchPtr(Assembler::NotEqual, Address(scratchReg, JSObject::offsetOfShape()),
ImmGCPtr(pobj->lastProperty()), &failures);
// Load elements vector.
masm.loadPtr(Address(scratchReg, NativeObject::offsetOfElements()), scratchReg);
// Also make sure there are no dense elements.
Label hole;
Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::NotEqual, initLength, Imm32(0), &failures);
pobj = pobj->getProto();
}
// Ensure the index is an int32 value.
Register indexReg = InvalidReg;
Register elementsReg = InvalidReg;
if (index.reg().hasValue()) {
indexReg = scratchReg;
MOZ_ASSERT(indexReg != InvalidReg);
ValueOperand val = index.reg().valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
// Unbox the index.
masm.unboxInt32(val, indexReg);
// Save the object register.
masm.push(object);
elementsReg = object;
} else {
MOZ_ASSERT(!index.reg().typedReg().isFloat());
indexReg = index.reg().typedReg().gpr();
elementsReg = scratchReg;
}
// Load elements vector.
masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elementsReg);
// Guard on the initialized length.
Label hole;
Address initLength(elementsReg, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);
// Load the value.
Label done;
masm.loadValue(BaseObjectElementIndex(elementsReg, indexReg), output.valueReg());
masm.branchTestMagic(Assembler::NotEqual, output.valueReg(), &done);
// Load undefined for the hole.
masm.bind(&hole);
masm.moveValue(UndefinedValue(), output.valueReg());
masm.bind(&done);
// Restore the object register.
if (elementsReg == object)
masm.pop(object);
attacher.jumpRejoin(masm);
// All failure flows through here.
masm.bind(&failures);
attacher.jumpNextStub(masm);
return true;
}
bool
GetElementIC::attachDenseElementHole(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject obj, const Value &idval)
{
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
RepatchStubAppender attacher(*this);
GenerateDenseElementHole(cx, masm, attacher, ion, obj, idval, object(), index(), output());
return linkAndAttachStub(cx, masm, attacher, ion, "dense hole");
}
/* static */ bool
GetElementIC::canAttachTypedArrayElement(JSObject *obj, const Value &idval,
TypedOrValueRegister output)
@ -3568,6 +3722,13 @@ GetElementIC::update(JSContext *cx, HandleScript outerScript, size_t cacheIndex,
return false;
attachedStub = true;
}
if (!attachedStub && cache.monitoredResult() &&
canAttachDenseElementHole(obj, idval, cache.output()))
{
if (!cache.attachDenseElementHole(cx, outerScript, ion, obj, idval))
return false;
attachedStub = true;
}
if (!attachedStub && canAttachTypedArrayElement(obj, idval, cache.output())) {
if (!cache.attachTypedArrayElement(cx, outerScript, ion, obj, idval))
return false;

View File

@ -842,6 +842,8 @@ class GetElementIC : public RepatchIonCache
static bool canAttachGetProp(JSObject *obj, const Value &idval, jsid id);
static bool canAttachDenseElement(JSObject *obj, const Value &idval);
static bool canAttachDenseElementHole(JSObject *obj, const Value &idval,
TypedOrValueRegister output);
static bool canAttachTypedArrayElement(JSObject *obj, const Value &idval,
TypedOrValueRegister output);
@ -851,6 +853,9 @@ class GetElementIC : public RepatchIonCache
bool attachDenseElement(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject obj, const Value &idval);
bool attachDenseElementHole(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject obj, const Value &idval);
bool attachTypedArrayElement(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject tarr, const Value &idval);

View File

@ -520,6 +520,9 @@ class LSimdBinaryBitwiseX4 : public LInstructionHelper<1, 2, 0>
MSimdBinaryBitwise::Operation operation() const {
return mir_->toSimdBinaryBitwise()->operation();
}
const char *extraName() const {
return MSimdBinaryBitwise::OperationName(operation());
}
MIRType type() const {
return mir_->type();
}

View File

@ -257,17 +257,29 @@ IonBuilder::inlineNativeCall(CallInfo &callInfo, JSFunction *target)
return inlineBoundFunction(callInfo, target);
// Simd functions
#define INLINE_INT32X4_SIMD_ARITH_(OP) \
if (native == js::simd_int32x4_##OP) \
return inlineSimdInt32x4BinaryArith(callInfo, native, MSimdBinaryArith::Op_##OP);
#define INLINE_INT32X4_SIMD_ARITH_(OP) \
if (native == js::simd_int32x4_##OP) \
return inlineBinarySimd<MSimdBinaryArith>(callInfo, native, MSimdBinaryArith::Op_##OP, \
SimdTypeDescr::TYPE_INT32);
ARITH_COMMONX4_SIMD_OP(INLINE_INT32X4_SIMD_ARITH_)
#undef INLINE_INT32X4_SIMD_ARITH_
#define INLINE_INT32X4_SIMD_BITWISE_(OP) \
if (native == js::simd_int32x4_##OP) \
return inlineSimdInt32x4BinaryBitwise(callInfo, native, MSimdBinaryBitwise::OP##_);
BITWISE_COMMONX4_SIMD_OP(INLINE_INT32X4_SIMD_BITWISE_)
#undef INLINE_INT32X4_SIMD_BITWISE_
#define INLINE_FLOAT32X4_SIMD_ARITH_(OP) \
if (native == js::simd_float32x4_##OP) \
return inlineBinarySimd<MSimdBinaryArith>(callInfo, native, MSimdBinaryArith::Op_##OP, \
SimdTypeDescr::TYPE_FLOAT32);
ARITH_FLOAT32X4_SIMD_OP(INLINE_FLOAT32X4_SIMD_ARITH_)
#undef INLINE_FLOAT32X4_SIMD_ARITH_
#define INLINE_SIMD_BITWISE_(OP) \
if (native == js::simd_int32x4_##OP) \
return inlineBinarySimd<MSimdBinaryBitwise>(callInfo, native, MSimdBinaryBitwise::OP##_, \
SimdTypeDescr::TYPE_INT32); \
if (native == js::simd_float32x4_##OP) \
return inlineBinarySimd<MSimdBinaryBitwise>(callInfo, native, MSimdBinaryBitwise::OP##_, \
SimdTypeDescr::TYPE_FLOAT32);
BITWISE_COMMONX4_SIMD_OP(INLINE_SIMD_BITWISE_)
#undef INLINE_SIMD_BITWISE_
return InliningStatus_NotInlined;
}
@ -2860,41 +2872,21 @@ IonBuilder::inlineConstructSimdObject(CallInfo &callInfo, SimdTypeDescr *descr)
return InliningStatus_Inlined;
}
IonBuilder::InliningStatus
IonBuilder::inlineSimdInt32x4BinaryArith(CallInfo &callInfo, JSNative native,
MSimdBinaryArith::Operation op)
static MIRType
SimdTypeDescrToMIRType(SimdTypeDescr::Type type)
{
if (callInfo.argc() != 2)
return InliningStatus_NotInlined;
JSObject *templateObject = inspector->getTemplateObjectForNative(pc, native);
if (!templateObject)
return InliningStatus_NotInlined;
InlineTypedObject *inlineTypedObject = &templateObject->as<InlineTypedObject>();
MOZ_ASSERT(inlineTypedObject->typeDescr().as<SimdTypeDescr>().type() == js::Int32x4::type);
// If the type of any of the arguments is neither a SIMD type, an Object
// type, or a Value, then the applyTypes phase will add a fallible box &
// unbox sequence. This does not matter much as the binary arithmetic
// instruction is supposed to produce a TypeError once it is called.
MSimdBinaryArith *ins = MSimdBinaryArith::New(alloc(), callInfo.getArg(0), callInfo.getArg(1),
op, MIRType_Int32x4);
MSimdBox *obj = MSimdBox::New(alloc(), constraints(), ins, inlineTypedObject,
inlineTypedObject->group()->initialHeap(constraints()));
current->add(ins);
current->add(obj);
current->push(obj);
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
switch (type) {
case SimdTypeDescr::TYPE_FLOAT32: return MIRType_Float32x4;
case SimdTypeDescr::TYPE_INT32: return MIRType_Int32x4;
case SimdTypeDescr::TYPE_FLOAT64: break;
}
MOZ_CRASH("unexpected SimdTypeDescr");
}
template<typename T>
IonBuilder::InliningStatus
IonBuilder::inlineSimdInt32x4BinaryBitwise(CallInfo &callInfo, JSNative native,
MSimdBinaryBitwise::Operation op)
IonBuilder::inlineBinarySimd(CallInfo &callInfo, JSNative native, typename T::Operation op,
SimdTypeDescr::Type type)
{
if (callInfo.argc() != 2)
return InliningStatus_NotInlined;
@ -2904,14 +2896,14 @@ IonBuilder::inlineSimdInt32x4BinaryBitwise(CallInfo &callInfo, JSNative native,
return InliningStatus_NotInlined;
InlineTypedObject *inlineTypedObject = &templateObject->as<InlineTypedObject>();
MOZ_ASSERT(inlineTypedObject->typeDescr().as<SimdTypeDescr>().type() == js::Int32x4::type);
MOZ_ASSERT(inlineTypedObject->typeDescr().as<SimdTypeDescr>().type() == type);
// If the type of any of the arguments is neither a SIMD type, an Object
// type, or a Value, then the applyTypes phase will add a fallible box &
// unbox sequence. This does not matter much as the binary bitwise
// instruction is supposed to produce a TypeError once it is called.
MSimdBinaryBitwise *ins = MSimdBinaryBitwise::New(alloc(), callInfo.getArg(0), callInfo.getArg(1),
op, MIRType_Int32x4);
T *ins = T::New(alloc(), callInfo.getArg(0), callInfo.getArg(1), op,
SimdTypeDescrToMIRType(type));
MSimdBox *obj = MSimdBox::New(alloc(), constraints(), ins, inlineTypedObject,
inlineTypedObject->group()->initialHeap(constraints()));

View File

@ -2036,6 +2036,15 @@ class MSimdBinaryBitwise
xor_
};
static const char* OperationName(Operation op) {
switch (op) {
case and_: return "and";
case or_: return "or";
case xor_: return "xor";
}
MOZ_CRASH("unexpected operation");
}
private:
Operation operation_;

View File

@ -80,7 +80,7 @@ MSG_DEF(JSMSG_BAD_GENERATOR_YIELD, 1, JSEXN_TYPEERR, "yield from closing gen
MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty array with no initial value")
MSG_DEF(JSMSG_UNEXPECTED_TYPE, 2, JSEXN_TYPEERR, "{0} is {1}")
MSG_DEF(JSMSG_MISSING_FUN_ARG, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 0, JSEXN_TYPEERR, "value is not a non-null object")
MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 1, JSEXN_TYPEERR, "{0} is not a non-null object")
MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 1, JSEXN_TYPEERR, "{0} is not extensible")
MSG_DEF(JSMSG_CANT_REDEFINE_PROP, 1, JSEXN_TYPEERR, "can't redefine non-configurable property '{0}'")

View File

@ -2145,7 +2145,7 @@ inline int CheckIsStrictPropertyOp(JSStrictPropertyOp op);
#define JS_SELF_HOSTED_GET(name, getterName, flags) \
{name, \
uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_GETTER), \
{ nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo *) }, \
{ { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo *) } }, \
JSNATIVE_WRAPPER(nullptr) }
#define JS_SELF_HOSTED_GETSET(name, getterName, setterName, flags) \
{name, \

View File

@ -495,14 +495,14 @@ JSCompartment::wrap(JSContext *cx, MutableHandle<PropDesc> desc)
}
/*
* This method marks pointers that cross compartment boundaries. It should be
* called only for per-compartment GCs, since full GCs naturally follow pointers
* across compartments.
* This method marks pointers that cross compartment boundaries. It is called in
* per-zone GCs (since full GCs naturally follow pointers across compartments)
* and when compacting to update cross-compartment pointers.
*/
void
JSCompartment::markCrossCompartmentWrappers(JSTracer *trc)
{
MOZ_ASSERT(!zone()->isCollecting());
MOZ_ASSERT(!zone()->isCollecting() || trc->runtime()->isHeapCompacting());
for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
Value v = e.front().value();

View File

@ -1952,7 +1952,7 @@ size_t ArenaHeader::countUsedCells()
}
ArenaHeader *
ArenaList::removeRemainingArenas(ArenaHeader **arenap, const AutoLockGC &lock)
ArenaList::removeRemainingArenas(ArenaHeader **arenap)
{
// This is only ever called to remove arenas that are after the cursor, so
// we don't need to update it.
@ -1966,53 +1966,52 @@ ArenaList::removeRemainingArenas(ArenaHeader **arenap, const AutoLockGC &lock)
return remainingArenas;
}
/*
* Choose which arenas to relocate all cells out of and remove them from the
* arena list. Return the head of a list of arenas to relocate.
*/
ArenaHeader *
ArenaList::pickArenasToRelocate(JSRuntime *runtime)
static bool
ShouldRelocateAllArenas(JSRuntime *runtime)
{
AutoLockGC lock(runtime);
check();
if (isEmpty())
return nullptr;
// In zeal mode and in debug builds on 64 bit architectures, we relocate all
// arenas. The purpose of this is to balance test coverage of object moving
// with test coverage of the arena selection routine below.
bool relocateAll = runtime->gc.zeal() == ZealCompactValue;
// In compacting zeal mode and in debug builds on 64 bit architectures, we
// relocate all arenas. The purpose of this is to balance test coverage of
// object moving with test coverage of the arena selection routine in
// pickArenasToRelocate().
#if defined(DEBUG) && defined(JS_PUNBOX64)
relocateAll = true;
return true;
#else
return runtime->gc.zeal() == ZealCompactValue;
#endif
if (relocateAll) {
ArenaHeader *allArenas = head();
clear();
return allArenas;
}
}
// Otherwise we relocate the greatest number of arenas such that the number
// of used cells in relocated arenas is less than or equal to the number of
// free cells in unrelocated arenas. In other words we only relocate cells
// we can move into existing arenas, and we choose the least full areans to
// relocate.
/*
* Choose which arenas to relocate all cells from. Return an arena cursor that
* can be passed to removeRemainingArenas().
*/
ArenaHeader **
ArenaList::pickArenasToRelocate(size_t &arenaTotalOut, size_t &relocTotalOut)
{
// Relocate the greatest number of arenas such that the number of used cells
// in relocated arenas is less than or equal to the number of free cells in
// unrelocated arenas. In other words we only relocate cells we can move
// into existing arenas, and we choose the least full areans to relocate.
//
// This is made easier by the fact that the arena list has been sorted in
// descending order of number of used cells, so we will always relocate a
// tail of the arena list. All we need to do is find the point at which to
// start relocating.
check();
if (isCursorAtEnd())
return nullptr;
ArenaHeader **arenap = cursorp_; // Next arena to consider
size_t previousFreeCells = 0; // Count of free cells before
size_t followingUsedCells = 0; // Count of used cells after arenap.
size_t arenaCount = 0; // Total number of arenas.
size_t arenaIndex = 0; // Index of the next arena to consider.
// Count of used cells after arenap.
size_t followingUsedCells = 0;
for (ArenaHeader *arena = *arenap; arena; arena = arena->next)
for (ArenaHeader *arena = *cursorp_; arena; arena = arena->next) {
followingUsedCells += arena->countUsedCells();
arenaCount++;
}
mozilla::DebugOnly<size_t> lastFreeCells(0);
size_t cellsPerArena = Arena::thingsPerArena((*arenap)->getThingSize());
@ -2020,7 +2019,8 @@ ArenaList::pickArenasToRelocate(JSRuntime *runtime)
while (*arenap) {
ArenaHeader *arena = *arenap;
if (followingUsedCells <= previousFreeCells)
return removeRemainingArenas(arenap, lock);
break;
size_t freeCells = arena->countFreeCells();
size_t usedCells = cellsPerArena - freeCells;
followingUsedCells -= usedCells;
@ -2030,10 +2030,16 @@ ArenaList::pickArenasToRelocate(JSRuntime *runtime)
#endif
previousFreeCells += freeCells;
arenap = &arena->next;
arenaIndex++;
}
check();
return nullptr;
size_t relocCount = arenaCount - arenaIndex;
MOZ_ASSERT(relocCount < arenaCount);
MOZ_ASSERT((relocCount == 0) == (!*arenap));
arenaTotalOut += arenaCount;
relocTotalOut += relocCount;
return arenap;
}
#ifdef DEBUG
@ -2148,36 +2154,79 @@ ArenaList::relocateArenas(ArenaHeader *toRelocate, ArenaHeader *relocated,
return relocated;
}
ArenaHeader *
ArenaLists::relocateArenas(ArenaHeader *relocatedList, gcstats::Statistics& stats)
// Skip compacting zones unless we can free a certain proportion of their GC
// heap memory.
static const double MIN_ZONE_RECLAIM_PERCENT = 2.0;
static bool ShouldRelocateZone(size_t arenaCount, size_t relocCount, JS::gcreason::Reason reason)
{
if (relocCount == 0)
return false;
if (reason == JS::gcreason::MEM_PRESSURE || reason == JS::gcreason::LAST_DITCH)
return true;
return (relocCount * 100.0) / arenaCount >= MIN_ZONE_RECLAIM_PERCENT;
}
bool
ArenaLists::relocateArenas(ArenaHeader *&relocatedListOut, JS::gcreason::Reason reason,
gcstats::Statistics& stats)
{
// This is only called from the main thread while we are doing a GC, so
// there is no need to lock.
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
MOZ_ASSERT(runtime_->isHeapCompacting());
MOZ_ASSERT(!runtime_->gc.isBackgroundSweeping());
// Flush all the freeLists back into the arena headers
purge();
checkEmptyFreeLists();
for (size_t i = 0; i < FINALIZE_LIMIT; i++) {
if (CanRelocateAllocKind(AllocKind(i))) {
ArenaList &al = arenaLists[i];
ArenaHeader *toRelocate = al.pickArenasToRelocate(runtime_);
if (toRelocate)
relocatedList = al.relocateArenas(toRelocate, relocatedList, stats);
if (ShouldRelocateAllArenas(runtime_)) {
for (size_t i = 0; i < FINALIZE_LIMIT; i++) {
if (CanRelocateAllocKind(AllocKind(i))) {
ArenaList &al = arenaLists[i];
ArenaHeader *allArenas = al.head();
al.clear();
relocatedListOut = al.relocateArenas(allArenas, relocatedListOut, stats);
}
}
} else {
size_t arenaCount = 0;
size_t relocCount = 0;
ArenaHeader **toRelocate[FINALIZE_LIMIT] = {nullptr};
for (size_t i = 0; i < FINALIZE_LIMIT; i++) {
if (CanRelocateAllocKind(AllocKind(i)))
toRelocate[i] = arenaLists[i].pickArenasToRelocate(arenaCount, relocCount);
}
if (!ShouldRelocateZone(arenaCount, relocCount, reason))
return false;
for (size_t i = 0; i < FINALIZE_LIMIT; i++) {
if (toRelocate[i]) {
ArenaList &al = arenaLists[i];
ArenaHeader *arenas = al.removeRemainingArenas(toRelocate[i]);
relocatedListOut = al.relocateArenas(arenas, relocatedListOut, stats);
}
}
}
/*
* When we allocate new locations for cells, we use
* allocateFromFreeList(). Reset the free list again so that
* AutoCopyFreeListToArenasForGC doesn't complain that the free lists
* are different now.
*/
// When we allocate new locations for cells, we use
// allocateFromFreeList(). Reset the free list again so that
// AutoCopyFreeListToArenasForGC doesn't complain that the free lists are
// different now.
purge();
checkEmptyFreeLists();
return relocatedList;
return true;
}
ArenaHeader *
GCRuntime::relocateArenas()
GCRuntime::relocateArenas(JS::gcreason::Reason reason)
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT_MOVE);
@ -2187,9 +2236,9 @@ GCRuntime::relocateArenas()
MOZ_ASSERT(!zone->isPreservingCode());
if (CanRelocateZone(rt, zone)) {
zone->setGCState(Zone::Compact);
jit::StopAllOffThreadCompilations(zone);
relocatedList = zone->arenas.relocateArenas(relocatedList, stats);
if (zone->arenas.relocateArenas(relocatedList, reason, stats))
zone->setGCState(Zone::Compact);
}
}
@ -2244,7 +2293,6 @@ GCRuntime::sweepZoneAfterCompacting(Zone *zone)
for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
c->sweepInnerViews();
c->sweepCrossCompartmentWrappers();
c->sweepBaseShapeTable();
c->sweepInitialShapeTable();
c->objectGroups.sweep(fop);
@ -2389,14 +2437,16 @@ ArenasToUpdate::next(AutoLockHelperThreadState& lock)
initialized = true;
for (; !zone.done(); zone.next()) {
for (kind = 0; kind < FINALIZE_LIMIT; ++kind) {
if (shouldProcessKind(kind)) {
for (arena = zone.get()->arenas.getFirstArena(AllocKind(kind));
arena;
arena = arena->next)
{
return arena;
resumePoint:;
if (zone->isGCCompacting()) {
for (kind = 0; kind < FINALIZE_LIMIT; ++kind) {
if (shouldProcessKind(kind)) {
for (arena = zone.get()->arenas.getFirstArena(AllocKind(kind));
arena;
arena = arena->next)
{
return arena;
resumePoint:;
}
}
}
}
@ -2560,8 +2610,10 @@ GCRuntime::updatePointersToRelocatedCells()
comp->fixupAfterMovingGC();
// Fixup cross compartment wrappers as we assert the existence of wrappers in the map.
for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next())
for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) {
comp->sweepCrossCompartmentWrappers();
comp->markCrossCompartmentWrappers(&trc);
}
// Iterate through all cells that can contain JSObject pointers to update
// them. Since updating each cell is independent we try to parallelize this
@ -2595,7 +2647,7 @@ GCRuntime::updatePointersToRelocatedCells()
WatchpointMap::sweepAll(rt);
Debugger::sweepAll(rt->defaultFreeOp());
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
if (CanRelocateZone(rt, zone))
if (zone->isGCCompacting())
rt->gc.sweepZoneAfterCompacting(zone);
}
@ -5419,7 +5471,7 @@ GCRuntime::endSweepPhase(bool lastGC)
}
GCRuntime::IncrementalProgress
GCRuntime::compactPhase(bool lastGC)
GCRuntime::compactPhase(bool lastGC, JS::gcreason::Reason reason)
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT);
@ -5435,8 +5487,9 @@ GCRuntime::compactPhase(bool lastGC)
MOZ_ASSERT(rt->gc.nursery.isEmpty());
assertBackgroundSweepingFinished();
ArenaHeader *relocatedList = relocateArenas();
updatePointersToRelocatedCells();
ArenaHeader *relocatedList = relocateArenas(reason);
if (relocatedList)
updatePointersToRelocatedCells();
#ifdef DEBUG
for (ArenaHeader *arena = relocatedList; arena; arena = arena->next) {
@ -5470,7 +5523,7 @@ GCRuntime::compactPhase(bool lastGC)
#ifdef DEBUG
CheckHashTablesAfterMovingGC(rt);
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
if (CanRelocateZone(rt, zone)) {
if (zone->isGCCompacting()) {
MOZ_ASSERT(!zone->isPreservingCode());
zone->arenas.checkEmptyFreeLists();
@ -5877,9 +5930,11 @@ GCRuntime::incrementalCollectSlice(SliceBudget &budget, JS::gcreason::Reason rea
break;
case COMPACT:
if (isCompacting && compactPhase(lastGC) == NotFinished)
if (isCompacting && compactPhase(lastGC, reason) == NotFinished)
break;
finishCollection();
incrementalState = NO_INCREMENTAL;
break;

View File

@ -474,8 +474,8 @@ class ArenaList {
return *this;
}
ArenaHeader *removeRemainingArenas(ArenaHeader **arenap, const AutoLockGC &lock);
ArenaHeader *pickArenasToRelocate(JSRuntime *runtime);
ArenaHeader *removeRemainingArenas(ArenaHeader **arenap);
ArenaHeader **pickArenasToRelocate(size_t &arenaTotalOut, size_t &relocTotalOut);
ArenaHeader *relocateArenas(ArenaHeader *toRelocate, ArenaHeader *relocated,
gcstats::Statistics& stats);
};
@ -804,7 +804,8 @@ class ArenaLists
MOZ_ASSERT(freeLists[kind].isEmpty());
}
ArenaHeader *relocateArenas(ArenaHeader *relocatedList, gcstats::Statistics& stats);
bool relocateArenas(ArenaHeader *&relocatedListOut, JS::gcreason::Reason reason,
gcstats::Statistics& stats);
void queueForegroundObjectsForSweep(FreeOp *fop);
void queueForegroundThingsForSweep(FreeOp *fop);

View File

@ -92,7 +92,11 @@ JSObject *
js::NonNullObject(JSContext *cx, const Value &v)
{
if (v.isPrimitive()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
RootedValue value(cx, v);
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, value, NullPtr());
if (!bytes)
return nullptr;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes);
return nullptr;
}
return &v.toObject();
@ -280,7 +284,10 @@ PropDesc::initialize(JSContext *cx, const Value &origval, bool checkAccessors)
/* 8.10.5 step 1 */
if (v.isPrimitive()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NullPtr());
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes);
return false;
}
RootedObject desc(cx, &v.toObject());

View File

@ -1620,6 +1620,8 @@ ExpressionDecompiler::decompilePC(jsbytecode *pc)
return false;
return write(str);
}
case JSOP_VOID:
return write("void ") && decompilePCForStackOperand(pc, -1);
default:
break;
}

View File

@ -384,7 +384,10 @@ WeakMap_set_impl(JSContext *cx, CallArgs args)
MOZ_ASSERT(IsWeakMap(args.thisv()));
if (!args.get(0).isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), NullPtr());
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes);
return false;
}
@ -565,7 +568,10 @@ WeakMap_construct(JSContext *cx, unsigned argc, Value *vp)
// Steps 12k-l.
if (isOriginalAdder) {
if (keyVal.isPrimitive()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, NullPtr());
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes);
return false;
}

View File

@ -210,7 +210,10 @@ static PerfMeasurement*
GetPM(JSContext* cx, JS::HandleValue value, const char* fname)
{
if (!value.isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, 0, JSMSG_NOT_NONNULL_OBJECT);
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, value, NullPtr());
if (!bytes)
return nullptr;
JS_ReportErrorNumber(cx, js_GetErrorMessage, 0, JSMSG_NOT_NONNULL_OBJECT, bytes);
return nullptr;
}
RootedObject obj(cx, &value.toObject());

View File

@ -46,16 +46,19 @@ function expectSyntaxError(code)
expectSyntaxError("function f1() {} 'use strict'; function f2() {}");
expectSyntaxError("function f3() { var x; 'use strict'; }");
expectSyntaxError("function f4() {} 'use asm'; function f5() {}");
if (isAsmJSCompilationAvailable())
expectSyntaxError("function f4() {} 'use asm'; function f5() {}");
expectSyntaxError("function f6() { var x; 'use strict'; }");
expectSyntaxError("'use asm'; function f7() {}");
if (isAsmJSCompilationAvailable())
expectSyntaxError("'use asm'; function f7() {}");
// No errors expected -- useless non-directives, but not contrary to used
// semantics.
evaluateNoRval("'use strict'; function f8() {} 'use strict'; function f9() {}");
evaluateNoRval("'use strict'; function f10() { var z; 'use strict' }");
evaluateNoRval("function f11() { 'use asm'; return {}; }");
if (isAsmJSCompilationAvailable())
evaluateNoRval("function f11() { 'use asm'; return {}; }");
/******************************************************************************/

View File

@ -7,14 +7,16 @@ assertEq(RegExp.prototype.flags, "");
assertEq(/foo/iymg.flags, "gimy");
assertEq(RegExp("").flags, "");
assertEq(RegExp("", "mygi").flags, "gimy");
// TODO: Uncomment lines 12, 16, 19 and remove lines 11, 15, 18 when bug 1135377 is fixed.
assertThrowsInstanceOf(() => RegExp("", "mygui").flags, SyntaxError);
// When the /u flag is supported, uncomment the line below and remove the line above
// assertEq(RegExp("", "mygui").flags, "gimuy");
assertEq(genericFlags({}), "");
assertEq(genericFlags({ignoreCase: true}), "i");
assertEq(genericFlags({sticky:1, unicode:1, global: 0}), "uy");
assertEq(genericFlags({sticky:1, unicode:1, global: 0}), "y");
// assertEq(genericFlags({sticky:1, unicode:1, global: 0}), "uy");
assertEq(genericFlags({__proto__: {multiline: true}}), "m");
assertEq(genericFlags(new Proxy({}, {get(){return true}})), "gimuy");
assertEq(genericFlags(new Proxy({}, {get(){return true}})), "gimy");
// assertEq(genericFlags(new Proxy({}, {get(){return true}})), "gimuy");
assertThrowsInstanceOf(() => genericFlags(), TypeError);
assertThrowsInstanceOf(() => genericFlags(1), TypeError);

View File

@ -123,7 +123,7 @@ GetOrCreateFunctionScript(JSContext *cx, HandleFunction fun)
bool
js::ReportObjectRequired(JSContext *cx)
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "value");
return false;
}

View File

@ -79,7 +79,7 @@ DebuggerMemory::checkThis(JSContext *cx, CallArgs &args, const char *fnName)
const Value &thisValue = args.thisv();
if (!thisValue.isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, InformalValueTypeName(thisValue));
return nullptr;
}

View File

@ -34,19 +34,6 @@ class ErrorObject : public NativeObject
friend JSObject *
::js_InitExceptionClasses(JSContext *cx, JS::HandleObject global);
/* For access to assignInitialShape. */
friend bool
EmptyShape::ensureInitialCustomShape<ErrorObject>(ExclusiveContext *cx,
Handle<ErrorObject*> obj);
/*
* Assign the initial error shape to the empty object. (This shape does
* *not* include .message, which must be added separately if needed; see
* ErrorObject::init.)
*/
static Shape *
assignInitialShape(ExclusiveContext *cx, Handle<ErrorObject*> obj);
static bool
init(JSContext *cx, Handle<ErrorObject*> obj, JSExnType type,
ScopedJSFreePtr<JSErrorReport> *errorReport, HandleString fileName, HandleString stack,
@ -85,6 +72,14 @@ class ErrorObject : public NativeObject
uint32_t lineNumber, uint32_t columnNumber, ScopedJSFreePtr<JSErrorReport> *report,
HandleString message);
/*
* Assign the initial error shape to the empty object. (This shape does
* *not* include .message, which must be added separately if needed; see
* ErrorObject::init.)
*/
static Shape *
assignInitialShape(ExclusiveContext *cx, Handle<ErrorObject*> obj);
JSExnType type() const {
return JSExnType(getReservedSlot(EXNTYPE_SLOT).toInt32());
}

View File

@ -289,9 +289,14 @@ class ObjectGroup : public gc::TenuredCell
return maybeUnboxedLayoutDontCheckGeneration();
}
UnboxedLayout &unboxedLayout() {
UnboxedLayout &unboxedLayoutDontCheckGeneration() const {
MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout);
return *maybeUnboxedLayout();
return *maybeUnboxedLayoutDontCheckGeneration();
}
UnboxedLayout &unboxedLayout() {
maybeSweep(nullptr);
return unboxedLayoutDontCheckGeneration();
}
void setUnboxedLayout(UnboxedLayout *layout) {

View File

@ -376,6 +376,14 @@ class RegExpObject : public NativeObject
createNoStatics(ExclusiveContext *cx, HandleAtom atom, RegExpFlag flags,
frontend::TokenStream *ts, LifoAlloc &alloc);
/*
* Compute the initial shape to associate with fresh RegExp objects,
* encoding their initial properties. Return the shape after
* changing |obj|'s last property to it.
*/
static Shape *
assignInitialShape(ExclusiveContext *cx, Handle<RegExpObject*> obj);
/* Accessors. */
static unsigned lastIndexSlot() { return LAST_INDEX_SLOT; }
@ -446,19 +454,6 @@ class RegExpObject : public NativeObject
private:
friend class RegExpObjectBuilder;
/* For access to assignInitialShape. */
friend bool
EmptyShape::ensureInitialCustomShape<RegExpObject>(ExclusiveContext *cx,
Handle<RegExpObject*> obj);
/*
* Compute the initial shape to associate with fresh RegExp objects,
* encoding their initial properties. Return the shape after
* changing |obj|'s last property to it.
*/
static Shape *
assignInitialShape(ExclusiveContext *cx, Handle<RegExpObject*> obj);
bool init(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags);
/*

View File

@ -341,7 +341,7 @@ SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName,
const Value &thisValue = args.thisv();
if (!thisValue.isObject()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, InformalValueTypeName(thisValue));
return false;
}

View File

@ -31,6 +31,14 @@ class StringObject : public NativeObject
static inline StringObject *create(JSContext *cx, HandleString str,
NewObjectKind newKind = GenericObject);
/*
* Compute the initial shape to associate with fresh String objects, which
* encodes the initial length property. Return the shape after changing
* |obj|'s last property to it.
*/
static Shape *
assignInitialShape(ExclusiveContext *cx, Handle<StringObject*> obj);
JSString *unbox() const {
return getFixedSlot(PRIMITIVE_VALUE_SLOT).toString();
}
@ -58,19 +66,6 @@ class StringObject : public NativeObject
/* For access to init, as String.prototype is special. */
friend JSObject *
::js_InitStringClass(JSContext *cx, js::HandleObject global);
/* For access to assignInitialShape. */
friend bool
EmptyShape::ensureInitialCustomShape<StringObject>(ExclusiveContext *cx,
Handle<StringObject*> obj);
/*
* Compute the initial shape to associate with fresh String objects, which
* encodes the initial length property. Return the shape after changing
* |obj|'s last property to it.
*/
static Shape *
assignInitialShape(ExclusiveContext *cx, Handle<StringObject*> obj);
};
} // namespace js

View File

@ -2268,8 +2268,8 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList *constraints, jsid
return false;
}
static inline bool
ClassCanHaveExtraProperties(const Class *clasp)
bool
js::ClassCanHaveExtraProperties(const Class *clasp)
{
return clasp->resolve
|| clasp->ops.lookupProperty

View File

@ -909,6 +909,9 @@ class TypeNewScript
/* Is this a reasonable PC to be doing inlining on? */
inline bool isInlinableCall(jsbytecode *pc);
bool
ClassCanHaveExtraProperties(const Class *clasp);
/*
* Whether Array.prototype, or an object on its proto chain, has an
* indexed property.

View File

@ -87,7 +87,8 @@ UnboxedPlainObject::setValue(JSContext *cx, const UnboxedLayout::Property &prope
case JSVAL_TYPE_STRING:
if (v.isString()) {
*reinterpret_cast<HeapPtrString*>(p) = v.toString();
MOZ_ASSERT(!IsInsideNursery(v.toString()));
*reinterpret_cast<PreBarrieredString*>(p) = v.toString();
return true;
}
return false;
@ -99,7 +100,14 @@ UnboxedPlainObject::setValue(JSContext *cx, const UnboxedLayout::Property &prope
// created.
AddTypePropertyId(cx, this, NameToId(property.name), v);
*reinterpret_cast<HeapPtrObject*>(p) = v.toObjectOrNull();
// Manually trigger post barriers on the whole object. If we treat
// the pointer as a HeapPtrObject we will get confused later if the
// object is converted to its native representation.
JSObject *obj = v.toObjectOrNull();
if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(this))
cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(this);
*reinterpret_cast<PreBarrieredObject*>(p) = obj;
return true;
}
return false;
@ -138,7 +146,7 @@ UnboxedPlainObject::getValue(const UnboxedLayout::Property &property)
void
UnboxedPlainObject::trace(JSTracer *trc, JSObject *obj)
{
const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layoutDontCheckGeneration();
const int32_t *list = layout.traceList();
if (!list)
return;

View File

@ -176,6 +176,10 @@ class UnboxedPlainObject : public JSObject
return group()->unboxedLayout();
}
const UnboxedLayout &layoutDontCheckGeneration() const {
return group()->unboxedLayoutDontCheckGeneration();
}
uint8_t *data() {
return &data_[0];
}

View File

@ -396,6 +396,23 @@ bool OpenSlesInput::EnqueueAllBuffers() {
return true;
}
void OpenSlesInput::SetupVoiceMode() {
SLAndroidConfigurationItf configItf;
SLresult res = (*sles_recorder_)->GetInterface(sles_recorder_, SL_IID_ANDROIDCONFIGURATION_,
(void*)&configItf);
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, id_, "OpenSL GetInterface: %d", res);
if (res == SL_RESULT_SUCCESS) {
SLuint32 voiceMode = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
SLuint32 voiceSize = sizeof(voiceMode);
res = (*configItf)->SetConfiguration(configItf,
SL_ANDROID_KEY_RECORDING_PRESET,
&voiceMode, voiceSize);
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, id_, "OpenSL Set Voice mode res: %d", res);
}
}
#if defined(WEBRTC_GONK) && defined(WEBRTC_HARDWARE_AEC_NS)
bool OpenSlesInput::CheckPlatformAEC() {
effect_descriptor_t fxDesc;
@ -519,16 +536,7 @@ bool OpenSlesInput::CreateAudioRecorder() {
&recorder_config),
false);
// Set audio recorder configuration to
// SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION which ensures that we
// use the main microphone tuned for audio communications.
SLuint32 stream_type = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
OPENSL_RETURN_ON_FAILURE(
(*recorder_config)->SetConfiguration(recorder_config,
SL_ANDROID_KEY_RECORDING_PRESET,
&stream_type,
sizeof(SLint32)),
false);
SetupVoiceMode();
// Realize the recorder in synchronous mode.
OPENSL_RETURN_ON_FAILURE((*sles_recorder_)->Realize(sles_recorder_,

View File

@ -147,9 +147,9 @@ class OpenSlesInput {
// etc, so it should be called when starting recording.
bool CreateAudioRecorder();
void DestroyAudioRecorder();
void SetupVoiceMode();
#if defined(WEBRTC_GONK) && defined(WEBRTC_HARDWARE_AEC_NS)
void SetupAECAndNS();
void SetupVoiceMode();
bool CheckPlatformAEC();
#endif

View File

@ -178,6 +178,8 @@ public class BrowserApp extends GeckoApp
public ViewFlipper mActionBarFlipper;
public ActionModeCompatView mActionBar;
private BrowserToolbar mBrowserToolbar;
// We can't name the TabStrip class because it's not included on API 9.
private Refreshable mTabStrip;
private ToolbarProgressView mProgressView;
private FirstrunPane mFirstrunPane;
private HomePager mHomePager;
@ -191,6 +193,7 @@ public class BrowserApp extends GeckoApp
private static final int GECKO_TOOLS_MENU = -1;
private static final int ADDON_MENU_OFFSET = 1000;
public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
private static class MenuItemInfo {
public int id;
public String label;
@ -721,7 +724,7 @@ public class BrowserApp extends GeckoApp
}
if (HardwareUtils.isTablet()) {
findViewById(R.id.new_tablet_tab_strip).setVisibility(View.VISIBLE);
mTabStrip = (Refreshable) (((ViewStub) findViewById(R.id.new_tablet_tab_strip)).inflate());
}
((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideOnTouchListener());
@ -1528,6 +1531,10 @@ public class BrowserApp extends GeckoApp
mTabsPanel.refresh();
}
if (mTabStrip != null) {
mTabStrip.refresh();
}
mBrowserToolbar.refresh();
}
@ -3509,6 +3516,12 @@ public class BrowserApp extends GeckoApp
return GeckoProfile.getDefaultProfileName(this);
}
// For use from tests only.
@RobocopTarget
public ReadingListHelper getReadingListHelper() {
return mReadingListHelper;
}
/**
* Launch UI that lets the user update Firefox.
*
@ -3603,4 +3616,8 @@ public class BrowserApp extends GeckoApp
appLocale,
previousSession);
}
public static interface Refreshable {
public void refresh();
}
}

View File

@ -11,6 +11,7 @@ import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.DBUtils;
import org.mozilla.gecko.db.ReadingListAccessor;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
@ -34,6 +35,8 @@ public final class ReadingListHelper implements NativeEventListener {
private final ReadingListAccessor readingListAccessor;
private final ContentObserver contentObserver;
volatile boolean fetchInBackground = true;
public ReadingListHelper(Context context, GeckoProfile profile) {
this.context = context;
this.db = profile.getDB();
@ -46,7 +49,9 @@ public final class ReadingListHelper implements NativeEventListener {
contentObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
fetchContent();
if (fetchInBackground) {
fetchContent();
}
}
};
@ -139,21 +144,50 @@ public final class ReadingListHelper implements NativeEventListener {
if (message.has("id")) {
values.put(ReadingListItems._ID, message.getInt("id"));
}
// url is actually required...
String url = null;
if (message.has("url")) {
values.put(ReadingListItems.URL, message.getString("url"));
url = message.getString("url");
values.put(ReadingListItems.URL, url);
}
String title = null;
if (message.has("title")) {
values.put(ReadingListItems.TITLE, message.getString("title"));
title = message.getString("title");
values.put(ReadingListItems.TITLE, title);
}
if (message.has("length")) {
values.put(ReadingListItems.LENGTH, message.getInt("length"));
// TODO: message actually has "length", but that's no use for us. See Bug 1127451.
if (message.has("word_count")) {
values.put(ReadingListItems.WORD_COUNT, message.getInt("word_count"));
}
if (message.has("excerpt")) {
values.put(ReadingListItems.EXCERPT, message.getString("excerpt"));
}
if (message.has("status")) {
values.put(ReadingListItems.CONTENT_STATUS, message.getInt("status"));
final int status = message.getInt("status");
values.put(ReadingListItems.CONTENT_STATUS, status);
if (status == ReadingListItems.STATUS_FETCHED_ARTICLE) {
if (message.has("resolved_title")) {
values.put(ReadingListItems.RESOLVED_TITLE, message.getString("resolved_title"));
} else {
if (title != null) {
values.put(ReadingListItems.RESOLVED_TITLE, title);
}
}
if (message.has("resolved_url")) {
values.put(ReadingListItems.RESOLVED_URL, message.getString("resolved_url"));
} else {
if (url != null) {
values.put(ReadingListItems.RESOLVED_URL, url);
}
}
}
}
return values;
}
@ -258,4 +292,13 @@ public final class ReadingListHelper implements NativeEventListener {
}
});
}
@RobocopTarget
/**
* Test code will want to disable background fetches to avoid upsetting
* the test harness. Call this by accessing the instance from BrowserApp.
*/
public void disableBackgroundFetches() {
fetchInBackground = false;
}
}

View File

@ -342,19 +342,36 @@ public class BrowserContract {
}
@RobocopTarget
public static final class ReadingListItems implements CommonColumns, URLColumns, SyncColumns {
public static final class ReadingListItems implements CommonColumns, URLColumns {
public static final String EXCERPT = "excerpt";
public static final String CLIENT_LAST_MODIFIED = "client_last_modified";
public static final String GUID = "guid";
public static final String SERVER_LAST_MODIFIED = "last_modified";
public static final String SERVER_STORED_ON = "stored_on";
public static final String ADDED_ON = "added_on";
public static final String MARKED_READ_ON = "marked_read_on";
public static final String IS_DELETED = "is_deleted";
public static final String IS_ARCHIVED = "is_archived";
public static final String IS_UNREAD = "is_unread";
public static final String IS_ARTICLE = "is_article";
public static final String IS_FAVORITE = "is_favorite";
public static final String RESOLVED_URL = "resolved_url";
public static final String RESOLVED_TITLE = "resolved_title";
public static final String ADDED_BY = "added_by";
public static final String MARKED_READ_BY = "marked_read_by";
public static final String WORD_COUNT = "word_count";
public static final String READ_POSITION = "read_position";
public static final String CONTENT_STATUS = "content_status";
public static final String SYNC_STATUS = "sync_status";
public static final String SYNC_CHANGE_FLAGS = "sync_change_flags";
private ReadingListItems() {}
public static final Uri CONTENT_URI = Uri.withAppendedPath(READING_LIST_AUTHORITY_URI, "items");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/readinglistitem";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/readinglistitem";
public static final String EXCERPT = "excerpt";
public static final String READ = "read";
public static final String LENGTH = "length";
public static final String CONTENT_STATUS = "content_status";
// CONTENT_STATUS represents the result of an attempt to fetch content for the reading list item.
public static final int STATUS_UNFETCHED = 0;
public static final int STATUS_FETCH_FAILED_TEMPORARY = 1;
@ -362,8 +379,40 @@ public class BrowserContract {
public static final int STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT = 3;
public static final int STATUS_FETCHED_ARTICLE = 4;
public static final String DEFAULT_SORT_ORDER = DATE_MODIFIED + " DESC";
public static final String[] DEFAULT_PROJECTION = new String[] { _ID, URL, TITLE, EXCERPT, LENGTH };
// See https://github.com/mozilla-services/readinglist/wiki/Client-phases for how this is expected to work.
//
// If an item is SYNCED, it doesn't need to be uploaded.
//
// If its status is NEW, the entire record should be uploaded.
//
// If DELETED, the record should be deleted. A record can only move into this state from SYNCED; NEW records
// are deleted immediately.
//
public static final int SYNC_STATUS_SYNCED = 0;
public static final int SYNC_STATUS_NEW = 1; // Upload everything.
public static final int SYNC_STATUS_DELETED = 2; // Delete the record from the server.
public static final int SYNC_STATUS_MODIFIED = 3; // Consult SYNC_CHANGE_FLAGS.
// SYNC_CHANGE_FLAG represents the sets of fields that need to be uploaded.
// If its status is only UNREAD_CHANGED (and maybe FAVORITE_CHANGED?), then it can easily be uploaded
// in a fire-and-forget manner. This change can never conflict.
//
// If its status is RESOLVED, then one or more of the content-oriented fields has changed, and a full
// upload of those fields should occur. These can result in conflicts.
//
// Note that these are flags; they should be considered together when deciding on a course of action.
//
// These flags are meaningless for records in any state other than SYNCED. They can be safely altered in
// other states (to avoid having to query to pre-fill a ContentValues), but should be ignored.
public static final int SYNC_CHANGE_NONE = 0;
public static final int SYNC_CHANGE_UNREAD_CHANGED = 1 << 0; // => marked_read_{on,by}, is_unread
public static final int SYNC_CHANGE_FAVORITE_CHANGED = 1 << 1; // => is_favorite
public static final int SYNC_CHANGE_RESOLVED = 1 << 2; // => is_article, resolved_{url,title}, excerpt, word_count
public static final String DEFAULT_SORT_ORDER = CLIENT_LAST_MODIFIED + " DESC";
public static final String[] DEFAULT_PROJECTION = new String[] { _ID, URL, TITLE, EXCERPT, WORD_COUNT };
// Minimum fields required to create a reading list item.
public static final String[] REQUIRED_FIELDS = { Bookmarks.URL, Bookmarks.TITLE };

View File

@ -32,9 +32,9 @@ import android.util.Log;
final class BrowserDatabaseHelper extends SQLiteOpenHelper {
private static final String LOGTAG = "GeckoBrowserDBHelper";
public static final int DATABASE_VERSION = 22;
public static final int DATABASE_VERSION = 23;
public static final String DATABASE_NAME = "browser.db";
final protected Context mContext;
@ -295,8 +295,10 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
R.string.bookmarks_folder_places, 0);
createOrUpdateAllSpecialFolders(db);
createReadingListTable(db);
createSearchHistoryTable(db);
createReadingListTable(db, TABLE_READING_LIST);
didCreateCurrentReadingListTable = true; // Mostly correct, in the absence of transactions.
createReadingListIndices(db, TABLE_READING_LIST);
}
private void createSearchHistoryTable(SQLiteDatabase db) {
@ -312,28 +314,54 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
SearchHistory.TABLE_NAME + "(" + SearchHistory.DATE_LAST_VISITED + ")");
}
private void createReadingListTable(SQLiteDatabase db) {
private boolean didCreateCurrentReadingListTable = false;
private void createReadingListTable(final SQLiteDatabase db, final String tableName) {
debug("Creating " + TABLE_READING_LIST + " table");
db.execSQL("CREATE TABLE " + TABLE_READING_LIST + "(" +
ReadingListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
ReadingListItems.URL + " TEXT NOT NULL, " +
ReadingListItems.TITLE + " TEXT, " +
ReadingListItems.EXCERPT + " TEXT, " +
ReadingListItems.READ + " TINYINT DEFAULT 0, " +
ReadingListItems.IS_DELETED + " TINYINT DEFAULT 0, " +
ReadingListItems.GUID + " TEXT UNIQUE NOT NULL, " +
ReadingListItems.DATE_MODIFIED + " INTEGER NOT NULL, " +
ReadingListItems.DATE_CREATED + " INTEGER NOT NULL, " +
ReadingListItems.LENGTH + " INTEGER DEFAULT 0, " +
ReadingListItems.CONTENT_STATUS + " TINYINT DEFAULT " + ReadingListItems.STATUS_UNFETCHED + "); ");
db.execSQL("CREATE TABLE " + tableName + "(" +
ReadingListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
ReadingListItems.GUID + " TEXT UNIQUE, " + // Server-assigned.
db.execSQL("CREATE INDEX reading_list_url ON " + TABLE_READING_LIST + "("
+ ReadingListItems.URL + ")");
db.execSQL("CREATE UNIQUE INDEX reading_list_guid ON " + TABLE_READING_LIST + "("
+ ReadingListItems.GUID + ")");
db.execSQL("CREATE INDEX reading_list_content_status ON " + TABLE_READING_LIST + "("
+ ReadingListItems.CONTENT_STATUS + ")");
ReadingListItems.CONTENT_STATUS + " TINYINT NOT NULL DEFAULT " + ReadingListItems.STATUS_UNFETCHED + ", " +
ReadingListItems.SYNC_STATUS + " TINYINT NOT NULL DEFAULT " + ReadingListItems.SYNC_STATUS_NEW + ", " +
ReadingListItems.SYNC_CHANGE_FLAGS + " TINYINT NOT NULL DEFAULT " + ReadingListItems.SYNC_CHANGE_NONE + ", " +
ReadingListItems.CLIENT_LAST_MODIFIED + " INTEGER NOT NULL, " + // Client time.
ReadingListItems.SERVER_LAST_MODIFIED + " INTEGER, " + // Server-assigned.
// Server-assigned.
ReadingListItems.SERVER_STORED_ON + " INTEGER, " +
ReadingListItems.ADDED_ON + " INTEGER, " + // Client time. Shouldn't be null, but not enforced. Formerly DATE_CREATED.
ReadingListItems.MARKED_READ_ON + " INTEGER, " +
// These boolean flags represent the server 'status', 'unread', 'is_article', and 'favorite' fields.
ReadingListItems.IS_DELETED + " TINYINT NOT NULL DEFAULT 0, " +
ReadingListItems.IS_ARCHIVED + " TINYINT NOT NULL DEFAULT 0, " +
ReadingListItems.IS_UNREAD + " TINYINT NOT NULL DEFAULT 1, " +
ReadingListItems.IS_ARTICLE + " TINYINT NOT NULL DEFAULT 0, " +
ReadingListItems.IS_FAVORITE + " TINYINT NOT NULL DEFAULT 0, " +
ReadingListItems.URL + " TEXT NOT NULL, " +
ReadingListItems.TITLE + " TEXT, " +
ReadingListItems.RESOLVED_URL + " TEXT, " +
ReadingListItems.RESOLVED_TITLE + " TEXT, " +
ReadingListItems.EXCERPT + " TEXT, " +
ReadingListItems.ADDED_BY + " TEXT, " +
ReadingListItems.MARKED_READ_BY + " TEXT, " +
ReadingListItems.WORD_COUNT + " INTEGER DEFAULT 0, " +
ReadingListItems.READ_POSITION + " INTEGER DEFAULT 0 " +
"); ");
}
private void createReadingListIndices(final SQLiteDatabase db, final String tableName) {
// No need to create an index on GUID; it's a UNIQUE column.
db.execSQL("CREATE INDEX reading_list_url ON " + tableName + "("
+ ReadingListItems.URL + ")");
db.execSQL("CREATE INDEX reading_list_content_status ON " + tableName + "("
+ ReadingListItems.CONTENT_STATUS + ")");
}
private void createOrUpdateAllSpecialFolders(SQLiteDatabase db) {
@ -687,8 +715,7 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
}
/*
* Moves reading list items from 'bookmarks' table to 'reading_list' table. Uses the
* same item GUID.
* Moves reading list items from 'bookmarks' table to 'reading_list' table.
*/
private void upgradeDatabaseFrom17to18(SQLiteDatabase db) {
debug("Moving reading list items from 'bookmarks' table to 'reading_list' table");
@ -701,49 +728,60 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
Bookmarks.DATE_MODIFIED,
Bookmarks.DATE_CREATED,
Bookmarks.TITLE };
Cursor cursor = null;
try {
// Start transaction
db.beginTransaction();
// Create 'reading_list' table
createReadingListTable(db);
// Create 'reading_list' table.
createReadingListTable(db, TABLE_READING_LIST);
// Get all the reading list items from bookmarks table
cursor = db.query(TABLE_BOOKMARKS, projection, selection, selectionArgs,
null, null, null);
// Get all the reading list items from bookmarks table.
final Cursor cursor = db.query(TABLE_BOOKMARKS, projection, selection, selectionArgs, null, null, null);
// Insert reading list items into reading_list table
while (cursor.moveToNext()) {
debug(DatabaseUtils.dumpCurrentRowToString(cursor));
ContentValues values = new ContentValues();
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.URL, values, ReadingListItems.URL);
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.GUID, values, ReadingListItems.GUID);
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.TITLE, values, ReadingListItems.TITLE);
DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_CREATED, values, ReadingListItems.DATE_CREATED);
DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_MODIFIED, values, ReadingListItems.DATE_MODIFIED);
db.insertOrThrow(TABLE_READING_LIST, null, values);
if (cursor == null) {
// This should never happen.
db.setTransactionSuccessful();
return;
}
// Delete reading list items from bookmarks table
try {
// Insert reading list items into reading_list table.
while (cursor.moveToNext()) {
debug(DatabaseUtils.dumpCurrentRowToString(cursor));
final ContentValues values = new ContentValues();
// We don't preserve bookmark GUIDs.
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.URL, values, ReadingListItems.URL);
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.TITLE, values, ReadingListItems.TITLE);
DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_CREATED, values, ReadingListItems.ADDED_ON);
DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_MODIFIED, values, ReadingListItems.CLIENT_LAST_MODIFIED);
db.insertOrThrow(TABLE_READING_LIST, null, values);
}
} finally {
cursor.close();
}
// Delete reading list items from bookmarks table.
db.delete(TABLE_BOOKMARKS,
Bookmarks.PARENT + " = ? ",
new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
// Delete reading list special folder
// Delete reading list special folder.
db.delete(TABLE_BOOKMARKS,
Bookmarks._ID + " = ? ",
new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
// Done
// Create indices.
createReadingListIndices(db, TABLE_READING_LIST);
// Done.
db.setTransactionSuccessful();
didCreateCurrentReadingListTable = true;
} catch (SQLException e) {
Log.e(LOGTAG, "Error migrating reading list items", e);
} finally {
if (cursor != null) {
cursor.close();
}
db.endTransaction();
}
}
@ -766,6 +804,11 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
}
private void upgradeDatabaseFrom21to22(SQLiteDatabase db) {
if (didCreateCurrentReadingListTable) {
debug("No need to add CONTENT_STATUS to reading list; we just created with the current schema.");
return;
}
debug("Adding CONTENT_STATUS column to reading list table.");
try {
@ -782,6 +825,60 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
}
}
private void upgradeDatabaseFrom22to23(SQLiteDatabase db) {
if (didCreateCurrentReadingListTable) {
debug("No need to rev reading list schema; we just created with the current schema.");
return;
}
debug("Rewriting reading list table.");
createReadingListTable(db, "tmp_rl");
// Remove indexes. We don't need them now, and we'll be throwing away the table.
db.execSQL("DROP INDEX IF EXISTS reading_list_url");
db.execSQL("DROP INDEX IF EXISTS reading_list_guid");
db.execSQL("DROP INDEX IF EXISTS reading_list_content_status");
final String thisDevice = ReadingListProvider.PLACEHOLDER_THIS_DEVICE;
db.execSQL("INSERT INTO tmp_rl (" +
// Here are the columns we can preserve.
ReadingListItems._ID + ", " +
ReadingListItems.URL + ", " +
ReadingListItems.TITLE + ", " +
ReadingListItems.RESOLVED_TITLE + ", " + // = TITLE (if CONTENT_STATUS = STATUS_FETCHED_ARTICLE)
ReadingListItems.RESOLVED_URL + ", " + // = URL (if CONTENT_STATUS = STATUS_FETCHED_ARTICLE)
ReadingListItems.EXCERPT + ", " +
ReadingListItems.IS_UNREAD + ", " + // = !READ
ReadingListItems.IS_DELETED + ", " + // = 0
ReadingListItems.GUID + ", " + // = NULL
ReadingListItems.CLIENT_LAST_MODIFIED + ", " + // = DATE_MODIFIED
ReadingListItems.ADDED_ON + ", " + // = DATE_CREATED
ReadingListItems.CONTENT_STATUS + ", " +
ReadingListItems.MARKED_READ_BY + ", " + // if READ + ", = this device
ReadingListItems.ADDED_BY + // = this device
") " +
"SELECT " +
"_id, url, title, " +
"CASE content_status WHEN " + ReadingListItems.STATUS_FETCHED_ARTICLE + " THEN title ELSE NULL END, " + // RESOLVED_TITLE.
"CASE content_status WHEN " + ReadingListItems.STATUS_FETCHED_ARTICLE + " THEN url ELSE NULL END, " + // RESOLVED_URL.
"excerpt, " +
"CASE read WHEN 1 THEN 0 ELSE 1 END, " + // IS_UNREAD.
"0, " + // IS_DELETED.
"NULL, modified, created, content_status, " +
"CASE read WHEN 1 THEN ? ELSE NULL END, " + // MARKED_READ_BY.
"?" + // ADDED_BY.
" FROM " + TABLE_READING_LIST +
" WHERE deleted = 0",
new String[] {thisDevice, thisDevice});
// Now switch these tables over and recreate the indices.
db.execSQL("DROP TABLE " + TABLE_READING_LIST);
db.execSQL("ALTER TABLE tmp_rl RENAME TO " + TABLE_READING_LIST);
createReadingListIndices(db, TABLE_READING_LIST);
}
private void createV19CombinedView(SQLiteDatabase db) {
db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
@ -847,11 +944,11 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
break;
case 22:
if (oldVersion <= 17) {
// We just created the right table in 17to18. Do nothing here.
} else {
upgradeDatabaseFrom21to22(db);
}
upgradeDatabaseFrom21to22(db);
break;
case 23:
upgradeDatabaseFrom22to23(db);
break;
}
}

View File

@ -5,62 +5,75 @@
package org.mozilla.gecko.db;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
import org.mozilla.gecko.mozglue.RobocopTarget;
@RobocopTarget
public class LocalReadingListAccessor implements ReadingListAccessor {
private static final String LOG_TAG = "GeckoReadingListAcc";
private static final String NOT_DELETED = ReadingListItems.IS_DELETED + " = 0";
private static final String NEITHER_DELETED_NOR_ARCHIVED = ReadingListItems.IS_ARCHIVED + " = 0 AND " + ReadingListItems.IS_DELETED + " = 0";
private static final String ITEMS_TO_FETCH = ReadingListItems.CONTENT_STATUS + " = " + ReadingListItems.STATUS_UNFETCHED + " AND " + NEITHER_DELETED_NOR_ARCHIVED;
private static final String SORT_ORDER_RECENT_FIRST = "COALESCE(" + ReadingListItems.SERVER_STORED_ON + ", " + ReadingListItems.ADDED_ON + ") DESC";
private final Uri mReadingListUriWithProfile;
public LocalReadingListAccessor(final String profile) {
mReadingListUriWithProfile = DBUtils.appendProfile(profile, BrowserContract.ReadingListItems.CONTENT_URI);
mReadingListUriWithProfile = DBUtils.appendProfile(profile, ReadingListItems.CONTENT_URI);
}
// Return a count of non-deleted items.
@Override
public int getCount(ContentResolver cr) {
final String[] columns = new String[]{BrowserContract.ReadingListItems._ID};
final Cursor cursor = cr.query(mReadingListUriWithProfile, columns, null, null, null);
int count = 0;
final String[] columns = new String[]{ReadingListItems._ID};
final Cursor cursor = cr.query(mReadingListUriWithProfile, columns, NOT_DELETED, null, null);
try {
count = cursor.getCount();
return cursor.getCount();
} finally {
cursor.close();
}
Log.d(LOG_TAG, "Got count " + count + " for reading list.");
return count;
}
@Override
public Cursor getReadingList(ContentResolver cr) {
// Return non-deleted, non-archived items, ordered by either server stored data or local added date,
// descending.
// This isn't ideal -- it depends on upload order! -- but the alternative is that a client with a
// very skewed clock will force its items to the front or end of the list on other devices.
return cr.query(mReadingListUriWithProfile,
BrowserContract.ReadingListItems.DEFAULT_PROJECTION,
ReadingListItems.DEFAULT_PROJECTION,
NEITHER_DELETED_NOR_ARCHIVED,
null,
null,
null);
SORT_ORDER_RECENT_FIRST);
}
@Override
public Cursor getReadingListUnfetched(ContentResolver cr) {
// Return unfetched, non-deleted, non-archived items, sorted by date added, newest first.
// This allows us to fetch the top of the list first.
return cr.query(mReadingListUriWithProfile,
new String[] { BrowserContract.ReadingListItems._ID, BrowserContract.ReadingListItems.URL },
BrowserContract.ReadingListItems.CONTENT_STATUS + " = " + BrowserContract.ReadingListItems.STATUS_UNFETCHED,
new String[] { ReadingListItems._ID, ReadingListItems.URL },
ITEMS_TO_FETCH,
null,
null);
SORT_ORDER_RECENT_FIRST);
}
@Override
public boolean isReadingListItem(ContentResolver cr, String uri) {
final Cursor c = cr.query(mReadingListUriWithProfile,
new String[] { BrowserContract.ReadingListItems._ID },
BrowserContract.ReadingListItems.URL + " = ? ",
new String[] { uri },
new String[] { ReadingListItems._ID },
ReadingListItems.URL + " = ? OR " + ReadingListItems.RESOLVED_URL + " = ?",
new String[] { uri, uri },
null);
if (c == null) {
@ -69,7 +82,7 @@ public class LocalReadingListAccessor implements ReadingListAccessor {
}
try {
return c.getCount() > 0;
return c.moveToNext();
} finally {
c.close();
}
@ -77,52 +90,91 @@ public class LocalReadingListAccessor implements ReadingListAccessor {
@Override
public void addReadingListItem(ContentResolver cr, ContentValues values) {
public long addReadingListItem(ContentResolver cr, ContentValues values) {
// Check that required fields are present.
for (String field: BrowserContract.ReadingListItems.REQUIRED_FIELDS) {
for (String field: ReadingListItems.REQUIRED_FIELDS) {
if (!values.containsKey(field)) {
throw new IllegalArgumentException("Missing required field for reading list item: " + field);
}
}
// Clear delete flag if necessary
values.put(BrowserContract.ReadingListItems.IS_DELETED, 0);
// We're adding locally, so we can specify these.
values.put(ReadingListItems.ADDED_ON, System.currentTimeMillis());
values.put(ReadingListItems.ADDED_BY, ReadingListProvider.PLACEHOLDER_THIS_DEVICE);
// Restore deleted record if possible
final Uri insertUri = mReadingListUriWithProfile
.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
.build();
// We never un-delete (and we can't; we wipe as we go).
// Re-add if necessary and allow the server to resolve conflicts.
return ContentUris.parseId(cr.insert(mReadingListUriWithProfile, values));
}
final int updated = cr.update(insertUri,
values,
BrowserContract.ReadingListItems.URL + " = ? ",
new String[] { values.getAsString(BrowserContract.ReadingListItems.URL) });
@Override
public long addBasicReadingListItem(ContentResolver cr, String url, String title) {
if (url == null) {
throw new IllegalArgumentException("URL must not be null.");
}
final ContentValues values = new ContentValues();
values.put(ReadingListItems.URL, url);
if (title != null) {
values.put(ReadingListItems.TITLE, title);
} else {
values.putNull(ReadingListItems.TITLE);
}
Log.d(LOG_TAG, "Updated " + updated + " rows to new modified time.");
return addReadingListItem(cr, values);
}
@Override
public void updateReadingListItem(ContentResolver cr, ContentValues values) {
if (!values.containsKey(BrowserContract.ReadingListItems._ID)) {
if (!values.containsKey(ReadingListItems._ID)) {
throw new IllegalArgumentException("Cannot update reading list item without an ID");
}
final int updated = cr.update(mReadingListUriWithProfile,
values,
BrowserContract.ReadingListItems._ID + " = ? ",
new String[] { values.getAsString(BrowserContract.ReadingListItems._ID) });
ReadingListItems._ID + " = ? ",
new String[] { values.getAsString(ReadingListItems._ID) });
Log.d(LOG_TAG, "Updated " + updated + " reading list rows.");
}
@Override
public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
cr.delete(mReadingListUriWithProfile, BrowserContract.ReadingListItems.URL + " = ? ", new String[]{uri});
cr.delete(mReadingListUriWithProfile,
ReadingListItems.URL + " = ? OR " + ReadingListItems.RESOLVED_URL + " = ?",
new String[]{ uri, uri });
}
@Override
public void deleteItem(ContentResolver cr, long itemID) {
cr.delete(ContentUris.appendId(mReadingListUriWithProfile.buildUpon(), itemID).build(),
null, null);
}
@Override
public void registerContentObserver(Context context, ContentObserver observer) {
context.getContentResolver().registerContentObserver(mReadingListUriWithProfile, false, observer);
}
@Override
public void markAsRead(ContentResolver cr, long itemID) {
final ContentValues values = new ContentValues();
values.put(ReadingListItems.MARKED_READ_BY, ReadingListProvider.PLACEHOLDER_THIS_DEVICE);
values.put(ReadingListItems.MARKED_READ_ON, System.currentTimeMillis());
values.put(ReadingListItems.IS_UNREAD, 0);
// The ContentProvider will take care of updating the sync metadata.
cr.update(mReadingListUriWithProfile, values, ReadingListItems._ID + " = " + itemID, null);
}
@Override
public void updateContent(ContentResolver cr, long itemID, String resolvedTitle, String resolvedURL, String excerpt) {
final ContentValues values = new ContentValues();
values.put(ReadingListItems.CONTENT_STATUS, ReadingListItems.STATUS_FETCHED_ARTICLE);
values.put(ReadingListItems.RESOLVED_URL, resolvedURL);
values.put(ReadingListItems.RESOLVED_TITLE, resolvedTitle);
values.put(ReadingListItems.EXCERPT, excerpt);
// The ContentProvider will take care of updating the sync metadata.
cr.update(mReadingListUriWithProfile, values, ReadingListItems._ID + " = " + itemID, null);
}
}

View File

@ -9,9 +9,14 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import org.mozilla.gecko.mozglue.RobocopTarget;
@RobocopTarget
public interface ReadingListAccessor {
/**
* Returns non-deleted, non-archived items.
* Fennec doesn't currently offer a way to display archived items.
*
* Can return <code>null</code>.
*/
Cursor getReadingList(ContentResolver cr);
@ -22,11 +27,16 @@ public interface ReadingListAccessor {
boolean isReadingListItem(ContentResolver cr, String uri);
void addReadingListItem(ContentResolver cr, ContentValues values);
long addReadingListItem(ContentResolver cr, ContentValues values);
long addBasicReadingListItem(ContentResolver cr, String url, String title);
void updateReadingListItem(ContentResolver cr, ContentValues values);
void removeReadingListItemWithURL(ContentResolver cr, String uri);
void registerContentObserver(Context context, ContentObserver observer);
void markAsRead(ContentResolver cr, long itemID);
void updateContent(ContentResolver cr, long itemID, String resolvedTitle, String resolvedURL, String excerpt);
void deleteItem(ContentResolver cr, long itemID);
}

View File

@ -4,9 +4,6 @@
package org.mozilla.gecko.db;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
import org.mozilla.gecko.sync.Utils;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
@ -16,13 +13,17 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.*;
public class ReadingListProvider extends SharedBrowserDatabaseProvider {
static final String TABLE_READING_LIST = ReadingListItems.TABLE_NAME;
static final String TABLE_READING_LIST = TABLE_NAME;
static final int ITEMS = 101;
static final int ITEMS_ID = 102;
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
public static final String PLACEHOLDER_THIS_DEVICE = "$local";
static {
URI_MATCHER.addURI(BrowserContract.READING_LIST_AUTHORITY, "items", ITEMS);
URI_MATCHER.addURI(BrowserContract.READING_LIST_AUTHORITY, "items/#", ITEMS_ID);
@ -32,81 +33,213 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
* Updates items that match the selection criteria. If no such items is found
* one is inserted with the attributes passed in. Returns 0 if no item updated.
*
* Only use this method for callers, not internally -- it futzes with the provided
* values to set syncing flags.
*
* @return Number of items updated or inserted
*/
public int updateOrInsertItem(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int updated = updateItems(uri, values, selection, selectionArgs);
if (!values.containsKey(CLIENT_LAST_MODIFIED)) {
values.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
}
if (isCallerSync(uri)) {
int updated = updateItemsWithFlags(uri, values, null, selection, selectionArgs);
if (updated > 0) {
return updated;
}
return insertItem(uri, values) != -1 ? 1 : 0;
}
// Assume updated.
final ContentValues flags = processChangeValues(values);
int updated = updateItemsWithFlags(uri, values, flags, selection, selectionArgs);
if (updated <= 0) {
// Must be an insertion. Let's make sure we're NEW and discard any update flags.
values.put(SYNC_STATUS, SYNC_STATUS_NEW);
values.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
updated = insertItem(uri, values) != -1 ? 1 : 0;
}
return updated;
}
/**
* This method does two things:
* * Based on the values provided, it computes and returns an incremental status change
* that can be applied to the database to track changes for syncing. This should be
* applied with {@link org.mozilla.gecko.db.DBUtils.UpdateOperation#BITWISE_OR}.
* * It mutates the provided values to mark absolute field changes.
*
* @return null if no values were provided, or no change needs to be recorded.
*/
private ContentValues processChangeValues(ContentValues values) {
if (values == null || values.size() == 0) {
return null;
}
// Otherwise, it must have been modified.
values.put(SYNC_STATUS, SYNC_STATUS_MODIFIED);
final ContentValues out = new ContentValues();
int flag = 0;
if (values.containsKey(MARKED_READ_BY) ||
values.containsKey(MARKED_READ_ON) ||
values.containsKey(IS_UNREAD)) {
flag |= SYNC_CHANGE_UNREAD_CHANGED;
}
if (values.containsKey(IS_FAVORITE)) {
flag |= SYNC_CHANGE_FAVORITE_CHANGED;
}
if (values.containsKey(RESOLVED_URL) ||
values.containsKey(RESOLVED_TITLE) ||
values.containsKey(EXCERPT)) {
flag |= SYNC_CHANGE_RESOLVED;
}
if (flag == 0) {
return null;
}
out.put(SYNC_CHANGE_FLAGS, flag);
return out;
}
/**
* Updates items that match the selection criteria.
*
* @return Number of items updated or inserted
*/
public int updateItems(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
public int updateItemsWithFlags(Uri uri, ContentValues values, ContentValues flags, String selection, String[] selectionArgs) {
trace("Updating ReadingListItems on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
if (!values.containsKey(ReadingListItems.DATE_MODIFIED)) {
values.put(ReadingListItems.DATE_MODIFIED, System.currentTimeMillis());
if (!values.containsKey(CLIENT_LAST_MODIFIED)) {
values.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
}
return db.update(TABLE_READING_LIST, values, selection, selectionArgs);
if (flags == null) {
// Dunno what we're doing with the DB that isn't changing anything we care about, but hey.
return db.update(TABLE_READING_LIST, values, selection, selectionArgs);
}
// Otherwise, we need to do smart updating to change flags.
final ContentValues[] valuesAndFlags = {values, flags};
final DBUtils.UpdateOperation[] ops = {DBUtils.UpdateOperation.ASSIGN, DBUtils.UpdateOperation.BITWISE_OR};
return DBUtils.updateArrays(db, TABLE_READING_LIST, valuesAndFlags, ops, selection, selectionArgs);
}
/**
* Inserts a new item into the DB. DATE_CREATED, DATE_MODIFIED
* and GUID fields are generated if they are not specified.
* Inserts a new item into the DB. CLIENT_LAST_MODIFIED is generated if it is not specified.
*
* Non-Sync callers will have ADDED_ON and ADDED_BY set appropriately if they are missing;
* the assumption is that this is a new item added on this device.
*
* @return ID of the newly inserted item
*/
long insertItem(Uri uri, ContentValues values) {
long now = System.currentTimeMillis();
if (!values.containsKey(ReadingListItems.DATE_CREATED)) {
values.put(ReadingListItems.DATE_CREATED, now);
private long insertItem(Uri uri, ContentValues values) {
if (!values.containsKey(CLIENT_LAST_MODIFIED)) {
values.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
}
if (!values.containsKey(ReadingListItems.DATE_MODIFIED)) {
values.put(ReadingListItems.DATE_MODIFIED, now);
// We trust the syncing code to specify SYNC_STATUS_SYNCED.
if (!isCallerSync(uri)) {
values.put(SYNC_STATUS, SYNC_STATUS_NEW);
if (!values.containsKey(ADDED_ON)) {
values.put(ADDED_ON, System.currentTimeMillis());
}
if (!values.containsKey(ADDED_BY)) {
values.put(ADDED_BY, PLACEHOLDER_THIS_DEVICE);
}
}
if (!values.containsKey(ReadingListItems.GUID)) {
values.put(ReadingListItems.GUID, Utils.generateGuid());
}
String url = values.getAsString(ReadingListItems.URL);
final String url = values.getAsString(URL);
debug("Inserting item in database with URL: " + url);
return getWritableDatabase(uri)
.insertOrThrow(TABLE_READING_LIST, null, values);
return getWritableDatabase(uri).insertOrThrow(TABLE_READING_LIST, null, values);
}
private static final ContentValues DELETED_VALUES;
static {
final ContentValues values = new ContentValues();
values.put(IS_DELETED, 1);
values.put(URL, ""); // Non-null column.
values.putNull(RESOLVED_URL);
values.putNull(RESOLVED_TITLE);
values.putNull(TITLE);
values.putNull(EXCERPT);
values.putNull(ADDED_BY);
values.putNull(MARKED_READ_BY);
// Mark it as deleted for sync purposes.
values.put(SYNC_STATUS, SYNC_STATUS_DELETED);
values.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
DELETED_VALUES = values;
}
/**
* Deletes items. Item is marked as 'deleted' so that sync can
* detect the change.
*
* It's the caller's responsibility to handle both original and resolved URLs.
* @return Number of deleted items
*/
int deleteItems(Uri uri, String selection, String[] selectionArgs) {
int deleteItems(final Uri uri, String selection, String[] selectionArgs) {
debug("Deleting item entry for URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
// TODO: also ensure that we delete affected items from the disk cache. Bug 1133158.
if (isCallerSync(uri)) {
debug("Directly deleting from reading list.");
return db.delete(TABLE_READING_LIST, selection, selectionArgs);
}
debug("Marking item entry as deleted for URI: " + uri);
ContentValues values = new ContentValues();
values.put(ReadingListItems.IS_DELETED, 1);
// If we don't have a GUID for this item, then it hasn't made it
// to the server. Just delete it.
// If we do have a GUID, blank the row and mark it as deleted.
int total = 0;
final String whereNullGUID = DBUtils.concatenateWhere(selection, GUID + " IS NULL");
final String whereNotNullGUID = DBUtils.concatenateWhere(selection, GUID + " IS NOT NULL");
cleanUpSomeDeletedRecords(uri, TABLE_READING_LIST);
return updateItems(uri, values, selection, selectionArgs);
total += db.delete(TABLE_READING_LIST, whereNullGUID, selectionArgs);
total += updateItemsWithFlags(uri, DELETED_VALUES, null, whereNotNullGUID, selectionArgs);
return total;
}
int deleteItemByID(final Uri uri, long id) {
debug("Deleting item entry for ID: " + id);
final SQLiteDatabase db = getWritableDatabase(uri);
// TODO: also ensure that we delete affected items from the disk cache. Bug 1133158.
if (isCallerSync(uri)) {
debug("Directly deleting from reading list.");
final String selection = _ID + " = " + id;
return db.delete(TABLE_READING_LIST, selection, null);
}
// If we don't have a GUID for this item, then it hasn't made it
// to the server. Just delete it.
final String whereNullGUID = _ID + " = " + id + " AND " + GUID + " IS NULL";
final int raw = db.delete(TABLE_READING_LIST, whereNullGUID, null);
if (raw > 0) {
// _ID is unique, so this should only ever be 1, but it definitely means
// we don't need to try the second part.
return raw;
}
// If we do have a GUID, blank the row and mark it as deleted.
final String whereNotNullGUID = _ID + " = " + id + " AND " + GUID + " IS NOT NULL";
final ContentValues values = new ContentValues(DELETED_VALUES);
values.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
return updateItemsWithFlags(uri, values, null, whereNotNullGUID, null);
}
@Override
@SuppressWarnings("fallthrough")
public int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
public int updateInTransaction(final Uri uri, ContentValues values, String selection, String[] selectionArgs) {
trace("Calling update in transaction on URI: " + uri);
int updated = 0;
@ -121,9 +254,14 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
case ITEMS: {
debug("Updating ITEMS: " + uri);
updated = shouldUpdateOrInsert(uri) ?
updateOrInsertItem(uri, values, selection, selectionArgs) :
updateItems(uri, values, selection, selectionArgs);
if (shouldUpdateOrInsert(uri)) {
// updateOrInsertItem handles change flags for us.
updated = updateOrInsertItem(uri, values, selection, selectionArgs);
} else {
// Don't use flags if we're inserting from sync.
ContentValues flags = isCallerSync(uri) ? null : processChangeValues(values);
updated = updateItemsWithFlags(uri, values, flags, selection, selectionArgs);
}
break;
}
@ -141,15 +279,18 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete in transaction on URI: " + uri);
// This will never clean up any items that we're about to delete, so we
// might as well run it first!
cleanUpSomeDeletedRecords(uri, TABLE_READING_LIST);
int numDeleted = 0;
int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS_ID:
debug("Deleting on ITEMS_ID: " + uri);
selection = DBUtils.concatenateWhere(selection, TABLE_READING_LIST + "._id = ?");
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
numDeleted = deleteItemByID(uri, ContentUris.parseId(uri));
break;
case ITEMS:
debug("Deleting ITEMS: " + uri);
@ -200,14 +341,15 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
switch (match) {
case ITEMS_ID:
trace("Query on ITEMS_ID: " + uri);
selection = DBUtils.concatenateWhere(selection, ReadingListItems._ID + " = ?");
selection = DBUtils.concatenateWhere(selection, _ID + " = ?");
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
case ITEMS:
trace("Query on ITEMS: " + uri);
if (!shouldShowDeleted(uri))
selection = DBUtils.concatenateWhere(ReadingListItems.IS_DELETED + " = 0", selection);
if (!shouldShowDeleted(uri)) {
selection = DBUtils.concatenateWhere(IS_DELETED + " = 0", selection);
}
break;
default:
@ -215,7 +357,7 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
}
if (TextUtils.isEmpty(sortOrder)) {
sortOrder = ReadingListItems.DEFAULT_SORT_ORDER;
sortOrder = DEFAULT_SORT_ORDER;
}
trace("Running built query.");
@ -234,14 +376,22 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
switch (match) {
case ITEMS:
trace("URI is ITEMS: " + uri);
return ReadingListItems.CONTENT_TYPE;
return CONTENT_TYPE;
case ITEMS_ID:
trace("URI is ITEMS_ID: " + uri);
return ReadingListItems.CONTENT_ITEM_TYPE;
return CONTENT_ITEM_TYPE;
}
debug("URI has unrecognized type: " + uri);
return null;
}
@Override
protected String getDeletedItemSelection(long earlierThan) {
if (earlierThan == -1L) {
return IS_DELETED + " = 1";
}
return IS_DELETED + " = 1 AND " + CLIENT_LAST_MODIFIED + " <= " + earlierThan;
}
}

View File

@ -102,9 +102,7 @@ public abstract class SharedBrowserDatabaseProvider extends AbstractPerProfileDa
// Android SQLite doesn't have LIMIT on DELETE. Instead, query for the
// IDs of matching rows, then delete them in one go.
final long now = System.currentTimeMillis();
final String selection = SyncColumns.IS_DELETED + " = 1 AND " +
SyncColumns.DATE_MODIFIED + " <= " +
(now - MAX_AGE_OF_DELETED_RECORDS);
final String selection = getDeletedItemSelection(now - MAX_AGE_OF_DELETED_RECORDS);
final String profile = fromUri.getQueryParameter(BrowserContract.PARAM_PROFILE);
final SQLiteDatabase db = getWritableDatabaseForProfile(profile, isTest(fromUri));
@ -119,4 +117,12 @@ public abstract class SharedBrowserDatabaseProvider extends AbstractPerProfileDa
db.delete(tableName, inClause, null);
}
// Override this, or override cleanUpSomeDeletedRecords.
protected String getDeletedItemSelection(long earlierThan) {
if (earlierThan == -1L) {
return SyncColumns.IS_DELETED + " = 1";
}
return SyncColumns.IS_DELETED + " = 1 AND " + SyncColumns.DATE_MODIFIED + " <= " + earlierThan;
}
}

View File

@ -48,22 +48,37 @@ class StubReadingListAccessor implements ReadingListAccessor {
}
@Override
public void addReadingListItem(ContentResolver cr, ContentValues values) {
public long addReadingListItem(ContentResolver cr, ContentValues values) {
return 0L;
}
@Override
public long addBasicReadingListItem(ContentResolver cr, String url, String title) {
return 0L;
}
@Override
public void updateReadingListItem(ContentResolver cr, ContentValues values) {
}
@Override
public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
}
@Override
public void registerContentObserver(Context context, ContentObserver observer) {
}
@Override
public void markAsRead(ContentResolver cr, long itemID) {
}
@Override
public void updateContent(ContentResolver cr, long itemID, String resolvedTitle, String resolvedURL, String excerpt) {
}
@Override
public void deleteItem(ContentResolver cr, long itemID) {
}
}

View File

@ -10,31 +10,30 @@ import android.content.Context;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.LocalBrowserDB;
import org.mozilla.gecko.db.ReadingListProvider;
import org.mozilla.gecko.overlays.service.ShareData;
import static org.mozilla.gecko.db.BrowserContract.Bookmarks;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems;
/**
* ShareMethod to add a page to the reading list.
*
* Inserts the given URL/title pair into the reading list database.
* TODO: In the event the page turns out not to be reader-mode-compatible, freezes sometimes occur
* when we subsequently load the page in reader mode. (Bug 1044781)
*/
public class AddToReadingList extends ShareMethod {
private static final String LOGTAG = "GeckoAddToReadingList";
@Override
public Result handle(ShareData shareData) {
ContentResolver resolver = context.getContentResolver();
final ContentResolver resolver = context.getContentResolver();
LocalBrowserDB browserDB = new LocalBrowserDB(GeckoProfile.DEFAULT_PROFILE);
final ContentValues values = new ContentValues();
values.put(ReadingListItems.TITLE, shareData.title);
values.put(ReadingListItems.URL, shareData.url);
values.put(ReadingListItems.ADDED_ON, System.currentTimeMillis());
values.put(ReadingListItems.ADDED_BY, ReadingListProvider.PLACEHOLDER_THIS_DEVICE);
ContentValues values = new ContentValues();
values.put(Bookmarks.TITLE, shareData.title);
values.put(Bookmarks.URL, shareData.url);
browserDB.getReadingListAccessor().addReadingListItem(resolver, values);
new LocalBrowserDB(GeckoProfile.DEFAULT_PROFILE).getReadingListAccessor().addReadingListItem(resolver, values);
return Result.SUCCESS;
}

View File

@ -60,7 +60,7 @@
android:key="sync_now"
android:defaultValue=""
android:persistent="false"
android:title="Sync now"
android:title="@string/fxaccount_status_sync_now"
android:summary="" />
<CheckBoxPreference

View File

@ -15,13 +15,15 @@ import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
import org.mozilla.gecko.BrowserApp.Refreshable;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.widget.ThemedImageButton;
import org.mozilla.gecko.widget.ThemedLinearLayout;
public class TabStrip extends ThemedLinearLayout {
public class TabStrip extends ThemedLinearLayout
implements Refreshable {
private static final String LOGTAG = "GeckoTabStrip";
private final TabStripView tabStripView;
@ -128,6 +130,11 @@ public class TabStrip extends ThemedLinearLayout {
}
}
@Override
public void refresh() {
tabStripView.refresh();
}
@Override
public void onLightweightThemeChanged() {
final Drawable drawable = getTheme().getDrawable(this);

View File

@ -102,7 +102,7 @@ public class TabStripView extends TwoWayView {
updateSelectedStyle(selected);
if (ensureVisible) {
ensurePositionIsVisible(selected);
ensurePositionIsVisible(selected, true);
}
}
}
@ -239,10 +239,14 @@ public class TabStripView extends TwoWayView {
});
}
private void ensurePositionIsVisible(final int position) {
/**
* Ensures the tab at the given position is visible. If we are not restoring tabs and
* shouldAnimate == true, the tab will animate to be visible, if it is not already visible.
*/
private void ensurePositionIsVisible(final int position, final boolean shouldAnimate) {
// We just want to move the strip to the right position
// when restoring tabs on startup.
if (isRestoringTabs) {
if (isRestoringTabs || !shouldAnimate) {
setSelection(position);
return;
}
@ -414,6 +418,13 @@ public class TabStripView extends TwoWayView {
drawFadingEdge(canvas);
}
public void refresh() {
final int selectedPosition = getPositionForSelectedTab();
if (selectedPosition != -1) {
ensurePositionIsVisible(selectedPosition, false);
}
}
private class TabAnimatorListener implements AnimatorListener {
private void setLayerType(int layerType) {
final int childCount = getChildCount();

View File

@ -8,8 +8,12 @@ import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.Callable;
import android.app.Activity;
import android.content.ContentResolver;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
import org.mozilla.gecko.db.ReadingListAccessor;
import org.mozilla.gecko.db.ReadingListProvider;
import android.content.ContentProvider;
@ -19,23 +23,46 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.*;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.ADDED_ON;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.CLIENT_LAST_MODIFIED;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.EXCERPT;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.GUID;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.IS_UNREAD;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.RESOLVED_TITLE;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.RESOLVED_URL;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SERVER_LAST_MODIFIED;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SERVER_STORED_ON;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_FLAGS;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_NONE;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_RESOLVED;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_UNREAD_CHANGED;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS_MODIFIED;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS_NEW;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS_SYNCED;
import static org.mozilla.gecko.db.BrowserContract.URLColumns.TITLE;
import static org.mozilla.gecko.db.BrowserContract.URLColumns.URL;
public class testReadingListProvider extends ContentProviderTest {
private static final String DB_NAME = "browser.db";
// List of tests to be run sorted by dependency.
private final TestCase[] TESTS_TO_RUN = { new TestInsertItems(),
new TestDeleteItems(),
new TestUpdateItems(),
new TestBatchOperations(),
new TestBrowserProviderNotifications() };
private final TestCase[] TESTS_TO_RUN = {
new TestInsertItems(),
new TestDeleteItems(),
new TestUpdateItems(),
new TestBatchOperations(),
new TestBrowserProviderNotifications(),
new TestStateSequencing(),
};
// Columns used to test for item equivalence.
final String[] TEST_COLUMNS = { ReadingListItems.TITLE,
ReadingListItems.URL,
ReadingListItems.EXCERPT,
ReadingListItems.LENGTH,
ReadingListItems.DATE_CREATED };
final String[] TEST_COLUMNS = { TITLE,
URL,
EXCERPT,
ADDED_ON };
// Indicates that insertions have been tested. ContentProvider.insert
// has been proven to work.
@ -64,6 +91,13 @@ public class testReadingListProvider extends ContentProviderTest {
for (TestCase test: TESTS_TO_RUN) {
mTests.add(test);
}
// Disable background fetches of content, because it causes the test harness
// to kill us for network fetches.
Activity a = getActivity();
if (a instanceof BrowserApp) {
((BrowserApp) a).getReadingListHelper().disableBackgroundFetches();
}
}
public void testReadingListProviderTests() throws Exception {
@ -85,21 +119,21 @@ public class testReadingListProvider extends ContentProviderTest {
@Override
public void test() throws Exception {
ContentValues b = createFillerReadingListItem();
long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
long id = ContentUris.parseId(mProvider.insert(CONTENT_URI, b));
Cursor c = getItemById(id);
try {
mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
assertRowEqualsContentValues(c, b);
mAsserter.is(c.getInt(c.getColumnIndex(ReadingListItems.CONTENT_STATUS)),
ReadingListItems.STATUS_UNFETCHED,
mAsserter.is(c.getInt(c.getColumnIndex(CONTENT_STATUS)),
STATUS_UNFETCHED,
"Inserted item has correct default content status");
} finally {
c.close();
}
testInsertWithNullCol(ReadingListItems.GUID);
testInsertWithNullCol(URL);
mContentProviderInsertTested = true;
}
@ -112,7 +146,7 @@ public class testReadingListProvider extends ContentProviderTest {
b.putNull(colName);
try {
ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
ContentUris.parseId(mProvider.insert(CONTENT_URI, b));
// If we get to here, the flawed insertion succeeded. Fail the test.
mAsserter.ok(false, "Insertion did not succeed with " + colName + " == null", "");
} catch (NullPointerException e) {
@ -128,17 +162,31 @@ public class testReadingListProvider extends ContentProviderTest {
@Override
public void test() throws Exception {
long id = insertAnItemWithAssertion();
// Test that the item is only marked as deleted and
final long one = insertAnItemWithAssertion();
final long two = insertAnItemWithAssertion();
assignGUID(one);
// Test that the item with a GUID is only marked as deleted and
// not removed from the database.
testNonFirefoxSyncDelete(id);
testNonSyncDelete(one, true);
// Test that the item is removed from the database.
testFirefoxSyncDelete(id);
// The item without a GUID is just deleted immediately.
testNonSyncDelete(two, false);
// Test that the item with a GUID is removed from the database when deleted by Sync.
testSyncDelete(one);
final long three = insertAnItemWithAssertion();
id = insertAnItemWithAssertion();
// Test that deleting works with only a URI.
testDeleteWithItemURI(id);
testDeleteWithItemURI(three);
}
private void assignGUID(final long id) {
final ContentValues values = new ContentValues();
values.put(GUID, "abcdefghi");
mProvider.update(CONTENT_URI, values, _ID + " = " + id, null);
}
/**
@ -146,19 +194,26 @@ public class testReadingListProvider extends ContentProviderTest {
* as deleted and not actually removed from the database. Also verify that the item
* marked as deleted doesn't show up in a query.
*
* Note that items are deleted immediately if they don't have a GUID.
*
* @param id of the item to be deleted
* @param hasGUID if true, we expect the item to stick around and be marked.
*/
private void testNonFirefoxSyncDelete(long id) {
final int deleted = mProvider.delete(ReadingListItems.CONTENT_URI,
ReadingListItems._ID + " = ?",
new String[] { String.valueOf(id) });
private void testNonSyncDelete(long id, boolean hasGUID) {
final int deleted = mProvider.delete(CONTENT_URI,
_ID + " = " + id,
null);
mAsserter.is(deleted, 1, "Inserted item was deleted");
// PARAM_SHOW_DELETED in the URI allows items marked as deleted to be
// included in the query.
Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
assertItemExistsByID(uri, id, "Deleted item was only marked as deleted");
Uri uri = appendUriParam(CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
if (hasGUID) {
assertItemExistsByID(uri, id, "Deleted item was only marked as deleted");
} else {
assertItemDoesNotExistByID(uri, id, "Deleted item had no GUID, so was really deleted.");
}
// Test that the 'deleted' item does not show up in a query when PARAM_SHOW_DELETED
// is not specified in the URI.
@ -171,14 +226,14 @@ public class testReadingListProvider extends ContentProviderTest {
*
* @param id of the item to be deleted
*/
private void testFirefoxSyncDelete(long id) {
final int deleted = mProvider.delete(appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
ReadingListItems._ID + " = ?",
new String[] { String.valueOf(id) });
private void testSyncDelete(long id) {
final int deleted = mProvider.delete(appendUriParam(CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
_ID + " = " + id,
null);
mAsserter.is(deleted, 1, "Inserted item was deleted");
Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
Uri uri = appendUriParam(CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
assertItemDoesNotExistByID(uri, id, "Inserted item is now actually deleted");
}
@ -189,7 +244,7 @@ public class testReadingListProvider extends ContentProviderTest {
* @param id of the item to be deleted
*/
private void testDeleteWithItemURI(long id) {
final int deleted = mProvider.delete(ContentUris.withAppendedId(ReadingListItems.CONTENT_URI, id), null, null);
final int deleted = mProvider.delete(ContentUris.withAppendedId(CONTENT_URI, id), null, null);
mAsserter.is(deleted, 1, "Inserted item was deleted using URI with id");
}
}
@ -205,7 +260,7 @@ public class testReadingListProvider extends ContentProviderTest {
ensureCanInsert();
ContentValues original = createFillerReadingListItem();
long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, original));
long id = ContentUris.parseId(mProvider.insert(CONTENT_URI, original));
int updated = 0;
Long originalDateCreated = null;
Long originalDateModified = null;
@ -214,16 +269,16 @@ public class testReadingListProvider extends ContentProviderTest {
try {
mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
originalDateCreated = c.getLong(c.getColumnIndex(ReadingListItems.DATE_CREATED));
originalDateModified = c.getLong(c.getColumnIndex(ReadingListItems.DATE_MODIFIED));
originalDateCreated = c.getLong(c.getColumnIndex(ADDED_ON));
originalDateModified = c.getLong(c.getColumnIndex(CLIENT_LAST_MODIFIED));
updates.put(ReadingListItems.TITLE, original.getAsString(ReadingListItems.TITLE) + "CHANGED");
updates.put(ReadingListItems.URL, original.getAsString(ReadingListItems.URL) + "/more/stuff");
updates.put(ReadingListItems.EXCERPT, original.getAsString(ReadingListItems.EXCERPT) + "CHANGED");
updates.put(TITLE, original.getAsString(TITLE) + "CHANGED");
updates.put(URL, original.getAsString(URL) + "/more/stuff");
updates.put(EXCERPT, original.getAsString(EXCERPT) + "CHANGED");
updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
ReadingListItems._ID + " = ?",
new String[] { String.valueOf(id) });
updated = mProvider.update(CONTENT_URI, updates,
_ID + " = ?",
new String[] { String.valueOf(id) });
mAsserter.is(updated, 1, "Inserted item was updated");
} finally {
@ -232,18 +287,17 @@ public class testReadingListProvider extends ContentProviderTest {
// Name change for clarity. These values will be compared with the
// current cursor row.
ContentValues expectedValues = updates;
final ContentValues expectedValues = updates;
c = getItemById(id);
try {
mAsserter.ok(c.moveToFirst(), "Updated item found", "");
mAsserter.isnot(c.getLong(c.getColumnIndex(ReadingListItems.DATE_MODIFIED)),
mAsserter.isnot(c.getLong(c.getColumnIndex(CLIENT_LAST_MODIFIED)),
originalDateModified,
"Date modified should have changed");
// DATE_CREATED and LENGTH should equal old values since they weren't updated.
expectedValues.put(ReadingListItems.DATE_CREATED, originalDateCreated);
expectedValues.put(ReadingListItems.LENGTH, original.getAsString(ReadingListItems.LENGTH));
assertRowEqualsContentValues(c, expectedValues, /* compareDateModified */ false);
// ADDED_ON shouldn't have changed.
expectedValues.put(ADDED_ON, originalDateCreated);
assertRowEqualsContentValues(c, expectedValues, /* compareDateModified */ false, TEST_COLUMNS);
} finally {
c.close();
}
@ -251,44 +305,24 @@ public class testReadingListProvider extends ContentProviderTest {
// Test that updates on an item that doesn't exist does not modify any rows.
testUpdateWithInvalidID();
// Test that update fails when a GUID is null.
testUpdateWithNullCol(id, ReadingListItems.GUID);
mContentProviderUpdateTested = true;
}
/**
* Test that updates on an item that doesn't exist does
* not modify any rows.
*
* @param id of the item to be deleted
*/
private void testUpdateWithInvalidID() {
ensureEmptyDatabase();
final ContentValues b = createFillerReadingListItem();
final long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
final long id = ContentUris.parseId(mProvider.insert(CONTENT_URI, b));
final long INVALID_ID = id + 1;
final ContentValues updates = new ContentValues();
updates.put(ReadingListItems.TITLE, b.getAsString(ReadingListItems.TITLE) + "CHANGED");
final int updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
ReadingListItems._ID + " = ?",
new String[] { String.valueOf(INVALID_ID) });
mAsserter.is(updated, 0, "Should not be able to update item with an invalid GUID");
}
/**
* Test that update fails when a required column is null.
*/
private int testUpdateWithNullCol(long id, String colName) {
ContentValues updates = new ContentValues();
updates.putNull(colName);
int updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
ReadingListItems._ID + " = ?",
new String[] { String.valueOf(id) });
mAsserter.is(updated, 0, "Should not be able to update item with " + colName + " == null ");
return updated;
updates.put(TITLE, b.getAsString(TITLE) + "CHANGED");
final int updated = mProvider.update(CONTENT_URI, updates,
_ID + " = ?",
new String[] { String.valueOf(INVALID_ID) });
mAsserter.is(updated, 0, "Should not be able to update item with an invalid ID");
}
}
@ -306,23 +340,22 @@ public class testReadingListProvider extends ContentProviderTest {
for (int i = 0; i < ITEM_COUNT; i++) {
final String url = "http://www.test.org/" + i;
allVals[i] = new ContentValues();
allVals[i].put(ReadingListItems.TITLE, "Test" + i);
allVals[i].put(ReadingListItems.URL, url);
allVals[i].put(ReadingListItems.EXCERPT, "EXCERPT" + i);
allVals[i].put(ReadingListItems.LENGTH, i);
allVals[i].put(TITLE, "Test" + i);
allVals[i].put(URL, url);
allVals[i].put(EXCERPT, "EXCERPT" + i);
urls.add(url);
}
int inserts = mProvider.bulkInsert(ReadingListItems.CONTENT_URI, allVals);
int inserts = mProvider.bulkInsert(CONTENT_URI, allVals);
mAsserter.is(inserts, ITEM_COUNT, "Excepted number of inserts matches");
Cursor c = mProvider.query(ReadingListItems.CONTENT_URI, null,
null,
null,
null);
final Cursor c = mProvider.query(CONTENT_URI, null,
null,
null,
null);
try {
while (c.moveToNext()) {
final String url = c.getString(c.getColumnIndex(ReadingListItems.URL));
final String url = c.getString(c.getColumnIndex(URL));
mAsserter.ok(urls.contains(url), "Bulk inserted item with url == " + url + " was found in the DB", "");
// We should only be seeing each item once. Remove from set to prevent dups.
urls.remove(url);
@ -369,10 +402,10 @@ public class testReadingListProvider extends ContentProviderTest {
// Update
mResolver.notifyChangeList.clear();
h.put(ReadingListItems.TITLE, "http://newexample.com");
h.put(TITLE, "http://newexample.com");
long numUpdated = mProvider.update(ReadingListItems.CONTENT_URI, h,
ReadingListItems._ID + " = ?",
_ID + " = ?",
new String[] { String.valueOf(id) });
mAsserter.is(numUpdated,
@ -423,12 +456,198 @@ public class testReadingListProvider extends ContentProviderTest {
}
}
private class TestStateSequencing extends TestCase {
@Override
protected void test() throws Exception {
final ReadingListAccessor accessor = getTestProfile().getDB().getReadingListAccessor();
final ContentResolver cr = getActivity().getContentResolver();
final Uri syncURI = CONTENT_URI.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1")
.appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1")
.build();
mAsserter.ok(accessor != null, "We have an accessor.", null);
// Verify that the accessor thinks we're empty.
mAsserter.ok(0 == accessor.getCount(cr), "We have no items.", null);
// Insert an item via the accessor.
final long addedItem = accessor.addBasicReadingListItem(cr, "http://example.org/", "Example A");
mAsserter.ok(1 == accessor.getCount(cr), "We have one item.", null);
final Cursor cursor = accessor.getReadingList(cr);
try {
mAsserter.ok(cursor.moveToNext(), "The cursor isn't empty.", null);
mAsserter.ok(1 == cursor.getCount(), "The cursor agrees.", null);
} finally {
cursor.close();
}
// Verify that it has no GUID, that its state is NEW, etc.
// This requires fetching more fields than the accessor uses.
final Cursor all = getEverything(syncURI);
try {
mAsserter.ok(all.moveToNext(), "The cursor isn't empty.", null);
mAsserter.ok(1 == all.getCount(), "The cursor agrees.", null);
ContentValues expected = new ContentValues();
expected.putNull(GUID);
expected.put(SYNC_STATUS, SYNC_STATUS_NEW);
expected.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
expected.put(URL, "http://example.org/");
expected.put(TITLE, "Example A");
expected.putNull(RESOLVED_URL);
expected.putNull(RESOLVED_TITLE);
final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, URL, TITLE, RESOLVED_URL, RESOLVED_TITLE, URL, TITLE};
assertRowEqualsContentValues(all, expected, false, testColumns);
} finally {
all.close();
}
// Pretend that it was just synced.
final long serverTime = System.currentTimeMillis();
final ContentValues wasSynced = new ContentValues();
wasSynced.put(GUID, "eeeeeeeeeeeeee");
wasSynced.put(SYNC_STATUS, SYNC_STATUS_SYNCED);
wasSynced.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
wasSynced.put(SERVER_STORED_ON, serverTime);
wasSynced.put(SERVER_LAST_MODIFIED, serverTime);
mAsserter.ok(1 == mProvider.update(syncURI, wasSynced, _ID + " = " + addedItem, null), "Updated one item.", null);
final Cursor afterSync = getEverything(syncURI);
try {
mAsserter.ok(afterSync.moveToNext(), "The cursor isn't empty.", null);
final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, SERVER_STORED_ON, SERVER_LAST_MODIFIED};
assertRowEqualsContentValues(afterSync, wasSynced, false, testColumns);
} finally {
afterSync.close();
}
// Make changes to the record that exercise the various change flags, verifying that
// the correct flags are set.
final long beforeMarkedRead = System.currentTimeMillis();
accessor.markAsRead(cr, addedItem);
final ContentValues markedAsRead = new ContentValues();
markedAsRead.put(GUID, "eeeeeeeeeeeeee");
markedAsRead.put(SYNC_STATUS, SYNC_STATUS_MODIFIED);
markedAsRead.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_UNREAD_CHANGED);
markedAsRead.put(IS_UNREAD, 0);
final Cursor afterMarkedRead = getEverything(syncURI);
try {
mAsserter.ok(afterMarkedRead.moveToNext(), "The cursor isn't empty.", null);
final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, IS_UNREAD};
assertRowEqualsContentValues(afterMarkedRead, markedAsRead, false, testColumns);
assertModifiedInRange(afterMarkedRead, beforeMarkedRead);
} finally {
afterMarkedRead.close();
}
// Now our content is here!
final long beforeContentUpdated = System.currentTimeMillis();
accessor.updateContent(cr, addedItem, "New title", "http://www.example.com/article", "The excerpt is long.");
// After this the content status should have changed, and we should be flagged to sync.
final ContentValues contentUpdated = new ContentValues();
contentUpdated.put(GUID, "eeeeeeeeeeeeee");
contentUpdated.put(SYNC_STATUS, SYNC_STATUS_MODIFIED);
contentUpdated.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_UNREAD_CHANGED | SYNC_CHANGE_RESOLVED);
contentUpdated.put(IS_UNREAD, 0);
contentUpdated.put(CONTENT_STATUS, STATUS_FETCHED_ARTICLE);
final Cursor afterContentUpdated = getEverything(syncURI);
try {
mAsserter.ok(afterContentUpdated.moveToNext(), "The cursor isn't empty.", null);
final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, IS_UNREAD, CONTENT_STATUS};
assertRowEqualsContentValues(afterContentUpdated, contentUpdated, false, testColumns);
assertModifiedInRange(afterContentUpdated, beforeContentUpdated);
} finally {
afterContentUpdated.close();
}
// Delete the record, and verify that its Sync state is DELETED.
final long beforeDeletion = System.currentTimeMillis();
accessor.deleteItem(cr, addedItem);
final ContentValues itemDeleted = new ContentValues();
itemDeleted.put(GUID, "eeeeeeeeeeeeee");
itemDeleted.put(SYNC_STATUS, SYNC_STATUS_DELETED);
itemDeleted.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
// TODO: CONTENT_STATUS on deletion?
final Cursor afterDeletion = getEverything(syncURI);
try {
mAsserter.ok(afterDeletion.moveToNext(), "The cursor isn't empty.", null);
final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS};
assertRowEqualsContentValues(afterDeletion, itemDeleted, false, testColumns);
assertModifiedInRange(afterDeletion, beforeDeletion);
} finally {
afterDeletion.close();
}
// The accessor will no longer return the record.
mAsserter.ok(0 == accessor.getCount(cr), "No items found.", null);
// Add a new record as Sync -- it should start in state SYNCED.
final ContentValues newRecord = new ContentValues();
final long newServerTime = System.currentTimeMillis() - 50000;
newRecord.put(GUID, "ffeeeeeeeeeeee");
newRecord.put(SYNC_STATUS, SYNC_STATUS_SYNCED);
newRecord.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
newRecord.put(SERVER_STORED_ON, newServerTime);
newRecord.put(SERVER_LAST_MODIFIED, newServerTime);
newRecord.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
newRecord.put(URL, "http://www.mozilla.org/");
newRecord.put(TITLE, "Mozilla");
final long newID = ContentUris.parseId(cr.insert(syncURI, newRecord));
mAsserter.ok(newID > 0, "New ID is greater than 0.", null);
mAsserter.ok(newID != addedItem, "New ID differs from last ID.", null);
final Cursor afterNewInsert = getEverything(syncURI);
try {
mAsserter.ok(afterNewInsert.moveToNext(), "The cursor isn't empty.", null);
mAsserter.ok(2 == afterNewInsert.getCount(), "The cursor has two rows.", null);
// Default sort order means newest first.
final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, SERVER_STORED_ON, SERVER_LAST_MODIFIED, CLIENT_LAST_MODIFIED, URL, TITLE};
assertRowEqualsContentValues(afterNewInsert, newRecord, false, testColumns);
} finally {
afterNewInsert.close();
}
// Make a change to it. Verify that it's now changed with the right flags.
final long beforeNewRead = System.currentTimeMillis();
accessor.markAsRead(cr, newID);
newRecord.put(SYNC_STATUS, SYNC_STATUS_MODIFIED);
newRecord.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_UNREAD_CHANGED);
final Cursor afterNewRead = getEverything(syncURI);
try {
mAsserter.ok(afterNewRead.moveToNext(), "The cursor isn't empty.", null);
mAsserter.ok(2 == afterNewRead.getCount(), "The cursor has two rows.", null);
// Default sort order means newest first.
final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, SERVER_STORED_ON, SERVER_LAST_MODIFIED, URL, TITLE};
assertRowEqualsContentValues(afterNewRead, newRecord, false, testColumns);
assertModifiedInRange(afterNewRead, beforeNewRead);
} finally {
afterNewRead.close();
}
}
private void assertModifiedInRange(Cursor cursor, long earliest) {
final long dbModified = cursor.getLong(cursor.getColumnIndexOrThrow(CLIENT_LAST_MODIFIED));
mAsserter.ok(dbModified >= earliest, "DB timestamp is at least as late as earliest.", null);
mAsserter.ok(dbModified <= System.currentTimeMillis(), "DB timestamp is earlier than now.", null);
}
}
/**
* Removes all items from the DB.
*/
private void ensureEmptyDatabase() {
Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1");
getWritableDatabase(uri).delete(ReadingListItems.TABLE_NAME, null, null);
getWritableDatabase(CONTENT_URI).delete(TABLE_NAME, null, null);
}
@ -443,34 +662,35 @@ public class testReadingListProvider extends ContentProviderTest {
* Checks that the values in the cursor's current row match those
* in the ContentValues object.
*
* @param cursor over the row to be checked
* @param values to be checked
* @param testColumns
* @param cursorWithActual over the row to be checked
* @param expectedValues to be checked
*/
private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues, boolean compareDateModified) {
for (String column: TEST_COLUMNS) {
private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues, boolean compareDateModified, String[] testColumns) {
for (String column: testColumns) {
String expected = expectedValues.getAsString(column);
String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(column));
mAsserter.is(actual, expected, "Item has correct " + column);
}
if (compareDateModified) {
String expected = expectedValues.getAsString(ReadingListItems.DATE_MODIFIED);
String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(ReadingListItems.DATE_MODIFIED));
mAsserter.is(actual, expected, "Item has correct " + ReadingListItems.DATE_MODIFIED);
String expected = expectedValues.getAsString(CLIENT_LAST_MODIFIED);
String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(CLIENT_LAST_MODIFIED));
mAsserter.is(actual, expected, "Item has correct " + CLIENT_LAST_MODIFIED);
}
}
private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues) {
assertRowEqualsContentValues(cursorWithActual, expectedValues, true);
assertRowEqualsContentValues(cursorWithActual, expectedValues, true, TEST_COLUMNS);
}
private ContentValues fillContentValues(String title, String url, String excerpt) {
ContentValues values = new ContentValues();
values.put(ReadingListItems.TITLE, title);
values.put(ReadingListItems.URL, url);
values.put(ReadingListItems.EXCERPT, excerpt);
values.put(ReadingListItems.LENGTH, excerpt.length());
values.put(TITLE, title);
values.put(URL, url);
values.put(EXCERPT, excerpt);
values.put(ADDED_ON, System.currentTimeMillis());
return values;
}
@ -480,15 +700,23 @@ public class testReadingListProvider extends ContentProviderTest {
return fillContentValues("Example", "http://example.com/?num=" + rand.nextInt(), "foo bar");
}
private Cursor getEverything(Uri uri) {
return mProvider.query(uri,
null,
null,
null,
null);
}
private Cursor getItemById(Uri uri, long id, String[] projection) {
return mProvider.query(uri, projection,
ReadingListItems._ID + " = ?",
_ID + " = ?",
new String[] { String.valueOf(id) },
null);
}
private Cursor getItemById(long id) {
return getItemById(ReadingListItems.CONTENT_URI, id, null);
return getItemById(CONTENT_URI, id, null);
}
private Cursor getItemById(Uri uri, long id) {
@ -518,7 +746,7 @@ public class testReadingListProvider extends ContentProviderTest {
ensureCanInsert();
ContentValues v = createFillerReadingListItem();
long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, v));
long id = ContentUris.parseId(mProvider.insert(CONTENT_URI, v));
assertItemExistsByID(id, "Inserted item found");
return id;

View File

@ -607,6 +607,11 @@ pref("gfx.color_management.enablev4", false);
pref("gfx.downloadable_fonts.enabled", true);
pref("gfx.downloadable_fonts.fallback_delay", 3000);
// disable downloadable font cache so that behavior is consistently
// the uncached load behavior across pages (useful for testing reflow problems)
pref("gfx.downloadable_fonts.disable_cache", false);
#ifdef RELEASE_BUILD
pref("gfx.downloadable_fonts.woff2.enabled", false);
#else

View File

@ -412,14 +412,11 @@ NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
if (NS_SUCCEEDED(aResult)) {
if (aIsNew) {
mozilla::Telemetry::AccumulateTimeDelta(
mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS,
mLoadStart);
}
else {
mozilla::Telemetry::AccumulateTimeDelta(
mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS,
mLoadStart);
CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
} else {
CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
}
}

View File

@ -3576,6 +3576,45 @@ CacheFileIOManager::CreateCacheTree()
StartRemovingTrash();
if (!CacheObserver::CacheFSReported()) {
uint32_t fsType = 4; // Other OS
#ifdef XP_WIN
nsAutoString target;
nsresult rv = mCacheDirectory->GetTarget(target);
if (NS_FAILED(rv)) {
return NS_OK;
}
wchar_t volume_path[MAX_PATH + 1] = { 0 };
if (!::GetVolumePathNameW(target.get(),
volume_path,
mozilla::ArrayLength(volume_path))) {
return NS_OK;
}
wchar_t fsName[6] = { 0 };
if (!::GetVolumeInformationW(volume_path, nullptr, 0, nullptr, nullptr,
nullptr, fsName,
mozilla::ArrayLength(fsName))) {
return NS_OK;
}
if (wcscmp(fsName, L"NTFS") == 0) {
fsType = 0;
} else if (wcscmp(fsName, L"FAT32") == 0) {
fsType = 1;
} else if (wcscmp(fsName, L"FAT") == 0) {
fsType = 2;
} else {
fsType = 3;
}
#endif
Telemetry::Accumulate(Telemetry::NETWORK_CACHE_FS_TYPE, fsType);
CacheObserver::SetCacheFSReported();
}
return NS_OK;
}
@ -3619,6 +3658,18 @@ CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate)
LOG(("CacheFileIOManager::OpenNSPRHandle() - Successfully evicted entry"
" with hash %08x%08x%08x%08x%08x. %s to create the new file.",
LOGSHA1(&hash), NS_SUCCEEDED(rv) ? "Succeeded" : "Failed"));
// Report the full size only once per session
static bool sSizeReported = false;
if (!sSizeReported) {
uint32_t cacheUsage;
if (NS_SUCCEEDED(CacheIndex::GetCacheSize(&cacheUsage))) {
cacheUsage >>= 10;
Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE_FULL_FAT,
cacheUsage);
sSizeReported = true;
}
}
} else {
LOG(("CacheFileIOManager::OpenNSPRHandle() - Couldn't evict an existing"
" entry."));

View File

@ -64,6 +64,7 @@ CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString
CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, const nsACString &aKey)
: CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL)
, mHandle(nullptr)
, mFirstRead(true)
, mHashArray(nullptr)
, mHashArraySize(0)
, mHashCount(0)
@ -95,6 +96,7 @@ CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, const nsACString &aKey)
CacheFileMetadata::CacheFileMetadata()
: CacheMemoryConsumer(DONT_REPORT /* This is a helper class */)
, mHandle(nullptr)
, mFirstRead(true)
, mHashArray(nullptr)
, mHashArraySize(0)
, mHashCount(0)
@ -206,6 +208,7 @@ CacheFileMetadata::ReadMetadata(CacheFileMetadataListener *aListener)
LOG(("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
"offset=%lld, filesize=%lld [this=%p]", offset, size, this));
mReadStart = mozilla::TimeStamp::Now();
mListener = aListener;
rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
if (NS_FAILED(rv)) {
@ -629,6 +632,16 @@ CacheFileMetadata::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
return NS_OK;
}
if (mFirstRead) {
Telemetry::AccumulateTimeDelta(
Telemetry::NETWORK_CACHE_METADATA_FIRST_READ_TIME_MS, mReadStart);
Telemetry::Accumulate(
Telemetry::NETWORK_CACHE_METADATA_FIRST_READ_SIZE, mBufSize);
} else {
Telemetry::AccumulateTimeDelta(
Telemetry::NETWORK_CACHE_METADATA_SECOND_READ_TIME_MS, mReadStart);
}
// check whether we have read all necessary data
uint32_t realOffset = NetworkEndian::readUint32(mBuf + mBufSize -
sizeof(uint32_t));
@ -663,6 +676,8 @@ CacheFileMetadata::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
LOG(("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
"have full metadata. [this=%p]", missing, this));
mFirstRead = false;
mReadStart = mozilla::TimeStamp::Now();
rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
if (NS_FAILED(rv)) {
LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() "
@ -680,6 +695,9 @@ CacheFileMetadata::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
return NS_OK;
}
Telemetry::Accumulate(Telemetry::NETWORK_CACHE_METADATA_SIZE,
size - realOffset);
// We have all data according to offset information at the end of the entry.
// Try to parse it.
rv = ParseMetadata(realOffset, realOffset - usedOffset, true);

View File

@ -176,6 +176,8 @@ private:
nsRefPtr<CacheFileHandle> mHandle;
nsCString mKey;
bool mFirstRead;
mozilla::TimeStamp mReadStart;
CacheHash::Hash16_t *mHashArray;
uint32_t mHashArraySize;
uint32_t mHashCount;

View File

@ -424,6 +424,116 @@ ValidityMap::operator[](uint32_t aIdx)
return mMap.ElementAt(aIdx);
}
StaticMutex DetailedCacheHitTelemetry::sLock;
uint32_t DetailedCacheHitTelemetry::sRecordCnt = 0;
DetailedCacheHitTelemetry::HitRate DetailedCacheHitTelemetry::sHRStats[kNumOfRanges];
DetailedCacheHitTelemetry::HitRate::HitRate()
{
Reset();
}
void
DetailedCacheHitTelemetry::HitRate::AddRecord(ERecType aType)
{
if (aType == HIT) {
++mHitCnt;
} else {
++mMissCnt;
}
}
uint32_t
DetailedCacheHitTelemetry::HitRate::GetHitRateBucket(uint32_t aNumOfBuckets) const
{
uint32_t bucketIdx = (aNumOfBuckets * mHitCnt) / (mHitCnt + mMissCnt);
if (bucketIdx == aNumOfBuckets) { // make sure 100% falls into the last bucket
--bucketIdx;
}
return bucketIdx;
}
uint32_t
DetailedCacheHitTelemetry::HitRate::Count()
{
return mHitCnt + mMissCnt;
}
void
DetailedCacheHitTelemetry::HitRate::Reset()
{
mHitCnt = 0;
mMissCnt = 0;
}
// static
void
DetailedCacheHitTelemetry::AddRecord(ERecType aType, TimeStamp aLoadStart)
{
bool isUpToDate = false;
CacheIndex::IsUpToDate(&isUpToDate);
if (!isUpToDate) {
// Ignore the record when the entry file count might be incorrect
return;
}
uint32_t entryCount;
nsresult rv = CacheIndex::GetEntryFileCount(&entryCount);
if (NS_FAILED(rv)) {
return;
}
uint32_t rangeIdx = entryCount / kRangeSize;
if (rangeIdx >= kNumOfRanges) { // The last range has no upper limit.
rangeIdx = kNumOfRanges - 1;
}
uint32_t hitMissValue = 2 * rangeIdx; // 2 values per range
if (aType == MISS) { // The order is HIT, MISS
++hitMissValue;
}
StaticMutexAutoLock lock(sLock);
if (aType == MISS) {
mozilla::Telemetry::AccumulateTimeDelta(
mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS,
aLoadStart);
} else {
mozilla::Telemetry::AccumulateTimeDelta(
mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS,
aLoadStart);
}
Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_MISS_STAT_PER_CACHE_SIZE,
hitMissValue);
sHRStats[rangeIdx].AddRecord(aType);
++sRecordCnt;
if (sRecordCnt < kTotalSamplesReportLimit) {
return;
}
sRecordCnt = 0;
for (uint32_t i = 0; i < kNumOfRanges; ++i) {
if (sHRStats[i].Count() >= kHitRateSamplesReportLimit) {
// The telemetry enums are grouped by buckets as follows:
// Telemetry value : 0,1,2,3, ... ,19,20,21,22, ... ,398,399
// Hit rate bucket : 0,0,0,0, ... , 0, 1, 1, 1, ... , 19, 19
// Cache size range: 0,1,2,3, ... ,19, 0, 1, 2, ... , 18, 19
uint32_t bucketOffset = sHRStats[i].GetHitRateBucket(kHitRateBuckets) *
kNumOfRanges;
Telemetry::Accumulate(Telemetry::NETWORK_CACHE_HIT_RATE_PER_CACHE_SIZE,
bucketOffset + i);
sHRStats[i].Reset();
}
}
}
} // CacheFileUtils
} // net
} // mozilla

View File

@ -9,6 +9,8 @@
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsTArray.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/TimeStamp.h"
class nsILoadContextInfo;
class nsACString;
@ -86,6 +88,64 @@ private:
nsTArray<ValidityPair> mMap;
};
class DetailedCacheHitTelemetry {
public:
enum ERecType {
HIT = 0,
MISS = 1
};
static void AddRecord(ERecType aType, TimeStamp aLoadStart);
private:
class HitRate {
public:
HitRate();
void AddRecord(ERecType aType);
// Returns the bucket index that the current hit rate falls into according
// to the given aNumOfBuckets.
uint32_t GetHitRateBucket(uint32_t aNumOfBuckets) const;
uint32_t Count();
void Reset();
private:
uint32_t mHitCnt;
uint32_t mMissCnt;
};
// Group the hits and misses statistics by cache files count ranges (0-5000,
// 5001-10000, ... , 95001- )
static const uint32_t kRangeSize = 5000;
static const uint32_t kNumOfRanges = 20;
// Use the same ranges to report an average hit rate. Report the hit rates
// (and reset the counters) every kTotalSamplesReportLimit samples.
static const uint32_t kTotalSamplesReportLimit = 1000;
// Report hit rate for a given cache size range only if it contains
// kHitRateSamplesReportLimit or more samples. This limit should avoid
// reporting a biased statistics.
static const uint32_t kHitRateSamplesReportLimit = 500;
// All hit rates are accumulated in a single telemetry probe, so to use
// a sane number of enumerated values the hit rate is divided into buckets
// instead of using a percent value. This constant defines number of buckets
// that we divide the hit rates into. I.e. we'll report ranges 0%-5%, 5%-10%,
// 10-%15%, ...
static const uint32_t kHitRateBuckets = 20;
// Protects sRecordCnt, sHitStats and Telemetry::Accumulated() calls.
static StaticMutex sLock;
// Counter of samples that is compared against kTotalSamplesReportLimit.
static uint32_t sRecordCnt;
// Hit rate statistics for every cache size range.
static HitRate sHRStats[kNumOfRanges];
};
} // CacheFileUtils
} // net
} // mozilla

View File

@ -1309,6 +1309,29 @@ CacheIndex::GetCacheSize(uint32_t *_retval)
return NS_OK;
}
// static
nsresult
CacheIndex::GetEntryFileCount(uint32_t *_retval)
{
LOG(("CacheIndex::GetEntryFileCount()"));
nsRefPtr<CacheIndex> index = gInstance;
if (!index) {
return NS_ERROR_NOT_INITIALIZED;
}
CacheIndexAutoLock lock(index);
if (!index->IsIndexUsable()) {
return NS_ERROR_NOT_AVAILABLE;
}
*_retval = index->mIndexStats.ActiveEntriesCount();
LOG(("CacheIndex::GetEntryFileCount() - returning %u", *_retval));
return NS_OK;
}
// static
nsresult
CacheIndex::GetCacheStats(nsILoadContextInfo *aInfo, uint32_t *aSize, uint32_t *aCount)

View File

@ -651,6 +651,9 @@ public:
// Returns cache size in kB.
static nsresult GetCacheSize(uint32_t *_retval);
// Returns number of entry files in the cache
static nsresult GetEntryFileCount(uint32_t *_retval);
// Synchronously returns the disk occupation and number of entries per-context.
// Callable on any thread.
static nsresult GetCacheStats(nsILoadContextInfo *aInfo, uint32_t *aSize, uint32_t *aCount);

View File

@ -86,6 +86,9 @@ bool CacheObserver::sSanitizeOnShutdown = kDefaultSanitizeOnShutdown;
static bool kDefaultClearCacheOnShutdown = false;
bool CacheObserver::sClearCacheOnShutdown = kDefaultClearCacheOnShutdown;
static bool kDefaultCacheFSReported = false;
bool CacheObserver::sCacheFSReported = kDefaultCacheFSReported;
NS_IMPL_ISUPPORTS(CacheObserver,
nsIObserver,
nsISupportsWeakReference)
@ -317,6 +320,32 @@ CacheObserver::StoreDiskCacheCapacity()
sDiskCacheCapacity);
}
// static
void
CacheObserver::SetCacheFSReported()
{
sCacheFSReported = true;
if (!sSelf) {
return;
}
if (NS_IsMainThread()) {
sSelf->StoreCacheFSReported();
} else {
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(sSelf, &CacheObserver::StoreCacheFSReported);
NS_DispatchToMainThread(event);
}
}
void
CacheObserver::StoreCacheFSReported()
{
mozilla::Preferences::SetInt("browser.cache.disk.filesystem_reported",
sCacheFSReported);
}
// static
void CacheObserver::ParentDirOverride(nsIFile** aDir)
{

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