Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-06-05 15:12:17 +02:00
commit f9152ef1b7
94 changed files with 1337 additions and 718 deletions

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "7f0af1e164a39efb732c0c341c2a8e51f681d913",
"revision": "c3d40600c0090c5ca6ca4427f3a870ff443a109d",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="994fa9a1f7ce0e63c880a48d571c3ab3e01884a3"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d2cfef555dabab415085e548ed44c48a99be5c32"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="908f94fda04462001ece86e6b6c15ad8b05f7526"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="531cf670e485649c69746e46d567929fcd54cbc5"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -45,6 +45,7 @@ svg|line.box-model-guide-bottom[hidden] {
html|*.highlighter-nodeinfobar-id,
html|*.highlighter-nodeinfobar-classes,
html|*.highlighter-nodeinfobar-pseudo-classes,
html|*.highlighter-nodeinfobar-dimensions,
html|*.highlighter-nodeinfobar-tagname {
-moz-user-select: text;
-moz-user-focus: normal;

View File

@ -210,7 +210,19 @@ let gPage = {
for (let site of gGrid.sites) {
if (site) {
site.captureIfMissing();
let {type} = site.link;
// Record which tile index a directory link was shown
let {directoryIndex, type} = site.link;
if (directoryIndex !== undefined) {
let tileIndex = site.cell.index;
// For telemetry, only handle the first 9 links in the first 9 cells
if (directoryIndex < 9) {
let shownId = "NEWTAB_PAGE_DIRECTORY_LINK" + directoryIndex + "_SHOWN";
Services.telemetry.getHistogramById(shownId).add(Math.min(9, tileIndex));
}
}
// Aggregate tile impression counts into directory types
if (type in directoryCount) {
directoryCount[type]++;
}

View File

@ -12,6 +12,70 @@ function test() {
}
function spawnTest() {
let lines = [
'Manifest has a character encoding of ISO-8859-1. Manifests must have the ' +
'utf-8 character encoding.',
'The first line of the manifest must be "CACHE MANIFEST" at line 1.',
'"CACHE MANIFEST" is only valid on the first line but was found at line 3.',
'images/sound-icon.png points to a resource that is not available at line 9.',
'images/background.png points to a resource that is not available at line 10.',
'/checking.cgi points to a resource that is not available at line 13.',
'Asterisk (*) incorrectly used in the NETWORK section at line 14. If a line ' +
'in the NETWORK section contains only a single asterisk character, then any ' +
'URI not listed in the manifest will be treated as if the URI was listed in ' +
'the NETWORK section. Otherwise such URIs will be treated as unavailable. ' +
'Other uses of the * character are prohibited',
'../rel.html points to a resource that is not available at line 17.',
'../../rel.html points to a resource that is not available at line 18.',
'../../../rel.html points to a resource that is not available at line 19.',
'../../../../rel.html points to a resource that is not available at line 20.',
'../../../../../rel.html points to a resource that is not available at line 21.',
'/../ is not a valid URI prefix at line 22.',
'/test.css points to a resource that is not available at line 23.',
'/test.js points to a resource that is not available at line 24.',
'test.png points to a resource that is not available at line 25.',
'/main/features.js points to a resource that is not available at line 27.',
'/main/settings/index.css points to a resource that is not available at line 28.',
'http://example.com/scene.jpg points to a resource that is not available at line 29.',
'/section1/blockedbyfallback.html points to a resource that is not available at line 30.',
'http://example.com/images/world.jpg points to a resource that is not available at line 31.',
'/section2/blockedbyfallback.html points to a resource that is not available at line 32.',
'/main/home points to a resource that is not available at line 34.',
'main/app.js points to a resource that is not available at line 35.',
'/settings/home points to a resource that is not available at line 37.',
'/settings/app.js points to a resource that is not available at line 38.',
'The file http://sub1.test1.example.com/browser/browser/devtools/' +
'commandline/test/browser_cmd_appcache_invalid_page3.html was modified ' +
'after http://sub1.test1.example.com/browser/browser/devtools/' +
'commandline/test/browser_cmd_appcache_invalid_appcache.appcache. Unless ' +
'the text in the manifest file is changed the cached version will be used ' +
'instead at line 39.',
'browser_cmd_appcache_invalid_page3.html has cache-control set to no-store. ' +
'This will prevent the application cache from storing the file at line 39.',
'http://example.com/logo.png points to a resource that is not available at line 40.',
'http://example.com/check.png points to a resource that is not available at line 41.',
'Spaces in URIs need to be replaced with % at line 42.',
'http://example.com/cr oss.png points to a resource that is not available at line 42.',
'Asterisk (*) incorrectly used in the CACHE section at line 43. If a line ' +
'in the NETWORK section contains only a single asterisk character, then ' +
'any URI not listed in the manifest will be treated as if the URI was ' +
'listed in the NETWORK section. Otherwise such URIs will be treated as ' +
'unavailable. Other uses of the * character are prohibited',
'The SETTINGS section may only contain a single value, "prefer-online" or "fast" at line 47.',
'FALLBACK section line 50 (/section1/ /offline1.html) prevents caching of ' +
'line 30 (/section1/blockedbyfallback.html) in the CACHE section.',
'/offline1.html points to a resource that is not available at line 50.',
'FALLBACK section line 51 (/section2/ offline2.html) prevents caching of ' +
'line 32 (/section2/blockedbyfallback.html) in the CACHE section.',
'offline2.html points to a resource that is not available at line 51.',
'Only two URIs separated by spaces are allowed in the FALLBACK section at line 52.',
'Asterisk (*) incorrectly used in the FALLBACK section at line 53. URIs ' +
'in the FALLBACK section simply need to match a prefix of the request URI.',
'offline3.html points to a resource that is not available at line 53.',
'Invalid section name (BLAH) at line 55.',
'Only two URIs separated by spaces are allowed in the FALLBACK section at line 55.'
];
let options = yield helpers.openTab(TEST_URI);
info("window open");
@ -25,7 +89,8 @@ function spawnTest() {
// Pages containing an appcache the notification bar gives options to allow
// or deny permission for the app to save data offline. Let's click Allow.
let notificationID = "offline-app-requested-sub1.test1.example.com";
let notification = PopupNotifications.getNotification(notificationID, gBrowser.selectedBrowser);
let notification =
PopupNotifications.getNotification(notificationID, gBrowser.selectedBrowser);
if (notification) {
info("Authorizing offline storage.");
@ -45,52 +110,7 @@ function spawnTest() {
args: {}
},
exec: {
output: [
/Manifest has a character encoding of ISO-8859-1\. Manifests must have the utf-8 character encoding\./,
/The first line of the manifest must be "CACHE MANIFEST" at line 1\./,
/"CACHE MANIFEST" is only valid on the first line but was found at line 3\./,
/images\/sound-icon\.png points to a resource that is not available at line 9\./,
/images\/background\.png points to a resource that is not available at line 10\./,
/NETWORK section line 13 \(\/checking\.cgi\) prevents caching of line 13 \(\/checking\.cgi\) in the NETWORK section\./,
/\/checking\.cgi points to a resource that is not available at line 13\./,
/Asterisk \(\*\) incorrectly used in the NETWORK section at line 14\. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section\. Otherwise such URIs will be treated as unavailable\. Other uses of the \* character are prohibited/,
/\.\.\/rel\.html points to a resource that is not available at line 17\./,
/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 18\./,
/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 19\./,
/\.\.\/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 20\./,
/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 21\./,
/\/\.\.\/ is not a valid URI prefix at line 22\./,
/\/test\.css points to a resource that is not available at line 23\./,
/\/test\.js points to a resource that is not available at line 24\./,
/test\.png points to a resource that is not available at line 25\./,
/\/main\/features\.js points to a resource that is not available at line 27\./,
/\/main\/settings\/index\.css points to a resource that is not available at line 28\./,
/http:\/\/example\.com\/scene\.jpg points to a resource that is not available at line 29\./,
/\/section1\/blockedbyfallback\.html points to a resource that is not available at line 30\./,
/http:\/\/example\.com\/images\/world\.jpg points to a resource that is not available at line 31\./,
/\/section2\/blockedbyfallback\.html points to a resource that is not available at line 32\./,
/\/main\/home points to a resource that is not available at line 34\./,
/main\/app\.js points to a resource that is not available at line 35\./,
/\/settings\/home points to a resource that is not available at line 37\./,
/\/settings\/app\.js points to a resource that is not available at line 38\./,
/The file http:\/\/sub1\.test1\.example\.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_appcache_invalid_page3\.html was modified after http:\/\/sub1\.test1\.example\.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_appcache_invalid_appcache\.appcache\. Unless the text in the manifest file is changed the cached version will be used instead at line 39\./,
/browser_cmd_appcache_invalid_page3\.html has cache-control set to no-store\. This will prevent the application cache from storing the file at line 39\./,
/http:\/\/example\.com\/logo\.png points to a resource that is not available at line 40\./,
/http:\/\/example\.com\/check\.png points to a resource that is not available at line 41\./,
/Spaces in URIs need to be replaced with % at line 42\./,
/http:\/\/example\.com\/cr oss\.png points to a resource that is not available at line 42\./,
/Asterisk \(\*\) incorrectly used in the CACHE section at line 43\. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section\. Otherwise such URIs will be treated as unavailable\. Other uses of the \* character are prohibited/,
/The SETTINGS section may only contain a single value, "prefer-online" or "fast" at line 47\./,
/FALLBACK section line 50 \(\/section1\/ \/offline1\.html\) prevents caching of line 30 \(\/section1\/blockedbyfallback\.html\) in the CACHE section\./,
/\/offline1\.html points to a resource that is not available at line 50\./,
/FALLBACK section line 51 \(\/section2\/ offline2\.html\) prevents caching of line 32 \(\/section2\/blockedbyfallback\.html\) in the CACHE section\./,
/offline2\.html points to a resource that is not available at line 51\./,
/Only two URIs separated by spaces are allowed in the FALLBACK section at line 52\./,
/Asterisk \(\*\) incorrectly used in the FALLBACK section at line 53\. URIs in the FALLBACK section simply need to match a prefix of the request URI\./,
/offline3\.html points to a resource that is not available at line 53\./,
/Invalid section name \(BLAH\) at line 55\./,
/Only two URIs separated by spaces are allowed in the FALLBACK section at line 55\./
]
output: lines.map(getRegexForString)
},
},
]);

View File

@ -27,6 +27,20 @@ function whenDelayedStartupFinished(aWindow, aCallback) {
}, "browser-delayed-startup-finished", false);
}
/**
* Creates a regular expression that matches a string. This greatly simplifies
* matching and debugging long strings.
*
* @param {String} text
* Text to convert
* @return {RegExp}
* Regular expression matching text
*/
function getRegexForString(str) {
str = str.replace(/(\.|\\|\/|\(|\)|\[|\]|\*|\+|\?|\$|\^|\|)/g, "\\$1");
return new RegExp(str);
}
/**
* Force GC on shutdown, because it seems that GCLI can outrun the garbage
* collector in some situations, which causes test failures in later tests

View File

@ -5,106 +5,71 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let doc;
let h1;
let div;
let rotated;
let inspector;
let contentViewer;
function createDocument() {
let div = doc.createElement("div");
h1 = doc.createElement("h1");
let p1 = doc.createElement("p");
let p2 = doc.createElement("p");
let div2 = doc.createElement("div");
let p3 = doc.createElement("p");
doc.title = "Inspector Highlighter Meatballs";
h1.textContent = "Inspector Tree Selection Test";
p1.textContent = "This is some example text";
p2.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
"elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
"laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
"dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
"fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
"proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
p3.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
"elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
"laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
"dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
"fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
"proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
let div3 = doc.createElement("div");
div3.id = "checkOutThisWickedSpread";
div3.setAttribute("style", "position: absolute; top: 20px; right: 20px; height: 20px; width: 20px; background-color: yellow; border: 1px dashed black;");
let p4 = doc.createElement("p");
p4.setAttribute("style", "font-weight: 200; font-size: 8px; text-align: center;");
p4.textContent = "Smörgåsbord!";
div.appendChild(h1);
div.appendChild(p1);
div.appendChild(p2);
div2.appendChild(p3);
div3.appendChild(p4);
div = doc.createElement("div");
div.setAttribute("style",
"padding:5px; border:7px solid red; margin: 9px; " +
"position:absolute; top:30px; left:150px;");
let textNode = doc.createTextNode("Gort! Klaatu barada nikto!");
rotated = doc.createElement("div");
rotated.setAttribute("style",
"padding:5px; border:7px solid red; margin: 9px; " +
"transform:rotate(45deg); " +
"position:absolute; top:30px; left:80px;");
div.appendChild(textNode);
doc.body.appendChild(div);
doc.body.appendChild(div2);
doc.body.appendChild(div3);
doc.body.appendChild(rotated);
openInspector(aInspector => {
inspector = aInspector;
inspector.selection.setNode(div, null);
inspector.once("inspector-updated", () => {
inspector.toolbox.highlighterUtils.startPicker().then(testMouseOverH1Highlights);
});
inspector.once("inspector-updated", testMouseOverDivHighlights);
});
}
function testMouseOverH1Highlights() {
function testMouseOverDivHighlights() {
ok(isHighlighting(), "Highlighter is shown");
is(getHighlitNode(), div, "Highlighter's outline correspond to the non-rotated div");
testNonTransformedBoxModelDimensionsNoZoom();
}
function testNonTransformedBoxModelDimensionsNoZoom() {
info("Highlighted the non-rotated div");
isNodeCorrectlyHighlighted(div, "non-zoomed");
inspector.toolbox.once("highlighter-ready", testNonTransformedBoxModelDimensionsZoomed);
contentViewer = gBrowser.selectedBrowser.docShell.contentViewer
.QueryInterface(Ci.nsIMarkupDocumentViewer);
contentViewer.fullZoom = 2;
}
function testNonTransformedBoxModelDimensionsZoomed() {
info("Highlighted the zoomed, non-rotated div");
isNodeCorrectlyHighlighted(div, "zoomed");
inspector.toolbox.once("highlighter-ready", testMouseOverRotatedHighlights);
contentViewer.fullZoom = 1;
}
function testMouseOverRotatedHighlights() {
inspector.toolbox.once("highlighter-ready", () => {
ok(isHighlighting(), "Highlighter is shown");
is(getHighlitNode(), h1, "Highlighter's outline correspond to the selected node");
testBoxModelDimensions();
info("Highlighted the rotated div");
isNodeCorrectlyHighlighted(rotated, "rotated");
executeSoon(finishUp);
});
EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
}
function testBoxModelDimensions() {
let h1Dims = h1.getBoundingClientRect();
let h1Width = Math.ceil(h1Dims.width);
let h1Height = Math.ceil(h1Dims.height);
let outlineDims = getSimpleBorderRect();
let outlineWidth = Math.ceil(outlineDims.width);
let outlineHeight = Math.ceil(outlineDims.height);
// Disabled due to bug 716245
is(outlineWidth, h1Width, "outline width matches dimensions of element (no zoom)");
is(outlineHeight, h1Height, "outline height matches dimensions of element (no zoom)");
// zoom the page by a factor of 2
let contentViewer = gBrowser.selectedBrowser.docShell.contentViewer
.QueryInterface(Ci.nsIMarkupDocumentViewer);
contentViewer.fullZoom = 2;
// simulate the zoomed dimensions of the div element
let h1Dims = h1.getBoundingClientRect();
// There seems to be some very minor differences in the floats, so let's
// floor the values
let h1Width = Math.floor(h1Dims.width * contentViewer.fullZoom);
let h1Height = Math.floor(h1Dims.height * contentViewer.fullZoom);
let outlineDims = getSimpleBorderRect();
let outlineWidth = Math.floor(outlineDims.width);
let outlineHeight = Math.floor(outlineDims.height);
is(outlineWidth, h1Width, "outline width matches dimensions of element (zoomed)");
is(outlineHeight, h1Height, "outline height matches dimensions of element (zoomed)");
executeSoon(finishUp);
inspector.selection.setNode(rotated);
}
function finishUp() {
inspector.toolbox.highlighterUtils.stopPicker().then(() => {
doc = h1 = inspector = null;
doc = div = rotated = inspector = contentViewer = null;
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);
gBrowser.removeCurrentTab();

View File

@ -17,19 +17,58 @@ function test() {
waitForFocus(setupInfobarTest, content);
}, true);
let style = "body{width:100%;height: 100%} div {position: absolute;height: 100px;width: 500px}#bottom {bottom: 0px}#vertical {height: 100%}#farbottom{bottom: -200px}";
let html = "<style>" + style + "</style><div id=vertical></div><div id=top class='class1 class2'></div><div id=bottom></div><div id=farbottom></div>"
let style = "body{width:100%;height: 100%} div {position: absolute;" +
"height: 100px;width: 500px}#bottom {bottom: 0px}#vertical {"+
"height: 100%}#farbottom{bottom: -200px}";
let html = "<style>" + style + "</style><div id=vertical></div>" +
"<div id=top class='class1 class2'></div><div id=bottom></div>" +
"<div id=farbottom></div>"
content.location = "data:text/html," + encodeURIComponent(html);
content.location = "data:text/html;charset=utf-8," + encodeURIComponent(html);
function setupInfobarTest() {
nodes = [
{node: doc.querySelector("#top"), position: "bottom", tag: "DIV", id: "#top", classes: ".class1.class2"},
{node: doc.querySelector("#vertical"), position: "overlap", tag: "DIV", id: "#vertical", classes: ""},
{node: doc.querySelector("#bottom"), position: "top", tag: "DIV", id: "#bottom", classes: ""},
{node: doc.querySelector("body"), position: "overlap", tag: "BODY", id: "", classes: ""},
{node: doc.querySelector("#farbottom"), position: "top", tag: "DIV", id: "#farbottom", classes: ""},
]
{
node: doc.querySelector("#top"),
position: "bottom",
tag: "DIV",
id: "#top",
classes: ".class1.class2",
dims: "500 x 100"
},
{
node: doc.querySelector("#vertical"),
position: "overlap",
tag: "DIV",
id: "#vertical",
classes: ""
// No dims as they will vary between computers
},
{
node: doc.querySelector("#bottom"),
position: "top",
tag: "DIV",
id: "#bottom",
classes: "",
dims: "500 x 100"
},
{
node: doc.querySelector("body"),
position: "overlap",
tag: "BODY",
id: "",
classes: ""
// No dims as they will vary between computers
},
{
node: doc.querySelector("#farbottom"),
position: "top",
tag: "DIV",
id: "#farbottom",
classes: "",
dims: "500 x 100"
},
];
for (let i = 0; i < nodes.length; i++) {
ok(nodes[i].node, "node " + i + " found");
@ -74,16 +113,24 @@ function test() {
let stack = browser.parentNode;
let container = stack.querySelector(".highlighter-nodeinfobar-positioner");
is(container.getAttribute("position"), nodes[cursor].position, "node " + cursor + ": position matches.");
is(container.getAttribute("position"),
nodes[cursor].position, "node " + cursor + ": position matches.");
let tagNameLabel = stack.querySelector(".highlighter-nodeinfobar-tagname");
is(tagNameLabel.textContent, nodes[cursor].tag, "node " + cursor + ": tagName matches.");
is(tagNameLabel.textContent, nodes[cursor].tag,
"node " + cursor + ": tagName matches.");
let idLabel = stack.querySelector(".highlighter-nodeinfobar-id");
is(idLabel.textContent, nodes[cursor].id, "node " + cursor + ": id matches.");
let classesBox = stack.querySelector(".highlighter-nodeinfobar-classes");
is(classesBox.textContent, nodes[cursor].classes, "node " + cursor + ": classes match.");
is(classesBox.textContent, nodes[cursor].classes,
"node " + cursor + ": classes match.");
if (nodes[cursor].dims) {
let dimBox = stack.querySelector(".highlighter-nodeinfobar-dimensions");
is(dimBox.textContent, nodes[cursor].dims, "node " + cursor + ": dims match.");
}
}
function finishUp() {

View File

@ -400,7 +400,7 @@ function focusSearchBoxUsingShortcut(panelWin, callback) {
altKey: modifiersAttr.match("alt"),
metaKey: modifiersAttr.match("meta"),
accelKey: modifiersAttr.match("accel")
}
};
let searchBox = panelWin.document.getElementById("inspector-searchbox");
searchBox.addEventListener("focus", function onFocus() {
@ -425,6 +425,60 @@ function getComputedPropertyValue(aName)
}
}
function isNodeCorrectlyHighlighted(node, prefix="") {
let boxModel = getBoxModelStatus();
let helper = new LayoutHelpers(window.content);
prefix += (prefix ? " " : "") + node.nodeName;
prefix += (node.id ? "#" + node.id : "");
prefix += (node.classList.length ? "." + [...node.classList].join(".") : "");
prefix += " ";
let quads = helper.getAdjustedQuads(node, "content");
let {p1:cp1, p2:cp2, p3:cp3, p4:cp4} = boxModel.content.points;
is(cp1.x, quads.p1.x, prefix + "content point 1 x co-ordinate is correct");
is(cp1.y, quads.p1.y, prefix + "content point 1 y co-ordinate is correct");
is(cp2.x, quads.p2.x, prefix + "content point 2 x co-ordinate is correct");
is(cp2.y, quads.p2.y, prefix + "content point 2 y co-ordinate is correct");
is(cp3.x, quads.p3.x, prefix + "content point 3 x co-ordinate is correct");
is(cp3.y, quads.p3.y, prefix + "content point 3 y co-ordinate is correct");
is(cp4.x, quads.p4.x, prefix + "content point 4 x co-ordinate is correct");
is(cp4.y, quads.p4.y, prefix + "content point 4 y co-ordinate is correct");
quads = helper.getAdjustedQuads(node, "padding");
let {p1:pp1, p2:pp2, p3:pp3, p4:pp4} = boxModel.padding.points;
is(pp1.x, quads.p1.x, prefix + "padding point 1 x co-ordinate is correct");
is(pp1.y, quads.p1.y, prefix + "padding point 1 y co-ordinate is correct");
is(pp2.x, quads.p2.x, prefix + "padding point 2 x co-ordinate is correct");
is(pp2.y, quads.p2.y, prefix + "padding point 2 y co-ordinate is correct");
is(pp3.x, quads.p3.x, prefix + "padding point 3 x co-ordinate is correct");
is(pp3.y, quads.p3.y, prefix + "padding point 3 y co-ordinate is correct");
is(pp4.x, quads.p4.x, prefix + "padding point 4 x co-ordinate is correct");
is(pp4.y, quads.p4.y, prefix + "padding point 4 y co-ordinate is correct");
quads = helper.getAdjustedQuads(node, "border");
let {p1:bp1, p2:bp2, p3:bp3, p4:bp4} = boxModel.border.points;
is(bp1.x, quads.p1.x, prefix + "border point 1 x co-ordinate is correct");
is(bp1.y, quads.p1.y, prefix + "border point 1 y co-ordinate is correct");
is(bp2.x, quads.p2.x, prefix + "border point 2 x co-ordinate is correct");
is(bp2.y, quads.p2.y, prefix + "border point 2 y co-ordinate is correct");
is(bp3.x, quads.p3.x, prefix + "border point 3 x co-ordinate is correct");
is(bp3.y, quads.p3.y, prefix + "border point 3 y co-ordinate is correct");
is(bp4.x, quads.p4.x, prefix + "border point 4 x co-ordinate is correct");
is(bp4.y, quads.p4.y, prefix + "border point 4 y co-ordinate is correct");
quads = helper.getAdjustedQuads(node, "margin");
let {p1:mp1, p2:mp2, p3:mp3, p4:mp4} = boxModel.margin.points;
is(mp1.x, quads.p1.x, prefix + "margin point 1 x co-ordinate is correct");
is(mp1.y, quads.p1.y, prefix + "margin point 1 y co-ordinate is correct");
is(mp2.x, quads.p2.x, prefix + "margin point 2 x co-ordinate is correct");
is(mp2.y, quads.p2.y, prefix + "margin point 2 y co-ordinate is correct");
is(mp3.x, quads.p3.x, prefix + "margin point 3 x co-ordinate is correct");
is(mp3.y, quads.p3.y, prefix + "margin point 3 y co-ordinate is correct");
is(mp4.x, quads.p4.x, prefix + "margin point 4 x co-ordinate is correct");
is(mp4.y, quads.p4.y, prefix + "margin point 4 y co-ordinate is correct");
}
function getContainerForRawNode(markupView, rawNode)
{
let front = markupView.walker.frontForRawNode(rawNode);

View File

@ -118,7 +118,8 @@ AppCacheUtils.prototype = {
for (let neturi of parsed.uris) {
if (neturi.section == "NETWORK") {
for (let parsedUri of parsed.uris) {
if (parsedUri.uri.startsWith(neturi.uri)) {
if (parsedUri.section !== "NETWORK" &&
parsedUri.uri.startsWith(neturi.uri)) {
this._addError(neturi.line, "networkBlocksURI", neturi.line,
neturi.original, parsedUri.line, parsedUri.original,
parsedUri.section);
@ -164,7 +165,7 @@ AppCacheUtils.prototype = {
this._addError(parsedUri.line, "cacheControlNoStore",
parsedUri.original, parsedUri.line);
}
} else {
} else if (parsedUri.original !== "*") {
this._addError(parsedUri.line, "notAvailable",
parsedUri.original, parsedUri.line);
}
@ -182,7 +183,6 @@ AppCacheUtils.prototype = {
let inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
let deferred = promise.defer();
let channelCharset = "";
let buffer = "";
let channel = Services.io.newChannel(uri, null, null);
@ -203,7 +203,7 @@ AppCacheUtils.prototype = {
},
onStopRequest: function onStartRequest(request, context, statusCode) {
if (statusCode == 0) {
if (statusCode === 0) {
request.QueryInterface(Ci.nsIHttpChannel);
let result = {
@ -279,7 +279,7 @@ AppCacheUtils.prototype = {
}
});
if (entries.length == 0) {
if (entries.length === 0) {
throw new Error(l10n.GetStringFromName("noResults"));
}
return entries;
@ -320,17 +320,23 @@ AppCacheUtils.prototype = {
_getManifestURI: function ACU__getManifestURI() {
let deferred = promise.defer();
let getURI = node => {
let getURI = () => {
let htmlNode = this.doc.querySelector("html[manifest]");
if (htmlNode) {
let pageUri = this.doc.location ? this.doc.location.href : this.uri;
let origin = pageUri.substr(0, pageUri.lastIndexOf("/") + 1);
return origin + htmlNode.getAttribute("manifest");
let manifestURI = htmlNode.getAttribute("manifest");
if (manifestURI.startsWith("/")) {
manifestURI = manifestURI.substr(1);
}
return origin + manifestURI;
}
};
if (this.doc) {
let uri = getURI(this.doc);
let uri = getURI();
return promise.resolve(uri);
} else {
this._getURIInfo(this.uri).then(uriInfo => {
@ -338,7 +344,7 @@ AppCacheUtils.prototype = {
let html = uriInfo.text;
let parser = _DOMParser;
this.doc = parser.parseFromString(html, "text/html");
let uri = getURI(this.doc);
let uri = getURI();
deferred.resolve(uri);
} else {
this.errors.push({
@ -394,10 +400,10 @@ ManifestParser.prototype = {
this.currSection = "CACHE";
for (let i = 0; i < lines.length; i++) {
let text = this.text = lines[i].replace(/^\s+|\s+$/g);
let text = this.text = lines[i].trim();
this.currentLine = i + 1;
if (i == 0 && text != "CACHE MANIFEST") {
if (i === 0 && text !== "CACHE MANIFEST") {
this._addError(1, "firstLineMustBeCacheManifest", 1);
}
@ -453,7 +459,7 @@ ManifestParser.prototype = {
if (/\s/.test(text)) {
this._addError(this.currentLine, "escapeSpaces", this.currentLine);
text = text.replace(/\s/g, "%20")
text = text.replace(/\s/g, "%20");
}
if (text[0] == "/") {
@ -506,7 +512,7 @@ ManifestParser.prototype = {
if (/\s/.test(namespace)) {
this._addError(this.currentLine, "escapeSpaces", this.currentLine);
namespace = namespace.replace(/\s/g, "%20")
namespace = namespace.replace(/\s/g, "%20");
}
if (namespace.substr(0, 4) == "/../") {

View File

@ -64,7 +64,7 @@ function StyleEditorUI(debuggee, target, panelDoc) {
this.selectedEditor = null;
this.savedLocations = {};
this._updateContextMenu = this._updateContextMenu.bind(this);
this._updateOptionsMenu = this._updateOptionsMenu.bind(this);
this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
this._onNewDocument = this._onNewDocument.bind(this);
this._onMediaPrefChanged = this._onMediaPrefChanged.bind(this);
@ -142,36 +142,26 @@ StyleEditorUI.prototype = {
this._importFromFile(this._mockImportFile || null, this._window);
}.bind(this));
this._contextMenu = this._panelDoc.getElementById("sidebar-context");
this._contextMenu.addEventListener("popupshowing",
this._updateContextMenu);
this._optionsMenu = this._panelDoc.getElementById("style-editor-options-popup");
this._optionsMenu.addEventListener("popupshowing",
this._updateOptionsMenu);
this._sourcesItem = this._panelDoc.getElementById("context-origsources");
this._sourcesItem = this._panelDoc.getElementById("options-origsources");
this._sourcesItem.addEventListener("command",
this._toggleOrigSources);
this._mediaItem = this._panelDoc.getElementById("context-show-media");
this._mediaItem = this._panelDoc.getElementById("options-show-media");
this._mediaItem.addEventListener("command",
this._toggleMediaSidebar);
},
/**
* Update text of context menu option to reflect current preference
* settings
* Update options menu items to reflect current preference settings.
*/
_updateContextMenu: function() {
let sourceString = "showOriginalSources";
if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
sourceString = "showCSSSources";
}
this._sourcesItem.setAttribute("label", _(sourceString + ".label"));
this._sourcesItem.setAttribute("accesskey", _(sourceString + ".accesskey"));
let mediaString = "showMediaSidebar"
if (Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR)) {
mediaString = "hideMediaSidebar";
}
this._mediaItem.setAttribute("label", _(mediaString + ".label"));
this._mediaItem.setAttribute("accesskey", _(mediaString + ".accesskey"));
_updateOptionsMenu: function() {
this._sourcesItem.setAttribute("checked",
Services.prefs.getBoolPref(PREF_ORIG_SOURCES));
this._mediaItem.setAttribute("checked",
Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR));
},
/**
@ -771,6 +761,9 @@ StyleEditorUI.prototype = {
destroy: function() {
this._clearStyleSheetEditors();
this._optionsMenu.removeEventListener("popupshowing",
this._updateOptionsMenu);
this._prefObserver.off(PREF_ORIG_SOURCES, this._onNewDocument);
this._prefObserver.off(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
this._prefObserver.destroy();

View File

@ -15,6 +15,10 @@ li.error > .stylesheet-info > .stylesheet-more > .stylesheet-error-message {
display: block;
}
.devtools-toolbar > spacer {
-moz-box-flex: 1;
}
.splitview-nav > li,
.stylesheet-info,
.stylesheet-more {

View File

@ -61,9 +61,16 @@
key="key_gotoLine"
command="cmd_gotoLine"/>
</xul:menupopup>
<xul:menupopup id="sidebar-context">
<xul:menuitem id="context-origsources"/>
<xul:menuitem id="context-show-media"/>
<xul:menupopup id="style-editor-options-popup"
position="before_start">
<xul:menuitem id="options-origsources"
type="checkbox"
label="&showOriginalSources.label;"
accesskey="&showOriginalSources.accesskey;"/>
<xul:menuitem id="options-show-media"
type="checkbox"
label="&showMediaSidebar.label;"
accesskey="&showMediaSidebar.accesskey;"/>
</xul:menupopup>
</xul:popupset>
@ -91,6 +98,11 @@
accesskey="&importButton.accesskey;"
tooltiptext="&importButton.tooltip;"
label="&importButton.label;"/>
<xul:spacer/>
<xul:toolbarbutton id="style-editor-options"
class="devtools-option-toolbarbutton"
tooltiptext="&optionsButton.tooltip;"
popup="style-editor-options-popup"/>
</xul:toolbar>
</xul:box>
<xul:box id="splitview-resizer-target" class="theme-sidebar splitview-nav-container"

View File

@ -24,6 +24,24 @@
<!ENTITY saveButton.tooltip "Save this style sheet to a file">
<!ENTITY saveButton.accesskey "S">
<!ENTITY optionsButton.tooltip "Style Editor options">
<!-- LOCALIZATION NOTE (showOriginalSources.label): This is the label on the context
menu item to toggle showing original sources in the editor. -->
<!ENTITY showOriginalSources.label "Show original sources">
<!-- LOCALIZATION NOTE (showOriginalSources.accesskey): This is the access key for
the menu item to toggle showing original sources in the editor. -->
<!ENTITY showOriginalSources.accesskey "o">
<!-- LOCALIZATION NOTE (showMediaSidebar.label): This is the label on the context
menu item to toggle showing @media rule shortcuts in a sidebar. -->
<!ENTITY showMediaSidebar.label "Show @media sidebar">
<!-- LOCALIZATION NOTE (showMediaSidebar.accesskey): This is the access key for
the menu item to toggle showing the @media sidebar. -->
<!ENTITY showMediaSidebar.accesskey "m">
<!-- LOCALICATION NOTE (mediaRules.label): This is shown above the list of @media rules
in each stylesheet editor sidebar. -->
<!ENTITY mediaRules.label "@media rules">

View File

@ -68,38 +68,6 @@ open.accesskey=l
# conjunction with accel (Command on Mac or Ctrl on other platforms) to Save
saveStyleSheet.commandkey=S
# LOCALIZATION NOTE (showOriginalSources.label): This is the label on the context
# menu item to toggle showing original sources in the editor.
showOriginalSources.label=Show original sources
# LOCALIZATION NOTE (showOriginalSources.accesskey): This is the access key for
# the menu item to toggle showing original sources in the editor.
showOriginalSources.accesskey=O
# LOCALIZATION NOTE (showCSSSources.label): This is the label on the context
# menu item to toggle back to showing only CSS sources in the editor.
showCSSSources.label=Show CSS sources
# LOCALIZATION NOTE (showCSSSources.accesskey): This is the access key for the
# menu item to toggle back to showing only CSS sources in the editor.
showCSSSources.accesskey=C
# LOCALIZATION NOTE (showMediaSidebar.label): This is the label on the context
# menu item to toggle showing @media rule shortcuts in a sidebar.
showMediaSidebar.label=Show @media sidebar
# LOCALIZATION NOTE (showMediaSidebar.accesskey): This is the access key for
# the menu item to toggle showing the @media sidebar.
showMediaSidebar.accesskey=M
# LOCALIZATION NOTE (hideMediaSidebar.label): This is the label on the context
# menu item to stop showing @media rule shortcuts in a sidebar.
hideMediaSidebar.label=Hide @media sidebar
# LOCALIZATION NOTE (hideMediaSidebar.accesskey): This is the access key for
# the menu item to stop showing the @media sidebar.
hideMediaSidebar.accesskey=H
# LOCALIZATION NOTE (ToolboxStyleEditor.label):
# This string is displayed in the title of the tab when the style editor is
# displayed inside the developer tools window and in the Developer Tools Menu.

View File

@ -285,6 +285,7 @@ browser.jar:
skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
skin/classic/browser/devtools/responsiveui-screenshot@2x.png (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
skin/classic/browser/devtools/toggle-tools.png (../shared/devtools/images/toggle-tools.png)
skin/classic/browser/devtools/toggle-tools@2x.png (../shared/devtools/images/toggle-tools@2x.png)
skin/classic/browser/devtools/dock-bottom@2x.png (../shared/devtools/images/dock-bottom@2x.png)
skin/classic/browser/devtools/dock-side@2x.png (../shared/devtools/images/dock-side@2x.png)
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)

View File

@ -405,6 +405,7 @@ browser.jar:
skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
skin/classic/browser/devtools/responsiveui-screenshot@2x.png (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
skin/classic/browser/devtools/toggle-tools.png (../shared/devtools/images/toggle-tools.png)
skin/classic/browser/devtools/toggle-tools@2x.png (../shared/devtools/images/toggle-tools@2x.png)
skin/classic/browser/devtools/dock-bottom@2x.png (../shared/devtools/images/dock-bottom@2x.png)
skin/classic/browser/devtools/dock-side@2x.png (../shared/devtools/images/dock-side@2x.png)
* skin/classic/browser/devtools/inspector.css (../shared/devtools/inspector.css)

View File

@ -28,6 +28,12 @@
margin: auto 10px;
}
.developer-toolbar-button > .toolbarbutton-icon,
#developer-toolbar-closebutton > .toolbarbutton-icon {
width: 16px;
height: 16px;
}
#developer-toolbar-toolbox-button {
list-style-image: url("chrome://browser/skin/devtools/toggle-tools.png");
-moz-image-region: rect(0px, 16px, 16px, 0px);
@ -49,6 +55,25 @@
-moz-image-region: rect(0px, 64px, 16px, 48px);
}
@media (min-resolution: 2dppx) {
#developer-toolbar-toolbox-button {
list-style-image: url("chrome://browser/skin/devtools/toggle-tools@2x.png");
-moz-image-region: rect(0px, 32px, 32px, 0px);
}
#developer-toolbar-toolbox-button:hover {
-moz-image-region: rect(0px, 64px, 32px, 32px);
}
#developer-toolbar-toolbox-button:hover:active {
-moz-image-region: rect(0px, 96px, 32px, 64px);
}
#developer-toolbar-toolbox-button[checked=true] {
-moz-image-region: rect(0px, 128px, 32px, 96px);
}
}
#developer-toolbar-closebutton {
list-style-image: url("chrome://browser/skin/devtools/close.png");
-moz-appearance: none;
@ -59,6 +84,12 @@
opacity: 0.6;
}
@media (min-resolution: 2dppx) {
#developer-toolbar-closebutton {
list-style-image: url("chrome://browser/skin/devtools/close@2x.png");
}
}
#developer-toolbar-closebutton > .toolbarbutton-icon {
/* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must
use evil CSS to give the impression of smaller content */
@ -109,7 +140,6 @@ html|*#gcli-output-frame {
.gclitoolbar-input-node {
-moz-appearance: none;
color: hsl(210,30%,85%);
padding-left: 20px;
background-color: #242b33;
background-repeat: no-repeat;
background-position: 4px center;
@ -118,14 +148,35 @@ html|*#gcli-output-frame {
-1px 0 0 hsla(206,37%,4%,.2) inset;
line-height: 32px;
outline-style: none;
background-image: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 16, 16, 0);
padding: 0;
}
.gclitoolbar-input-node[focused="true"] {
background-image: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 32, 16, 16);
background-color: #232e38;
}
.gclitoolbar-input-node::before {
content: "";
display: inline-block;
-moz-box-ordinal-group: 0;
width: 16px;
height: 16px;
margin: 0 2px;
background-image: url("chrome://browser/skin/devtools/commandline-icon.png");
background-position: 0 center;
background-size: 32px 16px;
}
.gclitoolbar-input-node[focused="true"]::before {
background-position: -16px center;
}
@media (min-resolution: 2dppx) {
.gclitoolbar-input-node::before {
background-image: url("chrome://browser/skin/devtools/commandline-icon@2x.png");
}
}
.gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
background-color: hsl(210,30%,85%);
color: hsl(210,24%,16%);

