Bug 877072 - HTML Imports part1. r=mrbkap

This commit is contained in:
Gabor Krizsanits 2014-05-21 19:08:12 +02:00
parent ff2a1d8115
commit 3ddd53889b
21 changed files with 847 additions and 31 deletions

View File

@ -106,6 +106,7 @@ struct ElementRegistrationOptions;
class Event;
class EventTarget;
class FrameRequestCallback;
class ImportManager;
class OverfillCallback;
class HTMLBodyElement;
struct LifecycleCallbackArgs;
@ -129,8 +130,8 @@ typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
} // namespace mozilla
#define NS_IDOCUMENT_IID \
{ 0x906d05e7, 0x39af, 0x4ff0, \
{ 0xbc, 0xcd, 0x30, 0x0c, 0x7f, 0xeb, 0x86, 0x21 } }
{ 0x0300e2e0, 0x24c9, 0x4ecf, \
{ 0x81, 0xec, 0x64, 0x26, 0x9a, 0x4b, 0xef, 0x18 } }
// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
@ -2255,6 +2256,13 @@ public:
virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
// Each import tree has exactly one master document which is
// the root of the tree, and owns the browser context.
virtual already_AddRefed<nsIDocument> MasterDocument() = 0;
virtual void SetMasterDocument(nsIDocument* master) = 0;
virtual bool IsMasterDocument() = 0;
virtual already_AddRefed<mozilla::dom::ImportManager> ImportManager() = 0;
private:
uint64_t mWarnedAbout;
SelectorCache mSelectorCache;

View File

