author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Tue, 01 Sep 2015 15:06:05 +0200 | |
changeset 260392 | 4aa12ff974247beecc4b131ddd6299f774163025 |
parent 260391 | 6ddd2771e1643911cedf92e8714da1fa26c1e74a (current diff) |
parent 260298 | dd509db16a138beb777599662ad88e3207bc2d2c (diff) |
child 260393 | 8613a4ad3e3ba283fa7cbf3a31ee91178dd37b6e |
push id | 64495 |
push user | ryanvm@gmail.com |
push date | Wed, 02 Sep 2015 01:16:33 +0000 |
treeherder | mozilla-inbound@e747377d86eb [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 43.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
|
dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ | file | annotate | diff | comparison | revisions | |
dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ | file | annotate | diff | comparison | revisions | |
js/src/jit-test/tests/basic/bug1195298.js | file | annotate | diff | comparison | revisions |
--- a/accessible/jsat/TraversalRules.jsm +++ b/accessible/jsat/TraversalRules.jsm @@ -24,43 +24,46 @@ XPCOMUtils.defineLazyModuleGetter(this, XPCOMUtils.defineLazyModuleGetter(this, 'Prefilters', // jshint ignore:line 'resource://gre/modules/accessibility/Constants.jsm'); let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images'); function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter) { this._explicitMatchRoles = new Set(aRoles); this._matchRoles = aRoles; - if (aRoles.indexOf(Roles.LABEL) < 0) { - this._matchRoles.push(Roles.LABEL); - } - if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) { - // Used for traversing in to child OOP frames. - this._matchRoles.push(Roles.INTERNAL_FRAME); + if (aRoles.length) { + if (aRoles.indexOf(Roles.LABEL) < 0) { + this._matchRoles.push(Roles.LABEL); + } + if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) { + // Used for traversing in to child OOP frames. + this._matchRoles.push(Roles.INTERNAL_FRAME); + } } this._matchFunc = aMatchFunc || function() { return Filters.MATCH; }; this.preFilter = aPreFilter || gSimplePreFilter; } BaseTraversalRule.prototype = { - getMatchRoles: function BaseTraversalRule_getmatchRoles(aRules) { - aRules.value = this._matchRoles; - return aRules.value.length; + getMatchRoles: function BaseTraversalRule_getmatchRoles(aRoles) { + aRoles.value = this._matchRoles; + return aRoles.value.length; }, match: function BaseTraversalRule_match(aAccessible) { let role = aAccessible.role; if (role == Roles.INTERNAL_FRAME) { return (Utils.getMessageManager(aAccessible.DOMNode)) ? Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE; } - let matchResult = this._explicitMatchRoles.has(role) ? - this._matchFunc(aAccessible) : Filters.IGNORE; + let matchResult = + (this._explicitMatchRoles.has(role) || !this._explicitMatchRoles.size) ? + this._matchFunc(aAccessible) : Filters.IGNORE; // If we are on a label that nests a checkbox/radio we should land on it. // It is a bigger touch target, and it reduces clutter. if (role == Roles.LABEL && !(matchResult & Filters.IGNORE_SUBTREE)) { let control = Utils.getEmbeddedControl(aAccessible); if (control && this._explicitMatchRoles.has(control.role)) { matchResult = this._matchFunc(control) | Filters.IGNORE_SUBTREE; }
--- a/accessible/tests/mochitest/jsat/doc_traversal.html +++ b/accessible/tests/mochitest/jsat/doc_traversal.html @@ -5,65 +5,68 @@ <meta charset="utf-8" /> <style> .content:before { content: "Content"; } </style> </head> <body> - <h3 id="heading-1">A small first heading</h3> - <form> - <label for="input-1-1">Name:</label> - <input id="input-1-1"> - <label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label> - <button id="button-1-1">Magic Button</button> - <label for="radio-1-1">Radios are old: </label> - <input id="radio-1-1" type="radio"> - <label for="radio-1-2">Radios are new: </label> - <input id="radio-1-2" type="radio"> - <label for="input-1-3">Password:</label> - <input id="input-1-3" type="password"> - <label for="input-1-4">Unlucky number:</label> - <input id="input-1-4" type="tel"> - <input id="button-1-2" type="button" value="Fun"> - <label for="checkbox-1-1">Check me: </label> - <input id="checkbox-1-1" type="checkbox"> - <select id="select-1-1"> - <option>Value 1</option> - <option>Value 2</option> - <option>Value 3</option> - </select> - <select id="select-1-2" multiple="true"> - <option>Value 1</option> - <option>Value 2</option> - <option>Value 3</option> - </select> - <label for="checkbox-1-2">Check me too: </label> - <input id="checkbox-1-2" type="checkbox"> - <label for="checkbox-1-3">But not me: </label> - <input id="checkbox-1-3" type="checkbox" aria-hidden="true"> - <label for="checkbox-1-4">Or me! </label> - <input id="checkbox-1-4" type="checkbox" style="visibility:hidden"> - <select id="select-1-3" size="3"> - <option>Value 1</option> - <option>Value 2</option> - <option>Value 3</option> - </select> - <label for="input-1-5">Electronic mailing address:</label> - <input id="input-1-5" type="email"> - <input id="button-1-3" type="submit" value="Submit"> - - </form> - <h2 id="heading-2">A larger second</h2> - <div id="heading-3" role="heading">ARIA is fun</div> - <input id="button-2-1" type="button" value="More Fun"> - <div id="button-2-2" tabindex="0" role="button">ARIA fun</div> - <div id="button-2-3" tabindex="0" role="button" aria-pressed="false">My little togglebutton</div> - <div id="button-2-4" tabindex="0" role="spinbutton">ARIA fun</div> + <header id="header-1"> + <h3 id="heading-1">A small first heading</h3> + <form> + <label for="input-1-1">Name:</label> + <input id="input-1-1"> + <label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label> + <button id="button-1-1">Magic Button</button> + <label for="radio-1-1">Radios are old: </label> + <input id="radio-1-1" type="radio"> + <label for="radio-1-2">Radios are new: </label> + <input id="radio-1-2" type="radio"> + <label for="input-1-3">Password:</label> + <input id="input-1-3" type="password"> + <label for="input-1-4">Unlucky number:</label> + <input id="input-1-4" type="tel"> + <input id="button-1-2" type="button" value="Fun"> + <label for="checkbox-1-1">Check me: </label> + <input id="checkbox-1-1" type="checkbox"> + <select id="select-1-1"> + <option>Value 1</option> + <option>Value 2</option> + <option>Value 3</option> + </select> + <select id="select-1-2" multiple="true"> + <option>Value 1</option> + <option>Value 2</option> + <option>Value 3</option> + </select> + <label for="checkbox-1-2">Check me too: </label> + <input id="checkbox-1-2" type="checkbox"> + <label for="checkbox-1-3">But not me: </label> + <input id="checkbox-1-3" type="checkbox" aria-hidden="true"> + <label for="checkbox-1-4">Or me! </label> + <input id="checkbox-1-4" type="checkbox" style="visibility:hidden"> + <select id="select-1-3" size="3"> + <option>Value 1</option> + <option>Value 2</option> + <option>Value 3</option> + </select> + <label for="input-1-5">Electronic mailing address:</label> + <input id="input-1-5" type="email"> + <input id="button-1-3" type="submit" value="Submit"> + </form> + </header> + <main id="main-1"> + <h2 id="heading-2">A larger second</h2> + <div id="heading-3" role="heading">ARIA is fun</div> + <input id="button-2-1" type="button" value="More Fun"> + <div id="button-2-2" tabindex="0" role="button">ARIA fun</div> + <div id="button-2-3" tabindex="0" role="button" aria-pressed="false">My little togglebutton</div> + <div id="button-2-4" tabindex="0" role="spinbutton">ARIA fun</div> + </main> <h1 id="heading-4" style="display:none">Invisible header</h1> <dl id="list-1"> <dt id="listitem-1-1">Programming Language</dt> <dd>A esoteric weapon wielded by only the most formidable warriors, for its unrelenting strict power is unfathomable.</dd> </dl> <ul id="list-2" onclick="alert('hi');"> <li id="listitem-2-1">Lists of Programming Languages</li> @@ -136,18 +139,20 @@ </tfoot> <tbody> <tr> <td>Dirt</td> <td>Messy Stuff</td> </tr> </tbody> </table> - <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div> - <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div> + <footer id="footer-1"> + <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div> + <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div> + </footer> <span id="switch-1" role="switch" aria-checked="false" aria-label="Light switch"></span> <p>This is a MathML formula <math id="math-1" display="block"> <mfrac> <mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow> <msqrt><mn>3</mn><mo>/</mo><mn>4</mn></msqrt> </mfrac> </math> with some text after.</p>
--- a/accessible/tests/mochitest/jsat/test_traversal.html +++ b/accessible/tests/mochitest/jsat/test_traversal.html @@ -122,16 +122,19 @@ 'link-2', 'anchor-2', 'link-3', '3', '1', '4', '1', 'Sunday', 'M', 'Week 1', '3', '4', '7', '2', '5 8', 'gridcell4', 'Just an innocuous separator', 'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt', 'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2', 'switch-1', 'This is a MathML formula ', 'math-1', 'with some text after.']); + queueTraversalSequence(gQueue, docAcc, TraversalRules.Landmark, null, + ['header-1', 'main-1', 'footer-1']); + gQueue.invoke(); } SimpleTest.waitForExplicitFinish(); addLoadEvent(function () { /* We open a new browser because we need to test with a top-level content document. */ openBrowserWindow(
--- a/b2g/chrome/content/devtools/hud.js +++ b/b2g/chrome/content/devtools/hud.js @@ -557,19 +557,19 @@ let consoleWatcher = { metric.name = 'warnings'; output += 'Warning (console)'; break; case 'info': this.handleTelemetryMessage(target, packet); // Currently, informational log entries are tracked only by - // advanced telemetry. Nonetheless, for consistency, we - // continue here and let the function return normally, when it - // concludes 'info' entries are not being watched. + // telemetry. Nonetheless, for consistency, we continue here + // and let the function return normally, when it concludes 'info' + // entries are not being watched. metric.name = 'info'; break; default: return; } break; @@ -590,16 +590,35 @@ let consoleWatcher = { value: Math.round(duration) }); break; default: return; } + if (developerHUD._telemetry) { + // Always record telemetry for these metrics. + if (metric.name === 'errors' || metric.name === 'warnings' || metric.name === 'reflows') { + let value = target.metrics.get(metric.name); + metric.value = (value || 0) + 1; + target._logHistogram(metric); + + // Telemetry has already been recorded. + metric.skipTelemetry = true; + + // If the metric is not being watched, persist the incremented value. + // If the metric is being watched, `target.bump` will increment the value + // of the metric and will persist the incremented value. + if (!this._watching[metric.name]) { + target.metrics.set(metric.name, metric.value); + } + } + } + if (!this._watching[metric.name]) { return; } target.bump(metric, output); }, formatSourceURL(packet) {
--- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -10,17 +10,17 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -10,17 +10,17 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -14,17 +14,17 @@ <!--original fetch url was git://github.com/apitrace/--> <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/> <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/> <!-- Gonk specific things and forks --> <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c"> <copyfile dest="Makefile" src="core/root.mk"/> </project> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> - <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia.git" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/> <!-- Stock Android things --> <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -12,17 +12,17 @@ <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f"> <copyfile dest="Makefile" src="core/root.mk"/> </project> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> - <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <!-- Stock Android things --> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -10,17 +10,17 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -10,17 +10,17 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -14,17 +14,17 @@ <!--original fetch url was git://github.com/apitrace/--> <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/> <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/> <!-- Gonk specific things and forks --> <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c"> <copyfile dest="Makefile" src="core/root.mk"/> </project> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> - <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia.git" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/> <!-- Stock Android things --> <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -10,17 +10,17 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "c80e8ff25425b007181fd6e3de0500a0358fab37", + "git_revision": "805025801a68f9ddbba6ffd2ed3926c97fa7fcc8", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "617c2b44e26a8bd8c79d96a12fb9fd373c873e44", + "revision": "fb284fae468b639c8dbfd33ad2395c8397c090ec", "repo_path": "integration/gaia-central" }
--- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -12,17 +12,17 @@ <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f"> <copyfile dest="Makefile" src="core/root.mk"/> </project> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> - <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <!-- Stock Android things --> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/> <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -10,17 +10,17 @@ <!--original fetch url was git://codeaurora.org/--> <remote fetch="https://git.mozilla.org/external/caf" name="caf"/> <!--original fetch url was https://git.mozilla.org/releases--> <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/> <!-- B2G specific things. --> <project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea"> <copyfile dest="Makefile" src="core/root.mk"/> </project> - <project name="gaia" path="gaia" remote="mozillaorg" revision="c80e8ff25425b007181fd6e3de0500a0358fab37"/> + <project name="gaia" path="gaia" remote="mozillaorg" revision="805025801a68f9ddbba6ffd2ed3926c97fa7fcc8"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/> <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/> <project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -7572,16 +7572,27 @@ let RestoreLastSessionObserver = { }; function restoreLastSession() { SessionStore.restoreLastSession(); } var TabContextMenu = { contextTab: null, + _updateToggleMuteMenuItem(aTab, aConditionFn) { + ["muted", "soundplaying"].forEach(attr => { + if (!aConditionFn || aConditionFn(attr)) { + if (aTab.hasAttribute(attr)) { + aTab.toggleMuteMenuItem.setAttribute(attr, "true"); + } else { + aTab.toggleMuteMenuItem.removeAttribute(attr); + } + } + }); + }, updateContextMenu: function updateContextMenu(aPopupMenu) { this.contextTab = aPopupMenu.triggerNode.localName == "tab" ? aPopupMenu.triggerNode : gBrowser.selectedTab; let disabled = gBrowser.tabs.length == 1; var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple"); for (let menuItem of menuItems) menuItem.disabled = disabled; @@ -7632,16 +7643,35 @@ var TabContextMenu = { let toggleMute = document.getElementById("context_toggleMuteTab"); if (this.contextTab.hasAttribute("muted")) { toggleMute.label = gNavigatorBundle.getString("unmuteTab.label"); toggleMute.accessKey = gNavigatorBundle.getString("unmuteTab.accesskey"); } else { toggleMute.label = gNavigatorBundle.getString("muteTab.label"); toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey"); } + + this.contextTab.toggleMuteMenuItem = toggleMute; + this._updateToggleMuteMenuItem(this.contextTab); + + this.contextTab.addEventListener("TabAttrModified", this, false); + aPopupMenu.addEventListener("popuphiding", this, false); + }, + handleEvent(aEvent) { + switch (aEvent.type) { + case "popuphiding": + gBrowser.removeEventListener("TabAttrModified", this); + aEvent.target.removeEventListener("popuphiding", this); + break; + case "TabAttrModified": + let tab = aEvent.target; + this._updateToggleMuteMenuItem(tab, + attr => aEvent.detail.changed.indexOf(attr) >= 0); + break; + } } }; XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "gDevToolsBrowser", "resource:///modules/devtools/gDevTools.jsm");
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -3953,20 +3953,28 @@ }; var label; if (tab.mOverCloseButton) { label = tab.selected ? stringWithShortcut("tabs.closeSelectedTab.tooltip", "key_close") : this.mStringBundle.getString("tabs.closeTab.tooltip"); } else if (tab._overPlayingIcon) { - let stringID = tab.linkedBrowser.audioMuted ? - "tabs.unmuteAudio.tooltip" : - "tabs.muteAudio.tooltip"; - label = stringWithShortcut(stringID, "key_toggleMute"); + let stringID; + if (tab.selected) { + stringID = tab.linkedBrowser.audioMuted ? + "tabs.unmuteAudio.tooltip" : + "tabs.muteAudio.tooltip"; + label = stringWithShortcut(stringID, "key_toggleMute"); + } else { + stringID = tab.linkedBrowser.audioMuted ? + "tabs.unmuteAudio.background.tooltip" : + "tabs.muteAudio.background.tooltip"; + label = this.mStringBundle.getString(stringID); + } } else { label = tab.getAttribute("label") + (this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : ""); } event.target.setAttribute("label", label); ]]></body> </method>
--- a/browser/base/content/test/general/browser_audioTabIcon.js +++ b/browser/base/content/test/general/browser_audioTabIcon.js @@ -5,16 +5,36 @@ function* wait_for_tab_playing_event(tab if (event.detail.changed.indexOf("soundplaying") >= 0) { is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); return true; } return false; }); } +function* play(tab) { + let browser = tab.linkedBrowser; + yield ContentTask.spawn(browser, {}, function* () { + let audio = content.document.querySelector("audio"); + audio.play(); + }); + + yield wait_for_tab_playing_event(tab, true); +} + +function* pause(tab) { + let browser = tab.linkedBrowser; + yield ContentTask.spawn(browser, {}, function* () { + let audio = content.document.querySelector("audio"); + audio.pause(); + }); + + yield wait_for_tab_playing_event(tab, false); +} + function disable_non_test_mouse(disable) { let utils = window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); utils.disableNonTestMouseEvents(disable); } function* hover_icon(icon, tooltip) { disable_non_test_mouse(true); @@ -31,21 +51,29 @@ function leave_icon(icon) { EventUtils.synthesizeMouse(icon, 0, 0, {type: "mouseout"}); EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"}); disable_non_test_mouse(false); } -function* test_tooltip(icon, expectedTooltip) { +function* test_tooltip(icon, expectedTooltip, isActiveTab) { let tooltip = document.getElementById("tabbrowser-tab-tooltip"); yield hover_icon(icon, tooltip); - is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected"); + if (isActiveTab) { + // The active tab should have the keybinding shortcut in the tooltip. + // We check this by ensuring that the strings are not equal but the expected + // message appears in the beginning. + isnot(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal"); + is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected"); + } else { + is(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal"); + } leave_icon(icon); } function get_wait_for_mute_promise(tab, expectMuted) { return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, event => { if (event.detail.changed.indexOf("muted") >= 0) { is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted"); return true; @@ -84,63 +112,68 @@ function* test_muting_using_menu(tab, ex yield popupShownPromise; // Check the menu let expectedLabel = expectMuted ? "Unmute Tab" : "Mute Tab"; let toggleMute = document.getElementById("context_toggleMuteTab"); is(toggleMute.label, expectedLabel, "Correct label expected"); is(toggleMute.accessKey, "M", "Correct accessKey expected"); + is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute"); + ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute"); + + yield play(tab); + + is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute"); + ok(toggleMute.hasAttribute("soundplaying"), "Should have the soundplaying attribute"); + + yield pause(tab); + + is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute"); + ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute"); + // Click on the menu and wait for the tab to be muted. let mutedPromise = get_wait_for_mute_promise(tab, !expectMuted); let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); EventUtils.synthesizeMouseAtCenter(toggleMute, {}); yield popupHiddenPromise; yield mutedPromise; } function* test_playing_icon_on_tab(tab, browser, isPinned) { let icon = document.getAnonymousElementByAttribute(tab, "anonid", isPinned ? "overlay-icon" : "soundplaying-icon"); + let isActiveTab = tab === gBrowser.selectedTab; - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.play(); - }); + yield play(tab); - yield wait_for_tab_playing_event(tab, true); - - yield test_tooltip(icon, "Mute tab"); + yield test_tooltip(icon, "Mute tab", isActiveTab); ok(!("muted" in get_tab_attributes(tab)), "No muted attribute should be persisted"); yield test_mute_tab(tab, icon, true); ok("muted" in get_tab_attributes(tab), "Muted attribute should be persisted"); - yield test_tooltip(icon, "Unmute tab"); + yield test_tooltip(icon, "Unmute tab", isActiveTab); yield test_mute_tab(tab, icon, false); ok(!("muted" in get_tab_attributes(tab)), "No muted attribute should be persisted"); - yield test_tooltip(icon, "Mute tab"); + yield test_tooltip(icon, "Mute tab", isActiveTab); yield test_mute_tab(tab, icon, true); - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.pause(); - }); - yield wait_for_tab_playing_event(tab, false); + yield pause(tab); ok(tab.hasAttribute("muted") && !tab.hasAttribute("soundplaying"), "Tab should still be muted but not playing"); - yield test_tooltip(icon, "Unmute tab"); + yield test_tooltip(icon, "Unmute tab", isActiveTab); yield test_mute_tab(tab, icon, false); ok(!tab.hasAttribute("muted") && !tab.hasAttribute("soundplaying"), "Tab should not be be muted or playing"); // Make sure it's possible to mute using the context menu. yield test_muting_using_menu(tab, false); @@ -179,39 +212,32 @@ function* test_swapped_browser(oldTab, n yield AudioPlaybackPromise; ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab"); is(newTab.hasAttribute("soundplaying"), isPlaying, "Expected the correct soundplaying attribute on the new tab"); } function* test_browser_swapping(tab, browser) { // First, test swapping with a playing but muted tab. - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.play(); - }); - yield wait_for_tab_playing_event(tab, true); + yield play(tab); let icon = document.getAnonymousElementByAttribute(tab, "anonid", "soundplaying-icon"); yield test_mute_tab(tab, icon, true); yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank", }, function*(newBrowser) { yield test_swapped_browser(tab, newBrowser, true) // Now, test swapping with a muted but not playing tab. // Note that the tab remains muted, so we only need to pause playback. tab = gBrowser.getTabForBrowser(newBrowser); - yield ContentTask.spawn(newBrowser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.pause(); - }); + yield pause(tab); yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank", }, newBrowser => test_swapped_browser(tab, newBrowser, false)); }); } @@ -221,34 +247,25 @@ function* test_click_on_pinned_tab_after gBrowser.selectedTab = originallySelectedTab; isnot(tab, gBrowser.selectedTab, "Sanity check, the tab should not be selected!"); // Steps to reproduce the bug: // Pin the tab. gBrowser.pinTab(tab); - // Start playbak. - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.play(); - }); - - // Wait for playback to start. - yield wait_for_tab_playing_event(tab, true); + // Start playback and wait for it to finish. + yield play(tab); // Mute the tab. let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon"); yield test_mute_tab(tab, icon, true); - // Stop playback - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.pause(); - }); + // Pause playback and wait for it to finish. + yield pause(tab); // Unmute tab. yield test_mute_tab(tab, icon, false); // Now click on the tab. let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image"); EventUtils.synthesizeMouseAtCenter(image, {button: 0}); @@ -267,24 +284,18 @@ function* test_click_on_pinned_tab_after }, test_on_browser); } // This test only does something useful in e10s! function* test_cross_process_load() { function* test_on_browser(browser) { let tab = gBrowser.getTabForBrowser(browser); - // Start playback. - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.play(); - }); - - // Wait for playback to start. - yield wait_for_tab_playing_event(tab, true); + // Start playback and wait for it to finish. + yield play(tab); let soundPlayingStoppedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, event => event.detail.changed.indexOf("soundplaying") >= 0 ); // Go to a different process. browser.loadURI("about:"); yield BrowserTestUtils.browserLoaded(browser); @@ -310,48 +321,33 @@ function* test_mute_keybinding() { yield mutedPromise; } function* test_on_browser(browser) { let tab = gBrowser.getTabForBrowser(browser); // Make sure it's possible to mute before the tab is playing. yield test_muting_using_keyboard(tab); - // Start playback. - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.play(); - }); - - // Wait for playback to start. - yield wait_for_tab_playing_event(tab, true); + // Start playback and wait for it to finish. + yield play(tab); // Make sure it's possible to mute after the tab is playing. yield test_muting_using_keyboard(tab); - // Start playback. - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.pause(); - }); + // Pause playback and wait for it to finish. + yield pause(tab); // Make sure things work if the tab is pinned. gBrowser.pinTab(tab); // Make sure it's possible to mute before the tab is playing. yield test_muting_using_keyboard(tab); - // Start playback. - yield ContentTask.spawn(browser, {}, function* () { - let audio = content.document.querySelector("audio"); - audio.play(); - }); - - // Wait for playback to start. - yield wait_for_tab_playing_event(tab, true); + // Start playback and wait for it to finish. + yield play(tab); // Make sure it's possible to mute after the tab is playing. yield test_muting_using_keyboard(tab); gBrowser.unpinTab(tab); } yield BrowserTestUtils.withNewTab({
--- a/browser/locales/en-US/chrome/browser/tabbrowser.properties +++ b/browser/locales/en-US/chrome/browser/tabbrowser.properties @@ -35,8 +35,10 @@ tabs.closeTab.tooltip=Close tab # %S is the keyboard shortcut for closing the current tab tabs.closeSelectedTab.tooltip=Close tab (%S) # LOCALIZATION NOTE (tabs.muteAudio.tooltip): # %S is the keyboard shortcut for "Mute tab" tabs.muteAudio.tooltip=Mute tab (%S) # LOCALIZATION NOTE (tabs.unmuteAudio.tooltip): # %S is the keyboard shortcut for "Unmute tab" tabs.unmuteAudio.tooltip=Unmute tab (%S) +tabs.muteAudio.background.tooltip=Mute tab +tabs.unmuteAudio.background.tooltip=Unmute tab
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -4965,16 +4965,17 @@ nsDocShell::DisplayLoadError(nsresult aE error.AssignLiteral("corruptedContentError"); break; case NS_ERROR_INTERCEPTION_FAILED: case NS_ERROR_OPAQUE_INTERCEPTION_DISABLED: case NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE: case NS_ERROR_INTERCEPTED_ERROR_RESPONSE: case NS_ERROR_INTERCEPTED_USED_RESPONSE: case NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION: + case NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION: // ServiceWorker intercepted request, but something went wrong. nsContentUtils::MaybeReportInterceptionErrorToConsole(GetDocument(), aError); error.AssignLiteral("corruptedContentError"); break; default: break; } @@ -10558,16 +10559,18 @@ nsDocShell::DoURILoad(nsIURI* aURI, httpChannelInternal->SetThirdPartyFlags( nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); } if (aFirstParty) { httpChannelInternal->SetDocumentURI(aURI); } else { httpChannelInternal->SetDocumentURI(aReferrerURI); } + httpChannelInternal->SetRedirectMode( + nsIHttpChannelInternal::REDIRECT_MODE_MANUAL); } nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel)); if (props) { // save true referrer for those who need it (e.g. xpinstall whitelisting) // Currently only http and ftp channels support this. props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), aReferrerURI);
--- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -674,18 +674,18 @@ nsIContent::PreHandleEvent(EventChainPre aVisitor.mCanHandle = true; aVisitor.mMayHaveListenerManager = HasListenerManager(); // Don't propagate mouseover and mouseout events when mouse is moving // inside chrome access only content. bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree(); if ((aVisitor.mEvent->mMessage == eMouseOver || aVisitor.mEvent->mMessage == eMouseOut || - aVisitor.mEvent->mMessage == NS_POINTER_OVER || - aVisitor.mEvent->mMessage == NS_POINTER_OUT) && + aVisitor.mEvent->mMessage == ePointerOver || + aVisitor.mEvent->mMessage == ePointerOut) && // Check if we should stop event propagation when event has just been // dispatched or when we're about to propagate from // chrome access only subtree or if we are about to propagate out of // a shadow root to a shadow root host. ((this == aVisitor.mEvent->originalTarget && !ChromeOnlyAccess()) || isAnonForEvents || GetShadowRoot())) { nsCOMPtr<nsIContent> relatedTarget = do_QueryInterface(aVisitor.mEvent->AsMouseEvent()->relatedTarget);
--- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -3453,16 +3453,18 @@ nsContentUtils::MaybeReportInterceptionE } else if (aError == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE) { messageName = "BadOpaqueInterceptionRequestMode"; } else if (aError == NS_ERROR_INTERCEPTED_ERROR_RESPONSE) { messageName = "InterceptedErrorResponse"; } else if (aError == NS_ERROR_INTERCEPTED_USED_RESPONSE) { messageName = "InterceptedUsedResponse"; } else if (aError == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION) { messageName = "ClientRequestOpaqueInterception"; + } else if (aError == NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION) { + messageName = "BadOpaqueRedirectInterception"; } if (messageName) { return ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Service Worker Interception"), aDocument, nsContentUtils::eDOM_PROPERTIES, messageName); @@ -7843,17 +7845,17 @@ nsContentUtils::SendMouseEvent(nsCOMPtr< msg = eMouseUp; else if (aType.EqualsLiteral("mousemove")) msg = eMouseMove; else if (aType.EqualsLiteral("mouseover")) msg = eMouseEnterIntoWidget; else if (aType.EqualsLiteral("mouseout")) msg = eMouseExitFromWidget; else if (aType.EqualsLiteral("contextmenu")) { - msg = NS_CONTEXTMENU; + msg = eContextMenu; contextMenuKey = (aButton == 0); } else if (aType.EqualsLiteral("MozMouseHittest")) msg = eMouseHitTest; else return NS_ERROR_FAILURE; if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) { aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
--- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1307,16 +1307,18 @@ public: * Get the textual contents of a node. This is a concatenation of all * textnodes that are direct or (depending on aDeep) indirect children * of the node. * * NOTE! No serialization takes place and <br> elements * are not converted into newlines. Only textnodes and cdata nodes are * added to the result. * + * @see nsLayoutUtils::GetFrameTextContent + * * @param aNode Node to get textual contents of. * @param aDeep If true child elements of aNode are recursivly descended * into to find text children. * @param aResult the result. Out param. * @return false on out of memory errors, true otherwise. */ MOZ_WARN_UNUSED_RESULT static bool GetNodeTextContent(nsINode* aNode, bool aDeep,
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -697,25 +697,25 @@ nsDOMWindowUtils::SendPointerEventCommon nsPoint offset; nsCOMPtr<nsIWidget> widget = GetWidget(&offset); if (!widget) { return NS_ERROR_FAILURE; } EventMessage msg; if (aType.EqualsLiteral("pointerdown")) { - msg = NS_POINTER_DOWN; + msg = ePointerDown; } else if (aType.EqualsLiteral("pointerup")) { - msg = NS_POINTER_UP; + msg = ePointerUp; } else if (aType.EqualsLiteral("pointermove")) { - msg = NS_POINTER_MOVE; + msg = ePointerMove; } else if (aType.EqualsLiteral("pointerover")) { - msg = NS_POINTER_OVER; + msg = ePointerOver; } else if (aType.EqualsLiteral("pointerout")) { - msg = NS_POINTER_OUT; + msg = ePointerOut; } else { return NS_ERROR_FAILURE; } if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) { aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE; }
--- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -1731,24 +1731,25 @@ public: } /** * Set the display document for this document. aDisplayDocument must not be * null. */ void SetDisplayDocument(nsIDocument* aDisplayDocument) { - NS_PRECONDITION(!GetShell() && - !GetContainer() && - !GetWindow(), - "Shouldn't set mDisplayDocument on documents that already " - "have a presentation or a docshell or a window"); - NS_PRECONDITION(aDisplayDocument != this, "Should be different document"); - NS_PRECONDITION(!aDisplayDocument->GetDisplayDocument(), - "Display documents should not nest"); + MOZ_ASSERT(!GetShell() && + !GetContainer() && + !GetWindow(), + "Shouldn't set mDisplayDocument on documents that already " + "have a presentation or a docshell or a window"); + MOZ_ASSERT(aDisplayDocument, "Must not be null"); + MOZ_ASSERT(aDisplayDocument != this, "Should be different document"); + MOZ_ASSERT(!aDisplayDocument->GetDisplayDocument(), + "Display documents should not nest"); mDisplayDocument = aDisplayDocument; mHasDisplayDocument = !!aDisplayDocument; } /** * A class that represents an external resource load that has begun but * doesn't have a document yet. Observers can be registered on this object, * and will be notified after the document is created. Observers registered @@ -1759,17 +1760,17 @@ public: * notified, with a null document pointer. */ class ExternalResourceLoad : public nsISupports { public: virtual ~ExternalResourceLoad() {} void AddObserver(nsIObserver* aObserver) { - NS_PRECONDITION(aObserver, "Must have observer"); + MOZ_ASSERT(aObserver, "Must have observer"); mObservers.AppendElement(aObserver); } const nsTArray< nsCOMPtr<nsIObserver> > & Observers() { return mObservers; } protected: nsAutoTArray< nsCOMPtr<nsIObserver>, 8 > mObservers;
--- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -262,16 +262,18 @@ support-files = [test_appname_override.html] [test_async_setTimeout_stack.html] [test_async_setTimeout_stack_across_globals.html] [test_audioWindowUtils.html] [test_audioNotification.html] skip-if = buildapp == 'mulet' [test_audioNotificationStopOnNavigation.html] skip-if = buildapp == 'mulet' +[test_audioNotificationWithEarlyPlay.html] +skip-if = buildapp == 'mulet' [test_bug1091883.html] [test_bug116083.html] [test_bug793311.html] [test_bug913761.html] [test_bug976673.html] [test_bug978522.html] [test_bug979109.html] [test_bug989665.html]
new file mode 100644 --- /dev/null +++ b/dom/base/test/test_audioNotificationWithEarlyPlay.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for audio controller in windows</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<pre id="test"> +</pre> + +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var expectedNotification = null; + +var observer = { + observe: function(subject, topic, data) { + is(topic, "audio-playback", "audio-playback received"); + is(data, expectedNotification, "This is the right notification"); + runTest(); + } +}; + +var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"] + .getService(SpecialPowers.Ci.nsIObserverService); + +var audio = new Audio(); +audio.loop = true; +audio.preload = "metadata"; + +var tests = [ + function() { + observerService.addObserver(observer, "audio-playback", false); + ok(true, "Observer set"); + runTest(); + }, + + function() { + expectedNotification = 'active'; + audio.src = "audio.ogg"; + audio.play(); + }, + + function() { + expectedNotification = 'inactive'; + audio.pause(); + }, + + function() { + observerService.removeObserver(observer, "audio-playback"); + ok(true, "Observer removed"); + runTest(); + } +]; + +function runTest() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +runTest(); + +</script> +</body> +</html> +
--- a/dom/cache/CacheStorage.cpp +++ b/dom/cache/CacheStorage.cpp @@ -234,16 +234,51 @@ CacheStorage::CreateOnWorker(Namespace a return ref.forget(); } nsRefPtr<CacheStorage> ref = new CacheStorage(aNamespace, aGlobal, principalInfo, feature); return ref.forget(); } +// static +bool +CacheStorage::DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL, + "Passed object is not a global object!"); + + if (NS_WARN_IF(!CacheStorageBinding::GetConstructorObject(aCx, aGlobal) || + !CacheBinding::GetConstructorObject(aCx, aGlobal))) { + return false; + } + + nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal); + MOZ_ASSERT(principal); + + ErrorResult rv; + nsRefPtr<CacheStorage> storage = + CreateOnMainThread(DEFAULT_NAMESPACE, xpc::NativeGlobal(aGlobal), principal, + false, /* private browsing */ + true, /* force trusted */ + rv); + if (NS_WARN_IF(rv.Failed())) { + return ThrowMethodFailed(aCx, rv); + } + + JS::Rooted<JS::Value> caches(aCx); + js::AssertSameCompartment(aCx, aGlobal); + if (NS_WARN_IF(!ToJSValue(aCx, storage, &caches))) { + return false; + } + + return JS_DefineProperty(aCx, aGlobal, "caches", caches, JSPROP_ENUMERATE); +} + CacheStorage::CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal, const PrincipalInfo& aPrincipalInfo, Feature* aFeature) : mNamespace(aNamespace) , mGlobal(aGlobal) , mPrincipalInfo(MakeUnique<PrincipalInfo>(aPrincipalInfo)) , mFeature(aFeature) , mActor(nullptr) , mStatus(NS_OK)
--- a/dom/cache/CacheStorage.h +++ b/dom/cache/CacheStorage.h @@ -51,16 +51,19 @@ public: CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal, nsIPrincipal* aPrincipal, bool aStorageDisabled, bool aForceTrustedOrigin, ErrorResult& aRv); static already_AddRefed<CacheStorage> CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal, workers::WorkerPrivate* aWorkerPrivate, ErrorResult& aRv); + static bool + DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal); + // webidl interface methods already_AddRefed<Promise> Match(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions, ErrorResult& aRv); already_AddRefed<Promise> Has(const nsAString& aKey, ErrorResult& aRv); already_AddRefed<Promise> Open(const nsAString& aKey, ErrorResult& aRv); already_AddRefed<Promise> Delete(const nsAString& aKey, ErrorResult& aRv); already_AddRefed<Promise> Keys(ErrorResult& aRv);
--- a/dom/cache/CacheTypes.ipdlh +++ b/dom/cache/CacheTypes.ipdlh @@ -8,16 +8,17 @@ include protocol PCacheStreamControl; include InputStreamParams; include ChannelInfo; include PBackgroundSharedTypes; using HeadersGuardEnum from "mozilla/dom/cache/IPCUtils.h"; using RequestCredentials from "mozilla/dom/cache/IPCUtils.h"; using RequestMode from "mozilla/dom/cache/IPCUtils.h"; using RequestCache from "mozilla/dom/cache/IPCUtils.h"; +using RequestRedirect from "mozilla/dom/cache/IPCUtils.h"; using ResponseType from "mozilla/dom/cache/IPCUtils.h"; using mozilla::void_t from "ipc/IPCMessageUtils.h"; using struct nsID from "nsID.h"; namespace mozilla { namespace dom { namespace cache { @@ -59,16 +60,17 @@ struct CacheRequest HeadersEntry[] headers; HeadersGuardEnum headersGuard; nsString referrer; RequestMode mode; RequestCredentials credentials; CacheReadStreamOrVoid body; uint32_t contentPolicyType; RequestCache requestCache; + RequestRedirect requestRedirect; }; union CacheRequestOrVoid { void_t; CacheRequest; };
--- a/dom/cache/DBAction.cpp +++ b/dom/cache/DBAction.cpp @@ -165,17 +165,17 @@ DBAction::OpenConnection(const QuotaInfo rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Check the schema to make sure it is not too old. int32_t schemaVersion = 0; rv = conn->GetSchemaVersion(&schemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (schemaVersion > 0 && schemaVersion < db::kMaxWipeSchemaVersion) { + if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) { conn = nullptr; rv = WipeDatabase(dbFile, aDBDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } }
--- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -9,16 +9,17 @@ #include "ipc/IPCMessageUtils.h" #include "mozilla/dom/InternalHeaders.h" #include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/SavedTypes.h" #include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/TypeUtils.h" #include "mozIStorageConnection.h" #include "mozIStorageStatement.h" +#include "mozStorageHelper.h" #include "nsCOMPtr.h" #include "nsTArray.h" #include "nsCRT.h" #include "nsHttp.h" #include "nsICryptoHash.h" #include "nsNetCID.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/HeadersBinding.h" @@ -26,21 +27,145 @@ #include "mozilla/dom/ResponseBinding.h" #include "nsIContentPolicy.h" namespace mozilla { namespace dom { namespace cache { namespace db { -const int32_t kMaxWipeSchemaVersion = 15; +const int32_t kFirstShippedSchemaVersion = 15; namespace { -const int32_t kLatestSchemaVersion = 15; +// Update this whenever the DB schema is changed. +const int32_t kLatestSchemaVersion = 16; + +// --------- +// The following constants define the SQL schema. These are defined in the +// same order the SQL should be executed in CreateOrMigrateSchema(). They are +// broken out as constants for convenient use in validation and migration. +// --------- + +// The caches table is the single source of truth about what Cache +// objects exist for the origin. The contents of the Cache are stored +// in the entries table that references back to caches. +// +// The caches table is also referenced from storage. Rows in storage +// represent named Cache objects. There are cases, however, where +// a Cache can still exist, but not be in a named Storage. For example, +// when content is still using the Cache after CacheStorage::Delete() +// has been run. +// +// For now, the caches table mainly exists for data integrity with +// foreign keys, but could be expanded to contain additional cache object +// information. +// +// AUTOINCREMENT is necessary to prevent CacheId values from being reused. +const char* const kTableCaches = + "CREATE TABLE caches (" + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT " + ")"; + +// Security blobs are quite large and duplicated for every Response from +// the same https origin. This table is used to de-duplicate this data. +const char* const kTableSecurityInfo = + "CREATE TABLE security_info (" + "id INTEGER NOT NULL PRIMARY KEY, " + "hash BLOB NOT NULL, " // first 8-bytes of the sha1 hash of data column + "data BLOB NOT NULL, " // full security info data, usually a few KB + "refcount INTEGER NOT NULL" + ")"; + +// Index the smaller hash value instead of the large security data blob. +const char* const kIndexSecurityInfoHash = + "CREATE INDEX security_info_hash_index ON security_info (hash)"; + +const char* const kTableEntries = + "CREATE TABLE entries (" + "id INTEGER NOT NULL PRIMARY KEY, " + "request_method TEXT NOT NULL, " + "request_url_no_query TEXT NOT NULL, " + "request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash + "request_url_query TEXT NOT NULL, " + "request_url_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash + "request_referrer TEXT NOT NULL, " + "request_headers_guard INTEGER NOT NULL, " + "request_mode INTEGER NOT NULL, " + "request_credentials INTEGER NOT NULL, " + "request_contentpolicytype INTEGER NOT NULL, " + "request_cache INTEGER NOT NULL, " + "request_body_id TEXT NULL, " + "response_type INTEGER NOT NULL, " + "response_url TEXT NOT NULL, " + "response_status INTEGER NOT NULL, " + "response_status_text TEXT NOT NULL, " + "response_headers_guard INTEGER NOT NULL, " + "response_body_id TEXT NULL, " + "response_security_info_id INTEGER NULL REFERENCES security_info(id), " + "response_principal_info TEXT NOT NULL, " + "response_redirected INTEGER NOT NULL, " + // Note that response_redirected_url is either going to be empty, or + // it's going to be a URL different than response_url. + "response_redirected_url TEXT NOT NULL, " + "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, " + + // New columns must be added at the end of table to migrate and + // validate properly. + "request_redirect INTEGER NOT NULL" + ")"; + +// Create an index to support the QueryCache() matching algorithm. This +// needs to quickly find entries in a given Cache that match the request +// URL. The url query is separated in order to support the ignoreSearch +// option. Finally, we index hashes of the URL values instead of the +// actual strings to avoid excessive disk bloat. The index will duplicate +// the contents of the columsn in the index. The hash index will prune +// the vast majority of values from the query result so that normal +// scanning only has to be done on a few values to find an exact URL match. +const char* const kIndexEntriesRequest = + "CREATE INDEX entries_request_match_index " + "ON entries (cache_id, request_url_no_query_hash, " + "request_url_query_hash)"; + +const char* const kTableRequestHeaders = + "CREATE TABLE request_headers (" + "name TEXT NOT NULL, " + "value TEXT NOT NULL, " + "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" + ")"; + +const char* const kTableResponseHeaders = + "CREATE TABLE response_headers (" + "name TEXT NOT NULL, " + "value TEXT NOT NULL, " + "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" + ")"; + +// We need an index on response_headers, but not on request_headers, +// because we quickly need to determine if a VARY header is present. +const char* const kIndexResponseHeadersName = + "CREATE INDEX response_headers_name_index " + "ON response_headers (name)"; + +// NOTE: key allows NULL below since that is how "" is represented +// in a BLOB column. We use BLOB to avoid encoding issues +// with storing DOMStrings. +const char* const kTableStorage = + "CREATE TABLE storage (" + "namespace INTEGER NOT NULL, " + "key BLOB NULL, " + "cache_id INTEGER NOT NULL REFERENCES caches(id), " + "PRIMARY KEY(namespace, key) " + ")"; + +// --------- +// End schema definition +// --------- + const int32_t kMaxEntriesPerStatement = 255; const uint32_t kPageSize = 4 * 1024; // Grow the database in chunks to reduce fragmentation const uint32_t kGrowthSize = 32 * 1024; const uint32_t kGrowthPages = kGrowthSize / kPageSize; static_assert(kGrowthSize % kPageSize == 0, @@ -82,22 +207,28 @@ static_assert(int(RequestCredentials::Om static_assert(int(RequestCache::Default) == 0 && int(RequestCache::No_store) == 1 && int(RequestCache::Reload) == 2 && int(RequestCache::No_cache) == 3 && int(RequestCache::Force_cache) == 4 && int(RequestCache::Only_if_cached) == 5 && int(RequestCache::EndGuard_) == 6, "RequestCache values are as expected"); +static_assert(int(RequestRedirect::Follow) == 0 && + int(RequestRedirect::Error) == 1 && + int(RequestRedirect::Manual) == 2 && + int(RequestRedirect::EndGuard_) == 3, + "RequestRedirect values are as expected"); static_assert(int(ResponseType::Basic) == 0 && int(ResponseType::Cors) == 1 && int(ResponseType::Default) == 2 && int(ResponseType::Error) == 3 && int(ResponseType::Opaque) == 4 && - int(ResponseType::EndGuard_) == 5, + int(ResponseType::Opaqueredirect) == 5 && + int(ResponseType::EndGuard_) == 6, "ResponseType values are as expected"); // If the static_asserts below fails, it means that you have changed the // Namespace enum in a way that may be incompatible with the existing data // stored in the DOM Cache. You would need to update the Cache database schema // accordingly and adjust the failing static_assert. static_assert(DEFAULT_NAMESPACE == 0 && CHROME_ONLY_NAMESPACE == 1 && @@ -201,169 +332,100 @@ static nsresult BindId(mozIStorageStatem static nsresult ExtractId(mozIStorageStatement* aState, uint32_t aPos, nsID* aIdOut); static nsresult CreateAndBindKeyStatement(mozIStorageConnection* aConn, const char* aQueryFormat, const nsAString& aKey, mozIStorageStatement** aStateOut); static nsresult HashCString(nsICryptoHash* aCrypto, const nsACString& aIn, nsACString& aOut); +nsresult Validate(mozIStorageConnection* aConn); +nsresult Migrate(mozIStorageConnection* aConn); } // namespace nsresult -CreateSchema(mozIStorageConnection* aConn) +CreateOrMigrateSchema(mozIStorageConnection* aConn) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); int32_t schemaVersion; nsresult rv = aConn->GetSchemaVersion(&schemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (schemaVersion == kLatestSchemaVersion) { - // We already have the correct schema, so just get started. + // We already have the correct schema version. Validate it matches + // our expected schema and then proceed. + rv = Validate(aConn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + return rv; } - if (!schemaVersion) { - // The caches table is the single source of truth about what Cache - // objects exist for the origin. The contents of the Cache are stored - // in the entries table that references back to caches. - // - // The caches table is also referenced from storage. Rows in storage - // represent named Cache objects. There are cases, however, where - // a Cache can still exist, but not be in a named Storage. For example, - // when content is still using the Cache after CacheStorage::Delete() - // has been run. - // - // For now, the caches table mainly exists for data integrity with - // foreign keys, but could be expanded to contain additional cache object - // information. - // - // AUTOINCREMENT is necessary to prevent CacheId values from being reused. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE caches (" - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT " - ");" - )); + mozStorageTransaction trans(aConn, false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + bool needVacuum = false; + + if (schemaVersion) { + // A schema exists, but its not the current version. Attempt to + // migrate it to our new schema. + rv = Migrate(aConn); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // Security blobs are quite large and duplicated for every Response from - // the same https origin. This table is used to de-duplicate this data. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE security_info (" - "id INTEGER NOT NULL PRIMARY KEY, " - "hash BLOB NOT NULL, " // first 8-bytes of the sha1 hash of data column - "data BLOB NOT NULL, " // full security info data, usually a few KB - "refcount INTEGER NOT NULL" - ");" - )); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - - // Index the smaller hash value instead of the large security data blob. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX security_info_hash_index ON security_info (hash);" - )); + // Migrations happen infrequently and reflect a chance in DB structure. + // This is a good time to rebuild the database. It also helps catch + // if a new migration is incorrect by fast failing on the corruption. + needVacuum = true; + + } else { + // There is no schema installed. Create the database from scratch. + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableCaches)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE entries (" - "id INTEGER NOT NULL PRIMARY KEY, " - "request_method TEXT NOT NULL, " - "request_url_no_query TEXT NOT NULL, " - "request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash - "request_url_query TEXT NOT NULL, " - "request_url_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash - "request_referrer TEXT NOT NULL, " - "request_headers_guard INTEGER NOT NULL, " - "request_mode INTEGER NOT NULL, " - "request_credentials INTEGER NOT NULL, " - "request_contentpolicytype INTEGER NOT NULL, " - "request_cache INTEGER NOT NULL, " - "request_body_id TEXT NULL, " - "response_type INTEGER NOT NULL, " - "response_url TEXT NOT NULL, " - "response_status INTEGER NOT NULL, " - "response_status_text TEXT NOT NULL, " - "response_headers_guard INTEGER NOT NULL, " - "response_body_id TEXT NULL, " - "response_security_info_id INTEGER NULL REFERENCES security_info(id), " - "response_principal_info TEXT NOT NULL, " - "response_redirected INTEGER NOT NULL, " - // Note that response_redirected_url is either going to be empty, or - // it's going to be a URL different than response_url. - "response_redirected_url TEXT NOT NULL, " - "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE" - ");" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableSecurityInfo)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexSecurityInfoHash)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableEntries)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexEntriesRequest)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // Create an index to support the QueryCache() matching algorithm. This - // needs to quickly find entries in a given Cache that match the request - // URL. The url query is separated in order to support the ignoreSearch - // option. Finally, we index hashes of the URL values instead of the - // actual strings to avoid excessive disk bloat. The index will duplicate - // the contents of the columsn in the index. The hash index will prune - // the vast majority of values from the query result so that normal - // scanning only has to be done on a few values to find an exact URL match. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX entries_request_match_index " - "ON entries (cache_id, request_url_no_query_hash, " - "request_url_query_hash);" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableRequestHeaders)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE request_headers (" - "name TEXT NOT NULL, " - "value TEXT NOT NULL, " - "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" - ");" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableResponseHeaders)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE response_headers (" - "name TEXT NOT NULL, " - "value TEXT NOT NULL, " - "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" - ");" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexResponseHeadersName)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // We need an index on response_headers, but not on request_headers, - // because we quickly need to determine if a VARY header is present. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX response_headers_name_index " - "ON response_headers (name);" - )); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - - // NOTE: key allows NULL below since that is how "" is represented - // in a BLOB column. We use BLOB to avoid encoding issues - // with storing DOMStrings. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE storage (" - "namespace INTEGER NOT NULL, " - "key BLOB NULL, " - "cache_id INTEGER NOT NULL REFERENCES caches(id), " - "PRIMARY KEY(namespace, key) " - ");" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableStorage)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->SetSchemaVersion(kLatestSchemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->GetSchemaVersion(&schemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } - if (schemaVersion != kLatestSchemaVersion) { - return NS_ERROR_FAILURE; + rv = Validate(aConn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = trans.Commit(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (needVacuum) { + // Unfortunately, this must be performed outside of the transaction. + aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM")); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return rv; } nsresult InitializeConnection(mozIStorageConnection* aConn) { @@ -1525,16 +1587,17 @@ InsertEntry(mozIStorageConnection* aConn "request_url_query, " "request_url_query_hash, " "request_referrer, " "request_headers_guard, " "request_mode, " "request_credentials, " "request_contentpolicytype, " "request_cache, " + "request_redirect, " "request_body_id, " "response_type, " "response_url, " "response_status, " "response_status_text, " "response_headers_guard, " "response_body_id, " "response_security_info_id, " @@ -1549,16 +1612,17 @@ InsertEntry(mozIStorageConnection* aConn ":request_url_query, " ":request_url_query_hash, " ":request_referrer, " ":request_headers_guard, " ":request_mode, " ":request_credentials, " ":request_contentpolicytype, " ":request_cache, " + ":request_redirect, " ":request_body_id, " ":response_type, " ":response_url, " ":response_status, " ":response_status_text, " ":response_headers_guard, " ":response_body_id, " ":response_security_info_id, " @@ -1617,16 +1681,19 @@ InsertEntry(mozIStorageConnection* aConn rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_contentpolicytype"), static_cast<int32_t>(aRequest.contentPolicyType())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_cache"), static_cast<int32_t>(aRequest.requestCache())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_redirect"), + static_cast<int32_t>(aRequest.requestRedirect())); + rv = BindId(state, NS_LITERAL_CSTRING("request_body_id"), aRequestBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_type"), static_cast<int32_t>(aResponse.type())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_url"), @@ -1896,16 +1963,17 @@ ReadRequest(mozIStorageConnection* aConn "request_url_no_query, " "request_url_query, " "request_referrer, " "request_headers_guard, " "request_mode, " "request_credentials, " "request_contentpolicytype, " "request_cache, " + "request_redirect, " "request_body_id " "FROM entries " "WHERE id=:id;" ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindInt32ByName(NS_LITERAL_CSTRING("id"), aEntryId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1950,23 +2018,29 @@ ReadRequest(mozIStorageConnection* aConn static_cast<nsContentPolicyType>(requestContentPolicyType); int32_t requestCache; rv = state->GetInt32(8, &requestCache); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedRequestOut->mValue.requestCache() = static_cast<RequestCache>(requestCache); + int32_t requestRedirect; + rv = state->GetInt32(9, &requestRedirect); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + aSavedRequestOut->mValue.requestRedirect() = + static_cast<RequestRedirect>(requestRedirect); + bool nullBody = false; - rv = state->GetIsNull(9, &nullBody); + rv = state->GetIsNull(10, &nullBody); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedRequestOut->mHasBodyId = !nullBody; if (aSavedRequestOut->mHasBodyId) { - rv = ExtractId(state, 9, &aSavedRequestOut->mBodyId); + rv = ExtractId(state, 10, &aSavedRequestOut->mBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT " "name, " "value " "FROM request_headers " @@ -2180,12 +2254,231 @@ IncrementalVacuum(mozIStorageConnection* if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(freePages <= kMaxFreePages); #endif return NS_OK; } +namespace { + +#ifdef DEBUG +struct Expect +{ + // Expect exact SQL + Expect(const char* aName, const char* aType, const char* aSql) + : mName(aName) + , mType(aType) + , mSql(aSql) + , mIgnoreSql(false) + { } + + // Ignore SQL + Expect(const char* aName, const char* aType) + : mName(aName) + , mType(aType) + , mIgnoreSql(true) + { } + + const nsCString mName; + const nsCString mType; + const nsCString mSql; + const bool mIgnoreSql; +}; +#endif + +nsresult +Validate(mozIStorageConnection* aConn) +{ + int32_t schemaVersion; + nsresult rv = aConn->GetSchemaVersion(&schemaVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (NS_WARN_IF(schemaVersion != kLatestSchemaVersion)) { + return NS_ERROR_FAILURE; + } + +#ifdef DEBUG + // This is the schema we expect the database at the latest version to + // contain. Update this list if you add a new table or index. + Expect expect[] = { + Expect("caches", "table", kTableCaches), + Expect("sqlite_sequence", "table"), // auto-gen by sqlite + Expect("security_info", "table", kTableSecurityInfo), + Expect("security_info_hash_index", "index", kIndexSecurityInfoHash), + Expect("entries", "table", kTableEntries), + Expect("entries_request_match_index", "index", kIndexEntriesRequest), + Expect("request_headers", "table", kTableRequestHeaders), + Expect("response_headers", "table", kTableResponseHeaders), + Expect("response_headers_name_index", "index", kIndexResponseHeadersName), + Expect("storage", "table", kTableStorage), + Expect("sqlite_autoindex_storage_1", "index"), // auto-gen by sqlite + }; + const uint32_t expectLength = sizeof(expect) / sizeof(Expect); + + // Read the schema from the sqlite_master table and compare. + nsCOMPtr<mozIStorageStatement> state; + rv = aConn->CreateStatement(NS_LITERAL_CSTRING( + "SELECT name, type, sql FROM sqlite_master;" + ), getter_AddRefs(state)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + bool hasMoreData = false; + while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) { + nsAutoCString name; + rv = state->GetUTF8String(0, name); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsAutoCString type; + rv = state->GetUTF8String(1, type); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsAutoCString sql; + rv = state->GetUTF8String(2, sql); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + bool foundMatch = false; + for (uint32_t i = 0; i < expectLength; ++i) { + if (name == expect[i].mName) { + if (type != expect[i].mType) { + NS_WARNING(nsPrintfCString("Unexpected type for Cache schema entry %s", + name.get()).get()); + return NS_ERROR_FAILURE; + } + + if (!expect[i].mIgnoreSql && sql != expect[i].mSql) { + NS_WARNING(nsPrintfCString("Unexpected SQL for Cache schema entry %s", + name.get()).get()); + return NS_ERROR_FAILURE; + } + + foundMatch = true; + break; + } + } + + if (NS_WARN_IF(!foundMatch)) { + NS_WARNING(nsPrintfCString("Unexpected schema entry %s in Cache database", + name.get()).get()); + return NS_ERROR_FAILURE; + } + } +#endif + + return rv; +} + +// ----- +// Schema migration code +// ----- + +typedef nsresult (*MigrationFunc)(mozIStorageConnection*); +struct Migration +{ + Migration(int32_t aFromVersion, MigrationFunc aFunc) + : mFromVersion(aFromVersion) + , mFunc(aFunc) + { } + int32_t mFromVersion; + MigrationFunc mFunc; +}; + +// Declare migration functions here. Each function should upgrade +// the version by a single increment. Don't skip versions. +nsresult MigrateFrom15To16(mozIStorageConnection* aConn); + +// Configure migration functions to run for the given starting version. +Migration sMigrationList[] = { + Migration(15, MigrateFrom15To16), +}; + +uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration); + +nsresult +Migrate(mozIStorageConnection* aConn) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConn); + + int32_t currentVersion = 0; + nsresult rv = aConn->GetSchemaVersion(¤tVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + while (currentVersion < kLatestSchemaVersion) { + // Wiping old databases is handled in DBAction because it requires + // making a whole new mozIStorageConnection. Make sure we don't + // accidentally get here for one of those old databases. + MOZ_ASSERT(currentVersion >= kFirstShippedSchemaVersion); + + for (uint32_t i = 0; i < sMigrationListLength; ++i) { + if (sMigrationList[i].mFromVersion == currentVersion) { + rv = sMigrationList[i].mFunc(aConn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + break; + } + } + + DebugOnly<int32_t> lastVersion = currentVersion; + rv = aConn->GetSchemaVersion(¤tVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(currentVersion > lastVersion); + } + + MOZ_ASSERT(currentVersion == kLatestSchemaVersion); + + return rv; +} + +nsresult MigrateFrom15To16(mozIStorageConnection* aConn) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConn); + + // Add the request_redirect column with a default value of "follow". Note, + // we only use a default value here because its required by ALTER TABLE and + // we need to apply the default "follow" to existing records in the table. + // We don't actually want to keep the default in the schema for future + // INSERTs. + nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE entries " + "ADD COLUMN request_redirect INTEGER NOT NULL DEFAULT 0" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Now overwrite the master SQL for the entries table to remove the column + // default value. This is also necessary for our Validate() method to + // pass on this database. + rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA writable_schema = ON" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<mozIStorageStatement> state; + rv = aConn->CreateStatement(NS_LITERAL_CSTRING( + "UPDATE sqlite_master SET sql=:sql WHERE name='entries'" + ), getter_AddRefs(state)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("sql"), + nsDependentCString(kTableEntries)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->SetSchemaVersion(16); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA writable_schema = OFF" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + +} // anonymous namespace + } // namespace db } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/DBSchema.h +++ b/dom/cache/DBSchema.h @@ -24,18 +24,19 @@ class CacheQueryParams; class CacheRequest; class CacheRequestOrVoid; class CacheResponse; struct SavedRequest; struct SavedResponse; namespace db { +// Note, this cannot be executed within a transaction. nsresult -CreateSchema(mozIStorageConnection* aConn); +CreateOrMigrateSchema(mozIStorageConnection* aConn); // Note, this cannot be executed within a transaction. nsresult InitializeConnection(mozIStorageConnection* aConn); nsresult CreateCacheId(mozIStorageConnection* aConn, CacheId* aCacheIdOut); @@ -111,17 +112,18 @@ StorageForgetCache(mozIStorageConnection nsresult StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace, nsTArray<nsString>& aKeysOut); // Note, this works best when its NOT executed within a transaction. nsresult IncrementalVacuum(mozIStorageConnection* aConn); -// We will wipe out databases with a schema versions less than this. -extern const int32_t kMaxWipeSchemaVersion; +// We will wipe out databases with a schema versions less than this. Newer +// versions will be migrated on open to the latest schema version. +extern const int32_t kFirstShippedSchemaVersion; } // namespace db } // namespace cache } // namespace dom } // namespace mozilla #endif // mozilla_dom_cache_DBSchema_h
--- a/dom/cache/IPCUtils.h +++ b/dom/cache/IPCUtils.h @@ -34,16 +34,21 @@ namespace IPC { mozilla::dom::RequestCredentials::Omit, mozilla::dom::RequestCredentials::EndGuard_> {}; template<> struct ParamTraits<mozilla::dom::RequestCache> : public ContiguousEnumSerializer<mozilla::dom::RequestCache, mozilla::dom::RequestCache::Default, mozilla::dom::RequestCache::EndGuard_> {}; template<> + struct ParamTraits<mozilla::dom::RequestRedirect> : + public ContiguousEnumSerializer<mozilla::dom::RequestRedirect, + mozilla::dom::RequestRedirect::Follow, + mozilla::dom::RequestRedirect::EndGuard_> {}; + template<> struct ParamTraits<mozilla::dom::ResponseType> : public ContiguousEnumSerializer<mozilla::dom::ResponseType, mozilla::dom::ResponseType::Basic, mozilla::dom::ResponseType::EndGuard_> {}; template<> struct ParamTraits<mozilla::dom::cache::Namespace> : public ContiguousEnumSerializer<mozilla::dom::cache::Namespace, mozilla::dom::cache::DEFAULT_NAMESPACE,
--- a/dom/cache/Manager.cpp +++ b/dom/cache/Manager.cpp @@ -49,26 +49,19 @@ public: virtual nsresult RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { nsresult rv = BodyCreateDir(aDBDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - { - mozStorageTransaction trans(aConn, false, - mozIStorageConnection::TRANSACTION_IMMEDIATE); - - rv = db::CreateSchema(aConn); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - - rv = trans.Commit(); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - } + // executes in its own transaction + rv = db::CreateOrMigrateSchema(aConn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // If the Context marker file exists, then the last session was // not cleanly shutdown. In these cases sqlite will ensure that // the database is valid, but we might still orphan data. Both // Cache objects and body files can be referenced by DOM objects // after they are "removed" from their parent. So we need to // look and see if any of these late access objects have been // orphaned. @@ -395,17 +388,17 @@ private: MOZ_ASSERT(!sFactory->mManagerList.IsEmpty()); { ManagerList::ForwardIterator iter(sFactory->mManagerList); while (iter.HasMore()) { nsRefPtr<Manager> manager = iter.GetNext(); if (aOrigin.IsVoid() || - manager->mManagerId->ExtendedOrigin() == aOrigin) { + manager->mManagerId->QuotaOrigin() == aOrigin) { manager->Abort(); } } } } static void ShutdownAllOnBackgroundThread()
--- a/dom/cache/ManagerId.cpp +++ b/dom/cache/ManagerId.cpp @@ -16,53 +16,42 @@ namespace cache { // static nsresult ManagerId::Create(nsIPrincipal* aPrincipal, ManagerId** aManagerIdOut) { MOZ_ASSERT(NS_IsMainThread()); // The QuotaManager::GetInfoFromPrincipal() has special logic for system - // and about: principals. We currently don't need the system principal logic - // because ManagerId only uses the origin for in memory comparisons. We - // also don't do any special logic to host the same Cache for different about: - // pages, so we don't need those checks either. - // - // But, if we get the same QuotaManager directory for different about: - // origins, we probably only want one Manager instance. So, we might - // want to start using the QM's concept of origin uniqueness here. - // - // TODO: consider using QuotaManager's modified origin here (bug 1112071) - - nsCString origin; - nsresult rv = aPrincipal->GetOriginNoSuffix(origin); + // and about: principals. We need to use the same modified origin in + // order to interpret calls from QM correctly. + nsCString quotaOrigin; + nsresult rv = QuotaManager::GetInfoFromPrincipal(aPrincipal, + nullptr, //group + "aOrigin, + nullptr); // is app if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - nsCString jarPrefix; - rv = aPrincipal->GetJarPrefix(jarPrefix); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - - nsRefPtr<ManagerId> ref = new ManagerId(aPrincipal, origin, jarPrefix); + nsRefPtr<ManagerId> ref = new ManagerId(aPrincipal, quotaOrigin); ref.forget(aManagerIdOut); return NS_OK; } already_AddRefed<nsIPrincipal> ManagerId::Principal() const { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr<nsIPrincipal> ref = mPrincipal; return ref.forget(); } -ManagerId::ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin, - const nsACString& aJarPrefix) +ManagerId::ManagerId(nsIPrincipal* aPrincipal, const nsACString& aQuotaOrigin) : mPrincipal(aPrincipal) - , mExtendedOrigin(aJarPrefix + aOrigin) + , mQuotaOrigin(aQuotaOrigin) { MOZ_ASSERT(mPrincipal); } ManagerId::~ManagerId() { // If we're already on the main thread, then default destruction is fine if (NS_IsMainThread()) {
--- a/dom/cache/ManagerId.h +++ b/dom/cache/ManagerId.h @@ -24,36 +24,35 @@ class ManagerId final { public: // Main thread only static nsresult Create(nsIPrincipal* aPrincipal, ManagerId** aManagerIdOut); // Main thread only already_AddRefed<nsIPrincipal> Principal() const; - const nsACString& ExtendedOrigin() const { return mExtendedOrigin; } + const nsACString& QuotaOrigin() const { return mQuotaOrigin; } bool operator==(const ManagerId& aOther) const { - return mExtendedOrigin == aOther.mExtendedOrigin; + return mQuotaOrigin == aOther.mQuotaOrigin; } private: - ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin, - const nsACString& aJarPrefix); + ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin); ~ManagerId(); ManagerId(const ManagerId&) = delete; ManagerId& operator=(const ManagerId&) = delete; // only accessible on main thread nsCOMPtr<nsIPrincipal> mPrincipal; // immutable to allow threadsfe access - const nsCString mExtendedOrigin; + const nsCString mQuotaOrigin; public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::cache::ManagerId) }; } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/TypeUtils.cpp +++ b/dom/cache/TypeUtils.cpp @@ -174,16 +174,17 @@ TypeUtils::ToCacheRequest(CacheRequest& nsRefPtr<InternalHeaders> headers = aIn->Headers(); MOZ_ASSERT(headers); ToHeadersEntryList(aOut.headers(), headers); aOut.headersGuard() = headers->Guard(); aOut.mode() = aIn->Mode(); aOut.credentials() = aIn->GetCredentialsMode(); aOut.contentPolicyType() = aIn->ContentPolicyType(); aOut.requestCache() = aIn->GetCacheMode(); + aOut.requestRedirect() = aIn->GetRedirectMode(); if (aBodyAction == IgnoreBody) { aOut.body() = void_t(); return; } // BodyUsed flag is checked and set previously in ToInternalRequest() @@ -207,18 +208,18 @@ TypeUtils::ToCacheResponseWithoutBody(Ca // Pass all Response URL schemes through... The spec only requires we take // action on invalid schemes for Request objects. ProcessURL(aOut.url(), nullptr, nullptr, nullptr, aRv); if (aRv.Failed()) { return; } } - aOut.status() = aIn.GetStatus(); - aOut.statusText() = aIn.GetStatusText(); + aOut.status() = aIn.GetUnfilteredStatus(); + aOut.statusText() = aIn.GetUnfilteredStatusText(); nsRefPtr<InternalHeaders> headers = aIn.UnfilteredHeaders(); MOZ_ASSERT(headers); if (HasVaryStar(headers)) { aRv.ThrowTypeError(MSG_RESPONSE_HAS_VARY_STAR); return; } ToHeadersEntryList(aOut.headers(), headers); aOut.headersGuard() = headers->Guard(); @@ -240,17 +241,17 @@ TypeUtils::ToCacheResponse(CacheResponse nsRefPtr<InternalResponse> ir = aIn.GetInternalResponse(); ToCacheResponseWithoutBody(aOut, *ir, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } nsCOMPtr<nsIInputStream> stream; - ir->GetInternalBody(getter_AddRefs(stream)); + ir->GetUnfilteredBody(getter_AddRefs(stream)); if (stream) { aIn.SetBodyUsed(); } SerializeCacheStream(stream, &aOut.body(), aRv); if (NS_WARN_IF(aRv.Failed())) { return; } @@ -299,26 +300,29 @@ TypeUtils::ToResponse(const CacheRespons ir->SetPrincipalInfo(Move(info)); } nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body()); ir->SetBody(stream); switch (aIn.type()) { + case ResponseType::Basic: + ir = ir->BasicResponse(); + break; + case ResponseType::Cors: + ir = ir->CORSResponse(); + break; case ResponseType::Default: break; case ResponseType::Opaque: ir = ir->OpaqueResponse(); break; - case ResponseType::Basic: - ir = ir->BasicResponse(); - break; - case ResponseType::Cors: - ir = ir->CORSResponse(); + case ResponseType::Opaqueredirect: + ir = ir->OpaqueRedirectResponse(); break; default: MOZ_CRASH("Unexpected ResponseType!"); } MOZ_ASSERT(ir); nsRefPtr<Response> ref = new Response(GetGlobalObject(), ir); return ref.forget(); @@ -335,16 +339,17 @@ TypeUtils::ToInternalRequest(const Cache url.Append(aIn.urlQuery()); internalRequest->SetURL(url); internalRequest->SetReferrer(aIn.referrer()); internalRequest->SetMode(aIn.mode()); internalRequest->SetCredentialsMode(aIn.credentials()); internalRequest->SetContentPolicyType(aIn.contentPolicyType()); internalRequest->SetCacheMode(aIn.requestCache()); + internalRequest->SetRedirectMode(aIn.requestRedirect()); nsRefPtr<InternalHeaders> internalHeaders = ToInternalHeaders(aIn.headers(), aIn.headersGuard()); ErrorResult result; internalRequest->Headers()->SetGuard(aIn.headersGuard(), result); MOZ_ASSERT(!result.Failed()); internalRequest->Headers()->Fill(*internalHeaders, result); MOZ_ASSERT(!result.Failed());
--- a/dom/cache/moz.build +++ b/dom/cache/moz.build @@ -95,8 +95,12 @@ MOCHITEST_MANIFESTS += [ MOCHITEST_CHROME_MANIFESTS += [ 'test/mochitest/chrome.ini', ] BROWSER_CHROME_MANIFESTS += [ 'test/mochitest/browser.ini', ] + +XPCSHELL_TESTS_MANIFESTS += [ + 'test/xpcshell/xpcshell.ini', +]
new file mode 100644 --- /dev/null +++ b/dom/cache/test/xpcshell/head.js @@ -0,0 +1,77 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +// services required be initialized in order to run CacheStorage +var ss = Cc['@mozilla.org/storage/service;1'] + .createInstance(Ci.mozIStorageService); +var sts = Cc['@mozilla.org/network/stream-transport-service;1'] + .getService(Ci.nsIStreamTransportService); +var hash = Cc['@mozilla.org/security/hash;1'] + .createInstance(Ci.nsICryptoHash); + +// Expose Cache and Fetch symbols on the global +Cu.importGlobalProperties(['caches', 'fetch']); + +// Extract a zip file into the profile +function create_test_profile(zipFileName) { + do_get_profile(); + + var directoryService = Cc['@mozilla.org/file/directory_service;1'] + .getService(Ci.nsIProperties); + var profileDir = directoryService.get('ProfD', Ci.nsIFile); + var currentDir = directoryService.get('CurWorkD', Ci.nsIFile); + + var packageFile = currentDir.clone(); + packageFile.append(zipFileName); + + var zipReader = Cc['@mozilla.org/libjar/zip-reader;1'] + .createInstance(Ci.nsIZipReader); + zipReader.open(packageFile); + + var entryNames = []; + var entries = zipReader.findEntries(null); + while (entries.hasMore()) { + var entry = entries.getNext(); + entryNames.push(entry); + } + entryNames.sort(); + + for (var entryName of entryNames) { + var zipentry = zipReader.getEntry(entryName); + + var file = profileDir.clone(); + entryName.split('/').forEach(function(part) { + file.append(part); + }); + + if (zipentry.isDirectory) { + file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0755', 8)); + } else { + var istream = zipReader.getInputStream(entryName); + + var ostream = Cc['@mozilla.org/network/file-output-stream;1'] + .createInstance(Ci.nsIFileOutputStream); + ostream.init(file, -1, parseInt('0644', 8), 0); + + var bostream = Cc['@mozilla.org/network/buffered-output-stream;1'] + .createInstance(Ci.nsIBufferedOutputStream); + bostream.init(ostream, 32 * 1024); + + bostream.writeFrom(istream, istream.available()); + + istream.close(); + bostream.close(); + } + } + + zipReader.close(); +}
new file mode 100644 --- /dev/null +++ b/dom/cache/test/xpcshell/make_profile.js @@ -0,0 +1,142 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +// Enumerate the directory tree and store results in entryList as +// +// { path: 'a/b/c', file: <nsIFile> } +// +// The algorithm starts with the first entry already in entryList. +function enumerate_tree(entryList) { + for (var index = 0; index < entryList.length; ++index) { + var path = entryList[index].path; + var file = entryList[index].file; + + if (file.isDirectory()) { + var dirList = file.directoryEntries; + while (dirList.hasMoreElements()) { + var dirFile = dirList.getNext().QueryInterface(Ci.nsIFile); + entryList.push({ path: path + '/' + dirFile.leafName, file: dirFile }); + } + } + } +} + +function zip_profile(zipFile, profileDir) { + var zipWriter = Cc['@mozilla.org/zipwriter;1'] + .createInstance(Ci.nsIZipWriter); + zipWriter.open(zipFile, 0x04 | 0x08 | 0x20); + + var root = profileDir.clone(); + root.append('storage'); + root.append('default'); + root.append('chrome'); + + var entryList = [{path: 'storage/default/chrome', file: root}]; + enumerate_tree(entryList); + + entryList.forEach(function(entry) { + if (entry.file.isDirectory()) { + zipWriter.addEntryDirectory(entry.path, entry.file.lastModifiedTime, + false); + } else { + var istream = Cc['@mozilla.org/network/file-input-stream;1'] + .createInstance(Ci.nsIFileInputStream); + istream.init(entry.file, -1, -1, 0); + zipWriter.addEntryStream(entry.path, entry.file.lastModifiedTime, + Ci.nsIZipWriter.COMPRESSION_DEFAULT, istream, + false); + istream.close(); + } + }); + + zipWriter.close(); +} + +function exactGC() { + return new Promise(function(resolve) { + var count = 0; + function doPreciseGCandCC() { + function scheduleGCCallback() { + Cu.forceCC(); + + if (++count < 2) { + doPreciseGCandCC(); + } else { + resolve(); + } + } + Cu.schedulePreciseGC(scheduleGCCallback); + } + doPreciseGCandCC(); + }); +} + +function resetQuotaManager() { + return new Promise(function(resolve) { + var qm = Cc['@mozilla.org/dom/quota/manager;1'] + .getService(Ci.nsIQuotaManager); + + var prefService = Cc['@mozilla.org/preferences-service;1'] + .getService(Ci.nsIPrefService); + + // enable quota manager testing mode + var pref = 'dom.quotaManager.testing'; + prefService.getBranch(null).setBoolPref(pref, true); + + qm.reset(); + + // disable quota manager testing mode + //prefService.getBranch(null).setBoolPref(pref, false); + + var uri = Cc['@mozilla.org/network/io-service;1'] + .getService(Ci.nsIIOService) + .newURI('http://example.com', null, null); + var principal = Cc['@mozilla.org/scriptsecuritymanager;1'] + .getService(Ci.nsIScriptSecurityManager) + .getSystemPrincipal(); + + // use getUsageForPrincipal() to get a callback when the reset() is done + qm.getUsageForPrincipal(principal, function(principal, usage, fileUsage) { + resolve(usage); + }); + }); +} + +function run_test() { + do_test_pending(); + do_get_profile(); + + var directoryService = Cc['@mozilla.org/file/directory_service;1'] + .getService(Ci.nsIProperties); + var profileDir = directoryService.get('ProfD', Ci.nsIFile); + var currentDir = directoryService.get('CurWorkD', Ci.nsIFile); + + var zipFile = currentDir.clone(); + zipFile.append('new_profile.zip'); + if (zipFile.exists()) { + zipFile.remove(false); + } + ok(!zipFile.exists()); + + caches.open('xpcshell-test').then(function(c) { + var request = new Request('http://example.com/index.html'); + var response = new Response('hello world'); + return c.put(request, response); + }).then(exactGC).then(resetQuotaManager).then(function() { + zip_profile(zipFile, profileDir); + dump('### ### created zip at: ' + zipFile.path + '\n'); + do_test_finished(); + }).catch(function(e) { + do_test_finished(); + ok(false, e); + }); +}
new file mode 100644 index 0000000000000000000000000000000000000000..6d742275b30921a721dddbcbbd12439d3081bdce GIT binary patch literal 2577 zc$}S;3p7-D9LKMfw0f{-YlSA9Xh=+AW<<2vXiGF|)Fwt_9*jq$d3ZD)Nh2-2ydstI z7-W!))Yc5UEn;g#8Hq7!5*f@PrqGzx^jb8j{oQl#J^yp=@ALcK|NY(H7vZElXE6ZG z1AwfJ=6%3_LKFBNAXA7Wv=0t~#o^J^015=-OCkp1AZ|z%K<S4!O8~_C?0NER`!RCn zPm`IT$x}~pwpk#Kg2tjL=#Lo8z7(f1zDa39bab@ox@@Bj@FmAoKEh{811W}M&=_BZ z0uS3iNR!NYOHcw<&Bl;l$!6pO0R#%}Be0(@AhvA+ss_nTJZGmD_tUa2^RY*2w0TS& z|C_qLO@DAR7{M=k))FEP-JzN}u`Idg=KH}1PjU1HtD>ru?VqZ5mUQYKQZ+D;0P5@Y zP@%%;`2J#Q5@Vb9&xPVVO>nnOrkHEz#W(1cs;92n>TcNJ6_QMKUH0R#BiHxuv5(7% zOo#&+OMBu`6)ier=GwMmu<_N(1t-%Qs;{&{94nW3MoL93K1X3Y1i3DYiqkh_NT0)P zugwKI-Tc@#SVnE_@uIT&J%#nLL*d<xk_Z0tO(fwR*M?rgQv*!g3C_H8)Yg2PV%EU3 zAxR*uy56+As68e(*H~2A&f34)R1lX{EKJ2++R=B1Q1qyxZ;4ICK7qOgbfro4-DE1U zi_0HPar19P@msee0&429M^&3%3S;vh{!Yps%(<K=KKuZQ(tvClG4Y5)vB13?A7cyl zDX4pG%zBPlLm?D<Y4!Q6VI8`}OwBVXHv5{Oj?dC{w}r7%$)Gy4F3$ao>OBg~KY$IQ z40L$Vguj#y44@b(6}rzuMqbsrl(&}d;3ZdvAxbJN-2U26gg;@hv7!jbU4d6xCBetN zjFE0f<2^cPa(OI?VP_KTw>+L9L9S1I4QXl9zONeI?n?(dB;UB=36^GD*lX73qMZQh zUOw7p6Q5XA*Z=ZhuY-R^WI5ucnsrtNB3QdCw23=*H*fcNjQ*=p@B8Jh>5WNRqI%77 z5f6Mbww}x3<*uStvq5b|iBaI~h$p`5L+*v{+oGx|{3mfZ;)Qs3y(EtIkn)C$delrR zdDMTHb-r)xYW~f#r$`+(DGZuRZf5&c^N|@N7Ywg8L@$5gk21GPza8wbr7^9eqQ}TH z#M38g6JcRjKK_;C-=xLK?TGPIQc#7X77d!M&u4hKTC;Zzc`JuBTs)3jP%;crD;Q5X zySKomS?_>AO(fhsm|KW#cs%f|mv<Dh4n)1d35~#yJ=V&`#gY+BX@A+R`!^!>g6XZq zBniixz{3WW`?8ZPANX*W?!EnF4%^0Qb(3x;Q|JH+mG;2w<4=&RHwV^@oP{>KdOSyY zU$jLtW1AO%yB%pp58K&|oKQF6u|Fy1xtMhIg%-9jJj-Q{L+nvQkChmeBceqCIZ&Bi zWjHtw&i3yf+O_g}Sk$PB{C~IFbQnIgS+>|(zB;tR2yKuxF57Tqk$l~0v_-3~6#1GS z-8p}vc0#14B!6l*A)nnjq5Cew^vxUD&P&Sj=U>;BC)T7{Mbf;nPbu|FE$7nB6GLzR zwq!-jih{Oa4ihQu9cHx$@kDN6+!0=0Ow{S1VkWP!EYmh(Sg3h&`$SMzp}_9oEvu)@ zFlTCW(JrjBVaK*O;u+1Ph7Ph%3w~>^Q)^Ro6TL6t-04;ma(DODfxgl7)*H}}VhTZ$ zBpH|0^px1E%EbirobTE0ye&sE(RD~HlM4(Qrf14zsGfJTZc)mXZ1He;vZy(kS-^bE zbTWiLq_BIDe&U<BTH>$N{*4p6B&64)G6DVlcEl}pzHmb7sTVGjf#1KfRgQ~RL`=(0 zoh&ntMh%?4gPROXZz|C~sKt@VJU&z^+{qTplz?ebB@=il9>oT;jnP0N$%p!xi98c! z^pz-cbGV`b?;cP+pg)Ov`_+-(fTOL&K)o&Tc(^Iv0&i|=iL=C+u7j<CnPQ-8ETJ%O z3=9v4MwsCV`_Ta(OOH3yR3ZA#EJbM4>D{TiV(0P7y28=X&&7pf9~SBAqBf~b={rqj z#qRnpG=Tn%Z|}2v?FZWD-S$J|MB6E*&Gg<ci3Cj%nV}dv!*!=ho$kAzqzYzZe%^(@ zB-&t#=#_J4DKN7?6{C%&pbvZ{dRo6u-J0ng`)O;cz5)Ng`WNA(GWTsM)yX6CI{@&I HPo#eV>zUe=
new file mode 100644 --- /dev/null +++ b/dom/cache/test/xpcshell/test_migration.js @@ -0,0 +1,38 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +function run_test() { + do_test_pending(); + create_test_profile('schema_15_profile.zip'); + + var cache; + caches.open('xpcshell-test').then(function(c) { + cache = c; + ok(cache, 'cache exists'); + return cache.keys(); + }).then(function(requestList) { + ok(requestList.length > 0, 'should have at least one request in cache'); + requestList.forEach(function(request) { + ok(request, 'each request in list should be non-null'); + ok(request.redirect === 'follow', 'request.redirect should default to "follow"'); + }); + return Promise.all(requestList.map(function(request) { + return cache.match(request); + })); + }).then(function(responseList) { + ok(responseList.length > 0, 'should have at least one response in cache'); + responseList.forEach(function(response) { + ok(response, 'each request in list should be non-null'); + }); + }).then(function() { + do_test_finished(); + }).catch(function(e) { + ok(false, 'caught exception ' + e); + do_test_finished(); + }); +}
new file mode 100644 --- /dev/null +++ b/dom/cache/test/xpcshell/xpcshell.ini @@ -0,0 +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/. + +[DEFAULT] +head = head.js +tail = +skip-if = toolkit == 'gonk' +support-files = + schema_15_profile.zip + +# dummy test entry to generate profile zip files +[make_profile.js] + skip-if = true + +[test_migration.js]
--- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -348,18 +348,18 @@ EventListenerManager::AddEventListenerIn aTypeAtom == nsGkAtoms::ontouchcancel) { mMayHaveTouchEventListener = true; nsPIDOMWindow* window = GetInnerWindowForTarget(); // we don't want touchevent listeners added by scrollbars to flip this flag // so we ignore listeners created with system event flag if (window && !aFlags.mInSystemGroup) { window->SetHasTouchEventListeners(); } - } else if (aEventMessage >= NS_POINTER_EVENT_START && - aEventMessage <= NS_POINTER_LOST_CAPTURE) { + } else if (aEventMessage >= ePointerEventFirst && + aEventMessage <= ePointerEventLast) { nsPIDOMWindow* window = GetInnerWindowForTarget(); if (aTypeAtom == nsGkAtoms::onpointerenter || aTypeAtom == nsGkAtoms::onpointerleave) { mMayHavePointerEnterLeaveEventListener = true; if (window) { #ifdef DEBUG nsCOMPtr<nsIDocument> d = window->GetExtantDoc(); NS_WARN_IF_FALSE(!nsContentUtils::IsChromeDoc(d),
--- a/dom/events/EventNameList.h +++ b/dom/events/EventNameList.h @@ -164,17 +164,17 @@ EVENT(change, NS_FORM_CHANGE, EventNameType_HTMLXUL, eBasicEventClass) EVENT(click, eMouseClick, EventNameType_All, eMouseEventClass) EVENT(contextmenu, - NS_CONTEXTMENU, + eContextMenu, EventNameType_HTMLXUL, eMouseEventClass) // Not supported yet // EVENT(cuechange) EVENT(dblclick, eMouseDoubleClick, EventNameType_HTMLXUL, eMouseEventClass) @@ -306,53 +306,53 @@ EVENT(mozpointerlockchange, NS_POINTERLOCKCHANGE, EventNameType_HTML, eBasicEventClass) EVENT(mozpointerlockerror, NS_POINTERLOCKERROR, EventNameType_HTML, eBasicEventClass) EVENT(pointerdown, - NS_POINTER_DOWN, + ePointerDown, EventNameType_All, ePointerEventClass) EVENT(pointermove, - NS_POINTER_MOVE, + ePointerMove, EventNameType_All, ePointerEventClass) EVENT(pointerup, - NS_POINTER_UP, + ePointerUp, EventNameType_All, ePointerEventClass) EVENT(pointercancel, - NS_POINTER_CANCEL, + ePointerCancel, EventNameType_All, ePointerEventClass) EVENT(pointerover, - NS_POINTER_OVER, + ePointerOver, EventNameType_All, ePointerEventClass) EVENT(pointerout, - NS_POINTER_OUT, + ePointerOut, EventNameType_All, ePointerEventClass) EVENT(pointerenter, - NS_POINTER_ENTER, + ePointerEnter, EventNameType_All, ePointerEventClass) EVENT(pointerleave, - NS_POINTER_LEAVE, + ePointerLeave, EventNameType_All, ePointerEventClass) EVENT(gotpointercapture, - NS_POINTER_GOT_CAPTURE, + ePointerGotCapture, EventNameType_All, ePointerEventClass) EVENT(lostpointercapture, - NS_POINTER_LOST_CAPTURE, + ePointerLostCapture, EventNameType_All, ePointerEventClass) // Not supported yet; probably never because "wheel" is a better idea. // EVENT(mousewheel) EVENT(pause, NS_PAUSE, EventNameType_HTML,
--- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -538,17 +538,17 @@ EventStateManager::PreHandleEvent(nsPres UIEvent::CalculateScreenPoint(aPresContext, aEvent); sLastClientPoint = UIEvent::CalculateClientPoint(aPresContext, aEvent, nullptr); } *aStatus = nsEventStatus_eIgnore; switch (aEvent->mMessage) { - case NS_CONTEXTMENU: + case eContextMenu: if (sIsPointerLocked) { return NS_ERROR_DOM_INVALID_STATE_ERR; } break; case eMouseDown: { switch (mouseEvent->button) { case WidgetMouseEvent::eLeftButton: BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame); @@ -617,26 +617,26 @@ EventStateManager::PreHandleEvent(nsPres // "exit" or "move" events. Any necessary "out" or "over" events // will be generated by GenerateMouseEnterExit mouseEvent->mMessage = eMouseMove; mouseEvent->reason = WidgetMouseEvent::eSynthesized; // then fall through... } else { if (sPointerEventEnabled) { // We should synthetize corresponding pointer events - GeneratePointerEnterExit(NS_POINTER_LEAVE, mouseEvent); + GeneratePointerEnterExit(ePointerLeave, mouseEvent); } GenerateMouseEnterExit(mouseEvent); //This is a window level mouse exit event and should stop here aEvent->mMessage = eVoidEvent; break; } case eMouseMove: - case NS_POINTER_DOWN: - case NS_POINTER_MOVE: { + case ePointerDown: + case ePointerMove: { // on the Mac, GenerateDragGesture() may not return until the drag // has completed and so |aTargetFrame| may have been deleted (moving // a bookmark, for example). If this is the case, however, we know // that ClearFrameRefs() has been called and it cleared out // |mCurrentTarget|. As a result, we should pass |mCurrentTarget| // into UpdateCursor(). GenerateDragGesture(aPresContext, mouseEvent); UpdateCursor(aPresContext, aEvent, mCurrentTarget, aStatus); @@ -1169,17 +1169,17 @@ CrossProcessSafeEvent(const WidgetEvent& case eKeyboardEventClass: case eWheelEventClass: return true; case eMouseEventClass: switch (aEvent.mMessage) { case eMouseDown: case eMouseUp: case eMouseMove: - case NS_CONTEXTMENU: + case eContextMenu: case eMouseEnterIntoWidget: case eMouseExitFromWidget: return true; default: return false; } case eTouchEventClass: switch (aEvent.mMessage) { @@ -1447,17 +1447,17 @@ EventStateManager::FireContextClick() nsGkAtoms::embed, nsGkAtoms::object)) { allowedToDispatch = false; } } if (allowedToDispatch) { // init the event while mCurrentTarget is still good - WidgetMouseEvent event(true, NS_CONTEXTMENU, targetWidget, + WidgetMouseEvent event(true, eContextMenu, targetWidget, WidgetMouseEvent::eReal); event.clickCount = 1; FillInEventFromGestureDown(&event); // stop selection tracking, we're in control now if (mCurrentTarget) { nsRefPtr<nsFrameSelection> frameSel = @@ -1473,17 +1473,17 @@ EventStateManager::FireContextClick() nsIDocument* doc = mGestureDownContent->GetCrossShadowCurrentDoc(); AutoHandlingUserInputStatePusher userInpStatePusher(true, &event, doc); // dispatch to DOM EventDispatcher::Dispatch(mGestureDownContent, mPresContext, &event, nullptr, &status); // We don't need to dispatch to frame handling because no frames - // watch NS_CONTEXTMENU except for nsMenuFrame and that's only for + // watch eContextMenu except for nsMenuFrame and that's only for // dismissal. That's just as well since we don't really know // which frame to send it to. } } // now check if the event has been handled. If so, stop tracking a drag if (status == nsEventStatus_eConsumeNoDefault) { StopTrackingDragGesture(); @@ -2996,24 +2996,24 @@ EventStateManager::PostHandleEvent(nsPre fm->SetFocusedWindow(mDocument->GetWindow()); } } } } SetActiveManager(this, activeContent); } break; - case NS_POINTER_CANCEL: { + case ePointerCancel: { if(WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) { GenerateMouseEnterExit(mouseEvent); } // This break was commented specially // break; } - case NS_POINTER_UP: { + case ePointerUp: { WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent(); // After UP/Cancel Touch pointers become invalid so we can remove relevant helper from Table // Mouse/Pen pointers are valid all the time (not only between down/up) if (pointerEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId); GenerateMouseEnterExit(pointerEvent); } break; @@ -3867,18 +3867,17 @@ public: // mouseenter/leave is fired only on elements. current = current->GetParent(); } } } ~EnterLeaveDispatcher() { - if (mEventMessage == eMouseEnter || - mEventMessage == NS_POINTER_ENTER) { + if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) { for (int32_t i = mTargets.Count() - 1; i >= 0; --i) { mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage, mTargets[i], mRelatedTarget); } } else { for (int32_t i = 0; i < mTargets.Count(); ++i) { mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage, mTargets[i], mRelatedTarget); @@ -3939,27 +3938,27 @@ EventStateManager::NotifyMouseOut(Widget // switching the hover state to null here. bool isPointer = aMouseEvent->mClass == ePointerEventClass; if (!aMovingInto && !isPointer) { // Unset :hover SetContentState(nullptr, NS_EVENT_STATE_HOVER); } // In case we go out from capturing element (retargetedByPointerCapture is true) - // we should dispatch NS_POINTER_LEAVE event and only for capturing element. + // we should dispatch ePointerLeave event and only for capturing element. nsRefPtr<nsIContent> movingInto = aMouseEvent->retargetedByPointerCapture ? wrapper->mLastOverElement->GetParent() : aMovingInto; EnterLeaveDispatcher leaveDispatcher(this, wrapper->mLastOverElement, movingInto, aMouseEvent, - isPointer ? NS_POINTER_LEAVE : eMouseLeave); + isPointer ? ePointerLeave : eMouseLeave); // Fire mouseout - DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? NS_POINTER_OUT : eMouseOut, + DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? ePointerOut : eMouseOut, wrapper->mLastOverElement, aMovingInto); wrapper->mLastOverFrame = nullptr; wrapper->mLastOverElement = nullptr; // Turn recursion protection back off wrapper->mFirstOutEventElement = nullptr; } @@ -4005,34 +4004,34 @@ EventStateManager::NotifyMouseOver(Widge // DispatchMouseOrPointerEvent() call below, since NotifyMouseOut() resets it, bug 298477. nsCOMPtr<nsIContent> lastOverElement = wrapper->mLastOverElement; bool isPointer = aMouseEvent->mClass == ePointerEventClass; Maybe<EnterLeaveDispatcher> enterDispatcher; if (dispatch) { enterDispatcher.emplace(this, aContent, lastOverElement, aMouseEvent, - isPointer ? NS_POINTER_ENTER : eMouseEnter); + isPointer ? ePointerEnter : eMouseEnter); } NotifyMouseOut(aMouseEvent, aContent); // Store the first mouseOver event we fire and don't refire mouseOver // to that element while the first mouseOver is still ongoing. wrapper->mFirstOverEventElement = aContent; if (!isPointer) { SetContentState(aContent, NS_EVENT_STATE_HOVER); } if (dispatch) { // Fire mouseover wrapper->mLastOverFrame = DispatchMouseOrPointerEvent(aMouseEvent, - isPointer ? NS_POINTER_OVER : eMouseOver, + isPointer ? ePointerOver : eMouseOver, aContent, lastOverElement); wrapper->mLastOverElement = aContent; } else { wrapper->mLastOverFrame = nullptr; wrapper->mLastOverElement = nullptr; } // Turn recursion protection back off @@ -4144,33 +4143,33 @@ EventStateManager::GenerateMouseEnterExi } else { aMouseEvent->lastRefPoint = sLastRefPoint; } // Update the last known refPoint with the current refPoint. sLastRefPoint = aMouseEvent->refPoint; } - case NS_POINTER_MOVE: - case NS_POINTER_DOWN: + case ePointerMove: + case ePointerDown: { // Get the target content target (mousemove target == mouseover target) nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent); if (!targetElement) { // We're always over the document root, even if we're only // over dead space in a page (whose frame is not associated with // any content) or in print preview dead space targetElement = mDocument->GetRootElement(); } if (targetElement) { NotifyMouseOver(aMouseEvent, targetElement); } } break; - case NS_POINTER_UP: + case ePointerUp: { // Get the target content target (mousemove target == mouseover target) nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent); if (!targetElement) { // We're always over the document root, even if we're only // over dead space in a page (whose frame is not associated with // any content) or in print preview dead space targetElement = mDocument->GetRootElement(); @@ -4179,18 +4178,18 @@ EventStateManager::GenerateMouseEnterExi OverOutElementsWrapper* helper = GetWrapperByEventID(aMouseEvent); if (helper) { helper->mLastOverElement = targetElement; } NotifyMouseOut(aMouseEvent, nullptr); } } break; - case NS_POINTER_LEAVE: - case NS_POINTER_CANCEL: + case ePointerLeave: + case ePointerCancel: case eMouseExitFromWidget: { // This is actually the window mouse exit or pointer leave event. We're not moving // into any new element. OverOutElementsWrapper* helper = GetWrapperByEventID(aMouseEvent); if (helper->mLastOverFrame && nsContentUtils::GetTopLevelWidget(aMouseEvent->widget) !=
--- a/dom/events/WheelHandlingHelper.cpp +++ b/dom/events/WheelHandlingHelper.cpp @@ -209,17 +209,17 @@ WheelTransaction::OnEvent(WidgetEvent* a } case eKeyPress: case eKeyUp: case eKeyDown: case eMouseUp: case eMouseDown: case eMouseDoubleClick: case eMouseClick: - case NS_CONTEXTMENU: + case eContextMenu: case NS_DRAGDROP_DROP: EndTransaction(); return; default: break; } }
--- a/dom/fetch/ChannelInfo.cpp +++ b/dom/fetch/ChannelInfo.cpp @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ChannelInfo.h" #include "nsCOMPtr.h" +#include "nsContentUtils.h" #include "nsIChannel.h" #include "nsIDocument.h" #include "nsIHttpChannel.h" #include "nsSerializationHelper.h" #include "mozilla/net/HttpBaseChannel.h" #include "mozilla/ipc/ChannelInfo.h" #include "nsIJARChannel.h" #include "nsJARChannel.h" @@ -64,16 +65,30 @@ ChannelInfo::InitFromChannel(nsIChannel* redirectedURI->GetSpec(mRedirectedURISpec); } } mInited = true; } void +ChannelInfo::InitFromChromeGlobal(nsIGlobalObject* aGlobal) +{ + MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); + MOZ_ASSERT(aGlobal); + + MOZ_RELEASE_ASSERT( + nsContentUtils::IsSystemPrincipal(aGlobal->PrincipalOrNull())); + + mSecurityInfo.Truncate(); + mRedirected = false; + mInited = true; +} + +void ChannelInfo::InitFromIPCChannelInfo(const ipc::IPCChannelInfo& aChannelInfo) { MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); mSecurityInfo = aChannelInfo.securityInfo(); mRedirectedURISpec = aChannelInfo.redirectedURI(); mRedirected = aChannelInfo.redirected();
--- a/dom/fetch/ChannelInfo.h +++ b/dom/fetch/ChannelInfo.h @@ -7,16 +7,17 @@ #ifndef mozilla_dom_ChannelInfo_h #define mozilla_dom_ChannelInfo_h #include "nsString.h" #include "nsCOMPtr.h" class nsIChannel; class nsIDocument; +class nsIGlobalObject; class nsIURI; namespace mozilla { namespace ipc { class IPCChannelInfo; } // namespace ipc namespace dom { @@ -64,16 +65,17 @@ public: mRedirectedURISpec = aRHS.mRedirectedURISpec; mInited = aRHS.mInited; mRedirected = aRHS.mRedirected; return *this; } void InitFromDocument(nsIDocument* aDoc); void InitFromChannel(nsIChannel* aChannel); + void InitFromChromeGlobal(nsIGlobalObject* aGlobal); void InitFromIPCChannelInfo(const IPCChannelInfo& aChannelInfo); // This restores every possible information stored from a previous channel // object on a new one. nsresult ResurrectInfoOnChannel(nsIChannel* aChannel); bool IsInitialized() const {
--- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -26,16 +26,17 @@ #include "nsHostObjectProtocolHandler.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" #include "nsStreamUtils.h" #include "nsStringStream.h" #include "mozilla/dom/File.h" #include "mozilla/dom/workers/Workers.h" +#include "mozilla/unused.h" #include "Fetch.h" #include "InternalRequest.h" #include "InternalResponse.h" namespace mozilla { namespace dom { @@ -44,16 +45,17 @@ NS_IMPL_ISUPPORTS(FetchDriver, nsIAsyncVerifyRedirectCallback, nsIThreadRetargetableStreamListener) FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) : mPrincipal(aPrincipal) , mLoadGroup(aLoadGroup) , mRequest(aRequest) , mFetchRecursionCount(0) + , mCORSFlagEverSet(false) , mResponseAvailableCalled(false) { } FetchDriver::~FetchDriver() { // We assert this since even on failures, we should call // FailWithNetworkError(). @@ -89,28 +91,28 @@ FetchDriver::Fetch(bool aCORSFlag) FailWithNetworkError(); } return rv; } MOZ_CRASH("Synchronous fetch not supported"); } -nsresult -FetchDriver::ContinueFetch(bool aCORSFlag) +FetchDriver::MainFetchOp +FetchDriver::SetTaintingAndGetNextOp(bool aCORSFlag) { workers::AssertIsOnMainThread(); nsAutoCString url; mRequest->GetURL(url); nsCOMPtr<nsIURI> requestURI; nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { - return FailWithNetworkError(); + return MainFetchOp(NETWORK_ERROR); } // CSP/mixed content checks. int16_t shouldLoad; rv = NS_CheckContentLoadPolicy(mRequest->ContentPolicyType(), requestURI, mPrincipal, mDocument, @@ -118,61 +120,101 @@ FetchDriver::ContinueFetch(bool aCORSFla // Content-Type header? EmptyCString(), /* mime guess */ nullptr, /* extra */ &shouldLoad, nsContentUtils::GetContentPolicy(), nsContentUtils::GetSecurityManager()); if (NS_WARN_IF(NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad))) { // Disallowed by content policy. - return FailWithNetworkError(); + return MainFetchOp(NETWORK_ERROR); } - // Begin Step 4 of the Fetch algorithm + // Begin Step 8 of the Main Fetch algorithm // https://fetch.spec.whatwg.org/#fetching nsAutoCString scheme; rv = requestURI->GetScheme(scheme); if (NS_WARN_IF(NS_FAILED(rv))) { - return FailWithNetworkError(); + return MainFetchOp(NETWORK_ERROR); } - rv = mPrincipal->CheckMayLoad(requestURI, false /* report */, false /* allowIfInheritsPrincipal */); + // request's current url's origin is request's origin and the CORS flag is unset + // request's current url's scheme is "data" and request's same-origin data-URL flag is set + // request's current url's scheme is "about" + rv = mPrincipal->CheckMayLoad(requestURI, false /* report */, + false /* allowIfInheritsPrincipal */); if ((!aCORSFlag && NS_SUCCEEDED(rv)) || (scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) || scheme.EqualsLiteral("about")) { - return BasicFetch(); + return MainFetchOp(BASIC_FETCH); + } + + // request's mode is "same-origin" + if (mRequest->Mode() == RequestMode::Same_origin) { + return MainFetchOp(NETWORK_ERROR); + } + + // request's mode is "no-cors" + if (mRequest->Mode() == RequestMode::No_cors) { + mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE); + return MainFetchOp(BASIC_FETCH); + } + + // request's current url's scheme is not one of "http" and "https" + if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { + return MainFetchOp(NETWORK_ERROR); } - if (mRequest->Mode() == RequestMode::Same_origin) { + // request's mode is "cors-with-forced-preflight" + // request's unsafe-request flag is set and either request's method is not + // a simple method or a header in request's header list is not a simple header + if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight || + (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() || + !mRequest->Headers()->HasOnlySimpleHeaders()))) { + mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS); + mRequest->SetRedirectMode(RequestRedirect::Error); + + // Note, the following text from Main Fetch step 8 is handled in + // nsCORSListenerProxy when CheckRequestApproved() fails: + // + // The result of performing an HTTP fetch using request with the CORS + // flag and CORS-preflight flag set. If the result is a network error, + // clear cache entries using request. + + return MainFetchOp(HTTP_FETCH, true /* cors */, true /* preflight */); + } + + // Otherwise + mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS); + return MainFetchOp(HTTP_FETCH, true /* cors */, false /* preflight */); +} + +nsresult +FetchDriver::ContinueFetch(bool aCORSFlag) +{ + workers::AssertIsOnMainThread(); + + MainFetchOp nextOp = SetTaintingAndGetNextOp(aCORSFlag); + + if (nextOp.mType == NETWORK_ERROR) { return FailWithNetworkError(); } - if (mRequest->Mode() == RequestMode::No_cors) { - mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE); + if (nextOp.mType == BASIC_FETCH) { return BasicFetch(); } - if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { - return FailWithNetworkError(); + if (nextOp.mType == HTTP_FETCH) { + return HttpFetch(nextOp.mCORSFlag, nextOp.mCORSPreflightFlag); } - bool corsPreflight = false; - if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight || - (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() || !mRequest->Headers()->HasOnlySimpleHeaders()))) { - corsPreflight = true; - } - // The Request constructor should ensure that no-cors requests have simple - // method and headers, so we should never attempt to preflight for such - // Requests. - MOZ_ASSERT_IF(mRequest->Mode() == RequestMode::No_cors, !corsPreflight); - - mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS); - return HttpFetch(true /* aCORSFlag */, corsPreflight); -} + MOZ_ASSERT_UNREACHABLE("Unexpected main fetch operation!"); + return FailWithNetworkError(); + } nsresult FetchDriver::BasicFetch() { nsAutoCString url; mRequest->GetURL(url); nsCOMPtr<nsIURI> uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), @@ -326,16 +368,21 @@ FetchDriver::BasicFetch() // Necko HTTP implementation. nsresult FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthenticationFlag) { // Step 1. "Let response be null." mResponse = nullptr; nsresult rv; + // We need to track the CORS flag through redirects. Since there is no way + // for us to go from CORS mode to non-CORS mode, we just need to remember + // if it has ever been set. + mCORSFlagEverSet = mCORSFlagEverSet || aCORSFlag; + nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } nsAutoCString url; mRequest->GetURL(url); @@ -482,16 +529,23 @@ FetchDriver::HttpFetch(bool aCORSFlag, b httpChan->SetRequestHeader(NS_LITERAL_CSTRING("origin"), NS_ConvertUTF16toUTF8(origin), false /* merge */); } // Bug 1120722 - Authorization will be handled later. // Auth may require prompting, we don't support it yet. // The next patch in this same bug prevents this from aborting the request. // Credentials checks for CORS are handled by nsCORSListenerProxy, + + nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan); + + // Conversion between enumerations is safe due to static asserts in + // dom/workers/ServiceWorkerManager.cpp + internalChan->SetCorsMode(static_cast<uint32_t>(mRequest->Mode())); + internalChan->SetRedirectMode(static_cast<uint32_t>(mRequest->GetRedirectMode())); } // Step 5. Proxy authentication will be handled by Necko. // FIXME(nsm): Bug 1120715. // Step 7-10. "If request's cache mode is neither no-store nor reload..." // Continue setting up 'HTTPRequest'. Content-Type and body data. nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(chan); @@ -529,20 +583,20 @@ FetchDriver::HttpFetch(bool aCORSFlag, b // If it is not an http channel, it has to be a jar one. MOZ_ASSERT(jarChannel); jarChannel->ForceNoIntercept(); } } nsCOMPtr<nsIStreamListener> listener = this; - // Unless the cors mode is explicitly no-cors, we set up a cors proxy even in - // the same-origin case, since the proxy does not enforce cors header checks - // in the same-origin case. - if (mRequest->Mode() != RequestMode::No_cors) { + // Only use nsCORSListenerProxy if we are in CORS mode. Otherwise it + // will overwrite the CorsMode flag unconditionally to "cors" or + // "cors-with-forced-preflight". + if (mRequest->Mode() == RequestMode::Cors) { // Set up a CORS proxy that will handle the various requirements of the CORS // protocol. It handles the preflight cache and CORS response headers. // If the request is allowed, it will start our original request // and our observer will be notified. On failure, our observer is notified // directly. nsRefPtr<nsCORSListenerProxy> corsListener = new nsCORSListenerProxy(this, mPrincipal, useCredentials); rv = corsListener->Init(chan, DataURIHandling::Allow); @@ -611,16 +665,19 @@ FetchDriver::BeginAndGetFilteredResponse filteredResponse = aResponse->BasicResponse(); break; case InternalRequest::RESPONSETAINT_CORS: filteredResponse = aResponse->CORSResponse(); break; case InternalRequest::RESPONSETAINT_OPAQUE: filteredResponse = aResponse->OpaqueResponse(); break; + case InternalRequest::RESPONSETAINT_OPAQUEREDIRECT: + filteredResponse = aResponse->OpaqueRedirectResponse(); + break; default: MOZ_CRASH("Unexpected case"); } MOZ_ASSERT(filteredResponse); MOZ_ASSERT(mObserver); mObserver->OnResponseAvailable(filteredResponse); mResponseAvailableCalled = true; @@ -691,25 +748,32 @@ public: NS_IMPL_ISUPPORTS(FillResponseHeaders, nsIHttpHeaderVisitor) } // namespace NS_IMETHODIMP FetchDriver::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { workers::AssertIsOnMainThread(); - MOZ_ASSERT(!mPipeOutputStream); - MOZ_ASSERT(mObserver); + + // Note, this can be called multiple times if we are doing an opaqueredirect. + // In that case we will get a simulated OnStartRequest() and then the real + // channel will call in with an errored OnStartRequest(). + nsresult rv; aRequest->GetStatus(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } + // We should only get to the following code once. + MOZ_ASSERT(!mPipeOutputStream); + MOZ_ASSERT(mObserver); + nsRefPtr<InternalResponse> response; nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); if (httpChannel) { uint32_t responseStatus; httpChannel->GetResponseStatus(&responseStatus); nsAutoCString statusText; httpChannel->GetResponseStatusText(statusText); @@ -834,43 +898,69 @@ FetchDriver::AsyncOnChannelRedirect(nsIC nsIChannel* aNewChannel, uint32_t aFlags, nsIAsyncVerifyRedirectCallback *aCallback) { NS_PRECONDITION(aNewChannel, "Redirect without a channel?"); nsresult rv; - // Section 4.2, Step 4.6-4.7, enforcing a redirect count is done by Necko. - // The pref used is "network.http.redirection-limit" which is set to 20 by - // default. - // - // Step 4.8. We only unset this for spec compatibility. Any actions we take - // on mRequest here do not affect what the channel does. + // HTTP Fetch step 5, "redirect status", step 1 + if (NS_WARN_IF(mRequest->GetRedirectMode() == RequestRedirect::Error)) { + aOldChannel->Cancel(NS_BINDING_FAILED); + return NS_BINDING_FAILED; + } + + // HTTP Fetch step 5, "redirect status", steps 2 through 6 are automatically + // handled by necko before calling AsyncOnChannelRedirect() with the new + // nsIChannel. + + // HTTP Fetch step 5, "redirect status", steps 7 and 8 enforcing a redirect + // count are done by Necko. The pref used is "network.http.redirection-limit" + // which is set to 20 by default. + + // HTTP Fetch Step 9, "redirect status". We only unset this for spec + // compatibility. Any actions we take on mRequest here do not affect what the + //channel does. mRequest->UnsetSameOriginDataURL(); - // - // Requests that require preflight are not permitted to redirect. - // Fetch spec section 4.2 "HTTP Fetch", step 4.9 just uses the manual - // redirect flag to decide whether to execute step 4.10 or not. We do not - // represent it in our implementation. - // The only thing we do is to check if the request requires a preflight (part - // of step 4.9), in which case we abort. This part cannot be done by - // nsCORSListenerProxy since it does not have access to mRequest. - // which case. Step 4.10.3 is handled by OnRedirectVerifyCallback(), and all - // the other steps are handled by nsCORSListenerProxy. - if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) { - rv = DoesNotRequirePreflight(aNewChannel); - if (NS_FAILED(rv)) { - NS_WARNING("FetchDriver::OnChannelRedirect: " - "DoesNotRequirePreflight returned failure"); - return rv; - } + // HTTP Fetch step 5, "redirect status", step 10 requires us to halt the + // redirect, but successfully return an opaqueredirect Response to the + // initiating Fetch. + if (mRequest->GetRedirectMode() == RequestRedirect::Manual) { + // Ideally we would simply not cancel the old channel and allow it to + // be processed as normal. Unfortunately this is quite fragile and + // other redirect handlers can easily break it for certain use cases. + // + // For example, nsCORSListenerProxy cancels vetoed redirect channels. + // The HTTP cache will also error on vetoed redirects when the + // redirect has been previously cached. + // + // Therefore simulate the completion of the channel to produce the + // opaqueredirect Response and then cancel the original channel. This + // will result in OnStartRequest() getting called twice, but the second + // time will be with an error response (from the Cancel) which will + // be ignored. + mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUEREDIRECT); + unused << OnStartRequest(aOldChannel, nullptr); + unused << OnStopRequest(aOldChannel, nullptr, NS_OK); + + aOldChannel->Cancel(NS_BINDING_FAILED); + + return NS_BINDING_FAILED; } + // The following steps are from HTTP Fetch step 5, "redirect status", step 11 + // which requires the RequestRedirect to be "follow". + MOZ_ASSERT(mRequest->GetRedirectMode() == RequestRedirect::Follow); + + // HTTP Fetch step 5, "redirect status", steps 11.1 and 11.2 block redirecting + // to a URL with credentials in CORS mode. This is implemented in + // nsCORSListenerProxy. + mRedirectCallback = aCallback; mOldRedirectChannel = aOldChannel; mNewRedirectChannel = aNewChannel; nsCOMPtr<nsIChannelEventSink> outer = do_GetInterface(mNotificationCallbacks); if (outer) { // The callee is supposed to call OnRedirectVerifyCallback() on success, @@ -953,38 +1043,54 @@ FetchDriver::GetInterface(const nsIID& a } return QueryInterface(aIID, aResult); } NS_IMETHODIMP FetchDriver::OnRedirectVerifyCallback(nsresult aResult) { - // On a successful redirect we perform the following substeps of Section 4.2, - // step 4.10. + // On a successful redirect we perform the following substeps of HTTP Fetch, + // step 5, "redirect status", step 11. if (NS_SUCCEEDED(aResult)) { - // Step 4.10.3 "Set request's url to locationURL." so that when we set the - // Response's URL from the Request's URL in Section 4, step 6, we get the - // final value. + // Step 11.5 "Append locationURL to request's url list." so that when we set the + // Response's URL from the Request's URL in Main Fetch, step 15, we get the + // final value. Note, we still use a single URL value instead of a list. nsCOMPtr<nsIURI> newURI; nsresult rv = NS_GetFinalChannelURI(mNewRedirectChannel, getter_AddRefs(newURI)); if (NS_WARN_IF(NS_FAILED(rv))) { aResult = rv; } else { // We need to update our request's URL. nsAutoCString newUrl; newURI->GetSpec(newUrl); mRequest->SetURL(newUrl); } } if (NS_FAILED(aResult)) { mOldRedirectChannel->Cancel(aResult); } + // Implement Main Fetch step 8 again on redirect. + MainFetchOp nextOp = SetTaintingAndGetNextOp(mCORSFlagEverSet); + + if (nextOp.mType == NETWORK_ERROR) { + // Cancel the channel if Main Fetch blocks the redirect from continuing. + aResult = NS_ERROR_DOM_BAD_URI; + mOldRedirectChannel->Cancel(aResult); + } else { + // Otherwise, we rely on necko and the CORS proxy to do the right thing + // as the redirect is followed. In general this means basic or http + // fetch. If we've ever been CORS, we need to stay CORS. + MOZ_ASSERT(nextOp.mType == BASIC_FETCH || nextOp.mType == HTTP_FETCH); + MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mType == HTTP_FETCH); + MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mCORSFlag); + } + mOldRedirectChannel = nullptr; mNewRedirectChannel = nullptr; mRedirectCallback->OnRedirectVerifyCallback(aResult); mRedirectCallback = nullptr; return NS_OK; } void
--- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -72,25 +72,48 @@ private: nsCOMPtr<nsIOutputStream> mPipeOutputStream; nsRefPtr<FetchDriverObserver> mObserver; nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks; nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; nsCOMPtr<nsIChannel> mOldRedirectChannel; nsCOMPtr<nsIChannel> mNewRedirectChannel; nsCOMPtr<nsIDocument> mDocument; uint32_t mFetchRecursionCount; + bool mCORSFlagEverSet; DebugOnly<bool> mResponseAvailableCalled; FetchDriver() = delete; FetchDriver(const FetchDriver&) = delete; FetchDriver& operator=(const FetchDriver&) = delete; ~FetchDriver(); + enum MainFetchOpType + { + NETWORK_ERROR, + BASIC_FETCH, + HTTP_FETCH, + NUM_MAIN_FETCH_OPS + }; + + struct MainFetchOp + { + explicit MainFetchOp(MainFetchOpType aType, bool aCORSFlag = false, + bool aCORSPreflightFlag = false) + : mType(aType), mCORSFlag(aCORSFlag), + mCORSPreflightFlag(aCORSPreflightFlag) + { } + + MainFetchOpType mType; + bool mCORSFlag; + bool mCORSPreflightFlag; + }; + nsresult Fetch(bool aCORSFlag); + MainFetchOp SetTaintingAndGetNextOp(bool aCORSFlag); nsresult ContinueFetch(bool aCORSFlag); nsresult BasicFetch(); nsresult HttpFetch(bool aCORSFlag = false, bool aCORSPreflightFlag = false, bool aAuthenticationFlag = false); nsresult ContinueHttpFetchAfterNetworkFetch(); // Returns the filtered response sent to the observer. // Callers who don't have access to a channel can pass null for aFinalURI. already_AddRefed<InternalResponse> BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI);
--- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -37,16 +37,17 @@ InternalRequest::GetRequestConstructorCo copy->mSameOriginDataURL = true; copy->mPreserveContentCodings = true; // The default referrer is already about:client. copy->mContentPolicyType = nsIContentPolicy::TYPE_FETCH; copy->mMode = mMode; copy->mCredentialsMode = mCredentialsMode; copy->mCacheMode = mCacheMode; + copy->mRedirectMode = mRedirectMode; copy->mCreatedByFetchEvent = mCreatedByFetchEvent; return copy.forget(); } already_AddRefed<InternalRequest> InternalRequest::Clone() { nsRefPtr<InternalRequest> clone = new InternalRequest(*this); @@ -75,16 +76,17 @@ InternalRequest::InternalRequest(const I , mURL(aOther.mURL) , mHeaders(new InternalHeaders(*aOther.mHeaders)) , mContentPolicyType(aOther.mContentPolicyType) , mReferrer(aOther.mReferrer) , mMode(aOther.mMode) , mCredentialsMode(aOther.mCredentialsMode) , mResponseTainting(aOther.mResponseTainting) , mCacheMode(aOther.mCacheMode) + , mRedirectMode(aOther.mRedirectMode) , mAuthenticationFlag(aOther.mAuthenticationFlag) , mForceOriginHeader(aOther.mForceOriginHeader) , mPreserveContentCodings(aOther.mPreserveContentCodings) , mSameOriginDataURL(aOther.mSameOriginDataURL) , mSandboxedStorageAreaURLs(aOther.mSandboxedStorageAreaURLs) , mSkipServiceWorker(aOther.mSkipServiceWorker) , mSynchronous(aOther.mSynchronous) , mUnsafeRequest(aOther.mUnsafeRequest)
--- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -87,26 +87,28 @@ class InternalRequest final public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest) enum ResponseTainting { RESPONSETAINT_BASIC, RESPONSETAINT_CORS, RESPONSETAINT_OPAQUE, + RESPONSETAINT_OPAQUEREDIRECT, }; explicit InternalRequest() : mMethod("GET") , mHeaders(new InternalHeaders(HeadersGuardEnum::None)) , mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR)) , mMode(RequestMode::No_cors) , mCredentialsMode(RequestCredentials::Omit) , mResponseTainting(RESPONSETAINT_BASIC) , mCacheMode(RequestCache::Default) + , mRedirectMode(RequestRedirect::Follow) , mAuthenticationFlag(false) , mForceOriginHeader(false) , mPreserveContentCodings(false) // FIXME(nsm): This should be false by default, but will lead to the // algorithm never loading data: URLs right now. See Bug 1018872 about // how certain contexts will override it to set it to true. Fetch // specification does not handle this yet. , mSameOriginDataURL(true) @@ -259,16 +261,28 @@ public: } void SetCacheMode(RequestCache aCacheMode) { mCacheMode = aCacheMode; } + RequestRedirect + GetRedirectMode() const + { + return mRedirectMode; + } + + void + SetRedirectMode(RequestRedirect aRedirectMode) + { + mRedirectMode = aRedirectMode; + } + nsContentPolicyType ContentPolicyType() const { return mContentPolicyType; } void SetContentPolicyType(nsContentPolicyType aContentPolicyType); @@ -385,16 +399,17 @@ private: // "about:client": client (default) // URL: an URL nsString mReferrer; RequestMode mMode; RequestCredentials mCredentialsMode; ResponseTainting mResponseTainting; RequestCache mCacheMode; + RequestRedirect mRedirectMode; bool mAuthenticationFlag; bool mForceOriginHeader; bool mPreserveContentCodings; bool mSameOriginDataURL; bool mSandboxedStorageAreaURLs; bool mSkipServiceWorker; bool mSynchronous;
--- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -120,26 +120,35 @@ InternalResponse::StripFragmentAndSetUrl already_AddRefed<InternalResponse> InternalResponse::OpaqueResponse() { MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response"); nsRefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString()); response->mType = ResponseType::Opaque; response->mTerminationReason = mTerminationReason; - response->mURL = mURL; response->mChannelInfo = mChannelInfo; if (mPrincipalInfo) { response->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo); } response->mWrappedResponse = this; return response.forget(); } already_AddRefed<InternalResponse> +InternalResponse::OpaqueRedirectResponse() +{ + MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueRedirectResponse a already wrapped response"); + nsRefPtr<InternalResponse> response = OpaqueResponse(); + response->mType = ResponseType::Opaqueredirect; + response->mURL = mURL; + return response.forget(); +} + +already_AddRefed<InternalResponse> InternalResponse::CreateIncompleteCopy() { nsRefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText); copy->mType = mType; copy->mTerminationReason = mTerminationReason; copy->mURL = mURL; copy->mChannelInfo = mChannelInfo; if (mPrincipalInfo) {
--- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -44,29 +44,33 @@ public: response->mType = ResponseType::Error; return response.forget(); } already_AddRefed<InternalResponse> OpaqueResponse(); already_AddRefed<InternalResponse> + OpaqueRedirectResponse(); + + already_AddRefed<InternalResponse> BasicResponse(); already_AddRefed<InternalResponse> CORSResponse(); ResponseType Type() const { MOZ_ASSERT_IF(mType == ResponseType::Error, !mWrappedResponse); MOZ_ASSERT_IF(mType == ResponseType::Default, !mWrappedResponse); MOZ_ASSERT_IF(mType == ResponseType::Basic, mWrappedResponse); MOZ_ASSERT_IF(mType == ResponseType::Cors, mWrappedResponse); MOZ_ASSERT_IF(mType == ResponseType::Opaque, mWrappedResponse); + MOZ_ASSERT_IF(mType == ResponseType::Opaqueredirect, mWrappedResponse); return mType; } bool IsError() const { return Type() == ResponseType::Error; } @@ -86,22 +90,42 @@ public: } uint16_t GetStatus() const { return mStatus; } + uint16_t + GetUnfilteredStatus() const + { + if (mWrappedResponse) { + return mWrappedResponse->GetStatus(); + } + + return GetStatus(); + } + const nsCString& GetStatusText() const { return mStatusText; } + const nsCString& + GetUnfilteredStatusText() const + { + if (mWrappedResponse) { + return mWrappedResponse->GetStatusText(); + } + + return GetStatusText(); + } + InternalHeaders* Headers() { return mHeaders; } InternalHeaders* UnfilteredHeaders() @@ -109,35 +133,36 @@ public: if (mWrappedResponse) { return mWrappedResponse->Headers(); }; return Headers(); } void - GetInternalBody(nsIInputStream** aStream) + GetUnfilteredBody(nsIInputStream** aStream) { if (mWrappedResponse) { MOZ_ASSERT(!mBody); return mWrappedResponse->GetBody(aStream); } nsCOMPtr<nsIInputStream> stream = mBody; stream.forget(aStream); } void GetBody(nsIInputStream** aStream) { - if (Type() == ResponseType::Opaque) { + if (Type() == ResponseType::Opaque || + Type() == ResponseType::Opaqueredirect) { *aStream = nullptr; return; } - return GetInternalBody(aStream); + return GetUnfilteredBody(aStream); } void SetBody(nsIInputStream* aBody) { if (mWrappedResponse) { return mWrappedResponse->SetBody(aBody); }
--- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -286,16 +286,20 @@ Request::Constructor(const GlobalObject& RequestCache cache = aInit.mCache.WasPassed() ? aInit.mCache.Value() : fallbackCache; if (cache != RequestCache::EndGuard_) { request->ClearCreatedByFetchEvent(); request->SetCacheMode(cache); } + if (aInit.mRedirect.WasPassed()) { + request->SetRedirectMode(aInit.mRedirect.Value()); + } + // Request constructor step 14. if (aInit.mMethod.WasPassed()) { nsAutoCString method(aInit.mMethod.Value()); nsAutoCString upperCaseMethod = method; ToUpperCase(upperCaseMethod); // Step 14.1. Disallow forbidden methods, and anything that is not a HTTP // token, since HTTP states that Method may be any of the defined values or
--- a/dom/fetch/Request.h +++ b/dom/fetch/Request.h @@ -71,16 +71,22 @@ public: } RequestCache Cache() const { return mRequest->GetCacheMode(); } + RequestRedirect + Redirect() const + { + return mRequest->GetRedirectMode(); + } + RequestContext Context() const { return mRequest->Context(); } void SetContentPolicyType(nsContentPolicyType aContentPolicyType)
--- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -157,22 +157,25 @@ Response::Constructor(const GlobalObject } nsRefPtr<InternalResponse> internalResponse = new InternalResponse(aInit.mStatus, statusText); // Grab a valid channel info from the global so this response is 'valid' for // interception. if (NS_IsMainThread()) { + ChannelInfo info; nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global); - MOZ_ASSERT(window); - nsIDocument* doc = window->GetExtantDoc(); - MOZ_ASSERT(doc); - ChannelInfo info; - info.InitFromDocument(doc); + if (window) { + nsIDocument* doc = window->GetExtantDoc(); + MOZ_ASSERT(doc); + info.InitFromDocument(doc); + } else { + info.InitFromChromeGlobal(global); + } internalResponse->InitChannelInfo(info); } else { workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); internalResponse->InitChannelInfo(worker->GetChannelInfo()); } nsRefPtr<Response> r = new Response(global, internalResponse);
--- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -111,16 +111,44 @@ static PRLogModuleInfo* gMediaElementEve // This controls the b2g specific of pausing the media element when the // AudioChannel tells us to mute it. #define PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL #endif using namespace mozilla::layers; using mozilla::net::nsMediaFragmentURIParser; +class MOZ_STACK_CLASS AutoNotifyAudioChannelAgent +{ + nsRefPtr<mozilla::dom::HTMLMediaElement> mElement; + bool mShouldNotify; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; +public: + AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement, + bool aNotify + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mElement(aElement) + , mShouldNotify(aNotify) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + if (mShouldNotify) { + mElement->NotifyAudioChannelAgent(false); + } + } + ~AutoNotifyAudioChannelAgent() + { + if (mShouldNotify) { + // The audio channel agent is destroyed at this point. + if (mElement->MaybeCreateAudioChannelAgent()) { + mElement->NotifyAudioChannelAgent(true); + } + } + } +}; + namespace mozilla { namespace dom { // Number of milliseconds between progress events as defined by spec static const uint32_t PROGRESS_MS = 350; // Number of milliseconds of no data before a stall event is fired as defined by spec static const uint32_t STALL_MS = 3000; @@ -3184,16 +3212,24 @@ void HTMLMediaElement::ProcessMediaFragm } } void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo, nsAutoPtr<const MetadataTags> aTags) { MOZ_ASSERT(NS_IsMainThread()); + // If the element is gaining or losing an audio track, we need to notify + // the audio channel agent so that the correct audio-playback events will + // get dispatched. + bool audioTrackChanging = mMediaInfo.HasAudio() != aInfo->HasAudio(); + AutoNotifyAudioChannelAgent autoNotify(this, + audioTrackChanging && + mPlayingThroughTheAudioChannel); + mMediaInfo = *aInfo; mIsEncrypted = aInfo->IsEncrypted() #ifdef MOZ_EME || mPendingEncryptedInitData.IsEncrypted() #endif // MOZ_EME ; mTags = aTags.forget(); mLoadedDataFired = false; @@ -4483,17 +4519,35 @@ nsresult HTMLMediaElement::UpdateChannel } #ifdef PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL SuspendOrResumeElement(ComputedMuted(), false); #endif return NS_OK; } -void HTMLMediaElement::UpdateAudioChannelPlayingState() +bool +HTMLMediaElement::MaybeCreateAudioChannelAgent() +{ + if (!mAudioChannelAgent) { + nsresult rv; + mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + MOZ_ASSERT(mAudioChannelAgent); + mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(), + static_cast<int32_t>(mAudioChannel), + this); + } + return true; +} + +void +HTMLMediaElement::UpdateAudioChannelPlayingState() { bool playingThroughTheAudioChannel = (!mPaused && !Muted() && std::fabs(Volume()) > 1e-7 && (HasAttr(kNameSpaceID_None, nsGkAtoms::loop) || (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA && !IsPlaybackEnded()) || @@ -4501,28 +4555,19 @@ void HTMLMediaElement::UpdateAudioChanne if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) { mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel; // If we are not playing, we don't need to create a new audioChannelAgent. if (!mAudioChannelAgent && !mPlayingThroughTheAudioChannel) { return; } - if (!mAudioChannelAgent) { - nsresult rv; - mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv); - if (!mAudioChannelAgent) { - return; - } - mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(), - static_cast<int32_t>(mAudioChannel), - this); + if (MaybeCreateAudioChannelAgent()) { + NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel); } - - NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel); } } void HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying) { // Immediately check if this should go to the MSG instead of the normal // media playback route.
--- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -30,20 +30,16 @@ #undef CurrentTime #endif #include "mozilla/dom/HTMLMediaElementBinding.h" // Define to output information on decoding and painting framerate /* #define DEBUG_FRAME_RATE 1 */ -class nsIChannel; -class nsIHttpChannel; -class nsILoadGroup; - typedef uint16_t nsMediaNetworkState; typedef uint16_t nsMediaReadyState; namespace mozilla { class ErrorResult; class MediaResource; class MediaDecoder; class VideoFrameContainer; @@ -51,19 +47,23 @@ namespace dom { class MediaKeys; class TextTrack; class TimeRanges; class WakeLock; class MediaTrack; } // namespace dom } // namespace mozilla +class AutoNotifyAudioChannelAgent; +class nsIChannel; +class nsIHttpChannel; +class nsILoadGroup; +class nsIRunnable; class nsITimer; class nsRange; -class nsIRunnable; namespace mozilla { namespace dom { // Number of milliseconds between timeupdate events as defined by spec #define TIMEUPDATE_MS 250 class MediaError; @@ -73,16 +73,18 @@ class AudioTrackList; class VideoTrackList; class HTMLMediaElement : public nsGenericHTMLElement, public nsIDOMHTMLMediaElement, public nsIObserver, public MediaDecoderOwner, public nsIAudioChannelAgentCallback { + friend AutoNotifyAudioChannelAgent; + public: typedef mozilla::TimeStamp TimeStamp; typedef mozilla::layers::ImageContainer ImageContainer; typedef mozilla::VideoFrameContainer VideoFrameContainer; typedef mozilla::MediaStream MediaStream; typedef mozilla::MediaResource MediaResource; typedef mozilla::MediaDecoderOwner MediaDecoderOwner; typedef mozilla::MetadataTags MetadataTags; @@ -1045,16 +1047,20 @@ protected: TextTrackManager* GetOrCreateTextTrackManager(); // Recomputes ready state and fires events as necessary based on current state. void UpdateReadyStateInternal(); // Notifies the audio channel agent when the element starts or stops playing. void NotifyAudioChannelAgent(bool aPlaying); + // Creates the audio channel agent if needed. Returns true if the audio + // channel agent is ready to be used. + bool MaybeCreateAudioChannelAgent(); + class nsAsyncEventRunner; using nsGenericHTMLElement::DispatchEvent; // For nsAsyncEventRunner. nsresult DispatchEvent(const nsAString& aName); // The current decoder. Load() has been called on this decoder. // At most one of mDecoder and mSrcStream can be non-null. nsRefPtr<MediaDecoder> mDecoder;
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -37,16 +37,19 @@ #include "mozilla/dom/DataStoreService.h" #include "mozilla/dom/DataTransfer.h" #include "mozilla/dom/DOMStorageIPC.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/File.h" #include "mozilla/dom/ExternalHelperAppParent.h" #include "mozilla/dom/FileSystemRequestParent.h" #include "mozilla/dom/GeolocationBinding.h" +#ifdef MOZ_EME +#include "mozilla/dom/MediaKeySystemAccess.h" +#endif #include "mozilla/dom/NuwaParent.h" #include "mozilla/dom/PContentBridgeParent.h" #include "mozilla/dom/PContentPermissionRequestParent.h" #include "mozilla/dom/PCycleCollectWithLogsParent.h" #include "mozilla/dom/PFMRadioParent.h" #include "mozilla/dom/PMemoryReportRequestParent.h" #include "mozilla/dom/ServiceWorkerRegistrar.h" #include "mozilla/dom/bluetooth/PBluetoothParent.h" @@ -1094,16 +1097,33 @@ ContentParent::RecvGetGMPPluginVersionFo nsCString* aVersion) { return GMPServiceParent::RecvGetGMPPluginVersionForAPI(aAPI, Move(aTags), aHasVersion, aVersion); } bool +ContentParent::RecvIsGMPPresentOnDisk(const nsString& aKeySystem, + const nsCString& aVersion, + bool* aIsPresent, + nsCString* aMessage) +{ +#ifdef MOZ_EME + *aIsPresent = MediaKeySystemAccess::IsGMPPresentOnDisk(aKeySystem, + aVersion, + *aMessage); +#else + *aIsPresent = false; +#endif + + return true; +} + +bool ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) { *aRv = NS_OK; return mozilla::plugins::SetupBridge(aPluginId, this, false, aRv, aRunID); } bool ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv)
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -171,16 +171,20 @@ public: TabId* aTabId) override; virtual bool RecvBridgeToChildProcess(const ContentParentId& aCpId) override; virtual bool RecvCreateGMPService() override; virtual bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI, nsTArray<nsCString>&& aTags, bool* aHasPlugin, nsCString* aVersion) override; + virtual bool RecvIsGMPPresentOnDisk(const nsString& aKeySystem, + const nsCString& aVersion, + bool* aIsPresent, + nsCString* aMessage) override; virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) override; virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) override; virtual bool RecvGetBlocklistState(const uint32_t& aPluginId, uint32_t* aIsBlocklisted) override; virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch, nsTArray<PluginTag>* aPlugins, uint32_t* aNewPluginEpoch) override;
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -691,16 +691,18 @@ parent: ProcessPriority priority, TabId openerTabId) returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId); sync BridgeToChildProcess(ContentParentId cpId); async CreateGMPService(); sync GetGMPPluginVersionForAPI(nsCString api, nsCString[] tags) returns (bool hasPlugin, nsCString version); + sync IsGMPPresentOnDisk(nsString keySystem, nsCString version) + returns (bool isPresent, nsCString message); /** * This call connects the content process to a plugin process. While this * call runs, a new PluginModuleParent will be created in the ContentChild * via bridging. The corresponding PluginModuleChild will live in the plugin * process. */ sync LoadPlugin(uint32_t aPluginId) returns (nsresult aResult, uint32_t aRunID);
--- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -168,10 +168,12 @@ InterceptionFailed=ServiceWorker network # LOCALIZATION NOTE: Do not translate "ServiceWorker", "FetchEvent.respondWith()", "opaque", or "Response". OpaqueInterceptionDisabled=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while opaque interception is disabled. # LOCALIZATION NOTE: Do not translate "ServiceWorker", "FetchEvent.respondWith()", "FetchEvent.request.type", "same-origin", "cors", "no-cors", "opaque", "Response", or "RequestMode". BadOpaqueInterceptionRequestMode=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while the FetchEvent.request.type was either "same-origin" or "cors". Opaque Response objects are only valid when the RequestMode is "no-cors". # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Error", "Response", "FetchEvent.respondWith()", or "fetch()". InterceptedErrorResponse=A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call. # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", or "Response.clone()". InterceptedUsedResponse=A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times. -# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker". +# LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaque", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker". ClientRequestOpaqueInterception=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while FetchEvent.request was a client request. A client request is generally a browser navigation or top-level Worker script. +# LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaqueredirect", "Response", "FetchEvent.respondWith()", or "FetchEvent.request". +BadOpaqueRedirectInterception=A ServiceWorker passed an opaqueredirect Response to FetchEvent.respondWith() while FetchEvent.request was not a navigation request.
--- a/dom/media/eme/GMPVideoDecoderTrialCreator.cpp +++ b/dom/media/eme/GMPVideoDecoderTrialCreator.cpp @@ -72,34 +72,62 @@ GMPVideoDecoderTrialCreator::GetCreateTr switch (Preferences::GetInt(pref, (int)Pending)) { case 0: return Pending; case 1: return Succeeded; case 2: return Failed; default: return Pending; } } +/* static */ void +GMPVideoDecoderTrialCreator::UpdateTrialCreateState(const nsAString& aKeySystem, + uint32_t aState) +{ + UpdateTrialCreateState(aKeySystem, (TrialCreateState)aState); +} + +/* static */ void +GMPVideoDecoderTrialCreator::UpdateTrialCreateState(const nsAString& aKeySystem, + TrialCreateState aState) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_GetProcessType() == GeckoProcessType_Content) { + // Pref has to be set from the chrome process. Dispatch to chrome via + // GMPService. + nsCOMPtr<mozIGeckoMediaPluginService> service = + do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + NS_ENSURE_TRUE_VOID(service); + + service->UpdateTrialCreateState(aKeySystem, (uint32_t)aState); + return; + } + + const char* pref = TrialCreatePrefName(aKeySystem); + if (pref) { + Preferences::SetInt(pref, (int)aState); + } +} + void GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed(const nsAString& aKeySystem, const nsACString& aReason) { MOZ_ASSERT(NS_IsMainThread()); EME_LOG("GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed(%s)", NS_ConvertUTF16toUTF8(aKeySystem).get()); TrialCreateData* data = mTestCreate.Get(aKeySystem); if (!data) { return; } data->mStatus = Failed; - const char* pref = TrialCreatePrefName(aKeySystem); - if (pref) { - Preferences::SetInt(pref, (int)Failed); - } + UpdateTrialCreateState(aKeySystem, Failed); + for (nsRefPtr<AbstractPromiseLike>& promise: data->mPending) { promise->Reject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, aReason); } data->mPending.Clear(); data->mTest = nullptr; } void @@ -110,20 +138,18 @@ GMPVideoDecoderTrialCreator::TrialCreate EME_LOG("GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderSucceeded(%s)", NS_ConvertUTF16toUTF8(aKeySystem).get()); TrialCreateData* data = mTestCreate.Get(aKeySystem); if (!data) { return; } data->mStatus = Succeeded; - const char* pref = TrialCreatePrefName(aKeySystem); - if (pref) { - Preferences::SetInt(pref, (int)Succeeded); - } + UpdateTrialCreateState(aKeySystem, Succeeded); + for (nsRefPtr<AbstractPromiseLike>& promise : data->mPending) { promise->Resolve(); } data->mPending.Clear(); data->mTest = nullptr; } nsresult @@ -479,36 +505,30 @@ TestGMPVideoDecoder::CreateGMPVideoDecod { MOZ_ASSERT(OnGMPThread()); nsTArray<nsCString> tags; tags.AppendElement(NS_LITERAL_CSTRING("h264")); tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem)); UniquePtr<GetGMPVideoDecoderCallback> callback(new Callback(this)); - nsCString fakeNodeId; - if (NS_FAILED(GenerateRandomName(fakeNodeId, 32)) || - NS_FAILED(mGMPService->GetGMPVideoDecoder(&tags, fakeNodeId, Move(callback)))) { + if (NS_FAILED(mGMPService->GetGMPVideoDecoder(&tags, + NS_LITERAL_CSTRING("fakeNodeId1234567890fakeNodeId12"), + Move(callback)))) { ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder GMPService GetGMPVideoDecoder returned failure")); } } void GMPVideoDecoderTrialCreator::MaybeAwaitTrialCreate(const nsAString& aKeySystem, AbstractPromiseLike* aPromisey, nsPIDOMWindow* aParent) { MOZ_ASSERT(NS_IsMainThread()); - if (XRE_GetProcessType() == GeckoProcessType_Content) { - // Currently broken with e10s... - aPromisey->Resolve(); - return; - } - if (!mTestCreate.Contains(aKeySystem)) { mTestCreate.Put(aKeySystem, new TrialCreateData(aKeySystem)); } TrialCreateData* data = mTestCreate.Get(aKeySystem); MOZ_ASSERT(data); switch (data->mStatus) { case TrialCreateState::Succeeded: {
--- a/dom/media/eme/GMPVideoDecoderTrialCreator.h +++ b/dom/media/eme/GMPVideoDecoderTrialCreator.h @@ -34,16 +34,18 @@ public: MediaKeySystemAccess* aAccess, T* aPromiseLike, nsPIDOMWindow* aParent) { nsRefPtr<PromiseLike<T>> p(new PromiseLike<T>(aPromiseLike, aAccess)); MaybeAwaitTrialCreate(aKeySystem, p, aParent); } + static void UpdateTrialCreateState(const nsAString& aKeySystem, uint32_t aState); + private: class AbstractPromiseLike { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractPromiseLike); virtual void Resolve() = 0; virtual void Reject(nsresult aResult, const nsACString& aMessage) = 0; @@ -83,16 +85,18 @@ private: // Note: Keep this in sync with GetCreateTrialState. enum TrialCreateState { Pending = 0, Succeeded = 1, Failed = 2, }; static TrialCreateState GetCreateTrialState(const nsAString& aKeySystem); + static void UpdateTrialCreateState(const nsAString& aKeySystem, + TrialCreateState aState); struct TrialCreateData { explicit TrialCreateData(const nsAString& aKeySystem) : mKeySystem(aKeySystem) , mStatus(GetCreateTrialState(aKeySystem)) {} ~TrialCreateData() {} const nsString mKeySystem; @@ -167,9 +171,9 @@ private: GMPVideoDecoderProxy* mGMP; GMPVideoHost* mHost; bool mReceivedDecoded; }; } // namespace dom } // namespace mozilla -#endif \ No newline at end of file +#endif
--- a/dom/media/eme/MediaKeySystemAccess.cpp +++ b/dom/media/eme/MediaKeySystemAccess.cpp @@ -1,14 +1,15 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/dom/ContentChild.h" #include "mozilla/dom/MediaKeySystemAccess.h" #include "mozilla/dom/MediaKeySystemAccessBinding.h" #include "mozilla/Preferences.h" #include "nsContentTypeParser.h" #ifdef MOZ_FMP4 #include "MP4Decoder.h" #endif #ifdef XP_WIN @@ -105,20 +106,17 @@ HaveGMPFor(mozIGeckoMediaPluginService* return hasPlugin; } #ifdef XP_WIN static bool AdobePluginFileExists(const nsACString& aVersionStr, const nsAString& aFilename) { - if (XRE_GetProcessType() != GeckoProcessType_Default) { - NS_WARNING("AdobePluginFileExists() lying because it doesn't work with e10s"); - return true; - } + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); nsCOMPtr<nsIFile> path; nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(path)); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = path->Append(NS_LITERAL_STRING("gmp-eme-adobe")); @@ -148,16 +146,71 @@ AdobePluginDLLExists(const nsACString& a static bool AdobePluginVoucherExists(const nsACString& aVersionStr) { return AdobePluginFileExists(aVersionStr, NS_LITERAL_STRING("eme-adobe.voucher")); } #endif +/* static */ bool +MediaKeySystemAccess::IsGMPPresentOnDisk(const nsAString& aKeySystem, + const nsACString& aVersion, + nsACString& aOutMessage) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_GetProcessType() != GeckoProcessType_Default) { + // We need to be able to access the filesystem, so call this in the + // main process via ContentChild. + ContentChild* contentChild = ContentChild::GetSingleton(); + if (NS_WARN_IF(!contentChild)) { + return false; + } + + nsCString message; + bool result = false; + bool ok = contentChild->SendIsGMPPresentOnDisk(nsString(aKeySystem), nsCString(aVersion), + &result, &message); + aOutMessage = message; + return ok && result; + } + + bool isPresent = true; + +#if XP_WIN + if (aKeySystem.EqualsLiteral("com.adobe.primetime")) { + if (!AdobePluginDLLExists(aVersion)) { + NS_WARNING("Adobe EME plugin disappeared from disk!"); + aOutMessage = NS_LITERAL_CSTRING("Adobe DLL was expected to be on disk but was not"); + isPresent = false; + } + if (!AdobePluginVoucherExists(aVersion)) { + NS_WARNING("Adobe EME voucher disappeared from disk!"); + aOutMessage = NS_LITERAL_CSTRING("Adobe plugin voucher was expected to be on disk but was not"); + isPresent = false; + } + + if (!isPresent) { + // Reset the prefs that Firefox's GMP downloader sets, so that + // Firefox will try to download the plugin next time the updater runs. + Preferences::ClearUser("media.gmp-eme-adobe.lastUpdate"); + Preferences::ClearUser("media.gmp-eme-adobe.version"); + } else if (!EMEVoucherFileExists()) { + // Gecko doesn't have a voucher file for the plugin-container. + // Adobe EME isn't going to work, so don't advertise that it will. + aOutMessage = NS_LITERAL_CSTRING("Plugin-container voucher not present"); + isPresent = false; + } + } +#endif + + return isPresent; +} + static MediaKeySystemStatus EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService, const nsAString& aKeySystem, int32_t aMinCdmVersion, nsACString& aOutMessage, nsACString& aOutCdmVersion) { nsTArray<nsCString> tags; @@ -174,39 +227,19 @@ EnsureMinCDMVersion(mozIGeckoMediaPlugin aOutCdmVersion = versionStr; if (!hasPlugin) { aOutMessage = NS_LITERAL_CSTRING("CDM is not installed"); return MediaKeySystemStatus::Cdm_not_installed; } -#ifdef XP_WIN - if (aKeySystem.EqualsLiteral("com.adobe.primetime")) { - // Verify that anti-virus hasn't "helpfully" deleted the Adobe GMP DLL, - // as we suspect may happen (Bug 1160382). - bool somethingMissing = false; - if (!AdobePluginDLLExists(versionStr)) { - aOutMessage = NS_LITERAL_CSTRING("Adobe DLL was expected to be on disk but was not"); - somethingMissing = true; - } - if (!AdobePluginVoucherExists(versionStr)) { - aOutMessage = NS_LITERAL_CSTRING("Adobe plugin voucher was expected to be on disk but was not"); - somethingMissing = true; - } - if (somethingMissing) { - NS_WARNING("Adobe EME plugin or voucher disappeared from disk!"); - // Reset the prefs that Firefox's GMP downloader sets, so that - // Firefox will try to download the plugin next time the updater runs. - Preferences::ClearUser("media.gmp-eme-adobe.lastUpdate"); - Preferences::ClearUser("media.gmp-eme-adobe.version"); - return MediaKeySystemStatus::Cdm_not_installed; - } + if (!MediaKeySystemAccess::IsGMPPresentOnDisk(aKeySystem, versionStr, aOutMessage)) { + return MediaKeySystemStatus::Cdm_not_installed; } -#endif nsresult rv; int32_t version = versionStr.ToInteger(&rv); if (aMinCdmVersion != NO_CDM_VERSION && (NS_FAILED(rv) || version < 0 || aMinCdmVersion > version)) { aOutMessage = NS_LITERAL_CSTRING("Installed CDM version insufficient"); return MediaKeySystemStatus::Cdm_insufficient_version; } @@ -251,22 +284,16 @@ MediaKeySystemAccess::GetKeySystemStatus } #endif #ifdef XP_MACOSX if (!nsCocoaFeatures::OnLionOrLater()) { aOutMessage = NS_LITERAL_CSTRING("Minimum MacOSX version not met for Adobe EME"); return MediaKeySystemStatus::Cdm_not_supported; } #endif - if (!EMEVoucherFileExists()) { - // Gecko doesn't have a voucher file for the plugin-container. - // Adobe EME isn't going to work, so don't advertise that it will. - aOutMessage = NS_LITERAL_CSTRING("Plugin-container voucher not present"); - return MediaKeySystemStatus::Cdm_not_supported; - } return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion); } #endif return MediaKeySystemStatus::Cdm_not_supported; } static bool
--- a/dom/media/eme/MediaKeySystemAccess.h +++ b/dom/media/eme/MediaKeySystemAccess.h @@ -54,16 +54,20 @@ public: static bool IsSupported(const nsAString& aKeySystem, const Sequence<MediaKeySystemOptions>& aOptions); static void NotifyObservers(nsIDOMWindow* aWindow, const nsAString& aKeySystem, MediaKeySystemStatus aStatus); + static bool IsGMPPresentOnDisk(const nsAString& aKeySystem, + const nsACString& aVersion, + nsACString& aOutMessage); + private: nsCOMPtr<nsPIDOMWindow> mParent; const nsString mKeySystem; const nsString mCDMVersion; }; } // namespace dom } // namespace mozilla
--- a/dom/media/eme/moz.build +++ b/dom/media/eme/moz.build @@ -36,9 +36,11 @@ UNIFIED_SOURCES += [ 'MediaKeyMessageEvent.cpp', 'MediaKeys.cpp', 'MediaKeySession.cpp', 'MediaKeyStatusMap.cpp', 'MediaKeySystemAccess.cpp', 'MediaKeySystemAccessManager.cpp', ] +include('/ipc/chromium/chromium-config.mozbuild') + FINAL_LIBRARY = 'xul'
--- a/dom/media/gmp/GMPChild.cpp +++ b/dom/media/gmp/GMPChild.cpp @@ -428,17 +428,17 @@ GMPChild::GetUTF8LibPath(nsACString& aOu libFile->GetPath(path); aOutLibPath = NS_ConvertUTF16toUTF8(path); return true; #endif } bool -GMPChild::RecvStartPlugin() +GMPChild::AnswerStartPlugin() { LOGD("%s", __FUNCTION__); #if defined(XP_WIN) PreLoadLibraries(mPluginPath); #endif if (!PreLoadPluginVoucher()) { NS_WARNING("Plugin voucher failed to load!");
--- a/dom/media/gmp/GMPChild.h +++ b/dom/media/gmp/GMPChild.h @@ -51,17 +51,17 @@ private: friend class GMPContentChild; bool PreLoadPluginVoucher(); void PreLoadSandboxVoucher(); bool GetUTF8LibPath(nsACString& aOutLibPath); virtual bool RecvSetNodeId(const nsCString& aNodeId) override; - virtual bool RecvStartPlugin() override; + virtual bool AnswerStartPlugin() override; virtual PCrashReporterChild* AllocPCrashReporterChild(const NativeThreadId& aThread) override; virtual bool DeallocPCrashReporterChild(PCrashReporterChild*) override; virtual PGMPTimerChild* AllocPGMPTimerChild() override; virtual bool DeallocPGMPTimerChild(PGMPTimerChild* aActor) override; virtual PGMPStorageChild* AllocPGMPStorageChild() override;
--- a/dom/media/gmp/GMPParent.cpp +++ b/dom/media/gmp/GMPParent.cpp @@ -159,27 +159,24 @@ GMPParent::LoadProcess() mProcess = nullptr; return NS_ERROR_FAILURE; } LOGD("%s: Opened channel to new child process", __FUNCTION__); bool ok = SendSetNodeId(mNodeId); if (!ok) { LOGD("%s: Failed to send node id to child process", __FUNCTION__); - mProcess->Delete(); - mProcess = nullptr; return NS_ERROR_FAILURE; } LOGD("%s: Sent node id to child process", __FUNCTION__); - ok = SendStartPlugin(); + // Intr call to block initialization on plugin load. + ok = CallStartPlugin(); if (!ok) { LOGD("%s: Failed to send start to child process", __FUNCTION__); - mProcess->Delete(); - mProcess = nullptr; return NS_ERROR_FAILURE; } LOGD("%s: Sent StartPlugin to child process", __FUNCTION__); } mState = GMPStateLoaded; // Hold a self ref while the child process is alive. This ensures that
--- a/dom/media/gmp/GMPServiceChild.cpp +++ b/dom/media/gmp/GMPServiceChild.cpp @@ -177,16 +177,50 @@ GeckoMediaPluginServiceChild::GetNodeId( { UniquePtr<GetServiceChildCallback> callback( new GetNodeIdDone(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, Move(aCallback))); GetServiceChild(Move(callback)); return NS_OK; } NS_IMETHODIMP +GeckoMediaPluginServiceChild::UpdateTrialCreateState(const nsAString& aKeySystem, + uint32_t aState) +{ + if (NS_GetCurrentThread() != mGMPThread) { + mGMPThread->Dispatch(NS_NewRunnableMethodWithArgs<nsString, uint32_t>( + this, &GeckoMediaPluginServiceChild::UpdateTrialCreateState, + aKeySystem, aState), NS_DISPATCH_NORMAL); + return NS_OK; + } + + class Callback : public GetServiceChildCallback + { + public: + Callback(const nsAString& aKeySystem, uint32_t aState) + : mKeySystem(aKeySystem) + , mState(aState) + { } + + virtual void Done(GMPServiceChild* aService) override + { + aService->SendUpdateGMPTrialCreateState(mKeySystem, mState); + } + + private: + nsString mKeySystem; + uint32_t mState; + }; + + UniquePtr<GetServiceChildCallback> callback(new Callback(aKeySystem, aState)); + GetServiceChild(Move(callback)); + return NS_OK; +} + +NS_IMETHODIMP GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aSomeData) { LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic)); if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) { if (mServiceChild) { mozilla::SyncRunnable::DispatchToThread(mGMPThread,
--- a/dom/media/gmp/GMPServiceChild.h +++ b/dom/media/gmp/GMPServiceChild.h @@ -44,16 +44,18 @@ public: NS_IMETHOD GetPluginVersionForAPI(const nsACString& aAPI, nsTArray<nsCString>* aTags, bool* aHasPlugin, nsACString& aOutVersion) override; NS_IMETHOD GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, bool aInPrivateBrowsingMode, UniquePtr<GetNodeIdCallback>&& aCallback) override; + NS_IMETHOD UpdateTrialCreateState(const nsAString& aKeySystem, + uint32_t aState) override; NS_DECL_NSIOBSERVER void SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild); void RemoveGMPContentParent(GMPContentParent* aGMPContentParent); protected:
--- a/dom/media/gmp/GMPServiceParent.cpp +++ b/dom/media/gmp/GMPServiceParent.cpp @@ -4,16 +4,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GMPServiceParent.h" #include "GMPService.h" #include "prio.h" #include "mozilla/Logging.h" #include "GMPParent.h" #include "GMPVideoDecoderParent.h" +#ifdef MOZ_EME +#include "mozilla/dom/GMPVideoDecoderTrialCreator.h" +#endif #include "nsIObserverService.h" #include "GeckoChildProcessHost.h" #include "mozilla/Preferences.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/SyncRunnable.h" #include "nsXPCOMPrivate.h" #include "mozilla/Services.h" #include "nsNativeCharsetUtils.h" @@ -1197,16 +1200,32 @@ GeckoMediaPluginServiceParent::GetNodeId UniquePtr<GetNodeIdCallback>&& aCallback) { nsCString nodeId; nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, nodeId); aCallback->Done(rv, nodeId); return rv; } +NS_IMETHODIMP +GeckoMediaPluginServiceParent::UpdateTrialCreateState(const nsAString& aKeySystem, + uint32_t aState) +{ +#ifdef MOZ_EME + nsString keySystem(aKeySystem); + NS_DispatchToMainThread(NS_NewRunnableFunction([keySystem, aState] { + mozilla::dom::GMPVideoDecoderTrialCreator::UpdateTrialCreateState(keySystem, aState); + })); + + return NS_OK; +#else + return NS_ERROR_FAILURE; +#endif +} + static bool ExtractHostName(const nsACString& aOrigin, nsACString& aOutData) { nsCString str; str.Assign(aOrigin); int begin = str.Find("://"); // The scheme is missing! if (begin == -1) { @@ -1566,16 +1585,24 @@ GMPServiceParent::RecvGetGMPNodeId(const const bool& aInPrivateBrowsing, nsCString* aID) { nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, *aID); return NS_SUCCEEDED(rv); } +bool +GMPServiceParent::RecvUpdateGMPTrialCreateState(const nsString& aKeySystem, + const uint32_t& aState) +{ + mService->UpdateTrialCreateState(aKeySystem, aState); + return true; +} + /* static */ bool GMPServiceParent::RecvGetGMPPluginVersionForAPI(const nsCString& aAPI, nsTArray<nsCString>&& aTags, bool* aHasPlugin, nsCString* aVersion) { nsRefPtr<GeckoMediaPluginServiceParent> service =
--- a/dom/media/gmp/GMPServiceParent.h +++ b/dom/media/gmp/GMPServiceParent.h @@ -38,16 +38,18 @@ public: NS_IMETHOD GetPluginVersionForAPI(const nsACString& aAPI, nsTArray<nsCString>* aTags, bool* aHasPlugin, nsACString& aOutVersion) override; NS_IMETHOD GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin, bool aInPrivateBrowsingMode, UniquePtr<GetNodeIdCallback>&& aCallback) override; + NS_IMETHOD UpdateTrialCreateState(const nsAString& aKeySystem, + uint32_t aState) override; NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE NS_DECL_NSIOBSERVER void AsyncShutdownNeeded(GMPParent* aParent); void AsyncShutdownComplete(GMPParent* aParent); int32_t AsyncShutdownTimeoutMs(); @@ -214,16 +216,18 @@ public: virtual bool RecvGetGMPNodeId(const nsString& aOrigin, const nsString& aTopLevelOrigin, const bool& aInPrivateBrowsing, nsCString* aID) override; static bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI, nsTArray<nsCString>&& aTags, bool* aHasPlugin, nsCString* aVersion); + virtual bool RecvUpdateGMPTrialCreateState(const nsString& aKeySystem, + const uint32_t& aState) override; virtual void ActorDestroy(ActorDestroyReason aWhy) override; static PGMPServiceParent* Create(Transport* aTransport, ProcessId aOtherPid); private: nsRefPtr<GeckoMediaPluginServiceParent> mService; };
--- a/dom/media/gmp/PGMP.ipdl +++ b/dom/media/gmp/PGMP.ipdl @@ -29,15 +29,15 @@ parent: async PGMPContentChildDestroyed(); async AsyncShutdownComplete(); async AsyncShutdownRequired(); child: async BeginAsyncShutdown(); async CrashPluginNow(); - async StartPlugin(); + intr StartPlugin(); async SetNodeId(nsCString nodeId); async CloseActive(); }; } // namespace gmp } // namespace mozilla
--- a/dom/media/gmp/PGMPService.ipdl +++ b/dom/media/gmp/PGMPService.ipdl @@ -16,12 +16,14 @@ sync protocol PGMPService parent: sync LoadGMP(nsCString nodeId, nsCString api, nsCString[] tags, ProcessId[] alreadyBridgedTo) returns (ProcessId id, nsCString displayName, uint32_t pluginId); sync GetGMPNodeId(nsString origin, nsString topLevelOrigin, bool inPrivateBrowsing) returns (nsCString id); + + async UpdateGMPTrialCreateState(nsString keySystem, uint32_t status); }; } // namespace gmp } // namespace mozilla
--- a/dom/media/gmp/mozIGeckoMediaPluginService.idl +++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl @@ -47,17 +47,17 @@ public: [ptr] native TagArray(nsTArray<nsCString>); native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&); native GetGMPAudioDecoderCallback(mozilla::UniquePtr<GetGMPAudioDecoderCallback>&&); native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&); native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&); native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&); -[scriptable, uuid(787cf744-eea8-48c8-b692-376e0485ef97)] +[scriptable, uuid(661492d6-726b-4ba0-8e6e-14bfaf2b62e4)] interface mozIGeckoMediaPluginService : nsISupports { /** * The GMP thread. Callable from any thread. */ readonly attribute nsIThread thread; @@ -142,9 +142,15 @@ interface mozIGeckoMediaPluginService : /** * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple. */ [noscript] void getNodeId(in AString origin, in AString topLevelOrigin, in bool inPrivateBrowsingMode, in GetNodeIdCallback callback); + + /** + * Stores the result of trying to create a decoder for the given keysystem. + */ + [noscript] + void updateTrialCreateState(in AString keySystem, in uint32_t status); };
--- a/dom/media/mediasource/ContainerParser.cpp +++ b/dom/media/mediasource/ContainerParser.cpp @@ -266,39 +266,50 @@ public: mLastMapping.reset(); return false; } if (mCompleteMediaHeaderRange.IsNull()) { mCompleteMediaHeaderRange = MediaByteRange(mapping[0].mSyncOffset, mapping[0].mEndOffset); } - mLastMapping = Some(mapping[completeIdx]); if (foundNewCluster && mOffset >= mapping[endIdx].mEndOffset) { // We now have all information required to delimit a complete cluster. int64_t endOffset = mapping[endIdx+1].mSyncOffset; if (mapping[endIdx+1].mInitOffset > mapping[endIdx].mInitOffset) { // We have a new init segment before this cluster. endOffset = mapping[endIdx+1].mInitOffset; } mCompleteMediaSegmentRange = MediaByteRange(mapping[endIdx].mSyncOffset, endOffset); } else if (mapping[endIdx].mClusterEndOffset >= 0 && mOffset >= mapping[endIdx].mClusterEndOffset) { mCompleteMediaSegmentRange = MediaByteRange(mapping[endIdx].mSyncOffset, mParser.EndSegmentOffset(mapping[endIdx].mClusterEndOffset)); } - if (!completeIdx) { + Maybe<WebMTimeDataOffset> previousMapping; + if (completeIdx) { + previousMapping = Some(mapping[completeIdx - 1]); + } else { + previousMapping = mLastMapping; + } + + mLastMapping = Some(mapping[completeIdx]); + + if (!previousMapping && completeIdx + 1u >= mapping.Length()) { + // We have no previous nor next block available, + // so we can't estimate this block's duration. return false; } - uint64_t frameDuration = - mapping[completeIdx].mTimecode - mapping[completeIdx - 1].mTimecode; + uint64_t frameDuration = (completeIdx + 1u < mapping.Length()) + ? mapping[completeIdx + 1].mTimecode - mapping[completeIdx].mTimecode + : mapping[completeIdx].mTimecode - previousMapping.ref().mTimecode; aStart = mapping[0].mTimecode / NS_PER_USEC; aEnd = (mapping[completeIdx].mTimecode + frameDuration) / NS_PER_USEC; MSE_DEBUG(WebMContainerParser, "[%lld, %lld] [fso=%lld, leo=%lld, l=%u processedIdx=%u fs=%lld]", aStart, aEnd, mapping[0].mSyncOffset, mapping[completeIdx].mEndOffset, mapping.Length(), completeIdx, mCompleteMediaSegmentRange.mEnd);
--- a/dom/media/test/test_fragment_play.html +++ b/dom/media/test/test_fragment_play.html @@ -6,17 +6,17 @@ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> <script type="text/javascript" src="manifest.js"></script> <script type="text/javascript" src="fragment_play.js"></script> </head> <body> <pre id="test"> <script class="testbody" type="text/javascript"> - +PARALLEL_TESTS = 1; var manager = new MediaTestManager; // Fragment parameters to try. These tests // try playing the video. Tests for other fragment // formats are in test_fragment_noplay.html. var gFragmentParams = [ { fragment: "", start: null, end: null }, { fragment: "#t=,", start: null, end: null }, @@ -73,19 +73,17 @@ function startTest(test, token) { var name = test.name + " fragment test"; var localIs = function(name) { return function(a, b, msg) { is(a, b, name + ": " + msg); }}(name); var localOk = function(name) { return function(a, msg) { ok(a, name + ": " + msg); }}(name); var localFinish = function(v, manager) { return function() { - if (v.parentNode) { - v.parentNode.removeChild(v); - } + removeNodeAndSource(v); manager.finished(v.token); }}(v, manager); window['test_fragment_play'](v, test.start, test.end, localIs, localOk, localFinish); } manager.runTests(createTestArray(), startTest); </script>
--- a/dom/media/webm/WebMDemuxer.cpp +++ b/dom/media/webm/WebMDemuxer.cpp @@ -126,18 +126,16 @@ WebMDemuxer::WebMDemuxer(MediaResource* WebMDemuxer::WebMDemuxer(MediaResource* aResource, bool aIsMediaSource) : mResource(aResource) , mBufferedState(nullptr) , mInitData(nullptr) , mContext(nullptr) , mVideoTrack(0) , mAudioTrack(0) , mSeekPreroll(0) - , mLastAudioFrameTime(0) - , mLastVideoFrameTime(0) , mAudioCodec(-1) , mVideoCodec(-1) , mHasVideo(false) , mHasAudio(false) , mNeedReIndex(true) , mLastWebMBlockOffset(-1) , mIsMediaSource(aIsMediaSource) { @@ -502,37 +500,47 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr return false; } int64_t tstamp = holder->Timestamp(); // The end time of this frame is the start time of the next frame. Fetch // the timestamp of the next packet for this track. If we've reached the // end of the resource, use the file's duration as the end time of this // video frame. - int64_t next_tstamp = 0; + int64_t next_tstamp = INT64_MIN; if (aType == TrackInfo::kAudioTrack) { nsRefPtr<NesteggPacketHolder> next_holder(NextPacket(aType)); if (next_holder) { next_tstamp = next_holder->Timestamp(); PushAudioPacket(next_holder); - } else { + } else if (!mIsMediaSource || + (mIsMediaSource && mLastAudioFrameTime.isSome())) { next_tstamp = tstamp; - next_tstamp += tstamp - mLastAudioFrameTime; + next_tstamp += tstamp - mLastAudioFrameTime.refOr(0); + } else { + PushAudioPacket(holder); } - mLastAudioFrameTime = tstamp; + mLastAudioFrameTime = Some(tstamp); } else if (aType == TrackInfo::kVideoTrack) { nsRefPtr<NesteggPacketHolder> next_holder(NextPacket(aType)); if (next_holder) { next_tstamp = next_holder->Timestamp(); PushVideoPacket(next_holder); - } else { + } else if (!mIsMediaSource || + (mIsMediaSource && mLastVideoFrameTime.isSome())) { next_tstamp = tstamp; - next_tstamp += tstamp - mLastVideoFrameTime; + next_tstamp += tstamp - mLastVideoFrameTime.refOr(0); + } else { + PushVideoPacket(holder); } - mLastVideoFrameTime = tstamp; + mLastVideoFrameTime = Some(tstamp); + } + + if (mIsMediaSource && next_tstamp == INT64_MIN) { + return false; } int64_t discardPadding = 0; (void) nestegg_packet_discard_padding(holder->Packet(), &discardPadding); for (uint32_t i = 0; i < count; ++i) { unsigned char* data; size_t length; @@ -652,17 +660,17 @@ WebMDemuxer::DemuxPacket() } int64_t WebMDemuxer::GetNextKeyframeTime() { EnsureUpToDateIndex(); uint64_t keyframeTime; uint64_t lastFrame = - media::TimeUnit::FromMicroseconds(mLastVideoFrameTime).ToNanoseconds(); + media::TimeUnit::FromMicroseconds(mLastVideoFrameTime.refOr(0)).ToNanoseconds(); if (!mBufferedState->GetNextKeyframeTime(lastFrame, &keyframeTime) || keyframeTime <= lastFrame) { return -1; } return media::TimeUnit::FromNanoseconds(keyframeTime).ToMicroseconds(); } void @@ -720,18 +728,18 @@ WebMDemuxer::SeekInternal(const media::T r = nestegg_offset_seek(mContext, offset); if (r == -1) { WEBM_DEBUG("and nestegg_offset_seek to %" PRIu64 " failed", offset); return NS_ERROR_FAILURE; } WEBM_DEBUG("got offset from buffered state: %" PRIu64 "", offset); } - mLastAudioFrameTime = 0; - mLastVideoFrameTime = 0; + mLastAudioFrameTime.reset(); + mLastVideoFrameTime.reset(); return NS_OK; } media::TimeIntervals WebMDemuxer::GetBuffered() { EnsureUpToDateIndex();
--- a/dom/media/webm/WebMDemuxer.h +++ b/dom/media/webm/WebMDemuxer.h @@ -151,21 +151,20 @@ private: uint32_t mAudioTrack; // Number of microseconds that must be discarded from the start of the Stream. uint64_t mCodecDelay; // Nanoseconds to discard after seeking. uint64_t mSeekPreroll; - int64_t mLastAudioFrameTime; - // Calculate the frame duration from the last decodeable frame using the // previous frame's timestamp. In NS. - int64_t mLastVideoFrameTime; + Maybe<int64_t> mLastAudioFrameTime; + Maybe<int64_t> mLastVideoFrameTime; // Codec ID of audio track int mAudioCodec; // Codec ID of video track int mVideoCodec; // Booleans to indicate if we have audio and/or video data bool mHasVideo;
--- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -404,16 +404,32 @@ public: void WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mNotification->DispatchTrustedEvent(mEventName); } }; +class ReleaseNotificationRunnable final : public NotificationWorkerRunnable +{ + Notification* mNotification; +public: + explicit ReleaseNotificationRunnable(Notification* aNotification) + : NotificationWorkerRunnable(aNotification->mWorkerPrivate) + , mNotification(aNotification) + {} + + void + WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + mNotification->ReleaseObject(); + } +}; + // Create one whenever you require ownership of the notification. Use with // UniquePtr<>. See Notification.h for details. class NotificationRef final { friend class WorkerNotificationObserver; private: Notification* mNotification; bool mInited; @@ -447,28 +463,41 @@ public: Initialized() { return mInited; } ~NotificationRef() { if (Initialized() && mNotification) { - if (mNotification->mWorkerPrivate && NS_IsMainThread()) { - nsRefPtr<ReleaseNotificationControlRunnable> r = - new ReleaseNotificationControlRunnable(mNotification); - AutoSafeJSContext cx; - if (!r->Dispatch(cx)) { - MOZ_CRASH("Will leak worker thread Notification!"); + Notification* notification = mNotification; + mNotification = nullptr; + if (notification->mWorkerPrivate && NS_IsMainThread()) { + // Try to pass ownership back to the worker. If the dispatch succeeds we + // are guaranteed this runnable will run, and that it will run after queued + // event runnables, so event runnables will have a safe pointer to the + // Notification. + // + // If the dispatch fails, the worker isn't running anymore and the event + // runnables have already run or been canceled. We can use a control + // runnable to release the reference. + nsRefPtr<ReleaseNotificationRunnable> r = + new ReleaseNotificationRunnable(notification); + + AutoJSAPI jsapi; + jsapi.Init(); + if (!r->Dispatch(jsapi.cx())) { + nsRefPtr<ReleaseNotificationControlRunnable> r = + new ReleaseNotificationControlRunnable(notification); + MOZ_ALWAYS_TRUE(r->Dispatch(jsapi.cx())); } } else { - mNotification->AssertIsOnTargetThread(); - mNotification->ReleaseObject(); + notification->AssertIsOnTargetThread(); + notification->ReleaseObject(); } - mNotification = nullptr; } } // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of // a rawptr that the NotificationRef can invalidate? Notification* GetNotification() { @@ -968,54 +997,16 @@ public: } protected: virtual ~WorkerNotificationObserver() { AssertIsOnMainThread(); MOZ_ASSERT(mNotificationRef); - Notification* notification = mNotificationRef->GetNotification(); - if (!notification) { - return; - } - - // Try to pass ownership back to the worker. If the dispatch succeeds we - // are guaranteed this runnable will run, and that it will run after queued - // event runnables, so event runnables will have a safe pointer to the - // Notification. - // - // If the dispatch fails, the worker isn't running anymore and the event - // runnables have already run. We can just let the standard NotificationRef - // release routine take over when ReleaseNotificationRunnable gets deleted. - class ReleaseNotificationRunnable final : public NotificationWorkerRunnable - { - UniquePtr<NotificationRef> mNotificationRef; - public: - explicit ReleaseNotificationRunnable(UniquePtr<NotificationRef> aRef) - : NotificationWorkerRunnable(aRef->GetNotification()->mWorkerPrivate) - , mNotificationRef(Move(aRef)) - {} - - void - WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override - { - UniquePtr<NotificationRef> ref; - mozilla::Swap(ref, mNotificationRef); - // Gets released at the end of the function. - } - }; - - nsRefPtr<ReleaseNotificationRunnable> r = - new ReleaseNotificationRunnable(Move(mNotificationRef)); - notification = nullptr; - - AutoJSAPI jsapi; - jsapi.Init(); - r->Dispatch(jsapi.cx()); } }; NS_IMPL_ISUPPORTS_INHERITED0(WorkerNotificationObserver, NotificationObserver) class ServiceWorkerNotificationObserver final : public nsIObserver { public:
--- a/dom/notification/Notification.h +++ b/dom/notification/Notification.h @@ -84,32 +84,22 @@ public: * the underlying Notification is null. * * To unify code-paths, we use the same NotificationRef in the main * thread implementation too. * * Note that the Notification's JS wrapper does it's standard * AddRef()/Release() and is not affected by any of this. * - * There is one case related to the WorkerNotificationObserver having to - * dispatch WorkerRunnables to the worker thread which will use the - * Notification object. We can end up in a situation where an event runnable is - * dispatched to the worker, gets queued in the worker's event queue, but then, - * the worker yields to the main thread. Here the main thread observer is - * destroyed, which frees its NotificationRef. The NotificationRef dispatches - * a ControlRunnable to the worker, which runs before the event runnable, - * leading to the event runnable possibly not having a valid Notification - * reference. - * We solve this problem by having WorkerNotificationObserver's dtor - * dispatching a standard WorkerRunnable to do the release (this guarantees the - * ordering of the release is after the event runnables). All WorkerRunnables - * that get dispatched successfully are guaranteed to run on the worker before - * it shuts down. If that dispatch fails, the standard ControlRunnable based - * shutdown is acceptable since the already dispatched event runnables have - * already run or canceled (the worker is already past Running). + * Since the worker event queue can have runnables that will dispatch events on + * the Notification, the NotificationRef destructor will first try to release + * the Notification by dispatching a normal runnable to the worker so that it is + * queued after any event runnables. If that dispatch fails, it means the worker + * is no longer running and queued WorkerRunnables will be canceled, so we + * dispatch a control runnable instead. * */ class Notification : public DOMEventTargetHelper { friend class CloseNotificationRunnable; friend class NotificationTask; friend class NotificationPermissionRequest; friend class NotificationObserver;
--- a/dom/push/test/mochitest.ini +++ b/dom/push/test/mochitest.ini @@ -14,10 +14,11 @@ skip-if = os == "android" || toolkit == [test_multiple_register.html] skip-if = os == "android" || toolkit == "gonk" [test_multiple_register_during_service_activation.html] skip-if = os == "android" || toolkit == "gonk" [test_unregister.html] skip-if = os == "android" || toolkit == "gonk" [test_multiple_register_different_scope.html] skip-if = os == "android" || toolkit == "gonk" -[test_try_registering_offline_disabled.html] -skip-if = os == "android" || toolkit == "gonk" +# Disabled for too many intermittent failures (bug 1164432) +# [test_try_registering_offline_disabled.html] +# skip-if = os == "android" || toolkit == "gonk"
--- a/dom/tests/mochitest/fetch/test_fetch_cors.js +++ b/dom/tests/mochitest/fetch/test_fetch_cors.js @@ -1120,40 +1120,70 @@ function testRedirects() { headers: { "Content-Type": "text/plain" }, hops: [{ server: "http://mochi.test:8888", }, { server: "http://example.com", allowOrigin: origin, }, ], }, - { pass: 0, + { pass: 1, method: "POST", body: "hi there", headers: { "Content-Type": "text/plain", "my-header": "myValue", }, hops: [{ server: "http://mochi.test:8888", }, { server: "http://example.com", allowOrigin: origin, allowHeaders: "my-header", }, ], }, { pass: 0, + method: "POST", + body: "hi there", + headers: { "Content-Type": "text/plain", + "my-header": "myValue", + }, + hops: [{ server: "http://mochi.test:8888", + }, + { server: "http://test1.example.com", + allowOrigin: origin, + allowHeaders: "my-header", + }, + { server: "http://test2.example.com", + allowOrigin: origin, + allowHeaders: "my-header", + } + ], + }, + { pass: 1, method: "DELETE", hops: [{ server: "http://mochi.test:8888", }, { server: "http://example.com", allowOrigin: origin, }, ], }, { pass: 0, + method: "DELETE", + hops: [{ server: "http://mochi.test:8888", + }, + { server: "http://test1.example.com", + allowOrigin: origin, + }, + { server: "http://test2.example.com", + allowOrigin: origin, + }, + ], + }, + { pass: 0, method: "POST", body: "hi there", headers: { "Content-Type": "text/plain", "my-header": "myValue", }, hops: [{ server: "http://example.com", allowOrigin: origin, },
--- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -18,16 +18,17 @@ interface Request { [SameObject] readonly attribute Headers headers; [Func="mozilla::dom::Request::RequestContextEnabled"] readonly attribute RequestContext context; readonly attribute DOMString referrer; readonly attribute RequestMode mode; readonly attribute RequestCredentials credentials; readonly attribute RequestCache cache; + readonly attribute RequestRedirect redirect; [Throws, NewObject] Request clone(); // Bug 1124638 - Allow chrome callers to set the context. [ChromeOnly] void setContentPolicyType(nsContentPolicyType context); }; @@ -35,16 +36,17 @@ Request implements Body; dictionary RequestInit { ByteString method; HeadersInit headers; BodyInit body; RequestMode mode; RequestCredentials credentials; RequestCache cache; + RequestRedirect redirect; }; // Gecko currently does not ship RequestContext, so please don't use it in IDL // that is exposed to script. enum RequestContext { "audio", "beacon", "cspreport", "download", "embed", "eventsource", "favicon", "fetch", "font", "form", "frame", "hyperlink", "iframe", "image", "imageset", "import", "internal", "location", "manifest", "object", "ping", "plugin", "prefetch", "script", @@ -56,8 +58,9 @@ enum RequestContext { // allows us to use the various conversion conveniences offered by the WebIDL // codegen. The Request constructor has explicit checks to prevent it being // passed as a valid value, while Request.mode never returns it. Since enums // are only exposed as strings to client JS, this has the same effect as not // exposing it at all. enum RequestMode { "same-origin", "no-cors", "cors", "cors-with-forced-preflight" }; enum RequestCredentials { "omit", "same-origin", "include" }; enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" }; +enum RequestRedirect { "follow", "error", "manual" };
--- a/dom/webidl/Response.webidl +++ b/dom/webidl/Response.webidl @@ -29,9 +29,9 @@ Response implements Body; dictionary ResponseInit { unsigned short status = 200; // WebIDL spec doesn't allow default values for ByteString. ByteString statusText; HeadersInit headers; }; -enum ResponseType { "basic", "cors", "default", "error", "opaque" }; +enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" };
--- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -125,17 +125,18 @@ public: // channel info for the worker script. channelInfo = mWorkerChannelInfo; } nsresult rv = mChannel->SetChannelInfo(&channelInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - mChannel->SynthesizeStatus(mInternalResponse->GetStatus(), mInternalResponse->GetStatusText()); + mChannel->SynthesizeStatus(mInternalResponse->GetUnfilteredStatus(), + mInternalResponse->GetUnfilteredStatusText()); nsAutoTArray<InternalHeaders::Entry, 5> entries; mInternalResponse->UnfilteredHeaders()->GetEntries(entries); for (uint32_t i = 0; i < entries.Length(); ++i) { mChannel->SynthesizeHeader(entries[i].mName, entries[i].mValue); } rv = mChannel->FinishSynthesizedResponse(); @@ -143,28 +144,31 @@ public: return rv; } }; class RespondWithHandler final : public PromiseNativeHandler { nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel; nsMainThreadPtrHandle<ServiceWorker> mServiceWorker; - RequestMode mRequestMode; - bool mIsClientRequest; + const RequestMode mRequestMode; + const bool mIsClientRequest; + const bool mIsNavigationRequest; public: NS_DECL_ISUPPORTS RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel, nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker, - RequestMode aRequestMode, bool aIsClientRequest) + RequestMode aRequestMode, bool aIsClientRequest, + bool aIsNavigationRequest) : mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) , mRequestMode(aRequestMode) , mIsClientRequest(aIsClientRequest) + , mIsNavigationRequest(aIsNavigationRequest) { } void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; void CancelRequest(nsresult aStatus); @@ -259,53 +263,62 @@ RespondWithHandler::ResolvedCallback(JSC // Allow opaque response interception to be disabled until we can ensure the // security implications are not a complete disaster. if (response->Type() == ResponseType::Opaque && !worker->OpaqueInterceptionEnabled()) { autoCancel.SetCancelStatus(NS_ERROR_OPAQUE_INTERCEPTION_DISABLED); return; } - // Section 4.2, step 2.2: + // Section "HTTP Fetch", step 2.2: // If one of the following conditions is true, return a network error: // * response's type is "error". // * request's mode is not "no-cors" and response's type is "opaque". // * request is a client request and response's type is neither "basic" // nor "default". + // * request is not a navigation request and response's type is + // "opaqueredirect". if (response->Type() == ResponseType::Error) { autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE); return; } if (response->Type() == ResponseType::Opaque && mRequestMode != RequestMode::No_cors) { autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE); return; } + // TODO: remove this case as its no longer in the spec (bug 1184967) if (mIsClientRequest && response->Type() != ResponseType::Basic && - response->Type() != ResponseType::Default) { + response->Type() != ResponseType::Default && + response->Type() != ResponseType::Opaqueredirect) { autoCancel.SetCancelStatus(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION); return; } + if (!mIsNavigationRequest && response->Type() == ResponseType::Opaqueredirect) { + autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION); + return; + } + if (NS_WARN_IF(response->BodyUsed())) { autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_USED_RESPONSE); return; } nsRefPtr<InternalResponse> ir = response->GetInternalResponse(); if (NS_WARN_IF(!ir)) { return; } nsAutoPtr<RespondWithClosure> closure( new RespondWithClosure(mInterceptedChannel, ir, worker->GetChannelInfo())); nsCOMPtr<nsIInputStream> body; - ir->GetInternalBody(getter_AddRefs(body)); + ir->GetUnfilteredBody(getter_AddRefs(body)); // Errors and redirects may not have a body. if (body) { response->SetBodyUsed(); nsCOMPtr<nsIOutputStream> responseBody; rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody)); if (NS_WARN_IF(NS_FAILED(rv))) { return; @@ -354,17 +367,17 @@ FetchEvent::RespondWith(Promise& aArg, E aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsRefPtr<InternalRequest> ir = mRequest->GetInternalRequest(); mWaitToRespond = true; nsRefPtr<RespondWithHandler> handler = new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode(), - ir->IsClientRequest()); + ir->IsClientRequest(), ir->IsNavigationRequest()); aArg.AppendNativeHandler(handler); } already_AddRefed<ServiceWorkerClient> FetchEvent::GetClient() { if (!mClient) { if (!mClientInfo) {
--- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -89,16 +89,25 @@ static_assert(nsIHttpChannelInternal::CO "RequestMode enumeration value should match Necko CORS mode value."); static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast<uint32_t>(RequestMode::No_cors), "RequestMode enumeration value should match Necko CORS mode value."); static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(RequestMode::Cors), "RequestMode enumeration value should match Necko CORS mode value."); static_assert(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT == static_cast<uint32_t>(RequestMode::Cors_with_forced_preflight), "RequestMode enumeration value should match Necko CORS mode value."); +static_assert(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW == static_cast<uint32_t>(RequestRedirect::Follow), + "RequestRedirect enumeration value should make Necko Redirect mode value."); +static_assert(nsIHttpChannelInternal::REDIRECT_MODE_ERROR == static_cast<uint32_t>(RequestRedirect::Error), + "RequestRedirect enumeration value should make Necko Redirect mode value."); +static_assert(nsIHttpChannelInternal::REDIRECT_MODE_MANUAL == static_cast<uint32_t>(RequestRedirect::Manual), + "RequestRedirect enumeration value should make Necko Redirect mode value."); +static_assert(3 == static_cast<uint32_t>(RequestRedirect::EndGuard_), + "RequestRedirect enumeration value should make Necko Redirect mode value."); + static StaticRefPtr<ServiceWorkerManager> gInstance; // Tracks the "dom.disable_open_click_delay" preference. Modified on main // thread, read on worker threads. This is set once in the ServiceWorkerManager // constructor before any service workers are spawned. // It is updated every time a "notificationclick" event is dispatched. While // this is done without synchronization, at the worst, the thread will just get // an older value within which a popup is allowed to be displayed, which will @@ -3611,33 +3620,37 @@ class FetchEventRunnable : public Worker nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel; nsMainThreadPtrHandle<ServiceWorker> mServiceWorker; nsTArray<nsCString> mHeaderNames; nsTArray<nsCString> mHeaderValues; nsAutoPtr<ServiceWorkerClientInfo> mClientInfo; nsCString mSpec; nsCString mMethod; bool mIsReload; + DebugOnly<bool> mIsHttpChannel; RequestMode mRequestMode; + RequestRedirect mRequestRedirect; RequestCredentials mRequestCredentials; nsContentPolicyType mContentPolicyType; nsCOMPtr<nsIInputStream> mUploadStream; nsCString mReferrer; public: FetchEventRunnable(WorkerPrivate* aWorkerPrivate, nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel, nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker, nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo, bool aIsReload) : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) , mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) , mClientInfo(aClientInfo) , mIsReload(aIsReload) + , mIsHttpChannel(false) , mRequestMode(RequestMode::No_cors) + , mRequestRedirect(RequestRedirect::Follow) // By default we set it to same-origin since normal HTTP fetches always // send credentials to same-origin websites unless explicitly forbidden. , mRequestCredentials(RequestCredentials::Same_origin) , mContentPolicyType(nsIContentPolicy::TYPE_INVALID) , mReferrer(kFETCH_CLIENT_REFERRER_STR) { MOZ_ASSERT(aWorkerPrivate); } @@ -3683,39 +3696,46 @@ public: // channels are intercepted but don't have referrers. if (NS_SUCCEEDED(rv) && referrerURI) { rv = referrerURI->GetSpec(mReferrer); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); if (httpChannel) { + mIsHttpChannel = true; + rv = httpChannel->GetRequestMethod(mMethod); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel); NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE); - uint32_t mode; - internalChannel->GetCorsMode(&mode); - switch (mode) { + uint32_t corsMode; + internalChannel->GetCorsMode(&corsMode); + switch (corsMode) { case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN: mRequestMode = RequestMode::Same_origin; break; case nsIHttpChannelInternal::CORS_MODE_NO_CORS: mRequestMode = RequestMode::No_cors; break; case nsIHttpChannelInternal::CORS_MODE_CORS: case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT: mRequestMode = RequestMode::Cors; break; default: MOZ_CRASH("Unexpected CORS mode"); } + // This is safe due to static_asserts at top of file. + uint32_t redirectMode; + internalChannel->GetRedirectMode(&redirectMode); + mRequestRedirect = static_cast<RequestRedirect>(redirectMode); + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { mRequestCredentials = RequestCredentials::Omit; } else { bool includeCrossOrigin; internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin); if (includeCrossOrigin) { mRequestCredentials = RequestCredentials::Include; } @@ -3798,16 +3818,17 @@ private: } } nsRefPtr<Headers> headers = new Headers(globalObj.GetAsSupports(), internalHeaders); reqInit.mHeaders.Construct(); reqInit.mHeaders.Value().SetAsHeaders() = headers; reqInit.mMode.Construct(mRequestMode); + reqInit.mRedirect.Construct(mRequestRedirect); reqInit.mCredentials.Construct(mRequestCredentials); ErrorResult result; nsRefPtr<Request> request = Request::Constructor(globalObj, requestInfo, reqInit, result); if (NS_WARN_IF(result.Failed())) { result.SuppressException(); return false; } @@ -3816,16 +3837,21 @@ private: MOZ_ASSERT(internalReq); internalReq->SetCreatedByFetchEvent(); internalReq->SetBody(mUploadStream); internalReq->SetReferrer(NS_ConvertUTF8toUTF16(mReferrer)); request->SetContentPolicyType(mContentPolicyType); + // TODO: remove conditional on http here once app protocol support is + // removed from service worker interception + MOZ_ASSERT_IF(mIsHttpChannel && internalReq->IsNavigationRequest(), + request->Redirect() == RequestRedirect::Manual); + RootedDictionary<FetchEventInit> init(aCx); init.mRequest.Construct(); init.mRequest.Value() = request; init.mBubbles = false; init.mCancelable = false; init.mIsReload.Construct(mIsReload); nsRefPtr<FetchEvent> event = FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, result);
--- a/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js +++ b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js @@ -1,15 +1,16 @@ var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/https/"; self.addEventListener("install", function(event) { event.waitUntil( self.caches.open("origin-cache") .then(c => { - return c.add(prefix + 'index-https.sjs'); + return c.add(new Request(prefix + 'index-https.sjs', + { redirect: 'manual' })); }) ); }); self.addEventListener("fetch", function(event) { if (event.request.url.indexOf("index-cached-https.sjs") >= 0) { event.respondWith( self.caches.open("origin-cache")
deleted file mode 100644 --- a/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ +++ /dev/null @@ -1,1 +0,0 @@ -Access-Control-Allow-Origin: https://example.com
--- a/dom/workers/test/serviceworkers/fetch/origin/origin_test.js +++ b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js @@ -1,18 +1,20 @@ var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/"; self.addEventListener("install", function(event) { event.waitUntil( self.caches.open("origin-cache") .then(c => { return Promise.all( [ - c.add(prefix + 'index.sjs'), - c.add(prefix + 'index-to-https.sjs'), + c.add(new Request(prefix + 'index.sjs', + { redirect: 'manual' } )), + c.add(new Request(prefix + 'index-to-https.sjs', + { redirect: 'manual' } )) ] ); }) ); }); self.addEventListener("fetch", function(event) { if (event.request.url.indexOf("index-cached.sjs") >= 0) {
deleted file mode 100644 --- a/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ +++ /dev/null @@ -1,1 +0,0 @@ -Access-Control-Allow-Origin: http://mochi.test:8888
--- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -48,23 +48,21 @@ support-files = fetch/https/https_test.js fetch/https/clonedresponse/index.html fetch/https/clonedresponse/register.html fetch/https/clonedresponse/unregister.html fetch/https/clonedresponse/https_test.js fetch/origin/index.sjs fetch/origin/index-to-https.sjs fetch/origin/realindex.html - fetch/origin/realindex.html^headers^ fetch/origin/register.html fetch/origin/unregister.html fetch/origin/origin_test.js fetch/origin/https/index-https.sjs fetch/origin/https/realindex.html - fetch/origin/https/realindex.html^headers^ fetch/origin/https/register.html fetch/origin/https/unregister.html fetch/origin/https/origin_test.js fetch/requesturl/index.html fetch/requesturl/redirect.sjs fetch/requesturl/redirector.html fetch/requesturl/register.html fetch/requesturl/requesturl_test.js
--- a/dom/xul/nsXULElement.cpp +++ b/dom/xul/nsXULElement.cpp @@ -1263,17 +1263,17 @@ nsresult nsXULElement::PreHandleEvent(EventChainPreVisitor& aVisitor) { aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119 if (IsRootOfNativeAnonymousSubtree() && (IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner)) && (aVisitor.mEvent->mMessage == eMouseClick || aVisitor.mEvent->mMessage == eMouseDoubleClick || aVisitor.mEvent->mMessage == NS_XUL_COMMAND || - aVisitor.mEvent->mMessage == NS_CONTEXTMENU || + aVisitor.mEvent->mMessage == eContextMenu || aVisitor.mEvent->mMessage == NS_DRAGDROP_START || aVisitor.mEvent->mMessage == NS_DRAGDROP_GESTURE)) { // Don't propagate these events from native anonymous scrollbar. aVisitor.mCanHandle = true; aVisitor.mParentTarget = nullptr; return NS_OK; } if (aVisitor.mEvent->mMessage == NS_XUL_COMMAND &&
--- a/editor/libeditor/nsHTMLEditorEventListener.cpp +++ b/editor/libeditor/nsHTMLEditorEventListener.cpp @@ -85,17 +85,17 @@ nsHTMLEditorEventListener::MouseDown(nsI // is fired outside of the active editing host), we need to commit // composition because it will be change the selection to the clicked // point. Then, we won't be able to commit the composition. return nsEditorEventListener::MouseDown(aMouseEvent); } // Detect only "context menu" click // XXX This should be easier to do! - // But eDOMEvents_contextmenu and NS_CONTEXTMENU is not exposed in any event + // But eDOMEvents_contextmenu and eContextMenu is not exposed in any event // interface :-( int16_t buttonNumber; nsresult rv = aMouseEvent->GetButton(&buttonNumber); NS_ENSURE_SUCCESS(rv, rv); bool isContextClick = buttonNumber == 2; int32_t clickCount;
--- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -888,17 +888,17 @@ APZCTreeManager::UpdateWheelTransaction( } case eKeyPress: case eKeyUp: case eKeyDown: case eMouseUp: case eMouseDown: case eMouseDoubleClick: case eMouseClick: - case NS_CONTEXTMENU: + case eContextMenu: case NS_DRAGDROP_DROP: txn->EndTransaction(); return; default: break; } }
--- a/gfx/src/FilterSupport.cpp +++ b/gfx/src/FilterSupport.cpp @@ -742,18 +742,20 @@ FilterNodeFromPrimitiveDescription(const BLEND_MODE_DIFFERENCE, BLEND_MODE_EXCLUSION, BLEND_MODE_HUE, BLEND_MODE_SATURATION, BLEND_MODE_COLOR, BLEND_MODE_LUMINOSITY }; filter->SetAttribute(ATT_BLEND_BLENDMODE, (uint32_t)blendModes[mode]); - filter->SetInput(IN_BLEND_IN, aSources[0]); - filter->SetInput(IN_BLEND_IN2, aSources[1]); + // The correct input order for both software and D2D filters is flipped + // from our source order, so flip here. + filter->SetInput(IN_BLEND_IN, aSources[1]); + filter->SetInput(IN_BLEND_IN2, aSources[0]); } return filter.forget(); } case PrimitiveType::ColorMatrix: { float colorMatrix[20]; uint32_t type = atts.GetUint(eColorMatrixType);
--- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -92,17 +92,16 @@ // ECMAScript specification describes objects as maps from property names to // sets of attributes (like ECMAScript's [[Value]]), in practice many objects // have only a pointer to a shape, shared with other similar objects, and // indexed slots that contain the [[Value]] attributes. As another example, a // string produced by concatenating two other strings may sometimes be // represented by a "rope", a structure that points to the two original // strings. // -// // We intend to use ubi::Node to write tools that report memory usage, so it's // important that ubi::Node accurately portray how much memory nodes consume. // Thus, for example, when data that apparently belongs to multiple nodes is // in fact shared in a common structure, ubi::Node's graph uses a separate // node for that shared structure, and presents edges to it from the data's // apparent owners. For example, ubi::Node exposes SpiderMonkey objects' // shapes and base shapes, and exposes rope string and substring structure, // because these optimizations become visible when a tool reports how much @@ -137,16 +136,35 @@ // save their intermediate state in some rooted structure if they must GC before // they complete. (For algorithms like path-finding and dominator tree // computation, we implement the algorithm avoiding any operation that could // cause a GC --- and use AutoCheckCannotGC to verify this.) // // If this restriction prevents us from implementing interesting tools, we may // teach the GC how to root ubi::Nodes, fix up hash tables that use them as // keys, etc. +// +// +// Hostile Graph Structure +// +// Analyses consuming ubi::Node graphs must be robust when presented with graphs +// that are deliberately constructed to exploit their weaknesses. When operating +// on live graphs, web content has control over the object graph, and less +// direct control over shape and string structure, and analyses should be +// prepared to handle extreme cases gracefully. For example, if an analysis were +// to use the C++ stack in a depth-first traversal, carefully constructed +// content could cause the analysis to overflow the stack. +// +// When ubi::Nodes refer to nodes deserialized from a heap snapshot, analyses +// must be even more careful: since snapshots often come from potentially +// compromised e10s content processes, even properties normally guaranteed by +// the platform (the proper linking of DOM nodes, for example) might be +// corrupted. While it is the deserializer's responsibility to check the basic +// structure of the snapshot file, the analyses should be prepared for ubi::Node +// graphs constructed from snapshots to be even more bizarre. class JSAtom; namespace JS { namespace ubi { class Edge; class EdgeRange;
--- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -248,17 +248,17 @@ function ArrayMap(callbackfn/*, thisArg* ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.map'); if (!IsCallable(callbackfn)) ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn)); /* Step 5. */ var T = arguments.length > 1 ? arguments[1] : void 0; /* Step 6. */ - var A = NewDenseArray(len); + var A = std_Array(len); /* Step 7-8. */ /* Step a (implicit), and d. */ for (var k = 0; k < len; k++) { /* Step b */ if (k in O) { /* Step c.i-iii. */ var mappedValue = callFunction(callbackfn, T, O[k], k, O); @@ -713,19 +713,17 @@ function ArrayIteratorNext() { UnsafeSetReservedSlot(this, ITERATOR_SLOT_NEXT_INDEX, index + 1); if (itemKind === ITEM_KIND_VALUE) { result.value = a[index]; return result; } if (itemKind === ITEM_KIND_KEY_AND_VALUE) { - var pair = NewDenseArray(2); - pair[0] = index; - pair[1] = a[index]; + var pair = [index, a[index]]; result.value = pair; return result; } assert(itemKind === ITEM_KIND_KEY, itemKind); result.value = index; return result; } @@ -802,17 +800,17 @@ function ArrayFrom(items, mapfn=undefine // Steps 8-9. var arrayLike = ToObject(items); // Steps 10-11. var len = ToLength(arrayLike.length); // Steps 12-14. - var A = IsConstructor(C) ? new C(len) : NewDenseArray(len); + var A = IsConstructor(C) ? new C(len) : std_Array(len); // Steps 15-16. for (var k = 0; k < len; k++) { // Steps 16.a-c. var kValue = items[k]; // Steps 16.d-e. var mappedValue = mapping ? callFunction(mapfn, thisArg, kValue, k) : kValue;
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1400,38 +1400,38 @@ Parser<ParseHandler>::newFunction(Handle flags = JSFunction::INTERPRETED_LAMBDA; break; case Arrow: flags = JSFunction::INTERPRETED_LAMBDA_ARROW; allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; case Method: MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator); - flags = (generatorKind == NotGenerator - ? JSFunction::INTERPRETED_METHOD - : JSFunction::INTERPRETED_METHOD_GENERATOR); + if (generatorKind == NotGenerator) + flags = JSFunction::INTERPRETED_METHOD; + else + flags = JSFunction::INTERPRETED_METHOD_GENERATOR; allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; case ClassConstructor: case DerivedClassConstructor: flags = JSFunction::INTERPRETED_CLASS_CONSTRUCTOR; allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; case Getter: flags = JSFunction::INTERPRETED_GETTER; allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; case Setter: flags = JSFunction::INTERPRETED_SETTER; allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; default: - flags = (generatorKind == NotGenerator - ? JSFunction::INTERPRETED_NORMAL - : JSFunction::INTERPRETED_GENERATOR); + flags = JSFunction::INTERPRETED_NORMAL; + break; } fun = NewFunctionWithProto(context, nullptr, 0, flags, nullptr, atom, proto, allocKind, TenuredObject); if (!fun) return nullptr; if (options().selfHostingMode) fun->setIsSelfHostedBuiltin();
deleted file mode 100644 --- a/js/src/jit-test/tests/basic/bug1195298.js +++ /dev/null @@ -1,9 +0,0 @@ -function t() { - var o = {l: 0xfffffffff}; - var l = o.l - 0xffffffffe; - var a = getSelfHostedValue('NewDenseArray'); - var arr = a(l); - assertEq(arr.length, 1); -} -t(); -t();
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1200108.js @@ -0,0 +1,5 @@ +// |jit-test| error: 987 +var obj = {length: -1, 0: 0}; +Array.prototype.map.call(obj, function () { + throw 987; +});
--- a/js/src/jit/mips32/Trampoline-mips32.cpp +++ b/js/src/jit/mips32/Trampoline-mips32.cpp @@ -309,17 +309,17 @@ JitRuntime::generateEnterJIT(JSContext* // Baseline OSR will return here. masm.bind(returnLabel.src()); masm.addCodeLabel(returnLabel); } // Pop arguments off the stack. // s0 <- 8*argc (size of all arguments we pushed on the stack) masm.pop(s0); - masm.rshiftPtr(Imm32(4), s0); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), s0); masm.addPtr(s0, StackPointer); // Store the returned value into the slotVp masm.loadPtr(slotVp, s1); masm.storeValue(JSReturnOperand, Address(s1, 0)); // Restore non-volatile registers and return. GenerateReturn(masm, ShortJump);
--- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -749,17 +749,16 @@ class AutoAssertNoException /* Exposed intrinsics so that Ion may inline them. */ bool intrinsic_ToObject(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_IsObject(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_ToInteger(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_ToString(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_IsCallable(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_ThrowRangeError(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_ThrowTypeError(JSContext* cx, unsigned argc, Value* vp); -bool intrinsic_NewDenseArray(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_IsConstructing(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_DefineDataProperty(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_UnsafeSetReservedSlot(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_UnsafeGetReservedSlot(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_UnsafeGetObjectFromReservedSlot(JSContext* cx, unsigned argc, Value* vp); bool intrinsic_UnsafeGetInt32FromReservedSlot(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -76,24 +76,23 @@ class JSFunction : public js::NativeObje SETTER_KIND = Setter << FUNCTION_KIND_SHIFT, /* Derived Flags values for convenience: */ NATIVE_FUN = 0, NATIVE_CTOR = NATIVE_FUN | CONSTRUCTOR, ASMJS_CTOR = ASMJS_KIND | NATIVE_CTOR, ASMJS_LAMBDA_CTOR = ASMJS_KIND | NATIVE_CTOR | LAMBDA, INTERPRETED_METHOD = INTERPRETED | METHOD_KIND, - INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND, + INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND | CONSTRUCTOR, INTERPRETED_CLASS_CONSTRUCTOR = INTERPRETED | CLASSCONSTRUCTOR_KIND | CONSTRUCTOR, INTERPRETED_GETTER = INTERPRETED | GETTER_KIND, INTERPRETED_SETTER = INTERPRETED | SETTER_KIND, INTERPRETED_LAMBDA = INTERPRETED | LAMBDA | CONSTRUCTOR, INTERPRETED_LAMBDA_ARROW = INTERPRETED | LAMBDA | ARROW_KIND, INTERPRETED_NORMAL = INTERPRETED | CONSTRUCTOR, - INTERPRETED_GENERATOR = INTERPRETED, NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME, STABLE_ACROSS_CLONES = IS_FUN_PROTO | CONSTRUCTOR | EXPR_BODY | HAS_GUESSED_ATOM | LAMBDA | SELF_HOSTED | HAS_REST | FUNCTION_KIND_MASK }; static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS, "jsfriendapi.h's JSFunction::INTERPRETED-alike is wrong");
--- a/js/src/tests/ecma_6/Class/methDefnGen.js +++ b/js/src/tests/ecma_6/Class/methDefnGen.js @@ -62,21 +62,26 @@ next = it.next("hello"); assertEq(next.done, false); assertEq(next.value, 2); next = it.next("world"); assertEq(next.done, true); assertEq(next.value.hello, 2); assertEq(next.value.world, 3); // prototype property -assertEq(b.g.hasOwnProperty("prototype"), false); +assertEq(b.g.hasOwnProperty("prototype"), true); // Strict mode a = {*b(c){"use strict";yield c;}}; assertEq(a.b(1).next().value, 1); a = {*["b"](c){"use strict";return c;}}; assertEq(a.b(1).next().value, 1); -// Generators should not have [[Construct]] +// Constructing a = {*g() { yield 1; }} -assertThrowsInstanceOf(() => { new a.g }, TypeError); +it = new a.g; +next = it.next(); +assertEq(next.done, false); +assertEq(next.value, 1); +next = it.next(); +assertEq(next.done, true); reportCompare(0, 0, "ok");
--- a/js/src/tests/ecma_6/Class/methodsPrototype.js +++ b/js/src/tests/ecma_6/Class/methodsPrototype.js @@ -8,27 +8,33 @@ class TestClass { static staticMethod() { } static get staticGetter() { } static set staticSetter(x) { } static *staticGenerator() { } } var test = new TestClass(); -assertEq(test.constructor.hasOwnProperty('prototype'), true); +var hasPrototype = [ + test.constructor, + test.generator, + TestClass.staticGenerator +] + +for (var fun of hasPrototype) { + assertEq(fun.hasOwnProperty('prototype'), true); +} var hasNoPrototype = [ test.method, - test.generator, - TestClass.staticGenerator, Object.getOwnPropertyDescriptor(test.__proto__, 'getter').get, Object.getOwnPropertyDescriptor(test.__proto__, 'setter').set, TestClass.staticMethod, Object.getOwnPropertyDescriptor(TestClass, 'staticGetter').get, - Object.getOwnPropertyDescriptor(TestClass, 'staticSetter').set + Object.getOwnPropertyDescriptor(TestClass, 'staticSetter').set, ] for (var fun of hasNoPrototype) { assertEq(fun.hasOwnProperty('prototype'), false); } `;
--- a/js/src/tests/ecma_6/Class/newTargetGenerators.js +++ b/js/src/tests/ecma_6/Class/newTargetGenerators.js @@ -5,16 +5,20 @@ function *generatorNewTarget(expected) { yield (() => new.target); } const ITERATIONS = 25; for (let i = 0; i < ITERATIONS; i++) assertEq(generatorNewTarget(undefined).next().value(), undefined); +for (let i = 0; i < ITERATIONS; i++) + assertEq(new generatorNewTarget(generatorNewTarget).next().value(), + generatorNewTarget); + // also check to make sure it's useful in yield inside generators. // Plus, this code is so ugly, how could it not be a test? ;) // Thanks to anba for supplying this ludicrous expression. assertDeepEq([...new function*(i) { yield i; if(i > 0) yield* new new.target(i-1) }(10)], [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]); if (typeof reportCompare === 'function') reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Generators/objects.js +++ b/js/src/tests/ecma_6/Generators/objects.js @@ -10,16 +10,24 @@ function TestGeneratorObject() { function* g() { yield 1; } var iter = g(); assertEq(Object.getPrototypeOf(iter), g.prototype); assertTrue(iter instanceof g); assertEq(String(iter), "[object Generator]"); assertDeepEq(Object.getOwnPropertyNames(iter), []); assertNotEq(g(), iter); + + // g() is the same as new g(). + iter = new g(); + assertEq(Object.getPrototypeOf(iter), g.prototype); + assertTrue(iter instanceof g); + assertEq(String(iter), "[object Generator]"); + assertDeepEq(Object.getOwnPropertyNames(iter), []); + assertNotEq(new g(), iter); } TestGeneratorObject(); // Test the methods of generator objects. function TestGeneratorObjectMethods() { function* g() { yield 1; } var iter = g();
--- a/js/src/tests/ecma_6/Generators/runtime.js +++ b/js/src/tests/ecma_6/Generators/runtime.js @@ -13,22 +13,21 @@ function assertSyntaxError(str) { function f() { } function* g() { yield 1; } var GeneratorFunctionPrototype = Object.getPrototypeOf(g); var GeneratorFunction = GeneratorFunctionPrototype.constructor; var GeneratorObjectPrototype = GeneratorFunctionPrototype.prototype; + // A generator function should have the same set of properties as any -// other function, minus a prototype. +// other function. function TestGeneratorFunctionInstance() { var f_own_property_names = Object.getOwnPropertyNames(f); - f_own_property_names.splice(f_own_property_names.indexOf("prototype"), 1); - var g_own_property_names = Object.getOwnPropertyNames(g); f_own_property_names.sort(); g_own_property_names.sort(); assertDeepEq(f_own_property_names, g_own_property_names); var i; for (i = 0; i < f_own_property_names.length; i++) { @@ -37,16 +36,17 @@ function TestGeneratorFunctionInstance() var g_desc = Object.getOwnPropertyDescriptor(g, prop); assertEq(f_desc.configurable, g_desc.configurable, prop); assertEq(f_desc.writable, g_desc.writable, prop); assertEq(f_desc.enumerable, g_desc.enumerable, prop); } } TestGeneratorFunctionInstance(); + // Generators have an additional object interposed in the chain between // themselves and Function.prototype. function TestGeneratorFunctionPrototype() { // Sanity check. assertEq(Object.getPrototypeOf(f), Function.prototype); assertNotEq(GeneratorFunctionPrototype, Function.prototype); assertEq(Object.getPrototypeOf(GeneratorFunctionPrototype), Function.prototype); @@ -107,15 +107,23 @@ function TestGeneratorFunction() { // as it contains "function*" and "yield 10". assertEq(GeneratorFunction('yield 10').toString(), "function* anonymous() {\nyield 10\n}"); } TestGeneratorFunction(); function TestPerGeneratorPrototype() { - assertEq(g.hasOwnProperty("prototype"), false); + assertNotEq((function*(){}).prototype, (function*(){}).prototype); + assertNotEq((function*(){}).prototype, g.prototype); + assertEq(typeof GeneratorFunctionPrototype, "object"); + assertEq(g.prototype.__proto__.constructor, GeneratorFunctionPrototype, "object"); + assertEq(Object.getPrototypeOf(g.prototype), GeneratorObjectPrototype); + assertFalse(g.prototype instanceof Function); + assertEq(typeof (g.prototype), "object"); + + assertDeepEq(Object.getOwnPropertyNames(g.prototype), []); } TestPerGeneratorPrototype(); if (typeof reportCompare == "function") reportCompare(true, true);
--- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -282,41 +282,16 @@ intrinsic_DecompileArg(JSContext* cx, un return false; RootedAtom atom(cx, Atomize(cx, str, strlen(str))); if (!atom) return false; args.rval().setString(atom); return true; } -/* - * NewDenseArray(length): Allocates and returns a new dense array with - * the given length where all values are initialized to holes. - */ -bool -js::intrinsic_NewDenseArray(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - double lengthDouble = args[0].toNumber(); - MOZ_ASSERT(lengthDouble >= 0); - MOZ_ASSERT(lengthDouble < INT32_MAX); - MOZ_ASSERT(uint32_t(lengthDouble) == lengthDouble); - - uint32_t length = uint32_t(lengthDouble); - - // Make a new buffer and initialize it up to length. - RootedObject buffer(cx, NewFullyAllocatedArrayForCallingAllocationSite(cx, length)); - if (!buffer) - return false; - - args.rval().setObject(*buffer); - return true; -} - bool js::intrinsic_DefineDataProperty(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() >= 3); MOZ_ASSERT(args[0].isObject()); @@ -1265,16 +1240,17 @@ intrinsic_ConstructorForTypedArray(JSCon // content script might have changed the builtins' prototypes' members. // Installing the whole set of builtins in the self-hosting compartment, OTOH, // would be wasteful: it increases memory usage and initialization time for // self-hosting compartment. // // Additionally, a set of C++-implemented helper functions is defined on the // self-hosting global. static const JSFunctionSpec intrinsic_functions[] = { + JS_FN("std_Array", ArrayConstructor, 1,0), JS_FN("std_Array_join", array_join, 1,0), JS_FN("std_Array_push", array_push, 1,0), JS_FN("std_Array_pop", array_pop, 0,0), JS_FN("std_Array_shift", array_shift, 0,0), JS_FN("std_Array_unshift", array_unshift, 1,0), JS_FN("std_Array_slice", array_slice, 2,0), JS_FN("std_Array_sort", array_sort, 1,0), @@ -1412,18 +1388,16 @@ static const JSFunctionSpec intrinsic_fu JS_FN("CallLegacyGeneratorMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<LegacyGeneratorObject>>, 2, 0), JS_FN("CallStarGeneratorMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<StarGeneratorObject>>, 2, 0), JS_FN("IsWeakSet", intrinsic_IsWeakSet, 1,0), - JS_FN("NewDenseArray", intrinsic_NewDenseArray, 1,0), - // See builtin/TypedObject.h for descriptors of the typedobj functions. JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0), JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0), JS_FN("TypedObjectBuffer", TypedObject::GetBuffer, 1, 0), JS_FN("TypedObjectByteOffset", TypedObject::GetByteOffset, 1, 0), JS_FN("AttachTypedObject", js::AttachTypedObject, 3, 0), JS_FN("SetTypedObjectOffset", js::SetTypedObjectOffset, 2, 0), JS_FN("ObjectIsTypeDescr" , js::ObjectIsTypeDescr, 1, 0),
--- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -24,16 +24,17 @@ #include "nsXMLHttpRequest.h" #include "WrapperFactory.h" #include "xpcprivate.h" #include "XPCWrapper.h" #include "XrayWrapper.h" #include "Crypto.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/CSSBinding.h" #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" #include "mozilla/dom/Fetch.h" #include "mozilla/dom/FileBinding.h" #include "mozilla/dom/PromiseBinding.h" #include "mozilla/dom/RequestBinding.h" #include "mozilla/dom/ResponseBinding.h" #ifdef MOZ_WEBRTC @@ -902,16 +903,18 @@ xpc::GlobalProperties::Parse(JSContext* } else if (!strcmp(name.ptr(), "crypto")) { crypto = true; #ifdef MOZ_WEBRTC } else if (!strcmp(name.ptr(), "rtcIdentityProvider")) { rtcIdentityProvider = true; #endif } else if (!strcmp(name.ptr(), "fetch")) { fetch = true; + } else if (!strcmp(name.ptr(), "caches")) { + caches = true; } else { JS_ReportError(cx, "Unknown property name: %s", name.ptr()); return false; } } return true; } @@ -967,16 +970,19 @@ xpc::GlobalProperties::Define(JSContext* #ifdef MOZ_WEBRTC if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj)) return false; #endif if (fetch && !SandboxCreateFetch(cx, obj)) return false; + if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) + return false; + return true; } nsresult xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prinOrSop, SandboxOptions& options) { // Create the sandbox global object
--- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -3413,16 +3413,17 @@ struct GlobalProperties { bool URLSearchParams : 1; bool atob : 1; bool btoa : 1; bool Blob : 1; bool File : 1; bool crypto : 1; bool rtcIdentityProvider : 1; bool fetch : 1; + bool caches : 1; }; // Infallible. already_AddRefed<nsIXPCComponents_utils_Sandbox> NewSandboxConstructor(); // Returns true if class of 'obj' is SandboxClass. bool
--- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -8572,8 +8572,31 @@ nsLayoutUtils::ShouldUseNoFramesSheet(ns { bool allowSubframes = true; nsIDocShell* docShell = aDocument->GetDocShell(); if (docShell) { docShell->GetAllowSubframes(&allowSubframes); } return !allowSubframes; } + +/* static */ void +nsLayoutUtils::GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult) +{ + aResult.Truncate(); + AppendFrameTextContent(aFrame, aResult); +} + +/* static */ void +nsLayoutUtils::AppendFrameTextContent(nsIFrame* aFrame, nsAString& aResult) +{ + if (aFrame->GetType() == nsGkAtoms::textFrame) { + auto textFrame = static_cast<nsTextFrame*>(aFrame); + auto offset = textFrame->GetContentOffset(); + auto length = textFrame->GetContentLength(); + textFrame->GetContent()-> + GetText()->AppendTo(aResult, offset, length); + } else { + for (nsIFrame* child : aFrame->PrincipalChildList()) { + AppendFrameTextContent(child, aResult); + } + } +}
--- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -2714,16 +2714,36 @@ public: * Looks in the layer subtree rooted at aLayer for a metrics with scroll id * aScrollId. Returns true if such is found. */ static bool ContainsMetricsWithId(const Layer* aLayer, const ViewID& aScrollId); static bool ShouldUseNoScriptSheet(nsIDocument* aDocument); static bool ShouldUseNoFramesSheet(nsIDocument* aDocument); + /** + * Get the text content inside the frame. This methods traverse the + * frame tree and collect the content from text frames. Note that this + * method is similiar to nsContentUtils::GetNodeTextContent, but it at + * least differs from that method in the following things: + * 1. it skips text content inside nodes like style, script, textarea + * which don't generate an in-tree text frame for the text; + * 2. it skips elements with display property set to none; + * 3. it skips out-of-flow elements; + * 4. it includes content inside pseudo elements; + * 5. it may include part of text content of a node if a text frame + * inside is split to different continuations. + */ + static void GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult); + + /** + * Same as GetFrameTextContent but appends the result rather than sets it. + */ + static void AppendFrameTextContent(nsIFrame* aFrame, nsAString& aResult); + private: static uint32_t sFontSizeInflationEmPerLine; static uint32_t sFontSizeInflationMinTwips; static uint32_t sFontSizeInflationLineThreshold; static int32_t sFontSizeInflationMappingIntercept; static uint32_t sFontSizeInflationMaxRatio; static bool sFontSizeInflationForceEnabled; static bool sFontSizeInflationDisabledInMasterProcess;
--- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -6346,24 +6346,24 @@ PresShell::UpdateActivePointerState(Widg switch (aEvent->mMessage) { case eMouseEnterIntoWidget: // In this case we have to know information about available mouse pointers if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) { gActivePointersIds->Put(mouseEvent->pointerId, new PointerInfo(false, mouseEvent->inputSource, true)); } break; - case NS_POINTER_DOWN: + case ePointerDown: // In this case we switch pointer to active state if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) { gActivePointersIds->Put(pointerEvent->pointerId, new PointerInfo(true, pointerEvent->inputSource, pointerEvent->isPrimary)); } break; - case NS_POINTER_UP: + case ePointerUp: // In this case we remove information about pointer or turn off active state if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) { if(pointerEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { gActivePointersIds->Put(pointerEvent->pointerId, new PointerInfo(false, pointerEvent->inputSource, pointerEvent->isPrimary)); } else { gActivePointersIds->Remove(pointerEvent->pointerId); } @@ -6667,23 +6667,23 @@ DispatchPointerFromMouseOrTouch(PresShel return NS_OK; } int16_t button = mouseEvent->button; switch (mouseEvent->mMessage) { case eMouseMove: if (mouseEvent->buttons == 0) { button = -1; } - pointerMessage = NS_POINTER_MOVE; + pointerMessage = ePointerMove; break; case eMouseUp: - pointerMessage = NS_POINTER_UP; + pointerMessage = ePointerUp; break; case eMouseDown: - pointerMessage = NS_POINTER_DOWN; + pointerMessage = ePointerDown; break; default: return NS_OK; } WidgetPointerEvent event(*mouseEvent); event.mMessage = pointerMessage; event.button = button; @@ -6693,26 +6693,26 @@ DispatchPointerFromMouseOrTouch(PresShel event.convertToPointer = mouseEvent->convertToPointer = false; aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus, aTargetContent); } else if (aEvent->mClass == eTouchEventClass) { WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); // loop over all touches and dispatch pointer events on each touch // copy the event switch (touchEvent->mMessage) { case NS_TOUCH_MOVE: - pointerMessage = NS_POINTER_MOVE; + pointerMessage = ePointerMove; break; case NS_TOUCH_END: - pointerMessage = NS_POINTER_UP; + pointerMessage = ePointerUp; break; case NS_TOUCH_START: - pointerMessage = NS_POINTER_DOWN; + pointerMessage = ePointerDown; break; case NS_TOUCH_CANCEL: - pointerMessage = NS_POINTER_CANCEL; + pointerMessage = ePointerCancel; break; default: return NS_OK; } for (uint32_t i = 0; i < touchEvent->touches.Length(); ++i) { mozilla::dom::Touch* touch = touchEvent->touches[i]; if (!touch || !touch->convertToPointer) { @@ -7445,35 +7445,36 @@ PresShell::HandleEvent(nsIFrame* aFrame, // Prevent application crashes, in case damaged frame. if (!frameKeeper.IsAlive()) { frame = nullptr; } } } if (aEvent->mClass == ePointerEventClass && - aEvent->mMessage != NS_POINTER_DOWN) { + aEvent->mMessage != ePointerDown) { if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) { uint32_t pointerId = pointerEvent->pointerId; nsIContent* pointerCapturingContent = GetPointerCapturingContent(pointerId); if (pointerCapturingContent) { if (nsIFrame* capturingFrame = pointerCapturingContent->GetPrimaryFrame()) { // If pointer capture is set, we should suppress pointerover/pointerenter events // for all elements except element which have pointer capture. (Code in EventStateManager) pointerEvent->retargetedByPointerCapture = frame && frame->GetContent() && !nsContentUtils::ContentIsDescendantOf(frame->GetContent(), pointerCapturingContent); frame = capturingFrame; } - if (pointerEvent->mMessage == NS_POINTER_UP || - pointerEvent->mMessage == NS_POINTER_CANCEL) { + if (pointerEvent->mMessage == ePointerUp || + pointerEvent->mMessage == ePointerCancel) { // Implicitly releasing capture for given pointer. - // LOST_POINTER_CAPTURE should be send after NS_POINTER_UP or NS_POINTER_CANCEL. + // ePointerLostCapture should be send after ePointerUp or + // ePointerCancel. releasePointerCaptureCaller.SetTarget(pointerId, pointerCapturingContent); } } } } // Suppress mouse event if it's being targeted at an element inside // a document which needs events suppressed @@ -7908,17 +7909,17 @@ PresShell::HandleEventInternal(WidgetEve if (!mTouchManager.PreHandleEvent(aEvent, aStatus, touchIsNew, isHandlingUserInput, mCurrentEventContent)) { return NS_OK; } } - if (aEvent->mMessage == NS_CONTEXTMENU) { + if (aEvent->mMessage == eContextMenu) { WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); if (mouseEvent->context == WidgetMouseEvent::eContextMenuKey && !AdjustContextMenuKeyEvent(mouseEvent)) { return NS_OK; } if (mouseEvent->IsShift()) { aEvent->mFlags.mOnlyChromeDispatch = true; aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
--- a/layout/generic/nsRubyBaseContainerFrame.cpp +++ b/layout/generic/nsRubyBaseContainerFrame.cpp @@ -8,17 +8,17 @@ #include "nsRubyBaseContainerFrame.h" #include "nsRubyTextContainerFrame.h" #include "nsRubyBaseFrame.h" #include "nsRubyTextFrame.h" #include "mozilla/DebugOnly.h" #include "mozilla/Maybe.h" #include "mozilla/WritingModes.h" -#include "nsContentUtils.h" +#include "nsLayoutUtils.h" #include "nsLineLayout.h" #include "nsPresContext.h" #include "nsStyleContext.h" #include "nsStyleStructInlines.h" #include "nsTextFrame.h" #include "RubyUtils.h" using namespace mozilla; @@ -604,27 +604,25 @@ nsRubyBaseContainerFrame::ReflowOneColum const uint32_t rtcCount = aReflowState.mTextContainers.Length(); MOZ_ASSERT(aColumn.mTextFrames.Length() == rtcCount); MOZ_ASSERT(textReflowStates.Length() == rtcCount); nscoord columnISize = 0; nsAutoString baseText; if (aColumn.mBaseFrame) { - nsContentUtils::GetNodeTextContent(aColumn.mBaseFrame->GetContent(), - true, baseText); + nsLayoutUtils::GetFrameTextContent(aColumn.mBaseFrame, baseText); } // Reflow text frames for (uint32_t i = 0; i < rtcCount; i++) { nsRubyTextFrame* textFrame = aColumn.mTextFrames[i]; if (textFrame) { nsAutoString annotationText; - nsContentUtils::GetNodeTextContent(textFrame->GetContent(), - true, annotationText); + nsLayoutUtils::GetFrameTextContent(textFrame, annotationText); // Per CSS Ruby spec, the content comparison for auto-hiding // takes place prior to white spaces collapsing (white-space) // and text transformation (text-transform), and ignores elements // (considers only the textContent of the boxes). Which means // using the content tree text comparison is correct. if (annotationText.Equals(baseText)) { textFrame->AddStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE);
--- a/layout/reftests/css-mediaqueries/reftest.list +++ b/layout/reftests/css-mediaqueries/reftest.list @@ -1,18 +1,18 @@ fuzzy-if(Android,8,454) skip-if(B2G||Mulet) == mq_print_height.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop, bug 1178697 skip-if(B2G||Mulet) == mq_print_deviceheight.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_width.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_minwidth.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_minheight.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_aspectratio.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_deviceaspectratio.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_devicewidth.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop -skip-if(B2G||Mulet) == mq_print_orientation.xhtml mq_print_orientation-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop -skip-if(B2G||Mulet) == mq_print_maxheight.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop +fuzzy-if(Android,8,454) skip-if(B2G||Mulet) == mq_print_orientation.xhtml mq_print_orientation-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop +fuzzy-if(Android,8,454) skip-if(B2G||Mulet) == mq_print_maxheight.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_maxwidth.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_maxwidth_updown.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_maxheight_updown.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_minheight_updown.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == mq_print_minwidth_updown.xhtml mq_print-ref.xhtml # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop == scoped-mq-update.html scoped-mq-update-ref.html
--- a/layout/reftests/svg/filters/feBlend-1-ref.svg +++ b/layout/reftests/svg/filters/feBlend-1-ref.svg @@ -1,19 +1,20 @@ <svg xmlns="http://www.w3.org/2000/svg"> -<rect x="0" y="0" width="50" height="50" fill="#ACCC10"/> -<rect x="50" y="0" width="50" height="50" fill="#B4B43F"/> +<rect x="0" y="0" width="50" height="50" fill="#DFB53F"/> +<rect x="50" y="0" width="50" height="50" fill="#B5B53F"/> <rect x="100" y="0" width="50" height="50" fill="#DFDF3F"/> -<rect x="150" y="0" width="50" height="50" fill="#B4B43F"/> +<rect x="150" y="0" width="50" height="50" fill="#B5B53F"/> <rect x="200" y="0" width="50" height="50" fill="#DFDF3F"/> -<rect x="250" y="0" width="50" height="50" fill="#DFB43F"/> -<rect x="300" y="0" width="50" height="50" fill="#DFB43F"/> -<rect x="350" y="0" width="50" height="50" fill="#DFB43F"/> -<rect x="0" y="50" width="50" height="50" fill="#E0B440"/> -<rect x="50" y="50" width="50" height="50" fill="#DFB43F"/> +<rect x="250" y="0" width="50" height="50" fill="#B5DF3F"/> +<rect x="300" y="0" width="50" height="50" fill="#B5DF3F"/> +<rect x="350" y="0" width="50" height="50" fill="#B5DF3F"/> +<rect x="0" y="50" width="50" height="50" fill="#DFB53F"/> +<rect x="50" y="50" width="50" height="50" fill="#B5DF3F"/> <rect x="100" y="50" width="50" height="50" fill="#DFDF3F"/> <rect x="150" y="50" width="50" height="50" fill="#DFDF3F"/> -<rect x="200" y="50" width="50" height="50" fill="#B4CC3F"/> -<rect x="250" y="50" width="50" height="50" fill="#DFB43F"/> -<rect x="300" y="50" width="50" height="50" fill="#B4CC3F"/> -<rect x="350" y="50" width="50" height="50" fill="#DFC88D"/> +<rect x="200" y="50" width="50" height="50" fill="#DFC88E"/> +<rect x="250" y="50" width="50" height="50" fill="#B5DF3F"/> +<rect x="300" y="50" width="50" height="50" fill="#DFC88E"/> +<rect x="350" y="50" width="50" height="50" fill="#B5CC3F"/> +<rect x="0" y="100" width="50" height="50" fill="#DFB53F"/> </svg>
--- a/layout/reftests/svg/filters/feBlend-1.svg +++ b/layout/reftests/svg/filters/feBlend-1.svg @@ -47,17 +47,17 @@ <feBlend in="a" in2="b" mode="color-burn"/> </filter> <rect x="350" y="0" width="50" height="50" filter="url(#f7)"/> <filter id="f8" x="0%" y="0%" width="100%" height="100%"> <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/> <feFlood result="b" flood-color="rgb(0,255,0)" flood-opacity="0.5"/> <feBlend in="a" in2="b" mode="hard-light"/> </filter> -<rect x="0" y="0" width="50" height="50" filter="url(#f8)"/> +<rect x="0" y="50" width="50" height="50" filter="url(#f8)"/> <filter id="f9" x="0%" y="0%" width="100%" height="100%"> <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/> <feFlood result="b" flood-color="rgb(0,255,0)" flood-opacity="0.5"/> <feBlend in="a" in2="b" mode="soft-light"/> </filter> <rect x="50" y="50" width="50" height="50" filter="url(#f9)"/> <filter id="f10" x="0%" y="0%" width="100%" height="100%"> <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/> @@ -90,15 +90,15 @@ </filter> <rect x="300" y="50" width="50" height="50" filter="url(#f14)"/> <filter id="f15" x="0%" y="0%" width="100%" height="100%"> <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/> <feFlood result="b" flood-color="rgb(0,255,0)" flood-opacity="0.5"/> <feBlend in="a" in2="b" mode="luminosity"/> </filter> <rect x="350" y="50" width="50" height="50" filter="url(#f15)"/> - <filter id="f16" x="0%" y="0%" width="100%" height="100%"> +<filter id="f16" x="0%" y="0%" width="100%" height="100%"> <feFlood result="a" flood-color="rgb(255,0,0)" flood-opacity="0.5"/> <feFlood result="b" flood-color="rgb(0,255,0)" flood-opacity="0.5"/> <feBlend in="a" in2="b" mode="undefined"/> </filter> -<rect x="0" y="50" width="50" height="50" filter="url(#f16)"/> -</svg> \ No newline at end of file +<rect x="0" y="100" width="50" height="50" filter="url(#f16)"/> +</svg>
--- a/layout/reftests/svg/filters/feBlend-2-ref.svg +++ b/layout/reftests/svg/filters/feBlend-2-ref.svg @@ -1,5 +1,6 @@ <svg xmlns="http://www.w3.org/2000/svg"> <rect x="0" y="0" width="100" height="100" fill="#00ff00"/> +<rect x="150" y="0" width="100" height="100" fill="#00ff00"/> </svg>
--- a/layout/reftests/svg/filters/feBlend-2.svg +++ b/layout/reftests/svg/filters/feBlend-2.svg @@ -1,9 +1,17 @@ <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"> <filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse"> <feFlood flood-color="#ff0000" result="flood" x="0" y="0" width="100" height="100"/> <feBlend mode="normal" in="SourceGraphic" in2="flood"/> </filter> <rect x="0" y="0" width="100" height="100" fill="#00ff00" filter="url(#f1)"/> +<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=1181317 --> +<filter id="f2" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse"> + <feFlood flood-color="#ff0000" result="red" x="150" y="0" + width="100" height="100"/> + <feBlend mode="hard-light" in="SourceGraphic" in2="red"/> +</filter> +<rect x="150" y="0" width="100" height="100" fill="#00ff00" filter="url(#f2)"/> + </svg>
--- a/layout/reftests/svg/filters/reftest.list +++ b/layout/reftests/svg/filters/reftest.list @@ -13,17 +13,17 @@ include css-svg-filter-chains/reftest.li # SVG filter chain tests include svg-filter-chains/reftest.list == dynamic-filtered-foreignObject-01.svg pass.svg == dynamic-filter-invalidation-01.svg pass.svg == dynamic-filter-invalidation-02.svg pass.svg -fuzzy(1,40000) == feBlend-1.svg feBlend-1-ref.svg +fuzzy(1,42500) == feBlend-1.svg feBlend-1-ref.svg == feBlend-2.svg feBlend-2-ref.svg fuzzy-if(d2d,1,6400) == feColorMatrix-1.svg feColorMatrix-1-ref.svg fuzzy-if(d2d,1,10000) == feColorMatrix-2.svg feColorMatrix-2-ref.svg == feComponentTransfer-1.svg feComponentTransfer-1-ref.svg == feComponentTransfer-2.svg feComponentTransfer-2-ref.svg
--- a/layout/reftests/w3c-css/submitted/ruby/reftest.list +++ b/layout/reftests/w3c-css/submitted/ruby/reftest.list @@ -4,9 +4,10 @@ == ruby-inlinize-blocks-003.html ruby-inlinize-blocks-003-ref.html == ruby-inlinize-blocks-004.html ruby-inlinize-blocks-004-ref.html == ruby-inlinize-blocks-005.html ruby-inlinize-blocks-005-ref.html # Tests for autohiding base-identical annotations == ruby-autohide-001.html ruby-autohide-001-ref.html == ruby-autohide-002.html ruby-autohide-002-ref.html == ruby-autohide-003.html ruby-autohide-003-ref.html +== ruby-autohide-004.html ruby-autohide-001-ref.html
new file mode 100644 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/ruby/ruby-autohide-004.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="ja"> +<head> + <meta charset="UTF-8"> + <title>CSS Test: Autohide ruby annotations which are identical to their bases</title> + <link rel="author" title="Xidorn Quan" href="mailto:quanxunzhen@gmail.com"> + <link rel="help" href="http://www.w3.org/TR/css-ruby-1/#autohide"> + <link rel="match" href="ruby-autohide-001-ref.html"> +</head> +<body> + <ruby> + 振<rt>ふ</rt>り<rt>り</rt>仮<rt>が</rt>名<rt>な</rt> + </ruby> +</body> +</html>
--- a/layout/svg/nsSVGOuterSVGFrame.cpp +++ b/layout/svg/nsSVGOuterSVGFrame.cpp @@ -163,25 +163,43 @@ nsSVGOuterSVGFrame::GetMinISize(nsRender /* virtual */ nscoord nsSVGOuterSVGFrame::GetPrefISize(nsRenderingContext *aRenderingContext) { nscoord result; DISPLAY_PREF_WIDTH(this, result); SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent); - nsSVGLength2 &width = svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + WritingMode wm = GetWritingMode(); + const nsSVGLength2& isize = wm.IsVertical() + ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT] + : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH]; + + if (isize.IsPercentage()) { + // It looks like our containing block's isize may depend on our isize. In + // that case our behavior is undefined according to CSS 2.1 section 10.3.2. + // As a last resort, we'll fall back to returning zero. + result = nscoord(0); - if (width.IsPercentage()) { - // It looks like our containing block's width may depend on our width. In - // that case our behavior is undefined according to CSS 2.1 section 10.3.2, - // so return zero. - result = nscoord(0); + // Returning zero may be unhelpful, however, as it leads to unexpected + // disappearance of %-sized SVGs in orthogonal contexts, where our + // containing block wants to shrink-wrap. So let's look for an ancestor + // with non-zero size in this dimension, and use that as a (somewhat + // arbitrary) result instead. + nsIFrame *parent = GetParent(); + while (parent) { + nscoord parentISize = parent->GetLogicalSize(wm).ISize(wm); + if (parentISize > 0 && parentISize != NS_UNCONSTRAINEDSIZE) { + result = parentISize; + break; + } + parent = parent->GetParent(); + } } else { - result = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(svg)); + result = nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValue(svg)); if (result < 0) { result = nscoord(0); } } return result; }
--- a/layout/xul/nsMenuFrame.cpp +++ b/layout/xul/nsMenuFrame.cpp @@ -429,17 +429,17 @@ nsMenuFrame::HandleEvent(nsPresContext* } } } else if ( #ifndef NSCONTEXTMENUISMOUSEUP (aEvent->mMessage == eMouseUp && aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) && #else - aEvent->mMessage == NS_CONTEXTMENU && + aEvent->mMessage == eContextMenu && #endif onmenu && !IsMenu() && !IsDisabled()) { // if this menu is a context menu it accepts right-clicks...fire away! // Make sure we cancel default processing of the context menu event so // that it doesn't bubble and get seen again by the popuplistener and show // another context menu. // // Furthermore (there's always more, isn't there?), on some platforms (win32
--- a/media/gmp-clearkey/0.1/VideoDecoder.cpp +++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp @@ -52,17 +52,17 @@ VideoDecoder::InitDecode(const GMPVideoC const uint8_t* aCodecSpecific, uint32_t aCodecSpecificLength, GMPVideoDecoderCallback* aCallback, int32_t aCoreCount) { mCallback = aCallback; assert(mCallback); mDecoder = new WMFH264Decoder(); - HRESULT hr = mDecoder->Init(); + HRESULT hr = mDecoder->Init(aCoreCount); if (FAILED(hr)) { CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder"); mCallback->Error(GMPGenericErr); return; } auto err = GetPlatform()->createmutex(&mMutex); if (GMP_FAILED(err)) {
--- a/media/gmp-clearkey/0.1/WMFH264Decoder.cpp +++ b/media/gmp-clearkey/0.1/WMFH264Decoder.cpp @@ -11,47 +11,55 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "WMFH264Decoder.h" #include <algorithm> +#include <codecapi.h> namespace wmf { WMFH264Decoder::WMFH264Decoder() : mDecoder(nullptr) { memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO)); memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO)); } WMFH264Decoder::~WMFH264Decoder() { } HRESULT -WMFH264Decoder::Init() +WMFH264Decoder::Init(int32_t aCoreCount) { HRESULT hr; hr = CreateMFT(__uuidof(CMSH264DecoderMFT), WMFDecoderDllNameFor(H264), mDecoder); if (FAILED(hr)) { // Windows 7 Enterprise Server N (which is what Mozilla's mochitests run // on) need a different CLSID to instantiate the H.264 decoder. hr = CreateMFT(CLSID_CMSH264DecMFT, WMFDecoderDllNameFor(H264), mDecoder); } ENSURE(SUCCEEDED(hr), hr); + CComPtr<IMFAttributes> attr; + hr = mDecoder->GetAttributes(&attr); + ENSURE(SUCCEEDED(hr), hr); + hr = attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads, + GetNumThreads(aCoreCount)); + ENSURE(SUCCEEDED(hr), hr); + hr = SetDecoderInputType(); ENSURE(SUCCEEDED(hr), hr); hr = SetDecoderOutputType(); ENSURE(SUCCEEDED(hr), hr); hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0); ENSURE(SUCCEEDED(hr), hr);
--- a/media/gmp-clearkey/0.1/WMFH264Decoder.h +++ b/media/gmp-clearkey/0.1/WMFH264Decoder.h @@ -21,17 +21,17 @@ namespace wmf { class WMFH264Decoder { public: WMFH264Decoder(); ~WMFH264Decoder(); - HRESULT Init(); + HRESULT Init(int32_t aCoreCount); HRESULT Input(const uint8_t* aData, uint32_t aDataSize, Microseconds aTimestamp, Microseconds aDuration); HRESULT Output(IMFSample** aOutput);
--- a/media/gmp-clearkey/0.1/WMFUtils.cpp +++ b/media/gmp-clearkey/0.1/WMFUtils.cpp @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ #include "WMFUtils.h" #include "ClearKeyUtils.h" #include <versionhelpers.h> +#include <algorithm> #include <stdio.h> #define INITGUID #include <guiddef.h> #pragma comment(lib, "mfuuid.lib") #pragma comment(lib, "wmcodecdspuuid") #pragma comment(lib, "mfplat.lib") @@ -248,9 +249,15 @@ CreateMFT(const CLSID& clsid, if (FAILED(hr)) { LOG("Failed to get create MFT\n"); return E_FAIL; } return S_OK; } +int32_t +GetNumThreads(int32_t aCoreCount) +{ + return aCoreCount > 4 ? -1 : (std::max)(aCoreCount - 1, 1); +} + } // namespace
--- a/media/gmp-clearkey/0.1/WMFUtils.h +++ b/media/gmp-clearkey/0.1/WMFUtils.h @@ -254,11 +254,15 @@ enum CodecType { H264, AAC, }; // Returns the name of the DLL that is needed to decode H.264 or AAC on // the given windows version we're running on. const char* WMFDecoderDllNameFor(CodecType aCodec); +// Returns the maximum number of threads we want WMF to use for decoding +// given the number of logical processors available. +int32_t GetNumThreads(int32_t aCoreCount); + } // namespace wmf #endif // __WMFUtils_h__
--- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -696,16 +696,23 @@ HRESULT get_default_endpoint(IMMDevice * return ERROR_SUCCESS; } double current_stream_delay(cubeb_stream * stm) { stm->stream_reset_lock->assert_current_thread_owns(); + /* If the default audio endpoint went away during playback and we weren't + able to configure a new one, it's possible the caller may call this + before the error callback has propogated back. */ + if (!stm->audio_clock) { + return 0; + } + UINT64 freq; HRESULT hr = stm->audio_clock->GetFrequency(&freq); if (FAILED(hr)) { LOG("GetFrequency failed: %x\n", hr); return 0; } UINT64 pos; @@ -723,16 +730,20 @@ current_stream_delay(cubeb_stream * stm) return delay; } int stream_set_volume(cubeb_stream * stm, float volume) { stm->stream_reset_lock->assert_current_thread_owns(); + if (!stm->audio_stream_volume) { + return CUBEB_ERROR; + } + uint32_t channels; HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels); if (hr != S_OK) { LOG("could not get the channel count: %x\n", hr); return CUBEB_ERROR; } /* up to 9.1 for now */ @@ -1284,16 +1295,20 @@ void wasapi_stream_destroy(cubeb_stream } int wasapi_stream_start(cubeb_stream * stm) { auto_lock lock(stm->stream_reset_lock); XASSERT(stm && !stm->thread && !stm->shutdown_event); + if (!stm->client) { + return CUBEB_ERROR; + } + HRESULT hr = stm->client->Start(); if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { LOG("audioclient invalid device, reconfiguring\n", hr); BOOL ok = ResetEvent(stm->reconfigure_event); if (!ok) { LOG("resetting reconfig event failed: %x\n", GetLastError()); }
--- a/memory/replace/logalloc/FdPrintf.cpp +++ b/memory/replace/logalloc/FdPrintf.cpp @@ -8,16 +8,17 @@ #ifdef _WIN32 #include <windows.h> #else #include <unistd.h> #endif #include <cstring> #include "mozilla/Assertions.h" +#include "mozilla/unused.h" /* Template class allowing a limited number of increments on a value */ template <typename T> class CheckedIncrement { public: CheckedIncrement(T aValue, size_t aMaxIncrement) : mValue(aValue), mMaxIncrement(aMaxIncrement) @@ -120,12 +121,12 @@ FdPrintf(intptr_t aFd, const char* aForm f++; } out: #ifdef _WIN32 // See comment in FdPrintf.h as to why WriteFile is used. DWORD written; WriteFile(reinterpret_cast<HANDLE>(aFd), buf, b - buf, &written, nullptr); #else - write(aFd, buf, b - buf); + mozilla::unused << write(aFd, buf, b - buf); #endif va_end(ap); }
--- a/memory/replace/logalloc/moz.build +++ b/memory/replace/logalloc/moz.build @@ -33,11 +33,8 @@ include('/ipc/chromium/chromium-config.m if CONFIG['OS_TARGET'] == 'Android': USE_LIBS += [ 'mozglue', ] DIRS += [ 'replay', ] - -# XXX: We should fix these warnings -ALLOW_COMPILER_WARNINGS = True
--- a/memory/replace/logalloc/replay/moz.build +++ b/memory/replace/logalloc/replay/moz.build @@ -16,11 +16,8 @@ LOCAL_INCLUDES += [ ] # Link replace-malloc and the default allocator. USE_LIBS += [ 'memory', ] DISABLE_STL_WRAPPING = True - -# XXX: We should fix these warnings -ALLOW_COMPILER_WARNINGS = True
--- a/mozglue/build/moz.build +++ b/mozglue/build/moz.build @@ -21,16 +21,20 @@ if CONFIG['OS_TARGET'] == 'Android': if CONFIG['MOZ_ASAN']: SOURCES += [ 'AsanOptions.cpp', ] if CONFIG['OS_TARGET'] == 'WINNT': DEFFILE = 'mozglue.def' + # We'll break the DLL blocklist if we immediately load user32.dll + DELAYLOAD_DLLS += [ + 'user32.dll', + ] if not CONFIG['JS_STANDALONE']: if CONFIG['MOZ_MEMORY'] and (CONFIG['MOZ_NATIVE_JEMALLOC'] or FORCE_SHARED_LIB): pass # TODO: SHARED_LIBRARY_LIBS go here else: # Temporary, until bug 662814 lands
--- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -1552,21 +1552,21 @@ Http2Session::RecvPushPromise(Http2Sessi if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) { self->mExpectedPushPromiseID = 0; self->mContinuedPromiseStream = 0; } else { self->mExpectedPushPromiseID = self->mInputFrameID; self->mContinuedPromiseStream = promisedID; } - if (paddingLength > self->mInputFrameDataSize) { + if ((paddingControlBytes + promiseLen + paddingLength) > self->mInputFrameDataSize) { // This is fatal to the session LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X " - "PROTOCOL_ERROR paddingLength %d > frame size %d\n", - self, promisedID, associatedID, paddingLength, + "PROTOCOL_ERROR extra %d > frame size %d\n", + self, promisedID, associatedID, (paddingControlBytes + promiseLen + paddingLength), self->mInputFrameDataSize)); RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); } LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X " "paddingLength %d padded %d\n", self, promisedID, associatedID, paddingLength, self->mInputFrameFlags & kFlag_PADDED));
--- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -83,16 +83,17 @@ HttpBaseChannel::HttpBaseChannel() , mProxyURI(nullptr) , mContentDispositionHint(UINT32_MAX) , mHttpHandler(gHttpHandler) , mReferrerPolicy(REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE) , mRedirectCount(0) , mForcePending(false) , mCorsIncludeCredentials(false) , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS) + , mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) , mOnStartRequestCalled(false) { LOG(("Creating HttpBaseChannel @%x\n", this)); // Subfields of unions cannot be targeted in an initializer list. #ifdef MOZ_VALGRIND // Zero the entire unions so that Valgrind doesn't complain when we send them // to another process. @@ -1997,16 +1998,30 @@ HttpBaseChannel::GetCorsMode(uint32_t* a NS_IMETHODIMP HttpBaseChannel::SetCorsMode(uint32_t aMode) { mCorsMode = aMode; return NS_OK; } +NS_IMETHODIMP +HttpBaseChannel::GetRedirectMode(uint32_t* aMode) +{ + *aMode = mRedirectMode; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetRedirectMode(uint32_t aMode) +{ + mRedirectMode = aMode; + return NS_OK; +} + //----------------------------------------------------------------------------- // HttpBaseChannel::nsISupportsPriority //----------------------------------------------------------------------------- NS_IMETHODIMP HttpBaseChannel::GetPriority(int32_t *value) { *value = mPriority; @@ -2272,24 +2287,16 @@ HttpBaseChannel::SetupReplacementChannel if (mPrivateBrowsingOverriden) { nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel = do_QueryInterface(newChannel); if (newPBChannel) { newPBChannel->SetPrivate(mPrivateBrowsing); } } - // Preserve any skip-serviceworker-flag if possible. - if (mForceNoIntercept) { - nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(newChannel); - if (httpChan) { - httpChan->ForceNoIntercept(); - } - } - // Propagate our loadinfo if needed. newChannel->SetLoadInfo(mLoadInfo); nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel); if (!httpChannel) return NS_OK; // no other options to set if (preserveMethod) { @@ -2379,16 +2386,27 @@ HttpBaseChannel::SetupReplacementChannel // if there is a chain of keys for redirect-responses we transfer it to // the new channel (see bug #561276) if (mRedirectedCachekeys) { LOG(("HttpBaseChannel::SetupReplacementChannel " "[this=%p] transferring chain of redirect cache-keys", this)); httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget()); } + + // Preserve any skip-serviceworker-flag. + if (mForceNoIntercept) { + httpInternal->ForceNoIntercept(); + } + + // Preserve CORS mode flag. + httpInternal->SetCorsMode(mCorsMode); + + // Preserve Redirect mode flag. + httpInternal->SetRedirectMode(mRedirectMode); } // transfer application cache information nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = do_QueryInterface(newChannel); if (appCacheChannel) { appCacheChannel->SetApplicationCache(mApplicationCache); appCacheChannel->SetInheritApplicationCache(mInheritApplicationCache);
--- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -190,16 +190,18 @@ public: NS_IMETHOD SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId) override; NS_IMETHOD ForcePending(bool aForcePending) override; NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override; NS_IMETHOD ForceNoIntercept() override; NS_IMETHOD GetCorsIncludeCredentials(bool* aInclude) override; NS_IMETHOD SetCorsIncludeCredentials(bool aInclude) override; NS_IMETHOD GetCorsMode(uint32_t* aCorsMode) override; NS_IMETHOD SetCorsMode(uint32_t aCorsMode) override; + NS_IMETHOD GetRedirectMode(uint32_t* aRedirectMode) override; + NS_IMETHOD SetRedirectMode(uint32_t aRedirectMode) override; NS_IMETHOD GetTopWindowURI(nsIURI **aTopWindowURI) override; NS_IMETHOD GetProxyURI(nsIURI **proxyURI) override; inline void CleanRedirectCacheChainIfNecessary() { mRedirectedCachekeys = nullptr; } NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName, @@ -424,16 +426,17 @@ protected: nsCOMPtr<nsIPrincipal> mPrincipal; bool mForcePending; nsCOMPtr<nsIURI> mTopWindowURI; bool mCorsIncludeCredentials; uint32_t mCorsMode; + uint32_t mRedirectMode; // This parameter is used to ensure that we do not call OnStartRequest more // than once. bool mOnStartRequestCalled; // The network interface id that's associated with this channel. nsCString mNetworkInterfaceId;
--- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -1027,57 +1027,65 @@ HttpChannelChild::RecvReportSecurityMess class Redirect1Event : public ChannelEvent { public: Redirect1Event(HttpChannelChild* child, const uint32_t& newChannelId, const URIParams& newURI, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead) + const nsHttpResponseHead& responseHead, + const nsACString& securityInfoSerialization) : mChild(child) , mNewChannelId(newChannelId) , mNewURI(newURI) , mRedirectFlags(redirectFlags) - , mResponseHead(responseHead) {} + , mResponseHead(responseHead) + , mSecurityInfoSerialization(securityInfoSerialization) {} void Run() { mChild->Redirect1Begin(mNewChannelId, mNewURI, mRedirectFlags, - mResponseHead); + mResponseHead, mSecurityInfoSerialization); } private: HttpChannelChild* mChild; uint32_t mNewChannelId; URIParams mNewURI; uint32_t mRedirectFlags; nsHttpResponseHead mResponseHead; + nsCString mSecurityInfoSerialization; }; bool HttpChannelChild::RecvRedirect1Begin(const uint32_t& newChannelId, const URIParams& newUri, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead) + const nsHttpResponseHead& responseHead, + const nsCString& securityInfoSerialization) { + // TODO: handle security info LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this)); if (mEventQ->ShouldEnqueue()) { mEventQ->Enqueue(new Redirect1Event(this, newChannelId, newUri, - redirectFlags, responseHead)); + redirectFlags, responseHead, + securityInfoSerialization)); } else { - Redirect1Begin(newChannelId, newUri, redirectFlags, responseHead); + Redirect1Begin(newChannelId, newUri, redirectFlags, responseHead, + securityInfoSerialization); } return true; } void HttpChannelChild::Redirect1Begin(const uint32_t& newChannelId, const URIParams& newUri, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead) + const nsHttpResponseHead& responseHead, + const nsACString& securityInfoSerialization) { LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this)); nsresult rv; nsCOMPtr<nsIIOService> ioService; rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); if (NS_FAILED(rv)) { // Veto redirect. nsHttpChannel decides to cancel or continue. @@ -1100,16 +1108,21 @@ HttpChannelChild::Redirect1Begin(const u // Veto redirect. nsHttpChannel decides to cancel or continue. OnRedirectVerifyCallback(rv); return; } // We won't get OnStartRequest, set cookies here. mResponseHead = new nsHttpResponseHead(responseHead); + if (!securityInfoSerialization.IsEmpty()) { + NS_DeserializeObject(securityInfoSerialization, + getter_AddRefs(mSecurityInfo)); + } + bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(mResponseHead->Status(), mRequestHead.ParsedMethod()); rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET); if (NS_FAILED(rv)) { // Veto redirect. nsHttpChannel decides to cancel or continue. OnRedirectVerifyCallback(rv); return;
--- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -130,17 +130,18 @@ protected: const uint32_t& count) override; bool RecvOnStopRequest(const nsresult& statusCode, const ResourceTimingStruct& timing) override; bool RecvOnProgress(const int64_t& progress, const int64_t& progressMax) override; bool RecvOnStatus(const nsresult& status) override; bool RecvFailedAsyncOpen(const nsresult& status) override; bool RecvRedirect1Begin(const uint32_t& newChannel, const URIParams& newURI, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead) override; + const nsHttpResponseHead& responseHead, + const nsCString& securityInfoSerialization) override; bool RecvRedirect3Complete() override; bool RecvAssociateApplicationCache(const nsCString& groupID, const nsCString& clientID) override; bool RecvFlushedForDiversion() override; bool RecvDivertMessages() override; bool RecvDeleteSelf() override; bool RecvReportSecurityMessage(const nsString& messageTag, @@ -234,17 +235,18 @@ private: void OnStopRequest(const nsresult& channelStatus, const ResourceTimingStruct& timing); void OnProgress(const int64_t& progress, const int64_t& progressMax); void OnStatus(const nsresult& status); void FailedAsyncOpen(const nsresult& status); void HandleAsyncAbort(); void Redirect1Begin(const uint32_t& newChannelId, const URIParams& newUri, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead); + const nsHttpResponseHead& responseHead, + const nsACString& securityInfoSerialization); void Redirect3Complete(); void DeleteSelf(); friend class AssociateApplicationCacheEvent; friend class StartRequestEvent; friend class StopRequestEvent; friend class TransportAndDataEvent; friend class ProgressEvent;
--- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -389,16 +389,17 @@ HttpChannelParent::DoAsyncOpen( const U if (stream) { mChannel->InternalSetUploadStream(stream); mChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders); } if (aSynthesizedResponseHead.type() == OptionalHttpResponseHead::TnsHttpResponseHead) { mSynthesizedResponseHead = new nsHttpResponseHead(aSynthesizedResponseHead.get_nsHttpResponseHead()); mShouldIntercept = true; + mChannel->SetCouldBeSynthesized(); if (!aSecurityInfoSerialization.IsEmpty()) { nsCOMPtr<nsISupports> secInfo; NS_DeserializeObject(aSecurityInfoSerialization, getter_AddRefs(secInfo)); mChannel->OverrideSecurityInfo(secInfo); } } else { @@ -831,24 +832,17 @@ HttpChannelParent::OnStartRequest(nsIReq nsCOMPtr<nsISupports> cacheEntry; chan->GetCacheToken(getter_AddRefs(cacheEntry)); mCacheEntry = do_QueryInterface(cacheEntry); nsresult channelStatus = NS_OK; chan->GetStatus(&channelStatus); nsCString secInfoSerialization; - nsCOMPtr<nsISupports> secInfoSupp; - chan->GetSecurityInfo(getter_AddRefs(secInfoSupp)); - if (secInfoSupp) { - mAssociatedContentSecurity = do_QueryInterface(secInfoSupp); - nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp); - if (secInfoSer) - NS_SerializeToString(secInfoSer, secInfoSerialization); - } + UpdateAndSerializeSecurityInfo(secInfoSerialization); uint16_t redirectCount = 0; mChannel->GetRedirectCount(&redirectCount); nsCOMPtr<nsISupports> cacheKey; mChannel->GetCacheKey(getter_AddRefs(cacheKey)); uint32_t cacheKeyValue = 0; if (cacheKey) { @@ -1043,20 +1037,24 @@ HttpChannelParent::StartRedirect(uint32_ return NS_BINDING_ABORTED; nsCOMPtr<nsIURI> newURI; newChannel->GetURI(getter_AddRefs(newURI)); URIParams uriParams; SerializeURI(newURI, uriParams); + nsCString secInfoSerialization; + UpdateAndSerializeSecurityInfo(secInfoSerialization); + nsHttpResponseHead *responseHead = mChannel->GetResponseHead(); bool result = SendRedirect1Begin(newChannelId, uriParams, redirectFlags, responseHead ? *responseHead - : nsHttpResponseHead()); + : nsHttpResponseHead(), + secInfoSerialization); if (!result) { // Bug 621446 investigation mSentRedirect1BeginFailed = true; return NS_BINDING_ABORTED; } // Bug 621446 investigation mSentRedirect1Begin = true; @@ -1321,16 +1319,30 @@ HttpChannelParent::GetAuthPrompt(uint32_ void** aResult) { nsCOMPtr<nsIAuthPrompt2> prompt = new NeckoParent::NestedFrameAuthPrompt(Manager(), mNestedFrameId); prompt.forget(aResult); return NS_OK; } +void +HttpChannelParent::UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut) +{ + nsCOMPtr<nsISupports> secInfoSupp; + mChannel->GetSecurityInfo(getter_AddRefs(secInfoSupp)); + if (secInfoSupp) { + mAssociatedContentSecurity = do_QueryInterface(secInfoSupp); + nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp); + if (secInfoSer) { + NS_SerializeToString(secInfoSer, aSerializedSecurityInfoOut); + } + } +} + //----------------------------------------------------------------------------- // HttpChannelSecurityWarningReporter //----------------------------------------------------------------------------- nsresult HttpChannelParent::ReportSecurityMessage(const nsAString& aMessageTag, const nsAString& aMessageCategory) {
--- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -153,16 +153,18 @@ protected: void OfflineDisconnect() override; uint32_t GetAppId() override; nsresult ReportSecurityMessage(const nsAString& aMessageTag, const nsAString& aMessageCategory) override; private: + void UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut); + nsRefPtr<nsHttpChannel> mChannel; nsCOMPtr<nsICacheEntry> mCacheEntry; nsCOMPtr<nsIAssociatedContentSecurity> mAssociatedContentSecurity; bool mIPCClosed; // PHttpChannel actor has been Closed() nsCOMPtr<nsIChannel> mRedirectChannel; nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
--- a/netwerk/protocol/http/PHttpChannel.ipdl +++ b/netwerk/protocol/http/PHttpChannel.ipdl @@ -116,17 +116,18 @@ child: // AsyncOpen of nsHttpChannel on the parent. FailedAsyncOpen(nsresult status); // Called to initiate content channel redirect, starts talking to sinks // on the content process and reports result via Redirect2Verify above Redirect1Begin(uint32_t newChannelId, URIParams newUri, uint32_t redirectFlags, - nsHttpResponseHead responseHead); + nsHttpResponseHead responseHead, + nsCString securityInfoSerialization); // Called if redirect successful so that child can complete setup. Redirect3Complete(); // Associate the child with an application ids AssociateApplicationCache(nsCString groupID, nsCString clientID);
--- a/netwerk/protocol/http/PackagedAppService.cpp +++ b/netwerk/protocol/http/PackagedAppService.cpp @@ -11,16 +11,17 @@ #include "nsICacheStorageService.h" #include "nsIResponseHeadProvider.h" #include "nsIMultiPartChannel.h" #include "../../cache2/CacheFileUtils.h" #include "nsStreamUtils.h" #include "mozilla/Logging.h" #include "mozilla/DebugOnly.h" #include "nsIHttpHeaderVisitor.h" +#include "mozilla/LoadContext.h" namespace mozilla { namespace net { static PackagedAppService *gPackagedAppService = nullptr; static PRLogModuleInfo *gPASLog = nullptr; #undef LOG @@ -806,16 +807,22 @@ PackagedAppService::GetResource(nsIChann } // Add the package to the hashtable. mDownloadingPackages.Put(key, downloader); nsRefPtr<PackagedAppChannelListener> listener = new PackagedAppChannelListener(downloader, mimeConverter); + nsCOMPtr<nsIInterfaceRequestor> loadContext; + aChannel->GetNotificationCallbacks(getter_AddRefs(loadContext)); + if (loadContext) { + channel->SetNotificationCallbacks(loadContext); + } + if (loadInfo && loadInfo->GetEnforceSecurity()) { return channel->AsyncOpen2(listener); } return channel->AsyncOpen(listener, nullptr); } nsresult
--- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -4981,17 +4981,17 @@ nsHttpChannel::AsyncOpen(nsIStreamListen rv = NS_CheckPortSafety(mURI); if (NS_FAILED(rv)) { ReleaseListeners(); return rv; } if (ShouldIntercept()) { mInterceptCache = MAYBE_INTERCEPT; - mResponseCouldBeSynthesized = true; + SetCouldBeSynthesized(); } // Remember the cookie header that was set, if any const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie); if (cookieHeader) { mUserSetCookieHeader = cookieHeader; } @@ -6961,10 +6961,16 @@ nsHttpChannel::OnPush(const nsACString & channel->mCallbacks = mCallbacks; // Link the pushed stream with the new channel and call listener channel->SetPushedStream(pushedStream); rv = pushListener->OnPush(this, pushHttpChannel); return rv; } +void +nsHttpChannel::SetCouldBeSynthesized() +{ + mResponseCouldBeSynthesized = true; +} + } // namespace net } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -230,16 +230,17 @@ public: /* internal necko use only */ private: nsHttpChannel* mChannel; uint32_t mKeep : 2; }; void MarkIntercepted(); NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override; bool AwaitingCacheCallbacks(); + void SetCouldBeSynthesized(); protected: virtual ~nsHttpChannel(); private: typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result); bool RequestIsConditional();
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -33,17 +33,17 @@ interface nsIHttpUpgradeListener : nsISu in nsIAsyncOutputStream aSocketOut); }; /** * Dumping ground for http. This interface will never be frozen. If you are * using any feature exposed by this interface, be aware that this interface * will change and you will be broken. You have been warned. */ -[scriptable, uuid(c4fab96f-0b10-4b14-b45b-517fc3f36842)] +[scriptable, uuid(e2eebad8-e51f-473a-bbb7-8e2829376625)] interface nsIHttpChannelInternal : nsISupports { /** * An http channel can own a reference to the document URI */ attribute nsIURI documentURI; @@ -234,16 +234,26 @@ interface nsIHttpChannelInternal : nsISu const unsigned long CORS_MODE_NO_CORS = 1; const unsigned long CORS_MODE_CORS = 2; const unsigned long CORS_MODE_CORS_WITH_FORCED_PREFLIGHT = 3; /** * Set by nsCORSListenerProxy to indicate CORS load type. Defaults to CORS_MODE_NO_CORS. */ attribute unsigned long corsMode; + const unsigned long REDIRECT_MODE_FOLLOW = 0; + const unsigned long REDIRECT_MODE_ERROR = 1; + const unsigned long REDIRECT_MODE_MANUAL = 2; + /** + * Set to indicate Request.redirect mode exposed during ServiceWorker + * interception. No policy enforcement is performed by the channel for this + * value. + */ + attribute unsigned long redirectMode; + /** * The URI of the top-level window that's associated with this channel. */ readonly attribute nsIURI topWindowURI; /** * The network interface id that's associated with this channel. */
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -2355,18 +2355,18 @@ WebSocketChannel::AbortSession(nsresult CleanupConnection(); return; } if (mStopped) return; mStopped = 1; - if (mTransport && reason != NS_BASE_STREAM_CLOSED && - !mRequestedClose && !mClientClosed && !mServerClosed) { + if (mTransport && reason != NS_BASE_STREAM_CLOSED && !mRequestedClose && + !mClientClosed && !mServerClosed && mConnecting == NOT_CONNECTING) { mRequestedClose = 1; mStopOnClose = reason; mSocketThread->Dispatch( new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)), nsIEventTarget::DISPATCH_NORMAL); } else { StopSession(reason); } @@ -3116,17 +3116,21 @@ WebSocketChannel::GetSecurityInfo(nsISup NS_IMETHODIMP WebSocketChannel::AsyncOpen(nsIURI *aURI, const nsACString &aOrigin, nsIWebSocketListener *aListener, nsISupports *aContext) { LOG(("WebSocketChannel::AsyncOpen() %p\n", this)); - MOZ_ASSERT(NS_IsMainThread(), "not main thread"); + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "not main thread"); + LOG(("WebSocketChannel::AsyncOpen() called off the main thread")); + return NS_ERROR_UNEXPECTED; + } if (!aURI || !aListener) { LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null")); return NS_ERROR_UNEXPECTED; } if (mListenerMT || mWasOpened) return NS_ERROR_ALREADY_OPENED; @@ -3260,16 +3264,36 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI // we want the same instance of the loadInfo to be set on the channel. rv = localChannel->SetLoadInfo(mLoadInfo); NS_ENSURE_SUCCESS(rv, rv); // Pass most GetInterface() requests through to our instantiator, but handle // nsIChannelEventSink in this object in order to deal with redirects localChannel->SetNotificationCallbacks(this); + class MOZ_STACK_CLASS CleanUpOnFailure + { + public: + explicit CleanUpOnFailure(WebSocketChannel* aWebSocketChannel) + : mWebSocketChannel(aWebSocketChannel) + {} + + ~CleanUpOnFailure() + { + if (!mWebSocketChannel->mWasOpened) { + mWebSocketChannel->mChannel = nullptr; + mWebSocketChannel->mHttpChannel = nullptr; + } + } + + WebSocketChannel *mWebSocketChannel; + }; + + CleanUpOnFailure cuof(this); + mChannel = do_QueryInterface(localChannel, &rv); NS_ENSURE_SUCCESS(rv, rv); mHttpChannel = do_QueryInterface(localChannel, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = SetupRequest(); if (NS_FAILED(rv)) @@ -3324,17 +3348,17 @@ WebSocketChannel::Close(uint16_t code, c // The API requires the UTF-8 string to be 123 or less bytes if (reason.Length() > 123) return NS_ERROR_ILLEGAL_VALUE; mRequestedClose = 1; mScriptCloseReason = reason; mScriptCloseCode = code; - if (!mTransport) { + if (!mTransport || mConnecting != NOT_CONNECTING) { nsresult rv; if (code == CLOSE_GOING_AWAY) { // Not an error: for example, tab has closed or navigated away LOG(("WebSocketChannel::Close() GOING_AWAY without transport.")); rv = NS_OK; } else { LOG(("WebSocketChannel::Close() without transport - error.")); rv = NS_ERROR_NOT_CONNECTED; @@ -3372,16 +3396,21 @@ WebSocketChannel::SendBinaryStream(nsIIn } nsresult WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary, uint32_t aLength, nsIInputStream *aStream) { MOZ_ASSERT(IsOnTargetThread(), "not target thread"); + if (!mDataStarted) { + LOG(("WebSocketChannel:: Error: data not started yet\n")); + return NS_ERROR_UNEXPECTED; + } + if (mRequestedClose) { LOG(("WebSocketChannel:: Error: send when closed\n")); return NS_ERROR_UNEXPECTED; } if (mStopped) { LOG(("WebSocketChannel:: Error: send when stopped\n")); return NS_ERROR_NOT_CONNECTED;
--- a/netwerk/test/unit/test_packaged_app_channel.js +++ b/netwerk/test/unit/test_packaged_app_channel.js @@ -118,33 +118,43 @@ function run_test() httpserver.start(-1); // Enable the feature and save the original pref value originalPref = Services.prefs.getBoolPref("network.http.enable-packaged-apps"); Services.prefs.setBoolPref("network.http.enable-packaged-apps", true); do_register_cleanup(reset_pref); add_test(test_channel); + add_test(test_channel_no_notificationCallbacks); add_test(test_channel_uris); // run tests run_next_test(); } -function test_channel() { +function test_channel(aNullNotificationCallbacks) { var channel = make_channel(uri+"/package!//index.html"); + + if (!aNullNotificationCallbacks) { + channel.notificationCallbacks = new LoadContextCallback(1024, false, false, false); + } + channel.asyncOpen(new Listener(function(l) { // XXX: no content length available for this resource //do_check_true(channel.contentLength > 0); do_check_true(l.gotStartRequest); do_check_true(l.gotStopRequest); run_next_test(); }), null); } +function test_channel_no_notificationCallbacks() { + test_channel(true); +} + function test_channel_uris() { // A `!//` in the query or ref should not be handled as a packaged app resource var channel = make_channel(uri+"/regular?bla!//bla#bla!//bla"); channel.asyncOpen(new ChannelListener(check_regular_response, null), null); } function check_regular_response(request, buffer) { request.QueryInterface(Components.interfaces.nsIHttpChannel);
--- a/testing/mach_commands.py +++ b/testing/mach_commands.py @@ -1,24 +1,27 @@ # 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/. from __future__ import absolute_import, print_function, unicode_literals +import json import os import sys +import tempfile from mach.decorators import ( CommandArgument, CommandProvider, Command, ) from mozbuild.base import MachCommandBase +from argparse import ArgumentParser UNKNOWN_TEST = ''' I was unable to find tests in the argument(s) given. You need to specify a test directory, filename, test suite name, or abbreviation. @@ -495,8 +498,95 @@ class PushToTry(MachCommandBase): if verbose: print('The following try syntax was calculated:\n\n\t%s\n' % msg) if push: at.push_to_try(msg, verbose) return + + +def get_parser(argv=None): + parser = ArgumentParser() + parser.add_argument(dest="suite_name", + nargs=1, + choices=['mochitest'], + type=str, + help="The test for which chunk should be found. It corresponds " + "to the mach test invoked (only 'mochitest' currently).") + + parser.add_argument(dest="test_path", + nargs=1, + type=str, + help="The test (any mochitest) for which chunk should be found.") + + parser.add_argument('--total-chunks', + type=int, + dest='total_chunks', + required=True, + help='Total number of chunks to split tests into.', + default=None + ) + + parser.add_argument('-f', "--flavor", + dest="flavor", + type=str, + help="Flavor to which the test belongs to.") + + parser.add_argument('--chunk-by-runtime', + action='store_true', + dest='chunk_by_runtime', + help='Group tests such that each chunk has roughly the same runtime.', + default=False, + ) + + parser.add_argument('--chunk-by-dir', + type=int, + dest='chunk_by_dir', + help='Group tests together in the same chunk that are in the same top ' + 'chunkByDir directories.', + default=None, + ) + + return parser + + +@CommandProvider +class ChunkFinder(MachCommandBase): + @Command('find-test-chunk', category='testing', + description='Find which chunk a test belongs to (works for mochitest).', + parser=get_parser) + def chunk_finder(self, **kwargs): + flavor = kwargs['flavor'] + total_chunks = kwargs['total_chunks'] + test_path = kwargs['test_path'][0] + suite_name = kwargs['suite_name'][0] + _, dump_tests = tempfile.mkstemp() + args = { + 'totalChunks': total_chunks, + 'dump_tests': dump_tests, + 'chunkByDir': kwargs['chunk_by_dir'], + 'chunkByRuntime': kwargs['chunk_by_runtime'], + } + + found = False + for this_chunk in range(1, total_chunks+1): + args['thisChunk'] = this_chunk + try: + self._mach_context.commands.dispatch(suite_name, self._mach_context, flavor=flavor, resolve_tests=False, **args) + except SystemExit: + pass + except KeyboardInterrupt: + break + + fp = open(os.path.expanduser(args['dump_tests']), 'r') + tests = json.loads(fp.read())['active_tests'] + paths = [t['path'] for t in tests] + if test_path in paths: + print("The test %s is present in chunk number: %d (it may be skipped)." % (test_path, this_chunk)) + found = True + break + + if not found: + raise Exception("Test %s not found." % test_path) + # Clean up the file + os.remove(dump_tests)
--- a/testing/marionette/actions.js +++ b/testing/marionette/actions.js @@ -35,37 +35,37 @@ this.ActionChain = function(utils, check // test utilities providing some event synthesis code this.utils = utils; }; ActionChain.prototype.dispatchActions = function( args, touchId, - frame, + container, elementManager, callbacks, touchProvider) { // Some touch events code in the listener needs to do ipc, so we can't // share this code across chrome/content. if (touchProvider) { this.touchProvider = touchProvider; } this.elementManager = elementManager; - let commandArray = elementManager.convertWrappedArguments(args, frame); + let commandArray = elementManager.convertWrappedArguments(args, container); this.onSuccess = callbacks.onSuccess; this.onError = callbacks.onError; - this.frame = frame; + this.container = container; if (touchId == null) { touchId = this.nextTouchId++; } - if (!frame.document.createTouch) { + if (!container.frame.document.createTouch) { this.mouseEventsOnly = true; } let keyModifiers = { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false @@ -121,32 +121,32 @@ ActionChain.prototype.emitMouseEvent = f } else { mods = 0; } domUtils.sendMouseEvent( type, elClientX, elClientY, - button || 0, + button || 0, clickCount || 1, mods, false, 0, this.inputSource); } }; /** * Reset any persisted values after a command completes. */ ActionChain.prototype.resetValues = function() { this.onSuccess = null; this.onError = null; - this.frame = null; + this.container = null; this.elementManager = null; this.touchProvider = null; this.mouseEventsOnly = false; }; /** * Function to emit touch events for each finger. e.g. * finger=[['press', id], ['wait', 5], ['release']] touchId represents @@ -172,27 +172,27 @@ ActionChain.prototype.actions = function if (!(touchId in this.touchIds) && !this.mouseEventsOnly) { this.resetValues(); throw new WebDriverError("Element has not been pressed"); } } switch(command) { case "keyDown": - this.utils.sendKeyDown(pack[1], keyModifiers, this.frame); + this.utils.sendKeyDown(pack[1], keyModifiers, this.container.frame); this.actions(chain, touchId, i, keyModifiers); break; case "keyUp": - this.utils.sendKeyUp(pack[1], keyModifiers, this.frame); + this.utils.sendKeyUp(pack[1], keyModifiers, this.container.frame); this.actions(chain, touchId, i, keyModifiers); break; case "click": - el = this.elementManager.getKnownElement(pack[1], this.frame); + el = this.elementManager.getKnownElement(pack[1], this.container); let button = pack[2]; let clickCount = pack[3]; c = this.coordinates(el, null, null); this.mouseTap(el.ownerDocument, c.x, c.y, button, clickCount, keyModifiers); if (button == 2) { this.emitMouseEvent(el.ownerDocument, "contextmenu", c.x, c.y, button, clickCount, keyModifiers); } @@ -212,17 +212,17 @@ ActionChain.prototype.actions = function throw new WebDriverError( "Invalid Command: press cannot follow an active touch event"); } // look ahead to check if we're scrolling. Needed for APZ touch dispatching. if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) { this.scrolling = true; } - el = this.elementManager.getKnownElement(pack[1], this.frame); + el = this.elementManager.getKnownElement(pack[1], this.container); c = this.coordinates(el, pack[2], pack[3]); touchId = this.generateEvents("press", c.x, c.y, null, el, keyModifiers); this.actions(chain, touchId, i, keyModifiers); break; case "release": this.generateEvents( "release", @@ -231,17 +231,17 @@ ActionChain.prototype.actions = function touchId, null, keyModifiers); this.actions(chain, null, i, keyModifiers); this.scrolling = false; break; case "move": - el = this.elementManager.getKnownElement(pack[1], this.frame); + el = this.elementManager.getKnownElement(pack[1], this.container); c = this.coordinates(el); this.generateEvents("move", c.x, c.y, touchId, null, keyModifiers); this.actions(chain, touchId, i, keyModifiers); break; case "moveByOffset": this.generateEvents( "move", @@ -269,29 +269,29 @@ ActionChain.prototype.actions = function } this.checkTimer.initWithCallback( () => this.actions(chain, touchId, i, keyModifiers), time, Components.interfaces.nsITimer.TYPE_ONE_SHOT); } else { this.actions(chain, touchId, i, keyModifiers); } break; - + case "cancel": this.generateEvents( "cancel", this.lastCoordinates[0], this.lastCoordinates[1], touchId, null, keyModifiers); this.actions(chain, touchId, i, keyModifiers); this.scrolling = false; break; - + case "longPress": this.generateEvents( "contextmenu", this.lastCoordinates[0], this.lastCoordinates[1], touchId, null, keyModifiers); @@ -350,17 +350,17 @@ ActionChain.prototype.getCoordinateInfo * to the viewport. * @param {number} y * Y coordinate of the location to generate the event that is relative * to the viewport. */ ActionChain.prototype.generateEvents = function( type, x, y, touchId, target, keyModifiers) { this.lastCoordinates = [x, y]; - let doc = this.frame.document; + let doc = this.container.frame.document; switch (type) { case "tap": if (this.mouseEventsOnly) { this.mouseTap( touch.target.ownerDocument, touch.clientX, touch.clientY, @@ -445,17 +445,17 @@ ActionChain.prototype.generateEvents = f this.touchIds[touchId].target, x, y, touchId); this.touchIds[touchId] = touch; this.touchProvider.emitTouchEvent("touchmove", touch); } break; case "contextmenu": this.isTap = false; - let event = this.frame.document.createEvent("MouseEvents"); + let event = this.container.frame.document.createEvent("MouseEvents"); if (this.mouseEventsOnly) { target = doc.elementFromPoint(this.lastCoordinates[0], this.lastCoordinates[1]); } else { target = this.touchIds[touchId].target; } let [clientX, clientY, pageX, pageY, screenX, screenY] = this.getCoordinateInfo(target, x, y);
new file mode 100644 --- /dev/null +++ b/testing/marionette/client/marionette/tests/unit/test_shadow_dom.py @@ -0,0 +1,65 @@ +# 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/. + +from marionette import MarionetteTestCase +from marionette_driver.errors import (NoSuchElementException, StaleElementException) + + +class TestShadowDom(MarionetteTestCase): + + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.enforce_gecko_prefs({"dom.webcomponents.enabled": True}) + self.marionette.navigate(self.marionette.absolute_url("test_shadow_dom.html")) + + self.host = self.marionette.find_element("id", "host") + self.marionette.switch_to_shadow_root(self.host) + self.button = self.marionette.find_element("id", "button") + + def test_shadow_dom(self): + # Button in shadow root should be actionable + self.button.click() + + def test_shadow_dom_after_switch_away_from_shadow_root(self): + # Button in shadow root should be actionable + self.button.click() + self.marionette.switch_to_shadow_root() + # After switching back to top content, button should be stale + self.assertRaises(StaleElementException, self.button.click) + + def test_shadow_dom_raises_stale_element_exception_when_button_remove(self): + self.marionette.execute_script( + 'document.getElementById("host").shadowRoot.getElementById("button").remove();') + # After removing button from shadow DOM, button should be stale + self.assertRaises(StaleElementException, self.button.click) + + def test_shadow_dom_raises_stale_element_exception_when_host_removed(self): + self.marionette.execute_script('document.getElementById("host").remove();') + # After removing shadow DOM host element, button should be stale + self.assertRaises(StaleElementException, self.button.click) + + def test_non_existent_shadow_dom(self): + # Jump back to top level content + self.marionette.switch_to_shadow_root() + # When no ShadowRoot is found, switch_to_shadow_root throws NoSuchElementException + self.assertRaises(NoSuchElementException, self.marionette.switch_to_shadow_root, + self.marionette.find_element("id", "empty-host")) + + def test_inner_shadow_dom(self): + # Button in shadow root should be actionable + self.button.click() + self.inner_host = self.marionette.find_element("id", "inner-host") + self.marionette.switch_to_shadow_root(self.inner_host) + self.inner_button = self.marionette.find_element("id", "inner-button") + # Nested nutton in nested shadow root should be actionable + self.inner_button.click() + self.marionette.switch_to_shadow_root() + # After jumping back to parent shadow root, button should again be actionable but inner + # button should now be stale + self.button.click() + self.assertRaises(StaleElementException, self.inner_button.click) + self.marionette.switch_to_shadow_root() + # After switching back to top content, both buttons should now be stale + self.assertRaises(StaleElementException, self.button.click) + self.assertRaises(StaleElementException, self.inner_button.click)
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini +++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini @@ -153,8 +153,10 @@ b2g = false [test_teardown_context_preserved.py] b2g = false [test_file_upload.py] b2g = false skip-if = os == "win" # http://bugs.python.org/issue14574 [test_execute_sandboxes.py] [test_using_permissions.py] + +[test_shadow_dom.py]
new file mode 100644 --- /dev/null +++ b/testing/marionette/client/marionette/www/test_shadow_dom.html @@ -0,0 +1,26 @@ +<!-- 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> + +<html> +<meta charset="UTF-8"> +<head> +<title>Marionette Test</title> +</head> +<body> + <div id="host"></div> + <div id="empty-host"></div> + <script> + 'use strict'; + var host = document.getElementById('host'); + var root = host.createShadowRoot(); + root.innerHTML = '<button id="button">Foo</button>' + + '<div id="inner-host"></div>'; + var innerHost = host.shadowRoot.getElementById('inner-host'); + var innerRoot = innerHost.createShadowRoot(); + innerRoot.innerHTML = '<button id="inner-button">Bar</button>'; + </script> +</body> +</html>
--- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -786,17 +786,18 @@ GeckoDriver.prototype.createExecuteSandb this.sandboxes[sandboxName] = sb; }; /** * Apply arguments sent from the client to the current (possibly reused) * execution sandbox. */ GeckoDriver.prototype.applyArgumentsToSandbox = function(win, sb, args) { - sb.__marionetteParams = this.curBrowser.elementManager.convertWrappedArguments(args, win); + sb.__marionetteParams = this.curBrowser.elementManager.convertWrappedArguments(args, + { frame: win }); sb.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args); }; /** * Executes a script in the given sandbox. * * @param {Response} resp * Response object given to the command calling this routine. @@ -1651,17 +1652,17 @@ GeckoDriver.prototype.switchToFrame = fu checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); return; } if (typeof cmd.parameters.element != "undefined") { if (this.curBrowser.elementManager.seenItems[cmd.parameters.element]) { // HTMLIFrameElement let wantedFrame = this.curBrowser.elementManager - .getKnownElement(cmd.parameters.element, curWindow); + .getKnownElement(cmd.parameters.element, { frame: curWindow }); // Deal with an embedded xul:browser case if (wantedFrame.tagName == "xul:browser" || wantedFrame.tagName == "browser") { curWindow = wantedFrame.contentWindow; this.curFrame = curWindow; if (cmd.parameters.focus) { this.curFrame.focus(); } checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); @@ -1832,17 +1833,17 @@ GeckoDriver.prototype.actionChain = func } let cbs = {}; cbs.onSuccess = val => resp.value = val; cbs.onError = err => { throw err; }; let win = this.getCurrentWindow(); let elm = this.curBrowser.elementManager; - this.actions.dispatchActions(chain, nextId, win, elm, cbs); + this.actions.dispatchActions(chain, nextId, { frame: win }, elm, cbs); break; case Context.CONTENT: this.addFrameCloseListener("action chain"); resp.value = yield this.listener.actionChain({chain: chain, nextId: nextId}); break; } }; @@ -1877,17 +1878,17 @@ GeckoDriver.prototype.multiAction = func * Value the client is looking for. */ GeckoDriver.prototype.findElement = function(cmd, resp) { switch (this.context) { case Context.CHROME: resp.value = yield new Promise((resolve, reject) => { let win = this.getCurrentWindow(); this.curBrowser.elementManager.find( - win, + { frame: win }, cmd.parameters, this.searchTimeout, false /* all */, resolve, reject); }).then(null, e => { throw e; }); break; @@ -1929,17 +1930,17 @@ GeckoDriver.prototype.findChildElement = * Value the client is looking for. */ GeckoDriver.prototype.findElements = function(cmd, resp) { switch (this.context) { case Context.CHROME: resp.value = yield new Promise((resolve, reject) => { let win = this.getCurrentWindow(); this.curBrowser.elementManager.find( - win, + { frame: win }, cmd.parameters, this.searchTimeout, true /* all */, resolve, reject); }).then(null, e => { throw new NoSuchElementError(e.message); }); break; @@ -1985,17 +1986,17 @@ GeckoDriver.prototype.getActiveElement = */ GeckoDriver.prototype.clickElement = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: // click atom fails, fall back to click() action let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); el.click(); break; case Context.CONTENT: // We need to protect against the click causing an OOP frame to close. // This fires the mozbrowserclose event when it closes so we need to // listen for it and then just send an error back. The person making the // call should be aware something isnt right and handle accordingly @@ -2014,17 +2015,17 @@ GeckoDriver.prototype.clickElement = fun * Name of the attribute to retrieve. */ GeckoDriver.prototype.getElementAttribute = function(cmd, resp) { let {id, name} = cmd.parameters; switch (this.context) { case Context.CHROME: let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); resp.value = utils.getElementAttribute(el, name); break; case Context.CONTENT: resp.value = yield this.listener.getElementAttribute(id, name); break; } }; @@ -2038,17 +2039,17 @@ GeckoDriver.prototype.getElementAttribut */ GeckoDriver.prototype.getElementText = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: // for chrome, we look at text nodes, and any node with a "label" field let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); let lines = []; this.getVisibleText(el, lines); resp.value = lines.join("\n"); break; case Context.CONTENT: resp.value = yield this.listener.getElementText(id); break; @@ -2062,17 +2063,17 @@ GeckoDriver.prototype.getElementText = f * Reference ID to the element that will be inspected. */ GeckoDriver.prototype.getElementTagName = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); resp.value = el.tagName.toLowerCase(); break; case Context.CONTENT: resp.value = yield this.listener.getElementTagName(id); break; } }; @@ -2084,17 +2085,17 @@ GeckoDriver.prototype.getElementTagName * Reference ID to the element that will be inspected. */ GeckoDriver.prototype.isElementDisplayed = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); resp.value = utils.isElementDisplayed(el); break; case Context.CONTENT: resp.value = yield this.listener.isElementDisplayed(id); break; } }; @@ -2108,17 +2109,17 @@ GeckoDriver.prototype.isElementDisplayed * CSS rule that is being requested. */ GeckoDriver.prototype.getElementValueOfCssProperty = function(cmd, resp) { let {id, propertyName: prop} = cmd.parameters; switch (this.context) { case Context.CHROME: let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); let sty = win.document.defaultView.getComputedStyle(el, null); resp.value = sty.getPropertyValue(prop); break; case Context.CONTENT: resp.value = yield this.listener.getElementValueOfCssProperty(id, prop); break; } @@ -2132,17 +2133,17 @@ GeckoDriver.prototype.getElementValueOfC */ GeckoDriver.prototype.isElementEnabled = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: // Selenium atom doesn't quite work here let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); resp.value = !(!!el.disabled); break; case Context.CONTENT: resp.value = yield this.listener.isElementEnabled(id); break; } }, @@ -2155,17 +2156,17 @@ GeckoDriver.prototype.isElementEnabled = */ GeckoDriver.prototype.isElementSelected = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: // Selenium atom doesn't quite work here let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); if (typeof el.checked != "undefined") { resp.value = !!el.checked; } else if (typeof el.selected != "undefined") { resp.value = !!el.selected; } else { resp.value = true; } break; @@ -2177,34 +2178,34 @@ GeckoDriver.prototype.isElementSelected }; GeckoDriver.prototype.getElementSize = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); let rect = el.getBoundingClientRect(); resp.value = {width: rect.width, height: rect.height}; break; case Context.CONTENT: resp.value = yield this.listener.getElementSize(id); break; } }; GeckoDriver.prototype.getElementRect = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); let rect = el.getBoundingClientRect(); resp.value = { x: rect.x + win.pageXOffset, y: rect.y + win.pageYOffset, width: rect.width, height: rect.height }; break; @@ -2228,17 +2229,17 @@ GeckoDriver.prototype.sendKeysToElement if (!value) { throw new InvalidArgumentError(`Expected character sequence: ${value}`); } switch (this.context) { case Context.CHROME: let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); utils.sendKeysToElement( win, el, value, () => {}, e => { throw e; }, cmd.id, true /* ignore visibility check */); @@ -2294,17 +2295,17 @@ GeckoDriver.prototype.setTestName = func */ GeckoDriver.prototype.clearElement = function(cmd, resp) { let id = cmd.parameters.id; switch (this.context) { case Context.CHROME: // the selenium atom doesn't work here let win = this.getCurrentWindow(); - let el = this.curBrowser.elementManager.getKnownElement(id, win); + let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win }); if (el.nodeName == "textbox") { el.value = ""; } else if (el.nodeName == "checkbox") { el.checked = false; } break; case Context.CONTENT: @@ -2322,16 +2323,27 @@ GeckoDriver.prototype.clearElement = fun * * @return {Object.<string, number>} * A point containing X and Y coordinates as properties. */ GeckoDriver.prototype.getElementLocation = function(cmd, resp) { return this.listener.getElementLocation(cmd.parameters.id); }; +/** + * Switch to shadow root of the given host element. + * + * @param {string} id element id. + */ +GeckoDriver.prototype.switchToShadowRoot = function(cmd, resp) { + let id; + if (cmd.parameters) { id = cmd.parameters.id; } + yield this.listener.switchToShadowRoot(id); +}; + /** Add a cookie to the document. */ GeckoDriver.prototype.addCookie = function(cmd, resp) { yield this.listener.addCookie({cookie: cmd.parameters.cookie}); }; /** * Get all the cookies for the current domain. * @@ -3014,16 +3026,17 @@ GeckoDriver.prototype.commands = { "getChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles, "getCurrentWindowHandles": GeckoDriver.prototype.getWindowHandles, // Selenium 2 compat "getWindows": GeckoDriver.prototype.getWindowHandles, // deprecated "getWindowPosition": GeckoDriver.prototype.getWindowPosition, "setWindowPosition": GeckoDriver.prototype.setWindowPosition, "getActiveFrame": GeckoDriver.prototype.getActiveFrame, "switchToFrame": GeckoDriver.prototype.switchToFrame, "switchToWindow": GeckoDriver.prototype.switchToWindow, + "switchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot, "deleteSession": GeckoDriver.prototype.deleteSession, "importScript": GeckoDriver.prototype.importScript, "clearImportedScripts": GeckoDriver.prototype.clearImportedScripts, "getAppCacheStatus": GeckoDriver.prototype.getAppCacheStatus, "close": GeckoDriver.prototype.close, "closeWindow": GeckoDriver.prototype.close, // deprecated "closeChromeWindow": GeckoDriver.prototype.closeChromeWindow, "setTestName": GeckoDriver.prototype.setTestName,
--- a/testing/marionette/driver/marionette_driver/marionette.py +++ b/testing/marionette/driver/marionette_driver/marionette.py @@ -1266,16 +1266,31 @@ class Marionette(object): """ body = {"focus": focus} if isinstance(frame, HTMLElement): body["element"] = frame.id elif frame is not None: body["id"] = frame self._send_message("switchToFrame", body) + def switch_to_shadow_root(self, host=None): + """Switch the current context to the specified host's Shadow DOM. + Subsequent commands will operate in the context of the specified Shadow + DOM, if applicable. + + :param host: A reference to the host element containing Shadow DOM. + This can be an ``HTMLElement``. If you call + ``switch_to_shadow_root`` without an argument, it will switch to the + parent Shadow DOM or the top-level frame. + """ + body = {} + if isinstance(host, HTMLElement): + body["id"] = host.id + return self._send_message("switchToShadowRoot", body) + def get_url(self): """Get a string representing the current URL. On Desktop this returns a string representation of the URL of the current top level browsing context. This is equivalent to document.location.href. When in the context of the chrome, this returns the canonical
--- a/testing/marionette/elements.js +++ b/testing/marionette/elements.js @@ -239,48 +239,83 @@ ElementManager.prototype = { return id; }, /** * Retrieve element from its unique ID * * @param String id * The DOM reference ID - * @param nsIDOMWindow win - * The window that contains the element + * @param nsIDOMWindow, ShadowRoot container + * The window and an optional shadow root that contains the element * * @returns nsIDOMElement * Returns the element or throws Exception if not found */ - getKnownElement: function EM_getKnownElement(id, win) { + getKnownElement: function EM_getKnownElement(id, container) { let el = this.seenItems[id]; if (!el) { throw new JavaScriptError("Element has not been seen before. Id given was " + id); } try { el = el.get(); } catch(e) { el = null; delete this.seenItems[id]; } // use XPCNativeWrapper to compare elements; see bug 834266 - let wrappedWin = XPCNativeWrapper(win); + let wrappedFrame = XPCNativeWrapper(container.frame); + let wrappedShadowRoot; + if (container.shadowRoot) { + wrappedShadowRoot = XPCNativeWrapper(container.shadowRoot); + } + if (!el || - !(XPCNativeWrapper(el).ownerDocument == wrappedWin.document) || - (XPCNativeWrapper(el).compareDocumentPosition(wrappedWin.document.documentElement) & - DOCUMENT_POSITION_DISCONNECTED)) { + !(XPCNativeWrapper(el).ownerDocument == wrappedFrame.document) || + this.isDisconnected(XPCNativeWrapper(el), wrappedShadowRoot, + wrappedFrame)) { throw new StaleElementReferenceError( "The element reference is stale. Either the element " + "is no longer attached to the DOM or the page has been refreshed."); } return el; }, /** + * Check if the element is detached from the current frame as well as the + * optional shadow root (when inside a Shadow DOM context). + * @param nsIDOMElement el + * element to be checked + * @param ShadowRoot shadowRoot + * an optional shadow root containing an element + * @param nsIDOMWindow frame + * window that contains the element or the current host of the shadow + * root. + * @return {Boolean} a flag indicating that the element is disconnected + */ + isDisconnected: function EM_isDisconnected(el, shadowRoot, frame) { + if (shadowRoot && frame.ShadowRoot) { + if (el.compareDocumentPosition(shadowRoot) & + DOCUMENT_POSITION_DISCONNECTED) { + return true; + } + // Looking for next possible ShadowRoot ancestor + let parent = shadowRoot.host; + while (parent && !(parent instanceof frame.ShadowRoot)) { + parent = parent.parentNode; + } + return this.isDisconnected(shadowRoot.host, parent, frame); + } else { + return el.compareDocumentPosition(frame.document.documentElement) & + DOCUMENT_POSITION_DISCONNECTED; + } + }, + + /** * Convert values to primitives that can be transported over the * Marionette protocol. * * This function implements the marshaling algorithm defined in the * WebDriver specification: * * https://dvcs.w3.org/hg/webdriver/raw-file/tip/webdriver-spec.html#synchronous-javascript-execution * @@ -333,54 +368,54 @@ ElementManager.prototype = { return result; }, /** * Convert any ELEMENT references in 'args' to the actual elements * * @param object args * Arguments passed in by client - * @param nsIDOMWindow win - * The window that contains the elements + * @param nsIDOMWindow, ShadowRoot container + * The window and an optional shadow root that contains the element * * @returns object * Returns the objects passed in by the client, with the * reference IDs replaced by the actual elements. */ - convertWrappedArguments: function EM_convertWrappedArguments(args, win) { + convertWrappedArguments: function EM_convertWrappedArguments(args, container) { let converted; switch (typeof(args)) { case 'number': case 'string': case 'boolean': converted = args; break; case 'object': if (args == null) { converted = null; } else if (Object.prototype.toString.call(args) == '[object Array]') { converted = []; for (let i in args) { - converted.push(this.convertWrappedArguments(args[i], win)); + converted.push(this.convertWrappedArguments(args[i], container)); } } else if (((typeof(args[this.elementKey]) === 'string') && args.hasOwnProperty(this.elementKey)) || ((typeof(args[this.w3cElementKey]) === 'string') && args.hasOwnProperty(this.w3cElementKey))) { let elementUniqueIdentifier = args[this.w3cElementKey] ? args[this.w3cElementKey] : args[this.elementKey]; - converted = this.getKnownElement(elementUniqueIdentifier, win); + converted = this.getKnownElement(elementUniqueIdentifier, container); if (converted == null) { throw new WebDriverError(`Unknown element: ${elementUniqueIdentifier}`); } } else { converted = {}; for (let prop in args) { - converted[prop] = this.convertWrappedArguments(args[prop], win); + converted[prop] = this.convertWrappedArguments(args[prop], container); } } break; } return converted; }, /* @@ -412,18 +447,18 @@ ElementManager.prototype = { return namedArgs; }, /** * Find an element or elements starting at the document root or * given node, using the given search strategy. Search * will continue until the search timelimit has been reached. * - * @param nsIDOMWindow win - * The window to search in + * @param nsIDOMWindow, ShadowRoot container + * The window and an optional shadow root that contains the element * @param object values * The 'using' member of values will tell us which search * method to use. The 'value' member tells us the value we * are looking for. * If this object has an 'element' member, this will be used * as the start node instead of the document root * If this object has a 'time' member, this number will be * used to see if we have hit the search timelimit. @@ -433,25 +468,26 @@ ElementManager.prototype = { * @param function on_success * Callback used when operating is successful. * @param function on_error * Callback to invoke when an error occurs. * * @return nsIDOMElement or list of nsIDOMElements * Returns the element(s) by calling the on_success function. */ - find: function EM_find(win, values, searchTimeout, all, on_success, on_error, command_id) { + find: function EM_find(container, values, searchTimeout, all, on_success, on_error, command_id) { let startTime = values.time ? values.time : new Date().getTime(); + let rootNode = container.shadowRoot || container.frame.document; let startNode = (values.element != undefined) ? - this.getKnownElement(values.element, win) : win.document; + this.getKnownElement(values.element, container) : rootNode; if (this.elementStrategies.indexOf(values.using) < 0) { throw new InvalidSelectorError("No such strategy: " + values.using); } - let found = all ? this.findElements(values.using, values.value, win.document, startNode) : - this.findElement(values.using, values.value, win.document, startNode); + let found = all ? this.findElements(values.using, values.value, rootNode, startNode) : + this.findElement(values.using, values.value, rootNode, startNode); let type = Object.prototype.toString.call(found); let isArrayLike = ((type == '[object Array]') || (type == '[object HTMLCollection]') || (type == '[object NodeList]')); if (found == null || (isArrayLike && found.length <= 0)) { if (!searchTimeout || new Date().getTime() - startTime > searchTimeout) { if (all) { on_success([], command_id); // findElements should return empty list } else { // Format message depending on strategy if necessary @@ -460,17 +496,17 @@ ElementManager.prototype = { message = "Unable to locate anonymous children"; } else if (values.using == ANON_ATTRIBUTE) { message = "Unable to locate anonymous element: " + JSON.stringify(values.value); } on_error(new NoSuchElementError(message), command_id); } } else { values.time = startTime; - this.timer.initWithCallback(this.find.bind(this, win, values, + this.timer.initWithCallback(this.find.bind(this, container, values, searchTimeout, all, on_success, on_error, command_id), 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT); } } else { if (isArrayLike) {
--- a/testing/marionette/listener.js +++ b/testing/marionette/listener.js @@ -30,19 +30,19 @@ loader.loadSubScript("chrome://marionett let marionetteLogObj = new MarionetteLogObj(); let isB2G = false; let marionetteTestName; let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); let listenerId = null; // unique ID of this listener -let curFrame = content; -let isRemoteBrowser = () => curFrame.contentWindow !== null; -let previousFrame = null; +let curContainer = { frame: content, shadowRoot: null }; +let isRemoteBrowser = () => curContainer.frame.contentWindow !== null; +let previousContainer = null; let elementManager = new ElementManager([]); let accessibility = new Accessibility(); let actions = new ActionChain(utils, checkForInterrupted); let importedScripts = null; // Contains the last file input element that was the target of // sendKeysToElement. let fileInputElement; @@ -79,19 +79,19 @@ let multiLast = {}; Cu.import("resource://gre/modules/Log.jsm"); let logger = Log.repository.getLogger("Marionette"); logger.info("loaded listener.js"); let modalHandler = function() { // This gets called on the system app only since it receives the mozbrowserprompt event sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true }); let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value; if (isLocal) { - previousFrame = curFrame; + previousContainer = curContainer; } - curFrame = content; + curContainer = { frame: content, shadowRoot: null }; }; /** * Called when listener is first started up. * The listener sends its unique window ID and its current URI to the actor. * If the actor returns an ID, we start the listeners. Otherwise, nothing happens. */ function registerSelf() { @@ -118,17 +118,17 @@ function registerSelf() { } } } function emitTouchEventForIFrame(message) { message = message.json; let identifier = actions.nextTouchId; - let domWindowUtils = curFrame. + let domWindowUtils = curContainer.frame. QueryInterface(Components.interfaces.nsIInterfaceRequestor). getInterface(Components.interfaces.nsIDOMWindowUtils); var ratio = domWindowUtils.screenPixelsPerCSSPixel; var typeForUtils; switch (message.type) { case 'touchstart': typeForUtils = domWindowUtils.TOUCH_CONTACT; @@ -207,16 +207,17 @@ let isElementEnabledFn = dispatch(isElem let getCurrentUrlFn = dispatch(getCurrentUrl); let findElementContentFn = dispatch(findElementContent); let findElementsContentFn = dispatch(findElementsContent); let isElementSelectedFn = dispatch(isElementSelected); let getElementLocationFn = dispatch(getElementLocation); let clearElementFn = dispatch(clearElement); let isElementDisplayedFn = dispatch(isElementDisplayed); let getElementValueOfCssPropertyFn = dispatch(getElementValueOfCssProperty); +let switchToShadowRootFn = dispatch(switchToShadowRoot); /** * Start all message listeners */ function startListeners() { addMessageListenerId("Marionette:receiveFiles", receiveFiles); addMessageListenerId("Marionette:newSession", newSession); addMessageListenerId("Marionette:executeScript", executeScript); @@ -246,16 +247,17 @@ function startListeners() { addMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated addMessageListenerId("Marionette:getElementRect", getElementRectFn); addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn); addMessageListenerId("Marionette:isElementSelected", isElementSelectedFn); addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement); addMessageListenerId("Marionette:getElementLocation", getElementLocationFn); //deprecated addMessageListenerId("Marionette:clearElement", clearElementFn); addMessageListenerId("Marionette:switchToFrame", switchToFrame); + addMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn); addMessageListenerId("Marionette:deleteSession", deleteSession); addMessageListenerId("Marionette:sleepSession", sleepSession); addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult); addMessageListenerId("Marionette:importScript", importScript); addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus); addMessageListenerId("Marionette:setTestName", setTestName); addMessageListenerId("Marionette:takeScreenshot", takeScreenshot); addMessageListenerId("Marionette:addCookie", addCookie); @@ -351,34 +353,35 @@ function deleteSession(msg) { removeMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated removeMessageListenerId("Marionette:getElementRect", getElementRectFn); removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn); removeMessageListenerId("Marionette:isElementSelected", isElementSelectedFn); removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement); removeMessageListenerId("Marionette:getElementLocation", getElementLocationFn); removeMessageListenerId("Marionette:clearElement", clearElementFn); removeMessageListenerId("Marionette:switchToFrame", switchToFrame); + removeMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn); removeMessageListenerId("Marionette:deleteSession", deleteSession); removeMessageListenerId("Marionette:sleepSession", sleepSession); removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult); removeMessageListenerId("Marionette:importScript", importScript); removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus); removeMessageListenerId("Marionette:setTestName", setTestName); removeMessageListenerId("Marionette:takeScreenshot", takeScreenshot); removeMessageListenerId("Marionette:addCookie", addCookie); removeMessageListenerId("Marionette:getCookies", getCookies); removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies); removeMessageListenerId("Marionette:deleteCookie", deleteCookie); if (isB2G) { content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false); } elementManager.reset(); - // reset frame to the top-most frame - curFrame = content; - curFrame.focus(); + // reset container frame to the top-most frame + curContainer = { frame: content, shadowRoot: null }; + curContainer.frame.focus(); actions.touchIds = {}; } /* * Helper methods */ /** @@ -422,49 +425,49 @@ function sendError(err, cmdId) { sendToServer("Marionette:error", null, {error: err}, cmdId); } /** * Clear test values after completion of test */ function resetValues() { sandboxes = {}; - curFrame = content; + curContainer = { frame: content, shadowRoot: null }; actions.mouseEventsOnly = false; } /** * Dump a logline to stdout. Prepends logline with a timestamp. */ function dumpLog(logline) { dump(Date.now() + " Marionette: " + logline); } /** * Check if our context was interrupted */ function wasInterrupted() { - if (previousFrame) { + if (previousContainer) { let element = content.document.elementFromPoint((content.innerWidth/2), (content.innerHeight/2)); if (element.id.indexOf("modal-dialog") == -1) { return true; } else { return false; } } return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value; } function checkForInterrupted() { if (wasInterrupted()) { - if (previousFrame) { - //if previousFrame is set, then we're in a single process environment - curFrame = actions.frame = previousFrame; - previousFrame = null; + if (previousContainer) { + // if previousContainer is set, then we're in a single process environment + curContainer = actions.container = previousContainer; + previousContainer = null; } else { //else we're in OOP environment, so we'll switch to the original OOP frame sendSyncMessage("Marionette:switchToModalOrigin"); } sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true }); } } @@ -507,21 +510,21 @@ function createExecuteContentSandbox(win sandbox[fn] = mn[fn].bind(mn); } else { sandbox[fn] = mn[fn]; } }); sandbox.asyncComplete = (obj, id) => { if (id == asyncTestCommandId) { - curFrame.removeEventListener("unload", onunload, false); - curFrame.clearTimeout(asyncTestTimeoutId); + curContainer.frame.removeEventListener("unload", onunload, false); + curContainer.frame.clearTimeout(asyncTestTimeoutId); if (inactivityTimeoutId != null) { - curFrame.clearTimeout(inactivityTimeoutId); + curContainer.frame.clearTimeout(inactivityTimeoutId); } sendSyncMessage("Marionette:shareData", {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); marionetteLogObj.clearLogs(); if (error.isError(obj)) { sendError(obj, id); @@ -557,36 +560,36 @@ function createExecuteContentSandbox(win /** * Execute the given script either as a function body (executeScript) * or directly (for mochitest like JS Marionette tests). */ function executeScript(msg, directInject) { // Set up inactivity timeout. if (msg.json.inactivityTimeout) { let setTimer = function() { - inactivityTimeoutId = curFrame.setTimeout(function() { + inactivityTimeoutId = curContainer.frame.setTimeout(function() { sendError(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId); }, msg.json.inactivityTimeout); }; setTimer(); heartbeatCallback = function() { - curFrame.clearTimeout(inactivityTimeoutId); + curContainer.frame.clearTimeout(inactivityTimeoutId); setTimer(); }; } asyncTestCommandId = msg.json.command_id; let script = msg.json.script; sandboxName = msg.json.sandboxName; if (msg.json.newSandbox || !(sandboxName in sandboxes) || - (sandboxes[sandboxName].window != curFrame)) { - createExecuteContentSandbox(curFrame, msg.json.timeout); + (sandboxes[sandboxName].window != curContainer.frame)) { + createExecuteContentSandbox(curContainer.frame, msg.json.timeout); if (!sandboxes[sandboxName]) { sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId); return; } } else { sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId; } @@ -612,17 +615,17 @@ function executeScript(msg, directInject } else { sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId); } } else { try { sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments( - msg.json.args, curFrame), sandbox, { wrapReflectors: true }); + msg.json.args, curContainer), sandbox, { wrapReflectors: true }); } catch (e) { sendError(e, asyncTestCommandId); return; } script = "let __marionetteFunc = function(){" + script + "};" + "__marionetteFunc.apply(null, __marionetteParams);"; if (importedScripts.exists()) { @@ -706,73 +709,73 @@ function executeJSScript(msg) { * For executeJSScript, it will return a message only when the finish() method is called. * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1] * method is called, or if it times out. */ function executeWithCallback(msg, useFinish) { // Set up inactivity timeout. if (msg.json.inactivityTimeout) { let setTimer = function() { - inactivityTimeoutId = curFrame.setTimeout(function() { + inactivityTimeoutId = curContainer.frame.setTimeout(function() { sandbox.asyncComplete(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId); }, msg.json.inactivityTimeout); }; setTimer(); heartbeatCallback = function() { - curFrame.clearTimeout(inactivityTimeoutId); + curContainer.frame.clearTimeout(inactivityTimeoutId); setTimer(); }; } let script = msg.json.script; asyncTestCommandId = msg.json.command_id; sandboxName = msg.json.sandboxName; onunload = function() { sendError(new JavaScriptError("unload was called"), asyncTestCommandId); }; - curFrame.addEventListener("unload", onunload, false); + curContainer.frame.addEventListener("unload", onunload, false); if (msg.json.newSandbox || !(sandboxName in sandboxes) || - (sandboxes[sandboxName].window != curFrame)) { - createExecuteContentSandbox(curFrame, msg.json.timeout); + (sandboxes[sandboxName].window != curContainer.frame)) { + createExecuteContentSandbox(curContainer.frame, msg.json.timeout); if (!sandboxes[sandboxName]) { sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId); return; } } else { sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId; } let sandbox = sandboxes[sandboxName]; sandbox.tag = script; - asyncTestTimeoutId = curFrame.setTimeout(function() { + asyncTestTimeoutId = curContainer.frame.setTimeout(function() { sandbox.asyncComplete(new ScriptTimeoutError("timed out"), asyncTestCommandId); }, msg.json.timeout); - originalOnError = curFrame.onerror; - curFrame.onerror = function errHandler(msg, url, line) { + originalOnError = curContainer.frame.onerror; + curContainer.frame.onerror = function errHandler(msg, url, line) { sandbox.asyncComplete(new JavaScriptError(msg + "@" + url + ", line " + line), asyncTestCommandId); - curFrame.onerror = originalOnError; + curContainer.frame.onerror = originalOnError; }; let scriptSrc; if (useFinish) { if (msg.json.timeout == null || msg.json.timeout == 0) { sendError(new TimeoutError("Please set a timeout"), asyncTestCommandId); } scriptSrc = script; } else { try { sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments( - msg.json.args, curFrame), sandbox, { wrapReflectors: true }); + msg.json.args, curContainer), sandbox, { wrapReflectors: true }); } catch (e) { sendError(e, asyncTestCommandId); return; } scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" + "let __marionetteFunc = function() { " + script + "};" + "__marionetteFunc.apply(null, __marionetteParams); "; @@ -802,17 +805,17 @@ function executeWithCallback(msg, useFin /** * This function creates a touch event given a touch type and a touch */ function emitTouchEvent(type, touch) { if (!wasInterrupted()) { let loggingInfo = "emitting Touch event of type " + type + " to element with id: " + touch.target.id + " and tag name: " + touch.target.tagName + " at coordinates (" + touch.clientX + ", " + touch.clientY + ") relative to the viewport"; dumpLog(loggingInfo); - var docShell = curFrame.document.defaultView. + var docShell = curContainer.frame.document.defaultView. QueryInterface(Components.interfaces.nsIInterfaceRequestor). getInterface(Components.interfaces.nsIWebNavigation). QueryInterface(Components.interfaces.nsIDocShell); if (docShell.asyncPanZoomEnabled && actions.scrolling) { // if we're in APZ and we're scrolling, we must use injectTouchEvent to dispatch our touchmove events let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId"); // only call emitTouchEventForIFrame if we're inside an iframe. if (index != null) { @@ -828,17 +831,17 @@ function emitTouchEvent(type, touch) { // we get here if we're not in asyncPacZoomEnabled land, or if we're the main process /* Disabled per bug 888303 marionetteLogObj.log(loggingInfo, "TRACE"); sendSyncMessage("Marionette:shareData", {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); marionetteLogObj.clearLogs(); */ - let domWindowUtils = curFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils); + let domWindowUtils = curContainer.frame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils); domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0); } } /** * This function generates a pair of coordinates relative to the viewport given a * target element and coordinates relative to that element's top-left corner. * @param 'x', and 'y' are the relative to the target. @@ -861,16 +864,17 @@ function coordinates(target, x, y) { /** * This function returns true if the given coordinates are in the viewport. * @param 'x', and 'y' are the coordinates relative to the target. * If they are not specified, then the center of the target is used. */ function elementInViewport(el, x, y) { let c = coordinates(el, x, y); + let curFrame = curContainer.frame; let viewPort = {top: curFrame.pageYOffset, left: curFrame.pageXOffset, bottom: (curFrame.pageYOffset + curFrame.innerHeight), right:(curFrame.pageXOffset + curFrame.innerWidth)}; return (viewPort.left <= c.x + curFrame.pageXOffset && c.x + curFrame.pageXOffset <= viewPort.right && viewPort.top <= c.y + curFrame.pageYOffset && c.y + curFrame.pageYOffset <= viewPort.bottom); @@ -912,27 +916,27 @@ function checkVisible(el, x, y) { /** * Function that perform a single tap */ function singleTap(msg) { let command_id = msg.json.command_id; try { - let el = elementManager.getKnownElement(msg.json.id, curFrame); + let el = elementManager.getKnownElement(msg.json.id, curContainer); let acc = accessibility.getAccessibleObject(el, true); // after this block, the element will be scrolled into view let visible = checkVisible(el, msg.json.corx, msg.json.cory); checkVisibleAccessibility(acc, visible); if (!visible) { sendError(new ElementNotVisibleError("Element is not currently visible and may not be manipulated"), command_id); return; } checkActionableAccessibility(acc); - if (!curFrame.document.createTouch) { + if (!curContainer.frame.document.createTouch) { actions.mouseEventsOnly = true; } let c = coordinates(el, msg.json.corx, msg.json.cory); if (!actions.mouseEventsOnly) { let touchId = actions.nextTouchId++; let touch = createATouch(el, c.x, c.y, touchId); emitTouchEvent('touchstart', touch); emitTouchEvent('touchend', touch); @@ -952,17 +956,17 @@ function singleTap(msg) { * @param Boolean enabled element's enabled state */ function checkEnabledAccessibility(accesible, element, enabled) { if (!accesible) { return; } let disabledAccessibility = accessibility.matchState( accesible, 'STATE_UNAVAILABLE'); - let explorable = curFrame.document.defaultView.getComputedStyle( + let explorable = curContainer.frame.document.defaultView.getComputedStyle( element, null).getPropertyValue('pointer-events') !== 'none'; let message; if (!explorable && !disabledAccessibility) { message = 'Element is enabled but is not explorable via the ' + 'accessibility API'; } else if (enabled && disabledAccessibility) { message = 'Element is enabled but disabled via the accessibility API'; @@ -1071,17 +1075,17 @@ function actionChain(msg) { let touchProvider = {}; touchProvider.createATouch = createATouch; touchProvider.emitTouchEvent = emitTouchEvent; try { actions.dispatchActions( args, touchId, - curFrame, + curContainer, elementManager, callbacks, touchProvider); } catch (e) { sendError(e, command_id); } } @@ -1152,32 +1156,32 @@ function setDispatch(batches, touches, c batchIndex++; // loop through the batch for (let i = 0; i < batch.length; i++) { pack = batch[i]; touchId = pack[0]; command = pack[1]; switch (command) { case 'press': - el = elementManager.getKnownElement(pack[2], curFrame); + el = elementManager.getKnownElement(pack[2], curContainer); c = coordinates(el, pack[3], pack[4]); touch = createATouch(el, c.x, c.y, touchId); multiLast[touchId] = touch; touches.push(touch); emitMultiEvents('touchstart', touch, touches); break; case 'release': touch = multiLast[touchId]; // the index of the previous touch for the finger may change in the touches array touchIndex = touches.indexOf(touch); touches.splice(touchIndex, 1); emitMultiEvents('touchend', touch, touches); break; case 'move': - el = elementManager.getKnownElement(pack[2], curFrame); + el = elementManager.getKnownElement(pack[2], curContainer); c = coordinates(el); touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId); touchIndex = touches.indexOf(lastTouch); touches[touchIndex] = touch; multiLast[touchId] = touch; emitMultiEvents('touchmove', touch, touches); break; case 'moveByOffset': @@ -1221,17 +1225,17 @@ function setDispatch(batches, touches, c */ function multiAction(msg) { let command_id = msg.json.command_id; let args = msg.json.value; // maxlen is the longest action chain for one finger let maxlen = msg.json.maxlen; try { // unwrap the original nested array - let commandArray = elementManager.convertWrappedArguments(args, curFrame); + let commandArray = elementManager.convertWrappedArguments(args, curContainer); let concurrentEvent = []; let temp; for (let i = 0; i < maxlen; i++) { let row = []; for (let j = 0; j < commandArray.length; j++) { if (commandArray[j][i] != undefined) { // add finger id to the front of each action, i.e. [finger_id, action, element] temp = commandArray[j][i]; @@ -1265,28 +1269,29 @@ function pollForReadyState(msg, start, c } let end = null; function checkLoad() { navTimer.cancel(); end = new Date().getTime(); let aboutErrorRegex = /about:.+(error)\?/; let elapse = end - start; + let doc = curContainer.frame.document; if (pageTimeout == null || elapse <= pageTimeout) { - if (curFrame.document.readyState == "complete") { + if (doc.readyState == "complete") { callback(); sendOk(command_id); - } else if (curFrame.document.readyState == "interactive" && - aboutErrorRegex.exec(curFrame.document.baseURI) && - !curFrame.document.baseURI.startsWith(url)) { + } else if (doc.readyState == "interactive" && + aboutErrorRegex.exec(doc.baseURI) && + !doc.baseURI.startsWith(url)) { // We have reached an error url without requesting it. callback(); sendError(new UnknownError("Error loading page"), command_id); - } else if (curFrame.document.readyState == "interactive" && - curFrame.document.baseURI.startsWith("about:")) { + } else if (doc.readyState == "interactive" && + doc.baseURI.startsWith("about:")) { callback(); sendOk(command_id); } else { navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); } } else { callback(); sendError(new TimeoutError("Error loading page, timed out (checkLoad)"), command_id); @@ -1304,33 +1309,33 @@ function pollForReadyState(msg, start, c function get(msg) { let start = new Date().getTime(); // Prevent DOMContentLoaded events from frames from invoking this // code, unless the event is coming from the frame associated with // the current window (i.e. someone has used switch_to_frame). onDOMContentLoaded = function onDOMContentLoaded(event) { if (!event.originalTarget.defaultView.frameElement || - event.originalTarget.defaultView.frameElement == curFrame.frameElement) { + event.originalTarget.defaultView.frameElement == curContainer.frame.frameElement) { pollForReadyState(msg, start, () => { removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); onDOMContentLoaded = null; }); } }; function timerFunc() { removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); sendError(new TimeoutError("Error loading page, timed out (onDOMContentLoaded)"), msg.json.command_id); } if (msg.json.pageTimeout != null) { navTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT); } addEventListener("DOMContentLoaded", onDOMContentLoaded, false); - curFrame.location = msg.json.url; + curContainer.frame.location = msg.json.url; } /** * Cancel the polling and remove the event listener associated with a current * navigation request in case we're interupted by an onbeforeunload handler * and navigation doesn't complete. */ function cancelRequest() { @@ -1340,118 +1345,118 @@ function cancelRequest() { } } /** * Get URL of the top-level browsing context. */ function getCurrentUrl(isB2G) { if (isB2G) { - return curFrame.location.href; + return curContainer.frame.location.href; } else { return content.location.href; } } /** * Get the title of the current browsing context. */ function getTitle() { - return curFrame.top.document.title; + return curContainer.frame.top.document.title; } /** * Get source of the current browsing context's DOM. */ function getPageSource() { - let XMLSerializer = curFrame.XMLSerializer; - let source = new XMLSerializer().serializeToString(curFrame.document); + let XMLSerializer = curContainer.frame.XMLSerializer; + let source = new XMLSerializer().serializeToString(curContainer.frame.document); return source; } /** * Cause the browser to traverse one step backward in the joint history * of the current top-level browsing context. */ function goBack() { - curFrame.history.back(); + curContainer.frame.history.back(); } /** * Go forward in history */ function goForward(msg) { - curFrame.history.forward(); + curContainer.frame.history.forward(); sendOk(msg.json.command_id); } /** * Refresh the page */ function refresh(msg) { let command_id = msg.json.command_id; - curFrame.location.reload(true); + curContainer.frame.location.reload(true); let listen = function() { removeEventListener("DOMContentLoaded", arguments.callee, false); sendOk(command_id); }; addEventListener("DOMContentLoaded", listen, false); } /** * Find an element in the current browsing context's document using the * given search strategy. */ function findElementContent(opts) { return new Promise((resolve, reject) => { elementManager.find( - curFrame, + curContainer, opts, opts.searchTimeout, false /* all */, resolve, reject); }); } /** * Find elements in the current browsing context's document using the * given search strategy. */ function findElementsContent(opts) { return new Promise((resolve, reject) => { elementManager.find( - curFrame, + curContainer, opts, opts.searchTimeout, true /* all */, resolve, reject); }); } /** * Find and return the active element on the page. * * @return {WebElement} * Reference to web element. */ function getActiveElement() { - let el = curFrame.document.activeElement; + let el = curContainer.frame.document.activeElement; return elementManager.addToKnownElements(el); } /** * Send click event to element. * * @param {WebElement} id * Reference to the web element to click. */ function clickElement(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); let acc = accessibility.getAccessibleObject(el, true); let visible = checkVisible(el); checkVisibleAccessibility(acc, visible); if (!visible) { throw new ElementNotVisibleError("Element is not visible"); } checkActionableAccessibility(acc); if (utils.isElementEnabled(el)) { @@ -1469,56 +1474,56 @@ function clickElement(id) { * Reference to the web element to get the attribute of. * @param {string} name * Name of the attribute. * * @return {string} * The value of the attribute. */ function getElementAttribute(id, name) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); return utils.getElementAttribute(el, name); } /** * Get the text of this element. This includes text from child elements. * * @param {WebElement} id * Reference to web element. * * @return {string} * Text of element. */ function getElementText(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); return utils.getElementText(el); } /** * Get the tag name of an element. * * @param {WebElement} id * Reference to web element. * * @return {string} * Tag name of element. */ function getElementTagName(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); return el.tagName.toLowerCase(); } /** * Determine the element displayedness of the given web element. * * Also performs additional accessibility checks if enabled by session * capability. */ function isElementDisplayed(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); let displayed = utils.isElementDisplayed(el); checkVisibleAccessibility(accessibility.getAccessibleObject(el), displayed); return displayed; } /** * Retrieves the computed value of the given CSS property of the given * web element. @@ -1527,129 +1532,129 @@ function isElementDisplayed(id) { * Web element reference. * @param {String} prop * The CSS property to get. * * @return {String} * Effective value of the requested CSS property. */ function getElementValueOfCssProperty(id, prop) { - let el = elementManager.getKnownElement(id, curFrame); - let st = curFrame.document.defaultView.getComputedStyle(el, null); + let el = elementManager.getKnownElement(id, curContainer); + let st = curContainer.frame.document.defaultView.getComputedStyle(el, null); return st.getPropertyValue(prop); } /** * Get the size of the element. * * @param {WebElement} id * Web element reference. * * @return {Object.<string, number>} * The width/height dimensions of th element. */ function getElementSize(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); let clientRect = el.getBoundingClientRect(); return {width: clientRect.width, height: clientRect.height}; } /** * Get the size of the element. * * @param {WebElement} id * Reference to web element. * * @return {Object.<string, number>} * The x, y, width, and height properties of the element. */ function getElementRect(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); let clientRect = el.getBoundingClientRect(); return { - x: clientRect.x + curFrame.pageXOffset, - y: clientRect.y + curFrame.pageYOffset, + x: clientRect.x + curContainer.frame.pageXOffset, + y: clientRect.y + curContainer.frame.pageYOffset, width: clientRect.width, height: clientRect.height }; } /** * Check if element is enabled. * * @param {WebElement} id * Reference to web element. * * @return {boolean} * True if enabled, false otherwise. */ function isElementEnabled(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); let enabled = utils.isElementEnabled(el); checkEnabledAccessibility( accessibility.getAccessibleObject(el), el, enabled); return enabled; } /** * Determines if the referenced element is selected or not. * * This operation only makes sense on input elements of the Checkbox- * and Radio Button states, or option elements. */ function isElementSelected(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); let selected = utils.isElementSelected(el); checkSelectedAccessibility(accessibility.getAccessibleObject(el), selected); return selected; } /** * Send keys to element */ function sendKeysToElement(msg) { let command_id = msg.json.command_id; let val = msg.json.value; try { - let el = elementManager.getKnownElement(msg.json.id, curFrame); + let el = elementManager.getKnownElement(msg.json.id, curContainer); // Element should be actionable from the accessibility standpoint to be able // to send keys to it. checkActionableAccessibility(accessibility.getAccessibleObject(el, true)); if (el.type == "file") { let p = val.join(""); fileInputElement = el; // In e10s, we can only construct File objects in the parent process, // so pass the filename to driver.js, which in turn passes them back // to this frame script in receiveFiles. sendSyncMessage("Marionette:getFiles", {value: p, command_id: command_id}); } else { - utils.sendKeysToElement(curFrame, el, val, sendOk, sendError, command_id); + utils.sendKeysToElement(curContainer.frame, el, val, sendOk, sendError, command_id); } } catch (e) { sendError(e, command_id); } } /** * Get the element's top left-hand corner point. */ function getElementLocation(id) { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); let rect = el.getBoundingClientRect(); return {x: rect.left, y: rect.top}; } /** * Clear the text of an element. */ function clearElement(id) { try { - let el = elementManager.getKnownElement(id, curFrame); + let el = elementManager.getKnownElement(id, curContainer); if (el.type == "file") { el.value = null; } else { utils.clearElement(el); } } catch (e) {