Bug 877072 - HTML Imports part1. r=mrbkap
authorGabor Krizsanits <gkrizsanits@mozilla.com>
Wed, 21 May 2014 19:08:12 +0200
changeset 184226 25763b7fe938e9b2ce486426a2e17d7142bdd69f
parent 184225 bfc1b283e10db22b7c4ffee0ea0c6ad35d722a52
child 184227 6534bace55111b7287ba0233a4f6e9335b7d661d
push id26814
push userryanvm@gmail.com
push dateWed, 21 May 2014 19:50:12 +0000
treeherdermozilla-central@7aee2fa0f655 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs877072
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 877072 - HTML Imports part1. r=mrbkap
content/base/public/nsIDocument.h
content/base/public/nsIScriptElement.h
content/base/src/ImportManager.cpp
content/base/src/ImportManager.h
content/base/src/moz.build
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsScriptLoader.cpp
content/base/src/nsScriptLoader.h
content/base/src/nsStyleLinkElement.cpp
content/base/src/nsStyleLinkElement.h
content/html/content/src/HTMLLinkElement.cpp
content/html/content/src/HTMLLinkElement.h
content/html/content/test/file_imports_basics.html
content/html/content/test/mochitest.ini
content/html/content/test/test_imports_basics.html
content/html/document/src/nsHTMLDocument.cpp
dom/webidl/HTMLLinkElement.webidl
dom/webidl/LinkImport.webidl
dom/webidl/moz.build
parser/html/nsHtml5Parser.cpp
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -101,16 +101,17 @@ class DocumentFragment;
 class DocumentType;
 class DOMImplementation;
 class DOMStringList;
 class Element;
 struct ElementRegistrationOptions;
 class Event;
 class EventTarget;
 class FrameRequestCallback;
+class ImportManager;
 class OverfillCallback;
 class HTMLBodyElement;
 struct LifecycleCallbackArgs;
 class Link;
 class GlobalObject;
 class NodeFilter;
 class NodeIterator;
 class ProcessingInstruction;
@@ -124,18 +125,18 @@ template<typename> class OwningNonNull;
 template<typename> class Sequence;
 
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
 } // namespace dom
 } // 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)
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
@@ -2250,16 +2251,23 @@ public:
   // ParentNode
   nsIHTMLCollection* Children();
   uint32_t ChildElementCount();
 
   virtual nsHTMLDocument* AsHTMLDocument() { return nullptr; }
 
   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;
 
 protected:
   ~nsIDocument();
   nsPropertyTable* GetExtraPropertyTable(uint16_t aCategory);
 
--- a/content/base/public/nsIScriptElement.h
+++ b/content/base/public/nsIScriptElement.h
@@ -12,18 +12,18 @@
 #include "nsIScriptLoaderObserver.h"
 #include "nsWeakPtr.h"
 #include "nsIParser.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsIDOMHTMLScriptElement.h"
 #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
  */
 class nsIScriptElement : public nsIScriptLoaderObserver {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTELEMENT_IID)
 
new file mode 100644
--- /dev/null
+++ b/content/base/src/ImportManager.cpp
@@ -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
new file mode 100644
--- /dev/null
+++ b/content/base/src/ImportManager.h
@@ -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__
--- a/content/base/src/moz.build
+++ b/content/base/src/moz.build
@@ -65,16 +65,17 @@ EXPORTS.mozilla.dom += [
     'DocumentType.h',
     'DOMImplementation.h',
     'DOMParser.h',
     'DOMPoint.h',
     'DOMQuad.h',
     'DOMRect.h',
     'DOMStringList.h',
     'EventSource.h',
+    'ImportManager.h',
     'Link.h',
     'NodeIterator.h',
     'ShadowRoot.h',
     'StyleSheetList.h',
     'Text.h',
     'TreeWalker.h',
 ]
 
@@ -90,16 +91,17 @@ UNIFIED_SOURCES += [
     'DOMPoint.cpp',
     'DOMQuad.cpp',
     'DOMRect.cpp',
     'DOMStringList.cpp',
     'Element.cpp',
     'EventSource.cpp',
     'FileIOObject.cpp',
     'FragmentOrElement.cpp',
+    'ImportManager.cpp',
     'Link.cpp',
     'NodeIterator.cpp',
     'nsAtomListUtils.cpp',
     'nsAttrAndChildArray.cpp',
     'nsAttrValue.cpp',
     'nsAttrValueOrString.cpp',
     'nsCCUncollectableMarker.cpp',
     'nsChannelPolicy.cpp',
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1937,16 +1937,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
 
   // Traverse all nsDocument nsCOMPtrs.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
   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);
 
   // The boxobject for an element will only exist as long as it's in the
   // document, so we'll traverse the table here instead of from the element.
   if (tmp->mBoxObjectTable) {
     tmp->mBoxObjectTable->EnumerateRead(BoxObjectTraverser, &cb);
   }