@ -17,8 +17,8 @@
#include "mozilla/CORSMode.h"
#define NS_ISCRIPTELEMENT_IID \
{ 0x491628bc, 0xce7c, 0x4db4, \
{ 0x93, 0x3f, 0xce, 0x1b, 0x75, 0xee, 0x75, 0xce } }
{ 0xe60fca9b, 0x1b96, 0x4e4e, \
{ 0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c } }
/**
* Internal interface implemented by script elements

View File

@ -0,0 +1,368 @@
/* -*- 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 "ImportManager.h"
#include "mozilla/EventListenerManager.h"
#include "HTMLLinkElement.h"
#include "nsContentPolicyUtils.h"
#include "nsContentUtils.h"
#include "nsIChannel.h"
#include "nsIChannelPolicy.h"
#include "nsIContentPolicy.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
#include "nsIDOMEvent.h"
#include "nsIPrincipal.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsScriptLoader.h"
#include "nsNetUtil.h"
class AutoError {
public:
AutoError(mozilla::dom::ImportLoader* loader)
: mLoader(loader)
, mPassed(false)
{}
~AutoError()
{
if (!mPassed) {
mLoader->Error();
}
}
void Pass() { mPassed = true; }
private:
mozilla::dom::ImportLoader* mLoader;
bool mPassed;
};
namespace mozilla {
namespace dom {
NS_INTERFACE_MAP_BEGIN(ImportLoader)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ImportLoader)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ImportLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportLoader)
NS_IMPL_CYCLE_COLLECTION(ImportLoader,
mDocument,
mLinks)
ImportLoader::ImportLoader(nsIURI* aURI, nsIDocument* aImportParent)
: mURI(aURI)
, mImportParent(aImportParent)
, mReady(false)
, mStopped(false)
, mBlockingScripts(false)
{}
void
ImportLoader::BlockScripts()
{
MOZ_ASSERT(!mBlockingScripts);
mImportParent->ScriptLoader()->AddExecuteBlocker();
mBlockingScripts = true;
}
void
ImportLoader::UnblockScripts()
{
MOZ_ASSERT(mBlockingScripts);
mImportParent->ScriptLoader()->RemoveExecuteBlocker();
mBlockingScripts = false;
}
void
ImportLoader::DispatchEventIfFinished(nsINode* aNode)
{
MOZ_ASSERT(!(mReady && mStopped));
if (mReady) {
DispatchLoadEvent(aNode);
}
if (mStopped) {
DispatchErrorEvent(aNode);
}
}
void
ImportLoader::AddLinkElement(nsINode* aNode)
{
// If a new link element is added to the import tree that
// refers to an import that is already finished loading or
// stopped trying, we need to fire the corresponding event
// on it.
mLinks.AppendObject(aNode);
DispatchEventIfFinished(aNode);
}
void
ImportLoader::RemoveLinkElement(nsINode* aNode)
{
mLinks.RemoveObject(aNode);
}
// Events has to be fired with a script runner, so mImport can
// be set on the link element before the load event is fired even
// if ImportLoader::Get returns an already loaded import and we
// fire the load event immediately on the new referring link element.
class AsyncEvent : public nsRunnable {
public:
AsyncEvent(nsINode* aNode, bool aSuccess)
: mNode(aNode)
, mSuccess(aSuccess)
{
MOZ_ASSERT(mNode);
}
NS_IMETHOD Run() {
return nsContentUtils::DispatchTrustedEvent(mNode->OwnerDoc(),
mNode,
mSuccess ? NS_LITERAL_STRING("load")
: NS_LITERAL_STRING("error"),
/* aCanBubble = */ true,
/* aCancelable = */ true);
}
private:
nsCOMPtr<nsINode> mNode;
bool mSuccess;
};
void
ImportLoader::DispatchErrorEvent(nsINode* aNode)
{
nsContentUtils::AddScriptRunner(new AsyncEvent(aNode, /* aSuccess = */ false));
}
void
ImportLoader::DispatchLoadEvent(nsINode* aNode)
{
nsContentUtils::AddScriptRunner(new AsyncEvent(aNode, /* aSuccess = */ true));
}
void
ImportLoader::Done()
{
mReady = true;
uint32_t count = mLinks.Count();
for (uint32_t i = 0; i < count; i++) {
DispatchLoadEvent(mLinks[i]);
}
UnblockScripts();
ReleaseResources();
}
void
ImportLoader::Error()
{
mDocument = nullptr;
mStopped = true;
uint32_t count = mLinks.Count();
for (uint32_t i = 0; i < count; i++) {
DispatchErrorEvent(mLinks[i]);
}
UnblockScripts();
ReleaseResources();
}
// Release all the resources we don't need after there is no more
// data available on the channel, and the parser is done.
void ImportLoader::ReleaseResources()
{
mParserStreamListener = nullptr;
mChannel = nullptr;
mImportParent = nullptr;
}
void
ImportLoader::Open()
{
AutoError ae(this);
// Imports should obey to the master documents CSP.
nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(master);
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
mURI,
principal,
mImportParent,
NS_LITERAL_CSTRING("text/html"),
/* extra = */ nullptr,
&shouldLoad,
nsContentUtils::GetContentPolicy(),
nsContentUtils::GetSecurityManager());
if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
NS_WARN_IF_FALSE(NS_CP_ACCEPTED(shouldLoad), "ImportLoader rejected by CSP");
return;
}
nsCOMPtr<nsILoadGroup> loadGroup = mImportParent->GetDocumentLoadGroup();
nsCOMPtr<nsIChannelPolicy> channelPolicy;
nsCOMPtr<nsIContentSecurityPolicy> csp;
rv = principal->GetCsp(getter_AddRefs(csp));
NS_ENSURE_SUCCESS_VOID(rv);
if (csp) {
channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
channelPolicy->SetContentSecurityPolicy(csp);
channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SUBDOCUMENT);
}
rv = NS_NewChannel(getter_AddRefs(mChannel),
mURI,
/* ioService = */ nullptr,
loadGroup,
/* callbacks = */ nullptr,
nsIRequest::LOAD_BACKGROUND,
channelPolicy);
NS_ENSURE_SUCCESS_VOID(rv);
mChannel->AsyncOpen(this, nullptr);
BlockScripts();
ae.Pass();
}
NS_IMETHODIMP
ImportLoader::OnDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aStream,
uint64_t aOffset,
uint32_t aCount)
{
MOZ_ASSERT(mParserStreamListener);
AutoError ae(this);
nsresult rv = mParserStreamListener->OnDataAvailable(mChannel, aContext,
aStream, aOffset,
aCount);
NS_ENSURE_SUCCESS(rv, rv);
ae.Pass();
return rv;
}
NS_IMETHODIMP
ImportLoader::HandleEvent(nsIDOMEvent *aEvent)
{
#ifdef DEBUG
nsAutoString type;
aEvent->GetType(type);
MOZ_ASSERT(type.EqualsLiteral("DOMContentLoaded"));
#endif
Done();
return NS_OK;
}
NS_IMETHODIMP
ImportLoader::OnStopRequest(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus)
{
MOZ_ASSERT(aRequest == mChannel,
"Wrong channel something went horribly wrong");
if (mParserStreamListener) {
mParserStreamListener->OnStopRequest(aRequest, aContext, aStatus);
}
nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mDocument);
EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
manager->AddEventListenerByType(this,
NS_LITERAL_STRING("DOMContentLoaded"),
TrustedEventsAtSystemGroupBubble());
return NS_OK;
}
NS_IMETHODIMP
ImportLoader::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
MOZ_ASSERT(aRequest == mChannel,
"Wrong channel, something went horribly wrong");
AutoError ae(this);
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mImportParent);
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
mChannel->SetOwner(principal);
nsAutoCString type;
mChannel->GetContentType(type);
if (!type.EqualsLiteral("text/html")) {
NS_WARNING("ImportLoader wrong content type");
return NS_ERROR_FAILURE;
}
// The scope object is same for all the imports in an import tree,
// let's get it form the import parent.
nsCOMPtr<nsIGlobalObject> global = mImportParent->GetScopeObject();
nsCOMPtr<nsIDOMDocument> importDoc;
nsCOMPtr<nsIURI> baseURI = mImportParent->GetBaseURI();
const nsAString& emptyStr = EmptyString();
nsresult rv = NS_NewDOMDocument(getter_AddRefs(importDoc),
emptyStr, emptyStr, nullptr, mURI,
baseURI, principal, false, global,
DocumentFlavorHTML);
NS_ENSURE_SUCCESS(rv, rv);
// The imported document must know which master document it belongs to.
mDocument = do_QueryInterface(importDoc);
nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
mDocument->SetMasterDocument(master);
// We have to connect the blank document we created with the channel we opened.
nsCOMPtr<nsIStreamListener> listener;
nsCOMPtr<nsILoadGroup> loadGroup;
mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
rv = mDocument->StartDocumentLoad("import", mChannel, loadGroup,
nullptr, getter_AddRefs(listener),
true);
NS_ENSURE_SUCCESS(rv, rv);
// Let's start parser.
mParserStreamListener = listener;
rv = listener->OnStartRequest(aRequest, aContext);
NS_ENSURE_SUCCESS(rv, rv);
ae.Pass();
return NS_OK;
}
NS_IMPL_CYCLE_COLLECTION(ImportManager,
mImports)
NS_INTERFACE_MAP_BEGIN(ImportManager)
NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ImportManager)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ImportManager)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportManager)
already_AddRefed<ImportLoader>
ImportManager::Get(nsIURI* aURI, nsINode* aNode, nsIDocument* aOrigDocument)
{
// Check if we have a loader for that URI, if not create one,
// and start it up.
nsRefPtr<ImportLoader> loader;
mImports.Get(aURI, getter_AddRefs(loader));
if (!loader) {
loader = new ImportLoader(aURI, aOrigDocument);
mImports.Put(aURI, loader);
loader->Open();
}
loader->AddLinkElement(aNode);
MOZ_ASSERT(loader);
return loader.forget();
}
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,148 @@
/* -*- 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/. */
/*
*
* For each import tree there is one master document (the root) and one
* import manager. The import manager is a map of URI ImportLoader pairs.
* An ImportLoader is responsible for loading an import document from a
* given location, and sending out load or error events to all the link
* nodes that refer to it when it's done. For loading it opens up a
* channel, using the same CSP as the master document. It then creates a
* blank document, and starts parsing the data from the channel. When
* there is no more data on the channel we wait for the DOMContentLoaded
* event from the parsed document. For the duration of the loading
* process the scripts on the parent documents are blocked. When an error
* occurs, or the DOMContentLoaded event is received, the scripts on the
* parent document are unblocked and we emit the corresponding event on
* all the referrer link nodes. If a new link node is added to one of the
* DOM trees in the import tree that refers to an import that was already
* loaded, the already existing ImportLoader is being used (without
* loading the referred import document twice) and if necessary the
* load/error is emitted on it immediately.
*
* Ownership model:
*
* ImportDocument ----------------------------
* ^ |
* | v
* MasterDocument <- ImportManager <-ImportLoader
* ^ ^
* | |
* LinkElement <-----------------------------
*
*/
#ifndef mozilla_dom_ImportManager_h__
#define mozilla_dom_ImportManager_h__
#include "nsCOMArray.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMEventListener.h"
#include "nsIStreamListener.h"
#include "nsIWeakReferenceUtils.h"
#include "nsRefPtrHashtable.h"
#include "nsURIHashKey.h"
class nsIDocument;
class nsIChannel;
class nsINode;
class AutoError;
namespace mozilla {
namespace dom {
class ImportManager;
class ImportLoader MOZ_FINAL : public nsIStreamListener
, public nsIDOMEventListener
{
friend class ::AutoError;
friend class ImportManager;
public:
ImportLoader(nsIURI* aURI, nsIDocument* aOriginDocument);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ImportLoader, nsIStreamListener)
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
// We need to listen to DOMContentLoaded event to know when the document
// is fully leaded.
NS_IMETHOD HandleEvent(nsIDOMEvent *aEvent) MOZ_OVERRIDE;
// Validation then opening and starting up the channel.
void Open();
void AddLinkElement(nsINode* aNode);
void RemoveLinkElement(nsINode* aNode);
bool IsReady() { return mReady; }
bool IsStopped() { return mStopped; }
bool IsBlocking() { return mBlockingScripts; }
already_AddRefed<nsIDocument> GetImport()
{
return mReady ? nsCOMPtr<nsIDocument>(mDocument).forget() : nullptr;
}
private:
// If a new referrer LinkElement was added, let's
// see if we are already finished and if so fire
// the right event.
void DispatchEventIfFinished(nsINode* aNode);
// Dispatch event for a single referrer LinkElement.
void DispatchErrorEvent(nsINode* aNode);
void DispatchLoadEvent(nsINode* aNode);
// Must be called when an error has occured during load.
void Error();
// Must be called when the import document has been loaded successfully.
void Done();
// When the reading from the channel and the parsing
// of the document is done, we can release the resources
// that we don't need any longer to hold on.
void ReleaseResources();
// While the document is being loaded we must block scripts
// on the import parent document.
void BlockScripts();
void UnblockScripts();
nsCOMPtr<nsIDocument> mDocument;
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsIStreamListener> mParserStreamListener;
nsCOMPtr<nsIDocument> mImportParent;
// List of the LinkElements that are referring to this import
// we need to keep track of them so we can fire event on them.
nsCOMArray<nsINode> mLinks;
bool mReady;
bool mStopped;
bool mBlockingScripts;
};
class ImportManager MOZ_FINAL : public nsISupports
{
typedef nsRefPtrHashtable<nsURIHashKey, ImportLoader> ImportMap;
public:
ImportManager() {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(ImportManager)
already_AddRefed<ImportLoader> Get(nsIURI* aURI, nsINode* aNode,
nsIDocument* aOriginDocument);
private:
ImportMap mImports;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_ImportManager_h__

View File

@ -70,6 +70,7 @@ EXPORTS.mozilla.dom += [
'DOMRect.h',
'DOMStringList.h',
'EventSource.h',
'ImportManager.h',
'Link.h',
'NodeIterator.h',
'ShadowRoot.h',
@ -95,6 +96,7 @@ UNIFIED_SOURCES += [
'EventSource.cpp',
'FileIOObject.cpp',
'FragmentOrElement.cpp',
'ImportManager.cpp',
'Link.cpp',
'NodeIterator.cpp',
'nsAtomListUtils.cpp',

View File

@ -1942,6 +1942,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImportManager)
tmp->mRadioGroups.EnumerateRead(RadioGroupsTraverser, &cb);
@ -2037,6 +2039,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRegistry)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMasterDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportManager)
tmp->mParentDocument = nullptr;
@ -4617,6 +4621,10 @@ nsDocument::GetWindowInternal() const
if (win) {
// mScriptGlobalObject is always the inner window, let's get the outer.
win = win->GetOuterWindow();
} else if (mMasterDocument) {
// For script execution in the imported document we need the window of
// the master document.
win = mMasterDocument->GetWindow();
}
}
@ -7959,6 +7967,9 @@ nsDocument::IsScriptEnabled()
NS_ENSURE_TRUE(sm, false);
nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(GetInnerWindow());
if (!globalObject && mMasterDocument) {
globalObject = do_QueryInterface(mMasterDocument->GetInnerWindow());
}
NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), false);
return sm->ScriptAllowed(globalObject->GetGlobalJSObject());

