--- a/accessible/tests/mochitest/events/test_mutation.html
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -313,18 +313,18 @@
this.eventSeq = [
new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode),
new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode),
new invokerChecker(EVENT_REORDER, this.containerNode)
];
this.invoke = function insertReferredElm_invoke() {
- this.containerNode.innerHTML =
- "<span id='insertReferredElms_span'></span><input aria-labelledby='insertReferredElms_span'>";
+ this.containerNode.unsafeSetInnerHTML(
+ "<span id='insertReferredElms_span'></span><input aria-labelledby='insertReferredElms_span'>");
};
this.getID = function insertReferredElm_getID() {
return "insert inaccessible element and then insert referring element to make it accessible";
};
}
function showHiddenParentOfVisibleChild() {
--- a/browser/base/content/test/permissions/browser_reservedkey.js
+++ b/browser/base/content/test/permissions/browser_reservedkey.js
@@ -5,17 +5,17 @@ add_task(async function test_reserved_sh
oncommand='this.setAttribute("count", Number(this.getAttribute("count")) + 1)'/>
<key id='kt_notreserved' modifiers='shift' key='P' reserved='false' count='0'
oncommand='this.setAttribute("count", Number(this.getAttribute("count")) + 1)'/>
<key id='kt_reserveddefault' modifiers='shift' key='Q' count='0'
oncommand='this.setAttribute("count", Number(this.getAttribute("count")) + 1)'/>
</keyset>`;
let container = document.createElement("box");
- container.innerHTML = keyset;
+ container.unsafeSetInnerHTML(keyset);
document.documentElement.appendChild(container);
/* eslint-enable no-unsanitized/property */
const pageUrl = "data:text/html,<body onload='document.body.firstChild.focus();'><div onkeydown='event.preventDefault();' tabindex=0>Test</div></body>";
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
EventUtils.synthesizeKey("O", { shiftKey: true });
EventUtils.synthesizeKey("P", { shiftKey: true });
--- a/devtools/client/responsive.html/components/Browser.js
+++ b/devtools/client/responsive.html/components/Browser.js
@@ -13,16 +13,22 @@ const PropTypes = require("devtools/clie
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const e10s = require("../utils/e10s");
const message = require("../utils/message");
const { getToplevelWindow } = require("../utils/window");
const FRAME_SCRIPT = "resource://devtools/client/responsive.html/browser/content.js";
+// Allow creation of HTML fragments without automatic sanitization, even
+// though we're in a chrom-privileged document.
+// This is, unfortunately, necessary in order to React to function
+// correctly.
+document.allowUnsafeHTML = true;
+
class Browser extends PureComponent {
/**
* This component is not allowed to depend directly on frequently changing
* data (width, height) due to the use of `dangerouslySetInnerHTML` below.
* Any changes in props will cause the <iframe> to be removed and added again,
* throwing away the current state of the page.
*/
static get propTypes() {
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_popup.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_popup.js
@@ -92,19 +92,23 @@ add_task(async function () {
is(popup.itemCount, 0, "items cleared");
ok(!input.hasAttribute("aria-activedescendant"), "no aria-activedescendant");
const onPopupClose = popup.once("popup-closed");
popup.hidePopup();
await onPopupClose;
});
+function stripNS(text) {
+ return text.replace(RegExp(' xmlns="http://www.w3.org/1999/xhtml"', "g"), "");
+}
+
function checkActiveDescendant(popup, input) {
let activeElement = input.ownerDocument.activeElement;
let descendantId = activeElement.getAttribute("aria-activedescendant");
let popupItem = popup._tooltip.panel.querySelector("#" + descendantId);
let cloneItem = input.ownerDocument.querySelector("#" + descendantId);
ok(popupItem, "Active descendant is found in the popup list");
ok(cloneItem, "Active descendant is found in the list clone");
- is(popupItem.outerHTML, cloneItem.outerHTML,
+ is(stripNS(popupItem.outerHTML), cloneItem.outerHTML,
"Cloned item has the same HTML as the original element");
}
--- a/devtools/shared/gcli/source/lib/gcli/util/util.js
+++ b/devtools/shared/gcli/source/lib/gcli/util/util.js
@@ -493,17 +493,21 @@ exports.setTextContent = function(elem,
*/
exports.setContents = function(elem, contents) {
if (typeof HTMLElement !== 'undefined' && contents instanceof HTMLElement) {
exports.clearElement(elem);
elem.appendChild(contents);
return;
}
- if ('innerHTML' in elem) {
+ if ('unsafeSetInnerHTML' in elem) {
+ // FIXME: Stop relying on unsanitized HTML.
+ elem.unsafeSetInnerHTML(contents);
+ }
+ else if ('innerHTML' in elem) {
elem.innerHTML = contents;
}
else {
try {
var ns = elem.ownerDocument.documentElement.namespaceURI;
if (!ns) {
ns = exports.NS_XHTML;
}
--- a/devtools/shared/tests/browser/browser_l10n_localizeMarkup.js
+++ b/devtools/shared/tests/browser/browser_l10n_localizeMarkup.js
@@ -13,31 +13,31 @@ add_task(function* () {
let TOOLBOX_L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
let str1 = STARTUP_L10N.getStr("inspector.label");
let str2 = STARTUP_L10N.getStr("inspector.accesskey");
let str3 = TOOLBOX_L10N.getStr("toolbox.defaultTitle");
ok(str1 && str2 && str3, "If this failed, strings should be updated in the test");
info("Create the test markup");
let div = document.createElement("div");
- div.innerHTML =
+ div.unsafeSetInnerHTML(
`<div data-localization-bundle="devtools/client/locales/startup.properties">
<div id="d0" data-localization="content=inspector.someInvalidKey"></div>
<div id="d1" data-localization="content=inspector.label">Text will disappear</div>
<div id="d2" data-localization="content=inspector.label;title=inspector.accesskey">
</div>
<!-- keep the following data-localization on two separate lines -->
<div id="d3" data-localization="content=inspector.label;
title=inspector.accesskey"></div>
<div id="d4" data-localization="aria-label=inspector.label">Some content</div>
<div data-localization-bundle="devtools/client/locales/toolbox.properties">
<div id="d5" data-localization="content=toolbox.defaultTitle"></div>
</div>
</div>
- `;
+ `);
info("Use localization helper to localize the test markup");
localizeMarkup(div);
let div1 = div.querySelector("#d1");
let div2 = div.querySelector("#d2");
let div3 = div.querySelector("#d3");
let div4 = div.querySelector("#d4");
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3923,16 +3923,22 @@ Element::GetInnerHTML(nsAString& aInnerH
void
Element::SetInnerHTML(const nsAString& aInnerHTML, nsIPrincipal* aSubjectPrincipal, ErrorResult& aError)
{
SetInnerHTMLInternal(aInnerHTML, aError);
}
void
+Element::UnsafeSetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError)
+{
+ SetInnerHTMLInternal(aInnerHTML, aError, true);
+}
+
+void
Element::GetOuterHTML(nsAString& aOuterHTML)
{
GetMarkup(true, aOuterHTML);
}
void
Element::SetOuterHTML(const nsAString& aOuterHTML, ErrorResult& aError)
{
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1413,16 +1413,17 @@ public:
void GetAnimations(const AnimationFilter& filter,
nsTArray<RefPtr<Animation>>& aAnimations);
static void GetAnimationsUnsorted(Element* aElement,
CSSPseudoElementType aPseudoType,
nsTArray<RefPtr<Animation>>& aAnimations);
NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML);
virtual void SetInnerHTML(const nsAString& aInnerHTML, nsIPrincipal* aSubjectPrincipal, ErrorResult& aError);
+ void UnsafeSetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
void GetOuterHTML(nsAString& aOuterHTML);
void SetOuterHTML(const nsAString& aOuterHTML, ErrorResult& aError);
void InsertAdjacentHTML(const nsAString& aPosition, const nsAString& aText,
ErrorResult& aError);
//----------------------------------------
/**
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -2269,17 +2269,18 @@ ContainsMarkup(const nsAString& aStr)
}
++start;
}
return false;
}
void
-FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError)
+FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError,
+ bool aNeverSanitize)
{
FragmentOrElement* target = this;
// Handle template case.
if (nsNodeUtils::IsTemplateElement(target)) {
DocumentFragment* frag =
static_cast<HTMLTemplateElement*>(target)->Content();
MOZ_ASSERT(frag);
target = frag;
@@ -2329,24 +2330,26 @@ FragmentOrElement::SetInnerHTMLInternal(
if (doc->IsHTMLDocument()) {
int32_t oldChildCount = target->GetChildCount();
aError = nsContentUtils::ParseFragmentHTML(aInnerHTML,
target,
contextLocalName,
contextNameSpaceID,
doc->GetCompatibilityMode() ==
eCompatibility_NavQuirks,
- true);
+ true,
+ aNeverSanitize);
mb.NodesAdded();
// HTML5 parser has notified, but not fired mutation events.
nsContentUtils::FireMutationEventsForDirectParsing(doc, target,
oldChildCount);
} else {
RefPtr<DocumentFragment> df =
- nsContentUtils::CreateContextualFragment(target, aInnerHTML, true, aError);
+ nsContentUtils::CreateContextualFragment(target, aInnerHTML, true, aError,
+ aNeverSanitize);
if (!aError.Failed()) {
// Suppress assertion about node removal mutation events that can't have
// listeners anyway, because no one has had the chance to register mutation
// listeners on the fragment that comes from the parser.
nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
static_cast<nsINode*>(target)->AppendChild(*df, aError);
mb.NodesAdded();
--- a/dom/base/FragmentOrElement.h
+++ b/dom/base/FragmentOrElement.h
@@ -327,17 +327,18 @@ public:
/**
* An object implementing the .classList property for this element.
*/
RefPtr<nsDOMTokenList> mClassList;
};
protected:
void GetMarkup(bool aIncludeSelf, nsAString& aMarkup);
- void SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError);
+ void SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError,
+ bool aNeverSanitize = false);
// Override from nsINode
nsIContent::nsContentSlots* CreateSlots() override
{
return new nsDOMSlots();
}
nsIContent::nsExtendedContentSlots* CreateExtendedSlots() final override
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -157,16 +157,17 @@
#include "nsIMIMEService.h"
#include "nsINode.h"
#include "mozilla/dom/NodeInfo.h"
#include "nsIObjectLoadingContent.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIOfflineCacheUpdate.h"
#include "nsIParser.h"
+#include "nsIParserUtils.h"
#include "nsIPermissionManager.h"
#include "nsIPluginHost.h"
#include "nsIRemoteBrowser.h"
#include "nsIRequest.h"
#include "nsIRunnable.h"
#include "nsIScriptContext.h"
#include "nsIScriptError.h"
#include "nsIScriptGlobalObject.h"
@@ -195,16 +196,17 @@
#include "nsSandboxFlags.h"
#include "nsScriptSecurityManager.h"
#include "nsSerializationHelper.h"
#include "nsStreamUtils.h"
#include "nsTextEditorState.h"
#include "nsTextFragment.h"
#include "nsTextNode.h"
#include "nsThreadUtils.h"
+#include "nsTreeSanitizer.h"
#include "nsUnicodeProperties.h"
#include "nsURLHelper.h"
#include "nsViewManager.h"
#include "nsViewportInfo.h"
#include "nsWidgetsCID.h"
#include "nsIWindowProvider.h"
#include "nsWrapperCacheInlines.h"
#include "nsXULPopupManager.h"
@@ -4954,17 +4956,18 @@ nsContentUtils::CreateContextualFragment
aPreventScriptExecution, rv).take();
return rv.StealNSResult();
}
already_AddRefed<DocumentFragment>
nsContentUtils::CreateContextualFragment(nsINode* aContextNode,
const nsAString& aFragment,
bool aPreventScriptExecution,
- ErrorResult& aRv)
+ ErrorResult& aRv,
+ bool aNeverSanitize)
{
if (!aContextNode) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return nullptr;
}
// If we don't have a document here, we can't get the right security context
// for compiling event handlers... so just bail out.
@@ -4989,24 +4992,26 @@ nsContentUtils::CreateContextualFragment
}
if (contextAsContent && !contextAsContent->IsHTMLElement(nsGkAtoms::html)) {
aRv = ParseFragmentHTML(aFragment, frag,
contextAsContent->NodeInfo()->NameAtom(),
contextAsContent->GetNameSpaceID(),
(document->GetCompatibilityMode() ==
eCompatibility_NavQuirks),
- aPreventScriptExecution);
+ aPreventScriptExecution,
+ aNeverSanitize);
} else {
aRv = ParseFragmentHTML(aFragment, frag,
nsGkAtoms::body,
kNameSpaceID_XHTML,
(document->GetCompatibilityMode() ==
eCompatibility_NavQuirks),
- aPreventScriptExecution);
+ aPreventScriptExecution,
+ aNeverSanitize);
}
return frag.forget();
}
AutoTArray<nsString, 32> tagStack;
nsAutoString uriStr, nameStr;
nsCOMPtr<nsIContent> content = do_QueryInterface(aContextNode);
@@ -5060,17 +5065,18 @@ nsContentUtils::CreateContextualFragment
}
}
content = content->GetParent();
}
nsCOMPtr<nsIDOMDocumentFragment> frag;
aRv = ParseFragmentXML(aFragment, document, tagStack,
- aPreventScriptExecution, getter_AddRefs(frag));
+ aPreventScriptExecution, getter_AddRefs(frag),
+ aNeverSanitize);
return frag.forget().downcast<DocumentFragment>();
}
/* static */
void
nsContentUtils::DropFragmentParsers()
{
NS_IF_RELEASE(sHTMLFragmentParser);
@@ -5087,37 +5093,64 @@ nsContentUtils::XPCOMShutdown()
/* static */
nsresult
nsContentUtils::ParseFragmentHTML(const nsAString& aSourceBuffer,
nsIContent* aTargetNode,
nsAtom* aContextLocalName,
int32_t aContextNamespace,
bool aQuirks,
- bool aPreventScriptExecution)
+ bool aPreventScriptExecution,
+ bool aNeverSanitize)
{
AutoTimelineMarker m(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML");
if (nsContentUtils::sFragmentParsingActive) {
NS_NOTREACHED("Re-entrant fragment parsing attempted.");
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
nsContentUtils::sFragmentParsingActive = true;
if (!sHTMLFragmentParser) {
NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser());
// Now sHTMLFragmentParser owns the object
}
+
+ nsIContent* target = aTargetNode;
+
+ // If this is a chrome-privileged document, create a fragment first, and
+ // sanitize it before insertion.
+ RefPtr<DocumentFragment> fragment;
+ if (!aNeverSanitize && !aTargetNode->OwnerDoc()->AllowUnsafeHTML()) {
+ fragment = new DocumentFragment(aTargetNode->OwnerDoc()->NodeInfoManager());
+ target = fragment;
+ }
+
nsresult rv =
sHTMLFragmentParser->ParseFragment(aSourceBuffer,
- aTargetNode,
+ target,
aContextLocalName,
aContextNamespace,
aQuirks,
aPreventScriptExecution);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fragment) {
+ // Don't fire mutation events for nodes removed by the sanitizer.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ nsTreeSanitizer sanitizer(nsIParserUtils::SanitizerAllowStyle |
+ nsIParserUtils::SanitizerAllowComments);
+ sanitizer.Sanitize(fragment);
+
+ ErrorResult error;
+ aTargetNode->AppendChild(*fragment, error);
+ rv = error.StealNSResult();
+ }
+
return rv;
}
/* static */
nsresult
nsContentUtils::ParseDocumentHTML(const nsAString& aSourceBuffer,
nsIDocument* aTargetDocument,
bool aScriptingEnabledForNoscriptParsing)
@@ -5142,17 +5175,18 @@ nsContentUtils::ParseDocumentHTML(const
}
/* static */
nsresult
nsContentUtils::ParseFragmentXML(const nsAString& aSourceBuffer,
nsIDocument* aDocument,
nsTArray<nsString>& aTagStack,
bool aPreventScriptExecution,
- nsIDOMDocumentFragment** aReturn)
+ nsIDOMDocumentFragment** aReturn,
+ bool aNeverSanitize)
{
AutoTimelineMarker m(aDocument->GetDocShell(), "Parse XML");
if (nsContentUtils::sFragmentParsingActive) {
NS_NOTREACHED("Re-entrant fragment parsing attempted.");
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
@@ -5182,16 +5216,29 @@ nsContentUtils::ParseFragmentXML(const n
NS_IF_RELEASE(sXMLFragmentSink);
return rv;
}
rv = sXMLFragmentSink->FinishFragmentParsing(aReturn);
sXMLFragmentParser->Reset();
+ // If this is a chrome-privileged document, sanitize the fragment before
+ // returning.
+ if (!aNeverSanitize && !aDocument->AllowUnsafeHTML()) {
+ // Don't fire mutation events for nodes removed by the sanitizer.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ RefPtr<DocumentFragment> fragment = static_cast<DocumentFragment*>(*aReturn);
+
+ nsTreeSanitizer sanitizer(nsIParserUtils::SanitizerAllowStyle |
+ nsIParserUtils::SanitizerAllowComments);
+ sanitizer.Sanitize(fragment);
+ }
+
return rv;
}
/* static */
nsresult
nsContentUtils::ConvertToPlainText(const nsAString& aSourceBuffer,
nsAString& aResultBuffer,
uint32_t aFlags,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1594,17 +1594,18 @@ public:
*/
static nsresult CreateContextualFragment(nsINode* aContextNode,
const nsAString& aFragment,
bool aPreventScriptExecution,
nsIDOMDocumentFragment** aReturn);
static already_AddRefed<mozilla::dom::DocumentFragment>
CreateContextualFragment(nsINode* aContextNode, const nsAString& aFragment,
bool aPreventScriptExecution,
- mozilla::ErrorResult& aRv);
+ mozilla::ErrorResult& aRv,
+ bool aNeverSanitize = false);
/**
* Invoke the fragment parsing algorithm (innerHTML) using the HTML parser.
*
* @param aSourceBuffer the string being set as innerHTML
* @param aTargetNode the target container
* @param aContextLocalName local name of context node
* @param aContextNamespace namespace of context node
@@ -1616,34 +1617,36 @@ public:
* fragments is made, NS_ERROR_OUT_OF_MEMORY if aSourceBuffer is too
* long and NS_OK otherwise.
*/
static nsresult ParseFragmentHTML(const nsAString& aSourceBuffer,
nsIContent* aTargetNode,
nsAtom* aContextLocalName,
int32_t aContextNamespace,
bool aQuirks,
- bool aPreventScriptExecution);
+ bool aPreventScriptExecution,
+ bool aNeverSanitize = false);
/**
* Invoke the fragment parsing algorithm (innerHTML) using the XML parser.
*
* @param aSourceBuffer the string being set as innerHTML
* @param aTargetNode the target container
* @param aTagStack the namespace mapping context
* @param aPreventExecution whether to mark scripts as already started
* @param aReturn the result fragment
* @return NS_ERROR_DOM_INVALID_STATE_ERR if a re-entrant attempt to parse
* fragments is made, a return code from the XML parser.
*/
static nsresult ParseFragmentXML(const nsAString& aSourceBuffer,
nsIDocument* aDocument,
nsTArray<nsString>& aTagStack,
bool aPreventScriptExecution,
- nsIDOMDocumentFragment** aReturn);
+ nsIDOMDocumentFragment** aReturn,
+ bool aNeverSanitize = false);
/**
* Parse a string into a document using the HTML parser.
* Script elements are marked unexecutable.
*
* @param aSourceBuffer the string to parse as an HTML document
* @param aTargetDocument the document object to parse into. Must not have
* child nodes.
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1478,16 +1478,17 @@ nsIDocument::nsIDocument()
mFrameRequestCallbacksScheduled(false),
mIsTopLevelContentDocument(false),
mIsContentDocument(false),
mDidCallBeginLoad(false),
mBufferingCSPViolations(false),
mAllowPaymentRequest(false),
mEncodingMenuDisabled(false),
mIsSVGGlyphsDocument(false),
+ mAllowUnsafeHTML(false),
mIsScopedStyleEnabled(eScopedStyle_Unknown),
mCompatMode(eCompatibility_FullStandards),
mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
mStyleBackendType(StyleBackendType::None),
#ifdef MOZILLA_INTERNAL_API
mVisibilityState(dom::VisibilityState::Hidden),
#else
mDummy(0),
@@ -6182,16 +6183,23 @@ nsIDocument::CreateAttributeNS(const nsA
return nullptr;
}
RefPtr<Attr> attribute = new Attr(nullptr, nodeInfo.forget(),
EmptyString());
return attribute.forget();
}
+bool
+nsIDocument::AllowUnsafeHTML() const
+{
+ return (!nsContentUtils::IsSystemPrincipal(NodePrincipal()) ||
+ mAllowUnsafeHTML);
+}
+
void
nsDocument::ScheduleSVGForPresAttrEvaluation(nsSVGElement* aSVG)
{
mLazySVGPresElements.PutEntry(aSVG);
}
void
nsDocument::UnscheduleSVGForPresAttrEvaluation(nsSVGElement* aSVG)
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2868,16 +2868,18 @@ public:
already_AddRefed<mozilla::dom::CDATASection>
CreateCDATASection(const nsAString& aData, mozilla::ErrorResult& rv);
already_AddRefed<mozilla::dom::Attr>
CreateAttribute(const nsAString& aName, mozilla::ErrorResult& rv);
already_AddRefed<mozilla::dom::Attr>
CreateAttributeNS(const nsAString& aNamespaceURI,
const nsAString& aQualifiedName,
mozilla::ErrorResult& rv);
+ void SetAllowUnsafeHTML(bool aAllow) { mAllowUnsafeHTML = aAllow; }
+ bool AllowUnsafeHTML() const;
void GetInputEncoding(nsAString& aInputEncoding) const;
already_AddRefed<mozilla::dom::Location> GetLocation() const;
void GetReferrer(nsAString& aReferrer) const;
void GetLastModified(nsAString& aLastModified) const;
void GetReadyState(nsAString& aReadyState) const;
// Not const because otherwise the compiler can't figure out whether to call
// this GetTitle or the nsAString version from non-const methods, since
// neither is an exact match.
@@ -3551,16 +3553,20 @@ protected:
// True if dom.webcomponents.shadowdom.enabled pref is set when document is
// created.
bool mIsShadowDOMEnabled : 1;
// True if this document is for an SVG-in-OpenType font.
bool mIsSVGGlyphsDocument : 1;
+ // True if unsafe HTML fragments should be allowed in chrome-privileged
+ // documents.
+ bool mAllowUnsafeHTML : 1;
+
// Whether <style scoped> support is enabled in this document.
enum { eScopedStyle_Unknown, eScopedStyle_Disabled, eScopedStyle_Enabled };
unsigned int mIsScopedStyleEnabled : 2;
// Compatibility mode
nsCompatibility mCompatMode;
// Our readyState
--- a/dom/base/test/chrome.ini
+++ b/dom/base/test/chrome.ini
@@ -20,16 +20,17 @@ support-files =
[test_bug715041.xul]
[test_bug715041_removal.xul]
[test_bug945152.html]
[test_bug1008126.html]
[test_bug1016960.html]
[test_copypaste.xul]
subsuite = clipboard
[test_domrequesthelper.xul]
+[test_fragment_sanitization.xul]
[test_messagemanager_principal.html]
[test_messagemanager_send_principal.html]
skip-if = buildapp == 'mulet'
[test_mozbrowser_apis_allowed.html]
[test_navigator_resolve_identity_xrays.xul]
support-files = file_navigator_resolve_identity_xrays.xul
[test_sandboxed_blob_uri.html]
[test_sendQueryContentAndSelectionSetEvent.html]
--- a/dom/base/test/chrome/test_bug683852.xul
+++ b/dom/base/test/chrome/test_bug683852.xul
@@ -15,16 +15,18 @@ https://bugzilla.mozilla.org/show_bug.cg
</body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 683852 **/
SimpleTest.waitForExplicitFinish();
+ const NS_HTML = "http://www.w3.org/1999/xhtml";
+
function startTest() {
is(document.contains(document), true, "Document should contain itself!");
var tb = document.getElementById("testbutton");
is(document.contains(tb), true, "Document should contain element in it!");
is(tb.contains(tb), true, "Element should contain itself.")
var anon = document.getAnonymousElementByAttribute(tb, "anonid", "button-box");
is(document.contains(anon), false, "Document should not contain anonymous element in it!");
@@ -43,17 +45,17 @@ https://bugzilla.mozilla.org/show_bug.cg
is(document.contains(null), false, "Document shouldn't contain null.");
var pi = document.createProcessingInstruction("adf", "asd");
is(pi.contains(document), false, "Processing instruction shouldn't contain document");
document.documentElement.appendChild(pi);
document.contains(pi, true, "Document should contain processing instruction");
- var df = document.createRange().createContextualFragment("<div>foo</div>");
+ var df = document.createRange().createContextualFragment(`<div xmlns="${NS_HTML}">foo</div>`);
is(df.contains(df.firstChild), true, "Document fragment should contain its child");
is(df.contains(df.firstChild.firstChild), true,
"Document fragment should contain its descendant");
is(df.contains(df), true, "Document fragment should contain itself.");
var d = document.implementation.createHTMLDocument("");
is(document.contains(d), false,
"Document shouldn't contain another document.");
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_fragment_sanitization.xul
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1432966
+-->
+<window title="Mozilla Bug 1432966"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"/>
+
+ <script type="application/javascript"><![CDATA[
+
+var { classes: Cc, interfaces: Ci } = Components;
+
+const NS_HTML = "http://www.w3.org/1999/xhtml";
+
+function awaitLoad(frame) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", resolve, {once: true});
+ });
+}
+
+async function testFrame(frame, html, expected = html) {
+ document.querySelector("body").appendChild(frame);
+ await awaitLoad(frame);
+
+ // Remove the xmlns attributes that will be automatically added when we're
+ // in an XML document, and break the comparison.
+ function unNS(text) {
+ return text.replace(RegExp(` xmlns="${NS_HTML}"`, "g"), "");
+ }
+
+ let doc = frame.contentDocument;
+ let body = doc.body || doc.documentElement;
+
+ let div = doc.createElementNS(NS_HTML, "div");
+ body.appendChild(div);
+
+ div.innerHTML = html;
+ is(unNS(div.innerHTML), expected, "innerHTML value");
+
+ div.innerHTML = "<div></div>";
+ div.firstChild.outerHTML = html;
+ is(unNS(div.innerHTML), expected, "outerHTML value");
+
+ div.textContent = "";
+ div.insertAdjacentHTML("beforeend", html);
+ is(unNS(div.innerHTML), expected, "insertAdjacentHTML('beforeend') value");
+
+ div.innerHTML = "<a>foo</a>";
+ div.firstChild.insertAdjacentHTML("afterend", html);
+ is(unNS(div.innerHTML), "<a>foo</a>" + expected, "insertAdjacentHTML('afterend') value");
+
+ frame.remove();
+}
+
+add_task(async function test_fragment_sanitization() {
+ const XUL_URL = "chrome://global/content/win.xul";
+ const HTML_URL = "chrome://mochitests/content/chrome/dom/base/test/file_empty.html";
+
+ const HTML = '<a onclick="foo()" href="javascript:foo"><script>bar()<\/script>Meh.</a><a href="http://foo/"></a>';
+ const SANITIZED = '<a>Meh.</a><a href="http://foo/"></a>';
+
+ info("Test content HTML document");
+ {
+ let frame = document.createElementNS(NS_HTML, "iframe");
+ frame.src = "http://example.com/";
+
+ await testFrame(frame, HTML);
+ }
+
+ info("Test chrome HTML document");
+ {
+ let frame = document.createElementNS(NS_HTML, "iframe");
+ frame.src = HTML_URL;
+
+ await testFrame(frame, HTML, SANITIZED);
+ }
+
+ info("Test chrome XUL document");
+ {
+ let frame = document.createElementNS(NS_HTML, "iframe");
+ frame.src = XUL_URL;
+
+ await testFrame(frame, HTML, SANITIZED);
+ }
+});
+
+ ]]></script>
+
+ <description style="-moz-user-focus: normal; -moz-user-select: text;"><![CDATA[
+ hello
+ world
+ ]]></description>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432966"
+ target="_blank">Mozilla Bug 1432966</a>
+ </body>
+</window>
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -93,16 +93,21 @@ interface Document : Node {
// These are not in the spec, but leave them for now for backwards compat.
// So sort of like Gecko extensions
[NewObject, Throws]
CDATASection createCDATASection(DOMString data);
[NewObject, Throws]
Attr createAttribute(DOMString name);
[NewObject, Throws]
Attr createAttributeNS(DOMString? namespace, DOMString name);
+
+ // Allows setting innerHTML without automatic sanitization.
+ // Do not use this.
+ [ChromeOnly]
+ attribute boolean allowUnsafeHTML;
};
// http://www.whatwg.org/specs/web-apps/current-work/#the-document-object
partial interface Document {
[PutForwards=href, Unforgeable] readonly attribute Location? location;
//(HTML only) attribute DOMString domain;
readonly attribute DOMString referrer;
//(HTML only) attribute DOMString cookie;
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -231,16 +231,26 @@ partial interface Element {
// http://domparsing.spec.whatwg.org/#extensions-to-the-element-interface
partial interface Element {
[CEReactions, SetterNeedsSubjectPrincipal=NonSystem, Pure, SetterThrows, TreatNullAs=EmptyString]
attribute DOMString innerHTML;
[CEReactions, Pure,SetterThrows,TreatNullAs=EmptyString]
attribute DOMString outerHTML;
[CEReactions, Throws]
void insertAdjacentHTML(DOMString position, DOMString text);
+
+ /**
+ * Like the innerHTML setter, but does not sanitize its values, even in
+ * chrome-privileged documents.
+ *
+ * If you're thinking about using this, don't. You have many, much better
+ * options.
+ */
+ [ChromeOnly, Throws]
+ void unsafeSetInnerHTML(DOMString html);
};
// http://www.w3.org/TR/selectors-api/#interface-definitions
partial interface Element {
[Throws, Pure]
Element? querySelector(DOMString selectors);
[Throws, Pure]
NodeList querySelectorAll(DOMString selectors);
--- a/layout/style/test/chrome/bug418986-2.js
+++ b/layout/style/test/chrome/bug418986-2.js
@@ -234,18 +234,18 @@ var green = (function () {
return getComputedStyle(temp).backgroundColor;
})();
// __testCSS(resisting)__.
// Creates a series of divs and CSS using media queries to set their
// background color. If all media queries match as expected, then
// all divs should have a green background color.
var testCSS = function (resisting) {
- document.getElementById("display").innerHTML = generateHtmlLines(resisting);
- document.getElementById("test-css").innerHTML = generateCSSLines(resisting);
+ document.getElementById("display").unsafeSetInnerHTML(generateHtmlLines(resisting));
+ document.getElementById("test-css").unsafeSetInnerHTML(generateCSSLines(resisting));
let cssTestDivs = document.querySelectorAll(".spoof,.suppress");
for (let div of cssTestDivs) {
let color = window.getComputedStyle(div).backgroundColor;
ok(color === green, "CSS for '" + div.id + "'");
}
};
// __testOSXFontSmoothing(resisting)__.
@@ -279,17 +279,17 @@ var testMediaQueriesInPictureElements =
if (expected) {
let query = constructQuery(key, expected);
lines += "<picture>\n";
lines += " <source srcset='/tests/layout/style/test/chrome/match.png' media='" + query + "' />\n";
lines += " <img title='" + key + ":" + expected + "' class='testImage' src='/tests/layout/style/test/chrome/mismatch.png' alt='" + key + "' />\n";
lines += "</picture><br/>\n";
}
}
- document.getElementById("pictures").innerHTML = lines;
+ document.getElementById("pictures").unsafeSetInnerHTML(lines);
var testImages = document.getElementsByClassName("testImage");
await sleep(0);
for (let testImage of testImages) {
ok(testImage.currentSrc.endsWith("/match.png"), "Media query '" + testImage.title + "' in picture should match.");
}
};
// __pushPref(key, value)__.
--- a/toolkit/content/tests/chrome/test_bug570192.xul
+++ b/toolkit/content/tests/chrome/test_bug570192.xul
@@ -31,18 +31,18 @@ https://bugzilla.mozilla.org/show_bug.cg
</body>
<script type="application/javascript">
<![CDATA[
addLoadEvent(function() {
try {
var content = document.getElementById("content");
- content.innerHTML = '<textbox newlines="pasteintact" ' +
- 'xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>';
+ content.unsafeSetInnerHTML('<textbox newlines="pasteintact" ' +
+ 'xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>');
var textbox = content.firstChild;
ok(textbox, "created the textbox");
ok(!textbox.editor, "do we have an editor?");
} catch (e) {
ok(false, "Got an exception: " + e);
}
SimpleTest.finish();
});