author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Thu, 02 Jun 2016 11:56:07 +0200 | |
changeset 300068 | 34a8be4346a9231e472fc36b1d7c0531e0fbf7c5 |
parent 299919 | 7c0dce574b3f2d5e8f6e4e60caab5532f8e13fd6 (current diff) |
parent 300067 | 5c3ef07cd34c800c5032e45d90b1114b48cf7977 (diff) |
child 300069 | 379f4fff66d54866353e62f184d54cd399b7445d |
child 300087 | 58ac3bd12914f8d2d683176139e5630b4e32c2ca |
child 300173 | a4ec9c9adf18e33f71a9a0daa92939900e8501e7 |
push id | 30305 |
push user | cbook@mozilla.com |
push date | Thu, 02 Jun 2016 09:56:32 +0000 |
treeherder | mozilla-central@34a8be4346a9 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 49.0a1 |
first release with | nightly linux32
34a8be4346a9
/
49.0a1
/
20160602030220
/
files
nightly linux64
34a8be4346a9
/
49.0a1
/
20160602030220
/
files
nightly mac
34a8be4346a9
/
49.0a1
/
20160602030220
/
files
nightly win32
34a8be4346a9
/
49.0a1
/
20160602030220
/
files
nightly win64
34a8be4346a9
/
49.0a1
/
20160602030220
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
49.0a1
/
20160602030220
/
pushlog to previous
nightly linux64
49.0a1
/
20160602030220
/
pushlog to previous
nightly mac
49.0a1
/
20160602030220
/
pushlog to previous
nightly win32
49.0a1
/
20160602030220
/
pushlog to previous
nightly win64
49.0a1
/
20160602030220
/
pushlog to previous
|
--- a/accessible/base/MarkupMap.h +++ b/accessible/base/MarkupMap.h @@ -29,16 +29,20 @@ MARKUPMAP(aside, MARKUPMAP(blockquote, New_HyperText, roles::SECTION) MARKUPMAP(dd, New_HTMLDefinition, roles::DEFINITION) +MARKUPMAP(details, + New_HyperText, + roles::DETAILS) + MARKUPMAP(div, nullptr, roles::SECTION) MARKUPMAP(dl, New_HTMLList, roles::DEFINITION_LIST) @@ -308,16 +312,20 @@ MARKUPMAP(q, New_HyperText, 0) MARKUPMAP(section, New_HyperText, roles::SECTION, Attr(xmlroles, region)) +MARKUPMAP(summary, + New_HTMLSummary, + roles::SUMMARY) + MARKUPMAP(time, New_HyperText, 0, Attr(xmlroles, time), AttrFromDOM(datetime, datetime)) MARKUPMAP(td, New_HTMLTableHeaderCellIfScope,
--- a/accessible/base/Role.h +++ b/accessible/base/Role.h @@ -967,17 +967,27 @@ enum Role { RADIO_GROUP = 165, /** * A text container exposing brief amount of information. See related * TEXT_CONTAINER role. */ TEXT = 166, - LAST_ROLE = TEXT + /** + * The html:details element. + */ + DETAILS = 167, + + /** + * The html:summary element. + */ + SUMMARY = 168, + + LAST_ROLE = SUMMARY }; } // namespace role typedef enum mozilla::a11y::roles::Role role; } // namespace a11y } // namespace mozilla
--- a/accessible/base/RoleMap.h +++ b/accessible/base/RoleMap.h @@ -1347,8 +1347,24 @@ ROLE(RADIO_GROUP, ROLE(TEXT, "text", ATK_ROLE_STATIC, NSAccessibilityGroupRole, USE_ROLE_STRING, IA2_ROLE_TEXT_FRAME, eNameFromSubtreeIfReqRule) +ROLE(DETAILS, + "details", + ATK_ROLE_PANEL, + NSAccessibilityGroupRole, + ROLE_SYSTEM_GROUPING, + ROLE_SYSTEM_GROUPING, + eNoNameRule) + +ROLE(SUMMARY, + "summary", + ATK_ROLE_PUSH_BUTTON, + NSAccessibilityGroupRole, + ROLE_SYSTEM_PUSHBUTTON, + ROLE_SYSTEM_PUSHBUTTON, + eNameFromSubtreeRule) +
--- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -196,16 +196,19 @@ static Accessible* New_HTMLLabel(nsICont { return new HTMLLabelAccessible(aContent, aContext->Document()); } static Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext) { return new HTMLOutputAccessible(aContent, aContext->Document()); } static Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext) { return new HTMLProgressMeterAccessible(aContent, aContext->Document()); } +static Accessible* New_HTMLSummary(nsIContent* aContent, Accessible* aContext) + { return new HTMLSummaryAccessible(aContent, aContext->Document()); } + static Accessible* New_HTMLTableAccessible(nsIContent* aContent, Accessible* aContext) { return new HTMLTableAccessible(aContent, aContext->Document()); } static Accessible* New_HTMLTableRowAccessible(nsIContent* aContent, Accessible* aContext) { return new HTMLTableRowAccessible(aContent, aContext->Document()); }
--- a/accessible/html/HTMLElementAccessibles.cpp +++ b/accessible/html/HTMLElementAccessibles.cpp @@ -9,16 +9,18 @@ #include "nsAccUtils.h" #include "nsIPersistentProperties2.h" #include "nsTextEquivUtils.h" #include "Relation.h" #include "Role.h" #include "States.h" #include "mozilla/dom/HTMLLabelElement.h" +#include "mozilla/dom/HTMLDetailsElement.h" +#include "mozilla/dom/HTMLSummaryElement.h" using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // HTMLHRAccessible //////////////////////////////////////////////////////////////////////////////// role @@ -111,8 +113,92 @@ Relation HTMLOutputAccessible::RelationByType(RelationType aType) { Relation rel = AccessibleWrap::RelationByType(aType); if (aType == RelationType::CONTROLLED_BY) rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::_for)); return rel; } + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSummaryAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLSummaryAccessible:: + HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ + mGenericTypes |= eButton; +} + +uint8_t +HTMLSummaryAccessible::ActionCount() +{ + return 1; +} + +void +HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) +{ + if (aIndex != eAction_Click) { + return; + } + + dom::HTMLSummaryElement* summary = dom::HTMLSummaryElement::FromContent(mContent); + if (!summary) { + return; + } + + dom::HTMLDetailsElement* details = summary->GetDetails(); + if (!details) { + return; + } + + if (details->Open()) { + aName.AssignLiteral("collapse"); + } else { + aName.AssignLiteral("expand"); + } +} + +bool +HTMLSummaryAccessible::DoAction(uint8_t aIndex) +{ + if (aIndex != eAction_Click) + return false; + + DoCommand(); + return true; +} + +uint64_t +HTMLSummaryAccessible::NativeState() +{ + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + dom::HTMLSummaryElement* summary = dom::HTMLSummaryElement::FromContent(mContent); + if (!summary) { + return state; + } + + dom::HTMLDetailsElement* details = summary->GetDetails(); + if (!details) { + return state; + } + + if (details->Open()) { + state |= states::EXPANDED; + } else { + state |= states::COLLAPSED; + } + + return state; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSummaryAccessible: Widgets + +bool +HTMLSummaryAccessible::IsWidget() const +{ + return true; +}
--- a/accessible/html/HTMLElementAccessibles.h +++ b/accessible/html/HTMLElementAccessibles.h @@ -87,12 +87,35 @@ public: // Accessible virtual Relation RelationByType(RelationType aType) override; protected: virtual ~HTMLOutputAccessible() {} }; +/** + * Accessible for the HTML summary element. + */ +class HTMLSummaryAccessible : public HyperTextAccessibleWrap +{ + +public: + enum { eAction_Click = 0 }; + + HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual uint64_t NativeState() override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + + // Widgets + virtual bool IsWidget() const override; +}; + } // namespace a11y } // namespace mozilla #endif
--- a/accessible/interfaces/nsIAccessibleRole.idl +++ b/accessible/interfaces/nsIAccessibleRole.idl @@ -3,17 +3,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" /** * Defines cross platform (Gecko) roles. */ -[scriptable, uuid(da0c7824-147c-11e5-917c-60a44c717042)] +[scriptable, uuid(05a9f33f-dcfd-4e7b-b825-138ba784c1f5)] interface nsIAccessibleRole : nsISupports { /** * Used when accessible hans't strong defined role. */ const unsigned long ROLE_NOTHING = 0; /** @@ -960,9 +960,21 @@ interface nsIAccessibleRole : nsISupport */ const unsigned long ROLE_RADIO_GROUP = 165; /** * A text container exposing brief amount of information. See related * TEXT_CONTAINER role. */ const unsigned long ROLE_TEXT = 166; + + /** + * A text container exposing brief amount of information. See related + * DETAILS role. + */ + const unsigned long ROLE_DETAILS = 167; + + /** + * A text container exposing brief amount of information. See related + * SUMMARY role. + */ + const unsigned long ROLE_SUMMARY = 168; };
--- a/accessible/mac/mozAccessible.mm +++ b/accessible/mac/mozAccessible.mm @@ -939,16 +939,22 @@ ConvertToNSArray(nsTArray<ProxyAccessibl return @"AXApplicationAlert"; case roles::SEPARATOR: return @"AXContentSeparator"; case roles::PROPERTYPAGE: return @"AXTabPanel"; + case roles::DETAILS: + return @"AXDetails"; + + case roles::SUMMARY: + return @"AXSummary"; + default: break; } return nil; } struct RoleDescrMap
--- a/accessible/tests/mochitest/elm/test_HTMLSpec.html +++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html @@ -367,19 +367,45 @@ children: [ { role: ROLE_TEXT_LEAF }, // plain text { role: ROLE_TEXT_LEAF } // HTML:del text ] }; testElm("del_container", obj); ////////////////////////////////////////////////////////////////////////// - // HTML:details + // HTML:details with open state + + obj = { + role: ROLE_DETAILS, + children: [ + { + role: ROLE_SUMMARY, + states: STATE_EXPANDED, + actions: "collapse" + }, + { role: ROLE_PARAGRAPH } + ] + }; + testElm("details", obj); - ok(isAccessible("details"), "details element is not accessible"); + ////////////////////////////////////////////////////////////////////////// + // HTML:details with closed (default) state + + obj = { + role: ROLE_DETAILS, + children: [ + { + role: ROLE_SUMMARY, + states: STATE_COLLAPSED, + actions: "expand" + } + ] + }; + testElm("details_closed", obj); ////////////////////////////////////////////////////////////////////////// // HTML:dfn contained by paragraph obj = { role: ROLE_PARAGRAPH, textAttrs: { 0: { "font-style": "italic" }, @@ -1398,16 +1424,21 @@ <p id="del_container">normal<del>Removed</del></p> <details id="details" open="open"> <summary>Information</summary> <p>If your browser supports this element, it should allow you to expand and collapse these details.</p> </details> + <details id="details_closed"> + <summary>Information</summary> + <p>If your browser supports this element, it should allow you to expand and collapse these details.</p> + </details> + <p id="dfn_container"><dfn id="def-internet">The Internet</dfn> is a global system of interconnected networks that use the Internet Protocol Suite (TCP/IP) to serve billions of users worldwide.</p> <dialog id="dialog" open="true">This is a dialog</dialog> <div id="div">div</div>
--- a/accessible/tests/mochitest/role.js +++ b/accessible/tests/mochitest/role.js @@ -14,16 +14,17 @@ const ROLE_CHECKBUTTON = nsIAccessibleRo const ROLE_CHECK_MENU_ITEM = nsIAccessibleRole.ROLE_CHECK_MENU_ITEM; const ROLE_CHROME_WINDOW = nsIAccessibleRole.ROLE_CHROME_WINDOW; const ROLE_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX; const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST; const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION; const ROLE_COLUMNHEADER = nsIAccessibleRole.ROLE_COLUMNHEADER; const ROLE_DEFINITION = nsIAccessibleRole.ROLE_DEFINITION; const ROLE_DEFINITION_LIST = nsIAccessibleRole.ROLE_DEFINITION_LIST; +const ROLE_DETAILS = nsIAccessibleRole.ROLE_DETAILS; const ROLE_DIAGRAM = nsIAccessibleRole.ROLE_DIAGRAM; const ROLE_DIALOG = nsIAccessibleRole.ROLE_DIALOG; const ROLE_DOCUMENT = nsIAccessibleRole.ROLE_DOCUMENT; const ROLE_EMBEDDED_OBJECT = nsIAccessibleRole.ROLE_EMBEDDED_OBJECT; const ROLE_ENTRY = nsIAccessibleRole.ROLE_ENTRY; const ROLE_EQUATION = nsIAccessibleRole.ROLE_EQUATION; const ROLE_FIGURE = nsIAccessibleRole.ROLE_FIGURE; const ROLE_FOOTER = nsIAccessibleRole.ROLE_FOOTER; @@ -100,16 +101,17 @@ const ROLE_ROW = nsIAccessibleRole.ROLE_ const ROLE_ROWHEADER = nsIAccessibleRole.ROLE_ROWHEADER; const ROLE_SCROLLBAR = nsIAccessibleRole.ROLE_SCROLLBAR; const ROLE_SECTION = nsIAccessibleRole.ROLE_SECTION; const ROLE_SEPARATOR = nsIAccessibleRole.ROLE_SEPARATOR; const ROLE_SLIDER = nsIAccessibleRole.ROLE_SLIDER; const ROLE_SPINBUTTON = nsIAccessibleRole.ROLE_SPINBUTTON; const ROLE_STATICTEXT = nsIAccessibleRole.ROLE_STATICTEXT; const ROLE_STATUSBAR = nsIAccessibleRole.ROLE_STATUSBAR; +const ROLE_SUMMARY = nsIAccessibleRole.ROLE_SUMMARY; const ROLE_SWITCH = nsIAccessibleRole.ROLE_SWITCH; const ROLE_TABLE = nsIAccessibleRole.ROLE_TABLE; const ROLE_TERM = nsIAccessibleRole.ROLE_TERM; const ROLE_TEXT = nsIAccessibleRole.ROLE_TEXT; const ROLE_TEXT_CONTAINER = nsIAccessibleRole.ROLE_TEXT_CONTAINER; const ROLE_TEXT_LEAF = nsIAccessibleRole.ROLE_TEXT_LEAF; const ROLE_TOGGLE_BUTTON = nsIAccessibleRole.ROLE_TOGGLE_BUTTON; const ROLE_TOOLBAR = nsIAccessibleRole.ROLE_TOOLBAR;
--- a/b2g/components/PresentationRequestUIGlue.js +++ b/b2g/components/PresentationRequestUIGlue.js @@ -18,17 +18,21 @@ Cu.import("resource://gre/modules/Servic XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", "resource://gre/modules/SystemAppProxy.jsm"); function PresentationRequestUIGlue() { } PresentationRequestUIGlue.prototype = { sendRequest: function(aUrl, aSessionId, aDevice) { - let localDevice = aDevice.QueryInterface(Ci.nsIPresentationLocalDevice); + let localDevice; + try { + localDevice = aDevice.QueryInterface(Ci.nsIPresentationLocalDevice); + } catch (e) {} + if (localDevice) { return this.sendTo1UA(aUrl, aSessionId, localDevice.windowId); } else { return this.sendTo2UA(aUrl, aSessionId); } }, // For 1-UA scenario
--- a/browser/base/content/test/general/browser_audioTabIcon.js +++ b/browser/base/content/test/general/browser_audioTabIcon.js @@ -1,19 +1,24 @@ const PAGE = "https://example.com/browser/browser/base/content/test/general/file_mediaPlayback.html"; function* wait_for_tab_playing_event(tab, expectPlaying) { - yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => { - if (event.detail.changed.indexOf("soundplaying") >= 0) { - is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); - is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); - return true; - } - return false; - }); + if (tab.soundPlaying == expectPlaying) { + ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + return true; + } else { + yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => { + if (event.detail.changed.indexOf("soundplaying") >= 0) { + is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + is(tab.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(); });
--- a/browser/base/content/test/general/browser_selectpopup.js +++ b/browser/base/content/test/general/browser_selectpopup.js @@ -3,18 +3,18 @@ // This test checks that a <select> with an <optgroup> opens and can be navigated // in a child process. This is different than single-process as a <menulist> is used // to implement the dropdown list. const XHTML_DTD = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'; const PAGECONTENT = - "<html xmlns='http://www.w3.org/1999/xhtml'>" + - "<body onload='gChangeEvents = 0; document.body.firstChild.focus()'><select onchange='gChangeEvents++'>" + + "<html xmlns='http://www.w3.org/1999/xhtml'>" + + "<body onload='gChangeEvents = 0;gInputEvents = 0; document.body.firstChild.focus()'><select oninput='gInputEvents++' onchange='gChangeEvents++'>" + " <optgroup label='First Group'>" + " <option value='One'>One</option>" + " <option value='Two'>Two</option>" + " </optgroup>" + " <option value='Three'>Three</option>" + " <optgroup label='Second Group' disabled='true'>" + " <option value='Four'>Four</option>" + " <option value='Five'>Five</option>" + @@ -69,16 +69,23 @@ function hideSelectPopup(selectPopup, wi } else { EventUtils.synthesizeKey("KEY_Enter", { code: "Enter" }); } return popupHiddenPromise; } +function getInputEvents() +{ + return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { + return content.wrappedJSObject.gInputEvents; + }); +} + function getChangeEvents() { return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { return content.wrappedJSObject.gChangeEvents; }); } function doSelectTests(contentType, dtd) @@ -106,56 +113,62 @@ function doSelectTests(contentType, dtd) EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" }); is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3"); is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex"); EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" }); // On Windows, one can navigate on disabled menuitems let expectedIndex = isWindows ? 5 : 9; - + is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(expectedIndex), "Skip optgroup header and disabled items select item 7"); is(menulist.selectedIndex, isWindows ? 5 : 1, "Select or skip disabled item selectedIndex"); for (let i = 0; i < 10; i++) { is(menulist.getItemAtIndex(i).disabled, i >= 4 && i <= 7, "item " + i + " disabled") } EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" }); is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3 again"); is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex"); + is((yield getInputEvents()), 0, "Before closed - number of input events"); is((yield getChangeEvents()), 0, "Before closed - number of change events"); EventUtils.synthesizeKey("a", { accelKey: true }); yield ContentTask.spawn(gBrowser.selectedBrowser, { isWindows }, function(args) { Assert.equal(String(content.getSelection()), args.isWindows ? "Text" : "", "Select all while popup is open"); }); yield hideSelectPopup(selectPopup); is(menulist.selectedIndex, 3, "Item 3 still selected"); + is((yield getInputEvents()), 1, "After closed - number of input events"); is((yield getChangeEvents()), 1, "After closed - number of change events"); // Opening and closing the popup without changing the value should not fire a change event. yield openSelectPopup(selectPopup, true); yield hideSelectPopup(selectPopup, true); + is((yield getInputEvents()), 1, "Open and close with no change - number of input events"); is((yield getChangeEvents()), 1, "Open and close with no change - number of change events"); EventUtils.synthesizeKey("VK_TAB", { }); EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + is((yield getInputEvents()), 1, "Tab away from select with no change - number of input events"); is((yield getChangeEvents()), 1, "Tab away from select with no change - number of change events"); yield openSelectPopup(selectPopup, true); EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" }); yield hideSelectPopup(selectPopup, true); + is((yield getInputEvents()), isWindows ? 2 : 1, "Open and close with change - number of input events"); is((yield getChangeEvents()), isWindows ? 2 : 1, "Open and close with change - number of change events"); EventUtils.synthesizeKey("VK_TAB", { }); EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); + is((yield getInputEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of input events"); is((yield getChangeEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of change events"); is(selectPopup.lastChild.previousSibling.label, "Seven", "Spaces collapsed"); is(selectPopup.lastChild.label, "\xA0\xA0Eight\xA0\xA0", "Non-breaking spaces not collapsed"); yield BrowserTestUtils.removeTab(tab); } @@ -180,17 +193,17 @@ add_task(function*() { yield openSelectPopup(selectPopup, true, "#one"); yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { content.document.body.removeChild(content.document.getElementById("two")); }); // Wait a bit just to make sure the popup won't close. yield new Promise(resolve => setTimeout(resolve, 1000)); - + is(selectPopup.state, "open", "Different popup did not affect open popup"); yield hideSelectPopup(selectPopup); // Next, try it when the same <select> element than the one that is open is removed yield openSelectPopup(selectPopup, true, "#three"); let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
--- a/browser/base/content/test/plugins/browser_private_clicktoplay.js +++ b/browser/base/content/test/plugins/browser_private_clicktoplay.js @@ -13,21 +13,24 @@ var gPrivateBrowser = null; function pageLoad(aEvent) { // The plugin events are async dispatched and can come after the load event // This just allows the events to fire before we then go on to test the states executeSoon(gNextTest); gNextTest = null; } -function prepareTest(nextTest, url, window) { +function prepareTest(nextTest, url, browser) { gNextTest = nextTest; - if (!window) - window = gTestBrowser.contentWindow; - window.location = url; + if (!browser) + browser = gTestBrowser; + + ContentTask.spawn(browser, url, function(url){ + content.location = url; + }); } function finishTest() { clearAllPluginPermissions(); gTestBrowser.removeEventListener("load", pageLoad, true); gBrowser.removeCurrentTab(); if (gPrivateWindow) { gPrivateWindow.close(); @@ -38,17 +41,17 @@ function finishTest() { function createPrivateWindow(nextTest, url) { gPrivateWindow = OpenBrowserWindow({private: true}); ok(!!gPrivateWindow, "should have created a private window."); whenDelayedStartupFinished(gPrivateWindow, function() { gPrivateBrowser = gPrivateWindow.getBrowser().selectedBrowser; gPrivateBrowser.addEventListener("load", pageLoad, true); gNextTest = function() { - prepareTest(nextTest, url, gPrivateBrowser.contentWindow); + prepareTest(nextTest, url, gPrivateBrowser); }; }); } function whenDelayedStartupFinished(aWindow, aCallback) { Services.obs.addObserver(function observer(aSubject, aTopic) { if (aWindow == aSubject) { Services.obs.removeObserver(observer, aTopic); @@ -187,17 +190,17 @@ function test3c() { // Check the button status let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel, "Shown"); popupNotification.reshow(); promiseShown.then(() => { let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer; ok(buttonContainer.hidden, "Test 3c, Activated plugin in a private window should not have visible buttons"); - prepareTest(test3d, gHttpTestRoot + "plugin_two_types.html", gPrivateBrowser.contentWindow); + prepareTest(test3d, gHttpTestRoot + "plugin_two_types.html", gPrivateBrowser); }); }); } function test3d() { let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser); ok(popupNotification, "Test 3d, Should have a click-to-play notification");
--- a/browser/components/customizableui/CustomizableWidgets.jsm +++ b/browser/components/customizableui/CustomizableWidgets.jsm @@ -1141,16 +1141,17 @@ if (Services.prefs.getBoolPref("privacy. ContextualIdentityService.getIdentities().forEach(identity => { let bundle = doc.getElementById("bundle_browser"); let label = bundle.getString(identity.label); let item = doc.createElementNS(kNSXUL, "toolbarbutton"); item.setAttribute("label", label); item.setAttribute("usercontextid", identity.userContextId); item.setAttribute("class", "subviewbutton"); + item.setAttribute("image", identity.icon); fragment.appendChild(item); }); items.appendChild(fragment); } }); }
--- a/browser/components/migration/MigrationUtils.jsm +++ b/browser/components/migration/MigrationUtils.jsm @@ -46,50 +46,16 @@ function getMigrationBundle() { if (!gMigrationBundle) { gMigrationBundle = Services.strings.createBundle( "chrome://browser/locale/migration/migration.properties"); } return gMigrationBundle; } /** - * Figure out what is the default browser, and if there is a migrator - * for it, return that migrator's internal name. - * For the time being, the "internal name" of a migrator is its contract-id - * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie), - * but it will soon be exposed properly. - */ -function getMigratorKeyForDefaultBrowser() { - // Canary uses the same description as Chrome so we can't distinguish them. - const APP_DESC_TO_KEY = { - "Internet Explorer": "ie", - "Safari": "safari", - "Firefox": "firefox", - "Google Chrome": "chrome", // Windows, Linux - "Chrome": "chrome", // OS X - "Chromium": "chromium", // Windows, OS X - "Chromium Web Browser": "chromium", // Linux - "360\u5b89\u5168\u6d4f\u89c8\u5668": "360se", - }; - - let browserDesc = ""; - try { - let browserDesc = - Cc["@mozilla.org/uriloader/external-protocol-service;1"]. - getService(Ci.nsIExternalProtocolService). - getApplicationDescription("http"); - return APP_DESC_TO_KEY[browserDesc] || ""; - } - catch(ex) { - Cu.reportError("Could not detect default browser: " + ex); - } - return ""; -} - -/** * Shared prototype for migrators, implementing nsIBrowserProfileMigrator. * * To implement a migrator: * 1. Import this module. * 2. Create the prototype for the migrator, extending MigratorPrototype. * Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype); * 3. Set classDescription, contractID and classID for your migrator, and set * NSGetFactory appropriately. @@ -531,16 +497,50 @@ this.MigrationUtils = Object.freeze({ this._migrators.set(aKey, migrator); } try { return migrator && migrator.sourceExists ? migrator : null; } catch (ex) { Cu.reportError(ex); return null } }, + /** + * Figure out what is the default browser, and if there is a migrator + * for it, return that migrator's internal name. + * For the time being, the "internal name" of a migrator is its contract-id + * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie), + * but it will soon be exposed properly. + */ + getMigratorKeyForDefaultBrowser() { + // Canary uses the same description as Chrome so we can't distinguish them. + const APP_DESC_TO_KEY = { + "Internet Explorer": "ie", + "Safari": "safari", + "Firefox": "firefox", + "Google Chrome": "chrome", // Windows, Linux + "Chrome": "chrome", // OS X + "Chromium": "chromium", // Windows, OS X + "Chromium Web Browser": "chromium", // Linux + "360\u5b89\u5168\u6d4f\u89c8\u5668": "360se", + }; + + let browserDesc = ""; + try { + let browserDesc = + Cc["@mozilla.org/uriloader/external-protocol-service;1"]. + getService(Ci.nsIExternalProtocolService). + getApplicationDescription("http"); + return APP_DESC_TO_KEY[browserDesc] || ""; + } + catch(ex) { + Cu.reportError("Could not detect default browser: " + ex); + } + return ""; + }, + // Whether or not we're in the process of startup migration get isStartupMigration() { return gProfileStartup != null; }, /** * In the case of startup migration, this is set to the nsIProfileStartup * instance passed to ProfileMigrator's migrate. @@ -671,17 +671,17 @@ this.MigrationUtils = Object.freeze({ this.finishMigration(); throw new Error("startMigration was asked to open auto-migrate from " + "a non-existent source: " + aMigratorKey); } migratorKey = aMigratorKey; skipSourcePage = true; } else { - let defaultBrowserKey = getMigratorKeyForDefaultBrowser(); + let defaultBrowserKey = this.getMigratorKeyForDefaultBrowser(); if (defaultBrowserKey) { migrator = this.getMigrator(defaultBrowserKey); if (migrator) migratorKey = defaultBrowserKey; } } if (!migrator) {
--- a/browser/components/migration/content/migration.js +++ b/browser/components/migration/content/migration.js @@ -29,16 +29,17 @@ var MigrationWizard = { os.addObserver(this, "Migration:ItemError", false); os.addObserver(this, "Migration:Ended", false); this._wiz = document.documentElement; let args = window.arguments; let entryPointId = args[0] || MigrationUtils.MIGRATION_ENTRYPOINT_UNKNOWN; Services.telemetry.getHistogramById("FX_MIGRATION_ENTRY_POINT").add(entryPointId); + this.isInitialMigration = entryPointId == MigrationUtils.MIGRATION_ENTRYPOINT_FIRSTRUN; if (args.length > 1) { this._source = args[1]; this._migrator = args[2] instanceof kIMig ? args[2] : null; this._autoMigrate = args[3].QueryInterface(kIPStartup); this._skipImportSourcePage = args[4]; if (this._migrator && args[5]) { let sourceProfiles = this._migrator.sourceProfiles; @@ -80,31 +81,42 @@ var MigrationWizard = { document.getElementById("closeSourceBrowser").style.visibility = visibility; } this._wiz.canRewind = false; var selectedMigrator = null; // Figure out what source apps are are available to import from: var group = document.getElementById("importSourceGroup"); + var availableMigratorCount = 0; for (var i = 0; i < group.childNodes.length; ++i) { var migratorKey = group.childNodes[i].id; if (migratorKey != "nothing") { var migrator = MigrationUtils.getMigrator(migratorKey); if (migrator) { // Save this as the first selectable item, if we don't already have // one, or if it is the migrator that was passed to us. if (!selectedMigrator || this._source == migratorKey) selectedMigrator = group.childNodes[i]; + availableMigratorCount++; } else { // Hide this option group.childNodes[i].hidden = true; } } } + if (this.isInitialMigration) { + Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_BROWSER_COUNT") + .add(availableMigratorCount); + let defaultBrowser = MigrationUtils.getMigratorKeyForDefaultBrowser(); + // This will record 0 for unknown default browser IDs. + defaultBrowser = MigrationUtils.getSourceIdForTelemetry(defaultBrowser); + Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_EXISTING_DEFAULT_BROWSER") + .add(defaultBrowser); + } group.addEventListener("command", toggleCloseBrowserWarning); if (selectedMigrator) { group.selectedItem = selectedMigrator; toggleCloseBrowserWarning(); } else { // We didn't find a migrator, notify the user
--- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -117,16 +117,21 @@ XPCOMUtils.defineLazyModuleGetter(this, XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch", "resource:///modules/ContentSearch.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"); if (AppConstants.MOZ_CRASHREPORTER) { XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter", "resource:///modules/ContentCrashHandlers.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit", + "resource://gre/modules/CrashSubmit.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); + } XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() { return Services.strings.createBundle('chrome://branding/locale/brand.properties'); }); XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() { return Services.strings.createBundle('chrome://browser/locale/browser.properties'); @@ -821,16 +826,70 @@ BrowserGlue.prototype = { let acceptableAge = Services.prefs.getIntPref("app.update.checkInstallTime.days") * millisecondsIn24Hours; if (buildDate + acceptableAge < today) { Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService).checkForBackgroundUpdates(); } } }, + checkForPendingCrashReports: function() { + // We don't process crash reports older than 28 days, so don't bother submitting them + const PENDING_CRASH_REPORT_DAYS = 28; + if (AppConstants.MOZ_CRASHREPORTER) { + let dateLimit = new Date(); + dateLimit.setDate(dateLimit.getDate() - PENDING_CRASH_REPORT_DAYS); + CrashSubmit.pendingIDsAsync(dateLimit).then( + function onSuccess(ids) { + let count = ids.length; + if (count) { + let win = RecentWindow.getMostRecentBrowserWindow(); + let nb = win.document.getElementById("global-notificationbox"); + let notification = nb.getNotificationWithValue("pending-crash-reports"); + if (notification) { + return; + } + let buttons = [ + { + label: win.gNavigatorBundle.getString("pendingCrashReports.submitAll"), + callback: function() { + ids.forEach(function(id) { + CrashSubmit.submit(id, {extraExtraKeyVals: {"SubmittedFromInfobar": true}}); + }); + } + }, + { + label: win.gNavigatorBundle.getString("pendingCrashReports.ignoreAll"), + callback: function() { + ids.forEach(function(id) { + CrashSubmit.ignore(id); + }); + } + }, + { + label: win.gNavigatorBundle.getString("pendingCrashReports.viewAll"), + callback: function() { + win.openUILinkIn("about:crashes", "tab"); + } + } + ]; + nb.appendNotification(PluralForm.get(count, + win.gNavigatorBundle.getString("pendingCrashReports.label")).replace("#1", count), + "pending-crash-reports", + "chrome://browser/skin/tab-crashed.svg", + nb.PRIORITY_INFO_HIGH, buttons); + } + }, + function onError(err) { + Cu.reportError(err); + } + ); + } + }, + _onSafeModeRestart: function BG_onSafeModeRestart() { // prompt the user to confirm let strings = gBrowserBundle; let promptTitle = strings.GetStringFromName("safeModeRestartPromptTitle"); let promptMessage = strings.GetStringFromName("safeModeRestartPromptMessage"); let restartText = strings.GetStringFromName("safeModeRestartButton"); let buttonFlags = (Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING) + @@ -1071,16 +1130,20 @@ BrowserGlue.prototype = { if (removalSuccessful && uninstalledValue == "True") { this._resetProfileNotification("uninstall"); } } } this._checkForOldBuildUpdates(); + if ("release" != AppConstants.MOZ_UPDATE_CHANNEL) { + this.checkForPendingCrashReports(); + } + this._firstWindowTelemetry(aWindow); this._firstWindowLoaded(); }, /** * Application shutdown handler. */ _onQuitApplicationGranted: function () {
new file mode 100644 --- /dev/null +++ b/browser/config/tooltool-manifests/linux64/asan-tc.manifest @@ -0,0 +1,27 @@ +[ +{ +"version": "gcc 4.8.5 + PR64905", +"size": 80160264, +"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e", +"algorithm": "sha512", +"filename": "gcc.tar.xz", +"unpack": true +}, +{ +"version": "clang 3.9.0/r262971", +"size": 117866656, +"visibility": "public", +"digest": "5529b4549e39838faf0f276fb5d1d9cf9972295a8fb2451e25b0490e4a94822bff9646d723cc3137bfc2eb7e7bcff8a25a331b881ec6ca0b73e29fd7dbb972f5", +"algorithm": "sha512", +"filename": "clang.tar.xz", + "unpack": true + }, +{ +"size": 12072532, +"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9", +"algorithm": "sha512", +"filename": "gtk3.tar.xz", +"setup": "setup.sh", +"unpack": true +} +]
--- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -744,16 +744,24 @@ certErrorDetailsHSTS.label = HTTP Strict certErrorDetailsKeyPinning.label = HTTP Public Key Pinning: %S certErrorDetailsCertChain.label = Certificate chain: # LOCALIZATION NOTE (tabgroups.migration.anonGroup): # %S is the group number/ID tabgroups.migration.anonGroup = Group %S tabgroups.migration.tabGroupBookmarkFolderName = Bookmarked Tab Groups +# LOCALIZATION NOTE (pendingCrashReports.label): Semi-colon list of plural forms +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# #1 is the number of pending crash reports +pendingCrashReports.label = You have an unsubmitted crash report;You have #1 unsubmitted crash reports +pendingCrashReports.viewAll = View +pendingCrashReports.submitAll = Submit +pendingCrashReports.ignoreAll = Ignore + decoder.noCodecs.button = Learn how decoder.noCodecs.accesskey = L decoder.noCodecs.message = To play video, you may need to install Microsoft’s Media Feature Pack. decoder.noCodecsVista.message = To play video, you may need to install Microsoft’s Platform Update Supplement for Windows Vista. decoder.noCodecsXP.message = To play video, you may need to enable Adobe’s Primetime Content Decryption Module. decoder.noCodecsLinux.message = To play video, you may need to install the required video codecs. decoder.noHWAcceleration.message = To improve video quality, you may need to install Microsoft’s Media Feature Pack. decoder.noHWAccelerationVista.message = To improve video quality, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
--- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -59,16 +59,22 @@ content: ""; display: -moz-box; height: 2px; margin-top: -2px; position: relative; z-index: 2; /* navbar is at 1 */ } +@media (-moz-mac-yosemite-theme) { + #navigator-toolbox::after { + background-image: linear-gradient(to top, hsla(0,0%,0%,.1), hsla(0,0%,0%,.1) 1px, hsla(0,0%,100%,0) 1px, hsla(0,0%,100%,0) 2px, transparent 3px); + } +} + #navigator-toolbox toolbarbutton:-moz-lwtheme { color: inherit; text-shadow: inherit; } #main-window { -moz-appearance: none; background-color: #eeeeee; @@ -143,16 +149,20 @@ toolbarseparator { background: url(chrome://browser/skin/Toolbar-background-noise.png) hsl(0,0%,83%); } /* remove noise texture on Yosemite */ @media (-moz-mac-yosemite-theme) { #navigator-toolbox > toolbar:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) { background-image: none; } + + #navigator-toolbox > toolbar:-moz-window-inactive:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) { + background-color: hsl(0,0%,95%); + } } #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):not(#addon-bar) { overflow: -moz-hidden-unscrollable; max-height: 4em; transition: min-height 170ms ease-out, max-height 170ms ease-out; } @@ -185,16 +195,20 @@ toolbarseparator { } } /* remove noise texture on Yosemite */ @media (-moz-mac-yosemite-theme) { #nav-bar { background: linear-gradient(hsl(0,0%,93%), hsl(0,0%,83%)); } + + #nav-bar:-moz-window-inactive { + background: linear-gradient(hsl(0,0%,97%), hsl(0,0%,95%)); + } } /* Draw the bottom border of the tabs toolbar when it's not using -moz-appearance: toolbar. */ #main-window:-moz-any([sizemode="fullscreen"],[customize-entered]) #TabsToolbar:not([collapsed="true"]) + #nav-bar, #main-window:not([tabsintitlebar]) #TabsToolbar:not([collapsed="true"]) + #nav-bar, #TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-lwtheme { border-top: 1px solid hsla(0,0%,0%,.3); @@ -211,16 +225,20 @@ toolbarseparator { #main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:not(:-moz-lwtheme) { border-top: 1px solid hsla(0,0%,0%,.2); background-clip: padding-box; margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap)); /* Position the toolbar above the bottom of background tabs */ position: relative; z-index: 1; } + + #main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-window-inactive:not(:-moz-lwtheme) { + border-top-color: hsla(0,0%,0%,.05); + } } #nav-bar-customization-target { padding: 4px; } #PersonalToolbar { padding: 0 4px 4px; @@ -2478,16 +2496,100 @@ toolbarbutton.chevron > .toolbarbutton-m list-style-image: url("chrome://browser/skin/tabbrowser/connecting@2x.png"); } .tab-throbber[progress] { list-style-image: url("chrome://global/skin/icons/loading@2x.png"); } } +@media (-moz-mac-yosemite-theme) { + /* image preloading hack from shared/tabs.inc.css */ + #tabbrowser-tabs::before { + background-image: + url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg), + url(chrome://browser/skin/yosemite/tab-selected-start-inactive.svg), + url(chrome://browser/skin/yosemite/tab-stroke-start-inactive.png), + url(chrome://browser/skin/yosemite/tab-active-middle-inactive.png), + url(chrome://browser/skin/yosemite/tab-stroke-end-inactive.png), + url(chrome://browser/skin/tabbrowser/tab-selected-end.svg), + url(chrome://browser/skin/tabbrowser/tab-selected-start.svg), + url(chrome://browser/skin/tabbrowser/tab-stroke-end.png), + url(chrome://browser/skin/tabbrowser/tab-active-middle.png), + url(chrome://browser/skin/tabbrowser/tab-stroke-start.png), + url(chrome://browser/skin/tabbrowser/tab-background-end.png), + url(chrome://browser/skin/tabbrowser/tab-background-middle.png), + url(chrome://browser/skin/tabbrowser/tab-background-start.png); + } + + .tab-background-middle[visuallyselected=true]:-moz-window-inactive { + background-image: url(chrome://browser/skin/yosemite/tab-active-middle-inactive.png), + @fgTabTextureYosemiteInactive@, + none; + } + + .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after, + .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after { + background-image: url(chrome://browser/skin/yosemite/tab-stroke-start-inactive.png); + } + + .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after, + .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after { + background-image: url(chrome://browser/skin/yosemite/tab-stroke-end-inactive.png); + } + + .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before, + .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before { + background-image: url(chrome://browser/skin/yosemite/tab-selected-start-inactive.svg); + background-size: 100% 100%; + } + + .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before, + .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before { + background-image: url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg); + background-size: 100% 100%; + } + + @media (min-resolution: 2dppx) { + /* image preloading hack from shared/tabs.inc.css */ + #tabbrowser-tabs::before { + background-image: + url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg), + url(chrome://browser/skin/yosemite/tab-selected-start-inactive.svg), + url(chrome://browser/skin/yosemite/tab-stroke-start-inactive@2x.png), + url(chrome://browser/skin/yosemite/tab-active-middle-inactive@2x.png), + url(chrome://browser/skin/yosemite/tab-stroke-end-inactive@2x.png), + url(chrome://browser/skin/tabbrowser/tab-selected-end.svg), + url(chrome://browser/skin/tabbrowser/tab-selected-start.svg), + url(chrome://browser/skin/tabbrowser/tab-stroke-end@2x.png), + url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png), + url(chrome://browser/skin/tabbrowser/tab-stroke-start@2x.png), + url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png), + url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png), + url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png); + } + + .tab-background-middle[visuallyselected=true]:-moz-window-inactive { + background-image: url(chrome://browser/skin/yosemite/tab-active-middle-inactive@2x.png), + @fgTabTextureYosemiteInactive@, + none; + } + + .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after, + .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after { + background-image: url(chrome://browser/skin/yosemite/tab-stroke-start-inactive@2x.png); + } + + .tab-background-end[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after, + .tab-background-start[visuallyselected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after { + background-image: url(chrome://browser/skin/yosemite/tab-stroke-end-inactive@2x.png); + } + } +} + .tabbrowser-tab:not(:hover) > .tab-stack > .tab-content > .tab-icon-image:not([visuallyselected="true"]) { opacity: .9; } /* * Force the overlay to create a new stacking context so it always appears on * top of the icon. */
--- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -226,16 +226,24 @@ browser.jar: skin/classic/browser/yosemite/menuPanel-help.png (menuPanel-help-yosemite.png) skin/classic/browser/yosemite/menuPanel-help@2x.png (menuPanel-help-yosemite@2x.png) skin/classic/browser/yosemite/menuPanel-small.png (menuPanel-small-yosemite.png) skin/classic/browser/yosemite/menuPanel-small@2x.png (menuPanel-small-yosemite@2x.png) skin/classic/browser/yosemite/reload-stop-go.png (reload-stop-go-yosemite.png) skin/classic/browser/yosemite/reload-stop-go@2x.png (reload-stop-go-yosemite@2x.png) skin/classic/browser/yosemite/sync-horizontalbar.png (sync-horizontalbar-yosemite.png) skin/classic/browser/yosemite/sync-horizontalbar@2x.png (sync-horizontalbar-yosemite@2x.png) + skin/classic/browser/yosemite/tab-selected-end-inactive.svg (tabbrowser/tab-selected-end-yosemite-inactive.svg) + skin/classic/browser/yosemite/tab-selected-start-inactive.svg (tabbrowser/tab-selected-start-yosemite-inactive.svg) + skin/classic/browser/yosemite/tab-active-middle-inactive.png (tabbrowser/tab-active-middle-yosemite-inactive.png) + skin/classic/browser/yosemite/tab-active-middle-inactive@2x.png (tabbrowser/tab-active-middle-yosemite-inactive@2x.png) + skin/classic/browser/yosemite/tab-stroke-end-inactive.png (tabbrowser/tab-stroke-end-yosemite-inactive.png) + skin/classic/browser/yosemite/tab-stroke-end-inactive@2x.png (tabbrowser/tab-stroke-end-yosemite-inactive@2x.png) + skin/classic/browser/yosemite/tab-stroke-start-inactive.png (tabbrowser/tab-stroke-start-yosemite-inactive.png) + skin/classic/browser/yosemite/tab-stroke-start-inactive@2x.png (tabbrowser/tab-stroke-start-yosemite-inactive@2x.png) #ifdef E10S_TESTING_ONLY skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png) #endif [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: % override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png % override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png % override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png @@ -254,10 +262,10 @@ browser.jar: % override chrome://browser/skin/menuPanel-help.png chrome://browser/skin/yosemite/menuPanel-help.png os=Darwin osversion>=10.10 % override chrome://browser/skin/menuPanel-help@2x.png chrome://browser/skin/yosemite/menuPanel-help@2x.png os=Darwin osversion>=10.10 % override chrome://browser/skin/menuPanel-small.png chrome://browser/skin/yosemite/menuPanel-small.png os=Darwin osversion>=10.10 % override chrome://browser/skin/menuPanel-small@2x.png chrome://browser/skin/yosemite/menuPanel-small@2x.png os=Darwin osversion>=10.10 % override chrome://browser/skin/preferences/checkbox.png chrome://browser/skin/yosemite/preferences/checkbox.png os=Darwin osversion>=10.10 % override chrome://browser/skin/preferences/checkbox@2x.png chrome://browser/skin/yosemite/preferences/checkbox@2x.png os=Darwin osversion>=10.10 % override chrome://browser/skin/reload-stop-go.png chrome://browser/skin/yosemite/reload-stop-go.png os=Darwin osversion>=10.10 % override chrome://browser/skin/reload-stop-go@2x.png chrome://browser/skin/yosemite/reload-stop-go@2x.png os=Darwin osversion>=10.10 -% override chrome://browser/skin/sync-horizontalbar.png chrome://browser/skin/yosemite/sync-horizontalbar.png os=Darwin osversion>=10.10 -% override chrome://browser/skin/sync-horizontalbar@2x.png chrome://browser/skin/yosemite/sync-horizontalbar@2x.png os=Darwin osversion>=10.10 +% override chrome://browser/skin/sync-horizontalbar.png chrome://browser/skin/yosemite/sync-horizontalbar.png os=Darwin osversion>=10.10 +% override chrome://browser/skin/sync-horizontalbar@2x.png chrome://browser/skin/yosemite/sync-horizontalbar@2x.png os=Darwin osversion>=10.10
--- a/browser/themes/osx/shared.inc +++ b/browser/themes/osx/shared.inc @@ -1,12 +1,13 @@ %include ../../../toolkit/themes/osx/global/shared.inc %include ../shared/browser.inc %filter substitution -%define fgTabTexture linear-gradient(transparent 2px, hsla(0,0%,100%,.6) 2px, hsla(0,0%,100%,.6) 3px, hsl(0,0%,99%) 3px, hsl(0,0%,93%)) +%define fgTabTexture linear-gradient(transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,93%)) +%define fgTabTextureYosemiteInactive linear-gradient(transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%)) %define toolbarColorLWT rgba(253,253,253,0.45) %define fgTabTextureLWT linear-gradient(transparent 2px, rgba(254,254,254,.72) 2px, @toolbarColorLWT@) %define fgTabBackgroundColor transparent %define hudButton -moz-appearance: none; color: #434343; border-radius: 4px; border: 1px solid #b5b5b5; background: linear-gradient(#fff, #f2f2f2); box-shadow: inset 0 1px rgba(255,255,255,.8), inset 0 0 1px rgba(255,255, 255,.25), 0 1px rgba(255,255,255,.3); background-clip: padding-box; background-origin: padding-box; padding: 2px 6px; %define hudButtonPressed box-shadow: inset 0 1px 4px -3px #000, 0 1px rgba(255,255,255,.3); %define hudButtonFocused box-shadow: 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 2px 1px -moz-mac-focusring;
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9f74c4e7654d18ca9b811c778ebf8586ba525dcd GIT binary patch literal 78 zc%17D@N?(olHy`uVBq!ia0vp^Y(Ol}!2%@nWJ)FgDN#=s$B+ufWCzwPj}P@yOhy-Y bdzctXJvg10Jp088RKVcr>gTe~DWM4fDHRb@
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..50961db7ec08f990d349ab94ebe4296cddd72368 GIT binary patch literal 89 zc%17D@N?(olHy`uVBq!ia0vp^JV0#6!2%?=o-;ZHq!c_|978G?lNFd(B)0ER{kI%M kzTk2@s3F0+I6;Jg;kAw`r|rIrB|x<dp00i_>zopr0EOolmjD0&
new file mode 100644 --- /dev/null +++ b/browser/themes/osx/tabbrowser/tab-selected-end-yosemite-inactive.svg @@ -0,0 +1,28 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg xmlns="http://www.w3.org/2000/svg" width="30px" height="31px" preserveAspectRatio="none"> + <defs> + <style> + #tab-background-fill { + background-color: transparent; + background-image: linear-gradient(transparent, transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%)); + background-repeat: no-repeat; + height: 100%; + width: 100%; + } + </style> + + <clipPath id="tab-curve-clip-path-end" clipPathUnits="objectBoundingBox"> + <path d="m 0,0.0625 -0.05,0 0,0.938 1,0 0,-0.028 C 0.67917542,0.95840561 0.56569036,0.81970962 0.51599998,0.5625 0.48279998,0.3905 0.465,0.0659 0,0.0625 z"/> + </clipPath> + + <clipPath id="tab-hover-clip-path" clipPathUnits="objectBoundingBox"> + <path d="M 0,0.2 0,1 1,1, 1,0.2 z"/> + </clipPath> + </defs> + + <foreignObject width="30" height="31" clip-path="url(#tab-curve-clip-path-end)"> + <div id="tab-background-fill" xmlns="http://www.w3.org/1999/xhtml"></div> + </foreignObject> +</svg>
new file mode 100644 --- /dev/null +++ b/browser/themes/osx/tabbrowser/tab-selected-start-yosemite-inactive.svg @@ -0,0 +1,28 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg xmlns="http://www.w3.org/2000/svg" width="30px" height="31px" preserveAspectRatio="none"> + <defs> + <style> + #tab-background-fill { + background-color: transparent; + background-image: linear-gradient(transparent, transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%)); + background-repeat: no-repeat; + height: 100%; + width: 100%; + } + </style> + + <clipPath id="tab-curve-clip-path-start" clipPathUnits="objectBoundingBox"> + <path d="m 1,0.0625 0.05,0 0,0.938 -1,0 0,-0.028 C 0.32082458,0.95840561 0.4353096,0.81970962 0.48499998,0.5625 0.51819998,0.3905 0.535,0.0659 1,0.0625 z"/> + </clipPath> + + <clipPath id="tab-hover-clip-path" clipPathUnits="objectBoundingBox"> + <path d="M 0,0.2 0,1 1,1, 1,0.2 z"/> + </clipPath> + </defs> + + <foreignObject width="30" height="31" clip-path="url(#tab-curve-clip-path-start)"> + <div id="tab-background-fill" xmlns="http://www.w3.org/1999/xhtml"></div> + </foreignObject> +</svg>
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4206f0c7dca0188d886a6a10f4a9ba0a6c7767dc GIT binary patch literal 339 zc$@)K0j&OsP)<h;3K|Lk000e1NJLTq0015U0018d1ONa4T4lA20003RNkl<Zcmbu< zGed?^6bJBswr$(CZQGolJ-6ms!=ArIV_wphZNK5vS9s3-%~wYkV%*^hXV}34`cQ#9 z)UIfLYB;J`&zp%0^@TOG<!x6dU9uuiN}}3>Vks1s(1d)XOpa8zo$ufUL-3IMU1hu< zjJVqkZ%cf<p~Ky7u$K>OaFOFg4EyQ8b)tp`z<&Bj6m-G07Ap|<yOW70l)yu(ong=h z*J2b*z_k(p8*rW3!v$QV{om!P;2Md59=MhxU<R&TKREECHpvZu8o1`-1g7geOlZ2M zVqpxf`vg~kPf2GlU&DhvBIK}N=-|cwQJwFq2BS3{U<O^NLs@p>DE+Z_SK9C2%vm&J l)c&b}0w|5jDVLH+`wyzbPouYMMS}nU002ovPDHLkV1j2in@#`#
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..21ac892703cffc3bb16e4c63e7735984b598f23c GIT binary patch literal 718 zc$@*w0x|uGP)<h;3K|Lk000e1NJLTq002Ay002G+1ONa4-951K0007&Nkl<Zcmd_u z1B{(P5CGtL#@V*5n&)@6`J6On!^Tb#)s4!q4(gyb;@UROH{E~g#`tH8nQWYVYr%gQ z=^|4h1ri|+A|V8P!N3Rqo=Kt#exOs;MK5~Cog?-a0}r~cG##v7F|Q{7dj(=3Kvnqn zd^>oiVa2?JEW+a<SUuc`77W5@Ove(e#@;Xdxa(q*yM*vK@B@xOf6NvYa47t(j-DdI zqd)^Zfd<ULHuO$<PSmGnQ>Pn%FEAcEl~MN=3px!<oU|5m(LU+kw1~Q#lcrG@oxY<~ z2Cc(V^lnk;M9>3o-~#k4n@t-Fu+t-sihMywaKjp`zuQS8ZQ!QO*tbrZ-_iWt9W!F5 z4aLe&mmaK^GsP?;gLHNo`RNi`(c%2C=~<V66M#Q(A)VDe{BX#z8UaTFe>`uXYoUbB zE^H{Evx*(|-&Q8zAmEQHHBz=tfS2<GoFZeClj-trkg#>GnlE5q;E(;v^a_;#UtS>P zlmd8NC~JDfFUk|No*%Z-^zsA#I9eg#FbVLjg#wO~0FUMSWe8fsKgy9yN(5}-hxNNE zByA$MswIu`_^i}yIY&h_J!yjZVeO3;0mlM=Oq9;elk=8}J6J8}{iuE49g->M##yqh zOir7vBZIW4{3QI2oT;ZDAsu_0(Y|z+obJx~V5j>?6#tJ<<CEUhY@#*X-QEABhv+!4 z@X}#ue>L#+hdUDVoOC$a$KCrY{W1xU0$)BlQ2F70J-B8eU5b>9fl%-VD;PlsS~Y1s z&O$eOKiDt!?qBE#17CrfMH;~Xdcrm8;Q?5#;OHH3-mE;=d#>_o<+c37AteTaRiEVr z*n%Fx|HuJRp&YcZ{E1;a7Go*aV;2sIQl`M40U(9}#4OR73;+NC07*qoM6N<$f^w8q Ao&W#<
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2acbac438c6aea4c4f3c4b0a2efb859a5f8945d7 GIT binary patch literal 339 zc$@)K0j&OsP)<h;3K|Lk000e1NJLTq0015U0018d1ONa4T4lA20003RNkl<ZcmciC zLt@+k6bA5bY}ajU+qP{xtJ%lc#K{R}AKxOI7@OEhH?`fqUSa;P_+8-x{wE3axDN-o z!X2IvK#fP?{<egzg6pbxP2XCn6LwPKfi?WNl<Fykyy+onNe(+1oqYBRshFHe41r9E z0ne++6AWrU-<M@%0?VU=0y2QbI2=vR;8A!zO$U>9G=|JzdbE}RCXvPavkREi-4P@O zlVvdmY>!uhz~*-D1SV->0I9*Gn~DP4-Q_5-IUNLmNtPePFQ$2pUre3XiwH28Z3(0W zi^kpebs|{Q3a^tw(tyF_<a1O^P7pv0IkdQ|bef5MRYR^2K(=^yC+}$R!1}hIG6g*) lsVRl>sgg1%=v@&6`3>EePmwbnyd?kt002ovPDHLkV1ikcku(4R
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a12fc39a7b10829cffefb0f736eebd8a545ef19f GIT binary patch literal 716 zc$@*u0yF)IP)<h;3K|Lk000e1NJLTq002Ay002G+1ONa4-951K0007$Nkl<ZcmeI$ zGnjQ(6vpv)Y};OA+qUhQ>ne4+^i5F3slqO9;wCn_*tW*==I&VKch>pz+x_#u2ls5i z5*LLa8c9e;4)RfiVh9A53PKFhQ2_a$VVO8&{Sg_gxuWC2kymCuT={Zq&vEFYk3Lu; z5xFLqSAIpuGgIGf)A!F|O*jTQChBQ?aputdduD?pY@{%g35;MU&bR<%nCM#l+e<V- zr$1Q1SnJ>2Tu5=n6O+eYe{_MR41t0$lt6q9=81Bdxlj@Yf!x${?4g5I3{=hD(MNNG zW1%J(`H)*owvRzjlm)r0m0os1Rhs!S?K!oxArQ!W%ba8YRAoZ0>){5&p(@gRF!mhG zg0d{ggCp#NvQRVs>}@jw%2Hwey^T<p2l;%3VNe!j=3I6|T{`5kF(yG>0pyD*s0)Kk z)v^xi;vu*7GY;x9ATN)?28EFC+o3K5GFHPvsEdYN)yYVxONQLv4;y4bUhaYoiXh+A zL0vGUr<%D?7h~6}l>&Km5H`qxyxk3T0g#x|gBqxdfLzkVbXz_icMUSwmM8qih%M7Y zam8T^)Fnc0>}Dp^WkcSaV<418LfWg@2Xz@Rd!-y3R+cekKd<QD=uDWEWupyqQgOwP zJKD2S5b`0<OtQ;{+k9yY9opBMVb&Z!I#{VY4Q809`{_ET8K5}{=9uh9Yuwbk&A~|j z?ezKUEjpN@wcpQxm=DJNLx&zZm<EdkA_$>~L@bh#1%Zi{Ueo=}Z>P`k*1S9>8jKKx zBHTYB5$#O5zx&1YA07Uvhb;`Y@M;@F9!`3#=GCF+hMrr0YiQ5qPnzg}g<Ee%u+ksc yqVI3R%Q@CD5{9MWey(tbE1YB>8<@*5Sne0?D*?YIK~4t%0000<MNUMnLSTZFxl?ig
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css +++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css @@ -1445,16 +1445,21 @@ toolbaritem[overflowedItem=true], -moz-appearance: none; margin-inline-end: 3px; } menuitem[checked="true"].subviewbutton > .menu-iconic-left { visibility: hidden; } +#PanelUI-containersItems > .subviewbutton > .toolbarbutton-icon { + width: 16px; + height: 16px; +} + .panel-mainview[panelid=customizationui-widget-panel], #customizationui-widget-multiview > .panel-viewcontainer, #customizationui-widget-multiview > .panel-viewcontainer > .panel-viewstack, #PanelUI-panicView > .panel-subview-body, #PanelUI-panicView { overflow: visible; }
--- a/build/buildconfig.py +++ b/build/buildconfig.py @@ -1,17 +1,17 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import imp import os import sys -path = os.path.dirname(__file__) +path = os.path.dirname(sys.executable) while not os.path.exists(os.path.join(path, 'config.status')): parent = os.path.normpath(os.path.join(path, os.pardir)) if parent == path: raise Exception("Can't find config.status") path = parent path = os.path.join(path, 'config.status') config = imp.load_module('_buildconfig', open(path), path, ('', 'r', imp.PY_SOURCE))
--- a/build/gecko_templates.mozbuild +++ b/build/gecko_templates.mozbuild @@ -66,18 +66,16 @@ def GeckoBinary(linkage='dependent', msv LDFLAGS += CONFIG['MOZ_GLUE_WRAP_LDFLAGS'] if mozglue == 'program': USE_LIBS += ['mozglue'] if CONFIG['MOZ_GLUE_IN_PROGRAM']: if CONFIG['GNU_CC']: LDFLAGS += ['-rdynamic'] if CONFIG['MOZ_MEMORY']: USE_LIBS += ['memory'] - if CONFIG['MOZ_LINKER']: - OS_LIBS += CONFIG['MOZ_ZLIB_LIBS'] elif mozglue == 'library': if not CONFIG['MOZ_GLUE_IN_PROGRAM']: USE_LIBS += ['mozglue'] else: error('`mozglue` must be "program" or "library"') if not CONFIG['JS_STANDALONE']: USE_LIBS += [
--- a/build/mach_bootstrap.py +++ b/build/mach_bootstrap.py @@ -228,21 +228,18 @@ def bootstrap(topsrcdir, mozilla_dir=Non # Global build system and mach state is stored in a central directory. By # default, this is ~/.mozbuild. However, it can be defined via an # environment variable. We detect first run (by lack of this directory # existing) and notify the user that it will be created. The logic for # creation is much simpler for the "advanced" environment variable use # case. For default behavior, we educate users and give them an opportunity # to react. We always exit after creating the directory because users don't # like surprises. - try: - import mach.main - except ImportError: - sys.path[0:0] = [os.path.join(mozilla_dir, path) for path in SEARCH_PATHS] - import mach.main + sys.path[0:0] = [os.path.join(mozilla_dir, path) for path in SEARCH_PATHS] + import mach.main def telemetry_handler(context, data): # We have not opted-in to telemetry if 'BUILD_SYSTEM_TELEMETRY' not in os.environ: return telemetry_dir = os.path.join(get_state_dir()[0], 'telemetry') try:
--- a/build/virtualenv_packages.txt +++ b/build/virtualenv_packages.txt @@ -18,17 +18,16 @@ macholib.pth:python/macholib mock.pth:python/mock-1.0.0 mozilla.pth:build mozilla.pth:config mozilla.pth:xpcom/typelib/xpt/tools mozilla.pth:dom/bindings mozilla.pth:dom/bindings/parser mozilla.pth:layout/tools/reftest moztreedocs.pth:tools/docs -copy:build/buildconfig.py packages.txt:testing/mozbase/packages.txt objdir:build gyp.pth:media/webrtc/trunk/tools/gyp/pylib pyasn1.pth:python/pyasn1 pyasn1_modules.pth:python/pyasn1-modules bitstring.pth:python/bitstring redo.pth:python/redo requests.pth:python/requests
--- a/dom/animation/AnimationEffectTiming.cpp +++ b/dom/animation/AnimationEffectTiming.cpp @@ -129,20 +129,24 @@ AnimationEffectTiming::SetDirection(cons PostSpecifiedTimingUpdated(mEffect); } void AnimationEffectTiming::SetEasing(JSContext* aCx, const nsAString& aEasing, ErrorResult& aRv) { + nsIDocument* document = AnimationUtils::GetCurrentRealmDocument(aCx); + if (!document) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + Maybe<ComputedTimingFunction> newFunction = - TimingParams::ParseEasing(aEasing, - AnimationUtils::GetCurrentRealmDocument(aCx), - aRv); + TimingParams::ParseEasing(aEasing, document, aRv); if (aRv.Failed()) { return; } if (mTiming.mFunction == newFunction) { return; }
--- a/dom/animation/EffectCompositor.cpp +++ b/dom/animation/EffectCompositor.cpp @@ -514,45 +514,36 @@ EffectCompositor::UpdateCascadeResults(E } /* static */ Maybe<NonOwningAnimationTarget> EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame) { // Always return the same object to benefit from return-value optimization. Maybe<NonOwningAnimationTarget> result; + CSSPseudoElementType pseudoType = + aFrame->StyleContext()->GetPseudoType(); + + if (pseudoType != CSSPseudoElementType::NotPseudo && + pseudoType != CSSPseudoElementType::before && + pseudoType != CSSPseudoElementType::after) { + return result; + } + nsIContent* content = aFrame->GetContent(); if (!content) { return result; } - CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo; - - if (aFrame->IsGeneratedContentFrame()) { - nsIFrame* parent = aFrame->GetParent(); - if (parent->IsGeneratedContentFrame()) { - return result; - } - nsIAtom* name = content->NodeInfo()->NameAtom(); - if (name == nsGkAtoms::mozgeneratedcontentbefore) { - pseudoType = CSSPseudoElementType::before; - } else if (name == nsGkAtoms::mozgeneratedcontentafter) { - pseudoType = CSSPseudoElementType::after; - } else { - return result; - } + if (pseudoType == CSSPseudoElementType::before || + pseudoType == CSSPseudoElementType::after) { content = content->GetParent(); if (!content) { return result; } - } else { - if (nsLayoutUtils::GetStyleFrame(content) != aFrame) { - // The effects associated with an element are for its primary frame. - return result; - } } if (!content->IsElement()) { return result; } result.emplace(content->AsElement(), pseudoType);
--- a/dom/animation/EffectSet.cpp +++ b/dom/animation/EffectSet.cpp @@ -44,52 +44,28 @@ EffectSet::GetEffectSet(dom::Element* aE { nsIAtom* propName = GetEffectSetPropertyAtom(aPseudoType); return static_cast<EffectSet*>(aElement->GetProperty(propName)); } /* static */ EffectSet* EffectSet::GetEffectSet(const nsIFrame* aFrame) { - nsIContent* content = aFrame->GetContent(); - if (!content) { + Maybe<NonOwningAnimationTarget> target = + EffectCompositor::GetAnimationElementAndPseudoForFrame(aFrame); + + if (!target) { return nullptr; } - nsIAtom* propName; - if (aFrame->IsGeneratedContentFrame()) { - nsIFrame* parent = aFrame->GetParent(); - if (parent->IsGeneratedContentFrame()) { - return nullptr; - } - nsIAtom* name = content->NodeInfo()->NameAtom(); - if (name == nsGkAtoms::mozgeneratedcontentbefore) { - propName = nsGkAtoms::animationEffectsForBeforeProperty; - } else if (name == nsGkAtoms::mozgeneratedcontentafter) { - propName = nsGkAtoms::animationEffectsForAfterProperty; - } else { - return nullptr; - } - content = content->GetParent(); - if (!content) { - return nullptr; - } - } else { - if (nsLayoutUtils::GetStyleFrame(content) != aFrame) { - // The effects associated with an element are for its primary frame. - return nullptr; - } - propName = nsGkAtoms::animationEffectsProperty; - } - - if (!content->MayHaveAnimations()) { + if (!target->mElement->MayHaveAnimations()) { return nullptr; } - return static_cast<EffectSet*>(content->GetProperty(propName)); + return GetEffectSet(target->mElement, target->mPseudoType); } /* static */ EffectSet* EffectSet::GetOrCreateEffectSet(dom::Element* aElement, CSSPseudoElementType aPseudoType) { EffectSet* effectSet = GetEffectSet(aElement, aPseudoType); if (effectSet) {
--- a/dom/animation/test/mochitest.ini +++ b/dom/animation/test/mochitest.ini @@ -81,8 +81,9 @@ skip-if = buildapp == 'mulet' skip-if = buildapp == 'mulet' [mozilla/test_deferred_start.html] skip-if = (toolkit == 'gonk' && debug) [mozilla/test_disabled_properties.html] [mozilla/test_hide_and_show.html] [mozilla/test_partial_keyframes.html] [style/test_animation-seeking-with-current-time.html] [style/test_animation-seeking-with-start-time.html] +[sandbox/test_set-easing.html]
new file mode 100644 --- /dev/null +++ b/dom/animation/test/sandbox/test_set-easing.html @@ -0,0 +1,32 @@ +<!doctype html> +<head> +<meta charset=utf-8> +<title>Tests AnimationTimingFunction::SetEasing in sandbox</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/SpawnTask.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> + +<script> +"use strict"; + +add_task(function* set_easing() { + const div = document.createElement("div"); + document.body.appendChild(div); + div.animate({ opacity: [0, 1] }, 100000 ); + + const contentScript = function() { + doesThrow(() => { + document.getAnimations()[0].effect.timing.easing = "linear"; + }, "AnimationTimingFunction::SetEasing should throw in sandbox."); + }; + + const sandbox = new SpecialPowers.Cu.Sandbox(window); + sandbox.importFunction(document, "document"); + sandbox.importFunction(SimpleTest.doesThrow, "doesThrow"); + SpecialPowers.Cu.evalInSandbox(`(${contentScript.toSource()})()`, sandbox); +}); + +</script> +</body>
--- a/dom/audiochannel/AudioChannelAgent.cpp +++ b/dom/audiochannel/AudioChannelAgent.cpp @@ -220,18 +220,18 @@ AudioChannelAgent::NotifyStartedPlaying( service->RegisterAudioChannelAgent(this, static_cast<AudioChannelService::AudibleState>(aAudible)); AudioPlaybackConfig config = service->GetMediaConfig(mWindow, mAudioChannelType); MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, NotifyStartedPlaying, this = %p, " - "mute = %d, volume = %f, suspend = %d\n", this, - config.mMuted, config.mVolume, config.mSuspend)); + "audible = %d, mute = %d, volume = %f, suspend = %d\n", this, + aAudible, config.mMuted, config.mVolume, config.mSuspend)); aConfig->SetConfig(config.mVolume, config.mMuted, config.mSuspend); mIsRegToService = true; return NS_OK; } NS_IMETHODIMP AudioChannelAgent::NotifyStoppedPlaying() @@ -249,29 +249,31 @@ AudioChannelAgent::NotifyStoppedPlaying( service->UnregisterAudioChannelAgent(this); } mIsRegToService = false; return NS_OK; } NS_IMETHODIMP -AudioChannelAgent::NotifyStartedAudible(bool aAudible) +AudioChannelAgent::NotifyStartedAudible(bool aAudible, uint32_t aReason) { MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, NotifyStartedAudible, this = %p, " - "audible = %d\n", this, aAudible)); + "audible = %d, reason = %d\n", this, aAudible, aReason)); RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate(); if (NS_WARN_IF(!service)) { return NS_ERROR_FAILURE; } service->AudioAudibleChanged( - this, static_cast<AudioChannelService::AudibleState>(aAudible)); + this, + static_cast<AudioChannelService::AudibleState>(aAudible), + static_cast<AudioChannelService::AudibleChangedReasons>(aReason)); return NS_OK; } already_AddRefed<nsIAudioChannelAgentCallback> AudioChannelAgent::GetCallback() { nsCOMPtr<nsIAudioChannelAgentCallback> callback = mCallback; if (!callback) {
--- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -100,42 +100,62 @@ bool IsParentProcess() { return XRE_GetProcessType() == GeckoProcessType_Default; } class AudioPlaybackRunnable final : public Runnable { public: - AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive) + AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive, + AudioChannelService::AudibleChangedReasons aReason) : mWindow(aWindow) , mActive(aActive) + , mReason(aReason) {} NS_IMETHOD Run() { nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); if (NS_WARN_IF(!observerService)) { return NS_ERROR_FAILURE; } + nsAutoString state; + GetActiveState(state); + observerService->NotifyObservers(ToSupports(mWindow), "audio-playback", - mActive ? MOZ_UTF16("active") - : MOZ_UTF16("inactive")); + state.get()); MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("AudioPlaybackRunnable, active = %d\n", mActive)); + ("AudioPlaybackRunnable, active = %d, reason = %d\n", + mActive, mReason)); + return NS_OK; } private: + void GetActiveState(nsAString& astate) + { + if (mActive) { + CopyASCIItoUTF16("active", astate); + } else { + if(mReason == AudioChannelService::AudibleChangedReasons::ePauseStateChanged) { + CopyASCIItoUTF16("inactive-pause", astate); + } else { + CopyASCIItoUTF16("inactive-nonaudible", astate); + } + } + } + nsCOMPtr<nsPIDOMWindowOuter> mWindow; bool mActive; + AudioChannelService::AudibleChangedReasons mReason; }; } // anonymous namespace StaticRefPtr<AudioChannelService> gAudioChannelService; // Mappings from 'mozaudiochannel' attribute strings to an enumeration. static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = { @@ -367,24 +387,25 @@ AudioChannelService::GetMediaConfig(nsPI // If there is no parent, or we are the toplevel we don't continue. } while (window && window != aWindow); return config; } void AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent, - AudibleState aAudible) + AudibleState aAudible, + AudibleChangedReasons aReason) { MOZ_ASSERT(aAgent); uint64_t windowID = aAgent->WindowID(); AudioChannelWindow* winData = GetWindowData(windowID); if (winData) { - winData->AudioAudibleChanged(aAgent, aAudible); + winData->AudioAudibleChanged(aAgent, aAudible, aReason); } } bool AudioChannelService::TelephonyChannelIsActive() { nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator windowsIter(mWindows); while (windowsIter.HasMore()) { @@ -1188,28 +1209,32 @@ AudioChannelService::AudioChannelWindow: AudibleState aAudible) { MOZ_ASSERT(aAgent); RequestAudioFocus(aAgent); AppendAgentAndIncreaseAgentsNum(aAgent); AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing); if (aAudible) { - AudioAudibleChanged(aAgent, AudibleState::eAudible); + AudioAudibleChanged(aAgent, + AudibleState::eAudible, + AudibleChangedReasons::eDataAudibleChanged); } } void AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent) { MOZ_ASSERT(aAgent); RemoveAgentAndReduceAgentsNum(aAgent); AudioCapturedChanged(aAgent, AudioCaptureState::eNotCapturing); - AudioAudibleChanged(aAgent, AudibleState::eNotAudible); + AudioAudibleChanged(aAgent, + AudibleState::eNotAudible, + AudibleChangedReasons::ePauseStateChanged); } void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent) { MOZ_ASSERT(aAgent); MOZ_ASSERT(!mAgents.Contains(aAgent)); @@ -1253,52 +1278,55 @@ AudioChannelService::AudioChannelWindow: if (mIsAudioCaptured) { aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(), aCapture); } } void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(AudioChannelAgent* aAgent, - AudibleState aAudible) + AudibleState aAudible, + AudibleChangedReasons aReason) { MOZ_ASSERT(aAgent); if (aAudible) { - AppendAudibleAgentIfNotContained(aAgent); + AppendAudibleAgentIfNotContained(aAgent, aReason); } else { - RemoveAudibleAgentIfContained(aAgent); + RemoveAudibleAgentIfContained(aAgent, aReason); } NotifyAudioCompetingChanged(aAgent, aAudible); } void -AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent) +AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent, + AudibleChangedReasons aReason) { MOZ_ASSERT(aAgent); MOZ_ASSERT(mAgents.Contains(aAgent)); if (!mAudibleAgents.Contains(aAgent)) { mAudibleAgents.AppendElement(aAgent); if (IsFirstAudibleAgent()) { - NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible); + NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible, aReason); } } } void -AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent) +AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent, + AudibleChangedReasons aReason) { MOZ_ASSERT(aAgent); if (mAudibleAgents.Contains(aAgent)) { mAudibleAgents.RemoveElement(aAgent); if (IsLastAudibleAgent()) { - NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible); + NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible, aReason); } } } bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const { return (mAudibleAgents.Length() == 1); @@ -1307,20 +1335,21 @@ AudioChannelService::AudioChannelWindow: bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const { return mAudibleAgents.IsEmpty(); } void AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow, - AudibleState aAudible) + AudibleState aAudible, + AudibleChangedReasons aReason) { RefPtr<AudioPlaybackRunnable> runnable = - new AudioPlaybackRunnable(aWindow, aAudible); + new AudioPlaybackRunnable(aWindow, aAudible, aReason); nsresult rv = NS_DispatchToCurrentThread(runnable); NS_WARN_IF(NS_FAILED(rv)); } void AudioChannelService::AudioChannelWindow::NotifyChannelActive(uint64_t aWindowID, AudioChannel aChannel, bool aActive)
--- a/dom/audiochannel/AudioChannelService.h +++ b/dom/audiochannel/AudioChannelService.h @@ -73,16 +73,22 @@ public: eNotAudible = false }; enum AudioCaptureState : bool { eCapturing = true, eNotCapturing = false }; + enum AudibleChangedReasons : uint32_t { + eVolumeChanged = 0, + eDataAudibleChanged = 1, + ePauseStateChanged = 2 + }; + /** * Returns the AudioChannelServce singleton. * If AudioChannelServce is not exist, create and return new one. * Only to be called from main thread. */ static already_AddRefed<AudioChannelService> GetOrCreate(); static bool IsAudioChannelMutedByDefault(); @@ -117,17 +123,19 @@ public: AudioPlaybackConfig GetMediaConfig(nsPIDOMWindowOuter* aWindow, uint32_t aAudioChannel) const; /** * Called this method when the audible state of the audio playback changed, * it would dispatch the playback event to observers which want to know the * actual audible state of the window. */ - void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible); + void AudioAudibleChanged(AudioChannelAgent* aAgent, + AudibleState aAudible, + AudibleChangedReasons aReason); /* Methods for the BrowserElementAudioChannel */ float GetAudioChannelVolume(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel); void SetAudioChannelVolume(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel, float aVolume); bool GetAudioChannelMuted(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel); @@ -245,17 +253,19 @@ private: , mIsAudioCaptured(false) , mOwningAudioFocus(!AudioChannelService::IsEnableAudioCompeting()) { // Workaround for bug1183033, system channel type can always playback. mChannels[(int16_t)AudioChannel::System].mMuted = false; } void AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent, bool aActive); - void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible); + void AudioAudibleChanged(AudioChannelAgent* aAgent, + AudibleState aAudible, + AudibleChangedReasons aReason); void AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible); void RemoveAgent(AudioChannelAgent* aAgent); uint64_t mWindowID; bool mIsAudioCaptured; AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS]; @@ -266,27 +276,31 @@ private: // Owning audio focus when the window starts playing audible sound, and // lose audio focus when other windows starts playing. bool mOwningAudioFocus; private: void AudioCapturedChanged(AudioChannelAgent* aAgent, AudioCaptureState aCapture); - void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent); - void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent); + void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent, + AudibleChangedReasons aReason); + void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent, + AudibleChangedReasons aReason); void AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent); void RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent); bool IsFirstAudibleAgent() const; bool IsLastAudibleAgent() const; void NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow, - AudibleState aAudible); + AudibleState aAudible, + AudibleChangedReasons aReason); + void NotifyChannelActive(uint64_t aWindowID, AudioChannel aChannel, bool aActive); void RequestAudioFocus(AudioChannelAgent* aAgent); void NotifyAudioCompetingChanged(AudioChannelAgent* aAgent, bool aActive); uint32_t GetCompetingBehavior(AudioChannelAgent* aAgent, int32_t aIncomingChannelType,
--- a/dom/audiochannel/nsIAudioChannelAgent.idl +++ b/dom/audiochannel/nsIAudioChannelAgent.idl @@ -178,10 +178,10 @@ interface nsIAudioChannelAgent : nsISupp /** * Notify agent that we already start producing audible data. * * Note : sometime audio might become silent during playing, this method is used to * notify the actually audible state to other services which want to know * about that, ex. tab sound indicator. */ - void notifyStartedAudible(in bool audible); + void notifyStartedAudible(in bool audible, in uint32_t reason); };
--- a/dom/base/File.cpp +++ b/dom/base/File.cpp @@ -338,51 +338,40 @@ Blob::SetMutable(bool aMutable) JSObject* Blob::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return BlobBinding::Wrap(aCx, this, aGivenProto); } /* static */ already_AddRefed<Blob> -Blob::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) +Blob::Constructor(const GlobalObject& aGlobal, + const Optional<Sequence<BlobPart>>& aData, + const BlobPropertyBag& aBag, + ErrorResult& aRv) { RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(); - impl->InitializeBlob(aRv); + if (aData.WasPassed()) { + impl->InitializeBlob(aGlobal.Context(), aData.Value(), aBag.mType, + aBag.mEndings == EndingTypes::Native, aRv); + } else { + impl->InitializeBlob(aRv); + } + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } MOZ_ASSERT(!impl->IsFile()); RefPtr<Blob> blob = Blob::Create(aGlobal.GetAsSupports(), impl); return blob.forget(); } -/* static */ already_AddRefed<Blob> -Blob::Constructor( - const GlobalObject& aGlobal, - const Sequence<OwningArrayBufferOrArrayBufferViewOrBlobOrString>& aData, - const BlobPropertyBag& aBag, - ErrorResult& aRv) -{ - RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(); - - impl->InitializeBlob(aGlobal.Context(), aData, aBag.mType, - aBag.mEndings == EndingTypes::Native, aRv); - if (aRv.Failed()) { - return nullptr; - } - MOZ_ASSERT(!impl->IsFile()); - - RefPtr<Blob> blob = Blob::Create(aGlobal.GetAsSupports(), impl); - return blob.forget(); -} - int64_t Blob::GetFileId() { return mImpl->GetFileId(); } bool Blob::IsMemoryFile() const @@ -538,22 +527,21 @@ ParseSize(int64_t aSize, int64_t& aStart } else { aStart = newStartOffset.value(); aEnd = newEndOffset.value(); } } /* static */ already_AddRefed<File> -File::Constructor( - const GlobalObject& aGlobal, - const Sequence<OwningArrayBufferOrArrayBufferViewOrBlobOrString>& aData, - const nsAString& aName, - const FilePropertyBag& aBag, - ErrorResult& aRv) +File::Constructor(const GlobalObject& aGlobal, + const Sequence<BlobPart>& aData, + const nsAString& aName, + const FilePropertyBag& aBag, + ErrorResult& aRv) { RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(aName); impl->InitializeBlob(aGlobal.Context(), aData, aBag.mType, false, aRv); if (aRv.Failed()) { return nullptr; } MOZ_ASSERT(impl->IsFile());
--- a/dom/base/File.h +++ b/dom/base/File.h @@ -37,32 +37,34 @@ class nsIInputStream; namespace mozilla { namespace dom { struct BlobPropertyBag; struct ChromeFilePropertyBag; struct FilePropertyBag; class BlobImpl; class File; -class OwningArrayBufferOrArrayBufferViewOrBlobOrString; +class OwningArrayBufferOrArrayBufferViewOrBlobOrUSVString; class Blob : public nsIDOMBlob , public nsIXHRSendable , public nsIMutable , public nsSupportsWeakReference , public nsWrapperCache { public: NS_DECL_NSIDOMBLOB NS_DECL_NSIXHRSENDABLE NS_DECL_NSIMUTABLE NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Blob, nsIDOMBlob) + typedef OwningArrayBufferOrArrayBufferViewOrBlobOrUSVString BlobPart; + // This creates a Blob or a File based on the type of BlobImpl. static Blob* Create(nsISupports* aParent, BlobImpl* aImpl); static already_AddRefed<Blob> Create(nsISupports* aParent, const nsAString& aContentType, uint64_t aLength); @@ -117,22 +119,18 @@ public: return mParent; } bool IsMemoryFile() const; // Blob constructor static already_AddRefed<Blob> - Constructor(const GlobalObject& aGlobal, ErrorResult& aRv); - - // Blob constructor - static already_AddRefed<Blob> Constructor(const GlobalObject& aGlobal, - const Sequence<OwningArrayBufferOrArrayBufferViewOrBlobOrString>& aData, + const Optional<Sequence<BlobPart>>& aData, const BlobPropertyBag& aBag, ErrorResult& aRv); virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; uint64_t GetSize(ErrorResult& aRv); @@ -199,17 +197,17 @@ public: // WebIDL methods virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override; // File constructor static already_AddRefed<File> Constructor(const GlobalObject& aGlobal, - const Sequence<OwningArrayBufferOrArrayBufferViewOrBlobOrString>& aData, + const Sequence<BlobPart>& aData, const nsAString& aName, const FilePropertyBag& aBag, ErrorResult& aRv); // File constructor - ChromeOnly static already_AddRefed<File> Constructor(const GlobalObject& aGlobal, Blob& aData,
--- a/dom/base/MultipartBlobImpl.cpp +++ b/dom/base/MultipartBlobImpl.cpp @@ -167,36 +167,35 @@ MultipartBlobImpl::CreateSlice(uint64_t void MultipartBlobImpl::InitializeBlob(ErrorResult& aRv) { SetLengthAndModifiedDate(aRv); NS_WARN_IF(aRv.Failed()); } void -MultipartBlobImpl::InitializeBlob( - JSContext* aCx, - const Sequence<OwningArrayBufferOrArrayBufferViewOrBlobOrString>& aData, - const nsAString& aContentType, - bool aNativeEOL, - ErrorResult& aRv) +MultipartBlobImpl::InitializeBlob(JSContext* aCx, + const Sequence<Blob::BlobPart>& aData, + const nsAString& aContentType, + bool aNativeEOL, + ErrorResult& aRv) { mContentType = aContentType; BlobSet blobSet; for (uint32_t i = 0, len = aData.Length(); i < len; ++i) { - const OwningArrayBufferOrArrayBufferViewOrBlobOrString& data = aData[i]; + const Blob::BlobPart& data = aData[i]; if (data.IsBlob()) { RefPtr<Blob> blob = data.GetAsBlob().get(); blobSet.AppendBlobImpl(blob->Impl()); } - else if (data.IsString()) { - aRv = blobSet.AppendString(data.GetAsString(), aNativeEOL, aCx); + else if (data.IsUSVString()) { + aRv = blobSet.AppendString(data.GetAsUSVString(), aNativeEOL, aCx); if (aRv.Failed()) { return; } } else if (data.IsArrayBuffer()) { const ArrayBuffer& buffer = data.GetAsArrayBuffer(); buffer.ComputeLengthAndData();
--- a/dom/base/MultipartBlobImpl.h +++ b/dom/base/MultipartBlobImpl.h @@ -48,22 +48,21 @@ public: MultipartBlobImpl() : BlobImplBase(EmptyString(), UINT64_MAX), mIsFromNsIFile(false) { } void InitializeBlob(ErrorResult& aRv); - void InitializeBlob( - JSContext* aCx, - const Sequence<OwningArrayBufferOrArrayBufferViewOrBlobOrString>& aData, - const nsAString& aContentType, - bool aNativeEOL, - ErrorResult& aRv); + void InitializeBlob(JSContext* aCx, + const Sequence<Blob::BlobPart>& aData, + const nsAString& aContentType, + bool aNativeEOL, + ErrorResult& aRv); void InitializeChromeFile(Blob& aData, const ChromeFilePropertyBag& aBag, ErrorResult& aRv); void InitializeChromeFile(nsPIDOMWindowInner* aWindow, const nsAString& aData, const ChromeFilePropertyBag& aBag,
--- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -81,16 +81,17 @@ public: NS_DECL_NSIOBSERVER NS_DECL_NSIREQUEST NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIEVENTTARGET using nsIEventTarget::Dispatch; explicit WebSocketImpl(WebSocket* aWebSocket) : mWebSocket(aWebSocket) + , mIsServerSide(false) , mOnCloseScheduled(false) , mFailed(false) , mDisconnectingOrDisconnected(false) , mCloseEventWasClean(false) , mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL) , mScriptLine(0) , mScriptColumn(0) , mInnerWindowID(0) @@ -113,25 +114,28 @@ public: { MOZ_ASSERT(IsTargetThread()); } bool IsTargetThread() const; void Init(JSContext* aCx, nsIPrincipal* aPrincipal, + bool aIsServerSide, const nsAString& aURL, nsTArray<nsString>& aProtocolArray, const nsACString& aScriptFile, uint32_t aScriptLine, uint32_t aScriptColumn, ErrorResult& aRv, bool* aConnectionFailed); void AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID, + nsITransportProvider* aTransportProvider, + const nsACString& aNegotiatedExtensions, ErrorResult& aRv); nsresult ParseURL(const nsAString& aURL); nsresult InitializeConnection(nsIPrincipal* aPrincipal); // These methods when called can release the WebSocket object void FailConnection(uint16_t reasonCode, const nsACString& aReasonString = EmptyCString()); @@ -164,16 +168,19 @@ public: void UnregisterFeature(); nsresult CancelInternal(); RefPtr<WebSocket> mWebSocket; nsCOMPtr<nsIWebSocketChannel> mChannel; + bool mIsServerSide; // True if we're implementing the server side of a + // websocket connection + bool mSecure; // if true it is using SSL and the wss scheme, // otherwise it is using the ws scheme with no SSL bool mOnCloseScheduled; bool mFailed; bool mDisconnectingOrDisconnected; // Set attributes of DOM 'onclose' message @@ -946,32 +953,44 @@ WebSocket::WrapObject(JSContext* cx, JS: // Constructor: already_AddRefed<WebSocket> WebSocket::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, ErrorResult& aRv) { Sequence<nsString> protocols; - return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); + return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr, + EmptyCString(), aRv); } already_AddRefed<WebSocket> WebSocket::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, const nsAString& aProtocol, ErrorResult& aRv) { Sequence<nsString> protocols; if (!protocols.AppendElement(aProtocol, fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return nullptr; } - return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); + return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr, + EmptyCString(), aRv); +} + +already_AddRefed<WebSocket> +WebSocket::Constructor(const GlobalObject& aGlobal, + const nsAString& aUrl, + const Sequence<nsString>& aProtocols, + ErrorResult& aRv) +{ + return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, nullptr, + EmptyCString(), aRv); } namespace { // This class is used to clear any exception. class MOZ_STACK_CLASS ClearException { public: @@ -1022,24 +1041,26 @@ protected: virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) = 0; virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0; }; class InitRunnable final : public WebSocketMainThreadRunnable { public: - InitRunnable(WebSocketImpl* aImpl, const nsAString& aURL, + InitRunnable(WebSocketImpl* aImpl, bool aIsServerSide, + const nsAString& aURL, nsTArray<nsString>& aProtocolArray, const nsACString& aScriptFile, uint32_t aScriptLine, uint32_t aScriptColumn, ErrorResult& aRv, bool* aConnectionFailed) : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate, NS_LITERAL_CSTRING("WebSocket :: init")) , mImpl(aImpl) + , mIsServerSide(aIsServerSide) , mURL(aURL) , mProtocolArray(aProtocolArray) , mScriptFile(aScriptFile) , mScriptLine(aScriptLine) , mScriptColumn(aScriptColumn) , mRv(aRv) , mConnectionFailed(aConnectionFailed) { @@ -1065,35 +1086,37 @@ protected: } nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal(); if (!principal) { mRv.Throw(NS_ERROR_FAILURE); return true; } - mImpl->Init(jsapi.cx(), principal, mURL, mProtocolArray, mScriptFile, - mScriptLine, mScriptColumn, mRv, mConnectionFailed); + mImpl->Init(jsapi.cx(), principal, mIsServerSide, mURL, mProtocolArray, + mScriptFile, mScriptLine, mScriptColumn, mRv, + mConnectionFailed); return true; } virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow()); - mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mURL, - mProtocolArray, mScriptFile, mScriptLine, mScriptColumn, mRv, - mConnectionFailed); + mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide, + mURL, mProtocolArray, mScriptFile, mScriptLine, mScriptColumn, + mRv, mConnectionFailed); return true; } // Raw pointer. This worker runs synchronously. WebSocketImpl* mImpl; + bool mIsServerSide; const nsAString& mURL; nsTArray<nsString>& mProtocolArray; nsCString mScriptFile; uint32_t mScriptLine; uint32_t mScriptColumn; ErrorResult& mRv; bool* mConnectionFailed; }; @@ -1135,44 +1158,48 @@ protected: if (topWindow) { topInner = topWindow->GetCurrentInnerWindow(); } if (topInner) { windowID = topInner->WindowID(); } - mImpl->AsyncOpen(principal, windowID, mRv); + mImpl->AsyncOpen(principal, windowID, nullptr, EmptyCString(), mRv); return true; } virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow()); - mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, mRv); + mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, nullptr, + EmptyCString(), mRv); return true; } private: // Raw pointer. This worker runs synchronously. WebSocketImpl* mImpl; ErrorResult& mRv; }; } // namespace already_AddRefed<WebSocket> -WebSocket::Constructor(const GlobalObject& aGlobal, - const nsAString& aUrl, - const Sequence<nsString>& aProtocols, - ErrorResult& aRv) +WebSocket::ConstructorCommon(const GlobalObject& aGlobal, + const nsAString& aUrl, + const Sequence<nsString>& aProtocols, + nsITransportProvider* aTransportProvider, + const nsACString& aNegotiatedExtensions, + ErrorResult& aRv) { + MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty()); nsCOMPtr<nsIPrincipal> principal; nsCOMPtr<nsPIDOMWindowInner> ownerWindow; if (NS_IsMainThread()) { nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal = do_QueryInterface(aGlobal.GetAsSupports()); if (!scriptPrincipal) { aRv.Throw(NS_ERROR_FAILURE); @@ -1224,37 +1251,38 @@ WebSocket::Constructor(const GlobalObjec } RefPtr<WebSocket> webSocket = new WebSocket(ownerWindow); RefPtr<WebSocketImpl> kungfuDeathGrip = webSocket->mImpl; bool connectionFailed = true; if (NS_IsMainThread()) { - webSocket->mImpl->Init(aGlobal.Context(), principal, aUrl, protocolArray, - EmptyCString(), 0, 0, aRv, &connectionFailed); + webSocket->mImpl->Init(aGlobal.Context(), principal, !!aTransportProvider, + aUrl, protocolArray, EmptyCString(), + 0, 0, aRv, &connectionFailed); } else { // In workers we have to keep the worker alive using a feature in order to // dispatch messages correctly. if (!webSocket->mImpl->RegisterFeature()) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } unsigned lineno, column; JS::AutoFilename file; if (!JS::DescribeScriptedCaller(aGlobal.Context(), &file, &lineno, &column)) { NS_WARNING("Failed to get line number and filename in workers."); } RefPtr<InitRunnable> runnable = - new InitRunnable(webSocket->mImpl, aUrl, protocolArray, - nsDependentCString(file.get()), lineno, column, aRv, - &connectionFailed); + new InitRunnable(webSocket->mImpl, !!aTransportProvider, aUrl, + protocolArray, nsDependentCString(file.get()), lineno, + column, aRv, &connectionFailed); runnable->Dispatch(aRv); } if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // It can be that we have been already disconnected because the WebSocket is @@ -1322,18 +1350,21 @@ WebSocket::Constructor(const GlobalObjec if (topWindow) { topInner = topWindow->GetCurrentInnerWindow(); } if (topInner) { windowID = topInner->WindowID(); } - webSocket->mImpl->AsyncOpen(principal, windowID, aRv); + webSocket->mImpl->AsyncOpen(principal, windowID, aTransportProvider, + aNegotiatedExtensions, aRv); } else { + MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(), + "not yet implemented"); RefPtr<AsyncOpenRunnable> runnable = new AsyncOpenRunnable(webSocket->mImpl, aRv); runnable->Dispatch(aRv); } if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -1420,16 +1451,17 @@ WebSocket::DisconnectFromOwner() //----------------------------------------------------------------------------- // WebSocketImpl:: initialization //----------------------------------------------------------------------------- void WebSocketImpl::Init(JSContext* aCx, nsIPrincipal* aPrincipal, + bool aIsServerSide, const nsAString& aURL, nsTArray<nsString>& aProtocolArray, const nsACString& aScriptFile, uint32_t aScriptLine, uint32_t aScriptColumn, ErrorResult& aRv, bool* aConnectionFailed) { @@ -1479,77 +1511,82 @@ WebSocketImpl::Init(JSContext* aCx, JS::AutoFilename file; if (JS::DescribeScriptedCaller(aCx, &file, &lineno, &column)) { mScriptFile = file.get(); mScriptLine = lineno; mScriptColumn = column; } } + mIsServerSide = aIsServerSide; + // If we don't have aCx, we are window-less, so we don't have a // inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in // DedicateWorkers created by JSM. if (aCx) { mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx); } // parses the url aRv = ParseURL(PromiseFlatString(aURL)); if (NS_WARN_IF(aRv.Failed())) { return; } - nsCOMPtr<nsIURI> uri; - { - nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI); - - // We crash here because we are sure that mURI is a valid URI, so either we - // are OOM'ing or something else bad is happening. - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_CRASH(); - } - } - - // Check content policy. - int16_t shouldLoad = nsIContentPolicy::ACCEPT; nsCOMPtr<nsIDocument> originDoc = mWebSocket->GetDocumentIfCurrent(); if (!originDoc) { nsresult rv = mWebSocket->CheckInnerWindowCorrectness(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } } - mOriginDocument = do_GetWeakReference(originDoc); - aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET, - uri, - aPrincipal, - originDoc, - EmptyCString(), - nullptr, - &shouldLoad, - nsContentUtils::GetContentPolicy(), - nsContentUtils::GetSecurityManager()); - if (NS_WARN_IF(aRv.Failed())) { - return; - } - - if (NS_CP_REJECTED(shouldLoad)) { - // Disallowed by content policy. - aRv.Throw(NS_ERROR_CONTENT_BLOCKED); - return; + + if (!mIsServerSide) { + nsCOMPtr<nsIURI> uri; + { + nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI); + + // We crash here because we are sure that mURI is a valid URI, so either we + // are OOM'ing or something else bad is happening. + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_CRASH(); + } + } + + // Check content policy. + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET, + uri, + aPrincipal, + originDoc, + EmptyCString(), + nullptr, + &shouldLoad, + nsContentUtils::GetContentPolicy(), + nsContentUtils::GetSecurityManager()); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + if (NS_CP_REJECTED(shouldLoad)) { + // Disallowed by content policy. + aRv.Throw(NS_ERROR_CONTENT_BLOCKED); + return; + } } // Potentially the page uses the CSP directive 'upgrade-insecure-requests'. // In such a case we have to upgrade ws: to wss: and also update mSecure // to reflect that upgrade. Please note that we can not upgrade from ws: // to wss: before performing content policy checks because CSP needs to // send reports in case the scheme is about to be upgraded. - if (!mSecure && originDoc && originDoc->GetUpgradeInsecureRequests(false)) { + if (!mIsServerSide && !mSecure && originDoc && + originDoc->GetUpgradeInsecureRequests(false)) { // let's use the old specification before the upgrade for logging NS_ConvertUTF8toUTF16 reportSpec(mURI); // upgrade the request from ws:// to wss:// and mark as secure mURI.ReplaceSubstring("ws://", "wss://"); if (NS_WARN_IF(mURI.Find("wss://") != 0)) { return; } @@ -1562,17 +1599,17 @@ WebSocketImpl::Init(JSContext* aCx, EmptyString(), // aScriptSample 0, // aLineNumber 0, // aColumnNumber nsIScriptError::warningFlag, "CSP", mInnerWindowID); } // Don't allow https:// to open ws:// - if (!mSecure && + if (!mIsServerSide && !mSecure && !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS", false)) { // Confirmed we are opening plain ws:// and want to prevent this from a // secure context (e.g. https). nsCOMPtr<nsIPrincipal> principal; nsCOMPtr<nsIURI> originURI; if (mWorkerPrivate) { // For workers, retrieve the URI from the WorkerPrivate @@ -1699,31 +1736,43 @@ WebSocketImpl::Init(JSContext* aCx, *aConnectionFailed = true; } else { *aConnectionFailed = false; } } void WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID, + nsITransportProvider* aTransportProvider, + const nsACString& aNegotiatedExtensions, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread"); + MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty()); nsCString asciiOrigin; aRv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin); if (NS_WARN_IF(aRv.Failed())) { return; } + if (aTransportProvider) { + aRv = mChannel->SetServerParameters(aTransportProvider, aNegotiatedExtensions); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } + ToLowerCase(asciiOrigin); nsCOMPtr<nsIURI> uri; - aRv = NS_NewURI(getter_AddRefs(uri), mURI); - MOZ_ASSERT(!aRv.Failed()); + if (!aTransportProvider) { + aRv = NS_NewURI(getter_AddRefs(uri), mURI); + MOZ_ASSERT(!aRv.Failed()); + } aRv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr); if (NS_WARN_IF(aRv.Failed())) { return; } mInnerWindowID = aInnerWindowID; } @@ -1979,16 +2028,23 @@ WebSocket::CreateAndDispatchCloseEvent(b } nsresult WebSocketImpl::ParseURL(const nsAString& aURL) { AssertIsOnMainThread(); NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR); + if (mIsServerSide) { + mWebSocket->mURI = aURL; + CopyUTF16toUTF8(mWebSocket->mURI, mURI); + + return NS_OK; + } + nsCOMPtr<nsIURI> uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); bool hasRef;
--- a/dom/base/WebSocket.h +++ b/dom/base/WebSocket.h @@ -19,16 +19,17 @@ #include "nsISupportsUtils.h" #include "nsString.h" #include "nsWrapperCache.h" #define DEFAULT_WS_SCHEME_PORT 80 #define DEFAULT_WSS_SCHEME_PORT 443 class nsIInputStream; +class nsITransportProvider; namespace mozilla { namespace dom { class Blob; class WebSocketImpl; @@ -77,16 +78,23 @@ public: // WebIDL interface: const nsAString& aProtocol, ErrorResult& rv); static already_AddRefed<WebSocket> Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, const Sequence<nsString>& aProtocols, ErrorResult& rv); + static already_AddRefed<WebSocket> ConstructorCommon(const GlobalObject& aGlobal, + const nsAString& aUrl, + const Sequence<nsString>& aProtocols, + nsITransportProvider* aTransportProvider, + const nsACString& aNegotiatedExtensions, + ErrorResult& rv); + // webIDL: readonly attribute DOMString url void GetUrl(nsAString& aResult); // webIDL: readonly attribute unsigned short readyState; uint16_t ReadyState(); // webIDL: readonly attribute unsigned long bufferedAmount; uint32_t BufferedAmount() const;
--- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -64,18 +64,16 @@ # undef SendMessage # endif #endif using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::ipc; -static const size_t kMinTelemetryMessageSize = 8192; - nsFrameMessageManager::nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback, nsFrameMessageManager* aParentManager, /* mozilla::dom::ipc::MessageManagerFlags */ uint32_t aFlags) : mChrome(!!(aFlags & mozilla::dom::ipc::MM_CHROME)), mGlobal(!!(aFlags & mozilla::dom::ipc::MM_GLOBAL)), mIsProcessManager(!!(aFlags & mozilla::dom::ipc::MM_PROCESSMANAGER)), mIsBroadcaster(!!(aFlags & mozilla::dom::ipc::MM_BROADCASTER)), mOwnsCallback(!!(aFlags & mozilla::dom::ipc::MM_OWNSCALLBACK)), @@ -700,16 +698,32 @@ nsFrameMessageManager::SendRpcMessage(co JSContext* aCx, uint8_t aArgc, JS::MutableHandle<JS::Value> aRetval) { return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc, aRetval, false); } +static void +RecordMessageSize(size_t aDataLength, const nsAString& aMessageName) +{ + static const size_t kMinTelemetryMessageSize = 8192; + + if (aDataLength < kMinTelemetryMessageSize) { + return; + } + + NS_ConvertUTF16toUTF8 messageName(aMessageName); + messageName.StripChars("0123456789"); + + Telemetry::Accumulate(Telemetry::MESSAGE_MANAGER_MESSAGE_SIZE2, messageName, + aDataLength); +} + nsresult nsFrameMessageManager::SendMessage(const nsAString& aMessageName, JS::Handle<JS::Value> aJSON, JS::Handle<JS::Value> aObjects, nsIPrincipal* aPrincipal, JSContext* aCx, uint8_t aArgc, JS::MutableHandle<JS::Value> aRetval, @@ -727,21 +741,17 @@ nsFrameMessageManager::SendMessage(const return NS_ERROR_UNEXPECTED; } StructuredCloneData data; if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, JS::UndefinedHandleValue, data)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } - if (data.DataLength() >= kMinTelemetryMessageSize) { - Telemetry::Accumulate(Telemetry::MESSAGE_MANAGER_MESSAGE_SIZE, - NS_ConvertUTF16toUTF8(aMessageName), - data.DataLength()); - } + RecordMessageSize(data.DataLength(), aMessageName); JS::Rooted<JSObject*> objects(aCx); if (aArgc >= 3 && aObjects.isObject()) { objects = &aObjects.toObject(); } nsTArray<StructuredCloneData> retval; @@ -813,21 +823,17 @@ nsFrameMessageManager::DispatchAsyncMess JSContext* aCx, uint8_t aArgc) { StructuredCloneData data; if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, aTransfers, data)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } - if (data.DataLength() >= kMinTelemetryMessageSize) { - Telemetry::Accumulate(Telemetry::MESSAGE_MANAGER_MESSAGE_SIZE, - NS_ConvertUTF16toUTF8(aMessageName), - data.DataLength()); - } + RecordMessageSize(data.DataLength(), aMessageName); JS::Rooted<JSObject*> objects(aCx); if (aArgc >= 3 && aObjects.isObject()) { objects = &aObjects.toObject(); } return DispatchAsyncMessageInternal(aCx, aMessageName, data, objects, aPrincipal);
--- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -945,16 +945,17 @@ GK_ATOM(onupgradeneeded, "onupgradeneede GK_ATOM(onussdreceived, "onussdreceived") GK_ATOM(onversionchange, "onversionchange") GK_ATOM(onvoicechange, "onvoicechange") GK_ATOM(onvoiceschanged, "onvoiceschanged") GK_ATOM(onwebkitAnimationEnd, "onwebkitAnimationEnd") GK_ATOM(onwebkitAnimationIteration, "onwebkitAnimationIteration") GK_ATOM(onwebkitAnimationStart, "onwebkitAnimationStart") GK_ATOM(onwebkitTransitionEnd, "onwebkitTransitionEnd") +GK_ATOM(onwebsocket, "onwebsocket") GK_ATOM(onwheel, "onwheel") GK_ATOM(open, "open") GK_ATOM(optgroup, "optgroup") GK_ATOM(optimum, "optimum") GK_ATOM(option, "option") GK_ATOM(_or, "or") GK_ATOM(order, "order") GK_ATOM(ordinal, "ordinal") @@ -1303,16 +1304,17 @@ GK_ATOM(viewport_maximum_scale, "viewpor GK_ATOM(viewport_minimum_scale, "viewport-minimum-scale") GK_ATOM(viewport_user_scalable, "viewport-user-scalable") GK_ATOM(viewport_width, "viewport-width") GK_ATOM(visibility, "visibility") GK_ATOM(visuallyselected, "visuallyselected") GK_ATOM(vlink, "vlink") GK_ATOM(vspace, "vspace") GK_ATOM(wbr, "wbr") +GK_ATOM(webkitdirectory, "webkitdirectory") GK_ATOM(when, "when") GK_ATOM(where, "where") GK_ATOM(widget, "widget") GK_ATOM(width, "width") GK_ATOM(window, "window") GK_ATOM(headerWindowTarget, "window-target") GK_ATOM(windowtype, "windowtype") GK_ATOM(withParam, "with-param")
--- a/dom/base/nsScriptLoader.cpp +++ b/dom/base/nsScriptLoader.cpp @@ -2318,16 +2318,25 @@ nsScriptLoader::OnStreamComplete(nsIIncr } else { nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo(); bool enforceSRI = false; loadInfo->GetEnforceSRI(&enforceSRI); if (enforceSRI) { MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, ("nsScriptLoader::OnStreamComplete, required SRI not found")); + nsCOMPtr<nsIContentSecurityPolicy> csp; + loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp)); + nsAutoCString violationURISpec; + mDocument->GetDocumentURI()->GetAsciiSpec(violationURISpec); + uint32_t lineNo = request->mElement ? request->mElement->GetScriptLineNumber() : 0; + csp->LogViolationDetails( + nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT, + NS_ConvertUTF8toUTF16(violationURISpec), + EmptyString(), lineNo, EmptyString(), EmptyString()); rv = NS_ERROR_SRI_CORRUPT; } } if (NS_SUCCEEDED(rv)) { rv = PrepareLoadedRequest(request, aLoader, aChannelStatus, aString); }
--- a/dom/base/test/test_audioNotification.html +++ b/dom/base/test/test_audioNotification.html @@ -37,17 +37,17 @@ var tests = [ }, function() { expectedNotification = 'active'; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; audio.pause(); }, function() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); runTest(); }
--- a/dom/base/test/test_audioNotificationSilent_audioFile.html +++ b/dom/base/test/test_audioNotificationSilent_audioFile.html @@ -46,17 +46,17 @@ function audioPlayingStart() { info("Audio playing start"); audio.play(); } function audioBecomeSilentDuringPlaying() { info("Audio would become silent during playing"); - expectedPlaybackActive = 'inactive'; + expectedPlaybackActive = 'inactive-nonaudible'; expectedPlaying = true; } function finish() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); SimpleTest.finish();
--- a/dom/base/test/test_audioNotificationSilent_webAudio.html +++ b/dom/base/test/test_audioNotificationSilent_webAudio.html @@ -54,17 +54,17 @@ function audioPlayingStart() { info("Audio playing start"); playOscillatorNode(); } function audioBecomeSilentDuringPlaying() { info("Audio would become silent during playing"); - expectedPlaybackActive = 'inactive'; + expectedPlaybackActive = 'inactive-pause'; expectedPlaying = "running"; } function finish() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); SimpleTest.finish();
--- a/dom/base/test/test_audioNotificationStopOnNavigation.html +++ b/dom/base/test/test_audioNotificationStopOnNavigation.html @@ -37,17 +37,17 @@ var tests = [ }, function() { expectedNotification = 'active'; iframe.src = "file_audioLoop.html"; }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; iframe.src = "data:text/html,page without audio"; }, function() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); runTest(); }
--- a/dom/base/test/test_audioNotificationStream.html +++ b/dom/base/test/test_audioNotificationStream.html @@ -37,17 +37,17 @@ var tests = [ }, function() { expectedNotification = 'active'; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; audio.pause(); }, function() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); runTest(); }
--- a/dom/base/test/test_audioNotificationWithEarlyPlay.html +++ b/dom/base/test/test_audioNotificationWithEarlyPlay.html @@ -39,17 +39,17 @@ var tests = [ function() { expectedNotification = 'active'; audio.src = "audio.ogg"; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; audio.pause(); }, function() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); runTest(); }
--- a/dom/base/test/test_noAudioNotificationOnMutedElement.html +++ b/dom/base/test/test_noAudioNotificationOnMutedElement.html @@ -45,38 +45,38 @@ var tests = [ // Verify that muting and unmuting dispatches the events as expected. function() { expectedNotification = 'active'; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-nonaudible'; audio.muted = true; }, function() { expectedNotification = 'active'; audio.muted = false; }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; audio.pause(); }, // Verify that no events are dispatched when muted. function() { expectedNotification = 'active'; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-nonaudible'; audio.muted = true; }, function() { expectedNotification = null; audio.onpause = function() { // Yield to the event loop a few times to make sure that audio-playback is not dispatched. SimpleTest.executeSoon(function() {
--- a/dom/base/test/test_noAudioNotificationOnMutedOrVolume0Element.html +++ b/dom/base/test/test_noAudioNotificationOnMutedOrVolume0Element.html @@ -45,17 +45,17 @@ var tests = [ // Verify that unmuting when the volume is 0 doesn't dispatch the events. function() { expectedNotification = 'active'; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-nonaudible'; audio.muted = true; }, function() { expectedNotification = null; audio.volume = 0; // Yield to the event loop a few times to make sure that audio-playback is not dispatched. SimpleTest.executeSoon(function() { @@ -81,28 +81,28 @@ var tests = [ }, function() { expectedNotification = 'active'; audio.volume = 0.5; }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; audio.pause(); }, // Verify that raising the volume when muted doesn't dispatch the events. function() { expectedNotification = 'active'; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-nonaudible'; audio.muted = true; }, function() { expectedNotification = null; audio.volume = 0; // Yield to the event loop a few times to make sure that audio-playback is not dispatched. SimpleTest.executeSoon(function() { @@ -128,17 +128,17 @@ var tests = [ }, function() { expectedNotification = 'active'; audio.muted = false; }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; audio.pause(); }, function() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); runTest(); }
--- a/dom/base/test/test_noAudioNotificationOnVolume0Element.html +++ b/dom/base/test/test_noAudioNotificationOnVolume0Element.html @@ -45,38 +45,38 @@ var tests = [ // Verify that muting and unmuting dispatches the events as expected. function() { expectedNotification = 'active'; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-nonaudible'; audio.volume = 0; }, function() { expectedNotification = 'active'; audio.volume = 1; }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; audio.pause(); }, // Verify that no events are dispatched when volume is set to 0.. function() { expectedNotification = 'active'; audio.play(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-nonaudible'; audio.volume = 0; }, function() { expectedNotification = null; audio.onpause = function() { // Yield to the event loop a few times to make sure that audio-playback is not dispatched. SimpleTest.executeSoon(function() {
--- a/dom/base/test/test_pluginAudioNotification.html +++ b/dom/base/test/test_pluginAudioNotification.html @@ -79,17 +79,17 @@ var tests = [ iframe.contentWindow.toggleMuteState(true); ok(iframe.contentWindow.pluginMuted(), "Plugin should be muted"); iframe.contentWindow.toggleMuteState(false); ok(!iframe.contentWindow.pluginMuted(), "Plugin should not be muted"); runTest(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; iframe.contentWindow.stopAudio(); }, function() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); runTest(); }
--- a/dom/base/test/test_webaudioNotification.html +++ b/dom/base/test/test_webaudioNotification.html @@ -37,27 +37,27 @@ var tests = [ }, function() { iframe.src = "file_webaudioLoop.html"; expectedNotification = 'active'; }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; iframe.contentWindow.suspendAC(); }, function() { expectedNotification = 'active'; iframe.contentWindow.resumeAC(); }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; iframe.contentWindow.closeAC(); }, function() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); runTest(); }
--- a/dom/base/test/test_webaudioNotificationStopOnNavigation.html +++ b/dom/base/test/test_webaudioNotificationStopOnNavigation.html @@ -37,17 +37,17 @@ var tests = [ }, function() { expectedNotification = 'active'; iframe.src = "file_webaudioLoop2.html"; }, function() { - expectedNotification = 'inactive'; + expectedNotification = 'inactive-pause'; iframe.src = "data:text/html,page without audio"; }, function() { observerService.removeObserver(observer, "audio-playback"); ok(true, "Observer removed"); runTest(); }
--- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -21,16 +21,18 @@ #include "nsContentUtils.h" #include "nsGlobalWindow.h" #include "nsIDocShell.h" #include "nsIDOMGlobalPropertyInitializer.h" #include "nsIPermissionManager.h" #include "nsIPrincipal.h" #include "nsIXPConnect.h" #include "nsUTF8Utils.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" #include "WrapperFactory.h" #include "xpcprivate.h" #include "XrayWrapper.h" #include "nsPrintfCString.h" #include "mozilla/Snprintf.h" #include "nsGlobalWindow.h" #include "mozilla/dom/ScriptSettings.h" @@ -51,16 +53,18 @@ #include "mozilla/jsipc/CrossProcessObjectWrappers.h" #include "nsDOMClassInfo.h" #include "ipc/ErrorIPCUtils.h" #include "mozilla/UseCounter.h" namespace mozilla { namespace dom { +using namespace workers; + const JSErrorFormatString ErrorFormatString[] = { #define MSG_DEF(_name, _argc, _exn, _str) \ { #_name, _str, _argc, _exn }, #include "mozilla/dom/Errors.msg" #undef MSG_DEF }; #define MSG_DEF(_name, _argc, _exn, _str) \ @@ -2496,32 +2500,30 @@ ConvertJSValueToByteString(JSContext* cx JS_EncodeStringToBuffer(cx, s, result.BeginWriting(), length); return true; } bool IsInPrivilegedApp(JSContext* aCx, JSObject* aObj) { - using mozilla::dom::workers::GetWorkerPrivateFromContext; if (!NS_IsMainThread()) { return GetWorkerPrivateFromContext(aCx)->IsInPrivilegedApp(); } nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aObj); uint16_t appStatus = principal->GetAppStatus(); return (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED || appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED) || Preferences::GetBool("dom.ignore_webidl_scope_checks", false); } bool IsInCertifiedApp(JSContext* aCx, JSObject* aObj) { - using mozilla::dom::workers::GetWorkerPrivateFromContext; if (!NS_IsMainThread()) { return GetWorkerPrivateFromContext(aCx)->IsInCertifiedApp(); } nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aObj); return principal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED || Preferences::GetBool("dom.ignore_webidl_scope_checks", false); } @@ -3280,38 +3282,159 @@ SetDocumentAndPageUseCounter(JSContext* UseCounter aUseCounter) { nsGlobalWindow* win = xpc::WindowGlobalOrNull(js::UncheckedUnwrap(aObject)); if (win && win->GetDocument()) { win->GetDocument()->SetDocumentAndPageUseCounter(aUseCounter); } } +namespace { + +// This runnable is used to write a deprecation message from a worker to the +// console running on the main-thread. +class DeprecationWarningRunnable final : public Runnable + , public WorkerFeature +{ + WorkerPrivate* mWorkerPrivate; + nsIDocument::DeprecatedOperations mOperation; + +public: + DeprecationWarningRunnable(WorkerPrivate* aWorkerPrivate, + nsIDocument::DeprecatedOperations aOperation) + : mWorkerPrivate(aWorkerPrivate) + , mOperation(aOperation) + { + MOZ_ASSERT(aWorkerPrivate); + } + + void + Dispatch() + { + if (NS_WARN_IF(!mWorkerPrivate->AddFeature(this))) { + return; + } + + if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(this)))) { + mWorkerPrivate->RemoveFeature(this); + return; + } + } + + virtual bool + Notify(Status aStatus) override + { + // We don't care about the notification. We just want to keep the + // mWorkerPrivate alive. + return true; + } + +private: + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + // Walk up to our containing page + WorkerPrivate* wp = mWorkerPrivate; + while (wp->GetParent()) { + wp = wp->GetParent(); + } + + nsPIDOMWindowInner* window = wp->GetWindow(); + if (window && window->GetExtantDoc()) { + window->GetExtantDoc()->WarnOnceAbout(mOperation); + } + + ReleaseWorker(); + return NS_OK; + } + + void + ReleaseWorker() + { + class ReleaseRunnable final : public WorkerRunnable + { + RefPtr<DeprecationWarningRunnable> mRunnable; + + public: + ReleaseRunnable(WorkerPrivate* aWorkerPrivate, + DeprecationWarningRunnable* aRunnable) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) + , mRunnable(aRunnable) + {} + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + aWorkerPrivate->RemoveFeature(mRunnable); + return true; + } + + virtual bool + PreDispatch(WorkerPrivate* aWorkerPrivate) override + { + AssertIsOnMainThread(); + return true; + } + + virtual void + PostDispatch(WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override + { + } + }; + + RefPtr<ReleaseRunnable> runnable = + new ReleaseRunnable(mWorkerPrivate, this); + NS_WARN_IF(!runnable->Dispatch()); + } +}; + +} // anonymous namespace + void DeprecationWarning(JSContext* aCx, JSObject* aObject, nsIDocument::DeprecatedOperations aOperation) { GlobalObject global(aCx, aObject); if (global.Failed()) { NS_ERROR("Could not create global for DeprecationWarning"); return; } - nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global.GetAsSupports()); - if (window && window->GetExtantDoc()) { - window->GetExtantDoc()->WarnOnceAbout(aOperation); + if (NS_IsMainThread()) { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global.GetAsSupports()); + if (window && window->GetExtantDoc()) { + window->GetExtantDoc()->WarnOnceAbout(aOperation); + } + + return; } + + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return; + } + + RefPtr<DeprecationWarningRunnable> runnable = + new DeprecationWarningRunnable(workerPrivate, aOperation); + runnable->Dispatch(); } namespace binding_detail { JSObject* UnprivilegedJunkScopeOrWorkerGlobal() { if (NS_IsMainThread()) { return xpc::UnprivilegedJunkScope(); } - return workers::GetCurrentThreadWorkerGlobal(); + return GetCurrentThreadWorkerGlobal(); } } // namespace binding_detail } // namespace dom } // namespace mozilla
--- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -1890,16 +1890,29 @@ struct FakeString { } void Rebind(const nsString::char_type* aData, nsString::size_type aLength) { MOZ_ASSERT(mFlags == nsString::F_TERMINATED); mData = const_cast<nsString::char_type*>(aData); mLength = aLength; } + // Share aString's string buffer, if it has one; otherwise, make this string + // depend upon aString's data. aString should outlive this instance of + // FakeString. + void ShareOrDependUpon(const nsAString& aString) { + RefPtr<nsStringBuffer> sharedBuffer = nsStringBuffer::FromString(aString); + if (!sharedBuffer) { + Rebind(aString.Data(), aString.Length()); + } else { + AssignFromStringBuffer(sharedBuffer.forget()); + mLength = aString.Length(); + } + } + void Truncate() { MOZ_ASSERT(mFlags == nsString::F_TERMINATED); mData = nsString::char_traits::sEmptyBuffer; mLength = 0; } void SetIsVoid(bool aValue) { MOZ_ASSERT(aValue, @@ -1924,23 +1937,22 @@ struct FakeString { } // Reserve space to write aLength chars, not including null-terminator. bool SetLength(nsString::size_type aLength, mozilla::fallible_t const&) { // Use mInlineStorage for small strings. if (aLength < sInlineCapacity) { SetData(mInlineStorage); } else { - nsStringBuffer *buf = nsStringBuffer::Alloc((aLength + 1) * sizeof(nsString::char_type)).take(); + RefPtr<nsStringBuffer> buf = nsStringBuffer::Alloc((aLength + 1) * sizeof(nsString::char_type)); if (MOZ_UNLIKELY(!buf)) { return false; } - SetData(static_cast<nsString::char_type*>(buf->Data())); - mFlags = nsString::F_SHARED | nsString::F_TERMINATED; + AssignFromStringBuffer(buf.forget()); } mLength = aLength; mData[mLength] = char16_t(0); return true; } // If this ever changes, change the corresponding code in the // Optional<nsAString> specialization as well. @@ -1966,16 +1978,20 @@ private: FakeString(const FakeString& other) = delete; void operator=(const FakeString& other) = delete; void SetData(nsString::char_type* aData) { MOZ_ASSERT(mFlags == nsString::F_TERMINATED); mData = const_cast<nsString::char_type*>(aData); } + void AssignFromStringBuffer(already_AddRefed<nsStringBuffer> aBuffer) { + SetData(static_cast<nsString::char_type*>(aBuffer.take()->Data())); + mFlags = nsString::F_SHARED | nsString::F_TERMINATED; + } friend class NonNull<nsAString>; // A class to use for our static asserts to ensure our object layout // matches that of nsString. class StringAsserter; friend class StringAsserter;
--- a/dom/bindings/Exceptions.cpp +++ b/dom/bindings/Exceptions.cpp @@ -198,17 +198,17 @@ GetCurrentJSStack(int32_t aMaxDepth) { // is there a current context available? JSContext* cx = nsContentUtils::GetCurrentJSContextForThread(); if (!cx || !js::GetContextCompartment(cx)) { return nullptr; } - return exceptions::CreateStack(cx, aMaxDepth); + return dom::exceptions::CreateStack(cx, aMaxDepth); } AutoForceSetExceptionOnContext::AutoForceSetExceptionOnContext(JSContext* aCx) : mCx(aCx) { mOldValue = JS::ContextOptionsRef(mCx).autoJSAPIOwnsErrorReporting(); JS::ContextOptionsRef(mCx).setAutoJSAPIOwnsErrorReporting(true); }
new file mode 100644 --- /dev/null +++ b/dom/browser-element/mochitest/browserElement_ActiveStateChange.js @@ -0,0 +1,110 @@ +"use strict"; + +SimpleTest.waitForExplicitFinish(); +browserElementTestHelpers.setEnabledPref(true); +browserElementTestHelpers.addPermission(); + +var fileURL = 'http://example.org/tests/dom/browser-element/mochitest/file_browserElement_ActiveStateChange.html'; +var generator = runTests(); +var testFrame; +var ac; + +function assert(aVal, aMessage) { + return (!aVal) ? error(aMessage) : 0; +} + +function error(aMessage) { + ok(false, "Error : " + aMessage); + finish(); +} + +function continueTest() { + try { + generator.next(); + } catch (e if e instanceof StopIteration) { + error("Stop test because of exception!"); + } +} + +function finish() { + document.body.removeChild(testFrame); + SimpleTest.finish(); +} + +function setCommand(aArg) { + assert(!!ac, "Audio channel doesn't exist!"); + info("# Command = " + aArg); + + testFrame.src = fileURL + '#' + aArg; + var expectedActive = false; + switch (aArg) { + case 'play': + expectedActive = true; + break; + case 'pause': + expectedActive = false; + break; + default : + error("Undefined command!"); + } + + ac.onactivestatechanged = () => { + ac.onactivestatechanged = null; + ac.isActive().onsuccess = (e) => { + is(expectedActive, e.target.result, + "Correct active state = " + expectedActive); + continueTest(); + } + }; +} + +function runTests() { + setCommand('play'); + yield undefined; + + setCommand('pause'); + yield undefined; + + finish(); + yield undefined; +} + +function setupTestFrame() { + testFrame = document.createElement('iframe'); + testFrame.setAttribute('mozbrowser', 'true'); + testFrame.setAttribute('mozapp', 'http://example.org/manifest.webapp'); + testFrame.src = fileURL; + + function loadend() { + testFrame.removeEventListener('mozbrowserloadend', loadend); + ok("allowedAudioChannels" in testFrame, "allowedAudioChannels exist"); + var channels = testFrame.allowedAudioChannels; + is(channels.length, 1, "1 audio channel by default"); + + ac = channels[0]; + + ok(ac instanceof BrowserElementAudioChannel, "Correct class"); + ok("isActive" in ac, "isActive exists"); + ok("onactivestatechanged" in ac, "onactivestatechanged exists"); + + generator.next(); + } + + function alertError(e) { + testFrame.removeEventListener('mozbrowsershowmodalprompt', alertError); + var message = e.detail.message + error(message); + } + + testFrame.addEventListener('mozbrowserloadend', loadend); + testFrame.addEventListener('mozbrowsershowmodalprompt', alertError); + document.body.appendChild(testFrame); +} + +addEventListener('testready', function() { + SpecialPowers.pushPrefEnv({'set': [["b2g.system_manifest_url", "http://mochi.test:8888/manifest.webapp"]]}, + function() { + SimpleTest.executeSoon(setupTestFrame); + }); +}); +
deleted file mode 100644 --- a/dom/browser-element/mochitest/browserElement_ActiveStateChangeOnChangingMutedOrVolume.js +++ /dev/null @@ -1,126 +0,0 @@ -"use strict"; - -SimpleTest.waitForExplicitFinish(); -browserElementTestHelpers.setEnabledPref(true); -browserElementTestHelpers.addPermission(); - -var fileURL = 'http://example.org/tests/dom/browser-element/mochitest/file_browserElement_ActiveStateChangeOnChangingMutedOrVolume.html'; -var generator = runTests(); -var testFrame; -var ac; - -function assert(aVal, aMessage) { - return (!aVal) ? error(aMessage) : 0; -} - -function error(aMessage) { - ok(false, "Error : " + aMessage); - finish(); -} - -function continueTest() { - try { - generator.next(); - } catch (e if e instanceof StopIteration) { - error("Stop test because of exception!"); - } -} - -function finish() { - document.body.removeChild(testFrame); - SimpleTest.finish(); -} - -function setCommand(aArg) { - assert(!!ac, "Audio channel doesn't exist!"); - info("# Command = " + aArg); - - testFrame.src = fileURL + '#' + aArg; - var expectedActive = false; - switch (aArg) { - case 'play': - case 'unmute': - case 'volume-1': - expectedActive = true; - break; - case 'pause': - case 'mute': - case 'volume-0': - expectedActive = false; - break; - default : - error("Undefined command!"); - } - - ac.onactivestatechanged = () => { - ac.onactivestatechanged = null; - ac.isActive().onsuccess = (e) => { - is(expectedActive, e.target.result, - "Correct active state = " + expectedActive); - continueTest(); - } - }; -} - -function runTests() { - setCommand('play'); - yield undefined; - - setCommand('mute'); - yield undefined; - - setCommand('unmute'); - yield undefined; - - setCommand('volume-0'); - yield undefined; - - setCommand('volume-1'); - yield undefined; - - setCommand('pause'); - yield undefined; - - finish(); - yield undefined; -} - -function setupTestFrame() { - testFrame = document.createElement('iframe'); - testFrame.setAttribute('mozbrowser', 'true'); - testFrame.setAttribute('mozapp', 'http://example.org/manifest.webapp'); - testFrame.src = fileURL; - - function loadend() { - testFrame.removeEventListener('mozbrowserloadend', loadend); - ok("allowedAudioChannels" in testFrame, "allowedAudioChannels exist"); - var channels = testFrame.allowedAudioChannels; - is(channels.length, 1, "1 audio channel by default"); - - ac = channels[0]; - - ok(ac instanceof BrowserElementAudioChannel, "Correct class"); - ok("isActive" in ac, "isActive exists"); - ok("onactivestatechanged" in ac, "onactivestatechanged exists"); - - generator.next(); - } - - function alertError(e) { - testFrame.removeEventListener('mozbrowsershowmodalprompt', alertError); - var message = e.detail.message - error(message); - } - - testFrame.addEventListener('mozbrowserloadend', loadend); - testFrame.addEventListener('mozbrowsershowmodalprompt', alertError); - document.body.appendChild(testFrame); -} - -addEventListener('testready', function() { - SpecialPowers.pushPrefEnv({'set': [["b2g.system_manifest_url", "http://mochi.test:8888/manifest.webapp"]]}, - function() { - SimpleTest.executeSoon(setupTestFrame); - }); -}); -
--- a/dom/browser-element/mochitest/browserElement_AudioPlayback.js +++ b/dom/browser-element/mochitest/browserElement_AudioPlayback.js @@ -42,25 +42,25 @@ function runTest() { // an audio element and play it. iframe.addEventListener('mozbrowserloadend', () => { let mm = SpecialPowers.getBrowserFrameMessageManager(iframe); mm.loadFrameScript('data:,(' + playAudioScript.toString() + ')();', false); }); // Two events should come in, when the audio starts, and stops playing. // The first one should have a detail of 'active' and the second one - // should have a detail of 'inactive'. + // should have a detail of 'inactive-pause'. let expectedNextData = 'active'; iframe.addEventListener('mozbrowseraudioplaybackchange', (e) => { is(e.detail, expectedNextData, 'Audio detail should be correct') is(e.target, iframe, 'event target should be the first iframe') - if (e.detail === 'inactive') { + if (e.detail === 'inactive-pause') { SimpleTest.finish(); } - expectedNextData = 'inactive'; + expectedNextData = 'inactive-pause'; }); // Make sure an event only goes to the first iframe. iframe2.addEventListener('mozbrowseraudioplaybackchange', (e) => { ok(false, 'mozbrowseraudioplaybackchange should dispatch to the correct browser'); });
new file mode 100644 --- /dev/null +++ b/dom/browser-element/mochitest/file_browserElement_ActiveStateChange.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<body> +<script type="application/javascript;version=1.7"> +var audio = new Audio(); +audio.src = "audio.ogg"; +audio.loop = true; + +function runCommands() +{ + switch(location.hash) { + case '#play': + audio.play(); + break; + case '#pause': + audio.pause(); + break; + default : + alert("Undefined command!"); + } +} +window.addEventListener('hashchange', runCommands); +</script> +</body> +</html> \ No newline at end of file
deleted file mode 100644 --- a/dom/browser-element/mochitest/file_browserElement_ActiveStateChangeOnChangingMutedOrVolume.html +++ /dev/null @@ -1,37 +0,0 @@ -<!DOCTYPE HTML> -<html> -<body> -<script type="application/javascript;version=1.7"> -var audio = new Audio(); -audio.src = "audio.ogg"; -audio.loop = true; - -function runCommands() -{ - switch(location.hash) { - case '#play': - audio.play(); - break; - case '#mute': - audio.muted = true; - break; - case '#unmute': - audio.muted = false; - break; - case '#volume-0': - audio.volume = 0.0; - break; - case '#volume-1': - audio.volume = 1.0; - break; - case '#pause': - audio.pause(); - break; - default : - alert("Undefined command!"); - } -} -window.addEventListener('hashchange', runCommands); -</script> -</body> -</html> \ No newline at end of file
--- a/dom/browser-element/mochitest/mochitest-oop.ini +++ b/dom/browser-element/mochitest/mochitest-oop.ini @@ -125,10 +125,10 @@ disabled = bug 924771 [test_browserElement_oop_AudioChannel.html] tags = audiochannel [test_browserElement_oop_AudioChannel_nested.html] tags = audiochannel [test_browserElement_oop_SetNFCFocus.html] [test_browserElement_oop_getWebManifest.html] [test_browserElement_oop_OpenWindowEmpty.html] skip-if = (toolkit == 'gonk') # Test doesn't work on B2G emulator -[test_browserElement_oop_ActiveStateChangeOnChangingMutedOrVolume.html] +[test_browserElement_oop_ActiveStateChange.html] tags = audiochannel \ No newline at end of file
--- a/dom/browser-element/mochitest/mochitest.ini +++ b/dom/browser-element/mochitest/mochitest.ini @@ -1,16 +1,16 @@ [DEFAULT] skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || e10s support-files = audio.ogg ../../../dom/media/test/short-video.ogv async.js browserElementTestHelpers.js - browserElement_ActiveStateChangeOnChangingMutedOrVolume.js + browserElement_ActiveStateChange.js browserElement_Alert.js browserElement_AlertInFrame.js browserElement_AllowEmbedAppsInNestedOOIframe.js browserElement_AppFramePermission.js browserElement_AppWindowNamespace.js browserElement_AudioChannelSeeking.js browserElement_AudioChannelMutedByDefault.js browserElement_AudioPlayback.js @@ -80,17 +80,17 @@ support-files = browserElement_VisibilityChange.js browserElement_XFrameOptions.js browserElement_XFrameOptionsAllowFrom.js browserElement_XFrameOptionsDeny.js browserElement_XFrameOptionsSameOrigin.js browserElement_GetContentDimensions.js browserElement_AudioChannel.js browserElement_AudioChannel_nested.js - file_browserElement_ActiveStateChangeOnChangingMutedOrVolume.html + file_browserElement_ActiveStateChange.html file_browserElement_AlertInFrame.html file_browserElement_AlertInFrame_Inner.html file_browserElement_AllowEmbedAppsInNestedOOIframe.html file_browserElement_AppFramePermission.html file_browserElement_AppWindowNamespace.html file_browserElement_AudioChannelSeeking.html file_browserElement_AudioChannel_nested.html file_browserElement_AudioChannelMutedByDefault.html @@ -262,10 +262,10 @@ disabled = bug 774100 [test_browserElement_inproc_GetContentDimensions.html] [test_browserElement_inproc_AudioChannel.html] tags = audiochannel [test_browserElement_inproc_AudioChannel_nested.html] tags = audiochannel [test_browserElement_inproc_SetNFCFocus.html] [test_browserElement_inproc_OpenWindowEmpty.html] skip-if = (toolkit == 'gonk') # Test doesn't work on B2G emulator -[test_browserElement_inproc_ActiveStateChangeOnChangingMutedOrVolume.html] +[test_browserElement_inproc_ActiveStateChange.html] tags = audiochannel
new file mode 100644 --- /dev/null +++ b/dom/browser-element/mochitest/test_browserElement_inproc_ActiveStateChange.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ActiveStateChangeOnChangingMutedOrVolume</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="browserElementTestHelpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script type="application/javascript;version=1.7" src="browserElement_ActiveStateChange.js"> +</script> +</body> +</html>
deleted file mode 100644 --- a/dom/browser-element/mochitest/test_browserElement_inproc_ActiveStateChangeOnChangingMutedOrVolume.html +++ /dev/null @@ -1,13 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <title>Test ActiveStateChangeOnChangingMutedOrVolume</title> - <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> - <script type="application/javascript" src="browserElementTestHelpers.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> -</head> -<body> -<script type="application/javascript;version=1.7" src="browserElement_ActiveStateChangeOnChangingMutedOrVolume.js"> -</script> -</body> -</html>
new file mode 100644 --- /dev/null +++ b/dom/browser-element/mochitest/test_browserElement_oop_ActiveStateChange.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ActiveStateChangeOnChangingMutedOrVolume</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="browserElementTestHelpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script type="application/javascript;version=1.7" src="browserElement_ActiveStateChange.js"> +</script> +</body> +</html>
deleted file mode 100644 --- a/dom/browser-element/mochitest/test_browserElement_oop_ActiveStateChangeOnChangingMutedOrVolume.html +++ /dev/null @@ -1,13 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <title>Test ActiveStateChangeOnChangingMutedOrVolume</title> - <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> - <script type="application/javascript" src="browserElementTestHelpers.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> -</head> -<body> -<script type="application/javascript;version=1.7" src="browserElement_ActiveStateChangeOnChangingMutedOrVolume.js"> -</script> -</body> -</html>
--- a/dom/cache/TypeUtils.cpp +++ b/dom/cache/TypeUtils.cpp @@ -276,17 +276,17 @@ TypeUtils::ToResponse(const CacheRespons ir->InitChannelInfo(aIn.channelInfo()); if (aIn.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) { UniquePtr<mozilla::ipc::PrincipalInfo> info(new mozilla::ipc::PrincipalInfo(aIn.principalInfo().get_PrincipalInfo())); ir->SetPrincipalInfo(Move(info)); } nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body()); - ir->SetBody(stream); + ir->SetBody(stream, InternalResponse::UNKNOWN_BODY_SIZE); switch (aIn.type()) { case ResponseType::Basic: ir = ir->BasicResponse(); break; case ResponseType::Cors: ir = ir->CORSResponse();
new file mode 100644 --- /dev/null +++ b/dom/canvas/test/reftest/clip-multiple-move-1-ref.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> +<canvas id="canvas" width="100" height="100"></canvas> +<script> + +var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); + +ctx.fillStyle = '#0f0'; +ctx.fillRect(0, 0, 100, 100); + +ctx.beginPath(); +ctx.rect(30, 30, 40, 40); +ctx.clip(); + +ctx.fillStyle = '#f00'; + +ctx.fillRect(0, 0, 100, 100); + +</script> +</body></html>
new file mode 100644 --- /dev/null +++ b/dom/canvas/test/reftest/clip-multiple-move-1.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> +<canvas id="canvas" width="100" height="100"></canvas> + +<script> + +var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); + +ctx.fillStyle = '#0f0'; +ctx.fillRect(0, 0, 100, 100); + +ctx.beginPath(); +ctx.moveTo(100, 30); +ctx.moveTo(30, 30); +ctx.lineTo(30, 70); +ctx.lineTo(70, 70); +ctx.lineTo(70, 30); +ctx.closePath(); +ctx.clip(); + +ctx.fillStyle = '#f00'; +ctx.fillRect(0, 0, 100, 100); + +</script> +</body></html>
new file mode 100644 --- /dev/null +++ b/dom/canvas/test/reftest/clip-multiple-move-2-ref.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> +<canvas id="canvas" width="150" height="150"></canvas> +<script> + +var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); +ctx.fillStyle = '#0f0'; +ctx.fillRect(0, 0, 150, 150); + +</script> +</body></html>
new file mode 100644 --- /dev/null +++ b/dom/canvas/test/reftest/clip-multiple-move-2.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> +<canvas id="canvas" width="150" height="150"></canvas> + +<script> + +var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); + +ctx.fillStyle = '#f00'; +ctx.fillRect(0, 0, 150, 150); + +ctx.beginPath(); +ctx.moveTo(0, 0); +ctx.moveTo(0, -1); +ctx.lineTo(0, 150); +ctx.lineTo(150, 150); + +// The coordinate '149.99999' makes skia use GrConvexPolyEffect to handle the points. +// The result should be same as '150'. So this test checks if the GrConvexPolyEffect +// works well. +ctx.lineTo(149.99999, -1); + +ctx.closePath(); +ctx.clip(); + +ctx.fillStyle = '#0f0'; +ctx.fillRect(0, 0, 150, 150); + +</script> +</body></html>
--- a/dom/canvas/test/reftest/reftest.list +++ b/dom/canvas/test/reftest/reftest.list @@ -143,16 +143,20 @@ fuzzy(9,40000) skip-if(!(Android||B2G)) skip-if(!winWidget) pref(webgl.disable-angle,true) == webgl-color-test.html?native-gl wrapper.html?colors-no-alpha.png # Non-WebGL Reftests! # Do we correctly handle multiple clip paths? != clip-multiple-paths.html clip-multiple-paths-badref.html +# Bug 1255062 +== clip-multiple-move-1.html clip-multiple-move-1-ref.html +== clip-multiple-move-2.html clip-multiple-move-2-ref.html + # Bug 815648 == stroketext-shadow.html stroketext-shadow-ref.html # focus rings pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(cocoaWidget) skip-if(winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(cocoaWidget) skip-if(Android) skip-if(winWidget) fuzzy-if(gtkWidget,64,410) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html # Check that captureStream() displays in a local video element
--- a/dom/events/JSEventHandler.cpp +++ b/dom/events/JSEventHandler.cpp @@ -138,17 +138,17 @@ JSEventHandler::HandleEvent(nsIDOMEvent* Optional<uint32_t> lineNumber; Optional<uint32_t> columnNumber; Optional<JS::Handle<JS::Value>> error; NS_ENSURE_TRUE(aEvent, NS_ERROR_UNEXPECTED); ErrorEvent* scriptEvent = aEvent->InternalDOMEvent()->AsErrorEvent(); if (scriptEvent) { scriptEvent->GetMessage(errorMsg); - msgOrEvent.SetAsString().Rebind(errorMsg.Data(), errorMsg.Length()); + msgOrEvent.SetAsString().ShareOrDependUpon(errorMsg); scriptEvent->GetFilename(file); fileName = &file; lineNumber.Construct(); lineNumber.Value() = scriptEvent->Lineno(); columnNumber.Construct();
--- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -444,67 +444,80 @@ WorkerFetchResolver::OnResponseEnd() NS_WARNING("Failed to dispatch WorkerFetchResponseEndControlRunnable"); } } } namespace { nsresult ExtractFromArrayBuffer(const ArrayBuffer& aBuffer, - nsIInputStream** aStream) + nsIInputStream** aStream, + uint64_t& aContentLength) { aBuffer.ComputeLengthAndData(); + aContentLength = aBuffer.Length(); //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok. return NS_NewByteInputStream(aStream, reinterpret_cast<char*>(aBuffer.Data()), aBuffer.Length(), NS_ASSIGNMENT_COPY); } nsresult ExtractFromArrayBufferView(const ArrayBufferView& aBuffer, - nsIInputStream** aStream) + nsIInputStream** aStream, + uint64_t& aContentLength) { aBuffer.ComputeLengthAndData(); + aContentLength = aBuffer.Length(); //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok. return NS_NewByteInputStream(aStream, reinterpret_cast<char*>(aBuffer.Data()), aBuffer.Length(), NS_ASSIGNMENT_COPY); } nsresult -ExtractFromBlob(const Blob& aBlob, nsIInputStream** aStream, - nsCString& aContentType) +ExtractFromBlob(const Blob& aBlob, + nsIInputStream** aStream, + nsCString& aContentType, + uint64_t& aContentLength) { RefPtr<BlobImpl> impl = aBlob.Impl(); ErrorResult rv; + aContentLength = impl->GetSize(rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + impl->GetInternalStream(aStream, rv); if (NS_WARN_IF(rv.Failed())) { return rv.StealNSResult(); } nsAutoString type; impl->GetType(type); aContentType = NS_ConvertUTF16toUTF8(type); return NS_OK; } nsresult -ExtractFromFormData(FormData& aFormData, nsIInputStream** aStream, - nsCString& aContentType) +ExtractFromFormData(FormData& aFormData, + nsIInputStream** aStream, + nsCString& aContentType, + uint64_t& aContentLength) { - uint64_t unusedContentLength; nsAutoCString unusedCharset; - return aFormData.GetSendInfo(aStream, &unusedContentLength, + return aFormData.GetSendInfo(aStream, &aContentLength, aContentType, unusedCharset); } nsresult ExtractFromUSVString(const nsString& aStr, nsIInputStream** aStream, - nsCString& aContentType) + nsCString& aContentType, + uint64_t& aContentLength) { nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8"); if (!encoder) { return NS_ERROR_OUT_OF_MEMORY; } int32_t destBufferLen; nsresult rv = encoder->GetMaxLength(aStr.get(), aStr.Length(), &destBufferLen); @@ -524,90 +537,106 @@ ExtractFromUSVString(const nsString& aSt if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(outLen <= destBufferLen); encoded.SetLength(outLen); aContentType = NS_LITERAL_CSTRING("text/plain;charset=UTF-8"); + aContentLength = outLen; return NS_NewCStringInputStream(aStream, encoded); } nsresult ExtractFromURLSearchParams(const URLSearchParams& aParams, nsIInputStream** aStream, - nsCString& aContentType) + nsCString& aContentType, + uint64_t& aContentLength) { nsAutoString serialized; aParams.Stringify(serialized); aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8"); + aContentLength = serialized.Length(); return NS_NewCStringInputStream(aStream, NS_ConvertUTF16toUTF8(serialized)); } } // namespace nsresult ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, nsIInputStream** aStream, - nsCString& aContentType) + nsCString& aContentType, + uint64_t& aContentLength) { MOZ_ASSERT(aStream); if (aBodyInit.IsArrayBuffer()) { const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer(); - return ExtractFromArrayBuffer(buf, aStream); - } else if (aBodyInit.IsArrayBufferView()) { + return ExtractFromArrayBuffer(buf, aStream, aContentLength); + } + if (aBodyInit.IsArrayBufferView()) { const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView(); - return ExtractFromArrayBufferView(buf, aStream); - } else if (aBodyInit.IsBlob()) { + return ExtractFromArrayBufferView(buf, aStream, aContentLength); + } + if (aBodyInit.IsBlob()) { const Blob& blob = aBodyInit.GetAsBlob(); - return ExtractFromBlob(blob, aStream, aContentType); - } else if (aBodyInit.IsFormData()) { + return ExtractFromBlob(blob, aStream, aContentType, aContentLength); + } + if (aBodyInit.IsFormData()) { FormData& form = aBodyInit.GetAsFormData(); - return ExtractFromFormData(form, aStream, aContentType); - } else if (aBodyInit.IsUSVString()) { + return ExtractFromFormData(form, aStream, aContentType, aContentLength); + } + if (aBodyInit.IsUSVString()) { nsAutoString str; str.Assign(aBodyInit.GetAsUSVString()); - return ExtractFromUSVString(str, aStream, aContentType); - } else if (aBodyInit.IsURLSearchParams()) { + return ExtractFromUSVString(str, aStream, aContentType, aContentLength); + } + if (aBodyInit.IsURLSearchParams()) { URLSearchParams& params = aBodyInit.GetAsURLSearchParams(); - return ExtractFromURLSearchParams(params, aStream, aContentType); + return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength); } NS_NOTREACHED("Should never reach here"); return NS_ERROR_FAILURE; } nsresult ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, nsIInputStream** aStream, - nsCString& aContentType) + nsCString& aContentType, + uint64_t& aContentLength) { MOZ_ASSERT(aStream); + MOZ_ASSERT(!*aStream); if (aBodyInit.IsArrayBuffer()) { const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer(); - return ExtractFromArrayBuffer(buf, aStream); - } else if (aBodyInit.IsArrayBufferView()) { + return ExtractFromArrayBuffer(buf, aStream, aContentLength); + } + if (aBodyInit.IsArrayBufferView()) { const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView(); - return ExtractFromArrayBufferView(buf, aStream); - } else if (aBodyInit.IsBlob()) { + return ExtractFromArrayBufferView(buf, aStream, aContentLength); + } + if (aBodyInit.IsBlob()) { const Blob& blob = aBodyInit.GetAsBlob(); - return ExtractFromBlob(blob, aStream, aContentType); - } else if (aBodyInit.IsFormData()) { + return ExtractFromBlob(blob, aStream, aContentType, aContentLength); + } + if (aBodyInit.IsFormData()) { FormData& form = aBodyInit.GetAsFormData(); - return ExtractFromFormData(form, aStream, aContentType); - } else if (aBodyInit.IsUSVString()) { + return ExtractFromFormData(form, aStream, aContentType, aContentLength); + } + if (aBodyInit.IsUSVString()) { nsAutoString str; str.Assign(aBodyInit.GetAsUSVString()); - return ExtractFromUSVString(str, aStream, aContentType); - } else if (aBodyInit.IsURLSearchParams()) { + return ExtractFromUSVString(str, aStream, aContentType, aContentLength); + } + if (aBodyInit.IsURLSearchParams()) { URLSearchParams& params = aBodyInit.GetAsURLSearchParams(); - return ExtractFromURLSearchParams(params, aStream, aContentType); + return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength); } NS_NOTREACHED("Should never reach here"); return NS_ERROR_FAILURE; } namespace { /*
--- a/dom/fetch/Fetch.h +++ b/dom/fetch/Fetch.h @@ -45,25 +45,27 @@ UpdateRequestReferrer(nsIGlobalObject* a /* * Creates an nsIInputStream based on the fetch specifications 'extract a byte * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract. * Stores content type in out param aContentType. */ nsresult ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, nsIInputStream** aStream, - nsCString& aContentType); + nsCString& aContentType, + uint64_t& aContentLength); /* * Non-owning version. */ nsresult ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, nsIInputStream** aStream, - nsCString& aContentType); + nsCString& aContentType, + uint64_t& aContentLength); template <class Derived> class FetchBodyFeature; /* * FetchBody's body consumption uses nsIInputStreamPump to read from the * underlying stream to a block of memory, which is then adopted by * ContinueConsumeBody() and converted to the right type based on the JS * function called.
--- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -483,16 +483,20 @@ FetchDriver::OnStartRequest(nsIRequest* nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); // On a successful redirect we perform the following substeps of HTTP Fetch, // step 5, "redirect status", step 11. bool foundOpaqueRedirect = false; + int64_t contentLength = InternalResponse::UNKNOWN_BODY_SIZE; + rv = channel->GetContentLength(&contentLength); + MOZ_ASSERT_IF(NS_FAILED(rv), contentLength == InternalResponse::UNKNOWN_BODY_SIZE); + if (httpChannel) { uint32_t responseStatus; httpChannel->GetResponseStatus(&responseStatus); if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) { if (mRequest->GetRedirectMode() == RequestRedirect::Error) { FailWithNetworkError(); return NS_BINDING_FAILED; @@ -507,16 +511,27 @@ FetchDriver::OnStartRequest(nsIRequest* response = new InternalResponse(responseStatus, statusText); RefPtr<FillResponseHeaders> visitor = new FillResponseHeaders(response); rv = httpChannel->VisitResponseHeaders(visitor); if (NS_WARN_IF(NS_FAILED(rv))) { NS_WARNING("Failed to visit all headers."); } + + // If Content-Encoding or Transfer-Encoding headers are set, then the actual + // Content-Length (which refer to the decoded data) is obscured behind the encodings. + ErrorResult result; + if (response->Headers()->Has(NS_LITERAL_CSTRING("content-encoding"), result) || + response->Headers()->Has(NS_LITERAL_CSTRING("transfer-encoding"), result)) { + NS_WARNING("Cannot know response Content-Length due to presence of Content-Encoding " + "or Transfer-Encoding headers."); + contentLength = InternalResponse::UNKNOWN_BODY_SIZE; + } + MOZ_ASSERT(!result.Failed()); } else { response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; nsAutoCString contentType; rv = channel->GetContentType(contentType); if (NS_SUCCEEDED(rv) && !contentType.IsEmpty()) { nsAutoCString contentCharset; @@ -526,19 +541,17 @@ FetchDriver::OnStartRequest(nsIRequest* } response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result); MOZ_ASSERT(!result.Failed()); } - int64_t contentLength; - rv = channel->GetContentLength(&contentLength); - if (NS_SUCCEEDED(rv) && contentLength) { + if (contentLength > 0) { nsAutoCString contentLenStr; contentLenStr.AppendInt(contentLength); response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), contentLenStr, result); MOZ_ASSERT(!result.Failed()); } } @@ -556,17 +569,17 @@ FetchDriver::OnStartRequest(nsIRequest* UINT32_MAX /* infinite pipe */, true /* non-blocking input, otherwise you deadlock */, false /* blocking output, since the pipe is 'in'finite */ ); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } - response->SetBody(pipeInputStream); + response->SetBody(pipeInputStream, contentLength); response->InitChannelInfo(channel); nsCOMPtr<nsIURI> channelURI; rv = channel->GetURI(getter_AddRefs(channelURI)); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request.
--- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -16,16 +16,17 @@ namespace mozilla { namespace dom { InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusText) : mType(ResponseType::Default) , mStatus(aStatus) , mStatusText(aStatusText) , mHeaders(new InternalHeaders(HeadersGuardEnum::Response)) + , mBodySize(UNKNOWN_BODY_SIZE) { } InternalResponse::~InternalResponse() { } already_AddRefed<InternalResponse>
--- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -171,47 +171,60 @@ public: if (mWrappedResponse) { return mWrappedResponse->Headers(); }; return Headers(); } void - GetUnfilteredBody(nsIInputStream** aStream) + GetUnfilteredBody(nsIInputStream** aStream, int64_t* aBodySize = nullptr) { if (mWrappedResponse) { MOZ_ASSERT(!mBody); - return mWrappedResponse->GetBody(aStream); + return mWrappedResponse->GetBody(aStream, aBodySize); } nsCOMPtr<nsIInputStream> stream = mBody; stream.forget(aStream); + if (aBodySize) { + *aBodySize = mBodySize; + } } void - GetBody(nsIInputStream** aStream) + GetBody(nsIInputStream** aStream, int64_t* aBodySize = nullptr) { if (Type() == ResponseType::Opaque || Type() == ResponseType::Opaqueredirect) { *aStream = nullptr; + if (aBodySize) { + *aBodySize = UNKNOWN_BODY_SIZE; + } return; } - return GetUnfilteredBody(aStream); + return GetUnfilteredBody(aStream, aBodySize); } void - SetBody(nsIInputStream* aBody) + SetBody(nsIInputStream* aBody, int64_t aBodySize) { if (mWrappedResponse) { - return mWrappedResponse->SetBody(aBody); + return mWrappedResponse->SetBody(aBody, aBodySize); } // A request's body may not be reset once set. MOZ_ASSERT(!mBody); + MOZ_ASSERT(mBodySize == UNKNOWN_BODY_SIZE); + // Check arguments. + MOZ_ASSERT(aBodySize == UNKNOWN_BODY_SIZE || aBodySize >= 0); + // If body is not given, then size must be unknown. + MOZ_ASSERT_IF(!aBody, aBodySize == UNKNOWN_BODY_SIZE); + mBody = aBody; + mBodySize = aBodySize; } void InitChannelInfo(nsIChannel* aChannel) { mChannelInfo.InitFromChannel(aChannel); } @@ -271,16 +284,20 @@ private: // A response has an associated url list (a list of zero or more fetch URLs). // Unless stated otherwise, it is the empty list. The current url is the last // element in mURLlist nsTArray<nsCString> mURLList; const uint16_t mStatus; const nsCString mStatusText; RefPtr<InternalHeaders> mHeaders; nsCOMPtr<nsIInputStream> mBody; + int64_t mBodySize; +public: + static const int64_t UNKNOWN_BODY_SIZE = -1; +private: ChannelInfo mChannelInfo; UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo; // For filtered responses. // Cache, and SW interception should always serialize/access the underlying // unfiltered headers and when deserializing, create an InternalResponse // with the unfiltered headers followed by wrapping it. RefPtr<InternalResponse> mWrappedResponse;
--- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -33,16 +33,19 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest) : FetchBody<Request>() , mOwner(aOwner) , mRequest(aRequest) { + MOZ_ASSERT(aRequest->Headers()->Guard() == HeadersGuardEnum::Immutable || + aRequest->Headers()->Guard() == HeadersGuardEnum::Request || + aRequest->Headers()->Guard() == HeadersGuardEnum::Request_no_cors); SetMimeType(); } Request::~Request() { } // static @@ -508,18 +511,21 @@ Request::Constructor(const GlobalObject& if (aInit.mBody.WasPassed()) { const Nullable<OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& bodyInitNullable = aInit.mBody.Value(); if (!bodyInitNullable.IsNull()) { const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit = bodyInitNullable.Value(); nsCOMPtr<nsIInputStream> stream; nsAutoCString contentType; + uint64_t contentLengthUnused; aRv = ExtractByteStreamFromBody(bodyInit, - getter_AddRefs(stream), contentType); + getter_AddRefs(stream), + contentType, + contentLengthUnused); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } temporaryBody = stream; if (!contentType.IsVoid() && !requestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
--- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -34,16 +34,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END Response::Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse) : FetchBody<Response>() , mOwner(aGlobal) , mInternalResponse(aInternalResponse) { + MOZ_ASSERT(aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable || + aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response); SetMimeType(); } Response::~Response() { } /* static */ already_AddRefed<Response> @@ -200,18 +202,25 @@ Response::Constructor(const GlobalObject if (aBody.WasPassed()) { if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) { aRv.ThrowTypeError<MSG_RESPONSE_NULL_STATUS_WITH_BODY>(); return nullptr; } nsCOMPtr<nsIInputStream> bodyStream; nsCString contentType; - aRv = ExtractByteStreamFromBody(aBody.Value(), getter_AddRefs(bodyStream), contentType); - internalResponse->SetBody(bodyStream); + uint64_t bodySize = 0; + aRv = ExtractByteStreamFromBody(aBody.Value(), + getter_AddRefs(bodyStream), + contentType, + bodySize); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + internalResponse->SetBody(bodyStream, bodySize); if (!contentType.IsVoid() && !internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) { // Ignore Append() failing here. ErrorResult error; internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, error); error.SuppressException(); } @@ -248,20 +257,20 @@ Response::CloneUnfiltered(ErrorResult& a RefPtr<InternalResponse> clone = mInternalResponse->Clone(); RefPtr<InternalResponse> ir = clone->Unfiltered(); RefPtr<Response> ref = new Response(mOwner, ir); return ref.forget(); } void -Response::SetBody(nsIInputStream* aBody) +Response::SetBody(nsIInputStream* aBody, int64_t aBodySize) { MOZ_ASSERT(!BodyUsed()); - mInternalResponse->SetBody(aBody); + mInternalResponse->SetBody(aBody, aBodySize); } already_AddRefed<InternalResponse> Response::GetInternalResponse() const { RefPtr<InternalResponse> ref = mInternalResponse; return ref.forget(); }
--- a/dom/fetch/Response.h +++ b/dom/fetch/Response.h @@ -129,17 +129,17 @@ public: already_AddRefed<Response> Clone(ErrorResult& aRv) const; already_AddRefed<Response> CloneUnfiltered(ErrorResult& aRv) const; void - SetBody(nsIInputStream* aBody); + SetBody(nsIInputStream* aBody, int64_t aBodySize); already_AddRefed<InternalResponse> GetInternalResponse() const; private: ~Response(); nsCOMPtr<nsIGlobalObject> mOwner;
--- a/dom/filesystem/Directory.cpp +++ b/dom/filesystem/Directory.cpp @@ -161,16 +161,31 @@ Directory::GetRoot(FileSystemBase* aFile return nullptr; } FileSystemPermissionRequest::RequestForTask(task); return task->GetPromise(); } /* static */ already_AddRefed<Directory> +Directory::Constructor(const GlobalObject& aGlobal, + const nsAString& aRealPath, + ErrorResult& aRv) +{ + nsCOMPtr<nsIFile> path; + aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(aRealPath), + true, getter_AddRefs(path)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return Create(aGlobal.GetAsSupports(), path); +} + +/* static */ already_AddRefed<Directory> Directory::Create(nsISupports* aParent, nsIFile* aFile, FileSystemBase* aFileSystem) { MOZ_ASSERT(aParent); MOZ_ASSERT(aFile); #ifdef DEBUG bool isDir;
--- a/dom/filesystem/Directory.h +++ b/dom/filesystem/Directory.h @@ -58,16 +58,21 @@ public: static bool WebkitBlinkDirectoryPickerEnabled(JSContext* aCx, JSObject* aObj); static already_AddRefed<Promise> GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv); static already_AddRefed<Directory> + Constructor(const GlobalObject& aGlobal, + const nsAString& aRealPath, + ErrorResult& aRv); + + static already_AddRefed<Directory> Create(nsISupports* aParent, nsIFile* aDirectory, FileSystemBase* aFileSystem = 0); // ========= Begin WebIDL bindings. =========== nsISupports* GetParentObject() const;
--- a/dom/filesystem/tests/mochitest.ini +++ b/dom/filesystem/tests/mochitest.ini @@ -1,8 +1,9 @@ [DEFAULT] support-files = filesystem_commons.js script_fileList.js worker_basic.js [test_basic.html] +[test_webkitdirectory.html] [test_worker_basic.html]
new file mode 100644 --- /dev/null +++ b/dom/filesystem/tests/test_webkitdirectory.html @@ -0,0 +1,122 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for webkitdirectory and webkitRelativePath</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> +<input id="inputFileWebkitDirectory" type="file" webkitdirectory></input> +<input id="inputFileWebkitDirectoryAndDirectory" type="file" webkitdirectory directory></input> +<input id="inputFileDirectory" type="file" directory></input> + +<script type="application/javascript;version=1.7"> + +function populateInputFile(aInputFile) { + var url = SimpleTest.getTestFileURL("script_fileList.js"); + var script = SpecialPowers.loadChromeScript(url); + + var MockFilePicker = SpecialPowers.MockFilePicker; + MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen); + + function onOpened(message) { + MockFilePicker.useDirectory(message.dir); + + var input = document.getElementById(aInputFile); + input.addEventListener('change', function() { + MockFilePicker.cleanup(); + script.destroy(); + next(); + }); + + input.click(); + } + + script.addMessageListener("dir.opened", onOpened); + script.sendAsyncMessage("dir.open", { path: 'test' }); +} + +function checkFile(file, fileList) { + for (var i = 0; i < fileList.length; ++i) { + ok(fileList[i] instanceof File, "We want just files."); + if (fileList[i].name == file.name) { + is(fileList[i].webkitRelativePath, file.path, "Path matches"); + return; + } + } + + ok(false, "File not found."); +} + +function test_fileList(aInputFile, aWhat) { + var input = document.getElementById(aInputFile); + var fileList = input.files; + + if (aWhat == null) { + is(fileList, null, "We want a null fileList for " + aInputFile); + next(); + return; + } + + is(fileList.length, aWhat.length, "We want just " + aWhat.length + " elements for " + aInputFile); + for (var i = 0; i < aWhat.length; ++i) { + checkFile(aWhat[i], fileList); + } + + next(); +} + +function test_webkitdirectory_attribute() { + var a = document.createElement("input"); + a.setAttribute("type", "file"); + + ok("webkitdirectory" in a, "HTMLInputElement.webkitdirectory exists"); + + ok(!a.hasAttribute("webkitdirectory"), "No webkitdirectory DOM attribute by default"); + ok(!a.webkitdirectory, "No webkitdirectory attribute by default"); + + a.webkitdirectory = true; + + ok(a.hasAttribute("webkitdirectory"), "Webkitdirectory DOM attribute is set"); + ok(a.webkitdirectory, "Webkitdirectory attribute is set"); + + next(); +} + +function test_setup() { + SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true], + ["dom.webkitBlink.dirPicker.enabled", true]]}, next); +} + +var tests = [ + test_setup, + + function() { populateInputFile('inputFileWebkitDirectory'); }, + function() { populateInputFile('inputFileWebkitDirectoryAndDirectory'); }, + function() { populateInputFile('inputFileDirectory'); }, + + function() { test_fileList('inputFileWebkitDirectory', [ { name: 'foo.txt', path: '/foo.txt' }, + { name: 'bar.txt', path: '/subdir/bar.txt' }]); }, + function() { test_fileList('inputFileWebkitDirectoryAndDirectory', [ { name: 'foo.txt', path: '/foo.txt' }, + { name: 'bar.txt', path: '/subdir/bar.txt' }]); }, + function() { test_fileList('inputFileDirectory', null); }, + + test_webkitdirectory_attribute, +]; + +function next() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +SimpleTest.waitForExplicitFinish(); +next(); +</script> +</body> +</html>
--- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -213,16 +213,28 @@ const Decimal HTMLInputElement::kStepAny 0x23e2, \ 0x4479, \ {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \ } #define PROGRESS_STR "progress" static const uint32_t kProgressEventInterval = 50; // ms +class GetFilesCallback +{ +public: + NS_INLINE_DECL_REFCOUNTING(GetFilesCallback); + + virtual void + Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0; + +protected: + virtual ~GetFilesCallback() {} +}; + // Retrieving the list of files can be very time/IO consuming. We use this // helper class to do it just once. class GetFilesHelper final : public Runnable { public: static already_AddRefed<GetFilesHelper> Create(nsIGlobalObject* aGlobal, const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory, @@ -290,16 +302,31 @@ public: mPromises.AppendElement(aPromise); return; } MOZ_ASSERT(mPromises.IsEmpty()); ResolveOrRejectPromise(aPromise); } + void + AddCallback(GetFilesCallback* aCallback) + { + MOZ_ASSERT(aCallback); + + // Still working. + if (!mListingCompleted) { + mCallbacks.AppendElement(aCallback); + return; + } + + MOZ_ASSERT(mCallbacks.IsEmpty()); + RunCallback(aCallback); + } + // CC methods void Unlink() { mGlobal = nullptr; mFiles.Clear(); mPromises.Clear(); } @@ -348,16 +375,24 @@ private: // Let's process the pending promises. nsTArray<RefPtr<Promise>> promises; promises.SwapElements(mPromises); for (uint32_t i = 0; i < promises.Length(); ++i) { ResolveOrRejectPromise(promises[i]); } + // Let's process the pending callbacks. + nsTArray<RefPtr<GetFilesCallback>> callbacks; + callbacks.SwapElements(mCallbacks); + + for (uint32_t i = 0; i < callbacks.Length(); ++i) { + RunCallback(callbacks[i]); + } + return NS_OK; } void RunIO() { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!mDirectoryPath.IsEmpty()); @@ -505,16 +540,26 @@ private: if (NS_FAILED(mErrorResult)) { aPromise->MaybeReject(mErrorResult); return; } aPromise->MaybeResolve(mFiles); } + void + RunCallback(GetFilesCallback* aCallback) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mListingCompleted); + MOZ_ASSERT(aCallback); + + aCallback->Callback(mErrorResult, mFiles); + } + nsCOMPtr<nsIGlobalObject> mGlobal; bool mRecursiveFlag; bool mListingCompleted; nsString mDirectoryPath; // We populate this array in the I/O thread with the paths of the Files that // we want to send as result to the promise objects. @@ -526,16 +571,88 @@ private: // This is the real File sequence that we expose via Promises. Sequence<RefPtr<File>> mFiles; // Error code to propagate. nsresult mErrorResult; nsTArray<RefPtr<Promise>> mPromises; + nsTArray<RefPtr<GetFilesCallback>> mCallbacks; +}; + +// An helper class for the dispatching of the 'change' event. +class DispatchChangeEventCallback final : public GetFilesCallback +{ +public: + explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement) + : mInputElement(aInputElement) + { + MOZ_ASSERT(aInputElement); + } + + virtual void + Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override + { + nsTArray<OwningFileOrDirectory> array; + for (uint32_t i = 0; i < aFiles.Length(); ++i) { + OwningFileOrDirectory* element = array.AppendElement(); + element->SetAsFile() = aFiles[i]; + } + + mInputElement->SetFilesOrDirectories(array, true); + NS_WARN_IF(NS_FAILED(DispatchEvents())); + } + + nsresult + DispatchEvents() + { + nsresult rv = NS_OK; + rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(), + static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()), + NS_LITERAL_STRING("input"), true, + false); + NS_WARN_IF(NS_FAILED(rv)); + + rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(), + static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()), + NS_LITERAL_STRING("change"), true, + false); + + return rv; + } + +private: + RefPtr<HTMLInputElement> mInputElement; +}; + +// This callback is used for postponing the calling of SetFilesOrDirectories +// when the exploration of the directory is completed. +class AfterSetFilesOrDirectoriesCallback : public GetFilesCallback +{ +public: + AfterSetFilesOrDirectoriesCallback(HTMLInputElement* aInputElement, + bool aSetValueChanged) + : mInputElement(aInputElement) + , mSetValueChanged(aSetValueChanged) + { + MOZ_ASSERT(aInputElement); + } + + void + Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override + { + if (NS_SUCCEEDED(aStatus)) { + mInputElement->AfterSetFilesOrDirectoriesInternal(mSetValueChanged); + } + } + +private: + RefPtr<HTMLInputElement> mInputElement; + bool mSetValueChanged; }; class HTMLInputElementState final : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID) NS_DECL_ISUPPORTS @@ -860,29 +977,32 @@ HTMLInputElement::nsFilePickerShownCallb mInput->OwnerDoc(), lastUsedDir); } // The text control frame (if there is one) isn't going to send a change // event because it will think this is done by a script. // So, we can safely send one by ourself. mInput->SetFilesOrDirectories(newFilesOrDirectories, true); - nsresult rv = NS_OK; - rv = nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(), - static_cast<nsIDOMHTMLInputElement*>(mInput.get()), - NS_LITERAL_STRING("input"), true, - false); - NS_WARN_IF(NS_FAILED(rv)); - - rv = nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(), - static_cast<nsIDOMHTMLInputElement*>(mInput.get()), - NS_LITERAL_STRING("change"), true, - false); - - return rv; + RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback = + new DispatchChangeEventCallback(mInput); + + if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) && + mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) { + ErrorResult error; + GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + + helper->AddCallback(dispatchChangeEventCallback); + return NS_OK; + } + + return dispatchChangeEventCallback->DispatchEvents(); } NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback, nsIFilePickerShownCallback) class nsColorPickerShownCallback final : public nsIColorPickerShownCallback { @@ -2916,16 +3036,29 @@ HTMLInputElement::SetFiles(nsIDOMFileLis } AfterSetFilesOrDirectories(aSetValueChanged); } void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged) { + if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) && + HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) { + // This will call AfterSetFilesOrDirectoriesInternal eventually. + ExploreDirectoryRecursively(aSetValueChanged); + return; + } + + AfterSetFilesOrDirectoriesInternal(aSetValueChanged); +} + +void +HTMLInputElement::AfterSetFilesOrDirectoriesInternal(bool aSetValueChanged) +{ // No need to flush here, if there's no frame at this point we // don't need to force creation of one just to tell it about this // new value. We just want the display to update as needed. nsIFormControlFrame* formControlFrame = GetFormControlFrame(false); if (formControlFrame) { nsAutoString readableValue; GetDisplayFileName(readableValue); formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue); @@ -2978,17 +3111,19 @@ HTMLInputElement::FireChangeEventIfNeede FileList* HTMLInputElement::GetFiles() { if (mType != NS_FORM_INPUT_FILE) { return nullptr; } if (Preferences::GetBool("dom.input.dirpicker", false) && - HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) { + HasAttr(kNameSpaceID_None, nsGkAtoms::directory) && + (!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) || + !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) { return nullptr; } if (!mFileList) { mFileList = new FileList(static_cast<nsIContent*>(this)); UpdateFileList(); } @@ -3660,16 +3795,25 @@ HTMLInputElement::PreHandleEvent(EventCh aVisitor.mEvent->AsMouseEvent()->button == WidgetMouseEvent::eMiddleButton) { aVisitor.mEvent->mFlags.mNoContentDispatch = false; } // We must cache type because mType may change during JS event (bug 2369) aVisitor.mItemFlags |= mType; + if (aVisitor.mEvent->mMessage == eFocus && + aVisitor.mEvent->IsTrusted() && + MayFireChangeOnBlur() && + // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before + // we get the 'focus' event. + !mIsDraggingRange) { + GetValue(mFocusedValue); + } + // Fire onchange (if necessary), before we do the blur, bug 357684. if (aVisitor.mEvent->mMessage == eBlur) { // Experimental mobile types rely on the system UI to prevent users to not // set invalid values but we have to be extra-careful. Especially if the // option has been enabled on desktop. if (IsExperimentalMobileType(mType)) { nsAutoString aValue; GetValueInternal(aValue); @@ -4068,18 +4212,20 @@ HTMLInputElement::MaybeInitPickers(Event // If the user clicked on the "Choose folder..." button we open the // directory picker, else we open the file picker. FilePickerType type = FILE_PICKER_FILE; nsCOMPtr<nsIContent> target = do_QueryInterface(aVisitor.mEvent->mOriginalTarget); if (target && target->GetParent() == this && target->IsRootOfNativeAnonymousSubtree() && - target->HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) { - MOZ_ASSERT(Preferences::GetBool("dom.input.dirpicker", false), + (target->HasAttr(kNameSpaceID_None, nsGkAtoms::directory) || + target->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) { + MOZ_ASSERT(Preferences::GetBool("dom.input.dirpicker", false) || + Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false), "No API or UI should have been exposed to allow this code to " "be reached"); type = FILE_PICKER_DIRECTORY; } return InitFilePicker(type); } if (mType == NS_FORM_INPUT_COLOR) { return InitColorPicker(); @@ -4093,22 +4239,16 @@ HTMLInputElement::PostHandleEvent(EventC if (!aVisitor.mPresContext) { // Hack alert! In order to open file picker even in case the element isn't // in document, try to init picker even without PresContext. return MaybeInitPickers(aVisitor); } if (aVisitor.mEvent->mMessage == eFocus || aVisitor.mEvent->mMessage == eBlur) { - if (aVisitor.mEvent->mMessage == eFocus && - MayFireChangeOnBlur() && - !mIsDraggingRange) { // StartRangeThumbDrag already set mFocusedValue - GetValue(mFocusedValue); - } - if (aVisitor.mEvent->mMessage == eBlur) { if (mIsDraggingRange) { FinishRangeThumbDrag(); } else if (mNumberControlSpinnerIsSpinning) { StopNumberControlSpinnerSpin(); } } @@ -5279,17 +5419,18 @@ nsChangeHint HTMLInputElement::GetAttributeChangeHint(const nsIAtom* aAttribute, int32_t aModType) const { nsChangeHint retval = nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType); if (aAttribute == nsGkAtoms::type || // The presence or absence of the 'directory' attribute determines what // buttons we show for type=file. - aAttribute == nsGkAtoms::directory) { + aAttribute == nsGkAtoms::directory || + aAttribute == nsGkAtoms::webkitdirectory) { retval |= NS_STYLE_HINT_FRAMECHANGE; } else if (mType == NS_FORM_INPUT_IMAGE && (aAttribute == nsGkAtoms::alt || aAttribute == nsGkAtoms::value)) { // We might need to rebuild our alt text. Just go ahead and // reconstruct our frame. This should be quite rare.. retval |= NS_STYLE_HINT_FRAMECHANGE; } else if (aAttribute == nsGkAtoms::value) { @@ -5415,51 +5556,28 @@ HTMLInputElement::GetFilesAndDirectories already_AddRefed<Promise> HTMLInputElement::GetFiles(bool aRecursiveFlag, ErrorResult& aRv) { if (mType != NS_FORM_INPUT_FILE) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } + GetFilesHelper* helper = GetOrCreateGetFilesHelper(aRecursiveFlag, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + MOZ_ASSERT(helper); + nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject(); MOZ_ASSERT(global); if (!global) { return nullptr; } - RefPtr<GetFilesHelper> helper; - if (aRecursiveFlag) { - if (!mGetFilesRecursiveHelper) { - mGetFilesRecursiveHelper = - GetFilesHelper::Create(global, - GetFilesOrDirectoriesInternal(), - aRecursiveFlag, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - } - - helper = mGetFilesRecursiveHelper; - } else { - if (!mGetFilesNonRecursiveHelper) { - mGetFilesNonRecursiveHelper = - GetFilesHelper::Create(global, - GetFilesOrDirectoriesInternal(), - aRecursiveFlag, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - } - - helper = mGetFilesNonRecursiveHelper; - } - - MOZ_ASSERT(helper); - RefPtr<Promise> p = Promise::Create(global, aRv); if (aRv.Failed()) { return nullptr; } helper->AddPromise(p); return p.forget(); } @@ -7970,12 +8088,66 @@ HTMLInputElement::ClearGetFilesHelpers() } if (mGetFilesNonRecursiveHelper) { mGetFilesNonRecursiveHelper->Unlink(); mGetFilesNonRecursiveHelper = nullptr; } } +GetFilesHelper* +HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag, + ErrorResult& aRv) +{ + nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject(); + MOZ_ASSERT(global); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (aRecursiveFlag) { + if (!mGetFilesRecursiveHelper) { + mGetFilesRecursiveHelper = + GetFilesHelper::Create(global, + GetFilesOrDirectoriesInternal(), + aRecursiveFlag, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } + + return mGetFilesRecursiveHelper; + } + + if (!mGetFilesNonRecursiveHelper) { + mGetFilesNonRecursiveHelper = + GetFilesHelper::Create(global, + GetFilesOrDirectoriesInternal(), + aRecursiveFlag, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } + + return mGetFilesNonRecursiveHelper; +} + +void +HTMLInputElement::ExploreDirectoryRecursively(bool aSetValueChanged) +{ + ErrorResult rv; + GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */, + rv); + if (NS_WARN_IF(rv.Failed())) { + AfterSetFilesOrDirectoriesInternal(aSetValueChanged); + return; + } + + RefPtr<AfterSetFilesOrDirectoriesCallback> callback = + new AfterSetFilesOrDirectoriesCallback(this, aSetValueChanged); + helper->AddCallback(callback); +} + } // namespace dom } // namespace mozilla #undef NS_ORIGINAL_CHECKED_VALUE
--- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -32,17 +32,19 @@ class nsIRadioVisitor; namespace mozilla { class EventChainPostVisitor; class EventChainPreVisitor; namespace dom { +class AfterSetFilesOrDirectoriesRunnable; class Date; +class DispatchChangeEventCallback; class File; class FileList; class GetFilesHelper; /** * A class we use to create a singleton object that is used to keep track of * the last directory from which the user has picked files (via * <input type=file>) on a per-domain basis. The implementation uses @@ -102,16 +104,19 @@ public: class HTMLInputElement final : public nsGenericHTMLFormElementWithState, public nsImageLoadingContent, public nsIDOMHTMLInputElement, public nsITextControlElement, public nsIPhonetic, public nsIDOMNSEditableElement, public nsIConstraintValidation { + friend class AfterSetFilesOrDirectoriesCallback; + friend class DispatchChangeEventCallback; + public: using nsIConstraintValidation::GetValidationMessage; using nsIConstraintValidation::CheckValidity; using nsIConstraintValidation::ReportValidity; using nsIConstraintValidation::WillValidate; using nsIConstraintValidation::Validity; using nsGenericHTMLFormElementWithState::GetForm; @@ -695,16 +700,26 @@ public: return HasAttr(kNameSpaceID_None, nsGkAtoms::directory); } void SetDirectoryAttr(bool aValue, ErrorResult& aRv) { SetHTMLBoolAttr(nsGkAtoms::directory, aValue, aRv); } + bool WebkitDirectoryAttr() const + { + return HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory); + } + + void SetWebkitDirectoryAttr(bool aValue, ErrorResult& aRv) + { + SetHTMLBoolAttr(nsGkAtoms::webkitdirectory, aValue, aRv); + } + bool IsFilesAndDirectoriesSupported() const; already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv); already_AddRefed<Promise> GetFiles(bool aRecursiveFlag, ErrorResult& aRv); void ChooseDirectory(ErrorResult& aRv); @@ -933,18 +948,26 @@ protected: /** * Update mFileList with the currently selected file. */ void UpdateFileList(); /** * Called after calling one of the SetFilesOrDirectories() functions. + * This method can explore the directory recursively if needed. */ void AfterSetFilesOrDirectories(bool aSetValueChanged); + void AfterSetFilesOrDirectoriesInternal(bool aSetValueChanged); + + /** + * Recursively explore the directory and populate mFileOrDirectories correctly + * for webkitdirectory. + */ + void ExploreDirectoryRecursively(bool aSetValuechanged); /** * Determine whether the editor needs to be initialized explicitly for * a particular event. */ bool NeedToInitializeEditorForEvent(EventChainPreVisitor& aVisitor) const; /** @@ -1251,16 +1274,19 @@ protected: * Use this function before trying to open a picker. * It checks if the page is allowed to open a new pop-up. * If it returns true, you should not create the picker. * * @return true if popup should be blocked, false otherwise */ bool IsPopupBlocked() const; + GetFilesHelper* GetOrCreateGetFilesHelper(bool aRecursiveFlag, + ErrorResult& aRv); + void ClearGetFilesHelpers(); nsCOMPtr<nsIControllers> mControllers; /* * In mInputData, the mState field is used if IsSingleLineTextControl returns * true and mValue is used otherwise. We have to be careful when handling it * on a type change.
--- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -1871,17 +1871,18 @@ void HTMLMediaElement::SetVolumeInternal if (mDecoder) { mDecoder->SetVolume(effectiveVolume); } else if (MediaStream* stream = GetSrcMediaStream()) { if (mSrcStreamIsPlaying) { stream->SetAudioOutputVolume(this, effectiveVolume); } } - UpdateAudioChannelPlayingState(); + NotifyAudioPlaybackChanged( + AudioChannelService::AudibleChangedReasons::eVolumeChanged); } NS_IMETHODIMP HTMLMediaElement::SetMuted(bool aMuted) { if (aMuted == Muted()) { return NS_OK; } @@ -2276,17 +2277,18 @@ HTMLMediaElement::HTMLMediaElement(alrea mAudioChannelVolume(1.0), mPlayingThroughTheAudioChannel(false), mDisableVideo(false), mPlayBlockedBecauseHidden(false), mElementInTreeState(ELEMENT_NOT_INTREE), mHasUserInteraction(false), mFirstFrameLoaded(false), mDefaultPlaybackStartPosition(0.0), - mIsAudioTrackAudible(false) + mIsAudioTrackAudible(false), + mAudible(IsAudible()) { mAudioChannel = AudioChannelService::GetDefaultAudioChannel(); mPaused.SetOuter(this); RegisterActivityObserver(); NotifyOwnerDocumentActivityChangedInternal(); @@ -4964,36 +4966,31 @@ HTMLMediaElement::MaybeCreateAudioChanne bool HTMLMediaElement::IsPlayingThroughTheAudioChannel() const { // It might be resumed from remote, we should keep the audio channel agent. if (IsSuspendedByAudioChannel()) { return true; } - // Are we paused or muted - if (mPaused || Muted()) { + // Are we paused + if (mPaused) { return false; } // If we have an error, we are not playing. if (mError) { return false; } // If this element doesn't have any audio tracks. if (!HasAudio()) { return false; } - // The volume should not be ~0 - if (std::fabs(Volume()) <= 1e-7) { - return false; - } - // We should consider any bfcached page or inactive document as non-playing. if (!IsActive()) { return false; } // A loop always is playing if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) { return true; @@ -5048,17 +5045,17 @@ HTMLMediaElement::NotifyAudioChannelAgen // The reason we don't call NotifyStartedPlaying after the media element // really becomes audible is because there is another case needs to block // element as early as we can, we would hear sound leaking if we block it // too late. In that case (block autoplay in non-visited-tab), we need to // create a connection before decoding, because we don't want user hearing // any sound. AudioPlaybackConfig config; nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(&config, - mIsAudioTrackAudible); + IsAudible()); if (NS_WARN_IF(NS_FAILED(rv))) { return; } WindowVolumeChanged(config.mVolume, config.mMuted); WindowSuspendChanged(config.mSuspend); } else { mAudioChannelAgent->NotifyStoppedPlaying(); @@ -5101,24 +5098,28 @@ HTMLMediaElement::WindowSuspendChanged(S case nsISuspendedTypes::SUSPENDED_PAUSE: case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE: PauseByAudioChannel(aSuspend); break; case nsISuspendedTypes::SUSPENDED_BLOCK: BlockByAudioChannel(); break; case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE: + SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED); Pause(); break; default: MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("HTMLMediaElement, WindowSuspendChanged, this = %p, " "Error : unknown suspended type!\n", this)); } + NotifyAudioPlaybackChanged( + AudioChannelService::AudibleChangedReasons::ePauseStateChanged); + return NS_OK; } void HTMLMediaElement::ResumeFromAudioChannel() { if (!IsSuspendedByAudioChannel()) { return; @@ -5604,22 +5605,51 @@ HTMLMediaElement::IsCurrentlyPlaying() c return false; } void HTMLMediaElement::SetAudibleState(bool aAudible) { if (mIsAudioTrackAudible != aAudible) { mIsAudioTrackAudible = aAudible; - NotifyAudioPlaybackChanged(); + NotifyAudioPlaybackChanged( + AudioChannelService::AudibleChangedReasons::eDataAudibleChanged); } } void -HTMLMediaElement::NotifyAudioPlaybackChanged() -{ - if (mAudioChannelAgent) { - mAudioChannelAgent->NotifyStartedAudible(mIsAudioTrackAudible); - } +HTMLMediaElement::NotifyAudioPlaybackChanged(AudibleChangedReasons aReason) +{ + if (!mAudioChannelAgent) { + return; + } + + if (mAudible == IsAudible()) { + return; + } + + mAudible = IsAudible(); + mAudioChannelAgent->NotifyStartedAudible(mAudible, aReason); +} + +bool +HTMLMediaElement::IsAudible() const +{ + // Muted or the volume should not be ~0 + if (Muted() || (std::fabs(Volume()) <= 1e-7)) { + return false; + } + + // No sound can be heard during suspending. + if (IsSuspendedByAudioChannel()) { + return false; + } + + // Silent audio track. + if (!mIsAudioTrackAudible) { + return false; + } + + return true; } } // namespace dom } // namespace mozilla
--- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -33,16 +33,17 @@ #include "mozilla/dom/HTMLMediaElementBinding.h" // Define to output information on decoding and painting framerate /* #define DEBUG_FRAME_RATE 1 */ typedef uint16_t nsMediaNetworkState; typedef uint16_t nsMediaReadyState; typedef uint32_t SuspendTypes; +typedef uint32_t AudibleChangedReasons; namespace mozilla { class DecoderDoctorDiagnostics; class DOMMediaStream; class ErrorResult; class MediaResource; class MediaDecoder; class VideoFrameContainer; @@ -448,17 +449,17 @@ public: // when the connection between Rtsp server and client gets lost. virtual void ResetConnectionState() final override; // Called by media decoder when the audible state changed or when input is // a media stream. virtual void SetAudibleState(bool aAudible) final override; // Notify agent when the MediaElement changes its audible state. - void NotifyAudioPlaybackChanged(); + void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason); // XPCOM GetPreload() is OK void SetPreload(const nsAString& aValue, ErrorResult& aRv) { SetHTMLAttr(nsGkAtoms::preload, aValue, aRv); } already_AddRefed<TimeRanges> Buffered() const; @@ -1170,16 +1171,18 @@ protected: void ResumeFromAudioChannelPaused(SuspendTypes aSuspend); void ResumeFromAudioChannelBlocked(); bool IsSuspendedByAudioChannel() const; void SetAudioChannelSuspended(SuspendTypes aSuspend); bool IsAllowedToPlay(); + bool IsAudible() const; + 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. RefPtr<MediaDecoder> mDecoder; @@ -1601,16 +1604,19 @@ private: // True if the first frame has been successfully loaded. bool mFirstFrameLoaded; // Media elements also have a default playback start position, which must // initially be set to zero seconds. This time is used to allow the element to // be seeked even before the media is loaded. double mDefaultPlaybackStartPosition; - // True if the audio track is producing audible sound. + // True if the audio track is not silent. bool mIsAudioTrackAudible; + + // True if media element is audible for users. + bool mAudible; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_HTMLMediaElement_h
--- a/dom/html/test/forms/mochitest.ini +++ b/dom/html/test/forms/mochitest.ini @@ -83,16 +83,18 @@ skip-if = buildapp == 'mulet' [test_progress_element.html] [test_radio_in_label.html] [test_radio_radionodelist.html] [test_required_attribute.html] [test_restore_form_elements.html] [test_save_restore_radio_groups.html] [test_select_change_event.html] skip-if = android_version == '18' || os == 'mac' +[test_select_input_change_event.html] +skip-if = android_version == '18' || os == 'mac' [test_select_selectedOptions.html] [test_select_validation.html] [test_set_range_text.html] [test_step_attribute.html] [test_stepup_stepdown.html] [test_textarea_attributes_reflection.html] [test_validation.html] skip-if = buildapp == 'b2g' # b2g(374 total, bug 901848, no keygen support) b2g-debug(374 total, bug 901848, no keygen support) b2g-desktop(374 total, bug 901848, no keygen support)
--- a/dom/html/test/forms/test_change_event.html +++ b/dom/html/test/forms/test_change_event.html @@ -26,27 +26,32 @@ https://bugzilla.mozilla.org/show_bug.cg <input type="button" id="input_button" onchange="++NonTextInputChange[0];"></input> <input type="submit" id="input_submit" onchange="++NonTextInputChange[1];"></input> <input type="image" id="input_image" onchange="++NonTextInputChange[2];"></input> <input type="reset" id="input_reset" onchange="++NonTextInputChange[3];"></input> <input type="radio" id="input_radio" onchange="++NonTextInputChange[4];"></input> <input type="checkbox" id="input_checkbox" onchange="++NonTextInputChange[5];"></input> <input type="number" id="input_number" onchange="++numberChange;"></input> <input type="range" id="input_range" onchange="++rangeChange;"></input> + +<!-- Input text with default value and blurs on focus--> +<input type="text" id="input_text_value" onchange="++textInputValueChange" + onfocus="this.blur();" value="foo"></input> </div> <pre id="test"> <script class="testbody" type="text/javascript"> /** Test for Bug 722599 **/ const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent); var textareaChange = 0; var fileInputChange = 0; + var textInputValueChange = 0; var textInputTypes = ["text", "email", "search", "tel", "url", "password"]; var textInputChange = [0, 0, 0, 0, 0, 0]; var NonTextInputTypes = ["button", "submit", "image", "reset", "radio", "checkbox"]; var NonTextInputChange = [0, 0, 0, 0, 0, 0]; var numberChange = 0; @@ -231,16 +236,25 @@ https://bugzilla.mozilla.org/show_bug.cg //Input type change test. input = document.getElementById("input_checkbox"); input.type = "text"; input.focus(); input.click(); input.blur(); is(NonTextInputChange[5], 1, "Change event shouldn't be dispatched for checkbox ---> text input type change"); + setTimeout(testInputWithDefaultValue, 0); + } + + function testInputWithDefaultValue() { + // focus and blur an input text should not trigger change event if content hasn't changed. + var input = document.getElementById('input_text_value'); + input.focus(); + is(textInputValueChange, 0, "change event shouldn't be dispatched on input text with default value"); + SimpleTest.finish(); } addLoadEvent(testUserInput); </script> </pre> </body>
new file mode 100644 --- /dev/null +++ b/dom/html/test/forms/test_select_input_change_event.html @@ -0,0 +1,122 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1265968 +--> +<head> + <title>Test for Bug 1024350</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1024350">Mozilla Bug 1024350</a> +<p id="display"></p> +<div id="content"> + <select oninput='++selectInput;' onchange="++selectChange;"> + <option>one</option> + </select> + <select oninput='++selectInput;' onchange="++selectChange;"> + <option>one</option> + <option>two</option> + </select> + <select multiple size='1' oninput='++selectInput;' onchange="++selectChange;"> + <option>one</option> + </select> + <select multiple oninput='++selectInput;' onchange="++selectChange;"> + <option>one</option> + <option>two</option> + </select> +</div> +<pre id="test"> +<script type="application/javascript"> + var selectSingleOneItem = document.getElementsByTagName('select')[0]; + var selectSingle = document.getElementsByTagName('select')[1]; + var selectMultipleOneItem = document.getElementsByTagName('select')[2]; + var selectMultiple = document.getElementsByTagName('select')[3]; + + var selectChange = 0; + var selectInput = 0; + var expectedChange = 0; + var expectedInput = 0; + + selectSingleOneItem.focus(); + synthesizeKey("VK_DOWN", {}); + is(selectInput, expectedInput, "Down key should not fire input event when reaching end of the list."); + is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list."); + + synthesizeKey("VK_UP", {}); + is(selectInput, expectedInput, "Up key should not fire input event when reaching top of the list."); + is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list."); + + selectSingle.focus(); + for (var i = 1; i < selectSingle.length; i++) { + synthesizeKey("VK_DOWN", {}); + + is(selectSingle.options[i].selected, true, "Option should be selected"); + is(selectInput, ++expectedInput, "Down key should fire input event."); + is(selectChange, ++expectedChange, "Down key should fire change event."); + } + + // We are at the end of the list, going down should not fire change event. + synthesizeKey("VK_DOWN", {}); + is(selectInput, expectedInput, "Down key should not fire input event when reaching end of the list."); + is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list."); + + for (var i = selectSingle.length - 2; i >= 0; i--) { + synthesizeKey("VK_UP", {}); + + is(selectSingle.options[i].selected, true, "Option should be selected"); + is(selectInput, ++expectedInput, "Up key should fire input event."); + is(selectChange, ++expectedChange, "Up key should fire change event."); + } + + // We are at the top of the list, going up should not fire change event. + synthesizeKey("VK_UP", {}); + is(selectInput, expectedInput, "Up key should not fire input event when reaching top of the list."); + is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list."); + + selectMultipleOneItem.focus(); + synthesizeKey("VK_DOWN", {}); + is(selectInput, ++expectedInput, "Down key should fire input event when reaching end of the list."); + is(selectChange, ++expectedChange, "Down key should fire change event when reaching end of the list."); + + synthesizeKey("VK_DOWN", {}); + is(selectInput, expectedInput, "Down key should not fire input event when reaching end of the list."); + is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list."); + + synthesizeKey("VK_UP", {}); + is(selectInput, expectedInput, "Up key should not fire input event when reaching top of the list."); + is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list."); + + selectMultiple.focus(); + for (var i = 0; i < selectMultiple.length; i++) { + synthesizeKey("VK_DOWN", {}); + + is(selectMultiple.options[i].selected, true, "Option should be selected"); + is(selectInput, ++expectedInput, "Down key should fire input event."); + is(selectChange, ++expectedChange, "Down key should fire change event."); + } + + // We are at the end of the list, going down should not fire change event. + synthesizeKey("VK_DOWN", {}); + is(selectInput, expectedInput, "Down key should not fire input event when reaching end of the list."); + is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list."); + + for (var i = selectMultiple.length - 2; i >= 0; i--) { + synthesizeKey("VK_UP", {}); + + is(selectMultiple.options[i].selected, true, "Option should be selected"); + is(selectInput, ++expectedInput, "Up key should fire input event."); + is(selectChange, ++expectedChange, "Up key should fire change event."); + } + + // We are at the top of the list, going up should not fire change event. + synthesizeKey("VK_UP", {}); + is(selectInput, expectedInput, "Up key should not fire input event when reaching top of the list."); + is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list."); + +</script> +</pre> +</body> +</html>
--- a/dom/interfaces/push/nsIPushNotifier.idl +++ b/dom/interfaces/push/nsIPushNotifier.idl @@ -1,32 +1,27 @@ /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsISupports.idl" %{C++ -#include "nsTArray.h" -#include "mozilla/Maybe.h" - #define PUSHNOTIFIER_CONTRACTID \ "@mozilla.org/push/Notifier;1" // These constants are duplicated in `PushComponents.js`. #define OBSERVER_TOPIC_PUSH "push-message" #define OBSERVER_TOPIC_SUBSCRIPTION_CHANGE "push-subscription-change" #define OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED "push-subscription-modified" %} interface nsIPrincipal; -[ref] native MaybeData(const mozilla::Maybe<nsTArray<uint8_t>>); - /** * Fires XPCOM observer notifications and service worker events for * messages sent to push subscriptions. */ [scriptable, builtinclass, uuid(b00dfdeb-14e5-425b-adc7-b531442e3216)] interface nsIPushNotifier : nsISupports { /** @@ -62,47 +57,16 @@ interface nsIPushNotifier : nsISupports * This is useful for Dev Tools and debugging add-ons that passively observe * when subscriptions are created or dropped. Other callers should listen for * `push-subscription-change` and resubscribe instead. */ void notifySubscriptionModified(in ACString scope, in nsIPrincipal principal); void notifyError(in ACString scope, in nsIPrincipal principal, in DOMString message, in uint32_t flags); - - /** - * Internal methods used to fire service worker events and observer - * notifications. These are not exposed to JavaScript. - */ - - [noscript, nostdcall] - void notifyPushWorkers(in ACString scope, - in nsIPrincipal principal, - in DOMString messageId, - in MaybeData data); - - [noscript, nostdcall] - void notifyPushObservers(in ACString scope, in nsIPrincipal principal, - in MaybeData data); - - [noscript, nostdcall] - void notifySubscriptionChangeWorkers(in ACString scope, - in nsIPrincipal principal); - - [noscript, nostdcall] - void notifySubscriptionChangeObservers(in ACString scope, - in nsIPrincipal principal); - - [noscript, nostdcall] - void notifySubscriptionModifiedObservers(in ACString scope, - in nsIPrincipal principal); - - [noscript, nostdcall, notxpcom] - void notifyErrorWorkers(in ACString scope, in DOMString message, - in uint32_t flags); }; /** * Provides methods for retrieving push message data in different formats. * This interface resembles the `PushMessageData` WebIDL interface. */ [scriptable, builtinclass, uuid(dfc4f151-cead-40df-8eb7-7a7a67c54b16)] interface nsIPushData : nsISupports
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl +++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl @@ -54,16 +54,17 @@ interface nsIContentSecurityPolicy : nsI const unsigned short REFLECTED_XSS_DIRECTIVE = 12; const unsigned short BASE_URI_DIRECTIVE = 13; const unsigned short FORM_ACTION_DIRECTIVE = 14; const unsigned short REFERRER_DIRECTIVE = 15; const unsigned short WEB_MANIFEST_SRC_DIRECTIVE = 16; const unsigned short UPGRADE_IF_INSECURE_DIRECTIVE = 17; const unsigned short CHILD_SRC_DIRECTIVE = 18; const unsigned short BLOCK_ALL_MIXED_CONTENT = 19; + const unsigned short REQUIRE_SRI_FOR = 20; /** * Accessor method for a read-only string version of the policy at a given * index. */ AString getPolicy(in unsigned long index); /** @@ -181,33 +182,42 @@ interface nsIContentSecurityPolicy : nsI */ void logViolationDetails(in unsigned short violationType, in AString sourceFile, in AString scriptSample, in int32_t lineNum, [optional] in AString nonce, [optional] in AString content); - const unsigned short VIOLATION_TYPE_INLINE_SCRIPT = 1; - const unsigned short VIOLATION_TYPE_EVAL = 2; - const unsigned short VIOLATION_TYPE_INLINE_STYLE = 3; - const unsigned short VIOLATION_TYPE_NONCE_SCRIPT = 4; - const unsigned short VIOLATION_TYPE_NONCE_STYLE = 5; - const unsigned short VIOLATION_TYPE_HASH_SCRIPT = 6; - const unsigned short VIOLATION_TYPE_HASH_STYLE = 7; + const unsigned short VIOLATION_TYPE_INLINE_SCRIPT = 1; + const unsigned short VIOLATION_TYPE_EVAL = 2; + const unsigned short VIOLATION_TYPE_INLINE_STYLE = 3; + const unsigned short VIOLATION_TYPE_NONCE_SCRIPT = 4; + const unsigned short VIOLATION_TYPE_NONCE_STYLE = 5; + const unsigned short VIOLATION_TYPE_HASH_SCRIPT = 6; + const unsigned short VIOLATION_TYPE_HASH_STYLE = 7; + const unsigned short VIOLATION_TYPE_REQUIRE_SRI_FOR_STYLE = 8; + const unsigned short VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT = 9; /** * Called after the CSP object is created to fill in appropriate request * context. Either use * * aDocument (preferred), or if no document is available, then provide * * aPrincipal */ void setRequestContext(in nsIDOMDocument aDocument, in nsIPrincipal aPrincipal); + + /* + * Checks if a CSP requires Subresource Integrity (SRI) + * for a given nsContentPolicyType. + */ + bool requireSRIForType(in nsContentPolicyType aContentType); + /** * Verifies ancestry as permitted by the policy. * * NOTE: Calls to this may trigger violation reports when queried, so this * value should not be cached. * * @param docShell * containing the protected resource
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -171,17 +171,17 @@ #endif #include "NuwaChild.h" #ifdef MOZ_GAMEPAD #include "mozilla/dom/GamepadService.h" #endif #ifndef MOZ_SIMPLEPUSH -#include "nsIPushNotifier.h" +#include "mozilla/dom/PushNotifier.h" #endif #include "mozilla/dom/File.h" #include "mozilla/dom/cellbroadcast/CellBroadcastIPCService.h" #include "mozilla/dom/icc/IccChild.h" #include "mozilla/dom/mobileconnection/MobileConnectionChild.h" #include "mozilla/dom/mobilemessage/SmsChild.h" #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h" @@ -3280,87 +3280,62 @@ ContentChild::RecvEndDragSession(const b } bool ContentChild::RecvPush(const nsCString& aScope, const IPC::Principal& aPrincipal, const nsString& aMessageId) { #ifndef MOZ_SIMPLEPUSH - nsCOMPtr<nsIPushNotifier> pushNotifier = - do_GetService("@mozilla.org/push/Notifier;1"); - if (NS_WARN_IF(!pushNotifier)) { - return true; - } - - nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal, - Nothing()); - Unused << NS_WARN_IF(NS_FAILED(rv)); - - rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal, - aMessageId, Nothing()); - Unused << NS_WARN_IF(NS_FAILED(rv)); + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing()); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); #endif return true; } bool ContentChild::RecvPushWithData(const nsCString& aScope, const IPC::Principal& aPrincipal, const nsString& aMessageId, InfallibleTArray<uint8_t>&& aData) { #ifndef MOZ_SIMPLEPUSH - nsCOMPtr<nsIPushNotifier> pushNotifier = - do_GetService("@mozilla.org/push/Notifier;1"); - if (NS_WARN_IF(!pushNotifier)) { - return true; - } - - nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal, - Some(aData)); - Unused << NS_WARN_IF(NS_FAILED(rv)); - - rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal, - aMessageId, Some(aData)); - Unused << NS_WARN_IF(NS_FAILED(rv)); + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Some(aData)); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); #endif return true; } bool ContentChild::RecvPushSubscriptionChange(const nsCString& aScope, const IPC::Principal& aPrincipal) { #ifndef MOZ_SIMPLEPUSH - nsCOMPtr<nsIPushNotifier> pushNotifier = - do_GetService("@mozilla.org/push/Notifier;1"); - if (NS_WARN_IF(!pushNotifier)) { - return true; - } - - nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope, - aPrincipal); - Unused << NS_WARN_IF(NS_FAILED(rv)); - - rv = pushNotifier->NotifySubscriptionChangeWorkers(aScope, aPrincipal); - Unused << NS_WARN_IF(NS_FAILED(rv)); + PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); #endif return true; } bool -ContentChild::RecvPushError(const nsCString& aScope, const nsString& aMessage, - const uint32_t& aFlags) +ContentChild::RecvPushError(const nsCString& aScope, const IPC::Principal& aPrincipal, + const nsString& aMessage, const uint32_t& aFlags) { #ifndef MOZ_SIMPLEPUSH - nsCOMPtr<nsIPushNotifier> pushNotifier = - do_GetService("@mozilla.org/push/Notifier;1"); - if (NS_WARN_IF(!pushNotifier)) { - return true; - } - pushNotifier->NotifyErrorWorkers(aScope, aMessage, aFlags); + PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); +#endif + return true; +} + +bool +ContentChild::RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope, + const IPC::Principal& aPrincipal) +{ +#ifndef MOZ_SIMPLEPUSH + PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers())); #endif return true; } } // namespace dom } // namespace mozilla
--- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -534,18 +534,22 @@ public: const nsString& aMessageId, InfallibleTArray<uint8_t>&& aData) override; virtual bool RecvPushSubscriptionChange(const nsCString& aScope, const IPC::Principal& aPrincipal) override; virtual bool - RecvPushError(const nsCString& aScope, const nsString& aMessage, - const uint32_t& aFlags) override; + RecvPushError(const nsCString& aScope, const IPC::Principal& aPrincipal, + const nsString& aMessage, const uint32_t& aFlags) override; + + virtual bool + RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope, + const IPC::Principal& aPrincipal) override; // Get the directory for IndexedDB files. We query the parent for this and // cache the value nsString &GetIndexedDBPath(); ContentParentId GetID() const { return mID; } bool IsForApp() const { return mIsForApp; }
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -262,17 +262,17 @@ using namespace mozilla::system; #include "nsIProfileSaveEvent.h" #endif #ifdef MOZ_GAMEPAD #include "mozilla/dom/GamepadMonitoring.h" #endif #ifndef MOZ_SIMPLEPUSH -#include "nsIPushNotifier.h" +#include "mozilla/dom/PushNotifier.h" #endif #ifdef XP_WIN #include "mozilla/widget/AudioSession.h" #endif #include "VRManagerParent.h" // for VRManagerParent @@ -5823,81 +5823,53 @@ ContentParent::StartProfiler(nsIProfiler } bool ContentParent::RecvNotifyPushObservers(const nsCString& aScope, const IPC::Principal& aPrincipal, const nsString& aMessageId) { #ifndef MOZ_SIMPLEPUSH - nsCOMPtr<nsIPushNotifier> pushNotifier = - do_GetService("@mozilla.org/push/Notifier;1"); - if (NS_WARN_IF(!pushNotifier)) { - return true; - } - - nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal, - Nothing()); - Unused << NS_WARN_IF(NS_FAILED(rv)); + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing()); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers())); #endif return true; } bool ContentParent::RecvNotifyPushObserversWithData(const nsCString& aScope, const IPC::Principal& aPrincipal, const nsString& aMessageId, InfallibleTArray<uint8_t>&& aData) { #ifndef MOZ_SIMPLEPUSH - nsCOMPtr<nsIPushNotifier> pushNotifier = - do_GetService("@mozilla.org/push/Notifier;1"); - if (NS_WARN_IF(!pushNotifier)) { - return true; - } - - nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal, - Some(aData)); - Unused << NS_WARN_IF(NS_FAILED(rv)); + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Some(aData)); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers())); #endif return true; } bool ContentParent::RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope, const IPC::Principal& aPrincipal) { #ifndef MOZ_SIMPLEPUSH - nsCOMPtr<nsIPushNotifier> pushNotifier = - do_GetService("@mozilla.org/push/Notifier;1"); - if (NS_WARN_IF(!pushNotifier)) { - return true; - } - - nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope, - aPrincipal); - Unused << NS_WARN_IF(NS_FAILED(rv)); + PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers())); #endif return true; } bool ContentParent::RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope, const IPC::Principal& aPrincipal) { #ifndef MOZ_SIMPLEPUSH - nsCOMPtr<nsIPushNotifier> pushNotifier = - do_GetService("@mozilla.org/push/Notifier;1"); - if (NS_WARN_IF(!pushNotifier)) { - return true; - } - - nsresult rv = pushNotifier->NotifySubscriptionModifiedObservers(aScope, - aPrincipal); - Unused << NS_WARN_IF(NS_FAILED(rv)); + PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers())); #endif return true; } } // namespace dom } // namespace mozilla NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -667,17 +667,18 @@ child: /** * Send a `pushsubscriptionchange` event to a service worker in the child. */ async PushSubscriptionChange(nsCString scope, Principal principal); /** * Send a Push error message to all service worker clients in the child. */ - async PushError(nsCString scope, nsString message, uint32_t flags); + async PushError(nsCString scope, Principal principal, nsString message, + uint32_t flags); /** * Windows specific: associate this content process with the browsers * audio session. */ async SetAudioSessionData(nsID aID, nsString aDisplayName, nsString aIconPath); @@ -1193,21 +1194,21 @@ parent: nsString messageId, uint8_t[] data); /** * Notify `push-subscription-change` observers in the parent. */ async NotifyPushSubscriptionChangeObservers(nsCString scope, Principal principal); +both: + async AsyncMessage(nsString aMessage, CpowEntry[] aCpows, + Principal aPrincipal, ClonedMessageData aData); + /** - * Notify `push-subscription-modified` observers in the parent. + * Notify `push-subscription-modified` observers in the parent and child. */ async NotifyPushSubscriptionModifiedObservers(nsCString scope, Principal principal); - -both: - async AsyncMessage(nsString aMessage, CpowEntry[] aCpows, - Principal aPrincipal, ClonedMessageData aData); }; } }
--- a/dom/locales/en-US/chrome/security/csp.properties +++ b/dom/locales/en-US/chrome/security/csp.properties @@ -66,16 +66,19 @@ ignoreSrcForDirective = Ignoring srcs for directive ‘%1$S’ # %1$S is the hostname in question and %2$S is the keyword hostNameMightBeKeyword = Interpreting %1$S as a hostname, not a keyword. If you intended this to be a keyword, use ‘%2$S’ (wrapped in single quotes). # LOCALIZATION NOTE (notSupportingDirective): # directive is not supported (e.g. 'reflected-xss') notSupportingDirective = Not supporting directive ‘%1$S’. Directive and values will be ignored. # LOCALIZATION NOTE (blockAllMixedContent): # %1$S is the URL of the blocked resource load. blockAllMixedContent = Blocking insecure request ‘%1$S’. +# LOCALIZATION NOTE (ignoringDirectiveWithNoValues): +# %1$S is the name of a CSP directive that requires additional values (e.g., 'require-sri-for') +ignoringDirectiveWithNoValues = Ignoring ‘%1$S‘ since it does not contain any parameters. # CSP Errors: # LOCALIZATION NOTE (couldntParseInvalidSource): # %1$S is the source that could not be parsed couldntParseInvalidSource = Couldn’t parse invalid source %1$S # LOCALIZATION NOTE (couldntParseInvalidHost): # %1$S is the host that's invalid couldntParseInvalidHost = Couldn’t parse invalid host %1$S
--- a/dom/media/AudioStream.h +++ b/dom/media/AudioStream.h @@ -73,88 +73,16 @@ private: // Input rate in Hz (characteristic of the media being played) uint32_t mInRate; // True if the we are timestretching, false if we are resampling. bool mPreservesPitch; // The history of frames sent to the audio engine in each DataCallback. const nsAutoPtr<FrameHistory> mFrameHistory; }; -class CircularByteBuffer -{ -public: - CircularByteBuffer() - : mBuffer(nullptr), mCapacity(0), mStart(0), mCount(0) - {} - - // Set the capacity of the buffer in bytes. Must be called before any - // call to append or pop elements. - void SetCapacity(uint32_t aCapacity) { - MOZ_ASSERT(!mBuffer, "Buffer allocated."); - mCapacity = aCapacity; - mBuffer = MakeUnique<uint8_t[]>(mCapacity); - } - - uint32_t Length() { - return mCount; - } - - uint32_t Capacity() { - return mCapacity; - } - - uint32_t Available() { - return Capacity() - Length(); - } - - // Append aLength bytes from aSrc to the buffer. Caller must check that - // sufficient space is available. - void AppendElements(const uint8_t* aSrc, uint32_t aLength) { - MOZ_ASSERT(mBuffer && mCapacity, "Buffer not initialized."); - MOZ_ASSERT(aLength <= Available(), "Buffer full."); - - uint32_t end = (mStart + mCount) % mCapacity; - - uint32_t toCopy = std::min(mCapacity - end, aLength); - memcpy(&mBuffer[end], aSrc, toCopy); - memcpy(&mBuffer[0], aSrc + toCopy, aLength - toCopy); - mCount += aLength; - } - - // Remove aSize bytes from the buffer. Caller must check returned size in - // aSize{1,2} before using the pointer returned in aData{1,2}. Caller - // must not specify an aSize larger than Length(). - void PopElements(uint32_t aSize, void** aData1, uint32_t* aSize1, - void** aData2, uint32_t* aSize2) { - MOZ_ASSERT(mBuffer && mCapacity, "Buffer not initialized."); - MOZ_ASSERT(aSize <= Length(), "Request too large."); - - *aData1 = &mBuffer[mStart]; - *aSize1 = std::min(mCapacity - mStart, aSize); - *aData2 = &mBuffer[0]; - *aSize2 = aSize - *aSize1; - mCount -= *aSize1 + *aSize2; - mStart += *aSize1 + *aSize2; - mStart %= mCapacity; - } - - size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const - { - size_t amount = 0; - amount += aMallocSizeOf(mBuffer.get()); - return amount; - } - -private: - UniquePtr<uint8_t[]> mBuffer; - uint32_t mCapacity; - uint32_t mStart; - uint32_t mCount; -}; - /* * A bookkeeping class to track the read/write position of an audio buffer. */ class AudioBufferCursor { public: AudioBufferCursor(AudioDataValue* aPtr, uint32_t aChannels, uint32_t aFrames) : mPtr(aPtr), mChannels(aChannels), mFrames(aFrames) {}
--- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -557,17 +557,13 @@ private: MozPromiseHolder<SeekPromise> mSeekPromise; RefPtr<VideoFrameContainer> mVideoFrameContainer; layers::ImageContainer* GetImageContainer(); #ifdef MOZ_EME RefPtr<CDMProxy> mCDMProxy; #endif - -#if defined(READER_DORMANT_HEURISTIC) - const bool mDormantEnabled; -#endif }; } // namespace mozilla #endif
--- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -105,18 +105,19 @@ const nsIID nsIMediaDevice::COMTypeInfo< namespace { already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() { nsCOMPtr<nsIAsyncShutdownService> svc = mozilla::services::GetAsyncShutdown(); MOZ_RELEASE_ASSERT(svc); nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase; nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(shutdownPhase)); if (!shutdownPhase) { - // We are probably in a content process. - rv = svc->GetContentChildShutdown(getter_AddRefs(shutdownPhase)); + // We are probably in a content process. We need to do cleanup at + // XPCOM shutdown in leakchecking builds. + rv = svc->GetXpcomWillShutdown(getter_AddRefs(shutdownPhase)); } MOZ_RELEASE_ASSERT(shutdownPhase); MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); return shutdownPhase.forget(); } } namespace mozilla {
--- a/dom/media/MediaShutdownManager.cpp +++ b/dom/media/MediaShutdownManager.cpp @@ -50,18 +50,19 @@ static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier() { nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown(); MOZ_RELEASE_ASSERT(svc); nsCOMPtr<nsIAsyncShutdownClient> barrier; nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier)); if (!barrier) { - // We are probably in a content process. - rv = svc->GetContentChildShutdown(getter_AddRefs(barrier)); + // We are probably in a content process. We need to do cleanup at + // XPCOM shutdown in leakchecking builds. + rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier)); } MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); MOZ_RELEASE_ASSERT(barrier); return barrier.forget(); } void MediaShutdownManager::EnsureCorrectShutdownObserverState()
--- a/dom/media/MediaStreamGraphImpl.h +++ b/dom/media/MediaStreamGraphImpl.h @@ -151,18 +151,19 @@ public: GetShutdownBarrier() { nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown(); MOZ_RELEASE_ASSERT(svc); nsCOMPtr<nsIAsyncShutdownClient> barrier; nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier)); if (!barrier) { - // We are probably in a content process. - rv = svc->GetContentChildShutdown(getter_AddRefs(barrier)); + // We are probably in a content process. We need to do cleanup at + // XPCOM shutdown in leakchecking builds. + rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier)); } MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); MOZ_RELEASE_ASSERT(barrier); return barrier.forget(); } class ShutdownTicket final {
--- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -25,28 +25,20 @@ #include "AndroidBridge.h" #endif #include "mozilla/layers/LayersTypes.h" #include "PDMFactory.h" namespace mozilla { -#if defined(MOZ_GONK_MEDIACODEC) || defined(XP_WIN) || defined(MOZ_APPLEMEDIA) || defined(MOZ_FFMPEG) -#define MP4_READER_DORMANT_HEURISTIC -#else -#undef MP4_READER_DORMANT_HEURISTIC -#endif - MP4Decoder::MP4Decoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) { -#if defined(MP4_READER_DORMANT_HEURISTIC) mDormantSupported = Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false); -#endif } MediaDecoderStateMachine* MP4Decoder::CreateStateMachine() { mReader = new MediaFormatReader(this, new MP4Demuxer(GetResource()), GetVideoFrameContainer());
--- a/dom/media/mediasource/SourceBuffer.cpp +++ b/dom/media/mediasource/SourceBuffer.cpp @@ -28,22 +28,16 @@ class JSObject; extern mozilla::LogModule* GetMediaSourceLog(); extern mozilla::LogModule* GetMediaSourceAPILog(); #define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__)) #define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__)) #define MSE_API(arg, ...) MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__)) -#if defined(MOZ_GONK_MEDIACODEC) || defined(XP_WIN) || defined(MOZ_APPLEMEDIA) || defined(MOZ_FFMPEG) -#define MP4_READER_DORMANT_HEURISTIC -#else -#undef MP4_READER_DORMANT_HEURISTIC -#endif - namespace mozilla { using media::TimeUnit; typedef SourceBufferAttributes::AppendState AppendState; namespace dom { void @@ -303,19 +297,17 @@ SourceBuffer::SourceBuffer(MediaSource* { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aMediaSource); mTrackBuffersManager = new TrackBuffersManager(aMediaSource->GetDecoder(), aType); // Now that we know what type we're dealing with, enable dormant as needed. -#if defined(MP4_READER_DORMANT_HEURISTIC) aMediaSource->GetDecoder()->NotifyDormantSupported(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false)); -#endif MSE_DEBUG("Create mTrackBuffersManager=%p", mTrackBuffersManager.get()); ErrorResult dummy; if (mCurrentAttributes.mGenerateTimestamps) { SetMode(SourceBufferAppendMode::Sequence, dummy); } else {
--- a/dom/media/ogg/OggReader.cpp +++ b/dom/media/ogg/OggReader.cpp @@ -567,19 +567,20 @@ nsresult OggReader::DecodeVorbis(ogg_pac nsresult OggReader::DecodeOpus(ogg_packet* aPacket) { NS_ASSERTION(aPacket->granulepos != -1, "Must know opus granulepos!"); // Maximum value is 63*2880, so there's no chance of overflow. int32_t frames_number = opus_packet_get_nb_frames(aPacket->packet, aPacket->bytes); if (frames_number <= 0) return NS_ERROR_FAILURE; // Invalid packet header. - int32_t samples = opus_packet_get_samples_per_frame(aPacket->packet, - (opus_int32) mOpusState->mRate); - int32_t frames = frames_number*samples; + int32_t samplesPerFrame = + opus_packet_get_samples_per_frame(aPacket->packet, + (opus_int32) mOpusState->mRate); + int32_t frames = frames_number * samplesPerFrame; // A valid Opus packet must be between 2.5 and 120 ms long. if (frames < 120 || frames > 5760) return NS_ERROR_FAILURE; uint32_t channels = mOpusState->mChannels; AlignedAudioBuffer buffer(frames * channels); if (!buffer) { return NS_ERROR_OUT_OF_MEMORY; @@ -617,22 +618,22 @@ nsresult OggReader::DecodeOpus(ogg_packe if (skipFrames == frames) { // discard the whole packet mOpusState->mSkip -= frames; LOG(LogLevel::Debug, ("Opus decoder skipping %d frames" " (whole packet)", frames)); return NS_OK; } int32_t keepFrames = frames - skipFrames; - int samples = keepFrames * channels; - AlignedAudioBuffer trimBuffer(samples); + int keepSamples = keepFrames * channels; + AlignedAudioBuffer trimBuffer(keepSamples); if (!trimBuffer) { return NS_ERROR_OUT_OF_MEMORY; } - for (int i = 0; i < samples; i++) + for (int i = 0; i < keepSamples; i++) trimBuffer[i] = buffer[skipFrames*channels + i]; startFrame = endFrame - keepFrames; frames = keepFrames; buffer = Move(trimBuffer); mOpusState->mSkip -= skipFrames; LOG(LogLevel::Debug, ("Opus decoder skipping %d frames", skipFrames)); @@ -640,26 +641,26 @@ nsresult OggReader::DecodeOpus(ogg_packe // Save this packet's granule position in case we need to perform end // trimming on the next packet. mOpusState->mPrevPacketGranulepos = endFrame; // Apply the header gain if one was specified. #ifdef MOZ_SAMPLE_TYPE_FLOAT32 if (mOpusState->mGain != 1.0f) { float gain = mOpusState->mGain; - int samples = frames * channels; - for (int i = 0; i < samples; i++) { + int gainSamples = frames * channels; + for (int i = 0; i < gainSamples; i++) { buffer[i] *= gain; } } #else if (mOpusState->mGain_Q16 != 65536) { int64_t gain_Q16 = mOpusState->mGain_Q16; - int samples = frames * channels; - for (int i = 0; i < samples; i++) { + int gainSamples = frames * channels; + for (int i = 0; i < gainSamples; i++) { int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16); buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val)); } } #endif // No channel mapping for more than 8 channels. if (channels > 8) { @@ -1332,17 +1333,16 @@ OggReader::IndexedSeekResult OggReader:: nsresult OggReader::SeekInBufferedRange(int64_t aTarget, int64_t aAdjustedTarget, int64_t aStartTime, int64_t aEndTime, const nsTArray<SeekRange>& aRanges, const SeekRange& aRange) { LOG(LogLevel::Debug, ("%p Seeking in buffered data to %lld using bisection search", mDecoder, aTarget)); - nsresult res = NS_OK; if (HasVideo() || aAdjustedTarget >= aTarget) { // We know the exact byte range in which the target must lie. It must // be buffered in the media cache. Seek there. nsresult res = SeekBisection(aTarget, aRange, 0); if (NS_FAILED(res) || !HasVideo()) { return res; } @@ -1366,16 +1366,18 @@ nsresult OggReader::SeekInBufferedRange( int shift = mTheoraState->mInfo.keyframe_granule_shift; int64_t keyframeGranulepos = (video->mTimecode >> shift) << shift; int64_t keyframeTime = mTheoraState->StartTime(keyframeGranulepos); SEEK_LOG(LogLevel::Debug, ("Keyframe for %lld is at %lld, seeking back to it", video->mTime, keyframeTime)); aAdjustedTarget = std::min(aAdjustedTarget, keyframeTime); } } + + nsresult res = NS_OK; if (aAdjustedTarget < aTarget) { SeekRange k = SelectSeekRange(aRanges, aAdjustedTarget, aStartTime, aEndTime, false); res = SeekBisection(aAdjustedTarget, k, SEEK_FUZZ_USECS); } @@ -1692,26 +1694,26 @@ nsresult OggReader::SeekBisection(int64_ NS_ASSERTION(guess != previousGuess, "Guess should be different to previous"); previousGuess = guess; hops++; // Locate the next page after our seek guess, and then figure out the // granule time of the audio and video bitstreams there. We can then // make a bisection decision based on our location in the media. - PageSyncResult res = PageSync(&mResource, - &mOggState, - false, - guess, - endOffset, - &page, - skippedBytes); - NS_ENSURE_TRUE(res != PAGE_SYNC_ERROR, NS_ERROR_FAILURE); + PageSyncResult pageSyncResult = PageSync(&mResource, + &mOggState, + false, + guess, + endOffset, + &page, + skippedBytes); + NS_ENSURE_TRUE(pageSyncResult != PAGE_SYNC_ERROR, NS_ERROR_FAILURE); - if (res == PAGE_SYNC_END_OF_RANGE) { + if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) { // Our guess was too close to the end, we've ended up reading the end // page. Backoff exponentially from the end point, in case the last // page/frame/sample is huge. mustBackoff = true; SEEK_LOG(LogLevel::Debug, ("Hit the end of range, backing off")); continue; } @@ -1886,26 +1888,26 @@ media::TimeIntervals OggReader::GetBuffe // Find the start time of the range. Read pages until we find one with a // granulepos which we can convert into a timestamp to use as the time of // the start of the buffered range. ogg_sync_reset(&sync.mState); while (startTime == -1) { ogg_page page; int32_t discard; - PageSyncResult res = PageSync(&mResource, - &sync.mState, - true, - startOffset, - endOffset, - &page, - discard); - if (res == PAGE_SYNC_ERROR) { + PageSyncResult pageSyncResult = PageSync(&mResource, + &sync.mState, + true, + startOffset, + endOffset, + &page, + discard); + if (pageSyncResult == PAGE_SYNC_ERROR) { return media::TimeIntervals::Invalid(); - } else if (res == PAGE_SYNC_END_OF_RANGE) { + } else if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) { // Hit the end of range without reading a page, give up trying to // find a start time for this buffered range, skip onto the next one. break; } int64_t granulepos = ogg_page_granulepos(&page); if (granulepos == -1) { // Page doesn't have an end time, advance to the next page @@ -2039,9 +2041,8 @@ bool OggCodecStore::Contains(uint32_t se OggCodecState* OggCodecStore::Get(uint32_t serial) { MonitorAutoLock mon(mMonitor); return mCodecStates.Get(serial); } } // namespace mozilla -
--- a/dom/media/ogg/OpusParser.cpp +++ b/dom/media/ogg/OpusParser.cpp @@ -160,18 +160,17 @@ bool OpusParser::DecodeTags(unsigned cha return false; uint32_t ncomments = LittleEndian::readUint32(buf); buf += 4; bytes -= 4; // If there are so many comments even their length fields // won't fit in the packet, stop reading now. if (ncomments > (bytes>>2)) return false; - uint32_t i; - for (i = 0; i < ncomments; i++) { + for (uint32_t i = 0; i < ncomments; i++) { if (bytes < 4) return false; len = LittleEndian::readUint32(buf); buf += 4; bytes -= 4; if (len > bytes) return false; mTags.AppendElement(nsCString(reinterpret_cast<const char*>(buf), len)); @@ -185,9 +184,8 @@ bool OpusParser::DecodeTags(unsigned cha for (uint32_t i = 0; i < mTags.Length(); i++) { OPUS_LOG(LogLevel::Debug, (" %s", mTags[i].get())); } #endif return true; } } // namespace mozilla -
--- a/dom/media/ogg/moz.build +++ b/dom/media/ogg/moz.build @@ -16,11 +16,8 @@ UNIFIED_SOURCES += [ 'OggCodecState.cpp', 'OggDecoder.cpp', 'OggReader.cpp', 'OggWriter.cpp', 'OpusParser.cpp', ] FINAL_LIBRARY = 'xul' - -if CONFIG['GNU_CXX']: - CXXFLAGS += ['-Wno-error=shadow']
--- a/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp +++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp @@ -170,31 +170,31 @@ GonkDecoderManager::ProcessInput(bool aE mToDo->setInt32("input-eos", 1); } mDecoder->requestActivityNotification(mToDo); } else if (aEndOfStream) { mToDo->setInt32("input-eos", 1); } } else { GMDD_LOG("input processed: error#%d", rv); - mDecodeCallback->Error(); + mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); } } void GonkDecoderManager::ProcessFlush() { MOZ_ASSERT(OnTaskLooper()); mLastTime = INT64_MIN; MonitorAutoLock lock(mFlushMonitor); mWaitOutput.Clear(); if (mDecoder->flush() != OK) { GMDD_LOG("flush error"); - mDecodeCallback->Error(); + mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); } mIsFlushing = false; lock.NotifyAll(); } // Use output timestamp to determine which output buffer is already returned // and remove corresponding info, except for EOS, from the waiting list. // This method handles the cases that audio decoder sends multiple output @@ -220,17 +220,17 @@ void GonkDecoderManager::ProcessToDo(bool aEndOfStream) { MOZ_ASSERT(OnTaskLooper()); MOZ_ASSERT(mToDo.get() != nullptr); mToDo.clear(); if (NumQueuedSamples() > 0 && ProcessQueuedSamples() < 0) { - mDecodeCallback->Error(); + mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); return; } while (mWaitOutput.Length() > 0) { RefPtr<MediaData> output; WaitOutputInfo wait = mWaitOutput.ElementAt(0); nsresult rv = Output(wait.mOffset, output); if (rv == NS_OK) { @@ -247,17 +247,17 @@ GonkDecoderManager::ProcessToDo(bool aEn MOZ_ASSERT(mWaitOutput.Length() == 1); mWaitOutput.RemoveElementAt(0); mDecodeCallback->DrainComplete(); ResetEOS(); return; } else if (rv == NS_ERROR_NOT_AVAILABLE) { break; } else { - mDecodeCallback->Error(); + mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); return; } } if (!aEndOfStream && NumQueuedSamples() <= MIN_QUEUED_SAMPLES) { mDecodeCallback->InputExhausted(); // No need to shedule todo task this time because InputExhausted() will // cause Input() to be invoked and do it for us. @@ -275,17 +275,17 @@ GonkDecoderManager::ProcessToDo(bool aEn void GonkDecoderManager::ResetEOS() { // After eos, android::MediaCodec needs to be flushed to receive next input mWaitOutput.Clear(); if (mDecoder->flush() != OK) { GMDD_LOG("flush error"); - mDecodeCallback->Error(); + mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR); } } void GonkDecoderManager::onMessageReceived(const sp<AMessage> &aMessage) { switch (aMessage->what()) { case kNotifyProcessInput:
--- a/dom/media/test/external/external_media_harness/testcase.py +++ b/dom/media/test/external/external_media_harness/testcase.py @@ -1,14 +1,15 @@ # 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/. import re import os +import time from marionette import BrowserMobProxyTestCaseMixin, MarionetteTestCase from marionette_driver import Wait from marionette_driver.errors import TimeoutException from marionette.marionette_test import SkipTest from firefox_puppeteer.testcases import BaseFirefoxTestCase from external_media_tests.utils import (timestamp_now, verbose_until) @@ -195,17 +196,17 @@ class NetworkBandwidthTestsMixin(object) def test_playback_limiting_bandwidth_1000(self): self.proxy.limits({'downstream_kbps': 1000}) self.run_videos(timeout=120) reset_adobe_gmp_script = """ navigator.requestMediaKeySystemAccess('com.adobe.primetime', -[{initDataType: 'cenc'}]).then( +[{initDataTypes: ['cenc']}]).then( function(access) { marionetteScriptFinished('success'); }, function(ex) { marionetteScriptFinished(ex); } ); """ @@ -236,18 +237,19 @@ class EMESetupMixin(object): # https://bugzilla.mozilla.org/show_bug.cgi?id=1187471#c28 # 2015-09-28 cpearce says this is no longer necessary, but in case # we are working with older firefoxes... self.prefs.set_pref('media.gmp.trial-create.enabled', False) def reset_GMP_version(self): if EMESetupMixin.version_needs_reset: with self.marionette.using_context('chrome'): - if self.prefs.get_pref('media.gm-eme-adobe.version'): - self.prefs.set_pref('media.gm-eme-adobe.version', None) + if self.prefs.get_pref('media.gmp-eme-adobe.version'): + self.prefs.reset_pref('media.gmp-eme-adobe.version') + with self.marionette.using_context('content'): result = self.marionette.execute_async_script( reset_adobe_gmp_script, script_timeout=60000) if not result == 'success': raise VideoException( 'ERROR: Resetting Adobe GMP failed % s' % result) EMESetupMixin.version_needs_reset = False
--- a/dom/push/PushNotifier.cpp +++ b/dom/push/PushNotifier.cpp @@ -49,322 +49,92 @@ PushNotifier::NotifyPushWithData(const n { nsTArray<uint8_t> data; if (!data.SetCapacity(aDataLen, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } if (!data.InsertElementsAt(0, aData, aDataLen, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } - return NotifyPush(aScope, aPrincipal, aMessageId, Some(data)); + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Some(data)); + return Dispatch(dispatcher); } NS_IMETHODIMP PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal, const nsAString& aMessageId) { - return NotifyPush(aScope, aPrincipal, aMessageId, Nothing()); + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing()); + return Dispatch(dispatcher); } NS_IMETHODIMP PushNotifier::NotifySubscriptionChange(const nsACString& aScope, nsIPrincipal* aPrincipal) { - nsresult rv = NotifySubscriptionChangeObservers(aScope, aPrincipal); - Unused << NS_WARN_IF(NS_FAILED(rv)); - - if (XRE_IsContentProcess()) { - // Forward XPCOM observer notifications to the parent. - ContentChild* parentActor = ContentChild::GetSingleton(); - if (!NS_WARN_IF(!parentActor)) { - Unused << NS_WARN_IF( - !parentActor->SendNotifyPushSubscriptionChangeObservers( - PromiseFlatCString(aScope), IPC::Principal(aPrincipal))); - } - } - - rv = NotifySubscriptionChangeWorkers(aScope, aPrincipal); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - return NS_OK; + PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal); + return Dispatch(dispatcher); } NS_IMETHODIMP PushNotifier::NotifySubscriptionModified(const nsACString& aScope, nsIPrincipal* aPrincipal) { - nsresult rv = NotifySubscriptionModifiedObservers(aScope, aPrincipal); - Unused << NS_WARN_IF(NS_FAILED(rv)); - - if (XRE_IsContentProcess()) { - ContentChild* parentActor = ContentChild::GetSingleton(); - if (!NS_WARN_IF(!parentActor)) { - Unused << NS_WARN_IF( - !parentActor->SendNotifyPushSubscriptionModifiedObservers( - PromiseFlatCString(aScope), IPC::Principal(aPrincipal))); - } - } - - return NS_OK; + PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal); + return Dispatch(dispatcher); } NS_IMETHODIMP PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal, const nsAString& aMessage, uint32_t aFlags) { - if (ShouldNotifyWorkers(aPrincipal)) { - // For service worker subscriptions, report the error to all clients. - NotifyErrorWorkers(aScope, aMessage, aFlags); - return NS_OK; - } - // For system subscriptions, log the error directly to the browser console. - return nsContentUtils::ReportToConsoleNonLocalized(aMessage, - aFlags, - NS_LITERAL_CSTRING("Push"), - nullptr, /* aDocument */ - nullptr, /* aURI */ - EmptyString(), /* aLine */ - 0, /* aLineNumber */ - 0, /* aColumnNumber */ - nsContentUtils::eOMIT_LOCATION); -} - -nsresult -PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal, - const nsAString& aMessageId, - const Maybe<nsTArray<uint8_t>>& aData) -{ - // Notify XPCOM observers in the current process. - nsresult rv = NotifyPushObservers(aScope, aPrincipal, aData); - Unused << NS_WARN_IF(NS_FAILED(rv)); - - if (XRE_IsContentProcess()) { - // If we're in the content process, forward the notification to the parent. - // We don't need to do anything if we're already in the parent; - // `ContentChild::RecvPush` will notify content process observers. - ContentChild* parentActor = ContentChild::GetSingleton(); - if (!NS_WARN_IF(!parentActor)) { - if (aData) { - Unused << NS_WARN_IF( - !parentActor->SendNotifyPushObserversWithData( - PromiseFlatCString(aScope), IPC::Principal(aPrincipal), - PromiseFlatString(aMessageId), aData.ref())); - } else { - Unused << NS_WARN_IF( - !parentActor->SendNotifyPushObservers( - PromiseFlatCString(aScope), IPC::Principal(aPrincipal), - PromiseFlatString(aMessageId))); - } - } - } - - rv = NotifyPushWorkers(aScope, aPrincipal, aMessageId, aData); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - return NS_OK; -} - -nsresult -PushNotifier::NotifyPushWorkers(const nsACString& aScope, - nsIPrincipal* aPrincipal, - const nsAString& aMessageId, - const Maybe<nsTArray<uint8_t>>& aData) -{ - AssertIsOnMainThread(); - NS_ENSURE_ARG(aPrincipal); - - if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) { - // Notify the worker from the current process. Either we're running in - // the content process and received a message from the parent, or e10s - // is disabled. - if (!ShouldNotifyWorkers(aPrincipal)) { - return NS_OK; - } - RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); - if (!swm) { - return NS_ERROR_FAILURE; - } - nsAutoCString originSuffix; - nsresult rv = aPrincipal->GetOriginSuffix(originSuffix); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - return swm->SendPushEvent(originSuffix, aScope, aMessageId, aData); - } - - // Otherwise, we're in the parent and e10s is enabled. Broadcast the event - // to all content processes. - bool ok = true; - nsTArray<ContentParent*> contentActors; - ContentParent::GetAll(contentActors); - for (uint32_t i = 0; i < contentActors.Length(); ++i) { - if (aData) { - ok &= contentActors[i]->SendPushWithData(PromiseFlatCString(aScope), - IPC::Principal(aPrincipal), PromiseFlatString(aMessageId), aData.ref()); - } else { - ok &= contentActors[i]->SendPush(PromiseFlatCString(aScope), - IPC::Principal(aPrincipal), PromiseFlatString(aMessageId)); - } - } - return ok ? NS_OK : NS_ERROR_FAILURE; + PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags); + return Dispatch(dispatcher); } nsresult -PushNotifier::NotifySubscriptionChangeWorkers(const nsACString& aScope, - nsIPrincipal* aPrincipal) +PushNotifier::Dispatch(PushDispatcher& aDispatcher) { - AssertIsOnMainThread(); - NS_ENSURE_ARG(aPrincipal); + if (XRE_IsParentProcess()) { + // Always notify XPCOM observers in the parent process. + Unused << NS_WARN_IF(NS_FAILED(aDispatcher.NotifyObservers())); - if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) { - // Content process or e10s disabled. - if (!ShouldNotifyWorkers(aPrincipal)) { + nsTArray<ContentParent*> contentActors; + ContentParent::GetAll(contentActors); + if (!contentActors.IsEmpty()) { + // At least one content process is active, so e10s must be enabled. + // Broadcast a message to notify observers and service workers. + for (uint32_t i = 0; i < contentActors.Length(); ++i) { + Unused << NS_WARN_IF(!aDispatcher.SendToChild(contentActors[i])); + } return NS_OK; } - RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); - if (!swm) { - return NS_ERROR_FAILURE; - } - nsAutoCString originSuffix; - nsresult rv = aPrincipal->GetOriginSuffix(originSuffix); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - return swm->SendPushSubscriptionChangeEvent(originSuffix, aScope); - } - // Parent process, e10s enabled. - bool ok = true; - nsTArray<ContentParent*> contentActors; - ContentParent::GetAll(contentActors); - for (uint32_t i = 0; i < contentActors.Length(); ++i) { - ok &= contentActors[i]->SendPushSubscriptionChange( - PromiseFlatCString(aScope), IPC::Principal(aPrincipal)); - } - return ok ? NS_OK : NS_ERROR_FAILURE; -} + if (BrowserTabsRemoteAutostart()) { + // e10s is enabled, but no content processes are active. + return aDispatcher.HandleNoChildProcesses(); + } -void -PushNotifier::NotifyErrorWorkers(const nsACString& aScope, - const nsAString& aMessage, - uint32_t aFlags) -{ - AssertIsOnMainThread(); - - if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) { - // Content process or e10s disabled. - RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); - if (swm) { - swm->ReportToAllClients(PromiseFlatCString(aScope), - PromiseFlatString(aMessage), - NS_ConvertUTF8toUTF16(aScope), /* aFilename */ - EmptyString(), /* aLine */ - 0, /* aLineNumber */ - 0, /* aColumnNumber */ - aFlags); - } - return; + // e10s is disabled; notify workers in the parent. + return aDispatcher.NotifyWorkers(); } - // Parent process, e10s enabled. - nsTArray<ContentParent*> contentActors; - ContentParent::GetAll(contentActors); - if (!contentActors.IsEmpty()) { - // At least one content process active. - for (uint32_t i = 0; i < contentActors.Length(); ++i) { - Unused << NS_WARN_IF( - !contentActors[i]->SendPushError(PromiseFlatCString(aScope), - PromiseFlatString(aMessage), aFlags)); - } - return; - } - // Report to the console if no content processes are active. - nsCOMPtr<nsIURI> scopeURI; - nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; - } - Unused << NS_WARN_IF(NS_FAILED( - nsContentUtils::ReportToConsoleNonLocalized(aMessage, - aFlags, - NS_LITERAL_CSTRING("Push"), - nullptr, /* aDocument */ - scopeURI, /* aURI */ - EmptyString(), /* aLine */ - 0, /* aLineNumber */ - 0, /* aColumnNumber */ - nsContentUtils::eOMIT_LOCATION))); -} + // Otherwise, we're in the content process, so e10s must be enabled. Notify + // observers and workers, then send a message to notify observers in the + // parent. + MOZ_ASSERT(XRE_IsContentProcess()); -nsresult -PushNotifier::NotifyPushObservers(const nsACString& aScope, - nsIPrincipal* aPrincipal, - const Maybe<nsTArray<uint8_t>>& aData) -{ - nsCOMPtr<nsIPushData> data; - if (aData) { - data = new PushData(aData.ref()); - } - nsCOMPtr<nsIPushMessage> message = new PushMessage(aPrincipal, data); - return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, aScope); -} + nsresult rv = aDispatcher.NotifyObserversAndWorkers(); -nsresult -PushNotifier::NotifySubscriptionChangeObservers(const nsACString& aScope, - nsIPrincipal* aPrincipal) -{ - return DoNotifyObservers(aPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE, - aScope); -} - -nsresult -PushNotifier::NotifySubscriptionModifiedObservers(const nsACString& aScope, - nsIPrincipal* aPrincipal) -{ - return DoNotifyObservers(aPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED, - aScope); -} - -nsresult -PushNotifier::DoNotifyObservers(nsISupports *aSubject, const char *aTopic, - const nsACString& aScope) -{ - nsCOMPtr<nsIObserverService> obsService = - mozilla::services::GetObserverService(); - if (!obsService) { - return NS_ERROR_FAILURE; + ContentChild* parentActor = ContentChild::GetSingleton(); + if (!NS_WARN_IF(!parentActor)) { + Unused << NS_WARN_IF(!aDispatcher.SendToParent(parentActor)); } - // If there's a service for this push category, make sure it is alive. - nsCOMPtr<nsICategoryManager> catMan = - do_GetService(NS_CATEGORYMANAGER_CONTRACTID); - if (catMan) { - nsXPIDLCString contractId; - nsresult rv = catMan->GetCategoryEntry("push", - PromiseFlatCString(aScope).get(), - getter_Copies(contractId)); - if (NS_SUCCEEDED(rv)) { - // Ensure the service is created - we don't need to do anything with - // it though - we assume the service constructor attaches a listener. - nsCOMPtr<nsISupports> service = do_GetService(contractId); - } - } - return obsService->NotifyObservers(aSubject, aTopic, - NS_ConvertUTF8toUTF16(aScope).get()); -} -bool -PushNotifier::ShouldNotifyWorkers(nsIPrincipal* aPrincipal) -{ - // System subscriptions use observer notifications instead of service worker - // events. The `testing.notifyWorkers` pref disables worker events for - // non-system subscriptions. - return !nsContentUtils::IsSystemPrincipal(aPrincipal) && - Preferences::GetBool("dom.push.testing.notifyWorkers", true); + return rv; } PushData::PushData(const nsTArray<uint8_t>& aData) : mData(aData) {} PushData::~PushData() {} @@ -476,10 +246,297 @@ PushMessage::GetData(nsIPushData** aData { NS_ENSURE_ARG_POINTER(aData); nsCOMPtr<nsIPushData> data = mData; data.forget(aData); return NS_OK; } +PushDispatcher::PushDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal) + : mScope(aScope) + , mPrincipal(aPrincipal) +{} + +PushDispatcher::~PushDispatcher() +{} + +nsresult +PushDispatcher::HandleNoChildProcesses() +{ + return NS_OK; +} + +nsresult +PushDispatcher::NotifyObserversAndWorkers() +{ + Unused << NS_WARN_IF(NS_FAILED(NotifyObservers())); + return NotifyWorkers(); +} + +bool +PushDispatcher::ShouldNotifyWorkers() +{ + // System subscriptions use observer notifications instead of service worker + // events. The `testing.notifyWorkers` pref disables worker events for + // non-system subscriptions. + return !nsContentUtils::IsSystemPrincipal(mPrincipal) && + Preferences::GetBool("dom.push.testing.notifyWorkers", true); +} + +nsresult +PushDispatcher::DoNotifyObservers(nsISupports *aSubject, const char *aTopic, + const nsACString& aScope) +{ + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (!obsService) { + return NS_ERROR_FAILURE; + } + // If there's a service for this push category, make sure it is alive. + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (catMan) { + nsXPIDLCString contractId; + nsresult rv = catMan->GetCategoryEntry("push", + mScope.BeginReading(), + getter_Copies(contractId)); + if (NS_SUCCEEDED(rv)) { + // Ensure the service is created - we don't need to do anything with + // it though - we assume the service constructor attaches a listener. + nsCOMPtr<nsISupports> service = do_GetService(contractId); + } + } + return obsService->NotifyObservers(aSubject, aTopic, + NS_ConvertUTF8toUTF16(mScope).get()); +} + +PushMessageDispatcher::PushMessageDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal, + const nsAString& aMessageId, + const Maybe<nsTArray<uint8_t>>& aData) + : PushDispatcher(aScope, aPrincipal) + , mMessageId(aMessageId) + , mData(aData) +{} + +PushMessageDispatcher::~PushMessageDispatcher() +{} + +nsresult +PushMessageDispatcher::NotifyObservers() +{ + nsCOMPtr<nsIPushData> data; + if (mData) { + data = new PushData(mData.ref()); + } + nsCOMPtr<nsIPushMessage> message = new PushMessage(mPrincipal, data); + return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, mScope); +} + +nsresult +PushMessageDispatcher::NotifyWorkers() +{ + if (!ShouldNotifyWorkers()) { + return NS_OK; + } + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + return NS_ERROR_FAILURE; + } + nsAutoCString originSuffix; + nsresult rv = mPrincipal->GetOriginSuffix(originSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return swm->SendPushEvent(originSuffix, mScope, mMessageId, mData); +} + +bool +PushMessageDispatcher::SendToParent(ContentChild* aParentActor) +{ + if (mData) { + return aParentActor->SendNotifyPushObserversWithData(mScope, + IPC::Principal(mPrincipal), + mMessageId, + mData.ref()); + } + return aParentActor->SendNotifyPushObservers(mScope, + IPC::Principal(mPrincipal), + mMessageId); +} + +bool +PushMessageDispatcher::SendToChild(ContentParent* aContentActor) +{ + if (mData) { + return aContentActor->SendPushWithData(mScope, IPC::Principal(mPrincipal), + mMessageId, mData.ref()); + } + return aContentActor->SendPush(mScope, IPC::Principal(mPrincipal), + mMessageId); +} + +PushSubscriptionChangeDispatcher::PushSubscriptionChangeDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal) + : PushDispatcher(aScope, aPrincipal) +{} + +PushSubscriptionChangeDispatcher::~PushSubscriptionChangeDispatcher() +{} + +nsresult +PushSubscriptionChangeDispatcher::NotifyObservers() +{ + return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE, + mScope); +} + +nsresult +PushSubscriptionChangeDispatcher::NotifyWorkers() +{ + if (!ShouldNotifyWorkers()) { + return NS_OK; + } + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + return NS_ERROR_FAILURE; + } + nsAutoCString originSuffix; + nsresult rv = mPrincipal->GetOriginSuffix(originSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return swm->SendPushSubscriptionChangeEvent(originSuffix, mScope); +} + +bool +PushSubscriptionChangeDispatcher::SendToParent(ContentChild* aParentActor) +{ + return aParentActor->SendNotifyPushSubscriptionChangeObservers(mScope, + IPC::Principal(mPrincipal)); +} + +bool +PushSubscriptionChangeDispatcher::SendToChild(ContentParent* aContentActor) +{ + return aContentActor->SendPushSubscriptionChange(mScope, + IPC::Principal(mPrincipal)); +} + +PushSubscriptionModifiedDispatcher::PushSubscriptionModifiedDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal) + : PushDispatcher(aScope, aPrincipal) +{} + +PushSubscriptionModifiedDispatcher::~PushSubscriptionModifiedDispatcher() +{} + +nsresult +PushSubscriptionModifiedDispatcher::NotifyObservers() +{ + return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED, + mScope); +} + +nsresult +PushSubscriptionModifiedDispatcher::NotifyWorkers() +{ + return NS_OK; +} + +bool +PushSubscriptionModifiedDispatcher::SendToParent(ContentChild* aParentActor) +{ + return aParentActor->SendNotifyPushSubscriptionModifiedObservers(mScope, + IPC::Principal(mPrincipal)); +} + +bool +PushSubscriptionModifiedDispatcher::SendToChild(ContentParent* aContentActor) +{ + return aContentActor->SendNotifyPushSubscriptionModifiedObservers(mScope, + IPC::Principal(mPrincipal)); +} + +PushErrorDispatcher::PushErrorDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal, + const nsAString& aMessage, + uint32_t aFlags) + : PushDispatcher(aScope, aPrincipal) + , mMessage(aMessage) + , mFlags(aFlags) +{} + +PushErrorDispatcher::~PushErrorDispatcher() +{} + +nsresult +PushErrorDispatcher::NotifyObservers() +{ + return NS_OK; +} + +nsresult +PushErrorDispatcher::NotifyWorkers() +{ + if (!ShouldNotifyWorkers()) { + // For system subscriptions, log the error directly to the browser console. + return nsContentUtils::ReportToConsoleNonLocalized(mMessage, + mFlags, + NS_LITERAL_CSTRING("Push"), + nullptr, /* aDocument */ + nullptr, /* aURI */ + EmptyString(), /* aLine */ + 0, /* aLineNumber */ + 0, /* aColumnNumber */ + nsContentUtils::eOMIT_LOCATION); + } + // For service worker subscriptions, report the error to all clients. + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (swm) { + swm->ReportToAllClients(mScope, + mMessage, + NS_ConvertUTF8toUTF16(mScope), /* aFilename */ + EmptyString(), /* aLine */ + 0, /* aLineNumber */ + 0, /* aColumnNumber */ + mFlags); + } + return NS_OK; +} + +bool +PushErrorDispatcher::SendToParent(ContentChild*) +{ + return true; +} + +bool +PushErrorDispatcher::SendToChild(ContentParent* aContentActor) +{ + return aContentActor->SendPushError(mScope, IPC::Principal(mPrincipal), + mMessage, mFlags); +} + +nsresult +PushErrorDispatcher::HandleNoChildProcesses() +{ + // Report to the console if no content processes are active. + nsCOMPtr<nsIURI> scopeURI; + nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), mScope); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return nsContentUtils::ReportToConsoleNonLocalized(mMessage, + mFlags, + NS_LITERAL_CSTRING("Push"), + nullptr, /* aDocument */ + scopeURI, /* aURI */ + EmptyString(), /* aLine */ + 0, /* aLineNumber */ + 0, /* aColumnNumber */ + nsContentUtils::eOMIT_LOCATION); +} + } // namespace dom } // namespace mozilla
--- a/dom/push/PushNotifier.h +++ b/dom/push/PushNotifier.h @@ -6,19 +6,70 @@ #define mozilla_dom_PushNotifier_h #include "nsIPushNotifier.h" #include "nsCycleCollectionParticipant.h" #include "nsIPrincipal.h" #include "nsString.h" +#include "mozilla/Maybe.h" + namespace mozilla { namespace dom { +class ContentChild; +class ContentParent; + +/** + * `PushDispatcher` is a base class used to forward observer notifications and + * service worker events to the correct process. + */ +class MOZ_STACK_CLASS PushDispatcher +{ +public: + // Fires an XPCOM observer notification. This method may be called from both + // processes. + virtual nsresult NotifyObservers() = 0; + + // Fires a service worker event. This method is called from the content + // process if e10s is enabled, or the parent otherwise. + virtual nsresult NotifyWorkers() = 0; + + // A convenience method that calls `NotifyObservers` and `NotifyWorkers`. + nsresult NotifyObserversAndWorkers(); + + // Sends an IPDL message to fire an observer notification in the parent + // process. This method is only called from the content process, and only + // if e10s is enabled. + virtual bool SendToParent(ContentChild* aParentActor) = 0; + + // Sends an IPDL message to fire an observer notification and a service worker + // event in the content process. This method is only called from the parent, + // and only if e10s is enabled. + virtual bool SendToChild(ContentParent* aContentActor) = 0; + + // An optional method, called from the parent if e10s is enabled and there + // are no active content processes. The default behavior is a no-op. + virtual nsresult HandleNoChildProcesses(); + +protected: + PushDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal); + + virtual ~PushDispatcher(); + + bool ShouldNotifyWorkers(); + nsresult DoNotifyObservers(nsISupports *aSubject, const char *aTopic, + const nsACString& aScope); + + const nsCString mScope; + nsCOMPtr<nsIPrincipal> mPrincipal; +}; + /** * `PushNotifier` implements the `nsIPushNotifier` interface. This service * broadcasts XPCOM observer notifications for incoming push messages, then * forwards incoming push messages to service workers. * * All scriptable methods on this interface may be called from the parent or * content process. Observer notifications are broadcasted to both processes. */ @@ -27,41 +78,36 @@ class PushNotifier final : public nsIPus public: PushNotifier(); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushNotifier, nsIPushNotifier) NS_DECL_NSIPUSHNOTIFIER private: - virtual ~PushNotifier(); + ~PushNotifier(); - nsresult NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal, - const nsAString& aMessageId, - const Maybe<nsTArray<uint8_t>>& aData); - nsresult DoNotifyObservers(nsISupports *aSubject, const char *aTopic, - const nsACString& aScope); - bool ShouldNotifyWorkers(nsIPrincipal* aPrincipal); + nsresult Dispatch(PushDispatcher& aDispatcher); }; /** * `PushData` provides methods for retrieving push message data in different * formats. This class is similar to the `PushMessageData` WebIDL interface. */ class PushData final : public nsIPushData { public: explicit PushData(const nsTArray<uint8_t>& aData); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushData, nsIPushData) NS_DECL_NSIPUSHDATA private: - virtual ~PushData(); + ~PushData(); nsresult EnsureDecodedText(); nsTArray<uint8_t> mData; nsString mDecodedText; }; /** @@ -74,18 +120,84 @@ class PushMessage final : public nsIPush public: PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushMessage, nsIPushMessage) NS_DECL_NSIPUSHMESSAGE private: - virtual ~PushMessage(); + ~PushMessage(); nsCOMPtr<nsIPrincipal> mPrincipal; nsCOMPtr<nsIPushData> mData; }; +class PushMessageDispatcher final : public PushDispatcher +{ +public: + PushMessageDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal, + const nsAString& aMessageId, + const Maybe<nsTArray<uint8_t>>& aData); + ~PushMessageDispatcher(); + + nsresult NotifyObservers() override; + nsresult NotifyWorkers() override; + bool SendToParent(ContentChild* aParentActor) override; + bool SendToChild(ContentParent* aContentActor) override; + +private: + const nsString mMessageId; + const Maybe<nsTArray<uint8_t>> mData; +}; + +class PushSubscriptionChangeDispatcher final : public PushDispatcher +{ +public: + PushSubscriptionChangeDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal); + ~PushSubscriptionChangeDispatcher(); + + nsresult NotifyObservers() override; + nsresult NotifyWorkers() override; + bool SendToParent(ContentChild* aParentActor) override; + bool SendToChild(ContentParent* aContentActor) override; +}; + +class PushSubscriptionModifiedDispatcher : public PushDispatcher +{ +public: + PushSubscriptionModifiedDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal); + ~PushSubscriptionModifiedDispatcher(); + + nsresult NotifyObservers() override; + nsresult NotifyWorkers() override; + bool SendToParent(ContentChild* aParentActor) override; + bool SendToChild(ContentParent* aContentActor) override; +}; + +class PushErrorDispatcher final : public PushDispatcher +{ +public: + PushErrorDispatcher(const nsACString& aScope, + nsIPrincipal* aPrincipal, + const nsAString& aMessage, + uint32_t aFlags); + ~PushErrorDispatcher(); + + nsresult NotifyObservers() override; + nsresult NotifyWorkers() override; + bool SendToParent(ContentChild* aParentActor) override; + bool SendToChild(ContentParent* aContentActor) override; + +private: + nsresult HandleNoChildProcesses() override; + + const nsString mMessage; + uint32_t mFlags; +}; + } // namespace dom } // namespace mozilla #endif // mozilla_dom_PushNotifier_h
deleted file mode 100644 --- a/dom/push/test/xpcshell/test_handler_service_parent.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -function run_test() { - do_get_profile(); - run_next_test(); -} - -add_task(function* test_observer_notifications() { - // Push observer notifications dispatched in the child should be forwarded to - // the parent. - let notifyPromise = promiseObserverNotification( - PushServiceComponent.pushTopic); - let subChangePromise = promiseObserverNotification( - PushServiceComponent.subscriptionChangeTopic); - let subModifiedPromise = promiseObserverNotification( - PushServiceComponent.subscriptionModifiedTopic); - - yield run_test_in_child('./test_handler_service.js'); - - let principal = Services.scriptSecurityManager.getSystemPrincipal(); - - let { - data: notifyScope, - subject: notifySubject, - } = yield notifyPromise; - equal(notifyScope, 'chrome://test-scope', - 'Should forward push notifications with the correct scope'); - let message = notifySubject.QueryInterface(Ci.nsIPushMessage); - equal(message.principal, principal, - 'Should include the principal in the push message'); - strictEqual(message.data, null, 'Should not include data'); - - let { - data: subChangeScope, - subject: subChangePrincipal, - } = yield subChangePromise; - equal(subChangeScope, 'chrome://test-scope', - 'Should forward subscription change notifications with the correct scope'); - equal(subChangePrincipal, principal, - 'Should pass the principal as the subject of a change notification'); - - let { - data: subModifiedScope, - subject: subModifiedPrincipal, - } = yield subModifiedPromise; - equal(subModifiedScope, 'chrome://test-scope', - 'Should forward subscription modified notifications with the correct scope'); - equal(subModifiedPrincipal, principal, - 'Should pass the principal as the subject of a modified notification'); -});
new file mode 100644 --- /dev/null +++ b/dom/push/test/xpcshell/test_observer_remoting.js @@ -0,0 +1,90 @@ +'use strict'; + +const pushNotifier = Cc['@mozilla.org/push/Notifier;1'] + .getService(Ci.nsIPushNotifier); + +add_task(function* test_observer_remoting() { + if (isParent) { + yield testInParent(); + } else { + yield testInChild(); + } +}); + +function* testInParent() { + // Register observers for notifications from the child, then run the test in + // the child and wait for the notifications. + let promiseNotifications = waitForNotifierObservers('Hello from child!'); + let promiseFinished = run_test_in_child('./test_observer_remoting.js'); + yield promiseNotifications; + + // Wait until the child is listening for notifications from the parent. + yield do_await_remote_message('push_test_observer_remoting_child_ready'); + + // Fire an observer notification in the parent that should be forwarded to + // the child. + yield waitForNotifierObservers('Hello from parent!', true); + + // Wait for the child to exit. + yield promiseFinished; +} + +function* testInChild() { + // Fire an observer notification in the child that should be forwarded to + // the parent. + yield waitForNotifierObservers('Hello from child!', true); + + // Register observers for notifications from the parent, let the parent know + // we're ready, and wait for the notifications. + let promiseNotifierObservers = waitForNotifierObservers('Hello from parent!'); + do_send_remote_message('push_test_observer_remoting_child_ready'); + yield promiseNotifierObservers; +} + +function* waitForNotifierObservers(expectedText, shouldNotify = false) { + let notifyPromise = promiseObserverNotification( + PushServiceComponent.pushTopic); + let subChangePromise = promiseObserverNotification( + PushServiceComponent.subscriptionChangeTopic); + let subModifiedPromise = promiseObserverNotification( + PushServiceComponent.subscriptionModifiedTopic); + + let scope = 'chrome://test-scope'; + let principal = Services.scriptSecurityManager.getSystemPrincipal(); + let data = new TextEncoder('utf-8').encode(expectedText); + + if (shouldNotify) { + pushNotifier.notifyPushWithData(scope, principal, '', data.length, data); + pushNotifier.notifySubscriptionChange(scope, principal); + pushNotifier.notifySubscriptionModified(scope, principal); + } + + let { + data: notifyScope, + subject: notifySubject, + } = yield notifyPromise; + equal(notifyScope, scope, + 'Should fire push notifications with the correct scope'); + let message = notifySubject.QueryInterface(Ci.nsIPushMessage); + equal(message.principal, principal, + 'Should include the principal in the push message'); + strictEqual(message.data.text(), expectedText, 'Should include data'); + + let { + data: subChangeScope, + subject: subChangePrincipal, + } = yield subChangePromise; + equal(subChangeScope, scope, + 'Should fire subscription change notifications with the correct scope'); + equal(subChangePrincipal, principal, + 'Should pass the principal as the subject of a change notification'); + + let { + data: subModifiedScope, + subject: subModifiedPrincipal, + } = yield subModifiedPromise; + equal(subModifiedScope, scope, + 'Should fire subscription modified notifications with the correct scope'); + equal(subModifiedPrincipal, principal, + 'Should pass the principal as the subject of a modified notification'); +}
--- a/dom/push/test/xpcshell/xpcshell.ini +++ b/dom/push/test/xpcshell/xpcshell.ini @@ -2,26 +2,26 @@ head = head.js head-http2.js tail = # Push notifications and alarms are currently disabled on Android. skip-if = toolkit == 'android' [test_clear_origin_data.js] [test_crypto.js] [test_drop_expired.js] -[test_handler_service_parent.js] [test_handler_service.js] support-files = PushServiceHandler.js PushServiceHandler.manifest [test_notification_ack.js] [test_notification_data.js] [test_notification_duplicate.js] [test_notification_error.js] [test_notification_incomplete.js] [test_notification_version_string.js] [test_observer_data.js] +[test_observer_remoting.js] [test_permissions.js] run-sequentially = This will delete all existing push subscriptions. [test_quota_exceeded.js] [test_quota_observer.js] [test_quota_with_notification.js] [test_record.js]
--- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -587,16 +587,21 @@ nsCSPContext::LogViolationDetails(uint16 CASE_CHECK_AND_REPORT(NONCE_SCRIPT, SCRIPT, aNonce, CSP_UNSAFE_INLINE, SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC); CASE_CHECK_AND_REPORT(NONCE_STYLE, STYLESHEET, aNonce, CSP_UNSAFE_INLINE, STYLE_NONCE_VIOLATION_OBSERVER_TOPIC); CASE_CHECK_AND_REPORT(HASH_SCRIPT, SCRIPT, aContent, CSP_UNSAFE_INLINE, SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC); CASE_CHECK_AND_REPORT(HASH_STYLE, STYLESHEET, aContent, CSP_UNSAFE_INLINE, STYLE_HASH_VIOLATION_OBSERVER_TOPIC); + CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_STYLE, STYLESHEET, NS_LITERAL_STRING(""), + CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_STYLE_VIOLATION_OBSERVER_TOPIC); + CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_SCRIPT, SCRIPT, NS_LITERAL_STRING(""), + CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_SCRIPT_VIOLATION_OBSERVER_TOPIC); + default: NS_ASSERTION(false, "LogViolationDetails with invalid type"); break; } } return NS_OK; } @@ -1127,16 +1132,31 @@ nsCSPContext::AsyncReportViolation(nsISu aObserverSubject, aSourceFile, aScriptSample, aLineNum, this)); return NS_OK; } +NS_IMETHODIMP +nsCSPContext::RequireSRIForType(nsContentPolicyType aContentType, bool* outRequiresSRIForType) +{ + *outRequiresSRIForType = false; + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + if (mPolicies[i]->hasDirective(REQUIRE_SRI_FOR)) { + if (mPolicies[i]->requireSRIForType(aContentType)) { + *outRequiresSRIForType = true; + return NS_OK; + } + } + } + return NS_OK; +} + /** * Based on the given docshell, determines if this CSP context allows the * ancestry. * * In order to determine the URI of the parent document (one causing the load * of this protected document), this function obtains the docShellTreeItem, * then walks up the hierarchy until it finds a privileged (chrome) tree item. * Getting the a tree item's URI looks like this in pseudocode:
--- a/dom/security/nsCSPParser.cpp +++ b/dom/security/nsCSPParser.cpp @@ -4,16 +4,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ArrayUtils.h" #include "nsCOMPtr.h" #include "nsCSPParser.h" #include "nsCSPUtils.h" #include "nsIConsoleService.h" +#include "nsIContentPolicy.h" #include "nsIScriptError.h" #include "nsIStringBundle.h" #include "nsNetUtil.h" #include "nsReadableUtils.h" #include "nsServiceManagerUtils.h" #include "nsUnicharUtils.h" #include "mozilla/net/ReferrerPolicy.h" @@ -52,16 +53,19 @@ static const char16_t CLOSINGBRACE = ')' static const char16_t EQUALS = '='; static const char16_t ATSYMBOL = '@'; static const uint32_t kSubHostPathCharacterCutoff = 512; static const char *const kHashSourceValidFns [] = { "sha256", "sha384", "sha512" }; static const uint32_t kHashSourceValidFnsLen = 3; +static const char* const kStyle = "style"; +static const char* const kScript = "script"; + /* ===== nsCSPTokenizer ==================== */ nsCSPTokenizer::nsCSPTokenizer(const char16_t* aStart, const char16_t* aEnd) : mCurChar(aStart) , mEndChar(aEnd) { CSPPARSERLOG(("nsCSPTokenizer::nsCSPTokenizer")); @@ -908,16 +912,56 @@ nsCSPParser::referrerDirectiveValue() return; } // the referrer policy is valid, so go ahead and use it. mPolicy->setReferrerPolicy(&mCurDir[1]); } void +nsCSPParser::requireSRIForDirectiveValue(nsRequireSRIForDirective* aDir) { + // directive-value = "style" / "script" + // directive name is token 0, we need to examine the remaining tokens + for (uint32_t i = 1; i < mCurDir.Length(); i++) { + // mCurToken is only set here and remains the current token + // to be processed, which avoid passing arguments between functions. + mCurToken = mCurDir[i]; + resetCurValue(); + CSPPARSERLOG(("nsCSPParser:::directive (require-sri-for directive), " + "mCurToken: %s (valid), mCurValue: %s", + NS_ConvertUTF16toUTF8(mCurToken).get(), + NS_ConvertUTF16toUTF8(mCurValue).get())); + // add contentPolicyTypes to the CSP's required-SRI list for this token + if (mCurToken.LowerCaseEqualsASCII(kScript)) { + aDir->addType(nsIContentPolicy::TYPE_SCRIPT); + } + else if (mCurToken.LowerCaseEqualsASCII(kStyle)) { + aDir->addType(nsIContentPolicy::TYPE_STYLESHEET); + } else { + const char16_t* invalidTokenName[] = { mCurToken.get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, "failedToParseUnrecognizedSource", + invalidTokenName, ArrayLength(invalidTokenName)); + CSPPARSERLOG(("nsCSPParser:::directive (require-sri-for directive), " + "mCurToken: %s (invalid), mCurValue: %s", + NS_ConvertUTF16toUTF8(mCurToken).get(), + NS_ConvertUTF16toUTF8(mCurValue).get())); + } + } + if (!(aDir->hasType(nsIContentPolicy::TYPE_STYLESHEET)) && + !(aDir->hasType(nsIContentPolicy::TYPE_SCRIPT))) { + const char16_t* directiveName[] = { mCurToken.get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringDirectiveWithNoValues", + directiveName, ArrayLength(directiveName)); + return; + } else { + mPolicy->addDirective(aDir); + } +} + +void nsCSPParser::reportURIList(nsTArray<nsCSPBaseSrc*>& outSrcs) { nsCOMPtr<nsIURI> uri; nsresult rv; // remember, srcs start at index 1 for (uint32_t i = 1; i < mCurDir.Length(); i++) { mCurToken = mCurDir[i]; @@ -958,16 +1002,23 @@ nsCSPParser::directiveValue(nsTArray<nsC // special case handling of the referrer directive (since it doesn't contain // source lists) if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) { referrerDirectiveValue(); return; } + // special case handling of the require-sri-for directive (since it doesn't + // contain a source lists but rather types, e.g., style or script) + if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) { + // handled in directive() + return; + } + // Otherwise just forward to sourceList sourceList(outSrcs); } // directive-name = 1*( ALPHA / DIGIT / "-" ) nsCSPDirective* nsCSPParser::directiveName() { @@ -1037,16 +1088,20 @@ nsCSPParser::directiveName() if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE)) { const char16_t* params[] = { mCurToken.get(), NS_LITERAL_STRING("child-src").get() }; logWarningErrorToConsole(nsIScriptError::warningFlag, "deprecatedDirective", params, ArrayLength(params)); mFrameSrc = new nsCSPDirective(CSP_StringToCSPDirective(mCurToken)); return mFrameSrc; } + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) { + return new nsRequireSRIForDirective(CSP_StringToCSPDirective(mCurToken)); + } + return new nsCSPDirective(CSP_StringToCSPDirective(mCurToken)); } // directive = *WSP [ directive-name [ WSP directive-value ] ] void nsCSPParser::directive() { // Set the directiveName to mCurToken @@ -1096,16 +1151,23 @@ nsCSPParser::directive() "ignoreSrcForDirective", params, ArrayLength(params)); } // add the directive and return mPolicy->addUpgradeInsecDir(static_cast<nsUpgradeInsecureDirective*>(cspDir)); return; } + // special case handling for require-sri-for, which has directive values that + // are well-defined tokens but are not sources + if (cspDir->equals(nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) { + requireSRIForDirectiveValue(static_cast<nsRequireSRIForDirective*>(cspDir)); + return; + } + // make sure to reset cache variables when trying to invalidate unsafe-inline; // unsafe-inline might not only appear in script-src, but also in default-src mHasHashOrNonce = false; mUnsafeInlineKeywordSrc = nullptr; // Try to parse all the srcs by handing the array off to directiveValue nsTArray<nsCSPBaseSrc*> srcs; directiveValue(srcs);
--- a/dom/security/nsCSPParser.h +++ b/dom/security/nsCSPParser.h @@ -110,34 +110,35 @@ class nsCSPParser { nsIURI* aSelfURI, nsCSPContext* aCSPContext, bool aDeliveredViaMetaTag); ~nsCSPParser(); // Parsing the CSP using the source-list from http://www.w3.org/TR/CSP11/#source-list - nsCSPPolicy* policy(); - void directive(); - nsCSPDirective* directiveName(); - void directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs); - void referrerDirectiveValue(); - void sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs); - nsCSPBaseSrc* sourceExpression(); - nsCSPSchemeSrc* schemeSource(); - nsCSPHostSrc* hostSource(); - nsCSPBaseSrc* keywordSource(); - nsCSPNonceSrc* nonceSource(); - nsCSPHashSrc* hashSource(); - nsCSPHostSrc* appHost(); // helper function to support app specific hosts - nsCSPHostSrc* host(); - bool hostChar(); - bool schemeChar(); - bool port(); - bool path(nsCSPHostSrc* aCspHost); + nsCSPPolicy* policy(); + void directive(); + nsCSPDirective* directiveName(); + void directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs); + void requireSRIForDirectiveValue(nsRequireSRIForDirective* aDir); + void referrerDirectiveValue(); + void sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs); + nsCSPBaseSrc* sourceExpression(); + nsCSPSchemeSrc* schemeSource(); + nsCSPHostSrc* hostSource(); + nsCSPBaseSrc* keywordSource(); + nsCSPNonceSrc* nonceSource(); + nsCSPHashSrc* hashSource(); + nsCSPHostSrc* appHost(); // helper function to support app specific hosts + nsCSPHostSrc* host(); + bool hostChar(); + bool schemeChar(); + bool port(); + bool path(nsCSPHostSrc* aCspHost); bool subHost(); // helper function to parse subDomains bool atValidUnreservedChar(); // helper function to parse unreserved bool atValidSubDelimChar(); // helper function to parse sub-delims bool atValidPctEncodedChar(); // helper function to parse pct-encoded bool subPath(nsCSPHostSrc* aCspHost); // helper function to parse paths void reportURIList(nsTArray<nsCSPBaseSrc*>& outSrcs); // helper function to parse report-uris void percentDecodeStr(const nsAString& aEncStr, // helper function to percent-decode
--- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -948,17 +948,17 @@ nsCSPDirective::toDomCSPStruct(mozilla:: // does not have any srcs return; case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE: outCSP.mChild_src.Construct(); outCSP.mChild_src.Value() = mozilla::Move(srcs); return; - // REFERRER_DIRECTIVE is handled in nsCSPPolicy::toDomCSPStruct() + // REFERRER_DIRECTIVE and REQUIRE_SRI_FOR are handled in nsCSPPolicy::toDomCSPStruct() default: NS_ASSERTION(false, "cannot find directive to convert CSP to JSON"); } } bool @@ -1070,16 +1070,66 @@ nsUpgradeInsecureDirective::~nsUpgradeIn void nsUpgradeInsecureDirective::toString(nsAString& outStr) const { outStr.AppendASCII(CSP_CSPDirectiveToString( nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)); } +/* ===== nsRequireSRIForDirective ========================= */ + +nsRequireSRIForDirective::nsRequireSRIForDirective(CSPDirective aDirective) +: nsCSPDirective(aDirective) +{ +} + +nsRequireSRIForDirective::~nsRequireSRIForDirective() +{ +} + +void +nsRequireSRIForDirective::toString(nsAString &outStr) const +{ + outStr.AppendASCII(CSP_CSPDirectiveToString( + nsIContentSecurityPolicy::REQUIRE_SRI_FOR)); + for (uint32_t i = 0; i < mTypes.Length(); i++) { + if (mTypes[i] == nsIContentPolicy::TYPE_SCRIPT) { + outStr.AppendASCII(" script"); + } + else if (mTypes[i] == nsIContentPolicy::TYPE_STYLESHEET) { + outStr.AppendASCII(" style"); + } + } +} + +bool +nsRequireSRIForDirective::hasType(nsContentPolicyType aType) const +{ + for (uint32_t i = 0; i < mTypes.Length(); i++) { + if (mTypes[i] == aType) { + return true; + } + } + return false; +} + +bool +nsRequireSRIForDirective::restrictsContentType(const nsContentPolicyType aType) const +{ + return this->hasType(aType); +} + +bool +nsRequireSRIForDirective::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const +{ + // can only disallow CSP_REQUIRE_SRI_FOR. + return (aKeyword != CSP_REQUIRE_SRI_FOR); +} + /* ===== nsCSPPolicy ========================= */ nsCSPPolicy::nsCSPPolicy() : mUpgradeInsecDir(nullptr) , mReportOnly(false) { CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy")); } @@ -1309,8 +1359,19 @@ nsCSPPolicy::visitDirectiveSrcs(CSPDirec { for (uint32_t i = 0; i < mDirectives.Length(); i++) { if (mDirectives[i]->equals(aDir)) { return mDirectives[i]->visitSrcs(aVisitor); } } return false; } + +bool +nsCSPPolicy::requireSRIForType(nsContentPolicyType aContentType) +{ + for (uint32_t i = 0; i < mDirectives.Length(); i++) { + if (mDirectives[i]->equals(nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) { + return static_cast<nsRequireSRIForDirective*>(mDirectives[i])->hasType(aContentType); + } + } + return false; +}
--- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -49,23 +49,25 @@ void CSP_LogMessage(const nsAString& aMe uint32_t aColumnNumber, uint32_t aFlags, const char* aCategory, uint32_t aInnerWindowID); /* =============== Constant and Type Definitions ================== */ -#define INLINE_STYLE_VIOLATION_OBSERVER_TOPIC "violated base restriction: Inline Stylesheets will not apply" -#define INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC "violated base restriction: Inline Scripts will not execute" -#define EVAL_VIOLATION_OBSERVER_TOPIC "violated base restriction: Code will not be created from strings" -#define SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC "Inline Script had invalid nonce" -#define STYLE_NONCE_VIOLATION_OBSERVER_TOPIC "Inline Style had invalid nonce" -#define SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC "Inline Script had invalid hash" -#define STYLE_HASH_VIOLATION_OBSERVER_TOPIC "Inline Style had invalid hash" +#define INLINE_STYLE_VIOLATION_OBSERVER_TOPIC "violated base restriction: Inline Stylesheets will not apply" +#define INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC "violated base restriction: Inline Scripts will not execute" +#define EVAL_VIOLATION_OBSERVER_TOPIC "violated base restriction: Code will not be created from strings" +#define SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC "Inline Script had invalid nonce" +#define STYLE_NONCE_VIOLATION_OBSERVER_TOPIC "Inline Style had invalid nonce" +#define SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC "Inline Script had invalid hash" +#define STYLE_HASH_VIOLATION_OBSERVER_TOPIC "Inline Style had invalid hash" +#define REQUIRE_SRI_SCRIPT_VIOLATION_OBSERVER_TOPIC "Missing required Subresource Integrity for Script" +#define REQUIRE_SRI_STYLE_VIOLATION_OBSERVER_TOPIC "Missing required Subresource Integrity for Style" // these strings map to the CSPDirectives in nsIContentSecurityPolicy // NOTE: When implementing a new directive, you will need to add it here but also // add a corresponding entry to the constants in nsIContentSecurityPolicy.idl // and also create an entry for the new directive in // nsCSPDirective::toDomCSPStruct() and add it to CSPDictionaries.webidl. // Order of elements below important! Make sure it matches the order as in // nsIContentSecurityPolicy.idl @@ -84,17 +86,19 @@ static const char* CSPStrDirectives[] = "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE "reflected-xss", // REFLECTED_XSS_DIRECTIVE "base-uri", // BASE_URI_DIRECTIVE "form-action", // FORM_ACTION_DIRECTIVE "referrer", // REFERRER_DIRECTIVE "manifest-src", // MANIFEST_SRC_DIRECTIVE "upgrade-insecure-requests", // UPGRADE_IF_INSECURE_DIRECTIVE "child-src", // CHILD_SRC_DIRECTIVE - "block-all-mixed-content" // BLOCK_ALL_MIXED_CONTENT + "block-all-mixed-content", // BLOCK_ALL_MIXED_CONTENT + "require-sri-for" // REQUIRE_SRI_FOR + }; inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) { return CSPStrDirectives[static_cast<uint32_t>(aDir)]; } inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) @@ -116,31 +120,33 @@ inline CSPDirective CSP_StringToCSPDirec // a string version for every enum >> using the same index << to // CSPStrKeywords underneath. enum CSPKeyword { CSP_SELF = 0, CSP_UNSAFE_INLINE, CSP_UNSAFE_EVAL, CSP_NONE, CSP_NONCE, + CSP_REQUIRE_SRI_FOR, // CSP_LAST_KEYWORD_VALUE always needs to be the last element in the enum // because we use it to calculate the size for the char* array. CSP_LAST_KEYWORD_VALUE, // Putting CSP_HASH after the delimitor, because CSP_HASH is not a valid // keyword (hash uses e.g. sha256, sha512) but we use CSP_HASH internally // to identify allowed hashes in ::allows. CSP_HASH }; static const char* CSPStrKeywords[] = { "'self'", // CSP_SELF = 0 "'unsafe-inline'", // CSP_UNSAFE_INLINE "'unsafe-eval'", // CSP_UNSAFE_EVAL "'none'", // CSP_NONE "'nonce-", // CSP_NONCE + "require-sri-for" // CSP_REQUIRE_SRI_FOR // Remember: CSP_HASH is not supposed to be used }; inline const char* CSP_EnumToKeyword(enum CSPKeyword aKey) { // Make sure all elements in enum CSPKeyword got added to CSPStrKeywords. static_assert((sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0]) == static_cast<uint32_t>(CSP_LAST_KEYWORD_VALUE)), @@ -474,16 +480,35 @@ class nsUpgradeInsecureDirective : publi { return false; } void toString(nsAString& outStr) const; void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs) { MOZ_ASSERT(false, "upgrade-insecure-requests does not hold any srcs"); } }; +/* ===== nsRequireSRIForDirective ========================= */ + +class nsRequireSRIForDirective : public nsCSPDirective { + public: + explicit nsRequireSRIForDirective(CSPDirective aDirective); + ~nsRequireSRIForDirective(); + + void toString(nsAString& outStr) const; + + void addType(nsContentPolicyType aType) + { mTypes.AppendElement(aType); } + bool hasType(nsContentPolicyType aType) const; + bool restrictsContentType(nsContentPolicyType aType) const; + bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; + + private: + nsTArray<nsContentPolicyType> mTypes; +}; + /* =============== nsCSPPolicy ================== */ class nsCSPPolicy { public: nsCSPPolicy(); virtual ~nsCSPPolicy(); bool permits(CSPDirective aDirective, @@ -528,16 +553,18 @@ class nsCSPPolicy { void getReportURIs(nsTArray<nsString> &outReportURIs) const; void getDirectiveStringForContentType(nsContentPolicyType aContentType, nsAString& outDirective) const; void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const; + bool requireSRIForType(nsContentPolicyType aContentType); + inline uint32_t getNumDirectives() const { return mDirectives.Length(); } bool visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const; private: nsUpgradeInsecureDirective* mUpgradeInsecDir; nsTArray<nsCSPDirective*> mDirectives;
--- a/dom/security/test/TestCSPParser.cpp +++ b/dom/security/test/TestCSPParser.cpp @@ -199,17 +199,19 @@ nsresult TestDirectives() { "connect-src http://www.example.com" }, { "report-uri http://www.example.com", "report-uri http://www.example.com/" }, { "script-src 'nonce-correctscriptnonce'", "script-src 'nonce-correctscriptnonce'" }, { "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='", "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" }, { "referrer no-referrer", - "referrer no-referrer" } + "referrer no-referrer" }, + { "require-sri-for script style", + "require-sri-for script style"} }; uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); return runTestSuite(policies, policyCount, 1); } // ============================= TestKeywords ======================== @@ -265,17 +267,19 @@ nsresult TestIgnoreUpperLowerCasePolicie "script-src 'nonce-correctscriptnonce'" }, { "script-src 'NoncE-NONCENEEDSTOBEUPPERCASE'", "script-src 'nonce-NONCENEEDSTOBEUPPERCASE'" }, { "script-src 'SHA256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='", "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" }, { "refERRer No-refeRRer", "referrer No-refeRRer" }, { "upgrade-INSECURE-requests", - "upgrade-insecure-requests" } + "upgrade-insecure-requests" }, + { "require-SRI-for sCript stYle", + "require-sri-for script style"} }; uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); return runTestSuite(policies, policyCount, 1); } // ============================= TestPaths ======================== @@ -502,19 +506,24 @@ nsresult TestPoliciesWithInvalidSrc() { { "script-src http://www.example.com:*.js", "script-src 'none'" }, { "script-src http://www.example.com:*.", "script-src 'none'" }, { "connect-src http://www.example.com/foo%zz;", "connect-src 'none'" }, { "script-src https://foo.com/%$", "script-src 'none'" }, + { "require-SRI-for script elephants", + "require-sri-for script"}, + { "require-sri-for paul", + ""} }; - uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + // amount of tests - 1, because the latest should be ignored. + uint32_t policyCount = (sizeof(policies) / sizeof(PolicyTest)) -1; return runTestSuite(policies, policyCount, 1); } // ============================= TestBadPolicies ======================== nsresult TestBadPolicies() { static const PolicyTest policies[] =
new file mode 100644 --- /dev/null +++ b/dom/security/test/sri/iframe_require-sri-for_main.html @@ -0,0 +1,31 @@ +<script> + window.hasCORSLoaded = false; // set through script_crossdomain1.js +</script> + +<!-- cors-enabled. should be loaded --> +<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain1.js" + crossorigin="" + integrity="sha512-9Tv2DL1fHvmPQa1RviwKleE/jq72jgxj8XGLyWn3H6Xp/qbtfK/jZINoPFAv2mf0Nn1TxhZYMFULAbzJNGkl4Q==" + onload="parent.postMessage('good_sriLoaded', '*');"></script> + +<!-- cors but not using SRI. should trigger onerror --> +<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain5.js" + onload="parent.postMessage('bad_nonsriLoaded', '*');" + onerror="parent.postMessage('good_nonsriBlocked', '*');"></script> + +<!-- cors and integrity. it should just load fine. --> +<link rel="stylesheet" href="style1.css" + integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8=" + onload="parent.postMessage('good_sriLoaded', '*');"> + +<!-- not using SRI, should trigger onerror --> +<link rel="stylesheet" href="style3.css" + onload="parent.postMessage('bad_nonsriLoaded', '*');" + onerror="parent.postMessage('good_nonsriBlocked', '*');"> + +<p id="black-text">black text</p> +<script> + window.onload = function() { + parent.postMessage("finish", '*'); + } +</script>
new file mode 100644 --- /dev/null +++ b/dom/security/test/sri/iframe_require-sri-for_main.html^headers^ @@ -0,0 +1,1 @@ +content-security-policy: require-sri-for script style
--- a/dom/security/test/sri/mochitest.ini +++ b/dom/security/test/sri/mochitest.ini @@ -1,10 +1,12 @@ [DEFAULT] support-files = + iframe_require-sri-for_main.html + iframe_require-sri-for_main.html^headers^ iframe_script_crossdomain.html iframe_script_sameorigin.html iframe_sri_disabled.html iframe_style_crossdomain.html iframe_style_sameorigin.html script_crossdomain1.js script_crossdomain1.js^headers^ script_crossdomain2.js @@ -29,8 +31,9 @@ support-files = style_301.css style_301.css^headers^ [test_script_sameorigin.html] [test_script_crossdomain.html] [test_sri_disabled.html] [test_style_crossdomain.html] [test_style_sameorigin.html] +[test_require-sri-for_csp_directive.html]
new file mode 100644 --- /dev/null +++ b/dom/security/test/sri/test_require-sri-for_csp_directive.html @@ -0,0 +1,44 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SRI require-sri-for CSP directive</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265318">Mozilla Bug 1265318</a> +<iframe style="width:200px;height:200px;" id="test_frame"></iframe> +</body> +<script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + function handler(event) { + switch (event.data) { + case 'good_sriLoaded': + ok(true, "Eligible SRI resources was correctly loaded."); + break; + case 'bad_nonsriLoaded': + ok(false, "Eligible non-SRI resource should be blocked by the CSP!"); + break; + case 'good_nonsriBlocked': + ok(true, "Eligible non-SRI resources was correctly blocked by the CSP."); + break; + case 'finish': + var blackText = frame.contentDocument.getElementById('black-text'); + var blackTextColor = frame.contentWindow.getComputedStyle(blackText, null).getPropertyValue('color'); + ok(blackTextColor == 'rgb(0, 0, 0)', "The second part should still be black."); + removeEventListener('message', handler); + SimpleTest.finish(); + break; + default: + break; + } + } + addEventListener("message", handler); + var frame = document.getElementById("test_frame"); + frame.src = "iframe_require-sri-for_main.html"; +</script> +</html>
--- a/dom/svg/nsSVGViewBox.cpp +++ b/dom/svg/nsSVGViewBox.cpp @@ -62,16 +62,21 @@ nsSVGAttrTearoffTable<nsSVGViewBox, dom: /* Implementation of nsSVGViewBox methods */ void nsSVGViewBox::Init() { mHasBaseVal = false; + // We shouldn't use mBaseVal for rendering (its usages should be guarded with + // "mHasBaseVal" checks), but just in case we do by accident, this will + // ensure that we treat it as "none" and ignore its numeric values: + mBaseVal.none = true; + mAnimVal = nullptr; } bool nsSVGViewBox::HasRect() const { // Check mAnimVal if we have one; otherwise, check mBaseVal if we have one; // otherwise, just return false (we clearly do not have a rect).
--- a/dom/tests/mochitest/localstorage/chrome.ini +++ b/dom/tests/mochitest/localstorage/chrome.ini @@ -1,13 +1,15 @@ [DEFAULT] skip-if = buildapp == 'b2g' || os == 'android' support-files = frame_clear_browser_data.html page_blank.html + frameQuota.html + interOriginFrame.js [test_app_uninstall.html] skip-if = buildapp != 'mulet' [test_clear_browser_data.html] skip-if = buildapp != 'mulet' [test_localStorageBasePrivateBrowsing_perwindowpb.html] skip-if = true # bug 1156725 [test_localStorageFromChrome.xhtml]
--- a/dom/tests/mochitest/localstorage/test_localStorageQuotaPrivateBrowsing_perwindowpb.html +++ b/dom/tests/mochitest/localstorage/test_localStorageQuotaPrivateBrowsing_perwindowpb.html @@ -7,17 +7,17 @@ <script type="text/javascript"> SimpleTest.waitForExplicitFinish(); Components.utils.import("resource://gre/modules/Services.jsm"); const Ci = Components.interfaces; const CONTENT_PAGE = "http://mochi.test:8888/chrome/dom/tests/mochitest/localstorage/page_blank.html"; -const slavePath = "/tests/dom/tests/mochitest/localstorage/"; +const slavePath = "/chrome/dom/tests/mochitest/localstorage/"; var currentTest = 1; var quota; try { quota = Services.prefs.getIntPref("dom.storage.default_quota"); } catch (ex) { quota = 5 * 1024; }
new file mode 100644 --- /dev/null +++ b/dom/u2f/NSSU2FTokenRemote.cpp @@ -0,0 +1,150 @@ +/* -*- 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/ContentChild.h" + +#include "NSSU2FTokenRemote.h" + +using mozilla::dom::ContentChild; + +NS_IMPL_ISUPPORTS(NSSU2FTokenRemote, nsIU2FToken) + +static mozilla::LazyLogModule gWebauthLog("webauth_u2f"); + +NSSU2FTokenRemote::NSSU2FTokenRemote() +{} + +NSSU2FTokenRemote::~NSSU2FTokenRemote() +{} + +NS_IMETHODIMP +NSSU2FTokenRemote::IsCompatibleVersion(const nsAString& aVersionString, + bool* aIsCompatible) +{ + NS_ENSURE_ARG_POINTER(aIsCompatible); + + ContentChild* cc = ContentChild::GetSingleton(); + MOZ_ASSERT(cc); + if (!cc->SendNSSU2FTokenIsCompatibleVersion( + nsString(aVersionString), aIsCompatible)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +NSSU2FTokenRemote::IsRegistered(uint8_t* aKeyHandle, uint32_t aKeyHandleLen, + bool* aIsRegistered) +{ + NS_ENSURE_ARG_POINTER(aKeyHandle); + NS_ENSURE_ARG_POINTER(aIsRegistered); + + nsTArray<uint8_t> keyHandle; + if (!keyHandle.ReplaceElementsAt(0, keyHandle.Length(), aKeyHandle, + aKeyHandleLen)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + ContentChild* cc = ContentChild::GetSingleton(); + MOZ_ASSERT(cc); + if (!cc->SendNSSU2FTokenIsRegistered(keyHandle, aIsRegistered)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +NSSU2FTokenRemote::Register(uint8_t* aApplication, + uint32_t aApplicationLen, + uint8_t* aChallenge, + uint32_t aChallengeLen, + uint8_t** aRegistration, + uint32_t* aRegistrationLen) +{ + NS_ENSURE_ARG_POINTER(aApplication); + NS_ENSURE_ARG_POINTER(aChallenge); + NS_ENSURE_ARG_POINTER(aRegistration); + NS_ENSURE_ARG_POINTER(aRegistrationLen); + + nsTArray<uint8_t> application; + if (!application.ReplaceElementsAt(0, application.Length(), aApplication, + aApplicationLen)) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsTArray<uint8_t> challenge; + if (!challenge.ReplaceElementsAt(0, challenge.Length(), aChallenge, + aChallengeLen)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsTArray<uint8_t> registrationBuffer; + ContentChild* cc = ContentChild::GetSingleton(); + MOZ_ASSERT(cc); + if (!cc->SendNSSU2FTokenRegister(application, challenge, + ®istrationBuffer)) { + return NS_ERROR_FAILURE; + } + + size_t dataLen = registrationBuffer.Length(); + uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(dataLen)); + if (NS_WARN_IF(!tmp)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + memcpy(tmp, registrationBuffer.Elements(), dataLen); + *aRegistration = tmp; + *aRegistrationLen = dataLen; + return NS_OK; +} + +NS_IMETHODIMP +NSSU2FTokenRemote::Sign(uint8_t* aApplication, uint32_t aApplicationLen, + uint8_t* aChallenge, uint32_t aChallengeLen, + uint8_t* aKeyHandle, uint32_t aKeyHandleLen, + uint8_t** aSignature, uint32_t* aSignatureLen) +{ + NS_ENSURE_ARG_POINTER(aApplication); + NS_ENSURE_ARG_POINTER(aChallenge); + NS_ENSURE_ARG_POINTER(aKeyHandle); + NS_ENSURE_ARG_POINTER(aSignature); + NS_ENSURE_ARG_POINTER(aSignatureLen); + + nsTArray<uint8_t> application; + if (!application.ReplaceElementsAt(0, application.Length(), aApplication, + aApplicationLen)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsTArray<uint8_t> challenge; + if (!challenge.ReplaceElementsAt(0, challenge.Length(), aChallenge, + aChallengeLen)) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsTArray<uint8_t> keyHandle; + if (!keyHandle.ReplaceElementsAt(0, keyHandle.Length(), aKeyHandle, + aKeyHandleLen)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsTArray<uint8_t> signatureBuffer; + ContentChild* cc = ContentChild::GetSingleton(); + MOZ_ASSERT(cc); + if (!cc->SendNSSU2FTokenSign(application, challenge, keyHandle, + &signatureBuffer)) { + return NS_ERROR_FAILURE; + } + + size_t dataLen = signatureBuffer.Length(); + uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(dataLen)); + if (NS_WARN_IF(!tmp)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + memcpy(tmp, signatureBuffer.Elements(), dataLen); + *aSignature = tmp; + *aSignatureLen = dataLen; + return NS_OK; +}
new file mode 100644 --- /dev/null +++ b/dom/u2f/NSSU2FTokenRemote.h @@ -0,0 +1,24 @@ +/* -*- 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/. */ + +#ifndef NSSU2FTokenRemote_h +#define NSSU2FTokenRemote_h + +#include "nsIU2FToken.h" + +class NSSU2FTokenRemote : public nsIU2FToken +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIU2FTOKEN + + NSSU2FTokenRemote(); + +private: + virtual ~NSSU2FTokenRemote(); +}; + +#endif // NSSU2FTokenRemote_h
--- a/dom/u2f/U2F.cpp +++ b/dom/u2f/U2F.cpp @@ -2,20 +2,22 @@ /* 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 "hasht.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/CryptoBuffer.h" +#include "mozilla/dom/NSSU2FTokenRemote.h" #include "mozilla/dom/U2F.h" #include "mozilla/dom/U2FBinding.h" #include "mozilla/Preferences.h" #include "nsContentUtils.h" +#include "nsINSSU2FToken.h" #include "nsNetCID.h" #include "nsNSSComponent.h" #include "nsURLParsers.h" #include "pk11pub.h" using mozilla::dom::ContentChild; namespace mozilla { @@ -32,17 +34,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F) NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent) -static mozilla::LazyLogModule gU2FLog("webauth_u2f"); +static mozilla::LazyLogModule gWebauthLog("webauth_u2f"); template <class CB, class Rsp> void SendError(CB* aCallback, ErrorCode aErrorCode) { Rsp response; response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode)); @@ -69,143 +71,35 @@ AssembleClientData(const nsAString& aOri if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) { return NS_ERROR_FAILURE; } return NS_OK; } -static nsresult -NSSTokenIsCompatible(nsINSSU2FToken* aNSSToken, const nsString& aVersionString, - bool* aIsCompatible) -{ - MOZ_ASSERT(aIsCompatible); - - if (XRE_IsParentProcess()) { - MOZ_ASSERT(aNSSToken); - return aNSSToken->IsCompatibleVersion(aVersionString, aIsCompatible); - } - - ContentChild* cc = ContentChild::GetSingleton(); - MOZ_ASSERT(cc); - if (!cc->SendNSSU2FTokenIsCompatibleVersion(aVersionString, aIsCompatible)) { - return NS_ERROR_FAILURE; - } - return NS_OK; -} - -static nsresult -NSSTokenIsRegistered(nsINSSU2FToken* aNSSToken, CryptoBuffer& aKeyHandle, - bool* aIsRegistered) -{ - MOZ_ASSERT(aIsRegistered); - - if (XRE_IsParentProcess()) { - MOZ_ASSERT(aNSSToken); - return aNSSToken->IsRegistered(aKeyHandle.Elements(), aKeyHandle.Length(), - aIsRegistered); - } - - ContentChild* cc = ContentChild::GetSingleton(); - MOZ_ASSERT(cc); - if (!cc->SendNSSU2FTokenIsRegistered(aKeyHandle, aIsRegistered)) { - return NS_ERROR_FAILURE; - } - return NS_OK; -} - -static nsresult -NSSTokenSign(nsINSSU2FToken* aNSSToken, CryptoBuffer& aKeyHandle, - CryptoBuffer& aApplication, CryptoBuffer& aChallenge, - CryptoBuffer& aSignatureData) -{ - if (XRE_IsParentProcess()) { - MOZ_ASSERT(aNSSToken); - uint8_t* buffer; - uint32_t bufferlen; - nsresult rv = aNSSToken->Sign(aApplication.Elements(), aApplication.Length(), - aChallenge.Elements(), aChallenge.Length(), - aKeyHandle.Elements(), aKeyHandle.Length(), - &buffer, &bufferlen); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - MOZ_ASSERT(buffer); - aSignatureData.Assign(buffer, bufferlen); - free(buffer); - return NS_OK; - } - - nsTArray<uint8_t> signatureBuffer; - ContentChild* cc = ContentChild::GetSingleton(); - MOZ_ASSERT(cc); - if (!cc->SendNSSU2FTokenSign(aApplication, aChallenge, aKeyHandle, - &signatureBuffer)) { - return NS_ERROR_FAILURE; - } - - aSignatureData.Assign(signatureBuffer); - return NS_OK; -} - -static nsresult -NSSTokenRegister(nsINSSU2FToken* aNSSToken, CryptoBuffer& aApplication, - CryptoBuffer& aChallenge, CryptoBuffer& aRegistrationData) -{ - if (XRE_IsParentProcess()) { - MOZ_ASSERT(aNSSToken); - uint8_t* buffer; - uint32_t bufferlen; - nsresult rv; - rv = aNSSToken->Register(aApplication.Elements(), aApplication.Length(), - aChallenge.Elements(), aChallenge.Length(), - &buffer, &bufferlen); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - MOZ_ASSERT(buffer); - aRegistrationData.Assign(buffer, bufferlen); - free(buffer); - return NS_OK; - } - - nsTArray<uint8_t> registrationBuffer; - ContentChild* cc = ContentChild::GetSingleton(); - MOZ_ASSERT(cc); - if (!cc->SendNSSU2FTokenRegister(aApplication, aChallenge, - ®istrationBuffer)) { - return NS_ERROR_FAILURE; - } - - aRegistrationData.Assign(registrationBuffer); - return NS_OK; -} - U2FTask::U2FTask(const nsAString& aOrigin, const nsAString& aAppId) : mOrigin(aOrigin) , mAppId(aAppId) {} U2FTask::~U2FTask() {} U2FRegisterTask::U2FRegisterTask(const nsAString& aOrigin, const nsAString& aAppId, const Sequence<RegisterRequest>& aRegisterRequests, const Sequence<RegisteredKey>& aRegisteredKeys, U2FRegisterCallback* aCallback, - const nsCOMPtr<nsINSSU2FToken>& aNSSToken) + const Sequence<Authenticator>& aAuthenticators) : U2FTask(aOrigin, aAppId) , mRegisterRequests(aRegisterRequests) , mRegisteredKeys(aRegisteredKeys) , mCallback(aCallback) - , mNSSToken(aNSSToken) + , mAuthenticators(aAuthenticators) {} U2FRegisterTask::~U2FRegisterTask() { nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) { return; @@ -223,20 +117,16 @@ NS_IMETHODIMP U2FRegisterTask::Run() { nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } - // TODO: Implement USB Tokens in Bug 1245527 - const bool softTokenEnabled = - Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED); - for (size_t i = 0; i < mRegisteredKeys.Length(); ++i) { RegisteredKey request(mRegisteredKeys[i]); // Check for required attributes if (!(request.mKeyHandle.WasPassed() && request.mVersion.WasPassed())) { continue; } @@ -257,25 +147,28 @@ U2FRegisterTask::Run() // We ignore mTransports, as it is intended to be used for sorting the // available devices by preference, but is not an exclusion factor. bool isCompatible = false; bool isRegistered = false; // Determine if the provided keyHandle is registered at any device. If so, // then we'll return DEVICE_INELIGIBLE to signify we're already registered. - if (softTokenEnabled) { - rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(), - &isCompatible); + for (auto token : mAuthenticators) { + rv = token->IsCompatibleVersion(request.mVersion.Value(), &isCompatible); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } + if (!isCompatible) { + continue; + } - rv = NSSTokenIsRegistered(mNSSToken, keyHandle, &isRegistered); + rv = token->IsRegistered(keyHandle.Elements(), keyHandle.Length(), + &isRegistered); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } if (isCompatible && isRegistered) { ReturnError(ErrorCode::DEVICE_INELIGIBLE); return NS_OK; @@ -328,31 +221,40 @@ U2FRegisterTask::Run() return NS_ERROR_FAILURE; } // Get the registration data from the token CryptoBuffer regData; bool registerSuccess = false; bool isCompatible = false; - if (!registerSuccess && softTokenEnabled) { - rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(), - &isCompatible); + for (auto token : mAuthenticators) { + rv = token->IsCompatibleVersion(request.mVersion.Value(), &isCompatible); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } if (isCompatible) { - rv = NSSTokenRegister(mNSSToken, appParam, challengeParam, regData); + uint8_t* buffer; + uint32_t bufferlen; + nsresult rv; + rv = token->Register(appParam.Elements(), appParam.Length(), + challengeParam.Elements(), challengeParam.Length(), + &buffer, &bufferlen); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } + + MOZ_ASSERT(buffer); + regData.Assign(buffer, bufferlen); + free(buffer); registerSuccess = true; + break; } } if (!registerSuccess) { // Try another request continue; } @@ -386,22 +288,22 @@ U2FRegisterTask::Run() return NS_ERROR_FAILURE; } U2FSignTask::U2FSignTask(const nsAString& aOrigin, const nsAString& aAppId, const nsAString& aChallenge, const Sequence<RegisteredKey>& aRegisteredKeys, U2FSignCallback* aCallback, - const nsCOMPtr<nsINSSU2FToken>& aNSSToken) + const Sequence<Authenticator>& aAuthenticators) : U2FTask(aOrigin, aAppId) , mChallenge(aChallenge) , mRegisteredKeys(aRegisteredKeys) , mCallback(aCallback) - , mNSSToken(aNSSToken) + , mAuthenticators(aAuthenticators) {} U2FSignTask::~U2FSignTask() { nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) { return; } @@ -418,20 +320,16 @@ NS_IMETHODIMP U2FSignTask::Run() { nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } - // TODO: Implement USB Tokens in Bug 1245527 - const bool softTokenEnabled = - Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED); - // Search the requests for one a token can fulfill for (size_t i = 0; i < mRegisteredKeys.Length(); i += 1) { RegisteredKey request(mRegisteredKeys[i]); // Check for required attributes if (!(request.mVersion.WasPassed() && request.mKeyHandle.WasPassed())) { continue; @@ -487,40 +385,52 @@ U2FSignTask::Run() // Get the signature from the token CryptoBuffer signatureData; bool signSuccess = false; // We ignore mTransports, as it is intended to be used for sorting the // available devices by preference, but is not an exclusion factor. - if (!signSuccess && softTokenEnabled) { + for (size_t a = 0; a < mAuthenticators.Length() && !signSuccess; ++a) { + Authenticator token(mAuthenticators[a]); bool isCompatible = false; bool isRegistered = false; - rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(), - &isCompatible); + rv = token->IsCompatibleVersion(request.mVersion.Value(), &isCompatible); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } + if (!isCompatible) { + continue; + } - rv = NSSTokenIsRegistered(mNSSToken, keyHandle, &isRegistered); + rv = token->IsRegistered(keyHandle.Elements(), keyHandle.Length(), + &isRegistered); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } if (isCompatible && isRegistered) { - rv = NSSTokenSign(mNSSToken, keyHandle, appParam, challengeParam, - signatureData); + uint8_t* buffer; + uint32_t bufferlen; + nsresult rv = token->Sign(appParam.Elements(), appParam.Length(), + challengeParam.Elements(), challengeParam.Length(), + keyHandle.Elements(), keyHandle.Length(), + &buffer, &bufferlen); if (NS_FAILED(rv)) { ReturnError(ErrorCode::OTHER_ERROR); return NS_ERROR_FAILURE; } + + MOZ_ASSERT(buffer); + signatureData.Assign(buffer, bufferlen); + free(buffer); signSuccess = true; } } if (!signSuccess) { // Try another request continue; } @@ -671,61 +581,79 @@ U2F::Init(nsPIDOMWindowInner* aParent, E } if (NS_WARN_IF(mOrigin.IsEmpty())) { aRv.Throw(NS_ERROR_FAILURE); return; } if (!EnsureNSSInitializedChromeOrContent()) { - MOZ_LOG(gU2FLog, LogLevel::Debug, ("Failed to get NSS context for U2F")); + MOZ_LOG(gWebauthLog, LogLevel::Debug, ("Failed to get NSS context for U2F")); aRv.Throw(NS_ERROR_FAILURE); return; } - if (XRE_IsParentProcess()) { - mNSSToken = do_GetService(NS_NSSU2FTOKEN_CONTRACTID); - if (NS_WARN_IF(!mNSSToken)) { - aRv.Throw(NS_ERROR_FAILURE); - return; + // Monolithically insert compatible nsIU2FToken objects into mAuthenticators. + // In future functionality expansions, this is where we could add a dynamic + // add/remove interface. + if (Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED)) { + if (!XRE_IsParentProcess()) { + MOZ_LOG(gWebauthLog, LogLevel::Debug, + ("Is e10s Process, getting remote U2F soft token")); + + if (!mAuthenticators.AppendElement(new NSSU2FTokenRemote(), + mozilla::fallible)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + } else { + MOZ_LOG(gWebauthLog, LogLevel::Debug, + ("Is non-e10s Process, getting direct U2F soft token")); + + nsCOMPtr<nsINSSU2FToken> softToken = + do_GetService(NS_NSSU2FTOKEN_CONTRACTID); + if (NS_WARN_IF(!softToken)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (!mAuthenticators.AppendElement(softToken, mozilla::fallible)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } } } - - aRv = mUSBToken.Init(); - if (NS_WARN_IF(aRv.Failed())) { - return; - } } void U2F::Register(const nsAString& aAppId, const Sequence<RegisterRequest>& aRegisterRequests, const Sequence<RegisteredKey>& aRegisteredKeys, U2FRegisterCallback& aCallback, const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds, ErrorResult& aRv) { RefPtr<U2FRegisterTask> registerTask = new U2FRegisterTask(mOrigin, aAppId, aRegisterRequests, aRegisteredKeys, &aCallback, - mNSSToken); + mAuthenticators); EvaluateAppIDAndRunTask(registerTask); } void U2F::Sign(const nsAString& aAppId, const nsAString& aChallenge, const Sequence<RegisteredKey>& aRegisteredKeys, U2FSignCallback& aCallback, const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds, ErrorResult& aRv) { RefPtr<U2FSignTask> signTask = new U2FSignTask(mOrigin, aAppId, aChallenge, aRegisteredKeys, &aCallback, - mNSSToken); + mAuthenticators); EvaluateAppIDAndRunTask(signTask); } } // namespace dom } // namespace mozilla
--- a/dom/u2f/U2F.h +++ b/dom/u2f/U2F.h @@ -8,17 +8,17 @@ #define mozilla_dom_U2F_h #include "js/TypeDecls.h" #include "mozilla/Attributes.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Nullable.h" #include "mozilla/ErrorResult.h" #include "nsCycleCollectionParticipant.h" -#include "nsINSSU2FToken.h" +#include "nsIU2FToken.h" #include "nsNSSShutDown.h" #include "nsPIDOMWindow.h" #include "nsWrapperCache.h" #include "USBToken.h" namespace mozilla { namespace dom { @@ -35,16 +35,18 @@ enum class ErrorCode { OK = 0, OTHER_ERROR = 1, BAD_REQUEST = 2, CONFIGURATION_UNSUPPORTED = 3, DEVICE_INELIGIBLE = 4, TIMEOUT = 5 }; +typedef nsCOMPtr<nsIU2FToken> Authenticator; + class U2FTask : public Runnable { public: U2FTask(const nsAString& aOrigin, const nsAString& aAppId); nsString mOrigin; nsString mAppId; @@ -60,59 +62,59 @@ class U2FRegisterTask final : public nsN public U2FTask { public: U2FRegisterTask(const nsAString& aOrigin, const nsAString& aAppId, const Sequence<RegisterRequest>& aRegisterRequests, const Sequence<RegisteredKey>& aRegisteredKeys, U2FRegisterCallback* aCallback, - const nsCOMPtr<nsINSSU2FToken>& aNSSToken); + const Sequence<Authenticator>& aAuthenticators); // No NSS resources to release. virtual void virtualDestroyNSSReference() override {}; void ReturnError(ErrorCode code) override; NS_DECL_NSIRUNNABLE private: ~U2FRegisterTask(); Sequence<RegisterRequest> mRegisterRequests; Sequence<RegisteredKey> mRegisteredKeys; RefPtr<U2FRegisterCallback> mCallback; - nsCOMPtr<nsINSSU2FToken> mNSSToken; + Sequence<Authenticator> mAuthenticators; }; class U2FSignTask final : public nsNSSShutDownObject, public U2FTask { public: U2FSignTask(const nsAString& aOrigin, const nsAString& aAppId, const nsAString& aChallenge, const Sequence<RegisteredKey>& aRegisteredKeys, U2FSignCallback* aCallback, - const nsCOMPtr<nsINSSU2FToken>& aNSSToken); + const Sequence<Authenticator>& aAuthenticators); // No NSS resources to release. virtual void virtualDestroyNSSReference() override {}; void ReturnError(ErrorCode code) override; NS_DECL_NSIRUNNABLE private: ~U2FSignTask(); nsString mChallenge; Sequence<RegisteredKey> mRegisteredKeys; RefPtr<U2FSignCallback> mCallback; - nsCOMPtr<nsINSSU2FToken> mNSSToken; + Sequence<Authenticator> mAuthenticators; }; class U2F final : public nsISupports, public nsWrapperCache, public nsNSSShutDownObject { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -150,18 +152,17 @@ public: // No NSS resources to release. virtual void virtualDestroyNSSReference() override {}; private: nsCOMPtr<nsPIDOMWindowInner> mParent; nsString mOrigin; - USBToken mUSBToken; - nsCOMPtr<nsINSSU2FToken> mNSSToken; + Sequence<Authenticator> mAuthenticators; ~U2F(); }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_U2F_h
--- a/dom/u2f/moz.build +++ b/dom/u2f/moz.build @@ -1,20 +1,22 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. EXPORTS.mozilla.dom += [ + 'NSSU2FTokenRemote.h', 'U2F.h', 'USBToken.h', ] UNIFIED_SOURCES += [ + 'NSSU2FTokenRemote.cpp', 'U2F.cpp', 'USBToken.cpp', ] include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul'
--- a/dom/webidl/Blob.webidl +++ b/dom/webidl/Blob.webidl @@ -5,18 +5,20 @@ * * The origin of this IDL file is * http://dev.w3.org/2006/webapi/FileAPI/#blob * * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C * liability, trademark and document use rules apply. */ -[Constructor, - Constructor(sequence<(ArrayBuffer or ArrayBufferView or Blob or DOMString)> blobParts, optional BlobPropertyBag options), +typedef (ArrayBuffer or ArrayBufferView or Blob or USVString) BlobPart; + +[Constructor(optional sequence<BlobPart> blobParts, + optional BlobPropertyBag options), Exposed=(Window,Worker)] interface Blob { [GetterThrows] readonly attribute unsigned long long size; readonly attribute DOMString type;
--- a/dom/webidl/CSPDictionaries.webidl +++ b/dom/webidl/CSPDictionaries.webidl @@ -23,13 +23,14 @@ dictionary CSP { // sequence<DOMString> reflected-xss; // not suppored in Firefox sequence<DOMString> base-uri; sequence<DOMString> form-action; sequence<DOMString> referrer; sequence<DOMString> manifest-src; sequence<DOMString> upgrade-insecure-requests; sequence<DOMString> child-src; sequence<DOMString> block-all-mixed-content; + sequence<DOMString> require-sri-for; }; dictionary CSPPolicies { sequence<CSP> csp-policies; };
--- a/dom/webidl/Directory.webidl +++ b/dom/webidl/Directory.webidl @@ -10,17 +10,20 @@ * path should be a descendent path like "path/to/file.txt" and not contain a * segment of ".." or ".". So the paths aren't allowed to walk up the directory * tree. For example, paths like "../foo", "..", "/foo/bar" or "foo/../bar" are * not allowed. * * http://w3c.github.io/filesystem-api/#idl-def-Directory * https://microsoftedge.github.io/directory-upload/proposal.html#directory-interface */ -[Exposed=(Window,Worker)] + +// This chromeConstructor is used by the MockFilePicker for testing only. +[ChromeConstructor(DOMString path), + Exposed=(Window,Worker)] interface Directory { /* * The leaf name of the directory. */ [Throws] readonly attribute DOMString name; /*
--- a/dom/webidl/File.webidl +++ b/dom/webidl/File.webidl @@ -4,17 +4,17 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is * https://w3c.github.io/FileAPI/#file */ interface nsIFile; -[Constructor(sequence<(ArrayBuffer or ArrayBufferView or Blob or DOMString)> fileBits, +[Constructor(sequence<BlobPart> fileBits, USVString fileName, optional FilePropertyBag options), // These constructors are just for chrome callers: Constructor(Blob fileBits, optional ChromeFilePropertyBag options), Constructor(nsIFile fileBits, optional ChromeFilePropertyBag options), Constructor(USVString fileBits, optional ChromeFilePropertyBag options), Exposed=(Window,Worker)]
--- a/dom/webidl/HTMLInputElement.webidl +++ b/dom/webidl/HTMLInputElement.webidl @@ -204,16 +204,19 @@ partial interface HTMLInputElement { [Throws, Pref="dom.input.dirpicker"] Promise<sequence<(File or Directory)>> getFilesAndDirectories(); [Throws, Pref="dom.input.dirpicker"] Promise<sequence<File>> getFiles(optional boolean recursiveFlag = false); [Throws, Pref="dom.input.dirpicker"] void chooseDirectory(); + + [Pref="dom.webkitBlink.dirPicker.enabled", BinaryName="WebkitDirectoryAttr", SetterThrows] + attribute boolean webkitdirectory; }; [NoInterfaceObject] interface MozPhonetic { [Pure, ChromeOnly] readonly attribute DOMString phonetic; };
--- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -652,17 +652,17 @@ private: ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); MOZ_ASSERT(channel == loadInfo.mChannel); // We synthesize the result code, but its never exposed to content. RefPtr<mozilla::dom::InternalResponse> ir = new mozilla::dom::InternalResponse(200, NS_LITERAL_CSTRING("OK")); - ir->SetBody(loadInfo.mCacheReadStream); + ir->SetBody(loadInfo.mCacheReadStream, InternalResponse::UNKNOWN_BODY_SIZE); // Drop our reference to the stream now that we've passed it along, so it // doesn't hang around once the cache is done with it and keep data alive. loadInfo.mCacheReadStream = nullptr; // Set the channel info of the channel on the response so that it's // saved in the cache. ir->InitChannelInfo(channel);
--- a/dom/workers/ServiceWorkerScriptCache.cpp +++ b/dom/workers/ServiceWorkerScriptCache.cpp @@ -542,17 +542,17 @@ private: if (NS_WARN_IF(result.Failed())) { MOZ_ASSERT(!result.IsErrorWithMessage()); Fail(result.StealNSResult()); return; } RefPtr<InternalResponse> ir = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); - ir->SetBody(body); + ir->SetBody(body, mCN->Buffer().Length()); ir->InitChannelInfo(mChannelInfo); if (mPrincipalInfo) { ir->SetPrincipalInfo(Move(mPrincipalInfo)); } RefPtr<Response> response = new Response(aCache->GetGlobalObject(), ir);
--- a/dom/xbl/nsBindingManager.cpp +++ b/dom/xbl/nsBindingManager.cpp @@ -400,21 +400,18 @@ nsBindingManager::DoProcessAttachedQueue // Hold a strong reference while calling UnblockOnload since that might // run script. nsCOMPtr<nsIDocument> doc = mDocument; doc->UnblockOnload(true); } } void -nsBindingManager::ProcessAttachedQueue(uint32_t aSkipSize) +nsBindingManager::ProcessAttachedQueueInternal(uint32_t aSkipSize) { - if (mProcessingAttachedStack || mAttachedStack.Length() <= aSkipSize) - return; - mProcessingAttachedStack = true; // Excute constructors. Do this from high index to low while (mAttachedStack.Length() > aSkipSize) { uint32_t lastItem = mAttachedStack.Length() - 1; RefPtr<nsXBLBinding> binding = mAttachedStack.ElementAt(lastItem); mAttachedStack.RemoveElementAt(lastItem); if (binding) { @@ -1012,31 +1009,16 @@ nsBindingManager::Traverse(nsIContent *a NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mWrapperTable key"); cb.NoteXPCOMChild(aContent); NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mWrapperTable value"); cb.NoteXPCOMChild(value); } } void -nsBindingManager::BeginOutermostUpdate() -{ - mAttachedStackSizeOnOutermost = mAttachedStack.Length(); -} - -void -nsBindingManager::EndOutermostUpdate() -{ - if (!mProcessingAttachedStack) { - ProcessAttachedQueue(mAttachedStackSizeOnOutermost); - mAttachedStackSizeOnOutermost = 0; - } -} - -void nsBindingManager::HandleChildInsertion(nsIContent* aContainer, nsIContent* aChild, uint32_t aIndexInContainer, bool aAppend) { NS_PRECONDITION(aChild, "Must have child"); NS_PRECONDITION(!aContainer || uint32_t(aContainer->IndexOf(aChild)) == aIndexInContainer,
--- a/dom/xbl/nsBindingManager.h +++ b/dom/xbl/nsBindingManager.h @@ -90,17 +90,28 @@ public: nsINodeList* GetAnonymousNodesFor(nsIContent* aContent); nsresult ClearBinding(nsIContent* aContent); nsresult LoadBindingDocument(nsIDocument* aBoundDoc, nsIURI* aURL, nsIPrincipal* aOriginPrincipal); nsresult AddToAttachedQueue(nsXBLBinding* aBinding); void RemoveFromAttachedQueue(nsXBLBinding* aBinding); - void ProcessAttachedQueue(uint32_t aSkipSize = 0); + void ProcessAttachedQueue(uint32_t aSkipSize = 0) + { + if (mProcessingAttachedStack || mAttachedStack.Length() <= aSkipSize) { + return; + } + + ProcessAttachedQueueInternal(aSkipSize); + } +private: + void ProcessAttachedQueueInternal(uint32_t aSkipSize); + +public: void ExecuteDetachedHandlers(); nsresult PutXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo); nsXBLDocumentInfo* GetXBLDocumentInfo(nsIURI* aURI); void RemoveXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo); nsresult PutLoadingDocListener(nsIURI* aURL, nsIStreamListener* aListener); @@ -130,18 +141,28 @@ public: void Traverse(nsIContent *aContent, nsCycleCollectionTraversalCallback &cb); NS_DECL_CYCLE_COLLECTION_CLASS(nsBindingManager) // Notify the binding manager when an outermost update begins and // ends. The end method can execute script. - void BeginOutermostUpdate(); - void EndOutermostUpdate(); + void BeginOutermostUpdate() + { + mAttachedStackSizeOnOutermost = mAttachedStack.Length(); + } + + void EndOutermostUpdate() + { + if (!mProcessingAttachedStack) { + ProcessAttachedQueue(mAttachedStackSizeOnOutermost); + mAttachedStackSizeOnOutermost = 0; + } + } // When removing an insertion point or a parent of one, clear the insertion // points and their insertion parents. void ClearInsertionPointsRecursively(nsIContent* aContent); // Called when the document is going away void DropDocumentReference();
--- a/dom/xul/XULDocument.cpp +++ b/dom/xul/XULDocument.cpp @@ -3143,17 +3143,17 @@ XULDocument::MaybeBroadcast() } if (!mHandlingDelayedAttrChange) { mHandlingDelayedAttrChange = true; for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) { nsIAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName; if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) { nsCOMPtr<nsIContent> listener = do_QueryInterface(mDelayedAttrChangeBroadcasts[i].mListener); - nsString value = mDelayedAttrChangeBroadcasts[i].mAttr; + const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr; if (mDelayedAttrChangeBroadcasts[i].mSetAttr) { listener->SetAttr(kNameSpaceID_None, attrName, value, true); } else { listener->UnsetAttr(kNameSpaceID_None, attrName, true); } }
--- a/embedding/components/printingui/ipc/PPrinting.ipdl +++ b/embedding/components/printingui/ipc/PPrinting.ipdl @@ -18,17 +18,17 @@ sync protocol PPrinting manager PContent; manages PPrintProgressDialog; manages PPrintSettingsDialog; manages PRemotePrintJob; parent: sync ShowProgress(PBrowser browser, PPrintProgressDialog printProgressDialog, - PRemotePrintJob remotePrintJob, + nullable PRemotePrintJob remotePrintJob, bool isForPrinting) returns(bool notifyOnOpen, nsresult rv); async ShowPrintDialog(PPrintSettingsDialog dialog, PBrowser browser, PrintData settings);
--- a/gfx/2d/DrawTargetCairo.cpp +++ b/gfx/2d/DrawTargetCairo.cpp @@ -1955,21 +1955,27 @@ DrawTarget::Draw3DTransformedSurface(Sou return false; } // Wrap the surfaces in pixman images and do the transform. pixman_image_t* dst = pixman_image_create_bits(PIXMAN_a8r8g8b8, xformBounds.width, xformBounds.height, (uint32_t*)dstSurf->GetData(), dstSurf->Stride()); + if (!dst) { + return false; + } pixman_image_t* src = pixman_image_create_bits(srcFormat, srcSurf->GetSize().width, srcSurf->GetSize().height, (uint32_t*)srcMap.GetData(), srcMap.GetStride()); - MOZ_ASSERT(src && dst, "Failed to create pixman images?"); + if (!src) { + pixman_image_unref(dst); + return false; + } pixman_image_set_filter(src, PIXMAN_FILTER_BILINEAR, nullptr, 0); pixman_image_set_transform(src, &xform); pixman_image_composite32(PIXMAN_OP_SRC, src, nullptr, dst, 0, 0, 0, 0, 0, 0, xformBounds.width, xformBounds.height);
--- a/gfx/cairo/cairo/src/cairo-surface-wrapper.c +++ b/gfx/cairo/cairo/src/cairo-surface-wrapper.c @@ -44,27 +44,19 @@ static void _copy_transformed_pattern (cairo_pattern_t *pattern, const cairo_pattern_t *original, const cairo_matrix_t *ctm_inverse) { _cairo_pattern_init_static_copy (pattern, original); - /* apply device_transform first so that it is transformed by ctm_inverse */ - if (original->type == CAIRO_PATTERN_TYPE_SURFACE) { - cairo_surface_pattern_t *surface_pattern; - cairo_surface_t *surface; - - surface_pattern = (cairo_surface_pattern_t *) original; - surface = surface_pattern->surface; - - if (_cairo_surface_has_device_transform (surface)) - _cairo_pattern_transform (pattern, &surface->device_transform); - } + /* Device transform should already have been applied before cairo_surface_wrapper_* functions + * are called in _cairo_gstate_copy_transformed_pattern which all the gstate drawing + * functions call through _cairo_gstate_copy_transformed_source and such. */ if (! _cairo_matrix_is_identity (ctm_inverse)) _cairo_pattern_transform (pattern, ctm_inverse); } static inline cairo_bool_t _cairo_surface_wrapper_needs_device_transform (cairo_surface_wrapper_t *wrapper) {
--- a/gfx/layers/LayersTypes.h +++ b/gfx/layers/LayersTypes.h @@ -185,41 +185,26 @@ struct EventRegions { explicit EventRegions(nsIntRegion aHitRegion) : mHitRegion(aHitRegion) { } bool operator==(const EventRegions& aRegions) const { return mHitRegion == aRegions.mHitRegion && - mDispatchToContentHitRegion == aRegions.mDispatchToContentHitRegion; + mDispatchToContentHitRegion == aRegions.mDispatchToContentHitRegion && + mNoActionRegion == aRegions.mNoActionRegion && + mHorizontalPanRegion == aRegions.mHorizontalPanRegion && + mVerticalPanRegion == aRegions.mVerticalPanRegion; } bool operator!=(const EventRegions& aRegions) const { return !(*this == aRegions); } - void OrWith(const EventRegions& aOther) - { - mHitRegion.OrWith(aOther.mHitRegion); - mDispatchToContentHitRegion.OrWith(aOther.mDispatchToContentHitRegion); - } - - void AndWith(const nsIntRegion& aRegion) - { - mHitRegion.AndWith(aRegion); - mDispatchToContentHitRegion.AndWith(aRegion); - } - - void Sub(const EventRegions& aMinuend, const nsIntRegion& aSubtrahend) - { - mHitRegion.Sub(aMinuend.mHitRegion, aSubtrahend); - mDispatchToContentHitRegion.Sub(aMinuend.mDispatchToContentHitRegion, aSubtrahend); - } - void ApplyTranslationAndScale(float aXTrans, float aYTrans, float aXScale, float aYScale) { mHitRegion.ScaleRoundOut(aXScale, aYScale); mDispatchToContentHitRegion.ScaleRoundOut(aXScale, aYScale); mNoActionRegion.ScaleRoundOut(aXScale, aYScale); mHorizontalPanRegion.ScaleRoundOut(aXScale, aYScale); mVerticalPanRegion.ScaleRoundOut(aXScale, aYScale); @@ -229,22 +214,28 @@ struct EventRegions { mHorizontalPanRegion.MoveBy(aXTrans, aYTrans); mVerticalPanRegion.MoveBy(aXTrans, aYTrans); } void Transform(const gfx::Matrix4x4& aTransform) { mHitRegion.Transform(aTransform); mDispatchToContentHitRegion.Transform(aTransform); + mNoActionRegion.Transform(aTransform); + mHorizontalPanRegion.Transform(aTransform); + mVerticalPanRegion.Transform(aTransform); } bool IsEmpty() const { return mHitRegion.IsEmpty() - && mDispatchToContentHitRegion.IsEmpty(); + && mDispatchToContentHitRegion.IsEmpty() + && mNoActionRegion.IsEmpty() + && mHorizontalPanRegion.IsEmpty() + && mVerticalPanRegion.IsEmpty(); } nsCString ToString() const { nsCString result = mHitRegion.ToString(); result.AppendLiteral(";dispatchToContent="); result.Append(mDispatchToContentHitRegion.ToString()); return result;
--- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -691,17 +691,17 @@ APZCTreeManager::ReceiveInputEvent(Input // When the mouse is outside the window we still want to handle dragging // but we won't find an APZC. Fallback to root APZC then. if (!apzc && mRootNode) { apzc = mRootNode->GetApzc(); } if (apzc) { - bool targetConfirmed = (hitResult == HitLayer); + bool targetConfirmed = (hitResult != HitNothing && hitResult != HitDispatchToContentRegion); if (gfxPrefs::APZDragEnabled() && hitScrollbar) { // If scrollbar dragging is enabled and we hit a scrollbar, wait // for the main-thread confirmation because it contains drag metrics // that we need. targetConfirmed = false; } result = mInputQueue->ReceiveInputEvent( apzc, targetConfirmed, @@ -747,17 +747,17 @@ APZCTreeManager::ReceiveInputEvent(Input wheelInput.mHandledByAPZ = WillHandleInput(wheelInput); if (!wheelInput.mHandledByAPZ) { return result; } RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(wheelInput.mOrigin, &hitResult); if (apzc) { - MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion); + MOZ_ASSERT(hitResult != HitNothing); // For wheel events, the call to ReceiveInputEvent below may result in // scrolling, which changes the async transform. However, the event we // want to pass to gecko should be the pre-scroll event coordinates, // transformed into the gecko space. (pre-scroll because the mouse // cursor is stationary during wheel scrolling, unlike touchmove // events). Since we just flushed the pending repaints the transform to