View File

@ -71,6 +71,7 @@
#include "mozilla/Attributes.h"
#include "nsIDOMXPathEvaluator.h"
#include "jsfriendapi.h"
#include "ImportManager.h"
#define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
#define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
@ -1246,6 +1247,41 @@ public:
mozilla::ErrorResult& rv) MOZ_OVERRIDE;
virtual void UseRegistryFromDocument(nsIDocument* aDocument) MOZ_OVERRIDE;
virtual already_AddRefed<nsIDocument> MasterDocument()
{
return mMasterDocument ? (nsCOMPtr<nsIDocument>(mMasterDocument)).forget()
: (nsCOMPtr<nsIDocument>(this)).forget();
}
virtual void SetMasterDocument(nsIDocument* master)
{
mMasterDocument = master;
}
virtual bool IsMasterDocument()
{
return !mMasterDocument;
}
virtual already_AddRefed<mozilla::dom::ImportManager> ImportManager()
{
if (mImportManager) {
MOZ_ASSERT(!mMasterDocument, "Only the master document has ImportManager set");
return nsRefPtr<mozilla::dom::ImportManager>(mImportManager).forget();
}
if (mMasterDocument) {
return mMasterDocument->ImportManager();
}
// ImportManager is created lazily.
// If the manager is not yet set it has to be the
// master document and this is the first import in it.
// Let's create a new manager.
mImportManager = new mozilla::dom::ImportManager();
return nsRefPtr<mozilla::dom::ImportManager>(mImportManager).forget();
}
virtual void UnblockDOMContentLoaded() MOZ_OVERRIDE;
protected:
@ -1663,6 +1699,9 @@ private:
CSPErrorQueue mCSPWebConsoleErrorQueue;
nsCOMPtr<nsIDocument> mMasterDocument;
nsRefPtr<mozilla::dom::ImportManager> mImportManager;
#ifdef DEBUG
protected:
bool mWillReparent;

View File

@ -975,7 +975,8 @@ nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadT
// The window may have gone away by this point, in which case there's no point
// in trying to run the script.
nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
nsPIDOMWindow *pwin = master->GetInnerWindow();
bool runScript = !!pwin;
if (runScript) {
nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
@ -985,7 +986,7 @@ nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadT
}
// Inner window could have gone away after firing beforescriptexecute
pwin = mDocument->GetInnerWindow();
pwin = master->GetInnerWindow();
if (!pwin) {
runScript = false;
}
@ -1047,7 +1048,8 @@ nsScriptLoader::FireScriptEvaluated(nsresult aResult,
already_AddRefed<nsIScriptGlobalObject>
nsScriptLoader::GetScriptGlobalObject()
{
nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
nsPIDOMWindow *pwin = master->GetInnerWindow();
if (!pwin) {
return nullptr;
}
@ -1147,18 +1149,27 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest,
bool oldProcessingScriptTag = context->GetProcessingScriptTag();
context->SetProcessingScriptTag(true);
nsresult rv;
{
// Update our current script.
AutoCurrentScriptUpdater scriptUpdater(this, aRequest->mElement);
Maybe<AutoCurrentScriptUpdater> masterScriptUpdater;
nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
if (master != mDocument) {
// If this script belongs to an import document, it will be
// executed in the context of the master document. During the
// execution currentScript of the master should refer to this
// script. So let's update the mCurrentScript of the ScriptLoader
// of the master document too.
masterScriptUpdater.construct(master->ScriptLoader(),
aRequest->mElement);
}
// Update our current script.
nsCOMPtr<nsIScriptElement> oldCurrent = mCurrentScript;
mCurrentScript = aRequest->mElement;
JS::CompileOptions options(entryScript.cx());
FillCompileOptionsForRequest(aRequest, global, &options);
nsresult rv = nsJSUtils::EvaluateString(entryScript.cx(), aSrcBuf, global, options,
aOffThreadToken);
// Put the old script back in case it wants to do anything else.
mCurrentScript = oldCurrent;
JS::CompileOptions options(entryScript.cx());
FillCompileOptionsForRequest(aRequest, global, &options);
rv = nsJSUtils::EvaluateString(entryScript.cx(), aSrcBuf, global, options,
aOffThreadToken);
}
context->SetProcessingScriptTag(oldProcessingScriptTag);
return rv;

View File

@ -31,7 +31,28 @@ namespace JS {
class nsScriptLoader : public nsIStreamLoaderObserver
{
class MOZ_STACK_CLASS AutoCurrentScriptUpdater
{
public:
AutoCurrentScriptUpdater(nsScriptLoader* aScriptLoader,
nsIScriptElement* aCurrentScript)
: mOldScript(aScriptLoader->mCurrentScript)
, mScriptLoader(aScriptLoader)
{
mScriptLoader->mCurrentScript = aCurrentScript;
}
~AutoCurrentScriptUpdater()
{
mScriptLoader->mCurrentScript.swap(mOldScript);
}
private:
nsCOMPtr<nsIScriptElement> mOldScript;
nsScriptLoader* mScriptLoader;
};
friend class nsScriptRequestProcessor;
friend class AutoCurrentScriptUpdater;
public:
nsScriptLoader(nsIDocument* aDocument);
virtual ~nsScriptLoader();

View File

@ -132,6 +132,8 @@ static uint32_t ToLinkMask(const nsAString& aLink)
return nsStyleLinkElement::eNEXT;
else if (aLink.EqualsLiteral("alternate"))
return nsStyleLinkElement::eALTERNATE;
else if (aLink.EqualsLiteral("import"))
return nsStyleLinkElement::eHTMLIMPORT;
else
return 0;
}

View File

@ -58,6 +58,7 @@ public:
eSTYLESHEET = 0x00000004,
eNEXT = 0x00000008,
eALTERNATE = 0x00000010,
eHTMLIMPORT = 0x00000020
};
// The return value is a bitwise or of 0 or more RelValues

View File

@ -11,6 +11,7 @@
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStates.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/HTMLLinkElementBinding.h"
#include "nsContentUtils.h"
#include "nsGenericHTMLElement.h"
@ -19,6 +20,7 @@
#include "nsIDocument.h"
#include "nsIDOMEvent.h"
#include "nsIDOMStyleSheet.h"
#include "nsINode.h"
#include "nsIStyleSheet.h"
#include "nsIStyleSheetLinkingElement.h"
#include "nsIURL.h"
@ -50,6 +52,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement,
tmp->nsStyleLinkElement::Traverse(cb);
tmp->Link::Traverse(cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImportLoader)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLLinkElement,
@ -57,6 +60,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLLinkElement,
tmp->nsStyleLinkElement::Unlink();
tmp->Link::Unlink();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportLoader)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ADDREF_INHERITED(HTMLLinkElement, Element)
@ -148,6 +152,9 @@ HTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, update));
void (HTMLLinkElement::*updateImport)() = &HTMLLinkElement::UpdateImport;
nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, updateImport));
CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMLinkAdded"));
return rv;
@ -188,6 +195,7 @@ HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
UpdateStyleSheetInternal(oldDoc, oldShadowRoot);
UpdateImport();
}
bool
@ -241,6 +249,63 @@ HTMLLinkElement::CreateAndDispatchEvent(nsIDocument* aDoc,
asyncDispatcher->PostDOMEvent();
}
void
HTMLLinkElement::UpdateImport()
{
if (!Preferences::GetBool("dom.webcomponents.enabled")) {
// For now imports are hidden behind a pref...
return;
}
// 1. link node should be attached to the document.
nsCOMPtr<nsIDocument> doc = GetCurrentDoc();
if (!doc) {
// We might have been just removed from the document, so
// let's remove ourself from the list of link nodes of
// the import and reset mImportLoader.
if (mImportLoader) {
mImportLoader->RemoveLinkElement(this);
mImportLoader = nullptr;
}
return;
}
// Until the script execution order is not sorted out for nested cases
// let's not allow them.
if (!doc->IsMasterDocument()) {
nsContentUtils::LogSimpleConsoleError(
NS_LITERAL_STRING("Nested imports are not supported yet"),
"Imports");
return;
}
// 2. rel type should be import.
nsAutoString rel;
GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel);
uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel);
if (!(linkTypes & eHTMLIMPORT)) {
mImportLoader = nullptr;
return;
}
nsCOMPtr<nsIURI> uri = GetHrefURI();
if (!uri) {
mImportLoader = nullptr;
return;
}
nsRefPtr<ImportManager> manager = doc->ImportManager();
MOZ_ASSERT(manager, "ImportManager should be created lazily when needed");
{
// The load even might fire sooner than we could set mImportLoader so
// we must use async event and a scriptBlocker here.
nsAutoScriptBlocker scriptBlocker;
// CORS check will happen at the start of the load.
mImportLoader = manager->Get(uri, this, doc);
}
}
nsresult
HTMLLinkElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
nsIAtom* aPrefix, const nsAString& aValue,
@ -265,9 +330,17 @@ HTMLLinkElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
aName == nsGkAtoms::media ||
aName == nsGkAtoms::type)) {
bool dropSheet = false;
if (aName == nsGkAtoms::rel && GetSheet()) {
if (aName == nsGkAtoms::rel) {
uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(aValue);
dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
if (GetSheet()) {
dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
} else if (linkTypes & eHTMLIMPORT) {
UpdateImport();
}
}
if (aName == nsGkAtoms::href) {
UpdateImport();
}
UpdateStyleSheetInternal(nullptr, nullptr,
@ -288,13 +361,18 @@ HTMLLinkElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
aNotify);
// Since removing href or rel makes us no longer link to a
// stylesheet, force updates for those too.
if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None &&
(aAttribute == nsGkAtoms::href ||
aAttribute == nsGkAtoms::rel ||
aAttribute == nsGkAtoms::title ||
aAttribute == nsGkAtoms::media ||
aAttribute == nsGkAtoms::type)) {
UpdateStyleSheetInternal(nullptr, nullptr, true);
if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::href ||
aAttribute == nsGkAtoms::rel ||
aAttribute == nsGkAtoms::title ||
aAttribute == nsGkAtoms::media ||
aAttribute == nsGkAtoms::type) {
UpdateStyleSheetInternal(nullptr, nullptr, true);
}
if (aAttribute == nsGkAtoms::href ||
aAttribute == nsGkAtoms::rel) {
UpdateImport();
}
}
// The ordering of the parent class's UnsetAttr call and Link::ResetLinkState
@ -444,5 +522,11 @@ HTMLLinkElement::WrapNode(JSContext* aCx)
return HTMLLinkElementBinding::Wrap(aCx, this);
}
already_AddRefed<nsIDocument>
HTMLLinkElement::GetImport()
{
return mImportLoader ? mImportLoader->GetImport() : nullptr;
}
} // namespace dom
} // namespace mozilla

View File

@ -8,6 +8,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/dom/Link.h"
#include "ImportManager.h"
#include "nsGenericHTMLElement.h"
#include "nsIDOMHTMLLinkElement.h"
#include "nsStyleLinkElement.h"
@ -42,6 +43,8 @@ public:
void LinkAdded();
void LinkRemoved();
void UpdateImport();
// nsIDOMEventTarget
virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) MOZ_OVERRIDE;
virtual nsresult PostHandleEvent(
@ -134,6 +137,12 @@ public:
SetHTMLAttr(nsGkAtoms::target, aTarget, aRv);
}
already_AddRefed<nsIDocument> GetImport();
already_AddRefed<ImportLoader> GetImportLoader()
{
return nsRefPtr<ImportLoader>(mImportLoader).forget();
}
protected:
// nsStyleLinkElement
virtual already_AddRefed<nsIURI> GetStyleSheetURL(bool* aIsInline) MOZ_OVERRIDE;
@ -148,6 +157,8 @@ protected:
virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;
virtual void SetItemValueText(const nsAString& text) MOZ_OVERRIDE;
nsRefPtr<nsDOMTokenList > mRelList;
private:
nsRefPtr<ImportLoader> mImportLoader;
};
} // namespace dom

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
</head>
<body>
<div id="foo">bar</div>
<script>
counter++;
var importDone = true;
is(document.currentScript.ownerDocument.getElementById("foo").textContent, "bar",
"currentScript.ownerDocument works in imported document,");
try{
document.currentScript.ownerDocument.open();
document.currentScript.ownerDocument.write("<h1>This should not show up!</h1>");
document.currentScript.ownerDocument.close();
ok(false, "document.write should have thrown (import)")
} catch (e) {
ok(true, "document.write has thrown (import)")
}
</script>
</body>
</html>