View File

@ -74,6 +74,13 @@ html|*.highlighter-nodeinfobar-pseudo-classes {
color: hsl(200,74%,57%);
}
html|*.highlighter-nodeinfobar-dimensions {
color: hsl(210,30%,85%);
-moz-border-start: 1px solid #5a6169;
-moz-margin-start: 6px;
-moz-padding-start: 6px;
}
/* Highlighter - Node Infobar - box & arrow */
.highlighter-nodeinfobar-arrow {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -119,6 +119,11 @@
background-position: -24px 8px;
}
#style-editor-options {
width: 20px;
overflow: hidden;
}
/* Invert all toggle icons but the one in the active row for light theme */
.theme-light .splitview-nav > li:not(.splitview-active) .stylesheet-enabled {
filter: url(filters.svg#invert);

View File

@ -323,6 +323,7 @@ browser.jar:
skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
skin/classic/browser/devtools/responsiveui-screenshot@2x.png (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
skin/classic/browser/devtools/toggle-tools.png (../shared/devtools/images/toggle-tools.png)
skin/classic/browser/devtools/toggle-tools@2x.png (../shared/devtools/images/toggle-tools@2x.png)
skin/classic/browser/devtools/dock-bottom@2x.png (../shared/devtools/images/dock-bottom@2x.png)
skin/classic/browser/devtools/dock-side@2x.png (../shared/devtools/images/dock-side@2x.png)
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
@ -723,6 +724,7 @@ browser.jar:
skin/classic/aero/browser/devtools/responsiveui-screenshot.png (../shared/devtools/images/responsivemode/responsiveui-screenshot.png)
skin/classic/aero/browser/devtools/responsiveui-screenshot@2x.png (../shared/devtools/images/responsivemode/responsiveui-screenshot@2x.png)
skin/classic/aero/browser/devtools/toggle-tools.png (../shared/devtools/images/toggle-tools.png)
skin/classic/aero/browser/devtools/toggle-tools@2x.png (../shared/devtools/images/toggle-tools@2x.png)
skin/classic/aero/browser/devtools/dock-bottom@2x.png (../shared/devtools/images/dock-bottom@2x.png)
skin/classic/aero/browser/devtools/dock-side@2x.png (../shared/devtools/images/dock-side@2x.png)
skin/classic/aero/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)

View File

@ -3938,6 +3938,7 @@ MOZ_ANDROID_HISTORY=
MOZ_WEBSMS_BACKEND=
MOZ_ANDROID_BEAM=
MOZ_ANDROID_SYNTHAPKS=
MOZ_LOCALE_SWITCHER=
ACCESSIBILITY=1
MOZ_TIME_MANAGER=
MOZ_PAY=
@ -4947,6 +4948,13 @@ if test -n "$MOZ_WEBSMS_BACKEND"; then
AC_DEFINE(MOZ_WEBSMS_BACKEND)
fi
dnl ========================================================
dnl = Enable runtime locale switching on Android
dnl ========================================================
if test -n "$MOZ_LOCALE_SWITCHER"; then
AC_DEFINE(MOZ_LOCALE_SWITCHER)
fi
dnl ========================================================
dnl = Enable NFC permission on Android
dnl ========================================================
@ -8567,6 +8575,7 @@ AC_SUBST(MOZ_ANDROID_HISTORY)
AC_SUBST(MOZ_WEBSMS_BACKEND)
AC_SUBST(MOZ_ANDROID_BEAM)
AC_SUBST(MOZ_ANDROID_SYNTHAPKS)
AC_SUBST(MOZ_LOCALE_SWITCHER)
AC_SUBST(MOZ_DISABLE_GECKOVIEW)
AC_SUBST(ENABLE_STRIP)
AC_SUBST(PKG_SKIP_STRIP)

View File

@ -24,6 +24,9 @@
#include "BluetoothService.h"
#include "BluetoothUtils.h"
#define ERR_INVALID_ADAPTER_STATE "InvalidAdapterStateError"
#define ERR_CHANGE_ADAPTER_STATE "ChangeAdapterStateError"
using namespace mozilla;
using namespace mozilla::dom;
@ -121,6 +124,7 @@ public:
BluetoothReplyRunnable::ReleaseMembers();
mAdapterPtr = nullptr;
}
private:
nsRefPtr<BluetoothAdapter> mAdapterPtr;
};
@ -156,6 +160,38 @@ public:
}
};
class EnableDisableAdapterTask : public BluetoothReplyRunnable
{
public:
EnableDisableAdapterTask(Promise* aPromise)
: BluetoothReplyRunnable(nullptr)
, mPromise(aPromise)
{ }
bool
ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
{
/*
* It is supposed to be Promise<void> according to BluetoothAdapter.webidl,
* but we have to pass "true" since it is mandatory to pass an
* argument while calling MaybeResolve.
*/
mPromise->MaybeResolve(true);
aValue.setUndefined();
return true;
}
void
ReleaseMembers()
{
BluetoothReplyRunnable::ReleaseMembers();
mPromise = nullptr;
}
private:
nsRefPtr<Promise> mPromise;
};
static int kCreatePairedDeviceTimeout = 50000; // unit: msec
BluetoothAdapter::BluetoothAdapter(nsPIDOMWindow* aWindow,
@ -164,12 +200,13 @@ BluetoothAdapter::BluetoothAdapter(nsPIDOMWindow* aWindow,
, BluetoothPropertyContainer(BluetoothObjectType::TYPE_ADAPTER)
, mJsUuids(nullptr)
, mJsDeviceAddresses(nullptr)
// TODO: Change to Disabled after Bug 1006309 landed
, mState(BluetoothAdapterState::Enabled)
, mDiscoverable(false)
, mDiscovering(false)
, mPairable(false)
, mPowered(false)
, mIsRooted(false)
, mState(BluetoothAdapterState::Disabled)
{
MOZ_ASSERT(aWindow);
MOZ_ASSERT(IsDOMBinding());
@ -231,7 +268,11 @@ BluetoothAdapter::SetPropertyByValue(const BluetoothNamedValue& aValue)
{
const nsString& name = aValue.name();
const BluetoothValue& value = aValue.value();
if (name.EqualsLiteral("Name")) {
if (name.EqualsLiteral("State")) {
bool isEnabled = value.get_bool();
mState = isEnabled ? BluetoothAdapterState::Enabled
: BluetoothAdapterState::Disabled;
} else if (name.EqualsLiteral("Name")) {
mName = value.get_nsString();
} else if (name.EqualsLiteral("Address")) {
mAddress = value.get_nsString();
@ -669,19 +710,55 @@ BluetoothAdapter::SetPairingConfirmation(const nsAString& aDeviceAddress,
return request.forget();
}
/*
* TODO: Implement Enable/Disable functions
*/
already_AddRefed<Promise>
BluetoothAdapter::EnableDisable(bool aEnable)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
NS_ENSURE_TRUE(global, nullptr);
nsRefPtr<Promise> promise = new Promise(global);
// Make sure BluetoothService is available before modifying adapter state
BluetoothService* bs = BluetoothService::Get();
if (!bs) {
promise->MaybeReject(ERR_CHANGE_ADAPTER_STATE);
return promise.forget();
}
if (aEnable) {
if (mState != BluetoothAdapterState::Disabled) {
promise->MaybeReject(ERR_INVALID_ADAPTER_STATE);
return promise.forget();
}
mState = BluetoothAdapterState::Enabling;
} else {
if (mState != BluetoothAdapterState::Enabled) {
promise->MaybeReject(ERR_INVALID_ADAPTER_STATE);
return promise.forget();
}
mState = BluetoothAdapterState::Disabling;
}
// TODO: Fire attr changed event for this state change
nsRefPtr<BluetoothReplyRunnable> result = new EnableDisableAdapterTask(promise);
if(NS_FAILED(bs->EnableDisable(aEnable, result))) {
promise->MaybeReject(ERR_CHANGE_ADAPTER_STATE);
}
return promise.forget();
}
already_AddRefed<Promise>
BluetoothAdapter::Enable()
{
return nullptr;
return EnableDisable(true);
}
already_AddRefed<Promise>
BluetoothAdapter::Disable()
{
return nullptr;
return EnableDisable(false);
}
already_AddRefed<DOMRequest>

View File

@ -122,10 +122,9 @@ public:
SetAuthorization(const nsAString& aDeviceAddress, bool aAllow,
ErrorResult& aRv);
already_AddRefed<Promise>
Enable();
already_AddRefed<Promise>
Disable();
already_AddRefed<Promise> EnableDisable(bool aEnable);
already_AddRefed<Promise> Enable();
already_AddRefed<Promise> Disable();
already_AddRefed<DOMRequest>
Connect(BluetoothDevice& aDevice,

View File

@ -163,14 +163,8 @@ BluetoothService::ToggleBtAck::Run()
sBluetoothService->SetEnabled(mEnabled);
sToggleInProgress = false;
nsAutoString signalName;
signalName = mEnabled ? NS_LITERAL_STRING("Enabled")
: NS_LITERAL_STRING("Disabled");
BluetoothSignal signal(signalName, NS_LITERAL_STRING(KEY_MANAGER), true);
sBluetoothService->DistributeSignal(signal);
// Event 'AdapterAdded' has to be fired after firing 'Enabled'
sBluetoothService->TryFiringAdapterAdded();
sBluetoothService->FireAdapterStateChanged(mEnabled);
return NS_OK;
}
@ -220,19 +214,6 @@ BluetoothService::~BluetoothService()
Cleanup();
}
PLDHashOperator
RemoveObserversExceptBluetoothManager
(const nsAString& key,
nsAutoPtr<BluetoothSignalObserverList>& value,
void* arg)
{
if (!key.EqualsLiteral(KEY_MANAGER)) {
return PL_DHASH_REMOVE;
}
return PL_DHASH_NEXT;
}
// static
BluetoothService*
BluetoothService::Create()
@ -384,7 +365,8 @@ BluetoothService::DistributeSignal(const BluetoothSignal& aSignal)
}
nsresult
BluetoothService::StartBluetooth(bool aIsStartup)
BluetoothService::StartBluetooth(bool aIsStartup,
BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
@ -405,7 +387,7 @@ BluetoothService::StartBluetooth(bool aIsStartup)
*/
if (aIsStartup || !sBluetoothService->IsEnabled()) {
// Switch Bluetooth on
if (NS_FAILED(sBluetoothService->StartInternal())) {
if (NS_FAILED(sBluetoothService->StartInternal(aRunnable))) {
BT_WARNING("Bluetooth service failed to start!");
}
} else {
@ -420,7 +402,8 @@ BluetoothService::StartBluetooth(bool aIsStartup)
}
nsresult
BluetoothService::StopBluetooth(bool aIsStartup)
BluetoothService::StopBluetooth(bool aIsStartup,
BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
@ -466,7 +449,7 @@ BluetoothService::StopBluetooth(bool aIsStartup)
*/
if (aIsStartup || sBluetoothService->IsEnabled()) {
// Switch Bluetooth off
if (NS_FAILED(sBluetoothService->StopInternal())) {
if (NS_FAILED(sBluetoothService->StopInternal(aRunnable))) {
BT_WARNING("Bluetooth service failed to stop!");
}
} else {
@ -481,13 +464,15 @@ BluetoothService::StopBluetooth(bool aIsStartup)
}
nsresult
BluetoothService::StartStopBluetooth(bool aStart, bool aIsStartup)
BluetoothService::StartStopBluetooth(bool aStart,
bool aIsStartup,
BluetoothReplyRunnable* aRunnable)
{
nsresult rv;
if (aStart) {
rv = StartBluetooth(aIsStartup);
rv = StartBluetooth(aIsStartup, aRunnable);
} else {
rv = StopBluetooth(aIsStartup);
rv = StopBluetooth(aIsStartup, aRunnable);
}
return rv;
}
@ -504,17 +489,6 @@ BluetoothService::SetEnabled(bool aEnabled)
unused << childActors[index]->SendEnabled(aEnabled);
}
if (!aEnabled) {
/**
* Remove all handlers except BluetoothManager when turning off bluetooth
* since it is possible that the event 'onAdapterAdded' would be fired after
* BluetoothManagers of child process are registered. Please see Bug 827759
* for more details.
*/
mBluetoothSignalObserverTable.Enumerate(
RemoveObserversExceptBluetoothManager, nullptr);
}
/**
* mEnabled: real status of bluetooth
* aEnabled: expected status of bluetooth
@ -553,7 +527,7 @@ nsresult
BluetoothService::HandleStartupSettingsCheck(bool aEnable)
{
MOZ_ASSERT(NS_IsMainThread());
return StartStopBluetooth(aEnable, true);
return StartStopBluetooth(aEnable, true, nullptr);
}
nsresult
@ -610,37 +584,6 @@ BluetoothService::HandleSettingsChanged(const nsAString& aData)
}
SWITCH_BT_DEBUG(value.toBoolean());
return NS_OK;
}
// Second, check if the string is BLUETOOTH_ENABLED_SETTING
if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_ENABLED_SETTING, &match)) {
MOZ_ASSERT(!JS_IsExceptionPending(cx));
return NS_ERROR_OUT_OF_MEMORY;
}
if (match) {
JS::Rooted<JS::Value> value(cx);
if (!JS_GetProperty(cx, obj, "value", &value)) {
MOZ_ASSERT(!JS_IsExceptionPending(cx));
return NS_ERROR_OUT_OF_MEMORY;
}
if (!value.isBoolean()) {
MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.enabled'!");
return NS_ERROR_UNEXPECTED;
}
if (sToggleInProgress || value.toBoolean() == IsEnabled()) {
// Nothing to do here.
return NS_OK;
}
sToggleInProgress = true;
nsresult rv = StartStopBluetooth(value.toBoolean(), false);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
@ -704,7 +647,7 @@ BluetoothService::HandleShutdown()
}
}
if (IsEnabled() && NS_FAILED(StopBluetooth(false))) {
if (IsEnabled() && NS_FAILED(StopBluetooth(false, nullptr))) {
MOZ_ASSERT(false, "Failed to deliver stop message!");
}
@ -785,6 +728,38 @@ BluetoothService::AdapterAddedReceived()
mAdapterAddedReceived = true;
}
/**
* Enable/Disable the local adapter.
*
* There is only one adapter on the mobile in current use cases.
* In addition, bluedroid couldn't enable/disable a single adapter.
* So currently we will turn on/off BT to enable/disable the adapter.
*
* TODO: To support enable/disable single adapter in the future,
* we will need to implement EnableDisableInternal for different stacks.
*/
nsresult
BluetoothService::EnableDisable(bool aEnable,
BluetoothReplyRunnable* aRunnable)
{
sToggleInProgress = true;
return StartStopBluetooth(aEnable, false, aRunnable);
}
void
BluetoothService::FireAdapterStateChanged(bool aEnable)
{
MOZ_ASSERT(NS_IsMainThread());
InfallibleTArray<BluetoothNamedValue> props;
BT_APPEND_NAMED_VALUE(props, "State", aEnable);
BluetoothValue value(props);
BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
NS_LITERAL_STRING(KEY_ADAPTER), value);
DistributeSignal(signal);
}
void
BluetoothService::Notify(const BluetoothSignal& aData)
{

View File

@ -315,6 +315,28 @@ public:
void TryFiringAdapterAdded();
void AdapterAddedReceived();
void FireAdapterStateChanged(bool aEnable);
nsresult EnableDisable(bool aEnable,
BluetoothReplyRunnable* aRunnable);
/**
* Platform specific startup functions go here. Usually deals with member
* variables, so not static. Guaranteed to be called outside of main thread.
*
* @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
*/
virtual nsresult
StartInternal(BluetoothReplyRunnable* aRunnable) = 0;
/**
* Platform specific startup functions go here. Usually deals with member
* variables, so not static. Guaranteed to be called outside of main thread.
*
* @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
*/
virtual nsresult
StopInternal(BluetoothReplyRunnable* aRunnable) = 0;
protected:
BluetoothService() : mEnabled(false)
, mAdapterAddedReceived(false)
@ -330,31 +352,15 @@ protected:
Cleanup();
nsresult
StartBluetooth(bool aIsStartup);
StartBluetooth(bool aIsStartup, BluetoothReplyRunnable* aRunnable);
nsresult
StopBluetooth(bool aIsStartup);
StopBluetooth(bool aIsStartup, BluetoothReplyRunnable* aRunnable);
nsresult
StartStopBluetooth(bool aStart, bool aIsStartup);
/**
* Platform specific startup functions go here. Usually deals with member
* variables, so not static. Guaranteed to be called outside of main thread.
*
* @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
*/
virtual nsresult
StartInternal() = 0;
/**
* Platform specific startup functions go here. Usually deals with member
* variables, so not static. Guaranteed to be called outside of main thread.
*
* @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
*/
virtual nsresult
StopInternal() = 0;
StartStopBluetooth(bool aStart,
bool aIsStartup,
BluetoothReplyRunnable* aRunnable);
/**
* Called when XPCOM first creates this service.

View File

@ -33,34 +33,47 @@
#include "mozilla/StaticPtr.h"
#include "mozilla/unused.h"
#define ENSURE_BLUETOOTH_IS_READY(runnable, result) \
do { \
if (!sBtInterface || !IsEnabled()) { \
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready"); \
DispatchBluetoothReply(runnable, BluetoothValue(), errorStr); \
return result; \
} \
} while(0)
using namespace mozilla;
using namespace mozilla::ipc;
USING_BLUETOOTH_NAMESPACE
/**
* Static variables
*/
static bluetooth_device_t* sBtDevice;
static const bt_interface_t* sBtInterface;
static bool sAdapterDiscoverable = false;
static bool sIsBtEnabled = false;
// TODO: Non thread-safe static variables
static nsString sAdapterBdAddress;
static nsString sAdapterBdName;
static uint32_t sAdapterDiscoverableTimeout;
static InfallibleTArray<nsString> sAdapterBondedAddressArray;
static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeAdapterStateRunnableArray;
// Static variables below should only be used on *main thread*
static const bt_interface_t* sBtInterface;
static nsTArray<nsRefPtr<BluetoothProfileController> > sControllerArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetDeviceRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
static nsTArray<int> sRequestedDeviceCountArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetDeviceRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
// Static variables below should only be used on *callback thread*
// Atomic static variables
static Atomic<bool> sAdapterDiscoverable(false);
static Atomic<uint32_t> sAdapterDiscoverableTimeout(0);
/**
* Classes only used in this file
*/
class DistributeBluetoothSignalTask : public nsRunnable {
class DistributeBluetoothSignalTask MOZ_FINAL : public nsRunnable
{
public:
DistributeBluetoothSignalTask(const BluetoothSignal& aSignal) :
mSignal(aSignal)
@ -84,12 +97,9 @@ private:
BluetoothSignal mSignal;
};
class SetupAfterEnabledTask : public nsRunnable
class SetupAfterEnabledTask MOZ_FINAL : public nsRunnable
{
public:
SetupAfterEnabledTask()
{ }
NS_IMETHOD
Run()
{
@ -135,17 +145,37 @@ public:
}
};
class CleanupTask : public nsRunnable
class CleanupTask MOZ_FINAL : public nsRunnable
{
public:
CleanupTask()
{ }
NS_IMETHOD
Run()
{
MOZ_ASSERT(NS_IsMainThread());
/*
* Cleanup static adapter properties and notify adapter to clean them
*
* TODO: clean up and notify Discovering also
*/
sAdapterBdAddress.Truncate();
sAdapterBdName.Truncate();
sAdapterDiscoverable = false;
InfallibleTArray<BluetoothNamedValue> props;
BT_APPEND_NAMED_VALUE(props, "Name", sAdapterBdName);
BT_APPEND_NAMED_VALUE(props, "Address", sAdapterBdAddress);
BT_APPEND_NAMED_VALUE(props, "Discoverable",
BluetoothValue(sAdapterDiscoverable));
BluetoothValue value(props);
BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
NS_LITERAL_STRING(KEY_ADAPTER), value);
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
bs->DistributeSignal(signal);
// Cleanup bluetooth interfaces after BT state becomes BT_STATE_OFF.
BluetoothHfpManager::DeinitHfpInterface();
BluetoothA2dpManager::DeinitA2dpInterface();
@ -270,45 +300,69 @@ PlayStatusStringToControlPlayStatus(const nsAString& aPlayStatus)
return playStatus;
}
static bool
IsReady()
{
if (!sBtInterface || !sIsBtEnabled) {
BT_LOGR("Warning! Bluetooth Service is not ready");
return false;
}
return true;
}
/**
* Bluedroid HAL callback functions
*
* Several callbacks are dispatched to main thread to avoid racing issues.
*/
static void
AdapterStateChangeCallback(bt_state_t aStatus)
{
MOZ_ASSERT(!NS_IsMainThread());
BT_LOGR("BT_STATE %d", aStatus);
BT_LOGR("BT_STATE: %d", aStatus);
sIsBtEnabled = (aStatus == BT_STATE_ON);
bool isBtEnabled = (aStatus == BT_STATE_ON);
if (!sIsBtEnabled && NS_FAILED(NS_DispatchToMainThread(new CleanupTask()))) {
if (!isBtEnabled &&
NS_FAILED(NS_DispatchToMainThread(new CleanupTask()))) {
BT_WARNING("Failed to dispatch to main thread!");
return;
}
nsRefPtr<nsRunnable> runnable =
new BluetoothService::ToggleBtAck(sIsBtEnabled);
new BluetoothService::ToggleBtAck(isBtEnabled);
if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
BT_WARNING("Failed to dispatch to main thread!");
return;
}
if (sIsBtEnabled &&
if (isBtEnabled &&
NS_FAILED(NS_DispatchToMainThread(new SetupAfterEnabledTask()))) {
BT_WARNING("Failed to dispatch to main thread!");
return;
}
// Resolve promise if existed
if(!sChangeAdapterStateRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sChangeAdapterStateRunnableArray[0],
BluetoothValue(true),
EmptyString());
sChangeAdapterStateRunnableArray.RemoveElementAt(0);
}
}
class AdapterPropertiesCallbackTask MOZ_FINAL : public nsRunnable
{
public:
NS_IMETHOD
Run()
{
MOZ_ASSERT(NS_IsMainThread());
if (!sSetPropertyRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sSetPropertyRunnableArray[0],
BluetoothValue(true), EmptyString());
sSetPropertyRunnableArray.RemoveElementAt(0);
}
return NS_OK;
}
};
/**
* AdapterPropertiesCallback will be called after enable() but before
* AdapterStateChangeCallback sIsBtEnabled get updated. At that moment, both
* AdapterStateChangeCallback is called. At that moment, both
* BluetoothManager/BluetoothAdapter does not register observer yet.
*/
static void
@ -385,14 +439,64 @@ AdapterPropertiesCallback(bt_status_t aStatus, int aNumProperties,
BT_WARNING("Failed to dispatch to main thread!");
}
// bluedroid BTU task was stored in the task queue, see GKI_send_msg
if (!sSetPropertyRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sSetPropertyRunnableArray[0], BluetoothValue(true),
EmptyString());
sSetPropertyRunnableArray.RemoveElementAt(0);
}
// Redirect to main thread to avoid racing problem
NS_DispatchToMainThread(new AdapterPropertiesCallbackTask());
}
class RemoteDevicePropertiesCallbackTask : public nsRunnable
{
const InfallibleTArray<BluetoothNamedValue> mProps;
nsString mRemoteDeviceBdAddress;
public:
RemoteDevicePropertiesCallbackTask(
const InfallibleTArray<BluetoothNamedValue>& aProps,
const nsAString& aRemoteDeviceBdAddress)
: mProps(aProps)
, mRemoteDeviceBdAddress(aRemoteDeviceBdAddress)
{ }
NS_IMETHOD
Run()
{
MOZ_ASSERT(NS_IsMainThread());
if (sRequestedDeviceCountArray.IsEmpty()) {
// This is possible because the callback would be called after turning
// Bluetooth on.
return NS_OK;
}
// Update to registered BluetoothDevice objects
BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
mRemoteDeviceBdAddress, mProps);
nsRefPtr<DistributeBluetoothSignalTask>
t = new DistributeBluetoothSignalTask(signal);
if (NS_FAILED(NS_DispatchToMainThread(t))) {
BT_WARNING("Failed to dispatch to main thread!");
return NS_OK;
}
static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
// Use address as the index
sRemoteDevicesPack.AppendElement(
BluetoothNamedValue(mRemoteDeviceBdAddress, mProps));
if (--sRequestedDeviceCountArray[0] == 0) {
if (!sGetDeviceRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sGetDeviceRunnableArray[0],
sRemoteDevicesPack, EmptyString());
sGetDeviceRunnableArray.RemoveElementAt(0);
}
sRequestedDeviceCountArray.RemoveElementAt(0);
sRemoteDevicesPack.Clear();
}
return NS_OK;
}
};
/**
* RemoteDevicePropertiesCallback will be called, as the following conditions:
* 1. When BT is turning on, bluedroid automatically execute this callback
@ -404,13 +508,6 @@ RemoteDevicePropertiesCallback(bt_status_t aStatus, bt_bdaddr_t *aBdAddress,
{
MOZ_ASSERT(!NS_IsMainThread());
if (sRequestedDeviceCountArray.IsEmpty()) {
MOZ_ASSERT(sGetDeviceRunnableArray.IsEmpty());
return;
}
sRequestedDeviceCountArray[0]--;
InfallibleTArray<BluetoothNamedValue> props;
nsString remoteDeviceBdAddress;
@ -435,36 +532,9 @@ RemoteDevicePropertiesCallback(bt_status_t aStatus, bt_bdaddr_t *aBdAddress,
}
}
// Update to registered BluetoothDevice objects
BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
remoteDeviceBdAddress, props);
nsRefPtr<DistributeBluetoothSignalTask>
t = new DistributeBluetoothSignalTask(signal);
if (NS_FAILED(NS_DispatchToMainThread(t))) {
BT_WARNING("Failed to dispatch to main thread!");
}
// Use address as the index
sRemoteDevicesPack.AppendElement(
BluetoothNamedValue(remoteDeviceBdAddress, props));
if (sRequestedDeviceCountArray[0] == 0) {
MOZ_ASSERT(!sGetDeviceRunnableArray.IsEmpty());
if (sGetDeviceRunnableArray.IsEmpty()) {
BT_LOGR("No runnable to return");
return;
}
DispatchBluetoothReply(sGetDeviceRunnableArray[0],
sRemoteDevicesPack, EmptyString());
// After firing it, clean up cache
sRemoteDevicesPack.Clear();
sRequestedDeviceCountArray.RemoveElementAt(0);
sGetDeviceRunnableArray.RemoveElementAt(0);
}
// Redirect to main thread to avoid racing problem
NS_DispatchToMainThread(
new RemoteDevicePropertiesCallbackTask(props, remoteDeviceBdAddress));
}
static void
@ -511,18 +581,33 @@ DeviceFoundCallback(int aNumProperties, bt_property_t *aProperties)
}
}
class DiscoveryStateChangedCallbackTask MOZ_FINAL : public nsRunnable
{
public:
NS_IMETHOD
Run()
{
MOZ_ASSERT(NS_IsMainThread());
if (!sChangeDiscoveryRunnableArray.IsEmpty()) {
BluetoothValue values(true);
DispatchBluetoothReply(sChangeDiscoveryRunnableArray[0],
values, EmptyString());
sChangeDiscoveryRunnableArray.RemoveElementAt(0);
}
return NS_OK;
}
};
static void
DiscoveryStateChangedCallback(bt_discovery_state_t aState)
{
MOZ_ASSERT(!NS_IsMainThread());
if (!sChangeDiscoveryRunnableArray.IsEmpty()) {
BluetoothValue values(true);
DispatchBluetoothReply(sChangeDiscoveryRunnableArray[0],
values, EmptyString());
sChangeDiscoveryRunnableArray.RemoveElementAt(0);
}
// Redirect to main thread to avoid racing problem
NS_DispatchToMainThread(new DiscoveryStateChangedCallbackTask());
}
static void
@ -581,26 +666,75 @@ SspRequestCallback(bt_bdaddr_t* aRemoteBdAddress, bt_bdname_t* aRemoteBdName,
}
}
class BondStateChangedCallbackTask : public nsRunnable
{
nsString mRemoteDeviceBdAddress;
bool mBonded;
public:
BondStateChangedCallbackTask(const nsAString& aRemoteDeviceBdAddress,
bool aBonded)
: mRemoteDeviceBdAddress(aRemoteDeviceBdAddress)
, mBonded(aBonded)
{ }
NS_IMETHOD
Run()
{
MOZ_ASSERT(NS_IsMainThread());
if (mBonded && !sBondingRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sBondingRunnableArray[0],
BluetoothValue(true), EmptyString());
sBondingRunnableArray.RemoveElementAt(0);
} else if (!mBonded && !sUnbondingRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sUnbondingRunnableArray[0],
BluetoothValue(true), EmptyString());
sUnbondingRunnableArray.RemoveElementAt(0);
}
// Update bonding status to gaia
InfallibleTArray<BluetoothNamedValue> propertiesArray;
BT_APPEND_NAMED_VALUE(propertiesArray, "address", mRemoteDeviceBdAddress);
BT_APPEND_NAMED_VALUE(propertiesArray, "status", mBonded);
BluetoothSignal signal(NS_LITERAL_STRING(PAIRED_STATUS_CHANGED_ID),
NS_LITERAL_STRING(KEY_ADAPTER),
BluetoothValue(propertiesArray));
NS_DispatchToMainThread(new DistributeBluetoothSignalTask(signal));
return NS_OK;
}
};
static void
BondStateChangedCallback(bt_status_t aStatus, bt_bdaddr_t* aRemoteBdAddress,
bt_bond_state_t aState)
{
MOZ_ASSERT(!NS_IsMainThread());
nsAutoString remoteAddress;
BdAddressTypeToString(aRemoteBdAddress, remoteAddress);
if (aState == BT_BOND_STATE_BONDING) {
// No need to handle bonding state
return;
}
nsAutoString remoteBdAddress;
BdAddressTypeToString(aRemoteBdAddress, remoteBdAddress);
if (aState == BT_BOND_STATE_BONDED &&
sAdapterBondedAddressArray.Contains(remoteBdAddress)) {
// See bug 940271 for more details about this case.
return;
}
// We don't need to handle bonding state
NS_ENSURE_TRUE_VOID(aState != BT_BOND_STATE_BONDING);
NS_ENSURE_FALSE_VOID(aState == BT_BOND_STATE_BONDED &&
sAdapterBondedAddressArray.Contains(remoteAddress));
bool bonded;
if (aState == BT_BOND_STATE_NONE) {
bonded = false;
sAdapterBondedAddressArray.RemoveElement(remoteAddress);
sAdapterBondedAddressArray.RemoveElement(remoteBdAddress);
} else if (aState == BT_BOND_STATE_BONDED) {
bonded = true;
sAdapterBondedAddressArray.AppendElement(remoteAddress);
sAdapterBondedAddressArray.AppendElement(remoteBdAddress);
}
// Update bonded address list to BluetoothAdapter
@ -614,27 +748,9 @@ BondStateChangedCallback(bt_status_t aStatus, bt_bdaddr_t* aRemoteBdAddress,
BluetoothValue(propertiesChangeArray));
NS_DispatchToMainThread(new DistributeBluetoothSignalTask(signal));
// Update bonding status to gaia
InfallibleTArray<BluetoothNamedValue> propertiesArray;
BT_APPEND_NAMED_VALUE(propertiesArray, "address", remoteAddress);
BT_APPEND_NAMED_VALUE(propertiesArray, "status", bonded);
BluetoothSignal newSignal(NS_LITERAL_STRING(PAIRED_STATUS_CHANGED_ID),
NS_LITERAL_STRING(KEY_ADAPTER),
BluetoothValue(propertiesArray));
NS_DispatchToMainThread(new DistributeBluetoothSignalTask(newSignal));
if (bonded && !sBondingRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sBondingRunnableArray[0],
BluetoothValue(true), EmptyString());
sBondingRunnableArray.RemoveElementAt(0);
} else if (!bonded && !sUnbondingRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sUnbondingRunnableArray[0],
BluetoothValue(true), EmptyString());
sUnbondingRunnableArray.RemoveElementAt(0);
}
// Redirect to main thread to avoid racing problem
NS_DispatchToMainThread(
new BondStateChangedCallbackTask(remoteBdAddress, bonded));
}
static void
@ -673,15 +789,17 @@ EnsureBluetoothHalLoad()
{
hw_module_t* module;
hw_device_t* device;
int err = hw_get_module(BT_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err != 0) {
BT_LOGR("Error: %s", strerror(err));
return false;
}
module->methods->open(module, BT_HARDWARE_MODULE_ID, &device);
sBtDevice = (bluetooth_device_t *)device;
NS_ENSURE_TRUE(sBtDevice, false);
sBtInterface = sBtDevice->get_bluetooth_interface();
bluetooth_device_t* btDevice = (bluetooth_device_t *)device;
NS_ENSURE_TRUE(btDevice, false);
sBtInterface = btDevice->get_bluetooth_interface();
NS_ENSURE_TRUE(sBtInterface, false);
return true;
@ -709,12 +827,16 @@ static nsresult
StartStopGonkBluetooth(bool aShouldEnable)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE(sBtInterface, NS_ERROR_FAILURE);
if (sIsBtEnabled == aShouldEnable) {
BluetoothService* bs = BluetoothService::Get();
NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
if (bs->IsEnabled() == aShouldEnable) {
// Keep current enable status
nsRefPtr<nsRunnable> runnable =
new BluetoothService::ToggleBtAck(sIsBtEnabled);
new BluetoothService::ToggleBtAck(aShouldEnable);
if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
BT_WARNING("Failed to dispatch to main thread!");
}
@ -772,10 +894,15 @@ BluetoothServiceBluedroid::~BluetoothServiceBluedroid()
}
nsresult
BluetoothServiceBluedroid::StartInternal()
BluetoothServiceBluedroid::StartInternal(BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
// aRunnable will be a nullptr while startup
if(aRunnable) {
sChangeAdapterStateRunnableArray.AppendElement(aRunnable);
}
nsresult ret = StartStopGonkBluetooth(true);
if (NS_FAILED(ret)) {
nsRefPtr<nsRunnable> runnable =
@ -790,10 +917,15 @@ BluetoothServiceBluedroid::StartInternal()
}
nsresult
BluetoothServiceBluedroid::StopInternal()
BluetoothServiceBluedroid::StopInternal(BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
// aRunnable will be a nullptr during starup and shutdown
if(aRunnable) {
sChangeAdapterStateRunnableArray.AppendElement(aRunnable);
}
nsresult ret = StartStopGonkBluetooth(false);
if (NS_FAILED(ret)) {
nsRefPtr<nsRunnable> runnable =
@ -828,6 +960,11 @@ BluetoothServiceBluedroid::GetAdaptersInternal(
uint32_t numAdapters = 1; // Bluedroid supports single adapter only
for (uint32_t i = 0; i < numAdapters; i++) {
// Since Atomic<*> is not acceptable for BT_APPEND_NAMED_VALUE(),
// create another variable to store data.
bool discoverable = sAdapterDiscoverable;
uint32_t discoverableTimeout = sAdapterDiscoverableTimeout;
BluetoothValue properties = InfallibleTArray<BluetoothNamedValue>();
// TODO: Revise here based on new BluetoothAdapter interface
@ -836,9 +973,9 @@ BluetoothServiceBluedroid::GetAdaptersInternal(
BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
"Name", sAdapterBdName);
BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
"Discoverable", sAdapterDiscoverable);
"Discoverable", discoverable);
BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
"DiscoverableTimeout", sAdapterDiscoverableTimeout);
"DiscoverableTimeout", discoverableTimeout);
BT_APPEND_NAMED_VALUE(properties.get_ArrayOfBluetoothNamedValue(),
"Devices", sAdapterBondedAddressArray);
@ -856,11 +993,7 @@ BluetoothServiceBluedroid::GetConnectedDevicePropertiesInternal(
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
BluetoothProfileManagerBase* profile =
BluetoothUuidHelper::GetBluetoothProfileManager(aServiceUuid);
@ -910,11 +1043,7 @@ BluetoothServiceBluedroid::GetPairedDevicePropertiesInternal(
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
int requestedDeviceCount = aDeviceAddress.Length();
if (requestedDeviceCount == 0) {
@ -947,12 +1076,8 @@ BluetoothServiceBluedroid::StartDiscoveryInternal(
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
return NS_OK;
}
int ret = sBtInterface->start_discovery();
if (ret != BT_STATUS_SUCCESS) {
ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("StartDiscovery"));
@ -970,11 +1095,7 @@ BluetoothServiceBluedroid::StopDiscoveryInternal(
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
int ret = sBtInterface->cancel_discovery();
if (ret != BT_STATUS_SUCCESS) {
@ -994,12 +1115,7 @@ BluetoothServiceBluedroid::SetProperty(BluetoothObjectType aType,
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
const nsString propName = aValue.name();
bt_property_t prop;
@ -1072,11 +1188,7 @@ BluetoothServiceBluedroid::CreatePairedDeviceInternal(
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
bt_bdaddr_t remoteAddress;
StringToBdAddressType(aDeviceAddress, &remoteAddress);
@ -1097,11 +1209,7 @@ BluetoothServiceBluedroid::RemoveDeviceInternal(
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
bt_bdaddr_t remoteAddress;
StringToBdAddressType(aDeviceAddress, &remoteAddress);
@ -1124,11 +1232,7 @@ BluetoothServiceBluedroid::SetPinCodeInternal(
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return false;
}
ENSURE_BLUETOOTH_IS_READY(aRunnable, false);
bt_bdaddr_t remoteAddress;
StringToBdAddressType(aDeviceAddress, &remoteAddress);
@ -1161,11 +1265,7 @@ BluetoothServiceBluedroid::SetPairingConfirmationInternal(
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return false;
}
ENSURE_BLUETOOTH_IS_READY(aRunnable, false);
bt_bdaddr_t remoteAddress;
StringToBdAddressType(aDeviceAddress, &remoteAddress);

View File

@ -22,8 +22,8 @@ public:
BluetoothServiceBluedroid();
~BluetoothServiceBluedroid();
virtual nsresult StartInternal();
virtual nsresult StopInternal();
virtual nsresult StartInternal(BluetoothReplyRunnable* aRunnable);
virtual nsresult StopInternal(BluetoothReplyRunnable* aRunnable);
virtual nsresult
GetAdaptersInternal(BluetoothReplyRunnable* aRunnable);

View File

@ -2097,8 +2097,10 @@ public:
};
nsresult
BluetoothDBusService::StartInternal()
BluetoothDBusService::StartInternal(BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(!aRunnable);
nsRefPtr<nsRunnable> runnable = new StartBluetoothRunnable();
nsresult rv = DispatchToBtThread(runnable);
if (NS_FAILED(rv)) {
@ -2225,8 +2227,10 @@ public:
};
nsresult
BluetoothDBusService::StopInternal()
BluetoothDBusService::StopInternal(BluetoothReplyRunnable* aRunnable)
{
MOZ_ASSERT(!aRunnable);
nsRefPtr<nsRunnable> runnable = new StopBluetoothRunnable();
nsresult rv = DispatchToBtThread(runnable);
if (NS_FAILED(rv)) {

View File

@ -47,9 +47,9 @@ public:
bool IsReady();
virtual nsresult StartInternal() MOZ_OVERRIDE;
virtual nsresult StartInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual nsresult StopInternal() MOZ_OVERRIDE;
virtual nsresult StopInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual nsresult
GetAdaptersInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;

View File

@ -192,6 +192,10 @@ BluetoothParent::RecvPBluetoothRequestConstructor(
switch (aRequest.type()) {
case Request::TGetAdaptersRequest:
return actor->DoRequest(aRequest.get_GetAdaptersRequest());
case Request::TStartBluetoothRequest:
return actor->DoRequest(aRequest.get_StartBluetoothRequest());
case Request::TStopBluetoothRequest:
return actor->DoRequest(aRequest.get_StopBluetoothRequest());
case Request::TSetPropertyRequest:
return actor->DoRequest(aRequest.get_SetPropertyRequest());
case Request::TStartDiscoveryRequest:
@ -321,6 +325,30 @@ BluetoothRequestParent::DoRequest(const GetAdaptersRequest& aRequest)
return true;
}
bool
BluetoothRequestParent::DoRequest(const StartBluetoothRequest& aRequest)
{
MOZ_ASSERT(mService);
MOZ_ASSERT(mRequestType == Request::TStartBluetoothRequest);
nsresult rv = mService->StartInternal(mReplyRunnable.get());
NS_ENSURE_SUCCESS(rv, false);
return true;
}
bool
BluetoothRequestParent::DoRequest(const StopBluetoothRequest& aRequest)
{
MOZ_ASSERT(mService);
MOZ_ASSERT(mRequestType == Request::TStopBluetoothRequest);
nsresult rv = mService->StopInternal(mReplyRunnable.get());
NS_ENSURE_SUCCESS(rv, false);
return true;
}
bool
BluetoothRequestParent::DoRequest(const SetPropertyRequest& aRequest)
{

View File

@ -128,6 +128,12 @@ protected:
bool
DoRequest(const GetAdaptersRequest& aRequest);
bool
DoRequest(const StartBluetoothRequest& aRequest);
bool
DoRequest(const StopBluetoothRequest& aRequest);
bool
DoRequest(const SetPropertyRequest& aRequest);

View File

@ -103,6 +103,20 @@ BluetoothServiceChildProcess::GetAdaptersInternal(
return NS_OK;
}
nsresult
BluetoothServiceChildProcess::StartInternal(BluetoothReplyRunnable* aRunnable)
{
SendRequest(aRunnable, StartBluetoothRequest());
return NS_OK;
}
nsresult
BluetoothServiceChildProcess::StopInternal(BluetoothReplyRunnable* aRunnable)
{
SendRequest(aRunnable, StopBluetoothRequest());
return NS_OK;
}
nsresult
BluetoothServiceChildProcess::GetConnectedDevicePropertiesInternal(
uint16_t aServiceUuid,
@ -377,18 +391,6 @@ BluetoothServiceChildProcess::HandleShutdown()
return NS_OK;
}
nsresult
BluetoothServiceChildProcess::StartInternal()
{
MOZ_CRASH("This should never be called!");
}
nsresult
BluetoothServiceChildProcess::StopInternal()
{
MOZ_CRASH("This should never be called!");
}
bool
BluetoothServiceChildProcess::IsConnected(uint16_t aServiceUuid)
{

View File

@ -46,6 +46,12 @@ public:
virtual nsresult
GetAdaptersInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual nsresult
StartInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual nsresult
StopInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
virtual nsresult
GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddresses,
BluetoothReplyRunnable* aRunnable)
@ -199,14 +205,6 @@ protected:
HandleShutdown() MOZ_OVERRIDE;
private:
// This method should never be called.
virtual nsresult
StartInternal() MOZ_OVERRIDE;
// This method should never be called.
virtual nsresult
StopInternal() MOZ_OVERRIDE;
bool
IsSignalRegistered(const nsAString& aNodeName) {
return !!mBluetoothSignalObserverTable.Get(aNodeName);

View File

@ -25,6 +25,14 @@ namespace bluetooth {
struct GetAdaptersRequest
{ };
struct StartBluetoothRequest
{
};
struct StopBluetoothRequest
{
};
struct SetPropertyRequest
{
BluetoothObjectType type;
@ -166,6 +174,8 @@ struct SendPlayStatusRequest
union Request
{
GetAdaptersRequest;
StartBluetoothRequest;
StopBluetoothRequest;
SetPropertyRequest;
GetPropertyRequest;
StartDiscoveryRequest;

View File

@ -2,6 +2,7 @@
* 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 "domstubs.idl"
#include "nsISupports.idl"
interface nsIDOMMozCellBroadcastEtwsInfo;
@ -10,7 +11,7 @@ interface nsIDOMMozCellBroadcastEtwsInfo;
* MozCellBroadcastMessage encapsulates Cell Broadcast short message service
* (CBS) messages.
*/
[scriptable, uuid(6abe65de-6729-41f7-906a-3f3a2dbe30ae)]
[scriptable, uuid(701e74a9-5fc4-4e2d-a324-9b7693395159)]
interface nsIDOMMozCellBroadcastMessage : nsISupports
{
/**
@ -53,7 +54,7 @@ interface nsIDOMMozCellBroadcastMessage : nsISupports
/**
* System time stamp at receival.
*/
readonly attribute jsval timestamp; // jsval is for Date.
readonly attribute DOMTimeStamp timestamp;
/**
* Additional ETWS-specific info.

View File

@ -196,7 +196,7 @@ function testReceiving_ETWS_Timestamp() {
doTestHelper(pdu, testReceiving_ETWS_WarningType, function(message) {
// Cell Broadcast messages do not contain a timestamp field (however, ETWS
// does). We only check the timestamp doesn't go too far (60 seconds) here.
let msMessage = message.timestamp.getTime();
let msMessage = message.timestamp;
let msNow = Date.now();
ok(Math.abs(msMessage - msNow) < (1000 * 60), "message.timestamp");
});

View File

@ -361,7 +361,7 @@ function testReceiving_GSM_Timestamp() {
doTestHelper(pdu, testReceiving_GSM_WarningType, function(message) {
// Cell Broadcast messages do not contain a timestamp field (however, ETWS
// does). We only check the timestamp doesn't go too far (60 seconds) here.
let msMessage = message.timestamp.getTime();
let msMessage = message.timestamp;
let msNow = Date.now();
ok(Math.abs(msMessage - msNow) < (1000 * 60), "message.timestamp");
});

View File

@ -7,5 +7,3 @@
DIRS += ['interfaces', 'src']
TEST_DIRS += ['tests']
if CONFIG['ENABLE_TESTS']:
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']

View File

@ -4,5 +4,8 @@
# 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/.
MOCHITEST_MANIFESTS += ['mochitest.ini']
MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
if CONFIG['ENABLE_TESTS']:
XPCSHELL_TESTS_MANIFESTS += ['xpcshell/xpcshell.ini']

View File

@ -1,9 +1,6 @@
[DEFAULT]
head = header_helpers.js
tail =
support-files =
test_sms_basics.html
test_smsfilter.html
[test_smsservice_createsmsmessage.js]
[test_wsp_pdu_helper.js]

View File

@ -312,7 +312,7 @@ function CellBroadcastMessage(pdu) {
this.language = pdu.language;
this.body = pdu.fullBody;
this.messageClass = pdu.messageClass;
this.timestamp = new Date(pdu.timestamp);
this.timestamp = pdu.timestamp;
if (pdu.etws != null) {
this.etws = new CellBroadcastEtwsInfo(pdu.etws);

View File

@ -12791,9 +12791,17 @@ SimRecordHelperObject.prototype = {
let ICCUtilsHelper = this.context.ICCUtilsHelper;
let RIL = this.context.RIL;
// TS 31.102, clause 4.2.18 EFAD
let mncLength = 0;
if (ad && ad[3]) {
mncLength = ad[3] & 0x0f;
if (mncLength != 0x02 && mncLength != 0x03) {
mncLength = 0;
}
}
// The 4th byte of the response is the length of MNC.
let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi,
ad && ad[3]);
mncLength);
if (mccMnc) {
RIL.iccInfo.mcc = mccMnc.mcc;
RIL.iccInfo.mnc = mccMnc.mnc;
@ -14130,6 +14138,7 @@ ICCUtilsHelperObject.prototype = {
* The imsi of icc.
* @param mncLength [optional]
* The length of mnc.
* Zero indicates we haven't got a valid mnc length.
*
* @return An object contains the parsing result of mcc and mnc.
* Or null if any error occurred.

View File

@ -2504,7 +2504,7 @@ add_test(function test_reading_ad_and_parsing_mcc_mnc() {
io.loadTransparentEF = function fakeLoadTransparentEF(options) {
let ad = [0x00, 0x00, 0x00];
if (mncLengthInEf) {
if (typeof mncLengthInEf === 'number') {
ad.push(mncLengthInEf);
}
@ -2531,9 +2531,20 @@ add_test(function test_reading_ad_and_parsing_mcc_mnc() {
}
do_test(undefined, "466923202422409", "466", "92" );
do_test(0x00, "466923202422409", "466", "92" );
do_test(0x01, "466923202422409", "466", "92" );
do_test(0x02, "466923202422409", "466", "92" );
do_test(0x03, "466923202422409", "466", "923");
do_test(0x04, "466923202422409", "466", "92" );
do_test(0xff, "466923202422409", "466", "92" );
do_test(undefined, "310260542718417", "310", "260");
do_test(0x00, "310260542718417", "310", "260");
do_test(0x01, "310260542718417", "310", "260");
do_test(0x02, "310260542718417", "310", "26" );
do_test(0x03, "310260542718417", "310", "260");
do_test(0x04, "310260542718417", "310", "260");
do_test(0xff, "310260542718417", "310", "260");
run_next_test();
});

View File

@ -121,6 +121,13 @@ public class AppConstants {
false;
#endif
public static final boolean MOZ_LOCALE_SWITCHER =
#ifdef MOZ_LOCALE_SWITCHER
true;
#else
false;
#endif
public static final boolean MOZ_UPDATER =
#ifdef MOZ_UPDATER
true;

View File

@ -75,6 +75,10 @@ public class BrowserLocaleManager implements LocaleManager {
}
}
public boolean isEnabled() {
return AppConstants.MOZ_LOCALE_SWITCHER;
}
/**
* Gecko uses locale codes like "es-ES", whereas a Java {@link Locale}
* stringifies as "es_ES".

View File

@ -18,6 +18,11 @@ import android.content.res.Resources;
*/
public interface LocaleManager {
void initialize(Context context);
/**
* @return true if locale switching is enabled.
*/
boolean isEnabled();
Locale getCurrentLocale(Context context);
String getAndApplyPersistedLocale(Context context);
void correctLocale(Context context, Resources resources, Configuration newConfig);

View File

@ -188,13 +188,18 @@ public class Tabs implements GeckoEventListener {
}
}
private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate) {
private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate, int tabIndex) {
final Tab tab = isPrivate ? new PrivateTab(mAppContext, id, url, external, parentId, title) :
new Tab(mAppContext, id, url, external, parentId, title);
synchronized (this) {
lazyRegisterBookmarkObserver();
mTabs.put(id, tab);
mOrder.add(tab);
if (tabIndex > -1) {
mOrder.add(tabIndex, tab);
} else {
mOrder.add(tab);
}
}
// Suppress the ADDED event to prevent animation of tabs created via session restore.
@ -427,7 +432,8 @@ public class Tabs implements GeckoEventListener {
tab = addTab(id, url, message.getBoolean("external"),
message.getInt("parentId"),
message.getString("title"),
message.getBoolean("isPrivate"));
message.getBoolean("isPrivate"),
message.getInt("tabIndex"));
// If we added the tab as a stub, we should have already
// selected it, so ignore this flag for stubbed tabs.
@ -799,7 +805,10 @@ public class Tabs implements GeckoEventListener {
// long as it's a valid URI.
String tabUrl = (url != null && Uri.parse(url).getScheme() != null) ? url : null;
added = addTab(tabId, tabUrl, external, parentId, url, isPrivate);
// Add the new tab to the end of the tab order.
final int tabIndex = -1;
added = addTab(tabId, tabUrl, external, parentId, url, isPrivate, tabIndex);
added.setDesktopMode(desktopMode);
}
} catch (Exception e) {

View File

@ -6,13 +6,11 @@ package org.mozilla.gecko.fxa.activities;
import java.util.Locale;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
import org.mozilla.gecko.sync.setup.activities.LocaleAware;

View File

@ -18,6 +18,7 @@ import org.mozilla.gecko.fxa.login.Married;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
import org.mozilla.gecko.sync.SyncConfiguration;
import android.accounts.Account;
@ -28,10 +29,13 @@ import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.text.TextUtils;
/**
* A fragment that displays the status of an AndroidFxAccount.
@ -39,7 +43,9 @@ import android.preference.PreferenceScreen;
* The owning activity is responsible for providing an AndroidFxAccount at
* appropriate times.
*/
public class FxAccountStatusFragment extends PreferenceFragment implements OnPreferenceClickListener {
public class FxAccountStatusFragment
extends PreferenceFragment
implements OnPreferenceClickListener, OnPreferenceChangeListener {
private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName();
// When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
@ -65,7 +71,12 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
protected CheckBoxPreference tabsPreference;
protected CheckBoxPreference passwordsPreference;
protected EditTextPreference deviceNamePreference;
protected volatile AndroidFxAccount fxAccount;
// The contract is: when fxAccount is non-null, then clientsDataDelegate is
// non-null. If violated then an IllegalStateException is thrown.
protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate;
// Used to post delayed sync requests.
protected Handler handler;
@ -88,6 +99,10 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferences();
}
protected void addPreferences() {
addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
emailPreference = ensureFindPreference("email");
@ -119,6 +134,9 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
historyPreference.setOnPreferenceClickListener(this);
tabsPreference.setOnPreferenceClickListener(this);
passwordsPreference.setOnPreferenceClickListener(this);
deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
deviceNamePreference.setOnPreferenceChangeListener(this);
}
/**
@ -177,6 +195,8 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
historyPreference.setEnabled(enabled);
tabsPreference.setEnabled(enabled);
passwordsPreference.setEnabled(enabled);
// Since we can't sync, we can't update our remote client record.
deviceNamePreference.setEnabled(enabled);
}
/**
@ -294,6 +314,14 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
throw new IllegalArgumentException("fxAccount must not be null");
}
this.fxAccount = fxAccount;
try {
this.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs());
} catch (Exception e) {
Logger.error(LOG_TAG, "Got exception fetching Sync prefs associated to Firefox Account; aborting.", e);
// Something is terribly wrong; best to get a stack trace rather than
// continue with a null clients delegate.
throw new IllegalStateException(e);
}
handler = new Handler(); // Attached to current (assumed to be UI) thread.
@ -319,6 +347,17 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate);
}
protected void hardRefresh() {
// This is the only way to guarantee that the EditText dialogs created by
// EditTextPreferences are re-created. This works around the issue described
// at http://androiddev.orkitra.com/?p=112079.
final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen");
statusScreen.removeAll();
addPreferences();
refresh();
}
protected void refresh() {
// refresh is called from our onResume, which can happen before the owning
// Activity tells us about an account (via our public
@ -372,6 +411,10 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
// No matter our state, we should update the checkboxes.
updateSelectedEngines();
}
final String clientName = clientsDataDelegate.getClientName();
deviceNamePreference.setSummary(clientName);
deviceNamePreference.setText(clientName);
}
/**
@ -571,4 +614,22 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
button.setOnPreferenceClickListener(listener);
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == deviceNamePreference) {
String newClientName = (String) newValue;
if (TextUtils.isEmpty(newClientName)) {
newClientName = clientsDataDelegate.getDefaultClientName();
}
final long now = System.currentTimeMillis();
clientsDataDelegate.setClientName(newClientName, now);
requestDelayedSync(); // Try to update our remote client record.
hardRefresh(); // Updates the value displayed to the user, among other things.
return true;
}
// For everything else, accept the change.
return true;
}
}

View File

@ -357,7 +357,11 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
FxAccountGlobalSession globalSession = null;
try {
ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
final ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
FxAccountConstants.pii(LOG_TAG, "Client device name is: '" + clientsDataDelegate.getClientName() + "'.");
FxAccountConstants.pii(LOG_TAG, "Client device data last modified: " + clientsDataDelegate.getLastModifiedTimestamp());
}
// We compute skew over time using SkewHandler. This yields an unchanging
// skew adjustment that the HawkAuthHeaderProvider uses to adjust its

View File

@ -175,6 +175,7 @@
<!ENTITY fxaccount_status_header2 'Firefox Account'>
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
<!ENTITY fxaccount_status_device_name 'Device name'>
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
<!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
<!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>

View File

@ -131,6 +131,7 @@ OnSharedPreferenceChangeListener
* Track the last locale so we know whether to redisplay.
*/
private Locale lastLocale = Locale.getDefault();
private boolean localeSwitchingIsEnabled;
private void updateActionBarTitle(int title) {
if (Build.VERSION.SDK_INT >= 14) {
@ -268,6 +269,10 @@ OnSharedPreferenceChangeListener
// Apply the current user-selected locale, if necessary.
checkLocale();
// Track this so we can decide whether to show locale options.
// See also the workaround below for Bug 1015209.
localeSwitchingIsEnabled = BrowserLocaleManager.getInstance().isEnabled();
// For Android v11+ where we use Fragments (v11+ only due to bug 866352),
// check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set
// (or set it) before super.onCreate() is called so Android can display
@ -283,10 +288,17 @@ OnSharedPreferenceChangeListener
updateTitle(getString(R.string.pref_header_customize));
}
// So that Android doesn't put the fragment title (or nothing at
// all) in the action bar.
if (onIsMultiPane()) {
// So that Android doesn't put the fragment title (or nothing at
// all) in the action bar.
updateActionBarTitle(R.string.settings_title);
if (Build.VERSION.SDK_INT < 13) {
// Affected by Bug 1015209 -- no detach/attach.
// If we try rejigging fragments, we'll crash, so don't
// enable locale switching at all.
localeSwitchingIsEnabled = false;
}
}
}
@ -397,6 +409,18 @@ OnSharedPreferenceChangeListener
public void onBuildHeaders(List<Header> target) {
if (onIsMultiPane()) {
loadHeadersFromResource(R.xml.preference_headers, target);
// If locale switching is disabled, remove the section
// entirely. This logic will need to be extended when
// content language selection (Bug 881510) is implemented.
if (!localeSwitchingIsEnabled) {
for (Header header : target) {
if (header.id == R.id.pref_header_language) {
target.remove(header);
break;
}
}
}
}
}
@ -564,9 +588,20 @@ OnSharedPreferenceChangeListener
private void setupPreferences(PreferenceGroup preferences, ArrayList<String> prefs) {
for (int i = 0; i < preferences.getPreferenceCount(); i++) {
Preference pref = preferences.getPreference(i);
// Eliminate locale switching if necessary.
// This logic will need to be extended when
// content language selection (Bug 881510) is implemented.
if (!localeSwitchingIsEnabled &&
"preferences_locale".equals(pref.getExtras().getString("resource", null))) {
preferences.removePreference(pref);
i--;
continue;
}
String key = pref.getKey();
if (pref instanceof PreferenceGroup) {
// If no datareporting is enabled, remove UI.
// If datareporting is disabled, remove UI.
if (PREFS_DATA_REPORTING_PREFERENCES.equals(key)) {
if (!AppConstants.MOZ_DATA_REPORTING) {
preferences.removePreference(pref);

View File

@ -28,7 +28,8 @@
</header>
<header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
android:title="@string/pref_header_language">
android:title="@string/pref_header_language"
android:id="@+id/pref_header_language">
<extra android:name="resource"
android:value="preferences_locale" />
</header>

View File

@ -66,6 +66,12 @@
android:key="passwords"
android:persistent="false"
android:title="@string/fxaccount_status_passwords" />
<EditTextPreference
android:singleLine="true"
android:key="device_name"
android:persistent="false"
android:title="@string/fxaccount_status_device_name" />
</PreferenceCategory>
<PreferenceCategory
android:key="legal_category"

View File

@ -30,12 +30,33 @@ public class SharedPreferencesClientsDataDelegate implements ClientsDataDelegate
return accountGUID;
}
/**
* Set client name.
*
* @param clientName to change to.
*/
@Override
public synchronized void setClientName(String clientName, long now) {
sharedPreferences
.edit()
.putString(SyncConfiguration.PREF_CLIENT_NAME, clientName)
.putLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, now)
.commit();
}
@Override
public String getDefaultClientName() {
// Bug 1019719: localize this string!
return GlobalConstants.MOZ_APP_DISPLAYNAME + " on " + android.os.Build.MODEL;
}
@Override
public synchronized String getClientName() {
String clientName = sharedPreferences.getString(SyncConfiguration.PREF_CLIENT_NAME, null);
if (clientName == null) {
clientName = GlobalConstants.MOZ_APP_DISPLAYNAME + " on " + android.os.Build.MODEL;
sharedPreferences.edit().putString(SyncConfiguration.PREF_CLIENT_NAME, clientName).commit();
clientName = getDefaultClientName();
long now = System.currentTimeMillis();
setClientName(clientName, now);
}
return clientName;
}
@ -54,4 +75,9 @@ public class SharedPreferencesClientsDataDelegate implements ClientsDataDelegate
public synchronized int getClientsCount() {
return (int) sharedPreferences.getLong(SyncConfiguration.PREF_NUM_CLIENTS, 0);
}
@Override
public long getLastModifiedTimestamp() {
return sharedPreferences.getLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, 0);
}
}

View File

@ -258,6 +258,7 @@ public class SyncConfiguration {
public static final String PREF_ACCOUNT_GUID = "account.guid";
public static final String PREF_CLIENT_NAME = "account.clientName";
public static final String PREF_NUM_CLIENTS = "account.numClients";
public static final String PREF_CLIENT_DATA_TIMESTAMP = "account.clientDataTimestamp";
private static final String API_VERSION = "1.5";

View File

@ -6,8 +6,22 @@ package org.mozilla.gecko.sync.delegates;
public interface ClientsDataDelegate {
public String getAccountGUID();
public String getDefaultClientName();
public void setClientName(String clientName, long now);
public String getClientName();
public void setClientsCount(int clientsCount);
public int getClientsCount();
public boolean isLocalGUID(String guid);
/**
* The last time the client's data was modified in a way that should be
* reflected remotely.
* <p>
* Changing the client's name should be reflected remotely, while changing the
* clients count should not (since that data is only used to inform local
* policy.)
*
* @return timestamp in milliseconds.
*/
public long getLastModifiedTimestamp();
}

View File

@ -9,6 +9,7 @@ import java.util.ArrayList;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.db.Tab;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.NoContentProviderException;
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
@ -30,12 +31,10 @@ import android.net.Uri;
import android.os.RemoteException;
public class FennecTabsRepository extends Repository {
protected final String localClientName;
protected final String localClientGuid;
protected final ClientsDataDelegate clientsDataDelegate;
public FennecTabsRepository(final String localClientName, final String localClientGuid) {
this.localClientName = localClientName;
this.localClientGuid = localClientGuid;
public FennecTabsRepository(ClientsDataDelegate clientsDataDelegate) {
this.clientsDataDelegate = clientsDataDelegate;
}
/**
@ -144,14 +143,17 @@ public class FennecTabsRepository extends Repository {
public void run() {
// We fetch all local tabs (since the record must contain them all)
// but only process the record if the timestamp is sufficiently
// recent.
// recent, or if the client data has been modified.
try {
final Cursor cursor = tabsHelper.safeQuery(tabsProvider, ".fetchSince()", null,
localClientSelection, localClientSelectionArgs, positionAscending);
try {
final String localClientGuid = clientsDataDelegate.getAccountGUID();
final String localClientName = clientsDataDelegate.getClientName();
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, localClientGuid, localClientName);
if (tabsRecord.lastModified >= timestamp) {
if (tabsRecord.lastModified >= timestamp ||
clientsDataDelegate.getLastModifiedTimestamp() >= timestamp) {
delegate.onFetchedRecord(tabsRecord);
}
} finally {

View File

@ -25,12 +25,9 @@ import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConstants;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository.FennecTabsRepositorySession;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareActivity;
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
import android.accounts.Account;

View File

@ -4,7 +4,6 @@
package org.mozilla.gecko.sync.stage;
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
import org.mozilla.gecko.sync.repositories.RecordFactory;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
@ -31,8 +30,7 @@ public class FennecTabsServerSyncStage extends ServerSyncStage {
@Override
protected Repository getLocalRepository() {
final ClientsDataDelegate clientsDelegate = session.getClientsDelegate();
return new FennecTabsRepository(clientsDelegate.getClientName(), clientsDelegate.getAccountGUID());
return new FennecTabsRepository(session.getClientsDelegate());
}
@Override

View File

@ -397,6 +397,11 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
return true;
}
if (session.getClientsDelegate().getLastModifiedTimestamp() > lastUpload) {
// Something's changed locally since we last uploaded.
return true;
}
// Note the opportunity for clock drift problems here.
// TODO: if we track download times, we can use the timestamp of most
// recent download response instead of the current time.

View File

@ -921,7 +921,12 @@ var BrowserApp = {
aParams = aParams || {};
let newTab = new Tab(aURI, aParams);
this._tabs.push(newTab);
if (typeof aParams.tabIndex == "number") {
this._tabs.splice(aParams.tabIndex, 0, newTab);
} else {
this._tabs.push(newTab);
}
let selected = "selected" in aParams ? aParams.selected : true;
if (selected)
@ -972,17 +977,23 @@ var BrowserApp = {
if (aTab == this.selectedTab)
this.selectedTab = null;
let tabIndex = this._tabs.indexOf(aTab);
let evt = document.createEvent("UIEvents");
evt.initUIEvent("TabClose", true, false, window, null);
evt.initUIEvent("TabClose", true, false, window, tabIndex);
aTab.browser.dispatchEvent(evt);
// Get a title for the undo close toast. Fall back to the URL if there is no title.
let title = aTab.browser.contentDocument.title || aTab.browser.contentDocument.URL;
aTab.destroy();
this._tabs.splice(this._tabs.indexOf(aTab), 1);
this._tabs.splice(tabIndex, 1);
if (aShowUndoToast) {
// Get a title for the undo close toast. Fall back to the URL if there is no title.
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
let closedTabData = ss.getClosedTabs(window)[0];
let historyEntry = closedTabData.entries[closedTabData.index - 1];
let title = historyEntry.title || historyEntry.url;
let message = Strings.browser.formatStringFromName("undoCloseToast.message", [title], 1);
NativeWindow.toast.show(message, "short", {
button: {
@ -990,7 +1001,6 @@ var BrowserApp = {
label: Strings.browser.GetStringFromName("undoCloseToast.action2"),
callback: function() {
UITelemetry.addEvent("undo.1", "toast", null, "closetab");
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
ss.undoCloseTab(window, 0);
}
}
@ -3050,6 +3060,7 @@ Tab.prototype = {
tabID: this.id,
uri: uri,
parentId: ("parentId" in aParams) ? aParams.parentId : -1,
tabIndex: ("tabIndex" in aParams) ? aParams.tabIndex : -1,
external: ("external" in aParams) ? aParams.external : false,
selected: ("selected" in aParams) ? aParams.selected : true,
title: title,

View File

@ -14,7 +14,7 @@ interface nsIDOMNode;
* tabs contained in them.
*/
[scriptable, uuid(fe116b56-0226-4562-b52a-a623dad07ead)]
[scriptable, uuid(91eca9cf-6741-4c8f-a3a0-2e957240894d)]
interface nsISessionStore : nsISupports
{
/**
@ -32,9 +32,9 @@ interface nsISessionStore : nsISupports
* Get closed tab data
*
* @param aWindow is the browser window for which to get closed tab data
* @returns a JSON string representing the list of closed tabs.
* @returns a JS array of closed tabs.
*/
AString getClosedTabData(in nsIDOMWindow aWindow);
jsval getClosedTabs(in nsIDOMWindow aWindow);
/**
* @param aWindow is the browser window to reopen a closed tab in.

View File

@ -46,6 +46,10 @@ SessionStore.prototype = {
_maxTabsUndo: 1,
_pendingWrite: 0,
// The index where the most recently closed tab was in the tabs array
// when it was closed.
_lastClosedTabIndex: -1,
init: function ss_init() {
// Get file references
this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
@ -98,6 +102,8 @@ SessionStore.prototype = {
for (let [ssid, win] in Iterator(this._windows))
win.closedTabs = [];
this._lastClosedTabIndex = -1;
if (this._loadState == STATE_RUNNING) {
// Save the purged state immediately
this.saveState();
@ -164,7 +170,7 @@ SessionStore.prototype = {
}
case "TabClose": {
let browser = aEvent.target;
this.onTabClose(window, browser);
this.onTabClose(window, browser, aEvent.detail);
this.onTabRemove(window, browser);
break;
}
@ -269,7 +275,7 @@ SessionStore.prototype = {
this.saveStateDelayed();
},
onTabClose: function ss_onTabClose(aWindow, aBrowser) {
onTabClose: function ss_onTabClose(aWindow, aBrowser, aTabIndex) {
if (this._maxTabsUndo == 0)
return;
@ -283,6 +289,8 @@ SessionStore.prototype = {
let length = this._windows[aWindow.__SSID].closedTabs.length;
if (length > this._maxTabsUndo)
this._windows[aWindow.__SSID].closedTabs.splice(this._maxTabsUndo, length - this._maxTabsUndo);
this._lastClosedTabIndex = aTabIndex;
}
},
@ -818,11 +826,11 @@ SessionStore.prototype = {
return this._windows[aWindow.__SSID].closedTabs.length;
},
getClosedTabData: function ss_getClosedTabData(aWindow) {
getClosedTabs: function ss_getClosedTabs(aWindow) {
if (!aWindow.__SSID)
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
return JSON.stringify(this._windows[aWindow.__SSID].closedTabs);
return this._windows[aWindow.__SSID].closedTabs;
},
undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
@ -845,11 +853,14 @@ SessionStore.prototype = {
let params = {
selected: true,
isPrivate: closedTab.isPrivate,
desktopMode: closedTab.desktopMode
desktopMode: closedTab.desktopMode,
tabIndex: this._lastClosedTabIndex
};
let tab = aWindow.BrowserApp.addTab(closedTab.entries[closedTab.index - 1].url, params);
this._restoreHistory(closedTab, tab.browser.sessionHistory);
this._lastClosedTabIndex = -1;
// Put back the extra data
tab.browser.__SS_extdata = closedTab.extData;
@ -869,6 +880,11 @@ SessionStore.prototype = {
// remove closed tab from the array
closedTabs.splice(aIndex, 1);
// Forget the last closed tab index if we're forgetting the last closed tab.
if (aIndex == 0) {
this._lastClosedTabIndex = -1;
}
},
getTabValue: function ss_getTabValue(aTab, aKey) {

View File

@ -64,6 +64,9 @@ MOZ_SERVICES_FXACCOUNTS=1
# Enable Wifi-AP/cell tower data reporting
MOZ_DATA_REPORTING=1
# Enable runtime locale switching.
MOZ_LOCALE_SWITCHER=1
# Enable the "synthetic APKs" implementation of Open Web Apps.
MOZ_ANDROID_SYNTHAPKS=1

View File

@ -169,6 +169,7 @@
<string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
<string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
<string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
<string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
<string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>

View File

@ -7,6 +7,7 @@ import org.json.simple.JSONArray;
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
import org.mozilla.gecko.background.testhelpers.MockClientsDataDelegate;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.repositories.NoContentProviderException;
import org.mozilla.gecko.sync.repositories.RepositorySession;
@ -24,8 +25,9 @@ import android.database.Cursor;
import android.os.RemoteException;
public class TestFennecTabsRepositorySession extends AndroidSyncTestCase {
public static final String TEST_CLIENT_GUID = "test guid"; // Real GUIDs never contain spaces.
public static final String TEST_CLIENT_NAME = "test client name";
public static final MockClientsDataDelegate clientsDataDelegate = new MockClientsDataDelegate();
public static final String TEST_CLIENT_GUID = clientsDataDelegate.getAccountGUID();
public static final String TEST_CLIENT_NAME = clientsDataDelegate.getClientName();
// Override these to test against data that is not live.
public static final String TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS ?";
@ -72,7 +74,7 @@ public class TestFennecTabsRepositorySession extends AndroidSyncTestCase {
* Override this chain in order to avoid our test code having to create two
* sessions all the time.
*/
return new FennecTabsRepository(TEST_CLIENT_NAME, TEST_CLIENT_GUID) {
return new FennecTabsRepository(clientsDataDelegate) {
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
@ -197,8 +199,19 @@ public class TestFennecTabsRepositorySession extends AndroidSyncTestCase {
// Not all tabs are modified after this, but the record should contain them all.
performWait(fetchSinceRunnable(session, 1000, new Record[] { tabsRecord }));
// No tabs are modified after this, so we shouldn't get a record at all.
performWait(fetchSinceRunnable(session, 4000, new Record[] { }));
// No tabs are modified after this, but our client name has changed in the interim.
performWait(fetchSinceRunnable(session, 4000, new Record[] { tabsRecord }));
// No tabs are modified after this, and our client name hasn't changed, so
// we shouldn't get a record at all. Note: this runs after our static
// initializer that sets the client data timestamp.
final long now = System.currentTimeMillis();
performWait(fetchSinceRunnable(session, now, new Record[] { }));
// No tabs are modified after this, but our client name has changed, so
// again we get a record.
clientsDataDelegate.setClientName("new client name", System.currentTimeMillis());
performWait(fetchSinceRunnable(session, now, new Record[] { tabsRecord }));
session.abort();
}

View File

@ -10,6 +10,7 @@ public class MockClientsDataDelegate implements ClientsDataDelegate {
private String accountGUID;
private String clientName;
private int clientsCount;
private long clientDataTimestamp = 0;
@Override
public synchronized String getAccountGUID() {
@ -19,10 +20,21 @@ public class MockClientsDataDelegate implements ClientsDataDelegate {
return accountGUID;
}
@Override
public synchronized String getDefaultClientName() {
return "Default client";
}
@Override
public synchronized void setClientName(String clientName, long now) {
this.clientName = clientName;
this.clientDataTimestamp = now;
}
@Override
public synchronized String getClientName() {
if (clientName == null) {
clientName = "Default Name";
setClientName(getDefaultClientName(), System.currentTimeMillis());
}
return clientName;
}
@ -38,7 +50,12 @@ public class MockClientsDataDelegate implements ClientsDataDelegate {
}
@Override
public boolean isLocalGUID(String guid) {
public synchronized boolean isLocalGUID(String guid) {
return getAccountGUID().equals(guid);
}
}
@Override
public synchronized long getLastModifiedTimestamp() {
return clientDataTimestamp;
}
}

View File

@ -12,7 +12,7 @@
[include:dom/activities/tests/unit/xpcshell.ini]
[include:dom/apps/tests/unit/xpcshell.ini]
[include:dom/encoding/test/unit/xpcshell.ini]
[include:dom/mobilemessage/tests/xpcshell.ini]
[include:dom/mobilemessage/tests/xpcshell/xpcshell.ini]
[include:dom/network/tests/unit/xpcshell.ini]
[include:dom/payment/tests/unit/xpcshell.ini]
[include:dom/permission/tests/unit/xpcshell.ini]

View File

@ -3,7 +3,7 @@
; file, You can obtain one at http://mozilla.org/MPL/2.0/.
[include:dom/apps/tests/unit/xpcshell.ini]
[include:dom/mobilemessage/tests/xpcshell.ini]
[include:dom/mobilemessage/tests/xpcshell/xpcshell.ini]
[include:dom/network/tests/unit_stats/xpcshell.ini]
[include:dom/system/gonk/tests/xpcshell.ini]
[include:dom/wappush/tests/xpcshell.ini]

View File

@ -4143,6 +4143,60 @@
"n_values": 10,
"description": "Track click count on about:newtab tiles per index (0-8). For non-default row or column configurations all clicks into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK0_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #0 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK1_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #1 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK2_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #2 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK3_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #3 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK4_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #4 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK5_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #5 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK6_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #6 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK7_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #7 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_LINK8_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",
"n_values": 10,
"description": "Track impression count of directory link #8 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
},
"NEWTAB_PAGE_DIRECTORY_AFFILIATE_SHOWN": {
"expires_in_version": "35",
"kind": "enumerated",

View File

@ -412,98 +412,4 @@ LayoutHelpers.prototype = {
return [xOffset * scale, yOffset * scale];
},
/********************************************************************
* GetBoxQuads POLYFILL START TODO: Remove this when bug 917755 is fixed.
********************************************************************/
_getBoxQuadsFromRect: function(rect, node) {
let scale = this.calculateScale(node);
let [xOffset, yOffset] = this._getNodeOffsets(node);
let out = {
p1: {
x: rect.left * scale + xOffset,
y: rect.top * scale + yOffset
},
p2: {
x: (rect.left + rect.width) * scale + xOffset,
y: rect.top * scale + yOffset
},
p3: {
x: (rect.left + rect.width) * scale + xOffset,
y: (rect.top + rect.height) * scale + yOffset
},
p4: {
x: rect.left * scale + xOffset,
y: (rect.top + rect.height) * scale + yOffset
}
};
out.bounds = {
bottom: out.p4.y,
height: out.p4.y - out.p1.y,
left: out.p1.x,
right: out.p2.x,
top: out.p1.y,
width: out.p2.x - out.p1.x,
x: out.p1.x,
y: out.p1.y
};
return out;
},
_parseNb: function(distance) {
let nb = parseFloat(distance, 10);
return isNaN(nb) ? 0 : nb;
},
getAdjustedQuadsPolyfill: function(node, region) {
// Get the border-box rect
// Note that this is relative to the node's viewport, so before we can use
// it, will need to go back up the frames like getRect
let borderRect = node.getBoundingClientRect();
// If the boxType is border, no need to go any further, we're done
if (region === "border") {
return this._getBoxQuadsFromRect(borderRect, node);
}
// Else, need to get margin/padding/border distances
let style = node.ownerDocument.defaultView.getComputedStyle(node);
let camel = s => s.substring(0, 1).toUpperCase() + s.substring(1);
let distances = {border:{}, padding:{}, margin: {}};
for (let side of ["top", "right", "bottom", "left"]) {
distances.border[side] = this._parseNb(style["border" + camel(side) + "Width"]);
distances.padding[side] = this._parseNb(style["padding" + camel(side)]);
distances.margin[side] = this._parseNb(style["margin" + camel(side)]);
}
// From the border-box rect, calculate the content-box, padding-box and
// margin-box rects
function offsetRect(rect, offsetType, dir=1) {
return {
top: rect.top + (dir * distances[offsetType].top),
left: rect.left + (dir * distances[offsetType].left),
width: rect.width - (dir * (distances[offsetType].left + distances[offsetType].right)),
height: rect.height - (dir * (distances[offsetType].top + distances[offsetType].bottom))
};
}
if (region === "margin") {
return this._getBoxQuadsFromRect(offsetRect(borderRect, "margin", -1), node);
} else if (region === "padding") {
return this._getBoxQuadsFromRect(offsetRect(borderRect, "border"), node);
} else if (region === "content") {
let paddingRect = offsetRect(borderRect, "border");
return this._getBoxQuadsFromRect(offsetRect(paddingRect, "padding"), node);
}
},
/********************************************************************
* GetBoxQuads POLYFILL END
********************************************************************/
};

View File

@ -398,6 +398,9 @@ BoxModelHighlighter.prototype = {
let pseudoClassesBox = this.chromeDoc.createElementNS(XHTML_NS, "span");
pseudoClassesBox.className = "highlighter-nodeinfobar-pseudo-classes";
let dimensionBox = this.chromeDoc.createElementNS(XHTML_NS, "span");
dimensionBox.className = "highlighter-nodeinfobar-dimensions";
// Add some content to force a better boundingClientRect
pseudoClassesBox.textContent = "&nbsp;";
@ -411,6 +414,7 @@ BoxModelHighlighter.prototype = {
texthbox.appendChild(idLabel);
texthbox.appendChild(classesBox);
texthbox.appendChild(pseudoClassesBox);
texthbox.appendChild(dimensionBox);
nodeInfobar.appendChild(texthbox);
@ -427,6 +431,7 @@ BoxModelHighlighter.prototype = {
idLabel: idLabel,
classesBox: classesBox,
pseudoClassesBox: pseudoClassesBox,
dimensionBox: dimensionBox,
positioner: infobarPositioner,
barHeight: barHeight,
};
@ -582,9 +587,7 @@ BoxModelHighlighter.prototype = {
options.region = options.region || "content";
// TODO: Remove this polyfill
this.rect =
this.layoutHelpers.getAdjustedQuadsPolyfill(this.currentNode, "margin");
this.rect = this.layoutHelpers.getAdjustedQuads(this.currentNode, "margin");
if (!this.rect) {
return null;
@ -592,9 +595,8 @@ BoxModelHighlighter.prototype = {
if (this.rect.bounds.width > 0 && this.rect.bounds.height > 0) {
for (let boxType in this._boxModelNodes) {
// TODO: Remove this polyfill
let {p1, p2, p3, p4} = boxType === "margin" ? this.rect :
this.layoutHelpers.getAdjustedQuadsPolyfill(this.currentNode, boxType);
this.layoutHelpers.getAdjustedQuads(this.currentNode, boxType);
let boxNode = this._boxModelNodes[boxType];
boxNode.setAttribute("points",
@ -705,29 +707,36 @@ BoxModelHighlighter.prototype = {
* Update node information (tagName#id.class)
*/
_updateInfobar: function() {
if (this.currentNode) {
// Tag name
this.nodeInfo.tagNameLabel.textContent = this.currentNode.tagName;
// ID
this.nodeInfo.idLabel.textContent = this.currentNode.id ? "#" + this.currentNode.id : "";
// Classes
let classes = this.nodeInfo.classesBox;
classes.textContent = this.currentNode.classList.length ?
"." + Array.join(this.currentNode.classList, ".") : "";
// Pseudo-classes
let pseudos = PSEUDO_CLASSES.filter(pseudo => {
return DOMUtils.hasPseudoClassLock(this.currentNode, pseudo);
}, this);
let pseudoBox = this.nodeInfo.pseudoClassesBox;
pseudoBox.textContent = pseudos.join("");
this._moveInfobar();
if (!this.currentNode) {
return;
}
// Tag name
this.nodeInfo.tagNameLabel.textContent = this.currentNode.tagName;
// ID
this.nodeInfo.idLabel.textContent = this.currentNode.id ? "#" + this.currentNode.id : "";
// Classes
let classes = this.nodeInfo.classesBox;
classes.textContent = this.currentNode.classList.length ?
"." + Array.join(this.currentNode.classList, ".") : "";
// Pseudo-classes
let pseudos = PSEUDO_CLASSES.filter(pseudo => {
return DOMUtils.hasPseudoClassLock(this.currentNode, pseudo);
}, this);
let pseudoBox = this.nodeInfo.pseudoClassesBox;
pseudoBox.textContent = pseudos.join("");
// Dimensions
let dimensionBox = this.nodeInfo.dimensionBox;
let rect = this.currentNode.getBoundingClientRect();
dimensionBox.textContent = Math.ceil(rect.width) + " x " +
Math.ceil(rect.height);
this._moveInfobar();
},
/**

View File

@ -268,6 +268,7 @@ let DirectoryLinksProvider = {
this._readDirectoryLinksFile().then(rawLinks => {
// all directory links have a frecency of DIRECTORY_FRECENCY
aCallback(rawLinks.map((link, position) => {
link.directoryIndex = position;
link.frecency = DIRECTORY_FRECENCY;
link.lastVisitDate = rawLinks.length - position;
return link;

View File

@ -259,7 +259,7 @@ add_task(function test_linksURL_locale() {
links = yield fetchData();
do_check_eq(links.length, 1);
expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 0}];
isIdentical(links, expected_data);
yield promiseDirectoryDownloadOnPrefChange("general.useragent.locale", "zh-CN");
@ -267,8 +267,8 @@ add_task(function test_linksURL_locale() {
links = yield fetchData();
do_check_eq(links.length, 2)
expected_data = [
{url: "http://example.net", title: "CN", frecency: DIRECTORY_FRECENCY, lastVisitDate: 2},
{url: "http://example.net/2", title: "CN2", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}
{url: "http://example.net", title: "CN", frecency: DIRECTORY_FRECENCY, lastVisitDate: 2, directoryIndex: 0},
{url: "http://example.net/2", title: "CN2", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 1}
];
isIdentical(links, expected_data);
@ -280,7 +280,7 @@ add_task(function test_DirectoryLinksProvider__prefObserver_url() {
let links = yield fetchData();
do_check_eq(links.length, 1);
let expectedData = [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
let expectedData = [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 0}];
isIdentical(links, expectedData);
// tests these 2 things: