merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 02 Dec 2015 15:20:57 +0100
changeset 309309 f6ac392322b3b06d7112a6db1bf6f8ab1853fdcf
parent 309205 a0897ec629f90951e685f42eefecea8437481ecb (current diff)
parent 309308 859f581be10971e7b76e2049b0258ab7d04495cf (diff)
child 309310 26c0d85b7714ab34d01b4f808eb6de1c00f741aa
child 309316 747816eaecb93bc252f1f0ee92d471bbedff9b21
child 309338 ad92bd131ec32b16999940cfc18630a1bbb1f188
child 309407 633bfeaa021b75241fba50ab3544c46b9a84c00c
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.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
merge mozilla-inbound to mozilla-central a=merge
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
dom/events/ContentEventHandler.cpp
gfx/angle/src/compiler/translator/parseConst.cpp
gfx/angle/src/libANGLE/renderer/d3d/DynamicHLSL_unittest.cpp
gfx/angle/src/libANGLE/renderer/d3d/d3d11/swizzle_format_info.cpp
gfx/angle/src/libANGLE/renderer/d3d/d3d11/texture_format_util.h
netwerk/protocol/http/HttpBaseChannel.cpp
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -225,17 +225,19 @@ NotificationController::WillRefresh(mozi
       NS_ASSERTION(!textAcc,
                    "Text node isn't rendered but accessible is kept alive!");
       continue;
     }
 
     nsIContent* containerElm = containerNode->IsElement() ?
       containerNode->AsElement() : nullptr;
 
-    nsIFrame::RenderedText text = textFrame->GetRenderedText();
+    nsIFrame::RenderedText text = textFrame->GetRenderedText(0,
+        UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+        nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
 
     // Remove text accessible if rendered text is empty.
     if (textAcc) {
       if (text.mString.IsEmpty()) {
   #ifdef A11Y_LOG
         if (logging::IsEnabled(logging::eTree | logging::eText)) {
           logging::MsgBegin("TREE", "text node lost its content");
           logging::Node("container", containerElm);
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -1086,17 +1086,19 @@ nsAccessibilityService::GetOrCreateAcces
                "Image map manages the area accessible creation!");
 #endif
 
   // Attempt to create an accessible based on what we know.
   RefPtr<Accessible> newAcc;
 
   // Create accessible for visible text frames.
   if (content->IsNodeOfType(nsINode::eTEXT)) {
-    nsIFrame::RenderedText text = frame->GetRenderedText();
+    nsIFrame::RenderedText text = frame->GetRenderedText(0,
+        UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+        nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
     // Ignore not rendered text nodes and whitespace text nodes between table
     // cells.
     if (text.mString.IsEmpty() ||
         (aContext->IsTableRow() && nsCoreUtils::IsWhitespaceString(text.mString))) {
       if (aIsSubtreeHidden)
         *aIsSubtreeHidden = true;
 
       return nullptr;
--- a/accessible/base/nsTextEquivUtils.cpp
+++ b/accessible/base/nsTextEquivUtils.cpp
@@ -134,17 +134,19 @@ nsTextEquivUtils::AppendTextEquivFromTex
           }
         }
       }
     }
     
     if (aContent->TextLength() > 0) {
       nsIFrame *frame = aContent->GetPrimaryFrame();
       if (frame) {
-        nsIFrame::RenderedText text = frame->GetRenderedText();
+        nsIFrame::RenderedText text = frame->GetRenderedText(0,
+            UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+            nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
         aString->Append(text.mString);
       } else {
         // If aContent is an object that is display: none, we have no a frame.
         aContent->AppendTextTo(*aString);
       }
       if (isHTMLBlock && !aString->IsEmpty()) {
         aString->Append(char16_t(' '));
       }
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -389,17 +389,19 @@ Accessible::VisibilityState()
   // Zero area rects can occur in the first frame of a multi-frame text flow,
   // in which case the rendered text is not empty and the frame should not be
   // marked invisible.
   // XXX Can we just remove this check? Why do we need to mark empty
   // text invisible?
   if (frame->GetType() == nsGkAtoms::textFrame &&
       !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
       frame->GetRect().IsEmpty()) {
-    nsIFrame::RenderedText text = frame->GetRenderedText();
+    nsIFrame::RenderedText text = frame->GetRenderedText(0,
+        UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+        nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
     if (text.mString.IsEmpty()) {
       return states::INVISIBLE;
     }
   }
 
   return 0;
 }
 
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -1980,17 +1980,18 @@ HyperTextAccessible::ContentToRenderedOf
   }
 
   NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
                "Need text frame for offset conversion");
   NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
                "Call on primary frame only");
 
   nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset,
-      aContentOffset + 1);
+      aContentOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+      nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
   *aRenderedOffset = text.mOffsetWithinNodeRenderedText;
 
   return NS_OK;
 }
 
 nsresult
 HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset,
                                              int32_t* aContentOffset) const
@@ -2004,17 +2005,18 @@ HyperTextAccessible::RenderedToContentOf
   NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
 
   NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
                "Need text frame for offset conversion");
   NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
                "Call on primary frame only");
 
   nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset,
-      aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT);
+      aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT,
+      nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
   *aContentOffset = text.mOffsetWithinNodeText;
 
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // HyperTextAccessible public
 
--- a/accessible/tests/mochitest/jsat/test_traversal.html
+++ b/accessible/tests/mochitest/jsat/test_traversal.html
@@ -108,28 +108,28 @@
                               'Or me! ', 'Value 1', 'Value 2', 'Value 3',
                               'Electronic mailing address:', 'input-1-5',
                               'button-1-3', 'heading-2', 'heading-3',
                               'button-2-1', 'button-2-2', 'button-2-3',
                               'button-2-4', 'Programming Language',
                               'A esoteric weapon wielded by only the most ' +
                               'formidable warriors, for its unrelenting strict' +
                               ' power is unfathomable.',
-                              '• Lists of Programming Languages', 'Lisp',
+                              '• Lists of Programming Languages', 'Lisp ',
                               '1. Scheme', '2. Racket', '3. Clojure',
                               '4. Standard Lisp', 'link-0', ' Lisp',
                               'checkbox-1-5', ' LeLisp', '• JavaScript',
                               'heading-5', 'image-2', 'image-3',
                               'Not actually an image', 'link-1', 'anchor-1',
                               'link-2', 'anchor-2', 'link-3', '3', '1', '4',
                               '1', 'Sunday', 'M', 'Week 1', '3', '4', '7', '2',
                               '5 8', 'gridcell4', 'Just an innocuous separator',
                               'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt',
                               'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2',
-                              'switch-1', 'This is a MathML formula', 'math-1',
+                              'switch-1', 'This is a MathML formula ', 'math-1',
                               'with some text after.']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Landmark, null,
                              ['header-1', 'main-1', 'footer-1']);
 
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Control, null,
                              ['input-1-1', 'label-1-2', 'button-1-1',
--- a/accessible/tests/mochitest/pivot/test_virtualcursor.html
+++ b/accessible/tests/mochitest/pivot/test_virtualcursor.html
@@ -47,17 +47,17 @@
 
       queueTraversalSequence(gQueue, docAcc, HeadersTraversalRule, null,
                              ['heading-1-1', 'heading-2-1', 'heading-2-2']);
 
       queueTraversalSequence(
         gQueue, docAcc, ObjectTraversalRule, null,
         ['Main Title', 'Lorem ipsum ',
          'dolor', ' sit amet. Integer vitae urna leo, id ',
-         'semper', ' nulla.', 'Second Section Title',
+         'semper', ' nulla. ', 'Second Section Title',
          'Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.',
          'An ', 'embedded', ' document.', 'Hide me', 'Link 1', 'Link 2',
          'Link 3', 'Hello', 'World']);
 
       // Just a random smoke test to see if our setTextRange works.
       gQueue.push(
         new setVCRangeInvoker(
           docAcc,
@@ -85,17 +85,17 @@
       // Attempting a coordinate outside any header, should move to null
       gQueue.push(new moveVCCoordInvoker(docAcc, x - 1, y - 1, false,
                                          HeadersTraversalRule, null));
 
       queueTraversalSequence(
         gQueue, docAcc, ObjectTraversalRule,
         getAccessible(doc.getElementById('paragraph-1')),
         ['Lorem ipsum ', 'dolor', ' sit amet. Integer vitae urna leo, id ',
-         'semper', ' nulla.']);
+         'semper', ' nulla. ']);
 
       gQueue.push(new setModalRootInvoker(docAcc, docAcc.parent,
                                           NS_ERROR_INVALID_ARG));
 
       // Put cursor in an ignored subtree
       // set isFromUserInput to false, just to test..
       gQueue.push(new setVCPosInvoker(docAcc, null, null,
                                       getAccessible(doc.getElementById("hidden-link")),
--- a/accessible/tests/mochitest/text/test_doc.html
+++ b/accessible/tests/mochitest/text/test_doc.html
@@ -11,18 +11,18 @@
           src="../common.js"></script>
   <script type="application/javascript"
           src="../text.js"></script>
   <script type="application/javascript">
     
     function doTest()
     {
       var iframeDoc = [ getNode("iframe").contentDocument ];
-      testCharacterCount(iframeDoc, 13);
-      testText(iframeDoc, 0, 13, "outbodyinbody");
+      testCharacterCount(iframeDoc, 15);
+      testText(iframeDoc, 0, 15, "outbody inbody ");
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
--- a/accessible/tests/mochitest/text/test_hypertext.html
+++ b/accessible/tests/mochitest/text/test_hypertext.html
@@ -53,17 +53,17 @@
       testText(IDs, 0, 1, "h");
       testText(IDs, 5, 7, " " + kEmbedChar);
       testText(IDs, 10, 13, "e " + kEmbedChar);
       testText(IDs, 0, 13, "hello " + kEmbedChar + " see " + kEmbedChar);
 
       ////////////////////////////////////////////////////////////////////////
       // getTextAtOffset line boundary
 
-      testTextAtOffset(0, BOUNDARY_LINE_START, "line", 0, 4,
+      testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
                        "hypertext3", kOk, kOk, kOk);
 
       // XXX: see bug 634202.
       testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
                        "hypertext4", kTodo, kOk, kTodo);
 
       //////////////////////////////////////////////////////////////////////////
       // list
--- a/accessible/tests/mochitest/text/test_lineboundary.html
+++ b/accessible/tests/mochitest/text/test_lineboundary.html
@@ -9,19 +9,19 @@
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../text.js"></script>
   <script type="application/javascript">
     function doTest()
     {
       testTextAtOffset("line_test_1", BOUNDARY_LINE_START,
-                       [[0, 5, "Line 1", 0, 6],
-                        [6, 6, "", 6, 6],
-                        [7, 13, "Line 3", 7, 13]]);
+                       [[0, 6, "Line 1 ", 0, 7],
+                        [7, 7, "", 7, 7],
+                        [8, 15, "Line 3 ", 8, 15]]);
 
       //////////////////////////////////////////////////////////////////////////
       // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
       //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
 
       var IDs = [ "input", "div", "editable", "textarea",
                   getNode("ta", getNode("ta_cntr").contentDocument) ];
 
@@ -109,20 +109,20 @@
       testTextAtOffset([ getAccessible("ht_2").firstChild.firstChild ],
                        BOUNDARY_LINE_START,
                        [ [ 0, 3, "foo", 0, 3 ] ]);
       testTextAtOffset([ getAccessible("ht_3").firstChild.firstChild ],
                        BOUNDARY_LINE_START,
                        [ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]);
 
       //////////////////////////////////////////////////////////////////////////
-      // 'Hello world'
+      // 'Hello world ' (\n is rendered as space)
 
       testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START,
-                       [ [ 0, 11, "Hello world", 0, 11 ] ]);
+                       [ [ 0, 12, "Hello world ", 0, 12 ] ]);
 
       //////////////////////////////////////////////////////////////////////////
       // list items
 
       testTextAtOffset([ "li1" ], BOUNDARY_LINE_START,
                        [ [ 0, 6, kDiscBulletText + "Item", 0, 6 ] ]);
       testTextAtOffset([ "li2" ], BOUNDARY_LINE_START,
                        [ [ 0, 2, kDiscBulletText, 0, 2 ] ]);
--- a/accessible/tests/mochitest/textattrs/test_general.html
+++ b/accessible/tests/mochitest/textattrs/test_general.html
@@ -84,17 +84,17 @@
       gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
       attrs = {"color": gComputedStyle.color};
       testTextAttrs(ID, 26, attrs, defAttrs, 26, 27);
 
       tempElem = tempElem.nextSibling;
       gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
       attrs = {"color": gComputedStyle.color,
                "background-color": gComputedStyle.backgroundColor};
-      testTextAttrs(ID, 27, attrs, defAttrs, 27, 49);
+      testTextAttrs(ID, 27, attrs, defAttrs, 27, 50);
 
       //////////////////////////////////////////////////////////////////////////
       // area4
       ID = "area4";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
       testDefaultTextAttrs(ID, defAttrs);
 
       tempElem = getNode(ID).firstChild.nextSibling;
@@ -105,17 +105,17 @@
       tempElem = tempElem.nextSibling.firstChild.nextSibling;
       gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
       attrs = {"color": gComputedStyle.color};
       testTextAttrs(ID, 16, attrs, defAttrs, 16, 33);
 
       tempElem = tempElem.parentNode;
       gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
       attrs = {"color": gComputedStyle.color};
-      testTextAttrs(ID, 34, attrs, defAttrs, 33, 45);
+      testTextAttrs(ID, 34, attrs, defAttrs, 33, 46);
 
       //////////////////////////////////////////////////////////////////////////
       // area5: "Green!*!RedNormal"
       ID = "area5";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
       testDefaultTextAttrs(ID, defAttrs);
 
       // Green
@@ -139,17 +139,17 @@
       // Red
       tempElem = tempElem.nextSibling.nextSibling.nextSibling.nextSibling;
       gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
       attrs = {"color": gComputedStyle.color};
       testTextAttrs(ID, 9, attrs, defAttrs, 8, 11);
 
       // Normal
       attrs = {};
-      testTextAttrs(ID, 11, attrs, defAttrs, 11, 17);
+      testTextAttrs(ID, 11, attrs, defAttrs, 11, 18);
 
       //////////////////////////////////////////////////////////////////////////
       // area6 (CSS vertical-align property, refer to bug 445938 for details
       // 	and sup and sub elements, refer to bug 735645 for details)
       ID = "area6";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
       testDefaultTextAttrs(ID, defAttrs);
 
@@ -318,17 +318,17 @@
 
       attrs = {
         "text-line-through-style": "solid",
         "text-line-through-color": gComputedStyle.color
       };
       testTextAttrs(ID, 152, attrs, defAttrs, 151, 164);
 
       attrs = {};
-      testTextAttrs(ID, 165, attrs, defAttrs, 164, 171);
+      testTextAttrs(ID, 165, attrs, defAttrs, 164, 172);
 
       //////////////////////////////////////////////////////////////////////////
       // area10, different single style spans in non-styled paragraph
       ID = "area10";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
       testDefaultTextAttrs(ID, defAttrs);
 
       attrs = {};
@@ -378,17 +378,17 @@
 
       attrs = {
         "text-line-through-style": "solid",
         "text-line-through-color": gComputedStyle.color
       };
       testTextAttrs(ID, 111, attrs, defAttrs, 110, 123);
 
       attrs = {};
-      testTextAttrs(ID, 124, attrs, defAttrs, 123, 130);
+      testTextAttrs(ID, 124, attrs, defAttrs, 123, 131);
 
       //////////////////////////////////////////////////////////////////////////
       // area11, "font-weight" tests
       ID = "area11";
       defAttrs = buildDefaultTextAttrs(ID, "12pt", kBoldFontWeight);
       testDefaultTextAttrs(ID, defAttrs);
 
       attrs = { };
@@ -405,17 +405,17 @@
 
       attrs = { };
       testTextAttrs(ID, 33, attrs, defAttrs, 33, 51);
 
       attrs = { "font-weight": kNormalFontWeight };
       testTextAttrs(ID, 51, attrs, defAttrs, 51, 57);
 
       attrs = { };
-      testTextAttrs(ID, 57, attrs, defAttrs, 57, 96);
+      testTextAttrs(ID, 57, attrs, defAttrs, 57, 97);
 
       //////////////////////////////////////////////////////////////////////////
       // test out of range offset
       testTextAttrsWrongOffset("area12", -1);
       testTextAttrsWrongOffset("area12", 500);
 
       //////////////////////////////////////////////////////////////////////////
       // test zero offset on empty hypertext accessibles
@@ -482,17 +482,17 @@
       if (!LINUX) {
         attrs = { };
         testTextAttrs(ID, 22, attrs, defAttrs, 22, 27);
 
         attrs = { "font-family": kCursiveFontFamily };
         testTextAttrs(ID, 27, attrs, defAttrs, 27, 31);
 
         attrs = { };
-        testTextAttrs(ID, 31, attrs, defAttrs, 31, 44);
+        testTextAttrs(ID, 31, attrs, defAttrs, 31, 45);
       }
 
       //////////////////////////////////////////////////////////////////////////
       // area17, "text-decoration" tests
       ID = "area17";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
       testDefaultTextAttrs(ID, defAttrs);
 
@@ -525,17 +525,17 @@
         "text-line-through-color": "rgb(0, 0, 255)",
       };
       testTextAttrs(ID, 34, attrs, defAttrs, 34, 39);
 
       attrs = {
         "text-line-through-style": "wavy",
         "text-line-through-color": "rgb(0, 0, 0)",
       };
-      testTextAttrs(ID, 39, attrs, defAttrs, 39, 43);
+      testTextAttrs(ID, 39, attrs, defAttrs, 39, 44);
 
       //////////////////////////////////////////////////////////////////////////
       // area18, "auto-generation text" tests
       ID = "area18";
       defAttrs = buildDefaultTextAttrs(ID, "12pt");
       testDefaultTextAttrs(ID, defAttrs);
 
       var attrs = {
@@ -555,17 +555,17 @@
       testTextAttrs(ID, 0, attrs, defAttrs, 0, 10);
       
       tempElem = getNode(ID).firstChild.nextSibling;
       gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
       attrs = { "background-color": gComputedStyle.backgroundColor };
       testTextAttrs(ID, 11, attrs, defAttrs, 10, 17);
 
       attrs = {};
-      testTextAttrs(ID, 18, attrs, defAttrs, 17, 27);
+      testTextAttrs(ID, 18, attrs, defAttrs, 17, 28);
 
        //////////////////////////////////////////////////////////////////////////
       // area20, "aOffset as -1 (Mozilla Bug 789621)" test
 
       ID = "area20";
       defAttrs = buildDefaultTextAttrs(ID, "15pt");
       testDefaultTextAttrs(ID, defAttrs);
 
--- a/accessible/tests/mochitest/textcaret/test_general.html
+++ b/accessible/tests/mochitest/textcaret/test_general.html
@@ -66,17 +66,17 @@
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
 
     function doTests()
     {
       turnCaretBrowsing(true);
 
       // test caret offsets
-      testCaretOffset(document, 15);
+      testCaretOffset(document, 16);
       testCaretOffset("textbox", -1);
       testCaretOffset("textarea", -1);
       testCaretOffset("p", -1);
 
       // test caret move events and caret offsets
       gQueue = new eventQueue();
 
       gQueue.push(new setCaretOffset("textbox", 1, "textbox"));
--- a/accessible/tests/mochitest/tree/test_txtcntr.html
+++ b/accessible/tests/mochitest/tree/test_txtcntr.html
@@ -44,52 +44,52 @@
             role: ROLE_TEXT_LEAF,
             name: "Hello2"
           },
           {
             role: ROLE_SEPARATOR
           },
           {
             role: ROLE_TEXT_LEAF,
-            name: "Hello3"
+            name: "Hello3 "
           },
           {
             role: ROLE_PARAGRAPH,
             children: [
               {
                 role: ROLE_TEXT_LEAF,
-                name: "Hello4"
+                name: "Hello4 "
               }
             ]
           }
         ]
       };
 
       testAccessibleTree("c3", accTree);
 
       // contentEditable div
       accTree = {
         role: ROLE_SECTION,
         children: [
           {
             role: ROLE_TEXT_LEAF,
-            name: "helllo"
+            name: "helllo "
           },
           {
             role: ROLE_PARAGRAPH,
             children: [
               {
                 role: ROLE_TEXT_LEAF,
                 name: "blabla"
               }
             ]
           },
           {
             role: ROLE_TEXT_LEAF,
-            name: "hello"
+            name: "hello "
           }
         ]
       };
 
       testAccessibleTree("c4", accTree);
 
       // blockquote
       accTree = {
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+    'test/browser/browser.ini',
+]
+
+with Files('**'):
+    BUG_COMPONENT = ('Firefox', 'Contextual Identity')
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+skip-if = buildapp == "mulet"
+support-files =
+  file_reflect_cookie_into_title.html
+
+[browser_usercontext.js]
+skip-if = e10s
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_usercontext.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+const USER_CONTEXTS = [
+  "default",
+  "personal",
+  "work",
+];
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+  + "contextualidentity/test/browser/file_reflect_cookie_into_title.html";
+
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+function openTabInUserContext(uri, userContextId) {
+  // open the tab in the correct userContextId
+  let tab = gBrowser.addTab(uri, {userContextId});
+
+  // select tab and make sure its browser is focused
+  gBrowser.selectedTab = tab;
+  tab.ownerDocument.defaultView.focus();
+
+  return tab;
+}
+
+add_task(function* setup() {
+  // make sure userContext is enabled.
+  SpecialPowers.pushPrefEnv({"set": [
+    ["privacy.userContext.enabled", true]
+  ]});
+});
+
+add_task(function* cleanup() {
+  // make sure we don't leave any prefs set for the next tests
+  registerCleanupFunction(function() {
+    SpecialPowers.popPrefEnv();
+  });
+});
+
+add_task(function* test() {
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    // load the page in 3 different contexts and set a cookie
+    // which should only be visible in that context
+    let cookie = USER_CONTEXTS[userContextId];
+
+    // open our tab in the given user context
+    let tab = openTabInUserContext(BASE_URI+"?"+cookie, userContextId);
+
+    // wait for tab load
+    yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
+
+    // remove the tab
+    gBrowser.removeTab(tab);
+  }
+
+  {
+    // Set a cookie in a different context so we can detect if that affects
+    // cross-context properly. If we don't do that, we get an UNEXPECTED-PASS
+    // for the localStorage case for the last tab we set.
+    let tab = openTabInUserContext(BASE_URI+"?foo", 9999);
+    yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
+    gBrowser.removeTab(tab);
+  }
+
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    // Load the page without setting the cookie this time
+    let expectedContext = USER_CONTEXTS[userContextId];
+
+    let tab = openTabInUserContext(BASE_URI, userContextId);
+
+    // wait for load
+    let browser = gBrowser.getBrowserForTab(tab);
+    yield BrowserTestUtils.browserLoaded(browser);
+
+    // get the title
+    let title = browser.contentDocument.title.trim().split("|");
+
+    // check each item in the title and validate it meets expectatations
+    for (let part of title) {
+      let [storageMethodName, value] = part.split("=");
+      let is_f = storageMethodName == "cookie" ? is : todo_is;
+      is_f(value, expectedContext,
+            "the title reflects the expected contextual identity of " +
+            expectedContext + " for method " + storageMethodName + ": " + value);
+    }
+
+    gBrowser.removeTab(tab);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/file_reflect_cookie_into_title.html
@@ -0,0 +1,23 @@
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>title not set</title>
+    <script>
+      // if we have a query string, use it to set the cookie and localStorage
+      if (window.location.search.length > 0) {
+        let context_name = window.location.search.substr(1);
+        document.cookie = "userContextId=" + context_name;
+        localStorage.setItem("userContext", context_name);
+      }
+
+      // get the cookie
+      let [name, val] = document.cookie.split("=");
+
+      // set the title to reflect the cookie and local storage values we find
+      document.title = "cookie=" + val + "|"
+                       + "local=" + localStorage.getItem("userContext");
+    </script>
+  </head>
+  <body></body>
+</html>
+
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'about',
+    'contextualidentity',
     'customizableui',
     'dirprovider',
     'downloads',
     'extensions',
     'feeds',
     'migration',
     'newtab',
     'places',
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -223,11 +223,14 @@ endif
 
 # test target, depends on make package
 # try to repack x-test, with just toolkit/defines.inc being there
 l10n-check:: INNER_UNMAKE_PACKAGE=true
 l10n-check::
 	$(RM) -rf x-test
 	$(NSINSTALL) -D x-test/toolkit
 	echo '#define MOZ_LANG_TITLE Just testing' > x-test/toolkit/defines.inc
-	$(MAKE) installers-x-test L10NBASEDIR='$(PWD)' LOCALE_MERGEDIR='$(PWD)/mergedir'
+	@# ZIP_IN='$(ZIP_IN)' will pass down the *current* value of ZIP_IN, based
+	@# on MOZ_SIMPLE_PACKAGE_NAME not being reset, overwriting the value it
+	@# would get with MOZ_SIMPLE_PACKAGE_NAME reset.
+	$(MAKE) installers-x-test L10NBASEDIR='$(PWD)' LOCALE_MERGEDIR='$(PWD)/mergedir' ZIP_IN='$(ZIP_IN)' MOZ_SIMPLE_PACKAGE_NAME=
 	$(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/unpack.py $(DIST)/l10n-stage/$(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)
 	cd $(DIST)/l10n-stage && test $$(cat $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/update.locale) = x-test
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -219,17 +219,43 @@ if test "$GNU_CXX"; then
                             ac_cv_cxx0x_clang_workaround="no")])
 
         if test "ac_cv_cxx0x_clang_workaround" = "no"; then
             AC_MSG_ERROR([Your toolchain does not support C++0x/C++11 mode properly. Please upgrade your toolchain])
         fi
     elif test "$ac_cv_cxx0x_headers_bug" = "yes"; then
         AC_MSG_ERROR([Your toolchain does not support C++0x/C++11 mode properly. Please upgrade your toolchain])
     fi
+
+    AC_CACHE_CHECK([whether 64-bits std::atomic requires -latomic],
+        ac_cv_needs_atomic,
+        AC_TRY_LINK(
+            [#include <cstdint>
+             #include <atomic>],
+            [ std::atomic<uint64_t> foo; foo = 1; ],
+            ac_cv_needs_atomic=no,
+            _SAVE_LIBS="$LIBS"
+            LIBS="$LIBS -latomic"
+            AC_TRY_LINK(
+                [#include <cstdint>
+                 #include <atomic>],
+                [ std::atomic<uint64_t> foo; foo = 1; ],
+                ac_cv_needs_atomic=yes,
+                ac_cv_needs_atomic="do not know; assuming no")
+            LIBS="$_SAVE_LIBS"
+        )
+    )
+    if test "$ac_cv_needs_atomic" = yes; then
+      MOZ_NEEDS_LIBATOMIC=1
+    else
+      MOZ_NEEDS_LIBATOMIC=
+    fi
+    AC_SUBST(MOZ_NEEDS_LIBATOMIC)
 fi
+
 if test -n "$CROSS_COMPILE"; then
     dnl When cross compile, we have no variable telling us what the host compiler is. Figure it out.
     cat > conftest.C <<EOF
 #if defined(__clang__)
 COMPILER CLANG __clang_major__.__clang_minor__.__clang_patchlevel__
 #elif defined(__GNUC__)
 COMPILER GCC __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
 #endif
--- a/docshell/base/nsDefaultURIFixup.h
+++ b/docshell/base/nsDefaultURIFixup.h
@@ -60,11 +60,11 @@ protected:
 private:
   nsCOMPtr<nsISupports> mConsumer;
   nsCOMPtr<nsIURI> mPreferredURI;
   nsCOMPtr<nsIURI> mFixedURI;
   bool mFixupChangedProtocol;
   bool mFixupCreatedAlternateURI;
   nsString mKeywordProviderName;
   nsString mKeywordAsSent;
-  nsAutoCString mOriginalInput;
+  nsCString mOriginalInput;
 };
 #endif
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -13740,16 +13740,23 @@ nsDocShell::SetIsBrowserInsideApp(uint32
 
 NS_IMETHODIMP
 nsDocShell::SetIsSignedPackage(const nsAString& aSignedPkg)
 {
   mSignedPkg = aSignedPkg;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDocShell::SetUserContextId(uint32_t aUserContextId)
+{
+  mUserContextId = aUserContextId;
+  return NS_OK;
+}
+
 /* [infallible] */ NS_IMETHODIMP
 nsDocShell::GetIsBrowserElement(bool* aIsBrowser)
 {
   *aIsBrowser = (mFrameType == eFrameTypeBrowser);
   return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
@@ -13849,16 +13856,18 @@ nsDocShell::GetOriginAttributes()
     // set before.
     attrs.mSignedPkg = mSignedPkg;
   }
 
   if (mOwnOrContainingAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
     attrs.mAppId = mOwnOrContainingAppId;
   }
 
+  attrs.mUserContextId = mUserContextId;
+
   if (mFrameType == eFrameTypeBrowser) {
     attrs.mInBrowser = true;
   }
 
   return attrs;
 }
 
 NS_IMETHODIMP
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -227,16 +227,17 @@ public:
   NS_IMETHOD IsAppOfType(uint32_t, bool*) override;
   NS_IMETHOD GetIsContent(bool*) override;
   NS_IMETHOD GetUsePrivateBrowsing(bool*) override;
   NS_IMETHOD SetUsePrivateBrowsing(bool) override;
   NS_IMETHOD SetPrivateBrowsing(bool) override;
   NS_IMETHOD GetUseRemoteTabs(bool*) override;
   NS_IMETHOD SetRemoteTabs(bool) override;
   NS_IMETHOD GetOriginAttributes(JS::MutableHandle<JS::Value>) override;
+  NS_IMETHOD SetUserContextId(uint32_t);
 
   // Restores a cached presentation from history (mLSHE).
   // This method swaps out the content viewer and simulates loads for
   // subframes. It then simulates the completion of the toplevel load.
   nsresult RestoreFromHistory();
 
   // Perform a URI load from a refresh timer. This is just like the
   // ForceRefreshURI method on nsIRefreshURI, but makes sure to take
@@ -997,16 +998,19 @@ protected:
   // inside an app, we'll retrieve the containing app-id by walking up the
   // docshell hierarchy.
   //
   // (This needs to be the docshell's own /or containing/ app id because the
   // containing app frame might be in another process, in which case we won't
   // find it by walking up the docshell hierarchy.)
   uint32_t mOwnOrContainingAppId;
 
+  // userContextId signifying which container we are in
+  uint32_t mUserContextId;
+
   nsString mPaymentRequestId;
 
   nsString GetInheritedPaymentRequestId();
 
   // The packageId for a signed packaged iff this docShell is created
   // for a signed package.
   nsString mSignedPkg;
 
--- a/docshell/base/timeline/RestyleTimelineMarker.h
+++ b/docshell/base/timeline/RestyleTimelineMarker.h
@@ -29,14 +29,14 @@ public:
     TimelineMarker::AddDetails(aCx, aMarker);
 
     if (GetTracingType() == MarkerTracingType::START) {
       aMarker.mRestyleHint.Construct(mRestyleHint);
     }
   }
 
 private:
-  nsAutoString mRestyleHint;
+  nsString mRestyleHint;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_RestyleTimelineMarker_h_
--- a/dom/base/Link.cpp
+++ b/dom/base/Link.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/EventStates.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/Element.h"
 #include "nsIURL.h"
 #include "nsISizeOf.h"
 
 #include "nsEscape.h"
 #include "nsGkAtoms.h"
+#include "nsHTMLDNSPrefetch.h"
 #include "nsString.h"
 #include "mozAutoDocUpdate.h"
 
 #include "mozilla/Services.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -42,16 +43,41 @@ Link::ElementHasHref() const
 {
   return ((!mElement->IsSVGElement() &&
            mElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href))
         || (!mElement->IsHTMLElement() &&
             mElement->HasAttr(kNameSpaceID_XLink, nsGkAtoms::href)));
 }
 
 void
+Link::TryDNSPrefetch()
+{
+  MOZ_ASSERT(mElement->IsInComposedDoc());
+  if (ElementHasHref() && nsHTMLDNSPrefetch::IsAllowed(mElement->OwnerDoc())) {
+    nsHTMLDNSPrefetch::PrefetchLow(this);
+  }
+}
+
+void
+Link::CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
+                        nsWrapperCache::FlagsType aRequestedFlag)
+{
+  // If prefetch was deferred, clear flag and move on
+  if (mElement->HasFlag(aDeferredFlag)) {
+    mElement->UnsetFlags(aDeferredFlag);
+    // Else if prefetch was requested, clear flag and send cancellation
+  } else if (mElement->HasFlag(aRequestedFlag)) {
+    mElement->UnsetFlags(aRequestedFlag);
+    // Possible that hostname could have changed since binding, but since this
+    // covers common cases, most DNS prefetch requests will be canceled
+    nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
+  }
+}
+
+void
 Link::SetLinkState(nsLinkState aState)
 {
   NS_ASSERTION(mRegistered,
                "Setting the link state of an unregistered Link!");
   NS_ASSERTION(mLinkState != aState,
                "Setting state to the currently set state!");
 
   // Set our current state as appropriate.
--- a/dom/base/Link.h
+++ b/dom/base/Link.h
@@ -106,16 +106,21 @@ public:
    */
   virtual bool HasDeferredDNSPrefetchRequest() { return true; }
 
   virtual size_t
     SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   bool ElementHasHref() const;
 
+  void TryDNSPrefetch();
+
+  void CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
+                         nsWrapperCache::FlagsType aRequestedFlag);
+
 protected:
   virtual ~Link();
 
   /**
    * Return true if the link has associated URI.
    */
   bool HasURI() const
   {
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -231,17 +231,17 @@ nsFrameLoader::LoadFrame()
   if (rv == NS_ERROR_MALFORMED_URI) {
     rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_STRING("about:blank"),
                    charset, base_uri);
   }
 
   if (NS_SUCCEEDED(rv)) {
     rv = LoadURI(uri);
   }
-  
+
   if (NS_FAILED(rv)) {
     FireErrorEvent();
 
     return rv;
   }
 
   return NS_OK;
 }
@@ -314,17 +314,17 @@ nsFrameLoader::SetIsPrerendered()
 
 nsresult
 nsFrameLoader::ReallyStartLoading()
 {
   nsresult rv = ReallyStartLoadingInternal();
   if (NS_FAILED(rv)) {
     FireErrorEvent();
   }
-  
+
   return rv;
 }
 
 nsresult
 nsFrameLoader::ReallyStartLoadingInternal()
 {
   NS_ENSURE_STATE(mURIToLoad && mOwnerContent && mOwnerContent->IsInComposedDoc());
 
@@ -334,17 +334,17 @@ nsFrameLoader::ReallyStartLoadingInterna
   if (IsRemoteFrame()) {
     if (!mRemoteBrowser && !TryRemoteBrowser()) {
         NS_WARNING("Couldn't create child process for iframe.");
         return NS_ERROR_FAILURE;
     }
 
     // FIXME get error codes from child
     mRemoteBrowser->LoadURL(mURIToLoad);
-    
+
     if (!mRemoteBrowserShown && !ShowRemoteFrame(ScreenIntSize(0, 0))) {
       NS_WARNING("[nsFrameLoader] ReallyStartLoadingInternal tried but couldn't show remote browser.\n");
     }
 
     return NS_OK;
   }
 
   nsresult rv = MaybeCreateDocShell();
@@ -367,17 +367,17 @@ nsFrameLoader::ReallyStartLoadingInterna
   // We do it there to correctly sandbox content that was loaded into
   // the frame via other methods than the src attribute.
   // We'll use our principal, not that of the document loaded inside us.  This
   // is very important; needed to prevent XSS attacks on documents loaded in
   // subframes!
   loadInfo->SetOwner(mOwnerContent->NodePrincipal());
 
   nsCOMPtr<nsIURI> referrer;
-  
+
   nsAutoString srcdoc;
   bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) &&
                   mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::srcdoc,
                                          srcdoc);
 
   if (isSrcdoc) {
     nsAutoString referrerStr;
     mOwnerContent->OwnerDoc()->GetReferrer(referrerStr);
@@ -541,17 +541,17 @@ SetTreeOwnerAndChromeEventHandlerOnDocsh
 bool
 nsFrameLoader::AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem,
                                       nsIDocShellTreeOwner* aOwner,
                                       int32_t aParentType,
                                       nsIDocShell* aParentNode)
 {
   NS_PRECONDITION(aItem, "Must have docshell treeitem");
   NS_PRECONDITION(mOwnerContent, "Must have owning content");
-  
+
   nsAutoString value;
   bool isContent = false;
   mOwnerContent->GetAttr(kNameSpaceID_None, TypeAttrName(), value);
 
   // we accept "content" and "content-xxx" values.
   // at time of writing, we expect "xxx" to be "primary" or "targetable", but
   // someday it might be an integer expressing priority or something else.
 
@@ -1122,17 +1122,17 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   // tree is a bit of a pain.  So make sure that if ourType is not
   // nsIDocShellTreeItem::typeContent then all of our descendants are the same
   // type as us.
   if (ourType != nsIDocShellTreeItem::typeContent &&
       (!AllDescendantsOfType(ourDocshell, ourType) ||
        !AllDescendantsOfType(otherDocshell, otherType))) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
-  
+
   // Save off the tree owners, frame elements, chrome event handlers, and
   // docshell and document parents before doing anything else.
   nsCOMPtr<nsIDocShellTreeOwner> ourOwner, otherOwner;
   ourDocshell->GetTreeOwner(getter_AddRefs(ourOwner));
   otherDocshell->GetTreeOwner(getter_AddRefs(otherOwner));
   // Note: it's OK to have null treeowners.
 
   nsCOMPtr<nsIDocShellTreeItem> ourParentItem, otherParentItem;
@@ -1232,17 +1232,17 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   // Now move the docshells to the right docshell trees.  Note that this
   // resets their treeowners to null.
   ourParentItem->RemoveChild(ourDocshell);
   otherParentItem->RemoveChild(otherDocshell);
   if (ourType == nsIDocShellTreeItem::typeContent) {
     ourOwner->ContentShellRemoved(ourDocshell);
     otherOwner->ContentShellRemoved(otherDocshell);
   }
-  
+
   ourParentItem->AddChild(otherDocshell);
   otherParentItem->AddChild(ourDocshell);
 
   // Restore the correct chrome event handlers.
   ourDocshell->SetChromeEventHandler(otherChromeEventHandler);
   otherDocshell->SetChromeEventHandler(ourChromeEventHandler);
   // Restore the correct treeowners
   // (and also chrome event handlers for content frames only).
@@ -1783,16 +1783,33 @@ nsFrameLoader::MaybeCreateDocShell()
       mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, frameName);
     }
   }
 
   if (!frameName.IsEmpty()) {
     mDocShell->SetName(frameName);
   }
 
+  //Grab the userContextId from owner if XUL
+  nsAutoString userContextIdStr;
+  if (namespaceID == kNameSpaceID_XUL) {
+    if (mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usercontextid)) {
+      mOwnerContent->GetAttr(kNameSpaceID_None,
+                             nsGkAtoms::usercontextid,
+                             userContextIdStr);
+    }
+  }
+
+  if (!userContextIdStr.IsEmpty()) {
+    nsresult err;
+    nsDocShell * ds = nsDocShell::Cast(mDocShell);
+    ds->SetUserContextId(userContextIdStr.ToInteger(&err));
+    NS_ENSURE_SUCCESS(err, err);
+  }
+
   // Inform our docShell that it has a new child.
   // Note: This logic duplicates a lot of logic in
   // nsSubDocumentFrame::AttributeChanged.  We should fix that.
 
   int32_t parentType = docShell->ItemType();
 
   // XXXbz why is this in content code, exactly?  We should handle
   // this some other way.....  Not sure how yet.
@@ -1956,31 +1973,31 @@ nsFrameLoader::CheckForRecursiveLoad(nsI
   }
 
   // Check that we're still in the docshell tree.
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
   mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
   NS_WARN_IF_FALSE(treeOwner,
                    "Trying to load a new url to a docshell without owner!");
   NS_ENSURE_STATE(treeOwner);
-  
+
   if (mDocShell->ItemType() != nsIDocShellTreeItem::typeContent) {
     // No need to do recursion-protection here XXXbz why not??  Do we really
     // trust people not to screw up with non-content docshells?
     return NS_OK;
   }
 
   // Bug 8065: Don't exceed some maximum depth in content frames
   // (MAX_DEPTH_CONTENT_FRAMES)
   nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
   mDocShell->GetSameTypeParent(getter_AddRefs(parentAsItem));
   int32_t depth = 0;
   while (parentAsItem) {
     ++depth;
-    
+
     if (depth >= MAX_DEPTH_CONTENT_FRAMES) {
       mDepthTooGreat = true;
       NS_WARNING("Too many nested content frames so giving up");
 
       return NS_ERROR_UNEXPECTED; // Too deep, give up!  (silently?)
     }
 
     nsCOMPtr<nsIDocShellTreeItem> temp;
@@ -2011,17 +2028,17 @@ nsFrameLoader::CheckForRecursiveLoad(nsI
       // Does the URI match the one we're about to load?
       nsCOMPtr<nsIURI> parentURI;
       parentAsNav->GetCurrentURI(getter_AddRefs(parentURI));
       if (parentURI) {
         // Bug 98158/193011: We need to ignore data after the #
         bool equal;
         rv = aURI->EqualsExceptRef(parentURI, &equal);
         NS_ENSURE_SUCCESS(rv, rv);
-        
+
         if (equal) {
           matchCount++;
           if (matchCount >= MAX_SAME_URL_CONTENT_FRAMES) {
             NS_WARNING("Too many nested content frames have the same url (recursion?) so giving up");
             return NS_ERROR_UNEXPECTED;
           }
         }
       }
@@ -3059,14 +3076,31 @@ nsFrameLoader::GetNewTabContext(MutableT
     NS_ENSURE_SUCCESS(rv, rv);
     NS_ENSURE_STATE(appId != nsIScriptSecurityManager::NO_APP_ID);
   }
   attrs.mAppId = appId;
 
   // Populate packageId to signedPkg.
   attrs.mSignedPkg = NS_ConvertUTF8toUTF16(aPackageId);
 
+  // set the userContextId on the attrs before we pass them into
+  // the tab context
+  if (mOwnerContent) {
+    nsAutoString userContextIdStr;
+    if (mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usercontextid)) {
+      mOwnerContent->GetAttr(kNameSpaceID_None,
+                             nsGkAtoms::usercontextid,
+                             userContextIdStr);
+    }
+    if (!userContextIdStr.IsEmpty()) {
+      nsresult err;
+      uint32_t userContextId = userContextIdStr.ToInteger(&err);
+      NS_ENSURE_SUCCESS(err, err);
+      attrs.mUserContextId = userContextId;
+    }
+  }
+
   bool tabContextUpdated =
     aTabContext->SetTabContext(ownApp, containingApp, attrs, signedPkgOrigin);
   NS_ENSURE_STATE(tabContextUpdated);
 
   return NS_OK;
 }
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2400,8 +2400,11 @@ GK_ATOM(onspeechend, "onspeechend")
 GK_ATOM(onresult, "onresult")
 GK_ATOM(onnomatch, "onnomatch")
 GK_ATOM(onresume, "onresume")
 GK_ATOM(onmark, "onmark")
 GK_ATOM(onboundary, "onboundary")
 #endif
 
 GK_ATOM(vr_state, "vr-state")
+
+// Contextual Identity / Containers
+GK_ATOM(usercontextid, "usercontextid")
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -9,17 +9,16 @@
  */
 
 #include "nsScriptLoader.h"
 
 #include "prsystem.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "xpcpublic.h"
-#include "nsIUnicodeDecoder.h"
 #include "nsIContent.h"
 #include "nsJSUtils.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/Element.h"
 #include "nsGkAtoms.h"
 #include "nsNetUtil.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
@@ -49,17 +48,16 @@
 #include "nsSandboxFlags.h"
 #include "nsContentTypeParser.h"
 #include "nsINetworkPredictor.h"
 #include "ImportManager.h"
 #include "mozilla/dom/EncodingUtils.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/unused.h"
-#include "mozilla/dom/SRICheck.h"
 #include "nsIScriptError.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static LazyLogModule gCspPRLog("CSP");
 
 static LogModule*
@@ -152,20 +150,20 @@ nsScriptLoader::~nsScriptLoader()
       req = req->getNext()) {
     req->FireScriptAvailable(NS_ERROR_ABORT);
   }
 
   // Unblock the kids, in case any of them moved to a different document
   // subtree in the meantime and therefore aren't actually going away.
   for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
     mPendingChildLoaders[j]->RemoveExecuteBlocker();
-  }  
+  }
 }
 
-NS_IMPL_ISUPPORTS(nsScriptLoader, nsIStreamLoaderObserver)
+NS_IMPL_ISUPPORTS(nsScriptLoader, nsISupports)
 
 // Helper method for checking if the script element is an event-handler
 // This means that it has both a for-attribute and a event-attribute.
 // Also, if the for-attribute has a value that matches "\s*window\s*",
 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
 // eventhandler. (both matches are case insensitive).
 // This is how IE seems to filter out a window's onload handler from a
 // <script for=... event=...> element.
@@ -264,47 +262,16 @@ nsScriptLoader::ShouldLoadScript(nsIDocu
   rv = CheckContentPolicy(aDocument, aContext, aURI, aType, aIsPreLoad);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return NS_OK;
 }
 
-class ContextMediator : public nsIStreamLoaderObserver
-{
-public:
-  explicit ContextMediator(nsScriptLoader *aScriptLoader, nsISupports *aContext)
-  : mScriptLoader(aScriptLoader)
-  , mContext(aContext) {}
-
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSISTREAMLOADEROBSERVER
-
-private:
-  virtual ~ContextMediator() {}
-  RefPtr<nsScriptLoader> mScriptLoader;
-  nsCOMPtr<nsISupports>  mContext;
-};
-
-NS_IMPL_ISUPPORTS(ContextMediator, nsIStreamLoaderObserver)
-
-NS_IMETHODIMP
-ContextMediator::OnStreamComplete(nsIStreamLoader* aLoader,
-                                  nsISupports* aContext,
-                                  nsresult aStatus,
-                                  uint32_t aStringLen,
-                                  const uint8_t* aString)
-{
-  // pass arguments through except for the aContext,
-  // we have to mediate and use mContext instead.
-  return mScriptLoader->OnStreamComplete(aLoader, mContext, aStatus,
-                                         aStringLen, aString);
-}
-
 nsresult
 nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
                           bool aScriptFromHead)
 {
   // If this document is sandboxed without 'allow-scripts', abort.
   if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) {
     return NS_OK;
   }
@@ -378,20 +345,26 @@ nsScriptLoader::StartLoad(nsScriptLoadRe
       nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, loadContext);
 
   // Set the initiator type
   nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
   if (timedChannel) {
     timedChannel->SetInitiatorType(NS_LITERAL_STRING("script"));
   }
 
-  RefPtr<ContextMediator> mediator = new ContextMediator(this, aRequest);
+  nsAutoPtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier;
+  if (!aRequest->mIntegrity.IsEmpty()) {
+    sriDataVerifier = new SRICheckDataVerifier(aRequest->mIntegrity, mDocument);
+  }
 
-  nsCOMPtr<nsIStreamLoader> loader;
-  rv = NS_NewStreamLoader(getter_AddRefs(loader), mediator);
+  RefPtr<nsScriptLoadHandler> handler =
+      new nsScriptLoadHandler(this, aRequest, sriDataVerifier.forget());
+
+  nsCOMPtr<nsIIncrementalStreamLoader> loader;
+  rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return channel->AsyncOpen2(loader);
 }
 
 bool
 nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
                                              nsIURI * const &aURI) const
@@ -1433,33 +1406,46 @@ nsScriptLoader::ConvertToUTF16(nsIChanne
   if (NS_FAILED(rv)) {
     js_free(aBufOut);
     aBufOut = nullptr;
     aLengthOut = 0;
   }
   return rv;
 }
 
-NS_IMETHODIMP
-nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
+nsresult
+nsScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
                                  nsISupports* aContext,
-                                 nsresult aStatus,
-                                 uint32_t aStringLen,
-                                 const uint8_t* aString)
+                                 nsresult aChannelStatus,
+                                 nsresult aSRIStatus,
+                                 mozilla::Vector<char16_t> &aString,
+                                 mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier)
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
-  nsresult rv = NS_ERROR_SRI_CORRUPT;
-  if (request->mIntegrity.IsEmpty() ||
-      NS_SUCCEEDED(SRICheck::VerifyIntegrity(request->mIntegrity, aLoader,
-                                             request->mCORSMode, aStringLen,
-                                             aString, mDocument))) {
-    rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, aString);
+  nsresult rv = NS_OK;
+  if (!request->mIntegrity.IsEmpty() &&
+      NS_SUCCEEDED((rv = aSRIStatus))) {
+    MOZ_ASSERT(aSRIDataVerifier);
+
+    nsCOMPtr<nsIRequest> channelRequest;
+    aLoader->GetRequest(getter_AddRefs(channelRequest));
+    nsCOMPtr<nsIChannel> channel;
+    channel = do_QueryInterface(channelRequest);
+
+    if (NS_FAILED(aSRIDataVerifier->Verify(request->mIntegrity, channel,
+                                           request->mCORSMode, mDocument))) {
+      rv = NS_ERROR_SRI_CORRUPT;
+    }
+  }
+
+  if (NS_SUCCEEDED(rv)) {
+    rv = PrepareLoadedRequest(request, aLoader, aChannelStatus, aString);
   }
 
   if (NS_FAILED(rv)) {
     /*
      * Handle script not loading error because source was a tracking URL.
      * We make a note of this script node by including it in a dedicated
      * array of blocked tracking nodes under its parent document.
      */
@@ -1492,26 +1478,22 @@ nsScriptLoader::OnStreamComplete(nsIStre
     } else if (mParserBlockingRequest == request) {
       mParserBlockingRequest = nullptr;
       UnblockParser(request);
       FireScriptAvailable(rv, request);
       ContinueParserAsync(request);
     } else {
       mPreloads.RemoveElement(request, PreloadRequestComparator());
     }
-    rv = NS_OK;
-  } else {
-    free(const_cast<uint8_t *>(aString));
-    rv = NS_SUCCESS_ADOPTED_DATA;
   }
 
   // Process our request and/or any pending ones
   ProcessPendingRequests();
 
-  return rv;
+  return NS_OK;
 }
 
 void
 nsScriptLoader::UnblockParser(nsScriptLoadRequest* aParserBlockingRequest)
 {
   aParserBlockingRequest->mElement->UnblockParser();
 }
 
@@ -1530,20 +1512,19 @@ nsScriptLoader::NumberOfProcessors()
   int32_t numProcs = PR_GetNumberOfProcessors();
   if (numProcs > 0)
     mNumberOfProcessors = numProcs;
   return mNumberOfProcessors;
 }
 
 nsresult
 nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
-                                     nsIStreamLoader* aLoader,
+                                     nsIIncrementalStreamLoader* aLoader,
                                      nsresult aStatus,
-                                     uint32_t aStringLen,
-                                     const uint8_t* aString)
+                                     mozilla::Vector<char16_t> &aString)
 {
   if (NS_FAILED(aStatus)) {
     return aStatus;
   }
 
   if (aRequest->IsCanceled()) {
     return NS_BINDING_ABORTED;
   }
@@ -1581,31 +1562,19 @@ nsScriptLoader::PrepareLoadedRequest(nsS
   // separate origin principal, so that it will treat our document's
   // principal as the origin principal
   if (aRequest->mCORSMode == CORS_NONE) {
     rv = nsContentUtils::GetSecurityManager()->
       GetChannelResultPrincipal(channel, getter_AddRefs(aRequest->mOriginPrincipal));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  if (aStringLen) {
-    // Check the charset attribute to determine script charset.
-    nsAutoString hintCharset;
-    if (!aRequest->IsPreload()) {
-      aRequest->mElement->GetScriptCharset(hintCharset);
-    } else {
-      nsTArray<PreloadInfo>::index_type i =
-        mPreloads.IndexOf(aRequest, 0, PreloadRequestComparator());
-      NS_ASSERTION(i != mPreloads.NoIndex, "Incorrect preload bookkeeping");
-      hintCharset = mPreloads[i].mCharset;
-    }
-    rv = ConvertToUTF16(channel, aString, aStringLen, hintCharset, mDocument,
-                        aRequest->mScriptTextBuf, aRequest->mScriptTextLength);
-
-    NS_ENSURE_SUCCESS(rv, rv);
+  if (!aString.empty()) {
+    aRequest->mScriptTextLength = aString.length();
+    aRequest->mScriptTextBuf = aString.extractRawBuffer();
   }
 
   // This assertion could fire errorously if we ran out of memory when
   // inserting the request in the array. However it's an unlikely case
   // so if you see this assertion it is likely something else that is
   // wrong, especially if you see it more than once.
   NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
                mLoadingAsyncRequests.Contains(aRequest) ||
@@ -1734,8 +1703,197 @@ nsScriptLoader::MaybeRemovedDeferRequest
   if (mDeferRequests.isEmpty() && mDocument &&
       mBlockingDOMContentLoaded) {
     mBlockingDOMContentLoaded = false;
     mDocument->UnblockDOMContentLoaded();
     return true;
   }
   return false;
 }
+
+//////////////////////////////////////////////////////////////
+//
+//////////////////////////////////////////////////////////////
+
+nsScriptLoadHandler::nsScriptLoadHandler(nsScriptLoader *aScriptLoader,
+                                         nsScriptLoadRequest *aRequest,
+                                         mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier)
+  : mScriptLoader(aScriptLoader),
+    mRequest(aRequest),
+    mSRIDataVerifier(aSRIDataVerifier),
+    mSRIStatus(NS_OK),
+    mDecoder(),
+    mBuffer()
+{}
+
+nsScriptLoadHandler::~nsScriptLoadHandler()
+{}
+
+NS_IMPL_ISUPPORTS(nsScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
+
+NS_IMETHODIMP
+nsScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
+                                       nsISupports* aContext,
+                                       uint32_t aDataLength,
+                                       const uint8_t* aData,
+                                       uint32_t *aConsumedLength)
+{
+  if (mRequest->IsCanceled()) {
+    // If request cancelled, ignore any incoming data.
+    *aConsumedLength = aDataLength;
+    return NS_OK;
+  }
+
+  if (!EnsureDecoder(aLoader, aData, aDataLength,
+                     /* aEndOfStream = */ false)) {
+    return NS_OK;
+  }
+
+  // Below we will/shall consume entire data chunk.
+  *aConsumedLength = aDataLength;
+
+  // Decoder has already been initialized. -- trying to decode all loaded bytes.
+  nsresult rv = TryDecodeRawData(aData, aDataLength,
+                                 /* aEndOfStream = */ false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If SRI is required for this load, appending new bytes to the hash.
+  if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+    mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+  }
+
+  return rv;
+}
+
+nsresult
+nsScriptLoadHandler::TryDecodeRawData(const uint8_t* aData,
+                                      uint32_t aDataLength,
+                                      bool aEndOfStream)
+{
+  int32_t srcLen = aDataLength;
+  const char* src = reinterpret_cast<const char *>(aData);
+  int32_t dstLen;
+  nsresult rv =
+    mDecoder->GetMaxLength(src, srcLen, &dstLen);
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  uint32_t haveRead = mBuffer.length();
+  uint32_t capacity = haveRead + dstLen;
+  if (!mBuffer.reserve(capacity)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  rv = mDecoder->Convert(src,
+                      &srcLen,
+                      mBuffer.begin() + haveRead,
+                      &dstLen);
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  haveRead += dstLen;
+  MOZ_ASSERT(haveRead <= capacity, "mDecoder produced more data than expected");
+  mBuffer.resizeUninitialized(haveRead);
+
+  return NS_OK;
+}
+
+bool
+nsScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
+                                   const uint8_t* aData,
+                                   uint32_t aDataLength,
+                                   bool aEndOfStream)
+{
+  // Check if decoder has already been created.
+  if (mDecoder) {
+    return true;
+  }
+
+  nsAutoCString charset;
+
+  // Determine if BOM check should be done.  This occurs either
+  // if end-of-stream has been reached, or at least 3 bytes have
+  // been read from input.
+  if (!aEndOfStream && (aDataLength < 3)) {
+    return false;
+  }
+
+  // Do BOM detection.
+  if (DetectByteOrderMark(aData, aDataLength, charset)) {
+    mDecoder = EncodingUtils::DecoderForEncoding(charset);
+    return true;
+  }
+
+  // BOM detection failed, check content stream for charset.
+  nsCOMPtr<nsIRequest> req;
+  nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
+  NS_ASSERTION(req, "StreamLoader's request went away prematurely");
+  NS_ENSURE_SUCCESS(rv, false);
+
+  nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
+
+  if (channel &&
+      NS_SUCCEEDED(channel->GetContentCharset(charset)) &&
+      EncodingUtils::FindEncodingForLabel(charset, charset)) {
+    mDecoder = EncodingUtils::DecoderForEncoding(charset);
+    return true;
+  }
+
+  // Check the hint charset from the script element or preload
+  // request.
+  nsAutoString hintCharset;
+  if (!mRequest->IsPreload()) {
+    mRequest->mElement->GetScriptCharset(hintCharset);
+  } else {
+    nsTArray<nsScriptLoader::PreloadInfo>::index_type i =
+      mScriptLoader->mPreloads.IndexOf(mRequest, 0,
+            nsScriptLoader::PreloadRequestComparator());
+
+    NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
+                 "Incorrect preload bookkeeping");
+    hintCharset = mScriptLoader->mPreloads[i].mCharset;
+  }
+
+  if (EncodingUtils::FindEncodingForLabel(hintCharset, charset)) {
+    mDecoder = EncodingUtils::DecoderForEncoding(charset);
+    return true;
+  }
+
+  // Get the charset from the charset of the document.
+  if (mScriptLoader->mDocument) {
+    charset = mScriptLoader->mDocument->GetDocumentCharacterSet();
+    mDecoder = EncodingUtils::DecoderForEncoding(charset);
+    return true;
+  }
+
+  // Curiously, there are various callers that don't pass aDocument. The
+  // fallback in the old code was ISO-8859-1, which behaved like
+  // windows-1252. Saying windows-1252 for clarity and for compliance
+  // with the Encoding Standard.
+  charset = "windows-1252";
+  mDecoder = EncodingUtils::DecoderForEncoding(charset);
+  return true;
+}
+
+NS_IMETHODIMP
+nsScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+                                      nsISupports* aContext,
+                                      nsresult aStatus,
+                                      uint32_t aDataLength,
+                                      const uint8_t* aData)
+{
+  if (!mRequest->IsCanceled()) {
+    DebugOnly<bool> encoderSet =
+      EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
+    MOZ_ASSERT(encoderSet);
+    DebugOnly<nsresult> rv = TryDecodeRawData(aData, aDataLength,
+                                              /* aEndOfStream = */ true);
+
+    // If SRI is required for this load, appending new bytes to the hash.
+    if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+      mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+    }
+  }
+
+  // we have to mediate and use mRequest.
+  return mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
+                                         mBuffer, mSRIDataVerifier);
+}
--- a/dom/base/nsScriptLoader.h
+++ b/dom/base/nsScriptLoader.h
@@ -7,26 +7,29 @@
 /*
  * A class that handles loading and evaluation of <script> elements.
  */
 
 #ifndef __nsScriptLoader_h__
 #define __nsScriptLoader_h__
 
 #include "nsCOMPtr.h"
+#include "nsIUnicodeDecoder.h"
 #include "nsIScriptElement.h"
 #include "nsCOMArray.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsIDocument.h"
-#include "nsIStreamLoader.h"
+#include "nsIIncrementalStreamLoader.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/dom/SRICheck.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/Vector.h"
 
 class nsScriptLoadRequestList;
 class nsIURI;
 
 namespace JS {
   class SourceBufferHolder;
 } // namespace JS
 
@@ -190,17 +193,17 @@ public:
     return Steal(getFirst());
   }
 };
 
 //////////////////////////////////////////////////////////////
 // Script loader implementation
 //////////////////////////////////////////////////////////////
 
-class nsScriptLoader final : public nsIStreamLoaderObserver
+class nsScriptLoader final : public nsISupports
 {
   class MOZ_STACK_CLASS AutoCurrentScriptUpdater
   {
   public:
     AutoCurrentScriptUpdater(nsScriptLoader* aScriptLoader,
                              nsIScriptElement* aCurrentScript)
       : mOldScript(aScriptLoader->mCurrentScript)
       , mScriptLoader(aScriptLoader)
@@ -212,23 +215,23 @@ class nsScriptLoader final : public nsIS
       mScriptLoader->mCurrentScript.swap(mOldScript);
     }
   private:
     nsCOMPtr<nsIScriptElement> mOldScript;
     nsScriptLoader* mScriptLoader;
   };
 
   friend class nsScriptRequestProcessor;
+  friend class nsScriptLoadHandler;
   friend class AutoCurrentScriptUpdater;
 
 public:
   explicit nsScriptLoader(nsIDocument* aDocument);
 
   NS_DECL_ISUPPORTS
-  NS_DECL_NSISTREAMLOADEROBSERVER
 
   /**
    * The loader maintains a weak reference to the document with
    * which it is initialized. This call forces the reference to
    * be dropped.
    */
   void DropDocumentReference()
   {
@@ -338,16 +341,28 @@ public:
    */
   static nsresult ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
                                  uint32_t aLength,
                                  const nsAString& aHintCharset,
                                  nsIDocument* aDocument,
                                  char16_t*& aBufOut, size_t& aLengthOut);
 
   /**
+   * Handle the completion of a stream.  This is called by the
+   * nsScriptLoadHandler object which observes the IncrementalStreamLoader
+   * loading the script.
+   */
+  nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+                            nsISupports* aContext,
+                            nsresult aChannelStatus,
+                            nsresult aSRIStatus,
+                            mozilla::Vector<char16_t> &aString,
+                            mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier);
+
+  /**
    * Processes any pending requests that are ready for processing.
    */
   void ProcessPendingRequests();
 
   /**
    * Check whether it's OK to load a script from aURI in
    * aDocument.
    */
@@ -484,20 +499,19 @@ private:
   already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
   void FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI &jsapi,
                                     nsScriptLoadRequest *aRequest,
                                     JS::Handle<JSObject *> aScopeChain,
                                     JS::CompileOptions *aOptions);
 
   uint32_t NumberOfProcessors();
   nsresult PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
-                                nsIStreamLoader* aLoader,
+                                nsIIncrementalStreamLoader* aLoader,
                                 nsresult aStatus,
-                                uint32_t aStringLen,
-                                const uint8_t* aString);
+                                mozilla::Vector<char16_t> &aString);
 
   void AddDeferRequest(nsScriptLoadRequest* aRequest);
   bool MaybeRemovedDeferRequests();
 
   nsIDocument* mDocument;                   // [WEAK]
   nsCOMArray<nsIScriptLoaderObserver> mObservers;
   nsScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;
   // mLoadingAsyncRequests holds async requests while they're loading; when they
@@ -533,16 +547,63 @@ private:
   uint32_t mBlockerCount;
   uint32_t mNumberOfProcessors;
   bool mEnabled;
   bool mDeferEnabled;
   bool mDocumentParsingDone;
   bool mBlockingDOMContentLoaded;
 };
 
+class nsScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver
+{
+public:
+  explicit nsScriptLoadHandler(nsScriptLoader* aScriptLoader,
+                               nsScriptLoadRequest *aRequest,
+                               mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
+
+private:
+  virtual ~nsScriptLoadHandler();
+
+  /*
+   * Try to decode some raw data.
+   */
+  nsresult TryDecodeRawData(const uint8_t* aData, uint32_t aDataLength,
+                            bool aEndOfStream);
+
+  /*
+   * Discover the charset by looking at the stream data, the script
+   * tag, and other indicators.  Returns true if charset has been
+   * discovered.
+   */
+  bool EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
+                     const uint8_t* aData, uint32_t aDataLength,
+                     bool aEndOfStream);
+
+  // ScriptLoader which will handle the parsed script.
+  RefPtr<nsScriptLoader>        mScriptLoader;
+
+  // The nsScriptLoadRequest for this load.
+  RefPtr<nsScriptLoadRequest>   mRequest;
+
+  // SRI data verifier.
+  nsAutoPtr<mozilla::dom::SRICheckDataVerifier> mSRIDataVerifier;
+
+  // Status of SRI data operations.
+  nsresult                      mSRIStatus;
+
+  // Unicode decoder for charset.
+  nsCOMPtr<nsIUnicodeDecoder>   mDecoder;
+
+  // Accumulated decoded char buffer.
+  mozilla::Vector<char16_t>     mBuffer;
+};
+
 class nsAutoScriptLoaderDisabler
 {
 public:
   explicit nsAutoScriptLoaderDisabler(nsIDocument* aDoc)
   {
     mLoader = aDoc->ScriptLoader();
     mWasEnabled = mLoader->GetEnabled();
     if (mWasEnabled) {
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2529,16 +2529,65 @@ CheckAllPermissions(JSContext* aCx, JSOb
     permMgr->TestPermissionFromWindow(window, *aPermissions, &permission);
     if (permission != nsIPermissionManager::ALLOW_ACTION) {
       return false;
     }
   } while (*(++aPermissions));
   return true;
 }
 
+bool
+IsNonExposedGlobal(JSContext* aCx, JSObject* aGlobal,
+                   uint32_t aNonExposedGlobals)
+{
+  MOZ_ASSERT(aNonExposedGlobals, "Why did we get called?");
+  MOZ_ASSERT((aNonExposedGlobals &
+              ~(GlobalNames::Window |
+                GlobalNames::BackstagePass |
+                GlobalNames::DedicatedWorkerGlobalScope |
+                GlobalNames::SharedWorkerGlobalScope |
+                GlobalNames::ServiceWorkerGlobalScope |
+                GlobalNames::WorkerDebuggerGlobalScope)) == 0,
+             "Unknown non-exposed global type");
+
+  const char* name = js::GetObjectClass(aGlobal)->name;
+
+  if ((aNonExposedGlobals & GlobalNames::Window) &&
+      !strcmp(name, "Window")) {
+    return true;
+  }
+
+  if ((aNonExposedGlobals & GlobalNames::BackstagePass) &&
+      !strcmp(name, "BackstagePass")) {
+    return true;
+  }
+
+  if ((aNonExposedGlobals & GlobalNames::DedicatedWorkerGlobalScope) &&
+      !strcmp(name, "DedicatedWorkerGlobalScope")) {
+    return true;
+  }
+
+  if ((aNonExposedGlobals & GlobalNames::SharedWorkerGlobalScope) &&
+      !strcmp(name, "SharedWorkerGlobalScope")) {
+    return true;
+  }
+
+  if ((aNonExposedGlobals & GlobalNames::ServiceWorkerGlobalScope) &&
+      !strcmp(name, "ServiceWorkerGlobalScope")) {
+    return true;
+  }
+
+  if ((aNonExposedGlobals & GlobalNames::WorkerDebuggerGlobalScope) &&
+      !strcmp(name, "WorkerDebuggerGlobalScopex")) {
+    return true;
+  }
+
+  return false;
+}
+
 void
 HandlePrerenderingViolation(nsPIDOMWindow* aWindow)
 {
   // Suspend the window and its workers, and its children too.
   aWindow->SuspendTimeouts();
 
   // Suspend event handling on the document
   nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -3155,26 +3155,16 @@ ConvertExceptionToPromise(JSContext* cx,
                           JS::MutableHandle<JS::Value> rval);
 
 #ifdef DEBUG
 void
 AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitinfo,
                                JS::Handle<JS::Value> aValue);
 #endif
 
-// Returns true if aObj's global has any of the permissions named in aPermissions
-// set to nsIPermissionManager::ALLOW_ACTION. aPermissions must be null-terminated.
-bool
-CheckAnyPermissions(JSContext* aCx, JSObject* aObj, const char* const aPermissions[]);
-
-// Returns true if aObj's global has all of the permissions named in aPermissions
-// set to nsIPermissionManager::ALLOW_ACTION. aPermissions must be null-terminated.
-bool
-CheckAllPermissions(JSContext* aCx, JSObject* aObj, const char* const aPermissions[]);
-
 // This function is called by the bindings layer for methods/getters/setters
 // that are not safe to be called in prerendering mode.  It checks to make sure
 // that the |this| object is not running in a global that is in prerendering
 // mode.  Otherwise, it aborts execution of timers and event handlers, and
 // returns false which gets converted to an uncatchable exception by the
 // bindings layer.
 bool
 EnforceNotInPrerendering(JSContext* aCx, JSObject* aObj);
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1901,26 +1901,36 @@ def getAvailableInTestFunc(obj):
     if availableIn[0] == "CertifiedApps":
         return "IsInCertifiedApp"
     raise TypeError("Unknown AvailableIn value '%s'" % availableIn[0])
 
 
 class MemberCondition:
     """
     An object representing the condition for a member to actually be
-    exposed.  Any of pref, func, and available can be None.  If not
-    None, they should be strings that have the pref name (for "pref")
-    or function name (for "func" and "available").
-    """
-    def __init__(self, pref, func, available=None, checkAnyPermissions=None, checkAllPermissions=None):
+    exposed.  Any of the arguments can be None.  If not
+    None, they should have the following types:
+
+    pref: The name of the preference.
+    func: The name of the function.
+    available: A string indicating where we should be available.
+    checkAnyPermissions: An integer index for the anypermissions_* to use.
+    checkAllPermissions: An integer index for the allpermissions_* to use.
+    nonExposedGlobals: A set of names of globals.  Can be empty, in which case
+                       it's treated the same way as None.
+    """
+    def __init__(self, pref=None, func=None, available=None,
+                 checkAnyPermissions=None, checkAllPermissions=None,
+                 nonExposedGlobals=None):
         assert pref is None or isinstance(pref, str)
         assert func is None or isinstance(func, str)
         assert available is None or isinstance(available, str)
         assert checkAnyPermissions is None or isinstance(checkAnyPermissions, int)
         assert checkAllPermissions is None or isinstance(checkAllPermissions, int)
+        assert nonExposedGlobals is None or isinstance(nonExposedGlobals, set)
         self.pref = pref
 
         def toFuncPtr(val):
             if val is None:
                 return "nullptr"
             return "&" + val
         self.func = toFuncPtr(func)
         self.available = toFuncPtr(available)
@@ -1928,21 +1938,30 @@ class MemberCondition:
             self.checkAnyPermissions = "nullptr"
         else:
             self.checkAnyPermissions = "anypermissions_%i" % checkAnyPermissions
         if checkAllPermissions is None:
             self.checkAllPermissions = "nullptr"
         else:
             self.checkAllPermissions = "allpermissions_%i" % checkAllPermissions
 
+        if nonExposedGlobals:
+            # Nonempty set
+            self.nonExposedGlobals = " | ".join(
+                map(lambda g: "GlobalNames::%s" % g,
+                    sorted(nonExposedGlobals)))
+        else:
+            self.nonExposedGlobals = "0"
+
     def __eq__(self, other):
         return (self.pref == other.pref and self.func == other.func and
                 self.available == other.available and
                 self.checkAnyPermissions == other.checkAnyPermissions and
-                self.checkAllPermissions == other.checkAllPermissions)
+                self.checkAllPermissions == other.checkAllPermissions and
+                self.nonExposedGlobals == other.nonExposedGlobals)
 
     def __ne__(self, other):
         return not self.__eq__(other)
 
 
 class PropertyDefiner:
     """
     A common superclass for defining things on prototype objects.
@@ -1995,23 +2014,44 @@ class PropertyDefiner:
             return None
         # It's a list of strings
         assert len(attr) == 1
         assert attr[0] is not None
         return attr[0]
 
     @staticmethod
     def getControllingCondition(interfaceMember, descriptor):
-        return MemberCondition(PropertyDefiner.getStringAttr(interfaceMember,
-                                                             "Pref"),
-                               PropertyDefiner.getStringAttr(interfaceMember,
-                                                             "Func"),
-                               getAvailableInTestFunc(interfaceMember),
-                               descriptor.checkAnyPermissionsIndicesForMembers.get(interfaceMember.identifier.name),
-                               descriptor.checkAllPermissionsIndicesForMembers.get(interfaceMember.identifier.name))
+        # We do a slightly complicated thing for exposure sets to deal nicely
+        # with the situation of an [Exposed=Window] thing on an interface
+        # exposed in workers that has a worker-specific descriptor.  In that
+        # situation, we already skip generation of the member entirely in the
+        # worker binding, and shouldn't need to check for the various worker
+        # scopes in the non-worker binding.
+        interface = descriptor.interface
+        nonExposureSet = interface.exposureSet - interfaceMember.exposureSet
+        # Skip getting the descriptor if we're just exposed everywhere or not
+        # looking at the non-worker descriptor.
+        if len(nonExposureSet) and not descriptor.workers:
+            workerProvider = descriptor.config.getDescriptorProvider(True)
+            workerDesc = workerProvider.getDescriptor(interface.identifier.name)
+            if workerDesc.workers:
+                # Just drop all the worker interface names from the
+                # nonExposureSet, since we know we'll have a mainthread global
+                # of some sort.
+                nonExposureSet.difference_update(interface.getWorkerExposureSet())
+
+        return MemberCondition(
+            PropertyDefiner.getStringAttr(interfaceMember,
+                                          "Pref"),
+            PropertyDefiner.getStringAttr(interfaceMember,
+                                          "Func"),
+            getAvailableInTestFunc(interfaceMember),
+            descriptor.checkAnyPermissionsIndicesForMembers.get(interfaceMember.identifier.name),
+            descriptor.checkAllPermissionsIndicesForMembers.get(interfaceMember.identifier.name),
+            nonExposureSet)
 
     def generatePrefableArray(self, array, name, specFormatter, specTerminator,
                               specType, getCondition, getDataTuple, doIdArrays):
         """
         This method generates our various arrays.
 
         array is an array of interface members as passed to generateArray
 
@@ -2039,29 +2079,30 @@ class PropertyDefiner:
         # pref control is added to members while still allowing us to define all
         # the members in the smallest number of JSAPI calls.
         assert len(array) != 0
         # So we won't put a specTerminator at the very front of the list:
         lastCondition = getCondition(array[0], self.descriptor)
         specs = []
         prefableSpecs = []
 
-        prefableTemplate = '  { true, %s, %s, %s, %s, &%s[%d] }'
+        prefableTemplate = '  { true, %s, %s, %s, %s, %s, &%s[%d] }'
         prefCacheTemplate = '&%s[%d].enabled'
 
         def switchToCondition(props, condition):
             # Remember the info about where our pref-controlled
             # booleans live.
             if condition.pref is not None:
                 props.prefCacheData.append(
                     (condition.pref,
                      prefCacheTemplate % (name, len(prefableSpecs))))
             # Set up pointers to the new sets of specs inside prefableSpecs
             prefableSpecs.append(prefableTemplate %
-                                 (condition.func,
+                                 (condition.nonExposedGlobals,
+                                  condition.func,
                                   condition.available,
                                   condition.checkAnyPermissions,
                                   condition.checkAllPermissions,
                                   name + "_specs", len(specs)))
 
         switchToCondition(self, lastCondition)
 
         for member in array:
@@ -2070,17 +2111,17 @@ class PropertyDefiner:
                 # Terminate previous list
                 specs.append(specTerminator)
                 # And switch to our new pref
                 switchToCondition(self, curCondition)
                 lastCondition = curCondition
             # And the actual spec
             specs.append(specFormatter(getDataTuple(member)))
         specs.append(specTerminator)
-        prefableSpecs.append("  { false, nullptr }")
+        prefableSpecs.append("  { false, 0, nullptr, nullptr, nullptr, nullptr, nullptr }")
 
         specType = "const " + specType
         arrays = fill(
             """
             static ${specType} ${name}_specs[] = {
             ${specs}
             };
 
@@ -2193,17 +2234,17 @@ class MethodDefiner(PropertyDefiner):
                                     "of whose ancestors are abstract: " +
                                     self.descriptor.name)
                 condition = "WantsQueryInterface<%s>::Enabled" % descriptor.nativeType
                 self.regular.append({
                     "name": 'QueryInterface',
                     "methodInfo": False,
                     "length": 1,
                     "flags": "0",
-                    "condition": MemberCondition(None, condition)
+                    "condition": MemberCondition(func=condition)
                 })
                 continue
 
             # Iterable methods should be enumerable, maplike/setlike methods
             # should not.
             isMaplikeOrSetlikeMethod = (m.isMaplikeOrSetlikeOrIterableMethod() and
                                         (m.maplikeOrSetlikeOrIterable.isMaplike() or
                                          m.maplikeOrSetlikeOrIterable.isSetlike()))
@@ -2247,33 +2288,33 @@ class MethodDefiner(PropertyDefiner):
                                 "maplike/setlike or aliased functions." %
                                 self.descriptor.interface.identifier.name)
             self.regular.append({
                 "name": "@@iterator",
                 "methodInfo": False,
                 "selfHostedName": "ArrayValues",
                 "length": 0,
                 "flags": "JSPROP_ENUMERATE",
-                "condition": MemberCondition(None, None)
+                "condition": MemberCondition()
             })
 
         # Output an @@iterator for generated iterator interfaces.  This should
         # not be necessary, but
         # https://bugzilla.mozilla.org/show_bug.cgi?id=1091945 means that
         # %IteratorPrototype%[@@iterator] is a broken puppy.
         if (not static and
             not unforgeable and
             descriptor.interface.isIteratorInterface()):
             self.regular.append({
                 "name": "@@iterator",
                 "methodInfo": False,
                 "selfHostedName": "IteratorIdentity",
                 "length": 0,
                 "flags": "0",
-                "condition": MemberCondition(None, None)
+                "condition": MemberCondition()
             })
 
         # Generate the maplike/setlike iterator, if one wasn't already
         # generated by a method. If we already have an @@iterator symbol, fail.
         if descriptor.interface.maplikeOrSetlikeOrIterable:
             if hasIterator(methods, self.regular):
                 raise TypeError("Cannot have maplike/setlike/iterable interface with "
                                 "other members that generate @@iterator "
@@ -2336,40 +2377,40 @@ class MethodDefiner(PropertyDefiner):
                 # Synthesize our valueOf method
                 self.regular.append({
                     "name": 'valueOf',
                     "nativeName": "UnforgeableValueOf",
                     "methodInfo": False,
                     "length": 0,
                     "flags": "JSPROP_ENUMERATE",  # readonly/permanent added
                                                   # automatically.
-                    "condition": MemberCondition(None, None)
+                    "condition": MemberCondition()
                 })
 
         if descriptor.interface.isJSImplemented():
             if static:
                 if descriptor.interface.hasInterfaceObject():
                     self.chrome.append({
                         "name": '_create',
                         "nativeName": ("%s::_Create" % descriptor.name),
                         "methodInfo": False,
                         "length": 2,
                         "flags": "0",
-                        "condition": MemberCondition(None, None)
+                        "condition": MemberCondition()
                     })
             else:
                 for m in clearableCachedAttrs(descriptor):
                     attrName = MakeNativeName(m.identifier.name)
                     self.chrome.append({
                         "name": "_clearCached%sValue" % attrName,
                         "nativeName": MakeJSImplClearCachedValueNativeName(m),
                         "methodInfo": False,
                         "length": "0",
                         "flags": "0",
-                        "condition": MemberCondition(None, None)
+                        "condition": MemberCondition()
                     })
 
         self.unforgeable = unforgeable
 
         if static:
             if not descriptor.interface.hasInterfaceObject():
                 # static methods go on the interface object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
--- a/dom/bindings/DOMJSClass.h
+++ b/dom/bindings/DOMJSClass.h
@@ -34,33 +34,71 @@ typedef bool
                        JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                        JS::MutableHandle<JSPropertyDescriptor> desc);
 
 typedef bool
 (* EnumerateOwnProperties)(JSContext* cx, JS::Handle<JSObject*> wrapper,
                            JS::Handle<JSObject*> obj,
                            JS::AutoIdVector& props);
 
+// Returns true if aObj's global has any of the permissions named in
+// aPermissions set to nsIPermissionManager::ALLOW_ACTION. aPermissions must be
+// null-terminated.
 bool
 CheckAnyPermissions(JSContext* aCx, JSObject* aObj, const char* const aPermissions[]);
 
+// Returns true if aObj's global has all of the permissions named in
+// aPermissions set to nsIPermissionManager::ALLOW_ACTION. aPermissions must be
+// null-terminated.
 bool
 CheckAllPermissions(JSContext* aCx, JSObject* aObj, const char* const aPermissions[]);
 
+// Returns true if the given global is of a type whose bit is set in
+// aNonExposedGlobals.
+bool
+IsNonExposedGlobal(JSContext* aCx, JSObject* aGlobal,
+                   uint32_t aNonExposedGlobals);
+
 struct ConstantSpec
 {
   const char* name;
   JS::Value value;
 };
 
 typedef bool (*PropertyEnabled)(JSContext* cx, JSObject* global);
 
+namespace GlobalNames {
+// The names of our possible globals.  These are the names of the actual
+// interfaces, not of the global names used to refer to them in IDL [Exposed]
+// annotations.
+static const uint32_t Window = 1u << 0;
+static const uint32_t BackstagePass = 1u << 1;
+static const uint32_t DedicatedWorkerGlobalScope = 1u << 2;
+static const uint32_t SharedWorkerGlobalScope = 1u << 3;
+static const uint32_t ServiceWorkerGlobalScope = 1u << 4;
+static const uint32_t WorkerDebuggerGlobalScope = 1u << 5;
+} // namespace GlobalNames
+
 template<typename T>
 struct Prefable {
   inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const {
+    // Reading "enabled" on a worker thread is technically undefined behavior,
+    // because it's written only on main threads, with no barriers of any sort.
+    // So we want to avoid doing that.  But we don't particularly want to make
+    // expensive NS_IsMainThread calls here.
+    //
+    // The good news is that "enabled" is only written for things that have a
+    // Pref annotation, and such things can never be exposed on non-Window
+    // globals; our IDL parser enforces that.  So as long as we check our
+    // exposure set before checking "enabled" we will be ok.
+    if (nonExposedGlobals &&
+        IsNonExposedGlobal(cx, js::GetGlobalForObjectCrossCompartment(obj),
+                           nonExposedGlobals)) {
+      return false;
+    }
     if (!enabled) {
       return false;
     }
     if (!enabledFunc && !availableFunc && !checkAnyPermissions && !checkAllPermissions) {
       return true;
     }
     if (enabledFunc &&
         !enabledFunc(cx, js::GetGlobalForObjectCrossCompartment(obj))) {
@@ -80,16 +118,18 @@ struct Prefable {
                              checkAllPermissions)) {
       return false;
     }
     return true;
   }
 
   // A boolean indicating whether this set of specs is enabled
   bool enabled;
+  // Bitmask of global names that we should not be exposed in.
+  uint32_t nonExposedGlobals;
   // A function pointer to a function that can say the property is disabled
   // even if "enabled" is set to true.  If the pointer is null the value of
   // "enabled" is used as-is unless availableFunc overrides.
   PropertyEnabled enabledFunc;
   // A function pointer to a function that can be used to disable a
   // property even if "enabled" is true and enabledFunc allowed.  This
   // is basically a hack to avoid having to codegen PropertyEnabled
   // implementations in case when we need to do two separate checks.
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -41,17 +41,19 @@ WebGLBuffer::BindTo(GLenum target)
     case LOCAL_GL_PIXEL_UNPACK_BUFFER:
     case LOCAL_GL_UNIFORM_BUFFER:
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
         mContent = Kind::OtherData;
         break;
 
     case LOCAL_GL_COPY_READ_BUFFER:
     case LOCAL_GL_COPY_WRITE_BUFFER:
-        /* Do nothing. Doesn't set the type of the buffer contents. */
+        if (mContent == Kind::Undefined) {
+          mContent = Kind::OtherData;
+        }
         break;
 
     default:
         MOZ_CRASH();
     }
 }
 
 void
--- a/dom/canvas/WebGLVertexArrayGL.cpp
+++ b/dom/canvas/WebGLVertexArrayGL.cpp
@@ -7,66 +7,56 @@
 
 #include "GLContext.h"
 #include "WebGLContext.h"
 
 namespace mozilla {
 
 WebGLVertexArrayGL::WebGLVertexArrayGL(WebGLContext* webgl)
     : WebGLVertexArray(webgl)
-#if defined(XP_LINUX)
     , mIsVAO(false)
-#endif
 { }
 
 WebGLVertexArrayGL::~WebGLVertexArrayGL()
 {
     DeleteOnce();
 }
 
 void
 WebGLVertexArrayGL::DeleteImpl()
 {
     mElementArrayBuffer = nullptr;
 
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteVertexArrays(1, &mGLName);
 
-#if defined(XP_LINUX)
     mIsVAO = false;
-#endif
 }
 
 void
 WebGLVertexArrayGL::BindVertexArrayImpl()
 {
     mContext->mBoundVertexArray = this;
     mContext->gl->fBindVertexArray(mGLName);
 
-#if defined(XP_LINUX)
     mIsVAO = true;
-#endif
 }
 
 void
 WebGLVertexArrayGL::GenVertexArray()
 {
     mContext->gl->fGenVertexArrays(1, &mGLName);
 }
 
 bool
 WebGLVertexArrayGL::IsVertexArrayImpl()
 {
-#if defined(XP_LINUX)
     gl::GLContext* gl = mContext->gl;
-    if (gl->WorkAroundDriverBugs() &&
-        gl->Vendor() == gl::GLVendor::VMware &&
-        gl->Renderer() == gl::GLRenderer::GalliumLlvmpipe)
+    if (gl->WorkAroundDriverBugs())
     {
         return mIsVAO;
     }
-#endif
 
     mContext->MakeContextCurrent();
     return mContext->gl->fIsVertexArray(mGLName) != 0;
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLVertexArrayGL.h
+++ b/dom/canvas/WebGLVertexArrayGL.h
@@ -20,20 +20,18 @@ public:
     virtual void BindVertexArrayImpl() override;
     virtual void GenVertexArray() override;
     virtual bool IsVertexArrayImpl() override;
 
 protected:
     explicit WebGLVertexArrayGL(WebGLContext* webgl);
     ~WebGLVertexArrayGL();
 
-#if defined(XP_LINUX)
     // Bug 1140459: Some drivers (including our test slaves!) don't
-    // give reasonable answers for IsRenderbuffer, maybe others.
+    // give reasonable answers for IsVertexArray, maybe others.
     //
     // So we track the `is a VAO` state ourselves.
     bool mIsVAO;
-#endif
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_VERTEX_ARRAY_GL_H_
--- a/dom/canvas/test/captureStream_common.js
+++ b/dom/canvas/test/captureStream_common.js
@@ -23,16 +23,17 @@ function CaptureStreamTestHelper(width, 
 }
 
 CaptureStreamTestHelper.prototype = {
   /* Predefined colors for use in the methods below. */
   black: { data: [0, 0, 0, 255], name: "black" },
   blackTransparent: { data: [0, 0, 0, 0], name: "blackTransparent" },
   green: { data: [0, 255, 0, 255], name: "green" },
   red: { data: [255, 0, 0, 255], name: "red" },
+  grey: { data: [128, 128, 128, 255], name: "grey" },
 
   /* Default element size for createAndAppendElement() */
   elemWidth: 100,
   elemHeight: 100,
 
   /*
    * Perform the drawing operation on each animation frame until stop is called
    * on the returned object.
@@ -92,17 +93,22 @@ CaptureStreamTestHelper.prototype = {
    * Returns a promise that resolves when the provided function |test|
    * returns true.
    */
   waitForPixel: function (video, offsetX, offsetY, test, timeout) {
     return new Promise(resolve => {
       const startTime = video.currentTime;
       CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
       var ontimeupdate = () => {
-        const pixelMatch = test(this.getPixel(video, offsetX, offsetY));
+        var pixelMatch = false;
+        try {
+          pixelMatch = test(this.getPixel(video, offsetX, offsetY));
+        } catch (NS_ERROR_NOT_AVAILABLE) {
+          info("Waiting for pixel but no video available");
+        }
         if (!pixelMatch &&
             (!timeout || video.currentTime < startTime + (timeout / 1000.0))) {
           // No match yet and,
           // No timeout (waiting indefinitely) or |timeout| has not passed yet.
           return;
         }
         video.removeEventListener("timeupdate", ontimeupdate);
         resolve(pixelMatch);
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -3,48 +3,109 @@
 /* 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 "ContentEventHandler.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
 #include "mozilla/dom/Selection.h"
 #include "nsCaret.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsCopySupport.h"
 #include "nsFocusManager.h"
 #include "nsFontMetrics.h"
 #include "nsFrameSelection.h"
 #include "nsIContentIterator.h"
 #include "nsIPresShell.h"
 #include "nsISelection.h"
 #include "nsISelectionController.h"
 #include "nsIFrame.h"
 #include "nsIObjectFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
+#include "nsQueryObject.h"
 #include "nsRange.h"
 #include "nsTextFragment.h"
 #include "nsTextFrame.h"
 #include "nsView.h"
 
 #include <algorithm>
 
 namespace mozilla {
 
 using namespace dom;
 using namespace widget;
 
 /******************************************************************/
 /* ContentEventHandler                                            */
 /******************************************************************/
 
+// NOTE
+//
+// ContentEventHandler *creates* ranges as following rules:
+// 1. Start of range:
+//   1.1. Cases: [textNode or text[Node or textNode[
+//        When text node is start of a range, start node is the text node and
+//        start offset is any number between 0 and the length of the text.
+//   1.2. Case: [<element>:
+//        When start of an element node is start of a range, start node is
+//        parent of the element and start offset is the element's index in the
+//        parent.
+//   1.3. Case: <element/>[
+//        When after an empty element node is start of a range, start node is
+//        parent of the element and start offset is the element's index in the
+//        parent + 1.
+//   1.4. Case: <element>[
+//        When start of a non-empty element is start of a range, start node is
+//        the element and start offset is 0.
+//   1.5. Case: <root>[
+//        When start of a range is 0 and there are no nodes causing text,
+//        start node is the root node and start offset is 0.
+//   1.6. Case: [</root>
+//        When start of a range is out of bounds, start node is the root node
+//        and start offset is number of the children.
+// 2. End of range:
+//   2.1. Cases: ]textNode or text]Node or textNode]
+//        When a text node is end of a range, end node is the text node and
+//        end offset is any number between 0 and the length of the text.
+//   2.2. Case: ]<element>
+//        When before an element node (meaning before the open tag of the
+//        element) is end of a range, end node is previous node causing text.
+//        Note that this case shouldn't be handled directly.  If rule 2.1 and
+//        2.3 are handled correctly, the loop with nsContentIterator shouldn't
+//        reach the element node since the loop should've finished already at
+//        handling the last node which caused some text.
+//   2.3. Case: <element>]
+//        When a line break is caused before a non-empty element node and it's
+//        end of a range, end node is the element and end offset is 0.
+//        (i.e., including open tag of the element)
+//   2.4. Cases: <element/>]
+//        When after an empty element node is end of a range, end node is
+//        parent of the element node and end offset is the element's index in
+//        the parent + 1.  (i.e., including close tag of the element or empty
+//        element)
+//   2.5. Case: ]</root>
+//        When end of a range is out of bounds, end node is the root node and
+//        end offset is number of the children.
+//
+// ContentEventHandler *treats* ranges as following additional rules:
+// 1. When the start node is an element node which doesn't have children,
+//    it includes a line break caused before itself (i.e., includes its open
+//    tag).  For example, if start position is { <br>, 0 }, the line break
+//    caused by <br> should be included into the flatten text.
+// 2. When the end node is an element node which doesn't have children,
+//    it includes the end (i.e., includes its close tag except empty element).
+//    Although, currently, any close tags don't cause line break, this also
+//    includes its open tag.  For example, if end position is { <br>, 0 }, the
+//    line break caused by the <br> should be included into the flatten text.
+
 ContentEventHandler::ContentEventHandler(nsPresContext* aPresContext)
   : mPresContext(aPresContext)
   , mPresShell(aPresContext->GetPresShell())
   , mSelection(nullptr)
   , mFirstSelectedRange(nullptr)
   , mRootContent(nullptr)
 {
 }
@@ -322,126 +383,215 @@ static uint32_t CountNewlinesInNativeLen
 
 /* static */ uint32_t
 ContentEventHandler::GetNativeTextLength(nsIContent* aContent,
                                          uint32_t aStartOffset,
                                          uint32_t aEndOffset)
 {
   MOZ_ASSERT(aEndOffset >= aStartOffset,
              "aEndOffset must be equals or larger than aStartOffset");
+  if (NS_WARN_IF(!aContent->IsNodeOfType(nsINode::eTEXT))) {
+    return 0;
+  }
   if (aStartOffset == aEndOffset) {
     return 0;
   }
   return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aEndOffset) -
            GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aStartOffset);
 }
 
 /* static */ uint32_t
 ContentEventHandler::GetNativeTextLength(nsIContent* aContent,
                                          uint32_t aMaxLength)
 {
+  if (NS_WARN_IF(!aContent->IsNodeOfType(nsINode::eTEXT))) {
+    return 0;
+  }
   return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aMaxLength);
 }
 
-static inline uint32_t
-GetBRLength(LineBreakType aLineBreakType)
+/* static */ uint32_t
+ContentEventHandler::GetNativeTextLengthBefore(nsIContent* aContent,
+                                               nsINode* aRootNode)
+{
+  if (NS_WARN_IF(aContent->IsNodeOfType(nsINode::eTEXT))) {
+    return 0;
+  }
+  return ShouldBreakLineBefore(aContent, aRootNode) ?
+           GetBRLength(LINE_BREAK_TYPE_NATIVE) : 0;
+}
+
+/* static inline */ uint32_t
+ContentEventHandler::GetBRLength(LineBreakType aLineBreakType)
 {
 #if defined(XP_WIN)
   // Length of \r\n
   return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1;
 #else
   return 1;
 #endif
 }
 
 /* static */ uint32_t
 ContentEventHandler::GetTextLength(nsIContent* aContent,
                                    LineBreakType aLineBreakType,
                                    uint32_t aMaxLength)
 {
-  if (aContent->IsNodeOfType(nsINode::eTEXT)) {
-    uint32_t textLengthDifference =
+  MOZ_ASSERT(aContent->IsNodeOfType(nsINode::eTEXT));
+
+  uint32_t textLengthDifference =
 #if defined(XP_WIN)
-      // On Windows, the length of a native newline ("\r\n") is twice the length
-      // of the XP newline ("\n"), so XP length is equal to the length of the
-      // native offset plus the number of newlines encountered in the string.
-      (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ?
-        CountNewlinesInXPLength(aContent, aMaxLength) : 0;
+    // On Windows, the length of a native newline ("\r\n") is twice the length
+    // of the XP newline ("\n"), so XP length is equal to the length of the
+    // native offset plus the number of newlines encountered in the string.
+    (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ?
+      CountNewlinesInXPLength(aContent, aMaxLength) : 0;
 #else
-      // On other platforms, the native and XP newlines are the same.
-      0;
+    // On other platforms, the native and XP newlines are the same.
+    0;
 #endif
 
-    const nsTextFragment* text = aContent->GetText();
-    if (!text) {
-      return 0;
-    }
-    uint32_t length = std::min(text->GetLength(), aMaxLength);
-    return length + textLengthDifference;
-  } else if (IsContentBR(aContent)) {
-    return GetBRLength(aLineBreakType);
+  const nsTextFragment* text = aContent->GetText();
+  if (!text) {
+    return 0;
   }
-  return 0;
+  uint32_t length = std::min(text->GetLength(), aMaxLength);
+  return length + textLengthDifference;
 }
 
 static uint32_t ConvertToXPOffset(nsIContent* aContent, uint32_t aNativeOffset)
 {
 #if defined(XP_WIN)
   // On Windows, the length of a native newline ("\r\n") is twice the length of
   // the XP newline ("\n"), so XP offset is equal to the length of the native
   // offset minus the number of newlines encountered in the string.
   return aNativeOffset - CountNewlinesInNativeLength(aContent, aNativeOffset);
 #else
   // On other platforms, the native and XP newlines are the same.
   return aNativeOffset;
 #endif
 }
 
-static nsresult GenerateFlatTextContent(nsRange* aRange,
-                                        nsAFlatString& aString,
-                                        LineBreakType aLineBreakType)
+/* static */ bool
+ContentEventHandler::ShouldBreakLineBefore(nsIContent* aContent,
+                                           nsINode* aRootNode)
 {
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  iter->Init(aRange);
+  // We don't need to append linebreak at the start of the root element.
+  if (aContent == aRootNode) {
+    return false;
+  }
+
+  // If it's not an HTML element (including other markup language's elements),
+  // we shouldn't insert like break before that for now.  Becoming this is a
+  // problem must be edge case.  E.g., when ContentEventHandler is used with
+  // MathML or SVG elements.
+  if (!aContent->IsHTMLElement()) {
+    return false;
+  }
+
+  // If the element is <br>, we need to check if the <br> is caused by web
+  // content.  Otherwise, i.e., it's caused by internal reason of Gecko,
+  // it shouldn't be exposed as a line break to flatten text.
+  if (aContent->IsHTMLElement(nsGkAtoms::br)) {
+    return IsContentBR(aContent);
+  }
 
+  // Note that ideally, we should refer the style of the primary frame of
+  // aContent for deciding if it's an inline.  However, it's difficult
+  // IMEContentObserver to notify IME of text change caused by style change.
+  // Therefore, currently, we should check only from the tag for now.
+  if (aContent->IsAnyOfHTMLElements(nsGkAtoms::a,
+                                    nsGkAtoms::abbr,
+                                    nsGkAtoms::acronym,
+                                    nsGkAtoms::b,
+                                    nsGkAtoms::bdi,
+                                    nsGkAtoms::bdo,
+                                    nsGkAtoms::big,
+                                    nsGkAtoms::cite,
+                                    nsGkAtoms::code,
+                                    nsGkAtoms::data,
+                                    nsGkAtoms::del,
+                                    nsGkAtoms::dfn,
+                                    nsGkAtoms::em,
+                                    nsGkAtoms::font,
+                                    nsGkAtoms::i,
+                                    nsGkAtoms::ins,
+                                    nsGkAtoms::kbd,
+                                    nsGkAtoms::mark,
+                                    nsGkAtoms::s,
+                                    nsGkAtoms::samp,
+                                    nsGkAtoms::small,
+                                    nsGkAtoms::span,
+                                    nsGkAtoms::strike,
+                                    nsGkAtoms::strong,
+                                    nsGkAtoms::sub,
+                                    nsGkAtoms::sup,
+                                    nsGkAtoms::time,
+                                    nsGkAtoms::tt,
+                                    nsGkAtoms::u,
+                                    nsGkAtoms::var)) {
+    return false;
+  }
+
+  // If the element is unknown element, we shouldn't insert line breaks before
+  // it since unknown elements should be ignored.
+  RefPtr<HTMLUnknownElement> unknownHTMLElement = do_QueryObject(aContent);
+  return !unknownHTMLElement;
+}
+
+nsresult
+ContentEventHandler::GenerateFlatTextContent(nsRange* aRange,
+                                             nsAFlatString& aString,
+                                             LineBreakType aLineBreakType)
+{
   NS_ASSERTION(aString.IsEmpty(), "aString must be empty string");
 
+  if (aRange->Collapsed()) {
+    return NS_OK;
+  }
+
   nsINode* startNode = aRange->GetStartParent();
-  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
   nsINode* endNode = aRange->GetEndParent();
-  NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
+  if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
+    return NS_ERROR_FAILURE;
+  }
 
   if (startNode == endNode && startNode->IsNodeOfType(nsINode::eTEXT)) {
-    nsIContent* content = static_cast<nsIContent*>(startNode);
+    nsIContent* content = startNode->AsContent();
     AppendSubString(aString, content, aRange->StartOffset(),
                     aRange->EndOffset() - aRange->StartOffset());
     ConvertToNativeNewlines(aString);
     return NS_OK;
   }
 
+  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
+  nsresult rv = iter->Init(aRange);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node) {
+    if (NS_WARN_IF(!node)) {
       break;
     }
-    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
+    if (!node->IsContent()) {
       continue;
     }
-    nsIContent* content = static_cast<nsIContent*>(node);
+    nsIContent* content = node->AsContent();
 
     if (content->IsNodeOfType(nsINode::eTEXT)) {
       if (content == startNode) {
         AppendSubString(aString, content, aRange->StartOffset(),
                         content->TextLength() - aRange->StartOffset());
       } else if (content == endNode) {
         AppendSubString(aString, content, 0, aRange->EndOffset());
       } else {
         AppendString(aString, content);
       }
-    } else if (IsContentBR(content)) {
+    } else if (ShouldBreakLineBefore(content, mRootContent)) {
       aString.Append(char16_t('\n'));
     }
   }
   if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
     ConvertToNativeNewlines(aString);
   }
   return NS_OK;
 }
@@ -455,29 +605,33 @@ AppendFontRange(nsTArray<FontRange>& aFo
 }
 
 /* static */ uint32_t
 ContentEventHandler::GetTextLengthInRange(nsIContent* aContent,
                                           uint32_t aXPStartOffset,
                                           uint32_t aXPEndOffset,
                                           LineBreakType aLineBreakType)
 {
+  MOZ_ASSERT(aContent->IsNodeOfType(nsINode::eTEXT));
+
   return aLineBreakType == LINE_BREAK_TYPE_NATIVE ?
     GetNativeTextLength(aContent, aXPStartOffset, aXPEndOffset) :
     aXPEndOffset - aXPStartOffset;
 }
 
 /* static */ void
 ContentEventHandler::AppendFontRanges(FontRangeArray& aFontRanges,
                                       nsIContent* aContent,
                                       int32_t aBaseOffset,
                                       int32_t aXPStartOffset,
                                       int32_t aXPEndOffset,
                                       LineBreakType aLineBreakType)
 {
+  MOZ_ASSERT(aContent->IsNodeOfType(nsINode::eTEXT));
+
   nsIFrame* frame = aContent->GetPrimaryFrame();
   if (!frame) {
     // It is a non-rendered content, create an empty range for it.
     AppendFontRange(aFontRanges, aBaseOffset);
     return;
   }
 
   int32_t baseOffset = aBaseOffset;
@@ -547,34 +701,42 @@ ContentEventHandler::AppendFontRanges(Fo
       baseOffset += GetTextLengthInRange(
         aContent, lastXPEndOffset, frameXPEnd, aLineBreakType);
     }
 
     curr = next;
   }
 }
 
-/* static */ nsresult
+nsresult
 ContentEventHandler::GenerateFlatFontRanges(nsRange* aRange,
                                             FontRangeArray& aFontRanges,
                                             uint32_t& aLength,
                                             LineBreakType aLineBreakType)
 {
   MOZ_ASSERT(aFontRanges.IsEmpty(), "aRanges must be empty array");
 
+  if (aRange->Collapsed()) {
+    return NS_OK;
+  }
+
   nsINode* startNode = aRange->GetStartParent();
   nsINode* endNode = aRange->GetEndParent();
-  if (NS_WARN_IF(!startNode || !endNode)) {
+  if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
     return NS_ERROR_FAILURE;
   }
 
   // baseOffset is the flattened offset of each content node.
   int32_t baseOffset = 0;
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  for (iter->Init(aRange); !iter->IsDone(); iter->Next()) {
+  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
+  nsresult rv = iter->Init(aRange);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
     if (!node->IsContent()) {
       continue;
     }
     nsIContent* content = node->AsContent();
@@ -582,17 +744,17 @@ ContentEventHandler::GenerateFlatFontRan
     if (content->IsNodeOfType(nsINode::eTEXT)) {
       int32_t startOffset = content != startNode ? 0 : aRange->StartOffset();
       int32_t endOffset = content != endNode ?
         content->TextLength() : aRange->EndOffset();
       AppendFontRanges(aFontRanges, content, baseOffset,
                        startOffset, endOffset, aLineBreakType);
       baseOffset += GetTextLengthInRange(content, startOffset, endOffset,
                                          aLineBreakType);
-    } else if (IsContentBR(content)) {
+    } else if (ShouldBreakLineBefore(content, mRootContent)) {
       if (aFontRanges.IsEmpty()) {
         MOZ_ASSERT(baseOffset == 0);
         FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset);
         nsIFrame* frame = content->GetPrimaryFrame();
         if (frame) {
           const nsFont& font = frame->GetParent()->StyleFont()->mFont;
           const FontFamilyList& fontList = font.fontlist;
           const FontFamilyName& fontName = fontList.IsEmpty() ?
@@ -667,115 +829,231 @@ ContentEventHandler::SetRangeFromFlatTex
                                                 LineBreakType aLineBreakType,
                                                 bool aExpandToClusterBoundaries,
                                                 uint32_t* aNewOffset)
 {
   if (aNewOffset) {
     *aNewOffset = aOffset;
   }
 
+  // Special case like <br contenteditable>
+  if (!mRootContent->HasChildren()) {
+    nsresult rv = aRange->SetStart(mRootContent, 0);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    rv = aRange->SetEnd(mRootContent, 0);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
   nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
   nsresult rv = iter->Init(mRootContent);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   uint32_t offset = 0;
   uint32_t endOffset = aOffset + aLength;
   bool startSet = false;
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node) {
+    if (NS_WARN_IF(!node)) {
       break;
     }
-    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
+    // FYI: mRootContent shouldn't cause any text. So, we can skip it simply.
+    if (node == mRootContent || !node->IsContent()) {
       continue;
     }
-    nsIContent* content = static_cast<nsIContent*>(node);
+    nsIContent* content = node->AsContent();
 
-    uint32_t textLength = GetTextLength(content, aLineBreakType);
+    uint32_t textLength =
+      content->IsNodeOfType(nsINode::eTEXT) ?
+        GetTextLength(content, aLineBreakType) :
+        (ShouldBreakLineBefore(content, mRootContent) ?
+           GetBRLength(aLineBreakType) : 0);
     if (!textLength) {
       continue;
     }
 
-    if (offset <= aOffset && aOffset < offset + textLength) {
-      uint32_t xpOffset;
-      if (!content->IsNodeOfType(nsINode::eTEXT)) {
-        xpOffset = 0;
-      } else {
-        xpOffset = aOffset - offset;
+    // When the start offset is in between accumulated offset and the last
+    // offset of the node, the node is the start node of the range.
+    if (!startSet && aOffset <= offset + textLength) {
+      nsINode* startNode = nullptr;
+      int32_t startNodeOffset = -1;
+      if (content->IsNodeOfType(nsINode::eTEXT)) {
+        // Rule #1.1: [textNode or text[Node or textNode[
+        uint32_t xpOffset = aOffset - offset;
         if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
           xpOffset = ConvertToXPOffset(content, xpOffset);
         }
-      }
 
-      if (aExpandToClusterBoundaries) {
-        uint32_t oldXPOffset = xpOffset;
-        rv = ExpandToClusterBoundary(content, false, &xpOffset);
-        NS_ENSURE_SUCCESS(rv, rv);
-        if (aNewOffset) {
-          // This is correct since a cluster shouldn't include line break.
-          *aNewOffset -= (oldXPOffset - xpOffset);
+        if (aExpandToClusterBoundaries) {
+          uint32_t oldXPOffset = xpOffset;
+          rv = ExpandToClusterBoundary(content, false, &xpOffset);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
+          if (aNewOffset) {
+            // This is correct since a cluster shouldn't include line break.
+            *aNewOffset -= (oldXPOffset - xpOffset);
+          }
+        }
+        startNode = content;
+        startNodeOffset = static_cast<int32_t>(xpOffset);
+      } else if (aOffset < offset + textLength) {
+        // Rule #1.2 [<element>
+        startNode = content->GetParent();
+        if (NS_WARN_IF(!startNode)) {
+          return NS_ERROR_FAILURE;
+        }
+        startNodeOffset = startNode->IndexOf(content);
+        if (NS_WARN_IF(startNodeOffset == -1)) {
+          // The content is being removed from the parent!
+          return NS_ERROR_FAILURE;
         }
+      } else if (!content->HasChildren()) {
+        // Rule #1.3: <element/>[
+        startNode = content->GetParent();
+        if (NS_WARN_IF(!startNode)) {
+          return NS_ERROR_FAILURE;
+        }
+        startNodeOffset = startNode->IndexOf(content) + 1;
+        if (NS_WARN_IF(startNodeOffset == 0)) {
+          // The content is being removed from the parent!
+          return NS_ERROR_FAILURE;
+        }
+      } else {
+        // Rule #1.4: <element>[
+        startNode = content;
+        startNodeOffset = 0;
       }
-
-      rv = aRange->SetStart(content, int32_t(xpOffset));
-      NS_ENSURE_SUCCESS(rv, rv);
+      NS_ASSERTION(startNode, "startNode must not be nullptr");
+      NS_ASSERTION(startNodeOffset >= 0,
+                   "startNodeOffset must not be negative");
+      rv = aRange->SetStart(startNode, startNodeOffset);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
       startSet = true;
-      if (aLength == 0) {
-        // Ensure that the end offset and the start offset are same.
-        rv = aRange->SetEnd(content, int32_t(xpOffset));
-        NS_ENSURE_SUCCESS(rv, rv);
+
+      if (!aLength) {
+        rv = aRange->SetEnd(startNode, startNodeOffset);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
         return NS_OK;
       }
     }
+
+    // When the end offset is in the content, the node is the end node of the
+    // range.
     if (endOffset <= offset + textLength) {
-      nsINode* endNode = content;
-      uint32_t xpOffset;
+      MOZ_ASSERT(startSet,
+        "The start of the range should've been set already");
       if (content->IsNodeOfType(nsINode::eTEXT)) {
-        xpOffset = endOffset - offset;
+        // Rule #2.1: ]textNode or text]Node or textNode]
+        uint32_t xpOffset = endOffset - offset;
         if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
           xpOffset = ConvertToXPOffset(content, xpOffset);
         }
         if (aExpandToClusterBoundaries) {
           rv = ExpandToClusterBoundary(content, true, &xpOffset);
-          NS_ENSURE_SUCCESS(rv, rv);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
+        }
+        NS_ASSERTION(xpOffset <= INT32_MAX,
+          "The end node offset is too large");
+        rv = aRange->SetEnd(content, static_cast<int32_t>(xpOffset));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
         }
-      } else {
-        // Use first position of next node, because the end node is ignored
-        // by ContentIterator when the offset is zero.
-        xpOffset = 0;
-        iter->Next();
-        if (iter->IsDone()) {
-          break;
-        }
-        endNode = iter->GetCurrentNode();
+        return NS_OK;
+      }
+
+      if (endOffset == offset) {
+        // Rule #2.2: ]<element>
+        // NOTE: Please don't crash on release builds because it must be
+        //       overreaction but we shouldn't allow this bug when some
+        //       automated tests find this.
+        MOZ_ASSERT(false, "This case should've already been handled at "
+                          "the last node which caused some text");
+        return NS_ERROR_FAILURE;
       }
 
-      rv = aRange->SetEnd(endNode, int32_t(xpOffset));
-      NS_ENSURE_SUCCESS(rv, rv);
+      if (content->HasChildren() &&
+          ShouldBreakLineBefore(content, mRootContent)) {
+        // Rule #2.3: </element>]
+        rv = aRange->SetEnd(content, 0);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+        return NS_OK;
+      }
+
+      // Rule #2.4: <element/>]
+      nsINode* endNode = content->GetParent();
+      if (NS_WARN_IF(!endNode)) {
+        return NS_ERROR_FAILURE;
+      }
+      int32_t indexInParent = endNode->IndexOf(content);
+      if (NS_WARN_IF(indexInParent == -1)) {
+        // The content is being removed from the parent!
+        return NS_ERROR_FAILURE;
+      }
+      rv = aRange->SetEnd(endNode, indexInParent + 1);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
       return NS_OK;
     }
 
     offset += textLength;
   }
 
-  if (offset < aOffset) {
-    return NS_ERROR_FAILURE;
-  }
-
   if (!startSet) {
     MOZ_ASSERT(!mRootContent->IsNodeOfType(nsINode::eTEXT));
-    rv = aRange->SetStart(mRootContent, int32_t(mRootContent->GetChildCount()));
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (!offset) {
+      // Rule #1.5: <root>[</root>
+      // When there are no nodes causing text, the start of the DOM range
+      // should be start of the root node since clicking on such editor (e.g.,
+      // <div contenteditable><span></span></div>) sets caret to the start of
+      // the editor (i.e., before <span> in the example).
+      rv = aRange->SetStart(mRootContent, 0);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      if (!aLength) {
+        rv = aRange->SetEnd(mRootContent, 0);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+        return NS_OK;
+      }
+    } else {
+      // Rule #1.5: [</root>
+      rv = aRange->SetStart(mRootContent,
+                     static_cast<int32_t>(mRootContent->GetChildCount()));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
     if (aNewOffset) {
       *aNewOffset = offset;
     }
   }
-  rv = aRange->SetEnd(mRootContent, int32_t(mRootContent->GetChildCount()));
-  NS_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetEnd failed");
-  return rv;
+  // Rule #2.5: ]</root>
+  rv = aRange->SetEnd(mRootContent,
+                      static_cast<int32_t>(mRootContent->GetChildCount()));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 /* static */ LineBreakType
 ContentEventHandler::GetLineBreakType(WidgetQueryContentEvent* aEvent)
 {
   return GetLineBreakType(aEvent->mUseNativeLineBreak);
 }
 
@@ -844,18 +1122,18 @@ ContentEventHandler::OnQuerySelectedText
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   NS_ASSERTION(aEvent->mReply.mString.IsEmpty(),
                "The reply string must be empty");
 
   LineBreakType lineBreakType = GetLineBreakType(aEvent);
-  rv = GetFlatTextOffsetOfRange(mRootContent, mFirstSelectedRange,
-                                &aEvent->mReply.mOffset, lineBreakType);
+  rv = GetFlatTextLengthBefore(mFirstSelectedRange,
+                               &aEvent->mReply.mOffset, lineBreakType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsINode> anchorNode, focusNode;
   int32_t anchorOffset, focusOffset;
   if (mSelection->RangeCount()) {
     anchorNode = mSelection->GetAnchorNode();
     focusNode = mSelection->GetFocusNode();
     if (NS_WARN_IF(!anchorNode) || NS_WARN_IF(!focusNode)) {
@@ -1116,18 +1394,18 @@ ContentEventHandler::OnQueryCaretRect(Wi
   nsRect caretRect;
 
   // When the selection is collapsed and the queried offset is current caret
   // position, we should return the "real" caret rect.
   if (mSelection->IsCollapsed()) {
     nsIFrame* caretFrame = nsCaret::GetGeometry(mSelection, &caretRect);
     if (caretFrame) {
       uint32_t offset;
-      rv = GetFlatTextOffsetOfRange(mRootContent, mFirstSelectedRange, &offset,
-                                    lineBreakType);
+      rv = GetFlatTextLengthBefore(mFirstSelectedRange,
+                                   &offset, lineBreakType);
       NS_ENSURE_SUCCESS(rv, rv);
       if (offset == aEvent->mInput.mOffset) {
         rv = ConvertToRootRelativeOffset(caretFrame, caretRect);
         NS_ENSURE_SUCCESS(rv, rv);
         nscoord appUnitsPerDevPixel =
           caretFrame->PresContext()->AppUnitsPerDevPixel();
         aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
           caretRect.ToOutsidePixels(appUnitsPerDevPixel));
@@ -1296,18 +1574,19 @@ ContentEventHandler::OnQueryCharacterAtP
   if (!tentativeCaretOffsets.content ||
       !nsContentUtils::ContentIsDescendantOf(tentativeCaretOffsets.content,
                                              mRootContent)) {
     // There is no character nor tentative caret point at the point.
     aEvent->mSucceeded = true;
     return NS_OK;
   }
 
-  rv = GetFlatTextOffsetOfRange(mRootContent, tentativeCaretOffsets.content,
-                                tentativeCaretOffsets.offset,
+  rv = GetFlatTextLengthInRange(NodePosition(mRootContent, 0),
+                                NodePosition(tentativeCaretOffsets),
+                                mRootContent,
                                 &aEvent->mReply.mTentativeCaretOffset,
                                 GetLineBreakType(aEvent));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (targetFrame->GetType() != nsGkAtoms::textFrame) {
     // There is no character at the point but there is tentative caret point.
@@ -1320,20 +1599,23 @@ ContentEventHandler::OnQueryCharacterAtP
     "The point is inside a character bounding box.  Why tentative caret point "
     "hasn't been found?");
 
   nsTextFrame* textframe = static_cast<nsTextFrame*>(targetFrame);
   nsIFrame::ContentOffsets contentOffsets =
     textframe->GetCharacterOffsetAtFramePoint(ptInTarget);
   NS_ENSURE_TRUE(contentOffsets.content, NS_ERROR_FAILURE);
   uint32_t offset;
-  rv = GetFlatTextOffsetOfRange(mRootContent, contentOffsets.content,
-                                contentOffsets.offset, &offset,
+  rv = GetFlatTextLengthInRange(NodePosition(mRootContent, 0),
+                                NodePosition(contentOffsets),
+                                mRootContent, &offset,
                                 GetLineBreakType(aEvent));
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   WidgetQueryContentEvent textRect(true, eQueryTextRect, aEvent->widget);
   textRect.InitForQueryTextRect(offset, 1, aEvent->mUseNativeLineBreak);
   rv = OnQueryTextRect(&textRect);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(textRect.mSucceeded, NS_ERROR_FAILURE);
 
   // currently, we don't need to get the actual text.
@@ -1385,92 +1667,168 @@ ContentEventHandler::OnQueryDOMWidgetHit
     }
   }
 
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 /* static */ nsresult
-ContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                              nsINode* aNode,
-                                              int32_t aNodeOffset,
-                                              uint32_t* aOffset,
-                                              LineBreakType aLineBreakType)
+ContentEventHandler::GetFlatTextLengthInRange(
+                       const NodePosition& aStartPosition,
+                       const NodePosition& aEndPosition,
+                       nsIContent* aRootContent,
+                       uint32_t* aLength,
+                       LineBreakType aLineBreakType,
+                       bool aIsRemovingNode /* = false */)
 {
-  NS_ENSURE_STATE(aRootContent);
-  NS_ASSERTION(aOffset, "param is invalid");
-
-  RefPtr<nsRange> prev = new nsRange(aRootContent);
-  nsCOMPtr<nsIDOMNode> rootDOMNode(do_QueryInterface(aRootContent));
-  prev->SetStart(rootDOMNode, 0);
+  if (NS_WARN_IF(!aRootContent) || NS_WARN_IF(!aStartPosition.IsValid()) ||
+      NS_WARN_IF(!aEndPosition.IsValid()) || NS_WARN_IF(!aLength)) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
-  nsCOMPtr<nsIDOMNode> startDOMNode(do_QueryInterface(aNode));
-  NS_ASSERTION(startDOMNode, "startNode doesn't have nsIDOMNode");
-
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-
-  if (aNode->Length() >= static_cast<uint32_t>(aNodeOffset)) {
-    // Offset is within node's length; set end of range to that offset
-    prev->SetEnd(startDOMNode, aNodeOffset);
-    iter->Init(prev);
-  } else if (aNode != static_cast<nsINode*>(aRootContent)) {
-    // Offset is past node's length; set end of range to end of node
-    prev->SetEndAfter(startDOMNode);
-    iter->Init(prev);
-  } else {
-    // Offset is past the root node; set end of range to end of root node
-    iter->Init(aRootContent);
+  if (aStartPosition == aEndPosition) {
+    *aLength = 0;
+    return NS_OK;
   }
 
-  nsCOMPtr<nsINode> startNode = do_QueryInterface(startDOMNode);
-  nsINode* endNode = aNode;
+  // Don't create nsContentIterator instance until it's really necessary since
+  // destroying without initializing causes unexpected NS_ASSERTION() call.
+  nsCOMPtr<nsIContentIterator> iter;
+
+  // This may be called for retrieving the text of removed nodes.  Even in this
+  // case, the node thinks it's still in the tree because UnbindFromTree() will
+  // be called after here.  However, the node was already removed from the
+  // array of children of its parent.  So, be careful to handle this case.
+  if (aIsRemovingNode) {
+    DebugOnly<nsIContent*> parent = aStartPosition.mNode->GetParent();
+    MOZ_ASSERT(parent && parent->IndexOf(aStartPosition.mNode) == -1,
+      "At removing the node, the node shouldn't be in the array of children "
+      "of its parent");
+    MOZ_ASSERT(aStartPosition.mNode == aEndPosition.mNode,
+      "At removing the node, start and end node should be same");
+    MOZ_ASSERT(aStartPosition.mOffset == 0,
+      "When the node is being removed, the start offset should be 0");
+    MOZ_ASSERT(static_cast<uint32_t>(aEndPosition.mOffset) ==
+                 aEndPosition.mNode->GetChildCount(),
+      "When the node is being removed, the end offset should be child count");
+    iter = NS_NewPreContentIterator();
+    nsresult rv = iter->Init(aStartPosition.mNode);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    RefPtr<nsRange> prev = new nsRange(aRootContent);
+    nsresult rv = aStartPosition.SetToRangeStart(prev);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
 
-  *aOffset = 0;
+    // When the end position is immediately after non-root element's open tag,
+    // we need to include a line break caused by the open tag.
+    NodePosition endPosition;
+    if (aEndPosition.mNode != aRootContent &&
+        aEndPosition.IsImmediatelyAfterOpenTag()) {
+      if (aEndPosition.mNode->HasChildren()) {
+        // When the end node has some children, move the end position to the
+        // start of its first child.
+        nsINode* firstChild = aEndPosition.mNode->GetFirstChild();
+        if (NS_WARN_IF(!firstChild)) {
+          return NS_ERROR_FAILURE;
+        }
+        endPosition = NodePosition(firstChild, 0);
+      } else {
+        // When the end node is empty, move the end position after the node.
+        nsIContent* parentContent = aEndPosition.mNode->GetParent();
+        if (NS_WARN_IF(!parentContent)) {
+          return NS_ERROR_FAILURE;
+        }
+        int32_t indexInParent = parentContent->IndexOf(aEndPosition.mNode);
+        if (NS_WARN_IF(indexInParent < 0)) {
+          return NS_ERROR_FAILURE;
+        }
+        endPosition = NodePosition(parentContent, indexInParent + 1);
+      }
+    } else {
+      endPosition = aEndPosition;
+    }
+
+    if (endPosition.OffsetIsValid()) {
+      // Offset is within node's length; set end of range to that offset
+      rv = endPosition.SetToRangeEnd(prev);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      iter = NS_NewPreContentIterator();
+      rv = iter->Init(prev);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    } else if (endPosition.mNode != aRootContent) {
+      // Offset is past node's length; set end of range to end of node
+      rv = endPosition.SetToRangeEndAfter(prev);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      iter = NS_NewPreContentIterator();
+      rv = iter->Init(prev);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    } else {
+      // Offset is past the root node; set end of range to end of root node
+      iter = NS_NewPreContentIterator();
+      rv = iter->Init(aRootContent);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+  }
+
+  *aLength = 0;
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node) {
+    if (NS_WARN_IF(!node)) {
       break;
     }
-    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
+    if (!node->IsContent()) {
       continue;
     }
-    nsIContent* content = static_cast<nsIContent*>(node);
+    nsIContent* content = node->AsContent();
 
     if (node->IsNodeOfType(nsINode::eTEXT)) {
       // Note: our range always starts from offset 0
-      if (node == endNode) {
-        *aOffset += GetTextLength(content, aLineBreakType, aNodeOffset);
+      if (node == aEndPosition.mNode) {
+        *aLength += GetTextLength(content, aLineBreakType,
+                                  aEndPosition.mOffset);
       } else {
-        *aOffset += GetTextLength(content, aLineBreakType);
+        *aLength += GetTextLength(content, aLineBreakType);
       }
-    } else if (IsContentBR(content)) {
-#if defined(XP_WIN)
-      // On Windows, the length of the newline is 2.
-      *aOffset += (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1;
-#else
-      // On other platforms, the length of the newline is 1.
-      *aOffset += 1;
-#endif
+    } else if (ShouldBreakLineBefore(content, aRootContent)) {
+      // If the start position is start of this node but doesn't include the
+      // open tag, don't append the line break length.
+      if (node == aStartPosition.mNode && !aStartPosition.IsBeforeOpenTag()) {
+        continue;
+      }
+      *aLength += GetBRLength(aLineBreakType);
     }
   }
   return NS_OK;
 }
 
-/* static */ nsresult
-ContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                              nsRange* aRange,
-                                              uint32_t* aOffset,
-                                              LineBreakType aLineBreakType)
+nsresult
+ContentEventHandler::GetFlatTextLengthBefore(nsRange* aRange,
+                                             uint32_t* aOffset,
+                                             LineBreakType aLineBreakType)
 {
-  nsINode* startNode = aRange->GetStartParent();
-  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
-  int32_t startOffset = aRange->StartOffset();
-  return GetFlatTextOffsetOfRange(aRootContent, startNode, startOffset,
-                                  aOffset, aLineBreakType);
+  MOZ_ASSERT(aRange);
+  return GetFlatTextLengthInRange(
+           NodePosition(mRootContent, 0),
+           NodePositionBefore(aRange->GetStartParent(), aRange->StartOffset()),
+           mRootContent, aOffset, aLineBreakType);
 }
 
 nsresult
 ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aRange)
 {
   MOZ_ASSERT(aRange);
   MOZ_ASSERT(aRange->Collapsed());
 
@@ -1566,43 +1924,46 @@ ContentEventHandler::ConvertToRootRelati
 }
 
 static void AdjustRangeForSelection(nsIContent* aRoot,
                                     nsINode** aNode,
                                     int32_t* aNodeOffset)
 {
   nsINode* node = *aNode;
   int32_t nodeOffset = *aNodeOffset;
-  if (aRoot != node && node->GetParent()) {
-    if (node->IsNodeOfType(nsINode::eTEXT)) {
-      // When the offset is at the end of the text node, set it to after the
-      // text node, to make sure the caret is drawn on a new line when the last
-      // character of the text node is '\n'
-      int32_t nodeLength =
-        static_cast<int32_t>(static_cast<nsIContent*>(node)->TextLength());
-      MOZ_ASSERT(nodeOffset <= nodeLength, "Offset is past length of text node");
-      if (nodeOffset == nodeLength) {
-        node = node->GetParent();
-        nodeOffset = node->IndexOf(*aNode) + 1;
-      }
-    } else {
-      node = node->GetParent();
-      nodeOffset = node->IndexOf(*aNode) + (nodeOffset ? 1 : 0);
-    }
+  if (aRoot == node || NS_WARN_IF(!node->GetParent()) ||
+      !node->IsNodeOfType(nsINode::eTEXT)) {
+    return;
+  }
+
+  // When the offset is at the end of the text node, set it to after the
+  // text node, to make sure the caret is drawn on a new line when the last
+  // character of the text node is '\n' in <textarea>.
+  int32_t textLength =
+    static_cast<int32_t>(static_cast<nsIContent*>(node)->TextLength());
+  MOZ_ASSERT(nodeOffset <= textLength, "Offset is past length of text node");
+  if (nodeOffset != textLength) {
+    return;
   }
 
-  nsIContent* brContent = node->GetChildAt(nodeOffset - 1);
-  while (brContent && brContent->IsHTMLElement()) {
-    if (!brContent->IsHTMLElement(nsGkAtoms::br) || IsContentBR(brContent)) {
-      break;
-    }
-    brContent = node->GetChildAt(--nodeOffset - 1);
+  nsIContent* aRootParent = aRoot->GetParent();
+  if (NS_WARN_IF(!aRootParent)) {
+    return;
   }
-  *aNode = node;
-  *aNodeOffset = std::max(nodeOffset, 0);
+  // If the root node is not an anonymous div of <textarea>, we don't need to
+  // do this hack.  If you did this, ContentEventHandler couldn't distinguish
+  // if the range includes open tag of the next node in some cases, e.g.,
+  // textNode]<p></p> vs. textNode<p>]</p>
+  if (!aRootParent->IsHTMLElement(nsGkAtoms::textarea)) {
+    return;
+  }
+
+  *aNode = node->GetParent();
+  MOZ_ASSERT((*aNode)->IndexOf(node) != -1);
+  *aNodeOffset = (*aNode)->IndexOf(node) + 1;
 }
 
 nsresult
 ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent)
 {
   aEvent->mSucceeded = false;
 
   // Get selection to manipulate
--- a/dom/events/ContentEventHandler.h
+++ b/dom/events/ContentEventHandler.h
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ContentEventHandler_h_
 #define mozilla_ContentEventHandler_h_
 
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/Selection.h"
 #include "nsCOMPtr.h"
+#include "nsIFrame.h"
+#include "nsINode.h"
 #include "nsRange.h"
 
 class nsPresContext;
 
 struct nsRect;
 
 namespace mozilla {
 
@@ -76,45 +78,171 @@ protected:
 
   nsresult InitBasic();
   nsresult InitCommon();
 
 public:
   // FlatText means the text that is generated from DOM tree. The BR elements
   // are replaced to native linefeeds. Other elements are ignored.
 
-  // Get the offset in FlatText of the range. (also used by IMEContentObserver)
-  static nsresult GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                           nsINode* aNode,
-                                           int32_t aNodeOffset,
-                                           uint32_t* aOffset,
-                                           LineBreakType aLineBreakType);
-  static nsresult GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                           nsRange* aRange,
-                                           uint32_t* aOffset,
-                                           LineBreakType aLineBreakType);
+  // NodePosition stores a pair of node and offset in the node.
+  // When mNode is an element and mOffset is 0, the start position means after
+  // the open tag of mNode.
+  // This is useful to receive one or more sets of them instead of nsRange.
+  struct NodePosition
+  {
+    nsCOMPtr<nsINode> mNode;
+    int32_t mOffset;
+    // Only when mNode is an element node and mOffset is 0, mAfterOpenTag is
+    // referred.
+    bool mAfterOpenTag;
+
+    NodePosition()
+      : mOffset(-1)
+      , mAfterOpenTag(true)
+    {
+    }
+
+    NodePosition(nsINode* aNode, int32_t aOffset)
+      : mNode(aNode)
+      , mOffset(aOffset)
+      , mAfterOpenTag(true)
+    {
+    }
+
+    explicit NodePosition(const nsIFrame::ContentOffsets& aContentOffsets)
+      : mNode(aContentOffsets.content)
+      , mOffset(aContentOffsets.offset)
+      , mAfterOpenTag(true)
+    {
+    }
+
+  protected:
+    NodePosition(nsINode* aNode, int32_t aOffset, bool aAfterOpenTag)
+      : mNode(aNode)
+      , mOffset(aOffset)
+      , mAfterOpenTag(aAfterOpenTag)
+    {
+    }
+
+  public:
+    bool operator==(const NodePosition& aOther) const
+    {
+      return mNode == aOther.mNode &&
+             mOffset == aOther.mOffset &&
+             mAfterOpenTag == aOther.mAfterOpenTag;
+    }
+
+    bool IsValid() const
+    {
+      return mNode && mOffset >= 0;
+    }
+    bool OffsetIsValid() const
+    {
+      return IsValid() && static_cast<uint32_t>(mOffset) <= mNode->Length();
+    }
+    bool IsBeforeOpenTag() const
+    {
+      return IsValid() && mNode->IsElement() && !mOffset && !mAfterOpenTag;
+    }
+    bool IsImmediatelyAfterOpenTag() const
+    {
+      return IsValid() && mNode->IsElement() && !mOffset && mAfterOpenTag;
+    }
+    nsresult SetToRangeStart(nsRange* aRange) const
+    {
+      nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode));
+      return aRange->SetStart(domNode, mOffset);
+    }
+    nsresult SetToRangeEnd(nsRange* aRange) const
+    {
+      nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode));
+      return aRange->SetEnd(domNode, mOffset);
+    }
+    nsresult SetToRangeEndAfter(nsRange* aRange) const
+    {
+      nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode));
+      return aRange->SetEndAfter(domNode);
+    }
+  };
+
+  // NodePositionBefore isn't good name if mNode isn't an element node nor
+  // mOffset is not 0, though, when mNode is an element node and mOffset is 0,
+  // this is treated as before the open tag of mNode.
+  struct NodePositionBefore final : public NodePosition
+  {
+    NodePositionBefore(nsINode* aNode, int32_t aOffset)
+      : NodePosition(aNode, aOffset, false)
+    {
+    }
+  };
+
+  // Get the flatten text length in the range.
+  // @param aStartPosition      Start node and offset in the node of the range.
+  // @param aEndPosition        End node and offset in the node of the range.
+  // @param aRootContent        The root content of the editor or document.
+  //                            aRootContent won't cause any text including
+  //                            line breaks.
+  // @param aLength             The result of the flatten text length of the
+  //                            range.
+  // @param aLineBreakType      Whether this computes flatten text length with
+  //                            native line breakers on the platform or
+  //                            with XP line breaker (\n).
+  // @param aIsRemovingNode     Should be true only when this is called from
+  //                            nsIMutationObserver::ContentRemoved().
+  //                            When this is true, aStartPosition.mNode should
+  //                            be the root node of removing nodes and mOffset
+  //                            should be 0 and aEndPosition.mNode should be
+  //                            same as aStartPosition.mNode and mOffset should
+  //                            be number of the children of mNode.
+  static nsresult GetFlatTextLengthInRange(const NodePosition& aStartPosition,
+                                           const NodePosition& aEndPosition,
+                                           nsIContent* aRootContent,
+                                           uint32_t* aLength,
+                                           LineBreakType aLineBreakType,
+                                           bool aIsRemovingNode = false);
   // Computes the native text length between aStartOffset and aEndOffset of
-  // aContent.  Currently, this method supports only text node or br element
-  // for aContent.
+  // aContent.  aContent must be a text node.
   static uint32_t GetNativeTextLength(nsIContent* aContent,
                                       uint32_t aStartOffset,
                                       uint32_t aEndOffset);
-  // Get the native text length of a content node excluding any children
+  // Get the native text length of aContent.  aContent must be a text node.
   static uint32_t GetNativeTextLength(nsIContent* aContent,
                                       uint32_t aMaxLength = UINT32_MAX);
+  // Get the native text length which is inserted before aContent.
+  // aContent should be an element.
+  static uint32_t GetNativeTextLengthBefore(nsIContent* aContent,
+                                            nsINode* aRootNode);
+
+protected:
+  // Get the text length of aContent.  aContent must be a text node.
+  static uint32_t GetTextLength(nsIContent* aContent,
+                                LineBreakType aLineBreakType,
+                                uint32_t aMaxLength = UINT32_MAX);
   // Get the text length of a given range of a content node in
   // the given line break type.
   static uint32_t GetTextLengthInRange(nsIContent* aContent,
                                        uint32_t aXPStartOffset,
                                        uint32_t aXPEndOffset,
                                        LineBreakType aLineBreakType);
-protected:
-  static uint32_t GetTextLength(nsIContent* aContent,
-                                LineBreakType aLineBreakType,
-                                uint32_t aMaxLength = UINT32_MAX);
+  // Get the contents of aRange as plain text.
+  nsresult GenerateFlatTextContent(nsRange* aRange,
+                                   nsAFlatString& aString,
+                                   LineBreakType aLineBreakType);
+  // Get the text length before the start position of aRange.
+  nsresult GetFlatTextLengthBefore(nsRange* aRange,
+                                   uint32_t* aOffset,
+                                   LineBreakType aLineBreakType);
+  // Check if we should insert a line break before aContent.
+  // This should return false only when aContent is an html element which
+  // is typically used in a paragraph like <em>.
+  static bool ShouldBreakLineBefore(nsIContent* aContent,
+                                    nsINode* aRootNode);
+  // Get the line breaker length.
+  static inline uint32_t GetBRLength(LineBreakType aLineBreakType);
   static LineBreakType GetLineBreakType(WidgetQueryContentEvent* aEvent);
   static LineBreakType GetLineBreakType(WidgetSelectionEvent* aEvent);
   static LineBreakType GetLineBreakType(bool aUseNativeLineBreak);
   // Returns focused content (including its descendant documents).
   nsIContent* GetFocusedContent();
   // Returns true if the content is a plugin host.
   bool IsPlugin(nsIContent* aContent);
   // QueryContentRect() sets the rect of aContent's frame(s) to aEvent.
@@ -147,17 +275,17 @@ protected:
 
   typedef nsTArray<mozilla::FontRange> FontRangeArray;
   static void AppendFontRanges(FontRangeArray& aFontRanges,
                                nsIContent* aContent,
                                int32_t aBaseOffset,
                                int32_t aXPStartOffset,
                                int32_t aXPEndOffset,
                                LineBreakType aLineBreakType);
-  static nsresult GenerateFlatFontRanges(nsRange* aRange,
-                                         FontRangeArray& aFontRanges,
-                                         uint32_t& aLength,
-                                         LineBreakType aLineBreakType);
+  nsresult GenerateFlatFontRanges(nsRange* aRange,
+                                  FontRangeArray& aFontRanges,
+                                  uint32_t& aLength,
+                                  LineBreakType aLineBreakType);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ContentEventHandler_h_
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -5711,37 +5711,42 @@ EventStateManager::WheelPrefs::NeedToCom
 {
   Index index = GetIndexFor(aEvent);
   Init(index);
 
   return (mMultiplierX[index] != 1.0 && mMultiplierX[index] != -1.0) ||
          (mMultiplierY[index] != 1.0 && mMultiplierY[index] != -1.0);
 }
 
-bool
-EventStateManager::WheelPrefs::HasUserPrefsForDelta(WidgetWheelEvent* aEvent)
+void
+EventStateManager::WheelPrefs::GetUserPrefsForEvent(WidgetWheelEvent* aEvent,
+                                                    double* aOutMultiplierX,
+                                                    double* aOutMultiplierY)
 {
   Index index = GetIndexFor(aEvent);
   Init(index);
 
-  return mMultiplierX[index] != 1.0 ||
-         mMultiplierY[index] != 1.0;
+  *aOutMultiplierX = mMultiplierX[index];
+  *aOutMultiplierY = mMultiplierY[index];
 }
 
 bool
 EventStateManager::WheelEventIsScrollAction(WidgetWheelEvent* aEvent)
 {
   return aEvent->mMessage == eWheel &&
          WheelPrefs::GetInstance()->ComputeActionFor(aEvent) == WheelPrefs::ACTION_SCROLL;
 }
 
-bool
-EventStateManager::WheelEventNeedsDeltaMultipliers(WidgetWheelEvent* aEvent)
+void
+EventStateManager::GetUserPrefsForWheelEvent(WidgetWheelEvent* aEvent,
+                                             double* aOutMultiplierX,
+                                             double* aOutMultiplierY)
 {
-  return WheelPrefs::GetInstance()->HasUserPrefsForDelta(aEvent);
+  WheelPrefs::GetInstance()->GetUserPrefsForEvent(
+    aEvent, aOutMultiplierX, aOutMultiplierY);
 }
 
 bool
 EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedX(
                                  WidgetWheelEvent* aEvent)
 {
   Index index = GetIndexFor(aEvent);
   Init(index);
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -237,19 +237,20 @@ public:
   // Sets the full-screen event state on aElement to aIsFullScreen.
   static void SetFullScreenState(dom::Element* aElement, bool aIsFullScreen);
 
   static bool IsRemoteTarget(nsIContent* aTarget);
 
   // Returns true if the given WidgetWheelEvent will resolve to a scroll action.
   static bool WheelEventIsScrollAction(WidgetWheelEvent* aEvent);
 
-  // Returns true if user prefs for wheel deltas apply to the given
-  // WidgetWheelEvent.
-  static bool WheelEventNeedsDeltaMultipliers(WidgetWheelEvent* aEvent);
+  // Returns user-set multipliers for a wheel event.
+  static void GetUserPrefsForWheelEvent(WidgetWheelEvent* aEvent,
+                                        double* aOutMultiplierX,
+                                        double* aOutMultiplierY);
 
   // Returns whether or not a frame can be vertically scrolled with a mouse
   // wheel (as opposed to, say, a selection or touch scroll).
   static bool CanVerticallyScrollFrameWithWheel(nsIFrame* aFrame);
 
   // Holds the point in screen coords that a mouse event was dispatched to,
   // before we went into pointer lock mode. This is constantly updated while
   // the pointer is not locked, but we don't update it while the pointer is
@@ -444,17 +445,19 @@ protected:
      * user prefs.
      */
     void ApplyUserPrefsToDelta(WidgetWheelEvent* aEvent);
 
     /**
      * Returns whether or not ApplyUserPrefsToDelta() would change the delta
      * values of an event.
      */
-    bool HasUserPrefsForDelta(WidgetWheelEvent* aEvent);
+    void GetUserPrefsForEvent(WidgetWheelEvent* aEvent,
+                              double* aOutMultiplierX,
+                              double* aOutMultiplierY);
 
     /**
      * If ApplyUserPrefsToDelta() changed the delta values with customized
      * prefs, the overflowDelta values would be inflated.
      * CancelApplyingUserPrefsFromOverflowDelta() cancels the inflation.
      */
     void CancelApplyingUserPrefsFromOverflowDelta(WidgetWheelEvent* aEvent);
 
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -871,21 +871,23 @@ IMEContentObserver::CharacterDataChanged
 
   MOZ_ASSERT(removedLength >= 0,
              "mPreCharacterDataChangeLength should've been set by "
              "CharacterDataWillChange()");
 
   uint32_t offset = 0;
   // get offsets of change and fire notification
   nsresult rv =
-    ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContent,
-                                                  aInfo->mChangeStart,
-                                                  &offset,
-                                                  LINE_BREAK_TYPE_NATIVE);
-  NS_ENSURE_SUCCESS_VOID(rv);
+    ContentEventHandler::GetFlatTextLengthInRange(
+                           NodePosition(mRootContent, 0),
+                           NodePosition(aContent, aInfo->mChangeStart),
+                           mRootContent, &offset, LINE_BREAK_TYPE_NATIVE);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
 
   uint32_t newLength =
     ContentEventHandler::GetNativeTextLength(aContent, aInfo->mChangeStart,
                                              aInfo->mChangeStart +
                                                aInfo->mReplaceLength);
 
   uint32_t oldEnd = offset + static_cast<uint32_t>(removedLength);
   uint32_t newEnd = offset + newLength;
@@ -907,32 +909,34 @@ IMEContentObserver::NotifyContentAdded(n
       !mUpdatePreference.WantChangesCausedByComposition()) {
     return;
   }
 
   uint32_t offset = 0;
   nsresult rv = NS_OK;
   if (!mEndOfAddedTextCache.Match(aContainer, aStartIndex)) {
     mEndOfAddedTextCache.Clear();
-    rv = ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContainer,
-                                                       aStartIndex, &offset,
-                                                       LINE_BREAK_TYPE_NATIVE);
+    rv = ContentEventHandler::GetFlatTextLengthInRange(
+                                NodePosition(mRootContent, 0),
+                                NodePositionBefore(aContainer, aStartIndex),
+                                mRootContent, &offset, LINE_BREAK_TYPE_NATIVE);
     if (NS_WARN_IF(NS_FAILED((rv)))) {
       return;
     }
   } else {
     offset = mEndOfAddedTextCache.mFlatTextLength;
   }
 
   // get offset at the end of the last added node
-  nsIContent* childAtStart = aContainer->GetChildAt(aStartIndex);
   uint32_t addingLength = 0;
-  rv = ContentEventHandler::GetFlatTextOffsetOfRange(childAtStart, aContainer,
-                                                     aEndIndex, &addingLength,
-                                                     LINE_BREAK_TYPE_NATIVE);
+  rv = ContentEventHandler::GetFlatTextLengthInRange(
+                              NodePositionBefore(aContainer, aStartIndex),
+                              NodePosition(aContainer, aEndIndex),
+                              mRootContent, &addingLength,
+                              LINE_BREAK_TYPE_NATIVE);
   if (NS_WARN_IF(NS_FAILED((rv)))) {
     mEndOfAddedTextCache.Clear();
     return;
   }
 
   // If multiple lines are being inserted in an HTML editor, next call of
   // NotifyContentAdded() is for adding next node.  Therefore, caching the text
   // length can skip to compute the text length before the adding node and
@@ -983,75 +987,68 @@ IMEContentObserver::ContentRemoved(nsIDo
     return;
   }
 
   nsINode* containerNode = NODE_FROM(aContainer, aDocument);
 
   uint32_t offset = 0;
   nsresult rv = NS_OK;
   if (!mStartOfRemovingTextRangeCache.Match(containerNode, aIndexInContainer)) {
-    rv =
-      ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, containerNode,
-                                                    aIndexInContainer, &offset,
-                                                    LINE_BREAK_TYPE_NATIVE);
+    // At removing a child node of aContainer, we need the line break caused
+    // by open tag of aContainer.  Be careful when aIndexInContainer is 0.
+    rv = ContentEventHandler::GetFlatTextLengthInRange(
+                                NodePosition(mRootContent, 0),
+                                NodePosition(containerNode, aIndexInContainer),
+                                mRootContent, &offset, LINE_BREAK_TYPE_NATIVE);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mStartOfRemovingTextRangeCache.Clear();
       return;
     }
     mStartOfRemovingTextRangeCache.Cache(containerNode, aIndexInContainer,
                                          offset);
   } else {
     offset = mStartOfRemovingTextRangeCache.mFlatTextLength;
   }
 
   // get offset at the end of the deleted node
-  int32_t nodeLength =
-    aChild->IsNodeOfType(nsINode::eTEXT) ?
-      static_cast<int32_t>(aChild->TextLength()) :
-      std::max(static_cast<int32_t>(aChild->GetChildCount()), 1);
-  MOZ_ASSERT(nodeLength >= 0, "The node length is out of range");
   uint32_t textLength = 0;
-  rv = ContentEventHandler::GetFlatTextOffsetOfRange(aChild, aChild,
-                                                     nodeLength, &textLength,
-                                                     LINE_BREAK_TYPE_NATIVE);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mStartOfRemovingTextRangeCache.Clear();
-    return;
+  if (aChild->IsNodeOfType(nsINode::eTEXT)) {
+    textLength = ContentEventHandler::GetNativeTextLength(aChild);
+  } else {
+    uint32_t nodeLength = static_cast<int32_t>(aChild->GetChildCount());
+    rv = ContentEventHandler::GetFlatTextLengthInRange(
+                                NodePositionBefore(aChild, 0),
+                                NodePosition(aChild, nodeLength),
+                                mRootContent, &textLength,
+                                LINE_BREAK_TYPE_NATIVE, true);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mStartOfRemovingTextRangeCache.Clear();
+      return;
+    }
   }
 
   if (!textLength) {
     return;
   }
 
   TextChangeData data(offset, offset + textLength, offset,
                       causedByComposition, IsEditorComposing());
   MaybeNotifyIMEOfTextChange(data);
 }
 
-static nsIContent*
-GetContentBR(dom::Element* aElement)
-{
-  if (!aElement->IsNodeOfType(nsINode::eCONTENT)) {
-    return nullptr;
-  }
-  nsIContent* content = static_cast<nsIContent*>(aElement);
-  return content->IsHTMLElement(nsGkAtoms::br) ? content : nullptr;
-}
-
 void
 IMEContentObserver::AttributeWillChange(nsIDocument* aDocument,
                                         dom::Element* aElement,
                                         int32_t aNameSpaceID,
                                         nsIAtom* aAttribute,
                                         int32_t aModType,
                                         const nsAttrValue* aNewValue)
 {
-  nsIContent *content = GetContentBR(aElement);
-  mPreAttrChangeLength = content ?
-    ContentEventHandler::GetNativeTextLength(content) : 0;
+  mPreAttrChangeLength =
+    ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent);
 }
 
 void
 IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
                                      dom::Element* aElement,
                                      int32_t aNameSpaceID,
                                      nsIAtom* aAttribute,
                                      int32_t aModType,
@@ -1061,32 +1058,30 @@ IMEContentObserver::AttributeChanged(nsI
   mStartOfRemovingTextRangeCache.Clear();
 
   bool causedByComposition = IsEditorHandlingEventForComposition();
   if (!mTextChangeData.IsValid() && causedByComposition &&
       !mUpdatePreference.WantChangesCausedByComposition()) {
     return;
   }
 
-  nsIContent *content = GetContentBR(aElement);
-  if (!content) {
-    return;
-  }
-
   uint32_t postAttrChangeLength =
-    ContentEventHandler::GetNativeTextLength(content);
+    ContentEventHandler::GetNativeTextLengthBefore(aElement, mRootContent);
   if (postAttrChangeLength == mPreAttrChangeLength) {
     return;
   }
   uint32_t start;
   nsresult rv =
-    ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, content,
-                                                  0, &start,
-                                                  LINE_BREAK_TYPE_NATIVE);
-  NS_ENSURE_SUCCESS_VOID(rv);
+    ContentEventHandler::GetFlatTextLengthInRange(
+                           NodePosition(mRootContent, 0),
+                           NodePositionBefore(aElement, 0),
+                           mRootContent, &start, LINE_BREAK_TYPE_NATIVE);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
 
   TextChangeData data(start, start + mPreAttrChangeLength,
                       start + postAttrChangeLength, causedByComposition,
                       IsEditorComposing());
   MaybeNotifyIMEOfTextChange(data);
 }
 
 void
--- a/dom/events/IMEContentObserver.h
+++ b/dom/events/IMEContentObserver.h
@@ -35,16 +35,18 @@ class EventStateManager;
 class IMEContentObserver final : public nsISelectionListener
                                , public nsStubMutationObserver
                                , public nsIReflowObserver
                                , public nsIScrollObserver
                                , public nsSupportsWeakReference
                                , public nsIEditorObserver
 {
 public:
+  typedef ContentEventHandler::NodePosition NodePosition;
+  typedef ContentEventHandler::NodePositionBefore NodePositionBefore;
   typedef widget::IMENotification::SelectionChangeData SelectionChangeData;
   typedef widget::IMENotification::TextChangeData TextChangeData;
   typedef widget::IMENotification::TextChangeDataBase TextChangeDataBase;
   typedef widget::IMEMessage IMEMessage;
 
   IMEContentObserver();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
--- a/dom/events/WheelHandlingHelper.cpp
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -16,16 +16,17 @@
 #include "nsIDocument.h"
 #include "nsIPresShell.h"
 #include "nsIScrollableFrame.h"
 #include "nsITimer.h"
 #include "nsPluginFrame.h"
 #include "nsPresContext.h"
 #include "prtime.h"
 #include "Units.h"
+#include "AsyncScrollBase.h"
 
 namespace mozilla {
 
 /******************************************************************/
 /* mozilla::DeltaValues                                           */
 /******************************************************************/
 
 DeltaValues::DeltaValues(WidgetWheelEvent* aEvent)
@@ -132,17 +133,17 @@ WheelTransaction::UpdateTransaction(Widg
     OnFailToScrollTarget();
     // We should not modify the transaction state when the view will not be
     // scrolled actually.
     return false;
   }
 
   SetTimeout();
 
-  if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeout)) {
+  if (sScrollSeriesCounter != 0 && OutOfTime(sTime, kScrollSeriesTimeoutMs)) {
     sScrollSeriesCounter = 0;
   }
   sScrollSeriesCounter++;
 
   // We should use current time instead of WidgetEvent.time.
   // 1. Some events doesn't have the correct creation time.
   // 2. If the computer runs slowly by other processes eating the CPU resource,
   //    the event creation time doesn't keep real time.
@@ -378,24 +379,19 @@ WheelTransaction::AccelerateWheelDelta(W
       result.deltaY = ComputeAcceleratedWheelDelta(result.deltaY, factor);
     }
   }
 
   return result;
 }
 
 /* static */ double
-WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
-                                               int32_t aFactor)
+WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor)
 {
-  if (aDelta == 0.0) {
-    return 0;
-  }
-
-  return (aDelta * sScrollSeriesCounter * (double)aFactor / 10);
+  return mozilla::ComputeAcceleratedWheelDelta(aDelta, sScrollSeriesCounter, aFactor);
 }
 
 /* static */ int32_t
 WheelTransaction::GetAccelerationStart()
 {
   return Preferences::GetInt("mousewheel.acceleration.start", -1);
 }
 
--- a/dom/events/WheelHandlingHelper.h
+++ b/dom/events/WheelHandlingHelper.h
@@ -146,18 +146,16 @@ public:
   static uint32_t GetTimeoutTime();
 
   static void OwnScrollbars(bool aOwn);
 
   static DeltaValues AccelerateWheelDelta(WidgetWheelEvent* aEvent,
                                           bool aAllowScrollSpeedOverride);
 
 protected:
-  static const uint32_t kScrollSeriesTimeout = 80; // in milliseconds
-
   static void BeginTransaction(nsIFrame* aTargetFrame,
                                WidgetWheelEvent* aEvent);
   // Be careful, UpdateTransaction may fire a DOM event, therefore, the target
   // frame might be destroyed in the event handler.
   static bool UpdateTransaction(WidgetWheelEvent* aEvent);
   static void MayEndTransaction();
 
   static nsIntPoint GetScreenPoint(WidgetGUIEvent* aEvent);
--- a/dom/html/HTMLAnchorElement.cpp
+++ b/dom/html/HTMLAnchorElement.cpp
@@ -153,42 +153,31 @@ HTMLAnchorElement::BindToTree(nsIDocumen
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Prefetch links
   nsIDocument* doc = GetComposedDoc();
   if (doc) {
     doc->RegisterPendingLinkUpdate(this);
-    if (nsHTMLDNSPrefetch::IsAllowed(OwnerDoc())) {
-      nsHTMLDNSPrefetch::PrefetchLow(this);
-    }
+    TryDNSPrefetch();
   }
 
   return rv;
 }
 
 void
 HTMLAnchorElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // Cancel any DNS prefetches
   // Note: Must come before ResetLinkState.  If called after, it will recreate
   // mCachedURI based on data that is invalid - due to a call to GetHostname.
+  CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
+                    HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
 
-  // If prefetch was deferred, clear flag and move on
-  if (HasFlag(HTML_ANCHOR_DNS_PREFETCH_DEFERRED))
-    UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
-  // Else if prefetch was requested, clear flag and send cancellation
-  else if (HasFlag(HTML_ANCHOR_DNS_PREFETCH_REQUESTED)) {
-    UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
-    // Possible that hostname could have changed since binding, but since this
-    // covers common cases, most DNS prefetch requests will be canceled
-    nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
-  }
-  
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   // Note, we need to use OwnerDoc() here, since GetComposedDoc() might
   // return null.
   nsIDocument* doc = OwnerDoc();
   if (doc) {
@@ -401,46 +390,61 @@ HTMLAnchorElement::SetAttr(int32_t aName
     // However, if we have a cached URI, we'll want to see if the value changed.
     else {
       nsAutoString val;
       GetHref(val);
       if (!val.Equals(aValue)) {
         reset = true;
       }
     }
+    if (reset) {
+      CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
+                        HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+    }
   }
 
   nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
                                               aValue, aNotify);
 
   // The ordering of the parent class's SetAttr call and Link::ResetLinkState
   // is important here!  The attribute is not set until SetAttr 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 (reset) {
     Link::ResetLinkState(!!aNotify, true);
+    if (IsInComposedDoc()) {
+      TryDNSPrefetch();
+    }
   }
 
   return rv;
 }
 
 nsresult
 HTMLAnchorElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify)
 {
+  bool href =
+    (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID);
+
+  if (href) {
+    CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
+                      HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+  }
+
   nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
                                                 aNotify);
 
   // 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) {
+  if (href) {
     Link::ResetLinkState(!!aNotify, false);
   }
 
   return rv;
 }
 
 bool
 HTMLAnchorElement::ParseAttribute(int32_t aNamespaceID,
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -23,16 +23,32 @@
 #include "nsINode.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsIURL.h"
 #include "nsPIDOMWindow.h"
 #include "nsReadableUtils.h"
 #include "nsStyleConsts.h"
 #include "nsUnicharUtils.h"
 
+#define LINK_ELEMENT_FLAG_BIT(n_) \
+  NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// Link element specific bits
+enum {
+  // Indicates that a DNS Prefetch has been requested from this Link element.
+  HTML_LINK_DNS_PREFETCH_REQUESTED = LINK_ELEMENT_FLAG_BIT(0),
+
+  // Indicates that a DNS Prefetch was added to the deferral queue
+  HTML_LINK_DNS_PREFETCH_DEFERRED =  LINK_ELEMENT_FLAG_BIT(1)
+};
+
+#undef LINK_ELEMENT_FLAG_BIT
+
+ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
+
 NS_IMPL_NS_NEW_HTML_ELEMENT(Link)
 
 namespace mozilla {
 namespace dom {
 
 HTMLLinkElement::HTMLLinkElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
   , Link(this)
@@ -121,16 +137,36 @@ HTMLLinkElement::GetItemValueText(DOMStr
 }
 
 void
 HTMLLinkElement::SetItemValueText(const nsAString& aValue)
 {
   SetHref(aValue);
 }
 
+void
+HTMLLinkElement::OnDNSPrefetchRequested()
+{
+  UnsetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
+  SetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
+}
+
+void
+HTMLLinkElement::OnDNSPrefetchDeferred()
+{
+  UnsetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
+  SetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
+}
+
+bool
+HTMLLinkElement::HasDeferredDNSPrefetchRequest()
+{
+  return HasFlag(HTML_LINK_DNS_PREFETCH_DEFERRED);
+}
+
 nsresult
 HTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                             nsIContent* aBindingParent,
                             bool aCompileEventHandlers)
 {
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
@@ -140,16 +176,19 @@ HTMLLinkElement::BindToTree(nsIDocument*
 
   // Link must be inert in ShadowRoot.
   if (aDocument && !GetContainingShadow()) {
     aDocument->RegisterPendingLinkUpdate(this);
   }
 
   if (IsInComposedDoc()) {
     UpdatePreconnect();
+    if (HasDNSPrefetchRel()) {
+      TryDNSPrefetch();
+    }
   }
 
   void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, update));
 
   void (HTMLLinkElement::*updateImport)() = &HTMLLinkElement::UpdateImport;
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, updateImport));
 
@@ -168,16 +207,22 @@ void
 HTMLLinkElement::LinkRemoved()
 {
   CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkRemoved"));
 }
 
 void
 HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
+  // Cancel any DNS prefetches
+  // Note: Must come before ResetLinkState.  If called after, it will recreate
+  // mCachedURI based on data that is invalid - due to a call to GetHostname.
+  CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
+                    HTML_LINK_DNS_PREFETCH_REQUESTED);
+
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   // If this is reinserted back into the document it will not be
   // from the parser.
   nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
 
@@ -317,16 +362,42 @@ HTMLLinkElement::UpdatePreconnect()
   if (owner) {
     nsCOMPtr<nsIURI> uri = GetHrefURI();
     if (uri) {
         owner->MaybePreconnect(uri, GetCORSMode());
     }
   }
 }
 
+bool
+HTMLLinkElement::HasDNSPrefetchRel()
+{
+  nsAutoString rel;
+  if (GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel)) {
+    return !!(ParseLinkTypes(rel, NodePrincipal()) &
+              nsStyleLinkElement::eDNS_PREFETCH);
+  }
+
+  return false;
+}
+
+nsresult
+HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                               nsAttrValueOrString* aValue, bool aNotify)
+{
+  if (aNameSpaceID == kNameSpaceID_None &&
+      (aName == nsGkAtoms::href || aName == nsGkAtoms::rel)) {
+    CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
+                      HTML_LINK_DNS_PREFETCH_REQUESTED);
+  }
+
+  return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
+                                             aValue, aNotify);
+}
+
 nsresult
 HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                               const nsAttrValue* aValue, bool aNotify)
 {
   // It's safe to call ResetLinkState here because our new attr value has
   // already been set or unset.  ResetLinkState needs 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
@@ -363,16 +434,21 @@ HTMLLinkElement::AfterSetAttr(int32_t aN
 
       if (aName == nsGkAtoms::href) {
         UpdateImport();
         if (IsInComposedDoc()) {
           UpdatePreconnect();
         }
       }
 
+      if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) &&
+          HasDNSPrefetchRel() && IsInComposedDoc()) {
+        TryDNSPrefetch();
+      }
+
       UpdateStyleSheetInternal(nullptr, nullptr,
                                dropSheet ||
                                (aName == nsGkAtoms::title ||
                                 aName == nsGkAtoms::media ||
                                 aName == nsGkAtoms::type));
     }
   } else {
     // Since removing href or rel makes us no longer link to a
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -56,32 +56,39 @@ public:
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
+  virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                                 nsAttrValueOrString* aValue,
+                                 bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
                                 bool aNotify) override;
   virtual bool IsLink(nsIURI** aURI) const override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
 
   // Element
   virtual bool ParseAttribute(int32_t aNamespaceID,
                               nsIAtom* aAttribute,
                               const nsAString& aValue,
                               nsAttrValue& aResult) override;
   virtual void GetLinkTarget(nsAString& aTarget) override;
   virtual EventStates IntrinsicState() const override;
 
   void CreateAndDispatchEvent(nsIDocument* aDoc, const nsAString& aEventName);
 
+  virtual void OnDNSPrefetchDeferred() override;
+  virtual void OnDNSPrefetchRequested() override;
+  virtual bool HasDeferredDNSPrefetchRequest() override;
+
   // WebIDL
   bool Disabled();
   void SetDisabled(bool aDisabled);
   // XPCOM GetHref is fine.
   void SetHref(const nsAString& aHref, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::href, aHref, aRv);
   }
@@ -161,16 +168,19 @@ protected:
                                  nsAString& aType,
                                  nsAString& aMedia,
                                  bool* aIsScoped,
                                  bool* aIsAlternate) override;
 protected:
   // nsGenericHTMLElement
   virtual void GetItemValueText(DOMString& text) override;
   virtual void SetItemValueText(const nsAString& text) override;
+
+  bool HasDNSPrefetchRel();
+
   RefPtr<nsDOMTokenList > mRelList;
 private:
   RefPtr<ImportLoader> mImportLoader;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3417,25 +3417,17 @@ void HTMLMediaElement::MetadataLoaded(co
     // Dispatch a distinct 'encrypted' event for each initData we have.
     for (const auto& initData : mPendingEncryptedInitData.mInitDatas) {
       DispatchEncrypted(initData.mInitData, initData.mType);
     }
     mPendingEncryptedInitData.mInitDatas.Clear();
 #endif // MOZ_EME
   }
 
-  // If this element had a video track, but consists only of an audio track now,
-  // delete the VideoFrameContainer. This happens when the src is changed to an
-  // audio only file.
-  // Else update its dimensions.
-  if (!aInfo->HasVideo()) {
-    ResetState();
-  } else {
-    mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
-  }
+  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
 
   if (IsVideo() && aInfo->HasVideo()) {
     // We are a video element playing video so update the screen wakelock
     NotifyOwnerDocumentActivityChangedInternal();
   }
 
   if (mDefaultPlaybackStartPosition != 0.0) {
     SetCurrentTime(mDefaultPlaybackStartPosition);
--- a/dom/html/HTMLSelectElement.cpp
+++ b/dom/html/HTMLSelectElement.cpp
@@ -220,44 +220,43 @@ HTMLSelectElement::RemoveChildAt(uint32_
 
 
 void
 HTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions,
                                          int32_t aListIndex,
                                          int32_t aDepth,
                                          bool aNotify)
 {
+  MOZ_ASSERT(aDepth == 0 || aDepth == 1);
   int32_t insertIndex = aListIndex;
 
   HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
   if (optElement) {
     mOptions->InsertOptionAt(optElement, insertIndex);
     insertIndex++;
-  } else {
+  } else if (aDepth == 0) {
     // If it's at the top level, then we just found out there are non-options
     // at the top level, which will throw off the insert count
-    if (aDepth == 0) {
-      mNonOptionChildren++;
-    }
+    mNonOptionChildren++;
 
     // Deal with optgroups
     if (aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
       mOptGroupCount++;
 
       for (nsIContent* child = aOptions->GetFirstChild();
            child;
            child = child->GetNextSibling()) {
         optElement = HTMLOptionElement::FromContent(child);
         if (optElement) {
           mOptions->InsertOptionAt(optElement, insertIndex);
           insertIndex++;
         }
       }
     }
-  }
+  } // else ignore even if optgroup; we want to ignore nested optgroups.
 
   // Deal with the selected list
   if (insertIndex - aListIndex) {
     // Fix the currently selected index
     if (aListIndex <= mSelectedIndex) {
       mSelectedIndex += (insertIndex - aListIndex);
       SetSelectionChanged(true, aNotify);
     }
@@ -302,31 +301,30 @@ HTMLSelectElement::InsertOptionsIntoList
 }
 
 nsresult
 HTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions,
                                          int32_t aListIndex,
                                          int32_t aDepth,
                                          bool aNotify)
 {
+  MOZ_ASSERT(aDepth == 0 || aDepth == 1);
   int32_t numRemoved = 0;
 
   HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
   if (optElement) {
     if (mOptions->ItemAsOption(aListIndex) != optElement) {
       NS_ERROR("wrong option at index");
       return NS_ERROR_UNEXPECTED;
     }
     mOptions->RemoveOptionAt(aListIndex);
     numRemoved++;
-  } else {
+  } else if (aDepth == 0) {
     // Yay, one less artifact at the top level.
-    if (aDepth == 0) {
-      mNonOptionChildren--;
-    }
+    mNonOptionChildren--;
 
     // Recurse down deeper for options
     if (mOptGroupCount && aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
       mOptGroupCount--;
 
       for (nsIContent* child = aOptions->GetFirstChild();
           child;
           child = child->GetNextSibling()) {
@@ -336,17 +334,17 @@ HTMLSelectElement::RemoveOptionsFromList
             NS_ERROR("wrong option at index");
             return NS_ERROR_UNEXPECTED;
           }
           mOptions->RemoveOptionAt(aListIndex);
           numRemoved++;
         }
       }
     }
-  }
+  } // else don't check for an optgroup; we want to ignore nested optgroups
 
   if (numRemoved) {
     // Tell the widget we removed the options
     nsISelectControlFrame* selectFrame = GetSelectFrame();
     if (selectFrame) {
       nsAutoScriptBlocker scriptBlocker;
       for (int32_t i = aListIndex; i < aListIndex + numRemoved; ++i) {
         selectFrame->RemoveOption(i);
--- a/dom/html/HTMLUnknownElement.cpp
+++ b/dom/html/HTMLUnknownElement.cpp
@@ -9,16 +9,19 @@
 #include "mozilla/dom/HTMLElementBinding.h"
 #include "jsapi.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Unknown)
 
 namespace mozilla {
 namespace dom {
 
+NS_IMPL_ISUPPORTS_INHERITED(HTMLUnknownElement, nsGenericHTMLElement,
+                            HTMLUnknownElement)
+
 JSObject*
 HTMLUnknownElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLUnknownElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
 NS_IMPL_ELEMENT_CLONE(HTMLUnknownElement)
 
--- a/dom/html/HTMLUnknownElement.h
+++ b/dom/html/HTMLUnknownElement.h
@@ -7,29 +7,40 @@
 #define mozilla_dom_HTMLUnknownElement_h
 
 #include "mozilla/Attributes.h"
 #include "nsGenericHTMLElement.h"
 
 namespace mozilla {
 namespace dom {
 
+#define NS_HTMLUNKNOWNELEMENT_IID \
+{ 0xc09e665b, 0x3876, 0x40dd, \
+  { 0x85, 0x28, 0x44, 0xc2, 0x3f, 0xd4, 0x58, 0xf2 } }
+
 class HTMLUnknownElement final : public nsGenericHTMLElement
 {
 public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTMLUNKNOWNELEMENT_IID)
+
+  NS_DECL_ISUPPORTS_INHERITED
+
   explicit HTMLUnknownElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
     : nsGenericHTMLElement(aNodeInfo)
   {
     if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
       SetHasDirAuto();
     }
   }
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
 protected:
+  virtual ~HTMLUnknownElement() {}
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(HTMLUnknownElement, NS_HTMLUNKNOWNELEMENT_IID)
+
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_HTMLUnknownElement_h */
new file mode 100644
--- /dev/null
+++ b/dom/html/crashtests/1228876.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+    var a = document.createElement("select");
+    var f = document.createElement("optgroup");
+    var g = document.createElement("optgroup");
+    a.appendChild(f);
+    g.appendChild(document.createElement("option"));
+    f.appendChild(g);
+    a.appendChild(document.createElement("option"));
+    document.body.appendChild(a);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
--- a/dom/html/crashtests/crashtests.list
+++ b/dom/html/crashtests/crashtests.list
@@ -68,9 +68,10 @@ load 837033.html
 load 838256-1.html
 load 862084.html
 load 865147.html
 load 877910.html
 load 903106.html
 load 916322-1.html
 load 916322-2.html
 load 1032654.html
-pref(dom.image.srcset.enabled,true) load 1141260.html
\ No newline at end of file
+pref(dom.image.srcset.enabled,true) load 1141260.html
+load 1228876.html
--- a/dom/html/test/test_bug741266.html
+++ b/dom/html/test/test_bug741266.html
@@ -8,28 +8,37 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug 741266</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=741266">Mozilla Bug 741266</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 741266 **/
-var w = window.open("", "", "width=100,height=100");
-is(w.innerHeight, 100, "Popup height should be 100 when opened with window.open");
-// XXXbz On at least some platforms, the innerWidth is off by the scrollbar
-// width for some reason.  So just make sure it's the same for both popups.
-var width = w.innerWidth;
-w.close();
-w = document.open("", "", "width=100,height=100");
-is(w.innerHeight, 100, "Popup height should be 100 when opened with document.open");
-is(w.innerWidth, width, "Popup width should be the same when opened with document.open");
-w.close();
+SimpleTest.waitForExplicitFinish();
+
+var url = URL.createObjectURL(new Blob([""], { type: "text/html" }));
+var w = window.open(url, "", "width=100,height=100");
+w.onload = function() {
+  is(w.innerHeight, 100, "Popup height should be 100 when opened with window.open");
+  // XXXbz On at least some platforms, the innerWidth is off by the scrollbar
+  // width for some reason.  So just make sure it's the same for both popups.
+  var width = w.innerWidth;
+  w.close();
+
+  w = document.open(url, "", "width=100,height=100");
+  w.onload = function() {
+    is(w.innerHeight, 100, "Popup height should be 100 when opened with document.open");
+    is(w.innerWidth, width, "Popup width should be the same when opened with document.open");
+    w.close();
+    SimpleTest.finish();
+  };
+};
 </script>
 </pre>
 </body>
 </html>
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1803,20 +1803,21 @@ TabChild::RecvMouseWheelEvent(const Widg
     APZCCallbackHelper::SendSetTargetAPZCNotification(
       mPuppetWidget, document, aEvent, aGuid, aInputBlockId);
   }
 
   WidgetWheelEvent event(aEvent);
   event.widget = mPuppetWidget;
   APZCCallbackHelper::DispatchWidgetEvent(event);
 
+  if (event.mCanTriggerSwipe) {
+    SendRespondStartSwipeEvent(aInputBlockId, event.TriggersSwipe());
+  }
+
   if (aEvent.mFlags.mHandledByAPZ) {
-    if (event.mCanTriggerSwipe) {
-      SendRespondStartSwipeEvent(aInputBlockId, event.TriggersSwipe());
-    }
     mAPZEventState->ProcessWheelEvent(event, aGuid, aInputBlockId);
   }
   return true;
 }
 
 bool
 TabChild::RecvMouseScrollTestEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
 {
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2689,16 +2689,22 @@ TabParent::GetWidget() const
   return widget.forget();
 }
 
 void
 TabParent::ApzAwareEventRoutingToChild(ScrollableLayerGuid* aOutTargetGuid,
                                        uint64_t* aOutInputBlockId,
                                        nsEventStatus* aOutApzResponse)
 {
+  // Let the widget know that the event will be sent to the child process,
+  // which will (hopefully) send a confirmation notice back to APZ.
+  // Do this even if APZ is off since we need it for swipe gesture support on
+  // OS X without APZ.
+  InputAPZContext::SetRoutedToChildProcess();
+
   if (AsyncPanZoomEnabled()) {
     if (aOutTargetGuid) {
       *aOutTargetGuid = InputAPZContext::GetTargetLayerGuid();
 
       // There may be cases where the APZ hit-testing code came to a different
       // conclusion than the main-thread hit-testing code as to where the event
       // is destined. In such cases the layersId of the APZ result may not match
       // the layersId of this renderframe. In such cases the main-thread hit-
@@ -2710,20 +2716,16 @@ TabParent::ApzAwareEventRoutingToChild(S
       }
     }
     if (aOutInputBlockId) {
       *aOutInputBlockId = InputAPZContext::GetInputBlockId();
     }
     if (aOutApzResponse) {
       *aOutApzResponse = InputAPZContext::GetApzResponse();
     }
-
-    // Let the widget know that the event will be sent to the child process,
-    // which will (hopefully) send a confirmation notice back to APZ.
-    InputAPZContext::SetRoutedToChildProcess();
   } else {
     if (aOutInputBlockId) {
       *aOutInputBlockId = 0;
     }
     if (aOutApzResponse) {
       *aOutApzResponse = nsEventStatus_eIgnore;
     }
   }
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -80,26 +80,16 @@ public:
   void DispatchUpdateEstimatedMediaDuration(int64_t aDuration)
   {
     nsCOMPtr<nsIRunnable> r =
       NS_NewRunnableMethodWithArg<int64_t>(this, &AbstractMediaDecoder::UpdateEstimatedMediaDuration,
                                            aDuration);
     NS_DispatchToMainThread(r);
   }
 
-  // Set the media as being seekable or not.
-  virtual void SetMediaSeekable(bool aMediaSeekable) = 0;
-
-  void DispatchSetMediaSeekable(bool aMediaSeekable)
-  {
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<bool>(
-      this, &AbstractMediaDecoder::SetMediaSeekable, aMediaSeekable);
-    NS_DispatchToMainThread(r);
-  }
-
   virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
   virtual mozilla::layers::ImageContainer* GetImageContainer() = 0;
 
   // Returns the owner of this media decoder. The owner should only be used
   // on the main thread.
   virtual MediaDecoderOwner* GetOwner() = 0;
 
   // Set by Reader if the current audio track can be offloaded
--- a/dom/media/BufferMediaResource.h
+++ b/dom/media/BufferMediaResource.h
@@ -130,14 +130,14 @@ private:
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
 private:
   const uint8_t * mBuffer;
   uint32_t mLength;
   uint32_t mOffset;
   nsCOMPtr<nsIPrincipal> mPrincipal;
-  const nsAutoCString mContentType;
+  const nsCString mContentType;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MP3FrameParser.cpp
+++ b/dom/media/MP3FrameParser.cpp
@@ -303,17 +303,17 @@ ParseXing(const char *aBuffer)
   if (flags & XING_HAS_NUM_FRAMES) {
     numFrames = FROM_BIG_ENDIAN(aBuffer + 8);
   }
 
   return numFrames;
 }
 
 static int64_t
-FindNumVBRFrames(const nsAutoCString& aFrame)
+FindNumVBRFrames(const nsCString& aFrame)
 {
   const char *buffer = aFrame.get();
   const char *bufferEnd = aFrame.get() + aFrame.Length();
 
   // VBRI header is nice and well-defined; let's try to find that first.
   if (aFrame.Length() > VBRI_MIN_FRAME_SIZE &&
       FROM_BIG_ENDIAN(buffer + VBRI_OFFSET) == VBRI_TAG) {
     return FROM_BIG_ENDIAN(buffer + VBRI_FRAME_COUNT_OFFSET);
--- a/dom/media/MP3FrameParser.h
+++ b/dom/media/MP3FrameParser.h
@@ -193,17 +193,17 @@ private:
   // Number of audio samples per second and per frame. Fixed through the whole
   // file. If we know these variables as well as the number of frames in the
   // file, we can get an exact duration for the stream.
   uint16_t mSamplesPerSecond;
   uint16_t mSamplesPerFrame;
 
   // If the MP3 has a variable bitrate, then there *should* be metadata about
   // the encoding in the first frame. We buffer the first frame here.
-  nsAutoCString mFirstFrame;
+  nsCString mFirstFrame;
 
   // While we are reading the first frame, this is the stream offset of the
   // last byte of that frame. -1 at all other times.
   int64_t mFirstFrameEnd;
 
   enum eIsMP3 {
     MAYBE_MP3, // We're giving the stream the benefit of the doubt...
     DEFINITELY_MP3, // We've hit at least one ID3 tag or MP3 frame.
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -612,16 +612,17 @@ MediaDecoder::Shutdown()
   // the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
   if (mDecoderStateMachine) {
     mDecoderStateMachine->DispatchShutdown();
     mTimedMetadataListener.Disconnect();
     mMetadataLoadedListener.Disconnect();
     mFirstFrameLoadedListener.Disconnect();
     mOnPlaybackEvent.Disconnect();
     mOnSeekingStart.Disconnect();
+    mOnMediaNotSeekable.Disconnect();
   }
 
   // Force any outstanding seek and byterange requests to complete
   // to prevent shutdown from deadlocking.
   if (mResource) {
     mResource->Close();
   }
 
@@ -723,16 +724,18 @@ MediaDecoder::SetStateMachineParameters(
     AbstractThread::MainThread(), this, &MediaDecoder::MetadataLoaded);
   mFirstFrameLoadedListener = mDecoderStateMachine->FirstFrameLoadedEvent().Connect(
     AbstractThread::MainThread(), this, &MediaDecoder::FirstFrameLoaded);
 
   mOnPlaybackEvent = mDecoderStateMachine->OnPlaybackEvent().Connect(
     AbstractThread::MainThread(), this, &MediaDecoder::OnPlaybackEvent);
   mOnSeekingStart = mDecoderStateMachine->OnSeekingStart().Connect(
     AbstractThread::MainThread(), this, &MediaDecoder::SeekingStarted);
+  mOnMediaNotSeekable = mDecoderStateMachine->OnMediaNotSeekable().Connect(
+    AbstractThread::MainThread(), this, &MediaDecoder::OnMediaNotSeekable);
 }
 
 void
 MediaDecoder::SetMinimizePrerollUntilPlaybackStarts()
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG("SetMinimizePrerollUntilPlaybackStarts()");
   mMinimizePreroll = true;
@@ -836,16 +839,17 @@ MediaDecoder::MetadataLoaded(nsAutoPtr<M
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShuttingDown);
 
   DECODER_LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
               aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
               aInfo->HasAudio(), aInfo->HasVideo());
 
+  SetMediaSeekable(aInfo->mMediaSeekable);
   mInfo = aInfo.forget();
   ConstructMediaTracks();
 
   // Make sure the element and the frame (if any) are told about
   // our new size.
   Invalidate();
   if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     mFiredMetadataLoaded = true;
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -466,17 +466,17 @@ protected:
   // element.
   void UpdateEstimatedMediaDuration(int64_t aDuration) override;
 
 public:
   // Called from HTMLMediaElement when owner document activity changes
   virtual void SetElementVisibility(bool aIsVisible) {}
 
   // Set a flag indicating whether seeking is supported
-  virtual void SetMediaSeekable(bool aMediaSeekable) override;
+  void SetMediaSeekable(bool aMediaSeekable);
 
   // Returns true if this media supports seeking. False for example for WebM
   // files without an index and chained ogg files.
   bool IsMediaSeekable();
   // Returns true if seeking is supported on a transport level (e.g. the server
   // supports range requests, we are playing a file, etc.).
   bool IsTransportSeekable();
 
@@ -799,16 +799,21 @@ private:
                       nsAutoPtr<MetadataTags> aTags,
                       MediaDecoderEventVisibility aEventVisibility);
 
   MediaEventSource<void>*
   DataArrivedEvent() override { return &mDataArrivedEvent; }
 
   void OnPlaybackEvent(MediaEventType aEvent);
 
+  void OnMediaNotSeekable()
+  {
+    SetMediaSeekable(false);
+  }
+
   MediaEventProducer<void> mDataArrivedEvent;
 
   // The state machine object for handling the decoding. It is safe to
   // call methods of this object from other threads. Its internal data
   // is synchronised on a monitor. The lifetime of this object is
   // after mPlayState is LOADING and before mPlayState is SHUTDOWN. It
   // is safe to access it during this period.
   //
@@ -922,16 +927,17 @@ protected:
   // A listener to receive metadata updates from MDSM.
   MediaEventListener mTimedMetadataListener;
 
   MediaEventListener mMetadataLoadedListener;
   MediaEventListener mFirstFrameLoadedListener;
 
   MediaEventListener mOnPlaybackEvent;
   MediaEventListener mOnSeekingStart;
+  MediaEventListener mOnMediaNotSeekable;
 
 protected:
   // Whether the state machine is shut down.
   Mirror<bool> mStateMachineIsShutdown;
 
   // Buffered range, mirrored from the reader.
   Mirror<media::TimeIntervals> mBuffered;
 
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -234,20 +234,16 @@ public:
   virtual MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
   virtual MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
 
   AbstractCanonical<media::TimeIntervals>* CanonicalBuffered()
   {
     return &mBuffered;
   }
 
-  // Indicates if the media is seekable.
-  // ReadMetada should be called before calling this method.
-  virtual bool IsMediaSeekable() = 0;
-
   void DispatchSetStartTime(int64_t aStartTime)
   {
     RefPtr<MediaDecoderReader> self = this;
     nsCOMPtr<nsIRunnable> r =
       NS_NewRunnableFunction([self, aStartTime] () -> void
     {
       MOZ_ASSERT(self->OnTaskQueue());
       MOZ_ASSERT(!self->HaveStartTime());
@@ -274,16 +270,19 @@ public:
 
   virtual void DisableHardwareAcceleration() {}
 
   TimedMetadataEventSource& TimedMetadataEvent()
   {
     return mTimedMetadataEvent;
   }
 
+  // Notified by the OggReader during playback when chained ogg is detected.
+  MediaEventSource<void>& OnMediaNotSeekable() { return mOnMediaNotSeekable; }
+
 protected:
   virtual ~MediaDecoderReader();
 
   // Populates aBuffered with the time ranges which are buffered. This may only
   // be called on the decode task queue, and should only be used internally by
   // UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
   // else.
   //
@@ -364,16 +363,19 @@ protected:
   // replace this with a promise-y mechanism as we make this stuff properly
   // async.
   bool mHitAudioDecodeError;
   bool mShutdown;
 
   // Used to send TimedMetadata to the listener.
   TimedMetadataEventProducer mTimedMetadataEvent;
 
+  // Notify if this media is not seekable.
+  MediaEventProducer<void> mOnMediaNotSeekable;
+
 private:
   // Does any spinup that needs to happen on this task queue. This runs on a
   // different thread than Init, and there should not be ordering dependencies
   // between the two (even though in practice, Init will always run first right
   // now thanks to the tail dispatcher).
   void InitializationTask();
 
   // Read header data for all bitstreams in the file. Fills aInfo with
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1904,17 +1904,16 @@ MediaDecoderStateMachine::OnMetadataRead
 
   if (mPendingDormant) {
     SetDormant(mPendingDormant.ref());
     return;
   }
 
   // Set mode to PLAYBACK after reading metadata.
   mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
-  mDecoder->DispatchSetMediaSeekable(mReader->IsMediaSeekable());
   mInfo = aMetadata->mInfo;
   mMetadataTags = aMetadata->mTags.forget();
   RefPtr<MediaDecoderStateMachine> self = this;
 
   // Set up the start time rendezvous if it doesn't already exist (which is
   // generally the case, unless we're coming out of dormant mode).
   if (!mStartTimeRendezvous) {
     mStartTimeRendezvous = new StartTimeRendezvous(OwnerThread(), HasAudio(), HasVideo(),
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -227,16 +227,20 @@ public:
     mResource = nullptr;
     mDecoder = nullptr;
   }
 
   TimedMetadataEventSource& TimedMetadataEvent() {
     return mMetadataManager.TimedMetadataEvent();
   }
 
+  MediaEventSource<void>& OnMediaNotSeekable() {
+    return mReader->OnMediaNotSeekable();
+  }
+
   MediaEventSourceExc<nsAutoPtr<MediaInfo>,
                       nsAutoPtr<MetadataTags>,
                       MediaDecoderEventVisibility>&
   MetadataLoadedEvent() { return mMetadataLoadedEvent; }
 
   MediaEventSourceExc<nsAutoPtr<MediaInfo>,
                       MediaDecoderEventVisibility>&
   FirstFrameLoadedEvent() { return mFirstFrameLoadedEvent; }
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -60,17 +60,16 @@ MediaFormatReader::MediaFormatReader(Abs
   : MediaDecoderReader(aDecoder)
   , mAudio(this, MediaData::AUDIO_DATA, Preferences::GetUint("media.audio-decode-ahead", 2))
   , mVideo(this, MediaData::VIDEO_DATA, Preferences::GetUint("media.video-decode-ahead", 2))
   , mDemuxer(aDemuxer)
   , mDemuxerInitDone(false)
   , mLastReportedNumDecodedFrames(0)
   , mLayersBackendType(aLayersBackend)
   , mInitDone(false)
-  , mSeekable(false)
   , mIsEncrypted(false)
   , mTrackDemuxersMayBlock(false)
   , mHardwareAccelerationDisabled(false)
   , mDemuxOnly(false)
   , mVideoFrameContainer(aVideoFrameContainer)
 {
   MOZ_ASSERT(aDemuxer);
   MOZ_COUNT_CTOR(MediaFormatReader);
@@ -319,17 +318,17 @@ MediaFormatReader::OnDemuxerInitDone(nsr
   int64_t videoDuration = HasVideo() ? mInfo.mVideo.mDuration : 0;
   int64_t audioDuration = HasAudio() ? mInfo.mAudio.mDuration : 0;
 
   int64_t duration = std::max(videoDuration, audioDuration);
   if (duration != -1) {
     mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
   }
 
-  mSeekable = mDemuxer->IsSeekable();
+  mInfo.mMediaSeekable = mDemuxer->IsSeekable();
 
   if (!videoActive && !audioActive) {
     mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
     return;
   }
 
   mInitDone = true;
   RefPtr<MetadataHolder> metadata = new MetadataHolder();
@@ -1326,17 +1325,17 @@ MediaFormatReader::Seek(int64_t aTime, i
 
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty());
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(mPendingSeekTime.isNothing());
   MOZ_DIAGNOSTIC_ASSERT(mVideo.mTimeThreshold.isNothing());
   MOZ_DIAGNOSTIC_ASSERT(mAudio.mTimeThreshold.isNothing());
 
-  if (!mSeekable) {
+  if (!mInfo.mMediaSeekable) {
     LOG("Seek() END (Unseekable)");
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   if (mShutdown) {
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -44,21 +44,16 @@ public:
 
   RefPtr<MetadataPromise> AsyncReadMetadata() override;
 
   void ReadUpdatedMetadata(MediaInfo* aInfo) override;
 
   RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aUnused) override;
 
-  bool IsMediaSeekable() override
-  {
-    return mSeekable;
-  }
-
 protected:
   void NotifyDataArrivedInternal() override;
 
 public:
   media::TimeIntervals GetBuffered() override;
 
   virtual bool ForceZeroStartTime() const override;
 
@@ -391,19 +386,16 @@ private:
   uint64_t mLastReportedNumDecodedFrames;
 
   layers::LayersBackend mLayersBackendType;
 
   // Metadata objects
   // True if we've read the streams' metadata.
   bool mInitDone;
   MozPromiseHolder<MetadataPromise> mMetadataPromise;
-  // Accessed from multiple thread, in particular the MediaDecoderStateMachine,
-  // however the value doesn't change after reading the metadata.
-  bool mSeekable;
   bool IsEncrypted()
   {
     return mIsEncrypted;
   }
   bool mIsEncrypted;
 
   // Set to true if any of our track buffers may be blocking.
   bool mTrackDemuxersMayBlock;
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -70,17 +70,17 @@ public:
   nsString mId;
   nsString mKind;
   nsString mLabel;
   nsString mLanguage;
   bool mEnabled;
 
   TrackID mTrackId;
 
-  nsAutoCString mMimeType;
+  nsCString mMimeType;
   int64_t mDuration;
   int64_t mMediaTime;
   CryptoTrack mCrypto;
 
   // True if the track is gonna be (decrypted)/decoded and
   // rendered directly by non-gecko components.
   bool mIsRenderedExternally;
 
@@ -395,16 +395,19 @@ public:
   // If the metadata includes a duration, we store it here.
   media::NullableTimeUnit mMetadataDuration;
 
   // The Ogg reader tries to kinda-sorta compute the duration by seeking to the
   // end and determining the timestamp of the last frame. This isn't useful as
   // a duration until we know the start time, so we need to track it separately.
   media::NullableTimeUnit mUnadjustedMetadataEndTime;
 
+  // True if the media is seekable (i.e. supports random access).
+  bool mMediaSeekable = true;
+
   EncryptionInfo mCrypto;
 };
 
 class SharedTrackInfo {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedTrackInfo)
 public:
   SharedTrackInfo(const TrackInfo& aOriginal, uint32_t aStreamID)
     : mInfo(aOriginal.Clone())
@@ -446,14 +449,14 @@ public:
 
 private:
   ~SharedTrackInfo() {};
   UniquePtr<TrackInfo> mInfo;
   // A unique ID, guaranteed to change when changing streams.
   uint32_t mStreamSourceID;
 
 public:
-  const nsAutoCString& mMimeType;
+  const nsCString& mMimeType;
 };
 
 } // namespace mozilla
 
 #endif // MediaInfo_h
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -446,20 +446,20 @@ protected:
 
   // URI in case the stream needs to be re-opened. Access from
   // main thread only.
   nsCOMPtr<nsIURI> mURI;
 
   // Content-Type of the channel. This is copied from the nsIChannel when the
   // MediaResource is created. This is constant, so accessing from any thread
   // is safe.
-  const nsAutoCString mContentType;
+  const nsCString mContentType;
 
   // Copy of the url of the channel resource.
-  nsAutoCString mContentURL;
+  nsCString mContentURL;
 
   // True if SetLoadInBackground() has been called with
   // aLoadInBackground = true, i.e. when the document load event is not
   // blocked by this resource, and all channel loads will be in the
   // background.
   bool mLoadInBackground;
 };
 
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -448,17 +448,17 @@ SimpleTimer::Create(nsIRunnable* aTask, 
   }
   return t.forget();
 }
 
 void
 LogToBrowserConsole(const nsAString& aMsg)
 {
   if (!NS_IsMainThread()) {
-    nsAutoString msg(aMsg);
+    nsString msg(aMsg);
     nsCOMPtr<nsIRunnable> task =
       NS_NewRunnableFunction([msg]() { LogToBrowserConsole(msg); });
     NS_DispatchToMainThread(task.forget(), NS_DISPATCH_NORMAL);
     return;
   }
   nsCOMPtr<nsIConsoleService> console(
     do_GetService("@mozilla.org/consoleservice;1"));
   if (!console) {
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -43,22 +43,16 @@ public:
                      const nsACString& aContentType);
 
   virtual nsresult ResetDecode();
 
   virtual bool DecodeAudioData();
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold);
 
-  virtual bool IsMediaSeekable()
-  {
-    // not used
-    return true;
-  }
-
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual RefPtr<ShutdownPromise> Shutdown() override;
 
   class ImageBufferCallback : public MPAPI::BufferCallback {
--- a/dom/media/directshow/DirectShowReader.cpp
+++ b/dom/media/directshow/DirectShowReader.cpp
@@ -185,16 +185,17 @@ DirectShowReader::ReadMetadata(MediaInfo
   mBytesPerSample = format.wBitsPerSample / 8;
 
   // Begin decoding!
   hr = mControl->Run();
   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
   DWORD seekCaps = 0;
   hr = mMediaSeeking->GetCapabilities(&seekCaps);
+  mInfo.mMediaSeekable = SUCCEEDED(hr) && (AM_SEEKING_CanSeekAbsolute & seekCaps);
 
   int64_t duration = mMP3FrameParser.GetDuration();
   if (SUCCEEDED(hr)) {
     mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
   }
 
   LOG("Successfully initialized DirectShow MP3 decoder.");
   LOG("Channels=%u Hz=%u duration=%lld bytesPerSample=%d",
@@ -205,25 +206,16 @@ DirectShowReader::ReadMetadata(MediaInfo
 
   *aInfo = mInfo;
   // Note: The SourceFilter strips ID3v2 tags out of the stream.
   *aTags = nullptr;
 
   return NS_OK;
 }
 
-bool
-DirectShowReader::IsMediaSeekable()
-{
-  DWORD seekCaps = 0;
-  HRESULT hr = mMediaSeeking->GetCapabilities(&seekCaps);
-  return ((AM_SEEKING_CanSeekAbsolute & seekCaps) ==
-          AM_SEEKING_CanSeekAbsolute);
-}
-
 inline float
 UnsignedByteToAudioSample(uint8_t aValue)
 {
   return aValue * (2.0f / UINT8_MAX) - 1.0f;
 }
 
 bool
 DirectShowReader::Finish(HRESULT aStatus)
--- a/dom/media/directshow/DirectShowReader.h
+++ b/dom/media/directshow/DirectShowReader.h
@@ -51,22 +51,18 @@ public:
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) override;
 
   RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
 protected:
   void NotifyDataArrivedInternal() override;
-public:
-
-  bool IsMediaSeekable() override;
 
 private:
-
   // Notifies the filter graph that playback is complete. aStatus is
   // the code to send to the filter graph. Always returns false, so
   // that we can just "return Finish()" from DecodeAudioData().
   bool Finish(HRESULT aStatus);
 
   nsresult SeekInternal(int64_t aTime);
 
   // DirectShow filter graph, and associated playback and seeking
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -180,18 +180,18 @@ public:
 #endif
 
 private:
   friend class gmp_InitDoneCallback;
   friend class gmp_InitGetGMPDecryptorCallback;
 
   struct InitData {
     uint32_t mPromiseId;
-    nsAutoString mOrigin;
-    nsAutoString mTopLevelOrigin;
+    nsString mOrigin;
+    nsString mTopLevelOrigin;
     nsString mGMPName;
     bool mInPrivateBrowsing;
   };
 
   // GMP thread only.
   void gmp_Init(nsAutoPtr<InitData>&& aData);
   void gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData);
   void gmp_InitGetGMPDecryptor(nsresult aResult,
@@ -203,39 +203,39 @@ private:
 
   // Main thread only.
   void OnCDMCreated(uint32_t aPromiseId);
 
   struct CreateSessionData {
     dom::SessionType mSessionType;
     uint32_t mCreateSessionToken;
     PromiseId mPromiseId;
-    nsAutoCString mInitDataType;
+    nsCString mInitDataType;
     nsTArray<uint8_t> mInitData;
   };
   // GMP thread only.
   void gmp_CreateSession(nsAutoPtr<CreateSessionData> aData);
 
   struct SessionOpData {
     PromiseId mPromiseId;
-    nsAutoCString mSessionId;
+    nsCString mSessionId;
   };
   // GMP thread only.
   void gmp_LoadSession(nsAutoPtr<SessionOpData> aData);
 
   struct SetServerCertificateData {
     PromiseId mPromiseId;
     nsTArray<uint8_t> mCert;
   };
   // GMP thread only.
   void gmp_SetServerCertificate(nsAutoPtr<SetServerCertificateData> aData);
 
   struct UpdateSessionData {
     PromiseId mPromiseId;
-    nsAutoCString mSessionId;
+    nsCString mSessionId;
     nsTArray<uint8_t> mResponse;
   };
   // GMP thread only.
   void gmp_UpdateSession(nsAutoPtr<UpdateSessionData> aData);
 
   // GMP thread only.
   void gmp_CloseSession(nsAutoPtr<SessionOpData> aData);
 
@@ -321,17 +321,17 @@ private:
     Type* mPtr;
   };
 
   // Our reference back to the MediaKeys object.
   // WARNING: This is a non-owning reference that is cleared by MediaKeys
   // destructor. only use on main thread, and always nullcheck before using!
   MainThreadOnlyRawPtr<dom::MediaKeys> mKeys;
 
-  const nsAutoString mKeySystem;
+  const nsString mKeySystem;
 
   // Gecko Media Plugin thread. All interactions with the out-of-process
   // EME plugin must come from this thread.
   RefPtr<nsIThread> mGMPThread;
 
   nsCString mNodeId;
 
   GMPDecryptorProxy* mCDM;
--- a/dom/media/gmp-plugin/fake.info
+++ b/dom/media/gmp-plugin/fake.info
@@ -1,5 +1,5 @@
 Name: fake
-Description: Fake GMP Plugin
+Description: Fake GMP Plugin, which deliberately uses GMP_API_DECRYPTOR_BACKWARDS_COMPAT for its decryptor.
 Version: 1.0
 APIs: decode-video[h264:broken], eme-decrypt-v7[fake]
 Libraries: dxva2.dll
--- a/dom/media/gmp-plugin/gmp-fake.cpp
+++ b/dom/media/gmp-plugin/gmp-fake.cpp
@@ -67,17 +67,17 @@ extern "C" {
   PUBLIC_FUNC GMPErr
   GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
     if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
       // Note: Deliberately advertise in our .info file that we support
       // video-decode, but we fail the "get" call here to simulate what
       // happens when decoder init fails.
       return GMPGenericErr;
 #if defined(GMP_FAKE_SUPPORT_DECRYPT)
-    } else if (!strcmp (aApiName, GMP_API_DECRYPTOR)) {
+    } else if (!strcmp (aApiName, GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) {
       *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI));
       return GMPNoErr;
     } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) {
       *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
       return GMPNoErr;
 #endif
     }
     return GMPGenericErr;
--- a/dom/media/gmp/GMPContentChild.cpp
+++ b/dom/media/gmp/GMPContentChild.cpp
@@ -114,17 +114,26 @@ bool
 GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
 {
   GMPDecryptorChild* child = static_cast<GMPDecryptorChild*>(aActor);
   GMPDecryptorHost* host = static_cast<GMPDecryptorHost*>(child);
 
   void* session = nullptr;
   GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, host, &session);
   if (err != GMPNoErr || !session) {
-    return false;
+    // We Adapt the previous GMPDecryptor version to the current, so that
+    // Gecko thinks it's only talking to the current version. Helpfully,
+    // v7 is ABI compatible with v8, it only has different enumerations.
+    // If the GMP uses a v8-only enum value in an IPDL message, the IPC
+    // layer will terminate, so we rev'd the API version to signal to the
+    // GMP that it's safe to use the new enum values.
+    err = mGMPChild->GetAPI(GMP_API_DECRYPTOR_BACKWARDS_COMPAT, host, &session);
+    if (err != GMPNoErr || !session) {
+      return false;
+    }
   }
 
   child->Init(static_cast<GMPDecryptor*>(session));
 
   return true;
 }
 
 bool
--- a/dom/media/gmp/GMPDecryptorChild.cpp
+++ b/dom/media/gmp/GMPDecryptorChild.cpp
@@ -74,17 +74,17 @@ GMPDecryptorChild::Init(GMPDecryptor* aS
 }
 
 void
 GMPDecryptorChild::SetSessionId(uint32_t aCreateSessionToken,
                                 const char* aSessionId,
                                 uint32_t aSessionIdLength)
 {
   CALL_ON_GMP_THREAD(SendSetSessionId,
-                     aCreateSessionToken, nsAutoCString(aSessionId, aSessionIdLength));
+                     aCreateSessionToken, nsCString(aSessionId, aSessionIdLength));
 }
 
 void
 GMPDecryptorChild::ResolveLoadSessionPromise(uint32_t aPromiseId,
                                              bool aSuccess)
 {
   CALL_ON_GMP_THREAD(SendResolveLoadSessionPromise, aPromiseId, aSuccess);
 }
@@ -97,75 +97,75 @@ GMPDecryptorChild::ResolvePromise(uint32
 
 void
 GMPDecryptorChild::RejectPromise(uint32_t aPromiseId,
                                  GMPDOMException aException,
                                  const char* aMessage,
                                  uint32_t aMessageLength)
 {
   CALL_ON_GMP_THREAD(SendRejectPromise,
-                     aPromiseId, aException, nsAutoCString(aMessage, aMessageLength));
+                     aPromiseId, aException, nsCString(aMessage, aMessageLength));
 }
 
 void
 GMPDecryptorChild::SessionMessage(const char* aSessionId,
                                   uint32_t aSessionIdLength,
                                   GMPSessionMessageType aMessageType,
                                   const uint8_t* aMessage,
                                   uint32_t aMessageLength)
 {
   nsTArray<uint8_t> msg;
   msg.AppendElements(aMessage, aMessageLength);
   CALL_ON_GMP_THREAD(SendSessionMessage,
-                     nsAutoCString(aSessionId, aSessionIdLength),
+                     nsCString(aSessionId, aSessionIdLength),
                      aMessageType, Move(msg));
 }
 
 void
 GMPDecryptorChild::ExpirationChange(const char* aSessionId,
                                     uint32_t aSessionIdLength,
                                     GMPTimestamp aExpiryTime)
 {
   CALL_ON_GMP_THREAD(SendExpirationChange,
-                     nsAutoCString(aSessionId, aSessionIdLength), aExpiryTime);
+                     nsCString(aSessionId, aSessionIdLength), aExpiryTime);
 }
 
 void
 GMPDecryptorChild::SessionClosed(const char* aSessionId,
                                  uint32_t aSessionIdLength)
 {
   CALL_ON_GMP_THREAD(SendSessionClosed,
-                     nsAutoCString(aSessionId, aSessionIdLength));
+                     nsCString(aSessionId, aSessionIdLength));
 }
 
 void
 GMPDecryptorChild::SessionError(const char* aSessionId,
                                 uint32_t aSessionIdLength,
                                 GMPDOMException aException,
                                 uint32_t aSystemCode,
                                 const char* aMessage,
                                 uint32_t aMessageLength)
 {
   CALL_ON_GMP_THREAD(SendSessionError,
-                     nsAutoCString(aSessionId, aSessionIdLength),
+                     nsCString(aSessionId, aSessionIdLength),
                      aException, aSystemCode,
-                     nsAutoCString(aMessage, aMessageLength));
+                     nsCString(aMessage, aMessageLength));
 }
 
 void
 GMPDecryptorChild::KeyStatusChanged(const char* aSessionId,
                                     uint32_t aSessionIdLength,
                                     const uint8_t* aKeyId,
                                     uint32_t aKeyIdLength,
                                     GMPMediaKeyStatus aStatus)
 {
   nsAutoTArray<uint8_t, 16> kid;
   kid.AppendElements(aKeyId, aKeyIdLength);
   CALL_ON_GMP_THREAD(SendKeyStatusChanged,
-                     nsAutoCString(aSessionId, aSessionIdLength), kid,
+                     nsCString(aSessionId, aSessionIdLength), kid,
                      aStatus);
 }
 
 void
 GMPDecryptorChild::Decrypted(GMPBuffer* aBuffer, GMPErr aResult)
 {
   if (!ON_GMP_THREAD()) {
     // We should run this whole method on the GMP thread since the buffer needs
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -863,16 +863,22 @@ GMPParent::ReadGMPMetaData()
         nsCCharSeparatedTokenizer tagTokens(ts, ':');
         while (tagTokens.hasMoreTokens()) {
           const nsDependentCSubstring tag(tagTokens.nextToken());
           cap->mAPITags.AppendElement(tag);
         }
       }
     }
 
+    // We support the current GMPDecryptor version, and the previous.
+    // We Adapt the previous to the current in the GMPContentChild.
+    if (cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) {
+      cap->mAPIName.AssignLiteral(GMP_API_DECRYPTOR);
+    }
+
     if (cap->mAPIName.EqualsLiteral(GMP_API_DECRYPTOR)) {
       mCanDecrypt = true;
 
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
       if (!mozilla::SandboxInfo::Get().CanSandboxMedia()) {
         printf_stderr("GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
                       " but this system can't sandbox it; not loading.\n",
                       mDisplayName.get());
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -204,17 +204,17 @@ private:
   bool mCanDecrypt;
 
   nsTArray<RefPtr<GMPTimerParent>> mTimers;
   nsTArray<RefPtr<GMPStorageParent>> mStorage;
   nsCOMPtr<nsIThread> mGMPThread;
   nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only.
   // NodeId the plugin is assigned to, or empty if the the plugin is not
   // assigned to a NodeId.
-  nsAutoCString mNodeId;
+  nsCString mNodeId;
   // This is used for GMP content in the parent, there may be more of these in
   // the content processes.
   RefPtr<GMPContentParent> mGMPContentParent;
   nsTArray<UniquePtr<GetGMPContentParentCallback>> mCallbacks;
   uint32_t mGMPContentChildCount;
 
   bool mAsyncShutdownRequired;
   bool mAsyncShutdownInProgress;
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -151,17 +151,17 @@ private:
 #ifdef MOZ_CRASHREPORTER
   Mutex mAsyncShutdownPluginStatesMutex; // Protects mAsyncShutdownPluginStates.
   class AsyncShutdownPluginStates
   {
   public:
     void Update(const nsCString& aPlugin, const nsCString& aInstance,
                 char aId, const nsCString& aState);
   private:
-    struct State { nsAutoCString mStateSequence; nsCString mLastStateDescription; };
+    struct State { nsCString mStateSequence; nsCString mLastStateDescription; };
     typedef nsClassHashtable<nsCStringHashKey, State> StatesByInstance;
     typedef nsClassHashtable<nsCStringHashKey, StatesByInstance> StateInstancesByPlugin;
     StateInstancesByPlugin mStates;
   } mAsyncShutdownPluginStates;
 #endif // MOZ_CRASHREPORTER
 
   // True if we've inspected MOZ_GMP_PATH on the GMP thread and loaded any
   // plugins found there into mPlugins.
--- a/dom/media/gmp/GMPStorageParent.cpp
+++ b/dom/media/gmp/GMPStorageParent.cpp
@@ -477,17 +477,17 @@ private:
     }
     nsString mFilename;
     nsCString mRecordName;
     PRFileDesc* mFileDesc;
   };
 
   // Hash record name to record data.
   nsClassHashtable<nsCStringHashKey, Record> mRecords;
-  const nsAutoCString mNodeId;
+  const nsCString mNodeId;
   const nsString mGMPName;
 };
 
 class GMPMemoryStorage : public GMPStorage {
 public:
   GMPErr Open(const nsCString& aRecordName) override
   {
     MOZ_ASSERT(!IsOpen(aRecordName));
--- a/dom/media/gmp/gmp-api/gmp-decryption.h
+++ b/dom/media/gmp/gmp-api/gmp-decryption.h
@@ -239,17 +239,19 @@ public:
 };
 
 enum GMPSessionType {
   kGMPTemporySession = 0,
   kGMPPersistentSession = 1,
   kGMPSessionInvalid = 2 // Must always be last.
 };
 
-#define GMP_API_DECRYPTOR "eme-decrypt-v7"
+// Gecko supports the current GMPDecryptor version, and the previous.
+#define GMP_API_DECRYPTOR "eme-decrypt-v8"
+#define GMP_API_DECRYPTOR_BACKWARDS_COMPAT "eme-decrypt-v7"
 
 // API exposed by plugin library to manage decryption sessions.
 // When the Host requests this by calling GMPGetAPIFunc().
 //
 // API name macro: GMP_API_DECRYPTOR
 // Host API: GMPDecryptorHost
 class GMPDecryptor {
 public:
--- a/dom/media/gstreamer/GStreamerReader.cpp
+++ b/dom/media/gstreamer/GStreamerReader.cpp
@@ -463,39 +463,45 @@ nsresult GStreamerReader::ReadMetadata(M
 
   if (NS_FAILED(ret))
     /* we couldn't get this to play */
     return ret;
 
   /* report the duration */
   gint64 duration;
 
+  bool isMediaSeekable = false;
+
   if (isMP3 && mMP3FrameParser.IsMP3()) {
     // The MP3FrameParser has reported a duration; use that over the gstreamer
     // reported duration for inter-platform consistency.
     mUseParserDuration = true;
     mLastParserDuration = mMP3FrameParser.GetDuration();
     mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mLastParserDuration));
+    isMediaSeekable = true;
   } else {
     LOG(LogLevel::Debug, "querying duration");
     // Otherwise use the gstreamer duration.
 #if GST_VERSION_MAJOR >= 1
     if (gst_element_query_duration(GST_ELEMENT(mPlayBin),
           GST_FORMAT_TIME, &duration)) {
 #else
     GstFormat format = GST_FORMAT_TIME;
     if (gst_element_query_duration(GST_ELEMENT(mPlayBin),
       &format, &duration) && format == GST_FORMAT_TIME) {
 #endif
       LOG(LogLevel::Debug, "have duration %" GST_TIME_FORMAT, GST_TIME_ARGS(duration));
       duration = GST_TIME_AS_USECONDS (duration);
       mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
+      isMediaSeekable = true;
     }
   }
 
+  mInfo.mMediaSeekable = isMediaSeekable;
+
   int n_video = 0, n_audio = 0;
   g_object_get(mPlayBin, "n-video", &n_video, "n-audio", &n_audio, nullptr);
 
   if (!n_video) {
     mInfo.mVideo = VideoInfo();
   }
   if (!n_audio) {
     mInfo.mAudio = AudioInfo();
@@ -513,38 +519,16 @@ nsresult GStreamerReader::ReadMetadata(M
 
   /* set the pipeline to PLAYING so that it starts decoding and queueing data in
    * the appsinks */
   gst_element_set_state(mPlayBin, GST_STATE_PLAYING);
 
   return NS_OK;
 }
 
-bool
-GStreamerReader::IsMediaSeekable()
-{
-  if (mUseParserDuration) {
-    return true;
-  }
-
-  gint64 duration;
-#if GST_VERSION_MAJOR >= 1
-  if (gst_element_query_duration(GST_ELEMENT(mPlayBin), GST_FORMAT_TIME,
-                                 &duration)) {
-#else
-  GstFormat format = GST_FORMAT_TIME;
-  if (gst_element_query_duration(GST_ELEMENT(mPlayBin), &format, &duration) &&
-      format == GST_FORMAT_TIME) {
-#endif
-    return true;
-  }
-
-  return false;
-}
-
 nsresult GStreamerReader::CheckSupportedFormats()
 {
   bool done = false;
   bool unsupported = false;
 
   GstIterator* it = gst_bin_iterate_recurse(GST_BIN(mPlayBin));
   while (!done) {
     GstIteratorResult res;
--- a/dom/media/gstreamer/GStreamerReader.h
+++ b/dom/media/gstreamer/GStreamerReader.h
@@ -49,21 +49,20 @@ public:
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) override;
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
   virtual media::TimeIntervals GetBuffered() override;
 
 protected:
   virtual void NotifyDataArrivedInternal() override;
+
 public:
   layers::ImageContainer* GetImageContainer() { return mDecoder->GetImageContainer(); }
 
-  virtual bool IsMediaSeekable() override;
-
 private:
   bool HasAudio() { return mInfo.HasAudio(); }
   bool HasVideo() { return mInfo.HasVideo(); }
   void ReadAndPushData(guint aLength);
   RefPtr<layers::PlanarYCbCrImage> GetImageFromBuffer(GstBuffer* aBuffer);
   void CopyIntoImageBuffer(GstBuffer *aBuffer, GstBuffer** aOutBuffer, RefPtr<layers::PlanarYCbCrImage> &image);
   GstCaps* BuildAudioSinkCaps();
   void InstallPadCallbacks();
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -481,30 +481,27 @@ nsresult OggReader::ReadMetadata(MediaIn
       if (endTime != -1) {
         mInfo.mUnadjustedMetadataEndTime.emplace(TimeUnit::FromMicroseconds(endTime));
         LOG(LogLevel::Debug, ("Got Ogg duration from seeking to end %lld", endTime));
       }
     }
   } else {
     return NS_ERROR_FAILURE;
   }
+
+  {
+    ReentrantMonitorAutoEnter mon(mMonitor);
+    mInfo.mMediaSeekable = !mIsChained;
+  }
+
   *aInfo = mInfo;
 
   return NS_OK;
 }
 
-bool
-OggReader::IsMediaSeekable()
-{
-  if (mIsChained) {
-    return false;
-  }
-  return true;
-}
-
 nsresult OggReader::DecodeVorbis(ogg_packet* aPacket) {
   NS_ASSERTION(aPacket->granulepos != -1, "Must know vorbis granulepos!");
 
   if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) {
     return NS_ERROR_FAILURE;
   }
   if (vorbis_synthesis_blockin(&mVorbisState->mDsp,
                                &mVorbisState->mBlock) != 0)
@@ -707,17 +704,17 @@ bool OggReader::DecodeAudioData()
   return true;
 }
 
 void OggReader::SetChained(bool aIsChained) {
   {
     ReentrantMonitorAutoEnter mon(mMonitor);
     mIsChained = aIsChained;
   }
-  mDecoder->DispatchSetMediaSeekable(false);
+  mOnMediaNotSeekable.Notify();
 }
 
 bool OggReader::ReadOggChain()
 {
   bool chained = false;
   OpusState* newOpusState = nullptr;
   VorbisState* newVorbisState = nullptr;
   nsAutoPtr<MetadataTags> tags;
--- a/dom/media/ogg/OggReader.h
+++ b/dom/media/ogg/OggReader.h
@@ -61,18 +61,16 @@ public:
                                 int64_t aTimeThreshold) override;
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) override;
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
   virtual media::TimeIntervals GetBuffered() override;
 
-  virtual bool IsMediaSeekable() override;
-
 private:
   bool HasAudio() {
     return (mVorbisState != 0 && mVorbisState->mActive) ||
            (mOpusState != 0 && mOpusState->mActive);
   }
 
   bool HasVideo() {
     return mTheoraState != 0 && mTheoraState->mActive;
--- a/dom/media/omx/MediaOmxReader.cpp
+++ b/dom/media/omx/MediaOmxReader.cpp
@@ -305,34 +305,29 @@ void MediaOmxReader::HandleResourceAlloc
   if (mOmxDecoder->HasAudio()) {
     int32_t numChannels, sampleRate;
     mOmxDecoder->GetAudioParameters(&numChannels, &sampleRate);
     mHasAudio = true;
     mInfo.mAudio.mChannels = numChannels;
     mInfo.mAudio.mRate = sampleRate;
   }
 
+  mInfo.mMediaSeekable = mExtractor->flags() & MediaExtractor::CAN_SEEK;
+
   RefPtr<MetadataHolder> metadata = new MetadataHolder();
   metadata->mInfo = mInfo;
   metadata->mTags = nullptr;
 
 #ifdef MOZ_AUDIO_OFFLOAD
   CheckAudioOffload();
 #endif
 
   mMetadataPromise.Resolve(metadata, __func__);
 }
 
-bool
-MediaOmxReader::IsMediaSeekable()
-{
-  // Check the MediaExtract flag if the source is seekable.
-  return (mExtractor->flags() & MediaExtractor::CAN_SEEK);
-}
-
 bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                       int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   EnsureActive();
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
--- a/dom/media/omx/MediaOmxReader.h
+++ b/dom/media/omx/MediaOmxReader.h
@@ -87,18 +87,16 @@ public:
 
   virtual void ReleaseMediaResources() override;
 
   virtual RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
 
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
-  virtual bool IsMediaSeekable() override;
-
   virtual void SetIdle() override;
 
   virtual RefPtr<ShutdownPromise> Shutdown() override;
 
   android::sp<android::MediaSource> GetAudioOffloadTrack();
 
   // This method is intended only for private use but public only for
   // MozPromise::InvokeCallbackMethod().
--- a/dom/media/raw/RawReader.cpp
+++ b/dom/media/raw/RawReader.cpp
@@ -96,23 +96,16 @@ nsresult RawReader::ReadMetadata(MediaIn
 
   *aInfo = mInfo;
 
   *aTags = nullptr;
 
   return NS_OK;
 }
 
-bool
-RawReader::IsMediaSeekable()
-{
-  // not used
-  return true;
-}
-
  bool RawReader::DecodeAudioData()
 {
   MOZ_ASSERT(OnTaskQueue());
   return false;
 }
 
 // Helper method that either reads until it gets aLength bytes
 // or returns false
--- a/dom/media/raw/RawReader.h
+++ b/dom/media/raw/RawReader.h
@@ -28,18 +28,16 @@ public:
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) override;
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual media::TimeIntervals GetBuffered() override;
 
-  virtual bool IsMediaSeekable() override;
-
 private:
   bool ReadFromResource(uint8_t *aBuf, uint32_t aLength);
 
   RawVideoHeader mMetadata;
   uint32_t mCurrentFrame;
   double mFrameRate;
   uint32_t mFrameSize;
   nsIntRect mPicture;
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -44,16 +44,17 @@ skip-if = (toolkit == 'gonk' || buildapp
 [test_getUserMedia_basicScreenshare.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # no screenshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
 [test_getUserMedia_basicTabshare.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # no windowshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
 [test_getUserMedia_basicWindowshare.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # no windowshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
 [test_getUserMedia_basicVideoAudio.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure, turned an intermittent (bug 962579) into a permanant orange
+[test_getUserMedia_bug1223696.html]
 [test_getUserMedia_constraints.html]
 [test_getUserMedia_callbacks.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # Bug 1063290, intermittent timeout # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
 [test_getUserMedia_gumWithinGum.html]
 [test_getUserMedia_mediaStreamConstructors.html]
 [test_getUserMedia_playAudioTwice.html]
 [test_getUserMedia_playVideoAudioTwice.html]
 [test_getUserMedia_playVideoTwice.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_bug1223696.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  "use strict";
+
+  createHTML({
+    title: "Testing that removeTrack+addTrack of video tracks still render the correct track in a media element",
+    bug: "1223696",
+    visible: true
+  });
+
+  runTest(() => Promise.resolve()
+      .then(() => getUserMedia({audio:true, video: true})).then(stream => {
+      info("Test addTrack()ing a video track to an audio-only gUM stream");
+
+      var video = createMediaElement("video", "test_video_track");
+      video.srcObject = stream;
+      video.play();
+
+      var h = new CaptureStreamTestHelper2D();
+      stream.removeTrack(stream.getVideoTracks()[0]);
+      video.onloadeddata = () => {
+        info("loadeddata");
+        var canvas = document.createElement("canvas");
+        canvas.getContext("2d");
+        var canvasStream = canvas.captureStream();
+        setInterval(() => h.drawColor(canvas, h.grey), 1000);
+
+        stream.addTrack(canvasStream.getVideoTracks()[0]);
+
+        checkMediaStreamContains(stream, [stream.getAudioTracks()[0],
+                                          canvasStream.getVideoTracks()[0]]);
+      };
+
+      return listenUntil(video, "loadeddata", () => true)
+        .then(() => h.waitForPixelColor(video, h.grey, 5,
+                                        "The canvas track should be rendered by the media element"));
+    }));
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/wave/WaveReader.cpp
+++ b/dom/media/wave/WaveReader.cpp
@@ -140,23 +140,16 @@ nsresult WaveReader::ReadMetadata(MediaI
   *aInfo = mInfo;
 
   *aTags = tags.forget();
 
 
   return NS_OK;
 }
 
-bool
-WaveReader::IsMediaSeekable()
-{
-  // not used
-  return true;
-}
-
 template <typename T> T UnsignedByteToAudioSample(uint8_t aValue);
 template <typename T> T SignedShortToAudioSample(int16_t aValue);
 
 template <> inline float
 UnsignedByteToAudioSample<float>(uint8_t aValue)
 {
   return aValue * (2.0f / UINT8_MAX) - 1.0f;
 }
--- a/dom/media/wave/WaveReader.h
+++ b/dom/media/wave/WaveReader.h
@@ -28,18 +28,16 @@ public:
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) override;
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual media::TimeIntervals GetBuffered() override;
 
-  virtual bool IsMediaSeekable() override;
-
 private:
   bool ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead = nullptr);
   bool LoadRIFFChunk();
   bool LoadFormatChunk(uint32_t aChunkSize);
   bool FindDataOffset(uint32_t aChunkSize);
   bool LoadListChunk(uint32_t aChunkSize, nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags);
   bool LoadAllChunks(nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags);
 
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -41,22 +41,16 @@ BufferDecoder::GetResource() const
 
 void
 BufferDecoder::NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped)
 {
   // ignore
 }
 
-void
-BufferDecoder::SetMediaSeekable(bool aMediaSeekable)
-{
-  // ignore
-}
-
 VideoFrameContainer*
 BufferDecoder::GetVideoFrameContainer()
 {
   // no video frame
   return nullptr;
 }
 
 layers::ImageContainer*
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -31,18 +31,16 @@ public:
   // This has to be called before decoding begins
   void BeginDecoding(TaskQueue* aTaskQueueIdentity);
 
   virtual MediaResource* GetResource() const final override;
 
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) final override;
 
-  virtual void SetMediaSeekable(bool aMediaSeekable) final override;
-
   virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   virtual layers::ImageContainer* GetImageContainer() final override;
 
   virtual MediaDecoderOwner* GetOwner() final override;
 
 private:
   virtual ~BufferDecoder();
   RefPtr<TaskQueue> mTaskQueueIdentity;
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -606,17 +606,17 @@ WebAudioDecodeJob::OnFailure(ErrorCode a
 
   mContext->RemoveFromDecodeQueue(this);
 }
 
 size_t
 WebAudioDecodeJob::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = 0;
-  amount += mContentType.SizeOfExcludingThisMustBeUnshared(aMallocSizeOf);
+  amount += mContentType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   if (mSuccessCallback) {
     amount += mSuccessCallback->SizeOfIncludingThis(aMallocSizeOf);
   }
   if (mFailureCallback) {
     amount += mFailureCallback->SizeOfIncludingThis(aMallocSizeOf);
   }
   if (mOutput) {
     amount += mOutput->SizeOfIncludingThis(aMallocSizeOf);
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -397,27 +397,23 @@ WebMReader::RetrieveWebMMetadata(MediaIn
       }
       if (NS_FAILED(mAudioDecoder->FinishInit(mInfo.mAudio))) {
         Cleanup();
         return NS_ERROR_FAILURE;
       }
     }
   }
 
+  mInfo.mMediaSeekable = nestegg_has_cues(mContext);
+
   *aInfo = mInfo;
 
   return NS_OK;
 }
 
-bool
-WebMReader::IsMediaSeekable()
-{
-  return mContext && nestegg_has_cues(mContext);
-}
-
 bool WebMReader::DecodeAudioPacket(NesteggPacketHolder* aHolder)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   int r = 0;
   unsigned int count = 0;
   r = nestegg_packet_count(aHolder->Packet(), &count);
   if (r == -1) {
--- a/dom/media/webm/WebMReader.h
+++ b/dom/media/webm/WebMReader.h
@@ -87,18 +87,16 @@ public:
 
   virtual RefPtr<MetadataPromise> AsyncReadMetadata() override;
 
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual media::TimeIntervals GetBuffered() override;
 
-  virtual bool IsMediaSeekable() override;
-
   // Value passed to NextPacket to determine if we are reading a video or an
   // audio packet.
   enum TrackType {
     VIDEO = 0,
     AUDIO = 1
   };
 
   // Read a packet from the nestegg file. Returns nullptr if all packets for
--- a/dom/media/webspeech/synth/windows/SapiService.cpp
+++ b/dom/media/webspeech/synth/windows/SapiService.cpp
@@ -183,17 +183,18 @@ SapiService::~SapiService()
 {
 }
 
 bool
 SapiService::Init()
 {
   MOZ_ASSERT(!mInitialized);
 
-  if (Preferences::GetBool("media.webspeech.synth.test")) {
+  if (Preferences::GetBool("media.webspeech.synth.test") ||
+      !Preferences::GetBool("media.webspeech.synth.enabled")) {
     // When enabled, we shouldn't add OS backend (Bug 1160844)
     return false;
   }
 
   if (FAILED(CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL, IID_ISpVoice,
                               getter_AddRefs(mSapiClient)))) {
     return false;
   }
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 this.EXPORTED_SYMBOLS = ["WebVTT"];
 
 /**
  * Code below is vtt.js the JS WebVTT implementation.
  * Current source code can be found at http://github.com/mozilla/vtt.js
  *
- * Code taken from commit 364c6b951a07306848a706d1d03c2a6ae942517d
+ * Code taken from commit 58d092419a1ee84e574ce2ba18bcbf92356fcb21
  */
 /**
  * Copyright 2013 vtt.js Contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -721,17 +721,18 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
 
   StyleBox.prototype.formatStyle = function(val, unit) {
     return val === 0 ? 0 : val + unit;
   };
 
   // Constructs the computed display state of the cue (a div). Places the div
   // into the overlay which should be a block level element (usually a div).
   function CueStyleBox(window, cue, styleOptions) {
-    var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent);
+    var isIE8 = (typeof navigator !== "undefined") &&
+      (/MSIE\s8\.0/).test(navigator.userAgent);
     var color = "rgba(255, 255, 255, 1)";
     var backgroundColor = "rgba(0, 0, 0, 0.8)";
 
     if (isIE8) {
       color = "rgb(255, 255, 255)";
       backgroundColor = "rgb(0, 0, 0)";
     }
 
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -233,19 +233,20 @@ nsPluginInstanceOwner::GetImageContainer
   container = LayerManager::CreateImageContainer();
 
   // Try to get it as an EGLImage first.
   RefPtr<Image> img;
   AttachToContainerAsEGLImage(container, mInstance, r, &img);
   if (!img) {
     AttachToContainerAsSurfaceTexture(container, mInstance, r, &img);
   }
-  MOZ_ASSERT(img);
-
-  container->SetCurrentImageInTransaction(img);
+
+  if (img) {
+    container->SetCurrentImageInTransaction(img);
+  }
 #else
   mInstance->GetImageContainer(getter_AddRefs(container));
 #endif
 
   return container.forget();
 }
 
 void
--- a/dom/plugins/ipc/PluginQuirks.cpp
+++ b/dom/plugins/ipc/PluginQuirks.cpp
@@ -1,69 +1,69 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: sw=4 ts=4 et :
- * 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 "PluginQuirks.h"
-
-#include "nsPluginHost.h"
-
-namespace mozilla {
-namespace plugins {
-
-int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
-                                     const nsCString& aPluginFilename)
-{
-    int quirks = 0;
-
-    nsPluginHost::SpecialType specialType = nsPluginHost::GetSpecialType(aMimeType);
-
-    if (specialType == nsPluginHost::eSpecialType_Silverlight) {
-        quirks |= QUIRK_SILVERLIGHT_DEFAULT_TRANSPARENT;
-#ifdef OS_WIN
-        quirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
-        quirks |= QUIRK_SILVERLIGHT_FOCUS_CHECK_PARENT;
-#endif
-    }
-
-    if (specialType == nsPluginHost::eSpecialType_Flash) {
-        quirks |= QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN;
-#ifdef OS_WIN
-        quirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
-        quirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS;
-        quirks |= QUIRK_FLASH_HOOK_SETLONGPTR;
-        quirks |= QUIRK_FLASH_HOOK_GETWINDOWINFO;
-        quirks |= QUIRK_FLASH_FIXUP_MOUSE_CAPTURE;
-#endif
-    }
-
-#ifdef OS_WIN
-    // QuickTime plugin usually loaded with audio/mpeg mimetype
-    NS_NAMED_LITERAL_CSTRING(quicktime, "npqtplugin");
-    if (FindInReadable(quicktime, aPluginFilename)) {
-        quirks |= QUIRK_QUICKTIME_AVOID_SETWINDOW;
-    }
-#endif
-
-#ifdef XP_MACOSX
-    // Whitelist Flash and Quicktime to support offline renderer
-    NS_NAMED_LITERAL_CSTRING(quicktime, "QuickTime Plugin.plugin");
-    if (specialType == nsPluginHost::eSpecialType_Flash) {
-        quirks |= QUIRK_FLASH_AVOID_CGMODE_CRASHES;
-        quirks |= QUIRK_ALLOW_OFFLINE_RENDERER;
-    } else if (FindInReadable(quicktime, aPluginFilename)) {
-        quirks |= QUIRK_ALLOW_OFFLINE_RENDERER;
-    }
-#endif
-
-#ifdef OS_WIN
-    if (specialType == nsPluginHost::eSpecialType_Unity) {
-        quirks |= QUIRK_UNITY_FIXUP_MOUSE_CAPTURE;
-    }
-#endif
-
-    return quirks;
-}
-
-} /* namespace plugins */
-} /* namespace mozilla */
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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 "PluginQuirks.h"
+
+#include "nsPluginHost.h"
+
+namespace mozilla {
+namespace plugins {
+
+int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
+                                     const nsCString& aPluginFilename)
+{
+    int quirks = 0;
+
+    nsPluginHost::SpecialType specialType = nsPluginHost::GetSpecialType(aMimeType);
+
+    if (specialType == nsPluginHost::eSpecialType_Silverlight) {
+        quirks |= QUIRK_SILVERLIGHT_DEFAULT_TRANSPARENT;
+#ifdef OS_WIN
+        quirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
+        quirks |= QUIRK_SILVERLIGHT_FOCUS_CHECK_PARENT;
+#endif
+    }
+
+    if (specialType == nsPluginHost::eSpecialType_Flash) {
+        quirks |= QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN;
+#ifdef OS_WIN
+        quirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK;
+        quirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS;
+        quirks |= QUIRK_FLASH_HOOK_SETLONGPTR;
+        quirks |= QUIRK_FLASH_HOOK_GETWINDOWINFO;
+        quirks |= QUIRK_FLASH_FIXUP_MOUSE_CAPTURE;
+#endif
+    }
+
+#ifdef OS_WIN
+    // QuickTime plugin usually loaded with audio/mpeg mimetype
+    NS_NAMED_LITERAL_CSTRING(quicktime, "npqtplugin");
+    if (FindInReadable(quicktime, aPluginFilename)) {
+        quirks |= QUIRK_QUICKTIME_AVOID_SETWINDOW;
+    }
+#endif
+
+#ifdef XP_MACOSX
+    // Whitelist Flash and Quicktime to support offline renderer
+    NS_NAMED_LITERAL_CSTRING(quicktime, "QuickTime Plugin.plugin");
+    if (specialType == nsPluginHost::eSpecialType_Flash) {
+        quirks |= QUIRK_FLASH_AVOID_CGMODE_CRASHES;
+        quirks |= QUIRK_ALLOW_OFFLINE_RENDERER;
+    } else if (FindInReadable(quicktime, aPluginFilename)) {
+        quirks |= QUIRK_ALLOW_OFFLINE_RENDERER;
+    }
+#endif
+
+#ifdef OS_WIN
+    if (specialType == nsPluginHost::eSpecialType_Unity) {
+        quirks |= QUIRK_UNITY_FIXUP_MOUSE_CAPTURE;
+    }
+#endif
+
+    return quirks;
+}
+
+} /* namespace plugins */
+} /* namespace mozilla */
--- a/dom/plugins/ipc/PluginQuirks.h
+++ b/dom/plugins/ipc/PluginQuirks.h
@@ -1,69 +1,69 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: sw=4 ts=4 et :
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef dom_plugins_PluginQuirks_h
-#define dom_plugins_PluginQuirks_h
-
-namespace mozilla {
-namespace plugins {
-
-// Quirks mode support for various plugin mime types
-enum PluginQuirks {
-  QUIRKS_NOT_INITIALIZED                          = 0,
-  // Silverlight assumes it is transparent in windowless mode. This quirk
-  // matches the logic in nsNPAPIPluginInstance::SetWindowless.
-  QUIRK_SILVERLIGHT_DEFAULT_TRANSPARENT           = 1 << 0,
-  // Win32: Hook TrackPopupMenu api so that we can swap out parent
-  // hwnds. The api will fail with parents not associated with our
-  // child ui thread. See WinlessHandleEvent for details.
-  QUIRK_WINLESS_TRACKPOPUP_HOOK                   = 1 << 1,
-  // Win32: Throttle flash WM_USER+1 heart beat messages to prevent
-  // flooding chromium's dispatch loop, which can cause ipc traffic
-  // processing lag.
-  QUIRK_FLASH_THROTTLE_WMUSER_EVENTS              = 1 << 2,
-  // Win32: Catch resets on our subclass by hooking SetWindowLong.
-  QUIRK_FLASH_HOOK_SETLONGPTR                     = 1 << 3,
-  // X11: Work around a bug in Flash up to 10.1 d51 at least, where
-  // expose event top left coordinates within the plugin-rect and
-  // not at the drawable origin are misinterpreted.
-  QUIRK_FLASH_EXPOSE_COORD_TRANSLATION            = 1 << 4,
-  // Win32: Catch get window info calls on the browser and tweak the
-  // results so mouse input works when flash is displaying it's settings
-  // window.
-  QUIRK_FLASH_HOOK_GETWINDOWINFO                  = 1 << 5,
-  // Win: Addresses a flash bug with mouse capture and full screen
-  // windows.
-  QUIRK_FLASH_FIXUP_MOUSE_CAPTURE                 = 1 << 6,
-  // Win: QuickTime steals focus on SetWindow calls even if it's hidden.
-  // Avoid calling SetWindow in that case.
-  QUIRK_QUICKTIME_AVOID_SETWINDOW                 = 1 << 7,
-  // Win: Check to make sure the parent window has focus before calling
-  // set focus on the child. Addresses a full screen dialog prompt
-  // problem in Silverlight.
-  QUIRK_SILVERLIGHT_FOCUS_CHECK_PARENT            = 1 << 8,
-  // Mac: Allow the plugin to use offline renderer mode.
-  // Use this only if the plugin is certified the support the offline renderer.
-  QUIRK_ALLOW_OFFLINE_RENDERER                    = 1 << 9,
-  // Mac: Work around a Flash bug that can cause plugin process crashes
-  // in CoreGraphics mode:  The Flash plugin sometimes accesses the
-  // CGContextRef we pass to it in NPP_HandleEvent(NPCocoaEventDrawRect)
-  // outside of that call.  See bug 804606.
-  QUIRK_FLASH_AVOID_CGMODE_CRASHES                = 1 << 10,
-  // Work around a Flash bug where it fails to check the error code of a
-  // NPN_GetValue(NPNVdocumentOrigin) call before trying to dereference
-  // its char* output.
-  QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN        = 1 << 11,
-  // Win: Addresses a Unity bug with mouse capture.
-  QUIRK_UNITY_FIXUP_MOUSE_CAPTURE                 = 1 << 12,
-};
-
-int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
-                                     const nsCString& aPluginFilename);
-
-} /* namespace plugins */
-} /* namespace mozilla */
-
-#endif  // ifndef dom_plugins_PluginQuirks_h
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef dom_plugins_PluginQuirks_h
+#define dom_plugins_PluginQuirks_h
+
+namespace mozilla {
+namespace plugins {
+
+// Quirks mode support for various plugin mime types
+enum PluginQuirks {
+  QUIRKS_NOT_INITIALIZED                          = 0,
+  // Silverlight assumes it is transparent in windowless mode. This quirk
+  // matches the logic in nsNPAPIPluginInstance::SetWindowless.
+  QUIRK_SILVERLIGHT_DEFAULT_TRANSPARENT           = 1 << 0,
+  // Win32: Hook TrackPopupMenu api so that we can swap out parent
+  // hwnds. The api will fail with parents not associated with our
+  // child ui thread. See WinlessHandleEvent for details.
+  QUIRK_WINLESS_TRACKPOPUP_HOOK                   = 1 << 1,
+  // Win32: Throttle flash WM_USER+1 heart beat messages to prevent
+  // flooding chromium's dispatch loop, which can cause ipc traffic
+  // processing lag.
+  QUIRK_FLASH_THROTTLE_WMUSER_EVENTS              = 1 << 2,
+  // Win32: Catch resets on our subclass by hooking SetWindowLong.
+  QUIRK_FLASH_HOOK_SETLONGPTR                     = 1 << 3,
+  // X11: Work around a bug in Flash up to 10.1 d51 at least, where
+  // expose event top left coordinates within the plugin-rect and
+  // not at the drawable origin are misinterpreted.
+  QUIRK_FLASH_EXPOSE_COORD_TRANSLATION            = 1 << 4,
+  // Win32: Catch get window info calls on the browser and tweak the
+  // results so mouse input works when flash is displaying it's settings
+  // window.
+  QUIRK_FLASH_HOOK_GETWINDOWINFO                  = 1 << 5,
+  // Win: Addresses a flash bug with mouse capture and full screen
+  // windows.
+  QUIRK_FLASH_FIXUP_MOUSE_CAPTURE                 = 1 << 6,
+  // Win: QuickTime steals focus on SetWindow calls even if it's hidden.
+  // Avoid calling SetWindow in that case.
+  QUIRK_QUICKTIME_AVOID_SETWINDOW                 = 1 << 7,
+  // Win: Check to make sure the parent window has focus before calling
+  // set focus on the child. Addresses a full screen dialog prompt
+  // problem in Silverlight.
+  QUIRK_SILVERLIGHT_FOCUS_CHECK_PARENT            = 1 << 8,
+  // Mac: Allow the plugin to use offline renderer mode.
+  // Use this only if the plugin is certified the support the offline renderer.
+  QUIRK_ALLOW_OFFLINE_RENDERER                    = 1 << 9,
+  // Mac: Work around a Flash bug that can cause plugin process crashes
+  // in CoreGraphics mode:  The Flash plugin sometimes accesses the
+  // CGContextRef we pass to it in NPP_HandleEvent(NPCocoaEventDrawRect)
+  // outside of that call.  See bug 804606.
+  QUIRK_FLASH_AVOID_CGMODE_CRASHES                = 1 << 10,
+  // Work around a Flash bug where it fails to check the error code of a
+  // NPN_GetValue(NPNVdocumentOrigin) call before trying to dereference
+  // its char* output.
+  QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN        = 1 << 11,
+  // Win: Addresses a Unity bug with mouse capture.
+  QUIRK_UNITY_FIXUP_MOUSE_CAPTURE                 = 1 << 12,
+};
+
+int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
+                                     const nsCString& aPluginFilename);
+
+} /* namespace plugins */
+} /* namespace mozilla */
+
+#endif  // ifndef dom_plugins_PluginQuirks_h
--- a/dom/security/SRICheck.cpp
+++ b/dom/security/SRICheck.cpp
@@ -6,22 +6,21 @@
 
 #include "SRICheck.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
 #include "nsIChannel.h"
-#include "nsICryptoHash.h"
 #include "nsIDocument.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
-#include "nsIStreamLoader.h"
+#include "nsIIncrementalStreamLoader.h"
 #include "nsIUnicharStreamLoader.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsWhitespaceTokenizer.h"
 
 static mozilla::LogModule*
 GetSriLog()
 {
@@ -92,75 +91,16 @@ IsEligible(nsIChannel* aChannel, const C
                                   NS_LITERAL_CSTRING("Sub-resource Integrity"),
                                   aDocument,
                                   nsContentUtils::eSECURITY_PROPERTIES,
                                   "IneligibleResource",
                                   params, ArrayLength(params));
   return NS_ERROR_SRI_NOT_ELIGIBLE;
 }
 
-/**
- * Compute the hash of a sub-resource and compare it with the expected
- * value.
- */
-static nsresult
-VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
-           uint32_t aStringLen, const uint8_t* aString,
-           const nsIDocument* aDocument)
-{
-  NS_ENSURE_ARG_POINTER(aString);
-  NS_ENSURE_ARG_POINTER(aDocument);
-
-  nsAutoCString base64Hash;
-  aMetadata.GetHash(aHashIndex, &base64Hash);
-  SRILOG(("SRICheck::VerifyHash, hash[%u]=%s", aHashIndex, base64Hash.get()));
-
-  nsAutoCString binaryHash;
-  if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
-    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
-                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
-                                    aDocument,
-                                    nsContentUtils::eSECURITY_PROPERTIES,
-                                    "InvalidIntegrityBase64");
-    return NS_ERROR_SRI_CORRUPT;
-  }
-
-  uint32_t hashLength;
-  int8_t hashType;
-  aMetadata.GetHashType(&hashType, &hashLength);
-  if (binaryHash.Length() != hashLength) {
-    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
-                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
-                                    aDocument,
-                                    nsContentUtils::eSECURITY_PROPERTIES,
-                                    "InvalidIntegrityLength");
-    return NS_ERROR_SRI_CORRUPT;
-  }
-
-  nsresult rv;
-  nsCOMPtr<nsICryptoHash> cryptoHash =
-    do_CreateInstance("@mozilla.org/security/hash;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = cryptoHash->Init(hashType);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = cryptoHash->Update(aString, aStringLen);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsAutoCString computedHash;
-  rv = cryptoHash->Finish(false, computedHash);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (!binaryHash.Equals(computedHash)) {
-    SRILOG(("SRICheck::VerifyHash, hash[%u] did not match", aHashIndex));
-    return NS_ERROR_SRI_CORRUPT;
-  }
-
-  SRILOG(("SRICheck::VerifyHash, hash[%u] verified successfully", aHashIndex));
-  return NS_OK;
-}
-
 /* static */ nsresult
 SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
                             const nsIDocument* aDocument,
                             SRIMetadata* outMetadata)
 {
   NS_ENSURE_ARG_POINTER(outMetadata);
   NS_ENSURE_ARG_POINTER(aDocument);
   MOZ_ASSERT(outMetadata->IsEmpty()); // caller must pass empty metadata
@@ -234,62 +174,16 @@ SRICheck::IntegrityMetadata(const nsAStr
       SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
     } else {
       SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
     }
   }
   return NS_OK;
 }
 
-static nsresult
-VerifyIntegrityInternal(const SRIMetadata& aMetadata,
-                        nsIChannel* aChannel,
-                        const CORSMode aCORSMode,
-                        uint32_t aStringLen,
-                        const uint8_t* aString,
-                        const nsIDocument* aDocument)
-{
-  MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
-
-  // IntegrityMetadata() checks this and returns "no metadata" if
-  // it's disabled so we should never make it this far
-  MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
-
-  if (NS_FAILED(IsEligible(aChannel, aCORSMode, aDocument))) {
-    return NS_ERROR_SRI_NOT_ELIGIBLE;
-  }
-  if (!aMetadata.IsValid()) {
-    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
-                                    aDocument,
-                                    nsContentUtils::eSECURITY_PROPERTIES,
-                                    "NoValidMetadata");
-    return NS_OK; // ignore invalid metadata for forward-compatibility
-  }
-
-  for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
-    if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aStringLen,
-                                aString, aDocument))) {
-      return NS_OK; // stop at the first valid hash
-    }
-  }
-
-  nsAutoCString alg;
-  aMetadata.GetAlgorithm(&alg);
-  NS_ConvertUTF8toUTF16 algUTF16(alg);
-  const char16_t* params[] = { algUTF16.get() };
-  nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
-                                  NS_LITERAL_CSTRING("Sub-resource Integrity"),
-                                  aDocument,
-                                  nsContentUtils::eSECURITY_PROPERTIES,
-                                  "IntegrityMismatch",
-                                  params, ArrayLength(params));
-  return NS_ERROR_SRI_CORRUPT;
-}
-
 /* static */ nsresult
 SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
                           nsIUnicharStreamLoader* aLoader,
                           const CORSMode aCORSMode,
                           const nsAString& aString,
                           const nsIDocument* aDocument)
 {
   NS_ENSURE_ARG_POINTER(aLoader);
@@ -301,46 +195,194 @@ SRICheck::VerifyIntegrity(const SRIMetad
   if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
     nsAutoCString requestURL;
     nsCOMPtr<nsIURI> originalURI;
     if (channel &&
         NS_SUCCEEDED(channel->GetOriginalURI(getter_AddRefs(originalURI))) &&
         originalURI) {
       originalURI->GetAsciiSpec(requestURL);
     }
-    SRILOG(("SRICheck::VerifyIntegrity (unichar stream), url=%s (length=%u)",
-            requestURL.get(), utf8Hash.Length()));
+    SRILOG(("SRICheck::VerifyIntegrity (unichar stream)"));
   }
 
-  return VerifyIntegrityInternal(aMetadata, channel, aCORSMode,
-                                 utf8Hash.Length(), (uint8_t*)utf8Hash.get(),
-                                 aDocument);
+  SRICheckDataVerifier verifier(aMetadata, aDocument);
+  nsresult rv;
+  rv = verifier.Update(utf8Hash.Length(), (uint8_t*)utf8Hash.get());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return verifier.Verify(aMetadata, channel, aCORSMode, aDocument);
+}
+
+//////////////////////////////////////////////////////////////
+//
+//////////////////////////////////////////////////////////////
+SRICheckDataVerifier::SRICheckDataVerifier(const SRIMetadata& aMetadata,
+                                           const nsIDocument* aDocument)
+  : mCryptoHash(nullptr),
+    mBytesHashed(0),
+    mInvalidMetadata(false),
+    mComplete(false)
+{
+  MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
+
+  // IntegrityMetadata() checks this and returns "no metadata" if
+  // it's disabled so we should never make it this far
+  MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
+
+  if (!aMetadata.IsValid()) {
+    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "NoValidMetadata");
+    mInvalidMetadata = true;
+    return; // ignore invalid metadata for forward-compatibility
+  }
+
+  uint32_t hashLength;
+  aMetadata.GetHashType(&mHashType, &hashLength);
+}
+
+nsresult
+SRICheckDataVerifier::EnsureCryptoHash()
+{
+  MOZ_ASSERT(!mInvalidMetadata);
+
+  if (mCryptoHash) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsICryptoHash> cryptoHash =
+    do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = cryptoHash->Init(mHashType);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mCryptoHash = cryptoHash;
+  return NS_OK;
 }
 
-/* static */ nsresult
-SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
-                          nsIStreamLoader* aLoader,
-                          const CORSMode aCORSMode,
-                          uint32_t aStringLen,
-                          const uint8_t* aString,
-                          const nsIDocument* aDocument)
+nsresult
+SRICheckDataVerifier::Update(uint32_t aStringLen, const uint8_t* aString)
+{
+  NS_ENSURE_ARG_POINTER(aString);
+  if (mInvalidMetadata) {
+    return NS_OK; // ignoring any data updates, see mInvalidMetadata usage
+  }
+
+  nsresult rv;
+  rv = EnsureCryptoHash();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mBytesHashed += aStringLen;
+
+  return mCryptoHash->Update(aString, aStringLen);
+}
+
+nsresult
+SRICheckDataVerifier::Finish()
+{
+  if (mInvalidMetadata || mComplete) {
+    return NS_OK; // already finished or invalid metadata
+  }
+
+  nsresult rv;
+  rv = EnsureCryptoHash(); // we need computed hash even for 0-length data
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mCryptoHash->Finish(false, mComputedHash);
+  mCryptoHash = nullptr;
+  mComplete = true;
+  return rv;
+}
+
+nsresult
+SRICheckDataVerifier::VerifyHash(const SRIMetadata& aMetadata,
+                                 uint32_t aHashIndex,
+                                 const nsIDocument* aDocument)
 {
-  NS_ENSURE_ARG_POINTER(aLoader);
+  NS_ENSURE_ARG_POINTER(aDocument);
+
+  nsAutoCString base64Hash;
+  aMetadata.GetHash(aHashIndex, &base64Hash);
+  SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u]=%s", aHashIndex, base64Hash.get()));
+
+  nsAutoCString binaryHash;
+  if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
+    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "InvalidIntegrityBase64");
+    return NS_ERROR_SRI_CORRUPT;
+  }
 
-  nsCOMPtr<nsIRequest> request;
-  aLoader->GetRequest(getter_AddRefs(request));
-  NS_ENSURE_ARG_POINTER(request);
-  nsCOMPtr<nsIChannel> channel;
-  channel = do_QueryInterface(request);
+  uint32_t hashLength;
+  int8_t hashType;
+  aMetadata.GetHashType(&hashType, &hashLength);
+  if (binaryHash.Length() != hashLength) {
+    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "InvalidIntegrityLength");
+    return NS_ERROR_SRI_CORRUPT;
+  }
+
+  if (!binaryHash.Equals(mComputedHash)) {
+    SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] did not match", aHashIndex));
+    return NS_ERROR_SRI_CORRUPT;
+  }
+
+  SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] verified successfully", aHashIndex));
+  return NS_OK;
+}
+
+nsresult
+SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata,
+                             nsIChannel* aChannel,
+                             const CORSMode aCORSMode,
+                             const nsIDocument* aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aDocument);
 
   if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
     nsAutoCString requestURL;
+    nsCOMPtr<nsIRequest> request;
+    request = do_QueryInterface(aChannel);
     request->GetName(requestURL);
-    SRILOG(("SRICheck::VerifyIntegrity (stream), url=%s (length=%u)",
-            requestURL.get(), aStringLen));
+    SRILOG(("SRICheckDataVerifier::Verify, url=%s (length=%lu)",
+            requestURL.get(), mBytesHashed));
+  }
+
+  nsresult rv = Finish();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (NS_FAILED(IsEligible(aChannel, aCORSMode, aDocument))) {
+    return NS_ERROR_SRI_NOT_ELIGIBLE;
+  }
+
+  if (mInvalidMetadata) {
+    return NS_OK; // ignore invalid metadata for forward-compatibility
   }
 
-  return VerifyIntegrityInternal(aMetadata, channel, aCORSMode,
-                                 aStringLen, aString, aDocument);
+  for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
+    if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aDocument))) {
+      return NS_OK; // stop at the first valid hash
+    }
+  }
+
+  nsAutoCString alg;
+  aMetadata.GetAlgorithm(&alg);
+  NS_ConvertUTF8toUTF16 algUTF16(alg);
+  const char16_t* params[] = { algUTF16.get() };
+  nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                  NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                  aDocument,
+                                  nsContentUtils::eSECURITY_PROPERTIES,
+                                  "IntegrityMismatch",
+                                  params, ArrayLength(params));
+  return NS_ERROR_SRI_CORRUPT;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/security/SRICheck.h
+++ b/dom/security/SRICheck.h
@@ -4,20 +4,21 @@
  * 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_SRICheck_h
 #define mozilla_dom_SRICheck_h
 
 #include "mozilla/CORSMode.h"
 #include "nsCOMPtr.h"
+#include "nsICryptoHash.h"
 #include "SRIMetadata.h"
 
+class nsIChannel;
 class nsIDocument;
-class nsIStreamLoader;
 class nsIUnicharStreamLoader;
 
 namespace mozilla {
 namespace dom {
 
 class SRICheck final
 {
 public:
@@ -36,25 +37,38 @@ public:
    * Process the integrity attribute of the element.  A result of false
    * must prevent the resource from loading.
    */
   static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
                                   nsIUnicharStreamLoader* aLoader,
                                   const CORSMode aCORSMode,
                                   const nsAString& aString,
                                   const nsIDocument* aDocument);
+};
 
-  /**
-   * Process the integrity attribute of the element.  A result of false
-   * must prevent the resource from loading.
-   */
-  static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
-                                  nsIStreamLoader* aLoader,
-                                  const CORSMode aCORSMode,
-                                  uint32_t aStringLen,
-                                  const uint8_t* aString,
-                                  const nsIDocument* aDocument);
+class SRICheckDataVerifier final
+{
+  public:
+    SRICheckDataVerifier(const SRIMetadata& aMetadata,
+                         const nsIDocument* aDocument);
+
+    nsresult Update(uint32_t aStringLen, const uint8_t* aString);
+    nsresult Verify(const SRIMetadata& aMetadata, nsIChannel* aChannel,
+                    const CORSMode aCORSMode, const nsIDocument* aDocument);
+
+  private:
+    nsCOMPtr<nsICryptoHash> mCryptoHash;
+    nsAutoCString           mComputedHash;
+    size_t                  mBytesHashed;
+    int8_t                  mHashType;
+    bool                    mInvalidMetadata;
+    bool                    mComplete;
+
+    nsresult EnsureCryptoHash();
+    nsresult Finish();
+    nsresult VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
+                        const nsIDocument* aDocument);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_SRICheck_h
--- a/dom/webidl/EventTarget.webidl
+++ b/dom/webidl/EventTarget.webidl
@@ -44,11 +44,11 @@ partial interface EventTarget {
   [ChromeOnly]
   EventHandler getEventHandler(DOMString type);
 };
 
 // Mozilla extension to make firing events on event targets from
 // chrome easier.  This returns the window which can be used to create
 // events to fire at this EventTarget, or null if there isn't one.
 partial interface EventTarget {
-  [ChromeOnly, Exposed=Window, BinaryName="ownerGlobalForBindings"]
+  [ChromeOnly, Exposed=(Window,System), BinaryName="ownerGlobalForBindings"]
   readonly attribute WindowProxy? ownerGlobal;
 };
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -606,23 +606,16 @@ nsXMLContentSink::CloseElement(nsIConten
         bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH;
         if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) {
           nsAutoString hrefVal;
           aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
           if (!hrefVal.IsEmpty()) {
             PrefetchHref(hrefVal, aContent, hasPrefetch);
           }
         }
-        if (linkTypes & nsStyleLinkElement::eDNS_PREFETCH) {
-          nsAutoString hrefVal;
-          aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
-          if (!hrefVal.IsEmpty()) {
-            PrefetchDNS(hrefVal);
-          }
-        }
       }
     }
   }
 
   return rv;
 }
 
 nsresult
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -16,16 +16,17 @@
 #include "nsIContent.h"
 #include "nsIDOMXULCommandDispatcher.h"
 #include "nsIDOMXULDocument.h"
 #include "nsCOMArray.h"
 #include "nsIURI.h"
 #include "nsIXULDocument.h"
 #include "nsScriptLoader.h"
 #include "nsIStreamListener.h"
+#include "nsIStreamLoader.h"
 #include "nsICSSLoaderObserver.h"
 #include "nsIXULStore.h"
 
 #include "mozilla/Attributes.h"
 
 #include "js/TracingAPI.h"
 #include "js/TypeDecls.h"
 
--- a/editor/libeditor/tests/test_contenteditable_text_input_handling.html
+++ b/editor/libeditor/tests/test_contenteditable_text_input_handling.html
@@ -20,16 +20,18 @@
 <pre id="test">
 </pre>
 
 <script class="testbody" type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(runTests);
 
+const kLF = !navigator.platform.indexOf("Win") ? "\r\n" : "\n";
+
 function runTests()
 {
   var fm = Components.classes["@mozilla.org/focus-manager;1"].
              getService(Components.interfaces.nsIFocusManager);
 
   var listener = {
     handleEvent: function _hv(aEvent)
     {
@@ -77,18 +79,29 @@ function runTests()
   textareaInEditor._isContentEditable = false;
   textareaInEditor._description = "textarea element in contenteditable editor";
 
   function getTextValue(aElement)
   {
     if (aElement == editor) {
       var value = "";
       for (var node = aElement.firstChild; node; node = node.nextSibling) {
-        if (node.nodeType == 3) {
+        if (node.nodeType == Node.TEXT_NODE) {
           value += node.data;
+        } else if (node.nodeType == Node.ELEMENT_NODE) {
+          var tagName = node.tagName.toLowerCase();
+          switch (tagName) {
+            case "input":
+            case "textarea":
+              value += kLF;
+              break;
+            default:
+              ok(false, "Undefined tag is used in the editor: " + tagName);
+              break;
+          }
         }
       }
       return value;
     }
     return aElement.value;
   }
 
   function testTextInput(aFocus)
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -585,16 +585,17 @@ NeedIntermediateSurface(const Pattern& a
     return false;
 
   return true;
 }
 
 DrawTargetCairo::DrawTargetCairo()
   : mContext(nullptr)
   , mSurface(nullptr)
+  , mTransformSingular(false)
   , mLockedBits(nullptr)
 {
 }
 
 DrawTargetCairo::~DrawTargetCairo()
 {
   cairo_destroy(mContext);
   if (mSurface) {
@@ -770,16 +771,20 @@ PaintWithAlpha(cairo_t* aContext, const 
 
 void
 DrawTargetCairo::DrawSurface(SourceSurface *aSurface,
                              const Rect &aDest,
                              const Rect &aSource,
                              const DrawSurfaceOptions &aSurfOptions,
                              const DrawOptions &aOptions)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aSurface);
 
   float sx = aSource.Width() / aDest.Width();
   float sy = aSource.Height() / aDest.Height();
 
   cairo_matrix_t src_mat;
   cairo_matrix_init_translate(&src_mat, aSource.X(), aSource.Y());
@@ -963,16 +968,20 @@ DrawTargetCairo::DrawPattern(const Patte
   cairo_pattern_destroy(pat);
 }
 
 void
 DrawTargetCairo::FillRect(const Rect &aRect,
                           const Pattern &aPattern,
                           const DrawOptions &aOptions)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   bool restoreTransform = false;
   Matrix mat;
   Rect r = aRect;
 
   /* Clamp coordinates to work around a design bug in cairo */
   if (r.width > CAIRO_COORD_MAX ||
@@ -1038,16 +1047,20 @@ DrawTargetCairo::CopySurfaceInternal(cai
   cairo_fill(mContext);
 }
 
 void
 DrawTargetCairo::CopySurface(SourceSurface *aSurface,
                              const IntRect &aSource,
                              const IntPoint &aDest)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aSurface);
 
   if (!aSurface) {
     gfxWarning() << "Unsupported surface type specified";
     return;
   }
 
@@ -1060,16 +1073,20 @@ DrawTargetCairo::CopySurface(SourceSurfa
   CopySurfaceInternal(surf, aSource, aDest);
   cairo_surface_destroy(surf);
 }
 
 void
 DrawTargetCairo::CopyRect(const IntRect &aSource,
                           const IntPoint &aDest)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   IntRect source = aSource;
   cairo_surface_t* surf = mSurface;
 
   if (!SupportsSelfCopy(mSurface) &&
       aDest.y >= aSource.y &&
       aDest.y < aSource.YMost()) {
@@ -1092,16 +1109,20 @@ DrawTargetCairo::CopyRect(const IntRect 
   if (surf != mSurface) {
     cairo_surface_destroy(surf);
   }
 }
 
 void
 DrawTargetCairo::ClearRect(const Rect& aRect)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   if (!mContext || aRect.Width() <= 0 || aRect.Height() <= 0 ||
       !IsFinite(aRect.X()) || !IsFinite(aRect.Width()) ||
       !IsFinite(aRect.Y()) || !IsFinite(aRect.Height())) {
     gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) << " with " << aRect.Width() << "x" << aRect.Height() << " [" << aRect.X() << ", " << aRect.Y() << "]";
   }
 
@@ -1114,62 +1135,78 @@ DrawTargetCairo::ClearRect(const Rect& a
 }
 
 void
 DrawTargetCairo::StrokeRect(const Rect &aRect,
                             const Pattern &aPattern,
                             const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
                             const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   cairo_new_path(mContext);
   cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height());
 
   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 }
 
 void
 DrawTargetCairo::StrokeLine(const Point &aStart,
                             const Point &aEnd,
                             const Pattern &aPattern,
                             const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
                             const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   cairo_new_path(mContext);
   cairo_move_to(mContext, aStart.x, aStart.y);
   cairo_line_to(mContext, aEnd.x, aEnd.y);
 
   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 }
 
 void
 DrawTargetCairo::Stroke(const Path *aPath,
                         const Pattern &aPattern,
                         const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
                         const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext, aPath);
 
   if (aPath->GetBackendType() != BackendType::CAIRO)
     return;
 
   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
   path->SetPathOnContext(mContext);
 
   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 }
 
 void
 DrawTargetCairo::Fill(const Path *aPath,
                       const Pattern &aPattern,
                       const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext, aPath);
 
   if (aPath->GetBackendType() != BackendType::CAIRO)
     return;
 
   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
   path->SetPathOnContext(mContext);
 
@@ -1188,16 +1225,20 @@ DrawTargetCairo::SetPermitSubpixelAA(boo
 
 void
 DrawTargetCairo::FillGlyphs(ScaledFont *aFont,
                             const GlyphBuffer &aBuffer,
                             const Pattern &aPattern,
                             const DrawOptions &aOptions,
                             const GlyphRenderingOptions*)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aPattern);
 
   ScaledFontBase* scaledFont = static_cast<ScaledFontBase*>(aFont);
   cairo_set_scaled_font(mContext, scaledFont->GetCairoScaledFont());
 
   cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
   if (!pat)
@@ -1227,16 +1268,20 @@ DrawTargetCairo::FillGlyphs(ScaledFont *
   cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs);
 }
 
 void
 DrawTargetCairo::Mask(const Pattern &aSource,
                       const Pattern &aMask,
                       const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clearSource(aSource);
   AutoClearDeviceOffset clearMask(aMask);
 
   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 
   cairo_pattern_t* source = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
   if (!source) {
@@ -1265,16 +1310,20 @@ DrawTargetCairo::Mask(const Pattern &aSo
 }
 
 void
 DrawTargetCairo::MaskSurface(const Pattern &aSource,
                              SourceSurface *aMask,
                              Point aOffset,
                              const DrawOptions &aOptions)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clearSource(aSource);
   AutoClearDeviceOffset clearMask(aMask);
 
   if (!PatternIsCompatible(aSource)) {
     return;
   }
 
@@ -1331,47 +1380,54 @@ DrawTargetCairo::PushClip(const Path *aP
   if (aPath->GetBackendType() != BackendType::CAIRO) {
     return;
   }
 
   WillChange(aPath);
   cairo_save(mContext);
 
   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
-  path->SetPathOnContext(mContext);
+
+  if (mTransformSingular) {
+    cairo_new_path(mContext);
+    cairo_rectangle(mContext, 0, 0, 0, 0);
+  } else {
+    path->SetPathOnContext(mContext);
+  }
   cairo_clip_preserve(mContext);
 }
 
 void
 DrawTargetCairo::PushClipRect(const Rect& aRect)
 {
   WillChange();
   cairo_save(mContext);
 
   cairo_new_path(mContext);
-  cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+  if (mTransformSingular) {
+    cairo_rectangle(mContext, 0, 0, 0, 0);
+  } else {
+    cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+  }
   cairo_clip_preserve(mContext);
 }
 
 void
 DrawTargetCairo::PopClip()
 {
   // save/restore does not affect the path, so no need to call WillChange()
 
   // cairo_restore will restore the transform too and we don't want to do that
   // so we'll save it now and restore it after the cairo_restore
   cairo_matrix_t mat;
   cairo_get_matrix(mContext, &mat);
 
   cairo_restore(mContext);
 
   cairo_set_matrix(mContext, &mat);
-
-  MOZ_ASSERT(cairo_status(mContext) || GetTransform() == Matrix(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0),
-             "Transforms are out of sync");
 }
 
 already_AddRefed<PathBuilder>
 DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FillRule::FILL_WINDING */) const
 {
   return MakeAndAddRef<PathBuilderCairo>(aFillRule);
 }
 
@@ -1704,21 +1760,24 @@ DrawTargetCairo::WillChange(const Path* 
 {
   MarkSnapshotIndependent();
   MOZ_ASSERT(!mLockedBits);
 }
 
 void
 DrawTargetCairo::SetTransform(const Matrix& aTransform)
 {
-  mTransform = aTransform;
+  DrawTarget::SetTransform(aTransform);
 
-  cairo_matrix_t mat;
-  GfxMatrixToCairoMatrix(mTransform, mat);
-  cairo_set_matrix(mContext, &mat);
+  mTransformSingular = aTransform.IsSingular();
+  if (!mTransformSingular) {
+    cairo_matrix_t mat;
+    GfxMatrixToCairoMatrix(mTransform, mat);
+    cairo_set_matrix(mContext, &mat);
+  }
 }
 
 Rect
 DrawTargetCairo::GetUserSpaceClip()
 {
   double clipX1, clipY1, clipX2, clipY2;
   cairo_clip_extents(mContext, &clipX1, &clipY1, &clipX2, &clipY2);
   return Rect(clipX1, clipY1, clipX2 - clipX1, clipY2 - clipY1); // Narrowing of doubles to floats
--- a/gfx/2d/DrawTargetCairo.h
+++ b/gfx/2d/DrawTargetCairo.h
@@ -204,16 +204,17 @@ private: // methods
   // If the current operator is "source" then clear the destination before we
   // draw into it, to simulate the effect of an unbounded source operator.
   void ClearSurfaceForUnboundedSource(const CompositionOp &aOperator);
 
 private: // data
   cairo_t* mContext;
   cairo_surface_t* mSurface;
   IntSize mSize;
+  bool mTransformSingular;
 
   uint8_t* mLockedBits;
 
   // The latest snapshot of this surface. This needs to be told when this
   // target is modified. We keep it alive as a cache.
   RefPtr<SourceSurfaceCairo> mSnapshot;
   static cairo_surface_t *mDummySurface;
 };
--- a/gfx/angle/AUTHORS
+++ b/gfx/angle/AUTHORS
@@ -36,8 +36,9 @@ James Hauxwell
 Sam Hocevar
 Pierre Leveille
 Jonathan Liu
 Boying Lu
 Aitor Moreno
 Yuri O'Donnell
 Josh Soref
 Maks Naumov
+Jinyoung Hur
--- a/gfx/angle/BUILD.gn
+++ b/gfx/angle/BUILD.gn
@@ -374,16 +374,17 @@ static_library("angle_util") {
   sources = rebase_path(util_gypi.util_sources, ".", "util")
 
   if (is_win) {
     sources += rebase_path(util_gypi.util_win32_sources, ".", "util")
   }
 
   if (is_linux) {
     sources += rebase_path(util_gypi.util_linux_sources, ".", "util")
+    libs = [ "rt" ]
   }
 
   if (is_mac) {
     sources += rebase_path(util_gypi.util_osx_sources, ".", "util")
   }
 
   if (use_x11) {
     sources += rebase_path(util_gypi.util_x11_sources, ".", "util")
--- a/gfx/angle/README.md
+++ b/gfx/angle/README.md
@@ -1,22 +1,39 @@
 #ANGLE
-The goal of ANGLE is to allow Windows users to seamlessly run WebGL and other OpenGL ES content by translating OpenGL ES API calls to DirectX 9 or DirectX 11 API calls.
+The goal of ANGLE is to allow users of multiple operating systems to seamlessly run WebGL and other OpenGL ES content by translating OpenGL ES API calls to one of the hardware-supported APIs available for that platform. ANGLE currently provides translation from OpenGL ES 2.0 to desktop OpenGL, Direct3D 9, and Direct3D 11. Support for translation from OpenGL ES 3.0 to all of these APIs is nearing completion, and future plans include enabling validated ES-to-ES support.
+
+|                |  Direct3D 9   |    Direct3D 11      |    Desktop GL      |    GL ES  |
+|----------------|:-------------:|:-------------------:|:------------------:|:---------:|
+| OpenGL ES 2.0  |    complete   |      complete       |     complete       |   planned |
+| OpenGL ES 3.0  |               |  nearing completion | nearing completion |   planned |
+[Level of OpenGL ES support via backing renderers]
 
-ANGLE is a conformant implementation of the OpenGL ES 2.0 specification that is hardware‐accelerated via Direct3D. ANGLE v1.0.772 was certified compliant by passing the ES 2.0.3 conformance tests in October 2011. ANGLE also provides an implementation of the EGL 1.4 specification. Work on ANGLE's OpenGL ES 3.0 implementation is currently in progress, but should not be considered stable.
+|             |    Direct3D 9  |   Direct3D 11  |   Desktop GL  |
+|------------:|:--------------:|:--------------:|:-------------:|
+| Windows     |        *       |        *       |       *       |
+| Linux       |                |                |       *       |
+| Mac OS X    |                |                |   in progress |
+[Platform support via backing renderers]
+
+ANGLE v1.0.772 was certified compliant by passing the ES 2.0.3 conformance tests in October 2011. ANGLE also provides an implementation of the EGL 1.4 specification.
 
 ANGLE is used as the default WebGL backend for both Google Chrome and Mozilla Firefox on Windows platforms. Chrome uses ANGLE for all graphics rendering on Windows, including the accelerated Canvas2D implementation and the Native Client sandbox environment.
 
 Portions of the ANGLE shader compiler are used as a shader validator and translator by WebGL implementations across multiple platforms. It is used on Mac OS X, Linux, and in mobile variants of the browsers. Having one shader validator helps to ensure that a consistent set of GLSL ES shaders are accepted across browsers and platforms. The shader translator can be used to translate shaders to other shading languages, and to optionally apply shader modifications to work around bugs or quirks in the native graphics drivers. The translator targets Desktop GLSL, Direct3D HLSL, and even ESSL for native GLES2 platforms.
 
 ##Building
 View the [Dev setup instructions](doc/DevSetup.md).
 
 ##Contributing
 * Join our [Google group](https://groups.google.com/group/angleproject) to keep up to date.
 * Join us on IRC in the #ANGLEproject channel on FreeNode.
 * Read about ANGLE development in our [documentation](doc).
 * Become a [code contributor](doc/ContributingCode.md).
+* Refer to ANGLE's [coding standard](doc/CodingStandard.md).
+* Learn how to [build ANGLE for Chromium development](doc/BuildingAngleForChromiumDevelopment.md).
+* [Choose an ANGLE branch](doc/ChoosingANGLEBranch.md) to track in your own project.
 * File bugs in the [issue tracker](http://code.google.com/p/angleproject/issues/list) (preferably with an isolated test-case).
 * Read about WebGL on the [Khronos WebGL Wiki](http://khronos.org/webgl/wiki/Main_Page).
 * Learn about implementation details in the [OpenGL Insights chapter on ANGLE](http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-ANGLE.pdf) and this [ANGLE presentation](https://code.google.com/p/angleproject/downloads/detail?name=ANGLE%20and%20Cross-Platform%20WebGL%20Support.pdf&can=2&q=).
 * Learn about the past, present, and future of the ANGLE implementation in [this recent presentation](https://docs.google.com/presentation/d/1CucIsdGVDmdTWRUbg68IxLE5jXwCb2y1E9YVhQo0thg/pub?start=false&loop=false).
-* If you use ANGLE in your own project, we'd love to hear about it!
\ No newline at end of file
+* If you use ANGLE in your own project, we'd love to hear about it!
+
--- a/gfx/angle/include/GLSLANG/ShaderLang.h
+++ b/gfx/angle/include/GLSLANG/ShaderLang.h
@@ -261,17 +261,19 @@ typedef struct
     int ARM_shader_framebuffer_fetch;
 
     // Set to 1 to enable replacing GL_EXT_draw_buffers #extension directives
     // with GL_NV_draw_buffers in ESSL output. This flag can be used to emulate
     // EXT_draw_buffers by using it in combination with GLES3.0 glDrawBuffers
     // function. This applies to Tegra K1 devices.
     int NV_draw_buffers;
 
-    // Set to 1 if highp precision is supported in the fragment language.
+    // Set to 1 if highp precision is supported in the ESSL 1.00 version of the
+    // fragment language. Does not affect versions of the language where highp
+    // support is mandatory.
     // Default is 0.
     int FragmentPrecisionHigh;
 
     // GLSL ES 3.0 constants.
     int MaxVertexOutputVectors;
     int MaxFragmentInputVectors;
     int MinProgramTexelOffset;
     int MaxProgramTexelOffset;
--- a/gfx/angle/include/platform/Platform.h
+++ b/gfx/angle/include/platform/Platform.h
@@ -6,17 +6,34 @@
 // Platform.h: The public interface ANGLE exposes to the API layer, for
 //   doing platform-specific tasks like gathering data, or for tracing.
 
 #ifndef ANGLE_PLATFORM_H
 #define ANGLE_PLATFORM_H
 
 #include <stdint.h>
 
-#include "../export.h"
+#if defined(_WIN32)
+#   if !defined(LIBANGLE_IMPLEMENTATION)
+#       define ANGLE_PLATFORM_EXPORT __declspec(dllimport)
+#   endif
+#elif defined(__GNUC__)
+#   if defined(LIBANGLE_IMPLEMENTATION)
+#       define ANGLE_PLATFORM_EXPORT __attribute__((visibility ("default")))
+#   endif
+#endif
+#if !defined(ANGLE_PLATFORM_EXPORT)
+#   define ANGLE_PLATFORM_EXPORT
+#endif
+
+#if defined(_WIN32)
+#   define ANGLE_APIENTRY __stdcall
+#else
+#   define ANGLE_APIENTRY
+#endif
 
 namespace angle
 {
 
 class Platform
 {
   public:
 
@@ -129,18 +146,23 @@ class Platform
     virtual void histogramBoolean(const char *name, bool sample) { }
 
   protected:
     virtual ~Platform() { }
 };
 
 }
 
-typedef void(*ANGLEPlatformInitializeFunc)(angle::Platform*);
-ANGLE_EXPORT void ANGLEPlatformInitialize(angle::Platform*);
+extern "C"
+{
+
+typedef void (ANGLE_APIENTRY *ANGLEPlatformInitializeFunc)(angle::Platform*);
+ANGLE_PLATFORM_EXPORT void ANGLE_APIENTRY ANGLEPlatformInitialize(angle::Platform*);
 
-typedef void (*ANGLEPlatformShutdownFunc)();
-ANGLE_EXPORT void ANGLEPlatformShutdown();
+typedef void (ANGLE_APIENTRY *ANGLEPlatformShutdownFunc)();
+ANGLE_PLATFORM_EXPORT void ANGLE_APIENTRY ANGLEPlatformShutdown();
 
-typedef angle::Platform *(*ANGLEPlatformCurrentFunc)();
-ANGLE_EXPORT angle::Platform *ANGLEPlatformCurrent();
+typedef angle::Platform *(ANGLE_APIENTRY *ANGLEPlatformCurrentFunc)();
+ANGLE_PLATFORM_EXPORT angle::Platform *ANGLE_APIENTRY ANGLEPlatformCurrent();
+
+}
 
 #endif // ANGLE_PLATFORM_H
--- a/gfx/angle/moz.build
+++ b/gfx/angle/moz.build
@@ -41,16 +41,17 @@ UNIFIED_SOURCES += [
     'src/compiler/translator/Compiler.cpp',
     'src/compiler/translator/depgraph/DependencyGraph.cpp',
     'src/compiler/translator/depgraph/DependencyGraphBuilder.cpp',
     'src/compiler/translator/depgraph/DependencyGraphOutput.cpp',
     'src/compiler/translator/depgraph/DependencyGraphTraverse.cpp',
     'src/compiler/translator/Diagnostics.cpp',
     'src/compiler/translator/DirectiveHandler.cpp',
     'src/compiler/translator/EmulatePrecision.cpp',
+    'src/compiler/translator/ExtensionGLSL.cpp',
     'src/compiler/translator/FlagStd140Structs.cpp',
     'src/compiler/translator/ForLoopUnroll.cpp',
     'src/compiler/translator/InfoSink.cpp',
     'src/compiler/translator/Initialize.cpp',
     'src/compiler/translator/InitializeDll.cpp',
     'src/compiler/translator/InitializeParseContext.cpp',
     'src/compiler/translator/InitializeVariables.cpp',
     'src/compiler/translator/Intermediate.cpp',
@@ -58,17 +59,16 @@ UNIFIED_SOURCES += [
     'src/compiler/translator/intermOut.cpp',
     'src/compiler/translator/IntermTraverse.cpp',
     'src/compiler/translator/LoopInfo.cpp',
     'src/compiler/translator/Operator.cpp',
     'src/compiler/translator/OutputESSL.cpp',
     'src/compiler/translator/OutputGLSL.cpp',
     'src/compiler/translator/OutputGLSLBase.cpp',
     'src/compiler/translator/OutputHLSL.cpp',
-    'src/compiler/translator/parseConst.cpp',
     'src/compiler/translator/ParseContext.cpp',
     'src/compiler/translator/PoolAlloc.cpp',
     'src/compiler/translator/PruneEmptyDeclarations.cpp',
     'src/compiler/translator/RecordConstantPrecision.cpp',
     'src/compiler/translator/RegenerateStructNames.cpp',
     'src/compiler/translator/RemoveDynamicIndexing.cpp',
     'src/compiler/translator/RemovePow.cpp',
     'src/compiler/translator/RemoveSwitchFallThrough.cpp',
--- a/gfx/angle/src/commit.h
+++ b/gfx/angle/src/commit.h
@@ -1,3 +1,3 @@
-#define ANGLE_COMMIT_HASH "c7fc1b46df29"
+#define ANGLE_COMMIT_HASH "2eb89424cc6d"
 #define ANGLE_COMMIT_HASH_SIZE 12
-#define ANGLE_COMMIT_DATE "2015-11-09 16:31:17 -0500"
+#define ANGLE_COMMIT_DATE "2015-11-28 17:17:38 -0500"
--- a/gfx/angle/src/common/angleutils.h
+++ b/gfx/angle/src/common/angleutils.h
@@ -65,19 +65,19 @@ void SafeDelete(T*& resource)
 {
     delete resource;
     resource = NULL;
 }
 
 template <typename T>
 void SafeDeleteContainer(T& resource)
 {
-    for (typename T::iterator i = resource.begin(); i != resource.end(); i++)
+    for (auto &element : resource)
     {
-        SafeDelete(*i);
+        SafeDelete(element);
     }
     resource.clear();
 }
 
 template <typename T>
 void SafeDeleteArray(T*& resource)
 {
     delete[] resource;
--- a/gfx/angle/src/common/utilities.cpp
+++ b/gfx/angle/src/common/utilities.cpp
@@ -792,37 +792,12 @@ void writeFile(const char* path, const v
 
 #if defined (ANGLE_PLATFORM_WINDOWS)
 
 // Causes the thread to relinquish the remainder of its time slice to any
 // other thread that is ready to run.If there are no other threads ready
 // to run, the function returns immediately, and the thread continues execution.
 void ScheduleYield()
 {
-#if defined(ANGLE_ENABLE_WINDOWS_STORE)
-    // This implementation of Sleep exists because it is not available prior to Update 4.
-    static HANDLE singletonEvent = nullptr;
-    HANDLE sleepEvent = singletonEvent;
-    if (!sleepEvent)
-    {
-        sleepEvent = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
-
-        if (!sleepEvent)
-            return;
-
-        HANDLE previousEvent = InterlockedCompareExchangePointerRelease(&singletonEvent, sleepEvent, nullptr);
-
-        if (previousEvent)
-        {
-            // Back out if multiple threads try to demand create at the same time.
-            CloseHandle(sleepEvent);
-            sleepEvent = previousEvent;
-        }
-    }
-
-    // Emulate sleep by waiting with timeout on an event that is never signalled.
-    WaitForSingleObjectEx(sleepEvent, 0, false);
-#else
     Sleep(0);
-#endif
 }
 
 #endif
--- a/gfx/angle/src/compiler.gypi
+++ b/gfx/angle/src/compiler.gypi
@@ -38,16 +38,18 @@
             'compiler/translator/ConstantUnion.h',
             'compiler/translator/Diagnostics.cpp',
             'compiler/translator/Diagnostics.h',
             'compiler/translator/DirectiveHandler.cpp',
             'compiler/translator/DirectiveHandler.h',
             'compiler/translator/EmulatePrecision.cpp',
             'compiler/translator/EmulatePrecision.h',
             'compiler/translator/ExtensionBehavior.h',
+            'compiler/translator/ExtensionGLSL.cpp',
+            'compiler/translator/ExtensionGLSL.h',
             'compiler/translator/FlagStd140Structs.cpp',
             'compiler/translator/FlagStd140Structs.h',
             'compiler/translator/ForLoopUnroll.cpp',
             'compiler/translator/ForLoopUnroll.h',
             'compiler/translator/HashNames.h',
             'compiler/translator/InfoSink.cpp',
             'compiler/translator/InfoSink.h',
             'compiler/translator/Initialize.cpp',
@@ -132,17 +134,16 @@
             'compiler/translator/glslang.h',
             'compiler/translator/glslang.l',
             'compiler/translator/glslang.y',
             'compiler/translator/glslang_lex.cpp',
             'compiler/translator/glslang_tab.cpp',
             'compiler/translator/glslang_tab.h',
             'compiler/translator/intermOut.cpp',
             'compiler/translator/length_limits.h',
-            'compiler/translator/parseConst.cpp',
             'compiler/translator/timing/RestrictFragmentShaderTiming.cpp',
             'compiler/translator/timing/RestrictFragmentShaderTiming.h',
             'compiler/translator/timing/RestrictVertexShaderTiming.cpp',
             'compiler/translator/timing/RestrictVertexShaderTiming.h',
             'compiler/translator/util.cpp',
             'compiler/translator/util.h',
             'third_party/compiler/ArrayBoundsClamper.cpp',
             'third_party/compiler/ArrayBoundsClamper.h',
--- a/gfx/angle/src/compiler/translator/BuiltInFunctionEmulatorGLSL.cpp
+++ b/gfx/angle/src/compiler/translator/BuiltInFunctionEmulatorGLSL.cpp
@@ -45,122 +45,146 @@ void InitBuiltInFunctionEmulatorForGLSLM
 {
     // Emulate packSnorm2x16, packHalf2x16, unpackSnorm2x16, and unpackHalf2x16 (GLSL 4.20)
     // by using floatBitsToInt, floatBitsToUint, intBitsToFloat, and uintBitsToFloat (GLSL 3.30).
     if (targetGLSLVersion >= GLSL_VERSION_330 && targetGLSLVersion < GLSL_VERSION_420)
     {
         const TType *float2 = TCache::getType(EbtFloat, 2);
         const TType *uint1 = TCache::getType(EbtUInt);
 
+        // clang-format off
         emu->addEmulatedFunction(EOpPackSnorm2x16, float2,
             "uint webgl_packSnorm2x16_emu(vec2 v)\n"
             "{\n"
-            "    int x = int(round(clamp(v.x, -1.0, 1.0) * 32767.0));\n"
-            "    int y = int(round(clamp(v.y, -1.0, 1.0) * 32767.0));\n"
-            "    return uint((y << 16) | (x & 0xFFFF));\n"
+            "    #if defined(GL_ARB_shading_language_packing)\n"
+            "        return packSnorm2x16(v);\n"
+            "    #else\n"
+            "        int x = int(round(clamp(v.x, -1.0, 1.0) * 32767.0));\n"
+            "        int y = int(round(clamp(v.y, -1.0, 1.0) * 32767.0));\n"
+            "        return uint((y << 16) | (x & 0xFFFF));\n"
+            "    #endif\n"
             "}\n");
         emu->addEmulatedFunction(EOpUnpackSnorm2x16, uint1,
-            "float webgl_fromSnorm(uint x)\n"
-            "{\n"
-            "    int xi = (int(x) & 0x7FFF) - (int(x) & 0x8000);\n"
-            "    return clamp(float(xi) / 32767.0, -1.0, 1.0);\n"
-            "}\n"
+            "#if !defined(GL_ARB_shading_language_packing)\n"
+            "    float webgl_fromSnorm(uint x)\n"
+            "    {\n"
+            "        int xi = (int(x) & 0x7FFF) - (int(x) & 0x8000);\n"
+            "        return clamp(float(xi) / 32767.0, -1.0, 1.0);\n"
+            "    }\n"
+            "#endif\n"
             "\n"
             "vec2 webgl_unpackSnorm2x16_emu(uint u)\n"
             "{\n"
-            "    uint y = (u >> 16);\n"
-            "    uint x = u;\n"
-            "    return vec2(webgl_fromSnorm(x), webgl_fromSnorm(y));\n"
+            "    #if defined(GL_ARB_shading_language_packing)\n"
+            "        return unpackSnorm2x16(u);\n"
+            "    #else\n"
+            "        uint y = (u >> 16);\n"
+            "        uint x = u;\n"
+            "        return vec2(webgl_fromSnorm(x), webgl_fromSnorm(y));\n"
+            "    #endif\n"
             "}\n");
         // Functions uint webgl_f32tof16(float val) and float webgl_f16tof32(uint val) are
         // based on the OpenGL redbook Appendix Session "Floating-Point Formats Used in OpenGL".
         emu->addEmulatedFunction(EOpPackHalf2x16, float2,
-            "uint webgl_f32tof16(float val)\n"
-            "{\n"
-            "    uint f32 = floatBitsToUint(val);\n"
-            "    uint f16 = 0u;\n"
-            "    uint sign = (f32 >> 16) & 0x8000u;\n"
-            "    int exponent = int((f32 >> 23) & 0xFFu) - 127;\n"
-            "    uint mantissa = f32 & 0x007FFFFFu;\n"
-            "    if (exponent == 128)\n"
-            "    {\n"
-            "        // Infinity or NaN\n"
-            "        // NaN bits that are masked out by 0x3FF get discarded.\n"
-            "        // This can turn some NaNs to infinity, but this is allowed by the spec.\n"
-            "        f16 = sign | (0x1Fu << 10);\n"
-            "        f16 |= (mantissa & 0x3FFu);\n"
-            "    }\n"
-            "    else if (exponent > 15)\n"
+            "#if !defined(GL_ARB_shading_language_packing)\n"
+            "    uint webgl_f32tof16(float val)\n"
             "    {\n"
-            "        // Overflow - flush to Infinity\n"
-            "        f16 = sign | (0x1Fu << 10);\n"
+            "        uint f32 = floatBitsToUint(val);\n"
+            "        uint f16 = 0u;\n"
+            "        uint sign = (f32 >> 16) & 0x8000u;\n"
+            "        int exponent = int((f32 >> 23) & 0xFFu) - 127;\n"
+            "        uint mantissa = f32 & 0x007FFFFFu;\n"
+            "        if (exponent == 128)\n"
+            "        {\n"
+            "            // Infinity or NaN\n"
+            "            // NaN bits that are masked out by 0x3FF get discarded.\n"
+            "            // This can turn some NaNs to infinity, but this is allowed by the spec.\n"
+            "            f16 = sign | (0x1Fu << 10);\n"
+            "            f16 |= (mantissa & 0x3FFu);\n"
+            "        }\n"
+            "        else if (exponent > 15)\n"
+            "        {\n"
+            "            // Overflow - flush to Infinity\n"
+            "            f16 = sign | (0x1Fu << 10);\n"
+            "        }\n"
+            "        else if (exponent > -15)\n"
+            "        {\n"
+            "            // Representable value\n"
+            "            exponent += 15;\n"
+            "            mantissa >>= 13;\n"
+            "            f16 = sign | uint(exponent << 10) | mantissa;\n"
+            "        }\n"
+            "        else\n"
+            "        {\n"
+            "            f16 = sign;\n"
+            "        }\n"
+            "        return f16;\n"
             "    }\n"
-            "    else if (exponent > -15)\n"
-            "    {\n"
-            "        // Representable value\n"
-            "        exponent += 15;\n"
-            "        mantissa >>= 13;\n"
-            "        f16 = sign | uint(exponent << 10) | mantissa;\n"
-            "    }\n"
-            "    else\n"
-            "    {\n"
-            "        f16 = sign;\n"
-            "    }\n"
-            "    return f16;\n"
-            "}\n"
+            "#endif\n"
             "\n"
             "uint webgl_packHalf2x16_emu(vec2 v)\n"
             "{\n"
-            "    uint x = webgl_f32tof16(v.x);\n"
-            "    uint y = webgl_f32tof16(v.y);\n"
-            "    return (y << 16) | x;\n"
+            "    #if defined(GL_ARB_shading_language_packing)\n"
+            "        return packHalf2x16(v);\n"
+            "    #else\n"
+            "        uint x = webgl_f32tof16(v.x);\n"
+            "        uint y = webgl_f32tof16(v.y);\n"
+            "        return (y << 16) | x;\n"
+            "    #endif\n"
             "}\n");
         emu->addEmulatedFunction(EOpUnpackHalf2x16, uint1,
-            "float webgl_f16tof32(uint val)\n"
-            "{\n"
-            "    uint sign = (val & 0x8000u) << 16;\n"
-            "    int exponent = int((val & 0x7C00u) >> 10);\n"
-            "    uint mantissa = val & 0x03FFu;\n"
-            "    float f32 = 0.0;\n"
-            "    if(exponent == 0)\n"
-            "    {\n"
-            "        if (mantissa != 0u)\n"
-            "        {\n"
-            "            const float scale = 1.0 / (1 << 24);\n"
-            "            f32 = scale * mantissa;\n"
-            "        }\n"
-            "    }\n"
-            "    else if (exponent == 31)\n"
-            "    {\n"
-            "        return uintBitsToFloat(sign | 0x7F800000u | mantissa);\n"
-            "    }\n"
-            "    else\n"
+            "#if !defined(GL_ARB_shading_language_packing)\n"
+            "    float webgl_f16tof32(uint val)\n"
             "    {\n"
-            "         exponent -= 15;\n"
-            "         float scale;\n"
-            "         if(exponent < 0)\n"
-            "         {\n"
-            "             scale = 1.0 / (1 << -exponent);\n"
-            "         }\n"
-            "         else\n"
-            "         {\n"
-            "             scale = 1 << exponent;\n"
-            "         }\n"
-            "         float decimal = 1.0 + float(mantissa) / float(1 << 10);\n"
-            "         f32 = scale * decimal;\n"
+            "        uint sign = (val & 0x8000u) << 16;\n"
+            "        int exponent = int((val & 0x7C00u) >> 10);\n"
+            "        uint mantissa = val & 0x03FFu;\n"
+            "        float f32 = 0.0;\n"
+            "        if(exponent == 0)\n"
+            "        {\n"
+            "            if (mantissa != 0u)\n"
+            "            {\n"
+            "                const float scale = 1.0 / (1 << 24);\n"
+            "                f32 = scale * mantissa;\n"
+            "            }\n"
+            "        }\n"
+            "        else if (exponent == 31)\n"
+            "        {\n"
+            "            return uintBitsToFloat(sign | 0x7F800000u | mantissa);\n"
+            "        }\n"
+            "        else\n"
+            "        {\n"
+            "            exponent -= 15;\n"
+            "            float scale;\n"
+            "            if(exponent < 0)\n"
+            "            {\n"
+            "                scale = 1.0 / (1 << -exponent);\n"
+            "            }\n"
+            "            else\n"
+            "            {\n"
+            "                scale = 1 << exponent;\n"
+            "            }\n"
+            "            float decimal = 1.0 + float(mantissa) / float(1 << 10);\n"
+            "            f32 = scale * decimal;\n"
+            "        }\n"
+            "\n"
+            "        if (sign != 0u)\n"
+            "        {\n"
+            "            f32 = -f32;\n"
+            "        }\n"
+            "\n"
+            "        return f32;\n"
             "    }\n"
-            "\n"
-            "    if (sign != 0u)\n"
-            "    {\n"
-            "        f32 = -f32;\n"
-            "    }\n"
-            "\n"
-            "    return f32;\n"
-            "}\n"
+            "#endif\n"
             "\n"
             "vec2 webgl_unpackHalf2x16_emu(uint u)\n"
             "{\n"
-            "    uint y = (u >> 16);\n"
-            "    uint x = u & 0xFFFFu;\n"
-            "    return vec2(webgl_f16tof32(x), webgl_f16tof32(y));\n"
+            "    #if defined(GL_ARB_shading_language_packing)\n"
+            "        return unpackHalf2x16(u);\n"
+            "    #else\n"
+            "        uint y = (u >> 16);\n"
+            "        uint x = u & 0xFFFFu;\n"
+            "        return vec2(webgl_f16tof32(x), webgl_f16tof32(y));\n"
+            "    #endif\n"
             "}\n");
+        // clang-format on
     }
 }
--- a/gfx/angle/src/compiler/translator/Compiler.cpp
+++ b/gfx/angle/src/compiler/translator/Compiler.cpp
@@ -208,23 +208,21 @@ TIntermNode *TCompiler::compileTreeImpl(
     // First string is path of source file if flag is set. The actual source follows.
     size_t firstSource = 0;
     if (compileOptions & SH_SOURCE_PATH)
     {
         mSourcePath = shaderStrings[0];
         ++firstSource;
     }
 
-    bool debugShaderPrecision = getResources().WEBGL_debug_shader_precision == 1;
     TIntermediate intermediate(infoSink);
-    TParseContext parseContext(symbolTable, extensionBehavior, intermediate,
-                               shaderType, shaderSpec, compileOptions, true,
-                               infoSink, debugShaderPrecision);
+    TParseContext parseContext(symbolTable, extensionBehavior, intermediate, shaderType, shaderSpec,
+                               compileOptions, true, infoSink, getResources());
 
-    parseContext.setFragmentPrecisionHigh(fragmentPrecisionHigh);
+    parseContext.setFragmentPrecisionHighOnESSL1(fragmentPrecisionHigh);
     SetGlobalParseContext(&parseContext);
 
     // We preserve symbols at the built-in level from compile-to-compile.
     // Start pushing the user-defined symbols at global level.
     TScopedSymbolTableLevel scopedSymbolLevel(&symbolTable);
 
     // Parse shader.
     bool success =
@@ -247,16 +245,19 @@ TIntermNode *TCompiler::compileTreeImpl(
         if (mPragma.stdgl.invariantAll)
         {
             symbolTable.setGlobalInvariant();
         }
 
         root = parseContext.getTreeRoot();
         root = intermediate.postProcess(root);
 
+        // Highp might have been auto-enabled based on shader version
+        fragmentPrecisionHigh = parseContext.getFragmentPrecisionHigh();
+
         // Disallow expressions deemed too complex.
         if (success && (compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY))
             success = limitExpressionComplexity(root);
 
         // Create the function DAG and check there is no recursion
         if (success)
             success = initCallDag(root);
 
new file mode 100644
--- /dev/null
+++ b/gfx/angle/src/compiler/translator/ExtensionGLSL.cpp
@@ -0,0 +1,100 @@
+//
+// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ExtensionGLSL.cpp: Implements the TExtensionGLSL class that tracks GLSL extension requirements
+// of shaders.
+
+#include "compiler/translator/ExtensionGLSL.h"
+
+#include "compiler/translator/VersionGLSL.h"
+
+TExtensionGLSL::TExtensionGLSL(ShShaderOutput output)
+    : TIntermTraverser(true, false, false), mTargetVersion(ShaderOutputTypeToGLSLVersion(output))
+{
+}
+
+const std::set<std::string> &TExtensionGLSL::getEnabledExtensions() const
+{
+    return mEnabledExtensions;
+}
+
+const std::set<std::string> &TExtensionGLSL::getRequiredExtensions() const
+{
+    return mRequiredExtensions;
+}
+
+bool TExtensionGLSL::visitUnary(Visit, TIntermUnary *node)
+{
+    checkOperator(node);
+
+    return true;
+}
+
+bool TExtensionGLSL::visitAggregate(Visit, TIntermAggregate *node)
+{
+    checkOperator(node);
+
+    return true;
+}
+
+void TExtensionGLSL::checkOperator(TIntermOperator *node)
+{
+    if (mTargetVersion < GLSL_VERSION_130)
+    {
+        return;
+    }
+
+    switch (node->getOp())
+    {
+        case EOpAbs:
+            break;
+
+        case EOpSign:
+            break;
+
+        case EOpMix:
+            break;
+
+        case EOpFloatBitsToInt:
+        case EOpFloatBitsToUint:
+        case EOpIntBitsToFloat:
+        case EOpUintBitsToFloat:
+            if (mTargetVersion < GLSL_VERSION_330)
+            {
+                // Bit conversion functions cannot be emulated.
+                mRequiredExtensions.insert("GL_ARB_shader_bit_encoding");
+            }
+            break;
+
+        case EOpPackSnorm2x16:
+        case EOpPackHalf2x16:
+        case EOpUnpackSnorm2x16:
+        case EOpUnpackHalf2x16:
+            if (mTargetVersion < GLSL_VERSION_420)
+            {
+                mEnabledExtensions.insert("GL_ARB_shading_language_packing");
+
+                if (mTargetVersion < GLSL_VERSION_330)
+                {
+                    // floatBitsToUint and uintBitsToFloat are needed to emulate
+                    // packHalf2x16 and unpackHalf2x16 respectively and cannot be
+                    // emulated themselves.
+                    mRequiredExtensions.insert("GL_ARB_shader_bit_encoding");
+                }
+            }
+            break;
+
+        case EOpPackUnorm2x16:
+        case EOpUnpackUnorm2x16:
+            if (mTargetVersion < GLSL_VERSION_410)
+            {
+                mEnabledExtensions.insert("GL_ARB_shading_language_packing");
+            }
+            break;
+
+        default:
+            break;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/gfx/angle/src/compiler/translator/ExtensionGLSL.h
@@ -0,0 +1,39 @@
+//
+// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ExtensionGLSL.h: Defines the TExtensionGLSL class that tracks GLSL extension requirements of
+// shaders.
+
+#ifndef COMPILER_TRANSLATOR_EXTENSIONGLSL_H_
+#define COMPILER_TRANSLATOR_EXTENSIONGLSL_H_
+
+#include <set>
+#include <string>
+
+#include "compiler/translator/IntermNode.h"
+
+// Traverses the intermediate tree to determine which GLSL extensions are required
+// to support the shader.
+class TExtensionGLSL : public TIntermTraverser
+{
+  public:
+    TExtensionGLSL(ShShaderOutput output);
+
+    const std::set<std::string> &getEnabledExtensions() const;
+    const std::set<std::string> &getRequiredExtensions() const;
+
+    bool visitUnary(Visit visit, TIntermUnary *node) override;
+    bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+
+  private:
+    void checkOperator(TIntermOperator *node);
+
+    int mTargetVersion;
+
+    std::set<std::string> mEnabledExtensions;
+    std::set<std::string> mRequiredExtensions;
+};
+
+#endif  // COMPILER_TRANSLATOR_EXTENSIONGLSL_H_
--- a/gfx/angle/src/compiler/translator/IntermNode.cpp
+++ b/gfx/angle/src/compiler/translator/IntermNode.cpp
@@ -61,83 +61,16 @@ bool ValidateMultiplication(TOperator op
                left.getRows() == right.getRows();
 
       default:
         UNREACHABLE();
         return false;
     }
 }
 
-bool CompareStructure(const TType& leftNodeType,
-                      const TConstantUnion *rightUnionArray,
-                      const TConstantUnion *leftUnionArray);
-
-bool CompareStruct(const TType &leftNodeType,
-                   const TConstantUnion *rightUnionArray,
-                   const TConstantUnion *leftUnionArray)
-{
-    const TFieldList &fields = leftNodeType.getStruct()->fields();
-
-    size_t structSize = fields.size();
-    size_t index = 0;
-
-    for (size_t j = 0; j < structSize; j++)
-    {
-        size_t size = fields[j]->type()->getObjectSize();
-        for (size_t i = 0; i < size; i++)
-        {
-            if (fields[j]->type()->getBasicType() == EbtStruct)
-            {
-                if (!CompareStructure(*fields[j]->type(),
-                                      &rightUnionArray[index],
-                                      &leftUnionArray[index]))
-                {
-                    return false;
-                }
-            }
-            else
-            {
-                if (leftUnionArray[index] != rightUnionArray[index])
-                    return false;
-                index++;
-            }
-        }
-    }
-    return true;
-}
-
-bool CompareStructure(const TType &leftNodeType,
-                      const TConstantUnion *rightUnionArray,
-                      const TConstantUnion *leftUnionArray)
-{
-    if (leftNodeType.isArray())
-    {
-        TType typeWithoutArrayness = leftNodeType;
-        typeWithoutArrayness.clearArrayness();
-
-        size_t arraySize = leftNodeType.getArraySize();
-
-        for (size_t i = 0; i < arraySize; ++i)
-        {
-            size_t offset = typeWithoutArrayness.getObjectSize() * i;
-            if (!CompareStruct(typeWithoutArrayness,
-                               &rightUnionArray[offset],
-                               &leftUnionArray[offset]))
-            {
-                return false;
-            }
-        }
-    }
-    else
-    {
-        return CompareStruct(leftNodeType, rightUnionArray, leftUnionArray);
-    }
-    return true;
-}
-
 TConstantUnion *Vectorize(const TConstantUnion &constant, size_t size)
 {
     TConstantUnion *constUnion = new TConstantUnion[size];
     for (unsigned int i = 0; i < size; ++i)
         constUnion[i] = constant;
 
     return constUnion;
 }
@@ -164,58 +97,64 @@ void UndefinedConstantFoldingError(const
       case EbtBool:
         result->setBConst(false);
         break;
       default:
         break;
     }
 }
 
-float VectorLength(TConstantUnion *paramArray, size_t paramArraySize)
+float VectorLength(const TConstantUnion *paramArray, size_t paramArraySize)
 {
     float result = 0.0f;
     for (size_t i = 0; i < paramArraySize; i++)
     {
         float f = paramArray[i].getFConst();
         result += f * f;
     }
     return sqrtf(result);
 }
 
-float VectorDotProduct(TConstantUnion *paramArray1, TConstantUnion *paramArray2, size_t paramArraySize)
+float VectorDotProduct(const TConstantUnion *paramArray1,
+                       const TConstantUnion *paramArray2,
+                       size_t paramArraySize)
 {
     float result = 0.0f;
     for (size_t i = 0; i < paramArraySize; i++)
         result += paramArray1[i].getFConst() * paramArray2[i].getFConst();
     return result;
 }
 
-TIntermTyped *CreateFoldedNode(TConstantUnion *constArray, const TIntermTyped *originalNode)
+TIntermTyped *CreateFoldedNode(TConstantUnion *constArray,
+                               const TIntermTyped *originalNode,
+                               TQualifier qualifier)
 {
     if (constArray == nullptr)
     {
         return nullptr;
     }
     TIntermTyped *folded = new TIntermConstantUnion(constArray, originalNode->getType());
-    folded->getTypePointer()->setQualifier(EvqConst);
+    folded->getTypePointer()->setQualifier(qualifier);
     folded->setLine(originalNode->getLine());
     return folded;
 }
 
-angle::Matrix<float> GetMatrix(TConstantUnion *paramArray, const unsigned int &rows, const unsigned int &cols)
+angle::Matrix<float> GetMatrix(const TConstantUnion *paramArray,
+                               const unsigned int &rows,
+                               const unsigned int &cols)
 {
     std::vector<float> elements;
     for (size_t i = 0; i < rows * cols; i++)
         elements.push_back(paramArray[i].getFConst());
     // Transpose is used since the Matrix constructor expects arguments in row-major order,
     // whereas the paramArray is in column-major order.
     return angle::Matrix<float>(elements, rows, cols).transpose();
 }
 
-angle::Matrix<float> GetMatrix(TConstantUnion *paramArray, const unsigned int &size)
+angle::Matrix<float> GetMatrix(const TConstantUnion *paramArray, const unsigned int &size)
 {
     std::vector<float> elements;
     for (size_t i = 0; i < size * size; i++)
         elements.push_back(paramArray[i].getFConst());
     // Transpose is used since the Matrix constructor expects arguments in row-major order,
     // whereas the paramArray is in column-major order.
     return angle::Matrix<float>(elements, size).transpose();
 }
@@ -254,17 +193,17 @@ void TIntermTyped::setTypePreservePrecis
     }
 
 bool TIntermLoop::replaceChildNode(
     TIntermNode *original, TIntermNode *replacement)
 {
     REPLACE_IF_IS(mInit, TIntermNode, original, replacement);
     REPLACE_IF_IS(mCond, TIntermTyped, original, replacement);
     REPLACE_IF_IS(mExpr, TIntermTyped, original, replacement);
-    REPLACE_IF_IS(mBody, TIntermNode, original, replacement);
+    REPLACE_IF_IS(mBody, TIntermAggregate, original, replacement);
     return false;
 }
 
 bool TIntermBranch::replaceChildNode(
     TIntermNode *original, TIntermNode *replacement)
 {
     REPLACE_IF_IS(mExpression, TIntermTyped, original, replacement);
     return false;
@@ -315,16 +254,29 @@ bool TIntermAggregate::insertChildNodes(
     {
         return false;
     }
     auto it = mSequence.begin() + position;
     mSequence.insert(it, insertions.begin(), insertions.end());
     return true;
 }
 
+bool TIntermAggregate::areChildrenConstQualified()
+{
+    for (TIntermNode *&child : mSequence)
+    {
+        TIntermTyped *typed = child->getAsTyped();
+        if (typed && typed->getQualifier() != EvqConst)
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
 void TIntermAggregate::setPrecisionFromChildren()
 {
     mGotPrecisionFromChildren = true;
     if (getBasicType() == EbtBool)
     {
         mType.setPrecision(EbpUndefined);
         return;
     }
@@ -396,22 +348,17 @@ TIntermTyped::TIntermTyped(const TInterm
     // Copy constructor is disallowed for TIntermNode in order to disallow it for subclasses that
     // don't explicitly allow it, so normal TIntermNode constructor is used to construct the copy.
     // We need to manually copy any fields of TIntermNode besides handling fields in TIntermTyped.
     mLine = node.mLine;
 }
 
 TIntermConstantUnion::TIntermConstantUnion(const TIntermConstantUnion &node) : TIntermTyped(node)
 {
-    size_t arraySize   = mType.getObjectSize();
-    mUnionArrayPointer = new TConstantUnion[arraySize];
-    for (size_t i = 0u; i < arraySize; ++i)
-    {
-        mUnionArrayPointer[i] = node.mUnionArrayPointer[i];
-    }
+    mUnionArrayPointer = node.mUnionArrayPointer;
 }
 
 TIntermAggregate::TIntermAggregate(const TIntermAggregate &node)
     : TIntermOperator(node),
       mName(node.mName),
       mUserDefined(node.mUserDefined),
       mFunctionId(node.mFunctionId),
       mUseEmulatedFunction(node.mUseEmulatedFunction),
@@ -582,17 +529,20 @@ void TIntermUnary::promote(const TType *
         }
         else
         {
             // Precision of the node has been set based on the operand.
             setTypePreservePrecision(*funcReturnType);
         }
     }
 
-    mType.setQualifier(EvqTemporary);
+    if (mOperand->getQualifier() == EvqConst)
+        mType.setQualifier(EvqConst);
+    else
+        mType.setQualifier(EvqTemporary);
 }
 
 //
 // Establishes the type of the resultant operation, as well as
 // makes the operator the correct one for the operands.
 //
 // For lots of operations it should already be established that the operand
 // combination is valid, but returns false if operator can't work on operands.
@@ -607,20 +557,22 @@ bool TIntermBinary::promote(TInfoSink &i
     //
     setType(mLeft->getType());
 
     // The result gets promoted to the highest precision.
     TPrecision higherPrecision = GetHigherPrecision(
         mLeft->getPrecision(), mRight->getPrecision());
     getTypePointer()->setPrecision(higherPrecision);
 
+    TQualifier resultQualifier = EvqConst;
     // Binary operations results in temporary variables unless both
     // operands are const.
     if (mLeft->getQualifier() != EvqConst || mRight->getQualifier() != EvqConst)
     {
+        resultQualifier = EvqTemporary;
         getTypePointer()->setQualifier(EvqTemporary);
     }
 
     const int nominalSize =
         std::max(mLeft->getNominalSize(), mRight->getNominalSize());
 
     //
     // All scalars or structs. Code after this test assumes this case is removed!
@@ -665,55 +617,57 @@ bool TIntermBinary::promote(TInfoSink &i
     switch (mOp)
     {
       case EOpMul:
         if (!mLeft->isMatrix() && mRight->isMatrix())
         {
             if (mLeft->isVector())
             {
                 mOp = EOpVectorTimesMatrix;
-                setType(TType(basicType, higherPrecision, EvqTemporary,
+                setType(TType(basicType, higherPrecision, resultQualifier,
                               static_cast<unsigned char>(mRight->getCols()), 1));
             }
             else
             {
                 mOp = EOpMatrixTimesScalar;
-                setType(TType(basicType, higherPrecision, EvqTemporary,
-                              static_cast<unsigned char>(mRight->getCols()), static_cast<unsigned char>(mRight->getRows())));
+                setType(TType(basicType, higherPrecision, resultQualifier,
+                              static_cast<unsigned char>(mRight->getCols()),
+                              static_cast<unsigned char>(mRight->getRows())));
             }
         }
         else if (mLeft->isMatrix() && !mRight->isMatrix())
         {
             if (mRight->isVector())
             {
                 mOp = EOpMatrixTimesVector;
-                setType(TType(basicType, higherPrecision, EvqTemporary,
+                setType(TType(basicType, higherPrecision, resultQualifier,
                               static_cast<unsigned char>(mLeft->getRows()), 1));
             }
             else
             {
                 mOp = EOpMatrixTimesScalar;
             }
         }
         else if (mLeft->isMatrix() && mRight->isMatrix())
         {
             mOp = EOpMatrixTimesMatrix;
-            setType(TType(basicType, higherPrecision, EvqTemporary,
-                          static_cast<unsigned char>(mRight->getCols()), static_cast<unsigned char>(mLeft->getRows())));
+            setType(TType(basicType, higherPrecision, resultQualifier,
+                          static_cast<unsigned char>(mRight->getCols()),
+                          static_cast<unsigned char>(mLeft->getRows())));
         }
         else if (!mLeft->isMatrix() && !mRight->isMatrix())
         {
             if (mLeft->isVector() && mRight->isVector())
             {
                 // leave as component product
             }
             else if (mLeft->isVector() || mRight->isVector())
             {
                 mOp = EOpVectorTimesScalar;
-                setType(TType(basicType, higherPrecision, EvqTemporary,
+                setType(TType(basicType, higherPrecision, resultQualifier,
                               static_cast<unsigned char>(nominalSize), 1));
             }
         }
         else
         {
             infoSink.info.message(EPrefixInternalError, getLine(),
                                   "Missing elses");
             return false;
@@ -746,31 +700,32 @@ bool TIntermBinary::promote(TInfoSink &i
             else
             {
                 mOp = EOpMatrixTimesScalarAssign;
             }
         }
         else if (mLeft->isMatrix() && mRight->isMatrix())
         {
             mOp = EOpMatrixTimesMatrixAssign;
-            setType(TType(basicType, higherPrecision, EvqTemporary,
-                          static_cast<unsigned char>(mRight->getCols()), static_cast<unsigned char>(mLeft->getRows())));
+            setType(TType(basicType, higherPrecision, resultQualifier,
+                          static_cast<unsigned char>(mRight->getCols()),
+                          static_cast<unsigned char>(mLeft->getRows())));
         }
         else if (!mLeft->isMatrix() && !mRight->isMatrix())
         {
             if (mLeft->isVector() && mRight->isVector())
             {
                 // leave as component product
             }
             else if (mLeft->isVector() || mRight->isVector())
             {
                 if (!mLeft->isVector())
                     return false;
                 mOp = EOpVectorTimesScalarAssign;
-                setType(TType(basicType, higherPrecision, EvqTemporary,
+                setType(TType(basicType, higherPrecision, resultQualifier,
                               static_cast<unsigned char>(mLeft->getNominalSize()), 1));
             }
         }
         else
         {
             infoSink.info.message(EPrefixInternalError, getLine(),
                                   "Missing elses");
             return false;
@@ -830,18 +785,19 @@ bool TIntermBinary::promote(TInfoSink &i
                 mOp == EOpBitShiftLeft ||
                 mOp == EOpBitShiftRight))
                 return false;
         }
 
         {
             const int secondarySize = std::max(
                 mLeft->getSecondarySize(), mRight->getSecondarySize());
-            setType(TType(basicType, higherPrecision, EvqTemporary,
-                          static_cast<unsigned char>(nominalSize), static_cast<unsigned char>(secondarySize)));
+            setType(TType(basicType, higherPrecision, resultQualifier,
+                          static_cast<unsigned char>(nominalSize),
+                          static_cast<unsigned char>(secondarySize)));
             if (mLeft->isArray())
             {
                 ASSERT(mLeft->getArraySize() == mRight->getArraySize());
                 mType.setArraySize(mLeft->getArraySize());
             }
         }
         break;
 
@@ -866,17 +822,24 @@ TIntermTyped *TIntermBinary::fold(TInfoS
 {
     TIntermConstantUnion *leftConstant = mLeft->getAsConstantUnion();
     TIntermConstantUnion *rightConstant = mRight->getAsConstantUnion();
     if (leftConstant == nullptr || rightConstant == nullptr)
     {
         return nullptr;
     }
     TConstantUnion *constArray = leftConstant->foldBinary(mOp, rightConstant, infoSink);
-    return CreateFoldedNode(constArray, this);
+
+    // Nodes may be constant folded without being qualified as constant.
+    TQualifier resultQualifier = EvqConst;
+    if (mLeft->getQualifier() != EvqConst || mRight->getQualifier() != EvqConst)
+    {
+        resultQualifier = EvqTemporary;
+    }
+    return CreateFoldedNode(constArray, this, resultQualifier);
 }
 
 TIntermTyped *TIntermUnary::fold(TInfoSink &infoSink)
 {
     TIntermConstantUnion *operandConstant = mOperand->getAsConstantUnion();
     if (operandConstant == nullptr)
     {
         return nullptr;
@@ -898,43 +861,53 @@ TIntermTyped *TIntermUnary::fold(TInfoSi
       case EOpPackHalf2x16:
       case EOpUnpackHalf2x16:
         constArray = operandConstant->foldUnaryWithDifferentReturnType(mOp, infoSink);
         break;
       default:
         constArray = operandConstant->foldUnaryWithSameReturnType(mOp, infoSink);
         break;
     }
-    return CreateFoldedNode(constArray, this);
+
+    // Nodes may be constant folded without being qualified as constant.
+    TQualifier resultQualifier = mOperand->getQualifier() == EvqConst ? EvqConst : EvqTemporary;
+    return CreateFoldedNode(constArray, this, resultQualifier);
 }
 
 TIntermTyped *TIntermAggregate::fold(TInfoSink &infoSink)
 {
     // Make sure that all params are constant before actual constant folding.
     for (auto *param : *getSequence())
     {
         if (param->getAsConstantUnion() == nullptr)
         {
             return nullptr;
         }
     }
-    TConstantUnion *constArray = TIntermConstantUnion::FoldAggregateBuiltIn(this, infoSink);
-    return CreateFoldedNode(constArray, this);
+    TConstantUnion *constArray = nullptr;
+    if (isConstructor())
+        constArray = TIntermConstantUnion::FoldAggregateConstructor(this, infoSink);
+    else
+        constArray = TIntermConstantUnion::FoldAggregateBuiltIn(this, infoSink);
+
+    // Nodes may be constant folded without being qualified as constant.
+    TQualifier resultQualifier = areChildrenConstQualified() ? EvqConst : EvqTemporary;
+    return CreateFoldedNode(constArray, this, resultQualifier);
 }
 
 //
 // The fold functions see if an operation on a constant can be done in place,
 // without generating run-time code.
 //
 // Returns the constant value to keep using or nullptr.
 //
 TConstantUnion *TIntermConstantUnion::foldBinary(TOperator op, TIntermConstantUnion *rightNode, TInfoSink &infoSink)
 {
-    TConstantUnion *leftArray = getUnionArrayPointer();
-    TConstantUnion *rightArray = rightNode->getUnionArrayPointer();
+    const TConstantUnion *leftArray  = getUnionArrayPointer();
+    const TConstantUnion *rightArray = rightNode->getUnionArrayPointer();
 
     if (!leftArray)
         return nullptr;
     if (!rightArray)
         return nullptr;
 
     size_t objectSize = getType().getObjectSize();
 
@@ -1224,29 +1197,22 @@ TConstantUnion *TIntermConstantUnion::fo
         resultArray->setBConst(!(*leftArray < *rightArray));
         break;
 
       case EOpEqual:
       case EOpNotEqual:
         {
             resultArray = new TConstantUnion[1];
             bool equal = true;
-            if (getType().getBasicType() == EbtStruct)
-            {
-                equal = CompareStructure(getType(), rightArray, leftArray);
-            }
-            else
+            for (size_t i = 0; i < objectSize; i++)
             {
-                for (size_t i = 0; i < objectSize; i++)
+                if (leftArray[i] != rightArray[i])
                 {
-                    if (leftArray[i] != rightArray[i])
-                    {
-                        equal = false;
-                        break;  // break out of for loop
-                    }
+                    equal = false;
+                    break;  // break out of for loop
                 }
             }
             if (op == EOpEqual)
             {
                 resultArray->setBConst(equal);
             }
             else
             {
@@ -1271,17 +1237,17 @@ TConstantUnion *TIntermConstantUnion::fo
 // Returns the constant value to keep using or nullptr.
 //
 TConstantUnion *TIntermConstantUnion::foldUnaryWithDifferentReturnType(TOperator op, TInfoSink &infoSink)
 {
     //
     // Do operations where the return type has a different number of components compared to the operand type.
     //
 
-    TConstantUnion *operandArray = getUnionArrayPointer();
+    const TConstantUnion *operandArray = getUnionArrayPointer();
     if (!operandArray)
         return nullptr;
 
     size_t objectSize = getType().getObjectSize();
     TConstantUnion *resultArray = nullptr;
     switch (op)
     {
       case EOpAny:
@@ -1484,17 +1450,17 @@ TConstantUnion *TIntermConstantUnion::fo
 }
 
 TConstantUnion *TIntermConstantUnion::foldUnaryWithSameReturnType(TOperator op, TInfoSink &infoSink)
 {
     //
     // Do unary operations where the return type is the same as operand type.
     //
 
-    TConstantUnion *operandArray = getUnionArrayPointer();
+    const TConstantUnion *operandArray = getUnionArrayPointer();
     if (!operandArray)
         return nullptr;
 
     size_t objectSize = getType().getObjectSize();
 
     TConstantUnion *resultArray = new TConstantUnion[objectSize];
     for (size_t i = 0; i < objectSize; i++)
     {
@@ -1931,22 +1897,122 @@ bool TIntermConstantUnion::foldFloatType
 
     infoSink.info.message(
         EPrefixInternalError, getLine(),
         "Unary operation not folded into constant");
     return false;
 }
 
 // static
+TConstantUnion *TIntermConstantUnion::FoldAggregateConstructor(TIntermAggregate *aggregate,
+                                                               TInfoSink &infoSink)
+{
+    ASSERT(aggregate->getSequence()->size() > 0u);
+    size_t resultSize           = aggregate->getType().getObjectSize();
+    TConstantUnion *resultArray = new TConstantUnion[resultSize];
+    TBasicType basicType        = aggregate->getBasicType();
+
+    size_t resultIndex = 0u;
+
+    if (aggregate->getSequence()->size() == 1u)
+    {
+        TIntermNode *argument                    = aggregate->getSequence()->front();
+        TIntermConstantUnion *argumentConstant   = argument->getAsConstantUnion();
+        const TConstantUnion *argumentUnionArray = argumentConstant->getUnionArrayPointer();
+        // Check the special case of constructing a matrix diagonal from a single scalar,
+        // or a vector from a single scalar.
+        if (argumentConstant->getType().getObjectSize() == 1u)
+        {
+            if (aggregate->isMatrix())
+            {
+                int resultCols = aggregate->getType().getCols();
+                int resultRows = aggregate->getType().getRows();
+                for (int col = 0; col < resultCols; ++col)
+                {
+                    for (int row = 0; row < resultRows; ++row)
+                    {
+                        if (col == row)
+                        {
+                            resultArray[resultIndex].cast(basicType, argumentUnionArray[0]);
+                        }
+                        else
+                        {
+                            resultArray[resultIndex].setFConst(0.0f);
+                        }
+                        ++resultIndex;
+                    }
+                }
+            }
+            else
+            {
+                while (resultIndex < resultSize)
+                {
+                    resultArray[resultIndex].cast(basicType, argumentUnionArray[0]);
+                    ++resultIndex;
+                }
+            }
+            ASSERT(resultIndex == resultSize);
+            return resultArray;
+        }
+        else if (aggregate->isMatrix() && argumentConstant->isMatrix())
+        {
+            // The special case of constructing a matrix from a matrix.
+            int argumentCols = argumentConstant->getType().getCols();
+            int argumentRows = argumentConstant->getType().getRows();
+            int resultCols   = aggregate->getType().getCols();
+            int resultRows = aggregate->getType().getRows();
+            for (int col = 0; col < resultCols; ++col)
+            {
+                for (int row = 0; row < resultRows; ++row)
+                {
+                    if (col < argumentCols && row < argumentRows)
+                    {
+                        resultArray[resultIndex].cast(basicType,
+                                                      argumentUnionArray[col * argumentRows + row]);
+                    }
+                    else if (col == row)
+                    {
+                        resultArray[resultIndex].setFConst(1.0f);
+                    }
+                    else
+                    {
+                        resultArray[resultIndex].setFConst(0.0f);
+                    }
+                    ++resultIndex;
+                }
+            }
+            ASSERT(resultIndex == resultSize);
+            return resultArray;
+        }
+    }
+
+    for (TIntermNode *&argument : *aggregate->getSequence())
+    {
+        TIntermConstantUnion *argumentConstant   = argument->getAsConstantUnion();
+        size_t argumentSize                      = argumentConstant->getType().getObjectSize();
+        const TConstantUnion *argumentUnionArray = argumentConstant->getUnionArrayPointer();
+        for (size_t i = 0u; i < argumentSize; ++i)
+        {
+            if (resultIndex >= resultSize)
+                break;
+            resultArray[resultIndex].cast(basicType, argumentUnionArray[i]);
+            ++resultIndex;
+        }
+    }
+    ASSERT(resultIndex == resultSize);
+    return resultArray;
+}
+
+// static
 TConstantUnion *TIntermConstantUnion::FoldAggregateBuiltIn(TIntermAggregate *aggregate, TInfoSink &infoSink)
 {
     TOperator op = aggregate->getOp();
     TIntermSequence *sequence = aggregate->getSequence();
     unsigned int paramsCount = static_cast<unsigned int>(sequence->size());
-    std::vector<TConstantUnion *> unionArrays(paramsCount);
+    std::vector<const TConstantUnion *> unionArrays(paramsCount);
     std::vector<size_t> objectSizes(paramsCount);
     size_t maxObjectSize = 0;
     TBasicType basicType = EbtVoid;
     TSourceLoc loc;
     for (unsigned int i = 0; i < paramsCount; i++)
     {
         TIntermConstantUnion *paramConstant = (*sequence)[i]->getAsConstantUnion();
         ASSERT(paramConstant != nullptr); // Should be checked already.
--- a/gfx/angle/src/compiler/translator/IntermNode.h
+++ b/gfx/angle/src/compiler/translator/IntermNode.h
@@ -168,44 +168,43 @@ enum TLoopType
     ELoopWhile,
     ELoopDoWhile
 };
 
 class TIntermLoop : public TIntermNode
 {
   public:
     TIntermLoop(TLoopType type,
-                TIntermNode *init, TIntermTyped *cond, TIntermTyped *expr,
-                TIntermNode *body)
-        : mType(type),
-          mInit(init),
-          mCond(cond),
-          mExpr(expr),
-          mBody(body),
-          mUnrollFlag(false) { }
+                TIntermNode *init,
+                TIntermTyped *cond,
+                TIntermTyped *expr,
+                TIntermAggregate *body)
+        : mType(type), mInit(init), mCond(cond), mExpr(expr), mBody(body), mUnrollFlag(false)
+    {
+    }
 
     TIntermLoop *getAsLoopNode() override { return this; }
     void traverse(TIntermTraverser *it) override;
     bool replaceChildNode(TIntermNode *original, TIntermNode *replacement) override;
 
     TLoopType getType() const { return mType; }
     TIntermNode *getInit() { return mInit; }
     TIntermTyped *getCondition() { return mCond; }
     TIntermTyped *getExpression() { return mExpr; }
-    TIntermNode *getBody() { return mBody; }
+    TIntermAggregate *getBody() { return mBody; }
 
     void setUnrollFlag(bool flag) { mUnrollFlag = flag; }
     bool getUnrollFlag() const { return mUnrollFlag; }
 
   protected:
     TLoopType mType;
     TIntermNode *mInit;  // for-loop initialization
     TIntermTyped *mCond; // loop exit condition
     TIntermTyped *mExpr; // for-loop expression
-    TIntermNode *mBody;  // loop body
+    TIntermAggregate *mBody;  // loop body
 
     bool mUnrollFlag; // Whether the loop should be unrolled or not.
 };
 
 //
 // Handle break, continue, return, and kill.
 //
 class TIntermBranch : public TIntermNode
@@ -289,29 +288,35 @@ class TIntermRaw : public TIntermTyped
 
     TIntermRaw *getAsRawNode() override { return this; }
     bool replaceChildNode(TIntermNode *, TIntermNode *) override { return false; }
 
   protected:
     TString mRawText;
 };
 
+// Constant folded node.
+// Note that nodes may be constant folded and not be constant expressions with the EvqConst
+// qualifier. This happens for example when the following expression is processed:
+// "true ? 1.0 : non_constant"
+// Other nodes than TIntermConstantUnion may also be constant expressions.
+//
 class TIntermConstantUnion : public TIntermTyped
 {
   public:
-    TIntermConstantUnion(TConstantUnion *unionPointer, const TType &type)
-        : TIntermTyped(type),
-          mUnionArrayPointer(unionPointer) { }
+    TIntermConstantUnion(const TConstantUnion *unionPointer, const TType &type)
+        : TIntermTyped(type), mUnionArrayPointer(unionPointer)
+    {
+    }
 
     TIntermTyped *deepCopy() const override { return new TIntermConstantUnion(*this); }
 
     bool hasSideEffects() const override { return false; }
 
     const TConstantUnion *getUnionArrayPointer() const { return mUnionArrayPointer; }
-    TConstantUnion *getUnionArrayPointer() { return mUnionArrayPointer; }
 
     int getIConst(size_t index) const
     {
         return mUnionArrayPointer ? mUnionArrayPointer[index].getIConst() : 0;
     }
     unsigned int getUConst(size_t index) const
     {
         return mUnionArrayPointer ? mUnionArrayPointer[index].getUConst() : 0;
@@ -320,34 +325,37 @@ class TIntermConstantUnion : public TInt
     {
         return mUnionArrayPointer ? mUnionArrayPointer[index].getFConst() : 0.0f;
     }
     bool getBConst(size_t index) const
     {
         return mUnionArrayPointer ? mUnionArrayPointer[index].getBConst() : false;
     }
 
-    void replaceConstantUnion(TConstantUnion *safeConstantUnion)
+    void replaceConstantUnion(const TConstantUnion *safeConstantUnion)
     {
         // Previous union pointer freed on pool deallocation.
         mUnionArrayPointer = safeConstantUnion;
     }
 
     TIntermConstantUnion *getAsConstantUnion() override { return this; }
     void traverse(TIntermTraverser *it) override;
     bool replaceChildNode(TIntermNode *, TIntermNode *) override { return false; }
 
     TConstantUnion *foldBinary(TOperator op, TIntermConstantUnion *rightNode, TInfoSink &infoSink);
     TConstantUnion *foldUnaryWithDifferentReturnType(TOperator op, TInfoSink &infoSink);
     TConstantUnion *foldUnaryWithSameReturnType(TOperator op, TInfoSink &infoSink);
 
+    static TConstantUnion *FoldAggregateConstructor(TIntermAggregate *aggregate,
+                                                    TInfoSink &infoSink);
     static TConstantUnion *FoldAggregateBuiltIn(TIntermAggregate *aggregate, TInfoSink &infoSink);
 
   protected:
-    TConstantUnion *mUnionArrayPointer;
+    // Same data may be shared between multiple constant unions, so it can't be modified.
+    const TConstantUnion *mUnionArrayPointer;
 
   private:
     typedef float(*FloatTypeUnaryFunc) (float);
     bool foldFloatTypeUnary(const TConstantUnion &parameter, FloatTypeUnaryFunc builtinFunc, TInfoSink &infoSink, TConstantUnion *result) const;
 
     TIntermConstantUnion(const TIntermConstantUnion &node);  // Note: not deleted, just private!
 };
 
@@ -511,16 +519,17 @@ class TIntermAggregate : public TIntermO
     bool isUserDefined() const { return mUserDefined; }
 
     void setFunctionId(int functionId) { mFunctionId = functionId; }
     int getFunctionId() const { return mFunctionId; }
 
     void setUseEmulatedFunction() { mUseEmulatedFunction = true; }
     bool getUseEmulatedFunction() { return mUseEmulatedFunction; }
 
+    bool areChildrenConstQualified();
     void setPrecisionFromChildren();
     void setBuiltInFunctionPrecision();
 
     // Returns true if changing parameter precision may affect the return value.
     bool gotPrecisionFromChildren() const { return mGotPrecisionFromChildren; }
 
   protected:
     TIntermSequence mSequence;
--- a/gfx/angle/src/compiler/translator/Intermediate.cpp
+++ b/gfx/angle/src/compiler/translator/Intermediate.cpp
@@ -255,17 +255,17 @@ TIntermAggregate *TIntermediate::ensureS
 TIntermNode *TIntermediate::addSelection(
     TIntermTyped *cond, TIntermNodePair nodePair, const TSourceLoc &line)
 {
     //
     // For compile time constant selections, prune the code and
     // test now.
     //
 
-    if (cond->getAsTyped() && cond->getAsTyped()->getAsConstantUnion())
+    if (cond->getAsConstantUnion())
     {
         if (cond->getAsConstantUnion()->getBConst(0) == true)
         {
             return nodePair.node1 ? setAggregateOperator(
                 nodePair.node1, EOpSequence, nodePair.node1->getLine()) : NULL;
         }
         else
         {
@@ -276,65 +276,81 @@ TIntermNode *TIntermediate::addSelection
 
     TIntermSelection *node = new TIntermSelection(
         cond, ensureSequence(nodePair.node1), ensureSequence(nodePair.node2));
     node->setLine(line);
 
     return node;
 }
 
-TIntermTyped *TIntermediate::addComma(
-    TIntermTyped *left, TIntermTyped *right, const TSourceLoc &line)
+TIntermTyped *TIntermediate::addComma(TIntermTyped *left,
+                                      TIntermTyped *right,
+                                      const TSourceLoc &line,
+                                      int shaderVersion)
 {
-    if (left->getType().getQualifier() == EvqConst &&
-        right->getType().getQualifier() == EvqConst)
+    TQualifier resultQualifier = EvqConst;
+    // ESSL3.00 section 12.43: The result of a sequence operator is not a constant-expression.
+    if (shaderVersion >= 300 || left->getQualifier() != EvqConst ||
+        right->getQualifier() != EvqConst)
     {
-        return right;
+        resultQualifier = EvqTemporary;
+    }
+
+    TIntermTyped *commaNode = nullptr;
+    if (!left->hasSideEffects())
+    {
+        commaNode = right;
     }
     else
     {
-        TIntermTyped *commaAggregate = growAggregate(left, right, line);
-        commaAggregate->getAsAggregate()->setOp(EOpComma);
-        commaAggregate->setType(right->getType());
-        commaAggregate->getTypePointer()->setQualifier(EvqTemporary);
-        return commaAggregate;
+        commaNode = growAggregate(left, right, line);
+        commaNode->getAsAggregate()->setOp(EOpComma);
+        commaNode->setType(right->getType());
     }
+    commaNode->getTypePointer()->setQualifier(resultQualifier);
+    return commaNode;
 }
 
 //
 // For "?:" test nodes.  There are three children; a condition,
 // a true path, and a false path.  The two paths are specified
 // as separate parameters.
 //
 // Returns the selection node created, or one of trueBlock and falseBlock if the expression could be folded.
 //
 TIntermTyped *TIntermediate::addSelection(TIntermTyped *cond, TIntermTyped *trueBlock, TIntermTyped *falseBlock,
                                           const TSourceLoc &line)
 {
-    // Right now it's safe to fold ternary operators only when all operands
-    // are constant. If only the condition is constant, it's theoretically
-    // possible to fold the ternary operator, but that requires making sure
-    // that the node returned from here won't be treated as a constant
-    // expression in case the node that gets eliminated was not a constant
-    // expression.
-    if (cond->getAsConstantUnion() &&
-        trueBlock->getAsConstantUnion() &&
-        falseBlock->getAsConstantUnion())
+    TQualifier resultQualifier = EvqTemporary;
+    if (cond->getQualifier() == EvqConst && trueBlock->getQualifier() == EvqConst &&
+        falseBlock->getQualifier() == EvqConst)
+    {
+        resultQualifier = EvqConst;
+    }
+    // Note that the node resulting from here can be a constant union without being qualified as
+    // constant.
+    if (cond->getAsConstantUnion())
     {
         if (cond->getAsConstantUnion()->getBConst(0))
+        {
+            trueBlock->getTypePointer()->setQualifier(resultQualifier);
             return trueBlock;
+        }
         else
+        {
+            falseBlock->getTypePointer()->setQualifier(resultQualifier);
             return falseBlock;
+        }
     }
 
     //
     // Make a selection node.
     //
     TIntermSelection *node = new TIntermSelection(cond, trueBlock, falseBlock, trueBlock->getType());
-    node->getTypePointer()->setQualifier(EvqTemporary);
+    node->getTypePointer()->setQualifier(resultQualifier);
     node->setLine(line);
 
     return node;
 }
 
 TIntermSwitch *TIntermediate::addSwitch(
     TIntermTyped *init, TIntermAggregate *statementList, const TSourceLoc &line)
 {
@@ -354,18 +370,19 @@ TIntermCase *TIntermediate::addCase(
 }
 
 //
 // Constant terminal nodes.  Has a union that contains bool, float or int constants
 //
 // Returns the constant union node created.
 //
 
-TIntermConstantUnion *TIntermediate::addConstantUnion(
-    TConstantUnion *constantUnion, const TType &type, const TSourceLoc &line)
+TIntermConstantUnion *TIntermediate::addConstantUnion(const TConstantUnion *constantUnion,
+                                                      const TType &type,
+                                                      const TSourceLoc &line)
 {
     TIntermConstantUnion *node = new TIntermConstantUnion(constantUnion, type);
     node->setLine(line);
 
     return node;
 }
 
 TIntermTyped *TIntermediate::addSwizzle(
@@ -448,39 +465,44 @@ TIntermAggregate *TIntermediate::postPro
 
     return aggRoot;
 }
 
 TIntermTyped *TIntermediate::foldAggregateBuiltIn(TIntermAggregate *aggregate)
 {
     switch (aggregate->getOp())
     {
-      case EOpAtan:
-      case EOpPow:
-      case EOpMod:
-      case EOpMin:
-      case EOpMax:
-      case EOpClamp:
-      case EOpMix:
-      case EOpStep:
-      case EOpSmoothStep:
-      case EOpMul:
-      case EOpOuterProduct:
-      case EOpLessThan:
-      case EOpLessThanEqual:
-      case EOpGreaterThan:
-      case EOpGreaterThanEqual:
-      case EOpVectorEqual:
-      case EOpVectorNotEqual:
-      case EOpDistance:
-      case EOpDot:
-      case EOpCross:
-      case EOpFaceForward:
-      case EOpReflect:
-      case EOpRefract:
-        return aggregate->fold(mInfoSink);
-      default:
-        // Constant folding not supported for the built-in.
-        return nullptr;
+        case EOpAtan:
+        case EOpPow:
+        case EOpMod:
+        case EOpMin:
+        case EOpMax:
+        case EOpClamp:
+        case EOpMix:
+        case EOpStep:
+        case EOpSmoothStep:
+        case EOpMul:
+        case EOpOuterProduct:
+        case EOpLessThan:
+        case EOpLessThanEqual:
+        case EOpGreaterThan:
+        case EOpGreaterThanEqual:
+        case EOpVectorEqual:
+        case EOpVectorNotEqual:
+        case EOpDistance:
+        case EOpDot:
+        case EOpCross:
+        case EOpFaceForward:
+        case EOpReflect:
+        case EOpRefract:
+            return aggregate->fold(mInfoSink);
+        default:
+            // TODO: Add support for folding array constructors
+            if (aggregate->isConstructor() && !aggregate->isArray())
+            {
+                return aggregate->fold(mInfoSink);
+            }
+            // Constant folding not supported for the built-in.
+            return nullptr;
     }
 
     return nullptr;
 }
--- a/gfx/angle/src/compiler/translator/Intermediate.h
+++ b/gfx/angle/src/compiler/translator/Intermediate.h
@@ -43,23 +43,23 @@ class TIntermediate
     TIntermAggregate *setAggregateOperator(TIntermNode *, TOperator, const TSourceLoc &);
     TIntermNode *addSelection(TIntermTyped *cond, TIntermNodePair code, const TSourceLoc &);
     TIntermTyped *addSelection(TIntermTyped *cond, TIntermTyped *trueBlock, TIntermTyped *falseBlock,
                                const TSourceLoc &line);
     TIntermSwitch *addSwitch(
         TIntermTyped *init, TIntermAggregate *statementList, const TSourceLoc &line);
     TIntermCase *addCase(
         TIntermTyped *condition, const TSourceLoc &line);
-    TIntermTyped *addComma(
-        TIntermTyped *left, TIntermTyped *right, const TSourceLoc &);
-    TIntermConstantUnion *addConstantUnion(
-        TConstantUnion *constantUnion, const TType &type, const TSourceLoc &line);
-    // TODO(zmo): Get rid of default value.
-    bool parseConstTree(const TSourceLoc &, TIntermNode *, TConstantUnion *,
-                        TOperator, TType, bool singleConstantParam = false);
+    TIntermTyped *addComma(TIntermTyped *left,
+                           TIntermTyped *right,
+                           const TSourceLoc &line,
+                           int shaderVersion);
+    TIntermConstantUnion *addConstantUnion(const TConstantUnion *constantUnion,
+                                           const TType &type,
+                                           const TSourceLoc &line);
     TIntermNode *addLoop(TLoopType, TIntermNode *, TIntermTyped *, TIntermTyped *,
                          TIntermNode *, const TSourceLoc &);
     TIntermBranch *addBranch(TOperator, const TSourceLoc &);
     TIntermBranch *addBranch(TOperator, TIntermTyped *, const TSourceLoc &);
     TIntermTyped *addSwizzle(TVectorFields &, const TSourceLoc &);
     TIntermAggregate *postProcess(TIntermNode *root);
 
     static void outputTree(TIntermNode *, TInfoSinkBase &);
--- a/gfx/angle/src/compiler/translator/OutputGLSLBase.cpp
+++ b/gfx/angle/src/compiler/translator/OutputGLSLBase.cpp
@@ -84,16 +84,29 @@ void TOutputGLSLBase::writeTriplet(
 void TOutputGLSLBase::writeBuiltInFunctionTriplet(
     Visit visit, const char *preStr, bool useEmulatedFunction)
 {
     TString preString = useEmulatedFunction ?
         BuiltInFunctionEmulator::GetEmulatedFunctionName(preStr) : preStr;
     writeTriplet(visit, preString.c_str(), ", ", ")");
 }
 
+void TOutputGLSLBase::writeLayoutQualifier(const TType &type)
+{
+    if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn)
+    {
+        const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
+        if (layoutQualifier.location >= 0)
+        {
+            TInfoSinkBase &out = objSink();
+            out << "layout(location = " << layoutQualifier.location << ") ";
+        }
+    }
+}
+
 void TOutputGLSLBase::writeVariableType(const TType &type)
 {
     TInfoSinkBase &out = objSink();
     if (type.isInvariant())
     {
         out << "invariant ";
     }
     if (type.getBasicType() == EbtInterfaceBlock)
@@ -871,16 +884,17 @@ bool TOutputGLSLBase::visitAggregate(Vis
         visitChildren = false;
         break;
       case EOpDeclaration:
         // Variable declaration.
         if (visit == PreVisit)
         {
             const TIntermSequence &sequence = *(node->getSequence());
             const TIntermTyped *variable = sequence.front()->getAsTyped();
+            writeLayoutQualifier(variable->getType());
             writeVariableType(variable->getType());
             out << " ";
             mDeclaringVariables = true;
         }
         else if (visit == InVisit)
         {
             out << ", ";
             mDeclaringVariables = true;
--- a/gfx/angle/src/compiler/translator/OutputGLSLBase.h
+++ b/gfx/angle/src/compiler/translator/OutputGLSLBase.h
@@ -27,16 +27,17 @@ class TOutputGLSLBase : public TIntermTr
     ShShaderOutput getShaderOutput() const
     {
         return mOutput;
     }
 
   protected:
     TInfoSinkBase &objSink() { return mObjSink; }
     void writeTriplet(Visit visit, const char *preStr, const char *inStr, const char *postStr);
+    void writeLayoutQualifier(const TType &type);
     void writeVariableType(const TType &type);
     virtual bool writeVariablePrecision(TPrecision precision) = 0;
     void writeFunctionParameters(const TIntermSequence &args);
     const TConstantUnion *writeConstantUnion(const TType &type, const TConstantUnion *pConstUnion);
     void writeConstructorTriplet(Visit visit, const TType &type, const char *constructorBaseType);
     TString getTypeName(const TType &type);
 
     void visitSymbol(TIntermSymbol *node) override;
--- a/gfx/angle/src/compiler/translator/OutputHLSL.cpp
+++ b/gfx/angle/src/compiler/translator/OutputHLSL.cpp
@@ -30,16 +30,55 @@
 namespace
 {
 
 bool IsSequence(TIntermNode *node)
 {
     return node->getAsAggregate() != nullptr && node->getAsAggregate()->getOp() == EOpSequence;
 }
 
+void WriteSingleConstant(TInfoSinkBase &out, const TConstantUnion *const constUnion)
+{
+    ASSERT(constUnion != nullptr);
+    switch (constUnion->getType())
+    {
+        case EbtFloat:
+            out << std::min(FLT_MAX, std::max(-FLT_MAX, constUnion->getFConst()));
+            break;
+        case EbtInt:
+            out << constUnion->getIConst();
+            break;
+        case EbtUInt:
+            out << constUnion->getUConst();
+            break;
+        case EbtBool:
+            out << constUnion->getBConst();
+            break;
+        default:
+            UNREACHABLE();
+    }
+}
+
+const TConstantUnion *WriteConstantUnionArray(TInfoSinkBase &out,
+                                              const TConstantUnion *const constUnion,
+                                              const size_t size)
+{
+    const TConstantUnion *constUnionIterated = constUnion;
+    for (size_t i = 0; i < size; i++, constUnionIterated++)
+    {
+        WriteSingleConstant(out, constUnionIterated);
+
+        if (i != size - 1)
+        {
+            out << ", ";
+        }
+    }
+    return constUnionIterated;
+}
+
 } // namespace
 
 namespace sh
 {
 
 TString OutputHLSL::TextureFunction::name() const
 {
     TString name = "gl_texture";
@@ -880,34 +919,34 @@ void OutputHLSL::header(const BuiltInFun
                             out << "    uint mip = 0;\n";
                         }
                         else if (textureFunction->method == TextureFunction::LOD0BIAS)
                         {
                             out << "    uint mip = bias;\n";
                         }
                         else
                         {
+
+                            out << "    x.GetDimensions(0, width, height, layers, levels);\n";
                             if (textureFunction->method == TextureFunction::IMPLICIT ||
                                 textureFunction->method == TextureFunction::BIAS)
                             {
-                                out << "    x.GetDimensions(0, width, height, layers, levels);\n"
-                                       "    float2 tSized = float2(t.x * width, t.y * height);\n"
+                                out << "    float2 tSized = float2(t.x * width, t.y * height);\n"
                                        "    float dx = length(ddx(tSized));\n"
                                        "    float dy = length(ddy(tSized));\n"
                                        "    float lod = log2(max(dx, dy));\n";
 
                                 if (textureFunction->method == TextureFunction::BIAS)
                                 {
                                     out << "    lod += bias;\n";
                                 }
                             }
                             else if (textureFunction->method == TextureFunction::GRAD)
                             {
-                                out << "    x.GetDimensions(0, width, height, layers, levels);\n"
-                                       "    float lod = log2(max(length(ddx), length(ddy)));\n";
+                                out << "    float lod = log2(max(length(ddx), length(ddy)));\n";
                             }
 
                             out << "    uint mip = uint(min(max(round(lod), 0), levels - 1));\n";
                         }
 
                         out << "    x.GetDimensions(mip, width, height, layers, levels);\n";
                     }
                     else
@@ -919,38 +958,34 @@ void OutputHLSL::header(const BuiltInFun
                             out << "    uint mip = 0;\n";
                         }
                         else if (textureFunction->method == TextureFunction::LOD0BIAS)
                         {
                             out << "    uint mip = bias;\n";
                         }
                         else
                         {
+                            out << "    x.GetDimensions(0, width, height, levels);\n";
+
                             if (textureFunction->method == TextureFunction::IMPLICIT ||
                                 textureFunction->method == TextureFunction::BIAS)
                             {
-                                out << "    x.GetDimensions(0, width, height, levels);\n"
-                                       "    float2 tSized = float2(t.x * width, t.y * height);\n"
+                                out << "    float2 tSized = float2(t.x * width, t.y * height);\n"
                                        "    float dx = length(ddx(tSized));\n"
                                        "    float dy = length(ddy(tSized));\n"
                                        "    float lod = log2(max(dx, dy));\n";
 
                                 if (textureFunction->method == TextureFunction::BIAS)
                                 {
                                     out << "    lod += bias;\n";
                                 }
                             }
-                            else if (textureFunction->method == TextureFunction::LOD)
-                            {
-                                out << "    x.GetDimensions(0, width, height, levels);\n";
-                            }
                             else if (textureFunction->method == TextureFunction::GRAD)
                             {
-                                out << "    x.GetDimensions(0, width, height, levels);\n"
-                                       "    float lod = log2(max(length(ddx), length(ddy)));\n";
+                                out << "    float lod = log2(max(length(ddx), length(ddy)));\n";
                             }
 
                             out << "    uint mip = uint(min(max(round(lod), 0), levels - 1));\n";
                         }
 
                         out << "    x.GetDimensions(mip, width, height, levels);\n";
                     }
                 }
@@ -963,34 +998,35 @@ void OutputHLSL::header(const BuiltInFun
                         out << "    uint mip = 0;\n";
                     }
                     else if (textureFunction->method == TextureFunction::LOD0BIAS)
                     {
                         out << "    uint mip = bias;\n";
                     }
                     else
                     {
+                        out << "    x.GetDimensions(0, width, height, depth, levels);\n";
+
                         if (textureFunction->method == TextureFunction::IMPLICIT ||
                             textureFunction->method == TextureFunction::BIAS)
                         {
-                            out << "    x.GetDimensions(0, width, height, depth, levels);\n"
-                                   "    float3 tSized = float3(t.x * width, t.y * height, t.z * depth);\n"
+                            out << "    float3 tSized = float3(t.x * width, t.y * height, t.z * "
+                                   "depth);\n"
                                    "    float dx = length(ddx(tSized));\n"
                                    "    float dy = length(ddy(tSized));\n"
                                    "    float lod = log2(max(dx, dy));\n";
 
                             if (textureFunction->method == TextureFunction::BIAS)
                             {
                                 out << "    lod += bias;\n";
                             }
                         }
                         else if (textureFunction->method == TextureFunction::GRAD)
                         {
-                            out << "    x.GetDimensions(0, width, height, depth, levels);\n"
-                                   "    float lod = log2(max(length(ddx), length(ddy)));\n";
+                            out << "    float lod = log2(max(length(ddx), length(ddy)));\n";
                         }
 
                         out << "    uint mip = uint(min(max(round(lod), 0), levels - 1));\n";
                     }
 
                     out << "    x.GetDimensions(mip, width, height, depth, levels);\n";
                 }
                 else UNREACHABLE();
@@ -1509,16 +1545,20 @@ bool OutputHLSL::visitBinary(Visit visit
                 const TString &initString = initializer(node->getType());
                 node->setRight(new TIntermRaw(node->getType(), initString));
             }
             else if (writeSameSymbolInitializer(out, symbolNode, expression))
             {
                 // Skip initializing the rest of the expression
                 return false;
             }
+            else if (writeConstantInitialization(out, symbolNode, expression))
+            {
+                return false;
+            }
         }
         else if (visit == InVisit)
         {
             out << " = ";
         }
         break;
       case EOpAddAssign:               outputTriplet(visit, "(", " += ", ")");          break;
       case EOpSubAssign:               outputTriplet(visit, "(", " -= ", ")");          break;
@@ -1864,17 +1904,19 @@ bool OutputHLSL::visitAggregate(Visit vi
         }
       case EOpDeclaration:
         if (visit == PreVisit)
         {
             TIntermSequence *sequence = node->getSequence();
             TIntermTyped *variable = (*sequence)[0]->getAsTyped();
             ASSERT(sequence->size() == 1);
 
-            if (variable && (variable->getQualifier() == EvqTemporary || variable->getQualifier() == EvqGlobal))
+            if (variable &&
+                (variable->getQualifier() == EvqTemporary ||
+                 variable->getQualifier() == EvqGlobal || variable->getQualifier() == EvqConst))
             {
                 ensureStructDefined(variable->getType());
 
                 if (!variable->getAsSymbolNode() || variable->getAsSymbolNode()->getSymbol() != "")   // Variable declaration
                 {
                     if (!mInsideFunction)
                     {
                         out << "static ";
@@ -2906,31 +2948,34 @@ void OutputHLSL::outputConstructor(Visit
         out << ", ";
     }
     else if (visit == PostVisit)
     {
         out << ")";
     }
 }
 
-const TConstantUnion *OutputHLSL::writeConstantUnion(const TType &type, const TConstantUnion *constUnion)
+const TConstantUnion *OutputHLSL::writeConstantUnion(const TType &type,
+                                                     const TConstantUnion *const constUnion)
 {
     TInfoSinkBase &out = getInfoSink();
 
+    const TConstantUnion *constUnionIterated = constUnion;
+
     const TStructure* structure = type.getStruct();
     if (structure)
     {
         out << StructNameString(*structure) + "_ctor(";
 
         const TFieldList& fields = structure->fields();
 
         for (size_t i = 0; i < fields.size(); i++)
         {
             const TType *fieldType = fields[i]->type();
-            constUnion = writeConstantUnion(*fieldType, constUnion);
+            constUnionIterated     = writeConstantUnion(*fieldType, constUnionIterated);
 
             if (i != fields.size() - 1)
             {
                 out << ", ";
             }
         }
 
         out << ")";
@@ -2939,41 +2984,24 @@ const TConstantUnion *OutputHLSL::writeC
     {
         size_t size = type.getObjectSize();
         bool writeType = size > 1;
 
         if (writeType)
         {
             out << TypeString(type) << "(";
         }
-
-        for (size_t i = 0; i < size; i++, constUnion++)
-        {
-            switch (constUnion->getType())
-            {
-              case EbtFloat: out << std::min(FLT_MAX, std::max(-FLT_MAX, constUnion->getFConst())); break;
-              case EbtInt:   out << constUnion->getIConst(); break;
-              case EbtUInt:  out << constUnion->getUConst(); break;
-              case EbtBool:  out << constUnion->getBConst(); break;
-              default: UNREACHABLE();
-            }
-
-            if (i != size - 1)
-            {
-                out << ", ";
-            }
-        }
-
+        constUnionIterated = WriteConstantUnionArray(out, constUnionIterated, size);
         if (writeType)
         {
             out << ")";
         }
     }
 
-    return constUnion;
+    return constUnionIterated;
 }
 
 void OutputHLSL::writeEmulatedFunctionTriplet(Visit visit, const char *preStr)
 {
     TString preString = BuiltInFunctionEmulator::GetEmulatedFunctionName(preStr);
     outputTriplet(visit, preString.c_str(), ", ", ")");
 }
 
@@ -2993,16 +3021,78 @@ bool OutputHLSL::writeSameSymbolInitiali
 
         mUniqueIndex++;
         return true;
     }
 
     return false;
 }
 
+bool OutputHLSL::canWriteAsHLSLLiteral(TIntermTyped *expression)
+{
+    // We support writing constant unions and constructors that only take constant unions as
+    // parameters as HLSL literals.
+    if (expression->getAsConstantUnion())
+    {
+        return true;
+    }
+    if (expression->getQualifier() != EvqConst || !expression->getAsAggregate() ||
+        !expression->getAsAggregate()->isConstructor())
+    {
+        return false;
+    }
+    TIntermAggregate *constructor = expression->getAsAggregate();
+    for (TIntermNode *&node : *constructor->getSequence())
+    {
+        if (!node->getAsConstantUnion())
+            return false;
+    }
+    return true;
+}
+
+bool OutputHLSL::writeConstantInitialization(TInfoSinkBase &out,
+                                             TIntermSymbol *symbolNode,
+                                             TIntermTyped *expression)
+{
+    if (canWriteAsHLSLLiteral(expression))
+    {
+        symbolNode->traverse(this);
+        if (expression->getType().isArray())
+        {
+            out << "[" << expression->getType().getArraySize() << "]";
+        }
+        out << " = {";
+        if (expression->getAsConstantUnion())
+        {
+            TIntermConstantUnion *nodeConst  = expression->getAsConstantUnion();
+            const TConstantUnion *constUnion = nodeConst->getUnionArrayPointer();
+            WriteConstantUnionArray(out, constUnion, nodeConst->getType().getObjectSize());
+        }
+        else
+        {
+            TIntermAggregate *constructor = expression->getAsAggregate();
+            ASSERT(constructor != nullptr);
+            for (TIntermNode *&node : *constructor->getSequence())
+            {
+                TIntermConstantUnion *nodeConst = node->getAsConstantUnion();
+                ASSERT(nodeConst);
+                const TConstantUnion *constUnion = nodeConst->getUnionArrayPointer();
+                WriteConstantUnionArray(out, constUnion, nodeConst->getType().getObjectSize());
+                if (node != constructor->getSequence()->back())
+                {
+                    out << ", ";
+                }
+            }
+        }
+        out << "}";
+        return true;
+    }
+    return false;
+}
+
 void OutputHLSL::writeDeferredGlobalInitializers(TInfoSinkBase &out)
 {
     out << "#define ANGLE_USES_DEFERRED_INIT\n"
         << "\n"
         << "void initializeDeferredGlobals()\n"
         << "{\n";
 
     for (const auto &deferredGlobal : mDeferredGlobalInitializers)
--- a/gfx/angle/src/compiler/translator/OutputHLSL.h
+++ b/gfx/angle/src/compiler/translator/OutputHLSL.h
@@ -42,16 +42,18 @@ class OutputHLSL : public TIntermTravers
 
     const std::map<std::string, unsigned int> &getInterfaceBlockRegisterMap() const;
     const std::map<std::string, unsigned int> &getUniformRegisterMap() const;
 
     static TString initializer(const TType &type);
 
     TInfoSinkBase &getInfoSink() { ASSERT(!mInfoSinkStack.empty()); return *mInfoSinkStack.top(); }
 
+    static bool canWriteAsHLSLLiteral(TIntermTyped *expression);
+
   protected:
     void header(const BuiltInFunctionEmulator *builtInFunctionEmulator);
 
     // Visit AST nodes and output their code to the body stream
     void visitSymbol(TIntermSymbol*);
     void visitRaw(TIntermRaw*);
     void visitConstantUnion(TIntermConstantUnion*);
     bool visitBinary(Visit visit, TIntermBinary*);
@@ -79,16 +81,21 @@ class OutputHLSL : public TIntermTravers
 
     void outputEqual(Visit visit, const TType &type, TOperator op, TInfoSinkBase &out);
 
     void writeEmulatedFunctionTriplet(Visit visit, const char *preStr);
     void makeFlaggedStructMaps(const std::vector<TIntermTyped *> &flaggedStructs);
 
     // Returns true if it found a 'same symbol' initializer (initializer that references the variable it's initting)
     bool writeSameSymbolInitializer(TInfoSinkBase &out, TIntermSymbol *symbolNode, TIntermTyped *expression);
+    // Returns true if variable initializer could be written using literal {} notation.
+    bool writeConstantInitialization(TInfoSinkBase &out,
+                                     TIntermSymbol *symbolNode,
+                                     TIntermTyped *expression);
+
     void writeDeferredGlobalInitializers(TInfoSinkBase &out);
     void writeSelection(TIntermSelection *node);
 
     // Returns the function name
     TString addStructEqualityFunction(const TStructure &structure);
     TString addArrayEqualityFunction(const TType &type);
     TString addArrayAssignmentFunction(const TType &type);
     TString addArrayConstructIntoFunction(const TType &type);
--- a/gfx/angle/src/compiler/translator/ParseContext.cpp
+++ b/gfx/angle/src/compiler/translator/ParseContext.cpp
@@ -159,16 +159,33 @@ void TParseContext::warning(const TSourc
                             const char *extraInfo)
 {
     pp::SourceLocation srcLoc;
     srcLoc.file = loc.first_file;
     srcLoc.line = loc.first_line;
     mDiagnostics.writeInfo(pp::Diagnostics::PP_WARNING, srcLoc, reason, token, extraInfo);
 }
 
+void TParseContext::outOfRangeError(bool isError,
+                                    const TSourceLoc &loc,
+                                    const char *reason,
+                                    const char *token,
+                                    const char *extraInfo)
+{
+    if (isError)
+    {
+        error(loc, reason, token, extraInfo);
+        recover();
+    }
+    else
+    {
+        warning(loc, reason, token, extraInfo);
+    }
+}
+
 //
 // Same error message for all places assignments don't work.
 //
 void TParseContext::assignError(const TSourceLoc &line, const char *op, TString left, TString right)
 {
     std::stringstream extraInfoStream;
     extraInfoStream << "cannot convert from '" << right << "' to '" << left << "'";
     std::string extraInfo = extraInfoStream.str();
@@ -471,17 +488,17 @@ bool TParseContext::reservedErrorCheck(c
 //
 // Make sure there is enough data provided to the constructor to build
 // something of the type of the constructor.  Also returns the type of
 // the constructor.
 //
 // Returns true if there was an error in construction.
 //
 bool TParseContext::constructorErrorCheck(const TSourceLoc &line,
-                                          TIntermNode *node,
+                                          TIntermNode *argumentsNode,
                                           TFunction &function,
                                           TOperator op,
                                           TType *type)
 {
     *type = function.getReturnType();
 
     bool constructingMatrix = false;
     switch (op)
@@ -582,31 +599,37 @@ bool TParseContext::constructorErrorChec
         if ((op != EOpConstructStruct && size != 1 && size < type->getObjectSize()) ||
             (op == EOpConstructStruct && size < type->getObjectSize()))
         {
             error(line, "not enough data provided for construction", "constructor");
             return true;
         }
     }
 
-    TIntermTyped *typed = node ? node->getAsTyped() : 0;
-    if (typed == 0)
+    if (argumentsNode == nullptr)
     {
-        error(line, "constructor argument does not have a type", "constructor");
+        error(line, "constructor does not have any arguments", "constructor");
         return true;
     }
-    if (op != EOpConstructStruct && IsSampler(typed->getBasicType()))
+
+    TIntermAggregate *argumentsAgg = argumentsNode->getAsAggregate();
+    for (TIntermNode *&argNode : *argumentsAgg->getSequence())
     {
-        error(line, "cannot convert a sampler", "constructor");
-        return true;
-    }
-    if (typed->getBasicType() == EbtVoid)
-    {
-        error(line, "cannot convert a void", "constructor");
-        return true;
+        TIntermTyped *argTyped = argNode->getAsTyped();
+        ASSERT(argTyped != nullptr);
+        if (op != EOpConstructStruct && IsSampler(argTyped->getBasicType()))
+        {
+            error(line, "cannot convert a sampler", "constructor");
+            return true;
+        }
+        if (argTyped->getBasicType() == EbtVoid)
+        {
+            error(line, "cannot convert a void", "constructor");
+            return true;
+        }
     }
 
     return false;
 }
 
 // This function checks to see if a void variable has been declared and raise an error message for
 // such a case
 //
@@ -730,17 +753,20 @@ bool TParseContext::containsSampler(cons
 // Do size checking for an array type's size.
 //
 // Returns true if there was an error.
 //
 bool TParseContext::arraySizeErrorCheck(const TSourceLoc &line, TIntermTyped *expr, int &size)
 {
     TIntermConstantUnion *constant = expr->getAsConstantUnion();
 
-    if (constant == nullptr || !constant->isScalarInt())
+    // TODO(oetuaho@nvidia.com): Get rid of the constant == nullptr check here once all constant
+    // expressions can be folded. Right now we don't allow constant expressions that ANGLE can't
+    // fold as array size.
+    if (expr->getQualifier() != EvqConst || constant == nullptr || !constant->isScalarInt())
     {
         error(line, "array size must be a constant integer expression", "");
         size = 1;
         return true;
     }
 
     unsigned int unsignedSize = 0;
 
@@ -1177,16 +1203,34 @@ const TVariable *TParseContext::getNamed
         TVariable *fakeVariable = new TVariable(name, type);
         symbolTable.declare(fakeVariable);
         variable = fakeVariable;
     }
 
     return variable;
 }
 
+TIntermTyped *TParseContext::parseVariableIdentifier(const TSourceLoc &location,
+                                                     const TString *name,
+                                                     const TSymbol *symbol)
+{
+    const TVariable *variable = getNamedVariable(location, name, symbol);
+
+    if (variable->getConstPointer())
+    {
+        const TConstantUnion *constArray = variable->getConstPointer();
+        return intermediate.addConstantUnion(constArray, variable->getType(), location);
+    }
+    else
+    {
+        return intermediate.addSymbol(variable->getUniqueId(), variable->getName(),
+                                      variable->getType(), location);
+    }
+}
+
 //
 // Look up a function name in the symbol table, and make sure it is a function.
 //
 // Return the function symbol if found, otherwise 0.
 //
 const TFunction *TParseContext::findFunction(const TSourceLoc &line,
                                              TFunction *call,
                                              int inputShaderVersion,
@@ -1286,80 +1330,56 @@ bool TParseContext::executeInitializer(c
         }
         if (type != initializer->getType())
         {
             error(line, " non-matching types for const initializer ",
                   variable->getType().getQualifierString());
             variable->getType().setQualifier(EvqTemporary);
             return true;
         }
+
+        // Save the constant folded value to the variable if possible. For example array
+        // initializers are not folded, since that way copying the array literal to multiple places
+        // in the shader is avoided.
+        // TODO(oetuaho@nvidia.com): Consider constant folding array initialization in cases where
+        // it would be beneficial.
         if (initializer->getAsConstantUnion())
         {
             variable->shareConstPointer(initializer->getAsConstantUnion()->getUnionArrayPointer());
+            *intermNode = nullptr;
+            return false;
         }
         else if (initializer->getAsSymbolNode())
         {
             const TSymbol *symbol =
                 symbolTable.find(initializer->getAsSymbolNode()->getSymbol(), 0);
             const TVariable *tVar = static_cast<const TVariable *>(symbol);
 
-            TConstantUnion *constArray = tVar->getConstPointer();
-            variable->shareConstPointer(constArray);
-        }
-        else
-        {
-            std::stringstream extraInfoStream;
-            extraInfoStream << "'" << variable->getType().getCompleteString() << "'";
-            std::string extraInfo = extraInfoStream.str();
-            error(line, " cannot assign to", "=", extraInfo.c_str());
-            variable->getType().setQualifier(EvqTemporary);
-            return true;
+            const TConstantUnion *constArray = tVar->getConstPointer();
+            if (constArray)
+            {
+                variable->shareConstPointer(constArray);
+                *intermNode = nullptr;
+                return false;
+            }
         }
     }
 
-    if (qualifier != EvqConst)
+    TIntermSymbol *intermSymbol = intermediate.addSymbol(
+        variable->getUniqueId(), variable->getName(), variable->getType(), line);
+    *intermNode = createAssign(EOpInitialize, intermSymbol, initializer, line);
+    if (*intermNode == nullptr)
     {
-        TIntermSymbol *intermSymbol = intermediate.addSymbol(
-            variable->getUniqueId(), variable->getName(), variable->getType(), line);
-        *intermNode = createAssign(EOpInitialize, intermSymbol, initializer, line);
-        if (*intermNode == nullptr)
-        {
-            assignError(line, "=", intermSymbol->getCompleteString(),
-                        initializer->getCompleteString());
-            return true;
-        }
-    }
-    else
-    {
-        *intermNode = nullptr;
+        assignError(line, "=", intermSymbol->getCompleteString(), initializer->getCompleteString());
+        return true;
     }
 
     return false;
 }
 
-bool TParseContext::areAllChildConst(TIntermAggregate *aggrNode)
-{
-    ASSERT(aggrNode != NULL);
-    if (!aggrNode->isConstructor())
-        return false;
-
-    bool allConstant = true;
-
-    // check if all the child nodes are constants so that they can be inserted into
-    // the parent node
-    TIntermSequence *sequence = aggrNode->getSequence();
-    for (TIntermSequence::iterator p = sequence->begin(); p != sequence->end(); ++p)
-    {
-        if (!(*p)->getAsTyped()->getAsConstantUnion())
-            return false;
-    }
-
-    return allConstant;
-}
-
 TPublicType TParseContext::addFullySpecifiedType(TQualifier qualifier,
                                                  bool invariant,
                                                  TLayoutQualifier layoutQualifier,
                                                  const TPublicType &typeSpecifier)
 {
     TPublicType returnType     = typeSpecifier;
     returnType.qualifier       = qualifier;
     returnType.invariant       = invariant;
@@ -2068,16 +2088,23 @@ TFunction *TParseContext::parseFunctionD
     // being redeclared.  So, pass back up this declaration, not the one in the symbol table.
     //
     return function;
 }
 
 TFunction *TParseContext::addConstructorFunc(const TPublicType &publicTypeIn)
 {
     TPublicType publicType = publicTypeIn;
+    if (publicType.isStructSpecifier)
+    {
+        error(publicType.line, "constructor can't be a structure definition",
+              getBasicString(publicType.type));
+        recover();
+    }
+
     TOperator op = EOpNull;
     if (publicType.userDef)
     {
         op = EOpConstructStruct;
     }
     else
     {
         switch (publicType.type)
@@ -2229,249 +2256,167 @@ TFunction *TParseContext::addConstructor
 // Returns 0 for an error or the constructed node (aggregate or typed) for no error.
 //
 TIntermTyped *TParseContext::addConstructor(TIntermNode *arguments,
                                             TType *type,
                                             TOperator op,
                                             TFunction *fnCall,
                                             const TSourceLoc &line)
 {
-    TIntermAggregate *aggregateArguments = arguments->getAsAggregate();
-
-    if (!aggregateArguments)
-    {
-        aggregateArguments = new TIntermAggregate;
-        aggregateArguments->getSequence()->push_back(arguments);
-    }
+    TIntermAggregate *constructor = arguments->getAsAggregate();
+    ASSERT(constructor != nullptr);
 
     if (type->isArray())
     {
         // GLSL ES 3.00 section 5.4.4: Each argument must be the same type as the element type of
         // the array.
-        TIntermSequence *args = aggregateArguments->getSequence();
+        TIntermSequence *args = constructor->getSequence();
         for (size_t i = 0; i < args->size(); i++)
         {
             const TType &argType = (*args)[i]->getAsTyped()->getType();
             // It has already been checked that the argument is not an array.
             ASSERT(!argType.isArray());
             if (!argType.sameElementType(*type))
             {
                 error(line, "Array constructor argument has an incorrect type", "Error");
                 recover();
                 return nullptr;
             }
         }
     }
     else if (op == EOpConstructStruct)
     {
         const TFieldList &fields = type->getStruct()->fields();
-        TIntermSequence *args    = aggregateArguments->getSequence();
+        TIntermSequence *args    = constructor->getSequence();
 
         for (size_t i = 0; i < fields.size(); i++)
         {
             if (i >= args->size() || (*args)[i]->getAsTyped()->getType() != *fields[i]->type())
             {
                 error(line, "Structure constructor arguments do not match structure fields",
                       "Error");
                 recover();
 
                 return 0;
             }
         }
     }