View File

@ -144,6 +144,7 @@ support-files =
file_iframe_sandbox_window_top_navigation_pass.html
file_iframe_sandbox_window_top_navigation_fail.html
file_iframe_sandbox_worker.js
file_imports_basics.html
file_srcdoc-2.html
file_srcdoc.html
form_submit_server.sjs
@ -448,6 +449,7 @@ skip-if = buildapp == 'b2g' || e10s # b2g(multiple concurrent window.open()s fai
[test_iframe_sandbox_workers.html]
[test_img_attributes_reflection.html]
[test_imageSrcSet.html]
[test_imports_basics.html]
[test_li_attributes_reflection.html]
[test_link_attributes_reflection.html]
[test_link_sizes.html]

View File

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=877072
-->
<head>
<title>Test for Bug 877072</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877072">Mozilla Bug 877072</a>
<script type="text/javascript">
SimpleTest.waitForExplicitFinish();
var loadEventFired = false;
var errorEventFired = false;
var counter = 0;
function loaded() {
loadEventFired = true;
}
function failed() {
errorEventFired = true;
}
</script>
<link rel="import" href="file_imports_basics.html" id="import1" onload="loaded()" onerror="failed()"></link>
<script type="text/javascript">
ok(importDone, "Script of the imported document runned before this script");
ok(loadEventFired, "Load eventhandler works");
ok(!errorEventFired, "There were no error event fired");
var import1 = document.getElementById("import1").import;
is(import1.getElementById("foo").textContent, "bar",
"import property of link import works");
try{
import1.open();
import1.write("<h1>This should not show up!</h1>");
import1.close();
} catch (e) {
ok(true, "import.open/write should throw");
}
// Let's add dynamically a new link import with the same URI
var link = document.createElement("link");
link.setAttribute("id", "inserted");
link.setAttribute("rel", "import");
link.setAttribute("href", "file_imports_basics.html");
function loaded2() {
ok(true, "Load event is fired for link import that refers to an already loaded import");
is(counter, 1, "Adding another link referring to the same import, does not load it twice");
is(document.getElementById("inserted").import.getElementById("foo").textContent, "bar",
"import property of dynamic link import works");
SimpleTest.finish();
};
link.setAttribute("onload", "loaded2()");
function failed2() {
ok(false, "You should not reach this code");
SimpleTest.finish();
};
link.setAttribute("onerror", "failed2()");
document.body.appendChild(link);
</script>
</body>
</html>

