author | Ryan VanderMeulen <ryanvm@gmail.com> |
Wed, 22 Jan 2014 15:56:42 -0500 | |
changeset 180819 | ee87d9380ff1d644dd816486b9f1f8b806105b85 |
parent 180818 | f449298d7a2553406f8eb4dc79f23e4ad54e2886 (current diff) |
parent 180734 | cad591993bfa410da4954c2f9e8617818b6a216d (diff) |
child 180820 | 47c7cd88eee195d5e91693bcebe70ec504d4044b |
push id | 3343 |
push user | ffxbld |
push date | Mon, 17 Mar 2014 21:55:32 +0000 |
treeherder | mozilla-beta@2f7d3415f79f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 29.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
accessible/tests/mochitest/jsat/test_braille.html | file | annotate | diff | comparison | revisions | |
accessible/tests/mochitest/jsat/test_utterance_order.html | file | annotate | diff | comparison | revisions | |
browser/metro/base/content/config.js | file | annotate | diff | comparison | revisions | |
browser/metro/base/content/config.xul | file | annotate | diff | comparison | revisions |
--- a/CLOBBER +++ b/CLOBBER @@ -17,10 +17,9 @@ # # Modifying this file will now automatically clobber the buildbot machines \o/ # # Are you updating CLOBBER because you think it's needed for your WebIDL # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -CLOBBER due to recent changes to JS build files that were causing -"STOP! configure has changed and needs to be run in this build directory." bustage +JS build system changes are apparently requiring clobbers.
--- a/accessible/src/jsat/OutputGenerator.jsm +++ b/accessible/src/jsat/OutputGenerator.jsm @@ -673,44 +673,57 @@ this.UtteranceGenerator = { this.BrailleGenerator = { __proto__: OutputGenerator, genForContext: function genForContext(aContext) { let output = OutputGenerator.genForContext.apply(this, arguments); let acc = aContext.accessible; + + // add the static text indicating a list item; do this for both listitems or + // direct first children of listitems, because these are both common browsing + // scenarios + let addListitemIndicator = function addListitemIndicator(indicator = '*') { + output.output.unshift(indicator); + }; + + if (acc.indexInParent === 1 && + acc.parent.role == Roles.LISTITEM && + acc.previousSibling.role == Roles.STATICTEXT) { + if (acc.parent.parent && acc.parent.parent.DOMNode && + acc.parent.parent.DOMNode.nodeName == 'UL') { + addListitemIndicator(); + } else { + addListitemIndicator(acc.previousSibling.name.trim()); + } + } else if (acc.role == Roles.LISTITEM && acc.firstChild && + acc.firstChild.role == Roles.STATICTEXT) { + if (acc.parent.DOMNode.nodeName == 'UL') { + addListitemIndicator(); + } else { + addListitemIndicator(acc.firstChild.name.trim()); + } + } + if (acc instanceof Ci.nsIAccessibleText) { output.endOffset = this.outputOrder === OUTPUT_DESC_FIRST ? output.output.join(' ').length : acc.characterCount; output.startOffset = output.endOffset - acc.characterCount; } return output; }, objectOutputFunctions: { __proto__: OutputGenerator.objectOutputFunctions, defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) { - let braille = this.objectOutputFunctions._generateBaseOutput.apply(this, arguments); - - if (aAccessible.indexInParent === 1 && - aAccessible.parent.role == Roles.LISTITEM && - aAccessible.previousSibling.role == Roles.STATICTEXT) { - if (aAccessible.parent.parent && aAccessible.parent.parent.DOMNode && - aAccessible.parent.parent.DOMNode.nodeName == 'UL') { - braille.unshift('*'); - } else { - braille.unshift(aAccessible.previousSibling.name); - } - } - - return braille; + return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments); }, listitem: function listitem(aAccessible, aRoleStr, aStates, aFlags) { let braille = []; this._addName(braille, aAccessible, aFlags); this._addLandmark(braille, aAccessible);
--- a/accessible/tests/mochitest/jsat/a11y.ini +++ b/accessible/tests/mochitest/jsat/a11y.ini @@ -1,14 +1,13 @@ [DEFAULT] support-files = jsatcommon.js output.js doc_traversal.html [test_alive.html] -[test_braille.html] [test_explicit_names.html] [test_landmarks.html] [test_live_regions.html] +[test_output.html] [test_tables.html] [test_traversal.html] -[test_utterance_order.html]
deleted file mode 100644 --- a/accessible/tests/mochitest/jsat/test_braille.html +++ /dev/null @@ -1,122 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=876475 ---> - <head> - <title>[AccessFu] braille generation test</title> - <meta charset="utf-8"> - <link rel="stylesheet" type="text/css" - href="chrome://mochikit/content/tests/SimpleTest/test.css" /> - <script type="application/javascript" - src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> - <script type="application/javascript" - src="../common.js"></script> - <script type="application/javascript" - src="./output.js"></script> - <script type="application/javascript"> - - function doTest() { - // Test the following accOrElmOrID (with optional old accOrElmOrID). - // Note: each accOrElmOrID entry maps to a unique object braille - // generator function within the BrailleGenerator. - var tests = [{ - accOrElmOrID: "link", - expected: [["lnk", "Link"], ["Link", "lnk"]] - },{ - accOrElmOrID: "button", - expected: [["btn", "I am a button"], ["I am a button", "btn"]] - },{ - accOrElmOrID: "password_input", - expected: [["passwdtxt", "Secret Password"], ["Secret Password", "passwdtxt"]] - },{ - accOrElmOrID: "checkbox_unchecked", - expected: [["( )", "checkboxtext"], ["checkboxtext", "( )"]] - },{ - accOrElmOrID: "checkbox_checked", - expected: [["(x)", "some more checkbox text"], ["some more checkbox text", "(x)"]] - },{ - accOrElmOrID: "radio_unselected", - expected: [["( )", "any old radio button"], ["any old radio button", "( )"]] - },{ - accOrElmOrID: "radio_selected", - expected: [["(x)", "a unique radio button"], ["a unique radio button", "(x)"]] - },{ - accOrElmOrID: "togglebutton_notpressed", - expected: [["( )", "I ain't pressed"], ["I ain't pressed", "( )"]] - },{ - accOrElmOrID: "togglebutton_pressed", - expected: [["(x)", "I am pressed!"], ["I am pressed!", "(x)"]] - },{ - accOrElmOrID: "ul_li_one", - expected: [["*", "ul item 1"], ["*", "ul item 1"]] - },{ - accOrElmOrID: "ol_li_one", - expected: [["1.", "ol item 1"], ["1.", "ol item 1"]] - },{ - accOrElmOrID: "textarea", - expected: [["txtarea", "Here lies treasure."], ["Here lies treasure.", "txtarea"]] - },{ - accOrElmOrID: "textentry", - expected: [["entry", "Mario", "First name:"], ["First name:", "Mario", "entry"]] - },{ - accOrElmOrID: "range", - expected: [["slider", "3", "Points:"], ["Points:", "3", "slider"]] - }]; - - // Test all possible braille order preference values. - tests.forEach(function run(test) { - var brailleOrderValues = [0, 1]; - brailleOrderValues.forEach( - function testBrailleOrder(brailleOrder) { - SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, brailleOrder); - var expected = test.expected[brailleOrder]; - testOutput(expected, test.accOrElmOrID, test.oldAccOrElmOrID, 2); - } - ); - }); - - // If there was an original utterance order preference, revert to it. - SpecialPowers.clearUserPref(PREF_UTTERANCE_ORDER); - SimpleTest.finish(); - } - - SimpleTest.waitForExplicitFinish(); - addA11yLoadEvent(doTest); - - </script> - </head> - <body> - <div id="root"> - <p id="display"></p> - <div id="content" style="display: none"></div> - <pre id="test"></pre> - <a href="example.com" id="link">Link</a> - <button id="button">I am a button</button> - <label for="password_input">Secret Password</label><input id="password_input" type="password"></input> - <label for="checkbox_unchecked">checkboxtext</label><input id="checkbox_unchecked" type="checkbox"></input> - <label for="checkbox_checked">some more checkbox text</label><input id="checkbox_checked" type="checkbox" checked></input> - <label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input> - <label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input> - <div id="togglebutton_notpressed" aria-pressed="false" role="button" tabindex="-1">I ain't pressed</div> - <div id="togglebutton_pressed" aria-pressed="true" role="button" tabindex="-1">I am pressed!</div> - <ol id="ordered_list"> - <li id="ol_li_one">ol item 1</li> - <li id="ol_li_two">ol item 2</li> - <li id="ol_li_three">ol item 3</li> - <li id="ol_li_three">ol item 4</li> - </ol> - <ul id="unordered_list"> - <li id="ul_li_one">ul item 1</li> - <li id="ul_li_two">ul item 2</li> - <li id="ul_li_three">ul item 3</li> - <li id="ul_li_three">ul item 4</li> - </ul> - <textarea id="textarea" cols="80" rows="5"> - Here lies treasure. - </textarea> - <label>First name: <input id="textentry" value="Mario"></label> - <label>Points: <input id="range" type="range" name="points" min="1" max="10" value="3"></label> - </div> - </body> -</html>
--- a/accessible/tests/mochitest/jsat/test_landmarks.html +++ b/accessible/tests/mochitest/jsat/test_landmarks.html @@ -70,19 +70,17 @@ "another main area", "main"]], expectedBraille: [["main", "another main area"], ["another main area", "main"]] }, { accOrElmOrID: "complementary", expectedUtterance: [["list 1 item", "complementary", "First item", "A complementary"], ["A complementary", "First item", "complementary", "list 1 item"]], - // XXX: The '*' should probably come before all of the context - // utterance. - expectedBraille: [["complementary", "*", "A complementary"], ["*", + expectedBraille: [["*", "complementary", "A complementary"], ["*", "A complementary", "complementary"]] }, { accOrElmOrID: "parent_main", expectedUtterance: [["main", "a parent main", "complementary", "a child complementary"], ["a parent main", "a child complementary", "complementary", "main"]], expectedBraille: [["main", "a parent main", "complementary", "a child complementary"], ["a parent main", "a child complementary", @@ -146,9 +144,9 @@ </li> </ul> <main id="parent_main"> a parent main <p id="child_complementary" role="complementary">a child complementary</article> </main> </div> </body> -</html> \ No newline at end of file +</html>
rename from accessible/tests/mochitest/jsat/test_utterance_order.html rename to accessible/tests/mochitest/jsat/test_output.html --- a/accessible/tests/mochitest/jsat/test_utterance_order.html +++ b/accessible/tests/mochitest/jsat/test_output.html @@ -17,222 +17,346 @@ https://bugzilla.mozilla.org/show_bug.cg <script type="application/javascript"> function doTest() { // Test the following accOrElmOrID (with optional old accOrElmOrID). // Note: each accOrElmOrID entry maps to a unique object utterance // generator function within the UtteranceGenerator. var tests = [{ accOrElmOrID: "anchor", - expected: [["link", "title"], ["title", "link"]] + expectedUtterance: [["link", "title"], ["title", "link"]], + expectedBraille: [["lnk", "title"], ["title", "lnk"]] }, { accOrElmOrID: "anchor_titleandtext", - expected: [[ - "link", "goes to the tests -", "Tests" - ], [ - "Tests", "- goes to the tests", "link" - ]] + expectedUtterance: [["link", "goes to the tests -", "Tests"], + ["Tests", "- goes to the tests", "link"]], + expectedBraille: [["lnk", "goes to the tests -", "Tests"], + ["Tests", "- goes to the tests", "lnk"]], }, { accOrElmOrID: "anchor_duplicatedtitleandtext", - expected: [["link", "Tests"], ["Tests", "link"]] + expectedUtterance: [["link", "Tests"], ["Tests", "link"]], + expectedBraille: [["lnk", "Tests"], ["Tests", "lnk"]] }, { accOrElmOrID: "anchor_arialabelandtext", - expected: [[ - "link", "goes to the tests - Tests" - ], [ - "Tests - goes to the tests", "link" - ]] + expectedUtterance: [["link", "goes to the tests - Tests"], + ["Tests - goes to the tests", "link"]], + expectedBraille: [["lnk", "goes to the tests - Tests"], + ["Tests - goes to the tests", "lnk"]], }, { accOrElmOrID: "textarea", - expected: [[ + expectedUtterance: [[ "text area", "This is the text area text." ], [ "This is the text area text.", "text area" - ]] + ],], + expectedBraille: [[ + "txtarea", "This is the text area text." + ], [ + "This is the text area text.", "txtarea" + ],], }, { accOrElmOrID: "heading", - expected: [ + expectedUtterance: [ ["heading level 1", "Test heading"], ["Test heading", "heading level 1"] + ], + expectedBraille: [ + ["heading", "Test heading"], + ["Test heading", "heading"] ] }, { accOrElmOrID: "list", - expected: [ + expectedUtterance: [ ["list 1 item", "First item", "1.", "list one"], ["1.", "list one", "First item", "list 1 item"] + ], + expectedBraille: [ + ["list", "list one"], + ["list one", "list"] ] }, { accOrElmOrID: "dlist", - expected: [ + expectedUtterance: [ ["definition list 0.5 items", "dd one"], ["dd one", "definition list 0.5 items"] + ], + expectedBraille: [ + ["definition list", "dd one"], + ["dd one", "definition list"] ] }, { accOrElmOrID: "li_one", - expected: [ + expectedUtterance: [ ["list 1 item", "First item", "1.", "list one"], ["1.", "list one", "First item", "list 1 item"] + ], + expectedBraille: [ + ["1.", "list one"], + ["1.", "list one"] ] }, { accOrElmOrID: "cell", - expected: [[ + expectedUtterance: [[ "table with 1 column and 1 row", "Fruits and vegetables", "Column 1 Row 1", "list 4 items", "First item", "link", "Apples", "link", "Bananas", "link", "Peaches", "Last item", "link", "Plums" ], [ "Apples", "link", "First item", "Bananas", "link", "Peaches", "link", "Plums", "link", "Last item", "list 4 items", "Column 1 Row 1", "Fruits and vegetables", "table with 1 column and 1 row" + ]], + expectedBraille: [[ + "c1r1", "list", "lnk", "Apples", "lnk", "Bananas", "lnk", + "Peaches", "lnk", "Plums" + ], [ + "Apples", "lnk", "Bananas", "lnk", "Peaches", "lnk", "Plums", + "lnk", "list", "c1r1" ]] }, { accOrElmOrID: "date", - expected: [["date entry", "2011-09-29"], ["2011-09-29", "date entry"]] + expectedUtterance: [["date entry", "2011-09-29"], ["2011-09-29", "date entry"]], + expectedBraille: [["date entry", "2011-09-29"], ["2011-09-29", "date entry"]] }, { accOrElmOrID: "email", - expected: [ + expectedUtterance: [ + ["e-mail entry", "test@example.com"], + ["test@example.com", "e-mail entry"] + ], + expectedBraille: [ ["e-mail entry", "test@example.com"], ["test@example.com", "e-mail entry"] ] }, { accOrElmOrID: "search", - expected: [ + expectedUtterance: [ + ["search entry", "This is a search"], + ["This is a search", "search entry"] + ], + expectedBraille: [ ["search entry", "This is a search"], ["This is a search", "search entry"] ] }, { accOrElmOrID: "tel", - expected: [ + expectedUtterance: [ + ["telephone entry", "555-5555"], ["555-5555", "telephone entry"] + ], + expectedBraille: [ ["telephone entry", "555-5555"], ["555-5555", "telephone entry"] ] }, { accOrElmOrID: "url", - expected: [ + expectedUtterance: [ + ["URL entry", "http://example.com"], + ["http://example.com", "URL entry"] + ], + expectedBraille: [ ["URL entry", "http://example.com"], ["http://example.com", "URL entry"] ] }, { accOrElmOrID: "textInput", - expected: [["entry", "This is text."], ["This is text.", "entry"]] + expectedUtterance: [["entry", "This is text."], ["This is text.", "entry"]], + expectedBraille: [["entry", "This is text."], ["This is text.", "entry"]] }, { // Test pivot to list from li_one. accOrElmOrID: "list", oldAccOrElmOrID: "li_one", - expected: [ + expectedUtterance: [ ["list 1 item", "First item", "1.", "list one"], ["1.", "list one", "First item", "list 1 item"] + ], + expectedBraille: [ + ["list", "list one"], + ["list one", "list"] ] }, { // Test pivot to "apples" link from the table cell. accOrElmOrID: "apples", oldAccOrElmOrID: "cell", - expected: [ + expectedUtterance: [ ["list 4 items", "First item", "link", "Apples"], ["Apples", "link", "First item", "list 4 items"] + ], + expectedBraille: [ + ["*", "lnk", "Apples"], + ["*", "Apples", "lnk"] ] }, { // Test pivot to 'bananas' link from 'apples' link. accOrElmOrID: "bananas", oldAccOrElmOrID: "apples", - expected: [["link", "Bananas"], ["Bananas", "link"]] + expectedUtterance: [["link", "Bananas"], ["Bananas", "link"]], + expectedBraille: [["*", "lnk", "Bananas"], ["*", "Bananas", "lnk"]] }, { // test unavailable state utterance accOrElmOrID: 'unavailableButton', - expected: [["unavailable button", "I am unavailable"], - ["I am unavailable", "unavailable button"]] + expectedUtterance: [["unavailable button", "I am unavailable"], + ["I am unavailable", "unavailable button"]], + expectedBraille: [["btn", "I am unavailable"], + ["I am unavailable", "btn"]] }, { // test expanded state utterance accOrElmOrID: 'expandedButton', - expected: [["expanded button", "I am expanded"], - ["I am expanded", "expanded button"]] + expectedUtterance: [["expanded button", "I am expanded"], + ["I am expanded", "expanded button"]], + expectedBraille: [["btn", "I am expanded"], + ["I am expanded", "btn"]] }, { // test collapsed state utterance accOrElmOrID: 'collapsedButton', - expected: [["collapsed button", "I am collapsed"], - ["I am collapsed", "collapsed button"]] + expectedUtterance: [["collapsed button", "I am collapsed"], + ["I am collapsed", "collapsed button"]], + expectedBraille: [["btn", "I am collapsed"], + ["I am collapsed", "btn"]] }, { // test required state utterance accOrElmOrID: 'requiredInput', - expected: [["required entry", "I am required"], - ["I am required", "required entry"]] + expectedUtterance: [["required entry", "I am required"], + ["I am required", "required entry"]], + expectedBraille: [["entry", "I am required"], + ["I am required", "entry"]] }, { // test has popup state utterance accOrElmOrID: 'hasPopupButton', - expected: [["has pop up button menu", "I have a popup"], - ["I have a popup", "has pop up button menu"]] + expectedUtterance: [["has pop up button menu", "I have a popup"], + ["I have a popup", "has pop up button menu"]], + expectedBraille: [["button menu", "I have a popup"], + ["I have a popup", "button menu"]] }, { // Test selected tab accOrElmOrID: 'tab1', - expected: [['tab list', 'selected tab 1 of 2', 'Account'], - ['Account', 'selected tab 1 of 2', 'tab list']] + expectedUtterance: [['tab list', 'selected tab 1 of 2', 'Account'], + ['Account', 'selected tab 1 of 2', 'tab list']], + expectedBraille: [['tab 1 of 2', 'Account'], + ['Account', 'tab 1 of 2']] }, { // Test unselected tab accOrElmOrID: 'tab2', - expected: [['tab list', 'tab 2 of 2', 'Advanced'], - ['Advanced', 'tab 2 of 2', 'tab list']] + expectedUtterance: [['tab list', 'tab 2 of 2', 'Advanced'], + ['Advanced', 'tab 2 of 2', 'tab list']], + expectedBraille: [['tab 2 of 2', 'Advanced'], + ['Advanced', 'tab 2 of 2']] }, { // Landing on this label should mimic landing on the checkbox. accOrElmOrID: "label1", - expected: [['not checked check button', 'Orange'], - ['Orange', 'not checked check button']] + expectedUtterance: [['not checked check button', 'Orange'], + ['Orange', 'not checked check button']], + expectedBraille: [['( )', 'Orange'], + ['Orange', '( )']] }, { // Here we get a top-level view of the form. accOrElmOrID: "form1", - expected: [['label', 'not checked check button', 'Orange', 'Orange', + expectedUtterance: [['label', 'not checked check button', 'Orange', 'Orange', 'not checked check button', 'Blue', 'label', 'Blue'], ['Orange', 'not checked check button', 'Orange', 'label', - 'Blue', 'not checked check button', 'Blue', 'label']] + 'Blue', 'not checked check button', 'Blue', 'label']], + expectedBraille: [['label', '( )', 'Orange', 'Orange', + '( )', 'Blue', 'label', 'Blue'], + ['Orange', '( )', 'Orange', 'label', + 'Blue', '( )', 'Blue', 'label']] }, { // This is a non-nesting label. accOrElmOrID: "label2", - expected: [['label', 'Blue'], ['Blue', 'label']] + expectedUtterance: [['label', 'Blue'], ['Blue', 'label']], + expectedBraille: [['label', 'Blue'], ['Blue', 'label']] }, { // This is a distinct control. accOrElmOrID: "input2", - expected: [['not checked check button', 'Blue'], - ['Blue', 'not checked check button']] + expectedUtterance: [['not checked check button', 'Blue'], + ['Blue', 'not checked check button']], + expectedBraille: [['( )', 'Blue'], + ['Blue', '( )']] }, { // This is a nested control. accOrElmOrID: "input1", - expected: [['not checked check button', 'Orange'], - ['Orange', 'not checked check button']] + expectedUtterance: [['not checked check button', 'Orange'], + ['Orange', 'not checked check button']], + expectedBraille: [['( )', 'Orange'], + ['Orange', '( )']] }, { // Landing on this label should mimic landing on the entry. accOrElmOrID: "label3", - expected: [['entry', 'Joe', 'First name:'], + expectedUtterance: [['entry', 'Joe', 'First name:'], + ['First name:', 'Joe', 'entry']], + expectedBraille: [['entry', 'Joe', 'First name:'], ['First name:', 'Joe', 'entry']] }, { // This is a nested control with a value. accOrElmOrID: "input3", - expected: [['entry', 'Joe', 'First name:'], + expectedUtterance: [['entry', 'Joe', 'First name:'], + ['First name:', 'Joe', 'entry']], + expectedBraille: [['entry', 'Joe', 'First name:'], ['First name:', 'Joe', 'entry']] }, { // This is a nested control with a value. accOrElmOrID: "input4", - expected: [['slider', '3', 'Points:'], + expectedUtterance: [['slider', '3', 'Points:'], + ['Points:', '3', 'slider']], + expectedBraille: [['slider', '3', 'Points:'], ['Points:', '3', 'slider']] - }]; + },{ + accOrElmOrID: "password", + expectedUtterance: [["password text", "Secret Password"], + ["Secret Password", "password text"]], + expectedBraille: [["passwdtxt", "Secret Password"], + ["Secret Password", "passwdtxt"]] + },{ + accOrElmOrID: "input5", + expectedUtterance: [["checked check button", "Boring label"], + ["Boring label", "checked check button"]], + expectedBraille: [["(x)", "Boring label"], + ["Boring label", "(x)"]] + },{ + accOrElmOrID: "radio_unselected", + expectedUtterance: [["not checked radio button", "any old radio button"], + ["any old radio button", "not checked radio button"]], + expectedBraille: [["( )", "any old radio button"], + ["any old radio button", "( )"]] + },{ + accOrElmOrID: "radio_selected", + expectedUtterance: [["checked radio button", "a unique radio button"], + ["a unique radio button", "checked radio button"]], + expectedBraille: [["(x)", "a unique radio button"], + ["a unique radio button", "(x)"]] + },{ + accOrElmOrID: "togglebutton_notpressed", + expectedUtterance: [["not checked toggle button", "I ain't pressed"], + ["I ain't pressed", "not checked toggle button"]], + expectedBraille: [["( )", "I ain't pressed"], + ["I ain't pressed", "( )"]] + },{ + accOrElmOrID: "togglebutton_pressed", + expectedUtterance: [["not checked toggle button", "I am pressed!"], + ["I am pressed!", "not checked toggle button"]], + expectedBraille: [["(x)", "I am pressed!"], + ["I am pressed!", "(x)"]] + } + ]; // Test all possible utterance order preference values. tests.forEach(function run(test) { var utteranceOrderValues = [0, 1]; utteranceOrderValues.forEach( function testUtteranceOrder(utteranceOrder) { SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, utteranceOrder); - var expected = test.expected[utteranceOrder]; - testOutput(expected, test.accOrElmOrID, test.oldAccOrElmOrID, 1); + testOutput(test.expectedUtterance[utteranceOrder], + test.accOrElmOrID, test.oldAccOrElmOrID, 1); + testOutput(test.expectedBraille[utteranceOrder], + test.accOrElmOrID, test.oldAccOrElmOrID, 0); } ); }); // If there was an original utterance order preference, revert to it. SpecialPowers.clearUserPref(PREF_UTTERANCE_ORDER); SimpleTest.finish(); } @@ -247,16 +371,28 @@ https://bugzilla.mozilla.org/show_bug.cg <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=753984" title="[AccessFu] utterance order test"> Mozilla Bug 753984</a> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=758675" title="[AccessFu] Add support for accDescription"> Mozilla Bug 758675</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=876475" + title="[AccessFu] Make braille output less verbose"> + Mozilla Bug 876475</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=924284" + title="[AccessFu] Output accessible values"> + Mozilla Bug 924284</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=925845" + title="[AccessFu] Unify output tests"> + Mozilla Bug 925845</a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"></pre> <a id="anchor" href="#test" title="title"></a> <a id="anchor_titleandtext" href="#test" title="goes to the tests">Tests</a> <a id="anchor_duplicatedtitleandtext" href="#test" title="Tests">Tests</a> <a id="anchor_arialabelandtext" href="#test" aria-label="Tests" title="goes to the tests">Tests</a> <textarea id="textarea" cols="80" rows="5"> @@ -300,17 +436,24 @@ https://bugzilla.mozilla.org/show_bug.cg <form id="form1"> <label id="label1"><input id="input1" type="checkbox">Orange</label> <input id="input2" type="checkbox"><label id="label2" for="input2">Blue</label> </form> <label id="label3">First name: <input id="input3" value="Joe"></label> <label id="label4">Points: <input id="input4" type="range" name="points" min="1" max="10" value="3"> </label> + <label for="input5">Boring label</label><input id="input5" type="checkbox" checked></input> + <label for="password">Secret Password</label><input id="password" type="password"></input> + <label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input> + <label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input> <input id="date" type="date" value="2011-09-29" /> <input id="email" type="email" value="test@example.com" /> <input id="search" type="search" value="This is a search" /> <input id="tel" type="tel" value="555-5555" /> <input id="url" type="url" value="http://example.com" /> <input id="textInput" type="text" value="This is text." /> + <label>Points: <input id="range" type="range" name="points" min="1" max="10" value="3"></label> + <div id="togglebutton_notpressed" aria-pressed="false" role="button" tabindex="-1">I ain't pressed</div> + <div id="togglebutton_pressed" aria-pressed="true" role="button" tabindex="-1">I am pressed!</div> </div> </body> </html>
--- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -522,16 +522,18 @@ @BINPATH@/components/HealthReportService.js #endif #ifdef MOZ_CAPTIVEDETECT @BINPATH@/components/CaptivePortalDetectComponents.manifest @BINPATH@/components/captivedetect.js #endif @BINPATH@/components/TelemetryPing.js @BINPATH@/components/TelemetryPing.manifest +@BINPATH@/components/TelemetryStartup.js +@BINPATH@/components/TelemetryStartup.manifest @BINPATH@/components/Webapps.js @BINPATH@/components/Webapps.manifest @BINPATH@/components/AppsService.js @BINPATH@/components/AppsService.manifest @BINPATH@/components/Push.js @BINPATH@/components/Push.manifest @BINPATH@/components/PushServiceLauncher.js
--- a/browser/base/content/browser-customization.js +++ b/browser/base/content/browser-customization.js @@ -9,16 +9,19 @@ * events. */ let CustomizationHandler = { handleEvent: function(aEvent) { switch(aEvent.type) { case "customizationstarting": this._customizationStarting(); break; + case "customizationchange": + this._customizationChange(); + break; case "customizationending": this._customizationEnding(aEvent.detail); break; } }, isCustomizing: function() { return document.documentElement.hasAttribute("customizing"); @@ -48,16 +51,22 @@ let CustomizationHandler = { // can cause the customize tab to get clipped. let tabContainer = gBrowser.tabContainer; if (tabContainer.getAttribute("overflow") == "true") { let tabstrip = tabContainer.mTabstrip; tabstrip.ensureElementIsVisible(gBrowser.selectedTab, true); } }, + _customizationChange: function() { + gHomeButton.updatePersonalToolbarStyle(); + BookmarkingUI.customizeChange(); + PlacesToolbarHelper.customizeChange(); + }, + _customizationEnding: function(aDetails) { // Update global UI elements that may have been added or removed if (aDetails.changed) { gURLBar = document.getElementById("urlbar"); gProxyFavIcon = document.getElementById("page-proxy-favicon"); gHomeButton.updateTooltip(); gIdentityHandler._cacheElements();
--- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -858,16 +858,20 @@ var PlacesMenuDNDHandler = { */ let PlacesToolbarHelper = { _place: "place:folder=TOOLBAR", get _viewElt() { return document.getElementById("PlacesToolbar"); }, + get _placeholder() { + return document.getElementById("bookmarks-toolbar-placeholder"); + }, + init: function PTH_init(forceToolbarOverflowCheck) { let viewElt = this._viewElt; if (!viewElt || viewElt._placesView) return; // If the bookmarks toolbar item is: // - not in a toolbar, or; // - the toolbar is collapsed, or; @@ -878,33 +882,59 @@ let PlacesToolbarHelper = { if (!toolbar || toolbar.collapsed || this._isCustomizing || getComputedStyle(toolbar, "").display == "none") return; new PlacesToolbar(this._place); if (forceToolbarOverflowCheck) { viewElt._placesView.updateOverflowStatus(); } + this.customizeChange(); }, customizeStart: function PTH_customizeStart() { try { let viewElt = this._viewElt; if (viewElt && viewElt._placesView) viewElt._placesView.uninit(); } finally { this._isCustomizing = true; } + this._shouldWrap = this._getShouldWrap(); + }, + + customizeChange: function PTH_customizeChange() { + let placeholder = this._placeholder; + if (!placeholder) { + return; + } + let shouldWrapNow = this._getShouldWrap(); + if (this._shouldWrap != shouldWrapNow) { + if (shouldWrapNow) { + placeholder.setAttribute("wrap", "true"); + } else { + placeholder.removeAttribute("wrap"); + } + placeholder.classList.toggle("toolbarbutton-1", shouldWrapNow); + this._shouldWrap = shouldWrapNow; + } }, customizeDone: function PTH_customizeDone() { this._isCustomizing = false; this.init(true); }, + _getShouldWrap: function PTH_getShouldWrap() { + let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks"); + let area = placement && placement.area; + let areaType = area && CustomizableUI.getAreaType(area); + return !area || CustomizableUI.TYPE_MENU_PANEL == areaType; + }, + onPlaceholderCommand: function () { let widgetGroup = CustomizableUI.getWidget("personal-bookmarks"); let widget = widgetGroup.forWindow(window); if (widget.overflowed || widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) { PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar"); } }, @@ -1077,18 +1107,24 @@ let BookmarkingUI = { this._updateToolbarStyle(); }, customizeDone: function BUI_customizeDone() { this.onToolbarVisibilityChange(); this._updateToolbarStyle(); }, + init: function() { + CustomizableUI.addListener(this); + }, + _hasBookmarksObserver: false, uninit: function BUI_uninit() { + CustomizableUI.removeListener(this); + this._uninitView(); if (this._hasBookmarksObserver) { PlacesUtils.removeLazyBookmarkObserver(this); } if (this._pendingStmt) { this._pendingStmt.cancel(); @@ -1274,12 +1310,47 @@ let BookmarkingUI = { }, onBeginUpdateBatch: function () {}, onEndUpdateBatch: function () {}, onBeforeItemRemoved: function () {}, onItemVisited: function () {}, onItemMoved: function () {}, + // CustomizableUI events: + _starButtonLabel: null, + _starButtonOverflowedLabel: null, + onWidgetOverflow: function(aNode, aContainer) { + let win = aNode.ownerDocument.defaultView; + if (aNode.id != "bookmarks-menu-button" || win != window) + return; + + if (!this._starButtonOverflowedLabel) { + this._starButtonOverflowedLabel = gNavigatorBundle.getString( + "starButtonOverflowed.label"); + } + + let currentLabel = aNode.getAttribute("label"); + if (!this._starButtonLabel) + this._starButtonLabel = currentLabel; + + if (currentLabel == this._starButtonLabel) + aNode.setAttribute("label", this._starButtonOverflowedLabel); + }, + + onWidgetUnderflow: function(aNode, aContainer) { + let win = aNode.ownerDocument.defaultView; + if (aNode.id != "bookmarks-menu-button" || win != window) + return; + + // If the button hasn't been in the overflow panel before, we may ignore + // this event. + if (!this._starButtonOverflowedLabel || !this._starButtonLabel) + return; + + if (aNode.getAttribute("label") == this._starButtonOverflowedLabel) + aNode.setAttribute("label", this._starButtonLabel); + }, + QueryInterface: XPCOMUtils.generateQI([ Ci.nsINavBookmarkObserver ]) };
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -742,16 +742,24 @@ toolbarbutton[type="socialmark"] > .tool max-width: 16px; max-height: 16px; } toolbarpaletteitem[place="palette"] > toolbarbutton[type="badged"] > .toolbarbutton-badge-container > .toolbarbutton-icon { max-width: 32px; max-height: 32px; } +toolbarbutton[sdk-button="true"] > .toolbarbutton-icon { + max-height: 32px; +} + +toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon { + max-height: 18px; +} + panelview > .social-panel-frame { width: auto; height: auto; } /* Note the chatbox 'width' values are duplicated in socialchat.xml */ chatbox { -moz-binding: url("chrome://browser/content/socialchat.xml#chatbox");
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1039,29 +1039,27 @@ var gBrowserInit = { sidebar.setAttribute("src", sidebarBox.getAttribute("src")); } UpdateUrlbarSearchSplitterState(); if (!isLoadingBlank || !focusAndSelectUrlBar()) gBrowser.selectedBrowser.focus(); - gNavToolbox.customizeDone = BrowserToolboxCustomizeDone; - gNavToolbox.customizeChange = BrowserToolboxCustomizeChange; - // Set up Sanitize Item this._initializeSanitizer(); // Enable/Disable auto-hide tabbar gBrowser.tabContainer.updateVisibility(); + BookmarkingUI.init(); + gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false); var homeButton = document.getElementById("home-button"); - gHomeButton.init(); gHomeButton.updateTooltip(homeButton); gHomeButton.updatePersonalToolbarStyle(homeButton); // BiDi UI gBidiUI = isBidiEnabled(); if (gBidiUI) { document.getElementById("documentDirection-separator").hidden = false; document.getElementById("documentDirection-swap").hidden = false; @@ -1146,16 +1144,17 @@ var gBrowserInit = { // Add Devtools menuitems and listeners gDevToolsBrowser.registerBrowserWindow(window); window.addEventListener("mousemove", MousePosTracker, false); window.addEventListener("dragover", MousePosTracker, false); gNavToolbox.addEventListener("customizationstarting", CustomizationHandler); + gNavToolbox.addEventListener("customizationchange", CustomizationHandler); gNavToolbox.addEventListener("customizationending", CustomizationHandler); // End startup crash tracking after a delay to catch crashes while restoring // tabs and to postpone saving the pref to disk. try { const startupCrashEndDelay = 30 * 1000; setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay); } catch (ex) { @@ -1243,17 +1242,16 @@ var gBrowserInit = { try { gBrowser.removeProgressListener(window.XULBrowserWindow); gBrowser.removeTabsProgressListener(window.TabsProgressListener); } catch (ex) { } BookmarkingUI.uninit(); - gHomeButton.uninit(); TabsInTitlebar.uninit(); var enumerator = Services.wm.getEnumerator(null); enumerator.getNext(); if (!enumerator.hasMoreElements()) { document.persist("sidebar-box", "sidebarcommand"); document.persist("sidebar-box", "width"); @@ -3273,30 +3271,21 @@ function OpenBrowserWindow(options) else // forget about the charset information. { win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs); } return win; } -//XXXunf Are these still useful to keep around? +// Only here for backwards compat, we should remove this soon function BrowserCustomizeToolbar() { gCustomizeMode.enter(); } -function BrowserToolboxCustomizeDone(aToolboxChanged) { - gCustomizeMode.exit(aToolboxChanged); -} - -function BrowserToolboxCustomizeChange(aType) { - gHomeButton.updatePersonalToolbarStyle(); - BookmarksMenuButton.customizeChange(); -} - /** * Update the global flag that tracks whether or not any edit UI (the Edit menu, * edit-related items in the context menu, and edit-related toolbar buttons * is visible, then update the edit commands' enabled state accordingly. We use * this flag to skip updating the edit commands on focus or selection changes * when no UI is visible to improve performance (including pageload performance, * since focus changes when you load a new page). * @@ -4757,26 +4746,16 @@ function fireSidebarFocusedEvent() { var sidebar = document.getElementById("sidebar"); var event = document.createEvent("Events"); event.initEvent("SidebarFocused", true, false); sidebar.contentWindow.dispatchEvent(event); } var gHomeButton = { - init: function() { - gNavToolbox.addEventListener("customizationchange", - this.onCustomizationChange); - }, - - uninit: function() { - gNavToolbox.removeEventListener("customizationchange", - this.onCustomizationChange); - }, - prefDomain: "browser.startup.homepage", observe: function (aSubject, aTopic, aPrefName) { if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain) return; this.updateTooltip(); }, @@ -4819,20 +4798,16 @@ var gHomeButton = { if (!homeButton) homeButton = document.getElementById("home-button"); if (homeButton) homeButton.className = homeButton.parentNode.id == "PersonalToolbar" || homeButton.parentNode.parentNode.id == "PersonalToolbar" ? homeButton.className.replace("toolbarbutton-1", "bookmark-item") : homeButton.className.replace("bookmark-item", "toolbarbutton-1"); }, - - onCustomizationChange: function(aEvent) { - gHomeButton.updatePersonalToolbarStyle(); - }, }; /** * Gets the selected text in the active browser. Leading and trailing * whitespace is removed, and consecutive whitespace is replaced by a single * space. A maximum of 150 characters will be returned, regardless of the value * of aCharLen. *
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -853,17 +853,16 @@ collapsed="true" customizable="true"> <toolbaritem id="personal-bookmarks" flex="1" title="&bookmarksToolbarItem.label;" cui-areatype="toolbar" removable="true"> <toolbarbutton id="bookmarks-toolbar-placeholder" - type="wrap" mousethrough="never" label="&bookmarksToolbarItem.label;" oncommand="PlacesToolbarHelper.onPlaceholderCommand();"/> <hbox flex="1" id="PlacesToolbar" context="placesContext" onclick="BookmarksEventHandler.onClick(event, this._placesView);" oncommand="BookmarksEventHandler.onCommand(event, this._placesView);"
--- a/browser/components/customizableui/content/panelUI.inc.xul +++ b/browser/components/customizableui/content/panelUI.inc.xul @@ -6,28 +6,30 @@ role="group" type="arrow" hidden="true" flip="slide" noautofocus="true"> <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView"> <panelview id="PanelUI-mainView" context="customizationPanelContextMenu"> <vbox id="PanelUI-contents-scroller"> - <vbox id="PanelUI-contents"/> + <vbox id="PanelUI-contents" class="panelUI-grid"/> </vbox> <footer id="PanelUI-footer"> <!-- The parentNode is used so that the footer is presented as the anchor instead of just the button being the anchor. --> <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;" exitLabel="&appMenuCustomizeExit.label;" tabindex="0" oncommand="gCustomizeMode.toggle();"/> + <toolbarseparator/> <toolbarbutton id="PanelUI-help" label="&helpMenu.label;" tabindex="0" tooltiptext="&helpMenu.label;" oncommand="PanelUI.showHelpView(this.parentNode);"/> + <toolbarseparator/> <toolbarbutton id="PanelUI-quit" tabindex="0" #ifdef XP_WIN label="&quitApplicationCmdWin.label;" tooltiptext="&quitApplicationCmdWin.label;" #else label="&quitApplicationCmd.label;" tooltiptext="&quitApplicationCmd.label;" #endif @@ -173,11 +175,11 @@ </panel> <panel id="widget-overflow" role="group" type="arrow" level="top" hidden="true"> <vbox id="widget-overflow-scroller"> - <vbox id="widget-overflow-list"/> + <vbox id="widget-overflow-list" class="widget-overflow-list"/> </vbox> </panel>
--- a/browser/components/customizableui/content/panelUI.js +++ b/browser/components/customizableui/content/panelUI.js @@ -202,16 +202,18 @@ const PanelUI = { * * @return a Promise that resolves once the panel is ready to roll. */ ensureReady: function(aCustomizing=false) { if (this._readyPromise) { return this._readyPromise; } this._readyPromise = Task.spawn(function() { + this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang", + getLocale()); if (!this._scrollWidth) { // In order to properly center the contents of the panel, while ensuring // that we have enough space on either side to show a scrollbar, we have to // do a bit of hackery. In particular, we calculate a new width for the // scroller, based on the system scrollbar width. this._scrollWidth = (yield ScrollbarSampler.getSystemScrollbarWidth()) + "px"; let cstyle = window.getComputedStyle(this.scroller); @@ -413,8 +415,30 @@ const PanelUI = { this.addEventListener("command", PanelUI.onCommandHandler); }, _onHelpViewHide: function(aEvent) { this.removeEventListener("command", PanelUI.onCommandHandler); } }; + +/** + * Gets the currently selected locale for display. + * @return the selected locale or "en-US" if none is selected + */ +function getLocale() { + try { + let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, + Ci.nsIPrefLocalizedString); + if (locale) + return locale; + } + catch (e) { } + + try { + return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); + } + catch (e) { } + + return "en-US"; +} +
--- a/browser/components/customizableui/src/CustomizableUI.jsm +++ b/browser/components/customizableui/src/CustomizableUI.jsm @@ -474,19 +474,17 @@ let CustomizableUIInternal = { if (!widget.showInPrivateBrowsing && inPrivateWindow) { continue; } } this.ensureButtonContextMenu(node, aAreaNode); if (node.localName == "toolbarbutton" && aArea == CustomizableUI.AREA_PANEL) { node.setAttribute("tabindex", "0"); - if (!node.hasAttribute("type")) { - node.setAttribute("type", "wrap"); - } + node.setAttribute("wrap", "true"); } this.insertWidgetBefore(node, currentNode, container, aArea); if (gResetting) { this.notifyListeners("onWidgetReset", node, container); } } @@ -653,19 +651,17 @@ let CustomizableUIInternal = { if (child.localName != "toolbarbutton") { if (child.localName == "toolbaritem") { this.ensureButtonContextMenu(child, aPanel); } continue; } this.ensureButtonContextMenu(child, aPanel); child.setAttribute("tabindex", "0"); - if (!child.hasAttribute("type")) { - child.setAttribute("type", "wrap"); - } + child.setAttribute("wrap", "true"); } this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanel); }, onWidgetAdded: function(aWidgetId, aArea, aPosition) { this.insertNode(aWidgetId, aArea, aPosition, true); }, @@ -704,19 +700,17 @@ let CustomizableUIInternal = { // We remove location attributes here to make sure they're gone too when a // widget is removed from a toolbar to the palette. See bug 930950. this.removeLocationAttributes(widgetNode); if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) { container.removeChild(widgetNode); } else { widgetNode.removeAttribute("tabindex"); - if (widgetNode.getAttribute("type") == "wrap") { - widgetNode.removeAttribute("type"); - } + widgetNode.removeAttribute("wrap"); areaNode.toolbox.palette.appendChild(widgetNode); } this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true); if (isToolbar) { areaNode.setAttribute("currentset", gPlacements.get(aArea).join(',')); } @@ -856,19 +850,17 @@ let CustomizableUIInternal = { return; } let areaId = aAreaNode.id; if (isNew) { this.ensureButtonContextMenu(widgetNode, aAreaNode); if (widgetNode.localName == "toolbarbutton" && areaId == CustomizableUI.AREA_PANEL) { widgetNode.setAttribute("tabindex", "0"); - if (!widgetNode.hasAttribute("type")) { - widgetNode.setAttribute("type", "wrap"); - } + widgetNode.setAttribute("wrap", "true"); } } let container = aAreaNode.customizationTarget; let [insertionContainer, nextNode] = this.findInsertionPoints(widgetNode, aNextNodeId, aAreaNode); this.insertWidgetBefore(widgetNode, nextNode, insertionContainer, areaId); if (gAreas.get(areaId).get("type") == CustomizableUI.TYPE_TOOLBAR) { @@ -1195,29 +1187,102 @@ let CustomizableUIInternal = { /* * If people put things in the panel which need more than single-click interaction, * we don't want to close it. Right now we check for text inputs and menu buttons. * We also check for being outside of any toolbaritem/toolbarbutton, ie on a blank * part of the menu. */ _isOnInteractiveElement: function(aEvent) { + function getMenuPopupForDescendant(aNode) { + let lastPopup = null; + while (aNode && aNode.parentNode && + aNode.parentNode.localName.startsWith("menu")) { + lastPopup = aNode.localName == "menupopup" ? aNode : lastPopup; + aNode = aNode.parentNode; + } + return lastPopup; + } + let target = aEvent.originalTarget; let panel = this._getPanelForNode(aEvent.currentTarget); + // We keep track of: + // whether we're in an input container (text field) let inInput = false; + // whether we're in a popup/context menu let inMenu = false; + // whether we're in a toolbarbutton/toolbaritem let inItem = false; - while (!inInput && !inMenu && !inItem && target != panel) { + // whether the current menuitem has a valid closemenu attribute + let menuitemCloseMenu = "auto"; + // whether the toolbarbutton/item has a valid closemenu attribute. + let closemenu = "auto"; + + // While keeping track of that, we go from the original target back up, + // to the panel if we have to. We bail as soon as we find an input, + // a toolbarbutton/item, or the panel: + while (true) { let tagName = target.localName; - inInput = tagName == "input"; - inMenu = target.type == "menu"; + inInput = tagName == "input" || tagName == "textbox"; inItem = tagName == "toolbaritem" || tagName == "toolbarbutton"; - target = target.parentNode; + let isMenuItem = tagName == "menuitem"; + inMenu = inMenu || isMenuItem; + if (inItem && target.hasAttribute("closemenu")) { + let closemenuVal = target.getAttribute("closemenu"); + closemenu = (closemenuVal == "single" || closemenuVal == "none") ? + closemenuVal : "auto"; + } + + if (isMenuItem && target.hasAttribute("closemenu")) { + let closemenuVal = target.getAttribute("closemenu"); + menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ? + closemenuVal : "auto"; + } + // This isn't in the loop condition because we want to break before + // changing |target| if any of these conditions are true + if (inInput || inItem || target == panel) { + break; + } + // We need specific code for popups: the item on which they were invoked + // isn't necessarily in their parentNode chain: + if (isMenuItem) { + let topmostMenuPopup = getMenuPopupForDescendant(target); + target = (topmostMenuPopup && topmostMenuPopup.triggerNode) || + target.parentNode; + } else { + target = target.parentNode; + } } - return inMenu || inInput || !inItem; + // If the user clicked a menu item... + if (inMenu) { + // We care if we're in an input also, + // or if the user specified closemenu!="auto": + if (inInput || menuitemCloseMenu != "auto") { + return true; + } + // Otherwise, we're probably fine to close the panel + return false; + } + // If we're not in a menu, and we *are* in a type="menu" toolbarbutton, + // we'll now interact with the menu + if (inItem && target.getAttribute("type") == "menu") { + return true; + } + // If we're not in a menu, and we *are* in a type="menu-button" toolbarbutton, + // it depends whether we're in the dropmarker or the 'real' button: + if (inItem && target.getAttribute("type") == "menu-button") { + // 'real' button (which has a single action): + if (target.getAttribute("anonid") == "button") { + return closemenu != "none"; + } + // otherwise, this is the outer button, and the user will now + // interact with the menu: + return true; + } + return inInput || !inItem; }, hidePanelForNode: function(aNode) { let panel = this._getPanelForNode(aNode); if (panel) { panel.hidePopup(); } }, @@ -2223,16 +2288,23 @@ this.CustomizableUI = { * different area. aArea will be the area the item is dragged to, or * undefined after the measurements have been done and the node has been * moved back to its 'regular' area. * * - onCustomizeStart(aWindow) * Fired when opening customize mode in aWindow. * - onCustomizeEnd(aWindow) * Fired when exiting customize mode in aWindow. + * + * - onWidgetOverflow(aNode, aContainer) + * Fired when a widget's DOM node is overflowing its container, a toolbar, + * and will be displayed in the overflow panel. + * - onWidgetUnderflow(aNode, aContainer) + * Fired when a widget's DOM node is *not* overflowing its container, a + * toolbar, anymore. */ addListener: function(aListener) { CustomizableUIInternal.addListener(aListener); }, /** * Remove a listener added with addListener * @param aListener the listener object to remove */ @@ -3230,16 +3302,17 @@ OverflowableToolbar.prototype = { while (child && this._target.scrollLeftMax > 0) { let prevChild = child.previousSibling; if (child.getAttribute("overflows") != "false") { this._collapsed.set(child.id, this._target.clientWidth); child.classList.add("overflowedItem"); child.setAttribute("cui-anchorid", this._chevron.id); + CustomizableUIInternal.notifyListeners("onWidgetOverflow", child, this._target); this._list.insertBefore(child, this._list.firstChild); if (!this._toolbar.hasAttribute("overflowing")) { CustomizableUI.addListener(this); } this._toolbar.setAttribute("overflowing", "true"); } child = prevChild; @@ -3286,16 +3359,17 @@ OverflowableToolbar.prototype = { break; } } if (!inserted) { this._target.appendChild(child); } child.removeAttribute("cui-anchorid"); child.classList.remove("overflowedItem"); + CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target); } let win = this._target.ownerDocument.defaultView; win.UpdateUrlbarSearchSplitterState(); if (!this._collapsed.size) { this._toolbar.removeAttribute("overflowing"); CustomizableUI.removeListener(this); @@ -3360,16 +3434,17 @@ OverflowableToolbar.prototype = { if (nowOverflowed) { // NB: we're guaranteed that it has a previousSibling, because if it didn't, // we would have added it to the toolbar instead. See getOverflowedNextNode. let prevId = aNode.previousSibling.id; let minSize = this._collapsed.get(prevId); this._collapsed.set(aNode.id, minSize); aNode.setAttribute("cui-anchorid", this._chevron.id); aNode.classList.add("overflowedItem"); + CustomizableUIInternal.notifyListeners("onWidgetOverflow", aNode, this._target); } // If it is not overflowed and not in the toolbar, and was not overflowed // either, it moved out of the toolbar. That means there's now space in there! // Let's try to move stuff back: else if (!nowInBar) { this._moveItemsBackToTheirOrigin(true); } // If it's in the toolbar now, then we don't care. An overflow event may @@ -3377,16 +3452,17 @@ OverflowableToolbar.prototype = { } // If it used to be overflowed... else { // ... and isn't anymore, let's remove our bookkeeping: if (!nowOverflowed) { this._collapsed.delete(aNode.id); aNode.removeAttribute("cui-anchorid"); aNode.classList.remove("overflowedItem"); + CustomizableUIInternal.notifyListeners("onWidgetUnderflow", aNode, this._target); if (!this._collapsed.size) { this._toolbar.removeAttribute("overflowing"); CustomizableUI.removeListener(this); } } // but if it still is, it must have changed places. Bookkeep: else {
--- a/browser/components/customizableui/test/browser.ini +++ b/browser/components/customizableui/test/browser.ini @@ -46,16 +46,17 @@ skip-if = os == "mac" [browser_946320_tabs_from_other_computers.js] skip-if = os == "linux" [browser_934951_zoom_in_toolbar.js] [browser_938980_navbar_collapsed.js] [browser_938995_indefaultstate_nonremovable.js] [browser_940013_registerToolbarNode_calls_registerArea.js] [browser_940107_home_button_in_bookmarks_toolbar.js] +[browser_940307_panel_click_closure_handling.js] [browser_940946_removable_from_navbar_customizemode.js] [browser_941083_invalidate_wrapper_cache_createWidget.js] [browser_942581_unregisterArea_keeps_placements.js] [browser_943683_migration_test.js] [browser_944887_destroyWidget_should_destroy_in_palette.js] [browser_945739_showInPrivateBrowsing_customize_mode.js] [browser_947987_removable_default.js] [browser_948985_non_removable_defaultArea.js]
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js @@ -0,0 +1,108 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +let button, menuButton; +/* Clicking a button should close the panel */ +add_task(function() { + button = document.createElement("toolbarbutton"); + button.id = "browser_940307_button"; + button.setAttribute("label", "Button"); + PanelUI.contents.appendChild(button); + yield PanelUI.show(); + let hiddenAgain = promisePanelHidden(window); + EventUtils.synthesizeMouseAtCenter(button, {}); + yield hiddenAgain; + button.remove(); +}); + +/* Clicking a menu button should close the panel, opening the popup shouldn't. */ +add_task(function() { + menuButton = document.createElement("toolbarbutton"); + menuButton.setAttribute("type", "menu-button"); + menuButton.id = "browser_940307_menubutton"; + menuButton.setAttribute("label", "Menu button"); + + let menuPopup = document.createElement("menupopup"); + menuPopup.id = "browser_940307_menupopup"; + + let menuItem = document.createElement("menuitem"); + menuItem.setAttribute("label", "Menu item"); + menuItem.id = "browser_940307_menuitem"; + + menuPopup.appendChild(menuItem); + menuButton.appendChild(menuPopup); + PanelUI.contents.appendChild(menuButton); + + yield PanelUI.show(); + let hiddenAgain = promisePanelHidden(window); + let innerButton = document.getAnonymousElementByAttribute(menuButton, "anonid", "button"); + EventUtils.synthesizeMouseAtCenter(innerButton, {}); + yield hiddenAgain; + + // Now click the dropmarker to show the menu + yield PanelUI.show(); + hiddenAgain = promisePanelHidden(window); + let menuShown = promisePanelElementShown(window, menuPopup); + let dropmarker = document.getAnonymousElementByAttribute(menuButton, "type", "menu-button"); + EventUtils.synthesizeMouseAtCenter(dropmarker, {}); + yield menuShown; + // Panel should stay open: + ok(isPanelUIOpen(), "Panel should still be open"); + let menuHidden = promisePanelElementHidden(window, menuPopup); + // Then click the menu item to close all the things + EventUtils.synthesizeMouseAtCenter(menuItem, {}); + yield menuHidden; + yield hiddenAgain; + menuButton.remove(); +}); + +add_task(function() { + let searchbar = document.getElementById("searchbar"); + gCustomizeMode.addToPanel(searchbar); + let placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel"); + yield PanelUI.show(); + yield waitForCondition(() => "value" in searchbar && searchbar.value === ""); + + searchbar.value = "foo"; + searchbar.focus(); + // Reaching into this context menu is pretty evil, but hey... it's a test. + let textbox = document.getAnonymousElementByAttribute(searchbar.textbox, "anonid", "textbox-input-box"); + let contextmenu = document.getAnonymousElementByAttribute(textbox, "anonid", "input-box-contextmenu"); + let contextMenuShown = promisePanelElementShown(window, contextmenu); + EventUtils.synthesizeMouseAtCenter(searchbar, {type: "contextmenu", button: 2}); + yield contextMenuShown; + + ok(isPanelUIOpen(), "Panel should still be open"); + + let selectAll = contextmenu.querySelector("[cmd='cmd_selectAll']"); + let contextMenuHidden = promisePanelElementHidden(window, contextmenu); + EventUtils.synthesizeMouseAtCenter(selectAll, {}); + yield contextMenuHidden; + + ok(isPanelUIOpen(), "Panel should still be open"); + + let hiddenPanelPromise = promisePanelHidden(window); + EventUtils.synthesizeKey("VK_ESCAPE", {}); + yield hiddenPanelPromise; + ok(!isPanelUIOpen(), "Panel should no longer be open"); +}); + +registerCleanupFunction(function() { + if (button && button.parentNode) { + button.remove(); + } + if (menuButton && menuButton.parentNode) { + menuButton.remove(); + } + // Sadly this isn't task.jsm-enabled, so we can't wait for this to happen. But we should + // definitely close it here and hope it won't interfere with other tests. + // Of course, all the tests are meant to do this themselves, but if they fail... + if (isPanelUIOpen()) { + PanelUI.hide(); + } +}); +
--- a/browser/components/customizableui/test/head.js +++ b/browser/components/customizableui/test/head.js @@ -245,16 +245,20 @@ function promisePanelElementHidden(win, aPanel.removeEventListener("popuphidden", onPanelClose); win.clearTimeout(timeoutId); deferred.resolve(); } aPanel.addEventListener("popuphidden", onPanelClose); return deferred.promise; } +function isPanelUIOpen() { + return PanelUI.panel.state == "open" || PanelUI.panel.state == "showing"; +} + function subviewShown(aSubview) { let deferred = Promise.defer(); let win = aSubview.ownerDocument.defaultView; let timeoutId = win.setTimeout(() => { deferred.reject("Subview (" + aSubview.id + ") did not show within 20 seconds."); }, 20000); function onViewShowing(e) { aSubview.removeEventListener("ViewShowing", onViewShowing);
--- a/browser/components/downloads/content/download.xml +++ b/browser/components/downloads/content/download.xml @@ -109,11 +109,13 @@ <binding id="download-toolbarbutton" extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton"> <content> <children /> <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/> <xul:label class="toolbarbutton-text" crop="right" flex="1" xbl:inherits="value=label,accesskey,crop"/> + <xul:label class="toolbarbutton-multiline-text" flex="1" + xbl:inherits="xbl:text=label,accesskey"/> </content> </binding> </bindings>
--- a/browser/components/tabview/tabitems.js +++ b/browser/components/tabview/tabitems.js @@ -841,16 +841,22 @@ let TabItems = { }, // Function: _isComplete // Checks whether the xul:tab has fully loaded and calls a callback with a // boolean indicates whether the tab is loaded or not. _isComplete: function TabItems__isComplete(tab, callback) { Utils.assertThrow(tab, "tab"); + // A pending tab can't be complete, yet. + if (tab.hasAttribute("pending")) { + setTimeout(() => callback(false)); + return; + } + let mm = tab.linkedBrowser.messageManager; let message = "Panorama:isDocumentLoaded"; mm.addMessageListener(message, function onMessage(cx) { mm.removeMessageListener(cx.name, onMessage); callback(cx.json.isLoaded); }); mm.sendAsyncMessage(message);
--- a/browser/components/tabview/test/browser.ini +++ b/browser/components/tabview/test/browser.ini @@ -159,15 +159,16 @@ skip-if = true # Bug 736425 [browser_tabview_dragdrop.js] [browser_tabview_exit_button.js] [browser_tabview_expander.js] [browser_tabview_firstrun_pref.js] [browser_tabview_group.js] skip-if = os == "mac" || os == "win" # Bug 945687 [browser_tabview_launch.js] [browser_tabview_multiwindow_search.js] +[browser_tabview_pending_tabs.js] [browser_tabview_privatebrowsing_perwindowpb.js] skip-if = os == 'linux' # Bug 944300 [browser_tabview_rtl.js] [browser_tabview_search.js] [browser_tabview_snapping.js] [browser_tabview_startup_transitions.js] [browser_tabview_undo_group.js]
new file mode 100644 --- /dev/null +++ b/browser/components/tabview/test/browser_tabview_pending_tabs.js @@ -0,0 +1,119 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const STATE = { + windows: [{ + tabs: [{ + entries: [{ url: "about:mozilla" }], + hidden: true, + extData: {"tabview-tab": '{"url":"about:mozilla","groupID":1}'} + },{ + entries: [{ url: "about:robots" }], + hidden: false, + extData: {"tabview-tab": '{"url":"about:robots","groupID":1}'}, + }], + selected: 1, + extData: { + "tabview-groups": '{"nextID":2,"activeGroupId":1, "totalNumber":1}', + "tabview-group": + '{"1":{"bounds":{"left":15,"top":5,"width":280,"height":232},"id":1}}' + } + }] +}; + +/** + * Make sure that tabs are restored on demand as otherwise the tab will start + * loading immediately and we can't check whether it shows cached data. + */ +add_task(function setup() { + Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true); + + registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand"); + }); +}); + +/** + * Ensure that a pending tab shows cached data. + */ +add_task(function () { + // Open a new window. + let win = OpenBrowserWindow(); + yield promiseDelayedStartupFinished(win); + + // Set the window to a specific state. + let ss = Cc["@mozilla.org/browser/sessionstore;1"] + .getService(Ci.nsISessionStore) + .setWindowState(win, JSON.stringify(STATE), true); + + // Open Panorama. + yield promiseTabViewShown(win); + + let [tab1, tab2] = win.gBrowser.tabs; + let cw = win.TabView.getContentWindow(); + + // Update the two tabs in reverse order. Panorama will first try to update + // the second tab but will put it back onto the queue once it detects that + // it hasn't loaded yet. It will then try to update the first tab. + cw.TabItems.update(tab2); + cw.TabItems.update(tab1); + + let tabItem1 = tab1._tabViewTabItem; + let tabItem2 = tab2._tabViewTabItem; + + // Wait for the first tabItem to be updated. Calling update() on the second + // tabItem won't send a notification as that is pushed back onto the queue. + yield promiseTabItemUpdated(tabItem1); + + // Check that the first tab doesn't show cached data, the second one does. + ok(!tabItem1.isShowingCachedData(), "doesn't show cached data"); + ok(tabItem2.isShowingCachedData(), "shows cached data"); + + // Cleanup. + yield promiseWindowClosed(win); +}); + +function promiseTabItemUpdated(tabItem) { + let deferred = Promise.defer(); + + tabItem.addSubscriber("updated", function onUpdated() { + tabItem.removeSubscriber("updated", onUpdated); + deferred.resolve(); + }); + + return deferred.promise; +} + +function promiseAllTabItemsUpdated(win) { + let deferred = Promise.defer(); + afterAllTabItemsUpdated(deferred.resolve, win); + return deferred.promise; +} + +function promiseDelayedStartupFinished(win) { + let deferred = Promise.defer(); + whenDelayedStartupFinished(win, deferred.resolve); + return deferred.promise; +} + +function promiseTabViewShown(win) { + let deferred = Promise.defer(); + showTabView(deferred.resolve, win); + return deferred.promise; +} + +function promiseWindowClosed(win) { + let deferred = Promise.defer(); + + Services.obs.addObserver(function obs(subject, topic) { + if (subject == win) { + Services.obs.removeObserver(obs, topic); + deferred.resolve(); + } + }, "domwindowclosed", false); + + win.close(); + return deferred.promise; +}
--- a/browser/devtools/framework/connect/connect.js +++ b/browser/devtools/framework/connect/connect.js @@ -32,16 +32,20 @@ window.addEventListener("DOMContentLoade if (host) { document.getElementById("host").value = host; } if (port) { document.getElementById("port").value = port; } + let form = document.querySelector("#connection-form form"); + form.addEventListener("submit", function() { + window.submit(); + }); }, true); /** * Called when the "connect" button is clicked. */ function submit() { // Show the "connecting" screen document.body.classList.add("connecting");
--- a/browser/devtools/framework/connect/connect.xhtml +++ b/browser/devtools/framework/connect/connect.xhtml @@ -14,17 +14,17 @@ <title>&title;</title> <link rel="stylesheet" href="chrome://browser/skin/devtools/dark-theme.css" type="text/css"/> <link rel="stylesheet" href="chrome://browser/content/devtools/connect.css" type="text/css"/> <script type="application/javascript;version=1.8" src="connect.js"></script> </head> <body> <h1>&header;</h1> <section id="connection-form"> - <form validate="validate" onsubmit="window.submit()" action="#"> + <form validate="validate" action="#"> <label> <span>&host;</span> <input required="required" class="devtools-textinput" id="host" type="text"></input> </label> <label> <span>&port;</span> <input required="required" class="devtools-textinput" id="port" type="number" pattern="\d+"></input> </label>
--- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -507,16 +507,18 @@ #ifdef MOZ_CAPTIVEDETECT @BINPATH@/components/CaptivePortalDetectComponents.manifest @BINPATH@/components/captivedetect.js #endif @BINPATH@/components/servicesComponents.manifest @BINPATH@/components/cryptoComponents.manifest @BINPATH@/components/TelemetryPing.js @BINPATH@/components/TelemetryPing.manifest +@BINPATH@/components/TelemetryStartup.js +@BINPATH@/components/TelemetryStartup.manifest @BINPATH@/components/messageWakeupService.js @BINPATH@/components/messageWakeupService.manifest @BINPATH@/components/SettingsManager.js @BINPATH@/components/SettingsManager.manifest @BINPATH@/components/SettingsService.js @BINPATH@/components/SettingsService.manifest @BINPATH@/components/Webapps.js @BINPATH@/components/Webapps.manifest
--- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -224,16 +224,17 @@ pasteAndGo.label=Paste & Go refreshBlocked.goButton=Allow refreshBlocked.goButton.accesskey=A refreshBlocked.refreshLabel=%S prevented this page from automatically reloading. refreshBlocked.redirectLabel=%S prevented this page from automatically redirecting to another page. # Star button starButtonOn.tooltip=Edit this bookmark starButtonOff.tooltip=Bookmark this page +starButtonOverflowed.label=Bookmark This Page # Offline web applications offlineApps.available=This website (%S) is asking to store data on your computer for offline use. offlineApps.allow=Allow offlineApps.allowAccessKey=A offlineApps.never=Never for This Site offlineApps.neverAccessKey=e offlineApps.notNow=Not Now
deleted file mode 100644 --- a/browser/metro/base/content/config.js +++ /dev/null @@ -1,407 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -let Ci = Components.interfaces; - -Components.utils.import("resource://gre/modules/Services.jsm"); - -var ViewConfig = { - get _main() { - delete this._main; - return this._main = document.getElementById("main-container"); - }, - - get _container() { - delete this._container; - return this._container = document.getElementById("prefs-container"); - }, - - get _editor() { - delete this._editor; - return this._editor = document.getElementById("editor"); - }, - - init: function init() { - this._main.addEventListener("click", this, false); - window.addEventListener("resize", this, false); - window.addEventListener("prefchange", this, false); - window.addEventListener("prefnew", this, false); - - this._handleWindowResize(); - this.filter(""); - - document.getElementById("textbox").focus(); - }, - - uninit: function uninit() { - this._main.removeEventListener("click", this, false); - window.removeEventListener("resize", this, false); - window.removeEventListener("prefchange", this, false); - window.removeEventListener("prefnew", this, false); - }, - - filter: function filter(aValue) { - let row = document.getElementById("editor-row"); - - let container = this._container; - container.scrollBoxObject.scrollTo(0, 0); - // Clear the list by replacing with a shallow copy - let empty = container.cloneNode(false); - empty.appendChild(row); - container.parentNode.replaceChild(empty, container); - this._container = empty; - - let result = Utils.getPrefs(aValue); - this._container.setItems(result.map(this._createItem, this)); - }, - - open: function open(aType) { - let buttons = document.getElementById("editor-buttons-add"); - buttons.setAttribute("hidden", "true"); - - let shouldFocus = false; - let setting = document.getElementById("editor-setting"); - switch (aType) { - case Ci.nsIPrefBranch.PREF_INT: - setting.setAttribute("type", "integer"); - setting.setAttribute("min", -Infinity); - break; - case Ci.nsIPrefBranch.PREF_BOOL: - setting.setAttribute("type", "bool"); - break; - case Ci.nsIPrefBranch.PREF_STRING: - setting.setAttribute("type", "string"); - break; - } - - setting.removeAttribute("title"); - setting.removeAttribute("pref"); - if (setting.input) - setting.input.value = ""; - - document.getElementById("editor-container").appendChild(this._editor); - let nameField = document.getElementById("editor-name"); - nameField.value = ""; - - this._editor.setAttribute("hidden", "false"); - this._currentItem = null; - nameField.focus(); - }, - - close: function close(aValid) { - this._editor.setAttribute("hidden", "true"); - let buttons = document.getElementById("editor-buttons-add"); - buttons.setAttribute("hidden", "false"); - - if (aValid) { - let name = document.getElementById("editor-name").inputField.value; - if (name != "") { - let setting = document.getElementById("editor-setting"); - setting.setAttribute("pref", name); - setting.valueToPreference(); - } - } - - document.getElementById("editor-container").appendChild(this._editor); - }, - - _currentItem: null, - - delayEdit: function(aItem) { - setTimeout(this.edit.bind(this), 0, aItem); - }, - - edit: function(aItem) { - if (!aItem) - return; - - let pref = Utils.getPref(aItem.getAttribute("name")); - if (pref.lock || !pref.name || aItem == this._currentItem) - return; - - this.close(false); - this._currentItem = aItem; - - let setting = document.getElementById("editor-setting"); - let shouldFocus = false; - switch (pref.type) { - case Ci.nsIPrefBranch.PREF_BOOL: - setting.setAttribute("type", "bool"); - break; - - case Ci.nsIPrefBranch.PREF_INT: - setting.setAttribute("type", "integer"); - setting.setAttribute("increment", this.getIncrementForValue(pref.value)); - setting.setAttribute("min", -Infinity); - shouldFocus = true; - break; - - case Ci.nsIPrefBranch.PREF_STRING: - setting.setAttribute("type", "string"); - shouldFocus = true; - break; - } - - setting.setAttribute("title", pref.name); - setting.setAttribute("pref", pref.name); - - this._container.insertBefore(this._editor, aItem); - - let resetButton = document.getElementById("editor-reset"); - resetButton.setAttribute("disabled", pref.default); - - this._editor.setAttribute("default", pref.default); - this._editor.setAttribute("hidden", "false"); - - if (shouldFocus && setting.input) - setting.input.focus(); - }, - - reset: function reset(aItem) { - let setting = document.getElementById("editor-setting"); - let pref = Utils.getPref(setting.getAttribute("pref")); - if (!pref.default) - Utils.resetPref(pref.name); - }, - - handleEvent: function handleEvent(aEvent) { - switch (aEvent.type) { - case "resize": - this._handleWindowResize(); - break; - - case "prefchange": - case "prefnew": - this._handlePrefChange(aEvent.detail, aEvent.type == "prefnew"); - break; - - case "click": - this._onClick(); - break; - } - }, - - _handleWindowResize: function _handleWindowResize() { - let mainBox = document.getElementById("main-container"); - let textbox = document.getElementById("textbox"); - let height = window.innerHeight - textbox.getBoundingClientRect().height; - - mainBox.setAttribute("height", height); - }, - - _onClick: function () { - // Blur the search box when tapping anywhere else in the content - // in order to close the soft keyboard. - document.getElementById("textbox").blur(); - }, - - _handlePrefChange: function _handlePrefChange(aIndex, aNew) { - let isEditing = !this._editor.hidden; - let shouldUpdateEditor = false; - if (isEditing) { - let setting = document.getElementById("editor-setting"); - let editorIndex = Utils.getPrefIndex(setting.getAttribute("pref")); - shouldUpdateEditor = (aIndex == editorIndex); - if(shouldUpdateEditor || aIndex > editorIndex) - aIndex += 1; - } - - // XXX An item display value will probably fail if a pref is changed in the - // background while there is a filter on the pref - let item = shouldUpdateEditor ? this._editor.nextSibling - : this._container.childNodes[aIndex + 1];// add 1 because of the new pref row - if (!item) // the pref is not viewable - return; - - if (aNew) { - let pref = Utils.getPrefByIndex(aIndex); - let row = this._createItem(pref); - this._container.insertBefore(row, item); - return; - } - - let pref = Utils.getPref(item.getAttribute("name")); - if (shouldUpdateEditor) { - this._editor.setAttribute("default", pref.default); - - let resetButton = document.getElementById("editor-reset"); - resetButton.disabled = pref.default; - } - - item.setAttribute("default", pref.default); - item.lastChild.setAttribute("value", pref.value); - }, - - _createItem: function _createItem(aPref) { - let row = document.createElement("richlistitem"); - - row.setAttribute("name", aPref.name); - row.setAttribute("type", aPref.type); - row.setAttribute("role", "button"); - row.setAttribute("default", aPref.default); - - let label = document.createElement("label"); - label.setAttribute("class", "preferences-title"); - label.setAttribute("value", aPref.name); - label.setAttribute("crop", "end"); - row.appendChild(label); - - label = document.createElement("label"); - label.setAttribute("class", "preferences-value"); - label.setAttribute("value", aPref.value); - label.setAttribute("crop", "end"); - row.appendChild(label); - - return row; - }, - - getIncrementForValue: function getIncrementForValue(aValue) { - let count = 1; - while (aValue >= 100) { - aValue /= 10; - count *= 10; - } - return count; - } -}; - -var Utils = { - QueryInterface: function(aIID) { - if (!aIID.equals(Ci.nsIObserver) && !aIID.equals(Ci.nsISupportsWeakReference)) - throw Components.results.NS_ERROR_NO_INTERFACE; - return this; - }, - - get _branch() { - delete this._branch; - this._branch = Services.prefs.getBranch(null); - this._branch.addObserver("", this, true); - return this._branch; - }, - - get _preferences() { - delete this._preferences; - let list = this._branch.getChildList("", {}).filter(function(element) { - return !(/^capability\./.test(element)); - }); - return this._preferences = list.sort().map(this.getPref, this); - }, - - getPrefs: function getPrefs(aValue) { - let result = this._preferences.slice();; - if (aValue != "") { - let reg = this._generateRegexp(aValue); - if (!reg) - return []; - - result = this._preferences.filter(function(element, index, array) { - return reg.test(element.name + ";" + element.value); - }); - } - - return result; - }, - - getPref: function getPref(aPrefName) { - let branch = this._branch; - let pref = { - name: aPrefName, - value: "", - default: !branch.prefHasUserValue(aPrefName), - lock: branch.prefIsLocked(aPrefName), - type: branch.getPrefType(aPrefName) - }; - - try { - switch (pref.type) { - case Ci.nsIPrefBranch.PREF_BOOL: - pref.value = branch.getBoolPref(aPrefName).toString(); - break; - case Ci.nsIPrefBranch.PREF_INT: - pref.value = branch.getIntPref(aPrefName).toString(); - break; - default: - case Ci.nsIPrefBranch.PREF_STRING: - pref.value = branch.getComplexValue(aPrefName, Ci.nsISupportsString).data; - // Try in case it's a localized string (will throw an exception if not) - if (pref.default && /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.value)) - pref.value = branch.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data; - break; - } - } catch (e) {} - - return pref; - }, - - getPrefByIndex: function getPrefByIndex(aIndex) { - return this._preferences[aIndex]; - }, - - getPrefIndex: function getPrefIndex(aPrefName) { - let prefs = this._preferences; - let high = prefs.length - 1; - let low = 0, middle, element; - - while (low <= high) { - middle = parseInt((low + high) / 2); - element = prefs[middle]; - - if (element.name > aPrefName) - high = middle - 1; - else if (element.name < aPrefName) - low = middle + 1; - else - return middle; - } - - return -1; - }, - - resetPref: function resetPref(aPrefName) { - this._branch.clearUserPref(aPrefName); - }, - - observe: function observe(aSubject, aTopic, aPrefName) { - if (aTopic != "nsPref:changed" || /^capability\./.test(aPrefName)) // avoid displaying "private" preferences - return; - - let type = "prefchange"; - let index = this.getPrefIndex(aPrefName); - if (index != - 1) { - // update the inner array - let pref = this.getPref(aPrefName); - this._preferences[index].value = pref.value; - } - else { - // XXX we could do better here - let list = this._branch.getChildList("", {}).filter(function(element, index, array) { - return !(/^capability\./.test(element)); - }); - this._preferences = list.sort().map(this.getPref, this); - - type = "prefnew"; - index = this.getPrefIndex(aPrefName); - } - - let evt = document.createEvent("UIEvents"); - evt.initUIEvent(type, true, true, window, index); - window.dispatchEvent(evt); - }, - - _generateRegexp: function _generateRegexp(aValue) { - if (aValue.charAt(0) == "/") { - try { - let rv = aValue.match(/^\/(.*)\/(i?)$/); - return RegExp(rv[1], rv[2]); - } - catch (e) { - return null; // Do nothing on incomplete or bad RegExp - } - } - - return RegExp(aValue.replace(/([^* \w])/g, "\\$1").replace(/^\*+/, "") - .replace(/\*+/g, ".*"), "i"); - } -}; -
deleted file mode 100644 --- a/browser/metro/base/content/config.xul +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0"?> - -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<?xml-stylesheet href="chrome://browser/skin/platform.css" type="text/css"?> -<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?> -<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?> -<?xml-stylesheet href="chrome://browser/skin/config.css" type="text/css"?> - -<!DOCTYPE window [ -<!ENTITY % configDTD SYSTEM "chrome://browser/locale/config.dtd"> -%configDTD; -]> - -<window id="about:config" - onload="ViewConfig.init();" - onunload="ViewConfig.uninit();" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <script type="application/x-javascript" src="chrome://browser/content/config.js"/> - - <vbox class="panel-dark" flex="1"> - <textbox id="textbox" - oncommand="ViewConfig.filter(this.value)" - type="search" - timeout="400" - emptytext="&empty.label;"/> - - <hbox id="main-container" class="panel-dark"> - <richlistbox id="prefs-container" flex="1" onselect="ViewConfig.delayEdit(this.selectedItem)" batch="25"> - <richlistitem id="editor-row"> - <vbox id="editor-container" flex="1"> - - <hbox align="center" flex="1"> - <label value="&newpref.label;" flex="1"/> - <spacer flex="1" /> - <hbox id="editor-buttons-add"> - <button label="&integer.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_INT)"/> - <button label="&boolean.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_BOOL)"/> - <button label="&string.label;" oncommand="ViewConfig.open(Ci.nsIPrefBranch.PREF_STRING)"/> - </hbox> - </hbox> - - <vbox id="editor" hidden="true"> - <hbox align="center"> - <textbox id="editor-name" emptytext="&addpref.name;" flex="1"/> - <setting id="editor-setting" emptytext="&addpref.value;" onlabel="true" offlabel="false" flex="1"/> - </hbox> - <hbox id="editor-buttons"> - <button id="editor-cancel" label="&cancel.label;" oncommand="ViewConfig.close(false)"/> - <spacer flex="1"/> - <button id="editor-reset" label="&reset.label;" oncommand="ViewConfig.reset(this.parentNode.parentNode.nextSibling)"/> - <button id="editor-done" label="&done.label;" oncommand="ViewConfig.close(true)"/> - </hbox> - </vbox> - - </vbox> - </richlistitem> - </richlistbox> - </hbox> - </vbox> -</window> -
copy from mobile/android/chrome/content/config.js copy to browser/metro/base/content/pages/config.js --- a/mobile/android/chrome/content/config.js +++ b/browser/metro/base/content/pages/config.js @@ -1,19 +1,19 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const {classes: Cc, interfaces: Ci, manager: Cm, utils: Cu} = Components; Cu.import("resource://gre/modules/Services.jsm"); -const VKB_ENTER_KEY = 13; // User press of VKB enter key +const PRIVATE_PREF_PREFIX = "capability."; // Tag to prevent exposing private preferences const INITIAL_PAGE_DELAY = 500; // Initial pause on program start for scroll alignment -const PREFS_BUFFER_MAX = 30; // Max prefs buffer size for getPrefsBuffer() +const PREFS_BUFFER_MAX = 100; // Max prefs buffer size for getPrefsBuffer() const PAGE_SCROLL_TRIGGER = 200; // Triggers additional getPrefsBuffer() on user scroll-to-bottom const FILTER_CHANGE_TRIGGER = 200; // Delay between responses to filterInput changes const INNERHTML_VALUE_DELAY = 100; // Delay before providing prefs innerHTML value let gStringBundle = Services.strings.createBundle("chrome://browser/locale/config.properties"); let gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); @@ -81,19 +81,19 @@ var NewPrefDialog = { // As new pref name is initially displayed, re-focused, or modifed during user input _updatePositiveButton: function AC_updatePositiveButton(aPrefName) { this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.createButton"); this._positiveButton.setAttribute("disabled", true); if (aPrefName == "") { return; } - // Avoid "private" preferences - if (/^capability\./.test(aPrefName)) { - this._positiveButton.textContent = "Private"; + // Prevent addition of new "private" preferences + if (aPrefName.startsWith(PRIVATE_PREF_PREFIX)) { + this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.privateButton"); return; } // If item already in list, it's being changed, else added let item = document.querySelector(".pref-item[name=" + aPrefName.quote() + "]"); if (item) { this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.changeButton"); } else { @@ -135,17 +135,17 @@ var NewPrefDialog = { this._prefsShield.removeAttribute("shown"); window.removeEventListener("keypress", this.handleKeypress, false); }, // Watch user key input so we can provide Enter key action, commit input values handleKeypress: function AC_handleKeypress(aEvent) { // Close our VKB on new pref enter key press - if (aEvent.keyCode == VKB_ENTER_KEY) + if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) aEvent.target.blur(); }, // New prefs create dialog only allows creating a non-existing preference, doesn't allow for // Changing an existing one on-the-fly, tap existing/displayed line item pref for that create: function AC_create(aEvent) { if (this._positiveButton.getAttribute("disabled") == "true") { return; @@ -188,33 +188,32 @@ var NewPrefDialog = { * * Main AboutConfig object and methods * * Implements User Interfaces for maintenance of a list of Preference settings * */ var AboutConfig = { - contextMenuLINode: null, filterInput: null, _filterPrevInput: null, _filterChangeTimer: null, _prefsContainer: null, _loadingContainer: null, _list: null, // Init the main AboutConfig dialog init: function AC_init() { this.filterInput = document.getElementById("filter-input"); this._prefsContainer = document.getElementById("prefs-container"); this._loadingContainer = document.getElementById("loading-container"); let list = Services.prefs.getChildList("", {}).filter(function(aElement) { - // Avoid "private" preferences - return !(/^capability\./.test(aElement)); + // Prevent display of "private" preferences + return !aElement.startsWith(PRIVATE_PREF_PREFIX); }); this._list = list.sort().map( function AC_getMapPref(aPref) { return new Pref(aPref); }, this); // Display the current prefs list (retains searchFilter value) this.bufferFilterInput(); @@ -253,33 +252,33 @@ var AboutConfig = { this.filterInput.setAttribute("value", this.filterInput.value); // Don't start new filter search if same as last if (this.filterInput.value == this._filterPrevInput) { return; } this._filterPrevInput = this.filterInput.value; - // Clear list item selection / context menu, prefs list, get first buffer, set scrolling on + // Clear list item selection and prefs list, get first buffer, set scrolling on this.selected = ""; this._clearPrefsContainer(); this._addMorePrefsToContainer(); window.onscroll = this.onScroll.bind(this); // Pause for screen to settle, then ensure at top setTimeout((function() { window.scrollTo(0, 0); }).bind(this), INITIAL_PAGE_DELAY); }, // Clear the displayed preferences list _clearPrefsContainer: function AC_clearPrefsContainer() { // Quick clear the prefsContainer list let empty = this._prefsContainer.cloneNode(false); - this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer); + this._prefsContainer.parentNode.replaceChild(empty, this._prefsContainer); this._prefsContainer = empty; // Quick clear the prefs li.HTML list this._list.forEach(function(item) { delete item.li; }); }, @@ -325,17 +324,16 @@ var AboutConfig = { onScroll: function AC_onScroll(aEvent) { if (this._prefsContainer.scrollHeight - (window.pageYOffset + window.innerHeight) < PAGE_SCROLL_TRIGGER) { if (!this._filterChangeTimer) { this._addMorePrefsToContainer(); } } }, - // Return currently selected list item node get selected() { return document.querySelector(".pref-item.selected"); }, // Set list item node as selected set selected(aSelection) { let currentSelection = this.selected; @@ -353,17 +351,17 @@ var AboutConfig = { if (aSelection) { aSelection.classList.add("selected"); aSelection.addEventListener("keypress", this.handleKeypress, false); } }, // Watch user key input so we can provide Enter key action, commit input values handleKeypress: function AC_handleKeypress(aEvent) { - if (aEvent.keyCode == VKB_ENTER_KEY) + if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) aEvent.target.blur(); }, // Return the target list item node of an action event getLINodeForEvent: function AC_getLINodeForEvent(aEvent) { let node = aEvent.target; while (node && node.nodeName != "li") { node = node.parentNode; @@ -458,18 +456,18 @@ var AboutConfig = { pref.value += aInt; }, // Observe preference changes observe: function AC_observe(aSubject, aTopic, aPrefName) { let pref = new Pref(aPrefName); - // Ignore uninteresting changes, and avoid "private" preferences - if ((aTopic != "nsPref:changed") || /^capability\./.test(pref.name)) { + // Ignore uninteresting preference changes, and external changes to "private" preferences + if ((aTopic != "nsPref:changed") || pref.name.startsWith(PRIVATE_PREF_PREFIX)) { return; } // If pref type invalid, refresh display as user reset/removed an item from the list if (pref.type == Services.prefs.PREF_INVALID) { document.location.reload(); return; } @@ -485,26 +483,16 @@ var AboutConfig = { item.setAttribute("value", pref.value); let input = item.querySelector("input"); input.setAttribute("value", pref.value); input.value = pref.value; pref.default ? item.querySelector(".reset").setAttribute("disabled", "true") : item.querySelector(".reset").removeAttribute("disabled"); - }, - - // Quick context menu helpers for about:config - clipboardCopy: function AC_clipboardCopy(aField) { - let pref = this._getPrefForNode(this.contextMenuLINode); - if (aField == 'name') { - gClipboardHelper.copyString(pref.name); - } else { - gClipboardHelper.copyString(pref.value); - } } } /* ============================== Pref ============================== * * Individual Preference object / methods * @@ -528,16 +516,17 @@ Pref.prototype = { case Services.prefs.PREF_INT: return Services.prefs.getIntPref(this.name); case Services.prefs.PREF_STRING: default: return Services.prefs.getCharPref(this.name); } }, + set value(aPrefValue) { switch (this.type) { case Services.prefs.PREF_BOOL: Services.prefs.setBoolPref(this.name, aPrefValue); break; case Services.prefs.PREF_INT: Services.prefs.setIntPref(this.name, aPrefValue); break; @@ -574,26 +563,16 @@ Pref.prototype = { // Click callback to ensure list item selected even on no-action tap events this.li.addEventListener("click", function(aEvent) { AboutConfig.selected = AboutConfig.getLINodeForEvent(aEvent); }, false ); - // Contextmenu callback to identify selected list item - this.li.addEventListener("contextmenu", - function(aEvent) { - AboutConfig.contextMenuLINode = AboutConfig.getLINodeForEvent(aEvent); - }, - false - ); - - this.li.setAttribute("contextmenu", "prefs-context-menu"); - // Create list item outline, bind to object actions this.li.innerHTML = "<div class='pref-name' " + "onclick='AboutConfig.selectOrToggleBoolPref(event);'>" + this.name + "</div>" + "<div class='pref-item-line'>" + "<input class='pref-value' value='' " + @@ -656,9 +635,8 @@ Pref.prototype = { } if (this.locked) { valDiv.setAttribute("disabled", this.locked); this.li.querySelector(".pref-name").setAttribute("locked", true); } } } -
copy from mobile/android/chrome/content/config.xhtml copy to browser/metro/base/content/pages/config.xhtml --- a/mobile/android/chrome/content/config.xhtml +++ b/browser/metro/base/content/pages/config.xhtml @@ -1,86 +1,81 @@ <?xml version="1.0"?> <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [ -<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" > +<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD; <!ENTITY % configDTD SYSTEM "chrome://browser/locale/config.dtd"> %configDTD; ]> <html xmlns="http://www.w3.org/1999/xhtml"> <head> - <meta name="viewport" content="width=device-width; user-scalable=0" /> + <meta name="viewport" content="width=device-width; user-scalable=false" /> <link rel="stylesheet" href="chrome://browser/skin/config.css" type="text/css"/> - <script type="text/javascript;version=1.8" src="chrome://browser/content/config.js"></script> + <script type="text/javascript;version=1.8" src="chrome://browser/content/pages/config.js"/> </head> <body dir="&locale.dir;" onload="NewPrefDialog.init(); AboutConfig.init();" onunload="AboutConfig.uninit();"> <div class="toolbar"> <div class="toolbar-container"> <div id="new-pref-toggle-button" onclick="NewPrefDialog.toggleShowHide();"/> <div class="toolbar-item" id="filter-container"> <div id="filter-search-button"/> - <input id="filter-input" type="search" placeholder="&toolbar.searchPlaceholder;" value="" + <input id="filter-input" type="search" placeholder="&empty.label;" value="" oninput="AboutConfig.bufferFilterInput();"/> <div id="filter-input-clear-button" onclick="AboutConfig.clearFilterInput();"/> </div> </div> </div> <div id="content" ontouchstart="AboutConfig.filterInput.blur();"> <div id="new-pref-container"> <li class="pref-item" id="new-pref-item"> <div class="pref-item-line"> - <input class="pref-name" id="new-pref-name" type="text" placeholder="&newPref.namePlaceholder;" + <input class="pref-name" id="new-pref-name" type="text" placeholder="&addpref.name;" onfocus="NewPrefDialog.focusName(event);" oninput="NewPrefDialog.updateName(event);"/> <select class="pref-value" id="new-pref-type" onchange="NewPrefDialog.type = event.target.value;"> - <option value="boolean">&newPref.valueBoolean;</option> - <option value="string">&newPref.valueString;</option> - <option value="int">&newPref.valueInteger;</option> + <option value="boolean">&boolean.label;</option> + <option value="string">&string.label;</option> + <option value="int">&integer.label;</option> </select> </div> <div class="pref-item-line" id="new-pref-line-boolean"> <input class="pref-value" id="new-pref-value-boolean" disabled="disabled"/> - <div class="pref-button toggle" onclick="NewPrefDialog.toggleBoolValue();">&newPref.toggleButton;</div> + <div class="pref-button toggle" onclick="NewPrefDialog.toggleBoolValue();">&toggle.label;</div> </div> <div class="pref-item-line"> - <input class="pref-value" id="new-pref-value-string" placeholder="&newPref.stringPlaceholder;"/> - <input class="pref-value" id="new-pref-value-int" placeholder="&newPref.numberPlaceholder;" type="number"/> + <input class="pref-value" id="new-pref-value-string" placeholder="&string.placeholder;"/> + <input class="pref-value" id="new-pref-value-int" placeholder="&number.placeholder;" type="number"/> </div> <div class="pref-item-line"> - <div class="pref-button cancel" id="negative-button" onclick="NewPrefDialog.hide();">&newPref.cancelButton;</div> + <div class="pref-button cancel" id="negative-button" onclick="NewPrefDialog.hide();">&cancel.label;</div> <div class="pref-button create" id="positive-button" onclick="NewPrefDialog.create(event);"></div> </div> </li> </div> <div id="prefs-shield"></div> <ul id="prefs-container"/> <ul id="loading-container"><li></li></ul> </div> - <menu type="context" id="prefs-context-menu"> - <menuitem label="&contextMenu.copyPrefName;" onclick="AboutConfig.clipboardCopy('name');"></menuitem> - <menuitem label="&contextMenu.copyPrefValue;" onclick="AboutConfig.clipboardCopy('value');"></menuitem> - </menu> - </body> </html>
--- a/browser/metro/base/jar.mn +++ b/browser/metro/base/jar.mn @@ -1,21 +1,22 @@ #filter substitution # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. chrome.jar: % content browser %content/ + content/aboutAddons.xhtml (content/pages/aboutAddons.xhtml) content/aboutCertError.xhtml (content/pages/aboutCertError.xhtml) content/aboutRights.xhtml (content/pages/aboutRights.xhtml) content/blockedSite.xhtml (content/pages/blockedSite.xhtml) + content/config.xhtml (content/pages/config.xhtml) content/netError.xhtml (content/pages/netError.xhtml) - content/aboutAddons.xhtml (content/pages/aboutAddons.xhtml) #ifdef MOZ_CRASHREPORTER content/crashprompt.xhtml (content/pages/crashprompt.xhtml) #endif content/bindings/bindings.xml (content/bindings/bindings.xml) content/bindings/tabs.xml (content/bindings/tabs.xml) content/bindings/toggleswitch.xml (content/bindings/toggleswitch.xml) content/bindings/browser.xml (content/bindings/browser.xml) @@ -59,18 +60,17 @@ chrome.jar: content/contenthandlers/ConsoleAPIObserver.js (content/contenthandlers/ConsoleAPIObserver.js) content/contenthandlers/Content.js (content/contenthandlers/Content.js) content/library/SelectionPrototype.js (content/library/SelectionPrototype.js) content/ContentAreaObserver.js (content/ContentAreaObserver.js) content/BrowserTouchHandler.js (content/BrowserTouchHandler.js) * content/WebProgress.js (content/WebProgress.js) - content/config.xul (content/config.xul) - content/config.js (content/config.js) + content/pages/config.js (content/pages/config.js) * content/browser.xul (content/browser.xul) content/browser.js (content/browser.js) * content/browser-ui.js (content/browser-ui.js) * content/browser-scripts.js (content/browser-scripts.js) content/ContextCommands.js (content/ContextCommands.js) content/commandUtil.js (content/commandUtil.js) content/appbar.js (content/appbar.js) content/shell.xul (content/jsshell/shell.xul) @@ -98,11 +98,11 @@ chrome.jar: content/BookmarksView.js (content/startui/BookmarksView.js) content/HistoryView.js (content/startui/HistoryView.js) content/TopSitesView.js (content/startui/TopSitesView.js) content/FirstRunOverlay.xul (content/startui/FirstRunOverlay.xul) #ifdef MOZ_SERVICES_SYNC content/RemoteTabsView.js (content/startui/RemoteTabsView.js) #endif -% override chrome://global/content/config.xul chrome://browser/content/config.xul +% override chrome://global/content/config.xul chrome://browser/content/config.xhtml % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml % override chrome://mozapps/content/extensions/extensions.xul chrome://browser/content/aboutAddons.xhtml
--- a/browser/metro/locales/en-US/chrome/config.dtd +++ b/browser/metro/locales/en-US/chrome/config.dtd @@ -1,16 +1,16 @@ <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <!ENTITY empty.label "Search"> -<!ENTITY newpref.label "Add a New Preference"> <!ENTITY addpref.name "Name"> -<!ENTITY addpref.value "Value"> <!ENTITY cancel.label "Cancel"> -<!ENTITY reset.label "Reset"> -<!ENTITY done.label "Done"> +<!ENTITY toggle.label "Toggle"> <!ENTITY integer.label "Integer"> <!ENTITY string.label "String"> <!ENTITY boolean.label "Boolean"> + +<!ENTITY string.placeholder "Enter a string"> +<!ENTITY number.placeholder "Enter a number">
new file mode 100644 --- /dev/null +++ b/browser/metro/locales/en-US/chrome/config.properties @@ -0,0 +1,10 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +newPref.createButton=Create +newPref.privateButton=Private +newPref.changeButton=Change + +pref.toggleButton=Toggle +pref.resetButton=Reset
--- a/browser/metro/locales/jar.mn +++ b/browser/metro/locales/jar.mn @@ -7,18 +7,19 @@ # Metro jar resources # @AB_CD@.jar: % locale browser @AB_CD@ %locale/browser/ locale/browser/aboutCertError.dtd (%chrome/aboutCertError.dtd) locale/browser/browser.dtd (%chrome/browser.dtd) locale/browser/browser.properties (%chrome/browser.properties) + locale/browser/config.dtd (%chrome/config.dtd) + locale/browser/config.properties (%chrome/config.properties) locale/browser/region.properties (%chrome/region.properties) - locale/browser/config.dtd (%chrome/config.dtd) locale/browser/preferences.dtd (%chrome/preferences.dtd) locale/browser/aboutPanel.dtd (%chrome/aboutPanel.dtd) locale/browser/searchPanel.dtd (%chrome/searchPanel.dtd) locale/browser/checkbox.dtd (%chrome/checkbox.dtd) locale/browser/sync.dtd (%chrome/sync.dtd) locale/browser/sync.properties (%chrome/sync.properties) locale/browser/passwordmgr.properties (%chrome/passwordmgr.properties) locale/browser/phishing.dtd (%chrome/phishing.dtd)
--- a/browser/metro/theme/config.css +++ b/browser/metro/theme/config.css @@ -1,97 +1,345 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -@media (max-width: 499px) { - #editor-container > hbox { - -moz-box-orient: vertical; - } +html, +body { + margin: 0; + padding: 0; + background-color: #ced7de; + -moz-user-select: none; + font-family: "Segoe UI", sans-serif; + -moz-text-size-adjust: none; } -richlistitem { - -moz-box-align: center; +.toolbar { + width: 100%; + height: 3em; + position: fixed; + top: 0; + left: 0; + z-index: 10; + box-shadow: 0 0 3px #444; + background-color: #ced7de; + color: #000000; + font-weight: bold; + border-bottom: 2px solid; + -moz-border-bottom-colors: #ff9100 #f27900; +} + +.toolbar-container { + max-width: 40em; + margin-left: auto; + margin-right: auto; } -richlistitem .preferences-title { - pointer-events: none; - min-width: 200px; - -moz-box-flex: 1; - margin-right: 8px; +#filter-container { + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-right: 0.5em; + height: 2em; + border: 1px solid transparent; + border-image-source: url("chrome://browser/skin/images/textfield.png"); + border-image-slice: 1 1 3 1; + border-image-width: 1px 1px 3px 1px; + overflow: hidden; + display: flex; + flex-direction: row; } -/* XXX look + sync */ -richlistitem[default="false"] .preferences-title { - font-weight: bold; +#filter-input { + -moz-appearance: none; + border: none; + background-image: none; + background-color: transparent; + display: inline-block; + width: 12em; + min-width: 0; + color: #000000; + opacity: 1; + flex: 1 1 auto; +} + +#filter-input:-moz-placeholder { + color: rgba(255,255,255,0.5); +} + +.toolbar input { + display: inline-block; + height: 100%; + min-width: 3em; + -moz-box-sizing: border-box; + opacity: 0.75; +} + +#new-pref-toggle-button { + background-position: center center; + background-image: url("chrome://browser/skin/images/reader-plus-icon-xhdpi.png"); + background-size: 48px 48px; + height: 48px; + width: 48px; + display: inline-block; + outline-style: none; } -richlistitem .preferences-value { - min-width: 200px; - pointer-events: none; - -moz-box-flex: 4; - text-align: end; - color: grey; +#filter-search-button { + background-image: url("chrome://browser/skin/images/search.png"); + background-size: 32px 32px; + height: 32px; + width: 32px; + display: inline-block; + outline-style: none; +} + +#filter-input-clear-button { + background-image: url("chrome://browser/skin/images/search-clear-30.png"); + background-size: 32px 32px; + height: 32px; + width: 32px; + display: inline-block; + outline-style: none; +} + +#filter-input[value=""] + #filter-input-clear-button { + display: none; +} + +.toolbar-item { + display: inline-block; + height: 3em; + min-width: 3em; + float: right; +} + +#content { + position: relative; + margin: 0; + margin-left: auto; + margin-right: auto; + padding-top: 3em; + padding-left: 0; + padding-right: 0; + min-height: 100%; + max-width: 40em; } -/* Editor */ -#editor-row { - padding: 0; - background: #E9E9E9; +ul { + list-style-position: inside; + border: 1px solid #808080; + background-color: #ffffff; + min-height: 100%; + width: 100%; + padding-top: 0; + margin: 0; + padding-left: 0; + -moz-box-sizing: border-box; + box-shadow: 0 0 5px #000000; + overflow-x: hidden; } -#editor { - border-bottom: 1px solid rgb(207,207,207); +#new-pref-container { + width: 100%; + margin: 0; + background-color: #ffffff; + -moz-box-sizing: border-box; + box-shadow: 0 0 5px #000000; + overflow-x: hidden; + max-width: 40em; + max-height: 100%; + position: fixed; + top: 3em; + left: auto; + display: none; + z-index: 5; } -#editor > hbox > #editor-name, -#editor > hbox > #editor-cancel, -#editor > hbox > #editor-done { - display: none; +#new-pref-container input, +#new-pref-container select { + border: none; + background-image: none; +} + +#new-pref-container.show { + display: block; +} + +li { + list-style-type: none; + border-bottom: 1px solid #d3d3d3; + opacity: 1; + background-color: #ffffff; + cursor: pointer; } -#editor-container > #editor > hbox > #editor-name, -#editor-container > #editor > hbox > #editor-cancel, -#editor-container > #editor > hbox > #editor-done { - display: -moz-box; +#new-pref-line-boolean, +#new-pref-value-string, +#new-pref-value-int { + display: none; +} +#new-pref-item[typestyle="boolean"] #new-pref-line-boolean, +#new-pref-item[typestyle="string"] #new-pref-value-string, +#new-pref-item[typestyle="int"] #new-pref-value-int { + display: block; +} + +.pref-name, +.pref-value { + padding: 15px 10px; + text-align: left; + text-overflow: ellipsis; + overflow: hidden; + background-image: none; } -#editor-container > #editor > hbox > #editor-reset { - display: none; +.pref-value { + color: rgba(0,0,0,0.5); + flex: 1 1 auto; + border: none; + -moz-appearance: none; + background-image: none; + background-color: transparent; +} + +.pref-name[locked] { + padding-right: 20px; + background-image: url("chrome://browser/skin/images/lock.png"); + background-repeat: no-repeat; + background-position: right 50%; + background-size: auto 60%; +} + +#new-pref-name { + width: 30em; } -#editor-container > hbox > label { - pointer-events: none; - color: black; +#new-pref-type { + display: inline-block !important; + border-left: 1px solid #d3d3d3; + width: 10em; + text-align: right; +} + +.pref-item-line { + border-top: 1px solid rgba(0,0,0,0.05); + color: rgba(0,0,0,0.5); + display: flex; + flex-direction: row; +} + +#new-pref-value-boolean { + flex: 1 1 auto; } -#editor + richlistitem { - display: none; +/* Disable newPref dialog spinbuttons, use custom version from Android */ +/* Filed Bug 962359 to enhance the default spinbutton style to be touch-friendly */ +#new-pref-value-int { + -moz-appearance: textfield; } -#editor[default="false"] .preferences-title { - font-weight: bold; +#new-pref-container .pref-button.toggle { + display: inline-block; + opacity: 1; + flex: 0 1 auto; + float: right; +} + +#new-pref-container .pref-button.cancel, +#new-pref-container .pref-button.create { + display: inline-block; + opacity: 1; + flex: 1 1 auto; +} + +.pref-item-line { + pointer-events: none; } -#editor-setting setting { - border-color: transparent !important; +#new-pref-container .pref-item-line, +.pref-item.selected .pref-item-line, +.pref-item:not(.selected) .pref-button.reset { + pointer-events: auto; +} + +#new-pref-container .pref-button.create[disabled] { + color: #d3d3d3; +} + +.pref-item.selected { + background-color: rgba(0,0,255,0.05); } -#editor-setting[type="string"] .setting-input { - -moz-box-flex: 4; +.pref-button { + display: inline-block; + -moz-box-sizing: border-box; + text-align: center; + padding: 10px 1em; + border-left: 1px solid rgba(0,0,0,0.1); + opacity: 0; + transition-property: opacity; + transition-duration: 500ms; } -#editor-setting[type="string"] .setting-input > textbox { - -moz-box-flex: 1; +.pref-item.selected .pref-item-line .pref-button { + opacity: 1; +} + +.pref-item:not(.selected) .pref-item-line .pref-button:not(.reset) { + display: none; +} + +.pref-item:not(.selected) .pref-button.reset { + opacity: 1; +} + +/* Disable detail list item spinbuttons, use custom version from Android */ +/* Filed Bug 962359 to enhance the default spinbutton style to be touch-friendly */ +.pref-item input[type="number"] { + -moz-appearance: textfield; } -/* bug 647650: keep 'text-align: right' here instead of using start/end since - * the field should looks like ltr as much as possible - */ -#editor-setting[type="string"] .setting-input > textbox:-moz-locale-dir(rtl) { - direction: ltr; - text-align: right; +.pref-button:active { + background-color: rgba(0,0,255,0.2); +} + +.pref-button[disabled] { + display: none; +} + +.pref-button.up { + background-image: url("chrome://browser/skin/images/arrowup-16.png"); + background-position: center center; + background-repeat: no-repeat; +} + +.pref-button.down { + background-image: url("chrome://browser/skin/images/arrowdown-16.png"); + background-position: center center; + background-repeat: no-repeat; } -#editor-buttons { - margin: 2px; +#prefs-shield { + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + position: fixed; + top: 0; + left: 0; + opacity: 0; + transition-property: opacity; + transition-duration: 500ms; + display: none; } +#prefs-shield[shown] { + display: block; + opacity: 1; +} + +#loading-container > li { + background-image: url(chrome://global/skin/media/throbber.png); + background-position: center center; + background-repeat: no-repeat; + padding-left: 40px; + height: 3em; + width: 100%; +}
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5ff3654d317252ce95b4d62b168cf9893dfe9487 GIT binary patch literal 636 zc%17D@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UOiAAEE)4(M`_JqL@;D1TB8!2v z2N=7Z%(epwmK8Xr!}Kz=|C%$Gfq}8b)5S5w<M`dH8}lv)h_F3)f6Fhbv%`hQK+*6y zr<O#dkx1uU_6Lkc3LDGUgk1Y#C=g_>7PlaHnqQ(5$4c+O&K%<#TV;-$zdR7smvYkT z`H%ho>;EmzmaA#`_oZfY)S;KPjI;WME9ZKN8cxtTq!W0G*XE#pw&S7~`ERW4HG1am z*s`VM<&g+QlQ-WCIK0ls&d^e+c(ZtWT9}h+ih8q^*s?rb8S8BY*;yCw?>MV4wK}tn z-_G;n(MvODhVS#7eb7ykMdsltOCip4u~%>EUjL<PbNEzmLay$PjfXDV3PqhRF89l5 zyA`r(-DSH5Eld5-+2OY3!TCmaLthz5Nc?^&z}(L>mE)hp1A)mux0bdvhP>N)eqC?D zlj_xGhpONA>@B?bZBh2)fHWo7v`Z^xPb_H*<Xo^~YI{`i?K@dYNAns^HDB11f9+72 z*|t;P`~EX5us(A3@8#&boEORxt*c^QFg^L9+sp98?e1@d*EN5xcE9@8?cwofh4uf? zE$3FfZ?4@Za-`0P-{c>Yk4OB<{aX&S0V77W#5JNMC9x#cD!C{XNHG{07#ivtnCKcB zh8S2{85&ucTIw2@TNxNszW-^7q9HdwB{QuOw+4<)j;%lqk{}Ji`DrEPiAAXljw$&` gsS2LCiRr09sfj6-g(p*OfQlGAUHx3vIVCg!08Rt~{{R30
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..abfdbec4b9a6a76418e2d311d739d9d1781436aa GIT binary patch literal 274 zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUto1QL?Ar-gY-caN_<RIYoP)R^Q zNRL6T$wkRPhN*ppV^NEio&(d$Lkk=aFnUyRu{2dP9M%u;y=;=Wxn#2Xyg7G&p6Tj6 zqw|6R1RQJ`x+`YpFG<T<G)qfsWm&_{D(&B8JiG-#(^d-$JqTiW!30*vP|y5g(mbhf z9c>05?Q?u{m`gI&#GSG!E#Tdu_4&qSQ-$DX#@8FxrXM(Kx?pWs*78eSwp`P0h0pN~ znV&8KG2sVe0iW!KbGr56y%QL8r>+Rey%ON6`f)EGM4II$yO!~@G#kdxq9CrPtDnm{ Hr-UW|1HD{P
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8bab39a08d350a1915c2fd1ae871333be1c9dd72 GIT binary patch literal 476 zc$@*;0VDp2P)<h;3K|Lk000e1NJLTq0015U0015c1^@s6J20-I0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzen~_@RCwCtm%nbpFc8KAVqmI52b3(G zSfdUMEmB{hec1LDDzzOLD%MOaq!|EZVru$XGLmaue0TXH1}uFNN}T)o&Uf}Ps_Qzs z#27~x8k6&nhsP&wZVaW+9J=w|RZt1-zCPcN67c98>Hs)_7SK%S%mK==fTAtfY(oa! zwT$K#+F${{nmwLEs}9h}e!GH0Qz_UyTSB)z<dCtk1bi$3XFWk(c*cQ~j^S+#JG@TF za<XILe}Uw9bxQ6fWSyC7Y!{TrF;_CKVa+=`W>`#2fm3R>zj6xTKMO=ArNHy1rH0@N z=s$*gfIM><!iAH|0c+z;eMy82OQ4-@Ckmj2wgO9%OAD^L?6DADQQ%SoHQ|+%nm0-c zywgB6bxtu{@X=o4>cc?@Ke*kApIrHX<&<+B$o&8})E3@;bLF&w_B}%P)+SrWrbk-$ z1UEL;RNvKffSjNY+px6`udxB>DcDyxcy9LPXNdN8A}~}~4b#wH&3~sq0R{jkX|XC4 S*loiA0000<MNUMnLSTZjHO+_s
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2369d03f336bb909b9c6d19d39b114737bdca59a GIT binary patch literal 858 zc%17D@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UOiAAEE)4(M`_JqL@;D1TB8!2v z2N=7Z%(epwmK8Xr!}Kz=|C%!wq|MXCF~sBe*{kPQhlYx<e>i{d_g%J6ZoEE*Cdz9$ z)|zuXUDe3p$SN3Qa-~u5riz7y_Z$;WOJ>n4^^X)ko|zc+rCHpgS)iV^X>!4Y8J89u z-240u+w++FFZWf?KOea6<jwTCd-uIQUcR>U$9`3xAhEf{KUElxdWf@1I)<IrW^7G; zw$f13eU`$iWzH>zpI2-<cKhDfMYnjl_nC`ay{vF(#`ja~em7Q>`F~pTVAbKeNy}UX zt)DL`I%0WH@%7s8b9(!3JaLy_z2x|n1s+<z&+dNW%->=Z%yN@kui~a(xJ#*}<bsS> zdynZ}H2eNFM3?*I0qu3aj?NPe(wP0cdS3aNT3hC=I;(lV-1)Ng?!A;CsgHpydzx8! zG^Z-(i|#uZt+ws%iVjn=-LKETJ1%6Dm;CyPQd#7RCI9!7|IAdH$#&t|gK1mMOd7?! z?+Ql0zaGY*G0WlnW92g@yOLfWV~oiYj%_^lz<hRYvAh3THxb@1f8WgcyU1qNB+p)v ztKWVcEPcQGZdrZE>)G4S*-cliv~)S0<2db3;bC<<)7SdUo45VG<hi-YT&F_AgzvZ5 zapf00m*)hRon+dSaIY}^%+8fX7sVfU_VgDAamH=G?!D=?-Y;3{n)L34lFMuF-IbUu z?iE%p=H>J@yqL|2`{GUhx$ok(tni<vx>EjkV9v_Zaa)6@g_K3Sj!tx9xx7U6=a%z4 zs?N6YmRc)MZ48^2c5ln&#PqWgt3KQ^l3IRx(Z3hZno7%NeC&7rml1YOi@{3ZV&(ZC zw-2USo8H~fC1vBK!tvImMO8{Zy4mg4*$JwiId!ICrw>I~PG^`e^i%xGi+vlocCk+c zCOg#<*NBpo#FA92<f2p{#b9J$XsByosB2^qVq|1xVs2$>tZQIlWni$xujnL-hTQy= z%(P0}8m<bTDg|ng1ZfD)Pb(=;EJ|f?Ovz75Rq)JBOiv9;O-!jQJeg_(RK(!v>gTe~ HDWM4fVe@hd
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..436853fa1ea73eb25a3b42f2bd1744fe7f523a17 GIT binary patch literal 166 zc%17D@N?(olHy`uVBq!ia0vp^%s|Y-!3HF~bz9j%oMI=>5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt;-)E{-7_ zGks5N<Yi#sIK1J)fBTaYQ=JYiUaqU2#dxJneLDkpb~6uaBLBUgK;;acu6{1-oD!M< D{>&{{
--- a/browser/metro/theme/jar.mn +++ b/browser/metro/theme/jar.mn @@ -145,8 +145,15 @@ chrome.jar: skin/images/autoscroll.png (images/autoscroll.png) skin/images/arrow-top.png (images/arrow-top.png) skin/images/arrow-top@1.4x.png (images/arrow-top@1.4x.png) skin/images/arrow-top@1.8x.png (images/arrow-top@1.8x.png) skin/images/arrow-left.png (images/arrow-left.png) skin/images/arrow-left@1.4x.png (images/arrow-left@1.4x.png) skin/images/arrow-left@1.8x.png (images/arrow-left@1.8x.png) + +# AboutConfig specific: + skin/images/textfield.png (images/textfield.png) + skin/images/reader-plus-icon-xhdpi.png (images/reader-plus-icon-xhdpi.png) + skin/images/search.png (images/search.png) + skin/images/search-clear-30.png (images/search-clear-30.png) + skin/images/lock.png (images/lock.png)
--- a/browser/themes/linux/customizableui/panelUIOverlay.css +++ b/browser/themes/linux/customizableui/panelUIOverlay.css @@ -2,8 +2,17 @@ * 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 ../../shared/customizableui/panelUIOverlay.inc.css #BMB_bookmarksPopup > menuitem[type="checkbox"] { -moz-appearance: none !important; /* important, to override toolkit rule */ } + +.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button { + -moz-appearance: none; + border: 0; +} + +.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker { + -moz-margin-start: 0; +}
--- a/browser/themes/osx/customizableui/panelUIOverlay.css +++ b/browser/themes/osx/customizableui/panelUIOverlay.css @@ -40,26 +40,29 @@ #PanelUI-customize:hover:active, #PanelUI-help:not([disabled]):hover:active, #PanelUI-quit:not([disabled]):hover:active { -moz-image-region: rect(0, 96px, 32px, 64px); } } -.panel-wide-item[cui-areatype="menu-panel"] > toolbarbutton, -toolbarbutton[cui-areatype="menu-panel"] { +.panelUI-grid .toolbarbutton-1 { margin-right: 0; margin-left: 0; margin-bottom: 0; } #BMB_bookmarksPopup > menu, #BMB_bookmarksPopup > menuitem { padding-top: 5px; padding-bottom: 5px; } /* Override OSX-specific toolkit styles for the bookmarks panel */ #BMB_bookmarksPopup > menuitem > .menu-accel-container, #BMB_bookmarksPopup > menu > .menu-right { -moz-margin-end: 0; } + +.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker { + -moz-margin-start: 4px; +}
--- a/browser/themes/shared/customizableui/customizeMode.inc.css +++ b/browser/themes/shared/customizableui/customizeMode.inc.css @@ -1,13 +1,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Customization mode */ +#main-window:-moz-any([customize-entering],[customize-exiting]) #tab-view-deck { + pointer-events: none; +} + #nav-bar[customize-entered] > #nav-bar-customization-target { margin: 1px 3px; } #nav-bar[customize-entered] > #nav-bar-customization-target, #PanelUI-contents > .panel-customization-placeholder { outline: 1px dashed transparent; }
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css +++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css @@ -63,24 +63,30 @@ box-shadow: none; } #PanelUI-contents { padding: .5em 0; } toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > .toolbarbutton-text, -#bookmarks-menu-button > toolbarbutton > .toolbarbutton-text, -:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarpaletteitem > toolbaritem > toolbarbutton > .toolbarbutton-text, -:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbaritem > toolbarbutton > .toolbarbutton-text, -:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarpaletteitem > toolbarbutton > .toolbarbutton-text, -:-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarbutton > .toolbarbutton-text { +.panelUI-grid .panel-combined-button > .toolbarbutton-text, +.widget-overflow-list .toolbarbutton-menubutton-button > .toolbarbutton-text, +.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-text { font-size: @panelTextSize@; } +.panelUI-grid .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text, +.panelUI-grid .toolbarbutton-1 > .toolbarbutton-multiline-text { + font-size: @panelTextSize@; + margin: 2px 0 0; + text-align: center; + -moz-hyphens: auto; +} + #wrapper-edit-controls:-moz-any([place="palette"],[place="panel"]) > #edit-controls, #wrapper-zoom-controls:-moz-any([place="palette"],[place="panel"]) > #zoom-controls { -moz-margin-start: 0; } #PanelUI-contents, .panel-mainview:not([panelid="PanelUI-popup"]) { max-width: @menuPanelWidth@; @@ -192,26 +198,36 @@ toolbarpaletteitem[place="palette"] > to #zoom-out-button > .toolbarbutton-text, #zoom-reset-button > .toolbarbutton-icon { display: none; } #PanelUI-footer { display: flex; background-color: rgba(0, 0, 0, 0.05); - border-top: 1px solid rgba(0,0,0,.1); + box-shadow: 0 -1px 0 rgba(0,0,0,.15); padding: 0; margin: 0; min-height: 4em; } +#PanelUI-footer > toolbarseparator { + border: 0; + border-left: 1px solid rgba(0,0,0,0.1); + margin: 7px 0 7px; +} + +#PanelUI-footer:hover > toolbarseparator { + margin: 0; +} + #PanelUI-help, #PanelUI-customize, #PanelUI-quit { - margin: -1px 0 0; + margin: 0; padding: 10px 0; -moz-appearance: none; box-shadow: none; background-image: none; border: 1px solid transparent; border-bottom-style: none; border-radius: 0; transition: background-color; @@ -274,24 +290,24 @@ toolbarpaletteitem[place="palette"] > to #PanelUI-help[disabled], #PanelUI-quit[disabled] { opacity: 0.4; } #PanelUI-help:not([disabled]):hover, #PanelUI-customize:hover, #PanelUI-quit:not([disabled]):hover { - border-color: rgba(8,25,42,0.2); - border-top-color: rgba(8,25,42,0.1); + outline: 1px solid rgba(0,0,0,0.1); background-color: rgba(0,0,0,0.1); box-shadow: none; } #PanelUI-quit:not([disabled]):hover { background-color: #d94141; + outline-color: #c23a3a; } #PanelUI-quit:not([disabled]):hover:active { background-color: #ad3434; } #main-window[customize-entered] #PanelUI-customize { color: white; @@ -300,16 +316,17 @@ toolbarpaletteitem[place="palette"] > to text-shadow: 0 1px 0 rgba(0,0,0,0.4); } #main-window[customize-entered] #PanelUI-customize:hover, #main-window[customize-entered] #PanelUI-customize:hover:active { background-image: linear-gradient(rgb(38,115,191), rgb(38,125,191)); } +#customization-palette .toolbarbutton-multiline-text, #customization-palette .toolbarbutton-text { display: none; } panelview toolbarbutton, #widget-overflow-list > toolbarbutton, .customizationmode-button, #edit-controls@inAnyPanel@ > toolbarbutton, @@ -527,16 +544,30 @@ toolbarpaletteitem[place="palette"] > #s -moz-padding-start: .5em; } #widget-overflow-list > #edit-controls, #widget-overflow-list > #zoom-controls { min-height: 28px; } +.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button::after { + content: ""; + display: -moz-box; + width: 1px; + height: 18px; + -moz-margin-end: -1px; + background-image: linear-gradient(hsla(210,54%,20%,.2) 0, hsla(210,54%,20%,.2) 18px); + background-clip: padding-box; + background-position: center; + background-repeat: no-repeat; + background-size: 1px 18px; + box-shadow: 0 0 0 1px hsla(0,0%,100%,.2); +} + #PanelUI-developerItems > toolbarbutton[checked="true"], #PanelUI-bookmarks > toolbarbutton[checked="true"], #PanelUI-history > toolbarbutton[checked="true"], .PanelUI-characterEncodingView-list > toolbarbutton[current] { -moz-padding-start: 4px; } #PanelUI-developerItems > toolbarbutton[checked="true"] > .toolbarbutton-text,
--- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -1370,17 +1370,17 @@ toolbarbutton[type="socialmark"] > .tool #bookmarks-menu-button.bookmark-item[starred] { -moz-image-region: rect(0px 48px 16px 32px); } #bookmarks-menu-button.bookmark-item > .toolbarbutton-menubutton-button > .toolbarbutton-icon { -moz-margin-start: 5px; } -#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon { +#bookmarks-menu-button[cui-areatype="toolbar"]:not(.bookmark-item):not(.overflowedItem) > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon { padding-top: 2px; padding-bottom: 2px; } #BMB_bookmarksPopup[side="top"], #BMB_bookmarksPopup[side="bottom"] { margin-left: -20px; margin-right: -20px;
--- a/browser/themes/windows/customizableui/panelUIOverlay.css +++ b/browser/themes/windows/customizableui/panelUIOverlay.css @@ -23,8 +23,24 @@ #BMB_bookmarksPopup > menu > .menu-text, #BMB_bookmarksPopup > menuitem > .menu-text, #BMB_bookmarksPopup > menu > .menu-iconic-text, #BMB_bookmarksPopup > menuitem > .menu-iconic-text, #BMB_bookmarksPopup > menuseparator { padding-top: 0; padding-bottom: 0; } + +.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button { + -moz-appearance: none; + border: 0; + -moz-margin-start: 3px; +} + +.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker { + padding: 0 2px; + -moz-padding-start: 0; + height: 18px; +} + +.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon { + padding: 0 6px; +}
--- a/configure.in +++ b/configure.in @@ -1346,22 +1346,24 @@ if test "$GNU_CC"; then AC_MSG_RESULT([no])) CFLAGS=$_SAVE_CFLAGS # Turn on GNU-specific warnings: # -Wall - turn on a lot of warnings # -Wpointer-arith - good to have # -Wdeclaration-after-statement - MSVC doesn't like these # -Werror=return-type - catches missing returns, zero false positives + # -Werror=int-to-pointer-cast - catches cast to pointer from integer of different size # -Wtype-limits - catches overflow bugs, few false positives # -Wempty-body - catches bugs, e.g. "if (c); foo();", few false positives # -Wsign-compare - catches comparison of signed and unsigned types # _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wall -Wpointer-arith -Wdeclaration-after-statement" MOZ_C_SUPPORTS_WARNING(-W, error=return-type, ac_c_has_werror_return_type) + MOZ_C_SUPPORTS_WARNING(-W, error=int-to-pointer-cast, ac_c_has_werror_int_to_pointer_cast) MOZ_C_SUPPORTS_WARNING(-W, type-limits, ac_c_has_wtype_limits) MOZ_C_SUPPORTS_WARNING(-W, empty-body, ac_c_has_wempty_body) MOZ_C_SUPPORTS_WARNING(-W, sign-compare, ac_c_has_sign_compare) # Turn off the following warnings that -Wall turns on: # -Wno-unused - lots of violations in third-party code # _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wno-unused" @@ -1410,22 +1412,24 @@ if test "$GNU_CXX"; then # FIXME: Let us build with strict aliasing. bug 414641. CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-strict-aliasing" # Turn on GNU-specific warnings: # -Wall - turn on a lot of warnings # -Wpointer-arith - good to have # -Woverloaded-virtual - ??? # -Werror=return-type - catches missing returns, zero false positives + # -Werror=int-to-pointer-cast - catches cast to pointer from integer of different size # -Wtype-limits - catches overflow bugs, few false positives # -Wempty-body - catches bugs, e.g. "if (c); foo();", few false positives # -Wsign-compare - catches comparison of signed and unsigned types # _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wall -Wpointer-arith -Woverloaded-virtual" MOZ_CXX_SUPPORTS_WARNING(-W, error=return-type, ac_cxx_has_werror_return_type) + MOZ_CXX_SUPPORTS_WARNING(-W, error=int-to-pointer-cast, ac_cxx_has_werror_int_to_pointer_cast) MOZ_CXX_SUPPORTS_WARNING(-W, type-limits, ac_cxx_has_wtype_limits) MOZ_CXX_SUPPORTS_WARNING(-W, empty-body, ac_cxx_has_wempty_body) MOZ_CXX_SUPPORTS_WARNING(-W, sign-compare, ac_cxx_has_sign_compare) # Turn off the following warnings that -Wall turns on: # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently # MOZ_CXX_SUPPORTS_WARNING(-Wno-, invalid-offsetof, ac_cxx_has_wno_invalid_offsetof) @@ -7737,18 +7741,24 @@ else dnl Don't override this for MSVC if test -z "$_WIN32_MSVC"; then _USE_CPP_INCLUDE_FLAG= _DEFINES_CFLAGS='$(ACDEFINES) -D_MOZILLA_CONFIG_H_ -DMOZILLA_CLIENT' _DEFINES_CXXFLAGS='$(ACDEFINES) -D_MOZILLA_CONFIG_H_ -DMOZILLA_CLIENT' else echo '#include <stdio.h>' > dummy-hello.c changequote(,) - CL_INCLUDES_PREFIX=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/^\([^:]*:[^:]*:\).*stdio.h$/\1/p'` + dnl This output is localized, split at the first double space or colon and space. + _CL_PREFIX_REGEX="^\([^:]*:.*[ :] \)\(.*stdio.h\)$" + CL_INCLUDES_PREFIX=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/'"$_CL_PREFIX_REGEX"'/\1/p'` + _CL_STDIO_PATH=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/'"$_CL_PREFIX_REGEX"'/\2/p'` changequote([,]) + if ! test -e "$_CL_STDIO_PATH"; then + AC_MSG_ERROR([Unable to parse cl -showIncludes prefix. This compiler's locale has an unsupported formatting.]) + fi if test -z "$CL_INCLUDES_PREFIX"; then AC_MSG_ERROR([Cannot find cl -showIncludes prefix.]) fi AC_SUBST(CL_INCLUDES_PREFIX) rm -f dummy-hello.c fi fi
--- a/content/canvas/src/WebGLQuery.cpp +++ b/content/canvas/src/WebGLQuery.cpp @@ -35,16 +35,16 @@ void WebGLQuery::Delete() { } bool WebGLQuery::IsActive() const { WebGLRefPtr<WebGLQuery>* targetSlot = mContext->GetQueryTargetSlot(mType, "WebGLQuery::IsActive()"); MOZ_ASSERT(targetSlot, "unknown query object's type"); - return *targetSlot == this; + return targetSlot && *targetSlot == this; } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQuery) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLQuery, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLQuery, Release)
--- a/dom/apps/src/AppsUtils.jsm +++ b/dom/apps/src/AppsUtils.jsm @@ -624,16 +624,35 @@ ManifestHelper.prototype = { get permissions() { if (this._manifest.permissions) { return this._manifest.permissions; } return {}; }, + get biggestIconURL() { + let icons = this._localeProp("icons"); + if (!icons) { + return null; + } + + let iconSizes = Object.keys(icons); + if (iconSizes.length == 0) { + return null; + } + + iconSizes.sort((a, b) => a - b); + let biggestIconSize = iconSizes.pop(); + let biggestIcon = icons[biggestIconSize]; + let biggestIconURL = this._origin.resolve(biggestIcon); + + return biggestIconURL; + }, + iconURLForSize: function(aSize) { let icons = this._localeProp("icons"); if (!icons) return null; let dist = 100000; let icon = null; for (let size in icons) { let iSize = parseInt(size);
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -2541,17 +2541,18 @@ nsGlobalWindow::SetNewDocument(nsIDocume if (newInnerWindow->mDoc != aDocument) { newInnerWindow->mDoc = aDocument; // We're reusing the inner window for a new document. In this // case we don't clear the inner window's scope, but we must // make sure the cached document property gets updated. // XXXmarkh - tell other languages about this? - ::JS_DeleteProperty(cx, currentInner->mJSObject, "document"); + JS::Rooted<JSObject*> obj(cx, currentInner->mJSObject); + ::JS_DeleteProperty(cx, obj, "document"); } } else { newInnerWindow->InnerSetNewDocument(aDocument); // Initialize DOM classes etc on the inner window. JS::Rooted<JSObject*> obj(cx, newInnerWindow->mJSObject); rv = mContext->InitClasses(obj); NS_ENSURE_SUCCESS(rv, rv); @@ -10860,17 +10861,19 @@ nsGlobalWindow::Observe(nsISupports* aSu { case nsPIDOMStorage::SessionStorage: { bool check = false; nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell()); if (storageManager) { rv = storageManager->CheckStorage(principal, changingStorage, &check); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + return rv; + } } if (!check) { // This storage event is not coming from our storage or is coming // from a different docshell, i.e. it is a clone, ignore this event. return NS_OK; }
--- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -2345,12 +2345,42 @@ CreateGlobal(JSContext* aCx, T* aObject, return nullptr; } mozilla::HoldJSObjects(aObject); return global; } +/* + * Holds a jsid that is initialized to an interned string, with conversion to + * Handle<jsid>. + */ +class InternedStringId +{ + jsid id; + + public: + InternedStringId() : id(JSID_VOID) {} + + bool init(JSContext *cx, const char *string) { + MOZ_ASSERT(id == JSID_VOID); + JSString* str = JS_InternString(cx, string); + if (!str) + return false; + id = INTERNED_STRING_TO_JSID(cx, str); + return true; + } + + operator const jsid& () { + return id; + } + + operator JS::Handle<jsid> () { + /* This is safe because we have interned the string. */ + return JS::Handle<jsid>::fromMarkedLocation(&id); + } +}; + } // namespace dom } // namespace mozilla #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/DOMJSProxyHandler.cpp +++ b/dom/bindings/DOMJSProxyHandler.cpp @@ -33,17 +33,18 @@ DefineStaticJSVals(JSContext* cx) const char HandlerFamily = 0; js::DOMProxyShadowsResult DOMProxyShadows(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) { JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO); if (v.isObject()) { bool hasOwn; - if (!JS_AlreadyHasOwnPropertyById(cx, &v.toObject(), id, &hasOwn)) + Rooted<JSObject*> object(cx, &v.toObject()); + if (!JS_AlreadyHasOwnPropertyById(cx, object, id, &hasOwn)) return js::ShadowCheckFailed; return hasOwn ? js::Shadows : js::DoesntShadow; } if (v.isUndefined()) { return js::DoesntShadow; }
--- a/dom/datastore/DataStoreChangeNotifier.jsm +++ b/dom/datastore/DataStoreChangeNotifier.jsm @@ -63,18 +63,22 @@ this.DataStoreChangeNotifier = { obj.mm.sendAsyncMessage(aMsgName, aData.message); } }); }, receiveMessage: function(aMessage) { debug("receiveMessage"); + // No check has to be done when the message is 'child-process-shutdown' + // because at this point the target is already disconnected from + // nsFrameMessageManager, so that assertAppHasStatus will always fail. let prefName = 'dom.testing.datastore_enabled_for_hosted_apps'; - if ((Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID || + if (aMessage.name != 'child-process-shutdown' && + (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID || !Services.prefs.getBoolPref(prefName)) && !aMessage.target.assertAppHasStatus(Ci.nsIPrincipal.APP_STATUS_CERTIFIED)) { return; } switch (aMessage.name) { case "DataStore:Changed": this.broadcastMessage("DataStore:Changed:Return:OK", aMessage.data);
--- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -706,17 +706,17 @@ nsJSObjWrapper::NP_InvokeDefault(NPObjec uint32_t argCount, NPVariant *result) { return doInvoke(npobj, NPIdentifier_VOID, args, argCount, false, result); } // static bool -nsJSObjWrapper::NP_HasProperty(NPObject *npobj, NPIdentifier id) +nsJSObjWrapper::NP_HasProperty(NPObject *npobj, NPIdentifier npid) { NPP npp = NPPStack::Peek(); JSContext *cx = GetJSContext(npp); if (!cx) { return false; } @@ -728,21 +728,23 @@ nsJSObjWrapper::NP_HasProperty(NPObject } nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; bool found, ok = false; nsCxPusher pusher; pusher.Push(cx); AutoJSExceptionReporter reporter(cx); - JSAutoCompartment ac(cx, npjsobj->mJSObj); - - NS_ASSERTION(NPIdentifierIsInt(id) || NPIdentifierIsString(id), + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), "id must be either string or int!\n"); - ok = ::JS_HasPropertyById(cx, npjsobj->mJSObj, NPIdentifierToJSId(id), &found); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_HasPropertyById(cx, jsobj, id, &found); return ok && found; } // static bool nsJSObjWrapper::NP_GetProperty(NPObject *npobj, NPIdentifier id, NPVariant *result) { @@ -805,17 +807,17 @@ nsJSObjWrapper::NP_SetProperty(NPObject "id must be either string or int!\n"); ok = ::JS_SetPropertyById(cx, npjsobj->mJSObj, NPIdentifierToJSId(id), v); return ok; } // static bool -nsJSObjWrapper::NP_RemoveProperty(NPObject *npobj, NPIdentifier id) +nsJSObjWrapper::NP_RemoveProperty(NPObject *npobj, NPIdentifier npid) { NPP npp = NPPStack::Peek(); JSContext *cx = GetJSContext(npp); if (!cx) { return false; } @@ -828,27 +830,29 @@ nsJSObjWrapper::NP_RemoveProperty(NPObje nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; bool ok = false; nsCxPusher pusher; pusher.Push(cx); AutoJSExceptionReporter reporter(cx); bool deleted = false; - JSAutoCompartment ac(cx, npjsobj->mJSObj); - - NS_ASSERTION(NPIdentifierIsInt(id) || NPIdentifierIsString(id), + JS::Rooted<JSObject*> obj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, obj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), "id must be either string or int!\n"); - ok = ::JS_DeletePropertyById2(cx, npjsobj->mJSObj, NPIdentifierToJSId(id), &deleted); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_DeletePropertyById2(cx, obj, id, &deleted); if (ok && deleted) { // FIXME: See bug 425823, we shouldn't need to do this, and once // that bug is fixed we can remove this code. bool hasProp; - ok = ::JS_HasPropertyById(cx, npjsobj->mJSObj, NPIdentifierToJSId(id), &hasProp); + ok = ::JS_HasPropertyById(cx, obj, id, &hasProp); if (ok && hasProp) { // The property might have been deleted, but it got // re-resolved, so no, it's not really deleted. deleted = false; } }
--- a/dom/src/storage/DOMStorageManager.cpp +++ b/dom/src/storage/DOMStorageManager.cpp @@ -452,17 +452,19 @@ DOMStorageManager::CheckStorage(nsIPrinc *aRetval = false; if (!aPrincipal) { return NS_ERROR_NOT_AVAILABLE; } nsAutoCString scope; nsresult rv = CreateScopeKey(aPrincipal, scope); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + return rv; + } DOMStorageCache* cache = GetCache(scope); if (cache != pstorage->GetCache()) { return NS_OK; } if (!pstorage->PrincipalEquals(aPrincipal)) { return NS_OK;
--- a/dom/system/OSFileConstants.cpp +++ b/dom/system/OSFileConstants.cpp @@ -23,16 +23,17 @@ #endif // defined(XP_LINUX) #if defined(XP_MACOSX) #include "copyfile.h" #endif // defined(XP_MACOSX) #if defined(XP_WIN) #include <windows.h> +#include <accctrl.h> #endif // defined(XP_WIN) #include "jsapi.h" #include "jsfriendapi.h" #include "BindingUtils.h" // Used to provide information on the OS @@ -675,16 +676,21 @@ static const dom::ConstantSpec gWinPrope // MoveFile flags INT_CONSTANT(MOVEFILE_COPY_ALLOWED), INT_CONSTANT(MOVEFILE_REPLACE_EXISTING), // GetFileAttributes error constant INT_CONSTANT(INVALID_FILE_ATTRIBUTES), + // GetNamedSecurityInfo and SetNamedSecurityInfo constants + INT_CONSTANT(UNPROTECTED_DACL_SECURITY_INFORMATION), + INT_CONSTANT(SE_FILE_OBJECT), + INT_CONSTANT(DACL_SECURITY_INFORMATION), + // Errors INT_CONSTANT(ERROR_ACCESS_DENIED), INT_CONSTANT(ERROR_DIR_NOT_EMPTY), INT_CONSTANT(ERROR_FILE_EXISTS), INT_CONSTANT(ERROR_ALREADY_EXISTS), INT_CONSTANT(ERROR_FILE_NOT_FOUND), INT_CONSTANT(ERROR_NO_MORE_FILES), INT_CONSTANT(ERROR_PATH_NOT_FOUND),
--- a/gfx/layers/composite/APZCTreeManager.cpp +++ b/gfx/layers/composite/APZCTreeManager.cpp @@ -315,17 +315,19 @@ APZCTreeManager::ReceiveInputEvent(const { nsEventStatus result = nsEventStatus_eIgnore; gfx3DMatrix transformToApzc; gfx3DMatrix transformToGecko; switch (aEvent.mInputType) { case MULTITOUCH_INPUT: { const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) { - mTouchCount++; + // MULTITOUCH_START input contains all active touches of the current + // session thus resetting mTouchCount. + mTouchCount = multiTouchInput.mTouches.Length(); mApzcForInputBlock = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint)); if (multiTouchInput.mTouches.Length() == 1) { // If we have one touch point, this might be the start of a pan. // Prepare for possible overscroll handoff. BuildOverscrollHandoffChain(mApzcForInputBlock); } for (size_t i = 1; i < multiTouchInput.mTouches.Length(); i++) { nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[i].mScreenPoint)); @@ -358,16 +360,17 @@ APZCTreeManager::ReceiveInputEvent(const for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) { ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc); } result = mApzcForInputBlock->ReceiveInputEvent(inputForApzc); } if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL || multiTouchInput.mType == MultiTouchInput::MULTITOUCH_END) { if (mTouchCount >= multiTouchInput.mTouches.Length()) { + // MULTITOUCH_END input contains only released touches thus decrementing. mTouchCount -= multiTouchInput.mTouches.Length(); } else { NS_WARNING("Got an unexpected touchend/touchcancel"); mTouchCount = 0; } // If we have an mApzcForInputBlock and it's the end of the touch sequence // then null it out so we don't keep a dangling reference and leak things. if (mTouchCount == 0) { @@ -433,17 +436,19 @@ APZCTreeManager::ProcessTouchEvent(const { MOZ_ASSERT(NS_IsMainThread()); nsEventStatus ret = nsEventStatus_eIgnore; if (!aEvent.touches.Length()) { return ret; } if (aEvent.message == NS_TOUCH_START) { - mTouchCount++; + // NS_TOUCH_START event contains all active touches of the current + // session thus resetting mTouchCount. + mTouchCount = aEvent.touches.Length(); mApzcForInputBlock = GetTouchInputBlockAPZC(aEvent); if (mApzcForInputBlock) { // Cache apz transform so it can be used for future events in this block. gfx3DMatrix transformToGecko; GetInputTransforms(mApzcForInputBlock, mCachedTransformToApzcForInputBlock, transformToGecko); } else { // Reset the cached apz transform mCachedTransformToApzcForInputBlock = gfx3DMatrix(); @@ -472,16 +477,17 @@ APZCTreeManager::ProcessTouchEvent(const ApplyTransform(&(aOutEvent->touches[i]->mRefPoint), outTransform); } } // If we have an mApzcForInputBlock and it's the end of the touch sequence // then null it out so we don't keep a dangling reference and leak things. if (aEvent.message == NS_TOUCH_CANCEL || aEvent.message == NS_TOUCH_END) { if (mTouchCount >= aEvent.touches.Length()) { + // NS_TOUCH_END event contains only released touches thus decrementing. mTouchCount -= aEvent.touches.Length(); } else { NS_WARNING("Got an unexpected touchend/touchcancel"); mTouchCount = 0; } if (mTouchCount == 0) { mApzcForInputBlock = nullptr; mOverscrollHandoffChain.clear();
--- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -536,40 +536,62 @@ AsyncCompositionManager::ApplyAsyncConte } if (container->GetScrollbarDirection() != Layer::NONE) { ApplyAsyncTransformToScrollbar(container); } return appliedTransform; } +static bool +LayerHasNonContainerDescendants(ContainerLayer* aContainer) +{ + for (Layer* child = aContainer->GetFirstChild(); + child; child = child->GetNextSibling()) { + ContainerLayer* container = child->AsContainerLayer(); + if (!container || LayerHasNonContainerDescendants(container)) { + return true; + } + } + + return false; +} + void AsyncCompositionManager::ApplyAsyncTransformToScrollbar(ContainerLayer* aLayer) { // If this layer corresponds to a scrollbar, then search backwards through the // siblings until we find the container layer with the right ViewID; this is // the content that this scrollbar is for. Pick up the transient async transform // from that layer and use it to update the scrollbar position. // Note that it is possible that the content layer is no longer there; in // this case we don't need to do anything because there can't be an async // transform on the content. + // We only apply the transform if the scroll-target layer has non-container + // children (i.e. when it has some possibly-visible content). This is to + // avoid moving scroll-bars in the situation that only a scroll information + // layer has been built for a scroll frame, as this would result in a + // disparity between scrollbars and visible content. for (Layer* scrollTarget = aLayer->GetPrevSibling(); scrollTarget; scrollTarget = scrollTarget->GetPrevSibling()) { if (!scrollTarget->AsContainerLayer()) { continue; } AsyncPanZoomController* apzc = scrollTarget->AsContainerLayer()->GetAsyncPanZoomController(); if (!apzc) { continue; } const FrameMetrics& metrics = scrollTarget->AsContainerLayer()->GetFrameMetrics(); if (metrics.mScrollId != aLayer->GetScrollbarTargetContainerId()) { continue; } + if (!LayerHasNonContainerDescendants(scrollTarget->AsContainerLayer())) { + return; + } gfx3DMatrix asyncTransform = gfx3DMatrix(apzc->GetCurrentAsyncTransform()); gfx3DMatrix nontransientTransform = apzc->GetNontransientAsyncTransform(); gfx3DMatrix transientTransform = asyncTransform * nontransientTransform.Inverse(); gfx3DMatrix scrollbarTransform; if (aLayer->GetScrollbarDirection() == Layer::VERTICAL) { float scale = metrics.CalculateCompositedRectInCssPixels().height / metrics.mScrollableRect.height;
--- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -1547,24 +1547,32 @@ CompositorOGL::NotifyLayersTransaction() mFPS->NotifyShadowTreeTransaction(); } } void CompositorOGL::Pause() { #ifdef MOZ_WIDGET_ANDROID + if (!gl() || gl()->IsDestroyed()) + return; + + // ReleaseSurface internally calls MakeCurrent. gl()->ReleaseSurface(); #endif } bool CompositorOGL::Resume() { #ifdef MOZ_WIDGET_ANDROID + if (!gl() || gl()->IsDestroyed()) + return false; + + // RenewSurface internally calls MakeCurrent. return gl()->RenewSurface(); #endif return true; } TemporaryRef<DataTextureSource> CompositorOGL::CreateDataTextureSource(TextureFlags aFlags) {
--- a/intl/uconv/ucvcn/nsISO2022CNToUnicode.h +++ b/intl/uconv/ucvcn/nsISO2022CNToUnicode.h @@ -13,27 +13,26 @@ #define PMASK 0xa0 #define SI 0x0f #define SO 0x0e #define ESC 0x1b #define SS2 0x4e #define SS3 0x4f -using namespace mozilla; - class nsISO2022CNToUnicode : public nsBasicDecoderSupport { public: nsISO2022CNToUnicode() : mState(eState_ASCII), mPlaneID(0), mRunLength(0) { - Telemetry::Accumulate(Telemetry::DECODER_INSTANTIATED_ISO2022CN, true); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::DECODER_INSTANTIATED_ISO2022CN, true); } virtual ~nsISO2022CNToUnicode() {} NS_IMETHOD Convert(const char *aSrc, int32_t * aSrcLength, char16_t * aDest, int32_t * aDestLength) ; NS_IMETHOD GetMaxLength(const char * aSrc, int32_t aSrcLength,
--- a/intl/uconv/ucvja/nsJapaneseToUnicode.h +++ b/intl/uconv/ucvja/nsJapaneseToUnicode.h @@ -2,18 +2,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef nsShiftJISToUnicode_h__ #define nsShiftJISToUnicode_h__ #include "nsUCSupport.h" #include "mozilla/Telemetry.h" -using namespace mozilla; - class nsShiftJISToUnicode : public nsBasicDecoderSupport { public: nsShiftJISToUnicode() { mState=0; mData=0; } @@ -80,17 +78,18 @@ public: mState = mState_ASCII; mLastLegalState = mState_ASCII; mData = 0; mRunLength = 0; G2charset = G2_unknown; mGB2312Decoder = nullptr; mEUCKRDecoder = nullptr; mISO88597Decoder = nullptr; - Telemetry::Accumulate(Telemetry::DECODER_INSTANTIATED_ISO2022JP, true); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::DECODER_INSTANTIATED_ISO2022JP, true); } virtual ~nsISO2022JPToUnicodeV2() { NS_IF_RELEASE(mGB2312Decoder); NS_IF_RELEASE(mEUCKRDecoder); NS_IF_RELEASE(mISO88597Decoder); }
--- a/intl/uconv/ucvko/nsISO2022KRToUnicode.h +++ b/intl/uconv/ucvko/nsISO2022KRToUnicode.h @@ -2,29 +2,28 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef nsISO2022KRToUnicode_h__ #define nsISO2022KRToUnicode_h__ #include "nsUCSupport.h" #include "mozilla/Telemetry.h" -using namespace mozilla; - class nsISO2022KRToUnicode : public nsBasicDecoderSupport { public: nsISO2022KRToUnicode() { mState = mState_Init; mLastLegalState = mState_ASCII; mData = 0; mEUCKRDecoder = nullptr; mRunLength = 0; - Telemetry::Accumulate(Telemetry::DECODER_INSTANTIATED_ISO2022KR, true); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::DECODER_INSTANTIATED_ISO2022KR, true); } virtual ~nsISO2022KRToUnicode() { NS_IF_RELEASE(mEUCKRDecoder); } NS_IMETHOD Convert(const char * aSrc, int32_t * aSrcLength,
--- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -133,16 +133,17 @@ class HeapBase {}; */ struct NullPtr { static void * const constNullValue; }; namespace gc { struct Cell; +struct PersistentRootedMarker; } /* namespace gc */ } /* namespace js */ namespace JS { template <typename T> class Rooted; template <typename T> class PersistentRooted; @@ -1093,17 +1094,16 @@ template <typename T> inline MutableHandle<T>::MutableHandle(PersistentRooted<T> *root) { static_assert(sizeof(MutableHandle<T>) == sizeof(T *), "MutableHandle must be binary compatible with T*."); ptr = root->address(); } - /* * A copyable, assignable global GC root type with arbitrary lifetime, an * infallible constructor, and automatic unrooting on destruction. * * These roots can be used in heap-allocated data structures, so they are not * associated with any particular JSContext or stack. They are registered with * the JSRuntime itself, without locking, so they require a full JSContext to be * constructed, not one of its more restricted superclasses. @@ -1127,19 +1127,20 @@ MutableHandle<T>::MutableHandle(Persiste * In the context of Firefox, this is a severe restriction: almost everything in * Firefox is owned by some JS object or another, so using PersistentRooted in * such objects would introduce leaks. For these kinds of edges, Heap<T> or * TenuredHeap<T> would be better types. It's up to the implementor of the type * containing Heap<T> or TenuredHeap<T> members to make sure their referents get * marked when the object itself is marked. */ template<typename T> -class PersistentRooted : public mozilla::LinkedListElement<PersistentRooted<T> > { - typedef mozilla::LinkedList<PersistentRooted> List; - typedef mozilla::LinkedListElement<PersistentRooted> Element; +class PersistentRooted : private mozilla::LinkedListElement<PersistentRooted<T> > { + friend class mozilla::LinkedList<PersistentRooted>; + friend class mozilla::LinkedListElement<PersistentRooted>; + friend class js::gc::PersistentRootedMarker; void registerWithRuntime(JSRuntime *rt) { JS::shadow::Runtime *srt = JS::shadow::Runtime::asShadowRuntime(rt); srt->getPersistentRootedList<T>().insertBack(this); } public: PersistentRooted(JSContext *cx) : ptr(js::GCMethods<T>::initial())
--- a/js/src/configure.in +++ b/js/src/configure.in @@ -1132,22 +1132,24 @@ if test "$GNU_CC"; then AC_MSG_RESULT([no]) LDFLAGS=$_SAVE_LDFLAGS) # Turn on GNU-specific warnings: # -Wall - turn on a lot of warnings # -Wpointer-arith - good to have # -Wdeclaration-after-statement - MSVC doesn't like these # -Werror=return-type - catches missing returns, zero false positives + # -Werror=int-to-pointer-cast - catches cast to pointer from integer of different size # -Wtype-limits - catches overflow bugs, few false positives # -Wempty-body - catches bugs, e.g. "if (c); foo();", few false positives # -Wsign-compare - catches comparison of signed and unsigned types # _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wall -Wpointer-arith -Wdeclaration-after-statement" MOZ_C_SUPPORTS_WARNING(-W, error=return-type, ac_c_has_werror_return_type) + MOZ_C_SUPPORTS_WARNING(-W, error=int-to-pointer-cast, ac_c_has_werror_int_to_pointer_cast) MOZ_C_SUPPORTS_WARNING(-W, empty-body, ac_c_has_wempty_body) MOZ_C_SUPPORTS_WARNING(-W, sign-compare, ac_c_has_sign_compare) # Turn off the following warnings that -Wall turns on: # -Wno-unused - lots of violations in third-party code # _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wno-unused" @@ -1191,23 +1193,25 @@ else fi if test "$GNU_CXX"; then # Turn on GNU-specific warnings: # -Wall - turn on a lot of warnings # -Wpointer-arith - good to have # -Woverloaded-virtual - ??? # -Werror=return-type - catches missing returns, zero false positives + # -Werror=int-to-pointer-cast - catches cast to pointer from integer of different size # -Wtype-limits - catches overflow bugs, few false positives # -Wempty-body - catches bugs, e.g. "if (c); foo();", few false positives # -Werror=conversion-null - catches conversions between NULL and non-pointer types # -Wsign-compare - catches comparison of signed and unsigned types # _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wall -Wpointer-arith -Woverloaded-virtual" MOZ_CXX_SUPPORTS_WARNING(-W, error=return-type, ac_cxx_has_werror_return_type) + MOZ_CXX_SUPPORTS_WARNING(-W, error=int-to-pointer-cast, ac_cxx_has_werror_int_to_pointer_cast) MOZ_CXX_SUPPORTS_WARNING(-W, type-limits, ac_cxx_has_wtype_limits) MOZ_CXX_SUPPORTS_WARNING(-W, empty-body, ac_cxx_has_wempty_body) MOZ_CXX_SUPPORTS_WARNING(-W, error=conversion-null, ac_cxx_has_werror_conversion_null) MOZ_CXX_SUPPORTS_WARNING(-W, sign-compare, ac_cxx_has_sign_compare) # Turn off the following warnings that -Wall turns on: # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently # @@ -3815,18 +3819,24 @@ else dnl Don't override this for MSVC if test -z "$_WIN32_MSVC"; then _USE_CPP_INCLUDE_FLAG= _DEFINES_CFLAGS='$(ACDEFINES) -D_JS_CONFDEFS_H_ -DMOZILLA_CLIENT' _DEFINES_CXXFLAGS='$(ACDEFINES) -D_JS_CONFDEFS_H_ -DMOZILLA_CLIENT' else echo '#include <stdio.h>' > dummy-hello.c changequote(,) - CL_INCLUDES_PREFIX=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/^\([^:]*:[^:]*:\).*stdio.h$/\1/p'` + dnl This output is localized, split at the first double space or colon and space. + _CL_PREFIX_REGEX="^\([^:]*:.*[ :] \)\(.*stdio.h\)$" + CL_INCLUDES_PREFIX=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/'"$_CL_PREFIX_REGEX"'/\1/p'` + _CL_STDIO_PATH=`${CC} -showIncludes -c -Fonul dummy-hello.c 2>&1 | sed -ne 's/'"$_CL_PREFIX_REGEX"'/\2/p'` changequote([,]) + if ! test -e "$_CL_STDIO_PATH"; then + AC_MSG_ERROR([Unable to parse cl -showIncludes prefix. This compiler's locale has an unsupported formatting.]) + fi if test -z "$CL_INCLUDES_PREFIX"; then AC_MSG_ERROR([Cannot find cl -showIncludes prefix.]) fi AC_SUBST(CL_INCLUDES_PREFIX) rm -f dummy-hello.c fi fi
--- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -613,52 +613,67 @@ JSPropertyDescriptor::trace(JSTracer *tr } if ((attrs & JSPROP_SETTER) && setter) { JSObject *tmp = JS_FUNC_TO_DATA_PTR(JSObject *, setter); MarkObjectRoot(trc, &tmp, "Descriptor::set"); setter = JS_DATA_TO_FUNC_PTR(JSStrictPropertyOp, tmp); } } -// Mark a chain of PersistentRooted pointers that might be null. -template<typename Referent> -static void -MarkPersistentRootedChain(JSTracer *trc, - mozilla::LinkedList<PersistentRooted<Referent *> > &list, - void (*marker)(JSTracer *trc, Referent **ref, const char *name), - const char *name) +namespace js { +namespace gc { +struct PersistentRootedMarker { - for (PersistentRooted<Referent *> *r = list.getFirst(); - r != nullptr; - r = r->getNext()) + template<typename Referent> + static void + markChainIfNotNull(JSTracer *trc, + mozilla::LinkedList<PersistentRooted<Referent *> > &list, + void (*marker)(JSTracer *trc, Referent **ref, const char *name), + const char *name) { - if (r->get()) + for (PersistentRooted<Referent *> *r = list.getFirst(); r; r = r->getNext()) { + if (r->get()) + marker(trc, r->address(), name); + } + } + + template<typename Referent> + static void + markChain(JSTracer *trc, + mozilla::LinkedList<PersistentRooted<Referent> > &list, + void (*marker)(JSTracer *trc, Referent *ref, const char *name), + const char *name) + { + for (PersistentRooted<Referent> *r = list.getFirst(); r; r = r->getNext()) marker(trc, r->address(), name); } +}; +} } void js::gc::MarkPersistentRootedChains(JSTracer *trc) { JSRuntime *rt = trc->runtime; - MarkPersistentRootedChain(trc, rt->functionPersistentRooteds, &MarkObjectRoot, - "PersistentRooted<JSFunction *>"); - MarkPersistentRootedChain(trc, rt->objectPersistentRooteds, &MarkObjectRoot, - "PersistentRooted<JSObject *>"); - MarkPersistentRootedChain(trc, rt->scriptPersistentRooteds, &MarkScriptRoot, - "PersistentRooted<JSScript *>"); - MarkPersistentRootedChain(trc, rt->stringPersistentRooteds, &MarkStringRoot, - "PersistentRooted<JSString *>"); + // Mark the PersistentRooted chains of types that may be null. + PersistentRootedMarker::markChainIfNotNull(trc, rt->functionPersistentRooteds, &MarkObjectRoot, + "PersistentRooted<JSFunction *>"); + PersistentRootedMarker::markChainIfNotNull(trc, rt->objectPersistentRooteds, &MarkObjectRoot, + "PersistentRooted<JSObject *>"); + PersistentRootedMarker::markChainIfNotNull(trc, rt->scriptPersistentRooteds, &MarkScriptRoot, + "PersistentRooted<JSScript *>"); + PersistentRootedMarker::markChainIfNotNull(trc, rt->stringPersistentRooteds, &MarkStringRoot, + "PersistentRooted<JSString *>"); // Mark the PersistentRooted chains of types that are never null. - for (JS::PersistentRootedId *r = rt->idPersistentRooteds.getFirst(); r != nullptr; r = r->getNext()) - MarkIdRoot(trc, r->address(), "PersistentRooted<jsid>"); - for (JS::PersistentRootedValue *r = rt->valuePersistentRooteds.getFirst(); r != nullptr; r = r->getNext()) - MarkValueRoot(trc, r->address(), "PersistentRooted<Value>"); + PersistentRootedMarker::markChain(trc, rt->idPersistentRooteds, &MarkIdRoot, + "PersistentRooted<jsid>"); + PersistentRootedMarker::markChain(trc, rt->valuePersistentRooteds, &MarkValueRoot, + "PersistentRooted<Value>"); } void js::gc::MarkRuntime(JSTracer *trc, bool useSavedRoots) { JSRuntime *rt = trc->runtime; JS_ASSERT(trc->callback != GCMarker::GrayCallback);
--- a/js/src/gc/StoreBuffer.h +++ b/js/src/gc/StoreBuffer.h @@ -82,27 +82,23 @@ class StoreBuffer static const size_t MinAvailableSize = (size_t)(LifoAllocBlockSize * 1.0 / 8.0); /* * This buffer holds only a single type of edge. Using this buffer is more * efficient than the generic buffer when many writes will be to the same * type of edge: e.g. Value or Cell*. */ template<typename T> - class MonoTypeBuffer + struct MonoTypeBuffer { - friend class StoreBuffer; - LifoAlloc *storage_; explicit MonoTypeBuffer() : storage_(nullptr) {} ~MonoTypeBuffer() { js_delete(storage_); } - MonoTypeBuffer &operator=(const MonoTypeBuffer& other) MOZ_DELETE; - bool init() { if (!storage_) storage_ = js_new<LifoAlloc>(LifoAllocBlockSize); clear(); return bool(storage_); } void clear() { @@ -137,48 +133,45 @@ class StoreBuffer compact(owner); if (isAboutToOverflow()) owner->setAboutToOverflow(); } } /* Mark the source of all edges in the store buffer. */ void mark(StoreBuffer *owner, JSTracer *trc); + + private: + MonoTypeBuffer &operator=(const MonoTypeBuffer& other) MOZ_DELETE; }; /* * Overrides the MonoTypeBuffer to support pointers that may be moved in * memory outside of the GC's control. */ template <typename T> - class RelocatableMonoTypeBuffer : public MonoTypeBuffer<T> + struct RelocatableMonoTypeBuffer : public MonoTypeBuffer<T> { - friend class StoreBuffer; - /* Override compaction to filter out removed items. */ void compactMoved(StoreBuffer *owner); virtual void compact(StoreBuffer *owner) MOZ_OVERRIDE; /* Record a removal from the buffer. */ void unput(StoreBuffer *owner, const T &v) { MonoTypeBuffer<T>::put(owner, v.tagged()); } }; - class GenericBuffer + struct GenericBuffer { - friend class StoreBuffer; - LifoAlloc *storage_; explicit GenericBuffer() : storage_(nullptr) {} ~GenericBuffer() { js_delete(storage_); } - GenericBuffer &operator=(const GenericBuffer& other) MOZ_DELETE; - bool init() { if (!storage_) storage_ = js_new<LifoAlloc>(LifoAllocBlockSize); clear(); return bool(storage_); } void clear() { @@ -210,24 +203,23 @@ class StoreBuffer T *tp = storage_->new_<T>(t); if (!tp) CrashAtUnhandlableOOM("Failed to allocate for GenericBuffer::put."); if (isAboutToOverflow()) owner->setAboutToOverflow(); } + + private: + GenericBuffer &operator=(const GenericBuffer& other) MOZ_DELETE; }; - class CellPtrEdge + struct CellPtrEdge { - friend class StoreBuffer; - friend class StoreBuffer::MonoTypeBuffer<CellPtrEdge>; - friend class StoreBuffer::RelocatableMonoTypeBuffer<CellPtrEdge>; - Cell **edge; explicit CellPtrEdge(Cell **v) : edge(v) {} bool operator==(const CellPtrEdge &other) const { return edge == other.edge; } bool operator!=(const CellPtrEdge &other) const { return edge != other.edge; } void *location() const { return (void *)untagged().edge; } @@ -241,22 +233,18 @@ class StoreBuffer void mark(JSTracer *trc); CellPtrEdge tagged() const { return CellPtrEdge((Cell **)(uintptr_t(edge) | 1)); } CellPtrEdge untagged() const { return CellPtrEdge((Cell **)(uintptr_t(edge) & ~1)); } bool isTagged() const { return bool(uintptr_t(edge) & 1); } }; - class ValueEdge + struct ValueEdge { - friend class StoreBuffer; - friend class StoreBuffer::MonoTypeBuffer<ValueEdge>; - friend class StoreBuffer::RelocatableMonoTypeBuffer<ValueEdge>; - JS::Value *edge; explicit ValueEdge(JS::Value *v) : edge(v) {} bool operator==(const ValueEdge &other) const { return edge == other.edge; } bool operator!=(const ValueEdge &other) const { return edge != other.edge; } void *deref() const { return edge->isGCThing() ? edge->toGCThing() : nullptr; } void *location() const { return (void *)untagged().edge; } @@ -273,19 +261,16 @@ class StoreBuffer ValueEdge tagged() const { return ValueEdge((JS::Value *)(uintptr_t(edge) | 1)); } ValueEdge untagged() const { return ValueEdge((JS::Value *)(uintptr_t(edge) & ~1)); } bool isTagged() const { return bool(uintptr_t(edge) & 1); } }; struct SlotEdge { - friend class StoreBuffer; - friend class StoreBuffer::MonoTypeBuffer<SlotEdge>; - JSObject *object; uint32_t offset; int kind; // this is really just HeapSlot::Kind, but we can't see that type easily here SlotEdge(JSObject *object, int kind, uint32_t offset) : object(object), offset(offset), kind(kind) {} @@ -302,21 +287,18 @@ class StoreBuffer JS_ALWAYS_INLINE void *deref() const; JS_ALWAYS_INLINE void *location() const; bool inRememberedSet(const Nursery &nursery) const; JS_ALWAYS_INLINE bool isNullEdge() const; void mark(JSTracer *trc); }; - class WholeCellEdges + struct WholeCellEdges { - friend class StoreBuffer; - friend class StoreBuffer::MonoTypeBuffer<WholeCellEdges>; - Cell *tenured; WholeCellEdges(Cell *cell) : tenured(cell) { JS_ASSERT(tenured->isTenured()); } bool operator==(const WholeCellEdges &other) const { return tenured == other.tenured; } bool operator!=(const WholeCellEdges &other) const { return tenured != other.tenured; } @@ -327,19 +309,18 @@ class StoreBuffer void *location() const { return (void *)tenured; } bool isNullEdge() const { return false; } void mark(JSTracer *trc); }; template <typename Key> - class CallbackRef : public BufferableRef + struct CallbackRef : public BufferableRef { - public: typedef void (*MarkCallback)(JSTracer *trc, Key *key, void *data); CallbackRef(MarkCallback cb, Key *k, void *d) : callback(cb), key(k), data(d) {} virtual void mark(JSTracer *trc) { callback(trc, key, data); }
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -784,36 +784,61 @@ bool CodeGenerator::visitRegExpTest(LRegExpTest *lir) { pushArg(ToRegister(lir->string())); pushArg(ToRegister(lir->regexp())); return callVM(RegExpTestRawInfo, lir); } typedef JSString *(*RegExpReplaceFn)(JSContext *, HandleString, HandleObject, HandleString); -static const VMFunction RegExpReplaceInfo = FunctionInfo<RegExpReplaceFn>(regexp_replace); +static const VMFunction RegExpReplaceInfo = FunctionInfo<RegExpReplaceFn>(RegExpReplace); bool CodeGenerator::visitRegExpReplace(LRegExpReplace *lir) { if (lir->replacement()->isConstant()) pushArg(ImmGCPtr(lir->replacement()->toConstant()->toString())); else pushArg(ToRegister(lir->replacement())); - pushArg(ToRegister(lir->regexp())); + pushArg(ToRegister(lir->pattern())); if (lir->string()->isConstant()) pushArg(ImmGCPtr(lir->string()->toConstant()->toString())); else pushArg(ToRegister(lir->string())); return callVM(RegExpReplaceInfo, lir); } +typedef JSString *(*StringReplaceFn)(JSContext *, HandleString, HandleString, HandleString); +static const VMFunction StringReplaceInfo = FunctionInfo<StringReplaceFn>(StringReplace); + +bool +CodeGenerator::visitStringReplace(LStringReplace *lir) +{ + if (lir->replacement()->isConstant()) + pushArg(ImmGCPtr(lir->replacement()->toConstant()->toString())); + else + pushArg(ToRegister(lir->replacement())); + + if (lir->pattern()->isConstant()) + pushArg(ImmGCPtr(lir->pattern()->toConstant()->toString())); + else + pushArg(ToRegister(lir->pattern())); + + if (lir->string()->isConstant()) + pushArg(ImmGCPtr(lir->string()->toConstant()->toString())); + else + pushArg(ToRegister(lir->string())); + + return callVM(StringReplaceInfo, lir); +} + + typedef JSObject *(*LambdaFn)(JSContext *, HandleFunction, HandleObject); static const VMFunction LambdaInfo = FunctionInfo<LambdaFn>(js::Lambda); bool CodeGenerator::visitLambdaForSingleton(LLambdaForSingleton *lir) { pushArg(ToRegister(lir->scopeChain()));
--- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -88,16 +88,17 @@ class CodeGenerator : public CodeGenerat bool visitTypeObjectDispatch(LTypeObjectDispatch *lir); bool visitIntToString(LIntToString *lir); bool visitDoubleToString(LDoubleToString *lir); bool visitInteger(LInteger *lir); bool visitRegExp(LRegExp *lir); bool visitRegExpExec(LRegExpExec *lir); bool visitRegExpTest(LRegExpTest *lir); bool visitRegExpReplace(LRegExpReplace *lir); + bool visitStringReplace(LStringReplace *lir); bool visitLambda(LLambda *lir); bool visitLambdaForSingleton(LLambdaForSingleton *lir); bool visitLambdaPar(LLambdaPar *lir); bool visitPointer(LPointer *lir); bool visitSlots(LSlots *lir); bool visitStoreSlotV(LStoreSlotV *store); bool visitElements(LElements *lir); bool visitConvertElementsToDoubles(LConvertElementsToDoubles *lir);
--- a/js/src/jit/CompileInfo-inl.h +++ b/js/src/jit/CompileInfo-inl.h @@ -6,24 +6,27 @@ #ifndef jit_CompileInfo_inl_h #define jit_CompileInfo_inl_h #include "jit/CompileInfo.h" #include "jsscriptinlines.h" -using namespace js; -using namespace jit; +namespace js { +namespace jit { inline RegExpObject * CompileInfo::getRegExp(jsbytecode *pc) const { return script_->getRegExp(pc); } inline JSFunction * CompileInfo::getFunction(jsbytecode *pc) const { return script_->getFunction(GET_UINT32_INDEX(pc)); } +} // namespace jit +} // namespace js + #endif /* jit_CompileInfo_inl_h */
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -624,17 +624,17 @@ class IonBuilder : public MIRGenerator InliningStatus inlineMathFunction(CallInfo &callInfo, MMathFunction::Function function); // String natives. InliningStatus inlineStringObject(CallInfo &callInfo); InliningStatus inlineStringSplit(CallInfo &callInfo); InliningStatus inlineStrCharCodeAt(CallInfo &callInfo); InliningStatus inlineStrFromCharCode(CallInfo &callInfo); InliningStatus inlineStrCharAt(CallInfo &callInfo); - InliningStatus inlineStrReplaceRegExp(CallInfo &callInfo); + InliningStatus inlineStrReplace(CallInfo &callInfo); // RegExp natives. InliningStatus inlineRegExpExec(CallInfo &callInfo); InliningStatus inlineRegExpTest(CallInfo &callInfo); // Array intrinsics. InliningStatus inlineUnsafePutElements(CallInfo &callInfo); bool inlineUnsafeSetDenseArrayElement(CallInfo &callInfo, uint32_t base);
--- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -3241,44 +3241,71 @@ class LRegExpTest : public LCallInstruct return getOperand(1); } const MRegExpTest *mir() const { return mir_->toRegExpTest(); } }; -class LRegExpReplace : public LCallInstructionHelper<1, 3, 0> -{ - public: - LIR_HEADER(RegExpReplace) - - LRegExpReplace(const LAllocation &string, const LAllocation ®exp, + +class LStrReplace : public LCallInstructionHelper<1, 3, 0> +{ + public: + LStrReplace(const LAllocation &string, const LAllocation &pattern, const LAllocation &replacement) { setOperand(0, string); - setOperand(1, regexp); + setOperand(1, pattern); setOperand(2, replacement); } const LAllocation *string() { return getOperand(0); } - const LAllocation *regexp() { + const LAllocation *pattern() { return getOperand(1); } const LAllocation *replacement() { return getOperand(2); } +}; + +class LRegExpReplace: public LStrReplace +{ + public: + LIR_HEADER(RegExpReplace); + + LRegExpReplace(const LAllocation &string, const LAllocation &pattern, + const LAllocation &replacement) + : LStrReplace(string, pattern, replacement) + { + } const MRegExpReplace *mir() const { return mir_->toRegExpReplace(); } }; +class LStringReplace: public LStrReplace +{ + public: + LIR_HEADER(StringReplace); + + LStringReplace(const LAllocation &string, const LAllocation &pattern, + const LAllocation &replacement) + : LStrReplace(string, pattern, replacement) + { + } + + const MStringReplace *mir() const { + return mir_->toStringReplace(); + } +}; + class LLambdaForSingleton : public LCallInstructionHelper<1, 1, 0> { public: LIR_HEADER(LambdaForSingleton) LLambdaForSingleton(const LAllocation &scopeChain) { setOperand(0, scopeChain);
--- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -147,16 +147,17 @@ _(OsrValue) \ _(OsrScopeChain) \ _(OsrReturnValue) \ _(OsrArgumentsObject) \ _(RegExp) \ _(RegExpExec) \ _(RegExpTest) \ _(RegExpReplace) \ + _(StringReplace) \ _(Lambda) \ _(LambdaForSingleton) \ _(LambdaPar) \ _(ImplicitThis) \ _(Slots) \ _(Elements) \ _(ConvertElementsToDoubles) \ _(MaybeToDoubleElement) \
--- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -1934,22 +1934,35 @@ LIRGenerator::visitRegExpTest(MRegExpTes LRegExpTest *lir = new(alloc()) LRegExpTest(useRegisterAtStart(ins->regexp()), useRegisterAtStart(ins->string())); return defineReturn(lir, ins) && assignSafepoint(lir, ins); } bool LIRGenerator::visitRegExpReplace(MRegExpReplace *ins) { - JS_ASSERT(ins->regexp()->type() == MIRType_Object); + JS_ASSERT(ins->pattern()->type() == MIRType_Object); JS_ASSERT(ins->string()->type() == MIRType_String); JS_ASSERT(ins->replacement()->type() == MIRType_String); LRegExpReplace *lir = new(alloc()) LRegExpReplace(useRegisterOrConstantAtStart(ins->string()), - useRegisterAtStart(ins->regexp()), + useRegisterAtStart(ins->pattern()), + useRegisterOrConstantAtStart(ins->replacement())); + return defineReturn(lir, ins) && assignSafepoint(lir, ins); +} + +bool +LIRGenerator::visitStringReplace(MStringReplace *ins) +{ + JS_ASSERT(ins->pattern()->type() == MIRType_String); + JS_ASSERT(ins->string()->type() == MIRType_String); + JS_ASSERT(ins->replacement()->type() == MIRType_String); + + LStringReplace *lir = new(alloc()) LStringReplace(useRegisterOrConstantAtStart(ins->string()), + useRegisterAtStart(ins->pattern()), useRegisterOrConstantAtStart(ins->replacement())); return defineReturn(lir, ins) && assignSafepoint(lir, ins); } bool LIRGenerator::visitLambda(MLambda *ins) { if (ins->info().singletonType || ins->info().useNewTypeForClone) {
--- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -143,16 +143,17 @@ class LIRGenerator : public LIRGenerator bool visitToFloat32(MToFloat32 *convert); bool visitToInt32(MToInt32 *convert); bool visitTruncateToInt32(MTruncateToInt32 *truncate); bool visitToString(MToString *convert); bool visitRegExp(MRegExp *ins); bool visitRegExpExec(MRegExpExec *ins); bool visitRegExpTest(MRegExpTest *ins); bool visitRegExpReplace(MRegExpReplace *ins); + bool visitStringReplace(MStringReplace *ins); bool visitLambda(MLambda *ins); bool visitLambdaPar(MLambdaPar *ins); bool visitImplicitThis(MImplicitThis *ins); bool visitSlots(MSlots *ins); bool visitElements(MElements *ins); bool visitConstantElements(MConstantElements *ins); bool visitConvertElementsToDoubles(MConvertElementsToDoubles *ins); bool visitMaybeToDoubleElement(MMaybeToDoubleElement *ins);
--- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -115,17 +115,17 @@ IonBuilder::inlineNativeCall(CallInfo &c return inlineStringSplit(callInfo); if (native == js_str_charCodeAt) return inlineStrCharCodeAt(callInfo); if (native == js::str_fromCharCode) return inlineStrFromCharCode(callInfo); if (native == js_str_charAt) return inlineStrCharAt(callInfo); if (native == str_replace) - return inlineStrReplaceRegExp(callInfo); + return inlineStrReplace(callInfo); // RegExp natives. if (native == regexp_exec && CallResultEscapes(pc)) return inlineRegExpExec(callInfo); if (native == regexp_exec && !CallResultEscapes(pc)) return inlineRegExpTest(callInfo); if (native == regexp_test) return inlineRegExpTest(callInfo); @@ -1173,48 +1173,53 @@ IonBuilder::inlineRegExpTest(CallInfo &c current->push(match); if (!resumeAfter(match)) return InliningStatus_Error; return InliningStatus_Inlined; } IonBuilder::InliningStatus -IonBuilder::inlineStrReplaceRegExp(CallInfo &callInfo) +IonBuilder::inlineStrReplace(CallInfo &callInfo) { if (callInfo.argc() != 2 || callInfo.constructing()) return InliningStatus_NotInlined; // Return: String. if (getInlineReturnType() != MIRType_String) return InliningStatus_NotInlined; // This: String. if (callInfo.thisArg()->type() != MIRType_String) return InliningStatus_NotInlined; // Arg 0: RegExp. types::TemporaryTypeSet *arg0Type = callInfo.getArg(0)->resultTypeSet(); const Class *clasp = arg0Type ? arg0Type->getKnownClass() : nullptr; - if (clasp != &RegExpObject::class_) + if (clasp != &RegExpObject::class_ && callInfo.getArg(0)->type() != MIRType_String) return InliningStatus_NotInlined; // Arg 1: String. if (callInfo.getArg(1)->type() != MIRType_String) return InliningStatus_NotInlined; callInfo.setImplicitlyUsedUnchecked(); - MInstruction *cte = MRegExpReplace::New(alloc(), callInfo.thisArg(), callInfo.getArg(0), - callInfo.getArg(1)); + MInstruction *cte; + if (callInfo.getArg(0)->type() == MIRType_String) { + cte = MStringReplace::New(alloc(), callInfo.thisArg(), callInfo.getArg(0), + callInfo.getArg(1)); + } else { + cte = MRegExpReplace::New(alloc(), callInfo.thisArg(), callInfo.getArg(0), + callInfo.getArg(1)); + } current->add(cte); current->push(cte); - if (!resumeAfter(cte)) + if (cte->isEffectful() && !resumeAfter(cte)) return InliningStatus_Error; - return InliningStatus_Inlined; } IonBuilder::InliningStatus IonBuilder::inlineUnsafePutElements(CallInfo &callInfo) { uint32_t argc = callInfo.argc(); if (argc < 3 || (argc % 3) != 0 || callInfo.constructing())
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -4880,54 +4880,91 @@ class MRegExpTest return this; } bool possiblyCalls() const { return true; } }; -class MRegExpReplace +template <class Policy1> +class MStrReplace : public MTernaryInstruction, - public Mix3Policy<StringPolicy<0>, ObjectPolicy<1>, StringPolicy<2> > -{ - private: - - MRegExpReplace(MDefinition *string, MDefinition *regexp, MDefinition *replacement) - : MTernaryInstruction(string, regexp, replacement) - { + public Mix3Policy<StringPolicy<0>, Policy1, StringPolicy<2> > +{ + protected: + + MStrReplace(MDefinition *string, MDefinition *pattern, MDefinition *replacement) + : MTernaryInstruction(string, pattern, replacement) + { + setMovable(); setResultType(MIRType_String); } public: - INSTRUCTION_HEADER(RegExpReplace) - - static MRegExpReplace *New(TempAllocator &alloc, MDefinition *string, MDefinition *regexp, MDefinition *replacement) { - return new(alloc) MRegExpReplace(string, regexp, replacement); - } MDefinition *string() const { return getOperand(0); } - MDefinition *regexp() const { + MDefinition *pattern() const { return getOperand(1); } MDefinition *replacement() const { return getOperand(2); } TypePolicy *typePolicy() { return this; } bool possiblyCalls() const { return true; } }; +class MRegExpReplace + : public MStrReplace< ObjectPolicy<1> > +{ + private: + + MRegExpReplace(MDefinition *string, MDefinition *pattern, MDefinition *replacement) + : MStrReplace< ObjectPolicy<1> >(string, pattern, replacement) + { + } + + public: + INSTRUCTION_HEADER(RegExpReplace); + + static MRegExpReplace *New(TempAllocator &alloc, MDefinition *string, MDefinition *pattern, MDefinition *replacement) { + return new(alloc) MRegExpReplace(string, pattern, replacement); + } +}; + +class MStringReplace + : public MStrReplace< StringPolicy<1> > +{ + private: + + MStringReplace(MDefinition *string, MDefinition *pattern, MDefinition *replacement) + : MStrReplace< StringPolicy<1> >(string, pattern, replacement) + { + } + + public: + INSTRUCTION_HEADER(StringReplace); + + static MStringReplace *New(TempAllocator &alloc, MDefinition *string, MDefinition *pattern, MDefinition *replacement) { + return new(alloc) MStringReplace(string, pattern, replacement); + } + + AliasSet getAliasSet() const { + return AliasSet::None(); + } +}; + struct LambdaFunctionInfo { // The functions used in lambdas are the canonical original function in // the script, and are immutable except for delazification. Record this // information while still on the main thread to avoid races. CompilerRootFunction fun; uint16_t flags; gc::Cell *scriptOrLazyScript;
--- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -96,16 +96,17 @@ namespace jit { _(InitPropGetterSetter) \ _(Start) \ _(OsrEntry) \ _(Nop) \ _(RegExp) \ _(RegExpExec) \ _(RegExpTest) \ _(RegExpReplace) \ + _(StringReplace) \ _(Lambda) \ _(ImplicitThis) \ _(Slots) \ _(Elements) \ _(ConstantElements) \ _(ConvertElementsToDoubles) \ _(MaybeToDoubleElement) \ _(LoadSlot) \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -276,16 +276,17 @@ class ParallelSafetyVisitor : public MIn UNSAFE_OP(SetDOMProperty) UNSAFE_OP(NewStringObject) UNSAFE_OP(Random) UNSAFE_OP(Pow) UNSAFE_OP(PowHalf) UNSAFE_OP(RegExpTest) UNSAFE_OP(RegExpExec) UNSAFE_OP(RegExpReplace) + UNSAFE_OP(StringReplace) UNSAFE_OP(CallInstanceOf) UNSAFE_OP(FunctionBoundary) UNSAFE_OP(GuardString) UNSAFE_OP(NewDeclEnvObject) UNSAFE_OP(In) UNSAFE_OP(InArray) SAFE_OP(GuardThreadExclusive) SAFE_OP(CheckInterruptPar)
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -909,28 +909,42 @@ InitBaselineFrameForOsr(BaselineFrame *f JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject type, HandleObject owner, int32_t offset) { return TypedObject::createDerived(cx, type, owner, offset); } JSString * -regexp_replace(JSContext *cx, HandleString string, HandleObject regexp, HandleString repl) +RegExpReplace(JSContext *cx, HandleString string, HandleObject regexp, HandleString repl) { - JS_ASSERT(!!string); - JS_ASSERT(!!repl); + JS_ASSERT(string); + JS_ASSERT(repl); RootedValue rval(cx); if (!str_replace_regexp_raw(cx, string, regexp, repl, &rval)) return nullptr; return rval.toString(); } +JSString * +StringReplace(JSContext *cx, HandleString string, HandleString pattern, HandleString repl) +{ + JS_ASSERT(string); + JS_ASSERT(pattern); + JS_ASSERT(repl); + + RootedValue rval(cx); + if (!str_replace_string_raw(cx, string, pattern, repl, &rval)) + return nullptr; + + return rval.toString(); +} + bool Recompile(JSContext *cx) { JS_ASSERT(cx->currentlyRunningInJit()); JitActivationIterator activations(cx->runtime()); IonFrameIterator iter(activations); JS_ASSERT(iter.type() == IonFrame_Exit);
--- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -658,18 +658,20 @@ bool DebugLeaveBlock(JSContext *cx, Base bool InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame, uint32_t numStackValues); JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject type, HandleObject owner, int32_t offset); bool Recompile(JSContext *cx); -JSString *regexp_replace(JSContext *cx, HandleString string, HandleObject regexp, - HandleString repl); +JSString *RegExpReplace(JSContext *cx, HandleString string, HandleObject regexp, + HandleString repl); +JSString *StringReplace(JSContext *cx, HandleString string, HandleString pattern, + HandleString repl); #ifdef DEBUG void AssertValidObjectPtr(JSContext *cx, JSObject *obj); void AssertValidStringPtr(JSContext *cx, JSString *str); void AssertValidValue(JSContext *cx, Value *v); #endif } // namespace jit
--- a/js/src/jit/arm/Bailouts-arm.cpp +++ b/js/src/jit/arm/Bailouts-arm.cpp @@ -71,21 +71,22 @@ class BailoutStack // inserts their own value int lr, which is then placed onto the stack along // with frameClassId_ above. This should be migrated to ip. public: union { uintptr_t frameSize_; uintptr_t tableOffset_; }; - private: + protected: // Silence Clang warning about unused private fields. mozilla::Array<double, FloatRegisters::Total> fpregs_; mozilla::Array<uintptr_t, Registers::Total> regs_; uintptr_t snapshotOffset_; + uintptr_t padding_; public: FrameSizeClass frameClass() const { return FrameSizeClass::FromClass(frameClassId_); } uintptr_t tableOffset() const { JS_ASSERT(frameClass() != FrameSizeClass::None()); return tableOffset_; @@ -104,16 +105,19 @@ class BailoutStack } uint8_t *parentStackPointer() const { if (frameClass() == FrameSizeClass::None()) return (uint8_t *)this + sizeof(BailoutStack); return (uint8_t *)this + offsetof(BailoutStack, snapshotOffset_); } }; +// Make sure the compiler doesn't add extra padding. +static_assert((sizeof(BailoutStack) % 8) == 0, "BailoutStack should be 8-byte aligned."); + } // namespace jit } // namespace js IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations, BailoutStack *bailout) : IonFrameIterator(activations), machine_(bailout->machine()) {
--- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -276,18 +276,18 @@ CodeGeneratorARM::bailout(LSnapshot *sna masm.ma_b(&label); return bailoutFrom(&label, snapshot); } bool CodeGeneratorARM::visitOutOfLineBailout(OutOfLineBailout *ool) { masm.ma_mov(Imm32(ool->snapshot()->snapshotOffset()), ScratchRegister); - masm.ma_push(ScratchRegister); - masm.ma_push(ScratchRegister); + masm.ma_push(ScratchRegister); // BailoutStack::padding_ + masm.ma_push(ScratchRegister); // BailoutStack::snapshotOffset_ masm.ma_b(&deoptLabel_); return true; } bool CodeGeneratorARM::visitMinMaxD(LMinMaxD *ins) { FloatRegister first = ToFloatRegister(ins->first());
--- a/js/src/jit/arm/Trampoline-arm.cpp +++ b/js/src/jit/arm/Trampoline-arm.cpp @@ -760,19 +760,20 @@ JitRuntime::generateVMWrapper(JSContext if (outReg != InvalidReg) masm.passABIArg(outReg); masm.callWithABI(f.wrapped); // Test for failure. switch (f.failType()) { case Type_Object: + masm.branchTestPtr(Assembler::Zero, r0, r0, masm.failureLabel(f.executionMode)); + break; case Type_Bool: - // Called functions return bools, which are 0/false and non-zero/true - masm.branch32(Assembler::Equal, r0, Imm32(0), masm.failureLabel(f.executionMode)); + masm.branchIfFalseBool(r0, masm.failureLabel(f.executionMode)); break; default: MOZ_ASSUME_UNREACHABLE("unknown failure kind"); } // Load the outparam and free any allocated stack. switch (f.outParam) { case Type_Handle:
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2809,60 +2809,59 @@ JS_LookupPropertyWithFlags(JSContext *cx if (!atom) return false; RootedId id(cx, AtomToId(atom)); return JS_LookupPropertyWithFlagsById(cx, obj, id, flags, &obj2, vp); } JS_PUBLIC_API(bool) -JS_HasPropertyById(JSContext *cx, JSObject *objArg, jsid idArg, bool *foundp) -{ - RootedObject obj(cx, objArg); - RootedId id(cx, idArg); +JS_HasPropertyById(JSContext *cx, HandleObject obj, HandleId id, bool *foundp) +{ RootedObject obj2(cx); RootedShape prop(cx); bool ok = LookupPropertyById(cx, obj, id, 0, &obj2, &prop); *foundp = (prop != nullptr); return ok; } JS_PUBLIC_API(bool) -JS_HasElement(JSContext *cx, JSObject *objArg, uint32_t index, bool *foundp) -{ - RootedObject obj(cx, objArg); +JS_HasElement(JSContext *cx, HandleObject obj, uint32_t index, bool *foundp) +{ AssertHeapIsIdle(cx); CHECK_REQUEST(cx); RootedId id(cx); if (!IndexToId(cx, index, &id)) return false; return JS_HasPropertyById(cx, obj, id, foundp); } JS_PUBLIC_API(bool) -JS_HasProperty(JSContext *cx, JSObject *objArg, const char *name, bool *foundp) -{ - RootedObject obj(cx, objArg); +JS_HasProperty(JSContext *cx, HandleObject obj, const char *name, bool *foundp) +{ JSAtom *atom = Atomize(cx, name, strlen(name)); - return atom && JS_HasPropertyById(cx, obj, AtomToId(atom), foundp); + if (!atom) + return false; + RootedId id(cx, AtomToId(atom)); + return JS_HasPropertyById(cx, obj, id, foundp); } JS_PUBLIC_API(bool) -JS_HasUCProperty(JSContext *cx, JSObject *objArg, const jschar *name, size_t namelen, bool *foundp) -{ - RootedObject obj(cx, objArg); +JS_HasUCProperty(JSContext *cx, HandleObject obj, const jschar *name, size_t namelen, bool *foundp) +{ JSAtom *atom = AtomizeChars(cx, name, AUTO_NAMELEN(name, namelen)); - return atom && JS_HasPropertyById(cx, obj, AtomToId(atom), foundp); + if (!atom) + return false; + RootedId id(cx, AtomToId(atom)); + return JS_HasPropertyById(cx, obj, id, foundp); } JS_PUBLIC_API(bool) -JS_AlreadyHasOwnPropertyById(JSContext *cx, JSObject *objArg, jsid id_, bool *foundp) -{ - RootedObject obj(cx, objArg); - RootedId id(cx, id_); +JS_AlreadyHasOwnPropertyById(JSContext *cx, HandleObject obj, HandleId id, bool *foundp) +{ AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, obj, id); if (!obj->isNative()) { RootedObject obj2(cx); RootedShape prop(cx); @@ -2877,42 +2876,45 @@ JS_AlreadyHasOwnPropertyById(JSContext * return true; } *foundp = obj->nativeContains(cx, id); return true; } JS_PUBLIC_API(bool) -JS_AlreadyHasOwnElement(JSContext *cx, JSObject *objArg, uint32_t index, bool *foundp) -{ - RootedObject obj(cx, objArg); +JS_AlreadyHasOwnElement(JSContext *cx, HandleObject obj, uint32_t index, bool *foundp) +{ AssertHeapIsIdle(cx); CHECK_REQUEST(cx); RootedId id(cx); if (!IndexToId(cx, index, &id)) return false; return JS_AlreadyHasOwnPropertyById(cx, obj, id, foundp); } JS_PUBLIC_API(bool) -JS_AlreadyHasOwnProperty(JSContext *cx, JSObject *objArg, const char *name, bool *foundp) -{ - RootedObject obj(cx, objArg); +JS_AlreadyHasOwnProperty(JSContext *cx, HandleObject obj, const char *name, bool *foundp) +{ JSAtom *atom = Atomize(cx, name, strlen(name)); - return atom && JS_AlreadyHasOwnPropertyById(cx, obj, AtomToId(atom), foundp); + if (!atom) + return false; + RootedId id(cx, AtomToId(atom)); + return JS_AlreadyHasOwnPropertyById(cx, obj, id, foundp); } JS_PUBLIC_API(bool) -JS_AlreadyHasOwnUCProperty(JSContext *cx, JSObject *objArg, const jschar *name, size_t namelen, +JS_AlreadyHasOwnUCProperty(JSContext *cx, HandleObject obj, const jschar *name, size_t namelen, bool *foundp) { - RootedObject obj(cx, objArg); JSAtom *atom = AtomizeChars(cx, name, AUTO_NAMELEN(name, namelen)); - return atom && JS_AlreadyHasOwnPropertyById(cx, obj, AtomToId(atom), foundp); + if (!atom) + return false; + RootedId id(cx, AtomToId(atom)); + return JS_AlreadyHasOwnPropertyById(cx, obj, id, foundp); } /* Wrapper functions to create wrappers with no corresponding JSJitInfo from API * function arguments. */ static JSPropertyOpWrapper GetterWrapper(JSPropertyOp getter) { @@ -3452,47 +3454,44 @@ JS_SetUCProperty(JSContext *cx, JSObject HandleValue v) { RootedObject obj(cx, objArg); JSAtom *atom = AtomizeChars(cx, name, AUTO_NAMELEN(name, namelen)); return atom && JS_SetPropertyById(cx, obj, AtomToId(atom), v); } JS_PUBLIC_API(bool) -JS_DeletePropertyById2(JSContext *cx, JSObject *objArg, jsid id, bool *result) -{ - RootedObject obj(cx, objArg); +JS_DeletePropertyById2(JSContext *cx, HandleObject obj, HandleId id, bool *result) +{ AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, obj, id); JSAutoResolveFlags rf(cx, 0); if (JSID_IS_SPECIAL(id)) { Rooted<SpecialId> sid(cx, JSID_TO_SPECIALID(id)); return JSObject::deleteSpecial(cx, obj, sid, result); } return JSObject::deleteByValue(cx, obj, IdToValue(id), result); } JS_PUBLIC_API(bool) -JS_DeleteElement2(JSContext *cx, JSObject *objArg, uint32_t index, bool *result) -{ - RootedObject obj(cx, objArg); +JS_DeleteElement2(JSContext *cx, HandleObject obj, uint32_t index, bool *result) +{ AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, obj); JSAutoResolveFlags rf(cx, 0); return JSObject::deleteElement(cx, obj, index, result); } JS_PUBLIC_API(bool) -JS_DeleteProperty2(JSContext *cx, JSObject *objArg, const char *name, bool *result) -{ - RootedObject obj(cx, objArg); +JS_DeleteProperty2(JSContext *cx, HandleObject obj, const char *name, bool *result) +{ CHECK_REQUEST(cx); assertSameCompartment(cx, obj); JSAutoResolveFlags rf(cx, 0); JSAtom *atom = Atomize(cx, name, strlen(name)); if (!atom) return false; return JSObject::deleteByValue(cx, obj, StringValue(atom), result); @@ -3509,34 +3508,34 @@ JS_DeleteUCProperty2(JSContext *cx, JSOb JSAtom *atom = AtomizeChars(cx, name, AUTO_NAMELEN(name, namelen)); if (!atom) return false; return JSObject::deleteByValue(cx, obj, StringValue(atom), result); } JS_PUBLIC_API(bool) -JS_DeletePropertyById(JSContext *cx, JSObject *objArg, jsid idArg) +JS_DeletePropertyById(JSContext *cx, HandleObject obj, HandleId id) { bool junk; - return JS_DeletePropertyById2(cx, objArg, idArg, &junk); + return JS_DeletePropertyById2(cx, obj, id, &junk); } JS_PUBLIC_API(bool) -JS_DeleteElement(JSContext *cx, JSObject *objArg, uint32_t index) +JS_DeleteElement(JSContext *cx, HandleObject obj, uint32_t index) { bool junk; - return JS_DeleteElement2(cx, objArg, index, &junk); + return JS_DeleteElement2(cx, obj, index, &junk); } JS_PUBLIC_API(bool) -JS_DeleteProperty(JSContext *cx, JSObject *objArg, const char *name) +JS_DeleteProperty(JSContext *cx, HandleObject obj, const char *name) { bool junk; - return JS_DeleteProperty2(cx, objArg, name, &junk); + return JS_DeleteProperty2(cx, obj, name, &junk); } static Shape * LastConfigurableShape(JSObject *obj) { for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) { Shape *shape = &r.front(); if (shape->configurable())
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2782,28 +2782,28 @@ JS_DefineOwnProperty(JSContext *cx, JSOb extern JS_PUBLIC_API(bool) JS_DefinePropertyWithTinyId(JSContext *cx, JSObject *obj, const char *name, int8_t tinyid, jsval value, JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs); extern JS_PUBLIC_API(bool) -JS_AlreadyHasOwnProperty(JSContext *cx, JSObject *obj, const char *name, +JS_AlreadyHasOwnProperty(JSContext *cx, JS::HandleObject obj, const char *name, bool *foundp); extern JS_PUBLIC_API(bool) -JS_AlreadyHasOwnPropertyById(JSContext *cx, JSObject *obj, jsid id, +JS_AlreadyHasOwnPropertyById(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *foundp); extern JS_PUBLIC_API(bool) -JS_HasProperty(JSContext *cx, JSObject *obj, const char *name, bool *foundp); +JS_HasProperty(JSContext *cx, JS::HandleObject obj, const char *name, bool *foundp); extern JS_PUBLIC_API(bool) -JS_HasPropertyById(JSContext *cx, JSObject *obj, jsid id, bool *foundp); +JS_HasPropertyById(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *foundp); extern JS_PUBLIC_API(bool) JS_LookupProperty(JSContext *cx, JSObject *obj, const char *name, JS::MutableHandleValue vp); extern JS_PUBLIC_API(bool) JS_LookupPropertyById(JSContext *cx, JSObject *obj, jsid id, JS::MutableHandleValue vp); extern JS_PUBLIC_API(bool) @@ -2996,46 +2996,46 @@ JS_ForwardGetPropertyTo(JSContext *cx, J extern JS_PUBLIC_API(bool) JS_SetProperty(JSContext *cx, JSObject *obj, const char *name, JS::HandleValue v); extern JS_PUBLIC_API(bool) JS_SetPropertyById(JSContext *cx, JSObject *obj, jsid id, JS::HandleValue v); extern JS_PUBLIC_API(bool) -JS_DeleteProperty(JSContext *cx, JSObject *obj, const char *name); +JS_DeleteProperty(JSContext *cx, JS::HandleObject obj, const char *name); extern JS_PUBLIC_API(bool) -JS_DeleteProperty2(JSContext *cx, JSObject *obj, const char *name, bool *succeeded); +JS_DeleteProperty2(JSContext *cx, JS::HandleObject obj, const char *name, bool *succeeded); extern JS_PUBLIC_API(bool) -JS_DeletePropertyById(JSContext *cx, JSObject *obj, jsid id); +JS_DeletePropertyById(JSContext *cx, JS::HandleObject obj, jsid id); extern JS_PUBLIC_API(bool) -JS_DeletePropertyById2(JSContext *cx, JSObject *obj, jsid id, bool *succeeded); +JS_DeletePropertyById2(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *succeeded); extern JS_PUBLIC_API(bool) JS_DefineUCProperty(JSContext *cx, JSObject *obj, const jschar *name, size_t namelen, jsval value, JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs); extern JS_PUBLIC_API(bool) JS_DefineUCPropertyWithTinyId(JSContext *cx, JSObject *obj, const jschar *name, size_t namelen, int8_t tinyid, jsval value, JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs); extern JS_PUBLIC_API(bool) -JS_AlreadyHasOwnUCProperty(JSContext *cx, JSObject *obj, const jschar *name, +JS_AlreadyHasOwnUCProperty(JSContext *cx, JS::HandleObject obj, const jschar *name, size_t namelen, bool *foundp); extern JS_PUBLIC_API(bool) -JS_HasUCProperty(JSContext *cx, JSObject *obj, +JS_HasUCProperty(JSContext *cx, JS::HandleObject obj, const jschar *name, size_t namelen, bool *vp); extern JS_PUBLIC_API(bool) JS_LookupUCProperty(JSContext *cx, JSObject *obj, const jschar *name, size_t namelen, JS::MutableHandleValue vp); @@ -3065,39 +3065,39 @@ JS_GetArrayLength(JSContext *cx, JS::Han extern JS_PUBLIC_API(bool) JS_SetArrayLength(JSContext *cx, JS::Handle<JSObject*> obj, uint32_t length); extern JS_PUBLIC_API(bool) JS_DefineElement(JSContext *cx, JSObject *obj, uint32_t index, jsval value, JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs); extern JS_PUBLIC_API(bool) -JS_AlreadyHasOwnElement(JSContext *cx, JSObject *obj, uint32_t index, bool *foundp); +JS_AlreadyHasOwnElement(JSContext *cx, JS::HandleObject obj, uint32_t index, bool *foundp); extern JS_PUBLIC_API(bool) -JS_HasElement(JSContext *cx, JSObject *obj, uint32_t index, bool *foundp); +JS_HasElement(JSContext *cx, JS::HandleObject obj, uint32_t index, bool *foundp); extern JS_PUBLIC_API(bool) JS_LookupElement(JSContext *cx, JSObject *obj, uint32_t index, JS::MutableHandleValue vp); extern JS_PUBLIC_API(bool) JS_GetElement(JSContext *cx, JSObject *obj, uint32_t index, JS::MutableHandleValue vp); extern JS_PUBLIC_API(bool) JS_ForwardGetElementTo(JSContext *cx, JSObject *obj, uint32_t index, JSObject *onBehalfOf, JS::MutableHandleValue vp); extern JS_PUBLIC_API(bool) JS_SetElement(JSContext *cx, JSObject *obj, uint32_t index, JS::MutableHandleValue vp); extern JS_PUBLIC_API(bool) -JS_DeleteElement(JSContext *cx, JSObject *obj, uint32_t index); +JS_DeleteElement(JSContext *cx, JS::HandleObject obj, uint32_t index); extern JS_PUBLIC_API(bool) -JS_DeleteElement2(JSContext *cx, JSObject *obj, uint32_t index, bool *succeeded); +JS_DeleteElement2(JSContext *cx, JS::HandleObject obj, uint32_t index, bool *succeeded); /* * Remove all configurable properties from the given (non-global) object and * assign undefined to all writable data properties. */ JS_PUBLIC_API(void) JS_ClearNonGlobalObject(JSContext *cx, JSObject *objArg);
--- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -382,18 +382,20 @@ EnsureTrackPropertyTypes(JSContext *cx, if (obj->hasSingletonType()) { AutoEnterAnalysis enter(cx); if (obj->hasLazyType() && !obj->getType(cx)) { cx->compartment()->types.setPendingNukeTypes(cx); cx->clearPendingException(); return; } - if (!obj->type()->unknownProperties()) - obj->type()->getProperty(cx, id); + if (!obj->type()->unknownProperties() && !obj->type()->getProperty(cx, id)) { + cx->compartment()->types.setPendingNukeTypes(cx); + return; + } } JS_ASSERT(obj->type()->unknownProperties() || TrackPropertyTypes(cx, obj, id)); } inline bool CanHaveEmptyPropertyTypesForOwnProperty(JSObject *obj) {
--- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -442,32 +442,29 @@ js::XDRScript(XDRState<mode> *xdr, Handl uint32_t scriptBits = 0; JSContext *cx = xdr->cx(); RootedScript script(cx); natoms = nsrcnotes = 0; nconsts = nobjects = nregexps = ntrynotes = nblockscopes = 0; /* XDR arguments and vars. */ - uint16_t nargs = 0, nvars = 0; - uint32_t argsVars = 0; + uint16_t nargs = 0; + uint32_t nvars = 0; if (mode == XDR_ENCODE) { script = scriptp.get(); JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment()); nargs = script->bindings.numArgs(); nvars = script->bindings.numVars(); - argsVars = (nargs << 16) | nvars; } - if (!xdr->codeUint32(&argsVars)) + if (!xdr->codeUint16(&nargs)) return false; - if (mode == XDR_DECODE) { - nargs = argsVars >> 16; - nvars = argsVars & 0xFFFF; - } + if (!xdr->codeUint32(&nvars)) + return false; if (mode == XDR_ENCODE) length = script->length(); if (!xdr->codeUint32(&length)) return false; if (mode == XDR_ENCODE) { prologLength = script->mainOffset();
--- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -1726,16 +1726,23 @@ class MOZ_STACK_CLASS StringRegExpGuard JS_ASSERT(ObjectClassIs(obj_, ESClass_RegExp, cx)); if (!RegExpToShared(cx, obj_, &re_)) return false; return true; } + bool init(JSContext *cx, HandleString pattern) { + fm.patstr = AtomizeString(cx, pattern); + if (!fm.patstr) + return false; + return true; + } + /* * Attempt to match |patstr| to |textstr|. A flags argument, metachars in * the pattern string, or a lengthy pattern string can thwart this process. * * |checkMetaChars| looks for regexp metachars in the pattern string. * * Return whether flat matching could be used. * @@ -2444,17 +2451,17 @@ ReplaceRegExp(JSContext *cx, RegExpStati rdata.sb.infallibleAppend(left, leftlen); /* skipped-over portion of the search value */ DoReplace(res, rdata); return true; } static bool BuildFlatReplacement(JSContext *cx, HandleString textstr, HandleString repstr, - const FlatMatch &fm, CallArgs *args) + const FlatMatch &fm, MutableHandleValue rval) { RopeBuilder builder(cx); size_t match = fm.match(); size_t matchEnd = match + fm.patternLength(); if (textstr->isRope()) { /* * If we are replacing over a rope, avoid flattening it by iterating @@ -2516,29 +2523,29 @@ BuildFlatReplacement(JSContext *cx, Hand if (!rightSide || !builder.append(leftSide) || !builder.append(repstr) || !builder.append(rightSide)) { return false; } } - args->rval().setString(builder.result()); + rval.setString(builder.result()); return true; } /* * Perform a linear-scan dollar substitution on the replacement text, * constructing a result string that looks like: * * newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:] */ static inline bool BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr, - const jschar *firstDollar, const FlatMatch &fm, CallArgs *args) + const jschar *firstDollar, const FlatMatch &fm, MutableHandleValue rval) { Rooted<JSLinearString*> textstr(cx, textstrArg->ensureLinear(cx)); if (!textstr) return false; JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length()); size_t matchStart = fm.match(); size_t matchLimit = matchStart + fm.patternLength(); @@ -2600,17 +2607,17 @@ BuildDollarReplacement(JSContext *cx, JS ENSURE(rightSide); RopeBuilder builder(cx); ENSURE(builder.append(leftSide) && builder.append(newReplace) && builder.append(rightSide)); #undef ENSURE - args->rval().setString(builder.result()); + rval.setString(builder.result()); return true; } struct StringRange { size_t start; size_t length; @@ -2856,20 +2863,56 @@ js::str_replace_regexp_raw(JSContext *cx if (!rdata.g.init(cx, regexp)) return false; return StrReplaceRegExp(cx, rdata, rval); } static inline bool +StrReplaceString(JSContext *cx, ReplaceData &rdata, const FlatMatch &fm, MutableHandleValue rval) +{ + /* + * Note: we could optimize the text.length == pattern.length case if we wanted, + * even in the presence of dollar metachars. + */ + if (rdata.dollar) + return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, fm, rval); + return BuildFlatReplacement(cx, rdata.str, rdata.repstr, fm, rval); +} + +static const uint32_t ReplaceOptArg = 2; + +bool +js::str_replace_string_raw(JSContext *cx, HandleString string, HandleString pattern, + HandleString replacement, MutableHandleValue rval) +{ + ReplaceData rdata(cx); + + rdata.str = string; + JSLinearString *repl = replacement->ensureLinear(cx); + if (!repl) + return false; + rdata.setReplacementString(repl); + + if (!rdata.g.init(cx, pattern)) + return false; + const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, ReplaceOptArg, false); + + if (fm->match() < 0) { + rval.setString(string); + return true; + } + + return StrReplaceString(cx, rdata, *fm, rval); +} + +static inline bool str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, const FlatMatch &fm) { - JS_ASSERT(fm.match() >= 0); - RootedString matchStr(cx, js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength())); if (!matchStr) return false; /* lambda(matchStr, matchStart, textstr) */ static const uint32_t lambdaArgc = 3; if (!rdata.fig.args().init(lambdaArgc)) return false; @@ -2906,18 +2949,16 @@ str_replace_flat_lambda(JSContext *cx, C builder.append(rightSide))) { return false; } outerArgs.rval().setString(builder.result()); return true; } -static const uint32_t ReplaceOptArg = 2; - /* * Pattern match the script to check if it is is indexing into a particular * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in * such cases, which are used by javascript packers (particularly the popular * Dean Edwards packer) to efficiently encode large scripts. We only handle the * code patterns generated by such packers here. */ static bool @@ -3012,38 +3053,31 @@ js::str_replace(JSContext *cx, unsigned * * However, if the user invokes our (non-standard) |flags| argument * extension then we revert to creating a regular expression. Note that * this is observable behavior through the side-effect mutation of the * |RegExp| statics. */ const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, args.length(), false); + if (!fm) { if (cx->isExceptionPending()) /* oom in RopeMatch in tryFlatMatch */ return false; return str_replace_regexp(cx, args, rdata); } if (fm->match() < 0) { args.rval().setString(rdata.str); return true; } if (rdata.lambda) return str_replace_flat_lambda(cx, args, rdata, *fm); - - /* - * Note: we could optimize the text.length == pattern.length case if we wanted, - * even in the presence of dollar metachars. - */ - if (rdata.dollar) - return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, *fm, &args); - - return BuildFlatReplacement(cx, rdata.str, rdata.repstr, *fm, &args); + return StrReplaceString(cx, rdata, *fm, args.rval()); } namespace { class SplitMatchResult { size_t endIndex_; size_t length_;
--- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -365,14 +365,18 @@ str_split_string(JSContext *cx, HandleTy bool str_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags, MutableHandleObject objp); bool str_replace_regexp_raw(JSContext *cx, HandleString string, HandleObject regexp, HandleString replacement, MutableHandleValue rval); +bool +str_replace_string_raw(JSContext *cx, HandleString string, HandleString pattern, + HandleString replacement, MutableHandleValue rval); + } /* namespace js */ extern bool js_String(JSContext *cx, unsigned argc, js::Value *vp); #endif /* jsstr_h */
--- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -17,17 +17,17 @@ namespace js { * Bytecode version number. Increment the subtrahend whenever JS bytecode * changes incompatibly. * * This version number is XDR'd near the front of xdr bytecode and * aborts deserialization if there is a mismatch between the current * and saved versions. If deserialization fails, the data should be * invalidated if possible. */ -static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 163); +static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 164); class XDRBuffer { public: XDRBuffer(JSContext *cx) : context(cx), base(nullptr), cursor(nullptr), limit(nullptr) { } JSContext *cx() const { return context;
--- a/js/xpconnect/src/XPCJSID.cpp +++ b/js/xpconnect/src/XPCJSID.cpp @@ -620,17 +620,18 @@ nsJSCID::NewID(const char* str) if (str[0] == '{') { NS_ENSURE_SUCCESS(idObj->Initialize(str), nullptr); } else { nsCOMPtr<nsIComponentRegistrar> registrar; NS_GetComponentRegistrar(getter_AddRefs(registrar)); NS_ENSURE_TRUE(registrar, nullptr); nsCID *cid; - NS_ENSURE_SUCCESS(registrar->ContractIDToCID(str, &cid), nullptr); + if (NS_FAILED(registrar->ContractIDToCID(str, &cid))) + return nullptr; bool success = idObj->mDetails.InitWithName(*cid, str); nsMemory::Free(cid); if (!success) return nullptr; } return idObj.forget(); }
--- a/js/xpconnect/src/dictionary_helper_gen.py +++ b/js/xpconnect/src/dictionary_helper_gen.py @@ -188,37 +188,30 @@ def print_cpp_file(fd, conf): for d in conf.dictionaries: idl = loadIDL(p, options.incdirs, d[1]) collect_names_and_non_primitive_attribute_types(idl, d[0], attrnames, includes) for c in includes: if not c in conf.exclude_automatic_type_include: fd.write("#include \"%s.h\"\n" % c) - fd.write("\nusing namespace mozilla::idl;\n\n") + fd.write("\n" + "using namespace mozilla::idl;\n" + "using namespace mozilla::dom;\n\n") for a in attrnames: - fd.write("static jsid %s = JSID_VOID;\n"% get_jsid(a)) + fd.write("static InternedStringId %s;\n" % get_jsid(a)) fd.write("\n" - "static bool\n" - "InternStaticJSVal(JSContext* aCx, jsid &id, const char* aString)\n" - "{\n" - " if (JSString* str = JS_InternString(aCx, aString)) {\n" - " id = INTERNED_STRING_TO_JSID(aCx, str);\n" - " return true;\n" - " }\n" - " return false;\n" - "}\n\n" "bool\n" "InternStaticDictionaryJSVals(JSContext* aCx)\n" "{\n" " return\n") for a in attrnames: - fd.write(" InternStaticJSVal(aCx, %s, \"%s\") &&\n" + fd.write(" %s.init(aCx, \"%s\") &&\n" % (get_jsid(a), a)) fd.write(" true;\n") fd.write("}\n\n") dicts = [] for d in conf.dictionaries: if not d[0] in set(dicts):
--- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -150,26 +150,33 @@ #if defined(MOZ_HAVE_NORETURN) # define MOZ_NORETURN MOZ_HAVE_NORETURN #else # define MOZ_NORETURN /* no support */ #endif /* * MOZ_ASAN_BLACKLIST is a macro to tell AddressSanitizer (a compile-time - * instrumentation shipped with Clang) to not instrument the annotated function. - * Furthermore, it will prevent the compiler from inlining the function because - * inlining currently breaks the blacklisting mechanism of AddressSanitizer. + * instrumentation shipped with Clang and GCC) to not instrument the annotated + * function. Furthermore, it will prevent the compiler from inlining the + * function because inlining currently breaks the blacklisting mechanism of + * AddressSanitizer. */ #if defined(__has_feature) # if __has_feature(address_sanitizer) -# define MOZ_ASAN_BLACKLIST MOZ_NEVER_INLINE __attribute__((no_sanitize_address)) -# else -# define MOZ_ASAN_BLACKLIST /* nothing */ +# define MOZ_HAVE_ASAN_BLACKLIST # endif +#elif defined(__GNUC__) +# if defined(__SANITIZE_ADDRESS__) +# define MOZ_HAVE_ASAN_BLACKLIST +# endif +#endif + +#if defined(MOZ_HAVE_ASAN_BLACKLIST) +# define MOZ_ASAN_BLACKLIST MOZ_NEVER_INLINE __attribute__((no_sanitize_address)) #else # define MOZ_ASAN_BLACKLIST /* nothing */ #endif /* * MOZ_TSAN_BLACKLIST is a macro to tell ThreadSanitizer (a compile-time * instrumentation shipped with Clang) to not instrument the annotated function. * Furthermore, it will prevent the compiler from inlining the function because
--- a/mobile/android/base/fxa/FxAccountConstants.java.in +++ b/mobile/android/base/fxa/FxAccountConstants.java.in @@ -7,19 +7,19 @@ package org.mozilla.gecko.fxa; import org.mozilla.gecko.background.common.log.Logger; public class FxAccountConstants { public static final String GLOBAL_LOG_TAG = "FxAccounts"; public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@"; public static final String DEFAULT_IDP_ENDPOINT = "https://api-accounts-onepw.dev.lcip.org"; - public static final String DEFAULT_AUTH_ENDPOINT = "http://auth.oldsync.dev.lcip.org"; - - public static final String PREFS_PATH = "fxa.v1"; + public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "http://auth.oldsync.dev.lcip.org"; + public static final String DEFAULT_TOKEN_SERVER_URI = DEFAULT_TOKEN_SERVER_ENDPOINT + + (DEFAULT_TOKEN_SERVER_ENDPOINT.endsWith("/") ? "" : "/") + "1.0/sync/1.1"; // For extra debugging. Not final so it can be changed from Fennec, or from // an add-on. public static boolean LOG_PERSONAL_INFORMATION = true; public static void pii(String tag, String message) { if (LOG_PERSONAL_INFORMATION) { Logger.info(tag, "$$FxA PII$$: " + message);
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java +++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java @@ -12,16 +12,17 @@ import org.mozilla.gecko.background.comm import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper; import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; import org.mozilla.gecko.background.fxa.FxAccountClient20; import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.sync.HTTPFailureException; import org.mozilla.gecko.sync.net.SyncStorageResponse; +import org.mozilla.gecko.sync.setup.Constants; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; @@ -155,18 +156,23 @@ public class FxAccountCreateAccountActiv @Override public void handleSuccess(String result) { Activity activity = FxAccountCreateAccountActivity.this; Logger.info(LOG_TAG, "Got success creating account."); // We're on the UI thread, but it's okay to create the account here. Account account; try { + final String profile = Constants.DEFAULT_PROFILE; + final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI; account = AndroidFxAccount.addAndroidAccount(activity, email, password, - serverURI, null, null, false); + profile, + serverURI, + tokenServerURI, + null, null, false); if (account == null) { throw new RuntimeException("XXX what?"); } } catch (Exception e) { handleError(e); return; }
--- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java +++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java @@ -12,16 +12,17 @@ import org.mozilla.gecko.background.comm import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; import org.mozilla.gecko.background.fxa.FxAccountClient20; import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.sync.HTTPFailureException; import org.mozilla.gecko.sync.net.SyncStorageResponse; +import org.mozilla.gecko.sync.setup.Constants; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; @@ -124,18 +125,22 @@ public class FxAccountSignInActivity ext @Override public void handleSuccess(LoginResponse result) { Activity activity = FxAccountSignInActivity.this; Logger.info(LOG_TAG, "Got success signing in."); // We're on the UI thread, but it's okay to create the account here. Account account; try { + final String profile = Constants.DEFAULT_PROFILE; + final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI; account = AndroidFxAccount.addAndroidAccount(activity, email, password, - serverURI, result.sessionToken, result.keyFetchToken, result.verified); + serverURI, + tokenServerURI, + profile, result.sessionToken, result.keyFetchToken, result.verified); if (account == null) { throw new RuntimeException("XXX what?"); } } catch (Exception e) { handleError(e); return; }
--- a/mobile/android/base/fxa/authenticator/AbstractFxAccount.java +++ b/mobile/android/base/fxa/authenticator/AbstractFxAccount.java @@ -33,17 +33,22 @@ import org.mozilla.gecko.browserid.Brows * <code>setWrappedKb</code> is called, at which point <code>kB</code> will be * computed and cached, ready to be returned by <code>getKb</code>. */ public interface AbstractFxAccount { /** * Get the Firefox Account auth server URI that this account login flow should * talk to. */ - public String getServerURI(); + public String getAccountServerURI(); + + /** + * @return the profile name associated with the account, such as "default". + */ + public String getProfile(); public boolean isValid(); public void setInvalid(); public byte[] getSessionToken(); public byte[] getKeyFetchToken(); public void setSessionToken(byte[] token);
--- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java +++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java @@ -1,19 +1,22 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.fxa.authenticator; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collections; +import org.mozilla.gecko.background.common.GlobalConstants; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.fxa.FxAccountUtils; import org.mozilla.gecko.browserid.BrowserIDKeyPair; import org.mozilla.gecko.browserid.RSACryptoImplementation; import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.sync.ExtendedJSONObject; import org.mozilla.gecko.sync.Utils; @@ -28,27 +31,40 @@ import android.os.Bundle; * <p> * Account user data is accessible only to the Android App(s) that own the * Account type. Account user data is not removed when the App's private data is * cleared. */ public class AndroidFxAccount implements AbstractFxAccount { protected static final String LOG_TAG = AndroidFxAccount.class.getSimpleName(); - public static final String ACCOUNT_KEY_ASSERTION = "assertion"; - public static final String ACCOUNT_KEY_CERTIFICATE = "certificate"; - public static final String ACCOUNT_KEY_INVALID = "invalid"; - public static final String ACCOUNT_KEY_SERVERURI = "serverURI"; - public static final String ACCOUNT_KEY_SESSION_TOKEN = "sessionToken"; - public static final String ACCOUNT_KEY_KEY_FETCH_TOKEN = "keyFetchToken"; - public static final String ACCOUNT_KEY_VERIFIED = "verified"; - public static final String ACCOUNT_KEY_KA = "kA"; - public static final String ACCOUNT_KEY_KB = "kB"; - public static final String ACCOUNT_KEY_UNWRAPKB = "unwrapkB"; - public static final String ACCOUNT_KEY_ASSERTION_KEY_PAIR = "assertionKeyPair"; + public static final int CURRENT_PREFS_VERSION = 1; + + public static final int CURRENT_ACCOUNT_VERSION = 3; + public static final String ACCOUNT_KEY_ACCOUNT_VERSION = "version"; + public static final String ACCOUNT_KEY_PROFILE = "profile"; + public static final String ACCOUNT_KEY_IDP_SERVER = "idpServerURI"; + + // The audience should always be a prefix of the token server URI. + public static final String ACCOUNT_KEY_AUDIENCE = "audience"; // Sync-specific. + public static final String ACCOUNT_KEY_TOKEN_SERVER = "tokenServerURI"; // Sync-specific. + public static final String ACCOUNT_KEY_DESCRIPTOR = "descriptor"; + + public static final int CURRENT_BUNDLE_VERSION = 1; + public static final String BUNDLE_KEY_BUNDLE_VERSION = "version"; + public static final String BUNDLE_KEY_ASSERTION = "assertion"; + public static final String BUNDLE_KEY_CERTIFICATE = "certificate"; + public static final String BUNDLE_KEY_INVALID = "invalid"; + public static final String BUNDLE_KEY_SESSION_TOKEN = "sessionToken"; + public static final String BUNDLE_KEY_KEY_FETCH_TOKEN = "keyFetchToken"; + public static final String BUNDLE_KEY_VERIFIED = "verified"; + public static final String BUNDLE_KEY_KA = "kA"; + public static final String BUNDLE_KEY_KB = "kB"; + public static final String BUNDLE_KEY_UNWRAPKB = "unwrapkB"; + public static final String BUNDLE_KEY_ASSERTION_KEY_PAIR = "assertionKeyPair"; protected final Context context; protected final AccountManager accountManager; protected final Account account; /** * Create an Android Firefox Account instance backed by an Android Account * instance. @@ -66,260 +82,430 @@ public class AndroidFxAccount implements * Android account to use for storage. */ public AndroidFxAccount(Context applicationContext, Account account) { this.context = applicationContext; this.account = account; this.accountManager = AccountManager.get(this.context); } + protected int getAccountVersion() { + String v = accountManager.getUserData(account, ACCOUNT_KEY_ACCOUNT_VERSION); + if (v == null) { + return 0; // Implicit. + } + + try { + return Integer.parseInt(v, 10); + } catch (NumberFormatException ex) { + return 0; + } + } + + protected void persistBundle(ExtendedJSONObject bundle) { + accountManager.setUserData(account, ACCOUNT_KEY_DESCRIPTOR, bundle.toJSONString()); + } + + protected ExtendedJSONObject unbundle() { + final int version = getAccountVersion(); + if (version < CURRENT_ACCOUNT_VERSION) { + // Needs upgrade. For now, do nothing. + return null; + } + + if (version > CURRENT_ACCOUNT_VERSION) { + // Oh dear. + return null; + } + + String bundle = accountManager.getUserData(account, ACCOUNT_KEY_DESCRIPTOR); + if (bundle == null) { + return null; + } + return unbundleAccountV1(bundle); + } + + protected String getBundleData(String key) { + ExtendedJSONObject o = unbundle(); + if (o == null) { + return null; + } + return o.getString(key); + } + + protected boolean getBundleDataBoolean(String key, boolean def) { + ExtendedJSONObject o = unbundle(); + if (o == null) { + return def; + } + Boolean b = o.getBoolean(key); + if (b == null) { + return def; + } + return b.booleanValue(); + } + + protected byte[] getBundleDataBytes(String key) { + ExtendedJSONObject o = unbundle(); + if (o == null) { + return null; + } + return o.getByteArrayHex(key); + } + + protected void updateBundleDataBytes(String key, byte[] value) { + updateBundleValue(key, value == null ? null : Utils.byte2Hex(value)); + } + + protected void updateBundleValue(String key, boolean value) { + ExtendedJSONObject descriptor = unbundle(); + if (descriptor == null) { + return; + } + descriptor.put(key, value); + persistBundle(descriptor); + } + + protected void updateBundleValue(String key, String value) { + ExtendedJSONObject descriptor = unbundle(); + if (descriptor == null) { + return; + } + descriptor.put(key, value); + persistBundle(descriptor); + } + + private ExtendedJSONObject unbundleAccountV1(String bundle) { + ExtendedJSONObject o; + try { + o = new ExtendedJSONObject(bundle); + } catch (Exception e) { + return null; + } + if (CURRENT_BUNDLE_VERSION == o.getIntegerSafely(BUNDLE_KEY_BUNDLE_VERSION)) { + return o; + } + return null; + } + @Override public byte[] getEmailUTF8() { try { return account.name.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { // Ignore. return null; } } + /** + * Note that if the user clears data, an account will be left pointing to a + * deleted profile. Such is life. + */ + @Override + public String getProfile() { + return accountManager.getUserData(account, ACCOUNT_KEY_PROFILE); + } + + @Override + public String getAccountServerURI() { + return accountManager.getUserData(account, ACCOUNT_KEY_IDP_SERVER); + } + + public String getAudience() { + return accountManager.getUserData(account, ACCOUNT_KEY_AUDIENCE); + } + + public String getTokenServerURI() { + return accountManager.getUserData(account, ACCOUNT_KEY_TOKEN_SERVER); + } + + /** + * This needs to return a string because of the tortured prefs access in GlobalSession. + */ + public String getSyncPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException { + String profile = getProfile(); + String username = account.name; + + if (profile == null) { + throw new IllegalStateException("Missing profile. Cannot fetch prefs."); + } + + if (username == null) { + throw new IllegalStateException("Missing username. Cannot fetch prefs."); + } + + final String tokenServerURI = getTokenServerURI(); + if (tokenServerURI == null) { + throw new IllegalStateException("No token server URI. Cannot fetch prefs."); + } + + final String fxaServerURI = getAccountServerURI(); + if (fxaServerURI == null) { + throw new IllegalStateException("No account server URI. Cannot fetch prefs."); + } + + final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".fxa"; + final long version = CURRENT_PREFS_VERSION; + + // This is unique for each syncing 'view' of the account. + final String serverURLThing = fxaServerURI + "!" + tokenServerURI; + return Utils.getPrefsPath(product, username, serverURLThing, profile, version); + } + @Override public void setQuickStretchedPW(byte[] quickStretchedPW) { accountManager.setPassword(account, quickStretchedPW == null ? null : Utils.byte2Hex(quickStretchedPW)); } @Override public byte[] getQuickStretchedPW() { String quickStretchedPW = accountManager.getPassword(account); return quickStretchedPW == null ? null : Utils.hex2Byte(quickStretchedPW); } @Override - public String getServerURI() { - return accountManager.getUserData(account, ACCOUNT_KEY_SERVERURI); - } - - protected byte[] getUserDataBytes(String key) { - String data = accountManager.getUserData(account, key); - if (data == null) { - return null; - } - return Utils.hex2Byte(data); - } - - @Override public byte[] getSessionToken() { - return getUserDataBytes(ACCOUNT_KEY_SESSION_TOKEN); + return getBundleDataBytes(BUNDLE_KEY_SESSION_TOKEN); } @Override public byte[] getKeyFetchToken() { - return getUserDataBytes(ACCOUNT_KEY_KEY_FETCH_TOKEN); + return getBundleDataBytes(BUNDLE_KEY_KEY_FETCH_TOKEN); } @Override public void setSessionToken(byte[] sessionToken) { - accountManager.setUserData(account, ACCOUNT_KEY_SESSION_TOKEN, sessionToken == null ? null : Utils.byte2Hex(sessionToken)); + updateBundleDataBytes(BUNDLE_KEY_SESSION_TOKEN, sessionToken); } @Override public void setKeyFetchToken(byte[] keyFetchToken) { - accountManager.setUserData(account, ACCOUNT_KEY_KEY_FETCH_TOKEN, keyFetchToken == null ? null : Utils.byte2Hex(keyFetchToken)); + updateBundleDataBytes(BUNDLE_KEY_KEY_FETCH_TOKEN, keyFetchToken); } @Override public boolean isVerified() { - String data = accountManager.getUserData(account, ACCOUNT_KEY_VERIFIED); - return Boolean.valueOf(data); + return getBundleDataBoolean(BUNDLE_KEY_VERIFIED, false); } @Override public void setVerified() { - accountManager.setUserData(account, ACCOUNT_KEY_VERIFIED, Boolean.valueOf(true).toString()); + updateBundleValue(BUNDLE_KEY_VERIFIED, true); } @Override public byte[] getKa() { - return getUserDataBytes(ACCOUNT_KEY_KA); + return getBundleDataBytes(BUNDLE_KEY_KA); } @Override public void setKa(byte[] kA) { - accountManager.setUserData(account, ACCOUNT_KEY_KA, Utils.byte2Hex(kA)); + updateBundleValue(BUNDLE_KEY_KA, Utils.byte2Hex(kA)); } @Override public void setWrappedKb(byte[] wrappedKb) { - byte[] unwrapKb = getUserDataBytes(ACCOUNT_KEY_UNWRAPKB); + if (wrappedKb == null) { + final String message = "wrappedKb is null: cannot set kB."; + Logger.error(LOG_TAG, message); + throw new IllegalArgumentException(message); + } + byte[] unwrapKb = getBundleDataBytes(BUNDLE_KEY_UNWRAPKB); + if (unwrapKb == null) { + Logger.error(LOG_TAG, "unwrapKb is null: cannot set kB."); + return; + } byte[] kB = new byte[wrappedKb.length]; // We could hard-code this to be 32. for (int i = 0; i < wrappedKb.length; i++) { kB[i] = (byte) (wrappedKb[i] ^ unwrapKb[i]); } - accountManager.setUserData(account, ACCOUNT_KEY_KB, Utils.byte2Hex(kB)); + updateBundleValue(BUNDLE_KEY_KB, Utils.byte2Hex(kB)); } @Override public byte[] getKb() { - return getUserDataBytes(ACCOUNT_KEY_KB); + return getBundleDataBytes(BUNDLE_KEY_KB); } protected BrowserIDKeyPair generateNewAssertionKeyPair() throws GeneralSecurityException { Logger.info(LOG_TAG, "Generating new assertion key pair."); // TODO Have the key size be a non-constant in FxAccountUtils, or read from SharedPreferences, or... return RSACryptoImplementation.generateKeyPair(1024); } @Override public BrowserIDKeyPair getAssertionKeyPair() throws GeneralSecurityException { try { - String data = accountManager.getUserData(account, ACCOUNT_KEY_ASSERTION_KEY_PAIR); + String data = getBundleData(BUNDLE_KEY_ASSERTION_KEY_PAIR); return RSACryptoImplementation.fromJSONObject(new ExtendedJSONObject(data)); } catch (Exception e) { // Fall through to generating a new key pair. } BrowserIDKeyPair keyPair = generateNewAssertionKeyPair(); - accountManager.setUserData(account, ACCOUNT_KEY_ASSERTION_KEY_PAIR, keyPair.toJSONObject().toJSONString()); + + ExtendedJSONObject descriptor = unbundle(); + if (descriptor == null) { + descriptor = new ExtendedJSONObject(); + } + descriptor.put(BUNDLE_KEY_ASSERTION_KEY_PAIR, keyPair.toJSONObject().toJSONString()); + persistBundle(descriptor); return keyPair; } @Override public String getCertificate() { - return accountManager.getUserData(account, ACCOUNT_KEY_CERTIFICATE); + return getBundleData(BUNDLE_KEY_CERTIFICATE); } @Override public void setCertificate(String certificate) { - accountManager.setUserData(account, ACCOUNT_KEY_CERTIFICATE, certificate); + updateBundleValue(BUNDLE_KEY_CERTIFICATE, certificate); } @Override public String getAssertion() { - return accountManager.getUserData(account, ACCOUNT_KEY_ASSERTION); + return getBundleData(BUNDLE_KEY_ASSERTION); } @Override public void setAssertion(String assertion) { - accountManager.setUserData(account, ACCOUNT_KEY_ASSERTION, assertion); + updateBundleValue(BUNDLE_KEY_ASSERTION, assertion); } /** * Extract a JSON dictionary of the string values associated to this account. * <p> * <b>For debugging use only!</b> The contents of this JSON object completely * determine the user's Firefox Account status and yield access to whatever * user data the device has access to. * * @return JSON-object of Strings. */ public ExtendedJSONObject toJSONObject() { - ExtendedJSONObject o = new ExtendedJSONObject(); - for (String key : new String[] { - ACCOUNT_KEY_ASSERTION, - ACCOUNT_KEY_CERTIFICATE, - ACCOUNT_KEY_SERVERURI, - ACCOUNT_KEY_SESSION_TOKEN, - ACCOUNT_KEY_INVALID, - ACCOUNT_KEY_KEY_FETCH_TOKEN, - ACCOUNT_KEY_VERIFIED, - ACCOUNT_KEY_KA, - ACCOUNT_KEY_KB, - ACCOUNT_KEY_UNWRAPKB, - ACCOUNT_KEY_ASSERTION_KEY_PAIR, - }) { - o.put(key, accountManager.getUserData(account, key)); - } + ExtendedJSONObject o = unbundle(); o.put("email", account.name); try { o.put("emailUTF8", Utils.byte2Hex(account.name.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { // Ignore. } o.put("quickStretchedPW", accountManager.getPassword(account)); return o; } - public static Account addAndroidAccount(Context context, String email, String password, - String serverURI, byte[] sessionToken, byte[] keyFetchToken, boolean verified) - throws UnsupportedEncodingException, GeneralSecurityException { + public static Account addAndroidAccount( + Context context, + String email, + String password, + String profile, + String idpServerURI, + String tokenServerURI, + byte[] sessionToken, + byte[] keyFetchToken, + boolean verified) + throws UnsupportedEncodingException, GeneralSecurityException, URISyntaxException { if (email == null) { throw new IllegalArgumentException("email must not be null"); } if (password == null) { throw new IllegalArgumentException("password must not be null"); } - if (serverURI == null) { - throw new IllegalArgumentException("serverURI must not be null"); + if (idpServerURI == null) { + throw new IllegalArgumentException("idpServerURI must not be null"); + } + if (tokenServerURI == null) { + throw new IllegalArgumentException("tokenServerURI must not be null"); } // sessionToken and keyFetchToken are allowed to be null; they can be // fetched via /account/login from the password. These tokens are generated // by the server and we have no length or formatting guarantees. However, if // one is given, both should be given: they come from the server together. if ((sessionToken == null && keyFetchToken != null) || (sessionToken != null && keyFetchToken == null)) { throw new IllegalArgumentException("none or both of sessionToken and keyFetchToken may be null"); } byte[] emailUTF8 = email.getBytes("UTF-8"); byte[] passwordUTF8 = password.getBytes("UTF-8"); byte[] quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8); byte[] unwrapBkey = FxAccountUtils.generateUnwrapBKey(quickStretchedPW); + // Android has internal restrictions that require all values in this + // bundle to be strings. *sigh* Bundle userdata = new Bundle(); - userdata.putString(AndroidFxAccount.ACCOUNT_KEY_SERVERURI, serverURI); - userdata.putString(AndroidFxAccount.ACCOUNT_KEY_SESSION_TOKEN, sessionToken == null ? null : Utils.byte2Hex(sessionToken)); - userdata.putString(AndroidFxAccount.ACCOUNT_KEY_KEY_FETCH_TOKEN, keyFetchToken == null ? null : Utils.byte2Hex(keyFetchToken)); - userdata.putString(AndroidFxAccount.ACCOUNT_KEY_VERIFIED, Boolean.valueOf(verified).toString()); - userdata.putString(AndroidFxAccount.ACCOUNT_KEY_UNWRAPKB, Utils.byte2Hex(unwrapBkey)); + userdata.putString(ACCOUNT_KEY_ACCOUNT_VERSION, "" + CURRENT_ACCOUNT_VERSION); + userdata.putString(ACCOUNT_KEY_IDP_SERVER, idpServerURI); + userdata.putString(ACCOUNT_KEY_TOKEN_SERVER, tokenServerURI); + userdata.putString(ACCOUNT_KEY_AUDIENCE, computeAudience(tokenServerURI)); + userdata.putString(ACCOUNT_KEY_PROFILE, profile); + + ExtendedJSONObject descriptor = new ExtendedJSONObject(); + descriptor.put(BUNDLE_KEY_BUNDLE_VERSION, CURRENT_BUNDLE_VERSION); + descriptor.put(BUNDLE_KEY_SESSION_TOKEN, sessionToken == null ? null : Utils.byte2Hex(sessionToken)); + descriptor.put(BUNDLE_KEY_KEY_FETCH_TOKEN, keyFetchToken == null ? null : Utils.byte2Hex(keyFetchToken)); + descriptor.put(BUNDLE_KEY_VERIFIED, verified); + descriptor.put(BUNDLE_KEY_UNWRAPKB, Utils.byte2Hex(unwrapBkey)); + + userdata.putString(ACCOUNT_KEY_DESCRIPTOR, descriptor.toJSONString()); Account account = new Account(email, FxAccountConstants.ACCOUNT_TYPE); AccountManager accountManager = AccountManager.get(context); boolean added = accountManager.addAccountExplicitly(account, Utils.byte2Hex(quickStretchedPW), userdata); if (!added) { return null; } FxAccountAuthenticator.enableSyncing(context, account); return account; } + // TODO: this is shit. + private static String computeAudience(String tokenServerURI) throws URISyntaxException { + URI uri = new URI(tokenServerURI); + return new URI(uri.getScheme(), uri.getHost(), null, null).toString(); + } + @Override public boolean isValid() { - // Boolean.valueOf only returns true for the string "true"; this errors in - // the direction of marking accounts valid. - boolean invalid = Boolean.valueOf(accountManager.getUserData(account, ACCOUNT_KEY_INVALID)).booleanValue(); - return !invalid; + return !getBundleDataBoolean(BUNDLE_KEY_INVALID, false); } @Override public void setInvalid() { - accountManager.setUserData(account, ACCOUNT_KEY_INVALID, Boolean.valueOf(true).toString()); + updateBundleValue(BUNDLE_KEY_INVALID, true); } /** * <b>For debugging only!</b> */ public void dump() { if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) { return; } ExtendedJSONObject o = toJSONObject(); ArrayList<String> list = new ArrayList<String>(o.keySet()); Collections.sort(list); for (String key : list) { - FxAccountConstants.pii(LOG_TAG, key + ": " + o.getString(key)); + FxAccountConstants.pii(LOG_TAG, key + ": " + o.get(key)); } } /** * <b>For debugging only!</b> */ public void forgetAccountTokens() { - accountManager.setUserData(account, ACCOUNT_KEY_SESSION_TOKEN, null); - accountManager.setUserData(account, ACCOUNT_KEY_KEY_FETCH_TOKEN, null); + ExtendedJSONObject descriptor = unbundle(); + if (descriptor == null) { + return; + } + descriptor.remove(BUNDLE_KEY_SESSION_TOKEN); + descriptor.remove(BUNDLE_KEY_KEY_FETCH_TOKEN); + persistBundle(descriptor); } /** * <b>For debugging only!</b> */ public void forgetQuickstretchedPW() { accountManager.setPassword(account, null); }
--- a/mobile/android/base/fxa/authenticator/FxAccountAuthenticator.java +++ b/mobile/android/base/fxa/authenticator/FxAccountAuthenticator.java @@ -51,17 +51,17 @@ public class FxAccountAuthenticator exte final AccountManager accountManager = AccountManager.get(context); final Account account = new Account(email, FxAccountConstants.ACCOUNT_TYPE); final Bundle userData = new Bundle(); userData.putString(JSON_KEY_UID, uid); userData.putString(JSON_KEY_SESSION_TOKEN, sessionToken); userData.putString(JSON_KEY_KA, kA); userData.putString(JSON_KEY_KB, kB); userData.putString(JSON_KEY_IDP_ENDPOINT, FxAccountConstants.DEFAULT_IDP_ENDPOINT); - userData.putString(JSON_KEY_AUTH_ENDPOINT, FxAccountConstants.DEFAULT_AUTH_ENDPOINT); + userData.putString(JSON_KEY_AUTH_ENDPOINT, FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT); if (!accountManager.addAccountExplicitly(account, sessionToken, userData)) { Logger.warn(LOG_TAG, "Error adding account named " + account.name + " of type " + account.type); return null; } // Enable syncing by default. enableSyncing(context, account); return account; }
--- a/mobile/android/base/fxa/authenticator/FxAccountLoginPolicy.java +++ b/mobile/android/base/fxa/authenticator/FxAccountLoginPolicy.java @@ -49,17 +49,17 @@ public class FxAccountLoginPolicy { return certificateDurationInMilliseconds; } public long getAssertionDurationInMilliseconds() { return assertionDurationInMilliseconds; } protected FxAccountClient makeFxAccountClient() { - String serverURI = fxAccount.getServerURI(); + String serverURI = fxAccount.getAccountServerURI(); return new FxAccountClient20(serverURI, executor); } private SkewHandler skewHandler; /** * Check if this certificate is not worth generating an assertion from: for * example, because it is not well-formed, or it is already expired. @@ -96,17 +96,17 @@ public class FxAccountLoginPolicy { NeedsVerification, NeedsKeys, NeedsCertificate, NeedsAssertion, Valid, }; public AccountState getAccountState(AbstractFxAccount fxAccount) { - String serverURI = fxAccount.getServerURI(); + String serverURI = fxAccount.getAccountServerURI(); byte[] emailUTF8 = fxAccount.getEmailUTF8(); byte[] quickStretchedPW = fxAccount.getQuickStretchedPW(); if (!fxAccount.isValid() || serverURI == null || emailUTF8 == null || quickStretchedPW == null) { return AccountState.Invalid; } byte[] sessionToken = fxAccount.getSessionToken(); if (sessionToken == null) {
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java +++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java @@ -18,16 +18,17 @@ import org.mozilla.gecko.fxa.FxAccountCo import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator; import org.mozilla.gecko.fxa.authenticator.FxAccountLoginDelegate; import org.mozilla.gecko.fxa.authenticator.FxAccountLoginException; import org.mozilla.gecko.fxa.authenticator.FxAccountLoginPolicy; import org.mozilla.gecko.sync.ExtendedJSONObject; import org.mozilla.gecko.sync.GlobalSession; import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate; +import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.crypto.KeyBundle; import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback; import org.mozilla.gecko.sync.delegates.ClientsDataDelegate; import org.mozilla.gecko.sync.net.AuthHeaderProvider; import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider; import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage; import org.mozilla.gecko.tokenserver.TokenServerClient; import org.mozilla.gecko.tokenserver.TokenServerClientDelegate; @@ -152,36 +153,40 @@ public class FxAccountSyncAdapter extend Logger.info(LOG_TAG, "Syncing FxAccount" + " account named " + account.name + " for authority " + authority + " with instance " + this + "."); final CountDownLatch latch = new CountDownLatch(1); final BaseGlobalSessionCallback callback = new SessionCallback(latch, syncResult); - try { - final String authEndpoint = FxAccountConstants.DEFAULT_AUTH_ENDPOINT; - final String tokenServerEndpoint = authEndpoint + (authEndpoint.endsWith("/") ? "" : "/") + "1.0/sync/1.1"; - final URI tokenServerEndpointURI = new URI(tokenServerEndpoint); - - final AndroidFxAccount fxAccount = new AndroidFxAccount(getContext(), account); + final Context context = getContext(); + final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { fxAccount.dump(); } - final SharedPreferences sharedPrefs = getContext().getSharedPreferences(FxAccountConstants.PREFS_PATH, Context.MODE_PRIVATE); // TODO Ensure preferences are per-Account. + final String prefsPath = fxAccount.getSyncPrefsPath(); + + // This will be the same chunk of SharedPreferences that GlobalSession/SyncConfiguration will later create. + final SharedPreferences sharedPrefs = context.getSharedPreferences(prefsPath, Utils.SHARED_PREFERENCES_MODE); - final FxAccountLoginPolicy loginPolicy = new FxAccountLoginPolicy(getContext(), fxAccount, executor); + final String audience = fxAccount.getAudience(); + final String tokenServerEndpoint = fxAccount.getTokenServerURI(); + final URI tokenServerEndpointURI = new URI(tokenServerEndpoint); + + // TODO: why doesn't the loginPolicy extract the audience from the account? + final FxAccountLoginPolicy loginPolicy = new FxAccountLoginPolicy(context, fxAccount, executor); loginPolicy.certificateDurationInMilliseconds = 20 * 60 * 1000; loginPolicy.assertionDurationInMilliseconds = 15 * 60 * 1000; Logger.info(LOG_TAG, "Asking for certificates to expire after 20 minutes and assertions to expire after 15 minutes."); - loginPolicy.login(authEndpoint, new FxAccountLoginDelegate() { + loginPolicy.login(audience, new FxAccountLoginDelegate() { @Override public void handleSuccess(final String assertion) { TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor); tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, new TokenServerClientDelegate() { @Override public void handleSuccess(final TokenServerToken token) { FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + "."); sharedPrefs.edit().putLong("tokenFailures", 0).commit(); @@ -196,17 +201,17 @@ public class FxAccountSyncAdapter extend // We compute skew over time using SkewHandler. This yields an unchanging // skew adjustment that the HawkAuthHeaderProvider uses to adjust its // timestamps. Eventually we might want this to adapt within the scope of a // global session. final SkewHandler tokenServerSkewHandler = SkewHandler.getSkewHandlerFromEndpointString(token.endpoint); final long tokenServerSkew = tokenServerSkewHandler.getSkewInSeconds(); AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, tokenServerSkew); - globalSession = new FxAccountGlobalSession(token.endpoint, token.uid, authHeaderProvider, FxAccountConstants.PREFS_PATH, syncKeyBundle, callback, getContext(), extras, clientsDataDelegate); + globalSession = new FxAccountGlobalSession(token.endpoint, token.uid, authHeaderProvider, prefsPath, syncKeyBundle, callback, context, extras, clientsDataDelegate); globalSession.start(); } catch (Exception e) { callback.handleError(globalSession, e); return; } } @Override
--- a/mobile/android/base/home/HomeConfig.java +++ b/mobile/android/base/home/HomeConfig.java @@ -13,17 +13,17 @@ import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; -final class HomeConfig { +public final class HomeConfig { /** * Used to determine what type of HomeFragment subclass to use when creating * a given panel. With the exception of DYNAMIC, all of these types correspond * to a default set of built-in panels. The DYNAMIC panel type is used by * third-party services to create panels with varying types of content. */ public static enum PanelType implements Parcelable { TOP_SITES("top_sites", TopSitesPanel.class), @@ -95,21 +95,24 @@ final class HomeConfig { private final EnumSet<Flags> mFlags; private static final String JSON_KEY_TYPE = "type"; private static final String JSON_KEY_TITLE = "title"; private static final String JSON_KEY_ID = "id"; private static final String JSON_KEY_LAYOUT = "layout"; private static final String JSON_KEY_VIEWS = "views"; private static final String JSON_KEY_DEFAULT = "default"; + private static final String JSON_KEY_DISABLED = "disabled"; private static final int IS_DEFAULT = 1; + private static final int IS_DISABLED = 1; public enum Flags { - DEFAULT_PANEL + DEFAULT_PANEL, + DISABLED_PANEL } public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException { mType = PanelType.fromId(json.getString(JSON_KEY_TYPE)); mTitle = json.getString(JSON_KEY_TITLE); mId = json.getString(JSON_KEY_ID); final String layoutTypeId = json.optString(JSON_KEY_LAYOUT, null); @@ -135,16 +138,21 @@ final class HomeConfig { mFlags = EnumSet.noneOf(Flags.class); final boolean isDefault = (json.optInt(JSON_KEY_DEFAULT, -1) == IS_DEFAULT); if (isDefault) { mFlags.add(Flags.DEFAULT_PANEL); } + final boolean isDisabled = (json.optInt(JSON_KEY_DISABLED, -1) == IS_DISABLED); + if (isDisabled) { + mFlags.add(Flags.DISABLED_PANEL); + } + validate(); } @SuppressWarnings("unchecked") public PanelConfig(Parcel in) { mType = (PanelType) in.readParcelable(getClass().getClassLoader()); mTitle = in.readString(); mId = in.readString(); @@ -153,32 +161,47 @@ final class HomeConfig { mViews = new ArrayList<ViewConfig>(); in.readTypedList(mViews, ViewConfig.CREATOR); mFlags = (EnumSet<Flags>) in.readSerializable(); validate(); } + public PanelConfig(PanelConfig panelConfig) { + mType = panelConfig.mType; + mTitle = panelConfig.mTitle; + mId = panelConfig.mId; + mLayoutType = panelConfig.mLayoutType; + + mViews = new ArrayList<ViewConfig>(); + for (ViewConfig viewConfig : panelConfig.mViews) { + mViews.add(new ViewConfig(viewConfig)); + } + mFlags = panelConfig.mFlags.clone(); + + validate(); + } + public PanelConfig(PanelType type, String title, String id) { this(type, title, id, EnumSet.noneOf(Flags.class)); } public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) { this(type, title, id, null, null, flags); } public PanelConfig(PanelType type, String title, String id, LayoutType layoutType, List<ViewConfig> views, EnumSet<Flags> flags) { mType = type; mTitle = title; mId = id; - mFlags = flags; mLayoutType = layoutType; mViews = views; + mFlags = flags; validate(); } private void validate() { if (mType == null) { throw new IllegalArgumentException("Can't create PanelConfig with null type"); } @@ -227,16 +250,36 @@ final class HomeConfig { public ViewConfig getViewAt(int index) { return (mViews != null ? mViews.get(index) : null); } public boolean isDefault() { return mFlags.contains(Flags.DEFAULT_PANEL); } + public void setIsDefault(boolean isDefault) { + if (isDefault) { + mFlags.add(Flags.DEFAULT_PANEL); + } else { + mFlags.remove(Flags.DEFAULT_PANEL); + } + } + + public boolean isDisabled() { + return mFlags.contains(Flags.DISABLED_PANEL); + } + + public void setIsDisabled(boolean isDisabled) { + if (isDisabled) { + mFlags.add(Flags.DISABLED_PANEL); + } else { + mFlags.remove(Flags.DISABLED_PANEL); + } + } + public JSONObject toJSON() throws JSONException { final JSONObject json = new JSONObject(); json.put(JSON_KEY_TYPE, mType.toString()); json.put(JSON_KEY_TITLE, mTitle); json.put(JSON_KEY_ID, mId); if (mLayoutType != null) { @@ -255,16 +298,20 @@ final class HomeConfig { json.put(JSON_KEY_VIEWS, jsonViews); } if (mFlags.contains(Flags.DEFAULT_PANEL)) { json.put(JSON_KEY_DEFAULT, IS_DEFAULT); } + if (mFlags.contains(Flags.DISABLED_PANEL)) { + json.put(JSON_KEY_DISABLED, IS_DISABLED); + } + return json; } @Override public int describeContents() { return 0; } @@ -410,16 +457,23 @@ final class HomeConfig { @SuppressWarnings("unchecked") public ViewConfig(Parcel in) { mType = (ViewType) in.readParcelable(getClass().getClassLoader()); mDatasetId = in.readString(); validate(); } + public ViewConfig(ViewConfig viewConfig) { + mType = viewConfig.mType; + mDatasetId = viewConfig.mDatasetId; + + validate(); + } + public ViewConfig(ViewType type, String datasetId) { mType = type; mDatasetId = datasetId; validate(); } private void validate() {
--- a/mobile/android/base/home/HomePager.java +++ b/mobile/android/base/home/HomePager.java @@ -23,16 +23,17 @@ import android.support.v4.app.LoaderMana import android.support.v4.content.Loader; import android.support.v4.view.ViewPager; import android.view.ViewGroup.LayoutParams; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewGroup; import android.view.View; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; public class HomePager extends ViewPager { private static final int LOADER_ID_CONFIG = 0; private final Context mContext; @@ -291,37 +292,44 @@ public class HomePager extends ViewPager } final HomeAdapter adapter = (HomeAdapter) getAdapter(); // Destroy any existing panels currently loaded // in the pager. setAdapter(null); - // Update the adapter with the new panel configs - adapter.update(panelConfigs); + // Only keep enabled panels. + final List<PanelConfig> enabledPanels = new ArrayList<PanelConfig>(); - // Hide the tab strip if the new configuration contains - // no panels for some reason. - final int count = (panelConfigs != null ? panelConfigs.size() : 0); + for (PanelConfig panelConfig : panelConfigs) { + if (!panelConfig.isDisabled()) { + enabledPanels.add(panelConfig); + } + } + + // Update the adapter with the new panel configs + adapter.update(enabledPanels); + + // Hide the tab strip if the new configuration contains no panels. + final int count = enabledPanels.size(); mTabStrip.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE); - // Re-install the adapter with the final state // in the pager. setAdapter(adapter); // Use the default panel as defined in the HomePager's configuration // if the initial panel wasn't explicitly set by the show() caller. if (mInitialPanelId != null) { // XXX: Handle the case where the desired panel isn't currently in the adapter (bug 949178) setCurrentItem(adapter.getItemPosition(mInitialPanelId), false); mInitialPanelId = null; } else { for (int i = 0; i < count; i++) { - final PanelConfig panelConfig = panelConfigs.get(i); + final PanelConfig panelConfig = enabledPanels.get(i); if (panelConfig.isDefault()) { setCurrentItem(i, false); break; } } } }
--- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -80,16 +80,19 @@ advisory message on the customise search providers settings page explaining how to add new search providers.--> <!ENTITY pref_search_tip "TIP: Add any website to your list of search providers by long-pressing on its search field."> <!ENTITY pref_category_devtools "Developer tools"> <!ENTITY pref_developer_remotedebugging "Remote debugging"> <!ENTITY pref_developer_remotedebugging_docs "Learn more"> <!ENTITY pref_remember_signons "Remember passwords"> +<!ENTITY pref_category_home "Home"> +<!ENTITY pref_category_home_panels "Panels"> + <!-- Localization note: These are shown in the left sidebar on tablets --> <!ENTITY pref_header_customize "Customize"> <!ENTITY pref_header_display "Display"> <!ENTITY pref_header_privacy_short "Privacy"> <!ENTITY pref_header_help "Help"> <!ENTITY pref_header_vendor "&vendorShortName;"> <!ENTITY pref_header_devtools "Developer tools"> @@ -146,21 +149,26 @@ size. --> <!ENTITY pref_private_data_offlineApps "Offline website data"> <!ENTITY pref_private_data_siteSettings2 "Site settings"> <!ENTITY pref_private_data_downloadFiles "Downloaded files"> <!ENTITY pref_about_firefox "About &brandShortName;"> <!ENTITY pref_vendor_faqs "FAQs"> <!ENTITY pref_vendor_feedback "Give feedback"> -<!ENTITY pref_search_set_default "Set as default"> -<!ENTITY pref_search_default "Default"> -<!ENTITY pref_search_remove "Remove"> + +<!ENTITY pref_dialog_set_default "Set as default"> +<!ENTITY pref_dialog_default "Default"> +<!ENTITY pref_dialog_remove "Remove"> + <!ENTITY pref_search_last_toast "You can\'t remove or disable your last search engine."> +<!ENTITY pref_panels_show "Show"> +<!ENTITY pref_panels_hide "Hide"> + <!ENTITY datareporting_notification_title "&brandShortName; stats & data"> <!ENTITY datareporting_notification_action_long "Choose what information to share"> <!ENTITY datareporting_notification_action "Choose what to share"> <!ENTITY datareporting_notification_summary "To improve your experience, &brandShortName; automatically sends some information to &vendorShortName;."> <!ENTITY datareporting_notification_summary_short "To improve your experience, &brandShortName;…"> <!ENTITY datareporting_notification_ticker_text "&datareporting_notification_title;: &datareporting_notification_action_long;"> <!-- Localization note (datareporting_fhr_title, datareporting_fhr_summary2,
--- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -264,21 +264,25 @@ gbjar.sources += [ 'NotificationHandler.java', 'NotificationHelper.java', 'NotificationService.java', 'NSSBridge.java', 'OrderedBroadcastHelper.java', 'preferences/AlignRightLinkPreference.java', 'preferences/AndroidImport.java', 'preferences/AndroidImportPreference.java', + 'preferences/CustomListCategory.java', + 'preferences/CustomListPreference.java', 'preferences/FontSizePreference.java', 'preferences/GeckoPreferenceFragment.java', 'preferences/GeckoPreferences.java', 'preferences/LinkPreference.java', 'preferences/MultiChoicePreference.java', + 'preferences/PanelsPreference.java', + 'preferences/PanelsPreferenceCategory.java', 'preferences/PrivateDataPreference.java', 'preferences/SearchEnginePreference.java', 'preferences/SearchPreferenceCategory.java', 'preferences/SyncPreference.java', 'PrefsHelper.java', 'PrivateTab.java', 'prompts/ColorPickerInput.java', 'prompts/IconGridInput.java',
new file mode 100644 --- /dev/null +++ b/mobile/android/base/preferences/CustomListCategory.java @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.preferences; + +import android.content.Context; +import android.preference.PreferenceCategory; +import android.util.AttributeSet; + +public abstract class CustomListCategory extends PreferenceCategory { + protected CustomListPreference mDefaultReference; + + public CustomListCategory(Context context) { + super(context); + } + + public CustomListCategory(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CustomListCategory(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onAttachedToActivity() { + super.onAttachedToActivity(); + + setOrderingAsAdded(true); + } + + /** + * Set the default to some available list item. Used if the current default is removed or + * disabled. + */ + protected void setFallbackDefault() { + if (getPreferenceCount() > 0) { + CustomListPreference aItem = (CustomListPreference) getPreference(0); + setDefault(aItem); + } + } + + /** + * Removes the given item from the set of available list items. + * This only updates the UI, so callers are responsible for persisting any state. + * + * @param item The given item to remove. + */ + public void uninstall(CustomListPreference item) { + removePreference(item); + if (item == mDefaultReference) { + // If the default is being deleted, set a new default. + setFallbackDefault(); + } + } + + /** + * Sets the given item as the current default. + * This only updates the UI, so callers are responsible for persisting any state. + * + * @param item The intended new default. + */ + public void setDefault(CustomListPreference item) { + if (mDefaultReference != null) { + mDefaultReference.setIsDefault(false); + } + + item.setIsDefault(true); + mDefaultReference = item; + } +}
new file mode 100644 --- /dev/null +++ b/mobile/android/base/preferences/CustomListPreference.java @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.preferences; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.preference.Preference; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import org.mozilla.gecko.R; +import org.mozilla.gecko.util.ThreadUtils; + +/** + * Represents an element in a <code>CustomListCategory</code> preference menu. + * This preference con display a dialog when clicked, and also supports + * being set as a default item within the preference list category. + */ + +public abstract class CustomListPreference extends Preference implements View.OnLongClickListener { + protected String LOGTAG = "CustomListPreference"; + + // Indices of the buttons of the Dialog. + public static final int INDEX_SET_DEFAULT_BUTTON = 0; + + // Dialog item labels. + protected final String[] mDialogItems; + + // Dialog displayed when this element is tapped. + protected AlertDialog mDialog; + + // Cache label to avoid repeated use of the resource system. + public final String LABEL_IS_DEFAULT; + public final String LABEL_SET_AS_DEFAULT; + + protected boolean mIsDefault; + + // Enclosing parent category that contains this preference. + protected final CustomListCategory mParentCategory; + + /** + * Create a preference object to represent a list preference that is attached to + * a category. + * + * @param context The activity context we operate under. + * @param parentCategory The PreferenceCategory this object exists within. + */ + public CustomListPreference(Context context, CustomListCategory parentCategory) { + super(context); + + mParentCategory = parentCategory; + setLayoutResource(getPreferenceLayoutResource()); + + setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + CustomListPreference sPref = (CustomListPreference) preference; + sPref.showDialog(); + return true; + } + }); + + Resources res = getContext().getResources(); + + // Fetch these strings now, instead of every time we ever want to relabel a button. + LABEL_IS_DEFAULT = res.getString(R.string.pref_default); + LABEL_SET_AS_DEFAULT = res.getString(R.string.pref_dialog_set_default); + + mDialogItems = getDialogStrings(); + } + + /** + * Returns the Android resource id for the layout. + */ + protected abstract int getPreferenceLayoutResource(); + + /** + * Set whether this object's UI should display this as the default item. To ensure proper ordering, + * this method should only be called after this Preference is added to the PreferenceCategory. + * @param isDefault Flag indicating if this represents the default list item. + */ + public void setIsDefault(boolean isDefault) { + mIsDefault = isDefault; + if (isDefault) { + setOrder(0); + setSummary(LABEL_IS_DEFAULT); + } else { + setOrder(1); + setSummary(""); + } + } + + /** + * Returns the strings to be displayed in the dialog. + */ + abstract protected String[] getDialogStrings(); + + /** + * Display a dialog for this preference, when the preference is clicked. + */ + public void showDialog() { + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(getTitle().toString()); + builder.setItems(mDialogItems, new DialogInterface.OnClickListener() { + // Forward relevant events to the container class for handling. + @Override + public void onClick(DialogInterface dialog, int indexClicked) { + hideDialog(); + onDialogIndexClicked(indexClicked); + } + }); + + configureDialogBuilder(builder); + + // We have to construct the dialog itself on the UI thread. + mDialog = builder.create(); + mDialog.setOnShowListener(new DialogInterface.OnShowListener() { + // Called when the dialog is shown (so we're finally able to manipulate button enabledness). + @Override + public void onShow(DialogInterface dialog) { + configureShownDialog(); + } + }); + mDialog.show(); + } + + /** + * (Optional) Configure the AlertDialog builder. + */ + protected void configureDialogBuilder(AlertDialog.Builder builder) { + return; + } + + abstract protected void onDialogIndexClicked(int index); + + /** + * Disables buttons in the shown AlertDialog as required. The button elements are not created + * until after show is called, so this method has to be called from the onShowListener above. + * @see this.showDialog + */ + protected void configureShownDialog() { + // If this is already the default list item, disable the button for setting this as the default. + final TextView defaultButton = (TextView) mDialog.getListView().getChildAt(INDEX_SET_DEFAULT_BUTTON); + if (mIsDefault) { + defaultButton.setEnabled(false); + + // Failure to unregister this listener leads to tapping the button dismissing the dialog + // without doing anything. + defaultButton.setOnClickListener(null); + } + } + + /** + * Hide the dialog we previously created, if any. + */ + public void hideDialog() { + if (mDialog != null && mDialog.isShowing()) { + mDialog.dismiss(); + } + } + + @Override + public boolean onLongClick(View view) { + // Show the preference dialog on long-press. + showDialog(); + return true; + } +}
--- a/mobile/android/base/preferences/GeckoPreferences.java +++ b/mobile/android/base/preferences/GeckoPreferences.java @@ -148,18 +148,18 @@ public class GeckoPreferences final ListView mListView = getListView(); mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { // Call long-click handler if it the item implements it. final ListAdapter listAdapter = ((ListView) parent).getAdapter(); final Object listItem = listAdapter.getItem(position); - // Only SearchEnginePreference handles long clicks. - if (listItem instanceof SearchEnginePreference && listItem instanceof View.OnLongClickListener) { + // Only CustomListPreference handles long clicks. + if (listItem instanceof CustomListPreference && listItem instanceof View.OnLongClickListener) { final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem; return longClickListener.onLongClick(view); } return false; } }); if (Build.VERSION.SDK_INT >= 14)
new file mode 100644 --- /dev/null +++ b/mobile/android/base/preferences/PanelsPreference.java @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.preferences; + +import android.content.Context; +import android.content.res.Resources; +import android.preference.Preference; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.mozilla.gecko.R; + +public class PanelsPreference extends CustomListPreference { + protected String LOGTAG = "PanelsPreference"; + + private static final int INDEX_SHOW_BUTTON = 1; + private static final int INDEX_REMOVE_BUTTON = 2; + + private final String LABEL_HIDE; + private final String LABEL_SHOW; + + protected boolean mIsHidden = false; + + public PanelsPreference(Context context, CustomListCategory parentCategory) { + super(context, parentCategory); + + Resources res = getContext().getResources(); + LABEL_HIDE = res.getString(R.string.pref_panels_hide); + LABEL_SHOW = res.getString(R.string.pref_panels_show); + } + + @Override + protected int getPreferenceLayoutResource() { + return R.layout.preference_panels; + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + + // Override view handling so we can grey out "hidden" PanelPreferences. + view.setEnabled(!mIsHidden); + + if (view instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) view; + for (int i = 0; i < group.getChildCount(); i++) { + group.getChildAt(i).setEnabled(!mIsHidden); + } + } + } + + @Override + protected String[] getDialogStrings() { + Resources res = getContext().getResources(); + // XXX: Don't provide the "Remove" string for now, because we only support built-in + // panels, which can only be disabled. + return new String[] { LABEL_SET_AS_DEFAULT, + LABEL_HIDE }; + } + + @Override + public void setIsDefault(boolean isDefault) { + mIsDefault = isDefault; + if (isDefault) { + setSummary(LABEL_IS_DEFAULT); + if (mIsHidden) { + // Unhide the panel if it's being set as the default. + setHidden(false); + } + } else { + setSummary(""); + } + } + + @Override + protected void onDialogIndexClicked(int index) { + switch(index) { + case INDEX_SET_DEFAULT_BUTTON: + mParentCategory.setDefault(this); + break; + + case INDEX_SHOW_BUTTON: + ((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden); + break; + + case INDEX_REMOVE_BUTTON: + mParentCategory.uninstall(this); + break; + + default: + Log.w(LOGTAG, "Selected index out of range: " + index); + } + } + + @Override + protected void configureShownDialog() { + super.configureShownDialog(); + + // Handle Show/Hide buttons. + final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_SHOW_BUTTON); + hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE); + } + + public void setHidden(boolean toHide) { + if (toHide) { + setIsDefault(false); + } + + if (mIsHidden != toHide) { + mIsHidden = toHide; + notifyChanged(); + } + } + + public boolean isHidden() { + return mIsHidden; + } +}
new file mode 100644 --- /dev/null +++ b/mobile/android/base/preferences/PanelsPreferenceCategory.java @@ -0,0 +1,242 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.preferences; + +import android.content.Context; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +import org.mozilla.gecko.home.HomeConfig; +import org.mozilla.gecko.home.HomeConfig.PanelConfig; +import org.mozilla.gecko.util.ThreadUtils; +import org.mozilla.gecko.util.UiAsyncTask; + +public class PanelsPreferenceCategory extends CustomListCategory { + public static final String LOGTAG = "PanelsPrefCategory"; + + protected HomeConfig mHomeConfig; + protected final List<PanelConfig> mPanelConfigs = new ArrayList<PanelConfig>(); + + protected UiAsyncTask<Void, Void, List<PanelConfig>> mLoadTask; + protected UiAsyncTask<Void, Void, Void> mSaveTask; + + public PanelsPreferenceCategory(Context context) { + super(context); + initConfig(context); + } + + public PanelsPreferenceCategory(Context context, AttributeSet attrs) { + super(context, attrs); + initConfig(context); + } + + public PanelsPreferenceCategory(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initConfig(context); + } + + protected void initConfig(Context context) { + mHomeConfig = HomeConfig.getDefault(context); + } + + @Override + public void onAttachedToActivity() { + super.onAttachedToActivity(); + + loadHomeConfig(); + } + + /** + * Load the Home Panels config and populate the preferences screen and maintain local state. + */ + private void loadHomeConfig() { + mLoadTask = new UiAsyncTask<Void, Void, List<PanelConfig>>(ThreadUtils.getBackgroundHandler()) { + @Override + public List<PanelConfig> doInBackground(Void... params) { + return mHomeConfig.load(); + } + + @Override + public void onPostExecute(List<PanelConfig> panelConfigs) { + displayPanelConfig(panelConfigs); + } + }; + mLoadTask.execute(); + } + + private void displayPanelConfig(List<PanelConfig> panelConfigs) { + for (PanelConfig panelConfig: panelConfigs) { + // Populate our local copy of the panels. + mPanelConfigs.add(panelConfig); + + // Create and add the pref. + final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this); + pref.setTitle(panelConfig.getTitle()); + pref.setKey(panelConfig.getId()); + // XXX: Pull icon from PanelInfo. + addPreference(pref); + + if (panelConfig.isDefault()) { + mDefaultReference = pref; + pref.setIsDefault(true); + } + + if (panelConfig.isDisabled()) { + pref.setHidden(true); + } + } + } + + /** + * Update HomeConfig off the main thread. + * + * @param panelConfigs Configuration to be saved + */ + private void saveHomeConfig() { + final List<PanelConfig> panelConfigs = makeConfigListDeepCopy(); + mSaveTask = new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) { + @Override + public Void doInBackground(Void... params) { + mHomeConfig.save(panelConfigs); + return null; + } + }; + mSaveTask.execute(); + } + + private List<PanelConfig> makeConfigListDeepCopy() { + List<PanelConfig> copiedList = new ArrayList<PanelConfig>(); + for (PanelConfig panelConfig : mPanelConfigs) { + copiedList.add(new PanelConfig(panelConfig)); + } + return copiedList; + } + + @Override + public void setDefault(CustomListPreference pref) { + super.setDefault(pref); + updateConfigDefault(); + saveHomeConfig(); + } + + @Override + protected void onPrepareForRemoval() { + if (mLoadTask != null) { + mLoadTask.cancel(true); + } + + if (mSaveTask != null) { + mSaveTask.cancel(true); + } + } + + /** + * Update the local HomeConfig default state from mDefaultReference. + */ + private void updateConfigDefault() { + String mId = null; + if (mDefaultReference != null) { + mId = mDefaultReference.getKey(); + } + + for (PanelConfig panelConfig : mPanelConfigs) { + if (TextUtils.equals(panelConfig.getId(), mId)) { + panelConfig.setIsDefault(true); + panelConfig.setIsDisabled(false); + } else { + panelConfig.setIsDefault(false); + } + } + } + + @Override + public void uninstall(CustomListPreference pref) { + super.uninstall(pref); + // This could change the default, so update the local version of the config. + updateConfigDefault(); + + final String mId = pref.getKey(); + PanelConfig toRemove = null; + for (PanelConfig panelConfig : mPanelConfigs) { + if (TextUtils.equals(panelConfig.getId(), mId)) { + toRemove = panelConfig; + break; + } + } + mPanelConfigs.remove(toRemove); + + saveHomeConfig(); + } + + /** + * Update the hide/show state of the preference and save the HomeConfig + * changes. + * + * @param pref Preference to update + * @param toHide New hidden state of the preference + */ + protected void setHidden(PanelsPreference pref, boolean toHide) { + pref.setHidden(toHide); + ensureDefaultForHide(pref, toHide); + + final String mId = pref.getKey(); + for (PanelConfig panelConfig : mPanelConfigs) { + if (TextUtils.equals(panelConfig.getId(), mId)) { + panelConfig.setIsDisabled(toHide); + break; + } + } + + saveHomeConfig(); + } + + /** + * Ensure a default is set (if possible) for hiding/showing a pref. + * If hiding, try to find an enabled pref to set as the default. + * If showing, set it as the default if there is no default currently. + * + * This updates the local HomeConfig state. + * + * @param pref Preference getting updated + * @param toHide Boolean of the new hidden state + */ + private void ensureDefaultForHide(PanelsPreference pref, boolean toHide) { + if (toHide) { + // Set a default if there is an enabled panel left. + if (pref == mDefaultReference) { + setFallbackDefault(); + updateConfigDefault(); + } + } else { + if (mDefaultReference == null) { + super.setDefault(pref); + updateConfigDefault(); + } + } + } + + /** + * When the default panel is removed or disabled, find an enabled panel + * if possible and set it as mDefaultReference. + */ + @Override + protected void setFallbackDefault() { + for (int i = 0; i < getPreferenceCount(); i++) { + final PanelsPreference pref = (PanelsPreference) getPreference(i); + if (!pref.isHidden()) { + super.setDefault(pref); + return; + } + } + mDefaultReference = null; + } +}
--- a/mobile/android/base/preferences/SearchEnginePreference.java +++ b/mobile/android/base/preferences/SearchEnginePreference.java @@ -1,25 +1,22 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.preferences; import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; -import android.preference.Preference; import android.text.SpannableString; import android.util.Log; import android.view.View; -import android.widget.TextView; import android.widget.Toast; import java.util.Iterator; import org.json.JSONException; import org.json.JSONObject; import org.mozilla.gecko.favicons.decoders.FaviconDecoder; @@ -27,99 +24,108 @@ import org.mozilla.gecko.favicons.decode import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.R; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.widget.FaviconView; /** * Represents an element in the list of search engines on the preferences menu. */ -public class SearchEnginePreference extends Preference implements View.OnLongClickListener { - private static final String LOGTAG = "SearchEnginePreference"; - - // Indices in button array of the AlertDialog of the three buttons. - public static final int INDEX_SET_DEFAULT_BUTTON = 0; - public static final int INDEX_REMOVE_BUTTON = 1; - - // Cache label to avoid repeated use of the resource system. - public final String LABEL_IS_DEFAULT; +public class SearchEnginePreference extends CustomListPreference { + protected String LOGTAG = "SearchEnginePreference"; - // Specifies if this engine is configured as the default search engine. - private boolean mIsDefaultEngine; - - // Dialog element labels. - private String[] mDialogItems; - - // The popup displayed when this element is tapped. - private AlertDialog mDialog; - - private final SearchPreferenceCategory mParentCategory; + protected static final int INDEX_REMOVE_BUTTON = 1; // The icon to display in the prompt when clicked. private BitmapDrawable mPromptIcon; + // The bitmap backing the drawable above - needed separately for the FaviconView. private Bitmap mIconBitmap; private FaviconView mFaviconView; - /** - * Create a preference object to represent a search engine that is attached to category - * containingCategory. - * @param context The activity context we operate under. - * @param parentCategory The PreferenceCategory this object exists within. - * @see this.setSearchEngine - */ public SearchEnginePreference(Context context, SearchPreferenceCategory parentCategory) { - super(context); - mParentCategory = parentCategory; - - Resources res = getContext().getResources(); - - // Set the layout resource for this preference - includes a FaviconView. - setLayoutResource(R.layout.preference_search_engine); - - setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - SearchEnginePreference sPref = (SearchEnginePreference) preference; - sPref.showDialog(); - - return true; - } - }); - - // Fetch this resource now, instead of every time we ever want to relabel a button. - LABEL_IS_DEFAULT = res.getString(R.string.pref_search_default); - - // Set up default dialog items. - mDialogItems = new String[] { res.getString(R.string.pref_search_set_default), - res.getString(R.string.pref_search_remove) }; + super(context, parentCategory); } /** * Called by Android when we're bound to the custom view. Allows us to set the custom properties * of our custom view elements as we desire (We can now use findViewById on them). * * @param view The view instance for this Preference object. */ @Override protected void onBindView(View view) { super.onBindView(view); + // Set the icon in the FaviconView. mFaviconView = ((FaviconView) view.findViewById(R.id.search_engine_icon)); mFaviconView.updateAndScaleImage(mIconBitmap, getTitle().toString()); } @Override - public boolean onLongClick(View view) { - // Show the preference dialog on long-press. - showDialog(); - return true; + protected int getPreferenceLayoutResource() { + return R.layout.preference_search_engine; + } + + /** + * Returns the strings to be displayed in the dialog. + */ + @Override + protected String[] getDialogStrings() { + Resources res = getContext().getResources(); + return new String[] { LABEL_SET_AS_DEFAULT, + res.getString(R.string.pref_dialog_remove) }; } + @Override + public void showDialog() { + // If this is the last engine, then we are the default, and none of the options + // on this menu can do anything. + if (mParentCategory.getPreferenceCount() == 1) { + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(getContext(), R.string.pref_search_last_toast, Toast.LENGTH_SHORT).show(); + } + }); + return; + } + + super.showDialog(); + } + + @Override + protected void configureDialogBuilder(AlertDialog.Builder builder) { + // Copy the icon from this object to the prompt we produce. We lazily create the drawable, + // as the user may not ever actually tap this object. + if (mPromptIcon == null && mIconBitmap != null) { + mPromptIcon = new BitmapDrawable(getContext().getResources(), mFaviconView.getBitmap()); + } + + builder.setIcon(mPromptIcon); + } + + @Override + protected void onDialogIndexClicked(int index) { + switch (index) { + case INDEX_SET_DEFAULT_BUTTON: + mParentCategory.setDefault(this); + break; + + case INDEX_REMOVE_BUTTON: + mParentCategory.uninstall(this); + break; + + default: + Log.w(LOGTAG, "Selected index out of range."); + break; + } + } + /** * Configure this Preference object from the Gecko search engine JSON object. * @param geckoEngineJSON The Gecko-formatted JSON object representing the search engine. * @throws JSONException If the JSONObject is invalid. */ public void setSearchEngineFromJSON(JSONObject geckoEngineJSON) throws JSONException { final String engineName = geckoEngineJSON.getString("name"); final SpannableString titleSpannable = new SpannableString(engineName); @@ -179,126 +185,9 @@ public class SearchEnginePreference exte } } catch (IllegalArgumentException e) { Log.e(LOGTAG, "IllegalArgumentException creating Bitmap. Most likely a zero-length bitmap.", e); } catch (NullPointerException e) { Log.e(LOGTAG, "NullPointerException creating Bitmap. Most likely a zero-length bitmap.", e); } } - - /** - * Set if this object's UI should show that this is the default engine. To ensure proper ordering, - * this method should only be called after this Preference is added to the PreferenceCategory. - * @param isDefault Flag indicating if this represents the default engine. - */ - public void setIsDefaultEngine(boolean isDefault) { - mIsDefaultEngine = isDefault; - if (isDefault) { - setOrder(0); - setSummary(LABEL_IS_DEFAULT); - } else { - setOrder(1); - setSummary(""); - } - } - - /** - * Display the AlertDialog providing options to reconfigure this search engine. Sets an event - * listener to disable buttons in the dialog as appropriate after they have been constructed by - * Android. - * @see this.configureShownDialog - * @see this.hideDialog - */ - public void showDialog() { - // If we are the only engine left, then we are the default engine, and none of the options - // on this menu can do anything. - if (mParentCategory.getPreferenceCount() == 1) { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(getContext(), R.string.pref_search_last_toast, Toast.LENGTH_SHORT).show(); - } - }); - return; - } - - final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setTitle(getTitle().toString()); - builder.setItems(mDialogItems, new DialogInterface.OnClickListener() { - // Forward the various events that we care about to the container class for handling. - @Override - public void onClick(DialogInterface dialog, int indexClicked) { - hideDialog(); - switch (indexClicked) { - case INDEX_SET_DEFAULT_BUTTON: - mParentCategory.setDefault(SearchEnginePreference.this); - break; - case INDEX_REMOVE_BUTTON: - mParentCategory.uninstall(SearchEnginePreference.this); - break; - default: - Log.w(LOGTAG, "Selected index out of range."); - break; - } - } - }); - - // Copy the icon from this object to the prompt we produce. We lazily create the drawable, - // as the user may not ever actually tap this object. - if (mPromptIcon == null && mIconBitmap != null) { - mPromptIcon = new BitmapDrawable(getContext().getResources(), mFaviconView.getBitmap()); - } - builder.setIcon(mPromptIcon); - - // Icons are hidden until Bug 926711 is fixed. - //builder.setIcon(mPromptIcon); - - // We have to construct the dialog itself on the UI thread. - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - mDialog = builder.create(); - mDialog.setOnShowListener(new DialogInterface.OnShowListener() { - // Called when the dialog is shown (so we're finally able to manipulate button enabledness). - @Override - public void onShow(DialogInterface dialog) { - configureShownDialog(); - } - }); - mDialog.show(); - } - }); - } - - /** - * Hide the dialog we previously created, if any. - */ - public void hideDialog() { - ThreadUtils.postToUiThread(new Runnable() { - @Override - public void run() { - // Null check so we can chain engine-mutating methods up in SearchPreferenceCategory - // without consequence. - if (mDialog != null && mDialog.isShowing()) { - mDialog.dismiss(); - } - } - }); - } - - /** - * Disables buttons in the shown AlertDialog as required. The button elements are not created - * until after we call show, so this method has to be called from the onShowListener above. - * @see this.showDialog - */ - private void configureShownDialog() { - // If we are the default engine, disable the "Set as default" button. - final TextView defaultButton = (TextView) mDialog.getListView().getChildAt(INDEX_SET_DEFAULT_BUTTON); - // Disable "Set as default" button if we are already the default. - if (mIsDefaultEngine) { - defaultButton.setEnabled(false); - // Failure to unregister this listener leads to tapping the button dismissing the dialog - // without doing anything. - defaultButton.setOnClickListener(null); - } - } }
--- a/mobile/android/base/preferences/SearchPreferenceCategory.java +++ b/mobile/android/base/preferences/SearchPreferenceCategory.java @@ -1,64 +1,71 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.preferences; import android.content.Context; import android.preference.Preference; -import android.preference.PreferenceCategory; import android.util.AttributeSet; import android.util.Log; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; + import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.util.GeckoEventListener; -public class SearchPreferenceCategory extends PreferenceCategory implements GeckoEventListener { +public class SearchPreferenceCategory extends CustomListCategory implements GeckoEventListener { public static final String LOGTAG = "SearchPrefCategory"; - private SearchEnginePreference mDefaultEngineReference; - - // These seemingly redundant constructors are mandated by the Android system, else it fails to - // inflate this object. - - public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SearchPreferenceCategory(Context context) { + super(context); } public SearchPreferenceCategory(Context context, AttributeSet attrs) { super(context, attrs); } - public SearchPreferenceCategory(Context context) { - super(context); + public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); } @Override protected void onAttachedToActivity() { super.onAttachedToActivity(); - // Ensures default engine remains at top of list. - setOrderingAsAdded(true); - // Register for SearchEngines messages and request list of search engines from Gecko. GeckoAppShell.registerEventListener("SearchEngines:Data", this); GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null)); } @Override protected void onPrepareForRemoval() { GeckoAppShell.unregisterEventListener("SearchEngines:Data", this); } @Override + public void setDefault(CustomListPreference item) { + super.setDefault(item); + + sendGeckoEngineEvent("SearchEngines:SetDefault", item.getTitle().toString()); + } + + @Override + public void uninstall(CustomListPreference item) { + super.uninstall(item); + + sendGeckoEngineEvent("SearchEngines:Remove", item.getTitle().toString()); + } + + @Override public void handleMessage(String event, final JSONObject data) { if (event.equals("SearchEngines:Data")) { // Parse engines array from JSON. JSONArray engines; try { engines = data.getJSONArray("searchEngines"); } catch (JSONException e) { Log.e(LOGTAG, "Unable to decode search engine data from Gecko.", e); @@ -88,73 +95,34 @@ public class SearchPreferenceCategory ex addPreference(enginePreference); // The first element in the array is the default engine. if (i == 0) { // We set this here, not in setSearchEngineFromJSON, because it allows us to // keep a reference to the default engine to use when the AlertDialog // callbacks are used. - enginePreference.setIsDefaultEngine(true); - mDefaultEngineReference = enginePreference; + enginePreference.setIsDefault(true); + mDefaultReference = enginePreference; } } catch (JSONException e) { Log.e(LOGTAG, "JSONException parsing engine at index " + i, e); } } } } /** - * Set the default engine to any available engine. Used if the current default is removed or - * disabled. - */ - private void setFallbackDefaultEngine() { - if (getPreferenceCount() > 0) { - SearchEnginePreference aEngine = (SearchEnginePreference) getPreference(0); - setDefault(aEngine); - } - } - - /** * Helper method to send a particular event string to Gecko with an associated engine name. * @param event The type of event to send. * @param engine The engine to which the event relates. */ - private void sendGeckoEngineEvent(String event, SearchEnginePreference engine) { + private void sendGeckoEngineEvent(String event, String engineName) { JSONObject json = new JSONObject(); try { - json.put("engine", engine.getTitle()); + json.put("engine", engineName); } catch (JSONException e) { Log.e(LOGTAG, "JSONException creating search engine configuration change message for Gecko.", e); return; } GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent(event, json.toString())); } - - // Methods called by tapping items on the submenus for each search engine are below. - - /** - * Removes the given engine from the set of available engines. - * @param engine The engine to remove. - */ - public void uninstall(SearchEnginePreference engine) { - removePreference(engine); - if (engine == mDefaultEngineReference) { - // If they're deleting their default engine, get them a new default engine. - setFallbackDefaultEngine(); - } - - sendGeckoEngineEvent("SearchEngines:Remove", engine); - } - - /** - * Sets the given engine as the current default engine. - * @param engine The intended new default engine. - */ - public void setDefault(SearchEnginePreference engine) { - engine.setIsDefaultEngine(true); - mDefaultEngineReference.setIsDefaultEngine(false); - mDefaultEngineReference = engine; - - sendGeckoEngineEvent("SearchEngines:SetDefault", engine); - } }
new file mode 100644 --- /dev/null +++ b/mobile/android/base/resources/layout/preference_panels.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:gravity="center_vertical" + android:orientation="vertical" + android:paddingLeft="15dp" + android:paddingRight="?android:attr/scrollbarSize"> + + <TextView android:id="@+android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:singleLine="true" + android:ellipsize="marquee" + android:fadingEdge="horizontal" /> + + <TextView android:id="@+android:id/summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:maxLines="2" /> + +</LinearLayout>
--- a/mobile/android/base/resources/xml-v11/preferences_customize.xml +++ b/mobile/android/base/resources/xml-v11/preferences_customize.xml @@ -10,16 +10,23 @@ android:enabled="false"> <PreferenceScreen android:title="@string/pref_category_search" android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" > <extra android:name="resource" android:value="preferences_search"/> </PreferenceScreen> + + <PreferenceScreen android:title="@string/pref_category_home" + android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" > + <extra android:name="resource" + android:value="preferences_home" /> + </PreferenceScreen> + <org.mozilla.gecko.preferences.AndroidImportPreference android:key="android.not_a_preference.import_android" gecko:entries="@array/pref_import_android_entries" gecko:entryKeys="@array/pref_import_android_keys" gecko:initialValues="@array/pref_import_android_values" android:title="@string/pref_import_android" android:positiveButtonText="@string/bookmarkhistory_button_import" android:negativeButtonText="@string/button_cancel"
--- a/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml +++ b/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml @@ -17,16 +17,22 @@ android:persistent="false" /> <PreferenceScreen android:title="@string/pref_category_search" android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" > <extra android:name="resource" android:value="preferences_search"/> </PreferenceScreen> + <PreferenceScreen android:title="@string/pref_category_home" + android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" > + <extra android:name="resource" + android:value="preferences_home" /> + </PreferenceScreen> + <org.mozilla.gecko.preferences.AndroidImportPreference android:key="android.not_a_preference.import_android" gecko:entries="@array/pref_import_android_entries" gecko:entryKeys="@array/pref_import_android_keys" gecko:initialValues="@array/pref_import_android_values" android:title="@string/pref_import_android" android:positiveButtonText="@string/bookmarkhistory_button_import" android:negativeButtonText="@string/button_cancel"
--- a/mobile/android/base/resources/xml/preferences_customize.xml +++ b/mobile/android/base/resources/xml/preferences_customize.xml @@ -1,26 +1,37 @@ <?xml version="1.0" encoding="utf-8"?> <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:gecko="http://schemas.android.com/apk/res-auto" android:enabled="false"> + <PreferenceScreen android:title="@string/pref_category_search" > <intent android:action="android.intent.action.VIEW" android:targetPackage="@string/android_package_name" android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" > <extra android:name="resource" android:value="preferences_search" /> </intent> </PreferenceScreen> + <PreferenceScreen android:title="@string/pref_category_home" > + <intent android:action="android.intent.action.VIEW" + android:targetPackage="@string/android_package_name" + android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" > + <extra + android:name="resource" + android:value="preferences_home" /> + </intent> + </PreferenceScreen> + <org.mozilla.gecko.preferences.AndroidImportPreference android:key="android.not_a_preference.import_android" gecko:entries="@array/pref_import_android_entries" gecko:entryKeys="@array/pref_import_android_keys" gecko:initialValues="@array/pref_import_android_values" android:title="@string/pref_import_android" android:positiveButtonText="@string/bookmarkhistory_button_import" android:negativeButtonText="@string/button_cancel"
new file mode 100644 --- /dev/null +++ b/mobile/android/base/resources/xml/preferences_home.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:gecko="http://schemas.android.com/apk/res-auto" + android:title="@string/pref_category_home" + android:enabled="false"> + + <org.mozilla.gecko.preferences.PanelsPreferenceCategory + android:title="@string/pref_category_home_panels"/> + +</PreferenceScreen>
--- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -103,16 +103,19 @@ <string name="pref_search_restore_defaults">&pref_search_restore_defaults;</string> <string name="pref_search_restore_defaults_summary">&pref_search_restore_defaults_summary;</string> <string name="pref_search_tip">&pref_search_tip;</string> <string name="pref_category_devtools">&pref_category_devtools;</string> <string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string> <string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string> + <string name="pref_category_home">&pref_category_home;</string> + <string name="pref_category_home_panels">&pref_category_home_panels;</string> + <string name="pref_header_customize">&pref_header_customize;</string> <string name="pref_header_display">&pref_header_display;</string> <string name="pref_header_privacy_short">&pref_header_privacy_short;</string> <string name="pref_header_vendor">&pref_header_vendor;</string> <string name="pref_header_devtools">&pref_header_devtools;</string> <string name="pref_remember_signons">&pref_remember_signons;</string> @@ -164,22 +167,25 @@ <string name="pref_update_autodownload_disabled">&pref_update_autodownload_never;</string> <string name="pref_update_autodownload_enabled">&pref_update_autodownload_always;</string> <string name="pref_about_firefox">&pref_about_firefox;</string> <string name="pref_vendor_faqs">&pref_vendor_faqs;</string> <string name="pref_vendor_feedback">&pref_vendor_feedback;</string> - <!-- Strings used in default search provider config preferences menu --> - <string name="pref_search_set_default">&pref_search_set_default;</string> - <string name="pref_search_default">&pref_search_default;</string> - <string name="pref_search_remove">&pref_search_remove;</string> + <string name="pref_dialog_set_default">&pref_dialog_set_default;</string> + <string name="pref_default">&pref_dialog_default;</string> + <string name="pref_dialog_remove">&pref_dialog_remove;</string> + <string name="pref_search_last_toast">&pref_search_last_toast;</string> + <string name="pref_panels_show">&pref_panels_show;</string> + <string name="pref_panels_hide">&pref_panels_hide;</string> + <string name="datareporting_notification_title">&datareporting_notification_title;</string> <string name="datareporting_notification_action_long">&datareporting_notification_action_long;</string> <string name="datareporting_notification_action">&datareporting_notification_action;</string> <string name="datareporting_notification_summary">&datareporting_notification_summary;</string> <string name="datareporting_notification_summary_short">&datareporting_notification_summary_short;</string> <string name="datareporting_notification_ticker_text">&datareporting_notification_ticker_text;</string> <string name="datareporting_telemetry_title">&datareporting_telemetry_title;</string>
--- a/mobile/android/base/sync/ExtendedJSONObject.java +++ b/mobile/android/base/sync/ExtendedJSONObject.java @@ -10,16 +10,17 @@ import java.io.StringReader; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; +import org.mozilla.apache.commons.codec.binary.Base64; import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException; /** * Extend JSONObject to do little things, like, y'know, accessing members. * * @author rnewman * */ @@ -358,9 +359,31 @@ public class ExtendedJSONObject { if (value == null) { throw new BadRequiredFieldJSONException("Expected key not present in result: " + k); } if (requiredFieldClass != null && !(requiredFieldClass.isInstance(value))) { throw new BadRequiredFieldJSONException("Value for key not an instance of " + requiredFieldClass + ": " + k); } } } + + /** + * Return a base64-encoded string value as a byte array. + */ + public byte[] getByteArrayBase64(String key) { + String s = (String) this.object.get(key); + if (s == null) { + return null; + } + return Base64.decodeBase64(s); + } + + /** + * Return a hex-encoded string value as a byte array. + */ + public byte[] getByteArrayHex(String key) { + String s = (String) this.object.get(key); + if (s == null) { + return null; + } + return Utils.hex2Byte(s); + } }
--- a/mobile/android/base/tests/testSettingsMenuItems.java +++ b/mobile/android/base/tests/testSettingsMenuItems.java @@ -27,16 +27,17 @@ public class testSettingsMenuItems exten * * These menu items are the ones that are always present - to test menu items that differ * based on build (e.g., release vs. nightly), add the items in <code>addConditionalSettings</code>. */ // Customize menu items. String[][] OPTIONS_CUSTOMIZE = { { "Search settings", "", "Show search suggestions", "Installed search engines"}, + { "Home", "", "Panels" }, { "Import from Android", "", "Bookmarks", "History", "Import" }, { "Tabs", "Don't restore after quitting " + BRAND_NAME, "Always restore", "Don't restore after quitting " + BRAND_NAME }, }; // Display menu items. String[][] OPTIONS_DISPLAY = { { "Text size" }, { "Title bar", "Show page title", "Show page title", "Show page address" },
--- a/mobile/android/chrome/content/aboutApps.js +++ b/mobile/android/chrome/content/aboutApps.js @@ -7,16 +7,18 @@ * ***** END LICENSE BLOCK ***** */ let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm") Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/AppsUtils.jsm"); +const DEFAULT_ICON = "chrome://browser/skin/images/default-app-icon.png"; + let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutApps.properties"); XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem) .rootTreeItem .QueryInterface(Ci.nsIInterfaceRequestor) @@ -34,16 +36,17 @@ function openLink(aEvent) { try { let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); let url = formatter.formatURLPref(aEvent.currentTarget.getAttribute("pref")); let BrowserApp = gChromeWin.BrowserApp; BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }); } catch (ex) {} } +#ifndef MOZ_ANDROID_SYNTHAPKS var ContextMenus = { target: null, init: function() { document.addEventListener("contextmenu", this, false); document.getElementById("addToHomescreenLabel").addEventListener("click", this.addToHomescreen, false); document.getElementById("uninstallLabel").addEventListener("click", this.uninstall, false); }, @@ -53,18 +56,17 @@ var ContextMenus = { this.target = event.target; while (!this.target.hasAttribute("contextmenu")) { this.target = this.target.parentNode; } }, addToHomescreen: function() { let manifest = this.target.manifest; - let origin = Services.io.newURI(this.target.app.origin, null, null); - gChromeWin.WebappsUI.createShortcut(manifest.name, manifest.fullLaunchPath(), gChromeWin.WebappsUI.getBiggestIcon(manifest.icons, origin), "webapp"); + gChromeWin.WebappsUI.createShortcut(manifest.name, manifest.fullLaunchPath(), manifest.biggestIconURL || DEFAULT_ICON, "webapp"); this.target = null; }, uninstall: function() { navigator.mozApps.mgmt.uninstall(this.target.app); let manifest = this.target.manifest; gChromeWin.sendMessageToJava({ @@ -72,35 +74,31 @@ var ContextMenus = { title: manifest.name, url: manifest.fullLaunchPath(), origin: this.target.app.origin, shortcutType: "webapp" }); this.target = null; } } +#endif function onLoad(aEvent) { - try { - let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter); - let link = document.getElementById("marketplaceURL"); - let url = formatter.formatURLPref(link.getAttribute("pref")); - link.setAttribute("href", url); - } catch (e) {} - let elmts = document.querySelectorAll("[pref]"); for (let i = 0; i < elmts.length; i++) { elmts[i].addEventListener("click", openLink, false); } navigator.mozApps.mgmt.oninstall = onInstall; navigator.mozApps.mgmt.onuninstall = onUninstall; updateList(); +#ifndef MOZ_ANDROID_SYNTHAPKS ContextMenus.init(); +#endif } function updateList() { let grid = document.getElementById("appgrid"); while (grid.lastChild) { grid.removeChild(grid.lastChild); } @@ -114,26 +112,31 @@ function updateList() { } function addApplication(aApp) { let list = document.getElementById("appgrid"); let manifest = new ManifestHelper(aApp.manifest, aApp.origin); let container = document.createElement("div"); container.className = "app list-item"; +#ifndef MOZ_ANDROID_SYNTHAPKS container.setAttribute("contextmenu", "appmenu"); +#endif container.setAttribute("id", "app-" + aApp.origin); container.setAttribute("mozApp", aApp.origin); container.setAttribute("title", manifest.name); let img = document.createElement("img"); - let origin = Services.io.newURI(aApp.origin, null, null); - img.src = gChromeWin.WebappsUI.getBiggestIcon(manifest.icons, origin); + img.src = manifest.biggestIconURL || DEFAULT_ICON; img.onerror = function() { - img.src = "chrome://browser/skin/images/default-app-icon.png"; + // If the image failed to load, and it was not our default icon, attempt to + // use our default as a fallback. + if (img.src != DEFAULT_ICON) { + img.src = DEFAULT_ICON; + } } img.setAttribute("title", manifest.name); let title = document.createElement("div"); title.appendChild(document.createTextNode(manifest.name)); container.appendChild(img); container.appendChild(title);
--- a/mobile/android/chrome/content/aboutApps.xhtml +++ b/mobile/android/chrome/content/aboutApps.xhtml @@ -24,21 +24,23 @@ <link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" /> <link rel="stylesheet" type="text/css" href="chrome://browser/skin/aboutBase.css" media="all" /> <link rel="stylesheet" type="text/css" href="chrome://browser/skin/aboutApps.css" media="all" /> <script type="text/javascript;version=1.8" src="chrome://browser/content/aboutApps.js"></script> </head> <body dir="&locale.dir;"> +#ifndef MOZ_ANDROID_SYNTHAPKS <menu type="context" id="appmenu"> <menuitem id="addToHomescreenLabel" label="&aboutApps.addToHomescreen;"></menuitem> <menuitem id="uninstallLabel" label="&aboutApps.uninstall;"></menuitem> </menu> - +#endif + <div class="header"> <div>&aboutApps.header;</div> <div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL"/> </div> <div id="main-container" class="hidden"> <div> <div class="spacer" id="spacer1"> </div>
--- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -413,62 +413,65 @@ var BrowserApp = { if (window.arguments[2]) gScreenHeight = window.arguments[2]; if (window.arguments[3]) pinned = window.arguments[3]; if (window.arguments[4]) this.isGuest = window.arguments[4]; } - let status = this.startupStatus(); if (pinned) { - this._initRuntime(status, url, aUrl => this.addTab(aUrl)); + this._initRuntime(this._startupStatus, url, aUrl => this.addTab(aUrl)); } else { SearchEngines.init(); this.initContextMenu(); } // The order that context menu items are added is important // Make sure the "Open in App" context menu item appears at the bottom of the list ExternalApps.init(); // XXX maybe we don't do this if the launch was kicked off from external Services.io.offline = false; // Broadcast a UIReady message so add-ons know we are finished with startup let event = document.createEvent("Events"); event.initEvent("UIReady", true, false); window.dispatchEvent(event); - if (status) + if (this._startupStatus) this.onAppUpdated(); // Store the low-precision buffer pref this.gUseLowPrecision = Services.prefs.getBoolPref("layers.low-precision-buffer"); // notify java that gecko has loaded sendMessageToJava({ type: "Gecko:Ready" }); #ifdef MOZ_SAFE_BROWSING // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008. setTimeout(function() { SafeBrowsing.init(); }, 5000); #endif }, - startupStatus: function() { - let savedmstone = null; + get _startupStatus() { + delete this._startupStatus; + + let savedMilestone = null; try { - savedmstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone"); + savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone"); } catch (e) { } -#expand let ourmstone = "__MOZ_APP_VERSION__"; - if (ourmstone != savedmstone) { - Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourmstone); - return savedmstone ? "upgrade" : "new"; - } - return ""; +#expand let ourMilestone = "__MOZ_APP_VERSION__"; + this._startupStatus = ""; + if (ourMilestone != savedMilestone) { + Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourMilestone); + this._startupStatus = savedMilestone ? "upgrade" : "new"; + } + + return this._startupStatus; }, /** * Pass this a locale string, such as "fr" or "es_ES". */ setLocale: function (locale) { console.log("browser.js: requesting locale set: " + locale); sendMessageToJava({ type: "Locale:Set", locale: locale }); @@ -911,18 +914,18 @@ var BrowserApp = { type: "Tab:Close", tabID: aTab.id }; sendMessageToJava(message); }, #ifdef MOZ_ANDROID_SYNTHAPKS _loadWebapp: function(aMessage) { - // TODO: figure out when (if ever) to pass "new" to the status parameter. - this._initRuntime("", aMessage.url, aUrl => { + + this._initRuntime(this._startupStatus, aMessage.url, aUrl => { this.manifestUrl = aMessage.url; this.addTab(aUrl, { title: aMessage.name }); }); }, #endif // Calling this will update the state in BrowserApp after a tab has been // closed in the Java UI. @@ -7088,16 +7091,17 @@ var WebappsUI = { uninit: function unint() { Services.obs.removeObserver(this, "webapps-ask-install"); Services.obs.removeObserver(this, "webapps-launch"); Services.obs.removeObserver(this, "webapps-uninstall"); Services.obs.removeObserver(this, "webapps-install-error"); }, + DEFAULT_ICON: "chrome://browser/skin/images/default-app-icon.png", DEFAULT_PREFS_FILENAME: "default-prefs.js", observe: function observe(aSubject, aTopic, aData) { let data = {}; try { data = JSON.parse(aData); data.mm = aSubject; } catch(ex) { } @@ -7129,49 +7133,16 @@ var WebappsUI = { sendMessageToJava({ type: "WebApps:Uninstall", origin: data.origin }); break; } }, - getBiggestIcon: function getBiggestIcon(aIcons, aOrigin) { - const DEFAULT_ICON = "chrome://browser/skin/images/default-app-icon.png"; - if (!aIcons) - return DEFAULT_ICON; - - let iconSizes = Object.keys(aIcons); - if (iconSizes.length == 0) - return DEFAULT_ICON; - iconSizes.sort(function(a, b) a - b); - - let biggestIcon = aIcons[iconSizes.pop()]; - let iconURI = null; - try { - iconURI = Services.io.newURI(biggestIcon, null, null); - if (iconURI.scheme == "data") { - return iconURI.spec; - } - } catch (ex) { - // we don't have a biggestIcon or its not a valid url - } - - // if we have an origin, try to resolve biggestIcon as a relative url - if (!iconURI && aOrigin) { - try { - iconURI = Services.io.newURI(aOrigin.resolve(biggestIcon), null, null); - } catch (ex) { - console.log("Could not resolve url: " + aOrigin.spec + " " + biggestIcon + " - " + ex); - } - } - - return iconURI ? iconURI.spec : DEFAULT_ICON; - }, - doInstall: function doInstall(aData) { let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest; let manifest = new ManifestHelper(jsonManifest, aData.app.origin); let showPrompt = true; if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) { // Get a profile for the app to be installed in. We'll download everything before creating the icons. let origin = aData.app.origin; @@ -7187,17 +7158,17 @@ var WebappsUI = { let self = this; DOMApplicationRegistry.confirmInstall(aData, file, function (aManifest) { let localeManifest = new ManifestHelper(aManifest, aData.app.origin); // the manifest argument is the manifest from within the zip file, // TODO so now would be a good time to ask about permissions. - self.makeBase64Icon(self.getBiggestIcon(manifest.icons, Services.io.newURI(aData.app.origin, null, null)), + self.makeBase64Icon(localeManifest.biggestIconURL || this.DEFAULT_ICON, function(scaledIcon, fullsizeIcon) { // if java returned a profile path to us, try to use it to pre-populate the app cache // also save the icon so that it can be used in the splash screen try { let iconFile = file.clone(); iconFile.append("logo.png"); let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist); persist.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; @@ -7317,19 +7288,18 @@ var WebappsUI = { canvas = null; aCallbackFunction.call(null, scaledIcon, fullsizeIcon); }; favicon.onerror = function() { Cu.reportError("CreateShortcut: favicon image load error"); // if the image failed to load, and it was not our default icon, attempt to // use our default as a fallback - let uri = Services.io.newURI(favicon.src, null, null); - if (!/^chrome$/.test(uri.scheme)) { - favicon.src = WebappsUI.getBiggestIcon(null); + if (favicon.src != WebappsUI.DEFAULT_ICON) { + favicon.src = WebappsUI.DEFAULT_ICON; } }; favicon.src = aIconURL; }, createShortcut: function createShortcut(aTitle, aURL, aIconURL, aType) { this.makeBase64Icon(aIconURL, function _createShortcut(icon) {
--- a/mobile/android/chrome/jar.mn +++ b/mobile/android/chrome/jar.mn @@ -21,18 +21,18 @@ chrome.jar: content/aboutPrivateBrowsing.js (content/aboutPrivateBrowsing.js) content/aboutReader.html (content/aboutReader.html) content/aboutReader.js (content/aboutReader.js) content/Readability.js (content/Readability.js) content/JSDOMParser.js (content/JSDOMParser.js) content/readerWorker.js (content/readerWorker.js) content/aboutHome.xhtml (content/aboutHome.xhtml) content/aboutRights.xhtml (content/aboutRights.xhtml) - content/aboutApps.xhtml (content/aboutApps.xhtml) - content/aboutApps.js (content/aboutApps.js) +* content/aboutApps.xhtml (content/aboutApps.xhtml) +* content/aboutApps.js (content/aboutApps.js) content/blockedSite.xhtml (content/blockedSite.xhtml) content/languages.properties (content/languages.properties) content/browser.xul (content/browser.xul) * content/browser.js (content/browser.js) content/bindings/checkbox.xml (content/bindings/checkbox.xml) content/bindings/settings.xml (content/bindings/settings.xml) content/exceptions.js (content/exceptions.js) content/downloads.js (content/downloads.js)
--- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -381,16 +381,18 @@ @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@ #endif @BINPATH@/components/nsINIProcessor.manifest @BINPATH@/components/nsINIProcessor.js @BINPATH@/components/nsPrompter.manifest @BINPATH@/components/nsPrompter.js @BINPATH@/components/TelemetryPing.js @BINPATH@/components/TelemetryPing.manifest +@BINPATH@/components/TelemetryStartup.js +@BINPATH@/components/TelemetryStartup.manifest @BINPATH@/components/Webapps.js @BINPATH@/components/Webapps.manifest @BINPATH@/components/AppsService.js @BINPATH@/components/AppsService.manifest @BINPATH@/components/Push.js @BINPATH@/components/Push.manifest
--- a/netwerk/cache2/CacheEntry.cpp +++ b/netwerk/cache2/CacheEntry.cpp @@ -1128,21 +1128,24 @@ NS_IMETHODIMP CacheEntry::AsyncDoom(nsIC { mozilla::MutexAutoLock lock(mLock); if (mIsDoomed || mDoomCallback) return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state mIsDoomed = true; mDoomCallback = aCallback; - BackgroundOp(Ops::DOOM); } - // Immediately remove the entry from the storage hash table - CacheStorageService::Self()->RemoveEntry(this); + // This immediately removes the entry from the master hashtable and also + // immediately dooms the file. This way we make sure that any consumer + // after this point asking for the same entry won't get + // a) this entry + // b) a new entry with the same file + PurgeAndDoom(); return NS_OK; } NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval) { NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE); @@ -1398,56 +1401,63 @@ bool CacheEntry::Purge(uint32_t aWhat) LOG((" ?")); return false; } void CacheEntry::PurgeAndDoom() { LOG(("CacheEntry::PurgeAndDoom [this=%p]", this)); - MOZ_ASSERT(CacheStorageService::IsOnManagementThread()); - CacheStorageService::Self()->RemoveEntry(this); DoomAlreadyRemoved(); } void CacheEntry::DoomAlreadyRemoved() { LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this)); + mozilla::MutexAutoLock lock(mLock); + mIsDoomed = true; - if (!CacheStorageService::IsOnManagementThread()) { - mozilla::MutexAutoLock lock(mLock); - - BackgroundOp(Ops::DOOM); - return; - } - - CacheStorageService::Self()->UnregisterEntry(this); + // This schedules dooming of the file, dooming is ensured to happen + // sooner than demand to open the same file made after this point + // so that we don't get this file for any newer opened entry(s). + DoomFile(); - { - mozilla::MutexAutoLock lock(mLock); + // Must force post here since may be indirectly called from + // InvokeCallbacks of this entry and we don't want reentrancy here. + BackgroundOp(Ops::CALLBACKS, true); + // Process immediately when on the management thread. + BackgroundOp(Ops::UNREGISTER); +} - if (mCallbacks.Length()) { - // Must force post here since may be indirectly called from - // InvokeCallbacks of this entry and we don't want reentrancy here. - BackgroundOp(Ops::CALLBACKS, true); - } - } +void CacheEntry::DoomFile() +{ + nsresult rv = NS_ERROR_NOT_AVAILABLE; if (NS_SUCCEEDED(mFileStatus)) { - nsresult rv = mFile->Doom(mDoomCallback ? this : nullptr); + // Always calls the callback asynchronously. + rv = mFile->Doom(mDoomCallback ? this : nullptr); if (NS_SUCCEEDED(rv)) { LOG((" file doomed")); return; } + + if (NS_ERROR_FILE_NOT_FOUND == rv) { + // File is set to be just memory-only, notify the callbacks + // and pretend dooming has succeeded. From point of view of + // the entry it actually did - the data is gone and cannot be + // reused. + rv = NS_OK; + } } - OnFileDoomed(NS_OK); + // Always posts to the main thread. + OnFileDoomed(rv); } void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync) { mLock.AssertCurrentThreadOwns(); if (!CacheStorageService::IsOnManagementThread() || aForceAsync) { if (mBackgroundOperations.Set(aOperations)) @@ -1491,20 +1501,20 @@ void CacheEntry::BackgroundOp(uint32_t a } if (aOperations & Ops::REGISTER) { LOG(("CacheEntry REGISTER [this=%p]", this)); CacheStorageService::Self()->RegisterEntry(this); } - if (aOperations & Ops::DOOM) { - LOG(("CacheEntry DOOM [this=%p]", this)); + if (aOperations & Ops::UNREGISTER) { + LOG(("CacheEntry UNREGISTER [this=%p]", this)); - DoomAlreadyRemoved(); + CacheStorageService::Self()->UnregisterEntry(this); } if (aOperations & Ops::CALLBACKS) { LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this)); mozilla::MutexAutoLock lock(mLock); InvokeCallbacks(); }
--- a/netwerk/cache2/CacheEntry.h +++ b/netwerk/cache2/CacheEntry.h @@ -216,16 +216,19 @@ private: void OnOutputClosed(); // Schedules a background operation on the management thread. // When executed on the management thread directly, the operation(s) // is (are) executed immediately. void BackgroundOp(uint32_t aOperation, bool aForceAsync = false); void StoreFrecency(); + // Called only from DoomAlreadyRemoved() + void DoomFile(); + already_AddRefed<CacheEntryHandle> ReopenTruncated(nsICacheEntryOpenCallback* aCallback); void TransferCallbacks(CacheEntry & aFromEntry); mozilla::Mutex mLock; // Reflects the number of existing handles for this entry friend class CacheEntryHandle; ::mozilla::ThreadSafeAutoRefCnt mHandlersCount; @@ -300,18 +303,18 @@ private: CacheEntryHandle* mWriter; // Background thread scheduled operation. Set (under the lock) one // of this flags to tell the background thread what to do. class Ops { public: static uint32_t const REGISTER = 1 << 0; static uint32_t const FRECENCYUPDATE = 1 << 1; - static uint32_t const DOOM = 1 << 2; - static uint32_t const CALLBACKS = 1 << 3; + static uint32_t const CALLBACKS = 1 << 2; + static uint32_t const UNREGISTER = 1 << 3; Ops() : mFlags(0) { } uint32_t Grab() { uint32_t flags = mFlags; mFlags = 0; return flags; } bool Set(uint32_t aFlags) { if (mFlags & aFlags) return false; mFlags |= aFlags; return true; } private: uint32_t mFlags; } mBackgroundOperations;
--- a/netwerk/cache2/CacheFile.cpp +++ b/netwerk/cache2/CacheFile.cpp @@ -3,28 +3,25 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CacheLog.h" #include "CacheFile.h" #include "CacheFileChunk.h" #include "CacheFileInputStream.h" #include "CacheFileOutputStream.h" -#include "nsITimer.h" #include "nsThreadUtils.h" #include "mozilla/DebugOnly.h" #include <algorithm> #include "nsComponentManagerUtils.h" #include "nsProxyRelease.h" namespace mozilla { namespace net { -#define kMetadataWriteDelay 5000 - class NotifyCacheFileListenerEvent : public nsRunnable { public: NotifyCacheFileListenerEvent(CacheFileListener *aCallback, nsresult aResult, bool aIsNew) : mCallback(aCallback) , mRV(aResult) , mIsNew(aIsNew) @@ -88,139 +85,16 @@ public: protected: nsCOMPtr<CacheFileChunkListener> mCallback; nsresult mRV; uint32_t mChunkIdx; nsRefPtr<CacheFileChunk> mChunk; }; -class MetadataWriteTimer : public nsITimerCallback -{ -public: - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_NSITIMERCALLBACK - - MetadataWriteTimer(CacheFile *aFile); - nsresult Fire(); - nsresult Cancel(); - bool ShouldFireNew(); - -protected: - virtual ~MetadataWriteTimer(); - - nsCOMPtr<nsIWeakReference> mFile; - nsCOMPtr<nsITimer> mTimer; - nsCOMPtr<nsIEventTarget> mTarget; - PRIntervalTime mFireTime; -}; - -NS_IMPL_ADDREF(MetadataWriteTimer) -NS_IMPL_RELEASE(MetadataWriteTimer) - -NS_INTERFACE_MAP_BEGIN(MetadataWriteTimer) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback) - NS_INTERFACE_MAP_ENTRY(nsITimerCallback) -NS_INTERFACE_MAP_END_THREADSAFE - -MetadataWriteTimer::MetadataWriteTimer(CacheFile *aFile) - : mFireTime(0) -{ - LOG(("MetadataWriteTimer::MetadataWriteTimer() [this=%p, file=%p]", - this, aFile)); - MOZ_COUNT_CTOR(MetadataWriteTimer); - - mFile = do_GetWeakReference(static_cast<CacheFileChunkListener *>(aFile)); - mTarget = NS_GetCurrentThread(); -} - -MetadataWriteTimer::~MetadataWriteTimer() -{ - LOG(("MetadataWriteTimer::~MetadataWriteTimer() [this=%p]", this)); - MOZ_COUNT_DTOR(MetadataWriteTimer); - - NS_ProxyRelease(mTarget, mTimer.forget().get()); - NS_ProxyRelease(mTarget, mFile.forget().get()); -} - -NS_IMETHODIMP -MetadataWriteTimer::Notify(nsITimer *aTimer) -{ - LOG(("MetadataWriteTimer::Notify() [this=%p, timer=%p]", this, aTimer)); - - MOZ_ASSERT(aTimer == mTimer); - - nsCOMPtr<nsISupports> supp = do_QueryReferent(mFile); - if (!supp) - return NS_OK; - - CacheFile *file = static_cast<CacheFile *>( - static_cast<CacheFileChunkListener *>(supp.get())); - - CacheFileAutoLock lock(file); - - if (file->mTimer != this) - return NS_OK; - - if (file->mMemoryOnly) - return NS_OK; - - file->WriteMetadataIfNeeded(); - file->mTimer = nullptr; - - return NS_OK; -} - -nsresult -MetadataWriteTimer::Fire() -{ - LOG(("MetadataWriteTimer::Fire() [this=%p]", this)); - - nsresult rv; - -#ifdef DEBUG - bool onCurrentThread = false; - rv = mTarget->IsOnCurrentThread(&onCurrentThread); - MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrentThread); -#endif - - mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mTimer->InitWithCallback(this, kMetadataWriteDelay, - nsITimer::TYPE_ONE_SHOT); - NS_ENSURE_SUCCESS(rv, rv); - - mFireTime = PR_IntervalNow(); - - return NS_OK; -} - -nsresult -MetadataWriteTimer::Cancel() -{ - LOG(("MetadataWriteTimer::Cancel() [this=%p]", this)); - return mTimer->Cancel(); -} - -bool -MetadataWriteTimer::ShouldFireNew() -{ - uint32_t delta = PR_IntervalToMilliseconds(PR_IntervalNow() - mFireTime); - - if (delta > kMetadataWriteDelay / 2) { - LOG(("MetadataWriteTimer::ShouldFireNew() - returning true [this=%p]", - this)); - return true; - } - - LOG(("MetadataWriteTimer::ShouldFireNew() - returning false [this=%p]", - this)); - return false; -} class DoomFileHelper : public CacheFileIOListener { public: NS_DECL_THREADSAFE_ISUPPORTS DoomFileHelper(CacheFileListener *aListener) : mListener(aListener) @@ -268,110 +142,22 @@ private: } nsCOMPtr<CacheFileListener> mListener; }; NS_IMPL_ISUPPORTS1(DoomFileHelper, CacheFileIOListener) -// We try to write metadata when the last reference to CacheFile is released. -// We call WriteMetadataIfNeeded() under the lock from CacheFile::Release() and -// if writing fails synchronously the listener is released and lock in -// CacheFile::Release() is re-entered. This helper class ensures that the -// listener is released outside the lock in case of synchronous failure. -class MetadataListenerHelper : public CacheFileMetadataListener -{ -public: - NS_DECL_THREADSAFE_ISUPPORTS - - MetadataListenerHelper(CacheFile *aFile) - : mFile(aFile) - { - MOZ_COUNT_CTOR(MetadataListenerHelper); - mListener = static_cast<CacheFileMetadataListener *>(aFile); - } - - NS_IMETHOD OnMetadataRead(nsresult aResult) - { - MOZ_CRASH("MetadataListenerHelper::OnMetadataRead should not be called!"); - return NS_ERROR_UNEXPECTED; - } - - NS_IMETHOD OnMetadataWritten(nsresult aResult) - { - mFile = nullptr; - return mListener->OnMetadataWritten(aResult); - } - -private: - virtual ~MetadataListenerHelper() - { - MOZ_COUNT_DTOR(MetadataListenerHelper); - if (mFile) { - mFile->ReleaseOutsideLock(mListener.forget().get()); - } - } - - CacheFile* mFile; - nsCOMPtr<CacheFileMetadataListener> mListener; -}; - -NS_IMPL_ISUPPORTS1(MetadataListenerHelper, CacheFileMetadataListener) - - NS_IMPL_ADDREF(CacheFile) -NS_IMETHODIMP_(nsrefcnt) -CacheFile::Release() -{ - NS_PRECONDITION(0 != mRefCnt, "dup release"); - nsrefcnt count = --mRefCnt; - NS_LOG_RELEASE(this, count, "CacheFile"); - - MOZ_ASSERT(count != 0, "Unexpected"); - - if (count == 1) { - bool deleteFile = false; - - // Not using CacheFileAutoLock since it hard-refers the file - // and in it's destructor reenters this method. - Lock(); - - if (mMemoryOnly) { - deleteFile = true; - } - else if (mMetadata) { - WriteMetadataIfNeeded(); - if (mWritingMetadata) { - MOZ_ASSERT(mRefCnt > 1); - } else { - if (mRefCnt == 1) - deleteFile = true; - } - } - - Unlock(); - - if (!deleteFile) { - return count; - } - - NS_LOG_RELEASE(this, 0, "CacheFile"); - delete (this); - return 0; - } - - return count; -} - +NS_IMPL_RELEASE(CacheFile) NS_INTERFACE_MAP_BEGIN(CacheFile) NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener) NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileIOListener) NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileMetadataListener) - NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozilla::net::CacheFileChunkListener) NS_INTERFACE_MAP_END_THREADSAFE CacheFile::CacheFile() : mLock("CacheFile.mLock") , mOpeningFile(false) , mReady(false) @@ -379,26 +165,25 @@ CacheFile::CacheFile() , mDataAccessed(false) , mDataIsDirty(false) , mWritingMetadata(false) , mStatus(NS_OK) , mDataSize(-1) , mOutput(nullptr) { LOG(("CacheFile::CacheFile() [this=%p]", this)); - - NS_ADDREF(this); - MOZ_COUNT_CTOR(CacheFile); } CacheFile::~CacheFile() { LOG(("CacheFile::~CacheFile() [this=%p]", this)); - MOZ_COUNT_DTOR(CacheFile); + MutexAutoLock lock(mLock); + if (!mMemoryOnly) + WriteMetadataIfNeededLocked(true); } nsresult CacheFile::Init(const nsACString &aKey, bool aCreateNew, bool aMemoryOnly, bool aPriority, bool aKeyIsHash, @@ -556,17 +341,17 @@ CacheFile::OnChunkWritten(nsresult aResu LOG(("CacheFile::OnChunkWritten() - Caching unused chunk [this=%p, chunk=%p]", this, aChunk)); aChunk->mRemovingChunk = true; ReleaseOutsideLock(static_cast<CacheFileChunkListener *>( aChunk->mFile.forget().get())); mCachedChunks.Put(aChunk->Index(), aChunk); mChunks.Remove(aChunk->Index()); - WriteMetadataIfNeeded(); + WriteMetadataIfNeededLocked(); return NS_OK; } nsresult CacheFile::OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx, CacheFileChunk *aChunk) { @@ -779,17 +564,17 @@ CacheFile::OnMetadataWritten(nsresult aR if (NS_FAILED(aResult)) { // TODO close streams with an error ??? } if (mOutput || mInputs.Length() || mChunks.Count()) return NS_OK; if (IsDirty()) - WriteMetadataIfNeeded(); + WriteMetadataIfNeededLocked(); if (!mWritingMetadata) { LOG(("CacheFile::OnMetadataWritten() - Releasing file handle [this=%p]", this)); CacheFileIOManager::ReleaseNSPRHandle(mHandle); } return NS_OK; @@ -912,21 +697,20 @@ nsresult CacheFile::Doom(CacheFileListener *aCallback) { CacheFileAutoLock lock(this); MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile); LOG(("CacheFile::Doom() [this=%p, listener=%p]", this, aCallback)); - nsresult rv; + nsresult rv = NS_OK; if (mMemoryOnly) { - // TODO what exactly to do here? - return NS_ERROR_NOT_AVAILABLE; + return NS_ERROR_FILE_NOT_FOUND; } nsCOMPtr<CacheFileIOListener> listener; if (aCallback || !mHandle) { listener = new DoomFileHelper(aCallback); } if (mHandle) { rv = CacheFileIOManager::DoomFile(mHandle, listener); @@ -1351,17 +1135,17 @@ CacheFile::RemoveChunk(CacheFileChunk *a this, chunk.get())); chunk->mRemovingChunk = true; ReleaseOutsideLock(static_cast<CacheFileChunkListener *>( chunk->mFile.forget().get())); mCachedChunks.Put(chunk->Index(), chunk); mChunks.Remove(chunk->Index()); if (!mMemoryOnly) - WriteMetadataIfNeeded(); + WriteMetadataIfNeededLocked(); } return NS_OK; } nsresult CacheFile::RemoveInput(CacheFileInputStream *aInput) { @@ -1371,17 +1155,17 @@ CacheFile::RemoveInput(CacheFileInputStr DebugOnly<bool> found; found = mInputs.RemoveElement(aInput); MOZ_ASSERT(found); ReleaseOutsideLock(static_cast<nsIInputStream*>(aInput)); if (!mMemoryOnly) - WriteMetadataIfNeeded(); + WriteMetadataIfNeededLocked(); return NS_OK; } nsresult CacheFile::RemoveOutput(CacheFileOutputStream *aOutput) { AssertOwnsLock(); @@ -1395,17 +1179,17 @@ CacheFile::RemoveOutput(CacheFileOutputS } mOutput = nullptr; // Cancel all queued chunk and update listeners that cannot be satisfied NotifyListenersAboutOutputRemoval(); if (!mMemoryOnly) - WriteMetadataIfNeeded(); + WriteMetadataIfNeededLocked(); // Notify close listener as the last action aOutput->NotifyCloseListener(); return NS_OK; } nsresult @@ -1527,81 +1311,70 @@ CacheFile::IsDirty() return mDataIsDirty || mMetadata->IsDirty(); } void CacheFile::WriteMetadataIfNeeded() { LOG(("CacheFile::WriteMetadataIfNeeded() [this=%p]", this)); + CacheFileAutoLock lock(this); + + if (!mMemoryOnly) + WriteMetadataIfNeededLocked(); +} + +void +CacheFile::WriteMetadataIfNeededLocked(bool aFireAndForget) +{ + // When aFireAndForget is set to true, we are called from dtor. + // |this| must not be referenced after this method returns! + + LOG(("CacheFile::WriteMetadataIfNeededLocked() [this=%p]", this)); + nsresult rv; AssertOwnsLock(); MOZ_ASSERT(!mMemoryOnly); - if (mTimer) { - mTimer->Cancel(); - mTimer = nullptr; + if (!aFireAndForget) { + // if aFireAndForget is set, we are called from dtor. Write + // scheduler hard-refers CacheFile otherwise, so we cannot be here. + CacheFileIOManager::UnscheduleMetadataWrite(this); } if (NS_FAILED(mStatus)) return; if (!IsDirty() || mOutput || mInputs.Length() || mChunks.Count() || mWritingMetadata || mOpeningFile) return; - LOG(("CacheFile::WriteMetadataIfNeeded() - Writing metadata [this=%p]", + LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing metadata [this=%p]", this)); - nsRefPtr<MetadataListenerHelper> mlh = new MetadataListenerHelper(this); - - rv = mMetadata->WriteMetadata(mDataSize, mlh); + rv = mMetadata->WriteMetadata(mDataSize, aFireAndForget ? nullptr : this); if (NS_SUCCEEDED(rv)) { mWritingMetadata = true; mDataIsDirty = false; - } - else { - LOG(("CacheFile::WriteMetadataIfNeeded() - Writing synchronously failed " + } else { + LOG(("CacheFile::WriteMetadataIfNeededLocked() - Writing synchronously failed " "[this=%p]", this)); // TODO: close streams with error if (NS_SUCCEEDED(mStatus)) mStatus = rv; } } void CacheFile::PostWriteTimer() { LOG(("CacheFile::PostWriteTimer() [this=%p]", this)); - nsresult rv; - - AssertOwnsLock(); - - if (mTimer) { - if (mTimer->ShouldFireNew()) { - LOG(("CacheFile::PostWriteTimer() - Canceling old timer [this=%p]", - this)); - mTimer->Cancel(); - mTimer = nullptr; - } - else { - LOG(("CacheFile::PostWriteTimer() - Keeping old timer [this=%p]", this)); - return; - } - } - - mTimer = new MetadataWriteTimer(this); - - rv = mTimer->Fire(); - if (NS_FAILED(rv)) { - LOG(("CacheFile::PostWriteTimer() - Firing timer failed with error 0x%08x " - "[this=%p]", rv, this)); - } + CacheFileIOManager::ScheduleMetadataWrite(this); } PLDHashOperator CacheFile::WriteAllCachedChunks(const uint32_t& aIdx, nsRefPtr<CacheFileChunk>& aChunk, void* aClosure) { CacheFile *file = static_cast<CacheFile*>(aClosure);
--- a/netwerk/cache2/CacheFile.h +++ b/netwerk/cache2/CacheFile.h @@ -1,17 +1,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef CacheFile__h__ #define CacheFile__h__ #include "CacheFileChunk.h" -#include "nsWeakReference.h" #include "CacheFileIOManager.h" #include "CacheFileMetadata.h" #include "nsRefPtrHashtable.h" #include "nsClassHashtable.h" #include "mozilla/Mutex.h" class nsIInputStream; class nsIOutputStream; @@ -42,17 +41,16 @@ public: }; NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileListener, CACHEFILELISTENER_IID) class CacheFile : public CacheFileChunkListener , public CacheFileIOListener , public CacheFileMetadataListener - , public nsSupportsWeakReference { public: NS_DECL_THREADSAFE_ISUPPORTS CacheFile(); nsresult Init(const nsACString &aKey, bool aCreateNew, @@ -96,22 +94,22 @@ public: nsresult GetFrecency(uint32_t *_retval); nsresult GetLastFetched(uint32_t *_retval); nsresult GetFetchCount(uint32_t *_retval); bool DataSize(int64_t* aSize); void Key(nsACString& aKey) { aKey = mKey; } private: + friend class CacheFileIOManager; friend class CacheFileChunk; friend class CacheFileInputStream; friend class CacheFileOutputStream; friend class CacheFileAutoLock; friend class MetadataWriteTimer; - friend class MetadataListenerHelper; virtual ~CacheFile(); void Lock(); void Unlock(); void AssertOwnsLock(); void ReleaseOutsideLock(nsISupports *aObject); @@ -134,16 +132,17 @@ private: CacheFileChunkListener *aCallback); nsresult NotifyChunkListeners(uint32_t aIndex, nsresult aResult, CacheFileChunk *aChunk); bool HaveChunkListeners(uint32_t aIndex); void NotifyListenersAboutOutputRemoval(); bool IsDirty(); void WriteMetadataIfNeeded(); + void WriteMetadataIfNeededLocked(bool aFireAndForget = false); void PostWriteTimer(); static PLDHashOperator WriteAllCachedChunks(const uint32_t& aIdx, nsRefPtr<CacheFileChunk>& aChunk, void* aClosure); static PLDHashOperator FailListenersIfNonExistentChunk( const uint32_t& aIdx, @@ -166,17 +165,16 @@ private: bool mKeyIsHash; nsresult mStatus; int64_t mDataSize; nsCString mKey; nsRefPtr<CacheFileHandle> mHandle; nsRefPtr<CacheFileMetadata> mMetadata; nsCOMPtr<CacheFileListener> mListener; - nsRefPtr<MetadataWriteTimer> mTimer; nsCOMPtr<CacheFileIOListener> mDoomAfterOpenListener; nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks; nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners; nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks; nsTArray<CacheFileInputStream*> mInputs; CacheFileOutputStream *mOutput;
--- a/netwerk/cache2/CacheFileIOManager.cpp +++ b/netwerk/cache2/CacheFileIOManager.cpp @@ -4,16 +4,17 @@ #include "CacheLog.h" #include "CacheFileIOManager.h" #include "../cache/nsCacheUtils.h" #include "CacheHashUtils.h" #include "CacheStorageService.h" #include "nsThreadUtils.h" +#include "CacheFile.h" #include "nsIFile.h" #include "mozilla/Telemetry.h" #include "mozilla/DebugOnly.h" #include "nsDirectoryServiceUtils.h" #include "nsAppDirectoryServiceDefs.h" #include "private/pprio.h" #include "mozilla/VisualEventTracer.h" @@ -31,343 +32,292 @@ // XXX add necessary include file for ftruncate (or equivalent) #endif namespace mozilla { namespace net { #define kOpenHandlesLimit 64 +#define kMetadataWriteDelay 5000 +bool +CacheFileHandle::DispatchRelease() +{ + if (CacheFileIOManager::IsOnIOThreadOrCeased()) + return false; + + nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget(); + if (!ioTarget) + return false; + + nsRefPtr<nsRunnableMethod<CacheFileHandle, nsrefcnt, false> > event = + NS_NewNonOwningRunnableMethod(this, &CacheFileHandle::Release); + nsresult rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); + if (NS_FAILED(rv)) + return false; + + return true; +} NS_IMPL_ADDREF(CacheFileHandle) NS_IMETHODIMP_(nsrefcnt) CacheFileHandle::Release() { + nsrefcnt count = mRefCnt - 1; + if (DispatchRelease()) { + // Redispatched to the IO thread. + return count; + } + + MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); + LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get())); NS_PRECONDITION(0 != mRefCnt, "dup release"); - nsrefcnt count = --mRefCnt; + count = --mRefCnt; NS_LOG_RELEASE(this, count, "CacheFileHandle"); if (0 == count) { mRefCnt = 1; delete (this); return 0; } - if (!mRemovingHandle && count == 1 && !mClosed) { - CacheFileIOManager::gInstance->CloseHandle(this); - } - return count; } NS_INTERFACE_MAP_BEGIN(CacheFileHandle) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END_THREADSAFE CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority) : mHash(aHash) , mIsDoomed(false) - , mRemovingHandle(false) , mPriority(aPriority) , mClosed(false) , mInvalid(false) , mFileExists(false) , mFileSize(-1) , mFD(nullptr) { LOG(("CacheFileHandle::CacheFileHandle() [this=%p]", this)); - MOZ_COUNT_CTOR(CacheFileHandle); - PR_INIT_CLIST(this); } CacheFileHandle::~CacheFileHandle() { LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this)); - MOZ_COUNT_DTOR(CacheFileHandle); + + MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); + + nsRefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance; + if (ioMan) { + ioMan->CloseHandleInternal(this); + } +} + +/****************************************************************************** + * CacheFileHandles::HandleHashKey + *****************************************************************************/ + +void +CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle) +{ + MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); + + mHandles.InsertElementAt(0, aHandle); +} + +void +CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle) +{ + MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); + + DebugOnly<bool> found; + found = mHandles.RemoveElement(aHandle); + MOZ_ASSERT(found); } +already_AddRefed<CacheFileHandle> +CacheFileHandles::HandleHashKey::GetNewestHandle() +{ + MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); + + nsRefPtr<CacheFileHandle> handle; + if (mHandles.Length()) + handle = mHandles[0]; + + return handle.forget(); +} + +void +CacheFileHandles::HandleHashKey::GetHandles(nsTArray<nsRefPtr<CacheFileHandle> > &aResult) +{ + MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); + + for (uint32_t i = 0; i < mHandles.Length(); ++i) { + CacheFileHandle* handle = mHandles[i]; + aResult.AppendElement(handle); + } +} + +#ifdef DEBUG + +void +CacheFileHandles::HandleHashKey::AssertHandlesState() +{ + for (uint32_t i = 0; i < mHandles.Length(); ++i) { + CacheFileHandle* handle = mHandles[i]; + MOZ_ASSERT(handle->IsDoomed()); + } +} + +#endif /****************************************************************************** * CacheFileHandles *****************************************************************************/ -class CacheFileHandlesEntry : public PLDHashEntryHdr -{ -public: - PRCList *mHandles; - SHA1Sum::Hash mHash; -}; - -const PLDHashTableOps CacheFileHandles::mOps = -{ - PL_DHashAllocTable, - PL_DHashFreeTable, - HashKey, - MatchEntry, - MoveEntry, - ClearEntry, - PL_DHashFinalizeStub -}; - CacheFileHandles::CacheFileHandles() - : mInitialized(false) { LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this)); MOZ_COUNT_CTOR(CacheFileHandles); } CacheFileHandles::~CacheFileHandles() { LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this)); MOZ_COUNT_DTOR(CacheFileHandles); - - if (mInitialized) - Shutdown(); -} - -nsresult -CacheFileHandles::Init() -{ - LOG(("CacheFileHandles::Init() %p", this)); - - MOZ_ASSERT(!mInitialized); - mInitialized = PL_DHashTableInit(&mTable, &mOps, nullptr, - sizeof(CacheFileHandlesEntry), 512); - - return mInitialized ? NS_OK : NS_ERROR_OUT_OF_MEMORY; -} - -void -CacheFileHandles::Shutdown() -{ - LOG(("CacheFileHandles::Shutdown() %p", this)); - - if (mInitialized) { - PL_DHashTableFinish(&mTable); - mInitialized = false; - } -} - -PLDHashNumber -CacheFileHandles::HashKey(PLDHashTable *table, const void *key) -{ - const SHA1Sum::Hash *hash = static_cast<const SHA1Sum::Hash *>(key); - return static_cast<PLDHashNumber>(((*hash)[0] << 24) | ((*hash)[1] << 16) | - ((*hash)[2] << 8) | (*hash)[3]); -} - -bool -CacheFileHandles::MatchEntry(PLDHashTable *table, - const PLDHashEntryHdr *header, - const void *key) -{ - const CacheFileHandlesEntry *entry; - - entry = static_cast<const CacheFileHandlesEntry *>(header); - - return (memcmp(&entry->mHash, key, sizeof(SHA1Sum::Hash)) == 0); -} - -void -CacheFileHandles::MoveEntry(PLDHashTable *table, - const PLDHashEntryHdr *from, - PLDHashEntryHdr *to) -{ - const CacheFileHandlesEntry *src; - CacheFileHandlesEntry *dst; - - src = static_cast<const CacheFileHandlesEntry *>(from); - dst = static_cast<CacheFileHandlesEntry *>(to); - - dst->mHandles = src->mHandles; - memcpy(&dst->mHash, &src->mHash, sizeof(SHA1Sum::Hash)); - - LOG(("CacheFileHandles::MoveEntry() hash=%08x%08x%08x%08x%08x " - "moving from %p to %p", LOGSHA1(src->mHash), from, to)); - - // update pointer to mHash in all handles - CacheFileHandle *handle = (CacheFileHandle *)PR_LIST_HEAD(dst->mHandles); - while (handle != dst->mHandles) { - handle->mHash = &dst->mHash; - handle = (CacheFileHandle *)PR_NEXT_LINK(handle); - } -} - -void -CacheFileHandles::ClearEntry(PLDHashTable *table, - PLDHashEntryHdr *header) -{ - CacheFileHandlesEntry *entry = static_cast<CacheFileHandlesEntry *>(header); - delete entry->mHandles; - entry->mHandles = nullptr; } nsresult CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash, bool aReturnDoomed, CacheFileHandle **_retval) { - MOZ_ASSERT(mInitialized); + MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased()); + MOZ_ASSERT(aHash); // find hash entry for key - CacheFileHandlesEntry *entry; - entry = static_cast<CacheFileHandlesEntry *>( - PL_DHashTableOperate(&mTable, - (void *)aHash, - PL_DHASH_LOOKUP)); - if (PL_DHASH_ENTRY_IS_FREE(entry)) { + HandleHashKey *entry = mTable.GetEntry(*aHash); + if (!entry) {