@@ -2032,16 +2034,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mUndoManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnimationTimeline)
   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;
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
 
 
   if (tmp->mBoxObjectTable) {
    tmp->mBoxObjectTable->EnumerateRead(ClearAllBoxObjects, nullptr);
@@ -4612,16 +4616,20 @@ nsDocument::GetWindowInternal() const
       // The docshell returns the outer window we are done.
       win = do_GetInterface(requestor);
     }
   } else {
     win = do_QueryInterface(mScriptGlobalObject);
     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();
     }
   }
 
   return win;
 }
 
 nsScriptLoader*
 nsDocument::ScriptLoader()
@@ -7954,16 +7962,19 @@ nsDocument::IsScriptEnabled()
   if (mSandboxFlags & SANDBOXED_SCRIPTS) {
     return false;
   }
 
   nsCOMPtr<nsIScriptSecurityManager> sm(do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID));
   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());
 }
 
 nsRadioGroupStruct*
 nsDocument::GetRadioGroupInternal(const nsAString& aName) const
 {
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -66,16 +66,17 @@
 #include "mozilla/dom/DOMImplementation.h"
 #include "mozilla/dom/StyleSheetList.h"
 #include "nsIDOMTouchEvent.h"
 #include "nsDataHashtable.h"
 #include "mozilla/TimeStamp.h"
 #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)
 #define XML_DECLARATION_BITS_STANDALONE_EXISTS    (1 << 2)
 #define XML_DECLARATION_BITS_STANDALONE_YES       (1 << 3)
 
 
 class nsDOMStyleSheetSetList;
@@ -1241,16 +1242,51 @@ public:
                                                   const nsAString& aTypeExtension,
                                                   mozilla::ErrorResult& rv) MOZ_OVERRIDE;
   virtual already_AddRefed<Element> CreateElementNS(const nsAString& aNamespaceURI,
                                                     const nsAString& aQualifiedName,
                                                     const nsAString& aTypeExtension,
                                                     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:
   friend class nsNodeUtils;
   friend class nsDocumentOnStack;
 
   void IncreaseStackRefCnt()
   {
@@ -1658,16 +1694,19 @@ private:
   bool mAutoSize, mAllowZoom, mAllowDoubleTapZoom, mValidScaleFloat, mValidMaxScale, mScaleStrEmpty, mWidthStrEmpty;
   mozilla::CSSIntSize mViewportSize;
 
   nsrefcnt mStackRefCnt;
   bool mNeedsReleaseAfterStackRefCntRelease;
 
   CSPErrorQueue mCSPWebConsoleErrorQueue;
 
+  nsCOMPtr<nsIDocument> mMasterDocument;
+  nsRefPtr<mozilla::dom::ImportManager> mImportManager;
+
 #ifdef DEBUG
 protected:
   bool mWillReparent;
 #endif
 };
 
 class nsDocumentOnStack
 {
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -970,27 +970,28 @@ nsScriptLoader::ProcessRequest(nsScriptL
     oldParserInsertedScript = mCurrentParserInsertedScript;
     mCurrentParserInsertedScript = aRequest->mElement;
   }
 
   FireScriptAvailable(NS_OK, aRequest);
 
   // 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(),
                                          scriptElem,
                                          NS_LITERAL_STRING("beforescriptexecute"),
                                          true, true, &runScript);
   }
 
   // Inner window could have gone away after firing beforescriptexecute
-  pwin = mDocument->GetInnerWindow();
+  pwin = master->GetInnerWindow();
   if (!pwin) {
     runScript = false;
   }
 
   nsresult rv = NS_OK;
   if (runScript) {
     if (doc) {
       doc->BeginEvaluatingExternalScript();
@@ -1042,17 +1043,18 @@ nsScriptLoader::FireScriptEvaluated(nsre
   }
 
   aRequest->FireScriptEvaluated(aResult);
 }
 
 already_AddRefed<nsIScriptGlobalObject>
 nsScriptLoader::GetScriptGlobalObject()
 {
-  nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
+  nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
+  nsPIDOMWindow *pwin = master->GetInnerWindow();
   if (!pwin) {
     return nullptr;
   }
 
   nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
   NS_ASSERTION(globalObject, "windows must be global objects");
 
   // and make sure we are setup for this type of script.
@@ -1142,28 +1144,37 @@ nsScriptLoader::EvaluateScript(nsScriptL
   // New script entry point required, due to the "Create a script" sub-step of
   // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
   AutoEntryScript entryScript(globalObject, true, context->GetNativeContext());
   JS::Rooted<JSObject*> global(entryScript.cx(),
                                globalObject->GetGlobalJSObject());
 
   bool oldProcessingScriptTag = context->GetProcessingScriptTag();
   context->SetProcessingScriptTag(true);
-
-  // Update our current script.
-  nsCOMPtr<nsIScriptElement> oldCurrent = mCurrentScript;
-  mCurrentScript = aRequest->mElement;
+  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);
+    }
 
-  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;
 }
 
 void
 nsScriptLoader::ProcessPendingRequestsAsync()
 {
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -26,17 +26,38 @@ namespace JS {
 }
 
 //////////////////////////////////////////////////////////////
 // Script loader implementation
 //////////////////////////////////////////////////////////////
 
 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();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSISTREAMLOADEROBSERVER
 
   /**
--- a/content/base/src/nsStyleLinkElement.cpp
+++ b/content/base/src/nsStyleLinkElement.cpp
@@ -127,16 +127,18 @@ static uint32_t ToLinkMask(const nsAStri
   else if (aLink.EqualsLiteral("dns-prefetch"))
     return nsStyleLinkElement::eDNS_PREFETCH;
   else if (aLink.EqualsLiteral("stylesheet"))
     return nsStyleLinkElement::eSTYLESHEET;
   else if (aLink.EqualsLiteral("next"))
     return nsStyleLinkElement::eNEXT;
   else if (aLink.EqualsLiteral("alternate"))
     return nsStyleLinkElement::eALTERNATE;
+  else if (aLink.EqualsLiteral("import"))
+    return nsStyleLinkElement::eHTMLIMPORT;
   else 
     return 0;
 }
 
 uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes)
 {
   uint32_t linkMask = 0;
   nsAString::const_iterator start, done;
--- a/content/base/src/nsStyleLinkElement.h
+++ b/content/base/src/nsStyleLinkElement.h
@@ -53,16 +53,17 @@ public:
   virtual void SetLineNumber(uint32_t aLineNumber) MOZ_OVERRIDE;
 
   enum RelValue {
     ePREFETCH =     0x00000001,
     eDNS_PREFETCH = 0x00000002,
     eSTYLESHEET =   0x00000004,
     eNEXT =         0x00000008,
     eALTERNATE =    0x00000010,
+    eHTMLIMPORT =   0x00000020
   };
 
   // The return value is a bitwise or of 0 or more RelValues
   static uint32_t ParseLinkTypes(const nsAString& aTypes);
 
   void UpdateStyleSheetInternal()
   {
     UpdateStyleSheetInternal(nullptr, nullptr);
--- a/content/html/content/src/HTMLLinkElement.cpp
+++ b/content/html/content/src/HTMLLinkElement.cpp
@@ -6,24 +6,26 @@
 
 #include "mozilla/dom/HTMLLinkElement.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Attributes.h"
 #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"
 #include "nsGkAtoms.h"
 #include "nsDOMTokenList.h"
 #include "nsIDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMStyleSheet.h"
+#include "nsINode.h"
 #include "nsIStyleSheet.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsIURL.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsReadableUtils.h"
 #include "nsStyleConsts.h"
 #include "nsUnicharUtils.h"
@@ -45,23 +47,25 @@ HTMLLinkElement::~HTMLLinkElement()
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLLinkElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement,
                                                   nsGenericHTMLElement)
   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,
                                                 nsGenericHTMLElement)
   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)
 NS_IMPL_RELEASE_INHERITED(HTMLLinkElement, Element)
 
 
 // QueryInterface implementation for HTMLLinkElement
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLLinkElement)
@@ -143,16 +147,19 @@ HTMLLinkElement::BindToTree(nsIDocument*
   // Link must be inert in ShadowRoot.
   if (aDocument && !GetContainingShadow()) {
     aDocument->RegisterPendingLinkUpdate(this);
   }
 
   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;
 }
 
 void
 HTMLLinkElement::LinkAdded()
 {
@@ -183,16 +190,17 @@ HTMLLinkElement::UnbindFromTree(bool aDe
 
   if (oldDoc && !oldShadowRoot) {
     oldDoc->UnregisterPendingLinkUpdate(this);
   }
   CreateAndDispatchEvent(oldDoc, NS_LITERAL_STRING("DOMLinkRemoved"));
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   UpdateStyleSheetInternal(oldDoc, oldShadowRoot);
+  UpdateImport();
 }
 
 bool
 HTMLLinkElement::ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult)
 {
@@ -236,16 +244,73 @@ HTMLLinkElement::CreateAndDispatchEvent(
 
   nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
     new AsyncEventDispatcher(this, aEventName, true, true);
   // Always run async in order to avoid running script when the content
   // sink isn't expecting it.
   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,
                          bool aNotify)
 {
   nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
                                               aValue, aNotify);
 
@@ -260,19 +325,27 @@ HTMLLinkElement::SetAttr(int32_t aNameSp
 
   if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None &&
       (aName == nsGkAtoms::href ||
        aName == nsGkAtoms::rel ||
        aName == nsGkAtoms::title ||
        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,
                              dropSheet ||
                              (aName == nsGkAtoms::title ||
                               aName == nsGkAtoms::media ||
                               aName == nsGkAtoms::type));
   }
@@ -283,23 +356,28 @@ HTMLLinkElement::SetAttr(int32_t aNameSp
 nsresult
 HTMLLinkElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                            bool aNotify)
 {
   nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, 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
   // is important here!  The attribute is not unset until UnsetAttr returns, and
   // we will need the updated attribute value because notifying the document
   // that content states have changed will call IntrinsicState, which will try
   // to get updated information about the visitedness from Link.
   if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
@@ -439,10 +517,16 @@ HTMLLinkElement::SizeOfExcludingThis(moz
 }
 
 JSObject*
 HTMLLinkElement::WrapNode(JSContext* aCx)
 {
   return HTMLLinkElementBinding::Wrap(aCx, this);
 }
 
+already_AddRefed<nsIDocument>
+HTMLLinkElement::GetImport()
+{
+  return mImportLoader ? mImportLoader->GetImport() : nullptr;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/content/html/content/src/HTMLLinkElement.h
+++ b/content/html/content/src/HTMLLinkElement.h
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_HTMLLinkElement_h
 #define mozilla_dom_HTMLLinkElement_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Link.h"
+#include "ImportManager.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMHTMLLinkElement.h"
 #include "nsStyleLinkElement.h"
 
 namespace mozilla {
 class EventChainPostVisitor;
 class EventChainPreVisitor;
 namespace dom {
@@ -37,16 +38,18 @@ public:
   NS_DECL_NSIDOMHTMLLINKELEMENT
 
   // DOM memory reporter participant
   NS_DECL_SIZEOF_EXCLUDING_THIS
 
   void LinkAdded();
   void LinkRemoved();
 
+  void UpdateImport();
+
   // nsIDOMEventTarget
   virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) MOZ_OVERRIDE;
   virtual nsresult PostHandleEvent(
                      EventChainPostVisitor& aVisitor) MOZ_OVERRIDE;
 
   // nsINode
   virtual nsresult Clone(nsINodeInfo* aNodeInfo, nsINode** aResult) const MOZ_OVERRIDE;
   virtual JSObject* WrapNode(JSContext* aCx) MOZ_OVERRIDE;
@@ -129,28 +132,36 @@ public:
     SetHTMLAttr(nsGkAtoms::rev, aRev, aRv);
   }
   // XPCOM GetTarget is fine.
   void SetTarget(const nsAString& aTarget, ErrorResult& aRv)
   {
     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;
   virtual void GetStyleSheetInfo(nsAString& aTitle,
                                  nsAString& aType,
                                  nsAString& aMedia,
                                  bool* aIsScoped,
                                  bool* aIsAlternate) MOZ_OVERRIDE;
   virtual CORSMode GetCORSMode() const;
 protected:
   // nsGenericHTMLElement
   virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;
   virtual void SetItemValueText(const nsAString& text) MOZ_OVERRIDE;
   nsRefPtr<nsDOMTokenList > mRelList;
+private:
+  nsRefPtr<ImportLoader> mImportLoader;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLLinkElement_h
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/file_imports_basics.html
@@ -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>
\ No newline at end of file
--- a/content/html/content/test/mochitest.ini
+++ b/content/html/content/test/mochitest.ini
@@ -139,16 +139,17 @@ support-files =
   file_iframe_sandbox_top_navigation_pass.html
   file_iframe_sandbox_window_form_fail.html
   file_iframe_sandbox_window_form_pass.html
   file_iframe_sandbox_window_navigation_fail.html
   file_iframe_sandbox_window_navigation_pass.html
   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
   image.png
   image-allow-credentials.png
   image-allow-credentials.png^headers^
   nnc_lockup.gif
   reflect.js
@@ -443,16 +444,17 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_iframe_sandbox_popups.html]
 skip-if = buildapp == 'b2g' # b2g(multiple concurrent window.open()s fail on B2G) b2g-debug(multiple concurrent window.open()s fail on B2G) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_iframe_sandbox_popups_inheritance.html]
 skip-if = buildapp == 'b2g' || e10s # b2g(multiple concurrent window.open()s fail on B2G) b2g-debug(multiple concurrent window.open()s fail on B2G) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_iframe_sandbox_same_origin.html]
 [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]
 [test_map_attributes_reflection.html]
 [test_meta_attributes_reflection.html]
 [test_mod_attributes_reflection.html]
 [test_mozaudiochannel.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(Perma-orange on debug emulator) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_imports_basics.html
@@ -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>
\ No newline at end of file
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -542,17 +542,18 @@ nsHTMLDocument::StartDocumentLoad(const 
 
   nsAutoCString contentType;
   aChannel->GetContentType(contentType);
 
   bool view = !strcmp(aCommand, "view") ||
               !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;
   }
 
   bool html = contentType.EqualsLiteral(TEXT_HTML);
   bool xhtml = !html && contentType.EqualsLiteral(APPLICATION_XHTML_XML);
   bool plainText = !html && !xhtml && nsContentUtils::IsPlainTextType(contentType);
   if (!(html || xhtml || plainText || viewSource)) {
@@ -1365,17 +1366,17 @@ nsHTMLDocument::Open(JSContext* /* unuse
 already_AddRefed<nsIDocument>
 nsHTMLDocument::Open(JSContext* cx,
                      const nsAString& aType,
                      const nsAString& aReplace,
                      ErrorResult& rv)
 {
   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;
   }
 
   nsAutoCString contentType;
   contentType.AssignLiteral("text/html");
 
@@ -1767,17 +1768,17 @@ nsresult
 nsHTMLDocument::WriteCommon(JSContext *cx,
                             const nsAString& aText,
                             bool aNewlineTerminate)
 {
   mTooDeepWriteRecursion =
     (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;
   }
 
   if (mParserAborted) {
     // Hixie says aborting the parser doesn't undefine the insertion point.
     // However, since we null out mParser in that case, we track the
--- a/dom/webidl/HTMLLinkElement.webidl
+++ b/dom/webidl/HTMLLinkElement.webidl
@@ -26,16 +26,17 @@ interface HTMLLinkElement : HTMLElement 
            attribute DOMString media;
   [SetterThrows, Pure]
            attribute DOMString hreflang;
   [SetterThrows, Pure]
            attribute DOMString type;
   [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 {
   [SetterThrows, Pure]
            attribute DOMString charset;
   [SetterThrows, Pure]
            attribute DOMString rev;
   [SetterThrows, Pure]
new file mode 100644
--- /dev/null
+++ b/dom/webidl/LinkImport.webidl
@@ -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;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -219,16 +219,17 @@ WEBIDL_FILES = [
     'InstallEvent.webidl',
     'InstallPhaseEvent.webidl',
     'InterAppConnection.webidl',
     'InterAppConnectionRequest.webidl',
     'InterAppMessagePort.webidl',
     'KeyboardEvent.webidl',
     'KeyEvent.webidl',
     'LegacyQueryInterface.webidl',
+    'LinkImport.webidl',
     'LinkStyle.webidl',
     'LocalMediaStream.webidl',
     'Location.webidl',
     'LockedFile.webidl',
     'MediaElementAudioSourceNode.webidl',
     'MediaError.webidl',
     'MediaList.webidl',
     'MediaQueryList.webidl',
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -74,16 +74,17 @@ nsHtml5Parser::GetCommand(nsCString& aCo
 }
 
 NS_IMETHODIMP_(void)
 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");
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::SetCommand(eParserCommands aParserCommand)
 {
   NS_ASSERTION(aParserCommand == eViewNormal, 
@@ -572,17 +573,18 @@ nsHtml5Parser::MarkAsNotScriptCreated(co
   } else if (!nsCRT::strcmp(aCommand, "plain-text")) {
     mode = PLAIN_TEXT;
   } else if (!nsCRT::strcmp(aCommand, kLoadAsData)) {
     mode = LOAD_AS_DATA;
   }
 #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
   mStreamListener =
     new nsHtml5StreamListener(new nsHtml5StreamParser(mExecutor, this, mode));
 }
 
 bool