View File

@ -547,7 +547,8 @@ nsHTMLDocument::StartDocumentLoad(const char* aCommand,
!strcmp(aCommand, "external-resource");
bool viewSource = !strcmp(aCommand, "view-source");
bool asData = !strcmp(aCommand, kLoadAsData);
if(!(view || viewSource || asData)) {
bool import = !strcmp(aCommand, "import");
if (!(view || viewSource || asData || import)) {
MOZ_ASSERT(false, "Bad parser command");
return NS_ERROR_INVALID_ARG;
}
@ -1370,7 +1371,7 @@ nsHTMLDocument::Open(JSContext* cx,
{
NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
"XOW should have caught this!");
if (!IsHTML() || mDisableDocWrite) {
if (!IsHTML() || mDisableDocWrite || !IsMasterDocument()) {
// No calling document.open() on XHTML
rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
@ -1772,7 +1773,7 @@ nsHTMLDocument::WriteCommon(JSContext *cx,
(mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
NS_ENSURE_STATE(!mTooDeepWriteRecursion);
if (!IsHTML() || mDisableDocWrite) {
if (!IsHTML() || mDisableDocWrite || !IsMasterDocument()) {
// No calling document.write*() on XHTML!
return NS_ERROR_DOM_INVALID_STATE_ERR;

View File

@ -31,6 +31,7 @@ interface HTMLLinkElement : HTMLElement {
[PutForwards=value] readonly attribute DOMSettableTokenList sizes;
};
HTMLLinkElement implements LinkStyle;
HTMLLinkElement implements LinkImport;
// http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis
partial interface HTMLLinkElement {

View File

@ -0,0 +1,13 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://www.w3.org/TR/2013/WD-html-imports-20130514/
*/
[NoInterfaceObject]
interface LinkImport {
readonly attribute Document? import;
};

View File

@ -224,6 +224,7 @@ WEBIDL_FILES = [
'KeyboardEvent.webidl',
'KeyEvent.webidl',
'LegacyQueryInterface.webidl',
'LinkImport.webidl',
'LinkStyle.webidl',
'LocalMediaStream.webidl',
'Location.webidl',

View File

@ -79,6 +79,7 @@ nsHtml5Parser::SetCommand(const char* aCommand)
NS_ASSERTION(!strcmp(aCommand, "view") ||
!strcmp(aCommand, "view-source") ||
!strcmp(aCommand, "external-resource") ||
!strcmp(aCommand, "import") ||
!strcmp(aCommand, kLoadAsData),
"Unsupported parser command");
}
@ -577,7 +578,8 @@ nsHtml5Parser::MarkAsNotScriptCreated(const char* aCommand)
#ifdef DEBUG
else {
NS_ASSERTION(!nsCRT::strcmp(aCommand, "view") ||
!nsCRT::strcmp(aCommand, "external-resource"),
!nsCRT::strcmp(aCommand, "external-resource") ||
!nsCRT::strcmp(aCommand, "import"),
"Unsupported parser command!");
}
#endif