author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Wed, 08 Jun 2016 11:57:53 +0200 | |
changeset 301056 | f8ad071a6e14331d73fa44c8d3108bc2b66b2174 |
parent 301055 | 33230ff64650d566863edb1891aaae227a580067 (current diff) |
parent 300997 | d31c4e9e910d1ea3cad06449943c17675c51392a (diff) |
child 301057 | d88ceacc2d2dc40964d017c7a4f0fcfbb42a0ce1 |
child 301151 | 81f4cc3f6f4c21758b63605d69b47fa6cb3c142a |
push id | 78183 |
push user | cbook@mozilla.com |
push date | Wed, 08 Jun 2016 10:14:20 +0000 |
treeherder | mozilla-inbound@d88ceacc2d2d [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 50.0a1 |
first release with | nightly linux32
f8ad071a6e14
/
50.0a1
/
20160608030219
/
files
nightly linux64
f8ad071a6e14
/
50.0a1
/
20160608030219
/
files
nightly mac
f8ad071a6e14
/
50.0a1
/
20160608030219
/
files
nightly win32
f8ad071a6e14
/
50.0a1
/
20160608030219
/
files
nightly win64
f8ad071a6e14
/
50.0a1
/
20160608030219
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
50.0a1
/
20160608030219
/
pushlog to previous
nightly linux64
50.0a1
/
20160608030219
/
pushlog to previous
nightly mac
50.0a1
/
20160608030219
/
pushlog to previous
nightly win32
50.0a1
/
20160608030219
/
pushlog to previous
nightly win64
50.0a1
/
20160608030219
/
pushlog to previous
|
js/src/jsapi-tests/testUncaughtError.cpp | file | annotate | diff | comparison | revisions | |
layout/generic/nsTextFrame.cpp | file | annotate | diff | comparison | revisions | |
testing/docker/desktop-test/dot-config/pip/pip.conf | file | annotate | diff | comparison | revisions | |
testing/docker/desktop-test/dot-config/user-dirs.dirs | file | annotate | diff | comparison | revisions | |
testing/docker/desktop-test/dot-config/user-dirs.locale | file | annotate | diff | comparison | revisions | |
testing/docker/desktop-test/dot-pulse/default.pa | file | annotate | diff | comparison | revisions |
--- a/accessible/base/AccIterator.h +++ b/accessible/base/AccIterator.h @@ -5,33 +5,35 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_a11y_AccIterator_h__ #define mozilla_a11y_AccIterator_h__ #include "DocAccessible.h" #include "Filters.h" +#include <memory> + class nsITreeView; namespace mozilla { namespace a11y { /** * AccIterable is a basic interface for iterators over accessibles. */ class AccIterable { public: virtual ~AccIterable() { } virtual Accessible* Next() = 0; private: friend class Relation; - nsAutoPtr<AccIterable> mNextIter; + std::unique_ptr<AccIterable> mNextIter; }; /** * Allows to iterate through accessible children or subtree complying with * filter function. */ class AccIterator : public AccIterable {
--- a/accessible/base/Logging.cpp +++ b/accessible/base/Logging.cpp @@ -612,23 +612,29 @@ logging::SelChange(nsISelection* aSelect Stack(); } void logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...) { if (IsEnabledAll(logging::eTree | aExtraFlags)) { - MsgBegin("TREE", aMsg); - va_list vl; va_start(vl, aExtraFlags); - const char* descr = nullptr; - while ((descr = va_arg(vl, const char*))) { - AccessibleInfo(descr, va_arg(vl, Accessible*)); + const char* descr = va_arg(vl, const char*); + if (descr) { + Accessible* acc = va_arg(vl, Accessible*); + MsgBegin("TREE", "%s; doc: %p", aMsg, acc ? acc->Document() : nullptr); + AccessibleInfo(descr, acc); + while ((descr = va_arg(vl, const char*))) { + AccessibleInfo(descr, va_arg(vl, Accessible*)); + } + } + else { + MsgBegin("TREE", aMsg); } va_end(vl); MsgEnd(); if (aExtraFlags & eStack) { Stack(); } @@ -636,17 +642,17 @@ logging::TreeInfo(const char* aMsg, uint } void logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, const char* aMsg1, Accessible* aAcc, const char* aMsg2, nsINode* aNode) { if (IsEnabledAll(logging::eTree | logging::eVerbose)) { - MsgBegin("TREE", aMsg); + MsgBegin("TREE", "%s; doc: %p", aMsg, aAcc ? aAcc->Document() : nullptr); AccessibleInfo(aMsg1, aAcc); Accessible* acc = aAcc->Document()->GetAccessible(aNode); if (acc) { AccessibleInfo(aMsg2, acc); } else { Node(aMsg2, aNode); } @@ -654,17 +660,17 @@ logging::TreeInfo(const char* aMsg, uint } } void logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, Accessible* aParent) { if (IsEnabledAll(logging::eTree | aExtraFlags)) { - MsgBegin("TREE", aMsg); + MsgBegin("TREE", "%s; doc: %p", aMsg, aParent->Document()); AccessibleInfo("container", aParent); for (uint32_t idx = 0; idx < aParent->ChildCount(); idx++) { AccessibleInfo("child", aParent->GetChildAt(idx)); } MsgEnd(); } }
--- a/accessible/base/Relation.h +++ b/accessible/base/Relation.h @@ -4,17 +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/. */ #ifndef mozilla_a11y_relation_h_ #define mozilla_a11y_relation_h_ #include "AccIterator.h" -#include "mozilla/Move.h" +#include <memory> namespace mozilla { namespace a11y { /** * A collection of relation targets of a certain type. Targets are computed * lazily while enumerating. */ @@ -46,19 +46,19 @@ public: mLastIter = aRH.mLastIter; aRH.mLastIter = nullptr; return *this; } inline void AppendIter(AccIterable* aIter) { if (mLastIter) - mLastIter->mNextIter = aIter; + mLastIter->mNextIter.reset(aIter); else - mFirstIter = aIter; + mFirstIter.reset(aIter); mLastIter = aIter; } /** * Append the given accessible to the set of related accessibles. */ inline void AppendTarget(Accessible* aAcc) @@ -79,31 +79,30 @@ public: /** * compute and return the next related accessible. */ inline Accessible* Next() { Accessible* target = nullptr; - // a trick nsAutoPtr deletes what it used to point to when assigned to while (mFirstIter && !(target = mFirstIter->Next())) - mFirstIter = mFirstIter->mNextIter; + mFirstIter = std::move(mFirstIter->mNextIter); if (!mFirstIter) mLastIter = nullptr; return target; } private: Relation& operator = (const Relation&) = delete; Relation(const Relation&) = delete; - nsAutoPtr<AccIterable> mFirstIter; + std::unique_ptr<AccIterable> mFirstIter; AccIterable* mLastIter; }; } // namespace a11y } // namespace mozilla #endif
--- a/accessible/ipc/DocAccessibleParent.cpp +++ b/accessible/ipc/DocAccessibleParent.cpp @@ -100,17 +100,18 @@ DocAccessibleParent::AddSubtree(ProxyAcc if (mAccessibles.Contains(newChild.ID())) { NS_ERROR("ID already in use"); return 0; } auto role = static_cast<a11y::role>(newChild.Role()); ProxyAccessible* newProxy = - new ProxyAccessible(newChild.ID(), aParent, this, role); + new ProxyAccessible(newChild.ID(), aParent, this, role, + newChild.Interfaces()); aParent->AddChildAt(aIdxInParent, newProxy); mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy; ProxyCreated(newProxy, newChild.Interfaces()); uint32_t accessibles = 1; uint32_t kids = newChild.ChildrenCount(); for (uint32_t i = 0; i < kids; i++) { uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i);
--- a/accessible/ipc/ProxyAccessible.h +++ b/accessible/ipc/ProxyAccessible.h @@ -19,24 +19,38 @@ namespace mozilla { namespace a11y { class Accessible; class Attribute; class DocAccessibleParent; enum class RelationType; +enum Interfaces +{ + HYPERTEXT = 1, + HYPERLINK = 1 << 1, + IMAGE = 1 << 2, + VALUE = 1 << 3, + TABLE = 1 << 4, + TABLECELL = 1 << 5, + DOCUMENT = 1 << 6, + SELECTION = 1 << 7, + ACTION = 1 << 8, +}; + class ProxyAccessible { public: ProxyAccessible(uint64_t aID, ProxyAccessible* aParent, - DocAccessibleParent* aDoc, role aRole) : + DocAccessibleParent* aDoc, role aRole, uint32_t aInterfaces) : mParent(aParent), mDoc(aDoc), mWrapper(0), mID(aID), mRole(aRole), - mOuterDoc(false), mIsDoc(false) + mOuterDoc(false), mIsDoc(false), + mHasValue(aInterfaces & Interfaces::VALUE) { MOZ_COUNT_CTOR(ProxyAccessible); } ~ProxyAccessible() { MOZ_COUNT_DTOR(ProxyAccessible); MOZ_ASSERT(!mWrapper); } @@ -401,41 +415,31 @@ public: * Return true if this proxy is a DocAccessibleParent. */ bool IsDoc() const { return mIsDoc; } DocAccessibleParent* AsDoc() const { return IsDoc() ? mDoc : nullptr; } protected: explicit ProxyAccessible(DocAccessibleParent* aThisAsDoc) : mParent(nullptr), mDoc(aThisAsDoc), mWrapper(0), mID(0), - mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true) + mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true), mHasValue(false) { MOZ_COUNT_CTOR(ProxyAccessible); } protected: ProxyAccessible* mParent; private: nsTArray<ProxyAccessible*> mChildren; DocAccessibleParent* mDoc; uintptr_t mWrapper; uint64_t mID; - role mRole : 30; + role mRole : 29; bool mOuterDoc : 1; - const bool mIsDoc: 1; -}; -enum Interfaces -{ - HYPERTEXT = 1, - HYPERLINK = 1 << 1, - IMAGE = 1 << 2, - VALUE = 1 << 3, - TABLE = 1 << 4, - TABLECELL = 1 << 5, - DOCUMENT = 1 << 6, - SELECTION = 1 << 7, - ACTION = 1 << 8, +public: + const bool mIsDoc: 1; + const bool mHasValue: 1; }; } } #endif
--- a/accessible/tests/browser/.eslintrc +++ b/accessible/tests/browser/.eslintrc @@ -6,20 +6,23 @@ "globals": { // Content scripts have global 'content' object "content": true, // Defined in accessible/tests/mochitest/ common.js, name.js, states.js "prettyName": true, "statesToString": true, "eventTypeToString": true, + "testAttrs": true, + "testAbsentAttrs": true, "testName": true, "testDescr": true, "testStates": true, "testRelation": true, + "testValue": true, "testAccessibleTree": true, "isAccessible": true, "getAccessibleDOMNodeID": true, // Defined for all accessibility browser tests. "addAccessibleTask": true, "BrowserTestUtils": true, "ContentTask": true,
--- a/accessible/tests/browser/browser.ini +++ b/accessible/tests/browser/browser.ini @@ -9,21 +9,24 @@ support-files = doc_treeupdate_removal.xhtml doc_treeupdate_visibility.html doc_treeupdate_whitespace.html !/accessible/tests/mochitest/*.js !/accessible/tests/mochitest/letters.gif !/accessible/tests/mochitest/moz.png # Caching tests +[browser_caching_attributes.js] [browser_caching_description.js] [browser_caching_name.js] skip-if = e10s [browser_caching_relations.js] [browser_caching_states.js] +[browser_caching_value.js] +skip-if = e10s # Bug 1276721: QueryInterface is not working with proxies. # Events tests [browser_events_caretmove.js] [browser_events_hide.js] [browser_events_show.js] [browser_events_statechange.js] [browser_events_textchange.js]
new file mode 100644 --- /dev/null +++ b/accessible/tests/browser/browser_caching_attributes.js @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +/* global EVENT_FOCUS */ + +loadScripts({ name: 'attributes.js', dir: MOCHITESTS_DIR }); + +/** + * Default textbox accessible attributes. + */ +const defaultAttributes = { + 'margin-top': '0px', + 'margin-right': '0px', + 'margin-bottom': '0px', + 'margin-left': '0px', + 'text-align': 'start', + 'text-indent': '0px', + 'id': 'textbox', + 'tag': 'input', + 'display': 'inline' +}; + +/** + * Test data has the format of: + * { + * desc {String} description for better logging + * expected {Object} expected attributes for given accessibles + * unexpected {Object} unexpected attributes for given accessibles + * + * action {?Function*} an optional action that yields a change in + * attributes + * attrs {?Array} an optional list of attributes to update + * waitFor {?Number} an optional event to wait for + * } + */ +const attributesTests = [{ + desc: 'Initiall accessible attributes', + expected: defaultAttributes, + unexpected: { + 'line-number': '1', + 'explicit-name': 'true', + 'container-live': 'polite', + 'live': 'polite' + } +}, { + desc: '@line-number attribute is present when textbox is focused', + action: function*(browser) { + yield invokeFocus(browser, 'textbox'); + }, + waitFor: EVENT_FOCUS, + expected: Object.assign({}, defaultAttributes, { 'line-number': '1' }), + unexpected: { + 'explicit-name': 'true', + 'container-live': 'polite', + 'live': 'polite' + } +}, { + desc: '@aria-live sets container-live and live attributes', + attrs: [{ + attr: 'aria-live', + value: 'polite' + }], + expected: Object.assign({}, defaultAttributes, { + 'line-number': '1', + 'container-live': 'polite', + 'live': 'polite' + }), + unexpected: { + 'explicit-name': 'true' + } +}, { + desc: '@title attribute sets explicit-name attribute to true', + attrs: [{ + attr: 'title', + value: 'textbox' + }], + expected: Object.assign({}, defaultAttributes, { + 'line-number': '1', + 'explicit-name': 'true', + 'container-live': 'polite', + 'live': 'polite' + }), + unexpected: {} +}]; + +/** + * Test caching of accessible object attributes + */ +addAccessibleTask(` + <input id="textbox" value="hello">`, + function* (browser, accDoc) { + let textbox = findAccessibleChildByID(accDoc, 'textbox'); + for (let { desc, action, attrs, expected, waitFor, unexpected } of attributesTests) { + info(desc); + let onUpdate; + + if (waitFor) { + onUpdate = waitForEvent(waitFor, 'textbox'); + } + + if (action) { + yield action(browser); + } else if (attrs) { + for (let { attr, value } of attrs) { + yield invokeSetAttribute(browser, 'textbox', attr, value); + } + } + + yield onUpdate; + testAttrs(textbox, expected); + testAbsentAttrs(textbox, unexpected); + } + } +);
new file mode 100644 --- /dev/null +++ b/accessible/tests/browser/browser_caching_value.js @@ -0,0 +1,155 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +/* global nsIAccessibleValue, EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE */ + +loadScripts({ name: 'value.js', dir: MOCHITESTS_DIR }); + +/** + * Test data has the format of: + * { + * desc {String} description for better logging + * id {String} given accessible DOMNode ID + * expected {String} expected value for a given accessible + * action {?Function*} an optional action that yields a value change + * attrs {?Array} an optional list of attributes to update + * waitFor {?Number} an optional value change event to wait for + * } + */ +const valueTests = [{ + desc: 'Initially value is set to 1st element of select', + id: 'select', + expected: '1st' +}, { + desc: 'Value should update to 3rd when 3 is pressed', + id: 'select', + action: function*(browser) { + yield invokeFocus(browser, 'select'); + yield BrowserTestUtils.synthesizeKey('3', {}, browser); + }, + waitFor: EVENT_TEXT_VALUE_CHANGE, + expected: '3rd' +}, { + desc: 'Initially value is set to @aria-valuenow for slider', + id: 'slider', + expected: ['5', 5, 0, 7, 0] +}, { + desc: 'Value should change when @aria-valuenow is updated', + id: 'slider', + attrs: [{ + attr: 'aria-valuenow', + value: '6' + }], + waitFor: EVENT_VALUE_CHANGE, + expected: ['6', 6, 0, 7, 0] +}, { + desc: 'Value should change when @aria-valuetext is set', + id: 'slider', + attrs: [{ + attr: 'aria-valuetext', + value: 'plain' + }], + waitFor: EVENT_TEXT_VALUE_CHANGE, + expected: ['plain', 6, 0, 7, 0] +}, { + desc: 'Value should change when @aria-valuetext is updated', + id: 'slider', + attrs: [{ + attr: 'aria-valuetext', + value: 'hey!' + }], + waitFor: EVENT_TEXT_VALUE_CHANGE, + expected: ['hey!', 6, 0, 7, 0] +}, { + desc: 'Value should change to @aria-valuetext when @aria-valuenow is removed', + id: 'slider', + attrs: [{ + attr: 'aria-valuenow' + }], + expected: ['hey!', 0, 0, 7, 0] +}, { + desc: 'Initially value is not set for combobox', + id: 'combobox', + expected: '' +}, { + desc: 'Value should change when @value attribute is updated', + id: 'combobox', + attrs: [{ + attr: 'value', + value: 'hello' + }], + waitFor: EVENT_TEXT_VALUE_CHANGE, + expected: 'hello' +}, { + desc: 'Initially value corresponds to @value attribute for progress', + id: 'progress', + expected: '22%' +}, { + desc: 'Value should change when @value attribute is updated', + id: 'progress', + attrs: [{ + attr: 'value', + value: '50' + }], + waitFor: EVENT_VALUE_CHANGE, + expected: '50%' +}, { + desc: 'Initially value corresponds to @value attribute for range', + id: 'range', + expected: '6' +}, { + desc: 'Value should change when slider is moved', + id: 'range', + action: function*(browser) { + yield invokeFocus(browser, 'range'); + yield BrowserTestUtils.synthesizeKey('VK_LEFT', {}, browser); + }, + waitFor: EVENT_VALUE_CHANGE, + expected: '5' +}]; + +/** + * Test caching of accessible object values + */ +addAccessibleTask(` + <div id="slider" role="slider" aria-valuenow="5" + aria-valuemin="0" aria-valuemax="7">slider</div> + <select id="select"> + <option>1st</option> + <option>2nd</option> + <option>3rd</option> + </select> + <input id="combobox" role="combobox" aria-autocomplete="inline"> + <progress id="progress" value="22" max="100"></progress> + <input type="range" id="range" min="0" max="10" value="6">`, + function* (browser, accDoc) { + for (let { desc, id, action, attrs, expected, waitFor } of valueTests) { + info(desc); + let acc = findAccessibleChildByID(accDoc, id); + let onUpdate; + + if (waitFor) { + onUpdate = waitForEvent(waitFor, id); + } + + if (action) { + yield action(browser); + } else if (attrs) { + for (let { attr, value } of attrs) { + yield invokeSetAttribute(browser, id, attr, value); + } + } + + yield onUpdate; + if (Array.isArray(expected)) { + acc.QueryInterface(nsIAccessibleValue); + testValue(acc, ...expected); + } else { + is(acc.value, expected, `Correct value for ${prettyName(acc)}`); + } + } + } +);
--- a/accessible/tests/browser/events.js +++ b/accessible/tests/browser/events.js @@ -5,28 +5,32 @@ 'use strict'; /* global nsIAccessibleEvent, nsIAccessibleDocument, nsIAccessibleStateChangeEvent, nsIAccessibleTextChangeEvent */ /* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED, EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED, EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE, + EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS, waitForEvent, waitForMultipleEvents */ const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE; const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE; const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER; const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW; const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE; const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED; const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED; const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED; const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE; const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE; +const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE; +const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE; +const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS; /** * Describe an event in string format. * @param {nsIAccessibleEvent} event event to strigify */ function eventToString(event) { let type = eventTypeToString(event.eventType); let info = `Event type: ${type}`;
--- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -286,16 +286,19 @@ function getAccessible(aAccOrElmOrID, aI if (!aInterfaces) return acc; if (!(aInterfaces instanceof Array)) aInterfaces = [ aInterfaces ]; for (var index = 0; index < aInterfaces.length; index++) { + if (acc instanceof aInterfaces[index]) { + continue; + } try { acc.QueryInterface(aInterfaces[index]); } catch (e) { if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE)) ok(false, "Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID); return null; }
--- a/accessible/xpcom/xpcAccessibleDocument.cpp +++ b/accessible/xpcom/xpcAccessibleDocument.cpp @@ -208,17 +208,22 @@ xpcAccessibleDocument::GetXPCAccessible( } xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy); if (acc) { return acc; } // XXX support exposing optional interfaces. - acc = new xpcAccessibleGeneric(aProxy, 0); + uint8_t interfaces = 0; + if (aProxy->mHasValue) { + interfaces |= eValue; + } + + acc = new xpcAccessibleGeneric(aProxy, interfaces); mCache.Put(aProxy, acc); return acc; } void xpcAccessibleDocument::Shutdown() {
--- a/accessible/xpcom/xpcAccessibleGeneric.h +++ b/accessible/xpcom/xpcAccessibleGeneric.h @@ -33,29 +33,18 @@ public: if (aInternal->IsSelect()) mSupportedIfaces |= eSelectable; if (aInternal->HasNumericValue()) mSupportedIfaces |= eValue; if (aInternal->IsLink()) mSupportedIfaces |= eHyperLink; } - xpcAccessibleGeneric(ProxyAccessible* aProxy, uint32_t aInterfaces) : - mIntl(aProxy), mSupportedIfaces(0) - { - if (aInterfaces & Interfaces::SELECTION) { - mSupportedIfaces |= eSelectable; - } - if (aInterfaces & Interfaces::VALUE) { - mSupportedIfaces |= eValue; - } - if (aInterfaces & Interfaces::HYPERLINK) { - mSupportedIfaces |= eHyperLink; - } - } + xpcAccessibleGeneric(ProxyAccessible* aProxy, uint8_t aInterfaces) : + mIntl(aProxy), mSupportedIfaces(aInterfaces) {} NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(xpcAccessibleGeneric, nsIAccessible) // nsIAccessible virtual Accessible* ToInternalAccessible() const final override; // xpcAccessibleGeneric
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -826,41 +826,56 @@ function _loadURIWithFlags(browser, uri, let mustChangeProcess = gMultiProcessBrowser && !E10SUtils.canLoadURIInProcess(uri, process); if ((!wasRemote && !mustChangeProcess) || (wasRemote && mustChangeProcess)) { browser.inLoadURI = true; } try { if (!mustChangeProcess) { + if (params.userContextId) { + browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId }); + } + browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy, postData, null, null); } else { if (postData) { postData = NetUtil.readInputStreamToString(postData, postData.available()); } - LoadInOtherProcess(browser, { + let loadParams = { uri: uri, flags: flags, referrer: referrer ? referrer.spec : null, referrerPolicy: referrerPolicy, - postData: postData, - }); + postData: postData + } + + if (params.userContextId) { + loadParams.userContextId = params.userContextId; + } + + LoadInOtherProcess(browser, loadParams); } } catch (e) { // If anything goes wrong when switching remoteness, just switch remoteness // manually and load the URI. // We might lose history that way but at least the browser loaded a page. // This might be necessary if SessionStore wasn't initialized yet i.e. // when the homepage is a non-remote page. if (mustChangeProcess) { Cu.reportError(e); gBrowser.updateBrowserRemotenessByURL(browser, uri); + + if (params.userContextId) { + browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId }); + } + browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy, postData, null, null); } else { throw e; } } finally { if ((!wasRemote && !mustChangeProcess) || (wasRemote && mustChangeProcess)) { @@ -1121,29 +1136,32 @@ var gBrowserInit = { } catch(e) { Cu.reportError(e); } } // window.arguments[2]: referrer (nsIURI | string) // [3]: postData (nsIInputStream) // [4]: allowThirdPartyFixup (bool) // [5]: referrerPolicy (int) + // [6]: userContextId (int) else if (window.arguments.length >= 3) { let referrerURI = window.arguments[2]; if (typeof(referrerURI) == "string") { try { referrerURI = makeURI(referrerURI); } catch (e) { referrerURI = null; } } let referrerPolicy = (window.arguments[5] != undefined ? window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT); + let userContextId = (window.arguments[6] != undefined ? + window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID); loadURI(uriToLoad, referrerURI, window.arguments[3] || null, - window.arguments[4] || false, referrerPolicy); + window.arguments[4] || false, referrerPolicy, userContextId); window.focus(); } // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3. // Such callers expect that window.arguments[0] is handled as a single URI. else { loadOneOrMoreURIs(uriToLoad); } } @@ -1994,23 +2012,25 @@ function BrowserCloseTabOrWindow() { } function BrowserTryToCloseWindow() { if (WindowIsClosing()) window.close(); // WindowIsClosing does all the necessary checks } -function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy) { +function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy, + userContextId) { try { openLinkIn(uri, "current", { referrerURI: referrer, referrerPolicy: referrerPolicy, postData: postData, - allowThirdPartyFixup: allowThirdPartyFixup }); + allowThirdPartyFixup: allowThirdPartyFixup, + userContextId: userContextId }); } catch (e) {} } /** * Given a urlbar value, discerns between URIs, keywords and aliases. * * @param url * The urlbar value.
--- a/browser/base/content/tab-content.js +++ b/browser/base/content/tab-content.js @@ -882,17 +882,22 @@ var UserContextIdNotifier = { }, handleEvent(aEvent) { // When the window is created, we want to inform the tabbrowser about // the userContextId in use in order to update the UI correctly. // Just because we cannot change the userContextId from an active docShell, // we don't need to check DOMContentLoaded again. this.uninit(); - let userContextId = content.document.nodePrincipal.originAttributes.userContextId; + + // We use the docShell because content.document can have been loaded before + // setting the originAttributes. + let loadContext = docShell.QueryInterface(Ci.nsILoadContext); + let userContextId = loadContext.originAttributes.userContextId; + sendAsyncMessage("Browser:WindowCreated", { userContextId }); } }; UserContextIdNotifier.init(); ExtensionContent.init(this); addEventListener("unload", () => {
--- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -209,17 +209,17 @@ skip-if = buildapp == 'mulet' || e10s # [browser_bug537474.js] [browser_bug550565.js] [browser_bug553455.js] skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works on mulet? [browser_bug555224.js] [browser_bug555767.js] [browser_bug559991.js] [browser_bug561636.js] -skip-if = os == 'win' # bug 1057615 +skip-if = true # bug 1057615 [browser_bug563588.js] [browser_bug565575.js] [browser_bug565667.js] skip-if = toolkit != "cocoa" [browser_bug567306.js] subsuite = clipboard [browser_bug575561.js] [browser_bug575830.js] @@ -232,17 +232,17 @@ subsuite = clipboard [browser_bug581253.js] [browser_bug585558.js] [browser_bug585785.js] [browser_bug585830.js] [browser_bug590206.js] [browser_bug592338.js] [browser_bug594131.js] [browser_bug595507.js] -skip-if = os == 'win' # bug 1057615 +skip-if = true # bug 1057615 [browser_bug596687.js] [browser_bug597218.js] [browser_bug609700.js] [browser_bug623893.js] [browser_bug624734.js] [browser_bug633691.js] [browser_bug647886.js] skip-if = buildapp == 'mulet'
--- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -269,22 +269,27 @@ function openLinkIn(url, where, params) createInstance(Ci.nsISupportsString); referrerURISupports.data = aReferrerURI.spec; } var referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"]. createInstance(Ci.nsISupportsPRUint32); referrerPolicySupports.data = aReferrerPolicy; + var userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"]. + createInstance(Ci.nsISupportsPRUint32); + userContextIdSupports.data = aUserContextId; + sa.AppendElement(wuri); sa.AppendElement(charset); sa.AppendElement(referrerURISupports); sa.AppendElement(aPostData); sa.AppendElement(allowThirdPartyFixupSupports); sa.AppendElement(referrerPolicySupports); + sa.AppendElement(userContextIdSupports); let features = "chrome,dialog=no,all"; if (aIsPrivate) { features += ",private"; } Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa); return; @@ -347,16 +352,17 @@ function openLinkIn(url, where, params) flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV; } w.gBrowser.loadURIWithFlags(url, { flags: flags, referrerURI: aNoReferrer ? null : aReferrerURI, referrerPolicy: aReferrerPolicy, postData: aPostData, + userContextId: aUserContextId }); break; case "tabshifted": loadInBackground = !loadInBackground; // fall through case "tab": w.gBrowser.loadOneTab(url, { referrerURI: aReferrerURI,
--- a/browser/components/feeds/FeedConverter.js +++ b/browser/components/feeds/FeedConverter.js @@ -507,26 +507,23 @@ GenericProtocolHandler.prototype = { // case we create a nested URI for the realURI) or feed://example.com, in // which case we create a nested URI for the real protocol which is http. let scheme = this._scheme + ":"; if (spec.substr(0, scheme.length) != scheme) throw Cr.NS_ERROR_MALFORMED_URI; let prefix = spec.substr(scheme.length, 2) == "//" ? "http:" : ""; - let inner = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService).newURI(spec.replace(scheme, prefix), - originalCharset, baseURI); - let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil); - const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler - .URI_INHERITS_SECURITY_CONTEXT; - if (netutil.URIChainHasFlags(inner, URI_INHERITS_SECURITY_CONTEXT)) + let inner = Services.io.newURI(spec.replace(scheme, prefix), + originalCharset, baseURI); + + if (!["http", "https"].includes(inner.scheme)) throw Cr.NS_ERROR_MALFORMED_URI; - let uri = netutil.newSimpleNestedURI(inner); + let uri = Services.io.QueryInterface(Ci.nsINetUtil).newSimpleNestedURI(inner); uri.spec = inner.spec.replace(prefix, scheme); return uri; }, newChannel2(aUri, aLoadInfo) { let inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI; let channel = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService).
--- a/browser/components/feeds/test/unit/test_355473.js +++ b/browser/components/feeds/test/unit/test_355473.js @@ -30,12 +30,14 @@ function run_test() { do_check_true(httpFeedURI.equals(httpChannel.originalURI)); do_check_true(httpsFeedURI.equals(httpsChannel.originalURI)); // actually using the horrible mess that's a feed: URI is suicidal do_check_true(httpURI.equals(feedChannel.URI)); do_check_true(httpURI.equals(httpChannel.URI)); do_check_true(httpsURI.equals(httpsChannel.URI)); - // check that we don't throw creating feed: URIs from file and ftp - var ftpFeedURI = ios.newURI("feed:ftp://example.com/feed.xml", null, null); - var fileFeedURI = ios.newURI("feed:file:///var/feed.xml", null, null); + // check that we throw creating feed: URIs from file and ftp + Assert.throws(function() { ios.newURI("feed:ftp://example.com/feed.xml", null, null); }, + "Should throw an exception when trying to create a feed: URI with an ftp: inner"); + Assert.throws(function() { ios.newURI("feed:file:///var/feed.xml", null, null); }, + "Should throw an exception when trying to create a feed: URI with a file: inner"); }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js @@ -9,37 +9,29 @@ // Step 1: create new tab, load a page that sets test=value in non-private storage // Step 2: create a new tab, load a page that sets test2=value2 in private storage // Step 3: load a page in the tab from step 1 that checks the value of test2 is value2 and the total count in non-private storage is 1 // Step 4: load a page in the tab from step 2 that checks the value of test is value and the total count in private storage is 1 add_task(function test() { let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html'; - function setUsePrivateBrowsing(browser, val) { - return ContentTask.spawn(browser, val, function* (val) { - docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = val; - }); - }; - function getElts(browser) { return browser.contentTitle.split('|'); }; // Step 1 - gBrowser.selectedTab = gBrowser.addTab(prefix + '?action=set&name=test&value=value&initial=true'); let non_private_browser = gBrowser.selectedBrowser; + non_private_browser.loadURI(prefix + '?action=set&name=test&value=value&initial=true'); yield BrowserTestUtils.browserLoaded(non_private_browser); // Step 2 - gBrowser.selectedTab = gBrowser.addTab(); - let private_browser = gBrowser.selectedBrowser; - yield BrowserTestUtils.browserLoaded(private_browser); - yield setUsePrivateBrowsing(private_browser, true); + let private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : true }); + let private_browser = private_window.getBrowser().selectedBrowser; private_browser.loadURI(prefix + '?action=set&name=test2&value=value2'); yield BrowserTestUtils.browserLoaded(private_browser); // Step 3 non_private_browser.loadURI(prefix + '?action=get&name=test2'); yield BrowserTestUtils.browserLoaded(non_private_browser); let elts = yield getElts(non_private_browser); @@ -50,35 +42,41 @@ add_task(function test() { // Step 4 private_browser.loadURI(prefix + '?action=get&name=test'); yield BrowserTestUtils.browserLoaded(private_browser); elts = yield getElts(private_browser); isnot(elts[0], 'value', "private window shouldn't see public storage"); is(elts[1], '1', "private window should only see private items"); - // Make the private tab public again, which should clear the + // Reopen the private window again, without privateBrowsing, which should clear the // the private storage. - yield setUsePrivateBrowsing(private_browser, false); + private_window.close(); + private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : false }); + private_browser = null; yield new Promise(resolve => Cu.schedulePreciseGC(resolve)); + private_browser = private_window.getBrowser().selectedBrowser; private_browser.loadURI(prefix + '?action=get&name=test2'); yield BrowserTestUtils.browserLoaded(private_browser); elts = yield getElts(private_browser); isnot(elts[0], 'value2', "public window shouldn't see cleared private storage"); is(elts[1], '1', "public window should only see public items"); // Making it private again should clear the storage and it shouldn't // be able to see the old private storage as well. - yield setUsePrivateBrowsing(private_browser, true); + private_window.close(); + private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : true }); + private_browser = null; + yield new Promise(resolve => Cu.schedulePreciseGC(resolve)); + private_browser = private_window.getBrowser().selectedBrowser; private_browser.loadURI(prefix + '?action=set&name=test3&value=value3'); - BrowserTestUtils.browserLoaded(private_browser); + yield BrowserTestUtils.browserLoaded(private_browser); elts = yield getElts(private_browser); is(elts[1], '1', "private window should only see new private items"); // Cleanup. non_private_browser.loadURI(prefix + '?final=true'); yield BrowserTestUtils.browserLoaded(non_private_browser); - gBrowser.removeCurrentTab(); - gBrowser.removeCurrentTab(); + private_window.close(); });
--- a/browser/components/sessionstore/ContentRestore.jsm +++ b/browser/components/sessionstore/ContentRestore.jsm @@ -199,16 +199,21 @@ ContentRestoreInternal.prototype = { // same state it was before the load started then trigger the load. let referrer = loadArguments.referrer ? Utils.makeURI(loadArguments.referrer) : null; let referrerPolicy = ('referrerPolicy' in loadArguments ? loadArguments.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT); let postData = loadArguments.postData ? Utils.makeInputStream(loadArguments.postData) : null; + + if (loadArguments.userContextId) { + webNavigation.setOriginAttributesBeforeLoading({ userContextId: loadArguments.userContextId }); + } + webNavigation.loadURIWithOptions(loadArguments.uri, loadArguments.flags, referrer, referrerPolicy, postData, null, null); } else if (tabData.userTypedValue && tabData.userTypedClear) { // If the user typed a URL into the URL bar and hit enter right before // we crashed, we want to start loading that page again. A non-zero // userTypedClear value means that the load had started. // Load userTypedValue and fix up the URL if it's partial/broken.
--- a/browser/themes/shared/devedition.inc.css +++ b/browser/themes/shared/devedition.inc.css @@ -147,17 +147,17 @@ #tabbrowser-tabs, #TabsToolbar, #browser-panel { background: var(--chrome-background-color); color: var(--chrome-color); } -#navigator-toolbox::after { +#navigator-toolbox:-moz-lwtheme::after { background: var(--chrome-navigator-toolbox-separator-color); } #navigator-toolbox > toolbar:not(#TabsToolbar):not(#toolbar-menubar), .browserContainer > findbar, #browser-bottombox { background-color: var(--chrome-secondary-background-color) !important; background-image: none !important;
--- a/build/autoconf/compiler-opts.m4 +++ b/build/autoconf/compiler-opts.m4 @@ -440,32 +440,34 @@ AC_DEFUN([MOZ_SET_WARNINGS_CFLAGS], _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wpointer-arith" _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wsign-compare" _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wtype-limits" _WARNINGS_CFLAGS="${_WARNINGS_CFLAGS} -Wunreachable-code" # -Wclass-varargs - catches objects passed by value to variadic functions. # -Wloop-analysis - catches issues around loops # -Wnon-literal-null-conversion - catches expressions used as a null pointer constant + # -Wstring-conversion - catches string literals used in boolean expressions # -Wthread-safety - catches inconsistent use of mutexes # # XXX: at the time of writing, the version of clang used on the OS X test # machines has a bug that causes it to reject some valid files if both # -Wnon-literal-null-conversion and -Wsometimes-uninitialized are # specified. We work around this by instead using # -Werror=non-literal-null-conversion, but we only do that when # --enable-warnings-as-errors is specified so that no unexpected fatal # warnings are produced. MOZ_C_SUPPORTS_WARNING(-W, class-varargs, ac_c_has_wclass_varargs) MOZ_C_SUPPORTS_WARNING(-W, loop-analysis, ac_c_has_wloop_analysis) if test "$MOZ_ENABLE_WARNINGS_AS_ERRORS"; then MOZ_C_SUPPORTS_WARNING(-Werror=, non-literal-null-conversion, ac_c_has_non_literal_null_conversion) fi + MOZ_C_SUPPORTS_WARNING(-W, string-conversion, ac_c_has_wstring_conversion) MOZ_C_SUPPORTS_WARNING(-W, thread-safety, ac_c_has_wthread_safety) # Turn off some non-useful warnings that -Wall turns on. # Prevent the following GCC warnings from being treated as errors: # -Wmaybe-uninitialized - too many false positives # -Wdeprecated-declarations - we don't want our builds held hostage when a # platform-specific API becomes deprecated. @@ -506,16 +508,17 @@ AC_DEFUN([MOZ_SET_WARNINGS_CXXFLAGS], _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wtype-limits" _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wunreachable-code" _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wwrite-strings" # -Wclass-varargs - catches objects passed by value to variadic functions. # -Wimplicit-fallthrough - catches unintentional switch case fallthroughs # -Wloop-analysis - catches issues around loops # -Wnon-literal-null-conversion - catches expressions used as a null pointer constant + # -Wstring-conversion - catches string literals used in boolean expressions # -Wthread-safety - catches inconsistent use of mutexes # # XXX: at the time of writing, the version of clang used on the OS X test # machines has a bug that causes it to reject some valid files if both # -Wnon-literal-null-conversion and -Wsometimes-uninitialized are # specified. We work around this by instead using # -Werror=non-literal-null-conversion, but we only do that when # --enable-warnings-as-errors is specified so that no unexpected fatal @@ -527,16 +530,17 @@ AC_DEFUN([MOZ_SET_WARNINGS_CXXFLAGS], MOZ_CXX_SUPPORTS_WARNING(-W, class-varargs, ac_cxx_has_wclass_varargs) MOZ_CXX_SUPPORTS_WARNING(-W, implicit-fallthrough, ac_cxx_has_wimplicit_fallthrough) MOZ_CXX_SUPPORTS_WARNING(-W, loop-analysis, ac_cxx_has_wloop_analysis) if test "$MOZ_ENABLE_WARNINGS_AS_ERRORS"; then MOZ_CXX_SUPPORTS_WARNING(-Werror=, non-literal-null-conversion, ac_cxx_has_non_literal_null_conversion) fi + MOZ_CXX_SUPPORTS_WARNING(-W, string-conversion, ac_cxx_has_wstring_conversion) MOZ_CXX_SUPPORTS_WARNING(-W, thread-safety, ac_cxx_has_wthread_safety) # Turn off some non-useful warnings that -Wall turns on. # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wno-invalid-offsetof" # -Wno-inline-new-delete - we inline 'new' and 'delete' in mozalloc
--- a/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js +++ b/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js @@ -5,47 +5,39 @@ * Tests that the destruction node event is fired and that the nodes are no * longer stored internally in the tool, that the graph is updated properly, and * that selecting a soon-to-be dead node clears the inspector. * * All done in one test since this test takes a few seconds to clear GC. */ add_task(function* () { - // Use a longer timeout as garbage collection event - // can be unpredictable. - requestLongerTimeout(2); - let { target, panel } = yield initWebAudioEditor(DESTROY_NODES_URL); let { panelWin } = panel; let { gFront, $, $$, gAudioNodes } = panelWin; let started = once(gFront, "start-context"); reload(target); let destroyed = getN(gAudioNodes, "remove", 10); - forceCC(); - let [created] = yield Promise.all([ getNSpread(gAudioNodes, "add", 13), waitForGraphRendered(panelWin, 13, 2) ]); // Flatten arrays of event arguments and take the first (AudioNodeModel) // and get its ID. let actorIDs = created.map(ev => ev[0].id); // Click a soon-to-be dead buffer node yield clickGraphNode(panelWin, actorIDs[5]); - // Wait for a tick before gc to prevent this test from intermittent timeout - // where the node never get collected. - yield DevToolsUtils.waitForTick(); + // Force a CC in the child process to collect the orphaned nodes. forceCC(); // Wait for destruction and graph to re-render yield Promise.all([destroyed, waitForGraphRendered(panelWin, 3, 2)]); // Test internal storage is(panelWin.gAudioNodes.length, 3, "All nodes should be GC'd except one gain, osc and dest node.");
--- a/devtools/client/webaudioeditor/test/head.js +++ b/devtools/client/webaudioeditor/test/head.js @@ -416,19 +416,22 @@ function countGraphObjects(win) { edges: win.document.querySelectorAll(".edgePaths > .edgePath").length }; } /** * Forces cycle collection and GC, used in AudioNode destruction tests. */ function forceCC() { - SpecialPowers.DOMWindowUtils.cycleCollect(); - SpecialPowers.DOMWindowUtils.garbageCollect(); - SpecialPowers.DOMWindowUtils.garbageCollect(); + ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() { + Cu.forceGC(); + Cu.forceCC(); + Cu.forceGC(); + Cu.forceCC(); + }); } /** * Takes a `values` array of automation value entries, * looking for the value at `time` seconds, checking * to see if the value is close to `expected`. */ function checkAutomationValue(values, time, expected) {
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -9,16 +9,17 @@ #include <algorithm> #include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" #include "mozilla/AutoRestore.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Casting.h" #include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ChromeUtils.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/TabChild.h" #include "mozilla/dom/ProfileTimelineMarkerBinding.h" #include "mozilla/dom/ScreenOrientation.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/workers/ServiceWorkerManager.h" #include "mozilla/EventStateManager.h" @@ -3659,17 +3660,17 @@ nsDocShell::FindItemWithName(const char1 if (foundItem) { foundItem.swap(*aResult); } return NS_OK; } } void -nsDocShell::AssertOriginAttributesMatchPrivateBrowsing(){ +nsDocShell::AssertOriginAttributesMatchPrivateBrowsing() { MOZ_ASSERT((mOriginAttributes.mPrivateBrowsingId != 0) == mInPrivateBrowsing); } nsresult nsDocShell::DoFindItemWithName(const char16_t* aName, nsISupports* aRequestor, nsIDocShellTreeItem* aOriginalRequestor, nsIDocShellTreeItem** aResult) @@ -7930,16 +7931,23 @@ nsDocShell::CreateAboutBlankContentViewe } AutoRestore<bool> creatingDocument(mCreatingDocument); mCreatingDocument = true; // mContentViewer->PermitUnload may release |this| docshell. nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); + if (aPrincipal && !nsContentUtils::IsSystemPrincipal(aPrincipal) && + mItemType != typeChrome) { + MOZ_ASSERT(ChromeUtils::IsOriginAttributesEqualIgnoringAddonId( + BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(), + mOriginAttributes)); + } + // Make sure timing is created. But first record whether we had it // already, so we don't clobber the timing for an in-progress load. bool hadTiming = mTiming; MaybeInitTiming(); if (mContentViewer) { // We've got a content viewer already. Make sure the user // permits us to discard the current document and replace it // with about:blank. And also ensure we fire the unload events @@ -10747,17 +10755,20 @@ nsDocShell::DoURILoad(nsIURI* aURI, new LoadInfo(loadingPrincipal, triggeringPrincipal, loadingNode, securityFlags, aContentPolicyType); // We have to do this in case our OriginAttributes are different from the // OriginAttributes of the parent document. Or in case there isn't a // parent document. NeckoOriginAttributes neckoAttrs; neckoAttrs.InheritFromDocShellToNecko(GetOriginAttributes()); - loadInfo->SetOriginAttributes(neckoAttrs); + rv = loadInfo->SetOriginAttributes(neckoAttrs); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } if (!isSrcdoc) { rv = NS_NewChannelInternal(getter_AddRefs(channel), aURI, loadInfo, nullptr, // loadGroup static_cast<nsIInterfaceRequestor*>(this), loadFlags); @@ -14121,51 +14132,79 @@ NS_IMETHODIMP nsDocShell::GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) { bool ok = ToJSValue(aCx, mOriginAttributes, aVal); NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); return NS_OK; } -void +nsresult nsDocShell::SetOriginAttributes(const DocShellOriginAttributes& aAttrs) { - MOZ_ASSERT(mChildList.Length() == 0); + MOZ_ASSERT(mChildList.IsEmpty()); + if (!mChildList.IsEmpty()) { + return NS_ERROR_FAILURE; + } // TODO: Bug 1273058 - mContentViewer should be null when setting origin // attributes. if (mContentViewer) { nsIDocument* doc = mContentViewer->GetDocument(); if (doc) { nsIURI* uri = doc->GetDocumentURI(); - MOZ_ASSERT(uri); - if (uri) { - nsAutoCString uriSpec; - uri->GetSpec(uriSpec); - MOZ_ASSERT(uriSpec.EqualsLiteral("about:blank")); + if (!uri) { + return NS_ERROR_FAILURE; + } + nsAutoCString uriSpec; + uri->GetSpec(uriSpec); + MOZ_ASSERT(uriSpec.EqualsLiteral("about:blank")); + if (!uriSpec.EqualsLiteral("about:blank")) { + return NS_ERROR_FAILURE; } } } mOriginAttributes = aAttrs; SetPrivateBrowsing(mOriginAttributes.mPrivateBrowsingId > 0); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes) +{ + if (!aOriginAttributes.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + AutoJSAPI jsapi; + jsapi.Init(&aOriginAttributes.toObject()); + JSContext* cx = jsapi.cx(); + if (NS_WARN_IF(!cx)) { + return NS_ERROR_FAILURE; + } + + DocShellOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(cx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return SetOriginAttributes(attrs); } NS_IMETHODIMP nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx) { DocShellOriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } - SetOriginAttributes(attrs); - return NS_OK; + return SetOriginAttributes(attrs); } NS_IMETHODIMP nsDocShell::GetAppManifestURL(nsAString& aAppManifestURL) { uint32_t appId = nsIDocShell::GetAppId(); if (appId != nsIScriptSecurityManager::NO_APP_ID && appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
--- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -272,17 +272,17 @@ public: bool InFrameSwap(); const mozilla::DocShellOriginAttributes& GetOriginAttributes() { return mOriginAttributes; } - void SetOriginAttributes(const mozilla::DocShellOriginAttributes& aAttrs); + nsresult SetOriginAttributes(const mozilla::DocShellOriginAttributes& aAttrs); void GetInterceptedDocumentId(nsAString& aId) { aId = mInterceptedDocumentId; } private: // An observed docshell wrapper is created when recording markers is enabled.
--- a/docshell/base/nsIWebNavigation.idl +++ b/docshell/base/nsIWebNavigation.idl @@ -350,9 +350,15 @@ interface nsIWebNavigation : nsISupports * The referring URI for the currently loaded URI or null. */ readonly attribute nsIURI referringURI; /** * The session history object used by this web navigation instance. */ attribute nsISHistory sessionHistory; + + /** + * Set an OriginAttributes dictionary in the docShell. This can be done only + * before loading any content. + */ + void setOriginAttributesBeforeLoading(in jsval originAttributes); };
--- a/docshell/shistory/nsSHistory.cpp +++ b/docshell/shistory/nsSHistory.cpp @@ -1492,16 +1492,22 @@ nsSHistory::LoadURIWithOptions(const cha nsIInputStream* aPostStream, nsIInputStream* aExtraHeaderStream, nsIURI* aBaseURI) { return NS_OK; } NS_IMETHODIMP +nsSHistory::SetOriginAttributesBeforeLoading(JS::HandleValue aOriginAttributes) +{ + return NS_OK; +} + +NS_IMETHODIMP nsSHistory::LoadURI(const char16_t* aURI, uint32_t aLoadFlags, nsIURI* aReferringURI, nsIInputStream* aPostStream, nsIInputStream* aExtraHeaderStream) { return NS_OK; }
--- a/dom/base/BodyUtil.cpp +++ b/dom/base/BodyUtil.cpp @@ -545,17 +545,16 @@ BodyUtil::ConsumeText(uint32_t aInputLen // static void BodyUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue, const nsString& aStr, ErrorResult& aRv) { aRv.MightThrowJSException(); - AutoForceSetExceptionOnContext forceExn(aCx); JS::Rooted<JS::Value> json(aCx); if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) { if (!JS_IsExceptionPending(aCx)) { aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR); return; } JS::Rooted<JS::Value> exn(aCx);
--- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -178,10 +178,21 @@ ChromeUtils::IsOriginAttributesEqual(dom return aA.mAddonId == aB.mAddonId && aA.mAppId == aB.mAppId && aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser && aA.mSignedPkg == aB.mSignedPkg && aA.mUserContextId == aB.mUserContextId && aA.mPrivateBrowsingId == aB.mPrivateBrowsingId; } +/* static */ bool +ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA, + const dom::OriginAttributesDictionary& aB) +{ + return aA.mAppId == aB.mAppId && + aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser && + aA.mSignedPkg == aB.mSignedPkg && + aA.mUserContextId == aB.mUserContextId && + aA.mPrivateBrowsingId == aB.mPrivateBrowsingId; +} + } // namespace dom } // namespace mozilla
--- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -82,14 +82,18 @@ public: FillNonDefaultOriginAttributes(dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs, dom::OriginAttributesDictionary& aNewAttrs); static bool IsOriginAttributesEqual(dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aA, const dom::OriginAttributesDictionary& aB); + + static bool + IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA, + const dom::OriginAttributesDictionary& aB); }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_ChromeUtils__
--- a/dom/base/DOMParser.cpp +++ b/dom/base/DOMParser.cpp @@ -5,16 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/DOMParser.h" #include "nsIDOMDocument.h" #include "nsNetUtil.h" #include "nsIStreamListener.h" #include "nsStringStream.h" +#include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsCRT.h" #include "nsStreamUtils.h" #include "nsContentUtils.h" #include "nsDOMJSUtils.h" #include "nsError.h" #include "nsPIDOMWindow.h" #include "nsNullPrincipal.h" @@ -316,34 +317,44 @@ DOMParser::ParseFromStream(nsIInputStrea } NS_IMETHODIMP DOMParser::Init(nsIPrincipal* principal, nsIURI* documentURI, nsIURI* baseURI, nsIGlobalObject* aScriptObject) { NS_ENSURE_STATE(!mAttemptedInit); mAttemptedInit = true; - NS_ENSURE_ARG(principal || documentURI); - mDocumentURI = documentURI; if (!mDocumentURI) { principal->GetURI(getter_AddRefs(mDocumentURI)); // If we have the system principal, then we'll just use the null principals // uri. if (!mDocumentURI && !nsContentUtils::IsSystemPrincipal(principal)) { return NS_ERROR_INVALID_ARG; } } mScriptHandlingObject = do_GetWeakReference(aScriptObject); mPrincipal = principal; nsresult rv; if (!mPrincipal) { + // BUG 1237080 -- in this case we're getting a chrome privilege scripted + // DOMParser object creation without an explicit principal set. This is + // now deprecated. + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DOM"), + nullptr, + nsContentUtils::eDOM_PROPERTIES, + "ChromeScriptedDOMParserWithoutPrincipal", + nullptr, + 0, + documentURI); + PrincipalOriginAttributes attrs; mPrincipal = BasePrincipal::CreateCodebasePrincipal(mDocumentURI, attrs); NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE); mOriginalPrincipal = mPrincipal; } else { mOriginalPrincipal = mPrincipal; if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { // Don't give DOMParsers the system principal. Use a null
--- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -32,16 +32,17 @@ #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "BatteryManager.h" #include "mozilla/dom/DeviceStorageAreaListener.h" #include "mozilla/dom/PowerManager.h" #include "mozilla/dom/WakeLock.h" #include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/CellBroadcast.h" +#include "mozilla/dom/FlyWebPublishedServer.h" #include "mozilla/dom/FlyWebService.h" #include "mozilla/dom/IccManager.h" #include "mozilla/dom/InputPortManager.h" #include "mozilla/dom/MobileMessageManager.h" #include "mozilla/dom/Permissions.h" #include "mozilla/dom/Presentation.h" #include "mozilla/dom/ServiceWorkerContainer.h" #include "mozilla/dom/TCPSocket.h" @@ -1609,17 +1610,38 @@ Navigator::PublishServer(const nsAString ErrorResult& aRv) { RefPtr<FlyWebService> service = FlyWebService::GetOrCreate(); if (!service) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } - return service->PublishServer(aName, aOptions, mWindow, aRv); + RefPtr<FlyWebPublishPromise> mozPromise = + service->PublishServer(aName, aOptions, mWindow); + MOZ_ASSERT(mozPromise); + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow); + ErrorResult result; + RefPtr<Promise> domPromise = Promise::Create(global, result); + if (result.Failed()) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + mozPromise->Then(AbstractThread::MainThread(), + __func__, + [domPromise] (FlyWebPublishedServer* aServer) { + domPromise->MaybeResolve(aServer); + }, + [domPromise] (nsresult aStatus) { + domPromise->MaybeReject(aStatus); + }); + + return domPromise.forget(); } already_AddRefed<Promise> Navigator::GetFeature(const nsAString& aName, ErrorResult& aRv) { nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow); RefPtr<Promise> p = Promise::Create(go, aRv); if (aRv.Failed()) {
--- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -291,41 +291,33 @@ GetWebIDLCallerPrincipal() return nullptr; } return aes->mWebIDLCallerPrincipal; } AutoJSAPI::AutoJSAPI() : mCx(nullptr) - , mOldAutoJSAPIOwnsErrorReporting(false) , mIsMainThread(false) // For lack of anything better { } AutoJSAPI::~AutoJSAPI() { if (!mCx) { // No need to do anything here: we never managed to Init, so can't have an // exception on our (nonexistent) JSContext. We also don't need to restore // any state on it. return; } ReportException(); - // We need to do this _after_ processing the existing exception, because the - // JS engine can throw while doing that, and uses this bit to determine what - // to do in that case: squelch the exception if the bit is set, otherwise - // call the error reporter. Calling WarningOnlyErrorReporter with a - // non-warning will assert, so we need to make sure we do the former. - JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting); - - if (mOldErrorReporter.isSome()) { - JS_SetErrorReporter(JS_GetRuntime(cx()), mOldErrorReporter.value()); + if (mOldWarningReporter.isSome()) { + JS::SetWarningReporter(JS_GetRuntime(cx()), mOldWarningReporter.value()); } } void WarningOnlyErrorReporter(JSContext* aCx, const char* aMessage, JSErrorReport* aRep); void @@ -351,21 +343,19 @@ AutoJSAPI::InitInternal(nsIGlobalObject* JS::Rooted<JSObject*> global(JS_GetRuntime(aCx), aGlobal); mCxPusher.emplace(mCx); mAutoNullableCompartment.emplace(mCx, global); } else { mAutoNullableCompartment.emplace(mCx, aGlobal); } JSRuntime* rt = JS_GetRuntime(aCx); - mOldErrorReporter.emplace(JS_GetErrorReporter(rt)); + mOldWarningReporter.emplace(JS::GetWarningReporter(rt)); - mOldAutoJSAPIOwnsErrorReporting = JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(); - JS::ContextOptionsRef(aCx).setAutoJSAPIOwnsErrorReporting(true); - JS_SetErrorReporter(rt, WarningOnlyErrorReporter); + JS::SetWarningReporter(rt, WarningOnlyErrorReporter); #ifdef DEBUG if (haveException) { JS::Rooted<JS::Value> exn(aCx); JS_GetPendingException(aCx, &exn); JS_ClearPendingException(aCx); if (exn.isObject()) { @@ -427,18 +417,17 @@ AutoJSAPI::InitInternal(nsIGlobalObject* MOZ_ASSERT(false, "We had an exception; we should not have"); } #endif // DEBUG } AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, JSContext* aCx) - : mOldAutoJSAPIOwnsErrorReporting(false) - , mIsMainThread(aIsMainThread) + : mIsMainThread(aIsMainThread) { MOZ_ASSERT(aGlobalObject); MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global"); MOZ_ASSERT(aCx); MOZ_ASSERT(aIsMainThread == NS_IsMainThread()); InitInternal(aGlobalObject, aGlobalObject->GetGlobalJSObject(), aCx, aIsMainThread);
--- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -306,21 +306,19 @@ private: // while we're being used. This _could_ become a JS::Rooted<JSObject*> if we // grabbed our JSContext in our constructor instead of waiting for Init(), so // we could construct this at that point. It might be worth it do to that. RefPtr<nsIGlobalObject> mGlobalObject; mozilla::Maybe<danger::AutoCxPusher> mCxPusher; mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment; JSContext *mCx; - // Track state between the old and new error reporting modes. - bool mOldAutoJSAPIOwnsErrorReporting; // Whether we're mainthread or not; set when we're initialized. bool mIsMainThread; - Maybe<JSErrorReporter> mOldErrorReporter; + Maybe<JS::WarningReporter> mOldWarningReporter; void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal, JSContext* aCx, bool aIsMainThread); AutoJSAPI(const AutoJSAPI&) = delete; AutoJSAPI& operator= (const AutoJSAPI&) = delete; };
--- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -2602,21 +2602,21 @@ WebSocketImpl::GetStatus(nsresult* aStat AssertIsOnTargetThread(); *aStatus = NS_OK; return NS_OK; } namespace { -class CancelRunnable final : public WorkerRunnable +class CancelRunnable final : public MainThreadWorkerRunnable { public: CancelRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl) - : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) + : MainThreadWorkerRunnable(aWorkerPrivate) , mImpl(aImpl) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { aWorkerPrivate->AssertIsOnWorkerThread(); aWorkerPrivate->ModifyBusyCountFromWorker(true); @@ -2624,33 +2624,16 @@ public: } void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) override { aWorkerPrivate->ModifyBusyCountFromWorker(false); } - bool - PreDispatch(WorkerPrivate* aWorkerPrivate) override - { - // We don't call WorkerRunnable::PreDispatch because it would assert the - // wrong thing about which thread we're on. - AssertIsOnMainThread(); - return true; - } - - void - PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override - { - // We don't call WorkerRunnable::PostDispatch because it would assert the - // wrong thing about which thread we're on. - AssertIsOnMainThread(); - } - private: RefPtr<WebSocketImpl> mImpl; }; } // namespace // Window closed, stop/reload button pressed, user navigated away from page, etc. NS_IMETHODIMP
--- a/dom/base/nsCopySupport.cpp +++ b/dom/base/nsCopySupport.cpp @@ -686,18 +686,18 @@ nsCopySupport::FireClipboardEvent(EventM const bool chromeShell = docShell && docShell->ItemType() == nsIDocShellTreeItem::typeChrome; // next, fire the cut, copy or paste event bool doDefault = true; RefPtr<DataTransfer> clipboardData; if (chromeShell || Preferences::GetBool("dom.event.clipboardevents.enabled", true)) { clipboardData = - new DataTransfer(piWindow, aEventMessage, aEventMessage == ePaste, - aClipboardType); + new DataTransfer(doc->GetScopeObject(), aEventMessage, + aEventMessage == ePaste, aClipboardType); nsEventStatus status = nsEventStatus_eIgnore; InternalClipboardEvent evt(true, aEventMessage); evt.mClipboardData = clipboardData; EventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt, nullptr, &status); // If the event was cancelled, don't do the clipboard operation doDefault = (status != nsEventStatus_eConsumeNoDefault);
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -302,16 +302,26 @@ int32_t gTimeoutCnt // The default shortest interval/timeout we permit #define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms #define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms static int32_t gMinTimeoutValue; static int32_t gMinBackgroundTimeoutValue; inline int32_t nsGlobalWindow::DOMMinTimeoutValue() const { bool isBackground = !mOuterWindow || mOuterWindow->IsBackground(); + if (isBackground) { + // Don't use the background timeout value when there are audio contexts with + // active nodes, so that background audio can keep running smoothly. + for (const AudioContext* ctx : mAudioContexts) { + if (ctx->ActiveNodeCount() > 0) { + isBackground = false; + break; + } + } + } return std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, 0); } // The number of nested timeouts before we start clamping. HTML5 says 1, WebKit // uses 5. #define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
--- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -144,18 +144,16 @@ nsJSUtils::EvaluateString(JSContext* aCx JS::CompileOptions& aCompileOptions, const EvaluateOptions& aEvaluateOptions, JS::MutableHandle<JS::Value> aRetValue, void **aOffThreadToken) { PROFILER_LABEL("nsJSUtils", "EvaluateString", js::ProfileEntry::Category::JS); - MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(), - "Caller must own error reporting"); MOZ_ASSERT_IF(aCompileOptions.versionSet, aCompileOptions.version != JSVERSION_UNKNOWN); MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(aSrcBuf.get()); MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) == aEvaluationGlobal); MOZ_ASSERT_IF(aOffThreadToken, aCompileOptions.noScriptRval); MOZ_ASSERT(NS_IsMainThread()); @@ -273,18 +271,16 @@ nsJSUtils::CompileModule(JSContext* aCx, JS::SourceBufferHolder& aSrcBuf, JS::Handle<JSObject*> aEvaluationGlobal, JS::CompileOptions &aCompileOptions, JS::MutableHandle<JSObject*> aModule) { PROFILER_LABEL("nsJSUtils", "CompileModule", js::ProfileEntry::Category::JS); - MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(), - "Caller must own error reporting"); MOZ_ASSERT_IF(aCompileOptions.versionSet, aCompileOptions.version != JSVERSION_UNKNOWN); MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(aSrcBuf.get()); MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) == aEvaluationGlobal); MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == aEvaluationGlobal); MOZ_ASSERT(NS_IsMainThread()); @@ -300,18 +296,16 @@ nsJSUtils::CompileModule(JSContext* aCx, } nsresult nsJSUtils::ModuleDeclarationInstantiation(JSContext* aCx, JS::Handle<JSObject*> aModule) { PROFILER_LABEL("nsJSUtils", "ModuleDeclarationInstantiation", js::ProfileEntry::Category::JS); - MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(), - "Caller must own error reporting"); MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(nsContentUtils::IsInMicroTask()); NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK); if (!JS::ModuleDeclarationInstantiation(aCx, aModule)) { return NS_ERROR_FAILURE; @@ -321,18 +315,16 @@ nsJSUtils::ModuleDeclarationInstantiatio } nsresult nsJSUtils::ModuleEvaluation(JSContext* aCx, JS::Handle<JSObject*> aModule) { PROFILER_LABEL("nsJSUtils", "ModuleEvaluation", js::ProfileEntry::Category::JS); - MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(), - "Caller must own error reporting"); MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(nsContentUtils::IsInMicroTask()); NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK); if (!JS::ModuleEvaluation(aCx, aModule)) { return NS_ERROR_FAILURE;
--- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -3353,49 +3353,36 @@ private: ReleaseWorker(); return NS_OK; } void ReleaseWorker() { - class ReleaseRunnable final : public WorkerRunnable + class ReleaseRunnable final : public MainThreadWorkerRunnable { RefPtr<DeprecationWarningRunnable> mRunnable; public: ReleaseRunnable(WorkerPrivate* aWorkerPrivate, DeprecationWarningRunnable* aRunnable) - : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) + : MainThreadWorkerRunnable(aWorkerPrivate) , 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()); } };
--- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -495,22 +495,20 @@ DOMInterfaces = { }, 'FileReaderSync': { 'wrapperCache': False, }, 'FlyWebFetchEvent': { 'headerFile': 'FlyWebServerEvents.h', - 'nativeType': 'mozilla::dom::FlyWebFetchEvent', }, 'FlyWebWebSocketEvent': { 'headerFile': 'FlyWebServerEvents.h', - 'nativeType': 'mozilla::dom::FlyWebWebSocketEvent', }, 'FontFaceSet': { 'implicitJSContext': [ 'load' ], }, 'FontFaceSetIterator': { 'wrapperCache': False,
--- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -1128,48 +1128,48 @@ class CGHeaders(CGWrapper): map(addHeadersForType, getAllTypes(descriptors + callbackDescriptors, dictionaries, callbacks)) # Now make sure we're not trying to include the header from inside itself declareIncludes.discard(prefix + ".h") + def addHeaderForFunc(func, desc): + if func is None: + return + # Include the right class header, which we can only do + # if this is a class member function. + if desc is not None and not desc.headerIsDefault: + # An explicit header file was provided, assume that we know + # what we're doing. + return + + if "::" in func: + # Strip out the function name and convert "::" to "/" + bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h") + # Now for non-callback descriptors make sure we include any - # headers needed by Func declarations. + # headers needed by Func declarations and other things like that. for desc in descriptors: # If this is an iterator interface generated for a seperate # iterable interface, skip generating type includes, as we have # what we need in IterableIterator.h if desc.interface.isExternal() or desc.interface.isIteratorInterface(): continue - def addHeaderForFunc(func): - if func is None: - return - # Include the right class header, which we can only do - # if this is a class member function. - if not desc.headerIsDefault: - # An explicit header file was provided, assume that we know - # what we're doing. - return - - if "::" in func: - # Strip out the function name and convert "::" to "/" - bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h") - for m in desc.interface.members: - addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func")) + addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), desc) staticTypeOverride = PropertyDefiner.getStringAttr(m, "StaticClassOverride") if staticTypeOverride: bindingHeaders.add("/".join(staticTypeOverride.split("::")) + ".h") # getExtendedAttribute() returns a list, extract the entry. funcList = desc.interface.getExtendedAttribute("Func") if funcList is not None: - addHeaderForFunc(funcList[0]) + addHeaderForFunc(funcList[0], desc) if desc.interface.maplikeOrSetlikeOrIterable: # We need ToJSValue.h for maplike/setlike type conversions bindingHeaders.add("mozilla/dom/ToJSValue.h") # Add headers for the key and value types of the # maplike/setlike/iterable, since they'll be needed for # convenience functions if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType(): @@ -1178,16 +1178,22 @@ class CGHeaders(CGWrapper): if desc.interface.maplikeOrSetlikeOrIterable.hasValueType(): addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.valueType, desc, None)) for d in dictionaries: if d.parent: declareIncludes.add(self.getDeclarationFilename(d.parent)) bindingHeaders.add(self.getDeclarationFilename(d)) + for m in d.members: + addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), + None) + # No need to worry about Func on members of ancestors, because that + # will happen automatically in whatever files those ancestors live + # in. for c in callbacks: bindingHeaders.add(self.getDeclarationFilename(c)) for c in callbackDescriptors: bindingHeaders.add(self.getDeclarationFilename(c.interface)) if len(callbacks) != 0: @@ -3318,32 +3324,57 @@ class CGDefineDOMInterfaceMethod(CGAbstr } return interfaceObject; """) else: getConstructor = "return GetConstructorObjectHandle(aCx, aGlobal, aDefineOnGlobal);\n" return getConstructor +def getConditionList(idlobj, cxName, objName): + """ + Get the list of conditions for idlobj (to be used in "is this enabled" + checks). This will be returned as a CGList with " &&\n" as the separator, + for readability. + + objName is the name of the object that we're working with, because some of + our test functions want that. + """ + conditions = [] + pref = idlobj.getExtendedAttribute("Pref") + if pref: + assert isinstance(pref, list) and len(pref) == 1 + conditions.append('Preferences::GetBool("%s")' % pref[0]) + if idlobj.getExtendedAttribute("ChromeOnly"): + conditions.append("nsContentUtils::ThreadsafeIsCallerChrome()") + func = idlobj.getExtendedAttribute("Func") + if func: + assert isinstance(func, list) and len(func) == 1 + conditions.append("%s(%s, %s)" % (func[0], cxName, objName)) + if idlobj.getExtendedAttribute("SecureContext"): + conditions.append("mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(%s, %s)" % (cxName, objName)) + + return CGList((CGGeneric(cond) for cond in conditions), " &&\n") + + class CGConstructorEnabled(CGAbstractMethod): """ A method for testing whether we should be exposing this interface object or navigator property. This can perform various tests depending on what conditions are specified on the interface. """ def __init__(self, descriptor): CGAbstractMethod.__init__(self, descriptor, 'ConstructorEnabled', 'bool', [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")]) def definition_body(self): body = CGList([], "\n") - conditions = [] iface = self.descriptor.interface if not iface.isExposedOnMainThread(): exposedInWindowCheck = dedent( """ MOZ_ASSERT(!NS_IsMainThread(), "Why did we even get called?"); """) body.append(CGGeneric(exposedInWindowCheck)) @@ -3360,44 +3391,33 @@ class CGConstructorEnabled(CGAbstractMet } """, workerCondition=workerCondition.define()) exposedInWorkerCheck = CGGeneric(exposedInWorkerCheck) if iface.isExposedOnMainThread(): exposedInWorkerCheck = CGIfWrapper(exposedInWorkerCheck, "!NS_IsMainThread()") body.append(exposedInWorkerCheck) - pref = iface.getExtendedAttribute("Pref") - if pref: - assert isinstance(pref, list) and len(pref) == 1 - conditions.append('Preferences::GetBool("%s")' % pref[0]) - if iface.getExtendedAttribute("ChromeOnly"): - conditions.append("nsContentUtils::ThreadsafeIsCallerChrome()") - func = iface.getExtendedAttribute("Func") - if func: - assert isinstance(func, list) and len(func) == 1 - conditions.append("%s(aCx, aObj)" % func[0]) - if iface.getExtendedAttribute("SecureContext"): - conditions.append("mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(aCx, aObj)") + conditions = getConditionList(iface, "aCx", "aObj") availableIn = getAvailableInTestFunc(iface) if availableIn: - conditions.append("%s(aCx, aObj)" % availableIn) + conditions.append(CGGeneric("%s(aCx, aObj)" % availableIn)) checkAnyPermissions = self.descriptor.checkAnyPermissionsIndex if checkAnyPermissions is not None: - conditions.append("CheckAnyPermissions(aCx, aObj, anypermissions_%i)" % checkAnyPermissions) + conditions.append(CGGeneric("CheckAnyPermissions(aCx, aObj, anypermissions_%i)" % checkAnyPermissions)) checkAllPermissions = self.descriptor.checkAllPermissionsIndex if checkAllPermissions is not None: - conditions.append("CheckAllPermissions(aCx, aObj, allpermissions_%i)" % checkAllPermissions) + conditions.append(CGGeneric("CheckAllPermissions(aCx, aObj, allpermissions_%i)" % checkAllPermissions)) + # We should really have some conditions assert len(body) or len(conditions) conditionsWrapper = "" if len(conditions): - conditionsWrapper = CGWrapper(CGList((CGGeneric(cond) for cond in conditions), - " &&\n"), + conditionsWrapper = CGWrapper(conditions, pre="return ", post=";\n", reindent=True) else: conditionsWrapper = CGGeneric("return true;\n") body.append(conditionsWrapper) return body.define() @@ -12757,28 +12777,29 @@ class CGDictionary(CGThing): "prop": self.makeMemberName(member.identifier.name), "convert": string.Template(conversionInfo.template).substitute(replacements), "propGet": propGet } # The conversion code will only run where a default value or a value passed # by the author needs to get converted, so we can remember if we have any # members present here. conversionReplacements["convert"] += "mIsAnyMemberPresent = true;\n" - if isChromeOnly(member): - conversion = ("if (!isNull) {\n" - " if (!nsContentUtils::ThreadsafeIsCallerChrome()) {\n" - " temp->setUndefined();\n" - " } else if (!${propGet}) {\n" - " return false;\n" - " }\n" - "}\n") - else: - conversion = ("if (!isNull && !${propGet}) {\n" - " return false;\n" - "}\n") + setTempValue = CGGeneric(dedent( + """ + if (!${propGet}) { + return false; + } + """)) + conditions = getConditionList(member, "cx", "*object") + if len(conditions) != 0: + setTempValue = CGIfElseWrapper(conditions.define(), + setTempValue, + CGGeneric("temp->setUndefined();\n")) + setTempValue = CGIfWrapper(setTempValue, "!isNull") + conversion = setTempValue.define() if member.defaultValue: if (member.type.isUnion() and (not member.type.nullable() or not isinstance(member.defaultValue, IDLNullValue))): # Since this has a default value, it might have been initialized # already. Go ahead and uninit it before we try to init it # again. memberName = self.makeMemberName(member.identifier.name) @@ -12878,18 +12899,19 @@ class CGDictionary(CGThing): conversion = CGWrapper( CGIndenter(conversion), pre=("do {\n" " // block for our 'break' successCode and scope for 'temp' and 'currentValue'\n"), post="} while(0);\n") if member.canHaveMissingValue(): # Only do the conversion if we have a value conversion = CGIfWrapper(conversion, "%s.WasPassed()" % memberLoc) - if isChromeOnly(member): - conversion = CGIfWrapper(conversion, "nsContentUtils::ThreadsafeIsCallerChrome()") + conditions = getConditionList(member, "cx", "obj") + if len(conditions) != 0: + conversion = CGIfWrapper(conversion, conditions.define()) return conversion def getMemberTrace(self, member): type = member.type assert typeNeedsRooting(type) memberLoc = self.makeMemberName(member.identifier.name) if not member.canHaveMissingValue(): memberData = memberLoc @@ -13492,28 +13514,37 @@ class CGBindingRoot(CGThing): (desc.interface.hasInterfaceObject() and (desc.interface.isJSImplemented() or (ctor and isChromeOnly(ctor)))) or # JS-implemented interfaces with clearable cached # attrs have chromeonly _clearFoo methods. (desc.interface.isJSImplemented() and any(clearableCachedAttrs(desc)))) - bindingHeaders["nsContentUtils.h"] = any( - descriptorHasChromeOnly(d) for d in descriptors) # XXXkhuey ugly hack but this is going away soon. bindingHeaders['xpcprivate.h'] = webIDLFile.endswith("EventTarget.webidl") hasWorkerStuff = len(config.getDescriptors(webIDLFile=webIDLFile, workers=True)) != 0 bindingHeaders["WorkerPrivate.h"] = hasWorkerStuff hasThreadChecks = hasWorkerStuff or any(d.hasThreadChecks() for d in descriptors) bindingHeaders["nsThreadUtils.h"] = hasThreadChecks dictionaries = config.getDictionaries(webIDLFile=webIDLFile) + + def dictionaryHasChromeOnly(dictionary): + while dictionary: + if (any(isChromeOnly(m) for m in dictionary.members)): + return True + dictionary = dictionary.parent + return False + + bindingHeaders["nsContentUtils.h"] = ( + any(descriptorHasChromeOnly(d) for d in descriptors) or + any(dictionaryHasChromeOnly(d) for d in dictionaries)) hasNonEmptyDictionaries = any( len(dict.members) > 0 for dict in dictionaries) mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile, workers=False) workerCallbacks = config.getCallbacks(webIDLFile=webIDLFile, workers=True) callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile, isCallback=True)
--- a/dom/bindings/Exceptions.cpp +++ b/dom/bindings/Exceptions.cpp @@ -201,28 +201,16 @@ GetCurrentJSStack(int32_t aMaxDepth) if (!cx || !js::GetContextCompartment(cx)) { return nullptr; } return dom::exceptions::CreateStack(cx, aMaxDepth); } -AutoForceSetExceptionOnContext::AutoForceSetExceptionOnContext(JSContext* aCx) - : mCx(aCx) -{ - mOldValue = JS::ContextOptionsRef(mCx).autoJSAPIOwnsErrorReporting(); - JS::ContextOptionsRef(mCx).setAutoJSAPIOwnsErrorReporting(true); -} - -AutoForceSetExceptionOnContext::~AutoForceSetExceptionOnContext() -{ - JS::ContextOptionsRef(mCx).setAutoJSAPIOwnsErrorReporting(mOldValue); -} - namespace exceptions { class JSStackFrame : public nsIStackFrame { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(JSStackFrame) NS_DECL_NSISTACKFRAME
--- a/dom/bindings/Exceptions.h +++ b/dom/bindings/Exceptions.h @@ -49,29 +49,16 @@ CreateException(JSContext* aCx, nsresult const nsACString& aMessage = EmptyCString()); // aMaxDepth can be used to define a maximal depth for the stack trace. If the // value is -1, a default maximal depth will be selected. Will return null if // there is no JS stack right now. already_AddRefed<nsIStackFrame> GetCurrentJSStack(int32_t aMaxDepth = -1); -// Throwing a TypeError on an ErrorResult may result in SpiderMonkey using its -// own error reporting mechanism instead of just setting the exception on the -// context. This happens if no script is running. Bug 1107777 adds a flag that -// forcibly turns this behaviour off. This is a stack helper to set the flag. -class MOZ_STACK_CLASS AutoForceSetExceptionOnContext { -private: - JSContext* mCx; - bool mOldValue; -public: - explicit AutoForceSetExceptionOnContext(JSContext* aCx); - ~AutoForceSetExceptionOnContext(); -}; - // Internal stuff not intended to be widely used. namespace exceptions { // aMaxDepth can be used to define a maximal depth for the stack trace. If the // value is -1, a default maximal depth will be selected. Will return null if // there is no JS stack right now. already_AddRefed<nsIStackFrame> CreateStack(JSContext* aCx, int32_t aMaxDepth = -1);
--- a/dom/bindings/ToJSValue.cpp +++ b/dom/bindings/ToJSValue.cpp @@ -53,17 +53,16 @@ ToJSValue(JSContext* aCx, bool ToJSValue(JSContext* aCx, ErrorResult& aArgument, JS::MutableHandle<JS::Value> aValue) { MOZ_ASSERT(aArgument.Failed()); MOZ_ASSERT(!aArgument.IsUncatchableException(), "Doesn't make sense to convert uncatchable exception to a JS value!"); - AutoForceSetExceptionOnContext forceExn(aCx); DebugOnly<bool> throwResult = aArgument.MaybeSetPendingException(aCx); MOZ_ASSERT(throwResult); DebugOnly<bool> getPendingResult = JS_GetPendingException(aCx, aValue); MOZ_ASSERT(getPendingResult); JS_ClearPendingException(aCx); return true; }
--- a/dom/bindings/mach_commands.py +++ b/dom/bindings/mach_commands.py @@ -37,16 +37,19 @@ class WebIDLProvider(MachCommandBase): def webidl_test(self, **kwargs): sys.path.insert(0, os.path.join(self.topsrcdir, 'other-licenses', 'ply')) # Make sure we drop our cached grammar bits in the objdir, not # wherever we happen to be running from. os.chdir(self.topobjdir) + if kwargs["verbose"] is None: + kwargs["verbose"] = False + # Now we're going to create the cached grammar file in the # objdir. But we're going to try loading it as a python # module, so we need to make sure the objdir is in our search # path. sys.path.insert(0, self.topobjdir) import runtests return runtests.run_tests(kwargs["tests"], verbose=kwargs["verbose"])
--- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -4397,20 +4397,21 @@ class IDLArgument(IDLObjectWithIdentifie raise WebIDLError("[EnforceRange] must take no arguments", [attribute.location]) if self.clamp: raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", [self.location]) self.enforceRange = True elif identifier == "TreatNonCallableAsNull": self._allowTreatNonCallableAsNull = True - elif self.dictionaryMember and identifier == "ChromeOnly": + elif (self.dictionaryMember and + (identifier == "ChromeOnly" or identifier == "Func")): if not self.optional: - raise WebIDLError("[ChromeOnly] must not be used on a required " - "dictionary member", + raise WebIDLError("[%s] must not be used on a required " + "dictionary member" % identifier, [attribute.location]) else: raise WebIDLError("Unhandled extended attribute on %s" % ("a dictionary member" if self.dictionaryMember else "an argument"), [attribute.location]) attrlist = attribute.listValue() self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
--- a/dom/bindings/parser/runtests.py +++ b/dom/bindings/parser/runtests.py @@ -88,19 +88,21 @@ def run_tests(tests, verbose): print '%s:' % test for failure in failures: print 'TEST-UNEXPECTED-FAIL | %s' % failure def get_parser(): usage = """%(prog)s [OPTIONS] [TESTS] Where TESTS are relative to the tests directory.""" parser = argparse.ArgumentParser(usage=usage) - parser.add_argument('-q', '--quiet', action='store_false', dest='verbose', default=True, - help="Don't print passing tests.") - parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', default=True, + parser.add_argument('-q', '--quiet', action='store_false', dest='verbose', + help="Don't print passing tests.", default=None) + parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help="Run tests in verbose mode.") parser.add_argument('tests', nargs="*", help="Tests to run") return parser if __name__ == '__main__': parser = get_parser() args = parser.parse_args() + if args.verbose is None: + args.verbose = True run_tests(args.tests, verbose=args.verbose)
--- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -1124,16 +1124,25 @@ dictionary DictForConstructor { sequence<any> seq4; sequence<any> seq5; sequence<DictContainingSequence> seq6; object obj1; object? obj2; any any1 = null; }; +dictionary DictWithConditionalMembers { + [ChromeOnly] + long chromeOnlyMember; + [Func="TestFuncControlledMember"] + long funcControlledMember; + [ChromeOnly, Func="nsGenericHTMLElement::TouchEventsEnabled"] + long chromeOnlyFuncControlledMember; +}; + interface TestIndexedGetterInterface { getter long item(unsigned long idx); readonly attribute unsigned long length; legacycaller void(); }; interface TestNamedGetterInterface { getter DOMString (DOMString name);
--- a/dom/cache/CacheTypes.ipdlh +++ b/dom/cache/CacheTypes.ipdlh @@ -4,23 +4,23 @@ include protocol PCache; include protocol PCacheStreamControl; include protocol PSendStream; include IPCStream; include ChannelInfo; include PBackgroundSharedTypes; -using HeadersGuardEnum from "mozilla/dom/cache/IPCUtils.h"; -using ReferrerPolicy from "mozilla/dom/cache/IPCUtils.h"; -using RequestCredentials from "mozilla/dom/cache/IPCUtils.h"; -using RequestMode from "mozilla/dom/cache/IPCUtils.h"; -using RequestCache from "mozilla/dom/cache/IPCUtils.h"; -using RequestRedirect from "mozilla/dom/cache/IPCUtils.h"; -using ResponseType from "mozilla/dom/cache/IPCUtils.h"; +using HeadersGuardEnum from "mozilla/dom/FetchIPCTypes.h"; +using ReferrerPolicy from "mozilla/dom/FetchIPCTypes.h"; +using RequestCredentials from "mozilla/dom/FetchIPCTypes.h"; +using RequestMode from "mozilla/dom/FetchIPCTypes.h"; +using RequestCache from "mozilla/dom/FetchIPCTypes.h"; +using RequestRedirect from "mozilla/dom/FetchIPCTypes.h"; +using ResponseType from "mozilla/dom/FetchIPCTypes.h"; using mozilla::void_t from "ipc/IPCMessageUtils.h"; using struct nsID from "nsID.h"; namespace mozilla { namespace dom { namespace cache { struct CacheQueryParams
--- a/dom/cache/IPCUtils.h +++ b/dom/cache/IPCUtils.h @@ -4,61 +4,20 @@ * 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 mozilla_dom_cache_IPCUtils_h #define mozilla_dom_cache_IPCUtils_h #include "ipc/IPCMessageUtils.h" -// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None -#undef None - -#include "mozilla/dom/HeadersBinding.h" -#include "mozilla/dom/RequestBinding.h" -#include "mozilla/dom/ResponseBinding.h" #include "mozilla/dom/cache/Types.h" namespace IPC { template<> - struct ParamTraits<mozilla::dom::HeadersGuardEnum> : - public ContiguousEnumSerializer<mozilla::dom::HeadersGuardEnum, - mozilla::dom::HeadersGuardEnum::None, - mozilla::dom::HeadersGuardEnum::EndGuard_> {}; - template<> - struct ParamTraits<mozilla::dom::ReferrerPolicy> : - public ContiguousEnumSerializer<mozilla::dom::ReferrerPolicy, - mozilla::dom::ReferrerPolicy::_empty, - mozilla::dom::ReferrerPolicy::EndGuard_> {}; - template<> - struct ParamTraits<mozilla::dom::RequestMode> : - public ContiguousEnumSerializer<mozilla::dom::RequestMode, - mozilla::dom::RequestMode::Same_origin, - mozilla::dom::RequestMode::EndGuard_> {}; - template<> - struct ParamTraits<mozilla::dom::RequestCredentials> : - public ContiguousEnumSerializer<mozilla::dom::RequestCredentials, - mozilla::dom::RequestCredentials::Omit, - mozilla::dom::RequestCredentials::EndGuard_> {}; - template<> - struct ParamTraits<mozilla::dom::RequestCache> : - public ContiguousEnumSerializer<mozilla::dom::RequestCache, - mozilla::dom::RequestCache::Default, - mozilla::dom::RequestCache::EndGuard_> {}; - template<> - struct ParamTraits<mozilla::dom::RequestRedirect> : - public ContiguousEnumSerializer<mozilla::dom::RequestRedirect, - mozilla::dom::RequestRedirect::Follow, - mozilla::dom::RequestRedirect::EndGuard_> {}; - template<> - struct ParamTraits<mozilla::dom::ResponseType> : - public ContiguousEnumSerializer<mozilla::dom::ResponseType, - mozilla::dom::ResponseType::Basic, - mozilla::dom::ResponseType::EndGuard_> {}; - template<> struct ParamTraits<mozilla::dom::cache::Namespace> : public ContiguousEnumSerializer<mozilla::dom::cache::Namespace, mozilla::dom::cache::DEFAULT_NAMESPACE, mozilla::dom::cache::NUMBER_OF_NAMESPACES> {}; } // namespace IPC #endif // mozilla_dom_cache_IPCUtils_h
--- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -3599,18 +3599,20 @@ struct MOZ_STACK_CLASS CanvasBidiProcess mCtx->EnsureTarget(); // If the operation is 'fill' with a simple color, we defer to gfxTextRun // which will handle color/svg-in-ot fonts appropriately. Such fonts will // not render well via the code below. if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL && mState->StyleIsColor(CanvasRenderingContext2D::Style::FILL)) { + // TODO: determine if mCtx->mTarget is guaranteed to be non-null and valid + // here. If it's not, thebes will be null and we'll crash. RefPtr<gfxContext> thebes = - gfxContext::ForDrawTargetWithTransform(mCtx->mTarget); + gfxContext::CreatePreservingTransformOrNull(mCtx->mTarget); nscolor fill = mState->colorStyles[CanvasRenderingContext2D::Style::FILL]; thebes->SetColor(Color::FromABGR(fill)); gfxTextRun::DrawParams params(thebes); mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params); return; } uint32_t numRuns; @@ -4783,17 +4785,17 @@ CanvasRenderingContext2D::DrawDirectlyTo nsIntSize scaledImageSize(std::ceil(aImgSize.width * scale.width), std::ceil(aImgSize.height * scale.height)); aSrc.Scale(scale.width, scale.height); // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore // the matrix even though this is a temp gfxContext. AutoRestoreTransform autoRestoreTransform(mTarget); - RefPtr<gfxContext> context = gfxContext::ForDrawTarget(tempTarget); + RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget); if (!context) { gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem"; return; } context->SetMatrix(contextMatrix. Scale(1.0 / contextScale.width, 1.0 / contextScale.height). Translate(aDest.x - aSrc.x, aDest.y - aSrc.y)); @@ -4986,30 +4988,31 @@ CanvasRenderingContext2D::DrawWindow(nsG RefPtr<gfxContext> thebes; RefPtr<DrawTarget> drawDT; // Rendering directly is faster and can be done if mTarget supports Azure // and does not need alpha blending. if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) && GlobalAlpha() == 1.0f && UsedOperation() == CompositionOp::OP_OVER) { - thebes = gfxContext::ForDrawTarget(mTarget); - MOZ_ASSERT(thebes); // alrady checked the draw target above + thebes = gfxContext::CreateOrNull(mTarget); + MOZ_ASSERT(thebes); // already checked the draw target above + // (in SupportsAzureContentForDrawTarget) thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32)); } else { drawDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(IntSize(ceil(sw), ceil(sh)), SurfaceFormat::B8G8R8A8); if (!drawDT || !drawDT->IsValid()) { aError.Throw(NS_ERROR_FAILURE); return; } - thebes = gfxContext::ForDrawTarget(drawDT); + thebes = gfxContext::CreateOrNull(drawDT); MOZ_ASSERT(thebes); // alrady checked the draw target above thebes->SetMatrix(gfxMatrix::Scaling(matrix._11, matrix._22)); } nsCOMPtr<nsIPresShell> shell = presContext->PresShell(); Unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes); // If this canvas was contained in the drawn window, the pre-transaction callback // may have returned its DT. If so, we must reacquire it here.
--- a/dom/canvas/DocumentRendererChild.cpp +++ b/dom/canvas/DocumentRendererChild.cpp @@ -78,17 +78,17 @@ DocumentRendererChild::RenderDocument(ns reinterpret_cast<uint8_t*>(data.BeginWriting()), IntSize(renderSize.width, renderSize.height), 4 * renderSize.width, SurfaceFormat::B8G8R8A8); if (!dt || !dt->IsValid()) { gfxWarning() << "DocumentRendererChild::RenderDocument failed to Factory::CreateDrawTargetForData"; return false; } - RefPtr<gfxContext> ctx = gfxContext::ForDrawTarget(dt); + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt); MOZ_ASSERT(ctx); // already checked the draw target above ctx->SetMatrix(mozilla::gfx::ThebesMatrix(transform)); nsCOMPtr<nsIPresShell> shell = presContext->PresShell(); shell->RenderDocument(documentRect, renderFlags, bgColor, ctx); return true; }
--- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -25,48 +25,39 @@ #include "nsCRT.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptContext.h" #include "nsIDocument.h" #include "nsIScriptGlobalObject.h" #include "nsVariant.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/DataTransferBinding.h" +#include "mozilla/dom/DataTransferItemList.h" #include "mozilla/dom/Directory.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/FileList.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/OSFileSystem.h" #include "mozilla/dom/Promise.h" +#include "nsNetUtil.h" namespace mozilla { namespace dom { -inline void -ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, - TransferItem& aField, - const char* aName, - uint32_t aFlags = 0) -{ - ImplCycleCollectionTraverse(aCallback, aField.mData, aName, aFlags); -} - NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList) NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragTarget) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragImage) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DataTransfer) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mItems) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragImage) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DataTransfer) NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransfer) @@ -100,16 +91,17 @@ DataTransfer::DataTransfer(nsISupports* , mReadOnly(true) , mIsExternal(aIsExternal) , mUserCancelled(false) , mIsCrossDomainSubFrameDrop(false) , mClipboardType(aClipboardType) , mDragImageX(0) , mDragImageY(0) { + mItems = new DataTransferItemList(this, aIsExternal, false /* aIsCrossDomainSubFrameDrop */); // For these events, we want to be able to add data to the data transfer, so // clear the readonly state. Otherwise, the data is already present. For // external usage, cache the data from the native clipboard or drag. if (aEventMessage == eCut || aEventMessage == eCopy || aEventMessage == eDragStart || aEventMessage == eLegacyDragGesture) { mReadOnly = false; @@ -126,36 +118,40 @@ DataTransfer::DataTransfer(nsISupports* DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage, const uint32_t aEffectAllowed, bool aCursorState, bool aIsExternal, bool aUserCancelled, bool aIsCrossDomainSubFrameDrop, int32_t aClipboardType, - nsTArray<nsTArray<TransferItem> >& aItems, + DataTransferItemList* aItems, Element* aDragImage, uint32_t aDragImageX, uint32_t aDragImageY) : mParent(aParent) , mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE) , mEffectAllowed(aEffectAllowed) , mEventMessage(aEventMessage) , mCursorState(aCursorState) , mReadOnly(true) , mIsExternal(aIsExternal) , mUserCancelled(aUserCancelled) , mIsCrossDomainSubFrameDrop(aIsCrossDomainSubFrameDrop) , mClipboardType(aClipboardType) - , mItems(aItems) , mDragImage(aDragImage) , mDragImageX(aDragImageX) , mDragImageY(aDragImageY) { MOZ_ASSERT(mParent); + MOZ_ASSERT(aItems); + + // We clone the items array after everything else, so that it has a valid + // mParent value + mItems = aItems->Clone(this); // The items are copied from aItems into mItems. There is no need to copy // the actual data in the items as the data transfer will be read only. The // draggesture and dragstart events are the only times when items are // modifiable, but those events should have been using the first constructor // above. NS_ASSERTION(aEventMessage != eLegacyDragGesture && aEventMessage != eDragStart, "invalid event type for DataTransfer constructor"); @@ -287,112 +283,90 @@ DataTransfer::GetMozUserCancelled(bool* { *aUserCancelled = MozUserCancelled(); return NS_OK; } FileList* DataTransfer::GetFiles(ErrorResult& aRv) { - return GetFileListInternal(aRv, nsContentUtils::SubjectPrincipal()); -} - -FileList* -DataTransfer::GetFileListInternal(ErrorResult& aRv, - nsIPrincipal* aSubjectPrincipal) -{ - if (mEventMessage != eDrop && - mEventMessage != eLegacyDragDrop && - mEventMessage != ePaste) { - return nullptr; - } - - if (!mFileList) { - mFileList = new FileList(static_cast<nsIDOMDataTransfer*>(this)); - - uint32_t count = mItems.Length(); - - for (uint32_t i = 0; i < count; i++) { - nsCOMPtr<nsIVariant> variant; - aRv = GetDataAtInternal(NS_ConvertUTF8toUTF16(kFileMime), i, - aSubjectPrincipal, getter_AddRefs(variant)); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - if (!variant) { - continue; - } - - nsCOMPtr<nsISupports> supports; - nsresult rv = variant->GetAsISupports(getter_AddRefs(supports)); - - if (NS_WARN_IF(NS_FAILED(rv))) { - continue; - } - - nsCOMPtr<nsIFile> file = do_QueryInterface(supports); - - RefPtr<File> domFile; - if (file) { - MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default, - "nsIFile objects are not expected on the content process"); - - bool isDir; - aRv = file->IsDirectory(&isDir); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - if (isDir) { - continue; - } - - domFile = File::CreateFromFile(GetParentObject(), file); - } else { - nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports); - if (!blobImpl) { - continue; - } - - MOZ_ASSERT(blobImpl->IsFile()); - - domFile = File::Create(GetParentObject(), blobImpl); - MOZ_ASSERT(domFile); - } - - mFileList->Append(domFile); - } - } - - return mFileList; + return mItems->Files(); } NS_IMETHODIMP DataTransfer::GetFiles(nsIDOMFileList** aFileList) { + if (!aFileList) { + return NS_ERROR_FAILURE; + } + ErrorResult rv; - NS_IF_ADDREF(*aFileList = - GetFileListInternal(rv, nsContentUtils::GetSystemPrincipal())); - return rv.StealNSResult(); + RefPtr<FileList> files = GetFiles(rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + files.forget(aFileList); + return NS_OK; } already_AddRefed<DOMStringList> -DataTransfer::Types() const +DataTransfer::GetTypes(ErrorResult& aRv) const { - ErrorResult rv; - return MozTypesAt(0, rv); + RefPtr<DOMStringList> types = new DOMStringList(); + + const nsTArray<RefPtr<DataTransferItem>>* items = mItems->MozItemsAt(0); + if (!items || items->IsEmpty()) { + return types.forget(); + } + + bool addFile = false; + for (uint32_t i = 0; i < items->Length(); i++) { + DataTransferItem* item = items->ElementAt(i); + MOZ_ASSERT(item); + + if (item->ChromeOnly() && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + continue; + } + + nsAutoString type; + item->GetType(type); + if (NS_WARN_IF(!types->Add(type))) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (!addFile) { + addFile = item->Kind() == DataTransferItem::KIND_FILE; + } + } + + // If we have any files, we need to also add the "Files" type! + if (addFile && NS_WARN_IF(!types->Add(NS_LITERAL_STRING("Files")))) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + return types.forget(); } NS_IMETHODIMP DataTransfer::GetTypes(nsISupports** aTypes) { - RefPtr<DOMStringList> types = Types(); + if (NS_WARN_IF(!aTypes)) { + return NS_ERROR_FAILURE; + } + + ErrorResult rv; + RefPtr<DOMStringList> types = GetTypes(rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + types.forget(aTypes); - return NS_OK; } void DataTransfer::GetData(const nsAString& aFormat, nsAString& aData, ErrorResult& aRv) { // return an empty string if data for the format was not found @@ -470,17 +444,17 @@ DataTransfer::SetData(const nsAString& a void DataTransfer::ClearData(const Optional<nsAString>& aFormat, ErrorResult& aRv) { if (mReadOnly) { aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } - if (mItems.Length() == 0) { + if (MozItemCount() == 0) { return; } if (aFormat.WasPassed()) { MozClearDataAtHelper(aFormat.Value(), 0, aRv); } else { MozClearDataAtHelper(EmptyString(), 0, aRv); } @@ -559,37 +533,39 @@ DataTransfer::MozTypesAt(uint32_t aIndex if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy || mEventMessage == ePaste)) { aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); return nullptr; } RefPtr<DOMStringList> types = new DOMStringList(); - if (aIndex < mItems.Length()) { - bool addFile = false; + if (aIndex < MozItemCount()) { // note that you can retrieve the types regardless of their principal - const nsTArray<TransferItem>& item = mItems[aIndex]; - for (uint32_t i = 0; i < item.Length(); i++) { - const nsString& format = item[i].mFormat; - types->Add(format); - if (!addFile) { - addFile = format.EqualsASCII(kFileMime); + const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(aIndex); + + bool addFile = false; + for (uint32_t i = 0; i < items.Length(); i++) { + if (items[i]->ChromeOnly() && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + continue; + } + + nsAutoString type; + items[i]->GetType(type); + if (NS_WARN_IF(!types->Add(type))) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (items[i]->Kind() == DataTransferItem::KIND_FILE) { + addFile = true; } } if (addFile) { - // If this is a content caller, and a file is in the data transfer, remove - // the non-file types. This prevents alternate text forms of the file - // from being returned. - if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { - types->Clear(); - types->Add(NS_LITERAL_STRING(kFileMime)); - } - types->Add(NS_LITERAL_STRING("Files")); } } return types.forget(); } NS_IMETHODIMP @@ -616,90 +592,82 @@ DataTransfer::GetDataAtInternal(const ns nsIVariant** aData) { *aData = nullptr; if (aFormat.IsEmpty()) { return NS_OK; } - if (aIndex >= mItems.Length()) { + if (aIndex >= MozItemCount()) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } // Only the first item is valid for clipboard events if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy || mEventMessage == ePaste)) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } nsAutoString format; GetRealFormat(aFormat, format); - nsTArray<TransferItem>& item = mItems[aIndex]; - - // If this is a content caller, and a file is in the data transfer, only - // return the file type. - if (!format.EqualsLiteral(kFileMime) && - !nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) { - uint32_t count = item.Length(); - for (uint32_t i = 0; i < count; i++) { - if (item[i].mFormat.EqualsLiteral(kFileMime)) { - return NS_OK; - } - } - } - // Check if the caller is allowed to access the drag data. Callers with // chrome privileges can always read the data. During the // drop event, allow retrieving the data except in the case where the // source of the drag is in a child frame of the caller. In that case, // we only allow access to data of the same principal. During other events, // only allow access to the data with the same principal. bool checkFormatItemPrincipal = mIsCrossDomainSubFrameDrop || (mEventMessage != eDrop && mEventMessage != eLegacyDragDrop && mEventMessage != ePaste); + MOZ_ASSERT(aSubjectPrincipal); - uint32_t count = item.Length(); - for (uint32_t i = 0; i < count; i++) { - TransferItem& formatitem = item[i]; - if (formatitem.mFormat.Equals(format)) { - if (formatitem.mPrincipal && checkFormatItemPrincipal && - !aSubjectPrincipal->Subsumes(formatitem.mPrincipal)) { - return NS_ERROR_DOM_SECURITY_ERR; - } + RefPtr<DataTransferItem> item = mItems->MozItemByTypeAt(format, aIndex); + if (!item) { + // The index exists but there's no data for the specified format, in this + // case we just return undefined + return NS_OK; + } + + // If we have chrome only content, and we aren't chrome, don't allow access + if (!nsContentUtils::IsSystemPrincipal(aSubjectPrincipal) && item->ChromeOnly()) { + return NS_OK; + } + + if (item->Principal() && checkFormatItemPrincipal && + !aSubjectPrincipal->Subsumes(item->Principal())) { + return NS_ERROR_DOM_SECURITY_ERR; + } - if (!formatitem.mData) { - FillInExternalData(formatitem, aIndex); - } else { - nsCOMPtr<nsISupports> data; - formatitem.mData->GetAsISupports(getter_AddRefs(data)); - // Make sure the code that is calling us is same-origin with the data. - nsCOMPtr<EventTarget> pt = do_QueryInterface(data); - if (pt) { - nsresult rv = NS_OK; - nsIScriptContext* c = pt->GetContextForEventHandlers(&rv); - NS_ENSURE_TRUE(c && NS_SUCCEEDED(rv), NS_ERROR_DOM_SECURITY_ERR); - nsIGlobalObject* go = c->GetGlobalObject(); - NS_ENSURE_TRUE(go, NS_ERROR_DOM_SECURITY_ERR); - nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go); - MOZ_ASSERT(sp, "This cannot fail on the main thread."); - nsIPrincipal* dataPrincipal = sp->GetPrincipal(); - NS_ENSURE_TRUE(dataPrincipal, NS_ERROR_DOM_SECURITY_ERR); - NS_ENSURE_TRUE(aSubjectPrincipal->Subsumes(dataPrincipal), - NS_ERROR_DOM_SECURITY_ERR); - } - } - *aData = formatitem.mData; - NS_IF_ADDREF(*aData); - return NS_OK; + nsCOMPtr<nsIVariant> data = item->Data(); + MOZ_ASSERT(data); + + nsCOMPtr<nsISupports> isupportsData; + nsresult rv = data->GetAsISupports(getter_AddRefs(isupportsData)); + + if (NS_SUCCEEDED(rv) && isupportsData) { + // Make sure the code that is calling us is same-origin with the data. + nsCOMPtr<EventTarget> pt = do_QueryInterface(isupportsData); + if (pt) { + nsresult rv = NS_OK; + nsIScriptContext* c = pt->GetContextForEventHandlers(&rv); + NS_ENSURE_TRUE(c && NS_SUCCEEDED(rv), NS_ERROR_DOM_SECURITY_ERR); + nsIGlobalObject* go = c->GetGlobalObject(); + NS_ENSURE_TRUE(go, NS_ERROR_DOM_SECURITY_ERR); + nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go); + MOZ_ASSERT(sp, "This cannot fail on the main thread."); + nsIPrincipal* dataPrincipal = sp->GetPrincipal(); + NS_ENSURE_TRUE(dataPrincipal, NS_ERROR_DOM_SECURITY_ERR); + NS_ENSURE_TRUE(aSubjectPrincipal->Subsumes(dataPrincipal), NS_ERROR_DOM_SECURITY_ERR); } } + data.forget(aData); return NS_OK; } void DataTransfer::MozGetDataAt(JSContext* aCx, const nsAString& aFormat, uint32_t aIndex, JS::MutableHandle<JS::Value> aRetval, mozilla::ErrorResult& aRv) @@ -733,17 +701,17 @@ DataTransfer::SetDataAtInternal(const ns } if (mReadOnly) { return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; } // Specifying an index less than the current length will replace an existing // item. Specifying an index equal to the current length will add a new item. - if (aIndex > mItems.Length()) { + if (aIndex > MozItemCount()) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } // Only the first item is valid for clipboard events if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy || mEventMessage == ePaste)) { return NS_ERROR_DOM_INDEX_SIZE_ERR; @@ -791,79 +759,57 @@ void DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex, ErrorResult& aRv) { if (mReadOnly) { aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); return; } - if (aIndex >= mItems.Length()) { + if (aIndex >= MozItemCount()) { aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); return; } // Only the first item is valid for clipboard events if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy || mEventMessage == ePaste)) { aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); return; } MozClearDataAtHelper(aFormat, aIndex, aRv); + + // If we just cleared the 0-th index, and there are still more than 1 indexes + // remaining, MozClearDataAt should cause the 1st index to become the 0th + // index. This should _only_ happen when the MozClearDataAt function is + // explicitly called by script, as this behavior is inconsistent with spec. + // (however, so is the MozClearDataAt API) + + if (aIndex == 0 && mItems->MozItemCount() > 1 && + mItems->MozItemsAt(0)->Length() == 0) { + mItems->PopIndexZero(); + } } void DataTransfer::MozClearDataAtHelper(const nsAString& aFormat, uint32_t aIndex, ErrorResult& aRv) { MOZ_ASSERT(!mReadOnly); - MOZ_ASSERT(aIndex < mItems.Length()); + MOZ_ASSERT(aIndex < MozItemCount()); MOZ_ASSERT(aIndex == 0 || (mEventMessage != eCut && mEventMessage != eCopy && mEventMessage != ePaste)); nsAutoString format; GetRealFormat(aFormat, format); - nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(); - - // if the format is empty, clear all formats - bool clearall = format.IsEmpty(); - - nsTArray<TransferItem>& item = mItems[aIndex]; - // count backwards so that the count and index don't have to be adjusted - // after removing an element - for (int32_t i = item.Length() - 1; i >= 0; i--) { - TransferItem& formatitem = item[i]; - if (clearall || formatitem.mFormat.Equals(format)) { - // don't allow removing data that has a stronger principal - bool subsumes; - if (formatitem.mPrincipal && principal && - (NS_FAILED(principal->Subsumes(formatitem.mPrincipal, &subsumes)) || - !subsumes)) { - aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); - return; - } - - item.RemoveElementAt(i); - - // if a format was specified, break out. Otherwise, loop around until - // all formats have been removed - if (!clearall) { - break; - } - } - } - - // if the last format for an item is removed, remove the entire item - if (!item.Length()) { - mItems.RemoveElementAt(aIndex); - } + mItems->MozRemoveByTypeAt(format, aIndex, aRv); } NS_IMETHODIMP DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex) { ErrorResult rv; MozClearDataAt(aFormat, aIndex, rv); return rv.StealNSResult(); @@ -906,29 +852,27 @@ DataTransfer::GetFilesAndDirectories(Err nsCOMPtr<nsIGlobalObject> global = parentNode->OwnerDoc()->GetScopeObject(); MOZ_ASSERT(global); if (!global) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr<Promise> p = Promise::Create(global, aRv); - if (aRv.Failed()) { + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } - if (!mFileList) { - GetFiles(aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } + RefPtr<FileList> files = mItems->Files(); + if (NS_WARN_IF(!files)) { + return nullptr; } Sequence<RefPtr<File>> filesSeq; - mFileList->ToSequence(filesSeq, aRv); + files->ToSequence(filesSeq, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } p->MaybeResolve(filesSeq); return p.forget(); } @@ -957,24 +901,23 @@ DataTransfer::AddElement(nsIDOMElement* return rv.StealNSResult(); } nsresult DataTransfer::Clone(nsISupports* aParent, EventMessage aEventMessage, bool aUserCancelled, bool aIsCrossDomainSubFrameDrop, DataTransfer** aNewDataTransfer) { - DataTransfer* newDataTransfer = + RefPtr<DataTransfer> newDataTransfer = new DataTransfer(aParent, aEventMessage, mEffectAllowed, mCursorState, mIsExternal, aUserCancelled, aIsCrossDomainSubFrameDrop, mClipboardType, mItems, mDragImage, mDragImageX, mDragImageY); - *aNewDataTransfer = newDataTransfer; - NS_ADDREF(*aNewDataTransfer); + newDataTransfer.forget(aNewDataTransfer); return NS_OK; } already_AddRefed<nsISupportsArray> DataTransfer::GetTransferables(nsIDOMNode* aDragTarget) { MOZ_ASSERT(aDragTarget); @@ -996,35 +939,35 @@ DataTransfer::GetTransferables(nsILoadCo { nsCOMPtr<nsISupportsArray> transArray = do_CreateInstance("@mozilla.org/supports-array;1"); if (!transArray) { return nullptr; } - uint32_t count = mItems.Length(); + uint32_t count = MozItemCount(); for (uint32_t i = 0; i < count; i++) { nsCOMPtr<nsITransferable> transferable = GetTransferable(i, aLoadContext); if (transferable) { transArray->AppendElement(transferable); } } return transArray.forget(); } already_AddRefed<nsITransferable> DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext) { - if (aIndex >= mItems.Length()) { + if (aIndex >= MozItemCount()) { return nullptr; } - nsTArray<TransferItem>& item = mItems[aIndex]; + const nsTArray<RefPtr<DataTransferItem>>& item = *mItems->MozItemsAt(aIndex); uint32_t count = item.Length(); if (!count) { return nullptr; } nsCOMPtr<nsITransferable> transferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); if (!transferable) { @@ -1066,35 +1009,38 @@ DataTransfer::GetTransferable(uint32_t a * <wide string> format * <32-bit> length of data * <wide string> data * A type of eCustomClipboardTypeId_None ends the list, without any following * data. */ do { for (uint32_t f = 0; f < count; f++) { - const TransferItem& formatitem = item[f]; - if (!formatitem.mData) { // skip empty items + RefPtr<DataTransferItem> formatitem = item[f]; + if (!formatitem->Data()) { // skip empty items continue; } + nsAutoString type; + formatitem->GetType(type); + // If the data is of one of the well-known formats, use it directly. bool isCustomFormat = true; for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) { - if (formatitem.mFormat.EqualsASCII(knownFormats[f])) { + if (type.EqualsASCII(knownFormats[f])) { isCustomFormat = false; break; } } uint32_t lengthInBytes; nsCOMPtr<nsISupports> convertedData; if (handlingCustomFormats) { - if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), + if (!ConvertFromVariant(formatitem->Data(), getter_AddRefs(convertedData), &lengthInBytes)) { continue; } // When handling custom types, add the data to the stream if this is a // custom type. if (isCustomFormat) { // If it isn't a string, just ignore it. The dataTransfer is cached in @@ -1111,22 +1057,21 @@ DataTransfer::GetTransferable(uint32_t a nsCOMPtr<nsIOutputStream> outputStream; storageStream->GetOutputStream(0, getter_AddRefs(outputStream)); stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); stream->SetOutputStream(outputStream); } - int32_t formatLength = - formatitem.mFormat.Length() * sizeof(nsString::char_type); + int32_t formatLength = type.Length() * sizeof(nsString::char_type); stream->Write32(eCustomClipboardTypeId_String); stream->Write32(formatLength); - stream->WriteBytes((const char *)formatitem.mFormat.get(), + stream->WriteBytes((const char *)type.get(), formatLength); stream->Write32(lengthInBytes); stream->WriteBytes((const char *)data.get(), lengthInBytes); // The total size of the stream is the format length, the data // length, two integers to hold the lengths and one integer for the // string flag. totalCustomLength += @@ -1170,25 +1115,25 @@ DataTransfer::GetTransferable(uint32_t a added = true; // Clear the stream so it doesn't get used again. stream = nullptr; } else { // This is the second pass of the loop and a known type is encountered. // Add it as is. - if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), + if (!ConvertFromVariant(formatitem->Data(), getter_AddRefs(convertedData), &lengthInBytes)) { continue; } // The underlying drag code uses text/unicode, so use that instead of // text/plain const char* format; - NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat); + NS_ConvertUTF16toUTF8 utf8format(type); if (utf8format.EqualsLiteral(kTextMime)) { format = kUnicodeMime; } else { format = utf8format.get(); } // If a converter is set for a format, set the converter for the // transferable and don't add the item @@ -1230,17 +1175,17 @@ DataTransfer::ConvertFromVariant(nsIVari *aLength = 0; uint16_t type; aVariant->GetDataType(&type); if (type == nsIDataType::VTYPE_INTERFACE || type == nsIDataType::VTYPE_INTERFACE_IS) { nsCOMPtr<nsISupports> data; if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data)))) { - return false; + return false; } nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data); if (fdp) { // for flavour data providers, use kFlavorHasDataProvider (which has the // value 0) as the length. fdp.forget(aSupports); *aLength = nsITransferable::kFlavorHasDataProvider; @@ -1286,71 +1231,41 @@ DataTransfer::ConvertFromVariant(nsIVari *aLength = str.Length() << 1; return true; } void DataTransfer::ClearAll() { - mItems.Clear(); + mItems->ClearAllItems(); +} + +uint32_t +DataTransfer::MozItemCount() const +{ + return mItems->MozItemCount(); } nsresult DataTransfer::SetDataWithPrincipal(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal) { nsAutoString format; GetRealFormat(aFormat, format); - // check if the item for the format already exists. In that case, - // just replace it. - TransferItem* formatitem; - if (aIndex < mItems.Length()) { - nsTArray<TransferItem>& item = mItems[aIndex]; - uint32_t count = item.Length(); - for (uint32_t i = 0; i < count; i++) { - TransferItem& itemformat = item[i]; - if (itemformat.mFormat.Equals(format)) { - // don't allow replacing data that has a stronger principal - bool subsumes; - if (itemformat.mPrincipal && aPrincipal && - (NS_FAILED(aPrincipal->Subsumes(itemformat.mPrincipal, - &subsumes)) || !subsumes)) { - return NS_ERROR_DOM_SECURITY_ERR; - } - - itemformat.mPrincipal = aPrincipal; - itemformat.mData = aData; - return NS_OK; - } - } - - // add a new format - formatitem = item.AppendElement(); - } - else { - NS_ASSERTION(aIndex == mItems.Length(), "Index out of range"); - - // add a new index - nsTArray<TransferItem>* item = mItems.AppendElement(); - NS_ENSURE_TRUE(item, NS_ERROR_OUT_OF_MEMORY); - - formatitem = item->AppendElement(); - } - - NS_ENSURE_TRUE(formatitem, NS_ERROR_OUT_OF_MEMORY); - - formatitem->mFormat = format; - formatitem->mPrincipal = aPrincipal; - formatitem->mData = aData; - - return NS_OK; + ErrorResult rv; + RefPtr<DataTransferItem> item = + mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal, + /* aInsertOnly = */ false, + /* aHidden= */ false, + rv); + return rv.StealNSResult(); } void DataTransfer::SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal) { @@ -1377,36 +1292,58 @@ DataTransfer::GetRealFormat(const nsAStr if (lowercaseFormat.EqualsLiteral("url")) { aOutFormat.AssignLiteral("text/uri-list"); return; } aOutFormat.Assign(lowercaseFormat); } -void +nsresult DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex, - nsIPrincipal* aPrincipal) + nsIPrincipal* aPrincipal, bool aHidden) { + ErrorResult rv; + RefPtr<DataTransferItem> item; + if (strcmp(aFormat, kUnicodeMime) == 0) { - SetDataWithPrincipal(NS_LITERAL_STRING("text/plain"), nullptr, aIndex, - aPrincipal); - return; + item = mItems->SetDataWithPrincipal(NS_LITERAL_STRING("text/plain"), nullptr, + aIndex, aPrincipal, false, aHidden, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + return NS_OK; } if (strcmp(aFormat, kURLDataMime) == 0) { - SetDataWithPrincipal(NS_LITERAL_STRING("text/uri-list"), nullptr, aIndex, - aPrincipal); - return; + item = mItems->SetDataWithPrincipal(NS_LITERAL_STRING("text/uri-list"), nullptr, + aIndex, aPrincipal, false, aHidden, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + return NS_OK; } - SetDataWithPrincipal(NS_ConvertUTF8toUTF16(aFormat), nullptr, aIndex, - aPrincipal); + nsAutoString format; + GetRealFormat(NS_ConvertUTF8toUTF16(aFormat), format); + item = mItems->SetDataWithPrincipal(format, nullptr, aIndex, + aPrincipal, false, aHidden, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + return NS_OK; } +// there isn't a way to get a list of the formats that might be available on +// all platforms, so just check for the types that can actually be imported +// XXXndeakin there are some other formats but those are platform specific. +const char* kFormats[] = { kFileMime, kHTMLMime, kURLMime, kURLDataMime, + kUnicodeMime, kPNGImageMime, kJPEGImageMime, + kGIFImageMime }; + void DataTransfer::CacheExternalDragFormats() { // Called during the constructor to cache the formats available from an // external drag. The data associated with each format will be set to null. // This data will instead only be retrieved in FillInExternalDragData when // asked for, as it may be time consuming for the source application to // generate it. @@ -1425,34 +1362,37 @@ DataTransfer::CacheExternalDragFormats() // all platforms, so just check for the types that can actually be imported // XXXndeakin there are some other formats but those are platform specific. const char* formats[] = { kFileMime, kHTMLMime, kRTFMime, kURLMime, kURLDataMime, kUnicodeMime }; uint32_t count; dragSession->GetNumDropItems(&count); for (uint32_t c = 0; c < count; c++) { + bool hasFileData = false; + dragSession->IsDataFlavorSupported(kFileMime, &hasFileData); + // First, check for the special format that holds custom types. bool supported; dragSession->IsDataFlavorSupported(kCustomTypesMime, &supported); if (supported) { FillInExternalCustomTypes(c, sysPrincipal); } for (uint32_t f = 0; f < ArrayLength(formats); f++) { // IsDataFlavorSupported doesn't take an index as an argument and just // checks if any of the items support a particular flavor, even though // the GetData method does take an index. Here, we just assume that // every item being dragged has the same set of flavors. bool supported; - dragSession->IsDataFlavorSupported(formats[f], &supported); + dragSession->IsDataFlavorSupported(kFormats[f], &supported); // if the format is supported, add an item to the array with null as // the data. When retrieved, GetRealData will read the data. if (supported) { - CacheExternalData(formats[f], c, sysPrincipal); + CacheExternalData(kFormats[f], c, sysPrincipal, /* hidden = */ f && hasFileData); } } } } void DataTransfer::CacheExternalClipboardFormats() { @@ -1468,151 +1408,75 @@ DataTransfer::CacheExternalClipboardForm if (!clipboard || mClipboardType < 0) { return; } nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); nsCOMPtr<nsIPrincipal> sysPrincipal; ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal)); + // Check if the clipboard has any files + bool hasFileData = false; + const char *fileMime[] = { kFileMime }; + clipboard->HasDataMatchingFlavors(fileMime, 1, mClipboardType, &hasFileData); + // there isn't a way to get a list of the formats that might be available on // all platforms, so just check for the types that can actually be imported. // Note that the loop below assumes that kCustomTypesMime will be first. const char* formats[] = { kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime, - kURLMime, kURLDataMime, kUnicodeMime }; + kURLMime, kURLDataMime, kUnicodeMime, kPNGImageMime, + kJPEGImageMime, kGIFImageMime }; for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) { // check each format one at a time bool supported; clipboard->HasDataMatchingFlavors(&(formats[f]), 1, mClipboardType, &supported); // if the format is supported, add an item to the array with null as // the data. When retrieved, GetRealData will read the data. if (supported) { if (f == 0) { FillInExternalCustomTypes(0, sysPrincipal); } else { - CacheExternalData(formats[f], 0, sysPrincipal); + // If we aren't the file data, and we have file data, we want to be hidden + CacheExternalData(formats[f], 0, sysPrincipal, /* hidden = */ f != 1 && hasFileData); } } } } void -DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex) -{ - NS_PRECONDITION(mIsExternal, "Not an external data transfer"); - - if (aItem.mData) { - return; - } - - // only drag and paste events should be calling FillInExternalData - NS_ASSERTION(mEventMessage != eCut && mEventMessage != eCopy, - "clipboard event with empty data"); - - NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat); - const char* format = utf8format.get(); - if (strcmp(format, "text/plain") == 0) { - format = kUnicodeMime; - } else if (strcmp(format, "text/uri-list") == 0) { - format = kURLDataMime; - } - - nsCOMPtr<nsITransferable> trans = - do_CreateInstance("@mozilla.org/widget/transferable;1"); - if (!trans) { - return; - } - - trans->Init(nullptr); - trans->AddDataFlavor(format); - - if (mEventMessage == ePaste) { - MOZ_ASSERT(aIndex == 0, "index in clipboard must be 0"); - - nsCOMPtr<nsIClipboard> clipboard = - do_GetService("@mozilla.org/widget/clipboard;1"); - if (!clipboard || mClipboardType < 0) { - return; - } - - clipboard->GetData(trans, mClipboardType); - } else { - nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); - if (!dragSession) { - return; - } - -#ifdef DEBUG - // Since this is an external drag, the source document will always be null. - nsCOMPtr<nsIDOMDocument> domDoc; - dragSession->GetSourceDocument(getter_AddRefs(domDoc)); - MOZ_ASSERT(!domDoc); -#endif - - dragSession->GetData(trans, aIndex); - } - - uint32_t length = 0; - nsCOMPtr<nsISupports> data; - trans->GetTransferData(format, getter_AddRefs(data), &length); - if (!data) - return; - - RefPtr<nsVariantCC> variant = new nsVariantCC(); - - nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data); - if (supportsstr) { - nsAutoString str; - supportsstr->GetData(str); - variant->SetAsAString(str); - } - else { - nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data); - if (supportscstr) { - nsAutoCString str; - supportscstr->GetData(str); - variant->SetAsACString(str); - } else { - variant->SetAsISupports(data); - } - } - - aItem.mData = variant; -} - -void DataTransfer::FillAllExternalData() { if (mIsExternal) { - for (uint32_t i = 0; i < mItems.Length(); ++i) { - nsTArray<TransferItem>& itemArray = mItems[i]; - for (uint32_t j = 0; j < itemArray.Length(); ++j) { - if (!itemArray[j].mData) { - FillInExternalData(itemArray[j], i); - } + for (uint32_t i = 0; i < MozItemCount(); ++i) { + const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(i); + for (uint32_t j = 0; j < items.Length(); ++j) { + MOZ_ASSERT(items[j]->Index() == i); + + items[j]->FillInExternalData(); } } } } void DataTransfer::FillInExternalCustomTypes(uint32_t aIndex, nsIPrincipal* aPrincipal) { - TransferItem item; - item.mFormat.AssignLiteral(kCustomTypesMime); + RefPtr<DataTransferItem> item = new DataTransferItem(mItems, + NS_LITERAL_STRING(kCustomTypesMime)); + item->SetKind(DataTransferItem::KIND_STRING); + item->SetIndex(aIndex); - FillInExternalData(item, aIndex); - if (!item.mData) { + if (!item->Data()) { return; } - FillInExternalCustomTypes(item.mData, aIndex, aPrincipal); + FillInExternalCustomTypes(item->Data(), aIndex, aPrincipal); } void DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal) { char* chrs; uint32_t len = 0; @@ -1624,21 +1488,22 @@ DataTransfer::FillInExternalCustomTypes( nsAutoCString str; str.Adopt(chrs, len); nsCOMPtr<nsIInputStream> stringStream; NS_NewCStringInputStream(getter_AddRefs(stringStream), str); nsCOMPtr<nsIBinaryInputStream> stream = do_CreateInstance("@mozilla.org/binaryinputstream;1"); - stream->SetInputStream(stringStream); if (!stream) { return; } + stream->SetInputStream(stringStream); + uint32_t type; do { stream->Read32(&type); if (type == eCustomClipboardTypeId_String) { uint32_t formatLength; stream->Read32(&formatLength); char* formatBytes; stream->ReadBytes(formatLength, &formatBytes);
--- a/dom/events/DataTransfer.h +++ b/dom/events/DataTransfer.h @@ -27,38 +27,27 @@ class nsISupportsArray; class nsILoadContext; namespace mozilla { class EventStateManager; namespace dom { +class DataTransferItem; +class DataTransferItemList; class DOMStringList; class Element; class FileList; class Promise; template<typename T> class Optional; -/** - * TransferItem is used to hold data for a particular format. Each piece of - * data has a principal set from the caller which added it. This allows a - * caller that wishes to retrieve the data to only be able to access the data - * it is allowed to, yet still allow a chrome caller to retrieve any of the - * data. - */ -struct TransferItem { - nsString mFormat; - nsCOMPtr<nsIPrincipal> mPrincipal; - nsCOMPtr<nsIVariant> mData; -}; - #define NS_DATATRANSFER_IID \ -{ 0x43ee0327, 0xde5d, 0x463d, \ - { 0x9b, 0xd0, 0xf1, 0x79, 0x09, 0x69, 0xf2, 0xfb } } +{ 0x6c5f90d1, 0xa886, 0x42c8, \ + { 0x85, 0x06, 0x10, 0xbe, 0x5c, 0x0d, 0xc6, 0x77 } } class DataTransfer final : public nsIDOMDataTransfer, public nsWrapperCache { public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_DATATRANSFER_IID) NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -83,27 +72,26 @@ protected: DataTransfer(nsISupports* aParent, EventMessage aEventMessage, const uint32_t aEffectAllowed, bool aCursorState, bool aIsExternal, bool aUserCancelled, bool aIsCrossDomainSubFrameDrop, int32_t aClipboardType, - nsTArray<nsTArray<TransferItem> >& aItems, + DataTransferItemList* aItems, Element* aDragImage, uint32_t aDragImageX, uint32_t aDragImageY); ~DataTransfer(); static const char sEffects[8][9]; public: - // Constructor for DataTransfer. // // aIsExternal must only be true when used to create a dataTransfer for a // paste or a drag that was started without using a data transfer. The // latter will occur when an external drag occurs, that is, a drag where the // source is another application, or a drag is started by calling the drag // service directly. For clipboard operations, aClipboardType indicates // which clipboard to use, from nsIClipboard, or -1 for non-clipboard @@ -144,36 +132,33 @@ public: } else { aEffectAllowed.AssignASCII(sEffects[mEffectAllowed]); } } void SetDragImage(Element& aElement, int32_t aX, int32_t aY, ErrorResult& aRv); - already_AddRefed<DOMStringList> Types() const; + already_AddRefed<DOMStringList> GetTypes(ErrorResult& rv) const; void GetData(const nsAString& aFormat, nsAString& aData, ErrorResult& aRv); void SetData(const nsAString& aFormat, const nsAString& aData, ErrorResult& aRv); void ClearData(const mozilla::dom::Optional<nsAString>& aFormat, mozilla::ErrorResult& aRv); FileList* GetFiles(mozilla::ErrorResult& aRv); already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv); void AddElement(Element& aElement, mozilla::ErrorResult& aRv); - uint32_t MozItemCount() const - { - return mItems.Length(); - } + uint32_t MozItemCount() const; void GetMozCursor(nsString& aCursor) { if (mCursorState) { aCursor.AssignLiteral("default"); } else { aCursor.AssignLiteral("auto"); } @@ -205,18 +190,24 @@ public: return mDragTarget; } nsresult GetDataAtNoSecurityCheck(const nsAString& aFormat, uint32_t aIndex, nsIVariant** aData); // a readonly dataTransfer cannot have new data added or existing data // removed. Only the dropEffect and effectAllowed may be modified. + DataTransferItemList* Items() const { return mItems; } + + bool IsReadOnly() const { return mReadOnly; } void SetReadOnly() { mReadOnly = true; } + int32_t ClipboardType() const { return mClipboardType; } + EventMessage GetEventMessage() const { return mEventMessage; } + // converts the data into an array of nsITransferable objects to be used for // drag and drop or clipboard operations. already_AddRefed<nsISupportsArray> GetTransferables(nsIDOMNode* aDragTarget); already_AddRefed<nsISupportsArray> GetTransferables(nsILoadContext* aLoadContext); // converts the data for a single item at aIndex into an nsITransferable @@ -255,42 +246,35 @@ public: *aY = mDragImageY; return mDragImage; } nsresult Clone(nsISupports* aParent, EventMessage aEventMessage, bool aUserCancelled, bool aIsCrossDomainSubFrameDrop, DataTransfer** aResult); -protected: - // converts some formats used for compatibility in aInFormat into aOutFormat. // Text and text/unicode become text/plain, and URL becomes text/uri-list void GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat) const; +protected: + // caches text and uri-list data formats that exist in the drag service or // clipboard for retrieval later. - void CacheExternalData(const char* aFormat, uint32_t aIndex, - nsIPrincipal* aPrincipal); + nsresult CacheExternalData(const char* aFormat, uint32_t aIndex, + nsIPrincipal* aPrincipal, bool aHidden); // caches the formats that exist in the drag service that were added by an // external drag void CacheExternalDragFormats(); // caches the formats that exist in the clipboard void CacheExternalClipboardFormats(); - // fills in the data field of aItem with the data from the drag service or - // clipboard for a given index. - void FillInExternalData(TransferItem& aItem, uint32_t aIndex); - - - FileList* GetFileListInternal(ErrorResult& aRv, - nsIPrincipal* aSubjectPrincipal); - + FileList* GetFilesInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal); nsresult GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex, nsIPrincipal* aSubjectPrincipal, nsIVariant** aData); nsresult SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aSubjectPrincipal); friend class ContentParent; @@ -332,22 +316,18 @@ protected: // true if this is a cross-domain drop from a subframe where access to the // data should be prevented bool mIsCrossDomainSubFrameDrop; // Indicates which clipboard type to use for clipboard operations. Ignored for // drag and drop. int32_t mClipboardType; - // array of items, each containing an array of format->data pairs - nsTArray<nsTArray<TransferItem> > mItems; - - // array of files and directories, containing only the files present in the - // dataTransfer - RefPtr<FileList> mFileList; + // The items contained with the DataTransfer + RefPtr<DataTransferItemList> mItems; // the target of the drag. The drag and dragend events will fire at this. nsCOMPtr<mozilla::dom::Element> mDragTarget; // the custom drag image and coordinates within the image. If mDragImage is // null, the default image is created from the drag target. nsCOMPtr<mozilla::dom::Element> mDragImage; uint32_t mDragImageX;
new file mode 100644 --- /dev/null +++ b/dom/events/DataTransferItem.cpp @@ -0,0 +1,368 @@ +/* -*- Mode: C++; 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 "DataTransferItem.h" +#include "DataTransferItemList.h" + +#include "mozilla/ContentEvents.h" +#include "mozilla/EventForwards.h" +#include "mozilla/dom/DataTransferItemBinding.h" +#include "nsIClipboard.h" +#include "nsISupportsPrimitives.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsContentUtils.h" +#include "nsVariant.h" + +namespace { + +struct FileMimeNameData +{ + const char* mMimeName; + const char* mFileName; +}; + +FileMimeNameData kFileMimeNameMap[] = { + { kFileMime, "GenericFileName" }, + { kJPEGImageMime, "GenericImageNameJPEG" }, + { kGIFImageMime, "GenericImageNameGIF" }, + { kPNGImageMime, "GenericImageNamePNG" }, +}; + +} // anonymous namespace + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItem, mData, + mPrincipal, mParent) +NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItem) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItem) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItem) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* +DataTransferItem::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return DataTransferItemBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<DataTransferItem> +DataTransferItem::Clone(DataTransferItemList* aParent) const +{ + MOZ_ASSERT(aParent); + + RefPtr<DataTransferItem> it = new DataTransferItem(aParent, mType); + + // Copy over all of the fields + it->mKind = mKind; + it->mIndex = mIndex; + it->mData = mData; + it->mPrincipal = mPrincipal; + + return it.forget(); +} + +void +DataTransferItem::SetType(const nsAString& aType) +{ + mType = aType; +} + +void +DataTransferItem::SetData(nsIVariant* aData) +{ + if (!aData) { + // We are holding a temporary null which will later be filled. + // These are provided by the system, and have guaranteed properties about + // their kind based on their type. + MOZ_ASSERT(!mType.IsEmpty()); + + mKind = KIND_STRING; + for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) { + if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) { + mKind = KIND_FILE; + break; + } + } + + mData = nullptr; + return; + } + + mKind = KIND_OTHER; + mData = aData; + + nsCOMPtr<nsISupports> supports; + nsresult rv = aData->GetAsISupports(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) { + RefPtr<File> file = FileFromISupports(supports); + if (file) { + mKind = KIND_FILE; + return; + } + } + + nsAutoString string; + // If we can't get the data type as a string, that means that the object + // should be considered to be of the "other" type. This is impossible + // through the APIs defined by the spec, but we provide extra Moz* APIs, + // which allow setting of non-string data. We determine whether we can + // consider it a string, by calling GetAsAString, and checking for success. + rv = aData->GetAsAString(string); + if (NS_SUCCEEDED(rv)) { + mKind = KIND_STRING; + } +} + +void +DataTransferItem::FillInExternalData() +{ + if (mData) { + return; + } + + NS_ConvertUTF16toUTF8 utf8format(mType); + const char* format = utf8format.get(); + if (strcmp(format, "text/plain") == 0) { + format = kUnicodeMime; + } else if (strcmp(format, "text/uri-list") == 0) { + format = kURLDataMime; + } + + nsCOMPtr<nsITransferable> trans = + do_CreateInstance("@mozilla.org/widget/transferable;1"); + if (NS_WARN_IF(!trans)) { + return; + } + + trans->Init(nullptr); + trans->AddDataFlavor(format); + + if (mParent->GetEventMessage() == ePaste) { + MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0"); + + nsCOMPtr<nsIClipboard> clipboard = + do_GetService("@mozilla.org/widget/clipboard;1"); + if (!clipboard || mParent->ClipboardType() < 0) { + return; + } + + nsresult rv = clipboard->GetData(trans, mParent->ClipboardType()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } else { + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (!dragSession) { + return; + } + + nsresult rv = dragSession->GetData(trans, mIndex); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } + + uint32_t length = 0; + nsCOMPtr<nsISupports> data; + nsresult rv = trans->GetTransferData(format, getter_AddRefs(data), &length); + if (NS_WARN_IF(NS_FAILED(rv) || !data)) { + return; + } + + if (Kind() == KIND_FILE) { + // Because this is an external piece of data, mType is one of kFileMime, + // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. We want to convert + // whatever type happens to actually be stored into a dom::File. + + RefPtr<File> file = FileFromISupports(data); + MOZ_ASSERT(file, "Invalid format for Kind() == KIND_FILE"); + + data = do_QueryObject(file); + } + + RefPtr<nsVariantCC> variant = new nsVariantCC(); + + nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data); + if (supportsstr) { + MOZ_ASSERT(Kind() == KIND_STRING); + nsAutoString str; + supportsstr->GetData(str); + variant->SetAsAString(str); + } else { + nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data); + if (supportscstr) { + MOZ_ASSERT(Kind() == KIND_STRING); + nsAutoCString str; + supportscstr->GetData(str); + variant->SetAsACString(str); + } else { + MOZ_ASSERT(Kind() == KIND_FILE); + variant->SetAsISupports(data); + } + } + + SetData(variant); +} + +already_AddRefed<File> +DataTransferItem::GetAsFile(ErrorResult& aRv) +{ + if (mKind != KIND_FILE) { + return nullptr; + } + + nsIVariant* data = Data(); + if (NS_WARN_IF(!data)) { + return nullptr; + } + + nsCOMPtr<nsISupports> supports; + aRv = data->GetAsISupports(getter_AddRefs(supports)); + MOZ_ASSERT(!aRv.Failed() && supports, + "Files should be stored with type dom::File!"); + if (aRv.Failed() || !supports) { + return nullptr; + } + + RefPtr<File> file = FileFromISupports(supports); + MOZ_ASSERT(file, "Files should be stored with type dom::File!"); + if (!file) { + return nullptr; + } + + // The File object should have been stored as a File in the nsIVariant. If it + // was stored as a Blob, with a file BlobImpl, we could still get to this + // point, except that file is a new File object, with a different identity + // then the original blob ibject. This should never happen so we assert + // against it. + MOZ_ASSERT(SameCOMIdentity(supports, NS_ISUPPORTS_CAST(nsIDOMBlob*, file)), + "Got back a new File object from FileFromISupports in GetAsFile!"); + + return file.forget(); +} + +already_AddRefed<File> +DataTransferItem::FileFromISupports(nsISupports* aSupports) +{ + MOZ_ASSERT(aSupports); + + RefPtr<File> file; + + nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(aSupports); + if (domBlob) { + // Get out the blob - this is OK, because nsIDOMBlob is a builtinclass + // and the only implementer is Blob. + Blob* blob = static_cast<Blob*>(domBlob.get()); + file = blob->ToFile(); + } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(aSupports)) { + printf("Creating a File from a nsIFile!\n"); + file = File::CreateFromFile(GetParentObject(), ifile); + } else if (nsCOMPtr<nsIInputStream> stream = do_QueryInterface(aSupports)) { + // This consumes the stream object + ErrorResult rv; + file = CreateFileFromInputStream(GetParentObject(), stream, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + } + } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(aSupports)) { + MOZ_ASSERT(blobImpl->IsFile()); + file = File::Create(GetParentObject(), blobImpl); + } + + return file.forget(); +} + +already_AddRefed<File> +DataTransferItem::CreateFileFromInputStream(nsISupports* aParent, + nsIInputStream* aStream, + ErrorResult& aRv) +{ + const char* key = nullptr; + for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) { + if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) { + key = kFileMimeNameMap[i].mFileName; + break; + } + } + if (!key) { + MOZ_ASSERT_UNREACHABLE("Unsupported mime type"); + key = "GenericFileName"; + } + + nsXPIDLString fileName; + aRv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + key, fileName); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + uint64_t available; + aRv = aStream->Available(&available); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + void* data = nullptr; + aRv = NS_ReadInputStreamToBuffer(aStream, &data, available); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return File::CreateMemoryFile(aParent, data, available, fileName, + mType, PR_Now()); +} + +void +DataTransferItem::GetAsString(FunctionStringCallback* aCallback, + ErrorResult& aRv) +{ + if (!aCallback || mKind != KIND_STRING) { + return; + } + + nsIVariant* data = Data(); + if (NS_WARN_IF(!data)) { + return; + } + + nsAutoString stringData; + data->GetAsAString(stringData); + + // Dispatch the callback to the main thread + class GASRunnable final : public Runnable + { + public: + GASRunnable(FunctionStringCallback* aCallback, + const nsAString& aStringData) + : mCallback(aCallback), mStringData(aStringData) + {} + + NS_IMETHOD Run() override + { + ErrorResult rv; + mCallback->Call(mStringData, rv); + NS_WARN_IF(rv.Failed()); + return rv.StealNSResult(); + } + private: + RefPtr<FunctionStringCallback> mCallback; + nsString mStringData; + }; + + RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData); + nsresult rv = NS_DispatchToMainThread(runnable); + if (NS_FAILED(rv)) { + NS_WARNING("NS_DispatchToMainThread Failed in " + "DataTransferItem::GetAsString!"); + } +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/events/DataTransferItem.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef mozilla_dom_DataTransferItem_h +#define mozilla_dom_DataTransferItem_h + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/DOMString.h" +#include "mozilla/dom/File.h" + +namespace mozilla { +namespace dom { + +class FunctionStringCallback; + +class DataTransferItem final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DataTransferItem); + +public: + // The spec only talks about the "file" and "string" kinds. Due to the Moz* + // APIs, it is possible to attach any type to a DataTransferItem, meaning that + // we can have other kinds then just FILE and STRING. These others are simply + // marked as "other" and can only be produced throug the Moz* APIs. + enum eKind { + KIND_FILE, + KIND_STRING, + KIND_OTHER, + }; + + DataTransferItem(DataTransferItemList* aParent, const nsAString& aType) + : mIndex(0), mChromeOnly(false), mKind(KIND_OTHER), mType(aType), mParent(aParent) + {} + + already_AddRefed<DataTransferItem> Clone(DataTransferItemList* aParent) const; + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + void GetAsString(FunctionStringCallback* aCallback, ErrorResult& aRv); + void GetKind(nsAString& aKind) const + { + switch (mKind) { + case KIND_FILE: + aKind = NS_LITERAL_STRING("file"); + return; + case KIND_STRING: + aKind = NS_LITERAL_STRING("string"); + return; + default: + aKind = NS_LITERAL_STRING("other"); + return; + } + } + + void GetType(nsAString& aType) const + { + aType = mType; + } + void SetType(const nsAString& aType); + + eKind Kind() const + { + return mKind; + } + void SetKind(eKind aKind) + { + mKind = aKind; + } + + already_AddRefed<File> GetAsFile(ErrorResult& aRv); + + DataTransferItemList* GetParentObject() const + { + return mParent; + } + + nsIPrincipal* Principal() const + { + return mPrincipal; + } + void SetPrincipal(nsIPrincipal* aPrincipal) + { + mPrincipal = aPrincipal; + } + + nsIVariant* Data() + { + if (!mData) { + FillInExternalData(); + } + return mData; + } + void SetData(nsIVariant* aData); + + uint32_t Index() const + { + return mIndex; + } + void SetIndex(uint32_t aIndex) + { + mIndex = aIndex; + } + void FillInExternalData(); + + bool ChromeOnly() const + { + return mChromeOnly; + } + void SetChromeOnly(bool aChromeOnly) + { + mChromeOnly = aChromeOnly; + } + +private: + ~DataTransferItem() {} + already_AddRefed<File> FileFromISupports(nsISupports* aParent); + already_AddRefed<File> CreateFileFromInputStream(nsISupports* aParent, + nsIInputStream* aStream, + ErrorResult& aRv); + + // The index in the 2d mIndexedItems array + uint32_t mIndex; + + bool mChromeOnly; + eKind mKind; + nsString mType; + nsCOMPtr<nsIVariant> mData; + nsCOMPtr<nsIPrincipal> mPrincipal; + RefPtr<DataTransferItemList> mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_DataTransferItem_h */
new file mode 100644 --- /dev/null +++ b/dom/events/DataTransferItemList.cpp @@ -0,0 +1,590 @@ +/* -*- Mode: C++; 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 "DataTransferItemList.h" + +#include "nsContentUtils.h" +#include "nsIGlobalObject.h" +#include "nsIClipboard.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsISupportsPrimitives.h" +#include "nsQueryObject.h" +#include "nsVariant.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/EventForwards.h" +#include "mozilla/storage/Variant.h" +#include "mozilla/dom/DataTransferItemListBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItemList, mParent, mItems, + mIndexedItems, mFiles) +NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItemList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItemList) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItemList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* +DataTransferItemList::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) +{ + return DataTransferItemListBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<DataTransferItemList> +DataTransferItemList::Clone(DataTransfer* aParent) const +{ + RefPtr<DataTransferItemList> list = + new DataTransferItemList(aParent, mIsExternal, mIsCrossDomainSubFrameDrop); + + // We need to clone the mItems and mIndexedItems lists while keeping the same + // correspondences between the mIndexedItems and mItems lists (namely, if an + // item is in mIndexedItems, and mItems it must have the same new identity) + + // First, we copy over indexedItems, and clone every entry. Then, we go over + // mItems. For every entry, we use its mIndex property to locate it in + // mIndexedItems on the original DataTransferItemList, and then copy over the + // reference from the same index pair on the new DataTransferItemList + + list->mIndexedItems.SetLength(mIndexedItems.Length()); + list->mItems.SetLength(mItems.Length()); + + // Copy over mIndexedItems, cloning every entry + for (uint32_t i = 0; i < mIndexedItems.Length(); i++) { + const nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i]; + nsTArray<RefPtr<DataTransferItem>>& newItems = list->mIndexedItems[i]; + newItems.SetLength(items.Length()); + for (uint32_t j = 0; j < items.Length(); j++) { + newItems[j] = items[j]->Clone(list); + } + } + + // Copy over mItems, getting the actual entries from mIndexedItems + for (uint32_t i = 0; i < mItems.Length(); i++) { + uint32_t index = mItems[i]->Index(); + MOZ_ASSERT(index < mIndexedItems.Length()); + uint32_t subIndex = mIndexedItems[index].IndexOf(mItems[i]); + + // Copy over the reference + list->mItems[i] = list->mIndexedItems[index][subIndex]; + } + + return list.forget(); +} + +void +DataTransferItemList::Remove(uint32_t aIndex, ErrorResult& aRv) +{ + if (IsReadOnly()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + if (aIndex >= Length()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + ClearDataHelper(mItems[aIndex], aIndex, -1, aRv); +} + +DataTransferItem* +DataTransferItemList::IndexedGetter(uint32_t aIndex, bool& aFound, ErrorResult& aRv) const +{ + if (aIndex >= mItems.Length()) { + aFound = false; + return nullptr; + } + + RefPtr<DataTransferItem> item = mItems[aIndex]; + + // Check if the caller is allowed to access the drag data. Callers with + // chrome privileges can always read the data. During the + // drop event, allow retrieving the data except in the case where the + // source of the drag is in a child frame of the caller. In that case, + // we only allow access to data of the same principal. During other events, + // only allow access to the data with the same principal. + nsIPrincipal* principal = nullptr; + if (mIsCrossDomainSubFrameDrop) { + principal = nsContentUtils::SubjectPrincipal(); + } + + if (item->Principal() && principal && + !principal->Subsumes(item->Principal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + aFound = false; + return nullptr; + } + + nsCOMPtr<nsIVariant> variantData = item->Data(); + nsCOMPtr<nsISupports> data; + if (variantData && + NS_SUCCEEDED(variantData->GetAsISupports(getter_AddRefs(data))) && + data) { + // Make sure the code that is calling us is same-origin with the data. + nsCOMPtr<EventTarget> pt = do_QueryInterface(data); + if (pt) { + nsresult rv = NS_OK; + nsIScriptContext* c = pt->GetContextForEventHandlers(&rv); + if (NS_WARN_IF(NS_FAILED(rv) || !c)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsIGlobalObject* go = c->GetGlobalObject(); + if (NS_WARN_IF(!go)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go); + MOZ_ASSERT(sp, "This cannot fail on the main thread."); + + nsIPrincipal* dataPrincipal = sp->GetPrincipal(); + if (!principal) { + principal = nsContentUtils::SubjectPrincipal(); + } + if (NS_WARN_IF(!dataPrincipal || !principal->Equals(dataPrincipal))) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + } + } + + aFound = true; + return item; +} + +uint32_t +DataTransferItemList::MozItemCount() const +{ + uint32_t length = mIndexedItems.Length(); + // XXX: Compat hack - Index 0 always exists due to changes in internals, but + // if it is empty, scripts using the moz* APIs should see it as not existing. + if (length == 1 && mIndexedItems[0].IsEmpty()) { + return 0; + } + return length; +} + +void +DataTransferItemList::Clear(ErrorResult& aRv) +{ + if (NS_WARN_IF(IsReadOnly())) { + return; + } + + uint32_t count = Length(); + for (uint32_t i = 0; i < count; i++) { + // We always remove the last item first, to avoid moving items around in + // memory as much + Remove(Length() - 1, aRv); + ENSURE_SUCCESS_VOID(aRv); + } + + MOZ_ASSERT(Length() == 0); +} + +DataTransferItem* +DataTransferItemList::Add(const nsAString& aData, + const nsAString& aType, + ErrorResult& aRv) +{ + if (NS_WARN_IF(IsReadOnly())) { + return nullptr; + } + + nsCOMPtr<nsIVariant> data(new storage::TextVariant(aData)); + + nsAutoString format; + mParent->GetRealFormat(aType, format); + + // We add the textual data to index 0. We set aInsertOnly to true, as we don't + // want to update an existing entry if it is already present, as per the spec. + RefPtr<DataTransferItem> item = + SetDataWithPrincipal(format, data, 0, + nsContentUtils::SubjectPrincipal(), + /* aInsertOnly = */ true, + /* aHidden = */ false, + aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + MOZ_ASSERT(item->Kind() != DataTransferItem::KIND_FILE); + + return item; +} + +DataTransferItem* +DataTransferItemList::Add(File& aData, ErrorResult& aRv) +{ + if (IsReadOnly()) { + return nullptr; + } + + nsCOMPtr<nsISupports> supports = do_QueryObject(&aData); + nsCOMPtr<nsIWritableVariant> data = new nsVariant(); + data->SetAsISupports(supports); + + nsAutoString type; + aData.GetType(type); + + + // We need to add this as a new item, as multiple files can't exist in the + // same item in the Moz DataTransfer layout. It will be appended at the end of + // the internal specced layout. + uint32_t index = mIndexedItems.Length(); + RefPtr<DataTransferItem> item = + SetDataWithPrincipal(type, data, index, + nsContentUtils::SubjectPrincipal(), + true, false, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + MOZ_ASSERT(item->Kind() == DataTransferItem::KIND_FILE); + + return item; +} + +FileList* +DataTransferItemList::Files() +{ + if (!mFiles) { + mFiles = new FileList(static_cast<nsIDOMDataTransfer*>(mParent)); + RegenerateFiles(); + } + + return mFiles; +} + +void +DataTransferItemList::MozRemoveByTypeAt(const nsAString& aType, + uint32_t aIndex, + ErrorResult& aRv) +{ + if (NS_WARN_IF(IsReadOnly() || + aIndex >= mIndexedItems.Length())) { + return; + } + + bool removeAll = aType.IsEmpty(); + + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aIndex]; + uint32_t count = items.Length(); + // We remove the last item of the list repeatedly - that way we don't + // have to worry about modifying the loop iterator + if (removeAll) { + for (uint32_t i = 0; i < count; ++i) { + uint32_t index = items.Length() - 1; + MOZ_ASSERT(index == count - i - 1); + + ClearDataHelper(items[index], -1, index, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } + + // items is no longer a valid reference, as removing the last element from + // it via ClearDataHelper invalidated it. so we can't MOZ_ASSERT that the + // length is now 0. + return; + } + + for (uint32_t i = 0; i < count; ++i) { + nsAutoString type; + items[i]->GetType(type); + if (type == aType) { + ClearDataHelper(items[i], -1, i, aRv); + return; + } + } +} + +DataTransferItem* +DataTransferItemList::MozItemByTypeAt(const nsAString& aType, uint32_t aIndex) +{ + if (NS_WARN_IF(aIndex >= mIndexedItems.Length())) { + return nullptr; + } + + uint32_t count = mIndexedItems[aIndex].Length(); + for (uint32_t i = 0; i < count; i++) { + RefPtr<DataTransferItem> item = mIndexedItems[aIndex][i]; + nsString type; + item->GetType(type); + if (type.Equals(aType)) { + return item; + } + } + + return nullptr; +} + +already_AddRefed<DataTransferItem> +DataTransferItemList::SetDataWithPrincipal(const nsAString& aType, + nsIVariant* aData, + uint32_t aIndex, + nsIPrincipal* aPrincipal, + bool aInsertOnly, + bool aHidden, + ErrorResult& aRv) +{ + if (aIndex < mIndexedItems.Length()) { + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aIndex]; + uint32_t count = items.Length(); + for (uint32_t i = 0; i < count; i++) { + RefPtr<DataTransferItem> item = items[i]; + nsString type; + item->GetType(type); + if (type.Equals(aType)) { + if (NS_WARN_IF(aInsertOnly)) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + // don't allow replacing data that has a stronger principal + bool subsumes; + if (NS_WARN_IF(item->Principal() && aPrincipal && + (NS_FAILED(aPrincipal->Subsumes(item->Principal(), + &subsumes)) + || !subsumes))) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + item->SetPrincipal(aPrincipal); + + DataTransferItem::eKind oldKind = item->Kind(); + item->SetData(aData); + + if (aIndex != 0) { + // If the item changes from being a file to not a file or vice-versa, + // its presence in the mItems array may need to change. + if (item->Kind() == DataTransferItem::KIND_FILE && + oldKind != DataTransferItem::KIND_FILE) { + // not file => file + mItems.AppendElement(item); + } else if (item->Kind() != DataTransferItem::KIND_FILE && + oldKind == DataTransferItem::KIND_FILE) { + // file => not file + mItems.RemoveElement(item); + } + } + + // Regenerate the Files array if we have modified a file's status + if (item->Kind() == DataTransferItem::KIND_FILE || + oldKind == DataTransferItem::KIND_FILE) { + RegenerateFiles(); + } + + return item.forget(); + } + } + } else { + // Make sure that we aren't adding past the end of the mIndexedItems array. + // XXX Should this be a MOZ_ASSERT instead? + aIndex = mIndexedItems.Length(); + } + + // Add the new item + RefPtr<DataTransferItem> item = AppendNewItem(aIndex, aType, aData, aPrincipal, aHidden); + + if (item->Kind() == DataTransferItem::KIND_FILE) { + RegenerateFiles(); + } + + return item.forget(); +} + +DataTransferItem* +DataTransferItemList::AppendNewItem(uint32_t aIndex, + const nsAString& aType, + nsIVariant* aData, + nsIPrincipal* aPrincipal, + bool aHidden) +{ + if (mIndexedItems.Length() <= aIndex) { + MOZ_ASSERT(mIndexedItems.Length() == aIndex); + mIndexedItems.AppendElement(); + } + RefPtr<DataTransferItem> item = new DataTransferItem(this, aType); + item->SetIndex(aIndex); + item->SetPrincipal(aPrincipal); + item->SetData(aData); + item->SetChromeOnly(aHidden); + + mIndexedItems[aIndex].AppendElement(item); + + // We only want to add the item to the main mItems list if the index we are + // adding to is 0, or the item we are adding is a file. If we add an item + // which is not a file to a non-zero index, invariants could be broken. + // (namely the invariant that there are not 2 non-file entries in the items + // array with the same type) + if (!aHidden && (item->Kind() == DataTransferItem::KIND_FILE || aIndex == 0)) { + mItems.AppendElement(item); + } + + return item; +} + +const nsTArray<RefPtr<DataTransferItem>>* +DataTransferItemList::MozItemsAt(uint32_t aIndex) // -- INDEXED +{ + if (aIndex >= mIndexedItems.Length()) { + return nullptr; + } + + return &mIndexedItems[aIndex]; +} + +bool +DataTransferItemList::IsReadOnly() const +{ + MOZ_ASSERT(mParent); + return mParent->IsReadOnly(); +} + +int32_t +DataTransferItemList::ClipboardType() const +{ + MOZ_ASSERT(mParent); + return mParent->ClipboardType(); +} + +EventMessage +DataTransferItemList::GetEventMessage() const +{ + MOZ_ASSERT(mParent); + return mParent->GetEventMessage(); +} + +void +DataTransferItemList::PopIndexZero() +{ + MOZ_ASSERT(mIndexedItems.Length() > 1); + MOZ_ASSERT(mIndexedItems[0].IsEmpty()); + + mIndexedItems.RemoveElementAt(0); + + // Update the index of every element which has now been shifted + for (uint32_t i = 0; i < mIndexedItems.Length(); i++) { + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i]; + for (uint32_t j = 0; j < items.Length(); j++) { + items[j]->SetIndex(i); + } + } +} + +void +DataTransferItemList::ClearAllItems() +{ + // We always need to have index 0, so don't delete that one + mItems.Clear(); + mIndexedItems.Clear(); + mIndexedItems.SetLength(1); + + // Re-generate files (into an empty list) + RegenerateFiles(); +} + +void +DataTransferItemList::ClearDataHelper(DataTransferItem* aItem, + uint32_t aIndexHint, + uint32_t aMozOffsetHint, + ErrorResult& aRv) +{ + MOZ_ASSERT(aItem); + if (NS_WARN_IF(IsReadOnly())) { + return; + } + + nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(); + if (aItem->Principal() && principal && + !principal->Subsumes(aItem->Principal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + // Check if the aIndexHint is actually the index, and then remove the item + // from aItems + ErrorResult rv; + bool found; + if (IndexedGetter(aIndexHint, found, rv) == aItem) { + mItems.RemoveElementAt(aIndexHint); + } else { + mItems.RemoveElement(aItem); + } + rv.SuppressException(); + + // Check if the aMozIndexHint and aMozOffsetHint are actually the index and + // offset, and then remove them from mIndexedItems + MOZ_ASSERT(aItem->Index() < mIndexedItems.Length()); + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aItem->Index()]; + if (aMozOffsetHint < items.Length() && aItem == items[aMozOffsetHint]) { + items.RemoveElementAt(aMozOffsetHint); + } else { + items.RemoveElement(aItem); + } + + // Check if we should remove the index. We never remove index 0. + if (items.Length() == 0 && aItem->Index() != 0) { + mIndexedItems.RemoveElementAt(aItem->Index()); + + // Update the index of every element which has now been shifted + for (uint32_t i = aItem->Index(); i < mIndexedItems.Length(); i++) { + nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i]; + for (uint32_t j = 0; j < items.Length(); j++) { + items[j]->SetIndex(i); + } + } + } + + // Give the removed item the invalid index + aItem->SetIndex(-1); + + if (aItem->Kind() == DataTransferItem::KIND_FILE) { + RegenerateFiles(); + } +} + +void +DataTransferItemList::RegenerateFiles() +{ + // We don't want to regenerate the files list unless we already have a files + // list. That way we can avoid the unnecessary work if the user never touches + // the files list. + if (mFiles) { + // We clear the list rather than performing smaller updates, because it + // simplifies the logic greatly on this code path, which should be very + // infrequently used. + mFiles->Clear(); + + uint32_t count = Length(); + for (uint32_t i = 0; i < count; i++) { + ErrorResult rv; + bool found; + RefPtr<DataTransferItem> item = IndexedGetter(i, found, rv); + if (NS_WARN_IF(!found || rv.Failed())) { + continue; + } + + if (item->Kind() == DataTransferItem::KIND_FILE) { + RefPtr<File> file = item->GetAsFile(rv); + if (NS_WARN_IF(rv.Failed() || !file)) { + continue; + } + mFiles->Append(file); + } + } + } +} + +} // namespace mozilla +} // namespace dom
new file mode 100644 --- /dev/null +++ b/dom/events/DataTransferItemList.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef mozilla_dom_DataTransferItemList_h +#define mozilla_dom_DataTransferItemList_h + +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/DataTransferItem.h" +#include "mozilla/dom/FileList.h" + +namespace mozilla { +namespace dom { + +class DataTransferItem; + +class DataTransferItemList final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DataTransferItemList); + + DataTransferItemList(DataTransfer* aParent, bool aIsExternal, + bool aIsCrossDomainSubFrameDrop) + : mParent(aParent) + , mIsCrossDomainSubFrameDrop(aIsCrossDomainSubFrameDrop) + , mIsExternal(aIsExternal) + { + // We always allocate an index 0 in our DataTransferItemList. This is done + // in order to maintain the invariants according to the spec. Mainly, within + // the spec's list, there is intended to be a single copy of each mime type, + // for string typed items. File typed items are allowed to have duplicates. + // In the old moz* system, this was modeled by having multiple indexes, each + // of which was independent. Files were fetched from all indexes, but + // strings were only fetched from the first index. In order to maintain this + // correlation and avoid breaking code with the new changes, index 0 is now + // always present and used to store strings, and all file items are given + // their own index starting at index 1. + mIndexedItems.SetLength(1); + } + + already_AddRefed<DataTransferItemList> Clone(DataTransfer* aParent) const; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + uint32_t Length() const + { + return mItems.Length(); + }; + + DataTransferItem* Add(const nsAString& aData, const nsAString& aType, + ErrorResult& rv); + DataTransferItem* Add(File& aData, ErrorResult& aRv); + + void Remove(uint32_t aIndex, ErrorResult& aRv); + + DataTransferItem* IndexedGetter(uint32_t aIndex, bool& aFound, + ErrorResult& aRv) const; + + void Clear(ErrorResult& aRv); + + DataTransfer* GetParentObject() const + { + return mParent; + } + + // Accessors for data from ParentObject + bool IsReadOnly() const; + int32_t ClipboardType() const; + EventMessage GetEventMessage() const; + + already_AddRefed<DataTransferItem> + SetDataWithPrincipal(const nsAString& aType, nsIVariant* aData, + uint32_t aIndex, nsIPrincipal* aPrincipal, + bool aInsertOnly, bool aHidden, ErrorResult& aRv); + + FileList* Files(); + + // Moz-style helper methods for interacting with the stored data + void MozRemoveByTypeAt(const nsAString& aType, uint32_t aIndex, + ErrorResult& aRv); + DataTransferItem* MozItemByTypeAt(const nsAString& aType, uint32_t aIndex); + const nsTArray<RefPtr<DataTransferItem>>* MozItemsAt(uint32_t aIndex); + uint32_t MozItemCount() const; + + // Causes everything in indexes above 0 to shift down one index. + void PopIndexZero(); + + // Delete every item in the DataTransferItemList, without checking for + // permissions or read-only status (for internal use only). + void ClearAllItems(); + +private: + void ClearDataHelper(DataTransferItem* aItem, uint32_t aIndexHint, + uint32_t aMozOffsetHint, ErrorResult& aRv); + DataTransferItem* AppendNewItem(uint32_t aIndex, const nsAString& aType, + nsIVariant* aData, nsIPrincipal* aPrincipal, + bool aHidden); + void RegenerateFiles(); + + ~DataTransferItemList() {} + + RefPtr<DataTransfer> mParent; + bool mIsCrossDomainSubFrameDrop; + bool mIsExternal; + RefPtr<FileList> mFiles; + nsTArray<RefPtr<DataTransferItem>> mItems; + nsTArray<nsTArray<RefPtr<DataTransferItem>>> mIndexedItems; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_DataTransferItemList_h
--- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -1807,17 +1807,17 @@ EventStateManager::GenerateDragGesture(n // Emit observer event to allow addons to modify the DataTransfer object. if (observerService) { observerService->NotifyObservers(dataTransfer, "on-datatransfer-available", nullptr); } // now that the dataTransfer has been updated in the dragstart and - // draggesture events, make it read only so that the data doesn't + // draggesture events, make it readonly so that the data doesn't // change during the drag. dataTransfer->SetReadOnly(); if (status != nsEventStatus_eConsumeNoDefault) { bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer, targetContent, selection); if (dragStarted) { sActiveESM = nullptr;
--- a/dom/events/IMEStateManager.cpp +++ b/dom/events/IMEStateManager.cpp @@ -1024,19 +1024,20 @@ IMEStateManager::SetIMEState(const IMESt nsIContent* inputContent = aContent->FindFirstNonChromeOnlyAccessContent(); // If we don't have an action hint and // return won't submit the form, use "next". if (context.mActionHint.IsEmpty() && inputContent->IsHTMLElement(nsGkAtoms::input)) { bool willSubmit = false; nsCOMPtr<nsIFormControl> control(do_QueryInterface(inputContent)); - mozilla::dom::Element* formElement = control->GetFormElement(); + mozilla::dom::Element* formElement = nullptr; nsCOMPtr<nsIForm> form; if (control) { + formElement = control->GetFormElement(); // is this a form and does it have a default submit element? if ((form = do_QueryInterface(formElement)) && form->GetDefaultSubmitElement()) { willSubmit = true; // is this an html form and does it only have a single text input element? } else if (formElement && formElement->IsHTMLElement(nsGkAtoms::form) && !static_cast<dom::HTMLFormElement*>(formElement)-> ImplicitSubmissionIsDisabled()) {
--- a/dom/events/moz.build +++ b/dom/events/moz.build @@ -39,16 +39,18 @@ EXPORTS.mozilla.dom += [ 'BeforeAfterKeyboardEvent.h', 'BeforeUnloadEvent.h', 'ClipboardEvent.h', 'CommandEvent.h', 'CompositionEvent.h', 'CustomEvent.h', 'DataContainerEvent.h', 'DataTransfer.h', + 'DataTransferItem.h', + 'DataTransferItemList.h', 'DeviceMotionEvent.h', 'DragEvent.h', 'Event.h', 'EventTarget.h', 'FocusEvent.h', 'ImageCaptureError.h', 'InputEvent.h', 'KeyboardEvent.h', @@ -117,16 +119,18 @@ UNIFIED_SOURCES += [ 'UIEvent.cpp', 'WheelEvent.cpp', 'WheelHandlingHelper.cpp', 'XULCommandEvent.cpp', ] # nsEventStateManager.cpp should be built separately because of Mac OS X headers. SOURCES += [ + 'DataTransferItem.cpp', + 'DataTransferItemList.cpp', 'EventStateManager.cpp', ] if CONFIG['MOZ_WEBSPEECH']: UNIFIED_SOURCES += ['SpeechRecognitionError.cpp'] include('/ipc/chromium/chromium-config.mozbuild')
--- a/dom/events/test/chrome.ini +++ b/dom/events/test/chrome.ini @@ -20,8 +20,9 @@ support-files = [test_bug602962.xul] [test_bug617528.xul] [test_bug679494.xul] [test_bug930374-chrome.html] [test_bug1128787-1.html] [test_bug1128787-2.html] [test_bug1128787-3.html] [test_eventctors.xul] +[test_DataTransferItemList.html] \ No newline at end of file
--- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -195,8 +195,9 @@ support-files = [test_offsetxy.html] [test_eventhandler_scoping.html] [test_bug1013412.html] skip-if = buildapp == 'b2g' # no wheel events on b2g [test_dom_activate_event.html] [test_bug1264380.html] run-if = (e10s && os != "win") # Bug 1270043, crash at windows platforms; Bug1264380 comment 20, nsDragService::InvokeDragSessionImpl behaves differently among platform implementations in non-e10s mode which prevents us to check the validity of nsIDragService::getCurrentSession() consistently via synthesize mouse clicks in non-e10s mode. [test_passive_listeners.html] +[test_paste_image.html]
new file mode 100644 --- /dev/null +++ b/dom/events/test/test_DataTransferItemList.html @@ -0,0 +1,227 @@ +<html> +<head> + <title>Tests for the DatTransferItemList object</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body style="height: 300px; overflow: auto;"> +<p id="display"> </p> +<img id="image" draggable="true" src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82"> +<div id="over" "style="width: 100px; height: 100px; border: 2px black dashed;"> + drag over here +</div> + +<script> + function spin() { + // Defer to the event loop twice to wait for any events to be flushed out. + return new Promise(function(a) { + SimpleTest.executeSoon(function() { + SimpleTest.executeSoon(a) + }); + }); + } + + add_task(function* () { + yield spin(); + var draggable = document.getElementById('image'); + var over = document.getElementById('over'); + + var dragstartFired = 0; + draggable.addEventListener('dragstart', onDragStart); + function onDragStart(e) { + draggable.removeEventListener('dragstart', onDragStart); + + var dt = e.dataTransfer; + dragstartFired++; + + ok(true, "dragStart event fired"); + var dtList = e.dataTransfer.items; + ok(dtList instanceof DataTransferItemList, + "DataTransfer.items returns a DataTransferItemList"); + + for (var i = 0; i < dtList.length; i++) { + var item = dtList[i]; + ok(item instanceof DataTransferItem, + "operator[] returns DataTransferItem objects"); + if (item.kind == "file") { + var file = item.getAsFile(); + ok(file instanceof File, "getAsFile() returns File objects"); + } + } + + dtList.clear(); + is(dtList.length, 0, "after .clear() DataTransferItemList should be empty"); + + dtList.add("this is some text", "text/plain"); + dtList.add("<a href='www.mozilla.org'>this is a link</a>", "text/html"); + dtList.add("http://www.mozilla.org", "text/uri-list"); + dtList.add("this is custom-data", "custom-data"); + + + var file = new File(['<a id="a"><b id="b">hey!</b></a>'], "myfile.html", + {type: "text/html"}); + + dtList.add(file); + + checkTypes(["text/plain", "text/html", "text/uri-list", "custom-data", "text/html"], + dtList, "DataTransferItemList.add test"); + + var files = e.dataTransfer.files; + is(files.length, 1, "DataTransfer.files should contain the one file we added earlier"); + is(files[0], file, "It should be the same file as the file we originally created"); + is(file, e.dataTransfer.mozGetDataAt("text/html", 1), + "It should be stored in index 1 for mozGetDataAt"); + + var file2 = new File(['<a id="c"><b id="d">yo!</b></a>'], "myotherfile.html", + {type: "text/html"}); + dtList.add(file2); + + is(files.length, 2, "The files property should have been updated in place"); + is(files[1], file2, "It should be the same file as the file we originally created"); + is(file2, e.dataTransfer.mozGetDataAt("text/html", 2), + "It should be stored in index 2 for mozGetDataAt"); + + var oldLength = dtList.length; + var randomString = "foo!"; + e.dataTransfer.mozSetDataAt("random/string", randomString, 3); + is(oldLength, dtList.length, + "Adding a non-file entry to a non-zero index should not add an item to the items list"); + + var file3 = new File(['<a id="e"><b id="f">heya!</b></a>'], "yetanotherfile.html", + {type: "text/html"}); + e.dataTransfer.mozSetDataAt("random/string", file3, 3); + is(oldLength + 1, dtList.length, + "Replacing the entry with a file should add it to the list!"); + is(dtList[oldLength].getAsFile(), file3, "It should be stored in the last index as a file"); + is(dtList[oldLength].type, "random/string", "It should have the correct type"); + is(dtList[oldLength].kind, "file", "It should have the correct kind"); + is(files[files.length - 1], file3, "It should also be in the files list"); + + oldLength = dtList.length; + var nonstring = {}; + e.dataTransfer.mozSetDataAt("jsobject", nonstring, 0); + is(oldLength + 1, dtList.length, + "Adding a non-string object using the mozAPIs to index 0 should add an item to the dataTransfer"); + is(dtList[oldLength].type, "jsobject", "It should have the correct type"); + is(dtList[oldLength].kind, "other", "It should have the correct kind"); + + // Clear the event's data and get it set up so we can read it later! + dtList.clear(); + + dtList.add(file); + dtList.add("this is some text", "text/plain"); + is(e.dataTransfer.mozGetDataAt("text/html", 1), file); + } + + var getAsStringCalled = 0; + var dragenterFired = 0; + over.addEventListener('dragenter', onDragEnter); + function onDragEnter(e) { + over.removeEventListener('dragenter', onDragEnter); + + var dt = e.dataTransfer; + dragenterFired++; + + readOnly(e); + } + + var dropFired = 0; + over.addEventListener('drop', onDrop); + function onDrop(e) { + over.removeEventListener('drop', onDrop); + + var dt = e.dataTransfer; + dropFired++; + e.preventDefault(); + + readOnly(e); + } + + + function readOnly(e) { + var dtList = e.dataTransfer.items; + var num = dtList.length; + + // .clear() should have no effect + dtList.clear(); + is(dtList.length, num, + ".clear() should have no effect on the object during a readOnly event"); + + // .remove(i) should throw InvalidStateError + for (var i = 0; i < dtList.length; i++) { + expectError(function() { dtList.remove(i); }, + "InvalidStateError", ".remove(" + i + ") during a readOnly event"); + } + + // .add() should return null and have no effect + var data = [["This is a plain string", "text/plain"], + ["This is <em>HTML!</em>", "text/html"], + ["http://www.mozilla.org/", "text/uri-list"], + ["this is some custom data", "custom-data"]]; + + for (var i = 0; i < data.length; i++) { + is(dtList.add(data[i][0], data[i][1]), null, + ".add() should return null during a readOnly event"); + + is(dtList.length, num, ".add() should have no effect during a readOnly event"); + } + + // .add() with a file should return null and have no effect + var file = new File(['<a id="a"><b id="b">hey!</b></a>'], "myfile.html", + {type: "text/html"}); + is(dtList.add(file), null, ".add() with a file should return null during a readOnly event"); + is(dtList.length, num, ".add() should have no effect during a readOnly event"); + + // We should be able to access the files + is(e.dataTransfer.files.length, 1, "Should be able to access files"); + ok(e.dataTransfer.files[0], "File should be the same file!"); + is(e.dataTransfer.items.length, 2, "Should be able to see there are 2 items"); + + is(e.dataTransfer.items[0].kind, "file", "First item should be a file"); + is(e.dataTransfer.items[1].kind, "string", "Second item should be a string"); + + is(e.dataTransfer.items[0].type, "text/html", "first item should be text/html"); + is(e.dataTransfer.items[1].type, "text/plain", "second item should be text/plain"); + + ok(e.dataTransfer.items[0].getAsFile(), "Should be able to get file"); + e.dataTransfer.items[1].getAsString(function(s) { + getAsStringCalled++; + is(s, "this is some text", "Should provide the correct string"); + }); + } + + synthesizeDrop(draggable, over, null, null); + + // Wait for the getAsString callbacks to complete + yield spin(); + is(getAsStringCalled, 2, "getAsString should be called twice"); + + // Sanity-check to make sure that the events were actually run + is(dragstartFired, 1, "dragstart fired"); + is(dragenterFired, 1, "dragenter fired"); + is(dropFired, 1, "drop fired"); + }); + + function expectError(fn, eid, testid) { + var error = ""; + try { + fn(); + } catch (ex) { + error = ex.name; + } + is(error, eid, testid + " causes exception " + eid); + } + + function checkTypes(aExpectedList, aDtList, aTestid) { + is(aDtList.length, aExpectedList.length, aTestid + " length test"); + for (var i = 0; i < aExpectedList.length; i++) { + is(aDtList[i].type, aExpectedList[i], aTestid + " type " + i); + } + } +</script> + +</body> +</html>
--- a/dom/events/test/test_dragstart.html +++ b/dom/events/test/test_dragstart.html @@ -353,24 +353,29 @@ function test_DataTransfer(dt) checkOneDataItem(dt, ["text/plain", "text/html"], ["First Item", "Changed with setData"], 0, "clearData type that does not exist item at index 0"); checkOneDataItem(dt, ["text/unknown"], ["Unknown type"], 1, "clearData type that does not exist item at index 1"); expectError(() => dt.mozClearDataAt("text/plain", 3), "IndexSizeError", "clearData index too high with two items"); - // ensure that clearData() removes all data associated with the first item + // ensure that clearData() removes all data associated with the first item, but doesn't + // shift the second item down into the first item's slot. dt.clearData(); - is(dt.mozItemCount, 1, "clearData no argument with multiple items itemCount"); + is(dt.mozItemCount, 2, "clearData no argument with multiple items itemCount"); + checkOneDataItem(dt, [], [], 0, + "clearData no argument with multiple items item at index 0"); checkOneDataItem(dt, ["text/unknown"], - ["Unknown type"], 0, "clearData no argument with multiple items item at index 1"); + ["Unknown type"], 1, "clearData no argument with multiple items item at index 1"); - // remove tha remaining data - dt.mozClearDataAt("", 0); + // remove tha remaining data in index 1. As index 0 is empty at this point, this will actually + // drop mozItemCount to 0. (XXX: This is because of spec-compliance reasons related + // to the more-recent dt.item API. It's an unfortunate, but hopefully rare edge case) + dt.mozClearDataAt("", 1); is(dt.mozItemCount, 0, "all data cleared"); // now check the effectAllowed and dropEffect properties is(dt.dropEffect, "none", "initial dropEffect"); is(dt.effectAllowed, "uninitialized", "initial effectAllowed"); ["copy", "none", "link", "", "other", "copyMove", "all", "uninitialized", "move"].forEach( function (i) {
new file mode 100644 --- /dev/null +++ b/dom/events/test/test_paste_image.html @@ -0,0 +1,196 @@ +<html><head> +<title>Test for bug 891247</title> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + +<script class="testbody" type="application/javascript"> + function ImageTester() { + var counter = 0; + var images = []; + var that = this; + + this.add = function(aFile) { + images.push(aFile); + }; + + this.test = function() { + for (var i = 0; i < images.length; i++) { + testImageSize(images[i]); + } + }; + + this.returned = function() { + counter++; + info("returned=" + counter + " images.length=" + images.length); + if (counter == images.length) { + info("test finish"); + SimpleTest.finish(); + } + }; + + function testImageSize(aFile) { + var source = window.URL.createObjectURL(aFile); + var image = new Image(); + image.src = source; + var imageTester = that; + image.onload = function() { + is(this.width, 62, "Check generated image width"); + is(this.height, 71, "Check generated image height"); + if (aFile.type == "image/gif") { + // this test fails for image/jpeg and image/png because the images + // generated are slightly different + testImageCanvas(image); + } + + imageTester.returned(); + } + + document.body.appendChild(image); + }; + + function testImageCanvas(aImage) { + var canvas = drawToCanvas(aImage); + + var refImage = document.getElementById('image'); + var refCanvas = drawToCanvas(refImage); + + is(canvas.toDataURL(), refCanvas.toDataURL(), "Image should map pixel-by-pixel"); + } + + function drawToCanvas(aImage) { + var canvas = document.createElement("CANVAS"); + document.body.appendChild(canvas); + canvas.width = aImage.width; + canvas.height = aImage.height; + canvas.getContext('2d').drawImage(aImage, 0, 0); + return canvas; + } + } + + function copyImage(aImageId) { + // selection of the node + var node = document.getElementById(aImageId); + var webnav = SpecialPowers.wrap(window) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + + var docShell = webnav.QueryInterface(SpecialPowers.Ci.nsIDocShell); + + // let's copy the node + var documentViewer = docShell.contentViewer + .QueryInterface(SpecialPowers.Ci.nsIContentViewerEdit); + documentViewer.setCommandNode(node); + documentViewer.copyImage(documentViewer.COPY_IMAGE_ALL); + } + + function doTest() { + SimpleTest.waitForExplicitFinish(); + + copyImage('image'); + + //--------- now check the content of the clipboard + var clipboard = SpecialPowers.Cc["@mozilla.org/widget/clipboard;1"] + .getService(SpecialPowers.Ci.nsIClipboard); + // does the clipboard contain text/unicode data ? + ok(clipboard.hasDataMatchingFlavors(["text/unicode"], 1, clipboard.kGlobalClipboard), + "clipboard contains unicode text"); + // does the clipboard contain text/html data ? + ok(clipboard.hasDataMatchingFlavors(["text/html"], 1, clipboard.kGlobalClipboard), + "clipboard contains html text"); + // does the clipboard contain image data ? + ok(clipboard.hasDataMatchingFlavors(["image/png"], 1, clipboard.kGlobalClipboard), + "clipboard contains image"); + + window.addEventListener("paste", onPaste); + + var textarea = SpecialPowers.wrap(document.getElementById('textarea')); + textarea.focus(); + textarea.editor.paste(clipboard.kGlobalClipboard); + } + + function onPaste(e) { + var imageTester = new ImageTester; + testFiles(e, imageTester); + testItems(e, imageTester); + imageTester.test(); + } + + function testItems(e, imageTester) { + var items = e.clipboardData.items; + is(items, e.clipboardData.items, + "Getting @items twice should return the same object"); + var haveFiles = false; + ok(items instanceof DataTransferItemList, "@items implements DataTransferItemList"); + ok(items.length > 0, "@items is not empty"); + for (var i = 0; i < items.length; i++) { + var item = items[i]; + ok(item instanceof DataTransferItem, "each element of @items must implement DataTransferItem"); + if (item.kind == "file") { + var file = item.getAsFile(); + ok(file instanceof File, ".getAsFile() returns a File object"); + ok(file.size > 0, "Files shouldn't have size 0"); + imageTester.add(file); + } + } + } + + function testFiles(e, imageTester) { + var files = e.clipboardData.files; + + is(files, e.clipboardData.files, + "Getting the files array twice should return the same array"); + ok(files.length > 0, "There should be at least one file in the clipboard"); + for (var i = 0; i < files.length; i++) { + var file = files[i]; + ok(file instanceof File, ".files should contain only File objects"); + ok(file.size > 0, "This file shouldn't have size 0"); + if (file.name == "image.png") { + is(file.type, "image/png", "This file should be a image/png"); + } else if (file.name == "image.jpeg") { + is(file.type, "image/jpeg", "This file should be a image/jpeg"); + } else if (file.name == "image.gif") { + is(file.type, "image/gif", "This file should be a image/gif"); + } else { + info("file.name=" + file.name); + ok(false, "Unexpected file name"); + } + + testSlice(file); + imageTester.add(file); + // Adding the same image again so we can test concurrency + imageTester.add(file); + } + } + + function testSlice(aFile) { + var blob = aFile.slice(); + ok(blob instanceof Blob, ".slice returns a blob"); + is(blob.size, aFile.size, "the blob has the same size"); + + blob = aFile.slice(123123); + is(blob.size, 0, ".slice overflow check"); + + blob = aFile.slice(123, 123141); + is(blob.size, aFile.size - 123, ".slice @size check"); + + blob = aFile.slice(123, 12); + is(blob.size, 0, ".slice @size check 2"); + + blob = aFile.slice(124, 134, "image/png"); + is(blob.size, 10, ".slice @size check 3"); + is(blob.type, "image/png", ".slice @type check"); + } + +</script> +<body onload="doTest();"> + <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABHCA + IAAADQjmMaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3goUAwAgSAORBwAAABl0RVh0Q29 + tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABPSURBVGje7c4BDQAACAOga//OmuMbJGAurTbq + 6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6s31B0IqAY2/t + QVCAAAAAElFTkSuQmCC" /> + <form> + <textarea id="textarea"></textarea> + </form> +</body> +</html>
--- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -257,26 +257,26 @@ MainThreadFetchResolver::OnResponseAvail } } MainThreadFetchResolver::~MainThreadFetchResolver() { NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); } -class WorkerFetchResponseRunnable final : public WorkerRunnable +class WorkerFetchResponseRunnable final : public MainThreadWorkerRunnable { RefPtr<WorkerFetchResolver> mResolver; // Passed from main thread to worker thread after being initialized. RefPtr<InternalResponse> mInternalResponse; public: WorkerFetchResponseRunnable(WorkerPrivate* aWorkerPrivate, WorkerFetchResolver* aResolver, InternalResponse* aResponse) - : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) + : MainThreadWorkerRunnable(aWorkerPrivate) , mResolver(aResolver) , mInternalResponse(aResponse) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { @@ -291,35 +291,16 @@ public: promise->MaybeResolve(response); } else { ErrorResult result; result.ThrowTypeError<MSG_FETCH_FAILED>(); promise->MaybeReject(result); } return true; } - - bool - PreDispatch(WorkerPrivate* aWorkerPrivate) override - { - // Don't call default PreDispatch() since it incorrectly asserts we are - // dispatching from the parent worker thread. We always dispatch from - // the main thread. - AssertIsOnMainThread(); - return true; - } - - void - PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override - { - // Don't call default PostDispatch() since it incorrectly asserts we are - // dispatching from the parent worker thread. We always dispatch from - // the main thread. - AssertIsOnMainThread(); - } }; class WorkerFetchResponseEndBase { RefPtr<PromiseWorkerProxy> mPromiseProxy; public: explicit WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy) : mPromiseProxy(aPromiseProxy) @@ -331,23 +312,22 @@ public: WorkerRunInternal(WorkerPrivate* aWorkerPrivate) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); mPromiseProxy->CleanUp(); } }; -class WorkerFetchResponseEndRunnable final : public WorkerRunnable +class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable , public WorkerFetchResponseEndBase { public: explicit WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy) - : WorkerRunnable(aPromiseProxy->GetWorkerPrivate(), - WorkerThreadUnchangedBusyCount) + : MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate()) , WorkerFetchResponseEndBase(aPromiseProxy) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { WorkerRunInternal(aWorkerPrivate); @@ -357,35 +337,16 @@ public: nsresult Cancel() override { // Execute Run anyway to make sure we cleanup our promise proxy to avoid // leaking the worker thread Run(); return WorkerRunnable::Cancel(); } - - bool - PreDispatch(WorkerPrivate* aWorkerPrivate) override - { - // Don't call default PreDispatch() since it incorrectly asserts we are - // dispatching from the parent worker thread. We always dispatch from - // the main thread. - AssertIsOnMainThread(); - return true; - } - - void - PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override - { - // Don't call default PostDispatch() since it incorrectly asserts we are - // dispatching from the parent worker thread. We always dispatch from - // the main thread. - AssertIsOnMainThread(); - } }; class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerControlRunnable , public WorkerFetchResponseEndBase { public: explicit WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy) : MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate()) @@ -638,62 +599,43 @@ ExtractByteStreamFromBody(const ArrayBuf return NS_ERROR_FAILURE; } namespace { /* * Called on successfully reading the complete stream. */ template <class Derived> -class ContinueConsumeBodyRunnable final : public WorkerRunnable +class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable { // This has been addrefed before this runnable is dispatched, // released in WorkerRun(). FetchBody<Derived>* mFetchBody; nsresult mStatus; uint32_t mLength; uint8_t* mResult; public: ContinueConsumeBodyRunnable(FetchBody<Derived>* aFetchBody, nsresult aStatus, uint32_t aLength, uint8_t* aResult) - : WorkerRunnable(aFetchBody->mWorkerPrivate, WorkerThreadUnchangedBusyCount) + : MainThreadWorkerRunnable(aFetchBody->mWorkerPrivate) , mFetchBody(aFetchBody) , mStatus(aStatus) , mLength(aLength) , mResult(aResult) { MOZ_ASSERT(NS_IsMainThread()); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mFetchBody->ContinueConsumeBody(mStatus, mLength, mResult); return true; } - - bool - PreDispatch(WorkerPrivate* aWorkerPrivate) override - { - // Don't call default PreDispatch() since it incorrectly asserts we are - // dispatching from the parent worker thread. We always dispatch from - // the main thread. - AssertIsOnMainThread(); - return true; - } - - void - PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override - { - // Don't call default PostDispatch() since it incorrectly asserts we are - // dispatching from the parent worker thread. We always dispatch from - // the main thread. - AssertIsOnMainThread(); - } }; // OnStreamComplete always adopts the buffer, utility class to release it in // a couple of places. class MOZ_STACK_CLASS AutoFreeBuffer final { uint8_t* mBuffer; public:
new file mode 100644 --- /dev/null +++ b/dom/fetch/FetchIPCTypes.h @@ -0,0 +1,57 @@ +/* -*- 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 mozilla_dom_fetch_IPCUtils_h +#define mozilla_dom_fetch_IPCUtils_h + +#include "ipc/IPCMessageUtils.h" + +// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None +#undef None + +#include "mozilla/dom/HeadersBinding.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/dom/ResponseBinding.h" + +namespace IPC { + template<> + struct ParamTraits<mozilla::dom::HeadersGuardEnum> : + public ContiguousEnumSerializer<mozilla::dom::HeadersGuardEnum, + mozilla::dom::HeadersGuardEnum::None, + mozilla::dom::HeadersGuardEnum::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::ReferrerPolicy> : + public ContiguousEnumSerializer<mozilla::dom::ReferrerPolicy, + mozilla::dom::ReferrerPolicy::_empty, + mozilla::dom::ReferrerPolicy::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::RequestMode> : + public ContiguousEnumSerializer<mozilla::dom::RequestMode, + mozilla::dom::RequestMode::Same_origin, + mozilla::dom::RequestMode::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::RequestCredentials> : + public ContiguousEnumSerializer<mozilla::dom::RequestCredentials, + mozilla::dom::RequestCredentials::Omit, + mozilla::dom::RequestCredentials::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::RequestCache> : + public ContiguousEnumSerializer<mozilla::dom::RequestCache, + mozilla::dom::RequestCache::Default, + mozilla::dom::RequestCache::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::RequestRedirect> : + public ContiguousEnumSerializer<mozilla::dom::RequestRedirect, + mozilla::dom::RequestRedirect::Follow, + mozilla::dom::RequestRedirect::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::ResponseType> : + public ContiguousEnumSerializer<mozilla::dom::ResponseType, + mozilla::dom::ResponseType::Basic, + mozilla::dom::ResponseType::EndGuard_> {}; +} // namespace IPC + +#endif // mozilla_dom_fetch_IPCUtils_h
new file mode 100644 --- /dev/null +++ b/dom/fetch/FetchTypes.ipdlh @@ -0,0 +1,61 @@ +/* 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 IPCStream; +include ChannelInfo; +include PBackgroundSharedTypes; + +using HeadersGuardEnum from "mozilla/dom/FetchIPCTypes.h"; +using ReferrerPolicy from "mozilla/dom/FetchIPCTypes.h"; +using RequestCredentials from "mozilla/dom/FetchIPCTypes.h"; +using RequestMode from "mozilla/dom/FetchIPCTypes.h"; +using ResponseType from "mozilla/dom/FetchIPCTypes.h"; +using RequestRedirect from "mozilla/dom/FetchIPCTypes.h"; +using RequestCache from "mozilla/dom/FetchIPCTypes.h"; + +namespace mozilla { +namespace dom { + +struct HeadersEntry +{ + nsCString name; + nsCString value; +}; + +// Note, this does not yet serialize *all* of InternalRequest +// Make sure that it contains the fields that you care about +struct IPCInternalRequest +{ + nsCString[] urls; + nsCString method; + HeadersEntry[] headers; + HeadersGuardEnum headersGuard; + nsString referrer; + ReferrerPolicy referrerPolicy; + RequestMode mode; + RequestCredentials credentials; + uint32_t contentPolicyType; + RequestCache requestCache; + RequestRedirect requestRedirect; +}; + +// Note, this does not yet serialize *all* of InternalResponse +// Make sure that it contains the fields that you care about +struct IPCInternalResponse +{ + ResponseType type; + nsCString[] urlList; + uint32_t status; + nsCString statusText; + HeadersEntry[] headers; + HeadersGuardEnum headersGuard; + IPCChannelInfo channelInfo; + OptionalPrincipalInfo principalInfo; + OptionalIPCStream body; + int64_t bodySize; +}; + + +} // namespace ipc +} // namespace mozilla
--- a/dom/fetch/InternalHeaders.cpp +++ b/dom/fetch/InternalHeaders.cpp @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/InternalHeaders.h" +#include "mozilla/dom/FetchTypes.h" #include "mozilla/ErrorResult.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsNetUtil.h" #include "nsReadableUtils.h" namespace mozilla { @@ -18,16 +19,37 @@ namespace dom { InternalHeaders::InternalHeaders(const nsTArray<Entry>&& aHeaders, HeadersGuardEnum aGuard) : mGuard(aGuard) , mList(aHeaders) { } +InternalHeaders::InternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList, + HeadersGuardEnum aGuard) + : mGuard(aGuard) +{ + for (const HeadersEntry& headersEntry : aHeadersEntryList) { + mList.AppendElement(Entry(headersEntry.name(), headersEntry.value())); + } +} + +void +InternalHeaders::ToIPC(nsTArray<HeadersEntry>& aIPCHeaders, + HeadersGuardEnum& aGuard) +{ + aGuard = mGuard; + + aIPCHeaders.Clear(); + for (Entry& entry : mList) { + aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue)); + } +} + void InternalHeaders::Append(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv) { nsAutoCString lowerName; ToLowerCase(aName, lowerName); if (IsInvalidMutableHeader(lowerName, aValue, aRv)) {
--- a/dom/fetch/InternalHeaders.h +++ b/dom/fetch/InternalHeaders.h @@ -16,16 +16,17 @@ namespace mozilla { class ErrorResult; namespace dom { template<typename T> class MozMap; +class HeadersEntry; class InternalHeaders final { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalHeaders) public: struct Entry { @@ -59,16 +60,22 @@ public: // Note that it's important to set the guard after Fill(), to make sure // that Fill() doesn't fail if aOther is immutable. mGuard = aOther.mGuard; } explicit InternalHeaders(const nsTArray<Entry>&& aHeaders, HeadersGuardEnum aGuard = HeadersGuardEnum::None); + InternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList, + HeadersGuardEnum aGuard); + + void ToIPC(nsTArray<HeadersEntry>& aIPCHeaders, + HeadersGuardEnum& aGuard); + void Append(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv); void Delete(const nsACString& aName, ErrorResult& aRv); void Get(const nsACString& aName, nsCString& aValue, ErrorResult& aRv) const; void GetAll(const nsACString& aName, nsTArray<nsCString>& aResults, ErrorResult& aRv) const; bool Has(const nsACString& aName, ErrorResult& aRv) const; void Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv);
--- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -6,16 +6,17 @@ #include "InternalRequest.h" #include "nsIContentPolicy.h" #include "nsIDocument.h" #include "nsStreamUtils.h" #include "mozilla/ErrorResult.h" +#include "mozilla/dom/FetchTypes.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/workers/Workers.h" #include "WorkerPrivate.h" namespace mozilla { namespace dom { @@ -97,21 +98,56 @@ InternalRequest::InternalRequest(const I , mUnsafeRequest(aOther.mUnsafeRequest) , mUseURLCredentials(aOther.mUseURLCredentials) , mCreatedByFetchEvent(aOther.mCreatedByFetchEvent) , mContentPolicyTypeOverridden(aOther.mContentPolicyTypeOverridden) { // NOTE: does not copy body stream... use the fallible Clone() for that } +InternalRequest::InternalRequest(const IPCInternalRequest& aIPCRequest) + : mMethod(aIPCRequest.method()) + , mURLList(aIPCRequest.urls()) + , mHeaders(new InternalHeaders(aIPCRequest.headers(), + aIPCRequest.headersGuard())) + , mContentPolicyType(aIPCRequest.contentPolicyType()) + , mReferrer(aIPCRequest.referrer()) + , mReferrerPolicy(aIPCRequest.referrerPolicy()) + , mMode(aIPCRequest.mode()) + , mCredentialsMode(aIPCRequest.credentials()) + , mCacheMode(aIPCRequest.requestCache()) + , mRedirectMode(aIPCRequest.requestRedirect()) +{ + MOZ_ASSERT(!mURLList.IsEmpty()); +} + InternalRequest::~InternalRequest() { } void +InternalRequest::ToIPC(IPCInternalRequest* aIPCRequest) +{ + MOZ_ASSERT(aIPCRequest); + MOZ_ASSERT(!mURLList.IsEmpty()); + aIPCRequest->urls() = mURLList; + aIPCRequest->method() = mMethod; + + mHeaders->ToIPC(aIPCRequest->headers(), aIPCRequest->headersGuard()); + + aIPCRequest->referrer() = mReferrer; + aIPCRequest->referrerPolicy() = mReferrerPolicy; + aIPCRequest->mode() = mMode; + aIPCRequest->credentials() = mCredentialsMode; + aIPCRequest->contentPolicyType() = mContentPolicyType; + aIPCRequest->requestCache() = mCacheMode; + aIPCRequest->requestRedirect() = mRedirectMode; +} + +void InternalRequest::SetContentPolicyType(nsContentPolicyType aContentPolicyType) { mContentPolicyType = aContentPolicyType; } void InternalRequest::OverrideContentPolicyType(nsContentPolicyType aContentPolicyType) {
--- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -73,16 +73,17 @@ namespace dom { * TODO: Add a content type for location * TODO: Add a content type for hyperlink * TODO: Add a content type for form * TODO: Add a content type for favicon * TODO: Add a content type for download */ class Request; +class IPCInternalRequest; #define kFETCH_CLIENT_REFERRER_STR "about:client" class InternalRequest final { friend class Request; public: @@ -145,16 +146,20 @@ public: , mSynchronous(false) , mUnsafeRequest(false) , mUseURLCredentials(false) { MOZ_ASSERT(!aURL.IsEmpty()); AddURL(aURL); } + explicit InternalRequest(const IPCInternalRequest& aIPCRequest); + + void ToIPC(IPCInternalRequest* aIPCRequest); + already_AddRefed<InternalRequest> Clone(); void GetMethod(nsCString& aMethod) const { aMethod.Assign(mMethod); }
--- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -5,35 +5,144 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "InternalResponse.h" #include "mozilla/Assertions.h" #include "mozilla/dom/InternalHeaders.h" #include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ipc/IPCStreamUtils.h" #include "nsIURI.h" #include "nsStreamUtils.h" 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) { } +already_AddRefed<InternalResponse> +InternalResponse::FromIPC(const IPCInternalResponse& aIPCResponse) +{ + MOZ_ASSERT(!aIPCResponse.urlList().IsEmpty()); + + if (aIPCResponse.type() == ResponseType::Error) { + return InternalResponse::NetworkError(); + } + + RefPtr<InternalResponse> response = + new InternalResponse(aIPCResponse.status(), + aIPCResponse.statusText()); + + response->SetURLList(aIPCResponse.urlList()); + + response->mHeaders = new InternalHeaders(aIPCResponse.headers(), + aIPCResponse.headersGuard()); + + response->InitChannelInfo(aIPCResponse.channelInfo()); + if (aIPCResponse.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) { + UniquePtr<mozilla::ipc::PrincipalInfo> info(new mozilla::ipc::PrincipalInfo(aIPCResponse.principalInfo().get_PrincipalInfo())); + response->SetPrincipalInfo(Move(info)); + } + + nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aIPCResponse.body()); + response->SetBody(stream, aIPCResponse.bodySize()); + + switch (aIPCResponse.type()) + { + case ResponseType::Basic: + response = response->BasicResponse(); + break; + case ResponseType::Cors: + response = response->CORSResponse(); + break; + case ResponseType::Default: + break; + case ResponseType::Opaque: + response = response->OpaqueResponse(); + break; + case ResponseType::Opaqueredirect: + response = response->OpaqueRedirectResponse(); + break; + default: + MOZ_CRASH("Unexpected ResponseType!"); + } + MOZ_ASSERT(response); + + return response.forget(); +} + InternalResponse::~InternalResponse() { } +template void +InternalResponse::ToIPC<PContentParent> + (IPCInternalResponse* aIPCResponse, + PContentParent* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); +template void +InternalResponse::ToIPC<PContentChild> + (IPCInternalResponse* aIPCResponse, + PContentChild* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); +template void +InternalResponse::ToIPC<mozilla::ipc::PBackgroundParent> + (IPCInternalResponse* aIPCResponse, + mozilla::ipc::PBackgroundParent* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); +template void +InternalResponse::ToIPC<mozilla::ipc::PBackgroundChild> + (IPCInternalResponse* aIPCResponse, + mozilla::ipc::PBackgroundChild* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); + +template<typename M> +void +InternalResponse::ToIPC(IPCInternalResponse* aIPCResponse, + M* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream) +{ + MOZ_ASSERT(aIPCResponse); + MOZ_ASSERT(!mURLList.IsEmpty()); + aIPCResponse->type() = mType; + aIPCResponse->urlList() = mURLList; + aIPCResponse->status() = GetUnfilteredStatus(); + aIPCResponse->statusText() = GetUnfilteredStatusText(); + + mHeaders->ToIPC(aIPCResponse->headers(), aIPCResponse->headersGuard()); + + aIPCResponse->channelInfo() = mChannelInfo.AsIPCChannelInfo(); + if (mPrincipalInfo) { + aIPCResponse->principalInfo() = *mPrincipalInfo; + } else { + aIPCResponse->principalInfo() = void_t(); + } + + nsCOMPtr<nsIInputStream> body; + int64_t bodySize; + GetUnfilteredBody(getter_AddRefs(body), &bodySize); + + if (body) { + aAutoStream.reset(new mozilla::ipc::AutoIPCStream(aIPCResponse->body())); + aAutoStream->Serialize(body, aManager); + } else { + aIPCResponse->body() = void_t(); + } + + aIPCResponse->bodySize() = bodySize; +} + already_AddRefed<InternalResponse> InternalResponse::Clone() { RefPtr<InternalResponse> clone = CreateIncompleteCopy(); clone->mHeaders = new InternalHeaders(*mHeaders); if (mWrappedResponse) { clone->mWrappedResponse = mWrappedResponse->Clone();
--- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -12,31 +12,42 @@ #include "mozilla/dom/ResponseBinding.h" #include "mozilla/dom/ChannelInfo.h" #include "mozilla/UniquePtr.h" namespace mozilla { namespace ipc { class PrincipalInfo; +class AutoIPCStream; } // namespace ipc namespace dom { class InternalHeaders; +class IPCInternalResponse; class InternalResponse final { friend class FetchDriver; public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalResponse) InternalResponse(uint16_t aStatus, const nsACString& aStatusText); + static already_AddRefed<InternalResponse> + FromIPC(const IPCInternalResponse& aIPCResponse); + + template<typename M> + void + ToIPC(IPCInternalResponse* aIPCResponse, + M* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); + already_AddRefed<InternalResponse> Clone(); static already_AddRefed<InternalResponse> NetworkError() { RefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString()); ErrorResult result; response->Headers()->SetGuard(HeadersGuardEnum::Immutable, result);
--- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -3,16 +3,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/. EXPORTS.mozilla.dom += [ 'ChannelInfo.h', 'Fetch.h', 'FetchDriver.h', + 'FetchIPCTypes.h', 'FetchUtil.h', 'Headers.h', 'InternalHeaders.h', 'InternalRequest.h', 'InternalResponse.h', 'Request.h', 'Response.h', ] @@ -27,16 +28,17 @@ UNIFIED_SOURCES += [ 'InternalRequest.cpp', 'InternalResponse.cpp', 'Request.cpp', 'Response.cpp', ] IPDL_SOURCES += [ 'ChannelInfo.ipdlh', + 'FetchTypes.ipdlh', ] LOCAL_INCLUDES += [ '../workers', # For HttpBaseChannel.h dependencies '/netwerk/base', # For nsDataHandler.h '/netwerk/protocol/data',
new file mode 100644 --- /dev/null +++ b/dom/flyweb/FlyWebPublishOptionsIPCSerializer.h @@ -0,0 +1,33 @@ +/* -*- 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 mozilla_dom_FlyWebPublishOptionsIPCSerialiser_h +#define mozilla_dom_FlyWebPublishOptionsIPCSerialiser_h + +#include "mozilla/dom/FlyWebPublishBinding.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::dom::FlyWebPublishOptions> +{ + typedef mozilla::dom::FlyWebPublishOptions paramType; + + // Function to serialize a FlyWebPublishOptions + static void Write(Message *aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mUiUrl); + } + // Function to de-serialize a FlyWebPublishOptions + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + return ReadParam(aMsg, aIter, &(aResult->mUiUrl)); + } +}; + +} + +#endif // mozilla_dom_FlyWebPublishOptionsIPCSerialiser_h
--- a/dom/flyweb/FlyWebPublishedServer.cpp +++ b/dom/flyweb/FlyWebPublishedServer.cpp @@ -1,175 +1,153 @@ /* -*- 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/FlyWebPublishedServer.h" +#include "mozilla/dom/FlyWebPublishedServerIPC.h" #include "mozilla/dom/FlyWebPublishBinding.h" #include "mozilla/dom/FlyWebService.h" #include "mozilla/dom/Request.h" #include "mozilla/dom/FlyWebServerEvents.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/InternalResponse.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/net/NeckoParent.h" +#include "mozilla/net/IPCTransportProvider.h" +#include "mozilla/ErrorResult.h" #include "mozilla/Preferences.h" +#include "mozilla/unused.h" +#include "nsCharSeparatedTokenizer.h" #include "nsGlobalWindow.h" +#include "WebSocketChannel.h" namespace mozilla { namespace dom { static LazyLogModule gFlyWebPublishedServerLog("FlyWebPublishedServer"); #undef LOG_I #define LOG_I(...) MOZ_LOG(mozilla::dom::gFlyWebPublishedServerLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) #undef LOG_E #define LOG_E(...) MOZ_LOG(mozilla::dom::gFlyWebPublishedServerLog, mozilla::LogLevel::Error, (__VA_ARGS__)) -NS_IMPL_ISUPPORTS_INHERITED0(FlyWebPublishedServer, mozilla::DOMEventTargetHelper) +/******** FlyWebPublishedServer ********/ FlyWebPublishedServer::FlyWebPublishedServer(nsPIDOMWindowInner* aOwner, const nsAString& aName, - const FlyWebPublishOptions& aOptions, - Promise* aPublishPromise) + const FlyWebPublishOptions& aOptions) : mozilla::DOMEventTargetHelper(aOwner) , mOwnerWindowID(aOwner ? aOwner->WindowID() : 0) - , mPublishPromise(aPublishPromise) , mName(aName) - , mCategory(aOptions.mCategory) - , mHttp(aOptions.mHttp) - , mMessage(aOptions.mMessage) , mUiUrl(aOptions.mUiUrl) , mIsRegistered(true) // Registered by the FlyWebService { - if (mCategory.IsEmpty()) { - mCategory.SetIsVoid(true); - } - - mHttpServer = new HttpServer(); - mHttpServer->Init(-1, Preferences::GetBool("flyweb.use-tls", false), this); } -FlyWebPublishedServer::~FlyWebPublishedServer() +void +FlyWebPublishedServer::LastRelease() { - // Make sure to unregister to avoid dangling pointers + // Make sure to unregister to avoid dangling pointers. Use the LastRelease + // hook rather than dtor since calling virtual functions during dtor + // wouldn't do what we want. Also, LastRelease is called earlier than dtor + // for CC objects. Close(); } JSObject* FlyWebPublishedServer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return FlyWebPublishedServerBinding::Wrap(aCx, this, aGivenProto); } void FlyWebPublishedServer::Close() { + LOG_I("FlyWebPublishedServer::Close(%p)", this); + // Unregister from server. if (mIsRegistered) { - FlyWebService::GetOrCreate()->UnregisterServer(this); + MOZ_ASSERT(FlyWebService::GetExisting()); + FlyWebService::GetExisting()->UnregisterServer(this); mIsRegistered = false; - } - if (mMDNSCancelRegister) { - mMDNSCancelRegister->Cancel(NS_BINDING_ABORTED); - mMDNSCancelRegister = nullptr; - } - - if (mHttpServer) { - RefPtr<HttpServer> server = mHttpServer.forget(); - server->Close(); + DispatchTrustedEvent(NS_LITERAL_STRING("close")); } } void -FlyWebPublishedServer::OnServerStarted(nsresult aStatus) -{ - if (NS_SUCCEEDED(aStatus)) { - FlyWebService::GetOrCreate()->StartDiscoveryOf(this); - } else { - DiscoveryStarted(aStatus); - } -} - -void -FlyWebPublishedServer::OnServerClose() -{ - mHttpServer = nullptr; - Close(); - - DispatchTrustedEvent(NS_LITERAL_STRING("close")); -} - -void -FlyWebPublishedServer::OnRequest(InternalRequest* aRequest) +FlyWebPublishedServer::FireFetchEvent(InternalRequest* aRequest) { nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner()); RefPtr<FlyWebFetchEvent> e = new FlyWebFetchEvent(this, new Request(global, aRequest), aRequest); e->Init(this); e->InitEvent(NS_LITERAL_STRING("fetch"), false, false); DispatchTrustedEvent(e); } void -FlyWebPublishedServer::OnWebSocket(InternalRequest* aConnectRequest) +FlyWebPublishedServer::FireWebsocketEvent(InternalRequest* aConnectRequest) { nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner()); RefPtr<FlyWebFetchEvent> e = new FlyWebWebSocketEvent(this, new Request(global, aConnectRequest), aConnectRequest); e->Init(this); e->InitEvent(NS_LITERAL_STRING("websocket"), false, false); DispatchTrustedEvent(e); } void -FlyWebPublishedServer::OnFetchResponse(InternalRequest* aRequest, - InternalResponse* aResponse) +FlyWebPublishedServer::PublishedServerStarted(nsresult aStatus) { - MOZ_ASSERT(aRequest); - MOZ_ASSERT(aResponse); + LOG_I("FlyWebPublishedServer::PublishedServerStarted(%p)", this); - LOG_I("FlyWebPublishedMDNSServer::OnFetchResponse(%p)", this); - - if (mHttpServer) { - mHttpServer->SendResponse(aRequest, aResponse); + RefPtr<FlyWebPublishPromise> promise = mPublishPromise.Ensure(__func__); + if (NS_SUCCEEDED(aStatus)) { + mPublishPromise.Resolve(this, __func__); + } else { + Close(); + mPublishPromise.Reject(aStatus, __func__); } } already_AddRefed<WebSocket> FlyWebPublishedServer::OnWebSocketAccept(InternalRequest* aConnectRequest, const Optional<nsAString>& aProtocol, ErrorResult& aRv) { MOZ_ASSERT(aConnectRequest); - LOG_I("FlyWebPublishedMDNSServer::OnWebSocketAccept(%p)", this); - - if (!mHttpServer) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; - } + LOG_I("FlyWebPublishedServer::OnWebSocketAccept(%p)", this); - nsAutoCString negotiatedExtensions; nsCOMPtr<nsITransportProvider> provider = - mHttpServer->AcceptWebSocket(aConnectRequest, - aProtocol, - negotiatedExtensions, - aRv); + OnWebSocketAcceptInternal(aConnectRequest, + aProtocol, + aRv); if (aRv.Failed()) { return nullptr; } MOZ_ASSERT(provider); nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetOwner()); AutoJSContext cx; GlobalObject global(cx, nsGlobalWindow::Cast(window)->FastGetGlobalJSObject()); + nsAutoCString extensions, negotiatedExtensions; + aConnectRequest->Headers()-> + Get(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions, aRv); + mozilla::net::ProcessServerWebSocketExtensions(extensions, + negotiatedExtensions); + nsCString url; aConnectRequest->GetURL(url); Sequence<nsString> protocols; if (aProtocol.WasPassed() && !protocols.AppendElement(aProtocol.Value(), fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return nullptr; } @@ -177,36 +155,491 @@ FlyWebPublishedServer::OnWebSocketAccept return WebSocket::ConstructorCommon(global, NS_ConvertUTF8toUTF16(url), protocols, provider, negotiatedExtensions, aRv); } +/******** FlyWebPublishedServerImpl ********/ + +NS_IMPL_ISUPPORTS_INHERITED0(FlyWebPublishedServerImpl, mozilla::DOMEventTargetHelper) + +FlyWebPublishedServerImpl::FlyWebPublishedServerImpl(nsPIDOMWindowInner* aOwner, + const nsAString& aName, + const FlyWebPublishOptions& aOptions) + : FlyWebPublishedServer(aOwner, aName, aOptions) + , mHttpServer(new HttpServer()) +{ + LOG_I("FlyWebPublishedServerImpl::FlyWebPublishedServerImpl(%p)", this); + + mHttpServer->Init(-1, Preferences::GetBool("flyweb.use-tls", false), this); +} + void -FlyWebPublishedServer::OnWebSocketResponse(InternalRequest* aConnectRequest, +FlyWebPublishedServerImpl::Close() +{ + FlyWebPublishedServer::Close(); + + if (mMDNSCancelRegister) { + mMDNSCancelRegister->Cancel(NS_BINDING_ABORTED); + mMDNSCancelRegister = nullptr; + } + + if (mHttpServer) { + RefPtr<HttpServer> server = mHttpServer.forget(); + server->Close(); + } +} + +void +FlyWebPublishedServerImpl::OnServerStarted(nsresult aStatus) +{ + if (NS_SUCCEEDED(aStatus)) { + FlyWebService::GetOrCreate()->StartDiscoveryOf(this); + } else { + PublishedServerStarted(aStatus); + } +} + +void +FlyWebPublishedServerImpl::OnFetchResponse(InternalRequest* aRequest, InternalResponse* aResponse) { + MOZ_ASSERT(aRequest); + MOZ_ASSERT(aResponse); + + LOG_I("FlyWebPublishedServerImpl::OnFetchResponse(%p)", this); + + if (mHttpServer) { + mHttpServer->SendResponse(aRequest, aResponse); + } +} + +void +FlyWebPublishedServerImpl::OnWebSocketResponse(InternalRequest* aConnectRequest, + InternalResponse* aResponse) +{ MOZ_ASSERT(aConnectRequest); MOZ_ASSERT(aResponse); LOG_I("FlyWebPublishedMDNSServer::OnWebSocketResponse(%p)", this); if (mHttpServer) { mHttpServer->SendWebSocketResponse(aConnectRequest, aResponse); } } -void FlyWebPublishedServer::DiscoveryStarted(nsresult aStatus) +already_AddRefed<nsITransportProvider> +FlyWebPublishedServerImpl::OnWebSocketAcceptInternal(InternalRequest* aConnectRequest, + const Optional<nsAString>& aProtocol, + ErrorResult& aRv) +{ + LOG_I("FlyWebPublishedServerImpl::OnWebSocketAcceptInternal(%p)", this); + + if (!mHttpServer) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + return mHttpServer->AcceptWebSocket(aConnectRequest, + aProtocol, + aRv); +} + +/******** FlyWebPublishedServerChild ********/ + +FlyWebPublishedServerChild::FlyWebPublishedServerChild(nsPIDOMWindowInner* aOwner, + const nsAString& aName, + const FlyWebPublishOptions& aOptions) + : FlyWebPublishedServer(aOwner, aName, aOptions) + , mActorDestroyed(false) +{ + LOG_I("FlyWebPublishedServerChild::FlyWebPublishedServerChild(%p)", this); + + ContentChild::GetSingleton()-> + SendPFlyWebPublishedServerConstructor(this, + PromiseFlatString(aName), + aOptions); + + // The matching release happens when the actor is destroyed, in + // ContentChild::DeallocPFlyWebPublishedServerChild + NS_ADDREF_THIS(); +} + +bool +FlyWebPublishedServerChild::RecvServerReady(const nsresult& aStatus) +{ + LOG_I("FlyWebPublishedServerChild::RecvServerReady(%p)", this); + MOZ_ASSERT(!mActorDestroyed); + + PublishedServerStarted(aStatus); + return true; +} + +bool +FlyWebPublishedServerChild::RecvServerClose() +{ + LOG_I("FlyWebPublishedServerChild::RecvServerClose(%p)", this); + MOZ_ASSERT(!mActorDestroyed); + + Close(); + + return true; +} + +bool +FlyWebPublishedServerChild::RecvFetchRequest(const IPCInternalRequest& aRequest, + const uint64_t& aRequestId) +{ + LOG_I("FlyWebPublishedServerChild::RecvFetchRequest(%p)", this); + MOZ_ASSERT(!mActorDestroyed); + + RefPtr<InternalRequest> request = new InternalRequest(aRequest); + mPendingRequests.Put(request, aRequestId); + FireFetchEvent(request); + + return true; +} + +bool +FlyWebPublishedServerChild::RecvWebSocketRequest(const IPCInternalRequest& aRequest, + const uint64_t& aRequestId, + PTransportProviderChild* aProvider) +{ + LOG_I("FlyWebPublishedServerChild::RecvWebSocketRequest(%p)", this); + MOZ_ASSERT(!mActorDestroyed); + + RefPtr<InternalRequest> request = new InternalRequest(aRequest); + mPendingRequests.Put(request, aRequestId); + + // Not addreffing here. The addref was already done when the + // PTransportProvider child constructor original ran. + mPendingTransportProviders.Put(aRequestId, + dont_AddRef(static_cast<TransportProviderChild*>(aProvider))); + + FireWebsocketEvent(request); + + return true; +} + +void +FlyWebPublishedServerChild::ActorDestroy(ActorDestroyReason aWhy) +{ + LOG_I("FlyWebPublishedServerChild::ActorDestroy(%p)", this); + + mActorDestroyed = true; +} + +void +FlyWebPublishedServerChild::OnFetchResponse(InternalRequest* aRequest, + InternalResponse* aResponse) +{ + LOG_I("FlyWebPublishedServerChild::OnFetchResponse(%p)", this); + + if (mActorDestroyed) { + LOG_I("FlyWebPublishedServerChild::OnFetchResponse(%p) - No actor!", this); + return; + } + + uint64_t id = mPendingRequests.Get(aRequest); + MOZ_ASSERT(id); + mPendingRequests.Remove(aRequest); + + IPCInternalResponse ipcResp; + UniquePtr<mozilla::ipc::AutoIPCStream> autoStream; + aResponse->ToIPC(&ipcResp, Manager(), autoStream); + Unused << SendFetchResponse(ipcResp, id); + if (autoStream) { + autoStream->TakeOptionalValue(); + } +} + +already_AddRefed<nsITransportProvider> +FlyWebPublishedServerChild::OnWebSocketAcceptInternal(InternalRequest* aRequest, + const Optional<nsAString>& aProtocol, + ErrorResult& aRv) +{ + LOG_I("FlyWebPublishedServerChild::OnWebSocketAcceptInternal(%p)", this); + + if (mActorDestroyed) { + LOG_I("FlyWebPublishedServerChild::OnWebSocketAcceptInternal(%p) - No actor!", this); + return nullptr; + } + + uint64_t id = mPendingRequests.Get(aRequest); + MOZ_ASSERT(id); + mPendingRequests.Remove(aRequest); + + RefPtr<TransportProviderChild> provider; + mPendingTransportProviders.Remove(id, getter_AddRefs(provider)); + + nsString protocol; + if (aProtocol.WasPassed()) { + protocol = aProtocol.Value(); + + nsAutoCString reqProtocols; + aRequest->Headers()-> + Get(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), reqProtocols, aRv); + if (!ContainsToken(reqProtocols, NS_ConvertUTF16toUTF8(protocol))) { + // Should throw a better error here + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + } else { + protocol.SetIsVoid(true); + } + + Unused << SendWebSocketAccept(protocol, id); + + return provider.forget(); +} + +void +FlyWebPublishedServerChild::OnWebSocketResponse(InternalRequest* aRequest, + InternalResponse* aResponse) +{ + LOG_I("FlyWebPublishedServerChild::OnFetchResponse(%p)", this); + + if (mActorDestroyed) { + LOG_I("FlyWebPublishedServerChild::OnFetchResponse(%p) - No actor!", this); + return; + } + + uint64_t id = mPendingRequests.Get(aRequest); + MOZ_ASSERT(id); + mPendingRequests.Remove(aRequest); + + mPendingTransportProviders.Remove(id); + + IPCInternalResponse ipcResp; + UniquePtr<mozilla::ipc::AutoIPCStream> autoStream; + aResponse->ToIPC(&ipcResp, Manager(), autoStream); + + Unused << SendWebSocketResponse(ipcResp, id); + if (autoStream) { + autoStream->TakeOptionalValue(); + } +} + +void +FlyWebPublishedServerChild::Close() { - if (NS_SUCCEEDED(aStatus)) { - mPublishPromise->MaybeResolve(this); - } else { - Close(); - mPublishPromise->MaybeReject(aStatus); + LOG_I("FlyWebPublishedServerChild::Close(%p)", this); + + FlyWebPublishedServer::Close(); + + if (!mActorDestroyed) { + LOG_I("FlyWebPublishedServerChild::Close - sending __delete__ (%p)", this); + + Send__delete__(this); + } +} + +/******** FlyWebPublishedServerParent ********/ + +NS_IMPL_ISUPPORTS(FlyWebPublishedServerParent, nsIDOMEventListener) + +FlyWebPublishedServerParent::FlyWebPublishedServerParent(const nsAString& aName, + const FlyWebPublishOptions& aOptions) + : mActorDestroyed(false) + , mNextRequestId(1) +{ + LOG_I("FlyWebPublishedServerParent::FlyWebPublishedServerParent(%p)", this); + + RefPtr<FlyWebService> service = FlyWebService::GetOrCreate(); + if (!service) { + Unused << SendServerReady(NS_ERROR_FAILURE); + return; + } + + RefPtr<FlyWebPublishPromise> mozPromise = + service->PublishServer(aName, aOptions, nullptr); + if (!mozPromise) { + Unused << SendServerReady(NS_ERROR_FAILURE); + return; + } + + RefPtr<FlyWebPublishedServerParent> self = this; + + mozPromise->Then( + AbstractThread::MainThread(), + __func__, + [this, self] (FlyWebPublishedServer* aServer) { + mPublishedServer = static_cast<FlyWebPublishedServerImpl*>(aServer); + if (mActorDestroyed) { + mPublishedServer->Close(); + return; + } + + mPublishedServer->AddEventListener(NS_LITERAL_STRING("fetch"), + this, false, false, 2); + mPublishedServer->AddEventListener(NS_LITERAL_STRING("websocket"), + this, false, false, 2); + mPublishedServer->AddEventListener(NS_LITERAL_STRING("close"), + this, false, false, 2); + Unused << SendServerReady(NS_OK); + }, + [this, self] (nsresult aStatus) { + MOZ_ASSERT(NS_FAILED(aStatus)); + if (!mActorDestroyed) { + Unused << SendServerReady(aStatus); + } + }); +} + +NS_IMETHODIMP +FlyWebPublishedServerParent::HandleEvent(nsIDOMEvent* aEvent) +{ + if (mActorDestroyed) { + return NS_OK; + } + + nsAutoString type; + aEvent->GetType(type); + if (type.EqualsLiteral("close")) { + Unused << SendServerClose(); + return NS_OK; + } + + if (type.EqualsLiteral("fetch")) { + RefPtr<InternalRequest> request = + static_cast<FlyWebFetchEvent*>(aEvent)->Request()->GetInternalRequest(); + uint64_t id = mNextRequestId++; + mPendingRequests.Put(id, request); + + IPCInternalRequest ipcReq; + request->ToIPC(&ipcReq); + Unused << SendFetchRequest(ipcReq, id); + return NS_OK; + } + + if (type.EqualsLiteral("websocket")) { + RefPtr<InternalRequest> request = + static_cast<FlyWebWebSocketEvent*>(aEvent)->Request()->GetInternalRequest(); + uint64_t id = mNextRequestId++; + mPendingRequests.Put(id, request); + + RefPtr<TransportProviderParent> provider = + static_cast<TransportProviderParent*>( + mozilla::net::gNeckoParent->SendPTransportProviderConstructor()); + + IPCInternalRequest ipcReq; + request->ToIPC(&ipcReq); + Unused << SendWebSocketRequest(ipcReq, id, provider); + + mPendingTransportProviders.Put(id, provider.forget()); + return NS_OK; } + + MOZ_CRASH("Unknown event type"); + + return NS_OK; +} + +bool +FlyWebPublishedServerParent::RecvFetchResponse(const IPCInternalResponse& aResponse, + const uint64_t& aRequestId) +{ + MOZ_ASSERT(!mActorDestroyed); + + RefPtr<InternalRequest> request; + mPendingRequests.Remove(aRequestId, getter_AddRefs(request)); + if (!request) { + static_cast<ContentParent*>(Manager())->KillHard("unknown request id"); + return false; + } + + RefPtr<InternalResponse> response = InternalResponse::FromIPC(aResponse); + + mPublishedServer->OnFetchResponse(request, response); + + return true; +} + +bool +FlyWebPublishedServerParent::RecvWebSocketResponse(const IPCInternalResponse& aResponse, + const uint64_t& aRequestId) +{ + MOZ_ASSERT(!mActorDestroyed); + + mPendingTransportProviders.Remove(aRequestId); + + RefPtr<InternalRequest> request; + mPendingRequests.Remove(aRequestId, getter_AddRefs(request)); + if (!request) { + static_cast<ContentParent*>(Manager())->KillHard("unknown websocket request id"); + return false; + } + + RefPtr<InternalResponse> response = InternalResponse::FromIPC(aResponse); + + mPublishedServer->OnWebSocketResponse(request, response); + + return true; +} + +bool +FlyWebPublishedServerParent::RecvWebSocketAccept(const nsString& aProtocol, + const uint64_t& aRequestId) +{ + MOZ_ASSERT(!mActorDestroyed); + + RefPtr<TransportProviderParent> providerIPC; + mPendingTransportProviders.Remove(aRequestId, getter_AddRefs(providerIPC)); + + RefPtr<InternalRequest> request; + mPendingRequests.Remove(aRequestId, getter_AddRefs(request)); + + if (!request || !providerIPC) { + static_cast<ContentParent*>(Manager())->KillHard("unknown websocket request id"); + return false; + } + + Optional<nsAString> protocol; + if (!aProtocol.IsVoid()) { + protocol = &aProtocol; + } + + ErrorResult result; + nsCOMPtr<nsITransportProvider> providerServer = + mPublishedServer->OnWebSocketAcceptInternal(request, protocol, result); + if (result.Failed()) { + return false; + } + + providerServer->SetListener(providerIPC); + + return true; +} + +void +FlyWebPublishedServerParent::ActorDestroy(ActorDestroyReason aWhy) +{ + LOG_I("FlyWebPublishedServerParent::ActorDestroy(%p)", this); + + mActorDestroyed = true; +} + +bool +FlyWebPublishedServerParent::Recv__delete__() +{ + LOG_I("FlyWebPublishedServerParent::Recv__delete__(%p)", this); + MOZ_ASSERT(!mActorDestroyed); + + if (mPublishedServer) { + mPublishedServer->RemoveEventListener(NS_LITERAL_STRING("fetch"), + this, false); + mPublishedServer->RemoveEventListener(NS_LITERAL_STRING("websocket"), + this, false); + mPublishedServer->RemoveEventListener(NS_LITERAL_STRING("close"), + this, false); + mPublishedServer->Close(); + mPublishedServer = nullptr; + } + return true; } } // namespace dom } // namespace mozilla
--- a/dom/flyweb/FlyWebPublishedServer.h +++ b/dom/flyweb/FlyWebPublishedServer.h @@ -2,132 +2,105 @@ /* 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 mozilla_dom_FlyWebPublishedServer_h #define mozilla_dom_FlyWebPublishedServer_h -#include "nsISupportsImpl.h" -#include "nsICancelable.h" #include "mozilla/DOMEventTargetHelper.h" -#include "mozilla/ErrorResult.h" -#include "HttpServer.h" -#include "mozilla/dom/WebSocket.h" +#include "mozilla/MozPromise.h" class nsPIDOMWindowInner; +class nsITransportProvider; namespace mozilla { + +class ErrorResult; + namespace dom { -class Promise; class InternalResponse; class InternalRequest; +class WebSocket; struct FlyWebPublishOptions; class FlyWebPublishedServer; -class FlyWebPublishedServer final : public mozilla::DOMEventTargetHelper - , public HttpServerListener +typedef MozPromise<RefPtr<FlyWebPublishedServer>, nsresult, false> + FlyWebPublishPromise; + +class FlyWebPublishedServer : public mozilla::DOMEventTargetHelper { public: FlyWebPublishedServer(nsPIDOMWindowInner* aOwner, const nsAString& aName, - const FlyWebPublishOptions& aOptions, - Promise* aPublishPromise); + const FlyWebPublishOptions& aOptions); + + virtual void LastRelease() override; virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override; - NS_DECL_ISUPPORTS_INHERITED - uint64_t OwnerWindowID() const { return mOwnerWindowID; } - int32_t Port() - { - return mHttpServer ? mHttpServer->GetPort() : 0; - } - void GetCertKey(nsACString& aKey) { - if (mHttpServer) { - mHttpServer->GetCertKey(aKey); - } else { - aKey.Truncate(); - } - } - void GetName(nsAString& aName) { aName = mName; } nsAString& Name() { return mName; } - void GetCategory(nsAString& aCategory) - { - aCategory = mCategory; - } - - bool Http() - { - return mHttp; - } - - bool Message() - { - return mMessage; - } - void GetUiUrl(nsAString& aUiUrl) { aUiUrl = mUiUrl; } - void OnFetchResponse(InternalRequest* aRequest, - InternalResponse* aResponse); + virtual void OnFetchResponse(InternalRequest* aRequest, + InternalResponse* aResponse) = 0; already_AddRefed<WebSocket> OnWebSocketAccept(InternalRequest* aConnectRequest, const Optional<nsAString>& aProtocol, ErrorResult& aRv); - void OnWebSocketResponse(InternalRequest* aConnectRequest, - InternalResponse* aResponse); - - void SetCancelRegister(nsICancelable* aCancelRegister) - { - mMDNSCancelRegister = aCancelRegister; - } + virtual void OnWebSocketResponse(InternalRequest* aConnectRequest, + InternalResponse* aResponse) = 0; + virtual already_AddRefed<nsITransportProvider> + OnWebSocketAcceptInternal(InternalRequest* aConnectRequest, + const Optional<nsAString>& aProtocol, + ErrorResult& aRv) = 0; - void Close(); + virtual void Close(); - // HttpServerListener - virtual void OnServerStarted(nsresult aStatus) override; - virtual void OnRequest(InternalRequest* aRequest) override; - virtual void OnWebSocket(InternalRequest* aConnectRequest) override; - virtual void OnServerClose() override; + void FireFetchEvent(InternalRequest* aRequest); + void FireWebsocketEvent(InternalRequest* aConnectRequest); + void PublishedServerStarted(nsresult aStatus); IMPL_EVENT_HANDLER(fetch) IMPL_EVENT_HANDLER(websocket) IMPL_EVENT_HANDLER(close) - void DiscoveryStarted(nsresult aStatus); + already_AddRefed<FlyWebPublishPromise> + GetPublishPromise() + { + return mPublishPromise.Ensure(__func__); + } -private: - ~FlyWebPublishedServer(); +protected: + virtual ~FlyWebPublishedServer() + { + MOZ_ASSERT(!mIsRegistered, "Subclass dtor forgot to call Close()"); + } uint64_t mOwnerWindowID; - RefPtr<HttpServer> mHttpServer; - RefPtr<Promise> mPublishPromise; - nsCOMPtr<nsICancelable> mMDNSCancelRegister; + MozPromiseHolder<FlyWebPublishPromise> mPublishPromise; nsString mName; - nsString mCategory; - bool mHttp; - bool mMessage; nsString mUiUrl; bool mIsRegistered; }; } // namespace dom } // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/flyweb/FlyWebPublishedServerIPC.h @@ -0,0 +1,170 @@ +/* -*- 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 mozilla_dom_FlyWebPublishedServerIPC_h +#define mozilla_dom_FlyWebPublishedServerIPC_h + +#include "HttpServer.h" +#include "mozilla/dom/FlyWebPublishedServer.h" +#include "mozilla/dom/PFlyWebPublishedServerParent.h" +#include "mozilla/dom/PFlyWebPublishedServerChild.h" +#include "mozilla/MozPromise.h" +#include "nsICancelable.h" +#include "nsIDOMEventListener.h" +#include "nsISupportsImpl.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +namespace net { +class TransportProviderParent; +class TransportProviderChild; +} + +namespace dom { + +class FlyWebPublishedServerParent; + +class FlyWebPublishedServerImpl final : public FlyWebPublishedServer + , public HttpServerListener +{ +public: + FlyWebPublishedServerImpl(nsPIDOMWindowInner* aOwner, + const nsAString& aName, + const FlyWebPublishOptions& aOptions); + + NS_DECL_ISUPPORTS_INHERITED + + int32_t Port() + { + return mHttpServer ? mHttpServer->GetPort() : 0; + } + void GetCertKey(nsACString& aKey) { + if (mHttpServer) { + mHttpServer->GetCertKey(aKey); + } else { + aKey.Truncate(); + } + } + + virtual void OnFetchResponse(InternalRequest* aRequest, + InternalResponse* aResponse) override; + virtual void OnWebSocketResponse(InternalRequest* aConnectRequest, + InternalResponse* aResponse) override; + virtual already_AddRefed<nsITransportProvider> + OnWebSocketAcceptInternal(InternalRequest* aConnectRequest, + const Optional<nsAString>& aProtocol, + ErrorResult& aRv) override; + + void SetCancelRegister(nsICancelable* aCancelRegister) + { + mMDNSCancelRegister = aCancelRegister; + } + + virtual void Close() override; + + // HttpServerListener + virtual void OnServerStarted(nsresult aStatus) override; + virtual void OnRequest(InternalRequest* aRequest) override + { + FireFetchEvent(aRequest); + } + virtual void OnWebSocket(InternalRequest* aConnectRequest) override + { + FireWebsocketEvent(aConnectRequest); + } + virtual void OnServerClose() override + { + mHttpServer = nullptr; + Close(); + } + +private: + ~FlyWebPublishedServerImpl() {} + + RefPtr<HttpServer> mHttpServer; + nsCOMPtr<nsICancelable> mMDNSCancelRegister; + RefPtr<FlyWebPublishedServerParent> mServerParent; +}; + +class FlyWebPublishedServerChild final : public FlyWebPublishedServer + , public PFlyWebPublishedServerChild +{ +public: + FlyWebPublishedServerChild(nsPIDOMWindowInner* aOwner, + const nsAString& aName, + const FlyWebPublishOptions& aOptions); + + virtual bool RecvServerReady(const nsresult& aStatus) override; + virtual bool RecvServerClose() override; + virtual bool RecvFetchRequest(const IPCInternalRequest& aRequest, + const uint64_t& aRequestId) override; + virtual bool RecvWebSocketRequest(const IPCInternalRequest& aRequest, + const uint64_t& aRequestId, + PTransportProviderChild* aProvider) override; + + virtual void OnFetchResponse(InternalRequest* aRequest, + InternalResponse* aResponse) override; + virtual void OnWebSocketResponse(InternalRequest* aConnectRequest, + InternalResponse* aResponse) override; + virtual already_AddRefed<nsITransportProvider> + OnWebSocketAcceptInternal(InternalRequest* aConnectRequest, + const Optional<nsAString>& aProtocol, + ErrorResult& aRv) override; + + virtual void Close() override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + +private: + ~FlyWebPublishedServerChild() {} + + nsDataHashtable<nsRefPtrHashKey<InternalRequest>, uint64_t> mPendingRequests; + nsRefPtrHashtable<nsUint64HashKey, TransportProviderChild> + mPendingTransportProviders; + bool mActorDestroyed; +}; + +class FlyWebPublishedServerParent final : public PFlyWebPublishedServerParent + , public nsIDOMEventListener +{ +public: + FlyWebPublishedServerParent(const nsAString& aName, + const FlyWebPublishOptions& aOptions); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + +private: + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + Recv__delete__() override; + virtual bool + RecvFetchResponse(const IPCInternalResponse& aResponse, + const uint64_t& aRequestId) override; + virtual bool + RecvWebSocketResponse(const IPCInternalResponse& aResponse, + const uint64_t& aRequestId) override; + virtual bool + RecvWebSocketAccept(const nsString& aProtocol, + const uint64_t& aRequestId) override; + + ~FlyWebPublishedServerParent() {} + + bool mActorDestroyed; + uint64_t mNextRequestId; + nsRefPtrHashtable<nsUint64HashKey, InternalRequest> mPendingRequests; + nsRefPtrHashtable<nsUint64HashKey, TransportProviderParent> + mPendingTransportProviders; + RefPtr<FlyWebPublishedServerImpl> mPublishedServer; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FlyWebPublishedServerIPC_h
--- a/dom/flyweb/FlyWebService.cpp +++ b/dom/flyweb/FlyWebService.cpp @@ -3,17 +3,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/. */ #include "mozilla/dom/FlyWebService.h" #include "mozilla/StaticPtr.h" #include "mozilla/ScopeExit.h" #include "mozilla/dom/Promise.h" -#include "mozilla/dom/FlyWebPublishedServer.h" +#include "mozilla/dom/FlyWebPublishedServerIPC.h" #include "nsISocketTransportService.h" #include "mdns/libmdns/nsDNSServiceInfo.h" #include "nsIUUIDGenerator.h" #include "nsStandardURL.h" #include "mozilla/Services.h" #include "nsISupportsPrimitives.h" #include "mozilla/dom/FlyWebDiscoveryManagerBinding.h" #include "prnetdb.h" @@ -72,17 +72,17 @@ private: nsresult StartDiscovery(); nsresult StopDiscovery(); void ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices); bool HasService(const nsAString& aServiceId); nsresult PairWithService(const nsAString& aServiceId, UniquePtr<FlyWebService::PairedInfo>& aInfo); - nsresult StartDiscoveryOf(FlyWebPublishedServer* aServer); + nsresult StartDiscoveryOf(FlyWebPublishedServerImpl* aServer); void EnsureDiscoveryStarted(); void EnsureDiscoveryStopped(); // Cycle-breaking link to manager. FlyWebService* mService; nsCString mServiceType; @@ -437,17 +437,17 @@ FlyWebMDNSService::OnServiceRegistered(n nsString name = NS_ConvertUTF8toUTF16(cName); RefPtr<FlyWebPublishedServer> existingServer = FlyWebService::GetOrCreate()->FindPublishedServerByName(name); if (!existingServer) { return NS_ERROR_FAILURE; } - existingServer->DiscoveryStarted(NS_OK); + existingServer->PublishedServerStarted(NS_OK); return NS_OK; } nsresult FlyWebMDNSService::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo) { LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceUnregistered"); @@ -484,17 +484,17 @@ FlyWebMDNSService::OnRegistrationFailed( FlyWebService::GetOrCreate()->FindPublishedServerByName(name); if (!existingServer) { return NS_ERROR_FAILURE; } LOG_I("OnServiceRegistered(MDNS): Registration of server with name %s failed.", cName.get()); // Remove the nsICancelable from the published server. - existingServer->DiscoveryStarted(NS_ERROR_FAILURE); + existingServer->PublishedServerStarted(NS_ERROR_FAILURE); return NS_OK; } nsresult FlyWebMDNSService::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t errorCode) { LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnUnregistrationFailed"); @@ -691,17 +691,17 @@ FlyWebMDNSService::PairWithService(const aInfo->mService.mDiscoveredService = discInfo->mService; aInfo->mDNSServiceInfo = discInfo->mDNSServiceInfo; return NS_OK; } nsresult -FlyWebMDNSService::StartDiscoveryOf(FlyWebPublishedServer* aServer) +FlyWebMDNSService::StartDiscoveryOf(FlyWebPublishedServerImpl* aServer) { RefPtr<FlyWebPublishedServer> existingServer = FlyWebService::GetOrCreate()->FindPublishedServerByName(aServer->Name()); MOZ_ASSERT(existingServer); // Advertise the service via mdns. RefPtr<net::nsDNSServiceInfo> serviceInfo(new net::nsDNSServiceInfo()); @@ -800,16 +800,24 @@ FlyWebService::GetOrCreate() } } return gFlyWebService; } ErrorResult FlyWebService::Init() { + // Most functions of FlyWebService should not be started in the child. + // Instead FlyWebService in the child is mainly responsible for tracking + // publishedServer lifetimes. Other functions are handled by the + // FlyWebService running in the parent. + if (XRE_GetProcessType() == GeckoProcessType_Content) { + return ErrorResult(NS_OK); + } + MOZ_ASSERT(NS_IsMainThread()); if (!mMDNSHttpService) { mMDNSHttpService = new FlyWebMDNSService(this, NS_LITERAL_CSTRING("_http._tcp.")); ErrorResult rv; rv = mMDNSHttpService->Init(); if (rv.Failed()) { LOG_E("FlyWebService failed to initialize MDNS _http._tcp."); @@ -828,46 +836,44 @@ FlyWebService::Init() mMDNSFlywebService = nullptr; rv.SuppressException(); } } return ErrorResult(NS_OK); } -already_AddRefed<Promise> +already_AddRefed<FlyWebPublishPromise> FlyWebService::PublishServer(const nsAString& aName, const FlyWebPublishOptions& aOptions, - nsPIDOMWindowInner* aWindow, - ErrorResult& aRv) + nsPIDOMWindowInner* aWindow) { - MOZ_ASSERT(NS_IsMainThread()); - nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow); - RefPtr<Promise> promise = Promise::Create(global, aRv); - if (aRv.Failed()) { - return nullptr; - } - // Scan uiUrl for illegal characters RefPtr<FlyWebPublishedServer> existingServer = FlyWebService::GetOrCreate()->FindPublishedServerByName(aName); if (existingServer) { LOG_I("PublishServer: Trying to publish server with already-existing name %s.", NS_ConvertUTF16toUTF8(aName).get()); - promise->MaybeReject(NS_ERROR_FAILURE); + MozPromiseHolder<FlyWebPublishPromise> holder; + RefPtr<FlyWebPublishPromise> promise = holder.Ensure(__func__); + holder.Reject(NS_ERROR_FAILURE, __func__); return promise.forget(); } - RefPtr<FlyWebPublishedServer> server = - new FlyWebPublishedServer(aWindow, aName, aOptions, promise); + RefPtr<FlyWebPublishedServer> server; + if (XRE_GetProcessType() == GeckoProcessType_Content) { + server = new FlyWebPublishedServerChild(aWindow, aName, aOptions); + } else { + server = new FlyWebPublishedServerImpl(aWindow, aName, aOptions); + } mServers.AppendElement(server); - return promise.forget(); + return server->GetPublishPromise(); } already_AddRefed<FlyWebPublishedServer> FlyWebService::FindPublishedServerByName( const nsAString& aName) { MOZ_ASSERT(NS_IsMainThread()); for (FlyWebPublishedServer* publishedServer : mServers) { @@ -1106,22 +1112,22 @@ FlyWebService::CreateTransportForHost(co types, typeCount, host, port, hostRoute, portRoute, proxyInfo, &netAddr); NS_ENSURE_SUCCESS(rv, rv); trans.forget(result); return NS_OK; } void -FlyWebService::StartDiscoveryOf(FlyWebPublishedServer* aServer) +FlyWebService::StartDiscoveryOf(FlyWebPublishedServerImpl* aServer) { MOZ_ASSERT(NS_IsMainThread()); nsresult rv = mMDNSFlywebService ? mMDNSFlywebService->StartDiscoveryOf(aServer) : NS_ERROR_FAILURE; if (NS_FAILED(rv)) { - aServer->DiscoveryStarted(rv); + aServer->PublishedServerStarted(rv); } } } // namespace dom } // namespace mozilla
--- a/dom/flyweb/FlyWebService.h +++ b/dom/flyweb/FlyWebService.h @@ -8,55 +8,59 @@ #define mozilla_dom_FlyWebService_h #include "nsISupportsImpl.h" #include "mozilla/ErrorResult.h" #include "nsIProtocolHandler.h" #include "nsDataHashtable.h" #include "nsClassHashtable.h" #include "nsIObserver.h" +#include "mozilla/MozPromise.h" #include "mozilla/ReentrantMonitor.h" #include "mozilla/dom/FlyWebDiscoveryManagerBinding.h" #include "nsITimer.h" #include "nsICancelable.h" #include "nsIDNSServiceDiscovery.h" class nsPIDOMWindowInner; class nsIProxyInfo; class nsISocketTransport; namespace mozilla { namespace dom { -class Promise; struct FlyWebPublishOptions; struct FlyWebFilter; class FlyWebPublishedServer; +class FlyWebPublishedServerImpl; class FlyWebPairingCallback; class FlyWebDiscoveryManager; class FlyWebMDNSService; +typedef MozPromise<RefPtr<FlyWebPublishedServer>, nsresult, false> + FlyWebPublishPromise; + class FlyWebService final : public nsIObserver { friend class FlyWebMDNSService; public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIOBSERVER static FlyWebService* GetExisting(); static FlyWebService* GetOrCreate(); static already_AddRefed<FlyWebService> GetOrCreateAddRefed() { return do_AddRef(GetOrCreate()); } - already_AddRefed<Promise> PublishServer(const nsAString& aName, - const FlyWebPublishOptions& aOptions, - nsPIDOMWindowInner* aWindow, - ErrorResult& aRv); + already_AddRefed<FlyWebPublishPromise> + PublishServer(const nsAString& aName, + const FlyWebPublishOptions& aOptions, + nsPIDOMWindowInner* aWindow); void UnregisterServer(FlyWebPublishedServer* aServer); bool HasConnectionOrServer(uint64_t aWindowID); void ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices); void PairWithService(const nsAString& aServiceId, FlyWebPairingCallback& aCallback); nsresult CreateTransportForHost(const char **types, @@ -69,18 +73,18 @@ public: nsISocketTransport **result); already_AddRefed<FlyWebPublishedServer> FindPublishedServerByName( const nsAString& aName); void RegisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager); void UnregisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager); - // Should only be called by FlyWebPublishedServer - void StartDiscoveryOf(FlyWebPublishedServer* aServer); + // Should only be called by FlyWebPublishedServerImpl + void StartDiscoveryOf(FlyWebPublishedServerImpl* aServer); private: FlyWebService(); ~FlyWebService(); ErrorResult Init(); void NotifyDiscoveredServicesChanged();
--- a/dom/flyweb/HttpServer.cpp +++ b/dom/flyweb/HttpServer.cpp @@ -170,25 +170,24 @@ HttpServer::SendResponse(InternalRequest } MOZ_ASSERT(false, "Unknown request"); } already_AddRefed<nsITransportProvider> HttpServer::AcceptWebSocket(InternalRequest* aConnectRequest, const Optional<nsAString>& aProtocol, - nsACString& aNegotiatedExtensions, ErrorResult& aRv) { for (Connection* conn : mConnections) { if (!conn->HasPendingWebSocketRequest(aConnectRequest)) { continue; } nsCOMPtr<nsITransportProvider> provider = - conn->HandleAcceptWebSocket(aProtocol, aNegotiatedExtensions, aRv); + conn->HandleAcceptWebSocket(aProtocol, aRv); if (aRv.Failed()) { conn->Close(); } // This connection is now owned by the websocket, or we just closed it mConnections.RemoveElement(conn); return provider.forget(); } @@ -216,18 +215,18 @@ void HttpServer::Close() { if (mServerSocket) { mServerSocket->Close(); mServerSocket = nullptr; } if (mListener) { - mListener->OnServerClose(); - mListener = nullptr; + RefPtr<HttpServerListener> listener = mListener.forget(); + listener->OnServerClose(); } for (Connection* conn : mConnections) { conn->Close(); } mConnections.Clear(); } @@ -256,16 +255,23 @@ HttpServer::TransportProvider::SetListen mListener = aListener; MaybeNotify(); return NS_OK; } +NS_IMETHODIMP_(PTransportProviderChild*) +HttpServer::TransportProvider::GetIPCChild() +{ + MOZ_CRASH("Don't call this in parent process"); + return nullptr; +} + void HttpServer::TransportProvider::SetTransport(nsISocketTransport* aTransport, nsIAsyncInputStream* aInput, nsIAsyncOutputStream* aOutput) { MOZ_ASSERT(!mTransport); MOZ_ASSERT(aTransport && aInput && aOutput); @@ -488,17 +494,17 @@ HttpServer::Connection::ConsumeInput(con mCurrentRequestBody = nullptr; mState = eRequestLine; } } return NS_OK; } -static bool +bool ContainsToken(const nsCString& aList, const nsCString& aToken) { nsCCharSeparatedTokenizer tokens(aList, ','); bool found = false; while (!found && tokens.hasMoreTokens()) { found = tokens.nextToken().Equals(aToken); } return found; @@ -785,17 +791,16 @@ HttpServer::Connection::TryHandleRespons } } return handledResponse; } already_AddRefed<nsITransportProvider> HttpServer::Connection::HandleAcceptWebSocket(const Optional<nsAString>& aProtocol, - nsACString& aNegotiatedExtensions, ErrorResult& aRv) { MOZ_ASSERT(mPendingWebSocketRequest); RefPtr<InternalResponse> response = new InternalResponse(101, NS_LITERAL_CSTRING("Switching Protocols")); InternalHeaders* headers = response->Headers(); @@ -825,24 +830,24 @@ HttpServer::Connection::HandleAcceptWebS Get(NS_LITERAL_CSTRING("Sec-WebSocket-Key"), key, aRv); nsresult rv = mozilla::net::CalculateWebSocketHashedSecret(key, hash); if (NS_FAILED(rv)) { aRv.Throw(rv); return nullptr; } headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Accept"), hash, aRv); - nsAutoCString extensions; + nsAutoCString extensions, negotiatedExtensions; mPendingWebSocketRequest->Headers()-> Get(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions, aRv); mozilla::net::ProcessServerWebSocketExtensions(extensions, - aNegotiatedExtensions); - if (!aNegotiatedExtensions.IsEmpty()) { + negotiatedExtensions); + if (!negotiatedExtensions.IsEmpty()) { headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), - aNegotiatedExtensions, aRv); + negotiatedExtensions, aRv); } RefPtr<TransportProvider> result = new TransportProvider(); mWebSocketTransportProvider = result; QueueResponse(response); return result.forget(); @@ -907,17 +912,17 @@ HttpServer::Connection::QueueResponse(In nsCString head(NS_LITERAL_CSTRING("HTTP/1.1 ")); head.AppendInt(aResponse->GetStatus()); // XXX is the statustext security checked? head.Append(NS_LITERAL_CSTRING(" ") + aResponse->GetStatusText() + NS_LITERAL_CSTRING("\r\n")); - nsTArray<InternalHeaders::Entry> entries; + AutoTArray<InternalHeaders::Entry, 16> entries; headers->GetEntries(entries); for (auto header : entries) { head.Append(header.mName + NS_LITERAL_CSTRING(": ") + header.mValue + NS_LITERAL_CSTRING("\r\n")); }
--- a/dom/flyweb/HttpServer.h +++ b/dom/flyweb/HttpServer.h @@ -18,16 +18,19 @@ #include "nsITransportProvider.h" #include "nsILocalCertService.h" class nsIX509Cert; namespace mozilla { namespace dom { +extern bool +ContainsToken(const nsCString& aList, const nsCString& aToken); + class InternalRequest; class InternalResponse; class HttpServerListener { public: // switch to NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING when that lands NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; @@ -50,17 +53,16 @@ public: NS_DECL_NSILOCALCERTGETCALLBACK void Init(int32_t aPort, bool aHttps, HttpServerListener* aListener); void SendResponse(InternalRequest* aRequest, InternalResponse* aResponse); already_AddRefed<nsITransportProvider> AcceptWebSocket(InternalRequest* aConnectRequest, const Optional<nsAString>& aProtocol, - nsACString& aNegotiatedExtensions, ErrorResult& aRv); void SendWebSocketResponse(InternalRequest* aConnectRequest, InternalResponse* aResponse); void Close(); void GetCertKey(nsACString& aKey); @@ -108,17 +110,16 @@ private: NS_DECL_NSIINPUTSTREAMCALLBACK NS_DECL_NSIOUTPUTSTREAMCALLBACK NS_DECL_NSITLSSERVERSECURITYOBSERVER bool TryHandleResponse(InternalRequest* aRequest, InternalResponse* aResponse); already_AddRefed<nsITransportProvider> HandleAcceptWebSocket(const Optional<nsAString>& aProtocol, - nsACString& aNegotiatedExtensions, ErrorResult& aRv); void HandleWebSocketResponse(InternalResponse* aResponse); bool HasPendingWebSocketRequest(InternalRequest* aRequest) { return aRequest == mPendingWebSocketRequest; } void Close();
new file mode 100644 --- /dev/null +++ b/dom/flyweb/PFlyWebPublishedServer.ipdl @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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 protocol PContent; +include protocol PSendStream; +include protocol PFileDescriptorSet; +include protocol PTransportProvider; +include FetchTypes; +include ChannelInfo; +include PBackgroundSharedTypes; + +namespace mozilla { +namespace dom { + +async protocol PFlyWebPublishedServer +{ + manager PContent; + +child: + async ServerReady(nsresult aStatus); + async FetchRequest(IPCInternalRequest aRequest, uint64_t aRequestId); + async WebSocketRequest(IPCInternalRequest aRequest, uint64_t aRequestId, + PTransportProvider aProvider); + async ServerClose(); + +parent: + async __delete__(); + + async FetchResponse(IPCInternalResponse aResponse, uint64_t aRequestId); + async WebSocketResponse(IPCInternalResponse aResponse, uint64_t aRequestId); + async WebSocketAccept(nsString aProtocol, uint64_t aRequestId); +}; + +} // namespace dom +} // namespace mozilla
--- a/dom/flyweb/moz.build +++ b/dom/flyweb/moz.build @@ -2,35 +2,41 @@ # 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 += [ 'FlyWebDiscoveryManager.h', 'FlyWebPublishedServer.h', + 'FlyWebPublishedServerIPC.h', + 'FlyWebPublishOptionsIPCSerializer.h', 'FlyWebServerEvents.h', 'FlyWebService.h', 'HttpServer.h', ] UNIFIED_SOURCES += [ 'FlyWebDiscoveryManager.cpp', 'FlyWebPublishedServer.cpp', 'FlyWebServerEvents.cpp', 'FlyWebService.cpp', 'HttpServer.cpp' ] -#include('/ipc/chromium/chromium-config.mozbuild') +IPDL_SOURCES += [ + 'PFlyWebPublishedServer.ipdl', +] FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '/dom/base', '/netwerk/base', '/netwerk/dns', '/netwerk/protocol/websocket', '/xpcom/io' ] +include('/ipc/chromium/chromium-config.mozbuild') + if CONFIG['GNU_CXX']: CXXFLAGS += ['-Wshadow']
--- a/dom/indexedDB/test/browser_permissionsPromptDeny.js +++ b/dom/indexedDB/test/browser_permissionsPromptDeny.js @@ -2,30 +2,18 @@ * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const testPageURL = "http://mochi.test:8888/browser/" + "dom/indexedDB/test/browser_permissionsPrompt.html"; const notificationID = "indexedDB-permissions-prompt"; -function setUsePrivateBrowsing(browser, val) { - if (!browser.isRemoteBrowser) { - browser.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = val; - return; - } - - return ContentTask.spawn(browser, val, function* (val) { - docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing = val; - }); -}; - - -function promiseMessage(aMessage) { - return ContentTask.spawn(gBrowser.selectedBrowser, aMessage, function* (aMessage) { +function promiseMessage(aMessage, browser) { + return ContentTask.spawn(browser.selectedBrowser, aMessage, function* (aMessage) { yield new Promise((resolve, reject) => { content.addEventListener("message", function messageListener(event) { content.removeEventListener("message", messageListener); is(event.data, aMessage, "received " + aMessage); if (event.data == aMessage) resolve(); else reject(); @@ -50,51 +38,52 @@ add_task(function test1() { registerPopupEventHandler("popupshown", function () { ok(true, "prompt shown"); triggerSecondaryCommand(this, 0); }); registerPopupEventHandler("popuphidden", function () { ok(true, "prompt hidden"); }); - yield promiseMessage("InvalidStateError"); + yield promiseMessage("InvalidStateError", gBrowser); is(getPermission(testPageURL, "indexedDB"), Components.interfaces.nsIPermissionManager.DENY_ACTION, "Correct permission set"); gBrowser.removeCurrentTab(); }); add_task(function test2() { + info("creating private window"); + let win = yield BrowserTestUtils.openNewBrowserWindow({ private : true }); + info("creating private tab"); - gBrowser.selectedTab = gBrowser.addTab(); - yield setUsePrivateBrowsing(gBrowser.selectedBrowser, true); + win.gBrowser.selectedTab = win.gBrowser.addTab(); info("loading test page: " + testPageURL); - gBrowser.selectedBrowser.loadURI(testPageURL); - yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); - + win.gBrowser.selectedBrowser.loadURI(testPageURL); + yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); + registerPopupEventHandler("popupshowing", function () { ok(false, "prompt showing"); }); registerPopupEventHandler("popupshown", function () { ok(false, "prompt shown"); }); registerPopupEventHandler("popuphidden", function () { ok(false, "prompt hidden"); }); - - yield promiseMessage("InvalidStateError"); + yield promiseMessage("InvalidStateError", win.gBrowser); is(getPermission(testPageURL, "indexedDB"), Components.interfaces.nsIPermissionManager.DENY_ACTION, "Correct permission set"); unregisterAllPopupEventHandlers(); - yield setUsePrivateBrowsing(gBrowser.selectedBrowser, false); - gBrowser.removeCurrentTab(); + win.gBrowser.removeCurrentTab(); + win.close(); }); add_task(function test3() { info("creating tab"); gBrowser.selectedTab = gBrowser.addTab(); info("loading test page: " + testPageURL); gBrowser.selectedBrowser.loadURI(testPageURL); @@ -105,17 +94,17 @@ add_task(function test3() { }); registerPopupEventHandler("popupshown", function () { ok(false, "Shouldn't show a popup this time"); }); registerPopupEventHandler("popuphidden", function () { ok(false, "Shouldn't show a popup this time"); }); - yield promiseMessage("InvalidStateError"); + yield promiseMessage("InvalidStateError", gBrowser); is(getPermission(testPageURL, "indexedDB"), Components.interfaces.nsIPermissionManager.DENY_ACTION, "Correct permission set"); gBrowser.removeCurrentTab(); unregisterAllPopupEventHandlers(); removePermission(testPageURL, "indexedDB"); });
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -27,16 +27,17 @@ #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h" #include "mozilla/docshell/OfflineCacheUpdateChild.h" #include "mozilla/dom/ContentBridgeChild.h" #include "mozilla/dom/ContentBridgeParent.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/DataTransfer.h" #include "mozilla/dom/DOMStorageIPC.h" #include "mozilla/dom/ExternalHelperAppChild.h" +#include "mozilla/dom/FlyWebPublishedServerIPC.h" #include "mozilla/dom/PCrashReporterChild.h" #include "mozilla/dom/ProcessGlobal.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/nsIContentChild.h" #include "mozilla/psm/PSMContentListener.h" #include "mozilla/hal_sandbox/PHalChild.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/FileDescriptorSetChild.h" @@ -1684,16 +1685,32 @@ ContentChild::AllocPPresentationChild() bool ContentChild::DeallocPPresentationChild(PPresentationChild* aActor) { delete aActor; return true; } +PFlyWebPublishedServerChild* +ContentChild::AllocPFlyWebPublishedServerChild(const nsString& name, + const FlyWebPublishOptions& params) +{ + MOZ_CRASH("We should never be manually allocating PFlyWebPublishedServerChild actors"); + return nullptr; +} + +bool +ContentChild::DeallocPFlyWebPublishedServerChild(PFlyWebPublishedServerChild* aActor) +{ + RefPtr<FlyWebPublishedServerChild> actor = + dont_AddRef(static_cast<FlyWebPublishedServerChild*>(aActor)); + return true; +} + bool ContentChild::RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe, const nsString& aSessionId) { nsCOMPtr<nsIDocShell> docShell = do_GetInterface(static_cast<TabChild*>(aIframe)->WebNavigation()); NS_WARN_IF(!docShell);
--- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -356,16 +356,22 @@ public: virtual PFMRadioChild* AllocPFMRadioChild() override; virtual bool DeallocPFMRadioChild(PFMRadioChild* aActor) override; virtual PPresentationChild* AllocPPresentationChild() override; virtual bool DeallocPPresentationChild(PPresentationChild* aActor) override; + virtual PFlyWebPublishedServerChild* + AllocPFlyWebPublishedServerChild(const nsString& name, + const FlyWebPublishOptions& params) override; + + virtual bool DeallocPFlyWebPublishedServerChild(PFlyWebPublishedServerChild* aActor) override; + virtual bool RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe, const nsString& aSessionId) override; virtual bool RecvNotifyPresentationReceiverCleanUp(const nsString& aSessionId) override; virtual bool RecvNotifyGMPsChanged() override;
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -63,16 +63,17 @@ #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h" #include "mozilla/dom/icc/IccParent.h" #include "mozilla/dom/mobileconnection/MobileConnectionParent.h" #include "mozilla/dom/mobilemessage/SmsParent.h" #include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/Permissions.h" #include "mozilla/dom/PresentationParent.h" #include "mozilla/dom/PPresentationParent.h" +#include "mozilla/dom/FlyWebPublishedServerIPC.h" #include "mozilla/dom/quota/QuotaManagerService.h" #include "mozilla/dom/telephony/TelephonyParent.h" #include "mozilla/dom/time/DateCacheCleaner.h" #include "mozilla/dom/voicemail/VoicemailParent.h" #include "mozilla/embedding/printingui/PrintingParent.h" #include "mozilla/gfx/GPUProcessManager.h" #include "mozilla/hal_sandbox/PHalParent.h" #include "mozilla/ipc/BackgroundChild.h" @@ -4143,16 +4144,33 @@ ContentParent::DeallocPPresentationParen } bool ContentParent::RecvPPresentationConstructor(PPresentationParent* aActor) { return static_cast<PresentationParent*>(aActor)->Init(); } +PFlyWebPublishedServerParent* +ContentParent::AllocPFlyWebPublishedServerParent(const nsString& name, + const FlyWebPublishOptions& params) +{ + RefPtr<FlyWebPublishedServerParent> actor = + new FlyWebPublishedServerParent(name, params); + return actor.forget().take(); +} + +bool +ContentParent::DeallocPFlyWebPublishedServerParent(PFlyWebPublishedServerParent* aActor) +{ + RefPtr<FlyWebPublishedServerParent> actor = + dont_AddRef(static_cast<FlyWebPublishedServerParent*>(aActor)); + return true; +} + PSpeechSynthesisParent* ContentParent::AllocPSpeechSynthesisParent() { #ifdef MOZ_WEBSPEECH return new mozilla::dom::SpeechSynthesisParent(); #else return nullptr; #endif
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -863,16 +863,22 @@ private: virtual bool DeallocPFMRadioParent(PFMRadioParent* aActor) override; virtual PPresentationParent* AllocPPresentationParent() override; virtual bool DeallocPPresentationParent(PPresentationParent* aActor) override; virtual bool RecvPPresentationConstructor(PPresentationParent* aActor) override; + virtual PFlyWebPublishedServerParent* + AllocPFlyWebPublishedServerParent(const nsString& name, + const FlyWebPublishOptions& params) override; + + virtual bool DeallocPFlyWebPublishedServerParent(PFlyWebPublishedServerParent* aActor) override; + virtual PSpeechSynthesisParent* AllocPSpeechSynthesisParent() override; virtual bool DeallocPSpeechSynthesisParent(PSpeechSynthesisParent* aActor) override; virtual bool RecvPSpeechSynthesisConstructor(PSpeechSynthesisParent* aActor) override;
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -52,16 +52,17 @@ include protocol PTelephony; include protocol PTestShell; include protocol PVoicemail; include protocol PJavaScript; include protocol PRemoteSpellcheckEngine; include protocol PWebBrowserPersistDocument; include protocol PWebrtcGlobal; include protocol PPresentation; include protocol PVRManager; +include protocol PFlyWebPublishedServer; include DOMTypes; include JavaScriptTypes; include InputStreamParams; include PTabContext; include URIParams; include PluginTypes; include ProtocolTypes; include PBackgroundSharedTypes; @@ -93,16 +94,17 @@ using mozilla::gfx::IntSize from "mozill using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h"; using mozilla::LayoutDeviceIntPoint from "Units.h"; using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h"; using class mozilla::dom::ipc::StructuredCloneData from "ipc/IPCMessageUtils.h"; using mozilla::DataStorageType from "ipc/DataStorageIPCUtils.h"; using mozilla::DocShellOriginAttributes from "mozilla/ipc/BackgroundUtils.h"; using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; +using struct mozilla::dom::FlyWebPublishOptions from "mozilla/dom/FlyWebPublishOptionsIPCSerializer.h"; union ChromeRegistryItem { ChromePackage; OverrideMapping; SubstitutionMapping; }; @@ -428,16 +430,17 @@ prio(normal upto urgent) sync protocol P manages PTelephony; manages PTestShell; manages PVoicemail; manages PJavaScript; manages PRemoteSpellcheckEngine; manages PWebBrowserPersistDocument; manages PWebrtcGlobal; manages PPresentation; + manages PFlyWebPublishedServer; both: // Depending on exactly how the new browser is being created, it might be // created from either the child or parent process! // // The child creates the PBrowser as part of // TabChild::BrowserFrameProvideWindow (which happens when the child's // content calls window.open()), and the parent creates the PBrowser as part @@ -848,16 +851,18 @@ parent: async PBluetooth(); async PFMRadio(); async PWebrtcGlobal(); async PPresentation(); + async PFlyWebPublishedServer(nsString name, FlyWebPublishOptions params); + // Services remoting async StartVisitedQuery(URIParams uri); async VisitURI(URIParams uri, OptionalURIParams referrer, uint32_t flags); async SetURITitle(URIParams uri, nsString title); async LoadURIExternal(URIParams uri, PBrowser windowContext);
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -1904,53 +1904,63 @@ TabChild::RecvSynthMouseMoveEvent(const return RecvRealMouseButtonEvent(aEvent, aGuid, aInputBlockId); } bool TabChild::RecvRealMouseButtonEvent(const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId) { + if (aInputBlockId) { + MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ); + nsCOMPtr<nsIDocument> document(GetDocument()); + APZCCallbackHelper::SendSetTargetAPZCNotification( + mPuppetWidget, document, aEvent, aGuid, aInputBlockId); + } + nsEventStatus unused; InputAPZContext context(aGuid, aInputBlockId, unused); WidgetMouseEvent localEvent(aEvent); localEvent.mWidget = mPuppetWidget; APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid, mPuppetWidget->GetDefaultScale()); APZCCallbackHelper::DispatchWidgetEvent(localEvent); - if (aEvent.mFlags.mHandledByAPZ) { + if (aInputBlockId) { + MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ); mAPZEventState->ProcessMouseEvent(aEvent, aGuid, aInputBlockId); } return true; } bool TabChild::RecvMouseWheelEvent(const WidgetWheelEvent& aEvent, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId) { - if (aEvent.mFlags.mHandledByAPZ) { + if (aInputBlockId) { + MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ); nsCOMPtr<nsIDocument> document(GetDocument()); APZCCallbackHelper::SendSetTargetAPZCNotification( mPuppetWidget, document, aEvent, aGuid, aInputBlockId); } WidgetWheelEvent localEvent(aEvent); localEvent.mWidget = mPuppetWidget; APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid, mPuppetWidget->GetDefaultScale()); APZCCallbackHelper::DispatchWidgetEvent(localEvent); if (localEvent.mCanTriggerSwipe) { SendRespondStartSwipeEvent(aInputBlockId, localEvent.TriggersSwipe()); } - if (aEvent.mFlags.mHandledByAPZ) { + if (aInputBlockId) { + MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ); mAPZEventState->ProcessWheelEvent(localEvent, aGuid, aInputBlockId); } return true; } bool TabChild::RecvMouseScrollTestEvent(const uint64_t& aLayersId, const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
--- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -224,8 +224,18 @@ TargetPrincipalDoesNotMatch=Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘%S’) does not match the recipient window’s origin (‘%S’). RewriteYoutubeEmbed=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible. # LOCALIZATION NOTE: Do not translate 'youtube'. %S values are origins, like https://domain.com:port RewriteYoutubeEmbedInvalidQuery=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Query was invalid and removed from URL. Please update page to use iframe instead of embed/object, if possible. # LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string. PushMessageDecryptionFailure=The ServiceWorker for scope ‘%1$S’ encountered an error decrypting a push message: ‘%2$S’. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption # LOCALIZATION NOTE: %1$S is the type of a DOM event. 'passive' is a literal parameter from the DOM spec. PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’. FileLastModifiedDateWarning=File.lastModifiedDate is deprecated. Use File.lastModified instead. +ChromeScriptedDOMParserWithoutPrincipal=Creating DOMParser without a principal is deprecated. +IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches. +BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches. +# LOCALIZATION NOTE: Do not translate ".jpeg" +GenericImageNameJPEG=image.jpeg +# LOCALIZATION NOTE: Do not translate ".gif" +GenericImageNameGIF=image.gif +# LOCALIZATION NOTE: Do not translate ".png" +GenericImageNamePNG=image.png +GenericFileName=file
--- a/dom/media/MediaDecoderReader.cpp +++ b/dom/media/MediaDecoderReader.cpp @@ -127,23 +127,25 @@ size_t MediaDecoderReader::SizeOfVideoQu return mVideoQueue.GetSize(); } size_t MediaDecoderReader::SizeOfAudioQueueInFrames() { return mAudioQueue.GetSize(); } -nsresult MediaDecoderReader::ResetDecode(TargetQueues aQueues /*= AUDIO_VIDEO*/) +nsresult MediaDecoderReader::ResetDecode(TrackSet aTracks) { - VideoQueue().Reset(); - mVideoDiscontinuity = true; - mBaseVideoPromise.RejectIfExists(CANCELED, __func__); + if (aTracks.contains(TrackInfo::kVideoTrack)) { + VideoQueue().Reset(); + mVideoDiscontinuity = true; + mBaseVideoPromise.RejectIfExists(CANCELED, __func__); + } - if (aQueues == AUDIO_VIDEO) { + if (aTracks.contains(TrackInfo::kAudioTrack)) { AudioQueue().Reset(); mAudioDiscontinuity = true; mBaseAudioPromise.RejectIfExists(CANCELED, __func__); } return NS_OK; }
--- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #if !defined(MediaDecoderReader_h_) #define MediaDecoderReader_h_ +#include "mozilla/EnumSet.h" #include "mozilla/MozPromise.h" #include "AbstractMediaDecoder.h" #include "MediaInfo.h" #include "MediaData.h" #include "MediaMetadataManager.h" #include "MediaQueue.h" #include "MediaTimer.h" @@ -68,20 +69,17 @@ class MediaDecoderReader { public: enum NotDecodedReason { END_OF_STREAM, DECODE_ERROR, WAITING_FOR_DATA, CANCELED }; - enum TargetQueues { - VIDEO_ONLY, - AUDIO_VIDEO - }; + using TrackSet = EnumSet<TrackInfo::TrackType>; using MetadataPromise = MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, IsExclusive>; using MediaDataPromise = MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>; using SeekPromise = MozPromise<media::TimeUnit, nsresult, IsExclusive>; // Note that, conceptually, WaitForData makes sense in a non-exclusive sense. @@ -125,17 +123,21 @@ public: // Request*Data() calls after this is called. Calls to Request*Data() // made after this should be processed as usual. // // Normally this call preceedes a Seek() call, or shutdown. // // The first samples of every stream produced after a ResetDecode() call // *must* be marked as "discontinuities". If it's not, seeking work won't // properly! - virtual nsresult ResetDecode(TargetQueues aQueues = AUDIO_VIDEO); + // + // aParam is a set of TrackInfo::TrackType enums specifying which + // queues need to be reset, defaulting to both audio and video tracks. + virtual nsresult ResetDecode(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack, + TrackInfo::kVideoTrack)); // Requests one audio sample from the reader. // // The decode should be performed asynchronously, and the promise should // be resolved when it is complete. Don't hold the decoder // monitor while calling this, as the implementation may try to wait // on something that needs the monitor and deadlock. virtual RefPtr<MediaDataPromise> RequestAudioData();
--- a/dom/media/MediaDecoderReaderWrapper.cpp +++ b/dom/media/MediaDecoderReaderWrapper.cpp @@ -388,31 +388,34 @@ MediaDecoderReaderWrapper::SetIdle() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); nsCOMPtr<nsIRunnable> r = NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle); mReader->OwnerThread()->Dispatch(r.forget()); } void -MediaDecoderReaderWrapper::ResetDecode(TargetQueues aQueues) +MediaDecoderReaderWrapper::ResetDecode(TrackSet aTracks) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); - if (aQueues == MediaDecoderReader::AUDIO_VIDEO) { + if (aTracks.contains(TrackInfo::kAudioTrack)) { mAudioDataRequest.DisconnectIfExists(); mAudioWaitRequest.DisconnectIfExists(); } - mVideoDataRequest.DisconnectIfExists(); - mVideoWaitRequest.DisconnectIfExists(); + + if (aTracks.contains(TrackInfo::kVideoTrack)) { + mVideoDataRequest.DisconnectIfExists(); + mVideoWaitRequest.DisconnectIfExists(); + } nsCOMPtr<nsIRunnable> r = - NewRunnableMethod<TargetQueues>(mReader, - &MediaDecoderReader::ResetDecode, - aQueues); + NewRunnableMethod<TrackSet>(mReader, + &MediaDecoderReader::ResetDecode, + aTracks); mReader->OwnerThread()->Dispatch(r.forget()); } RefPtr<ShutdownPromise> MediaDecoderReaderWrapper::Shutdown() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); MOZ_ASSERT(!mRequestAudioDataCB);
--- a/dom/media/MediaDecoderReaderWrapper.h +++ b/dom/media/MediaDecoderReaderWrapper.h @@ -27,17 +27,17 @@ typedef MozPromise<bool, bool, /* isExcl * is passed to the underlying reader. */ class MediaDecoderReaderWrapper { typedef MediaDecoderReader::MetadataPromise MetadataPromise; typedef MediaDecoderReader::MediaDataPromise MediaDataPromise; typedef MediaDecoderReader::SeekPromise SeekPromise; typedef MediaDecoderReader::WaitForDataPromise WaitForDataPromise; typedef MediaDecoderReader::BufferedUpdatePromise BufferedUpdatePromise; - typedef MediaDecoderReader::TargetQueues TargetQueues; + typedef MediaDecoderReader::TrackSet TrackSet; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReaderWrapper); /* * Type 1: void(MediaData*) * void(RefPtr<MediaData>) */ template <typename T> class ArgType1CheckHelper { @@ -375,17 +375,17 @@ public: bool IsWaitingVideoData() const; RefPtr<SeekPromise> Seek(SeekTarget aTarget, media::TimeUnit aEndTime); RefPtr<BufferedUpdatePromise> UpdateBufferedWithPromise(); RefPtr<ShutdownPromise> Shutdown(); void ReleaseMediaResources(); void SetIdle(); - void ResetDecode(TargetQueues aQueues); + void ResetDecode(TrackSet aTracks); nsresult Init() { return mReader->Init(); } bool IsWaitForDataSupported() const { return mReader->IsWaitForDataSupported(); } bool IsAsync() const { return mReader->IsAsync(); } bool UseBufferingHeuristics() const { return mReader->UseBufferingHeuristics(); } bool ForceZeroStartTime() const { return mReader->ForceZeroStartTime(); } bool VideoIsHardwareAccelerated() const {
--- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -593,19 +593,17 @@ MediaDecoderStateMachine::OnAudioDecoded RefPtr<MediaData> audio(aAudioSample); MOZ_ASSERT(audio); // audio->GetEndTime() is not always mono-increasing in chained ogg. mDecodedAudioEndTime = std::max(audio->GetEndTime(), mDecodedAudioEndTime); SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d", - (audio ? audio->mTime : -1), - (audio ? audio->GetEndTime() : -1), - (audio ? audio->mDiscontinuity : 0)); + audio->mTime, audio->GetEndTime(), audio->mDiscontinuity); switch (mState) { case DECODER_STATE_BUFFERING: { // If we're buffering, this may be the sample we need to stop buffering. // Save it and schedule the state machine. Push(audio, MediaData::AUDIO_DATA); ScheduleStateMachine(); return; @@ -783,19 +781,17 @@ MediaDecoderStateMachine::OnVideoDecoded RefPtr<MediaData> video(aVideoSample); MOZ_ASSERT(video); // Handle abnormal or negative timestamps. mDecodedVideoEndTime = std::max(mDecodedVideoEndTime, video->GetEndTime()); SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d", - (video ? video->mTime : -1), - (video ? video->GetEndTime() : -1), - (video ? video->mDiscontinuity : 0)); + video->mTime, video->GetEndTime(), video->mDiscontinuity); switch (mState) { case DECODER_STATE_BUFFERING: { // If we're buffering, this may be the sample we need to stop buffering. // Save it and schedule the state machine. Push(video, MediaData::VIDEO_DATA); ScheduleStateMachine(); return; @@ -1422,17 +1418,17 @@ void MediaDecoderStateMachine::InitiateV mSeekTask = new AccurateSeekTask(mDecoderID, OwnerThread(), mReader.get(), Move(seekJob), mInfo, Duration(), GetMediaTime()); mOnSeekingStart.Notify(MediaDecoderEventVisibility::Suppressed); // Reset our state machine and decoding pipeline before seeking. if (mSeekTask->NeedToResetMDSM()) { - Reset(MediaDecoderReader::VIDEO_ONLY); + Reset(TrackInfo::kVideoTrack); } // Do the seek. mSeekTaskRequest.Begin( mSeekTask->Seek(Duration())->Then(OwnerThread(), __func__, this, &MediaDecoderStateMachine::OnSeekTaskResolved, &MediaDecoderStateMachine::OnSeekTaskRejected)); // Nobody is listening to this as OnSeekTaskResolved handles what is @@ -2390,50 +2386,59 @@ nsresult MediaDecoderStateMachine::RunSt return NS_OK; } } return NS_OK; } void -MediaDecoderStateMachine::Reset(MediaDecoderReader::TargetQueues aQueues /*= AUDIO_VIDEO*/) +MediaDecoderStateMachine::Reset(TrackSet aTracks) { MOZ_ASSERT(OnTaskQueue()); DECODER_LOG("MediaDecoderStateMachine::Reset"); // We should be resetting because we're seeking, shutting down, or entering // dormant state. We could also be in the process of going dormant, and have // just switched to exiting dormant before we finished entering dormant, // hence the DECODING_NONE case below. MOZ_ASSERT(IsShutdown() || mState == DECODER_STATE_SEEKING || mState == DECODER_STATE_DORMANT); - - mDecodedVideoEndTime = 0; - mVideoCompleted = false; - VideoQueue().Reset(); - - if (aQueues == MediaDecoderReader::AUDIO_VIDEO) { + // Assert that aTracks specifies to reset the video track because we + // don't currently support resetting just the audio track. + MOZ_ASSERT(aTracks.contains(TrackInfo::kVideoTrack)); + + if (aTracks.contains(TrackInfo::kAudioTrack) && + aTracks.contains(TrackInfo::kVideoTrack)) { // Stop the audio thread. Otherwise, MediaSink might be accessing AudioQueue // outside of the decoder monitor while we are clearing the queue and causes // crash for no samples to be popped. StopMediaSink(); + } + + if (aTracks.contains(TrackInfo::kVideoTrack)) { + mDecodedVideoEndTime = 0; + mVideoCompleted = false; + VideoQueue().Reset(); + } + + if (aTracks.contains(TrackInfo::kAudioTrack)) { mDecodedAudioEndTime = 0; mAudioCompleted = false; AudioQueue().Reset(); } mMetadataRequest.DisconnectIfExists(); mSeekTaskRequest.DisconnectIfExists(); mPlaybackOffset = 0; - mReader->ResetDecode(aQueues); + mReader->ResetDecode(aTracks); } int64_t MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const { MOZ_ASSERT(OnTaskQueue()); int64_t clockTime = mMediaSink->GetPosition(aTimeStamp); NS_ASSERTION(GetMediaTime() <= clockTime, "Clock should go forwards.");
--- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -130,16 +130,19 @@ enum class MediaEventType : int8_t { are propagated by scheduling the state machine to run another cycle on the shared state machine thread. See MediaDecoder.h for more details. */ class MediaDecoderStateMachine { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachine) + + using TrackSet = MediaDecoderReader::TrackSet; + public: typedef MediaDecoderOwner::NextFrameStatus NextFrameStatus; typedef mozilla::layers::ImageContainer::FrameID FrameID; MediaDecoderStateMachine(MediaDecoder* aDecoder, MediaDecoderReader* aReader, bool aRealTime = false); nsresult Init(MediaDecoder* aDecoder); @@ -363,17 +366,18 @@ private: void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason) { MOZ_ASSERT(OnTaskQueue()); OnNotDecoded(MediaData::VIDEO_DATA, aReason); } // Resets all state related to decoding and playback, emptying all buffers // and aborting all pending operations on the decode task queue. - void Reset(MediaDecoderReader::TargetQueues aQueues = MediaDecoderReader::AUDIO_VIDEO); + void Reset(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack, + TrackInfo::kVideoTrack)); protected: virtual ~MediaDecoderStateMachine(); void SetState(State aState); void BufferedRangeUpdated();
--- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -1338,49 +1338,57 @@ MediaFormatReader::WaitForData(MediaData return WaitForDataPromise::CreateAndResolve(decoder.mType, __func__); } RefPtr<WaitForDataPromise> p = decoder.mWaitingPromise.Ensure(__func__); ScheduleUpdate(trackType); return p; } nsresult -MediaFormatReader::ResetDecode(TargetQueues aQueues) +MediaFormatReader::ResetDecode(TrackSet aTracks) { MOZ_ASSERT(OnTaskQueue()); LOGV(""); mSeekPromise.RejectIfExists(NS_OK, __func__); mSkipRequest.DisconnectIfExists(); // Do the same for any data wait promises. - if (aQueues == AUDIO_VIDEO) { - mAudio.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::AUDIO_DATA, WaitForDataRejectValue::CANCELED), __func__); + if (aTracks.contains(TrackInfo::kAudioTrack)) { + mAudio.mWaitingPromise.RejectIfExists( + WaitForDataRejectValue(MediaData::AUDIO_DATA, + WaitForDataRejectValue::CANCELED), __func__); } - mVideo.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::VIDEO_DATA, WaitForDataRejectValue::CANCELED), __func__); + + if (aTracks.contains(TrackInfo::kVideoTrack)) { + mVideo.mWaitingPromise.RejectIfExists( + WaitForDataRejectValue(MediaData::VIDEO_DATA, + WaitForDataRejectValue::CANCELED), __func__); + } // Reset miscellaneous seeking state. mPendingSeekTime.reset(); - if (HasVideo()) { + if (HasVideo() && aTracks.contains(TrackInfo::kVideoTrack)) { mVideo.ResetDemuxer(); Reset(TrackInfo::kVideoTrack); if (mVideo.HasPromise()) { mVideo.RejectPromise(CANCELED, __func__); } } - if (HasAudio() && aQueues == AUDIO_VIDEO) { + if (HasAudio() && aTracks.contains(TrackInfo::kAudioTrack)) { mAudio.ResetDemuxer(); Reset(TrackInfo::kAudioTrack); if (mAudio.HasPromise()) { mAudio.RejectPromise(CANCELED, __func__); } } - return MediaDecoderReader::ResetDecode(aQueues); + + return MediaDecoderReader::ResetDecode(aTracks); } void MediaFormatReader::Output(TrackType aTrack, MediaData* aSample) { if (!aSample) { NS_WARNING("MediaFormatReader::Output() passed a null sample"); Error(aTrack);
--- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -57,17 +57,17 @@ public: RefPtr<BufferedUpdatePromise> UpdateBufferedWithPromise() override; bool ForceZeroStartTime() const override; // For Media Resource Management void ReleaseMediaResources() override; - nsresult ResetDecode(TargetQueues aQueues) override; + nsresult ResetDecode(TrackSet aTracks) override; RefPtr<ShutdownPromise> Shutdown() override; bool IsAsync() const override { return true; } bool VideoIsHardwareAccelerated() const override; bool IsWaitForDataSupported() const override { return true; }
--- a/dom/media/android/AndroidMediaReader.cpp +++ b/dom/media/android/AndroidMediaReader.cpp @@ -89,34 +89,34 @@ nsresult AndroidMediaReader::ReadMetadat *aInfo = mInfo; *aTags = nullptr; return NS_OK; } RefPtr<ShutdownPromise> AndroidMediaReader::Shutdown() { - ResetDecode(AUDIO_VIDEO); + ResetDecode(); if (mPlugin) { GetAndroidMediaPluginHost()->DestroyDecoder(mPlugin); mPlugin = nullptr; } return MediaDecoderReader::Shutdown(); } // Resets all state related to decoding, emptying all buffers etc. -nsresult AndroidMediaReader::ResetDecode(TargetQueues aQueues) +nsresult AndroidMediaReader::ResetDecode(TrackSet aTracks) { if (mLastVideoFrame) { mLastVideoFrame = nullptr; } mSeekRequest.DisconnectIfExists(); mSeekPromise.RejectIfExists(NS_OK, __func__); - return MediaDecoderReader::ResetDecode(aQueues); + return MediaDecoderReader::ResetDecode(aTracks); } bool AndroidMediaReader::DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) { // Record number of frames decoded and parsed. Automatically update the // stats counters using the AutoNotifyDecoded stack-based class. AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
--- a/dom/media/android/AndroidMediaReader.h +++ b/dom/media/android/AndroidMediaReader.h @@ -37,17 +37,18 @@ class AndroidMediaReader : public MediaD int64_t mAudioSeekTimeUs; RefPtr<VideoData> mLastVideoFrame; MozPromiseHolder<MediaDecoderReader::SeekPromise> mSeekPromise; MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mSeekRequest; public: AndroidMediaReader(AbstractMediaDecoder* aDecoder, const nsACString& aContentType); - nsresult ResetDecode(TargetQueues aQueues) override; + nsresult ResetDecode(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack, + TrackInfo::kVideoTrack)) override; bool DecodeAudioData() override; bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override; nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override; RefPtr<SeekPromise> Seek(SeekTarget aTarget, int64_t aEndTime) override; RefPtr<ShutdownPromise> Shutdown() override;
--- a/dom/media/compiledtest/TestAudioBuffers.cpp +++ b/dom/media/compiledtest/TestAudioBuffers.cpp @@ -1,16 +1,16 @@ /* -*- Mode: C++; 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 <stdint.h> -#include <assert.h> #include "AudioBufferUtils.h" +#include <mozilla/Assertions.h> const uint32_t FRAMES = 256; const uint32_t CHANNELS = 2; const uint32_t SAMPLES = CHANNELS * FRAMES; int main() { mozilla::AudioCallbackBufferWrapper<float, CHANNELS> mBuffer; mozilla::SpillBuffer<float, 128, CHANNELS> b; @@ -21,39 +21,39 @@ int main() { other[i] = 1.0; fromCallback[i] = 0.0; } // Set the buffer in the wrapper from the callback mBuffer.SetBuffer(fromCallback, FRAMES); // Fill the SpillBuffer with data. - assert(b.Fill(other, 15) == 15); - assert(b.Fill(other, 17) == 17); + MOZ_RELEASE_ASSERT(b.Fill(other, 15) == 15); + MOZ_RELEASE_ASSERT(b.Fill(other, 17) == 17); for (uint32_t i = 0; i < 32 * CHANNELS; i++) { other[i] = 0.0; } // Empty it in the AudioCallbackBufferWrapper - assert(b.Empty(mBuffer) == 32); + MOZ_RELEASE_ASSERT(b.Empty(mBuffer) == 32); // Check available return something reasonnable - assert(mBuffer.Available() == FRAMES - 32); + MOZ_RELEASE_ASSERT(mBuffer.Available() == FRAMES - 32); // Fill the buffer with the rest of the data mBuffer.WriteFrames(other + 32 * CHANNELS, FRAMES - 32); // Check the buffer is now full - assert(mBuffer.Available() == 0); + MOZ_RELEASE_ASSERT(mBuffer.Available() == 0); for (uint32_t i = 0 ; i < SAMPLES; i++) { if (fromCallback[i] != 1.0) { fprintf(stderr, "Difference at %d (%f != %f)\n", i, fromCallback[i], 1.0); - assert(false); + MOZ_CRASH("Samples differ"); } } - assert(b.Fill(other, FRAMES) == 128); - assert(b.Fill(other, FRAMES) == 0); - assert(b.Empty(mBuffer) == 0); + MOZ_RELEASE_ASSERT(b.Fill(other, FRAMES) == 128); + MOZ_RELEASE_ASSERT(b.Fill(other, FRAMES) == 0); + MOZ_RELEASE_ASSERT(b.Empty(mBuffer) == 0); return 0; }
--- a/dom/media/compiledtest/TestAudioMixer.cpp +++ b/dom/media/compiledtest/TestAudioMixer.cpp @@ -1,15 +1,14 @@ /* -*- Mode: C++; 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 "AudioMixer.h" -#include <assert.h> using mozilla::AudioDataValue; using mozilla::AudioSampleFormat; struct MixerConsumer : public mozilla::MixerCallbackReceiver { /* In this test, the different audio stream and channels are always created to * cancel each other. */ @@ -157,9 +156,8 @@ int main(int argc, char* argv[]) { mixer.FinishMixing(); mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE); mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE); mixer.FinishMixing(); } return 0; } -
--- a/dom/media/compiledtest/TestAudioPacketizer.cpp +++ b/dom/media/compiledtest/TestAudioPacketizer.cpp @@ -1,17 +1,17 @@ /* -*- Mode: C++; 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 <stdint.h> -#include <assert.h> #include <math.h> #include "../AudioPacketizer.h" +#include <mozilla/Assertions.h> using namespace mozilla; template<typename T> class AutoBuffer { public: explicit AutoBuffer(size_t aLength) @@ -37,28 +37,28 @@ int16_t Sequence(int16_t* aBuffer, uint3 return aStart + i; } void IsSequence(int16_t* aBuffer, uint32_t aSize, uint32_t aStart = 0) { for (uint32_t i = 0; i < aSize; i++) { if (aBuffer[i] != static_cast<int64_t>(aStart + i)) { fprintf(stderr, "Buffer is not a sequence at offset %u\n", i); - assert(false); + MOZ_CRASH("Buffer is not a sequence"); } } - assert("Buffer is a sequence."); + // Buffer is a sequence. } void Zero(int16_t* aBuffer, uint32_t aSize) { for (uint32_t i = 0; i < aSize; i++) { if (aBuffer[i] != 0) { fprintf(stderr, "Buffer is not null at offset %u\n", i); - assert(false); + MOZ_CRASH("Buffer is not null"); } } } double sine(uint32_t aPhase) { return sin(aPhase * 2 * M_PI * 440 / 44100); } @@ -152,18 +152,18 @@ int main() { } phase++; } ap.Input(b.Get(), 128); while (ap.PacketsAvailable()) { int16_t* packet = ap.Output(); for (uint32_t k = 0; k < ap.PacketSize(); k++) { for (int32_t c = 0; c < channels; c++) { - assert(packet[k * channels + c] == - static_cast<int16_t>(((2 << 14) * sine(outPhase)))); + MOZ_RELEASE_ASSERT(packet[k * channels + c] == + static_cast<int16_t>(((2 << 14) * sine(outPhase)))); } outPhase++; } delete [] packet; } } } }
--- a/dom/media/compiledtest/TestAudioSegment.cpp +++ b/dom/media/compiledtest/TestAudioSegment.cpp @@ -1,16 +1,16 @@ /* -*- Mode: C++; 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 "AudioSegment.h" -#include <assert.h> #include <iostream> +#include <mozilla/Assertions.h> using namespace mozilla; namespace mozilla { uint32_t GetAudioChannelsSuperset(uint32_t aChannels1, uint32_t aChannels2) { return std::max(aChannels1, aChannels2); @@ -137,18 +137,18 @@ void TestInterleaveAndConvert() for (uint32_t channels = 1; channels < maxChannels; channels++) { const SrcT* const* src = GetPlanarChannelArray<SrcT>(channels, arraySize); DstT* dst = new DstT[channels * arraySize]; InterleaveAndConvertBuffer(src, arraySize, 1.0, channels, dst); uint32_t channelIndex = 0; for (size_t i = 0; i < arraySize * channels; i++) { - assert(FuzzyEqual(dst[i], - FloatToAudioSample<DstT>(1. / (channelIndex + 1)))); + MOZ_RELEASE_ASSERT(FuzzyEqual(dst[i], + FloatToAudioSample<DstT>(1. / (channelIndex + 1)))); channelIndex++; channelIndex %= channels; } DeletePlanarChannelsArray(src, channels); delete [] dst; } } @@ -161,18 +161,18 @@ void TestDeinterleaveAndConvert() for (uint32_t channels = 1; channels < maxChannels; channels++) { const SrcT* src = GetInterleavedChannelArray<SrcT>(channels, arraySize); DstT** dst = GetPlanarArray<DstT>(channels, arraySize); DeinterleaveAndConvertBuffer(src, arraySize, channels, dst); for (size_t channel = 0; channel < channels; channel++) { for (size_t i = 0; i < arraySize; i++) { - assert(FuzzyEqual(dst[channel][i], - FloatToAudioSample<DstT>(1. / (channel + 1)))); + MOZ_RELEASE_ASSERT(FuzzyEqual(dst[channel][i], + FloatToAudioSample<DstT>(1. / (channel + 1)))); } } DeleteInterleavedChannelArray(src); DeletePlanarArray(dst, channels); } } @@ -200,22 +200,19 @@ void TestUpmixStereo() channels[0][i] = GetHighValue<T>(); } channelsptr[0] = channels[0]; AudioChannelsUpMix(&channelsptr, 2, ::SilentChannel<T>()); for (size_t channel = 0; channel < 2; channel++) { for (size_t i = 0; i < arraySize; i++) { - if (channelsptr[channel][i] != GetHighValue<T>()) { - assert(false); - } + MOZ_RELEASE_ASSERT(channelsptr[channel][i] == GetHighValue<T>()); } } - assert(true); delete channels[0]; } template<typename T> void TestDownmixStereo() { const size_t arraySize = 1024; nsTArray<const T*> inputptr; @@ -234,21 +231,19 @@ void TestDownmixStereo() input[channel][i] = channel == 0 ? GetLowValue<T>() : GetHighValue<T>(); } inputptr[channel] = input[channel]; } AudioChannelsDownMix(inputptr, output, 1, arraySize); for (size_t i = 0; i < arraySize; i++) { - if (output[0][i] != GetSilentValue<T>()) { - assert(false); - } + MOZ_RELEASE_ASSERT(output[0][i] == GetSilentValue<T>()); + MOZ_RELEASE_ASSERT(output[0][i] == GetSilentValue<T>()); } - assert(true); delete output[0]; delete output; } int main(int argc, char* argv[]) { TestInterleaveAndConvert<float, float>(); TestInterleaveAndConvert<float, int16_t>(); @@ -260,9 +255,8 @@ int main(int argc, char* argv[]) { TestDeinterleaveAndConvert<int16_t, int16_t>(); TestUpmixStereo<float>(); TestUpmixStereo<int16_t>(); TestDownmixStereo<float>(); TestDownmixStereo<int16_t>(); return 0; } -
--- a/dom/media/ogg/OggReader.cpp +++ b/dom/media/ogg/OggReader.cpp @@ -164,27 +164,27 @@ OggReader::~OggReader() } nsresult OggReader::Init() { int ret = ogg_sync_init(&mOggState); NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE); return NS_OK; } -nsresult OggReader::ResetDecode(TargetQueues aQueues) +nsresult OggReader::ResetDecode(TrackSet aTracks) { - return ResetDecode(false, aQueues); + return ResetDecode(false, aTracks); } -nsresult OggReader::ResetDecode(bool start, TargetQueues aQueues) +nsresult OggReader::ResetDecode(bool start, TrackSet aTracks) { MOZ_ASSERT(OnTaskQueue()); nsresult res = NS_OK; - if (NS_FAILED(MediaDecoderReader::ResetDecode(aQueues))) { + if (NS_FAILED(MediaDecoderReader::ResetDecode(aTracks))) { res = NS_ERROR_FAILURE; } // Discard any previously buffered packets/pages. ogg_sync_reset(&mOggState); if (mVorbisState && NS_FAILED(mVorbisState->Reset())) { res = NS_ERROR_FAILURE; }
--- a/dom/media/ogg/OggReader.h +++ b/dom/media/ogg/OggReader.h @@ -46,17 +46,18 @@ class OggReader final : public MediaDeco public: explicit OggReader(AbstractMediaDecoder* aDecoder); protected: ~OggReader(); public: nsresult Init() override; - nsresult ResetDecode(TargetQueues aQueues = AUDIO_VIDEO) override; + nsresult ResetDecode(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack, + TrackInfo::kVideoTrack)) override; bool DecodeAudioData() override; // If the Theora granulepos has not been captured, it may read several packets // until one with a granulepos has been captured, to ensure that all packets // read have valid time info. bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override; nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override; @@ -81,17 +82,19 @@ private: RefPtr<AudioData> SyncDecodeToFirstAudioData(); RefPtr<VideoData> SyncDecodeToFirstVideoData(); // This monitor should be taken when reading or writing to mIsChained. ReentrantMonitor mMonitor; // Specialized Reset() method to signal if the seek is // to the start of the stream. - nsresult ResetDecode(bool start, TargetQueues aQueues = AUDIO_VIDEO); + nsresult ResetDecode(bool start, + TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack, + TrackInfo::kVideoTrack)); nsresult SeekInternal(int64_t aTime, int64_t aEndTime); bool HasSkeleton() { return mSkeletonState != 0 && mSkeletonState->mActive; } // Seeks to the keyframe preceding the target time using available
--- a/dom/media/omx/MediaOmxReader.cpp +++ b/dom/media/omx/MediaOmxReader.cpp @@ -171,17 +171,17 @@ MediaOmxReader::Shutdown() return p; } void MediaOmxReader::ReleaseMediaResources() { mMediaResourceRequest.DisconnectIfExists(); mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__); - ResetDecode(AUDIO_VIDEO); + ResetDecode(); // Before freeing a video codec, all video buffers needed to be released // even from graphics pipeline. VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); if (container) { container->ClearCurrentFrame(); } if (mOmxDecoder.get()) { mOmxDecoder->ReleaseMediaResources();
--- a/dom/media/omx/MediaOmxReader.h +++ b/dom/media/omx/MediaOmxReader.h @@ -69,21 +69,21 @@ protected: public: MediaOmxReader(AbstractMediaDecoder* aDecoder); ~MediaOmxReader(); protected: void NotifyDataArrivedInternal() override; public: - nsresult ResetDecode(TargetQueues aQueues) override + nsresult ResetDecoder(TrackSet aTracks) override; { mSeekRequest.DisconnectIfExists(); mSeekPromise.RejectIfExists(NS_OK, __func__); - return MediaDecoderReader::ResetDecode(aQueues); + return MediaDecoderReader::ResetDecode(aTracks); } bool DecodeAudioData() override; bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override; void ReleaseMediaResources() override; RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
--- a/dom/media/omx/OMXCodecWrapper.cpp +++ b/dom/media/omx/OMXCodecWrapper.cpp @@ -408,45 +408,43 @@ ConvertGrallocImageToNV12(GrallocImage* } graphicBuffer->unlock(); } static nsresult ConvertSourceSurfaceToNV12(const RefPtr<SourceSurface>& aSurface, uint8_t* aDestination) { + if (!aSurface) { + CODEC_ERROR("Getting surface %s from image failed"); + return NS_ERROR_FAILURE; + } + uint32_t width = aSurface->GetSize().width; uint32_t height = aSurface->GetSize().height; uint8_t* y = aDestination; int yStride = width; uint8_t* uv = y + (yStride * height); int uvStride = width / 2; - SurfaceFormat format = aSurface->GetFormat(); - - if (!aSurface) { - CODEC_ERROR("Getting surface %s from image failed", Stringify(format).c_str()); - return NS_ERROR_FAILURE; - } - RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); if (!data) { - CODEC_ERROR("Getting data surface from %s image with %s (%s) surface failed", - Stringify(format).c_str(), Stringify(aSurface->GetType()).c_str(), - Stringify(aSurface->GetFormat()).c_str()); + CODEC_ERROR("Getting data surface from %s image with %s surface failed", + Stringify(aSurface->GetFormat()).c_str(), + Stringify(aSurface->GetType()).c_str()); return NS_ERROR_FAILURE; } DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ); if (!map.IsMapped()) { - CODEC_ERROR("Reading DataSourceSurface from %s image with %s (%s) surface failed", - Stringify(format).c_str(), Stringify(aSurface->GetType()).c_str(), - Stringify(aSurface->GetFormat()).c_str()); + CODEC_ERROR("Reading DataSourceSurface from %s image with %s surface failed", + Stringify(aSurface->GetFormat()).c_str(), + Stringify(aSurface->GetType()).c_str()); return NS_ERROR_FAILURE; } int rv; switch (aSurface->GetFormat()) { case SurfaceFormat::B8G8R8A8: case SurfaceFormat::B8G8R8X8: rv = libyuv::ARGBToNV12(static_cast<uint8*>(map.GetData()),
--- a/dom/media/platforms/agnostic/WAVDecoder.h +++ b/dom/media/platforms/agnostic/WAVDecoder.h @@ -16,27 +16,27 @@ class WaveDataDecoder : public MediaData { public: WaveDataDecoder(const AudioInfo& aConfig, MediaDataDecoderCallback* aCallback); // Return true if mimetype is Wave static bool IsWave(const nsACString& aMimeType); -private: RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; const char* GetDescriptionName() const override { return "wave audio decoder"; } +private: bool DoDecode(MediaRawData* aSample); const AudioInfo& mInfo; MediaDataDecoderCallback* mCallback; }; } // namespace mozilla #endif
--- a/dom/media/platforms/apple/AppleVDADecoder.h +++ b/dom/media/platforms/apple/AppleVDADecoder.h @@ -66,17 +66,16 @@ public: MediaDataDecoderCallback* aCallback, layers::ImageContainer* aImageContainer); // Access from the taskqueue and the decoder's thread. // OutputFrame is thread-safe. nsresult OutputFrame(CVPixelBufferRef aImage, AppleFrameRef aFrameRef); -private: RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; bool IsHardwareAccelerated(nsACString& aFailureReason) const override { return true;
--- a/dom/media/platforms/apple/AppleVTDecoder.h +++ b/dom/media/platforms/apple/AppleVTDecoder.h @@ -15,31 +15,31 @@ namespace mozilla { class AppleVTDecoder : public AppleVDADecoder { public: AppleVTDecoder(const VideoInfo& aConfig, TaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback, layers::ImageContainer* aImageContainer); -private: - virtual ~AppleVTDecoder(); RefPtr<InitPromise> Init() override; bool IsHardwareAccelerated(nsACString& aFailureReason) const override { return mIsHardwareAccelerated; } const char* GetDescriptionName() const override { return mIsHardwareAccelerated ? "apple hardware VT decoder" : "apple software VT decoder"; } +private: + virtual ~AppleVTDecoder(); void ProcessFlush() override; void ProcessDrain() override; void ProcessShutdown() override; CMVideoFormatDescriptionRef mFormat; VTDecompressionSessionRef mSession; // Method to pass a frame to VideoToolbox for decoding.
--- a/dom/media/platforms/omx/OmxPromiseLayer.cpp +++ b/dom/media/platforms/omx/OmxPromiseLayer.cpp @@ -167,25 +167,26 @@ OmxPromiseLayer::FindBufferById(OMX_DIRT } return nullptr; } void OmxPromiseLayer::EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData* aData) { - MOZ_ASSERT(!!aData); - LOG("type %d, buffer %p", aType, aData->mBuffer); if (aData) { + LOG("type %d, buffer %p", aType, aData->mBuffer); if (aType == OMX_DirOutput) { aData->mRawData = nullptr; aData->mRawData = FindAndRemoveRawData(aData->mBuffer->nTimeStamp); } aData->mStatus = BufferData::BufferStatus::OMX_CLIENT; aData->mPromise.Resolve(aData, __func__); + } else { + LOG("type %d, no buffer", aType); } } void OmxPromiseLayer::EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData::BufferID aID) { RefPtr<BufferData> holder = FindAndRemoveBufferHolder(aType, aID); EmptyFillBufferDone(aType, holder);
--- a/dom/media/platforms/wrappers/FuzzingWrapper.h +++ b/dom/media/platforms/wrappers/FuzzingWrapper.h @@ -95,31 +95,31 @@ public: // public for the benefit of Dec void Shutdown(); }; class DecoderFuzzingWrapper : public MediaDataDecoder { public: DecoderFuzzingWrapper(already_AddRefed<MediaDataDecoder> aDecoder, already_AddRefed<DecoderCallbackFuzzingWrapper> aCallbackWrapper); - virtual ~DecoderFuzzingWrapper(); -private: // MediaDataDecoder implementation. RefPtr<InitPromise> Init() override; nsresult Input(MediaRawData* aSample) override; nsresult Flush() override; nsresult Drain() override; nsresult Shutdown() override; bool IsHardwareAccelerated(nsACString& aFailureReason) const override; nsresult ConfigurationChanged(const TrackInfo& aConfig) override; const char* GetDescriptionName() const override { return mDecoder->GetDescriptionName(); } +private: + virtual ~DecoderFuzzingWrapper(); RefPtr<MediaDataDecoder> mDecoder; RefPtr<DecoderCallbackFuzzingWrapper> mCallbackWrapper; }; } // namespace mozilla #endif
--- a/dom/media/raw/RawReader.cpp +++ b/dom/media/raw/RawReader.cpp @@ -22,20 +22,20 @@ RawReader::RawReader(AbstractMediaDecode MOZ_COUNT_CTOR(RawReader); } RawReader::~RawReader() { MOZ_COUNT_DTOR(RawReader); } -nsresult RawReader::ResetDecode(TargetQueues aQueues) +nsresult RawReader::ResetDecode(TrackSet aTracks) { mCurrentFrame = 0; - return MediaDecoderReader::ResetDecode(aQueues); + return MediaDecoderReader::ResetDecode(aTracks); } nsresult RawReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) { MOZ_ASSERT(OnTaskQueue()); if (!ReadFromResource(reinterpret_cast<uint8_t*>(&mMetadata),
--- a/dom/media/raw/RawReader.h +++ b/dom/media/raw/RawReader.h @@ -15,17 +15,17 @@ class RawReader : public MediaDecoderRea { public: explicit RawReader(AbstractMediaDecoder* aDecoder); protected: ~RawReader(); public: - nsresult ResetDecode(TargetQueues aQueues) override; + nsresult ResetDecode(TrackSet aTracks) override; bool DecodeAudioData() override; bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override; nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override; RefPtr<SeekPromise> Seek(SeekTarget aTarget, int64_t aEndTime) override;
--- a/dom/media/test/external/external_media_harness/testcase.py +++ b/dom/media/test/external/external_media_harness/testcase.py @@ -263,20 +263,21 @@ class EMESetupMixin(object): adobe_result = self.marionette.execute_async_script( reset_adobe_gmp_script, script_timeout=60000) widevine_result = self.marionette.execute_async_script( reset_widevine_gmp_script, script_timeout=60000) if not adobe_result == 'success': raise VideoException( - 'ERROR: Resetting Adobe GMP failed % s' % result) + 'ERROR: Resetting Adobe GMP failed % s' % adobe_result) if not widevine_result == 'success': raise VideoException( - 'ERROR: Resetting Widevine GMP failed % s' % result) + 'ERROR: Resetting Widevine GMP failed % s' + % widevine_result) EMESetupMixin.version_needs_reset = False def check_and_log_boolean_pref(self, pref_name, expected_value): with self.marionette.using_context('chrome'): pref_value = self.prefs.get_pref(pref_name) if pref_value is None: @@ -306,42 +307,43 @@ class EMESetupMixin(object): self.logger.info('Pref %s is not an integer' % pref_name) return False return pref_value >= minimum_value def chceck_and_log_version_string_pref(self, pref_name, minimum_value='0'): """ Compare a pref made up of integers separated by stops .s, with a - version string of the same format. The number of integers in each string - does not need to match. The comparison is done by converting each to an - integer array and comparing those. Both version strings must be made - up of only integers, or this method will raise an unhandled exception - of type ValueError when the conversion to int fails. + version string of the same format. The number of integers in each + string does not need to match. The comparison is done by converting + each to an integer array and comparing those. Both version strings + must be made up of only integers, or this method will raise an + unhandled exception of type ValueError when the conversion to int + fails. """ with self.marionette.using_context('chrome'): pref_value = self.prefs.get_pref(pref_name) if pref_value is None: self.logger.info('Pref %s has no value.' % pref_name) return False else: self.logger.info('Pref %s = %s' % (pref_name, pref_value)) match = re.search('^\d(.\d+)*$', pref_value) if not match: - self.logger.info('Pref %s is not a version string' % pref_name) + self.logger.info('Pref %s is not a version string' + % pref_name) return False pref_ints = [int(n) for n in pref_value.split('.')] minumum_ints = [int(n) for n in minimum_value.split('.')] return pref_ints >= minumum_ints - def check_eme_prefs(self): with self.marionette.using_context('chrome'): return all([ self.check_and_log_boolean_pref( 'media.mediasource.enabled', True), self.check_and_log_boolean_pref( 'media.eme.enabled', True), self.check_and_log_boolean_pref(
--- a/dom/media/webaudio/AudioContext.cpp +++ b/dom/media/webaudio/AudioContext.cpp @@ -25,16 +25,17 @@ #include "AudioStream.h" #include "BiquadFilterNode.h" #include "ChannelMergerNode.h" #include "ChannelSplitterNode.h" #include "ConvolverNode.h" #include "DelayNode.h" #include "DynamicsCompressorNode.h" #include "GainNode.h" +#include "IIRFilterNode.h" #include "MediaElementAudioSourceNode.h" #include "MediaStreamAudioDestinationNode.h" #include "MediaStreamAudioSourceNode.h" #include "MediaStreamGraph.h" #include "nsContentUtils.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" @@ -504,16 +505,52 @@ AudioContext::CreateBiquadFilter(ErrorRe return nullptr; } RefPtr<BiquadFilterNode> filterNode = new BiquadFilterNode(this); return filterNode.forget(); } +already_AddRefed<IIRFilterNode> +AudioContext::CreateIIRFilter(const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward, + const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback, + mozilla::ErrorResult& aRv) +{ + if (CheckClosed(aRv)) { + return nullptr; + } + + if (aFeedforward.Length() == 0 || aFeedforward.Length() > 20) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + if (aFeedback.Length() == 0 || aFeedback.Length() > 20) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + bool feedforwardAllZeros = true; + for (size_t i = 0; i < aFeedforward.Length(); ++i) { + if (aFeedforward.Elements()[i] != 0.0) { + feedforwardAllZeros = false; + } + } + + if (feedforwardAllZeros || aFeedback.Elements()[0] == 0.0) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + RefPtr<IIRFilterNode> filterNode = + new IIRFilterNode(this, aFeedforward, aFeedback); + return filterNode.forget(); +} + already_AddRefed<OscillatorNode> AudioContext::CreateOscillator(ErrorResult& aRv) { if (CheckClosed(aRv)) { return nullptr; } RefPtr<OscillatorNode> oscillatorNode = @@ -654,16 +691,22 @@ AudioContext::UpdatePannerSource() } uint32_t AudioContext::MaxChannelCount() const { return mIsOffline ? mNumberOfChannels : CubebUtils::MaxNumberOfChannels(); } +uint32_t +AudioContext::ActiveNodeCount() const +{ + return mActiveNodes.Count(); +} + MediaStreamGraph* AudioContext::Graph() const { return Destination()->Stream()->Graph(); } MediaStream* AudioContext::DestinationStream() const
--- a/dom/media/webaudio/AudioContext.h +++ b/dom/media/webaudio/AudioContext.h @@ -52,19 +52,20 @@ class AudioListener; class AudioNode; class BiquadFilterNode; class ChannelMergerNode; class ChannelSplitterNode; class ConvolverNode; class DelayNode; class DynamicsCompressorNode; class GainNode; +class GlobalObject; class HTMLMediaElement; +class IIRFilterNode; class MediaElementAudioSourceNode; -class GlobalObject; class MediaStreamAudioDestinationNode; class MediaStreamAudioSourceNode; class OscillatorNode; class PannerNode; class ScriptProcessorNode; class StereoPannerNode; class WaveShaperNode; class PeriodicWave; @@ -246,16 +247,21 @@ public: CreateChannelMerger(uint32_t aNumberOfInputs, ErrorResult& aRv); already_AddRefed<DynamicsCompressorNode> CreateDynamicsCompressor(ErrorResult& aRv); already_AddRefed<BiquadFilterNode> CreateBiquadFilter(ErrorResult& aRv); + already_AddRefed<IIRFilterNode> + CreateIIRFilter(const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward, + const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback, + mozilla::ErrorResult& aRv); + already_AddRefed<OscillatorNode> CreateOscillator(ErrorResult& aRv); already_AddRefed<PeriodicWave> CreatePeriodicWave(const Float32Array& aRealData, const Float32Array& aImagData, const PeriodicWaveConstraints& aConstraints, ErrorResult& aRv); @@ -289,16 +295,18 @@ public: void UnregisterActiveNode(AudioNode* aNode); void UnregisterAudioBufferSourceNode(AudioBufferSourceNode* aNode); void UnregisterPannerNode(PannerNode* aNode); void UpdatePannerSource(); uint32_t MaxChannelCount() const; + uint32_t ActiveNodeCount() const; + void Mute() const; void Unmute() const; JSObject* GetGlobalJSObject() const; AudioChannel MozAudioChannelType() const; AudioChannel TestAudioChannelInAudioNodeStream();
--- a/dom/media/webaudio/BiquadFilterNode.cpp +++ b/dom/media/webaudio/BiquadFilterNode.cpp @@ -72,26 +72,29 @@ SetParamsOnBiquad(WebCore::Biquad& aBiqu NS_NOTREACHED("We should never see the alternate names here"); break; } } class BiquadFilterNodeEngine final : public AudioNodeEngine { public: - BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination) + BiquadFilterNodeEngine(AudioNode* aNode, + AudioDestinationNode* aDestination, + uint64_t aWindowID) : AudioNodeEngine(aNode) , mDestination(aDestination->Stream()) // Keep the default values in sync with the default values in // BiquadFilterNode::BiquadFilterNode , mType(BiquadFilterType::Lowpass) , mFrequency(350.f) , mDetune(0.f) , mQ(1.f) , mGain(0.f) + , mWindowID(aWindowID) { } enum Parameteres { TYPE, FREQUENCY, DETUNE, Q, @@ -168,17 +171,18 @@ public: } else if(mBiquads.Length() != aInput.ChannelCount()){ if (mBiquads.IsEmpty()) { RefPtr<PlayingRefChangeHandler> refchanged = new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF); aStream->Graph()-> DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); } else { // Help people diagnose bug 924718 - NS_WARNING("BiquadFilterNode channel count changes may produce audio glitches"); + WebAudioUtils::LogToDeveloperConsole(mWindowID, + "BiquadFilterChannelCountChangeWarning"); } // Adjust the number of biquads based on the number of channels mBiquads.SetLength(aInput.ChannelCount()); } uint32_t numberOfChannels = mBiquads.Length(); aOutput->AllocateChannels(numberOfChannels); @@ -232,31 +236,33 @@ public: private: AudioNodeStream* mDestination; BiquadFilterType mType; AudioParamTimeline mFrequency; AudioParamTimeline mDetune; AudioParamTimeline mQ; AudioParamTimeline mGain; nsTArray<WebCore::Biquad> mBiquads; + uint64_t mWindowID; }; BiquadFilterNode::BiquadFilterNode(AudioContext* aContext) : AudioNode(aContext, 2, ChannelCountMode::Max, ChannelInterpretation::Speakers) , mType(BiquadFilterType::Lowpass) , mFrequency(new AudioParam(this, BiquadFilterNodeEngine::FREQUENCY, 350.f, "frequency")) , mDetune(new AudioParam(this, BiquadFilterNodeEngine::DETUNE, 0.f, "detune")) , mQ(new AudioParam(this, BiquadFilterNodeEngine::Q, 1.f, "Q")) , mGain(new AudioParam(this, BiquadFilterNodeEngine::GAIN, 0.f, "gain")) { - BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination()); + uint64_t windowID = aContext->GetParentObject()->WindowID(); + BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination(), windowID); mStream = AudioNodeStream::Create(aContext, engine, AudioNodeStream::NO_STREAM_FLAGS); } BiquadFilterNode::~BiquadFilterNode() { }
new file mode 100644 --- /dev/null +++ b/dom/media/webaudio/IIRFilterNode.cpp @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "IIRFilterNode.h" +#include "AudioNodeEngine.h" + +#include "blink/IIRFilter.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS_INHERITED0(IIRFilterNode, AudioNode) + +class IIRFilterNodeEngine final : public AudioNodeEngine +{ +public: + IIRFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination, + const AudioDoubleArray &aFeedforward, + const AudioDoubleArray &aFeedback, + uint64_t aWindowID) + : AudioNodeEngine(aNode) + , mDestination(aDestination->Stream()) + , mFeedforward(aFeedforward) + , mFeedback(aFeedback) + , mWindowID(aWindowID) + { + } + + void ProcessBlock(AudioNodeStream* aStream, + GraphTime aFrom, + const AudioBlock& aInput, + AudioBlock* aOutput, + bool* aFinished) override + { + float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4]; + float* alignedInputBuffer = ALIGNED16(inputBuffer); + ASSERT_ALIGNED16(alignedInputBuffer); + + if (aInput.IsNull()) { + if (!mIIRFilters.IsEmpty()) { + bool allZero = true; + for (uint32_t i = 0; i < mIIRFilters.Length(); ++i) { + allZero &= mIIRFilters[i]->buffersAreZero(); + } + + // all filter buffer values are zero, so the output will be zero + // as well. + if (allZero) { + mIIRFilters.Clear(); + aStream->ScheduleCheckForInactive(); + + RefPtr<PlayingRefChangeHandler> refchanged = + new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE); + aStream->Graph()-> + DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); + + aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); + return; + } + + PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE); + } + } else if(mIIRFilters.Length() != aInput.ChannelCount()){ + if (mIIRFilters.IsEmpty()) { + RefPtr<PlayingRefChangeHandler> refchanged = + new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF); + aStream->Graph()-> + DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); + } else { + WebAudioUtils::LogToDeveloperConsole(mWindowID, + "IIRFilterChannelCountChangeWarning"); + } + + // Adjust the number of filters based on the number of channels + mIIRFilters.SetLength(aInput.ChannelCount()); + for (size_t i = 0; i < aInput.ChannelCount(); ++i) { + mIIRFilters[i] = new blink::IIRFilter(&mFeedforward, &mFeedback); + } + } + + uint32_t numberOfChannels = mIIRFilters.Length(); + aOutput->AllocateChannels(numberOfChannels); + + for (uint32_t i = 0; i < numberOfChannels; ++i) { + const float* input; + if (aInput.IsNull()) { + input = alignedInputBuffer; + } else { + input = static_cast<const float*>(aInput.mChannelData[i]); + if (aInput.mVolume != 1.0) { + AudioBlockCopyChannelWithScale(input, aInput.mVolume, alignedInputBuffer); + input = alignedInputBuffer; + } + } + + mIIRFilters[i]->process(input, + aOutput->ChannelFloatsForWrite(i), + aInput.GetDuration()); + } + } + + bool IsActive() const override + { + return !mIIRFilters.IsEmpty(); + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + { + // Not owned: + // - mDestination - probably not owned + // - AudioParamTimelines - counted in the AudioNode + size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); + amount += mIIRFilters.ShallowSizeOfExcludingThis(aMallocSizeOf); + return amount; + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + +private: + AudioNodeStream* mDestination; + nsTArray<nsAutoPtr<blink::IIRFilter>> mIIRFilters; + AudioDoubleArray mFeedforward; + AudioDoubleArray mFeedback; + uint64_t mWindowID; +}; + +IIRFilterNode::IIRFilterNode(AudioContext* aContext, + const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward, + const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback) + : AudioNode(aContext, + 2, + ChannelCountMode::Max, + ChannelInterpretation::Speakers) +{ + mFeedforward.SetLength(aFeedforward.Length()); + PodCopy(mFeedforward.Elements(), aFeedforward.Elements(), aFeedforward.Length()); + mFeedback.SetLength(aFeedback.Length()); + PodCopy(mFeedback.Elements(), aFeedback.Elements(), aFeedback.Length()); + + // Scale coefficients -- we guarantee that mFeedback != 0 when creating + // the IIRFilterNode. + double scale = mFeedback[0]; + double* elements = mFeedforward.Elements(); + for (size_t i = 0; i < mFeedforward.Length(); ++i) { + elements[i] /= scale; + } + + elements = mFeedback.Elements(); + for (size_t i = 0; i < mFeedback.Length(); ++i) { + elements[i] /= scale; + } + + // We check that this is exactly equal to one later in blink/IIRFilter.cpp + elements[0] = 1.0; + + uint64_t windowID = aContext->GetParentObject()->WindowID(); + IIRFilterNodeEngine* engine = new IIRFilterNodeEngine(this, aContext->Destination(), mFeedforward, mFeedback, windowID); + mStream = AudioNodeStream::Create(aContext, engine, + AudioNodeStream::NO_STREAM_FLAGS); +} + +IIRFilterNode::~IIRFilterNode() +{ +} + +size_t +IIRFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); + return amount; +} + +size_t +IIRFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +JSObject* +IIRFilterNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return IIRFilterNodeBinding::Wrap(aCx, this, aGivenProto); +} + +void +IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz, + const Float32Array& aMagResponse, + const Float32Array& aPhaseResponse) +{ + aFrequencyHz.ComputeLengthAndData(); + aMagResponse.ComputeLengthAndData(); + aPhaseResponse.ComputeLengthAndData(); + + uint32_t length = std::min(std::min(aFrequencyHz.Length(), + aMagResponse.Length()), + aPhaseResponse.Length()); + if (!length) { + return; + } + + auto frequencies = MakeUnique<float[]>(length); + float* frequencyHz = aFrequencyHz.Data(); + const double nyquist = Context()->SampleRate() * 0.5; + + // Normalize the frequencies + for (uint32_t i = 0; i < length; ++i) { + if (frequencyHz[i] >= 0 && frequencyHz[i] <= nyquist) { + frequencies[i] = static_cast<float>(frequencyHz[i] / nyquist); + } else { + frequencies[i] = std::numeric_limits<float>::quiet_NaN(); + } + } + + blink::IIRFilter filter(&mFeedforward, &mFeedback); + filter.getFrequencyResponse(int(length), frequencies.get(), aMagResponse.Data(), aPhaseResponse.Data()); +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/media/webaudio/IIRFilterNode.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IIRFilterNode_h_ +#define IIRFilterNode_h_ + +#include "AudioNode.h" +#include "AudioParam.h" +#include "mozilla/dom/IIRFilterNodeBinding.h" + +namespace mozilla { +namespace dom { + +class AudioContext; + +class IIRFilterNode final : public AudioNode +{ +public: + explicit IIRFilterNode(AudioContext* aContext, + const mozilla::dom::binding_detail::AutoSequence<double>& aFeedforward, + const mozilla::dom::binding_detail::AutoSequence<double>& aFeedback);